#include #include #include #include #include #include #include #include #include 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 filtered; { QWriteLocker locker(&adaptixWidget->TargetsLock); QSet 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 targetList) { if (targetList.isEmpty()) return; if (bufferingEnabled) { pendingTargets.append(targetList); return; } QList filtered; { QWriteLocker locker(&adaptixWidget->TargetsLock); QSet 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 set1 = QSet(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 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 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(); 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(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(); 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()); }