diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index c69ebe60e..1318fc3d6 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -7731,6 +7731,10 @@ Please consider generating a new key file. + + Browser Statistics + + QtIOCompressor @@ -7766,6 +7770,88 @@ Please consider generating a new key file. Internal zlib error: + + ReportsWidgetBrowserStatistics + + Exclude expired entries from the report + + + + Show only entries which have URL set + + + + Show only entries which have browser settings in custom data + + + + Double-click entries to edit. + + + + List of entry URLs + + + + Entry has no URLs set + + + + Allowed URLs + + + + Entry has no Browser Integration settings + + + + Denied URLs + + + + (Excluded) + + + + This entry is being excluded from reports + + + + Please wait, browser statistics is being calculated… + + + + No entries with a URL, or none has browser extension settings saved. + + + + URLs + + + + Title + Title + + + Path + Path + + + Edit Entry… + + + + Delete Entry(s)… + + + + + + + Exclude from reports + + + ReportsWidgetHealthcheck diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b08910d7..1cdb0dfc9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -242,6 +242,8 @@ if(WITH_XC_BROWSER) set(keepassxcbrowser_LIB keepassxcbrowser) set(keepassx_SOURCES ${keepassx_SOURCES} gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp) set(keepassx_SOURCES ${keepassx_SOURCES} gui/entry/EntryURLModel.cpp) + set(keepassx_SOURCES ${keepassx_SOURCES} gui/reports/ReportsWidgetBrowserStatistics.cpp) + set(keepassx_SOURCES ${keepassx_SOURCES} gui/reports/ReportsPageBrowserStatistics.cpp) endif() add_subdirectory(autotype) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index d6e072223..980ab163f 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -355,6 +355,26 @@ QString Entry::url() const return m_attributes->value(EntryAttributes::URLKey); } +QStringList Entry::getAllUrls() const +{ + QStringList urlList; + + if (!url().isEmpty()) { + urlList << url(); + } + + for (const auto& key : m_attributes->keys()) { + if (key.startsWith("KP2A_URL")) { + auto additionalUrl = m_attributes->value(key); + if (!additionalUrl.isEmpty()) { + urlList << additionalUrl; + } + } + } + + return urlList; +} + QString Entry::webUrl() const { QString url = resolveMultiplePlaceholders(m_attributes->value(EntryAttributes::URLKey)); diff --git a/src/core/Entry.h b/src/core/Entry.h index 50c3427ee..6227aa1a9 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -98,6 +98,7 @@ public: const AutoTypeAssociations* autoTypeAssociations() const; QString title() const; QString url() const; + QStringList getAllUrls() const; QString webUrl() const; QString displayUrl() const; QString username() const; diff --git a/src/gui/reports/ReportsDialog.cpp b/src/gui/reports/ReportsDialog.cpp index ecd015b89..406237459 100644 --- a/src/gui/reports/ReportsDialog.cpp +++ b/src/gui/reports/ReportsDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,10 @@ #include "ReportsPageHealthcheck.h" #include "ReportsPageHibp.h" #include "ReportsPageStatistics.h" +#ifdef WITH_XC_BROWSER +#include "ReportsPageBrowserStatistics.h" +#include "ReportsWidgetBrowserStatistics.h" +#endif #include "ReportsWidgetHealthcheck.h" #include "ReportsWidgetHibp.h" @@ -58,14 +62,20 @@ ReportsDialog::ReportsDialog(QWidget* parent) , m_healthPage(new ReportsPageHealthcheck()) , m_hibpPage(new ReportsPageHibp()) , m_statPage(new ReportsPageStatistics()) +#ifdef WITH_XC_BROWSER + , m_browserStatPage(new ReportsPageBrowserStatistics()) +#endif , m_editEntryWidget(new EditEntryWidget(this)) { m_ui->setupUi(this); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); + addPage(m_statPage); +#ifdef WITH_XC_BROWSER + addPage(m_browserStatPage); +#endif addPage(m_healthPage); addPage(m_hibpPage); - addPage(m_statPage); m_ui->stackedWidget->setCurrentIndex(0); @@ -77,6 +87,11 @@ ReportsDialog::ReportsDialog(QWidget* parent) connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int))); connect(m_healthPage->m_healthWidget, SIGNAL(entryActivated(Entry*)), SLOT(entryActivationSignalReceived(Entry*))); connect(m_hibpPage->m_hibpWidget, SIGNAL(entryActivated(Entry*)), SLOT(entryActivationSignalReceived(Entry*))); +#ifdef WITH_XC_BROWSER + connect(m_browserStatPage->m_browserWidget, + SIGNAL(entryActivated(Entry*)), + SLOT(entryActivationSignalReceived(Entry*))); +#endif connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool))); } @@ -142,6 +157,11 @@ void ReportsDialog::switchToMainView(bool previousDialogAccepted) } else if (m_sender == m_hibpPage->m_hibpWidget) { m_hibpPage->m_hibpWidget->refreshAfterEdit(); } +#ifdef WITH_XC_BROWSER + if (m_sender == m_browserStatPage->m_browserWidget) { + m_browserStatPage->m_browserWidget->calculateBrowserStatistics(); + } +#endif } // Don't process the same sender twice diff --git a/src/gui/reports/ReportsDialog.h b/src/gui/reports/ReportsDialog.h index 99da7f25f..25cb623eb 100644 --- a/src/gui/reports/ReportsDialog.h +++ b/src/gui/reports/ReportsDialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +29,9 @@ class QTabWidget; class ReportsPageHealthcheck; class ReportsPageHibp; class ReportsPageStatistics; +#ifdef WITH_XC_BROWSER +class ReportsPageBrowserStatistics; +#endif namespace Ui { @@ -74,6 +77,9 @@ private: const QSharedPointer m_healthPage; const QSharedPointer m_hibpPage; const QSharedPointer m_statPage; +#ifdef WITH_XC_BROWSER + const QSharedPointer m_browserStatPage; +#endif QPointer m_editEntryWidget; QWidget* m_sender = nullptr; diff --git a/src/gui/reports/ReportsPageBrowserStatistics.cpp b/src/gui/reports/ReportsPageBrowserStatistics.cpp new file mode 100644 index 000000000..97325745c --- /dev/null +++ b/src/gui/reports/ReportsPageBrowserStatistics.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ReportsPageBrowserStatistics.h" + +#include "ReportsWidgetBrowserStatistics.h" +#include "gui/Icons.h" + +ReportsPageBrowserStatistics::ReportsPageBrowserStatistics() + : m_browserWidget(new ReportsWidgetBrowserStatistics()) +{ +} + +QString ReportsPageBrowserStatistics::name() +{ + return QObject::tr("Browser Statistics"); +} + +QIcon ReportsPageBrowserStatistics::icon() +{ + return icons()->icon("internet-web-browser"); +} + +QWidget* ReportsPageBrowserStatistics::createWidget() +{ + return m_browserWidget; +} + +void ReportsPageBrowserStatistics::loadSettings(QWidget* widget, QSharedPointer db) +{ + const auto settingsWidget = reinterpret_cast(widget); + settingsWidget->loadSettings(db); +} + +void ReportsPageBrowserStatistics::saveSettings(QWidget* widget) +{ + const auto settingsWidget = reinterpret_cast(widget); + settingsWidget->saveSettings(); +} diff --git a/src/gui/reports/ReportsPageBrowserStatistics.h b/src/gui/reports/ReportsPageBrowserStatistics.h new file mode 100644 index 000000000..fb1b20a9f --- /dev/null +++ b/src/gui/reports/ReportsPageBrowserStatistics.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_REPORTSPAGEBROWSERSTATISTICS_H +#define KEEPASSXC_REPORTSPAGEBROWSERSTATISTICS_H + +#include "ReportsDialog.h" + +class ReportsWidgetBrowserStatistics; + +class ReportsPageBrowserStatistics : public IReportsPage +{ +public: + ReportsWidgetBrowserStatistics* m_browserWidget; + + ReportsPageBrowserStatistics(); + + QString name() override; + QIcon icon() override; + QWidget* createWidget() override; + void loadSettings(QWidget* widget, QSharedPointer db) override; + void saveSettings(QWidget* widget) override; +}; + +#endif // KEEPASSXC_REPORTSPAGEBROWSERSTATISTICS_H diff --git a/src/gui/reports/ReportsWidgetBrowserStatistics.cpp b/src/gui/reports/ReportsWidgetBrowserStatistics.cpp new file mode 100644 index 000000000..1a3ad5d7e --- /dev/null +++ b/src/gui/reports/ReportsWidgetBrowserStatistics.cpp @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ReportsWidgetBrowserStatistics.h" +#include "ui_ReportsWidgetBrowserStatistics.h" + +#include "browser/BrowserService.h" +#include "core/AsyncTask.h" +#include "core/Group.h" +#include "core/Metadata.h" +#include "gui/GuiTools.h" +#include "gui/Icons.h" +#include "gui/styles/StateColorPalette.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + class BrowserStatistics + { + public: + struct Item + { + QPointer group; + QPointer entry; + bool hasUrls; + bool hasSettings; + bool exclude = false; + + Item(Group* g, Entry* e, bool hU, bool hS) + : group(g) + , entry(e) + , hasUrls(hU) + , hasSettings(hS) + , exclude(e->excludeFromReports()) + { + } + }; + + explicit BrowserStatistics(QSharedPointer); + + const QList>& items() const + { + return m_items; + } + + private: + QSharedPointer m_db; + QList> m_items; + }; +} // namespace + +BrowserStatistics::BrowserStatistics(QSharedPointer db) + : m_db(db) +{ + for (auto group : db->rootGroup()->groupsRecursive(true)) { + // Skip recycle bin + if (group->isRecycled()) { + continue; + } + + for (auto entry : group->entries()) { + if (entry->isRecycled()) { + continue; + } + + auto hasUrls = !entry->getAllUrls().isEmpty(); + auto hasSettings = entry->customData()->contains(BrowserService::KEEPASSXCBROWSER_NAME); + + const auto item = QSharedPointer(new Item(group, entry, hasUrls, hasSettings)); + m_items.append(item); + } + } +} + +ReportsWidgetBrowserStatistics::ReportsWidgetBrowserStatistics(QWidget* parent) + : QWidget(parent) + , m_ui(new Ui::ReportsWidgetBrowserStatistics()) + , m_referencesModel(new QStandardItemModel(this)) + , m_modelProxy(new QSortFilterProxyModel(this)) +{ + m_ui->setupUi(this); + + m_modelProxy->setSourceModel(m_referencesModel.data()); + m_modelProxy->setSortLocaleAware(true); + m_ui->browserStatisticsTableView->setModel(m_modelProxy.data()); + m_ui->browserStatisticsTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + m_ui->browserStatisticsTableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + + connect(m_ui->browserStatisticsTableView, + SIGNAL(customContextMenuRequested(QPoint)), + SLOT(customMenuRequested(QPoint))); + connect( + m_ui->browserStatisticsTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); + connect(m_ui->showEntriesWithUrlOnlyCheckBox, SIGNAL(stateChanged(int)), this, SLOT(calculateBrowserStatistics())); + connect(m_ui->showConnectedOnlyCheckBox, SIGNAL(stateChanged(int)), this, SLOT(calculateBrowserStatistics())); + connect(m_ui->excludeExpired, SIGNAL(stateChanged(int)), this, SLOT(calculateBrowserStatistics())); + + new QShortcut(Qt::Key_Delete, this, SLOT(deleteSelectedEntries())); +} + +ReportsWidgetBrowserStatistics::~ReportsWidgetBrowserStatistics() +{ +} + +void ReportsWidgetBrowserStatistics::addStatisticsRow(bool hasUrls, + bool hasSettings, + Group* group, + Entry* entry, + bool excluded) +{ + StateColorPalette statePalette; + + auto urlList = entry->getAllUrls(); + auto urlToolTip = hasUrls ? tr("List of entry URLs") : tr("Entry has no URLs set"); + + auto browserConfig = getBrowserConfigFromEntry(entry); + auto allowedUrlsList = browserConfig["Allow"]; + auto deniedUrlsList = browserConfig["Deny"]; + + auto allowedUrlsToolTip = hasSettings ? tr("Allowed URLs") : tr("Entry has no Browser Integration settings"); + auto deniedUrlsToolTip = hasSettings ? tr("Denied URLs") : tr("Entry has no Browser Integration settings"); + + auto title = entry->title(); + if (excluded) { + title.append(tr(" (Excluded)")); + } + + auto row = QList(); + row << new QStandardItem(Icons::entryIconPixmap(entry), title); + row << new QStandardItem(Icons::groupIconPixmap(group), group->hierarchy().join("/")); + row << new QStandardItem(urlList.join('\n')); + row << new QStandardItem(allowedUrlsList.join('\n')); + row << new QStandardItem(deniedUrlsList.join('\n')); + + // Set tooltips + row[2]->setToolTip(urlToolTip); + row[3]->setToolTip(allowedUrlsToolTip); + row[4]->setToolTip(deniedUrlsToolTip); + if (excluded) { + row[0]->setToolTip(tr("This entry is being excluded from reports")); + } + + // Store entry pointer per table row (used in double click handler) + m_referencesModel->appendRow(row); + m_rowToEntry.append({group, entry}); +} + +void ReportsWidgetBrowserStatistics::loadSettings(QSharedPointer db) +{ + m_db = std::move(db); + m_statisticsCalculated = false; + m_referencesModel->clear(); + m_rowToEntry.clear(); + + auto row = QList(); + row << new QStandardItem(tr("Please wait, browser statistics is being calculated…")); + m_referencesModel->appendRow(row); +} + +void ReportsWidgetBrowserStatistics::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + + if (!m_statisticsCalculated) { + // Perform stats calculation on next event loop to allow widget to appear + m_statisticsCalculated = true; + QTimer::singleShot(0, this, SLOT(calculateBrowserStatistics())); + } +} + +void ReportsWidgetBrowserStatistics::calculateBrowserStatistics() +{ + m_referencesModel->clear(); + + // Perform the statistics check + const QScopedPointer browserStatistics( + AsyncTask::runAndWaitForFuture([this] { return new BrowserStatistics(m_db); })); + + const auto showExcluded = m_ui->showConnectedOnlyCheckBox->isChecked(); + const auto showEntriesWithUrlOnly = m_ui->showEntriesWithUrlOnlyCheckBox->isChecked(); + const auto showOnlyEntriesWithSettings = m_ui->showConnectedOnlyCheckBox->isChecked(); + + // Display the entries + m_rowToEntry.clear(); + for (const auto& item : browserStatistics->items()) { + auto excluded = item->exclude || (item->entry->isExpired() && m_ui->excludeExpired->isChecked()); + if (excluded && !showExcluded) { + // Exclude this entry from the report + continue; + } + + // Exclude this entry if URL are not set + if (showEntriesWithUrlOnly && !item->hasUrls) { + continue; + } + + // Exclude this entry if it doesn't have any Browser Integration settings + if (showOnlyEntriesWithSettings + && !item->entry->customData()->contains(BrowserService::KEEPASSXCBROWSER_NAME)) { + continue; + } + + // Show the entry in the report + addStatisticsRow(item->hasUrls, item->hasSettings, item->group, item->entry, item->exclude); + } + + // Set the table header + if (m_referencesModel->rowCount() == 0) { + m_referencesModel->setHorizontalHeaderLabels( + QStringList() << tr("No entries with a URL, or none has browser extension settings saved.")); + } else { + m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("Title") << tr("Path") << tr("URLs") + << tr("Allowed URLs") << tr("Denied URLs")); + m_ui->browserStatisticsTableView->sortByColumn(0, Qt::AscendingOrder); + } + + m_ui->browserStatisticsTableView->resizeColumnsToContents(); +} + +void ReportsWidgetBrowserStatistics::emitEntryActivated(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + + auto mappedIndex = m_modelProxy->mapToSource(index); + const auto row = m_rowToEntry[mappedIndex.row()]; + const auto group = row.first; + const auto entry = row.second; + + if (group && entry) { + emit entryActivated(const_cast(entry)); + } +} + +void ReportsWidgetBrowserStatistics::customMenuRequested(QPoint pos) +{ + auto selected = m_ui->browserStatisticsTableView->selectionModel()->selectedRows(); + if (selected.isEmpty()) { + return; + } + + // Create the context menu + const auto menu = new QMenu(this); + + // Create the "edit entry" menu item (only if 1 row is selected) + if (selected.size() == 1) { + const auto edit = new QAction(icons()->icon("entry-edit"), tr("Edit Entry…"), this); + menu->addAction(edit); + connect(edit, &QAction::triggered, edit, [this, selected] { + auto row = m_modelProxy->mapToSource(selected[0]).row(); + auto entry = m_rowToEntry[row].second; + emit entryActivated(entry); + }); + } + + // Create the "delete entry" menu item + const auto delEntry = new QAction(icons()->icon("entry-delete"), tr("Delete Entry(s)…", "", selected.size()), this); + menu->addAction(delEntry); + connect(delEntry, &QAction::triggered, this, &ReportsWidgetBrowserStatistics::deleteSelectedEntries); + + // Create the "exclude from reports" menu item + const auto exclude = new QAction(icons()->icon("reports-exclude"), tr("Exclude from reports"), this); + + bool isExcluded = false; + for (auto index : selected) { + auto row = m_modelProxy->mapToSource(index).row(); + auto entry = m_rowToEntry[row].second; + if (entry && entry->excludeFromReports()) { + // If at least one entry is excluded switch to inclusion + isExcluded = true; + break; + } + } + exclude->setCheckable(true); + exclude->setChecked(isExcluded); + + menu->addAction(exclude); + connect(exclude, &QAction::toggled, exclude, [this, selected](bool state) { + for (auto index : selected) { + auto row = m_modelProxy->mapToSource(index).row(); + auto entry = m_rowToEntry[row].second; + if (entry) { + entry->setExcludeFromReports(state); + } + } + calculateBrowserStatistics(); + }); + + // Show the context menu + menu->popup(m_ui->browserStatisticsTableView->viewport()->mapToGlobal(pos)); +} + +void ReportsWidgetBrowserStatistics::saveSettings() +{ + // Nothing to do - the tab is passive +} + +void ReportsWidgetBrowserStatistics::deleteSelectedEntries() +{ + QList selectedEntries; + for (auto index : m_ui->browserStatisticsTableView->selectionModel()->selectedRows()) { + auto row = m_modelProxy->mapToSource(index).row(); + auto entry = m_rowToEntry[row].second; + if (entry) { + selectedEntries << entry; + } + } + + bool permanent = !m_db->metadata()->recycleBinEnabled(); + if (GuiTools::confirmDeleteEntries(this, selectedEntries, permanent)) { + GuiTools::deleteEntriesResolveReferences(this, selectedEntries, permanent); + } + + calculateBrowserStatistics(); +} + +QMap ReportsWidgetBrowserStatistics::getBrowserConfigFromEntry(Entry* entry) const +{ + QMap configList; + + auto config = entry->customData()->value(BrowserService::KEEPASSXCBROWSER_NAME); + if (!config.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(config.toUtf8()); + if (!doc.isNull()) { + auto jsonObject = doc.object(); + auto allowedSites = jsonObject["Allow"].toArray(); + auto deniedSites = jsonObject["Deny"].toArray(); + + QStringList allowed; + foreach (const auto& value, allowedSites) { + auto url = value.toString(); + if (!url.isEmpty()) { + allowed << url; + } + } + + QStringList denied; + foreach (const auto& value, deniedSites) { + auto url = value.toString(); + if (!url.isEmpty()) { + denied << url; + } + } + + configList.insert("Allow", allowed); + configList.insert("Deny", denied); + } + } + + return configList; +} diff --git a/src/gui/reports/ReportsWidgetBrowserStatistics.h b/src/gui/reports/ReportsWidgetBrowserStatistics.h new file mode 100644 index 000000000..8aa6651ee --- /dev/null +++ b/src/gui/reports/ReportsWidgetBrowserStatistics.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_REPORTSWIDGETBROWSERSTATISTICS_H +#define KEEPASSXC_REPORTSWIDGETBROWSERSTATISTICS_H + +#include "gui/entry/EntryModel.h" +#include + +class Database; +class Entry; +class Group; +class PasswordHealth; +class QSortFilterProxyModel; +class QStandardItemModel; + +namespace Ui +{ + class ReportsWidgetBrowserStatistics; +} + +class ReportsWidgetBrowserStatistics : public QWidget +{ + Q_OBJECT +public: + explicit ReportsWidgetBrowserStatistics(QWidget* parent = nullptr); + ~ReportsWidgetBrowserStatistics() override; + + void loadSettings(QSharedPointer db); + void saveSettings(); + +protected: + void showEvent(QShowEvent* event) override; + +signals: + void entryActivated(Entry*); + +public slots: + void calculateBrowserStatistics(); + void emitEntryActivated(const QModelIndex& index); + void customMenuRequested(QPoint); + void deleteSelectedEntries(); + +private: + void addStatisticsRow(bool hasUrls, bool hasSettings, Group*, Entry*, bool); + QMap getBrowserConfigFromEntry(Entry* entry) const; + + QScopedPointer m_ui; + + bool m_statisticsCalculated = false; + QScopedPointer m_referencesModel; + QScopedPointer m_modelProxy; + QSharedPointer m_db; + QList> m_rowToEntry; +}; + +#endif // KEEPASSXC_REPORTSWIDGETBROWSERSTATISTICS_H diff --git a/src/gui/reports/ReportsWidgetBrowserStatistics.ui b/src/gui/reports/ReportsWidgetBrowserStatistics.ui new file mode 100644 index 000000000..4236da6e1 --- /dev/null +++ b/src/gui/reports/ReportsWidgetBrowserStatistics.ui @@ -0,0 +1,100 @@ + + + ReportsWidgetBrowserStatistics + + + + 0 + 0 + 505 + 379 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + QAbstractItemView::SelectRows + + + Qt::ElideMiddle + + + true + + + true + + + false + + + + + + + Exclude expired entries from the report + + + + + + + Show only entries which have URL set + + + + + + + Show only entries which have browser settings in custom data + + + + + + + + true + + + + Double-click entries to edit. + + + + + + + browserStatisticsTableView + excludeExpired + showEntriesWithUrlOnlyCheckBox + showConnectedOnlyCheckBox + + + + diff --git a/src/gui/styles/StateColorPalette.cpp b/src/gui/styles/StateColorPalette.cpp index c729e3269..8a2b563da 100644 --- a/src/gui/styles/StateColorPalette.cpp +++ b/src/gui/styles/StateColorPalette.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,6 +40,9 @@ void StateColorPalette::initDefaultPaletteLight() setColor(ColorRole::HealthWeak, QStringLiteral("#FFD30F")); setColor(ColorRole::HealthOk, QStringLiteral("#5EA10E")); setColor(ColorRole::HealthExcellent, QStringLiteral("#118f17")); + + setColor(ColorRole::True, QStringLiteral("#5EA10E")); + setColor(ColorRole::False, QStringLiteral("#C43F31")); } void StateColorPalette::initDefaultPaletteDark() @@ -54,4 +57,7 @@ void StateColorPalette::initDefaultPaletteDark() setColor(ColorRole::HealthWeak, QStringLiteral("#F0C400")); setColor(ColorRole::HealthOk, QStringLiteral("#608A22")); setColor(ColorRole::HealthExcellent, QStringLiteral("#1F8023")); + + setColor(ColorRole::True, QStringLiteral("#608A22")); + setColor(ColorRole::False, QStringLiteral("#C43F31")); } diff --git a/src/gui/styles/StateColorPalette.h b/src/gui/styles/StateColorPalette.h index 408fe032a..082f6a893 100644 --- a/src/gui/styles/StateColorPalette.h +++ b/src/gui/styles/StateColorPalette.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,7 +43,9 @@ public: HealthPoor, HealthWeak, HealthOk, - HealthExcellent + HealthExcellent, + True, + False }; inline void setColor(ColorRole role, const QColor& color)