package adminconsole import "html/template" // The *View types are the display models the gin handlers fill and the templates // render. Time values are pre-formatted to strings by the handlers so the // templates stay logic-free. // Pager is the shared list pagination state. type Pager struct { Page int PageSize int Total int HasPrev bool HasNext bool PrevPage int NextPage int } // NewPager builds the pagination state for a 1-based page of pageSize over total // items. func NewPager(page, pageSize, total int) Pager { if page < 1 { page = 1 } p := Pager{Page: page, PageSize: pageSize, Total: total, PrevPage: page - 1, NextPage: page + 1} p.HasPrev = page > 1 p.HasNext = page*pageSize < total return p } // VariantVersions lists the dictionary versions resident for one variant. type VariantVersions struct { Variant string Latest string Versions []string } // DashboardView is the landing-page summary. type DashboardView struct { Accounts int Games int ActiveGames int OpenComplaints int OpenFeedback int PendingChanges int // ActiveVersion is the dictionary version new games pin (the persisted active // version), distinct from the per-variant resident versions. ActiveVersion string Variants []VariantVersions } // UsersView is the paginated account list. type UsersView struct { Items []UserRow Pager Pager // Robots is the active people/robots toggle; NameMask/ExternalIDMask are the current // glob filters; FilterQuery is those URL-encoded for the pager links. It is an // already-escaped query fragment (url.Values.Encode), so it is typed template.URL to // be emitted verbatim โ€” interpolated as a plain string it would have its "=" and "&" // percent-encoded again by the contextual escaper. Robots bool NameMask string ExternalIDMask string FilterQuery template.URL } // UserRow is one account row in the list. MoveMin/Avg/Max are the account's // pre-formatted move-duration summary (empty when it has no timed move); // FlaggedHighRate marks the soft high-rate badge. type UserRow struct { ID string DisplayName string Kind string Language string Guest bool FlaggedHighRate bool CreatedAt string HasMoveStats bool MoveMin string MoveAvg string MoveMax string } // MessagesView is the paginated chat-message moderation list. NameMask/ExtMask are the // current sender glob filters; GameID/UserID pin the list to one game / sender (set from a // game or user card); FilterQuery is the active filters URL-encoded for the pager and CSV // links โ€” an already-escaped query fragment, hence template.URL so it is not re-encoded // inside the link (see UsersView.FilterQuery). type MessagesView struct { Items []MessageRow Pager Pager NameMask string ExtMask string GameID string UserID string UnreadOnly bool FilterQuery template.URL } // MessageRow is one chat message in the moderation list: its sender (linked to the user // card), source, IP, body, game (linked to the game card), time, and whether it is still // unread by at least one recipient. type MessageRow struct { ID string SenderID string SenderName string Source string IP string Body string GameID string CreatedAt string Unread bool } // ChatMessageDetailView is one chat message with its per-seat read breakdown, for the // console message card. type ChatMessageDetailView struct { ID string GameID string SenderID string SenderName string Source string Kind string Body string IP string CreatedAt string Unread bool Seats []ChatSeatStatusRow } // ChatSeatStatusRow is one seat's read status on the message card: the seat index, the // occupant (linked to the user card) and its role ("sender", "read" or "unread"). type ChatSeatStatusRow struct { Seat int AccountID string DisplayName string Role string } // UserDetailView is one account with its stats, identities and recent games. type UserDetailView struct { ID string DisplayName string Language string TimeZone string Guest bool NotificationsInAppOnly bool PaidAccount bool // MergedInto is the primary account id when this account has been retired by a // merge, or empty for a live account. MergedInto string // FlaggedHighRateAt is the pre-formatted soft high-rate marker timestamp, // empty for an unflagged account; the card shows it with the Clear action. FlaggedHighRateAt string HintBalance int // HintGrantMax is the per-grant cap the operator's "add hints" form enforces (it mirrors the // server's maxHintGrant), passed through so the policy value lives in one place. HintGrantMax int CreatedAt string HasStats bool Stats StatsRow Identities []IdentityRow Games []GameRow TelegramID string ConnectorEnabled bool // MoveChart is the pre-rendered inline SVG of the account's per-move-number think // time (min/mean/max), empty when the account has no timed move. MoveChart template.HTML // Suspension is the account's current manual-block state, shown with the block/unblock // form; Reasons is the operator-editable reason picklist offered in the block form. Suspension SuspensionView Reasons []ReasonOption // Roles is the account's current roles (each revocable); KnownRoles is the set the // grant form offers. The first role is the feedback ban (see internal/account). Roles []string KnownRoles []string // Blocks, BlockedBy and Friends are the social graph on the card: who this account has // blocked, who currently blocks it, and its mutual friendships โ€” each cross-linked to the // other account with the date it happened. They are the full truth; the asymmetric block // suppression that hides relationships from players never applies to the console. Blocks []RelationRow BlockedBy []RelationRow Friends []RelationRow } // RelationRow is one cross-linked account in the user card's blocks / blocked-by / friends // lists: the other account's id (the link target), its display name, and the pre-formatted date. type RelationRow struct { AccountID string DisplayName string Date string } // SuspensionView is an account's current manual-block state shown on the user card: whether it // is blocked, whether the block is permanent, the pre-formatted expiry (empty when permanent), // when it was applied, and the reason snapshot in both languages (empty when none was cited). type SuspensionView struct { Blocked bool Permanent bool Until string BlockedAt string ReasonEn string ReasonRu string } // ReasonOption is one suspension-reason picklist entry offered in the block form's dropdown. type ReasonOption struct { ID string TextEn string TextRu string } // StatsRow is an account's lifetime statistics. type StatsRow struct { Wins int Losses int Draws int MaxGamePoints int MaxWordPoints int Moves int HintsUsed int } // IdentityRow is one platform/email identity of an account. type IdentityRow struct { Kind string ExternalID string Confirmed bool CreatedAt string } // GameRow is one game row in a list. type GameRow struct { ID string Variant string Status string Players int UpdatedAt string // VsAI marks an honest-AI game (rendered as ๐Ÿค– in the list's AI column). VsAI bool } // GamesView is the paginated games list, optionally filtered by status. type GamesView struct { Items []GameRow Status string Pager Pager } // GameDetailView is one game with its seats. type GameDetailView struct { ID string Variant string DictVersion string Status string Players int ToMove int EndReason string MoveCount int CreatedAt string UpdatedAt string FinishedAt string // VsAI marks an honest-AI game (shown as a ๐Ÿค– flag in the summary). VsAI bool Seats []SeatRow // HasRobot is true when any seat is a robot, gating the robot-target caption; // RobotTargetPct is the configured global play-to-win rate, in percent. HasRobot bool RobotTargetPct int // ReplayJSON is the game-replay payload (board, seats, per-step racks/scores/bag) the // game_detail page feeds to its vanilla-JS stepper; HasReplay gates the replay section. ReplayJSON template.JS HasReplay bool // SetupDraws is the first-move draw โ€” one row per tile drawn (docs/ARCHITECTURE.md ยง6) โ€” // and FirstMover is the resolved name of the seat-0 player the draw elected. SetupDraws []SetupDrawRow FirstMover string } // SetupDrawRow is one tile drawn in the first-move seeding (docs/ARCHITECTURE.md ยง6): the // round, the player (Name/AccountID, or "(opponent)" with an empty AccountID for an // auto-match synthetic draw not yet back-filled), the drawn letter (upper-cased; "?" for a // blank) and its draw rank. type SetupDrawRow struct { Round int Name string AccountID string Letter string Blank bool Rank int } // SeatRow is one seat of a game. For a robot seat (IsRobot) RobotIntent is the game's // deterministic play-to-win decision ("play to win"/"play to lose"), and NextMove is the // scheduled next-move ETA shown only while it is that robot's turn in an active game. type SeatRow struct { Seat int DisplayName string AccountID string Score int HintsUsed int Winner bool IsRobot bool RobotIntent string NextMove string } // ComplaintsView is the paginated complaint review queue. type ComplaintsView struct { Items []ComplaintRow Status string Pager Pager } // ComplaintRow is one complaint row in the queue. type ComplaintRow struct { ID string Word string Variant string WasValid bool Status string Disposition string CreatedAt string } // ComplaintDetailView is one complaint with its resolution state and form. type ComplaintDetailView struct { ID string Word string Variant string DictVersion string WasValid bool Note string Status string Disposition string ResolutionNote string CreatedAt string ResolvedAt string GameID string Resolved bool } // DictionaryView lists the resident versions per variant, the active version new // games pin, and the pending wordlist changes from accepted complaints. type DictionaryView struct { // ActiveVersion is the dictionary version new games pin; the update form sets it. ActiveVersion string Variants []VariantVersions Changes []DictChangeRow } // DictChangeRow is one pending wordlist edit. type DictChangeRow struct { Variant string Word string Action string ResolvedAt string } // DictionaryPreviewView is the second step of a dictionary update: the version // parsed from the uploaded archive (editable before confirming), the staging token // that names the uploaded files on disk, the active version the diff is against, and // the per-variant word diff. type DictionaryPreviewView struct { Version string Token string ActiveVersion string Variants []VariantDiffRow } // VariantDiffRow summarises one variant's word diff in the update preview. // AddedCount/RemovedCount are the full totals; AddedSample/RemovedSample are the // first words shown (capped); AddedTruncated/RemovedTruncated mark a capped list; // LargeRemoval flags a removal large enough to warrant caution before confirming. type VariantDiffRow struct { Variant string AddedCount int RemovedCount int AddedSample []string RemovedSample []string AddedTruncated bool RemovedTruncated bool LargeRemoval bool } // BroadcastView is the operator-broadcast form page. type BroadcastView struct { ConnectorEnabled bool } // ThrottledView is the rate-limit observability page: the temporary IP bans the // gateway is currently enforcing, the recent gateway-reported throttle episodes // (in-memory, reset on restart) and the accounts currently carrying the high-rate // flag. FlagThreshold and FlagWindow caption the active auto-flag tuning. type ThrottledView struct { Bans []BanRow Episodes []ThrottleEpisodeRow Flagged []FlaggedAccountRow FlagThreshold int FlagWindow string } // BanRow is one temporary IP ban the gateway is enforcing, with its reason and its // since/expiry timestamps; the row carries an unban action. type BanRow struct { IP string Reason string Since string Expires string } // ThrottleEpisodeRow is one recently throttled limiter key. UserID links to the // user card and is set only for the user class (the other classes key by IP). type ThrottleEpisodeRow struct { Class string Key string UserID string Rejected int FirstSeen string LastSeen string } // FlaggedAccountRow is one account carrying the high-rate flag. type FlaggedAccountRow struct { ID string DisplayName string FlaggedAt string } // ReasonsView is the suspension-reason picklist management page: every editable reason entry. type ReasonsView struct { Items []ReasonRow } // ReasonRow is one editable suspension-reason entry, with its English and Russian text. type ReasonRow struct { ID string TextEn string TextRu string CreatedAt string } // MessageView is the result page shown after a POST action. type MessageView struct { Heading string Body string Back string } // BannersView is the advertising-campaign list page. type BannersView struct { Items []BannerCampaignRow } // BannerCampaignRow is one campaign in the list. Window is a human-readable // validity window ("perpetual" for the default); ActiveNow reports whether it // would rotate right now (enabled and within its window). type BannerCampaignRow struct { ID string Name string Weight int IsDefault bool Enabled bool Window string Messages int ActiveNow bool } // BannerDetailView is the campaign detail/edit page. StartsAt/EndsAt are the // "YYYY-MM-DDTHH:MM" (UTC) values for the datetime-local inputs, empty when open. type BannerDetailView struct { ID string Name string Weight int IsDefault bool Enabled bool StartsAt string EndsAt string Messages []BannerMessageRow } // BannerMessageRow is one bilingual message of a campaign. First/Last drive the // reorder buttons (disabled at the ends). type BannerMessageRow struct { ID string BodyEn string BodyRu string First bool Last bool } // BannerSettingsView is the global display-timings form. type BannerSettingsView struct { HoldMs int EdgePauseMs int ScrollPxPerSec int FadeOutMs int GapMs int FadeInMs int } // FeedbackView is the paginated user-feedback queue. Status is the active // unread/read/archived filter; NameMask/ExtMask are the sender glob filters; // UserID pins the list to one account (the per-user link from /users); // FilterQuery is the active filters URL-encoded for the pager links (already // escaped, hence template.URL โ€” see UsersView.FilterQuery). type FeedbackView struct { Items []FeedbackRow Status string NameMask string ExtMask string UserID string Pager Pager FilterQuery template.URL } // FeedbackRow is one feedback message in the queue: its sender (linked to the user // card), source, channel, whether it has an attachment / a reply, its state and // time. type FeedbackRow struct { ID string AccountID string SenderName string Source string Channel string HasAttachment bool Read bool Replied bool Archived bool CreatedAt string } // FeedbackDetailView is one feedback message with its body, attachment, state and // the reply / archive / delete forms. Body, AttachmentName, SenderName and IP are // user-controlled and rendered as plain auto-escaped text. IsImage gates the inline // preview; Banned shows whether the sender already holds the feedback ban. type FeedbackDetailView struct { ID string AccountID string SenderName string Source string Channel string // InterfaceLanguage is the sender's interface language (account preference). InterfaceLanguage string IP string Body string HasAttachment bool AttachmentName string IsImage bool Read bool Archived bool Replied bool ReplyBody string RepliedAt string CreatedAt string Banned bool }