375 lines
10 KiB
C++
375 lines
10 KiB
C++
#ifndef CREDENTIALSWIDGET_H
|
|
#define CREDENTIALSWIDGET_H
|
|
|
|
#include <main.h>
|
|
#include <UI/Widgets/AbstractDock.h>
|
|
|
|
#include <QSortFilterProxyModel>
|
|
|
|
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<CredentialData> creds;
|
|
QHash<QString, int> 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<CredentialData>& 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<QString>& credIds) {
|
|
if (credIds.isEmpty() || creds.isEmpty())
|
|
return;
|
|
|
|
QList<int> 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<int>());
|
|
|
|
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<CredentialData> pendingCreds;
|
|
|
|
void createUI();
|
|
void flushPendingCreds();
|
|
|
|
public:
|
|
explicit CredentialsWidget(AdaptixWidget* w);
|
|
~CredentialsWidget() override;
|
|
|
|
void SetUpdatesEnabled(const bool enabled);
|
|
|
|
void AddCredentialsItems(QList<CredentialData> 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<CredentialData> 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 |