mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Client] Alt tab filters and unified user timeline
TabAlts gains client-side match filters (Email / Client ID / IP toggles plus a hide-IPs-shared-by-more-than-N-users threshold for VPN noise) with a shown/hidden status count, and a Selected user panel that loads the row account's counts, admin notes, warning history, and ban history in one place. Detail requests are guarded against stale selections.
This commit is contained in:
parent
66ae189a57
commit
4ad392d705
2 changed files with 331 additions and 28 deletions
|
|
@ -4,21 +4,31 @@
|
|||
#include "abstract_client.h"
|
||||
#include "libcockatrice/protocol/pb/response.pb.h"
|
||||
#include "libcockatrice/protocol/pb/response_user_alts.pb.h"
|
||||
#include "libcockatrice/protocol/pb/serverinfo_user_alt.pb.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCheckBox>
|
||||
#include <QDateTime>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QList>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QTableWidget>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
|
||||
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_ban_history.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_report_user_info.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_warn_history.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_ban.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_warning.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <string>
|
||||
|
||||
|
|
@ -38,6 +48,21 @@ constexpr int COL_ALTS_ACTIVE = 10;
|
|||
constexpr int COL_ALTS_COUNT = 11;
|
||||
|
||||
constexpr int MAX_ALT_DEPTH = 3;
|
||||
// Starting default only (adjustable 1-100000): shared VPN exits routinely
|
||||
// cover dozens of users, so 10 hides the obvious VPN noise but keeps
|
||||
// small shared networks visible.
|
||||
constexpr int DEFAULT_VPN_THRESHOLD = 10;
|
||||
|
||||
void configureAltTable(QTableWidget *table)
|
||||
{
|
||||
table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
table->verticalHeader()->setVisible(false);
|
||||
table->setAlternatingRowColors(true);
|
||||
table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
table->horizontalHeader()->setStretchLastSection(true);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const QString &initialUser)
|
||||
|
|
@ -70,27 +95,80 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
|||
topBar->addWidget(depthSpin);
|
||||
topBar->addWidget(investigateButton);
|
||||
|
||||
emailCheck = new QCheckBox;
|
||||
emailCheck->setChecked(true);
|
||||
connect(emailCheck, &QCheckBox::toggled, this, &TabAlts::onFilterChanged);
|
||||
clientIdCheck = new QCheckBox;
|
||||
clientIdCheck->setChecked(true);
|
||||
connect(clientIdCheck, &QCheckBox::toggled, this, &TabAlts::onFilterChanged);
|
||||
ipCheck = new QCheckBox;
|
||||
ipCheck->setChecked(true);
|
||||
connect(ipCheck, &QCheckBox::toggled, this, &TabAlts::onFilterChanged);
|
||||
vpnLabel = new QLabel;
|
||||
vpnSpin = new QSpinBox;
|
||||
vpnSpin->setMinimum(1);
|
||||
vpnSpin->setMaximum(100000);
|
||||
vpnSpin->setValue(DEFAULT_VPN_THRESHOLD);
|
||||
connect(vpnSpin, &QSpinBox::valueChanged, this, &TabAlts::onFilterChanged);
|
||||
|
||||
auto *filterBar = new QHBoxLayout;
|
||||
filterBar->addWidget(emailCheck);
|
||||
filterBar->addWidget(clientIdCheck);
|
||||
filterBar->addWidget(ipCheck);
|
||||
filterBar->addWidget(vpnLabel);
|
||||
filterBar->addWidget(vpnSpin);
|
||||
filterBar->addStretch();
|
||||
|
||||
altsTable = new QTableWidget(0, COL_ALTS_COUNT);
|
||||
altsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
altsTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
altsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
altsTable->verticalHeader()->setVisible(false);
|
||||
altsTable->setAlternatingRowColors(true);
|
||||
altsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
altsTable->horizontalHeader()->setStretchLastSection(true);
|
||||
configureAltTable(altsTable);
|
||||
connect(altsTable, &QTableWidget::cellDoubleClicked, this, [this](int row, int) {
|
||||
auto *userItem = altsTable->item(row, COL_ALTS_USER);
|
||||
if (userItem != nullptr && !userItem->text().isEmpty() && userItem->text() != currentUser) {
|
||||
investigate(userItem->text());
|
||||
}
|
||||
});
|
||||
connect(altsTable, &QTableWidget::itemSelectionChanged, this, &TabAlts::onAltSelected);
|
||||
|
||||
detailNameValue = new QLabel;
|
||||
detailCountsValue = new QLabel;
|
||||
detailNotesEdit = new QTextEdit;
|
||||
detailNotesEdit->setReadOnly(true);
|
||||
// Compact cap so the warn/ban tables stay visible without scrolling the tab.
|
||||
detailNotesEdit->setMaximumHeight(60);
|
||||
warnsTable = new QTableWidget(0, 3);
|
||||
configureAltTable(warnsTable);
|
||||
bansTable = new QTableWidget(0, 4);
|
||||
configureAltTable(bansTable);
|
||||
|
||||
auto *detailGrid = new QGridLayout;
|
||||
detailNameLabel = new QLabel;
|
||||
detailCountsLabel = new QLabel;
|
||||
detailNotesLabel = new QLabel;
|
||||
detailGrid->addWidget(detailNameLabel, 0, 0);
|
||||
detailGrid->addWidget(detailNameValue, 0, 1);
|
||||
detailGrid->addWidget(detailCountsLabel, 0, 2);
|
||||
detailGrid->addWidget(detailCountsValue, 0, 3);
|
||||
detailGrid->addWidget(detailNotesLabel, 1, 0);
|
||||
detailGrid->addWidget(detailNotesEdit, 1, 1, 1, 3);
|
||||
|
||||
auto *detailTables = new QHBoxLayout;
|
||||
detailTables->addWidget(warnsTable);
|
||||
detailTables->addWidget(bansTable);
|
||||
|
||||
detailsGroup = new QGroupBox;
|
||||
auto *detailsLayout = new QVBoxLayout(detailsGroup);
|
||||
detailsLayout->addLayout(detailGrid);
|
||||
detailsLayout->addLayout(detailTables);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(centralWidget);
|
||||
mainLayout->addLayout(topBar);
|
||||
mainLayout->addLayout(filterBar);
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addWidget(altsTable);
|
||||
mainLayout->addWidget(altsTable, 3);
|
||||
mainLayout->addWidget(detailsGroup, 2);
|
||||
|
||||
retranslateUi();
|
||||
clearDetails();
|
||||
investigate(initialUser);
|
||||
}
|
||||
|
||||
|
|
@ -106,9 +184,22 @@ void TabAlts::retranslateUi()
|
|||
statusLabel->setText(tr("Enter a user name to investigate."));
|
||||
}
|
||||
|
||||
emailCheck->setText(tr("Email"));
|
||||
clientIdCheck->setText(tr("Client ID"));
|
||||
ipCheck->setText(tr("IP"));
|
||||
vpnLabel->setText(tr("Hide IPs shared by more than"));
|
||||
vpnSpin->setSuffix(tr(" users"));
|
||||
|
||||
altsTable->setHorizontalHeaderLabels({tr("User"), tr("Via"), tr("Depth"), tr("Shared"), tr("eMail"),
|
||||
tr("Client ID"), tr("Registered"), tr("Last login"), tr("Warns"), tr("Bans"),
|
||||
tr("Active")});
|
||||
warnsTable->setHorizontalHeaderLabels({tr("Warning time"), tr("Moderator"), tr("Reason")});
|
||||
bansTable->setHorizontalHeaderLabels({tr("Ban time"), tr("Moderator"), tr("Length"), tr("Reason")});
|
||||
|
||||
detailsGroup->setTitle(tr("Selected user"));
|
||||
detailNameLabel->setText(tr("Name:"));
|
||||
detailCountsLabel->setText(tr("Counts:"));
|
||||
detailNotesLabel->setText(tr("Admin notes:"));
|
||||
}
|
||||
|
||||
QString TabAlts::formatEpoch(quint64 ts) const
|
||||
|
|
@ -152,6 +243,9 @@ void TabAlts::investigateUser()
|
|||
return;
|
||||
}
|
||||
currentUser = userName;
|
||||
lastAlts.clear();
|
||||
selectedUser.clear();
|
||||
clearDetails();
|
||||
altsTable->setRowCount(0);
|
||||
statusLabel->setText(tr("Loading alts for %1...").arg(currentUser));
|
||||
requestAlts(userName, depthSpin->value());
|
||||
|
|
@ -173,7 +267,7 @@ void TabAlts::requestAlts(const QString &userName, int depth)
|
|||
|
||||
void TabAlts::altsResponse(const Response &response)
|
||||
{
|
||||
altsTable->setRowCount(0);
|
||||
lastAlts.clear();
|
||||
if (response.response_code() != Response::RespOk) {
|
||||
statusLabel->setText(tr("Could not load alts for %1.").arg(currentUser));
|
||||
return;
|
||||
|
|
@ -186,31 +280,207 @@ void TabAlts::altsResponse(const Response &response)
|
|||
return;
|
||||
}
|
||||
|
||||
altsTable->setRowCount(resp.alts_size());
|
||||
int relatedCount = 0;
|
||||
for (int i = 0; i < resp.alts_size(); ++i) {
|
||||
const ServerInfo_UserAlt &alt = resp.alts(i);
|
||||
// The server appends the frontier user as a depth-0 seed row.
|
||||
if (!alt.has_depth() || alt.depth() > 0) {
|
||||
++relatedCount;
|
||||
lastAlts << resp.alts(i);
|
||||
}
|
||||
renderTable();
|
||||
}
|
||||
|
||||
bool TabAlts::rowVisible(const ServerInfo_UserAlt &alt) const
|
||||
{
|
||||
const QString name = QString::fromStdString(alt.user_name());
|
||||
if (name == currentUser) {
|
||||
return true;
|
||||
}
|
||||
if (!alt.has_match_reason() || alt.match_reason().empty()) {
|
||||
return true;
|
||||
}
|
||||
const QString reason = QString::fromStdString(alt.match_reason());
|
||||
if (reason == QStringLiteral("email")) {
|
||||
return emailCheck->isChecked();
|
||||
}
|
||||
if (reason.startsWith(QStringLiteral("clientid:"))) {
|
||||
return clientIdCheck->isChecked();
|
||||
}
|
||||
if (reason.startsWith(QStringLiteral("ip:"))) {
|
||||
return ipCheck->isChecked() &&
|
||||
(!alt.has_shared_count() || alt.shared_count() <= static_cast<unsigned>(vpnSpin->value()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TabAlts::renderTable()
|
||||
{
|
||||
altsTable->setRowCount(0);
|
||||
int hidden = 0;
|
||||
int row = 0;
|
||||
for (const ServerInfo_UserAlt &alt : lastAlts) {
|
||||
if (!rowVisible(alt)) {
|
||||
++hidden;
|
||||
continue;
|
||||
}
|
||||
altsTable->setItem(i, COL_ALTS_USER, new QTableWidgetItem(QString::fromStdString(alt.user_name())));
|
||||
altsTable->setItem(i, COL_ALTS_VIA,
|
||||
altsTable->insertRow(row);
|
||||
altsTable->setItem(row, COL_ALTS_USER, new QTableWidgetItem(QString::fromStdString(alt.user_name())));
|
||||
altsTable->setItem(row, COL_ALTS_VIA,
|
||||
new QTableWidgetItem(alt.has_match_reason() && !alt.match_reason().empty()
|
||||
? QString::fromStdString(alt.match_reason())
|
||||
: QStringLiteral("-")));
|
||||
altsTable->setItem(i, COL_ALTS_DEPTH,
|
||||
altsTable->setItem(row, COL_ALTS_DEPTH,
|
||||
new QTableWidgetItem(alt.has_depth() ? QString::number(alt.depth()) : QStringLiteral("-")));
|
||||
altsTable->setItem(
|
||||
i, COL_ALTS_SHARED,
|
||||
row, COL_ALTS_SHARED,
|
||||
new QTableWidgetItem(alt.has_shared_count() ? QString::number(alt.shared_count()) : QStringLiteral("-")));
|
||||
altsTable->setItem(i, COL_ALTS_EMAIL, new QTableWidgetItem(QString::fromStdString(alt.email())));
|
||||
altsTable->setItem(i, COL_ALTS_CLIENTID, new QTableWidgetItem(QString::fromStdString(alt.clientid())));
|
||||
altsTable->setItem(i, COL_ALTS_REGISTERED, new QTableWidgetItem(formatEpoch(alt.registration_time())));
|
||||
altsTable->setItem(i, COL_ALTS_LAST_LOGIN, new QTableWidgetItem(formatEpoch(alt.last_login())));
|
||||
altsTable->setItem(i, COL_ALTS_WARNS, new QTableWidgetItem(QString::number(alt.warn_count())));
|
||||
altsTable->setItem(i, COL_ALTS_BANS, new QTableWidgetItem(QString::number(alt.ban_count())));
|
||||
altsTable->setItem(i, COL_ALTS_ACTIVE, new QTableWidgetItem(alt.is_active() ? tr("yes") : tr("no")));
|
||||
altsTable->setItem(row, COL_ALTS_EMAIL, new QTableWidgetItem(QString::fromStdString(alt.email())));
|
||||
altsTable->setItem(row, COL_ALTS_CLIENTID, new QTableWidgetItem(QString::fromStdString(alt.clientid())));
|
||||
altsTable->setItem(row, COL_ALTS_REGISTERED, new QTableWidgetItem(formatEpoch(alt.registration_time())));
|
||||
altsTable->setItem(row, COL_ALTS_LAST_LOGIN, new QTableWidgetItem(formatEpoch(alt.last_login())));
|
||||
altsTable->setItem(row, COL_ALTS_WARNS, new QTableWidgetItem(QString::number(alt.warn_count())));
|
||||
altsTable->setItem(row, COL_ALTS_BANS, new QTableWidgetItem(QString::number(alt.ban_count())));
|
||||
altsTable->setItem(row, COL_ALTS_ACTIVE, new QTableWidgetItem(alt.is_active() ? tr("yes") : tr("no")));
|
||||
++row;
|
||||
}
|
||||
// The server appends the frontier user as a depth-0 seed row; it is the
|
||||
// investigated user, not one of their related accounts.
|
||||
int relatedCount = 0;
|
||||
for (const ServerInfo_UserAlt &alt : lastAlts) {
|
||||
if (!alt.has_depth() || alt.depth() > 0) {
|
||||
++relatedCount;
|
||||
}
|
||||
}
|
||||
if (hidden == 0) {
|
||||
statusLabel->setText(tr("%1 account(s) related to %2.").arg(relatedCount).arg(currentUser));
|
||||
} else {
|
||||
statusLabel->setText(tr("%1 shown, %2 hidden by filters (%3 total) related to %4.")
|
||||
.arg(row)
|
||||
.arg(hidden)
|
||||
.arg(relatedCount)
|
||||
.arg(currentUser));
|
||||
}
|
||||
}
|
||||
|
||||
void TabAlts::onFilterChanged()
|
||||
{
|
||||
if (lastAlts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
renderTable();
|
||||
}
|
||||
|
||||
void TabAlts::onAltSelected()
|
||||
{
|
||||
const auto selected = altsTable->selectedItems();
|
||||
if (selected.isEmpty()) {
|
||||
selectedUser.clear();
|
||||
clearDetails();
|
||||
return;
|
||||
}
|
||||
auto *userItem = altsTable->item(selected.first()->row(), COL_ALTS_USER);
|
||||
if (userItem == nullptr || userItem->text().isEmpty() || userItem->text() == selectedUser) {
|
||||
return;
|
||||
}
|
||||
selectedUser = userItem->text();
|
||||
requestDetails(selectedUser);
|
||||
}
|
||||
|
||||
void TabAlts::requestDetails(const QString &userName)
|
||||
{
|
||||
detailNameValue->setText(userName);
|
||||
detailCountsValue->setText(tr("Loading..."));
|
||||
detailNotesEdit->clear();
|
||||
warnsTable->setRowCount(0);
|
||||
bansTable->setRowCount(0);
|
||||
|
||||
Command_ReportUserInfo infoCmd;
|
||||
infoCmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *infoPend = client->prepareModeratorCommand(infoCmd);
|
||||
connect(infoPend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
||||
if (userName == selectedUser) {
|
||||
userDetailsResponse(response);
|
||||
}
|
||||
});
|
||||
client->sendCommand(infoPend);
|
||||
|
||||
Command_GetWarnHistory warnCmd;
|
||||
warnCmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *warnPend = client->prepareModeratorCommand(warnCmd);
|
||||
connect(warnPend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
||||
if (userName == selectedUser) {
|
||||
warnHistoryResponse(response);
|
||||
}
|
||||
});
|
||||
client->sendCommand(warnPend);
|
||||
|
||||
Command_GetBanHistory banCmd;
|
||||
banCmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *banPend = client->prepareModeratorCommand(banCmd);
|
||||
connect(banPend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
||||
if (userName == selectedUser) {
|
||||
banHistoryResponse(response);
|
||||
}
|
||||
});
|
||||
client->sendCommand(banPend);
|
||||
}
|
||||
|
||||
void TabAlts::clearDetails()
|
||||
{
|
||||
detailNameValue->clear();
|
||||
detailCountsValue->clear();
|
||||
detailNotesEdit->clear();
|
||||
warnsTable->setRowCount(0);
|
||||
bansTable->setRowCount(0);
|
||||
}
|
||||
|
||||
void TabAlts::userDetailsResponse(const Response &response)
|
||||
{
|
||||
if (response.response_code() != Response::RespOk) {
|
||||
detailCountsValue->setText(tr("Error loading user info."));
|
||||
return;
|
||||
}
|
||||
|
||||
const Response_ReportUserInfo &resp = response.GetExtension(Response_ReportUserInfo::ext);
|
||||
detailCountsValue->setText(tr("Reports: %1 Bans: %2 Warnings: %3")
|
||||
.arg(resp.total_reports())
|
||||
.arg(resp.total_bans())
|
||||
.arg(resp.total_warns()));
|
||||
|
||||
if (resp.has_admin_notes() && !resp.admin_notes().empty()) {
|
||||
detailNotesEdit->setPlainText(QString::fromStdString(resp.admin_notes()));
|
||||
} else {
|
||||
detailNotesEdit->setPlainText(tr("(none)"));
|
||||
}
|
||||
}
|
||||
|
||||
void TabAlts::warnHistoryResponse(const Response &response)
|
||||
{
|
||||
warnsTable->setRowCount(0);
|
||||
if (response.response_code() != Response::RespOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Response_WarnHistory &resp = response.GetExtension(Response_WarnHistory::ext);
|
||||
warnsTable->setRowCount(resp.warn_list_size());
|
||||
for (int i = 0; i < resp.warn_list_size(); ++i) {
|
||||
const ServerInfo_Warning &warn = resp.warn_list(i);
|
||||
warnsTable->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(warn.time_of())));
|
||||
warnsTable->setItem(i, 1, new QTableWidgetItem(QString::fromStdString(warn.admin_name())));
|
||||
warnsTable->setItem(i, 2, new QTableWidgetItem(QString::fromStdString(warn.reason())));
|
||||
}
|
||||
}
|
||||
|
||||
void TabAlts::banHistoryResponse(const Response &response)
|
||||
{
|
||||
bansTable->setRowCount(0);
|
||||
if (response.response_code() != Response::RespOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Response_BanHistory &resp = response.GetExtension(Response_BanHistory::ext);
|
||||
bansTable->setRowCount(resp.ban_list_size());
|
||||
for (int i = 0; i < resp.ban_list_size(); ++i) {
|
||||
const ServerInfo_Ban &ban = resp.ban_list(i);
|
||||
bansTable->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(ban.ban_time())));
|
||||
bansTable->setItem(i, 1, new QTableWidgetItem(QString::fromStdString(ban.admin_name())));
|
||||
bansTable->setItem(i, 2, new QTableWidgetItem(QString::fromStdString(ban.ban_length())));
|
||||
bansTable->setItem(i, 3, new QTableWidgetItem(QString::fromStdString(ban.ban_reason())));
|
||||
}
|
||||
statusLabel->setText(tr("%1 account(s) related to %2.").arg(relatedCount).arg(currentUser));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,20 @@
|
|||
|
||||
#include "tab.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user_alt.pb.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class AbstractClient;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
class QTableWidget;
|
||||
class QTextEdit;
|
||||
class Response;
|
||||
class TabSupervisor;
|
||||
|
||||
|
|
@ -20,6 +24,7 @@ class TabSupervisor;
|
|||
* Staff alt-graph investigation tool. Shows accounts related to the
|
||||
* investigated user (shared email, client id, or recent IP address),
|
||||
* including transitive alts-of-alts, with per-row match evidence.
|
||||
* Selecting a row loads that account's warnings, bans, and admin notes.
|
||||
*/
|
||||
class TabAlts : public Tab
|
||||
{
|
||||
|
|
@ -37,15 +42,26 @@ private slots:
|
|||
void investigateUser();
|
||||
void goBack();
|
||||
void altsResponse(const Response &response);
|
||||
void onFilterChanged();
|
||||
void onAltSelected();
|
||||
void userDetailsResponse(const Response &response);
|
||||
void warnHistoryResponse(const Response &response);
|
||||
void banHistoryResponse(const Response &response);
|
||||
|
||||
private:
|
||||
void loadUser(const QString &userName);
|
||||
void requestAlts(const QString &userName, int depth);
|
||||
void requestDetails(const QString &userName);
|
||||
void clearDetails();
|
||||
void renderTable();
|
||||
[[nodiscard]] bool rowVisible(const ServerInfo_UserAlt &alt) const;
|
||||
[[nodiscard]] QString formatEpoch(quint64 ts) const;
|
||||
|
||||
AbstractClient *client;
|
||||
QString currentUser;
|
||||
QString selectedUser;
|
||||
QList<QString> backStack;
|
||||
QList<ServerInfo_UserAlt> lastAlts;
|
||||
|
||||
QLineEdit *searchEdit;
|
||||
QSpinBox *depthSpin;
|
||||
|
|
@ -53,6 +69,23 @@ private:
|
|||
QPushButton *backButton;
|
||||
QLabel *statusLabel;
|
||||
QTableWidget *altsTable;
|
||||
|
||||
QCheckBox *emailCheck;
|
||||
QCheckBox *clientIdCheck;
|
||||
QCheckBox *ipCheck;
|
||||
QLabel *vpnLabel;
|
||||
QSpinBox *vpnSpin;
|
||||
|
||||
QLabel *detailNameValue;
|
||||
QLabel *detailCountsValue;
|
||||
QTextEdit *detailNotesEdit;
|
||||
QTableWidget *warnsTable;
|
||||
QTableWidget *bansTable;
|
||||
|
||||
QGroupBox *detailsGroup;
|
||||
QLabel *detailNameLabel;
|
||||
QLabel *detailCountsLabel;
|
||||
QLabel *detailNotesLabel;
|
||||
};
|
||||
|
||||
#endif // TAB_ALTS_H
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue