#ifndef CREDENTIALSWIDGET_H #define CREDENTIALSWIDGET_H #include #include #include class AdaptixWidget; class ClickableLabel; enum CredsColumns { CC_Id, CC_Username, CC_Password, CC_Realm, CC_Type, CC_Tag, CC_Date, CC_Storage, CC_Agent, CC_Host, CC_ColumnCount }; class CredsFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT QString filter; QString typeFilter; QString storageFilter; bool searchVisible = false; bool matchesTerm(const QString &term, const QString &rowData) const { if (term.isEmpty()) return true; QRegularExpression re(QRegularExpression::escape(term.trimmed()), QRegularExpression::CaseInsensitiveOption); return rowData.contains(re); } bool evaluateExpression(const QString &expr, const QString &rowData) const { QString e = expr.trimmed(); if (e.isEmpty()) return true; int depth = 0; int lastOr = -1; for (int i = e.length() - 1; i >= 0; --i) { QChar c = e[i]; if (c == ')') depth++; else if (c == '(') depth--; else if (depth == 0 && c == '|') { lastOr = i; break; } } if (lastOr != -1) { QString left = e.left(lastOr).trimmed(); QString right = e.mid(lastOr + 1).trimmed(); return evaluateExpression(left, rowData) || evaluateExpression(right, rowData); } depth = 0; int lastAnd = -1; for (int i = e.length() - 1; i >= 0; --i) { QChar c = e[i]; if (c == ')') depth++; else if (c == '(') depth--; else if (depth == 0 && c == '&') { lastAnd = i; break; } } if (lastAnd != -1) { QString left = e.left(lastAnd).trimmed(); QString right = e.mid(lastAnd + 1).trimmed(); return evaluateExpression(left, rowData) && evaluateExpression(right, rowData); } if (e.startsWith("^(") && e.endsWith(')')) { return !evaluateExpression(e.mid(2, e.length() - 3), rowData); } if (e.startsWith('(') && e.endsWith(')')) { return evaluateExpression(e.mid(1, e.length() - 2), rowData); } return matchesTerm(e, rowData); } public: explicit CredsFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); setSortRole(Qt::UserRole); }; void setSearchVisible(bool visible) { if (searchVisible == visible) return; searchVisible = visible; invalidateFilter(); } void setTextFilter(const QString &text){ if (filter == text) return; filter = text; invalidateFilter(); } void setTypeFilter(const QString &type) { if (typeFilter == type) return; typeFilter = type; invalidateFilter(); } void setStorageFilter(const QString &storage) { if (storageFilter == storage) return; storageFilter = storage; invalidateFilter(); } protected: bool filterAcceptsRow(const int row, const QModelIndex &parent) const override { auto model = sourceModel(); if (!model) return true; if (!searchVisible) return true; if (!typeFilter.isEmpty()) { QString typeVal = model->index(row, CC_Type, parent).data().toString(); if (typeVal != typeFilter) return false; } if (!storageFilter.isEmpty()) { QString storageVal = model->index(row, CC_Storage, parent).data().toString(); if (storageVal != storageFilter) return false; } if (!filter.isEmpty()) { const int colCount = model->columnCount(); QString rowData; for (int col = 0; col < colCount; ++col) { rowData += model->index(row, col, parent).data().toString() + " "; } if (!evaluateExpression(filter, rowData)) return false; } return true; } }; class CredsTableModel : public QAbstractTableModel { Q_OBJECT QVector creds; QHash idToRow; void rebuildIndex() { idToRow.clear(); for (int i = 0; i < creds.size(); ++i) idToRow[creds[i].CredId] = i; } public: explicit CredsTableModel(QObject* parent = nullptr) : QAbstractTableModel(parent) {} int rowCount(const QModelIndex&) const override { return creds.size(); } int columnCount(const QModelIndex&) const override { return CC_ColumnCount; } QVariant data(const QModelIndex& index, const int role) const override { if (!index.isValid() || index.row() >= creds.size()) return {}; const CredentialData& c = creds.at(index.row()); if (role == Qt::DisplayRole) { switch (index.column()) { case CC_Id: return c.CredId; case CC_Username: return c.Username; case CC_Password: return c.Password; case CC_Realm: return c.Realm; case CC_Type: return c.Type; case CC_Tag: return c.Tag; case CC_Date: return c.Date; case CC_Storage: return c.Storage; case CC_Agent: return c.AgentId; case CC_Host: return c.Host; default: ; } } if (role == Qt::UserRole) { switch (index.column()) { case CC_Date: return c.DateTimestamp; default: return data(index, Qt::DisplayRole); } } if (role == Qt::TextAlignmentRole) { switch (index.column()) { case CC_Type: case CC_Date: case CC_Storage: case CC_Agent: return Qt::AlignCenter; default: ; } } return {}; } QVariant headerData(const int section, const Qt::Orientation o, const int role) const override { if (role != Qt::DisplayRole || o != Qt::Horizontal) return {}; static QStringList headers = { "CredId","Username","Password","Realm","Type", "Tag","Date","Storage","Agent","Host" }; return headers.value(section); } void add(const CredentialData& item) { const int row = creds.size(); beginInsertRows(QModelIndex(), row, row); creds.append(item); idToRow[item.CredId] = row; endInsertRows(); } void add(const QList& list) { if (list.isEmpty()) return; const int start = creds.size(); const int end = start + list.size() - 1; beginInsertRows(QModelIndex(), start, end); for (const auto& item : list) { idToRow[item.CredId] = creds.size(); creds.append(item); } endInsertRows(); } void update(const QString& credId, const CredentialData& newCred) { auto it = idToRow.find(credId); if (it == idToRow.end()) return; int row = it.value(); creds[row] = newCred; Q_EMIT dataChanged(index(row, 0), index(row, CC_ColumnCount - 1)); } void remove(const QList& credIds) { if (credIds.isEmpty() || creds.isEmpty()) return; QList rowsToRemove; rowsToRemove.reserve(credIds.size()); for (const QString& id : credIds) { auto it = idToRow.find(id); if (it != idToRow.end()) rowsToRemove.append(it.value()); } if (rowsToRemove.isEmpty()) return; std::ranges::sort(rowsToRemove, std::greater()); for (const int row : rowsToRemove) { beginRemoveRows(QModelIndex(), row, row); idToRow.remove(creds[row].CredId); creds.removeAt(row); endRemoveRows(); } rebuildIndex(); } void setTag(const QStringList &credIds, const QString &tag) { if (credIds.isEmpty() || creds.isEmpty()) return; for (const QString& id : credIds) { auto it = idToRow.find(id); if (it == idToRow.end()) continue; int row = it.value(); creds[row].Tag = tag; Q_EMIT dataChanged(index(row, CC_Tag), index(row, CC_Tag), {Qt::DisplayRole}); } } void clear() { beginResetModel(); creds.clear(); idToRow.clear(); endResetModel(); } }; class CredentialsWidget : public DockTab { Q_OBJECT AdaptixWidget* adaptixWidget = nullptr; QGridLayout* mainGridLayout = nullptr; QTableView* tableView = nullptr; QShortcut* shortcutSearch = nullptr; CredsTableModel* credsModel = nullptr; CredsFilterProxyModel* proxyModel = nullptr; QWidget* searchWidget = nullptr; QHBoxLayout* searchLayout = nullptr; QLineEdit* inputFilter = nullptr; QCheckBox* autoSearchCheck = nullptr; QComboBox* typeComboBox = nullptr; QComboBox* storageComboBox = nullptr; ClickableLabel* hideButton = nullptr; bool bufferingEnabled = false; QList pendingCreds; void createUI(); void flushPendingCreds(); public: explicit CredentialsWidget(AdaptixWidget* w); ~CredentialsWidget() override; void SetUpdatesEnabled(const bool enabled); void AddCredentialsItems(QList credsList); void EditCredentialsItem(const CredentialData &newCredentials) const; void RemoveCredentialsItem(const QStringList &credsId) const; void CredsSetTag(const QStringList &credsIds, const QString &tag) const; void UpdateColumnsSize() const; void UpdateFilterComboBoxes() const; void Clear() const; void CredentialsAdd(QList credsList); public Q_SLOTS: void toggleSearchPanel() const; void onFilterUpdate() const; void onTypeFilterUpdate(const QString &text) const; void onStorageFilterUpdate(const QString &text) const; void handleCredentialsMenu( const QPoint &pos ) const; void onCreateCreds(); void onEditCreds() const; void onRemoveCreds() const; void onSetTag() const; void onExportCreds() const; void onCopyToClipboard() const; }; #endif