403 lines
14 KiB
C++
403 lines
14 KiB
C++
#include <UI/Dialogs/DialogSubscriptions.h>
|
|
#include <UI/Widgets/AdaptixWidget.h>
|
|
#include <QPointer>
|
|
#include <Client/AuthProfile.h>
|
|
#include <Client/HttpRequestManager.h>
|
|
#include <Client/Storage.h>
|
|
#include <MainAdaptix.h>
|
|
#include <Utils/NonBlockingDialogs.h>
|
|
|
|
namespace {
|
|
|
|
static QListWidgetItem* makeSectionHeader(const QString &title)
|
|
{
|
|
auto *item = new QListWidgetItem(title);
|
|
QFont f = item->font();
|
|
f.setBold(true);
|
|
item->setFont(f);
|
|
item->setFlags(Qt::ItemIsEnabled);
|
|
item->setData(Qt::UserRole, true);
|
|
return item;
|
|
}
|
|
|
|
static bool isSectionHeaderItem(const QListWidgetItem *item)
|
|
{
|
|
return item && item->data(Qt::UserRole).toBool();
|
|
}
|
|
|
|
}
|
|
|
|
void DialogSubscriptions::createUI()
|
|
{
|
|
setWindowTitle("Subscription Settings");
|
|
setProperty("Main", "base");
|
|
setFixedSize(500, 500);
|
|
|
|
auto mainLayout = new QGridLayout(this);
|
|
mainLayout->setContentsMargins(10, 10, 10, 10);
|
|
mainLayout->setSpacing(10);
|
|
|
|
auto historyLabel = new QLabel("History:", this);
|
|
QFont boldFont = historyLabel->font();
|
|
boldFont.setBold(true);
|
|
historyLabel->setFont(boldFont);
|
|
|
|
auto realtimeLabel = new QLabel("RealTime:", this);
|
|
realtimeLabel->setFont(boldFont);
|
|
|
|
historyListWidget = new QListWidget(this);
|
|
historyListWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
|
|
|
realtimeListWidget = new QListWidget(this);
|
|
realtimeListWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
|
|
|
historyListWidget->addItem(makeSectionHeader("Data"));
|
|
for (const QString &cat : {"chat_history", "downloads_history", "screenshot_history", "credentials_history", "targets_history"}) {
|
|
auto *item = new QListWidgetItem(cat);
|
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
|
item->setCheckState(Qt::Unchecked);
|
|
historyListWidget->addItem(item);
|
|
}
|
|
historyListWidget->addItem(makeSectionHeader("Agent"));
|
|
for (const QString &cat : {"console_history", "tasks_history"}) {
|
|
auto *item = new QListWidgetItem(cat);
|
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
|
item->setCheckState(Qt::Unchecked);
|
|
historyListWidget->addItem(item);
|
|
}
|
|
|
|
realtimeListWidget->addItem(makeSectionHeader("Data"));
|
|
for (const QString &cat : {"chat_realtime", "downloads_realtime", "screenshot_realtime", "credentials_realtime", "targets_realtime", "notifications", "tunnels"}) {
|
|
auto *item = new QListWidgetItem(cat);
|
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
|
item->setCheckState(Qt::Unchecked);
|
|
realtimeListWidget->addItem(item);
|
|
}
|
|
realtimeListWidget->addItem(makeSectionHeader("Agent"));
|
|
for (const QString &cat : {"tasks_manager"}) {
|
|
auto *item = new QListWidgetItem(cat);
|
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
|
item->setCheckState(Qt::Unchecked);
|
|
realtimeListWidget->addItem(item);
|
|
}
|
|
|
|
consoleTeammodeCheck = new QCheckBox("Console Team Mode", this);
|
|
consoleTeammodeCheck->setChecked(true);
|
|
consoleTeammodeCheck->setToolTip("See console output from all operators");
|
|
|
|
agentsOnlyActiveCheck = new QCheckBox("Only active agents", this);
|
|
agentsOnlyActiveCheck->setToolTip("Synchronize only active agents");
|
|
agentsOnlyActiveCheck->setEnabled(false);
|
|
|
|
tasksOnlyJobsCheck = new QCheckBox("Only JOB tasks", this);
|
|
tasksOnlyJobsCheck->setToolTip("Synchronize only jobs");
|
|
tasksOnlyJobsCheck->setEnabled(false);
|
|
|
|
syncInactiveAgentsButton = new QPushButton("Sync inactive agents", this);
|
|
syncInactiveAgentsButton->setEnabled(false);
|
|
syncInactiveAgentsButton->setVisible(false);
|
|
|
|
auto buttonLayout = new QHBoxLayout();
|
|
buttonLayout->setSpacing(10);
|
|
|
|
applyButton = new QPushButton("Apply", this);
|
|
applyButton->setDefault(true);
|
|
applyButton->setFixedWidth(100);
|
|
|
|
closeButton = new QPushButton("Close", this);
|
|
closeButton->setFixedWidth(100);
|
|
|
|
buttonLayout->addStretch();
|
|
buttonLayout->addWidget(applyButton);
|
|
buttonLayout->addWidget(closeButton);
|
|
|
|
auto optionsLayout = new QVBoxLayout();
|
|
optionsLayout->setSpacing(6);
|
|
optionsLayout->addWidget(consoleTeammodeCheck);
|
|
|
|
auto agentsRow = new QHBoxLayout();
|
|
agentsRow->setSpacing(8);
|
|
agentsRow->addWidget(agentsOnlyActiveCheck);
|
|
agentsRow->addWidget(syncInactiveAgentsButton);
|
|
agentsRow->addStretch();
|
|
optionsLayout->addLayout(agentsRow);
|
|
|
|
optionsLayout->addWidget(tasksOnlyJobsCheck);
|
|
|
|
mainLayout->addWidget(historyLabel, 0, 0, 1, 1);
|
|
mainLayout->addWidget(realtimeLabel, 0, 1, 1, 1);
|
|
mainLayout->addWidget(historyListWidget, 1, 0, 1, 1);
|
|
mainLayout->addWidget(realtimeListWidget, 1, 1, 1, 1);
|
|
mainLayout->addLayout(optionsLayout, 2, 0, 1, 2);
|
|
mainLayout->addLayout(buttonLayout, 3, 0, 1, 2);
|
|
}
|
|
|
|
void DialogSubscriptions::updateItemState(QListWidgetItem *item, const bool active, bool registered)
|
|
{
|
|
Qt::ItemFlags flags = item->flags();
|
|
flags |= Qt::ItemIsUserCheckable;
|
|
if (registered)
|
|
flags |= Qt::ItemIsUserTristate;
|
|
else
|
|
flags &= ~Qt::ItemIsUserTristate;
|
|
item->setFlags(flags);
|
|
if (active) {
|
|
item->setCheckState(Qt::Checked);
|
|
} else if (registered) {
|
|
item->setCheckState(Qt::PartiallyChecked);
|
|
} else {
|
|
item->setCheckState(Qt::Unchecked);
|
|
}
|
|
}
|
|
|
|
DialogSubscriptions::DialogSubscriptions(AdaptixWidget* adaptixWidget, QWidget* parent) : QDialog(parent), adaptixWidget(adaptixWidget)
|
|
{
|
|
dataCategories = {
|
|
"chat_history", "chat_realtime",
|
|
"downloads_history", "downloads_realtime",
|
|
"screenshot_history", "screenshot_realtime",
|
|
"credentials_history", "credentials_realtime",
|
|
"targets_history", "targets_realtime",
|
|
"notifications", "tunnels"
|
|
};
|
|
agentCategories = {
|
|
"console_history",
|
|
"tasks_history",
|
|
"tasks_manager",
|
|
"tasks_only_jobs"
|
|
};
|
|
createUI();
|
|
|
|
connect(applyButton, &QPushButton::clicked, this, &DialogSubscriptions::onApply);
|
|
connect(closeButton, &QPushButton::clicked, this, &QDialog::close);
|
|
connect(historyListWidget, &QListWidget::itemChanged, this, &DialogSubscriptions::onItemChanged);
|
|
connect(realtimeListWidget, &QListWidget::itemChanged, this, &DialogSubscriptions::onItemChanged);
|
|
connect(syncInactiveAgentsButton, &QPushButton::clicked, this, &DialogSubscriptions::onSyncInactiveAgents);
|
|
}
|
|
|
|
void DialogSubscriptions::updateSyncInactiveAgentsButtonState()
|
|
{
|
|
if (!syncInactiveAgentsButton)
|
|
return;
|
|
|
|
bool agentsInactiveRegistered = registeredCategories.contains("agents_inactive");
|
|
bool show = agentsOnlyActiveLocked && !agentsInactiveRegistered;
|
|
syncInactiveAgentsButton->setVisible(show);
|
|
syncInactiveAgentsButton->setEnabled(show && !syncInactiveAgentsTriggered);
|
|
}
|
|
|
|
void DialogSubscriptions::SetCurrentSubscriptions(const QStringList &subs)
|
|
{
|
|
currentSubscriptions = subs;
|
|
|
|
historyListWidget->blockSignals(true);
|
|
for (int i = 0; i < historyListWidget->count(); ++i) {
|
|
auto *item = historyListWidget->item(i);
|
|
if (isSectionHeaderItem(item))
|
|
continue;
|
|
bool active = subs.contains(item->text());
|
|
bool registered = registeredCategories.contains(item->text());
|
|
updateItemState(item, active, registered);
|
|
}
|
|
historyListWidget->blockSignals(false);
|
|
|
|
realtimeListWidget->blockSignals(true);
|
|
for (int i = 0; i < realtimeListWidget->count(); ++i) {
|
|
auto *item = realtimeListWidget->item(i);
|
|
if (isSectionHeaderItem(item))
|
|
continue;
|
|
bool active = subs.contains(item->text());
|
|
bool registered = registeredCategories.contains(item->text());
|
|
updateItemState(item, active, registered);
|
|
}
|
|
realtimeListWidget->blockSignals(false);
|
|
|
|
bool agentsOnlyActive = subs.contains("agents_only_active");
|
|
agentsOnlyActiveLocked = agentsOnlyActive;
|
|
agentsOnlyActiveCheck->setChecked(agentsOnlyActive);
|
|
agentsOnlyActiveCheck->setEnabled(false);
|
|
|
|
updateSyncInactiveAgentsButtonState();
|
|
|
|
tasksOnlyJobsCheck->setChecked(subs.contains("tasks_only_jobs"));
|
|
}
|
|
|
|
void DialogSubscriptions::SetRegisteredCategories(const QStringList &cats)
|
|
{
|
|
registeredCategories = cats;
|
|
updateSyncInactiveAgentsButtonState();
|
|
}
|
|
|
|
void DialogSubscriptions::SetConsoleMultiuser(bool multiuser)
|
|
{
|
|
consoleTeammodeCheck->setChecked(multiuser);
|
|
}
|
|
|
|
void DialogSubscriptions::onApply()
|
|
{
|
|
if (!adaptixWidget || !adaptixWidget->GetProfile())
|
|
return;
|
|
|
|
AuthProfile* profile = adaptixWidget->GetProfile();
|
|
|
|
bool wasChatHistory = currentSubscriptions.contains("chat_history");
|
|
bool wasConsoleHistory = currentSubscriptions.contains("console_history");
|
|
bool wasNotifications = currentSubscriptions.contains("notifications");
|
|
|
|
QStringList newSubs;
|
|
for (int i = 0; i < historyListWidget->count(); ++i) {
|
|
auto *item = historyListWidget->item(i);
|
|
if (isSectionHeaderItem(item))
|
|
continue;
|
|
if (item->checkState() == Qt::Checked)
|
|
newSubs.append(item->text());
|
|
}
|
|
for (int i = 0; i < realtimeListWidget->count(); ++i) {
|
|
auto *item = realtimeListWidget->item(i);
|
|
if (isSectionHeaderItem(item))
|
|
continue;
|
|
if (item->checkState() == Qt::Checked)
|
|
newSubs.append(item->text());
|
|
}
|
|
|
|
if (agentsOnlyActiveLocked)
|
|
newSubs.append("agents_only_active");
|
|
if (tasksOnlyJobsCheck && tasksOnlyJobsCheck->isChecked())
|
|
newSubs.append("tasks_only_jobs");
|
|
|
|
QStringList toRegister;
|
|
for (const QString &cat : newSubs) {
|
|
if (!registeredCategories.contains(cat))
|
|
toRegister.append(cat);
|
|
}
|
|
|
|
bool enablingChatHistory = newSubs.contains("chat_history") && !wasChatHistory;
|
|
bool enablingConsoleHistory = newSubs.contains("console_history") && !wasConsoleHistory;
|
|
bool enablingNotifications = newSubs.contains("notifications") && !wasNotifications;
|
|
if (adaptixWidget) {
|
|
if (enablingChatHistory)
|
|
adaptixWidget->ClearChatStream();
|
|
if (enablingConsoleHistory)
|
|
adaptixWidget->ClearConsoleStreams();
|
|
if (enablingNotifications)
|
|
adaptixWidget->ClearNotificationsStream();
|
|
}
|
|
|
|
bool consoleMultiuser = consoleTeammodeCheck->isChecked();
|
|
|
|
if (!toRegister.isEmpty() || consoleMultiuser != profile->GetConsoleMultiuser()) {
|
|
QJsonObject subscribeData;
|
|
subscribeData["categories"] = toJsonArray(toRegister);
|
|
if (consoleMultiuser != profile->GetConsoleMultiuser())
|
|
subscribeData["console_team_mode"] = consoleMultiuser;
|
|
|
|
if (!subscribeData.isEmpty()) {
|
|
QByteArray jsonData = QJsonDocument(subscribeData).toJson();
|
|
QPointer<AdaptixWidget> safeWidget = adaptixWidget;
|
|
QStringList allRegs = registeredCategories + toRegister;
|
|
QStringList activeSubs = newSubs;
|
|
HttpRequestManager::instance().post(
|
|
profile->GetURL(), "/subscribe", profile->GetAccessToken(), jsonData,
|
|
[safeWidget, allRegs, activeSubs, consoleMultiuser](bool success, const QString& message, const QJsonObject& response) {
|
|
Q_UNUSED(response);
|
|
if (success) {
|
|
if (safeWidget && safeWidget->GetProfile()) {
|
|
safeWidget->GetProfile()->SetRegisteredCategories(allRegs);
|
|
safeWidget->GetProfile()->SetSubscriptions(activeSubs);
|
|
safeWidget->GetProfile()->SetConsoleMultiuser(consoleMultiuser);
|
|
GlobalClient->storage->UpdateProject(*safeWidget->GetProfile());
|
|
}
|
|
} else {
|
|
MessageError("Subscribe failed: " + message);
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
profile->SetSubscriptions(newSubs);
|
|
GlobalClient->storage->UpdateProject(*profile);
|
|
}
|
|
|
|
close();
|
|
}
|
|
|
|
void DialogSubscriptions::onSyncInactiveAgents()
|
|
{
|
|
if (!agentsOnlyActiveLocked)
|
|
return;
|
|
if (syncInactiveAgentsTriggered)
|
|
return;
|
|
|
|
if (!adaptixWidget || !adaptixWidget->GetProfile())
|
|
return;
|
|
|
|
if (registeredCategories.contains("agents_inactive")) {
|
|
updateSyncInactiveAgentsButtonState();
|
|
return;
|
|
}
|
|
|
|
syncInactiveAgentsTriggered = true;
|
|
updateSyncInactiveAgentsButtonState();
|
|
|
|
AuthProfile* profile = adaptixWidget->GetProfile();
|
|
QJsonObject subscribeData;
|
|
subscribeData["categories"] = toJsonArray(QStringList{ "agents_inactive" });
|
|
QByteArray jsonData = QJsonDocument(subscribeData).toJson();
|
|
|
|
QPointer<DialogSubscriptions> self(this);
|
|
HttpRequestManager::instance().post(
|
|
profile->GetURL(), "/subscribe", profile->GetAccessToken(), jsonData,
|
|
[self](bool success, const QString& message, const QJsonObject& response) {
|
|
Q_UNUSED(response);
|
|
if (!self)
|
|
return;
|
|
|
|
if (!success) {
|
|
MessageError("Subscribe failed: " + message);
|
|
self->syncInactiveAgentsTriggered = false;
|
|
self->updateSyncInactiveAgentsButtonState();
|
|
return;
|
|
}
|
|
|
|
if (self->adaptixWidget && self->adaptixWidget->GetProfile()) {
|
|
AuthProfile* profile = self->adaptixWidget->GetProfile();
|
|
|
|
if (!self->registeredCategories.contains("agents_inactive"))
|
|
self->registeredCategories.append("agents_inactive");
|
|
profile->SetRegisteredCategories(self->registeredCategories);
|
|
|
|
QStringList subs = profile->GetSubscriptions();
|
|
if (!subs.contains("agents_inactive"))
|
|
subs.append("agents_inactive");
|
|
profile->SetSubscriptions(subs);
|
|
|
|
GlobalClient->storage->UpdateProject(*profile);
|
|
}
|
|
|
|
self->updateSyncInactiveAgentsButtonState();
|
|
});
|
|
}
|
|
|
|
void DialogSubscriptions::onItemChanged(QListWidgetItem *item)
|
|
{
|
|
if (isSectionHeaderItem(item))
|
|
return;
|
|
|
|
QString cat = item->text();
|
|
bool registered = registeredCategories.contains(cat);
|
|
|
|
QListWidget *listWidget = item->listWidget();
|
|
if (!listWidget)
|
|
return;
|
|
|
|
if (item->checkState() == Qt::PartiallyChecked && !registered) {
|
|
listWidget->blockSignals(true);
|
|
item->setCheckState(Qt::Checked);
|
|
listWidget->blockSignals(false);
|
|
}
|
|
else if (item->checkState() == Qt::Unchecked && registered) {
|
|
listWidget->blockSignals(true);
|
|
item->setCheckState(Qt::PartiallyChecked);
|
|
listWidget->blockSignals(false);
|
|
}
|
|
}
|