Cockatrice/cockatrice/src/interface/widgets/tabs/tab_supervisor.h
BruebachL d5d99e4dfb
[Server/Client/Protocol] Add developer staff role (#7211)
* [Server/Client/Protocol] Add developer staff role

Introduce a Developer staff level (proto flag 32, DB admin bit 8) that
sits between admin and moderator: no kick/ban/warn/report/admin powers,
but gets server log access via a new developer command container family
(GET_SERVER_STATS, VIEWLOG_HISTORY) and an idle-timeout exemption.

- Protocol: IsDeveloper flag, developer_commands.proto envelope,
  Command_GetServerStats/Command_GetLogHistory, Response_GetServerStats,
  Command_AdjustMod.should_be_developer
- Servatrice: fail-closed developer dispatcher, uptime snapshot handler,
  shared log history handler reuse, bit-8 DB mapping
- Client: burgundy pawn/badge/labels/sort order, prepareDeveloperCommand,
  minimal Developer stats tab, log tab access, promote/demote actions

Took 24 minutes

Took 18 seconds

* [Server/Client/Protocol] Address developer role review feedback

Address ZeizaZach's review of the developer staff role:

- Nudge the developer log query to exclude private chat and sender IPs
  (the ModeratorCommand path still sees everything).
- Deduplicate Command_GetLogHistory into Command_ViewLogHistory, which now
  extends both ModeratorCommand (ext) and DeveloperCommand (dev_ext); the
  client picks the DeveloperCommand-scoped extension by extendee, and the
  server reads it via the extension number.
- Pull the uptime snapshot SQL into Servatrice_DatabaseInterface as
  getLatestUptimeSnapshot() and widen the reported counters to 64-bit.
- Document the admin bitfield (1 admin, 2 moderator, 4 judge, 8 developer)
  and add a server-side test for the developer command path.

* Add missing trailing newline to user_context_menu.cpp

* Remove stale includes of deleted command_get_log_history proto

The Command_GetLogHistory message was folded into Command_ViewLogHistory,
which deleted command_get_log_history.proto, but serversocketinterface
still #included its generated header. Fresh CI builds fail on the missing
file; local builds masked it by reusing a previously generated header.

* [Server] Exclude chat rows when private-chat filter is bypassable

A developer who omits log_location entirely — or sends only "chat" —
leaves chatType, gameType, roomType all false, so getMessageLogHistory
skips the target_type clause and returns every row, private messages
included. When !allowPrivateChat the server now forces game+room when
no surviving location was requested, guaranteeing the query always
carries a target_type restriction.

[Client] Demote mod+dev to moderator path in log-tab dispatch

The developer command family is strictly weaker than the moderator one
(no private chat, no sender_ip, ip filter ignored), so granting the
developer bit to an existing moderator must not silently strip their
capabilities. useDeveloperCommands is now true only when the user holds
the developer bit and not the moderator bit.

[Client] Hide the IP-address filter for developer log tab users

The developer path ignores the ip_address query field server-side.
Showing the field lets a developer type an IP and get results that are
silently unfiltered by it rather than an empty result set — reads as a
broken filter. Hide labelFindIPAddress/findIPAddress alongside the
privateChat checkbox.

* Developer pawn is silver.

* [Client] Fix indentation of merged Card Art Rules / Developer tabs

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:18:56 +02:00

247 lines
7.6 KiB
C++

/**
* @file tab_supervisor.h
* @ingroup Core
* @ingroup Tabs
*/
//! \todo Document this file.
#ifndef TAB_SUPERVISOR_H
#define TAB_SUPERVISOR_H
#include "../../deck_loader/deck_loader.h"
#include "../interface/widgets/server/game_link.h"
#include "../interface/widgets/server/user/user_list_proxy.h"
#include "abstract_tab_deck_editor.h"
#include "api/archidekt/tab_archidekt.h"
#include "api/edhrec/tab_edhrec.h"
#include "api/edhrec/tab_edhrec_main.h"
#include "tab_visual_database_display.h"
#include "visual_deck_editor/tab_deck_editor_visual.h"
#include "visual_deck_storage/tab_deck_storage_visual.h"
#include <QAbstractButton>
#include <QLoggingCategory>
#include <QMap>
#include <QProxyStyle>
#include <QTabWidget>
#include <libcockatrice/network/client/abstract/latency_tracker.h>
class TabCardArtRules;
inline Q_LOGGING_CATEGORY(TabSupervisorLog, "tab_supervisor");
class UserListManager;
class QMenu;
class AbstractClient;
class Tab;
class TabServer;
class TabRoom;
class TabHome;
class TabGame;
class TabDeckStorage;
class TabReplays;
class TabAdmin;
class TabMessage;
class TabReport;
class TabModeration;
class TabAccount;
class TabDeckEditor;
class TabDeveloper;
class TabLog;
class RoomEvent;
class GameEventContainer;
class Event_GameJoined;
class Event_UserMessage;
class Event_NotifyUser;
class ServerInfo_Room;
class ServerInfo_User;
class GameReplay;
class DeckList;
class MacOSTabFixStyle : public QProxyStyle
{
Q_OBJECT
public:
QRect subElementRect(SubElement, const QStyleOption *, const QWidget *) const override;
};
class CloseButton : public QAbstractButton
{
Q_OBJECT
public:
explicit CloseButton(QWidget *parent = nullptr);
[[nodiscard]] QSize sizeHint() const override;
[[nodiscard]] QSize minimumSizeHint() const override
{
return sizeHint();
}
protected:
void enterEvent(QEnterEvent *event) override;
void leaveEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
};
class TabSupervisor : public QTabWidget
{
Q_OBJECT
public:
enum DeckEditorType
{
ClassicDeckEditor,
VisualDeckEditor
};
private:
ServerInfo_User *userInfo;
AbstractClient *client;
UserListManager *userListManager;
QList<AbstractClient *> localClients;
QMenu *tabsMenu;
TabHome *tabHome;
TabDeckStorageVisual *tabVisualDeckStorage;
TabServer *tabServer;
TabAccount *tabAccount;
TabDeckStorage *tabDeckStorage;
TabReplays *tabReplays;
TabAdmin *tabAdmin;
TabCardArtRules *tabCardArtRules;
TabLog *tabLog;
TabReport *tabReport;
TabModeration *tabModeration;
TabDeveloper *tabDeveloper;
QMap<int, TabRoom *> roomTabs;
QMap<int, TabGame *> gameTabs;
QList<TabGame *> replayTabs;
QMap<QString, TabMessage *> messageTabs;
QList<AbstractTabDeckEditor *> deckEditorTabs;
bool isLocalGame;
QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage,
*aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin,
*aTabCardArtRules, *aTabLog, *aTabReport, *aTabModeration, *aTabDeveloper;
int myAddTab(Tab *tab, QAction *manager = nullptr);
void addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager);
static QString sanitizeTabName(QString dirty);
static QString sanitizeHtml(QString dirty);
void resetTabsMenu();
public:
explicit TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent = nullptr);
~TabSupervisor() override;
void retranslateUi();
void initStartupTabs();
void start(const ServerInfo_User &userInfo);
void startLocal(const QList<AbstractClient *> &_clients);
void stop();
[[nodiscard]] bool getIsLocalGame() const
{
return isLocalGame;
}
[[nodiscard]] int getGameCount() const
{
return gameTabs.size();
}
[[nodiscard]] TabAccount *getTabAccount() const
{
return tabAccount;
}
[[nodiscard]] ServerInfo_User *getUserInfo() const
{
return userInfo;
}
[[nodiscard]] AbstractClient *getClient() const;
[[nodiscard]] UserListManager *getUserListManager() const
{
return userListManager;
}
[[nodiscard]] TabServer *getTabServer() const
{
return tabServer;
}
[[nodiscard]] const QMap<int, TabRoom *> &getRoomTabs() const
{
return roomTabs;
}
[[nodiscard]] QList<AbstractTabDeckEditor *> getDeckEditorTabs() const
{
return deckEditorTabs;
}
[[nodiscard]] QList<GameInviteOption> getGameInviteLinksForRoom(int roomId) const;
void sendInviteToUser(const QString &userName, const QString &inviteText);
[[nodiscard]] bool getAdminLocked() const;
void closeEvent(QCloseEvent *event) override;
bool switchToGameTabIfAlreadyExists(const int gameId);
static void actShowPopup(const QString &message);
signals:
void setMenu(const QList<QMenu *> &newMenuList = QList<QMenu *>());
void localGameEnded();
void adminLockChanged(bool lock);
void showWindowIfHidden();
void cockatriceLinkActivated(const QString &url);
public slots:
void openDeckInNewTab(const LoadedDeck &deckToOpen);
TabDeckEditor *addDeckEditorTab(const LoadedDeck &deckToOpen);
TabDeckEditorVisual *addVisualDeckEditorTab(const LoadedDeck &deckToOpen);
TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab();
TabEdhRecMain *addEdhrecMainTab();
TabArchidekt *addArchidektTab();
TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
void openReplay(GameReplay *replay);
void joinReportGame(int gameId, int roomId);
void openTabModeration(const QString &userName = {});
void switchToFirstAvailableNetworkTab();
void maximizeMainWindow();
void actTabVisualDeckStorage(bool checked);
void actTabReplays(bool checked);
void openTabServer();
void addRoomTab(const ServerInfo_Room &info, bool setCurrent);
private slots:
void refreshShortcuts();
void actTabHome(bool checked);
void actTabServer(bool checked);
void actTabAccount(bool checked);
void actTabDeckStorage(bool checked);
void actTabAdmin(bool checked);
void actTabLog(bool checked);
void actTabReport(bool checked);
void actTabModeration(bool checked);
void actTabDeveloper(bool checked);
void openTabVisualDeckStorage();
void openTabHome();
void openTabAccount();
void openTabDeckStorage();
void openTabReplays();
void openTabAdmin();
void actTabCardArtRules(bool checked);
void openTabCardArtRules();
void openTabLog();
void openTabReport();
void openTabDeveloper();
void updateCurrent(int index);
void updatePingTime(int value, int max);
void updateLatencyTooltip(const LatencyTracker::Stats &stats);
void gameJoined(const Event_GameJoined &event);
void localGameJoined(const Event_GameJoined &event);
void gameLeft(TabGame *tab);
void roomLeft(TabRoom *tab);
TabMessage *addMessageTab(const QString &userName, bool focus);
void replayLeft(TabGame *tab);
void processUserLeft(const QString &userName);
void processUserJoined(const ServerInfo_User &userInfo);
void talkLeft(TabMessage *tab);
void deckEditorClosed(AbstractTabDeckEditor *tab);
void tabUserEvent(bool globalEvent);
void updateTabText(Tab *tab, const QString &newTabText);
void processRoomEvent(const RoomEvent &event);
void processGameEventContainer(const GameEventContainer &cont);
void processUserMessageEvent(const Event_UserMessage &event);
void processNotifyUserEvent(const Event_NotifyUser &event);
};
#endif