mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Client] Moderation and alts dashboards
Turn the two staff tables into dashboards without behavior changes: TabModeration gets an identity header (display-size name, status, registered/last-login subline), Reports/Warnings/Bans/Alts count cards with an Open-graph deep link, full-width admin notes, and Alts/Sessions/Staff tabbed sections with tab memory. TabAlts gets toggle chips with match counts, bold usernames, palette-token dimming of shared-IP rows, and a resizable side card for the selected-user timeline with selection restore. Row activation works via Enter as well as double-click; async responses are guarded against stale investigations and retranslation re-applies cached dashboard data.
This commit is contained in:
parent
e36b59c2f5
commit
459c61933b
4 changed files with 286 additions and 126 deletions
|
|
@ -5,17 +5,19 @@
|
|||
#include "libcockatrice/protocol/pb/response_user_alts.pb.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCheckBox>
|
||||
#include <QDateTime>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QList>
|
||||
#include <QPushButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSpinBox>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QTextEdit>
|
||||
|
|
@ -46,6 +48,17 @@ constexpr int COL_ALTS_BANS = 9;
|
|||
constexpr int COL_ALTS_ACTIVE = 10;
|
||||
constexpr int COL_ALTS_COUNT = 11;
|
||||
|
||||
constexpr int COL_WARNS_TIME = 0;
|
||||
constexpr int COL_WARNS_MODERATOR = 1;
|
||||
constexpr int COL_WARNS_REASON = 2;
|
||||
constexpr int COL_WARNS_COUNT = 3;
|
||||
|
||||
constexpr int COL_BANS_TIME = 0;
|
||||
constexpr int COL_BANS_MODERATOR = 1;
|
||||
constexpr int COL_BANS_LENGTH = 2;
|
||||
constexpr int COL_BANS_REASON = 3;
|
||||
constexpr int COL_BANS_COUNT = 4;
|
||||
|
||||
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
|
||||
|
|
@ -94,15 +107,16 @@ 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);
|
||||
auto makeChip = [this](const QString &text) {
|
||||
auto *chip = new QPushButton(text);
|
||||
chip->setCheckable(true);
|
||||
chip->setChecked(true);
|
||||
connect(chip, &QPushButton::toggled, this, &TabAlts::onFilterChanged);
|
||||
return chip;
|
||||
};
|
||||
emailChip = makeChip(QString());
|
||||
clientIdChip = makeChip(QString());
|
||||
ipChip = makeChip(QString());
|
||||
vpnLabel = new QLabel;
|
||||
vpnSpin = new QSpinBox;
|
||||
vpnSpin->setMinimum(1);
|
||||
|
|
@ -111,17 +125,20 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
|||
connect(vpnSpin, &QSpinBox::valueChanged, this, &TabAlts::onFilterChanged);
|
||||
|
||||
auto *filterBar = new QHBoxLayout;
|
||||
filterBar->addWidget(emailCheck);
|
||||
filterBar->addWidget(clientIdCheck);
|
||||
filterBar->addWidget(ipCheck);
|
||||
filterBar->addWidget(emailChip);
|
||||
filterBar->addWidget(clientIdChip);
|
||||
filterBar->addWidget(ipChip);
|
||||
filterBar->addWidget(vpnLabel);
|
||||
filterBar->addWidget(vpnSpin);
|
||||
filterBar->addStretch();
|
||||
|
||||
altsTable = new QTableWidget(0, COL_ALTS_COUNT);
|
||||
configureAltTable(altsTable);
|
||||
connect(altsTable, &QTableWidget::cellDoubleClicked, this, [this](int row, int) {
|
||||
auto *userItem = altsTable->item(row, COL_ALTS_USER);
|
||||
connect(altsTable, &QTableWidget::activated, this, [this](const QModelIndex &index) {
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
auto *userItem = altsTable->item(index.row(), COL_ALTS_USER);
|
||||
if (userItem != nullptr && !userItem->text().isEmpty() && userItem->text() != currentUser) {
|
||||
investigate(userItem->text());
|
||||
}
|
||||
|
|
@ -134,9 +151,9 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
|||
detailNotesEdit->setReadOnly(true);
|
||||
// Compact cap so the warn/ban tables stay visible without scrolling the tab.
|
||||
detailNotesEdit->setMaximumHeight(60);
|
||||
warnsTable = new QTableWidget(0, 3);
|
||||
warnsTable = new QTableWidget(0, COL_WARNS_COUNT);
|
||||
configureAltTable(warnsTable);
|
||||
bansTable = new QTableWidget(0, 4);
|
||||
bansTable = new QTableWidget(0, COL_BANS_COUNT);
|
||||
configureAltTable(bansTable);
|
||||
|
||||
auto *detailGrid = new QGridLayout;
|
||||
|
|
@ -150,21 +167,24 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
|||
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);
|
||||
detailsLayout->addWidget(warnsTable, 1);
|
||||
detailsLayout->addWidget(bansTable, 1);
|
||||
|
||||
auto *contentSplitter = new QSplitter(Qt::Horizontal);
|
||||
contentSplitter->addWidget(altsTable);
|
||||
contentSplitter->addWidget(detailsGroup);
|
||||
contentSplitter->setStretchFactor(0, 3);
|
||||
contentSplitter->setStretchFactor(1, 2);
|
||||
detailsGroup->setMinimumWidth(260);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(centralWidget);
|
||||
mainLayout->addLayout(topBar);
|
||||
mainLayout->addLayout(filterBar);
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addWidget(altsTable, 3);
|
||||
mainLayout->addWidget(detailsGroup, 2);
|
||||
mainLayout->addWidget(contentSplitter, 1);
|
||||
|
||||
retranslateUi();
|
||||
clearDetails();
|
||||
|
|
@ -183,15 +203,19 @@ 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"));
|
||||
emailChip->setText(tr("Email"));
|
||||
emailChip->setToolTip(tr("Show or hide email matches"));
|
||||
clientIdChip->setText(tr("Client ID"));
|
||||
clientIdChip->setToolTip(tr("Show or hide client ID matches"));
|
||||
ipChip->setText(tr("IP"));
|
||||
ipChip->setToolTip(tr("Show or hide IP address matches"));
|
||||
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")});
|
||||
altsTable->setToolTip(tr("Double-click or press Enter on a row to investigate that account"));
|
||||
warnsTable->setHorizontalHeaderLabels({tr("Warning time"), tr("Moderator"), tr("Reason")});
|
||||
bansTable->setHorizontalHeaderLabels({tr("Ban time"), tr("Moderator"), tr("Length"), tr("Reason")});
|
||||
|
||||
|
|
@ -199,6 +223,10 @@ void TabAlts::retranslateUi()
|
|||
detailNameLabel->setText(tr("Name:"));
|
||||
detailCountsLabel->setText(tr("Counts:"));
|
||||
detailNotesLabel->setText(tr("Admin notes:"));
|
||||
|
||||
if (!lastAlts.isEmpty()) {
|
||||
renderTable();
|
||||
}
|
||||
}
|
||||
|
||||
QString TabAlts::formatEpoch(quint64 ts) const
|
||||
|
|
@ -259,7 +287,11 @@ void TabAlts::requestAlts(const QString &userName, int depth)
|
|||
cmd.set_user_name(userName.toStdString());
|
||||
cmd.set_depth(static_cast<google::protobuf::uint32>(depth));
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &TabAlts::altsResponse);
|
||||
connect(pend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
||||
if (userName == currentUser) {
|
||||
altsResponse(response);
|
||||
}
|
||||
});
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
|
|
@ -290,13 +322,13 @@ bool TabAlts::rowVisible(const ServerInfo_UserAlt &alt) const
|
|||
}
|
||||
const QString reason = QString::fromStdString(alt.match_reason());
|
||||
if (reason == QStringLiteral("email")) {
|
||||
return emailCheck->isChecked();
|
||||
return emailChip->isChecked();
|
||||
}
|
||||
if (reason.startsWith(QStringLiteral("clientid:"))) {
|
||||
return clientIdCheck->isChecked();
|
||||
return clientIdChip->isChecked();
|
||||
}
|
||||
if (reason.startsWith(QStringLiteral("ip:"))) {
|
||||
return ipCheck->isChecked() &&
|
||||
return ipChip->isChecked() &&
|
||||
(!alt.has_shared_count() || alt.shared_count() <= static_cast<unsigned>(vpnSpin->value()));
|
||||
}
|
||||
return true;
|
||||
|
|
@ -304,7 +336,32 @@ bool TabAlts::rowVisible(const ServerInfo_UserAlt &alt) const
|
|||
|
||||
void TabAlts::renderTable()
|
||||
{
|
||||
// Blocked so the programmatic refill neither wipes the details panel
|
||||
// (via the selection-cleared path) nor refetches them on restore.
|
||||
const QSignalBlocker blocker(altsTable);
|
||||
altsTable->setRowCount(0);
|
||||
|
||||
int emailCount = 0;
|
||||
int clientIdCount = 0;
|
||||
int ipCount = 0;
|
||||
for (const ServerInfo_UserAlt &alt : lastAlts) {
|
||||
const QString reason = QString::fromStdString(alt.match_reason());
|
||||
if (reason == QStringLiteral("email")) {
|
||||
++emailCount;
|
||||
} else if (reason.startsWith(QStringLiteral("clientid:"))) {
|
||||
++clientIdCount;
|
||||
} else if (reason.startsWith(QStringLiteral("ip:"))) {
|
||||
++ipCount;
|
||||
}
|
||||
}
|
||||
emailChip->setText(tr("Email (%1)").arg(emailCount));
|
||||
clientIdChip->setText(tr("Client ID (%1)").arg(clientIdCount));
|
||||
ipChip->setText(tr("IP (%1)").arg(ipCount));
|
||||
|
||||
QFont userFont = altsTable->font();
|
||||
userFont.setBold(true);
|
||||
const QColor dimColor = palette().color(QPalette::Disabled, QPalette::Text);
|
||||
|
||||
int hidden = 0;
|
||||
int row = 0;
|
||||
for (const ServerInfo_UserAlt &alt : lastAlts) {
|
||||
|
|
@ -313,7 +370,9 @@ void TabAlts::renderTable()
|
|||
continue;
|
||||
}
|
||||
altsTable->insertRow(row);
|
||||
altsTable->setItem(row, COL_ALTS_USER, new QTableWidgetItem(QString::fromStdString(alt.user_name())));
|
||||
auto *userItem = new QTableWidgetItem(QString::fromStdString(alt.user_name()));
|
||||
userItem->setFont(userFont);
|
||||
altsTable->setItem(row, COL_ALTS_USER, userItem);
|
||||
altsTable->setItem(row, COL_ALTS_VIA,
|
||||
new QTableWidgetItem(alt.has_match_reason() && !alt.match_reason().empty()
|
||||
? QString::fromStdString(alt.match_reason())
|
||||
|
|
@ -330,6 +389,14 @@ void TabAlts::renderTable()
|
|||
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")));
|
||||
if (QString::fromStdString(alt.match_reason()).startsWith(QStringLiteral("ip:")) && alt.has_shared_count() &&
|
||||
alt.shared_count() > 1) {
|
||||
for (int col = 0; col < COL_ALTS_COUNT; ++col) {
|
||||
if (auto *item = altsTable->item(row, col)) {
|
||||
item->setForeground(dimColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
++row;
|
||||
}
|
||||
if (hidden == 0) {
|
||||
|
|
@ -341,6 +408,22 @@ void TabAlts::renderTable()
|
|||
.arg(lastAlts.size())
|
||||
.arg(currentUser));
|
||||
}
|
||||
|
||||
if (!selectedUser.isEmpty()) {
|
||||
bool restored = false;
|
||||
for (int restoreRow = 0; restoreRow < altsTable->rowCount(); ++restoreRow) {
|
||||
auto *userItem = altsTable->item(restoreRow, COL_ALTS_USER);
|
||||
if (userItem != nullptr && userItem->text() == selectedUser) {
|
||||
altsTable->setCurrentItem(userItem, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
||||
restored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!restored) {
|
||||
selectedUser.clear();
|
||||
clearDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabAlts::onFilterChanged()
|
||||
|
|
@ -409,7 +492,7 @@ void TabAlts::requestDetails(const QString &userName)
|
|||
void TabAlts::clearDetails()
|
||||
{
|
||||
detailNameValue->clear();
|
||||
detailCountsValue->clear();
|
||||
detailCountsValue->setText(tr("Select a row to inspect its history."));
|
||||
detailNotesEdit->clear();
|
||||
warnsTable->setRowCount(0);
|
||||
bansTable->setRowCount(0);
|
||||
|
|
@ -446,9 +529,9 @@ void TabAlts::warnHistoryResponse(const Response &response)
|
|||
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())));
|
||||
warnsTable->setItem(i, COL_WARNS_TIME, new QTableWidgetItem(QString::fromStdString(warn.time_of())));
|
||||
warnsTable->setItem(i, COL_WARNS_MODERATOR, new QTableWidgetItem(QString::fromStdString(warn.admin_name())));
|
||||
warnsTable->setItem(i, COL_WARNS_REASON, new QTableWidgetItem(QString::fromStdString(warn.reason())));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -463,9 +546,9 @@ void TabAlts::banHistoryResponse(const Response &response)
|
|||
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())));
|
||||
bansTable->setItem(i, COL_BANS_TIME, new QTableWidgetItem(QString::fromStdString(ban.ban_time())));
|
||||
bansTable->setItem(i, COL_BANS_MODERATOR, new QTableWidgetItem(QString::fromStdString(ban.admin_name())));
|
||||
bansTable->setItem(i, COL_BANS_LENGTH, new QTableWidgetItem(QString::fromStdString(ban.ban_length())));
|
||||
bansTable->setItem(i, COL_BANS_REASON, new QTableWidgetItem(QString::fromStdString(ban.ban_reason())));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
#include <qtmetamacros.h>
|
||||
|
||||
class AbstractClient;
|
||||
class QCheckBox;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
|
|
@ -70,9 +70,9 @@ private:
|
|||
QLabel *statusLabel;
|
||||
QTableWidget *altsTable;
|
||||
|
||||
QCheckBox *emailCheck;
|
||||
QCheckBox *clientIdCheck;
|
||||
QCheckBox *ipCheck;
|
||||
QPushButton *emailChip;
|
||||
QPushButton *clientIdChip;
|
||||
QPushButton *ipChip;
|
||||
QLabel *vpnLabel;
|
||||
QSpinBox *vpnSpin;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <QSharedPointer>
|
||||
#include <QSplitter>
|
||||
#include <QStringList>
|
||||
#include <QTabWidget>
|
||||
#include <QTableWidget>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QTextEdit>
|
||||
|
|
@ -94,38 +95,67 @@ TabModeration::TabModeration(TabSupervisor *_tabSupervisor, AbstractClient *_cli
|
|||
topBar->addWidget(removeAvatarButton);
|
||||
|
||||
userInfoGroup = new QGroupBox;
|
||||
userInfoNameLabel = new QLabel;
|
||||
userInfoNameValue = new QLabel;
|
||||
userInfoRegisteredLabel = new QLabel;
|
||||
userInfoRegisteredValue = new QLabel;
|
||||
userInfoLastLoginLabel = new QLabel;
|
||||
userInfoLastLoginValue = new QLabel;
|
||||
userInfoStatusLabel = new QLabel;
|
||||
QFont displayFont = userInfoNameValue->font();
|
||||
displayFont.setPointSize(displayFont.pointSize() + 6);
|
||||
displayFont.setBold(true);
|
||||
userInfoNameValue->setFont(displayFont);
|
||||
userInfoStatusValue = new QLabel;
|
||||
userInfoCountsLabel = new QLabel;
|
||||
userInfoCountsValue = new QLabel;
|
||||
QFont statusFont = userInfoStatusValue->font();
|
||||
statusFont.setBold(true);
|
||||
userInfoStatusValue->setFont(statusFont);
|
||||
sublineLabel = new QLabel;
|
||||
|
||||
auto makeCountCard = [](const QString &title, QLabel *&value) {
|
||||
auto *card = new QGroupBox(title);
|
||||
value = new QLabel(QStringLiteral("–"), card);
|
||||
QFont cardFont = value->font();
|
||||
cardFont.setPointSize(cardFont.pointSize() + 6);
|
||||
cardFont.setBold(true);
|
||||
value->setFont(cardFont);
|
||||
value->setAlignment(Qt::AlignCenter);
|
||||
auto *cardLayout = new QVBoxLayout(card);
|
||||
cardLayout->addWidget(value);
|
||||
return card;
|
||||
};
|
||||
cardReports = makeCountCard(QString(), cardReportsValue);
|
||||
cardWarns = makeCountCard(QString(), cardWarnsValue);
|
||||
cardBans = makeCountCard(QString(), cardBansValue);
|
||||
|
||||
altsCard = new QGroupBox;
|
||||
altsCountValue = new QLabel(QStringLiteral("–"), altsCard);
|
||||
QFont altsFont = altsCountValue->font();
|
||||
altsFont.setPointSize(altsFont.pointSize() + 6);
|
||||
altsFont.setBold(true);
|
||||
altsCountValue->setFont(altsFont);
|
||||
altsCountValue->setAlignment(Qt::AlignCenter);
|
||||
openAltsButton = new QPushButton;
|
||||
connect(openAltsButton, &QPushButton::clicked, this, &TabModeration::openAltsGraph);
|
||||
auto *altsCardLayout = new QVBoxLayout(altsCard);
|
||||
altsCardLayout->addWidget(altsCountValue);
|
||||
altsCardLayout->addWidget(openAltsButton);
|
||||
|
||||
auto *cardsRow = new QHBoxLayout;
|
||||
cardsRow->addWidget(cardReports);
|
||||
cardsRow->addWidget(cardWarns);
|
||||
cardsRow->addWidget(cardBans);
|
||||
cardsRow->addWidget(altsCard);
|
||||
|
||||
userInfoNotesLabel = new QLabel;
|
||||
userInfoNotesEdit = new QTextEdit;
|
||||
userInfoNotesEdit->setReadOnly(true);
|
||||
|
||||
auto *infoGrid = new QGridLayout;
|
||||
infoGrid->addWidget(userInfoNameLabel, 0, 0);
|
||||
infoGrid->addWidget(userInfoNameValue, 0, 1);
|
||||
infoGrid->addWidget(userInfoRegisteredLabel, 0, 2);
|
||||
infoGrid->addWidget(userInfoRegisteredValue, 0, 3);
|
||||
infoGrid->addWidget(userInfoLastLoginLabel, 1, 0);
|
||||
infoGrid->addWidget(userInfoLastLoginValue, 1, 1);
|
||||
infoGrid->addWidget(userInfoStatusLabel, 1, 2);
|
||||
infoGrid->addWidget(userInfoStatusValue, 1, 3);
|
||||
infoGrid->addWidget(userInfoCountsLabel, 2, 0);
|
||||
infoGrid->addWidget(userInfoCountsValue, 2, 1, 1, 3);
|
||||
infoGrid->addWidget(userInfoNotesLabel, 3, 0, Qt::AlignTop);
|
||||
infoGrid->addWidget(userInfoNotesEdit, 3, 1, 1, 3);
|
||||
infoGrid->setColumnStretch(1, 1);
|
||||
infoGrid->setColumnStretch(3, 1);
|
||||
auto *identityRow = new QHBoxLayout;
|
||||
identityRow->addWidget(userInfoNameValue);
|
||||
identityRow->addStretch();
|
||||
identityRow->addWidget(userInfoStatusValue);
|
||||
|
||||
auto *infoLayout = new QVBoxLayout(userInfoGroup);
|
||||
infoLayout->addLayout(infoGrid);
|
||||
infoLayout->addLayout(identityRow);
|
||||
infoLayout->addWidget(sublineLabel);
|
||||
infoLayout->addLayout(cardsRow);
|
||||
infoLayout->addWidget(userInfoNotesLabel);
|
||||
infoLayout->addWidget(userInfoNotesEdit);
|
||||
|
||||
auto configureTable = [](QTableWidget *table) {
|
||||
table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
|
|
@ -137,31 +167,27 @@ TabModeration::TabModeration(TabSupervisor *_tabSupervisor, AbstractClient *_cli
|
|||
table->horizontalHeader()->setStretchLastSection(true);
|
||||
};
|
||||
|
||||
altsGroup = new QGroupBox;
|
||||
altsTable = new QTableWidget(0, COL_ALTS_COUNT);
|
||||
configureTable(altsTable);
|
||||
connect(altsTable, &QTableWidget::cellDoubleClicked, this, [this](int row, int) {
|
||||
auto *userItem = altsTable->item(row, COL_ALTS_USER);
|
||||
connect(altsTable, &QTableWidget::activated, this, [this](const QModelIndex &index) {
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
auto *userItem = altsTable->item(index.row(), COL_ALTS_USER);
|
||||
if (userItem != nullptr && !userItem->text().isEmpty()) {
|
||||
getTabSupervisor()->openTabAlts(userItem->text());
|
||||
}
|
||||
});
|
||||
openAltsButton = new QPushButton;
|
||||
connect(openAltsButton, &QPushButton::clicked, this, &TabModeration::openAltsGraph);
|
||||
auto *altsHeader = new QHBoxLayout;
|
||||
altsHeader->addStretch();
|
||||
altsHeader->addWidget(openAltsButton);
|
||||
auto *altsLayout = new QVBoxLayout(altsGroup);
|
||||
altsLayout->addLayout(altsHeader);
|
||||
auto *altsPage = new QWidget;
|
||||
auto *altsLayout = new QVBoxLayout(altsPage);
|
||||
altsLayout->addWidget(altsTable);
|
||||
|
||||
sessionsGroup = new QGroupBox;
|
||||
sessionsTable = new QTableWidget(0, COL_SESSIONS_COUNT);
|
||||
configureTable(sessionsTable);
|
||||
auto *sessionsLayout = new QVBoxLayout(sessionsGroup);
|
||||
auto *sessionsPage = new QWidget;
|
||||
auto *sessionsLayout = new QVBoxLayout(sessionsPage);
|
||||
sessionsLayout->addWidget(sessionsTable);
|
||||
|
||||
staffGroup = new QGroupBox;
|
||||
staffTable = new QTableWidget(0, COL_STAFF_COUNT);
|
||||
configureTable(staffTable);
|
||||
refreshStaffButton = new QPushButton;
|
||||
|
|
@ -169,19 +195,21 @@ TabModeration::TabModeration(TabSupervisor *_tabSupervisor, AbstractClient *_cli
|
|||
auto *staffHeader = new QHBoxLayout;
|
||||
staffHeader->addStretch();
|
||||
staffHeader->addWidget(refreshStaffButton);
|
||||
auto *staffLayout = new QVBoxLayout(staffGroup);
|
||||
auto *staffPage = new QWidget;
|
||||
auto *staffLayout = new QVBoxLayout(staffPage);
|
||||
staffLayout->addWidget(staffTable);
|
||||
staffLayout->addLayout(staffHeader);
|
||||
|
||||
sectionTabs = new QTabWidget;
|
||||
sectionTabs->addTab(altsPage, QString());
|
||||
sectionTabs->addTab(sessionsPage, QString());
|
||||
sectionTabs->addTab(staffPage, QString());
|
||||
|
||||
auto *splitter = new QSplitter(Qt::Vertical);
|
||||
splitter->addWidget(userInfoGroup);
|
||||
splitter->addWidget(altsGroup);
|
||||
splitter->addWidget(sessionsGroup);
|
||||
splitter->addWidget(staffGroup);
|
||||
splitter->addWidget(sectionTabs);
|
||||
splitter->setStretchFactor(0, 1);
|
||||
splitter->setStretchFactor(1, 2);
|
||||
splitter->setStretchFactor(2, 2);
|
||||
splitter->setStretchFactor(3, 1);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(centralWidget);
|
||||
mainLayout->addLayout(topBar);
|
||||
|
|
@ -202,21 +230,28 @@ void TabModeration::retranslateUi()
|
|||
refreshStaffButton->setText(tr("Refresh"));
|
||||
|
||||
userInfoGroup->setTitle(tr("User Info"));
|
||||
userInfoNameLabel->setText(tr("Name:"));
|
||||
userInfoRegisteredLabel->setText(tr("Registered:"));
|
||||
userInfoLastLoginLabel->setText(tr("Last login:"));
|
||||
userInfoStatusLabel->setText(tr("Status:"));
|
||||
userInfoCountsLabel->setText(tr("Counts:"));
|
||||
userInfoNotesLabel->setText(tr("Admin notes:"));
|
||||
|
||||
altsGroup->setTitle(tr("Alts"));
|
||||
openAltsButton->setText(tr("Open alt graph"));
|
||||
cardReports->setTitle(tr("Reports"));
|
||||
cardWarns->setTitle(tr("Warnings"));
|
||||
cardBans->setTitle(tr("Bans"));
|
||||
altsCard->setTitle(tr("Alts"));
|
||||
openAltsButton->setText(tr("Open graph"));
|
||||
openAltsButton->setToolTip(tr("Open this user in the User Alts tab"));
|
||||
sessionsGroup->setTitle(tr("Sessions"));
|
||||
staffGroup->setTitle(tr("Staff Last Logins"));
|
||||
|
||||
sectionTabs->setTabText(0, tr("Alts"));
|
||||
sectionTabs->setTabText(1, tr("Sessions"));
|
||||
sectionTabs->setTabText(2, tr("Staff Last Logins"));
|
||||
|
||||
if (cachedUserInfoValid) {
|
||||
applyUserInfo();
|
||||
}
|
||||
if (cachedAltsCount >= 0) {
|
||||
applyAltsCount();
|
||||
}
|
||||
|
||||
altsTable->setHorizontalHeaderLabels({tr("User"), tr("eMail"), tr("Client ID"), tr("Registered"), tr("Last login"),
|
||||
tr("Warns"), tr("Bans"), tr("Active")});
|
||||
altsTable->setToolTip(tr("Double-click or press Enter on a row to open it in the User Alts tab"));
|
||||
sessionsTable->setHorizontalHeaderLabels({tr("IP"), tr("Client ID"), tr("Start"), tr("End"), tr("Type")});
|
||||
staffTable->setHorizontalHeaderLabels({tr("User"), tr("Level"), tr("Last login")});
|
||||
}
|
||||
|
|
@ -232,11 +267,15 @@ QString TabModeration::formatEpoch(quint64 ts) const
|
|||
void TabModeration::clearUserData()
|
||||
{
|
||||
currentUser.clear();
|
||||
cachedUserInfoValid = false;
|
||||
cachedAltsCount = -1;
|
||||
userInfoNameValue->clear();
|
||||
userInfoRegisteredValue->clear();
|
||||
userInfoLastLoginValue->clear();
|
||||
userInfoStatusValue->clear();
|
||||
userInfoCountsValue->clear();
|
||||
sublineLabel->clear();
|
||||
cardReportsValue->setText(QStringLiteral("–"));
|
||||
cardWarnsValue->setText(QStringLiteral("–"));
|
||||
cardBansValue->setText(QStringLiteral("–"));
|
||||
altsCountValue->setText(QStringLiteral("–"));
|
||||
userInfoNotesEdit->clear();
|
||||
altsTable->setRowCount(0);
|
||||
sessionsTable->setRowCount(0);
|
||||
|
|
@ -261,9 +300,12 @@ void TabModeration::investigateUser()
|
|||
return;
|
||||
}
|
||||
currentUser = userName;
|
||||
cachedUserInfoValid = false;
|
||||
cachedAltsCount = -1;
|
||||
resetPasswordButton->setEnabled(true);
|
||||
removeAvatarButton->setEnabled(true);
|
||||
openAltsButton->setEnabled(true);
|
||||
altsCountValue->setText(QStringLiteral("…"));
|
||||
altsTable->setRowCount(0);
|
||||
sessionsTable->setRowCount(0);
|
||||
requestUserInfo(userName);
|
||||
|
|
@ -282,10 +324,11 @@ void TabModeration::openAltsGraph()
|
|||
void TabModeration::requestUserInfo(const QString &userName)
|
||||
{
|
||||
userInfoNameValue->setText(userName);
|
||||
userInfoRegisteredValue->setText(tr("Loading..."));
|
||||
userInfoLastLoginValue->setText(tr("Loading..."));
|
||||
sublineLabel->setText(tr("Loading..."));
|
||||
userInfoStatusValue->clear();
|
||||
userInfoCountsValue->clear();
|
||||
cardReportsValue->setText(QStringLiteral("…"));
|
||||
cardWarnsValue->setText(QStringLiteral("…"));
|
||||
cardBansValue->setText(QStringLiteral("…"));
|
||||
userInfoNotesEdit->clear();
|
||||
|
||||
Command_ReportUserInfo cmd;
|
||||
|
|
@ -298,9 +341,12 @@ void TabModeration::requestUserInfo(const QString &userName)
|
|||
void TabModeration::userInfoResponse(const Response &response)
|
||||
{
|
||||
if (response.response_code() != Response::RespOk) {
|
||||
userInfoRegisteredValue->clear();
|
||||
userInfoLastLoginValue->clear();
|
||||
cachedUserInfoValid = false;
|
||||
sublineLabel->clear();
|
||||
userInfoStatusValue->setText(tr("Error loading user info."));
|
||||
cardReportsValue->setText(QStringLiteral("–"));
|
||||
cardWarnsValue->setText(QStringLiteral("–"));
|
||||
cardBansValue->setText(QStringLiteral("–"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -309,20 +355,28 @@ void TabModeration::userInfoResponse(const Response &response)
|
|||
return;
|
||||
}
|
||||
|
||||
userInfoRegisteredValue->setText(formatEpoch(resp.registration_time()));
|
||||
userInfoLastLoginValue->setText(formatEpoch(resp.last_login()));
|
||||
cachedUserInfo.CopyFrom(resp);
|
||||
cachedUserInfoValid = true;
|
||||
applyUserInfo();
|
||||
}
|
||||
|
||||
void TabModeration::applyUserInfo()
|
||||
{
|
||||
const Response_ReportUserInfo &resp = cachedUserInfo;
|
||||
sublineLabel->setText(tr("Registered %1 · Last login %2")
|
||||
.arg(formatEpoch(resp.registration_time()))
|
||||
.arg(formatEpoch(resp.last_login())));
|
||||
|
||||
QStringList statusParts;
|
||||
statusParts << (resp.is_active() ? tr("active") : tr("inactive"));
|
||||
if (resp.has_is_admin() && resp.is_admin()) {
|
||||
statusParts << tr("admin");
|
||||
}
|
||||
userInfoStatusValue->setText(statusParts.join(", "));
|
||||
userInfoStatusValue->setText(statusParts.join(QStringLiteral(", ")));
|
||||
|
||||
userInfoCountsValue->setText(tr("Reports: %1 Bans: %2 Warnings: %3")
|
||||
.arg(resp.total_reports())
|
||||
.arg(resp.total_bans())
|
||||
.arg(resp.total_warns()));
|
||||
cardReportsValue->setText(QString::number(resp.total_reports()));
|
||||
cardWarnsValue->setText(QString::number(resp.total_warns()));
|
||||
cardBansValue->setText(QString::number(resp.total_bans()));
|
||||
|
||||
if (resp.has_admin_notes() && !resp.admin_notes().empty()) {
|
||||
userInfoNotesEdit->setPlainText(QString::fromStdString(resp.admin_notes()));
|
||||
|
|
@ -331,12 +385,21 @@ void TabModeration::userInfoResponse(const Response &response)
|
|||
}
|
||||
}
|
||||
|
||||
void TabModeration::applyAltsCount()
|
||||
{
|
||||
altsCountValue->setText(tr("%1 related").arg(cachedAltsCount));
|
||||
}
|
||||
|
||||
void TabModeration::requestSessions(const QString &userName)
|
||||
{
|
||||
Command_GetUserSessions cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &TabModeration::sessionsResponse);
|
||||
connect(pend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
||||
if (userName == currentUser) {
|
||||
sessionsResponse(response);
|
||||
}
|
||||
});
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
|
|
@ -368,7 +431,11 @@ void TabModeration::requestAlts(const QString &userName)
|
|||
Command_GetUserAlts cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &TabModeration::altsResponse);
|
||||
connect(pend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
||||
if (userName == currentUser) {
|
||||
altsResponse(response);
|
||||
}
|
||||
});
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
|
|
@ -376,10 +443,14 @@ void TabModeration::altsResponse(const Response &response)
|
|||
{
|
||||
altsTable->setRowCount(0);
|
||||
if (response.response_code() != Response::RespOk) {
|
||||
cachedAltsCount = -1;
|
||||
altsCountValue->setText(QStringLiteral("–"));
|
||||
return;
|
||||
}
|
||||
|
||||
const Response_UserAlts &resp = response.GetExtension(Response_UserAlts::ext);
|
||||
cachedAltsCount = resp.alts_size();
|
||||
applyAltsCount();
|
||||
altsTable->setRowCount(resp.alts_size());
|
||||
for (int i = 0; i < resp.alts_size(); ++i) {
|
||||
const ServerInfo_UserAlt &alt = resp.alts(i);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
#include <libcockatrice/protocol/pb/response_report_user_info.pb.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class AbstractClient;
|
||||
|
|
@ -13,6 +14,7 @@ class QLabel;
|
|||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QTableWidget;
|
||||
class QTabWidget;
|
||||
class QTextEdit;
|
||||
class Response;
|
||||
class TabSupervisor;
|
||||
|
|
@ -52,10 +54,15 @@ private:
|
|||
void requestAlts(const QString &userName);
|
||||
void requestModeratorLogins();
|
||||
void clearUserData();
|
||||
void applyUserInfo();
|
||||
void applyAltsCount();
|
||||
[[nodiscard]] QString formatEpoch(quint64 ts) const;
|
||||
|
||||
AbstractClient *client;
|
||||
QString currentUser;
|
||||
Response_ReportUserInfo cachedUserInfo;
|
||||
bool cachedUserInfoValid = false;
|
||||
int cachedAltsCount = -1;
|
||||
|
||||
QLineEdit *searchEdit;
|
||||
QPushButton *investigateButton;
|
||||
|
|
@ -63,26 +70,25 @@ private:
|
|||
QPushButton *removeAvatarButton;
|
||||
|
||||
QGroupBox *userInfoGroup;
|
||||
QLabel *userInfoNameLabel;
|
||||
QLabel *userInfoNameValue;
|
||||
QLabel *userInfoRegisteredLabel;
|
||||
QLabel *userInfoRegisteredValue;
|
||||
QLabel *userInfoLastLoginLabel;
|
||||
QLabel *userInfoLastLoginValue;
|
||||
QLabel *userInfoStatusLabel;
|
||||
QLabel *userInfoStatusValue;
|
||||
QLabel *userInfoCountsLabel;
|
||||
QLabel *userInfoCountsValue;
|
||||
QLabel *sublineLabel;
|
||||
QGroupBox *cardReports;
|
||||
QGroupBox *cardWarns;
|
||||
QGroupBox *cardBans;
|
||||
QGroupBox *altsCard;
|
||||
QLabel *cardReportsValue;
|
||||
QLabel *cardWarnsValue;
|
||||
QLabel *cardBansValue;
|
||||
QLabel *altsCountValue;
|
||||
QLabel *userInfoNotesLabel;
|
||||
QTextEdit *userInfoNotesEdit;
|
||||
|
||||
QGroupBox *altsGroup;
|
||||
QTabWidget *sectionTabs;
|
||||
QTableWidget *altsTable;
|
||||
QPushButton *openAltsButton;
|
||||
QGroupBox *sessionsGroup;
|
||||
QTableWidget *sessionsTable;
|
||||
QGroupBox *staffGroup;
|
||||
QTableWidget *staffTable;
|
||||
QPushButton *openAltsButton;
|
||||
QPushButton *refreshStaffButton;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue