diff --git a/src/ui/conf/confrulemanager.cpp b/src/ui/conf/confrulemanager.cpp index 1f211ce97..dac29973b 100644 --- a/src/ui/conf/confrulemanager.cpp +++ b/src/ui/conf/confrulemanager.cpp @@ -13,8 +13,6 @@ #include #include -#include "confmanager.h" - using namespace Fort; namespace { @@ -140,6 +138,20 @@ const char *const sqlUpdateRuleName = "UPDATE rule SET name = ?2 WHERE rule_id = const char *const sqlUpdateRuleEnabled = "UPDATE rule SET enabled = ?2 WHERE rule_id = ?1;"; +const char *const sqlSelectRuleIdDpendentApps = + "SELECT CASE " + " WHEN name IS NOT NULL AND name != '' THEN name " + " ELSE path " + "END AS display_name " + "FROM app WHERE rule_id = ?1 " + "ORDER BY lower(display_name);"; + +const char *const sqlSelectRuleIdDpendentRules = + "SELECT r.name, r.rule_type FROM rule r " + "JOIN rule_set rs ON rs.rule_id = r.rule_id " + "WHERE rs.sub_rule_id = ?1 " + "ORDER BY r.rule_type, lower(r.name);"; + bool driverWriteRules(ConfBuffer &confBuf, bool onlyFlags = false) { if (confBuf.hasError()) { @@ -415,6 +427,30 @@ bool ConfRuleManager::updateRuleEnabled(quint16 ruleId, bool enabled) return ok; } +ConfRuleManager::RuleDependentsInfo ConfRuleManager::getRuleDependentsInfo(quint16 ruleId, Rule::RuleType ruleType) const +{ + RuleDependentsInfo depsInfo; + SqliteStmt stmt; + + if (ruleType == Rule::AppRule && + DbQuery(sqliteDb()).sql(sqlSelectRuleIdDpendentApps).vars({ ruleId }).prepare(stmt)) { + while (stmt.step() == SqliteStmt::StepRow) { + depsInfo.appNames << stmt.columnText(0); + } + } + + if (ruleType == Rule::PresetRule && + DbQuery(sqliteDb()).sql(sqlSelectRuleIdDpendentRules).vars({ ruleId }).prepare(stmt)) { + while (stmt.step() == SqliteStmt::StepRow) { + QString name = stmt.columnText(0); + auto type = static_cast(stmt.columnInt(1)); + depsInfo.ruleNamesByType[type] << name; + } + } + + return depsInfo; +} + bool ConfRuleManager::walkRules( WalkRulesArgs &wra, const std::function &func) const { diff --git a/src/ui/conf/confrulemanager.h b/src/ui/conf/confrulemanager.h index a9cc0e474..021e43006 100644 --- a/src/ui/conf/confrulemanager.h +++ b/src/ui/conf/confrulemanager.h @@ -33,6 +33,16 @@ class ConfRuleManager : public ConfManagerBase, public ConfRulesWalker, public I virtual bool updateRuleName(quint16 ruleId, const QString &ruleName); virtual bool updateRuleEnabled(quint16 ruleId, bool enabled); + struct RuleDependentsInfo { + QStringList appNames; + QMap ruleNamesByType; + + bool isEmpty() const { + return appNames.isEmpty() && ruleNamesByType.isEmpty(); + } + }; + RuleDependentsInfo getRuleDependentsInfo(quint16 ruleId, Rule::RuleType ruleType) const; + bool walkRules(WalkRulesArgs &wra, const std::function &func) const override; static void walkRulesMapByStmt(WalkRulesArgs &wra, SqliteStmt &stmt); diff --git a/src/ui/conf/confzonemanager.cpp b/src/ui/conf/confzonemanager.cpp index ed0905404..adb9616b6 100644 --- a/src/ui/conf/confzonemanager.cpp +++ b/src/ui/conf/confzonemanager.cpp @@ -14,8 +14,6 @@ #include #include -#include "confmanager.h" - using namespace Fort; namespace { @@ -61,6 +59,24 @@ const char *const sqlUpdateZoneName = "UPDATE zone SET name = ?2 WHERE zone_id = const char *const sqlUpdateZoneEnabled = "UPDATE zone SET enabled = ?2 WHERE zone_id = ?1;"; +const char *const sqlSelectZoneIdDpendentAddressGroupIds = + "SELECT addr_group_id FROM address_group " + "WHERE (include_zones & ?1) <> 0 OR (exclude_zones & ?1) <> 0 " + "ORDER BY addr_group_id;"; + +const char *const sqlSelectZoneIdDpendentApps = + "SELECT CASE " + " WHEN name IS NOT NULL AND name != '' THEN name " + " ELSE path " + "END AS display_name FROM app " + "WHERE (accept_zones & ?1) <> 0 OR (reject_zones & ?1) <> 0 " + "ORDER BY lower(display_name);"; + +const char *const sqlSelectZoneIdDpendentRules = + "SELECT name, rule_type FROM rule " + "WHERE (accept_zones & ?1) <> 0 OR (reject_zones & ?1) <> 0 " + "ORDER BY rule_type, lower(name);"; + const char *const sqlUpdateZoneResult = "UPDATE zone" " SET address_count = ?2, text_checksum = ?3, bin_checksum = ?4," @@ -243,6 +259,36 @@ bool ConfZoneManager::updateZoneEnabled(quint8 zoneId, bool enabled) return ok; } +ConfZoneManager::ZoneDependentsInfo ConfZoneManager::getZoneDependentsInfo(quint8 zoneId) const +{ + ZoneDependentsInfo depsInfo; + SqliteStmt stmt; + + const quint32 zoneBit = (quint32(1) << (zoneId - 1)); + + if (DbQuery(sqliteDb()).sql(sqlSelectZoneIdDpendentAddressGroupIds).vars({ zoneBit }).prepare(stmt)) { + while (stmt.step() == SqliteStmt::StepRow) { + depsInfo.addressGroupIds << stmt.columnInt(0); + } + } + + if (DbQuery(sqliteDb()).sql(sqlSelectZoneIdDpendentApps).vars({ zoneBit }).prepare(stmt)) { + while (stmt.step() == SqliteStmt::StepRow) { + depsInfo.appNames << stmt.columnText(0); + } + } + + if (DbQuery(sqliteDb()).sql(sqlSelectZoneIdDpendentRules).vars({ zoneBit }).prepare(stmt)) { + while (stmt.step() == SqliteStmt::StepRow) { + QString name = stmt.columnText(0); + auto type = static_cast(stmt.columnInt(1)); + depsInfo.ruleNamesByType[type] << name; + } + } + + return depsInfo; +} + bool ConfZoneManager::updateZoneResult(const Zone &zone) { bool ok = false; diff --git a/src/ui/conf/confzonemanager.h b/src/ui/conf/confzonemanager.h index 19e905dec..e83d60b88 100644 --- a/src/ui/conf/confzonemanager.h +++ b/src/ui/conf/confzonemanager.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -27,6 +28,17 @@ class ConfZoneManager : public ConfManagerBase, public IocService virtual bool updateZoneName(quint8 zoneId, const QString &zoneName); virtual bool updateZoneEnabled(quint8 zoneId, bool enabled); + struct ZoneDependentsInfo { + QList addressGroupIds; + QStringList appNames; + QMap ruleNamesByType; + + bool isEmpty() const { + return appNames.isEmpty() && addressGroupIds.isEmpty() && ruleNamesByType.isEmpty(); + } + }; + ZoneDependentsInfo getZoneDependentsInfo(quint8 zoneId) const; + bool updateZoneResult(const Zone &zone); void updateDriverZones(quint32 zonesMask, quint32 enabledMask, quint32 dataSize, diff --git a/src/ui/form/rule/ruleswindow.cpp b/src/ui/form/rule/ruleswindow.cpp index 72d525539..e8e60937b 100644 --- a/src/ui/form/rule/ruleswindow.cpp +++ b/src/ui/form/rule/ruleswindow.cpp @@ -8,6 +8,7 @@ #include #include +#include #include
#include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include @@ -96,6 +98,7 @@ void RulesWindow::retranslateUi() m_actAddRule->setText(tr("Add")); m_actEditRule->setText(tr("Edit")); m_actRemoveRule->setText(tr("Remove")); + m_actShowRuleUsage->setText(tr("Show Usage")); m_editSearch->setPlaceholderText(tr("Search") + " /"); ruleListModel()->refresh(); @@ -160,12 +163,18 @@ QLayout *RulesWindow::setupHeader() m_actRemoveRule = editMenu->addAction(IconCache::icon(":/icons/delete.png"), QString()); m_actRemoveRule->setShortcut(Qt::Key_Delete); + editMenu->addSeparator(); + + m_actShowRuleUsage = editMenu->addAction(IconCache::icon(":/icons/information.png"), QString()); + m_actShowRuleUsage->setShortcut(Qt::Key_Exclam); + connect(m_actAddRule, &QAction::triggered, this, &RulesWindow::addNewRule); connect(m_actEditRule, &QAction::triggered, this, &RulesWindow::editSelectedRule); connect(m_actRemoveRule, &QAction::triggered, this, [&] { windowManager()->showConfirmBox( [&] { deleteSelectedRule(); }, tr("Are you sure to remove selected rule?")); }); + connect(m_actShowRuleUsage, &QAction::triggered, this, &RulesWindow::showRuleUsage); m_btEdit = ControlUtil::createButton(":/icons/pencil.png"); m_btEdit->setMenu(editMenu); @@ -252,6 +261,13 @@ void RulesWindow::setupTreeRulesChanged() const bool isRuleSelected = RuleListModel::isIndexRule(ruleIndex); m_actEditRule->setEnabled(isRuleSelected); m_actRemoveRule->setEnabled(isRuleSelected); + + bool isRuleSelectedNonGlobal = isRuleSelected; + if (isRuleSelected) { + const auto ruleType = RuleListModel::indexRuleType(ruleIndex); + isRuleSelectedNonGlobal = ruleType == Rule::AppRule || ruleType == Rule::PresetRule; + } + m_actShowRuleUsage->setEnabled(isRuleSelectedNonGlobal); }; refreshTreeRulesChanged(); @@ -371,6 +387,44 @@ void RulesWindow::deleteSelectedRule() ctrl()->deleteRule(ruleRow.ruleId); } +void RulesWindow::showRuleUsage() const +{ + const auto ruleIndex = ruleListCurrentIndex(); + if (!RuleListModel::isIndexRule(ruleIndex)) + return; + //get rule row + const auto &ruleRow = ruleListModel()->ruleRowAt(ruleIndex); + if (ruleRow.isNull()) + return; + + //dependents + ConfRuleManager::RuleDependentsInfo depsInfo = confRuleManager()->getRuleDependentsInfo( + ruleRow.ruleId, ruleRow.ruleType); + + if (depsInfo.isEmpty()) { + QString msg; + if (ruleRow.ruleType == Rule::AppRule) { + msg = tr("This rule is currently not in use by any programs."); + } else if (ruleRow.ruleType == Rule::PresetRule) { + msg = tr("This rule is currently not in use by any other rules."); + } else { + msg = tr("This rule is currently not in use."); + } + windowManager()->showInfoBox(msg, tr("Rule Usage")); + return; + } + + QStringList output; + const QString itemPrefix = "  "; + const QString itemSuffix = ""; + + const auto usageInAppRule = ConfUtil::formatUsageInAppRule(depsInfo.appNames, depsInfo.ruleNamesByType, + itemPrefix, itemSuffix); + output << usageInAppRule; + + windowManager()->showInfoBox(output.join("
").trimmed(), tr("Rule Usage")); +} + QModelIndex RulesWindow::ruleListCurrentIndex() const { return m_ruleListView->currentIndex(); diff --git a/src/ui/form/rule/ruleswindow.h b/src/ui/form/rule/ruleswindow.h index 53b33886c..1d0d78a32 100644 --- a/src/ui/form/rule/ruleswindow.h +++ b/src/ui/form/rule/ruleswindow.h @@ -64,6 +64,8 @@ class RulesWindow : public FormWindow void deleteSelectedRule(); + void showRuleUsage() const; + QModelIndex ruleListCurrentIndex() const; private: @@ -77,6 +79,7 @@ class RulesWindow : public FormWindow QAction *m_actAddRule = nullptr; QAction *m_actEditRule = nullptr; QAction *m_actRemoveRule = nullptr; + QAction *m_actShowRuleUsage = nullptr; QLineEdit *m_editSearch = nullptr; QToolButton *m_btOptions = nullptr; QPushButton *m_btMenu = nullptr; diff --git a/src/ui/form/zone/zoneswindow.cpp b/src/ui/form/zone/zoneswindow.cpp index 9f529ae32..97f19a096 100644 --- a/src/ui/form/zone/zoneswindow.cpp +++ b/src/ui/form/zone/zoneswindow.cpp @@ -7,11 +7,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -82,6 +84,7 @@ void ZonesWindow::retranslateUi() m_actAddZone->setText(tr("Add")); m_actEditZone->setText(tr("Edit")); m_actRemoveZone->setText(tr("Remove")); + m_actShowZoneUsage->setText(tr("Show Usage")); m_btSaveAsText->setText(tr("Save As Text")); m_btUpdateZones->setText(tr("Update Zones")); @@ -132,12 +135,18 @@ QLayout *ZonesWindow::setupHeader() m_actRemoveZone = editMenu->addAction(IconCache::icon(":/icons/delete.png"), QString()); m_actRemoveZone->setShortcut(Qt::Key_Delete); + editMenu->addSeparator(); + + m_actShowZoneUsage = editMenu->addAction(IconCache::icon(":/icons/information.png"), QString()); + m_actShowZoneUsage->setShortcut(Qt::Key_Exclam); + connect(m_actAddZone, &QAction::triggered, this, &ZonesWindow::addNewZone); connect(m_actEditZone, &QAction::triggered, this, &ZonesWindow::editSelectedZone); connect(m_actRemoveZone, &QAction::triggered, this, [&] { windowManager()->showConfirmBox( [&] { deleteSelectedZone(); }, tr("Are you sure to remove selected zone?")); }); + connect(m_actShowZoneUsage, &QAction::triggered, this, &ZonesWindow::showZoneUsage); m_btEdit = ControlUtil::createButton(":/icons/pencil.png"); m_btEdit->setMenu(editMenu); @@ -218,6 +227,7 @@ void ZonesWindow::setupTableZonesChanged() const bool zoneSelected = (zoneIndex >= 0); m_actEditZone->setEnabled(zoneSelected); m_actRemoveZone->setEnabled(zoneSelected); + m_actShowZoneUsage->setEnabled(zoneSelected); m_btSaveAsText->setEnabled(zoneSelected); }; @@ -283,6 +293,47 @@ void ZonesWindow::deleteSelectedZone() deleteZone(zoneListCurrentIndex()); } +void ZonesWindow::showZoneUsage() const +{ + const auto zoneIndex = zoneListCurrentIndex(); + if (zoneIndex < 0) + return; + //get zone row + const auto &zoneRow = zoneListModel()->zoneRowAt(zoneIndex); + if (zoneRow.isNull()) + return; + + //dependents + ConfZoneManager::ZoneDependentsInfo depsInfo = confZoneManager()->getZoneDependentsInfo(zoneRow.zoneId); + + if (depsInfo.isEmpty()) { + windowManager()->showInfoBox(tr("This zone is currently not in use."), tr("Zone Usage")); + return; + } + + QStringList output; + const QString itemPrefix = "  "; + const QString itemSuffix = ""; + + if (!depsInfo.addressGroupIds.isEmpty()) { + output << "" + tr("In Address Groups").toHtmlEscaped() + ""; + for (const int &addressGroupId : std::as_const(depsInfo.addressGroupIds)) { + QString addressGroupName; + if (addressGroupId == 1) { addressGroupName = tr("Local Network Addresses"); } + else if (addressGroupId == 2) { addressGroupName = tr("Block Addresses"); } + else { addressGroupName = tr("Address Group ID %1").arg(addressGroupId); } + output << itemPrefix + addressGroupName.toHtmlEscaped() + itemSuffix; + } + output << ""; + } + + const auto usageInAppRule = ConfUtil::formatUsageInAppRule(depsInfo.appNames, depsInfo.ruleNamesByType, + itemPrefix, itemSuffix); + output << usageInAppRule; + + windowManager()->showInfoBox(output.join("
").trimmed(), tr("Zone Usage")); +} + void ZonesWindow::downloadZones() { taskManager()->runTask(TaskInfo::ZoneDownloader); diff --git a/src/ui/form/zone/zoneswindow.h b/src/ui/form/zone/zoneswindow.h index 52c0d32fc..6e70b4244 100644 --- a/src/ui/form/zone/zoneswindow.h +++ b/src/ui/form/zone/zoneswindow.h @@ -45,6 +45,8 @@ class ZonesWindow : public FormWindow void deleteZone(int row); void deleteSelectedZone(); + void showZoneUsage() const; + void downloadZones(); int zoneListCurrentIndex() const; @@ -56,6 +58,7 @@ class ZonesWindow : public FormWindow QAction *m_actAddZone = nullptr; QAction *m_actEditZone = nullptr; QAction *m_actRemoveZone = nullptr; + QAction *m_actShowZoneUsage = nullptr; QToolButton *m_btSaveAsText = nullptr; QToolButton *m_btUpdateZones = nullptr; QToolButton *m_btOptions = nullptr; diff --git a/src/ui/util/conf/confutil.cpp b/src/ui/util/conf/confutil.cpp index dc9017573..03ecc1426 100644 --- a/src/ui/util/conf/confutil.cpp +++ b/src/ui/util/conf/confutil.cpp @@ -1,7 +1,9 @@ #include "confutil.h" #include - +#include +#include +#include #include int ConfUtil::ruleMaxCount() @@ -34,11 +36,52 @@ int ConfUtil::zoneMaxCount() return FORT_CONF_ZONE_MAX; } +QStringList ConfUtil::formatUsageInAppRule(const QStringList &appNames, const QMap &ruleNamesByType, + const QString itemPrefix, const QString itemSuffix) +{ + QStringList output; + + if (!appNames.isEmpty()) { + output << "" + RuleListModel::tr("In Programs").toHtmlEscaped() + ""; + for (const QString &appName : appNames) { + output << itemPrefix + appName.toHtmlEscaped() + itemSuffix; + } + output << ""; + } + + for (auto it = ruleNamesByType.cbegin(); it != ruleNamesByType.cend(); ++it) { + QString header; + switch (it.key()) { + case Rule::AppRule: + header = RuleListModel::tr("Application Rules"); + break; + case Rule::GlobalBeforeAppsRule: + header = RuleListModel::tr("Global Rules, applied before App Rules"); + break; + case Rule::GlobalAfterAppsRule: + header = RuleListModel::tr("Global Rules, applied after App Rules"); + break; + case Rule::PresetRule: + header = RuleListModel::tr("Preset Rules"); + break; + default: + continue; + } + output << "" + header.toHtmlEscaped() + ""; + + for (const QString &name : it.value()) { + output << itemPrefix + name.toHtmlEscaped() + itemSuffix; + } + output << ""; + } + return output; +} + QRegularExpressionMatch ConfUtil::matchWildcard(const QStringView path) { - static const QRegularExpression wildMatcher("([*?])"); + static const QRegularExpression wildMatcher("([*?]|^\\[)"); - return path.startsWith('[') || StringUtil::match(wildMatcher, path); + return StringUtil::match(wildMatcher, path); } bool ConfUtil::hasWildcard(const QString &path) diff --git a/src/ui/util/conf/confutil.h b/src/ui/util/conf/confutil.h index 107b1747d..6a212eed4 100644 --- a/src/ui/util/conf/confutil.h +++ b/src/ui/util/conf/confutil.h @@ -4,6 +4,9 @@ #include #include +#include +#include + class ConfUtil { public: @@ -15,6 +18,9 @@ class ConfUtil static int zoneMaxCount(); + static QStringList formatUsageInAppRule(const QStringList &appNames, const QMap &ruleNamesByType, + const QString itemPrefix, const QString itemSuffix); + static QRegularExpressionMatch matchWildcard(const QStringView path); static bool hasWildcard(const QString &path);