AdaptixC2-Mod0/Source/UI/Widgets/TargetsWidget.cpp
2026-04-06 00:20:51 -05:00

595 lines
20 KiB
C++

#include <UI/Widgets/TargetsWidget.h>
#include <UI/Widgets/AdaptixWidget.h>
#include <UI/Widgets/DockWidgetRegister.h>
#include <UI/Dialogs/DialogTarget.h>
#include <Client/Requestor.h>
#include <Client/AuthProfile.h>
#include <Client/AxScript/AxScriptManager.h>
#include <Utils/CustomElements.h>
#include <Utils/NonBlockingDialogs.h>
REGISTER_DOCK_WIDGET(TargetsWidget, "Targets", true)
TargetsWidget::TargetsWidget(AdaptixWidget* w) : DockTab("Targets", w->GetProfile()->GetProject(), ":/icons/devices"), adaptixWidget(w)
{
this->createUI();
connect(tableView, &QTableView::customContextMenuRequested, this, &TargetsWidget::handleTargetsMenu);
connect(tableView, &QTableView::doubleClicked, this, &TargetsWidget::onEditTarget);
connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selected, const QItemSelection &deselected){
Q_UNUSED(selected)
Q_UNUSED(deselected)
if (!inputFilter->hasFocus())
tableView->setFocus();
});
connect(hideButton, &ClickableLabel::clicked, this, &TargetsWidget::toggleSearchPanel);
connect(inputFilter, &QLineEdit::textChanged, this, &TargetsWidget::onFilterUpdate);
connect(inputFilter, &QLineEdit::returnPressed, this, [this]() { proxyModel->setTextFilter(inputFilter->text()); });
shortcutSearch = new QShortcut(QKeySequence("Ctrl+F"), this);
shortcutSearch->setContext(Qt::WidgetWithChildrenShortcut);
connect(shortcutSearch, &QShortcut::activated, this, &TargetsWidget::toggleSearchPanel);
auto shortcutEsc = new QShortcut(QKeySequence(Qt::Key_Escape), inputFilter);
shortcutEsc->setContext(Qt::WidgetShortcut);
connect(shortcutEsc, &QShortcut::activated, this, [this]() { searchWidget->setVisible(false); });
this->dockWidget->setWidget(this);
}
TargetsWidget::~TargetsWidget() = default;
void TargetsWidget::SetUpdatesEnabled(const bool enabled)
{
if (!enabled) {
bufferingEnabled = true;
} else {
bufferingEnabled = false;
flushPendingTargets();
}
if (proxyModel)
proxyModel->setDynamicSortFilter(enabled);
if (tableView)
tableView->setSortingEnabled(enabled);
tableView->setUpdatesEnabled(enabled);
}
void TargetsWidget::flushPendingTargets()
{
if (pendingTargets.isEmpty())
return;
QList<TargetData> filtered;
{
QWriteLocker locker(&adaptixWidget->TargetsLock);
QSet<QString> existingIds;
for (const auto& t : adaptixWidget->Targets)
existingIds.insert(t.TargetId);
for (const auto& target : pendingTargets) {
if (existingIds.contains(target.TargetId))
continue;
existingIds.insert(target.TargetId);
adaptixWidget->Targets.push_back(target);
filtered.append(target);
}
}
if (!filtered.isEmpty())
targetsModel->add(filtered);
pendingTargets.clear();
}
void TargetsWidget::createUI()
{
auto horizontalSpacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
searchWidget = new QWidget(this);
searchWidget->setVisible(false);
inputFilter = new QLineEdit(searchWidget);
inputFilter->setPlaceholderText("filter: (win | linux) & ^(test)");
inputFilter->setMaximumWidth(300);
autoSearchCheck = new QCheckBox("auto", searchWidget);
autoSearchCheck->setChecked(true);
autoSearchCheck->setToolTip("Auto search on text change. If unchecked, press Enter to search.");
hideButton = new ClickableLabel(" x ");
hideButton->setCursor(Qt::PointingHandCursor);
searchLayout = new QHBoxLayout(searchWidget);
searchLayout->setContentsMargins(0, 5, 0, 0);
searchLayout->setSpacing(4);
searchLayout->addWidget(inputFilter);
searchLayout->addWidget(autoSearchCheck);
searchLayout->addSpacing(8);
searchLayout->addWidget(hideButton);
searchLayout->addSpacerItem(horizontalSpacer2);
targetsModel = new TargetsTableModel(this);
proxyModel = new TargetsFilterProxyModel(this);
proxyModel->setSourceModel(targetsModel);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
tableView = new QTableView( this );
tableView->setModel(proxyModel);
tableView->setHorizontalHeader(new BoldHeaderView(Qt::Horizontal, tableView));
tableView->setContextMenuPolicy( Qt::CustomContextMenu );
tableView->setAutoFillBackground( false );
tableView->setShowGrid( false );
tableView->setSortingEnabled( true );
tableView->setWordWrap( true );
tableView->setCornerButtonEnabled( false );
tableView->setSelectionBehavior( QAbstractItemView::SelectRows );
tableView->setFocusPolicy( Qt::NoFocus );
tableView->setAlternatingRowColors( true );
tableView->horizontalHeader()->setSectionResizeMode( QHeaderView::ResizeToContents );
tableView->horizontalHeader()->setCascadingSectionResizes( true );
tableView->horizontalHeader()->setHighlightSections( false );
tableView->verticalHeader()->setVisible( false );
tableView->sortByColumn(TRC_Date, Qt::AscendingOrder);
tableView->horizontalHeader()->setSectionResizeMode( TRC_Tag, QHeaderView::Stretch );
tableView->horizontalHeader()->setSectionResizeMode( TRC_Os, QHeaderView::Stretch );
tableView->horizontalHeader()->setSectionResizeMode( TRC_Info, QHeaderView::Stretch );
tableView->setItemDelegate(new PaddingDelegate(tableView));
tableView->hideColumn(0);
mainGridLayout = new QGridLayout( this );
mainGridLayout->setContentsMargins( 0, 0, 0, 0);
mainGridLayout->addWidget( searchWidget, 0, 0, 1, 1);
mainGridLayout->addWidget( tableView, 1, 0, 1, 1);
}
/// Main
void TargetsWidget::AddTargetsItems(QList<TargetData> targetList)
{
if (targetList.isEmpty())
return;
if (bufferingEnabled) {
pendingTargets.append(targetList);
return;
}
QList<TargetData> filtered;
{
QWriteLocker locker(&adaptixWidget->TargetsLock);
QSet<QString> existingIds;
for (const auto& t : adaptixWidget->Targets)
existingIds.insert(t.TargetId);
for (const auto& target : targetList) {
if (existingIds.contains(target.TargetId))
continue;
existingIds.insert(target.TargetId);
adaptixWidget->Targets.push_back(target);
filtered.append(target);
}
}
if (filtered.isEmpty())
return;
targetsModel->add(filtered);
if (adaptixWidget->IsSynchronized())
this->UpdateColumnsSize();
}
void TargetsWidget::EditTargetsItem(const TargetData &newTarget) const
{
{
QWriteLocker locker(&adaptixWidget->TargetsLock);
for ( int i = 0; i < adaptixWidget->Targets.size(); i++ ) {
if( adaptixWidget->Targets[i].TargetId == newTarget.TargetId ) {
TargetData* td = &adaptixWidget->Targets[i];
td->Computer = newTarget.Computer;
td->Domain = newTarget.Domain;
td->Address = newTarget.Address;
td->Tag = newTarget.Tag;
td->Os = newTarget.Os;
td->OsIcon = newTarget.OsIcon;
td->OsDesc = newTarget.OsDesc;
td->Date = newTarget.Date;
td->Info = newTarget.Info;
td->Alive = newTarget.Alive;
td->Agents = newTarget.Agents;
break;
}
}
}
targetsModel->update(newTarget.TargetId, newTarget);
}
void TargetsWidget::RemoveTargetsItem(const QStringList &targetsId) const
{
QStringList filtered;
{
QWriteLocker locker(&adaptixWidget->TargetsLock);
for (auto targetId : targetsId) {
for ( int i = 0; i < adaptixWidget->Targets.size(); i++ ) {
if( adaptixWidget->Targets[i].TargetId == targetId ) {
filtered.append(targetId);
adaptixWidget->Targets.erase( adaptixWidget->Targets.begin() + i );
break;
}
}
}
}
targetsModel->remove(filtered);
}
void TargetsWidget::TargetsSetTag(const QStringList &targetIds, const QString &tag) const
{
{
QWriteLocker locker(&adaptixWidget->TargetsLock);
QSet<QString> set1 = QSet<QString>(targetIds.begin(), targetIds.end());
for ( int i = 0; i < adaptixWidget->Targets.size(); i++ ) {
if( set1.contains(adaptixWidget->Targets[i].TargetId) ) {
adaptixWidget->Targets[i].Tag = tag;
set1.remove(adaptixWidget->Targets[i].TargetId);
if (set1.size() == 0)
break;
}
}
}
targetsModel->setTag(targetIds, tag);
}
void TargetsWidget::UpdateColumnsSize() const
{
tableView->resizeColumnToContents(TRC_Computer);
tableView->resizeColumnToContents(TRC_Domain);
tableView->resizeColumnToContents(TRC_Address);
tableView->resizeColumnToContents(TRC_Os);
tableView->resizeColumnToContents(TRC_Date);
}
void TargetsWidget::Clear() const
{
{
QWriteLocker locker(&adaptixWidget->TargetsLock);
adaptixWidget->Targets.clear();
}
targetsModel->clear();
inputFilter->clear();
}
/// Sender
void TargetsWidget::TargetsAdd(QList<TargetData> targetList)
{
QJsonArray jsonArray;
for (const auto &target : targetList) {
QJsonObject obj;
obj["computer"] = target.Computer;
obj["domain"] = target.Domain;
obj["address"] = target.Address;
obj["os"] = target.Os;
obj["os_desk"] = target.OsDesc;
obj["tag"] = target.Tag;
obj["info"] = target.Info;
obj["alive"] = target.Alive;
jsonArray.append(obj);
}
QJsonObject dataJson;
dataJson["targets"] = jsonArray;
QByteArray jsonData = QJsonDocument(dataJson).toJson();
HttpReqTargetsCreateAsync(jsonData, *(adaptixWidget->GetProfile()), [](bool success, const QString &message, const QJsonObject&) {
if (!success)
MessageError(message);
});
}
/// Slots
void TargetsWidget::toggleSearchPanel() const
{
if (this->searchWidget->isVisible()) {
this->searchWidget->setVisible(false);
proxyModel->setSearchVisible(false);
}
else {
this->searchWidget->setVisible(true);
proxyModel->setSearchVisible(true);
inputFilter->setFocus();
}
}
void TargetsWidget::onFilterUpdate() const
{
if (autoSearchCheck->isChecked()) {
proxyModel->setTextFilter(inputFilter->text());
}
inputFilter->setFocus();
UpdateColumnsSize();
}
void TargetsWidget::handleTargetsMenu(const QPoint &pos ) const
{
auto ctxMenu = QMenu();
ctxMenu.addAction("Create", this, &TargetsWidget::onCreateTarget );
QModelIndex index = tableView->indexAt(pos);
if (index.isValid()) {
QStringList targets;
QModelIndexList selectedRows = tableView->selectionModel()->selectedRows();
for (const QModelIndex &proxyIndex : selectedRows) {
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
if (!sourceIndex.isValid()) continue;
QString taskId = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Id), Qt::DisplayRole).toString();
targets.append(taskId);
}
int topCount = adaptixWidget->ScriptManager->AddMenuTargets(&ctxMenu, "TargetsTop", targets);
if (topCount > 0)
ctxMenu.addSeparator();
ctxMenu.addAction("Edit", this, &TargetsWidget::onEditTarget );
ctxMenu.addAction("Remove", this, &TargetsWidget::onRemoveTarget );
ctxMenu.addSeparator();
int centerCount = adaptixWidget->ScriptManager->AddMenuTargets(&ctxMenu, "TargetsCenter", targets);
if (centerCount > 0)
ctxMenu.addSeparator();
ctxMenu.addAction("Set tag", this, &TargetsWidget::onSetTag );
ctxMenu.addAction("Export to file", this, &TargetsWidget::onExportTarget );
ctxMenu.addAction("Copy to clipboard", this, &TargetsWidget::onCopyToClipboard );
adaptixWidget->ScriptManager->AddMenuTargets(&ctxMenu, "TargetsBottom", targets);
}
QPoint globalPos = tableView->mapToGlobal(pos);
ctxMenu.exec(globalPos);
}
void TargetsWidget::onCreateTarget()
{
DialogTarget* dialogTargets = new DialogTarget();
while (true) {
dialogTargets->StartDialog();
if (dialogTargets->IsValid())
break;
QString msg = dialogTargets->GetMessage();
if (msg.isEmpty()) {
delete dialogTargets;
return;
}
MessageError(msg);
}
TargetData targetData = dialogTargets->GetTargetData();
delete dialogTargets;
QList<TargetData> targetList;
targetList.append(targetData);
this->TargetsAdd(targetList);
}
void TargetsWidget::onEditTarget() const
{
auto idx = tableView->currentIndex();
if (!idx.isValid()) return;
QString targetId = proxyModel->index(idx.row(), TRC_Id).data().toString();
bool found = false;
TargetData targetData;
for (auto target : adaptixWidget->Targets) {
if (target.TargetId == targetId) {
targetData = target;
found = true;
break;
}
}
if (!found)
return;
DialogTarget* dialogTarget = new DialogTarget();
dialogTarget->SetEditmode(targetData);
while (true) {
dialogTarget->StartDialog();
if (dialogTarget->IsValid())
break;
QString msg = dialogTarget->GetMessage();
if (msg.isEmpty()) {
delete dialogTarget;
return;
}
MessageError(msg);
}
TargetData newTargetData = dialogTarget->GetTargetData();
QJsonObject dataJson;
dataJson["t_target_id"] = newTargetData.TargetId;
dataJson["t_computer"] = newTargetData.Computer;
dataJson["t_domain"] = newTargetData.Domain;
dataJson["t_address"] = newTargetData.Address;
dataJson["t_os"] = newTargetData.Os;
dataJson["t_os_desk"] = newTargetData.OsDesc;
dataJson["t_tag"] = newTargetData.Tag;
dataJson["t_info"] = newTargetData.Info;
dataJson["t_alive"] = newTargetData.Alive;
QByteArray jsonData = QJsonDocument(dataJson).toJson();
delete dialogTarget;
HttpReqTargetEditAsync(jsonData, *(adaptixWidget->GetProfile()), [](bool success, const QString& message, const QJsonObject&) {
if (!success)
MessageError(message.isEmpty() ? "Server is not responding" : message);
});
}
void TargetsWidget::onRemoveTarget() const
{
QStringList listId;
QModelIndexList selectedRows = tableView->selectionModel()->selectedRows();
for (const QModelIndex &proxyIndex : selectedRows) {
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
if (!sourceIndex.isValid()) continue;
QString agentId = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Id), Qt::DisplayRole).toString();
listId.append(agentId);
}
if(listId.empty())
return;
HttpReqTargetRemoveAsync(listId, *(adaptixWidget->GetProfile()), [](bool success, const QString& message, const QJsonObject&) {
if (!success)
MessageError(message.isEmpty() ? "Response timeout" : message);
});
}
void TargetsWidget::onSetTag() const
{
QString tag = "";
QStringList listId;
QModelIndexList selectedRows = tableView->selectionModel()->selectedRows();
for (const QModelIndex &proxyIndex : selectedRows) {
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
if (!sourceIndex.isValid()) continue;
QString cTag = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Tag), Qt::DisplayRole).toString();
QString agentId = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Id), Qt::DisplayRole).toString();
listId.append(agentId);
if (tag.isEmpty())
tag = cTag;
}
if(listId.empty())
return;
bool inputOk;
QString newTag = QInputDialog::getText(nullptr, "Set tags", "New tag", QLineEdit::Normal,tag, &inputOk);
if ( inputOk ) {
HttpReqTargetSetTagAsync(listId, newTag, *(adaptixWidget->GetProfile()), [](bool success, const QString& message, const QJsonObject&) {
if (!success)
MessageError(message.isEmpty() ? "Response timeout" : message);
});
}
}
void TargetsWidget::onExportTarget() const
{
auto idx = tableView->currentIndex();
if (!idx.isValid()) return;
QInputDialog dialog;
dialog.setWindowTitle("Format for saving");
dialog.setLabelText("Format:");
dialog.setTextValue("%computer%.%domain% - %address%");
QLineEdit *lineEdit = dialog.findChild<QLineEdit*>();
if (lineEdit)
lineEdit->setMinimumWidth(400);
bool inputOk = (dialog.exec() == QDialog::Accepted);
if (!inputOk)
return;
QString format = dialog.textValue();
QString baseDir = QStringLiteral("targets.txt");
if (adaptixWidget && adaptixWidget->GetProfile())
baseDir = QDir(adaptixWidget->GetProfile()->GetProjectDir()).filePath(QStringLiteral("targets.txt"));
NonBlockingDialogs::getSaveFileName(const_cast<TargetsWidget*>(this), "Save Targets", baseDir, "Text Files (*.txt);;All Files (*)",
[this, format](const QString& fileName) {
if (fileName.isEmpty())
return;
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
MessageError("Failed to open file for writing");
return;
}
QString content = "";
QModelIndexList selectedRows = tableView->selectionModel()->selectedRows();
for (const QModelIndex &proxyIndex : selectedRows) {
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
if (!sourceIndex.isValid()) continue;
QString computer = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Computer), Qt::DisplayRole).toString();
QString domain = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Domain), Qt::DisplayRole).toString();
QString address = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Address), Qt::DisplayRole).toString();
QString temp = format;
content += temp
.replace("%computer%", computer)
.replace("%domain%", domain)
.replace("%address%", address)
+ "\n";
}
file.write(content.trimmed().toUtf8());
file.close();
});
}
void TargetsWidget::onCopyToClipboard() const
{
auto idx = tableView->currentIndex();
if (!idx.isValid()) return;
QInputDialog dialog;
dialog.setWindowTitle("Format for clipboard");
dialog.setLabelText("Format:");
dialog.setTextValue("%computer%.%domain% - %address%");
QLineEdit *lineEdit = dialog.findChild<QLineEdit*>();
if (lineEdit)
lineEdit->setMinimumWidth(400);
bool inputOk = (dialog.exec() == QDialog::Accepted);
if (!inputOk)
return;
QString format = dialog.textValue();
QString content = "";
QModelIndexList selectedRows = tableView->selectionModel()->selectedRows();
for (const QModelIndex &proxyIndex : selectedRows) {
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
if (!sourceIndex.isValid()) continue;
QString computer = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Computer), Qt::DisplayRole).toString();
QString domain = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Domain), Qt::DisplayRole).toString();
QString address = targetsModel->data(targetsModel->index(sourceIndex.row(), TRC_Address), Qt::DisplayRole).toString();
QString temp = format;
content += temp
.replace("%computer%", computer)
.replace("%domain%", domain)
.replace("%address%", address)
+ "\n";
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(content.trimmed());
}