[Report] Add context menu to report queue rows (#7444)

* [Report] Add report reopen action for closed reports

- New Command_ReportReopen moderator command (extension 1206) with server
  handler that reopens resolved/dismissed reports, resets resolution state
  and notified, and records the reopening note in the audit log
- Reopen... button in the report queue action bar, gated to resolved and
  dismissed reports, with optional note prompt and tooltip
- Success confirmations for Assign, Resolve/Dismiss and Reopen now survive
  the list refresh via a pending status message instead of being clobbered
- Dispatch/auth coverage in server_moderator_role_test

* [Report] Surface resolution audit info in report details

- Add resolved_by_name to ServerInfo_Report and join resolved_by users in
  the report list, my-list and details queries
- Render resolution note and resolved-at/by in the report detail pane
  instead of duplicating the description into the chat log context

* [Report] Add context menu to report queue rows

- Open the standard UserContextMenu on right-click/Menu key for the
  reported user, selecting the row first so the details panel matches
  the menu target
- Carry the reported user's level in ServerInfo_Report so the menu
  shows the same actions as the user list, with an IsUser fallback for
  deleted or unregistered users
- Wire the Private chat action through TabReport to TabSupervisor and
  retranslate the menu on live language switches

* [Server] Fix reported_user_level column indices in report queries

The SELECT lists resolved_by_name at index 10, shifting room_id to 11,
replay_id to 12, reported_user_id to 13 and reported_user_admin to 14.
The code read 12/13, so 'user exists' meant 'a replay exists' and the
admin flags came from the user id (e.g. id 5 -> admin+judge; users
without a replay reported as unregistered, hiding registered-only
actions). Fixed in cmdReportList and cmdReportMyList, plus the column
comment.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-10-11 15:33:47 +02:00 • committed by GitHub
parent 74a6acde91
commit 38acddeede
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 80 additions and 5 deletions

View file

@ -1986,6 +1986,29 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com
return Response::RespOk;
}
namespace
{
int reportUserLevel(bool userExists, int adminFlags)
{
int userLevel = ServerInfo_User::IsUser;
if (userExists) {
userLevel |= ServerInfo_User::IsRegistered;
if (adminFlags & 1) {
userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator;
} else if (adminFlags & 2) {
userLevel |= ServerInfo_User::IsModerator;
}
if (adminFlags & 4) {
userLevel |= ServerInfo_User::IsJudge;
}
if (adminFlags & 8) {
userLevel |= ServerInfo_User::IsDeveloper;
}
}
return userLevel;
}
} // namespace
Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Command_ReportList &cmd,
ResponseContainer &rc)
{
@ -1999,8 +2022,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman
// Columns: 0=id, 1=reporter_name, 2=reported_user_name, 3=game_id,
// 4=category, 5=description, 6=created_at, 7=status,
// 8=resolution_note, 9=assigned_mod_name,
// 10=room_id, 11=replay_id
// 8=resolution_note, 9=assigned_mod_name, 10=resolved_by_name,
// 11=room_id, 12=replay_id,
// 13=reported_user_id, 14=reported_user_admin
QString whereClause;
if (unresolvedOnly) {
whereClause = "WHERE r.status = 'open' OR r.status = 'assigned' ";
@ -2017,10 +2041,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman
"r.category, r.description, r.created_at, r.status, "
"r.resolution_note, u.name AS assigned_mod_name, ru.name AS resolved_by_name, r.room_id, "
"(SELECT id FROM {prefix}_replays WHERE id_game = r.game_id ORDER BY id DESC LIMIT 1) "
"AS replay_id "
"AS replay_id, u2.id AS reported_user_id, u2.admin AS reported_user_admin "
"FROM {prefix}_reports r "
"LEFT JOIN {prefix}_users u ON r.assigned_to = u.id "
"LEFT JOIN {prefix}_users ru ON r.resolved_by = ru.id ";
"LEFT JOIN {prefix}_users ru ON r.resolved_by = ru.id "
"LEFT JOIN {prefix}_users u2 ON u2.name = r.reported_user_name ";
if (unresolvedOnly) {
queryStr += "WHERE r.status = 'open' OR r.status = 'assigned' ";
@ -2072,6 +2097,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman
if (!query->value(12).isNull()) {
info->set_replay_id(query->value(12).toInt());
}
info->set_reported_user_level(reportUserLevel(!query->value(13).isNull(), query->value(14).toInt()));
}
rc.setResponseExtension(re);
@ -2746,10 +2773,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm
"r.category, r.description, r.created_at, r.status, "
"r.resolution_note, u.name AS assigned_mod_name, ru.name AS resolved_by_name, r.room_id, "
"(SELECT id FROM {prefix}_replays WHERE id_game = r.game_id ORDER BY id DESC LIMIT 1) "
"AS replay_id "
"AS replay_id, u2.id AS reported_user_id, u2.admin AS reported_user_admin "
"FROM {prefix}_reports r "
"LEFT JOIN {prefix}_users u ON r.assigned_to = u.id "
"LEFT JOIN {prefix}_users ru ON r.resolved_by = ru.id "
"LEFT JOIN {prefix}_users u2 ON u2.name = r.reported_user_name "
"WHERE r.reporter_id = :reporter_id "
"ORDER BY r.created_at DESC LIMIT 200";
@ -2796,6 +2824,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm
if (!query->value(12).isNull()) {
info->set_replay_id(query->value(12).toInt());
}
info->set_reported_user_level(reportUserLevel(!query->value(13).isNull(), query->value(14).toInt()));
}
rc.setResponseExtension(re);