diff --git a/frontend/assets/app.js b/frontend/assets/app.js index 1d03098..7ee9585 100644 --- a/frontend/assets/app.js +++ b/frontend/assets/app.js @@ -1,10907 +1,10291 @@ (() => { - const whenTemplatesReady = async () => { - if (document.readyState === 'loading') { - await new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve, { once: true })); - } - if (window.loadTemplatesPromise) { - try { - await window.loadTemplatesPromise; - } catch (err) { - console.error('Template load failed', err); - } - } + var __defProp = Object.defineProperty; + var __defProps = Object.defineProperties; + var __getOwnPropDescs = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols = Object.getOwnPropertySymbols; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __propIsEnum = Object.prototype.propertyIsEnumerable; + var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; }; - - const start = () => { - const $ = (sel) => document.querySelector(sel); - - const serversEl = $('#servers'); - const consoleEl = $('#console'); - const loginPanel = $('#loginPanel'); - const appPanel = $('#appPanel'); - const userBox = $('#userBox'); - const mainNav = $('#mainNav'); - const navDashboard = $('#navDashboard'); - const navLinked = $('#navLinked'); - const navTeam = $('#navTeam'); - const navAdmin = $('#navAdmin'); - const navDiscord = $('#navDiscord'); - const navSettings = $('#navSettings'); - const teamSwitcher = $('#teamSwitcher'); - const teamSelect = $('#teamSelect'); - const teamSelectLabel = $('#teamSelectLabel'); - const dashboardPanel = $('#dashboardPanel'); - const linkedAccountsPanel = $('#linkedAccountsPanel'); - const teamPanel = $('#teamPanel'); - const adminPanel = $('#adminPanel'); - const discordPanel = $('#discordPanel'); - const workspacePanel = $('#workspacePanel'); - const settingsPanel = $('#settingsPanel'); - const loginError = $('#loginError'); - const registerInfo = $('#registerInfo'); - const registerForm = $('#registerForm'); - const registerError = $('#registerError'); - const registerSuccess = $('#registerSuccess'); - const btnRegister = $('#btnRegister'); - const userCard = $('#userCard'); - const userList = $('#userList'); - const userFeedback = $('#userFeedback'); - const adminUserList = $('#adminUserList'); - const adminUserEmpty = $('#adminUserEmpty'); - const adminUserDetails = $('#adminUserDetails'); - const adminDetailUsername = $('#adminDetailUsername'); - const adminDetailCreated = $('#adminDetailCreated'); - const adminUserRoleSelect = $('#adminUserRoleSelect'); - const adminUserSaveRole = $('#adminUserSaveRole'); - const adminUserRoleStatus = $('#adminUserRoleStatus'); - const adminUserResetPassword = $('#adminUserResetPassword'); - const adminUserDelete = $('#adminUserDelete'); - const adminUserDangerStatus = $('#adminUserDangerStatus'); - const adminUserTeams = $('#adminUserTeams'); - const adminAssignTeam = $('#adminAssignTeam'); - const adminAssignRole = $('#adminAssignRole'); - const adminUserSuperuser = $('#adminUserSuperuser'); - const btnAdminAssignTeam = $('#btnAdminAssignTeam'); - const adminAssignStatus = $('#adminAssignStatus'); - const adminNewUserName = $('#adminNewUserName'); - const adminNewUserPassword = $('#adminNewUserPassword'); - const adminNewUserRole = $('#adminNewUserRole'); - const adminNewUserTeam = $('#adminNewUserTeam'); - const adminNewUserSuperuser = $('#adminNewUserSuperuser'); - const btnAdminCreateUser = $('#btnAdminCreateUser'); - const adminUserFeedback = $('#adminUserFeedback'); - const adminTeamList = $('#adminTeamList'); - const adminUserSearch = $('#adminUserSearch'); - const adminUserCount = $('#adminUserCount'); - const adminSuperuserCount = $('#adminSuperuserCount'); - const adminOrgCount = $('#adminOrgCount'); - const adminScopeBadge = $('#adminScopeBadge'); - const btnRefreshServers = $('#btnRefreshServers'); - const btnClearConsole = $('#btnClearConsole'); - const btnAddServer = $('#btnAddServer'); - const addServerPrompt = $('#addServerPrompt'); - const svName = $('#svName'); - const svHost = $('#svHost'); - const svPort = $('#svPort'); - const svPass = $('#svPass'); - const svTLS = $('#svTLS'); - const btnSend = $('#btnSend'); - const cmdInput = $('#cmd'); - const cmdInputDefaultPlaceholder = cmdInput?.getAttribute('placeholder') || ''; - - const DISCORD_ALLOWED_PRESENCES = new Set(['online', 'idle', 'dnd', 'invisible']); - const DISCORD_PRESENCE_LABELS = { - online: 'Online', - idle: 'Idle', - dnd: 'Do Not Disturb', - invisible: 'Invisible' - }; - const DISCORD_PRESENCE_CLASS = { - online: 'online', - idle: 'degraded', - dnd: 'offline', - invisible: 'offline' - }; - const DEFAULT_DISCORD_PRESENCE_TEMPLATE = '{statusEmoji} {playerCount} on {serverName}'; - const DEFAULT_DISCORD_PRESENCE_STATUSES = { - online: 'online', - offline: 'dnd', - stale: 'idle', - waiting: 'idle' - }; - const DEFAULT_DISCORD_STATUS_FIELDS = { - joining: true, - queued: true, - sleepers: true, - fps: true, - lastUpdate: true - }; - const DEFAULT_DISCORD_TICKETING_CONFIG = { - enabled: false, - categoryId: '', - logChannelId: '', - staffRoleId: '', - pingStaffOnOpen: true, - panelChannelId: '', - panelMessageId: '' - }; - const DEFAULT_DISCORD_COMMAND_PERMISSIONS = { - status: [], - ticket: [], - rustlookup: [], - auth: [] - }; - const DISCORD_STATUS_FIELD_LABELS = { - joining: 'Joining players', - queued: 'Queue length', - sleepers: 'Sleeping players', - fps: 'Server FPS', - lastUpdate: 'Last update timestamp' - }; - - const logMfa = (message, details = {}) => { - try { - const safeDetails = { ...details }; - if (safeDetails.secret) delete safeDetails.secret; - if (Array.isArray(safeDetails.backupCodes)) { - safeDetails.backupCodes = `(${safeDetails.backupCodes.length} codes hidden)`; - } - console.info('[mfa]', message, safeDetails); - } catch (err) { - console.warn('[mfa] log failed', err); - } - }; - - logMfa('browser logger ready', { - page: typeof window !== 'undefined' ? window.location?.href : '(no-window)' - }); - - const superuserUi = false; - const defaultPanel = (typeof window !== 'undefined' && window.DEFAULT_PANEL) - ? String(window.DEFAULT_PANEL) - : 'dashboard'; - const state = { - API: '', - TOKEN: localStorage.getItem('token') || '', - currentUser: null, - currentServerId: null, - serverItems: new Map(), - allowRegistration: false, - statusTimer: null, - settings: {}, - security: { - totpEnabled: false, - passkeys: [], - pendingSecret: null, - backupCodes: [], - backupCodesRemaining: 0, - loaded: false, - lastLoadedAt: null - }, - activePanel: defaultPanel, - activeTeamId: null, - activeTeamName: null, - teams: [], - roles: [], - roleTemplates: { serverCapabilities: [], globalPermissions: [] }, - teamDiscord: { - hasToken: false, - guildId: null, - tokenPreview: null, - loading: false, - loadedTeamId: null, - config: defaultTeamDiscordConfig(), - configLoaded: false - }, - teamDiscordRoles: defaultTeamDiscordRolesState(), - teamDiscordChannels: defaultTeamDiscordChannelsState(), - teamAuth: { loading: false, enabled: false, roleId: null, logChannelId: null, loaded: false, loadedTeamId: null }, - workspaceDiscord: { - serverId: null, - loading: false, - saving: false, - error: null, - integration: null, - status: null, - config: defaultWorkspaceDiscordConfig() - }, - pendingMfa: null, - admin: { - users: [], - teams: [], - selectedUserId: null, - loading: false - }, - superuserUi - }; - let adminUserFilter = ''; - const loginUsername = $('#username'); - const loginPassword = $('#password'); - const loginMfaStep = $('#mfaStep'); - const loginMfaCode = $('#loginMfaCode'); - const loginMfaStatus = $('#loginMfaStatus'); - const btnSubmitMfa = $('#btnSubmitMfa'); - const btnMfaUsePasskey = $('#btnMfaUsePasskey'); - const btnLogin = $('#btnLogin'); - const regUsername = $('#regUsername'); - const regPassword = $('#regPassword'); - const regConfirm = $('#regConfirm'); - const userCreateSection = $('#userCreateSection'); - const newUserName = $('#newUserName'); - const newUserPassword = $('#newUserPassword'); - const newUserRole = $('#newUserRole'); - const btnCreateUser = $('#btnCreateUser'); - const existingUserName = $('#existingUserName'); - const existingUserRole = $('#existingUserRole'); - const btnAddExistingUser = $('#btnAddExistingUser'); - const existingUserFeedback = $('#existingUserFeedback'); - const roleManager = $('#roleManager'); - const roleEditor = $('#roleEditor'); - const rolesHeader = $('#rolesHeader'); - const rolesDescription = $('#rolesDescription'); - const rolesSection = roleManager?.closest('.team-section') || null; - const teamDiscordSection = $('#teamDiscordSection'); - const teamDiscordStatusSection = $('#teamDiscordStatusSection'); - const teamDiscordForm = $('#teamDiscordForm'); - const teamDiscordGuildId = $('#teamDiscordGuildId'); - const teamDiscordToken = $('#teamDiscordToken'); - const btnSaveTeamDiscord = $('#btnSaveTeamDiscord'); - const btnRemoveTeamDiscord = $('#btnRemoveTeamDiscord'); - const teamDiscordStatus = $('#teamDiscordStatus'); - const teamDiscordSummary = $('#teamDiscordSummary'); - const teamDiscordSummaryGuild = $('#teamDiscordSummaryGuild'); - const teamDiscordSummaryToken = $('#teamDiscordSummaryToken'); - const teamOAuthForm = $('#teamOAuthForm'); - const teamDiscordOauthClientId = $('#teamDiscordOauthClientId'); - const teamDiscordOauthClientSecret = $('#teamDiscordOauthClientSecret'); - const teamDiscordOauthRedirectUri = $('#teamDiscordOauthRedirectUri'); - const teamSteamApiKeyInput = $('#teamSteamApiKey'); - const teamOAuthStatus = $('#teamOAuthStatus'); - const btnSaveTeamOAuth = $('#btnSaveTeamOAuth'); - const btnClearTeamOAuth = $('#btnClearTeamOAuth'); - const teamAuthSection = $('#teamAuthSection'); - const teamAuthForm = $('#teamAuthForm'); - const teamAuthEnabledInput = $('#teamAuthEnabled'); - const teamAuthRoleInput = $('#teamAuthRole'); - const teamAuthRoleSelect = $('#teamAuthRoleSelect'); - const teamAuthLogChannelInput = $('#teamAuthLogChannel'); - const teamAuthLogChannelSelect = $('#teamAuthLogChannelSelect'); - const teamAuthStatus = $('#teamAuthStatus'); - const btnSaveTeamAuth = $('#teamAuthSave'); - const teamTicketingSection = $('#teamTicketingSection'); - const teamTicketingForm = $('#teamTicketingForm'); - const teamTicketingStatus = $('#teamTicketingStatus'); - const roleSelect = $('#roleSelect'); - const roleNameInput = $('#roleName'); - const roleDescriptionInput = $('#roleDescription'); - const roleServersList = $('#roleServersList'); - const roleCapabilitiesContainer = $('#roleCapabilities'); - const roleGlobalContainer = $('#roleGlobalPermissions'); - const newRoleKey = $('#newRoleKey'); - const newRoleName = $('#newRoleName'); - const btnCreateRole = $('#btnCreateRole'); - const btnSaveRole = $('#btnSaveRole'); - const btnDeleteRole = $('#btnDeleteRole'); - const roleFeedback = $('#roleFeedback'); - const roleEditorEmpty = $('#roleEditorEmpty'); - const roleLockedNotice = $('#roleLockedNotice'); - const quickCommandsEl = $('#quickCommands'); - const rustMapsKeyInput = $('#rustMapsKey'); - const btnSaveSettings = $('#btnSaveSettings'); - const settingsStatus = $('#settingsStatus'); - const currentPasswordInput = $('#currentPassword'); - const newPasswordInput = $('#newPassword'); - const confirmPasswordInput = $('#confirmPassword'); - const btnChangePassword = $('#btnChangePassword'); - const passwordStatus = $('#passwordStatus'); - const mfaStatusLabel = $('#mfaStatus'); - const totpSecretValue = $('#totpSecret'); - const totpQrImage = $('#totpQr'); - const btnCopyTotpSecret = $('#btnCopyTotpSecret'); - const backupCodesList = $('#backupCodesList'); - const backupCodesInfo = $('#backupCodesInfo'); - const btnGenerateBackupCodes = $('#btnGenerateBackupCodes'); - const totpSetupFields = $('#totpSetupFields'); - const totpCodeInput = $('#totpCode'); - const btnStartTotp = $('#btnStartTotp'); - const btnEnableTotp = $('#btnEnableTotp'); - const btnDisableTotp = $('#btnDisableTotp'); - const mfaStatusMessage = $('#mfaStatusMessage'); - const passkeyList = $('#passkeyList'); - const btnRegisterPasskey = $('#btnRegisterPasskey'); - const passkeyStatus = $('#passkeyStatus'); - const serversEmpty = $('#serversEmpty'); - const addServerCard = $('#addServerCard'); - const welcomeName = $('#welcomeName'); - const profileMenuTrigger = $('#profileMenuTrigger'); - const brandAccount = $('.brand-account'); - const workspaceName = $('#workspaceServerName'); - const workspaceMeta = $('#workspaceServerMeta'); - const workspaceStatus = $('#workspaceStatus'); - const workspacePlayers = $('#workspacePlayers'); - const workspaceQueue = $('#workspaceQueue'); - const workspaceJoining = $('#workspaceJoining'); - const workspaceMenu = $('#workspaceMenu'); - const workspaceInfoPlayers = $('#workspaceInfoPlayers'); - const workspaceInfoMaxPlayers = $('#workspaceInfoMaxPlayers'); - const workspaceInfoQueue = $('#workspaceInfoQueue'); - const workspaceInfoJoining = $('#workspaceInfoJoining'); - const workspaceInfoGameTime = $('#workspaceInfoGameTime'); - const workspaceInfoUptime = $('#workspaceInfoUptime'); - const workspaceInfoFramerate = $('#workspaceInfoFramerate'); - const workspaceInfoNetworkIn = $('#workspaceInfoNetworkIn'); - const workspaceInfoNetworkOut = $('#workspaceInfoNetworkOut'); - const workspaceInfoSaveCreatedTime = $('#workspaceInfoSaveCreatedTime'); - const workspaceChatBody = $('#workspaceChatBody'); - const workspaceChatList = $('#workspaceChatList'); - const workspaceChatEmpty = $('#workspaceChatEmpty'); - const workspaceChatLoading = $('#workspaceChatLoading'); - const workspaceChatNotice = $('#workspaceChatNotice'); - const discordBotStatusPill = $('#discord-bot-status'); - const discordConnectionGuildInput = $('#discord-connection-guild-input'); - const discordConnectionChannelInput = $('#discord-connection-channel-input'); - const discordConnectionTokenInput = $('#discord-connection-token-input'); - const discordConnectionStatus = $('#discord-connection-status'); - const discordStatusServerSelect = null; - const discordSettingsGrid = $('#discord-settings'); - const discordCurrentPlayers = $('#discord-current-players'); - const discordMaxPlayers = $('#discord-max-players'); - const discordJoiningPlayers = $('#discord-joining'); - const discordLastCheck = $('#discord-last-check'); - const discordPresenceTemplateSummary = $('#discord-presence-template'); - const discordPresenceStatusOnline = $('#discord-presence-status-online'); - const discordPresenceStatusOffline = $('#discord-presence-status-offline'); - const discordPresenceStatusStale = $('#discord-presence-status-stale'); - const discordPresenceStatusWaiting = $('#discord-presence-status-waiting'); - const discordEnabledFieldsList = $('#discord-enabled-fields'); - const discordEnabledFieldsEmpty = $('#discord-enabled-fields-empty'); - const discordTicketingSummaryStatus = $('#discord-ticketing-summary-status'); - const discordTicketingSummaryCategory = $('#discord-ticketing-summary-category'); - const discordTicketingSummaryLog = $('#discord-ticketing-summary-log'); - const discordTicketingSummaryRole = $('#discord-ticketing-summary-role'); - const discordStatusForm = $('#discord-status-form'); - const discordStatusNotice = $('#discord-status-notice'); - const discordPresenceTemplateInput = $('#discord-presence-template-input'); - const discordPresenceOnlineSelect = $('#discord-presence-online'); - const discordPresenceOfflineSelect = $('#discord-presence-offline'); - const discordPresenceStaleSelect = $('#discord-presence-stale'); - const discordPresenceWaitingSelect = $('#discord-presence-waiting'); - const discordStatusFieldInputs = Array.from(document.querySelectorAll('[data-status-field]')); - const discordTicketingEnabledInput = $('#discord-ticketing-enabled'); - const discordTicketingCategoryInput = $('#discord-ticketing-category'); - const discordTicketingLogInput = $('#discord-ticketing-log'); - const discordTicketingRoleInput = $('#discord-ticketing-role'); - const discordTicketingPingInput = $('#discord-ticketing-ping'); - const discordTicketingPanelChannelInput = $('#discord-ticketing-panel-channel'); - const discordTicketingPanelMessageInput = $('#discord-ticketing-panel-message'); - const discordTicketingCategorySelect = $('#discord-ticketing-category-select'); - const discordTicketingLogSelect = $('#discord-ticketing-log-select'); - const discordTicketingRoleSelect = $('#discord-ticketing-role-select'); - const discordTicketingPanelChannelSelect = $('#discord-ticketing-panel-channel-select'); - const discordCategoryOptions = $('#discord-category-options'); - const discordChannelOptions = $('#discord-channel-options'); - const discordRoleOptions = $('#discord-role-options'); - const teamCommandStatusInput = $('#team-command-status-role-input'); - const teamCommandTicketInput = $('#team-command-ticket-role-input'); - const teamCommandLookupInput = $('#team-command-lookup-role-input'); - const teamCommandAuthInput = $('#team-command-auth-role-input'); - const teamCommandStatusSelect = $('#team-command-status-role-select'); - const teamCommandTicketSelect = $('#team-command-ticket-role-select'); - const teamCommandLookupSelect = $('#team-command-lookup-role-select'); - const teamCommandAuthSelect = $('#team-command-auth-role-select'); - const teamCommandStatusList = $('#team-command-status-role-list'); - const teamCommandTicketList = $('#team-command-ticket-role-list'); - const teamCommandLookupList = $('#team-command-lookup-role-list'); - const teamCommandAuthList = $('#team-command-auth-role-list'); - const teamCommandSummaryStatus = $('#team-command-summary-status'); - const teamCommandSummaryTicket = $('#team-command-summary-ticket'); - const teamCommandSummaryLookup = $('#team-command-summary-lookup'); - const teamCommandSummaryAuth = $('#team-command-summary-auth'); - const teamCommandRolesStatus = $('#teamCommandRolesStatus'); - const btnRefreshTeamCommandRoles = $('#team-command-refresh-roles'); - const teamDiscordChannelsStatus = $('#teamDiscordChannelsStatus'); - const btnRefreshTeamDiscordChannels = $('#team-discord-refresh-channels'); - const aqTicketsList = $('#aqTicketsList'); - const aqTicketsLoading = $('#aqTicketsLoading'); - const aqTicketsError = $('#aqTicketsError'); - const aqTicketsEmpty = $('#aqTicketsEmpty'); - const aqTicketsCount = $('#aqTicketsCount'); - const aqTicketPlaceholder = $('#aqTicketPlaceholder'); - const aqTicketDetail = $('#aqTicketDetail'); - const aqTicketSubject = $('#aqTicketSubject'); - const aqTicketMeta = $('#aqTicketMeta'); - const aqTicketNumber = $('#aqTicketNumber'); - const aqTicketPreviewLink = $('#aqTicketPreviewLink'); - const aqTicketDialog = $('#aqTicketDialog'); - const aqTicketDialogLoading = $('#aqTicketDialogLoading'); - const aqTicketDialogEmpty = $('#aqTicketDialogEmpty'); - const aqTicketDialogError = $('#aqTicketDialogError'); - const aqTicketReply = $('#aqTicketReply'); - const aqTicketReplyForm = $('#aqTicketReplyForm'); - const aqTicketReplyInput = $('#aqTicketReplyInput'); - const aqTicketReplySubmit = $('#aqTicketReplySubmit'); - const aqTicketReplyError = $('#aqTicketReplyError'); - const aqTicketReplyNotice = $('#aqTicketReplyNotice'); - const f7ReportsList = $('#f7ReportsList'); - const f7ReportsLoading = $('#f7ReportsLoading'); - const f7ReportsEmpty = $('#f7ReportsEmpty'); - const f7ReportsError = $('#f7ReportsError'); - const f7ReportsCount = $('#f7ReportsCount'); - const f7ReportsSearch = $('#f7ReportsSearch'); - const f7ReportPlaceholder = $('#f7ReportPlaceholder'); - const f7ReportDetail = $('#f7ReportDetail'); - const f7ReportDetailTime = $('#f7ReportDetailTime'); - const f7ReportTarget = $('#f7ReportTarget'); - const f7ReportReporter = $('#f7ReportReporter'); - const f7ReportCategory = $('#f7ReportCategory'); - const f7ReportId = $('#f7ReportId'); - const f7ReportMessage = $('#f7ReportMessage'); - const f7ReportTargetSummary = $('#f7ReportTargetSummary'); - const f7ReportSummaryTotal = $('#f7ReportSummaryTotal'); - const f7ReportSummaryRecent = $('#f7ReportSummaryRecent'); - const f7ReportSummaryRecentLabel = $('#f7ReportSummaryRecentLabel'); - const f7ReportSummaryReporters = $('#f7ReportSummaryReporters'); - const f7ReportSummaryFirst = $('#f7ReportSummaryFirst'); - const f7ReportSummaryLast = $('#f7ReportSummaryLast'); - const f7ReportSummaryCategories = $('#f7ReportSummaryCategories'); - const f7ReportTargetProfile = $('#f7ReportTargetProfile'); - const f7ReportProfileAvatar = $('#f7ReportProfileAvatar'); - const f7ReportProfileName = $('#f7ReportProfileName'); - const f7ReportProfileSteam = $('#f7ReportProfileSteam'); - const f7ReportProfileCountry = $('#f7ReportProfileCountry'); - const f7ReportProfileVac = $('#f7ReportProfileVac'); - const f7ReportProfileGameBans = $('#f7ReportProfileGameBans'); - const f7ReportProfileLastBan = $('#f7ReportProfileLastBan'); - const f7ReportProfilePlaytime = $('#f7ReportProfilePlaytime'); - const f7ReportProfileStatus = $('#f7ReportProfileStatus'); - const f7ReportHistory = $('#f7ReportHistory'); - const f7ReportHistoryList = $('#f7ReportHistoryList'); - const f7ReportsFocus = $('#f7ReportsFocus'); - const f7ReportsFocusLabel = $('#f7ReportsFocusLabel'); - const f7ReportsClearFocus = $('#f7ReportsClearFocus'); - const f7ReportShowAll = $('#f7ReportShowAll'); - const f7ReportOpenProfile = $('#f7ReportOpenProfile'); - const f7ScopeButtons = Array.from(document.querySelectorAll('.reports-scope-btn')); - - const regionDisplay = typeof Intl !== 'undefined' && typeof Intl.DisplayNames === 'function' - ? new Intl.DisplayNames(['en'], { type: 'region' }) - : null; - const COUNTRY_FALLBACKS = { UK: 'United Kingdom', EU: 'European Union' }; - const killFeedList = $('#killFeedList'); - const killFeedEmpty = $('#killFeedEmpty'); - const killFeedLoading = $('#killFeedLoading'); - const killFeedNotice = $('#killFeedNotice'); - const btnBackToDashboard = $('#btnBackToDashboard'); - const profileUsername = $('#profileUsername'); - const profileRole = $('#profileRole'); - const moduleFallback = $('#moduleFallback'); - const userDetailsOverlay = $('#userDetailsOverlay'); - const userDetailsPanel = $('#userDetailsPanel'); - const userDetailsClose = $('#userDetailsClose'); - const userDetailsName = $('#userDetailsName'); - const userDetailsSubtitle = $('#userDetailsSubtitle'); - const userDetailsRole = $('#userDetailsRole'); - const userDetailsRoleBadge = $('#userDetailsRoleBadge'); - const userDetailsCreated = $('#userDetailsCreated'); - const userDetailsId = $('#userDetailsId'); - const userDetailsRoleSelect = $('#userDetailsRoleSelect'); - const userDetailsSaveRole = $('#userDetailsSaveRole'); - const userDetailsResetPassword = $('#userDetailsResetPassword'); - const userDetailsDelete = $('#userDetailsDelete'); - const userDetailsSelfNotice = $('#userDetailsSelfNotice'); - const userDetailsRoleStatus = $('#userDetailsRoleStatus'); - const dialogOverlay = $('#dialogOverlay'); - const dialogModal = $('#dialogModal'); - const dialogTitle = $('#dialogTitle'); - const dialogMessage = $('#dialogMessage'); - const dialogInputWrap = $('#dialogInputWrap'); - const dialogInputLabel = $('#dialogInputLabel'); - const dialogInput = $('#dialogInput'); - const dialogError = $('#dialogError'); - const dialogConfirmBtn = $('#dialogConfirm'); - const dialogCancelBtn = $('#dialogCancel'); - - const workspaceViewSections = Array.from(document.querySelectorAll('.workspace-view')); - const workspaceViewSectionMap = new Map(workspaceViewSections.map((section) => [section.dataset.view, section])); - const workspaceViewButtons = workspaceMenu ? Array.from(workspaceMenu.querySelectorAll('.menu-tab')) : []; - const chatFilterButtons = Array.from(document.querySelectorAll('.chat-filter-btn')); - const CHAT_REFRESH_INTERVAL_MS = 5000; - const KILL_FEED_REFRESH_INTERVAL_MS = 30000; - const KILL_FEED_RETENTION_MS = 24 * 60 * 60 * 1000; - const DEFAULT_TEAM_CHAT_COLOR = '#3b82f6'; - const workspaceViewDefault = 'players'; - let activeWorkspaceView = workspaceViewDefault; - const chatState = { - filter: 'all', - cache: new Map(), - lastFetched: new Map(), - loading: false, - error: null, - profileCache: new Map(), - profileRequests: new Map(), - teamColors: new Map(), - teamPalettes: new Map(), - playerTeams: new Map() - }; - const killFeedState = { - cache: new Map(), - lastFetched: new Map(), - renderSignatures: new Map(), - loading: false, - error: null - }; - let killFeedRefreshTimer = null; - - const f7State = { - serverId: null, - scope: 'new', - list: [], - loading: false, - error: null, - activeId: null, - detailCache: new Map(), - listRequestToken: null, - detailRequests: new Map(), - filter: '', - filterActive: false, - totalCount: 0, - visibleCount: 0, - focusTarget: null, - focusLabel: '' + var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); + var __objRest = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) + target[prop] = source[prop]; + } + return target; }; - let f7FilterTimer = null; - const f7ProfileCache = new Map(); - const f7ProfileRequests = new Map(); - - const aqState = { - serverId: null, - list: [], - loadingList: false, - listError: null, - selectedId: null, - detailCache: new Map(), - listRequestToken: null, - detailRequests: new Map(), - detailLoading: false, - detailError: null, - replying: false, - replyError: null - }; - - function emitWorkspaceEvent(name, detail) { - if (typeof window === 'undefined' || typeof window.dispatchEvent !== 'function') return; - try { - window.dispatchEvent(new CustomEvent(name, { detail })); - } catch { /* ignore */ } - } - - const moduleSlots = new Map(); - document.querySelectorAll('[data-module-card]').forEach((card) => { - const id = card?.dataset?.moduleCard; - if (!id) return; - const body = card.querySelector('[data-module-slot]'); - if (!body) return; - const header = card.querySelector('.card-header') || null; - const actions = card.querySelector('[data-module-actions]') || null; - const titleEl = header?.querySelector('[data-module-title]') || header?.querySelector('h3') || null; - moduleSlots.set(id, { card, body, header, actions, titleEl }); - }); - function setWorkspaceView(nextView = workspaceViewDefault) { - const visibleButtons = workspaceViewButtons.filter((btn) => !btn.classList.contains('permission-hidden') && !btn.disabled); - if (visibleButtons.length === 0) { - activeWorkspaceView = workspaceViewDefault; - workspaceViewSections.forEach((section) => { - section.classList.remove('active'); - section.setAttribute('aria-hidden', 'true'); - }); - return; - } - const available = new Set(visibleButtons.map((btn) => btn.dataset.view)); - let target = available.has(nextView) ? nextView : visibleButtons[0]?.dataset.view || workspaceViewDefault; - if (!available.has(target)) target = visibleButtons[0]?.dataset.view || workspaceViewDefault; - activeWorkspaceView = target; - workspaceViewButtons.forEach((btn) => { - const allowed = !btn.classList.contains('permission-hidden') && !btn.disabled; - const match = allowed && btn.dataset.view === target; - btn.classList.toggle('active', match); - btn.setAttribute('aria-pressed', match ? 'true' : 'false'); - }); - workspaceViewSections.forEach((section) => { - const allowed = !section.classList.contains('permission-hidden'); - const match = allowed && section.dataset.view === target; - section.classList.toggle('active', match); - section.setAttribute('aria-hidden', match ? 'false' : 'true'); - }); - if (target === 'settings') { - ensureDiscordStatusSelection(); - } - } - - workspaceViewButtons.forEach((btn) => { - btn.addEventListener('click', () => { - if (btn.disabled || btn.classList.contains('permission-hidden')) return; - setWorkspaceView(btn.dataset.view); - }); - }); - - chatFilterButtons.forEach((btn) => { - btn.addEventListener('click', () => { - if (btn.disabled) return; - setChatFilter(btn.dataset.channel || 'all'); - }); - }); - - f7ScopeButtons.forEach((btn) => { - btn.addEventListener('click', () => { - if (btn.disabled) return; - setF7Scope(btn.dataset.scope || 'new'); - }); - }); - - if (f7ReportShowAll) { - f7ReportShowAll.addEventListener('click', () => { - const detail = f7State.activeId != null ? f7State.detailCache.get(f7State.activeId) : null; - const targetSteamId = pickString(detail?.targetSteamId)?.trim(); - if (!targetSteamId) return; - const label = pickString(detail?.targetName) || targetSteamId; - if (f7State.focusTarget === targetSteamId) { - clearF7FocusTarget(); - } else { - focusF7ReportsOnTarget(targetSteamId, label); - } - if (f7ReportDetail?.scrollIntoView) { - try { f7ReportDetail.scrollIntoView({ behavior: 'smooth', block: 'start' }); } - catch { /* ignore */ } - } - }); - } - - if (f7ReportsClearFocus) { - f7ReportsClearFocus.addEventListener('click', () => { - clearF7FocusTarget(); - if (f7ReportDetail?.scrollIntoView) { - try { f7ReportDetail.scrollIntoView({ behavior: 'smooth', block: 'start' }); } - catch { /* ignore */ } - } - }); - } - - if (f7ReportsSearch) { - const commitFilter = () => { - const value = f7ReportsSearch.value || ''; - if (value === f7State.filter) return; - f7State.filter = value; - renderF7Reports(); - }; - const onSearchInput = () => { - if (f7FilterTimer) clearTimeout(f7FilterTimer); - f7FilterTimer = setTimeout(() => { - f7FilterTimer = null; - commitFilter(); - }, 200); + // frontend/assets/app.js + /* @__PURE__ */ (() => { + const whenTemplatesReady = async () => { + if (document.readyState === "loading") { + await new Promise((resolve) => document.addEventListener("DOMContentLoaded", resolve, { once: true })); + } + if (window.loadTemplatesPromise) { + try { + await window.loadTemplatesPromise; + } catch (err) { + console.error("Template load failed", err); + } + } }; - f7ReportsSearch.addEventListener('input', onSearchInput); - f7ReportsSearch.addEventListener('search', commitFilter); - } - - if (f7ReportOpenProfile) { - f7ReportOpenProfile.addEventListener('click', () => { - const detail = f7State.activeId != null ? f7State.detailCache.get(f7State.activeId) : null; - const payload = buildF7PlayerPayload(detail); - if (!payload?.steamid) return; - window.dispatchEvent(new CustomEvent('players:open-profile', { - detail: { - steamId: payload.steamid, - player: payload, - source: 'f7-reports' - } - })); - }); - } - - if (workspaceViewSections.length) { - setWorkspaceView(workspaceViewDefault); - } - - function normalizeChatFilter(value) { - const text = typeof value === 'string' ? value.trim().toLowerCase() : ''; - if (text === 'team') return 'team'; - if (text === 'global') return 'global'; - return 'all'; - } - - function normalizeChatChannel(value) { - if (typeof value === 'number') { - return value === 1 ? 'team' : 'global'; - } - if (typeof value === 'string') { - const trimmed = value.trim(); - if (!trimmed) return 'global'; - const numeric = Number(trimmed); - if (Number.isFinite(numeric)) return numeric === 1 ? 'team' : 'global'; - const lower = trimmed.toLowerCase(); - if (lower === 'team') return 'team'; - if (lower === 'global') return 'global'; - if (lower === '1') return 'team'; - if (lower === '0') return 'global'; - } - return 'global'; - } - - function normalizeChatColor(value) { - const text = typeof value === 'string' ? value.trim() : ''; - if (!text) return null; - const hexMatch = text.match(/^#?([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i); - if (hexMatch) { - return `#${hexMatch[1].toLowerCase()}`; - } - const compact = text.replace(/\s+/g, ''); - const rgbMatch = compact.match(/^rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3})(?:,(0|1|0?\.\d+))?\)$/i); - if (rgbMatch) { - const numeric = rgbMatch.slice(1, 4).map((part) => { - const parsed = Number(part); - return Number.isFinite(parsed) ? parsed : null; - }); - if (numeric.some((part) => part == null || part < 0 || part > 255)) return null; - const alphaRaw = rgbMatch[4]; - if (typeof alphaRaw === 'string') { - const alpha = Number(alphaRaw); - if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) return null; - return `rgba(${numeric[0]}, ${numeric[1]}, ${numeric[2]}, ${alpha})`; - } - return `rgb(${numeric[0]}, ${numeric[1]}, ${numeric[2]})`; - } - return null; - } - - function parseCssColor(value) { - if (typeof value !== 'string') return null; - const trimmed = value.trim(); - if (!trimmed) return null; - const hexMatch = trimmed.match(/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i); - if (hexMatch) { - const hex = hexMatch[1]; - if (hex.length === 3 || hex.length === 4) { - const r = parseInt(hex[0] + hex[0], 16); - const g = parseInt(hex[1] + hex[1], 16); - const b = parseInt(hex[2] + hex[2], 16); - const a = hex.length === 4 ? parseInt(hex[3] + hex[3], 16) / 255 : 1; - return { r, g, b, a }; - } - if (hex.length === 6 || hex.length === 8) { - const r = parseInt(hex.slice(0, 2), 16); - const g = parseInt(hex.slice(2, 4), 16); - const b = parseInt(hex.slice(4, 6), 16); - const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1; - return { r, g, b, a }; - } - } - const rgbMatch = trimmed.match(/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(0|1|0?\.\d+))?\s*\)$/i); - if (rgbMatch) { - const r = Number(rgbMatch[1]); - const g = Number(rgbMatch[2]); - const b = Number(rgbMatch[3]); - const a = rgbMatch[4] != null ? Number(rgbMatch[4]) : 1; - if ([r, g, b].some((n) => !Number.isFinite(n) || n < 0 || n > 255)) return null; - if (!Number.isFinite(a) || a < 0 || a > 1) return null; - return { r, g, b, a }; - } - return null; - } - - function rgbaFromColor(value, alpha = 1) { - const parsed = parseCssColor(value) || parseCssColor(DEFAULT_TEAM_CHAT_COLOR); - if (!parsed) return null; - const clampChannel = (n) => Math.max(0, Math.min(255, Math.round(n))); - const clampAlpha = (n) => Math.max(0, Math.min(1, Number.isFinite(n) ? n : 1)); - const a = Math.round(clampAlpha(alpha) * 1000) / 1000; - return `rgba(${clampChannel(parsed.r)}, ${clampChannel(parsed.g)}, ${clampChannel(parsed.b)}, ${a})`; - } - - function findLatestTeamColor(entries) { - if (!Array.isArray(entries)) return null; - for (let i = entries.length - 1; i >= 0; i -= 1) { - const entry = entries[i]; - if (!entry) continue; - if (entry.channel === 'team' && typeof entry.color === 'string' && entry.color) { - return entry.color; - } - } - return null; - } - - function getTeamChatColor(serverId, fallback = null) { - const numeric = Number(serverId); - if (!Number.isFinite(numeric)) { - return fallback || DEFAULT_TEAM_CHAT_COLOR; - } - if (chatState.teamColors.has(numeric)) { - return chatState.teamColors.get(numeric); - } - if (fallback) { - chatState.teamColors.set(numeric, fallback); - return fallback; - } - return DEFAULT_TEAM_CHAT_COLOR; - } - - function normalizeServerId(value) { - const numeric = Number(value); - return Number.isFinite(numeric) ? numeric : null; - } - - function normalizeTeamId(value) { - const numeric = Number(value); - return Number.isFinite(numeric) && numeric > 0 ? numeric : null; - } - - function resolvePlayerSteamIdForMapping(player) { - if (!player || typeof player !== 'object') return ''; - const candidates = [ - player.steamId, - player.steamid, - player.SteamID, - player.SteamId, - player.userid, - player.userId, - player.UserId, - player.id, - player.ID, - player.entityId, - player.EntityId - ]; - for (const candidate of candidates) { - if (candidate == null) continue; - const value = String(candidate).trim(); - if (value) return value; - } - return ''; - } - - function resolvePlayerTeamIdForMapping(player) { - if (!player || typeof player !== 'object') return null; - const candidates = [ - player.teamId, - player.TeamId, - player.teamID, - player.TeamID, - player.team, - player.Team - ]; - for (const candidate of candidates) { - const teamId = normalizeTeamId(candidate); - if (teamId != null) return teamId; - } - return null; - } - - function assignMapIfChanged(storage, key, nextMap) { - const previous = storage.get(key); - if (previous && previous.size === nextMap.size) { - let identical = true; - for (const [entryKey, entryValue] of nextMap.entries()) { - if (previous.get(entryKey) !== entryValue) { - identical = false; - break; - } - } - if (identical) { - for (const entryKey of previous.keys()) { - if (!nextMap.has(entryKey)) { - identical = false; - break; + const start = () => { + var _a, _b; + const $ = (sel) => document.querySelector(sel); + const serversEl = $("#servers"); + const consoleEl = $("#console"); + const loginPanel = $("#loginPanel"); + const appPanel = $("#appPanel"); + const userBox = $("#userBox"); + const mainNav = $("#mainNav"); + const navDashboard = $("#navDashboard"); + const navLinked = $("#navLinked"); + const navTeam = $("#navTeam"); + const navAdmin = $("#navAdmin"); + const navDiscord = $("#navDiscord"); + const navSettings = $("#navSettings"); + const teamSwitcher = $("#teamSwitcher"); + const teamSelect = $("#teamSelect"); + const teamSelectLabel = $("#teamSelectLabel"); + const dashboardPanel = $("#dashboardPanel"); + const linkedAccountsPanel = $("#linkedAccountsPanel"); + const teamPanel = $("#teamPanel"); + const adminPanel = $("#adminPanel"); + const discordPanel = $("#discordPanel"); + const workspacePanel = $("#workspacePanel"); + const settingsPanel = $("#settingsPanel"); + const loginError = $("#loginError"); + const registerInfo = $("#registerInfo"); + const registerForm = $("#registerForm"); + const registerError = $("#registerError"); + const registerSuccess = $("#registerSuccess"); + const btnRegister = $("#btnRegister"); + const userCard = $("#userCard"); + const userList = $("#userList"); + const userFeedback = $("#userFeedback"); + const adminUserList = $("#adminUserList"); + const adminUserEmpty = $("#adminUserEmpty"); + const adminUserDetails = $("#adminUserDetails"); + const adminDetailUsername = $("#adminDetailUsername"); + const adminDetailCreated = $("#adminDetailCreated"); + const adminUserRoleSelect = $("#adminUserRoleSelect"); + const adminUserSaveRole = $("#adminUserSaveRole"); + const adminUserRoleStatus = $("#adminUserRoleStatus"); + const adminUserResetPassword = $("#adminUserResetPassword"); + const adminUserDelete = $("#adminUserDelete"); + const adminUserDangerStatus = $("#adminUserDangerStatus"); + const adminUserTeams = $("#adminUserTeams"); + const adminAssignTeam = $("#adminAssignTeam"); + const adminAssignRole = $("#adminAssignRole"); + const adminUserSuperuser = $("#adminUserSuperuser"); + const btnAdminAssignTeam = $("#btnAdminAssignTeam"); + const adminAssignStatus = $("#adminAssignStatus"); + const adminNewUserName = $("#adminNewUserName"); + const adminNewUserPassword = $("#adminNewUserPassword"); + const adminNewUserRole = $("#adminNewUserRole"); + const adminNewUserTeam = $("#adminNewUserTeam"); + const adminNewUserSuperuser = $("#adminNewUserSuperuser"); + const btnAdminCreateUser = $("#btnAdminCreateUser"); + const adminUserFeedback = $("#adminUserFeedback"); + const adminTeamList = $("#adminTeamList"); + const adminUserSearch = $("#adminUserSearch"); + const adminUserCount = $("#adminUserCount"); + const adminSuperuserCount = $("#adminSuperuserCount"); + const adminOrgCount = $("#adminOrgCount"); + const adminScopeBadge = $("#adminScopeBadge"); + const btnRefreshServers = $("#btnRefreshServers"); + const btnClearConsole = $("#btnClearConsole"); + const btnAddServer = $("#btnAddServer"); + const addServerPrompt = $("#addServerPrompt"); + const svName = $("#svName"); + const svHost = $("#svHost"); + const svPort = $("#svPort"); + const svPass = $("#svPass"); + const svTLS = $("#svTLS"); + const btnSend = $("#btnSend"); + const cmdInput = $("#cmd"); + const cmdInputDefaultPlaceholder = (cmdInput == null ? void 0 : cmdInput.getAttribute("placeholder")) || ""; + const DISCORD_ALLOWED_PRESENCES = /* @__PURE__ */ new Set(["online", "idle", "dnd", "invisible"]); + const DISCORD_PRESENCE_LABELS = { + online: "Online", + idle: "Idle", + dnd: "Do Not Disturb", + invisible: "Invisible" + }; + const DISCORD_PRESENCE_CLASS = { + online: "online", + idle: "degraded", + dnd: "offline", + invisible: "offline" + }; + const DEFAULT_DISCORD_PRESENCE_TEMPLATE = "{statusEmoji} {playerCount} on {serverName}"; + const DEFAULT_DISCORD_PRESENCE_STATUSES = { + online: "online", + offline: "dnd", + stale: "idle", + waiting: "idle" + }; + const DEFAULT_DISCORD_STATUS_FIELDS = { + joining: true, + queued: true, + sleepers: true, + fps: true, + lastUpdate: true + }; + const DEFAULT_DISCORD_TICKETING_CONFIG = { + enabled: false, + categoryId: "", + logChannelId: "", + staffRoleId: "", + pingStaffOnOpen: true, + panelChannelId: "", + panelMessageId: "" + }; + const DEFAULT_DISCORD_COMMAND_PERMISSIONS = { + status: [], + ticket: [], + rustlookup: [], + auth: [] + }; + const DISCORD_STATUS_FIELD_LABELS = { + joining: "Joining players", + queued: "Queue length", + sleepers: "Sleeping players", + fps: "Server FPS", + lastUpdate: "Last update timestamp" + }; + const logMfa = (message, details = {}) => { + try { + const safeDetails = __spreadValues({}, details); + if (safeDetails.secret) delete safeDetails.secret; + if (Array.isArray(safeDetails.backupCodes)) { + safeDetails.backupCodes = `(${safeDetails.backupCodes.length} codes hidden)`; } + console.info("[mfa]", message, safeDetails); + } catch (err) { + console.warn("[mfa] log failed", err); + } + }; + logMfa("browser logger ready", { + page: typeof window !== "undefined" ? (_a = window.location) == null ? void 0 : _a.href : "(no-window)" + }); + const superuserUi = false; + const defaultPanel = typeof window !== "undefined" && window.DEFAULT_PANEL ? String(window.DEFAULT_PANEL) : "dashboard"; + const state = { + API: "", + TOKEN: localStorage.getItem("token") || "", + currentUser: null, + currentServerId: null, + serverItems: /* @__PURE__ */ new Map(), + allowRegistration: false, + statusTimer: null, + settings: {}, + security: { + totpEnabled: false, + passkeys: [], + pendingSecret: null, + backupCodes: [], + backupCodesRemaining: 0, + loaded: false, + lastLoadedAt: null + }, + activePanel: defaultPanel, + activeTeamId: null, + activeTeamName: null, + teams: [], + roles: [], + roleTemplates: { serverCapabilities: [], globalPermissions: [] }, + teamDiscord: { + hasToken: false, + guildId: null, + tokenPreview: null, + loading: false, + loadedTeamId: null, + config: defaultTeamDiscordConfig(), + configLoaded: false + }, + teamDiscordRoles: defaultTeamDiscordRolesState(), + teamDiscordChannels: defaultTeamDiscordChannelsState(), + teamAuth: { loading: false, enabled: false, roleId: null, logChannelId: null, loaded: false, loadedTeamId: null }, + workspaceDiscord: { + serverId: null, + loading: false, + saving: false, + error: null, + integration: null, + status: null, + config: defaultWorkspaceDiscordConfig() + }, + pendingMfa: null, + admin: { + users: [], + teams: [], + selectedUserId: null, + loading: false + }, + superuserUi + }; + let adminUserFilter = ""; + const loginUsername = $("#username"); + const loginPassword = $("#password"); + const loginMfaStep = $("#mfaStep"); + const loginMfaCode = $("#loginMfaCode"); + const loginMfaStatus = $("#loginMfaStatus"); + const btnSubmitMfa = $("#btnSubmitMfa"); + const btnMfaUsePasskey = $("#btnMfaUsePasskey"); + const btnLogin = $("#btnLogin"); + const regUsername = $("#regUsername"); + const regPassword = $("#regPassword"); + const regConfirm = $("#regConfirm"); + const userCreateSection = $("#userCreateSection"); + const newUserName = $("#newUserName"); + const newUserPassword = $("#newUserPassword"); + const newUserRole = $("#newUserRole"); + const btnCreateUser = $("#btnCreateUser"); + const existingUserName = $("#existingUserName"); + const existingUserRole = $("#existingUserRole"); + const btnAddExistingUser = $("#btnAddExistingUser"); + const existingUserFeedback = $("#existingUserFeedback"); + const roleManager = $("#roleManager"); + const roleEditor = $("#roleEditor"); + const rolesHeader = $("#rolesHeader"); + const rolesDescription = $("#rolesDescription"); + const rolesSection = (roleManager == null ? void 0 : roleManager.closest(".team-section")) || null; + const teamDiscordSection = $("#teamDiscordSection"); + const teamDiscordStatusSection = $("#teamDiscordStatusSection"); + const teamDiscordForm = $("#teamDiscordForm"); + const teamDiscordGuildId = $("#teamDiscordGuildId"); + const teamDiscordToken = $("#teamDiscordToken"); + const btnSaveTeamDiscord = $("#btnSaveTeamDiscord"); + const btnRemoveTeamDiscord = $("#btnRemoveTeamDiscord"); + const teamDiscordStatus = $("#teamDiscordStatus"); + const teamDiscordSummary = $("#teamDiscordSummary"); + const teamDiscordSummaryGuild = $("#teamDiscordSummaryGuild"); + const teamDiscordSummaryToken = $("#teamDiscordSummaryToken"); + const teamOAuthForm = $("#teamOAuthForm"); + const teamDiscordOauthClientId = $("#teamDiscordOauthClientId"); + const teamDiscordOauthClientSecret = $("#teamDiscordOauthClientSecret"); + const teamDiscordOauthRedirectUri = $("#teamDiscordOauthRedirectUri"); + const teamSteamApiKeyInput = $("#teamSteamApiKey"); + const teamOAuthStatus = $("#teamOAuthStatus"); + const btnSaveTeamOAuth = $("#btnSaveTeamOAuth"); + const btnClearTeamOAuth = $("#btnClearTeamOAuth"); + const teamAuthSection = $("#teamAuthSection"); + const teamAuthForm = $("#teamAuthForm"); + const teamAuthEnabledInput = $("#teamAuthEnabled"); + const teamAuthRoleInput = $("#teamAuthRole"); + const teamAuthRoleSelect = $("#teamAuthRoleSelect"); + const teamAuthLogChannelInput = $("#teamAuthLogChannel"); + const teamAuthLogChannelSelect = $("#teamAuthLogChannelSelect"); + const teamAuthStatus = $("#teamAuthStatus"); + const btnSaveTeamAuth = $("#teamAuthSave"); + const teamTicketingSection = $("#teamTicketingSection"); + const teamTicketingForm = $("#teamTicketingForm"); + const teamTicketingStatus = $("#teamTicketingStatus"); + const roleSelect = $("#roleSelect"); + const roleNameInput = $("#roleName"); + const roleDescriptionInput = $("#roleDescription"); + const roleServersList = $("#roleServersList"); + const roleCapabilitiesContainer = $("#roleCapabilities"); + const roleGlobalContainer = $("#roleGlobalPermissions"); + const newRoleKey = $("#newRoleKey"); + const newRoleName = $("#newRoleName"); + const btnCreateRole = $("#btnCreateRole"); + const btnSaveRole = $("#btnSaveRole"); + const btnDeleteRole = $("#btnDeleteRole"); + const roleFeedback = $("#roleFeedback"); + const roleEditorEmpty = $("#roleEditorEmpty"); + const roleLockedNotice = $("#roleLockedNotice"); + const quickCommandsEl = $("#quickCommands"); + const rustMapsKeyInput = $("#rustMapsKey"); + const btnSaveSettings = $("#btnSaveSettings"); + const settingsStatus = $("#settingsStatus"); + const currentPasswordInput = $("#currentPassword"); + const newPasswordInput = $("#newPassword"); + const confirmPasswordInput = $("#confirmPassword"); + const btnChangePassword = $("#btnChangePassword"); + const passwordStatus = $("#passwordStatus"); + const mfaStatusLabel = $("#mfaStatus"); + const totpSecretValue = $("#totpSecret"); + const totpQrImage = $("#totpQr"); + const btnCopyTotpSecret = $("#btnCopyTotpSecret"); + const backupCodesList = $("#backupCodesList"); + const backupCodesInfo = $("#backupCodesInfo"); + const btnGenerateBackupCodes = $("#btnGenerateBackupCodes"); + const totpSetupFields = $("#totpSetupFields"); + const totpCodeInput = $("#totpCode"); + const btnStartTotp = $("#btnStartTotp"); + const btnEnableTotp = $("#btnEnableTotp"); + const btnDisableTotp = $("#btnDisableTotp"); + const mfaStatusMessage = $("#mfaStatusMessage"); + const passkeyList = $("#passkeyList"); + const btnRegisterPasskey = $("#btnRegisterPasskey"); + const passkeyStatus = $("#passkeyStatus"); + const serversEmpty = $("#serversEmpty"); + const addServerCard = $("#addServerCard"); + const welcomeName = $("#welcomeName"); + const profileMenuTrigger = $("#profileMenuTrigger"); + const brandAccount = $(".brand-account"); + const workspaceName = $("#workspaceServerName"); + const workspaceMeta = $("#workspaceServerMeta"); + const workspaceStatus = $("#workspaceStatus"); + const workspacePlayers = $("#workspacePlayers"); + const workspaceQueue = $("#workspaceQueue"); + const workspaceJoining = $("#workspaceJoining"); + const workspaceMenu = $("#workspaceMenu"); + const workspaceInfoPlayers = $("#workspaceInfoPlayers"); + const workspaceInfoMaxPlayers = $("#workspaceInfoMaxPlayers"); + const workspaceInfoQueue = $("#workspaceInfoQueue"); + const workspaceInfoJoining = $("#workspaceInfoJoining"); + const workspaceInfoGameTime = $("#workspaceInfoGameTime"); + const workspaceInfoUptime = $("#workspaceInfoUptime"); + const workspaceInfoFramerate = $("#workspaceInfoFramerate"); + const workspaceInfoNetworkIn = $("#workspaceInfoNetworkIn"); + const workspaceInfoNetworkOut = $("#workspaceInfoNetworkOut"); + const workspaceInfoSaveCreatedTime = $("#workspaceInfoSaveCreatedTime"); + const workspaceChatBody = $("#workspaceChatBody"); + const workspaceChatList = $("#workspaceChatList"); + const workspaceChatEmpty = $("#workspaceChatEmpty"); + const workspaceChatLoading = $("#workspaceChatLoading"); + const workspaceChatNotice = $("#workspaceChatNotice"); + const discordBotStatusPill = $("#discord-bot-status"); + const discordConnectionGuildInput = $("#discord-connection-guild-input"); + const discordConnectionChannelInput = $("#discord-connection-channel-input"); + const discordConnectionTokenInput = $("#discord-connection-token-input"); + const discordConnectionStatus = $("#discord-connection-status"); + const discordStatusServerSelect = null; + const discordSettingsGrid = $("#discord-settings"); + const discordCurrentPlayers = $("#discord-current-players"); + const discordMaxPlayers = $("#discord-max-players"); + const discordJoiningPlayers = $("#discord-joining"); + const discordLastCheck = $("#discord-last-check"); + const discordPresenceTemplateSummary = $("#discord-presence-template"); + const discordPresenceStatusOnline = $("#discord-presence-status-online"); + const discordPresenceStatusOffline = $("#discord-presence-status-offline"); + const discordPresenceStatusStale = $("#discord-presence-status-stale"); + const discordPresenceStatusWaiting = $("#discord-presence-status-waiting"); + const discordEnabledFieldsList = $("#discord-enabled-fields"); + const discordEnabledFieldsEmpty = $("#discord-enabled-fields-empty"); + const discordTicketingSummaryStatus = $("#discord-ticketing-summary-status"); + const discordTicketingSummaryCategory = $("#discord-ticketing-summary-category"); + const discordTicketingSummaryLog = $("#discord-ticketing-summary-log"); + const discordTicketingSummaryRole = $("#discord-ticketing-summary-role"); + const discordStatusForm = $("#discord-status-form"); + const discordStatusNotice = $("#discord-status-notice"); + const discordPresenceTemplateInput = $("#discord-presence-template-input"); + const discordPresenceOnlineSelect = $("#discord-presence-online"); + const discordPresenceOfflineSelect = $("#discord-presence-offline"); + const discordPresenceStaleSelect = $("#discord-presence-stale"); + const discordPresenceWaitingSelect = $("#discord-presence-waiting"); + const discordStatusFieldInputs = Array.from(document.querySelectorAll("[data-status-field]")); + const discordTicketingEnabledInput = $("#discord-ticketing-enabled"); + const discordTicketingCategoryInput = $("#discord-ticketing-category"); + const discordTicketingLogInput = $("#discord-ticketing-log"); + const discordTicketingRoleInput = $("#discord-ticketing-role"); + const discordTicketingPingInput = $("#discord-ticketing-ping"); + const discordTicketingPanelChannelInput = $("#discord-ticketing-panel-channel"); + const discordTicketingPanelMessageInput = $("#discord-ticketing-panel-message"); + const discordTicketingCategorySelect = $("#discord-ticketing-category-select"); + const discordTicketingLogSelect = $("#discord-ticketing-log-select"); + const discordTicketingRoleSelect = $("#discord-ticketing-role-select"); + const discordTicketingPanelChannelSelect = $("#discord-ticketing-panel-channel-select"); + const discordCategoryOptions = $("#discord-category-options"); + const discordChannelOptions = $("#discord-channel-options"); + const discordRoleOptions = $("#discord-role-options"); + const teamCommandStatusInput = $("#team-command-status-role-input"); + const teamCommandTicketInput = $("#team-command-ticket-role-input"); + const teamCommandLookupInput = $("#team-command-lookup-role-input"); + const teamCommandAuthInput = $("#team-command-auth-role-input"); + const teamCommandStatusSelect = $("#team-command-status-role-select"); + const teamCommandTicketSelect = $("#team-command-ticket-role-select"); + const teamCommandLookupSelect = $("#team-command-lookup-role-select"); + const teamCommandAuthSelect = $("#team-command-auth-role-select"); + const teamCommandStatusList = $("#team-command-status-role-list"); + const teamCommandTicketList = $("#team-command-ticket-role-list"); + const teamCommandLookupList = $("#team-command-lookup-role-list"); + const teamCommandAuthList = $("#team-command-auth-role-list"); + const teamCommandSummaryStatus = $("#team-command-summary-status"); + const teamCommandSummaryTicket = $("#team-command-summary-ticket"); + const teamCommandSummaryLookup = $("#team-command-summary-lookup"); + const teamCommandSummaryAuth = $("#team-command-summary-auth"); + const teamCommandRolesStatus = $("#teamCommandRolesStatus"); + const btnRefreshTeamCommandRoles = $("#team-command-refresh-roles"); + const teamDiscordChannelsStatus = $("#teamDiscordChannelsStatus"); + const btnRefreshTeamDiscordChannels = $("#team-discord-refresh-channels"); + const aqTicketsList = $("#aqTicketsList"); + const aqTicketsLoading = $("#aqTicketsLoading"); + const aqTicketsError = $("#aqTicketsError"); + const aqTicketsEmpty = $("#aqTicketsEmpty"); + const aqTicketsCount = $("#aqTicketsCount"); + const aqTicketPlaceholder = $("#aqTicketPlaceholder"); + const aqTicketDetail = $("#aqTicketDetail"); + const aqTicketSubject = $("#aqTicketSubject"); + const aqTicketMeta = $("#aqTicketMeta"); + const aqTicketNumber = $("#aqTicketNumber"); + const aqTicketPreviewLink = $("#aqTicketPreviewLink"); + const aqTicketDialog = $("#aqTicketDialog"); + const aqTicketDialogLoading = $("#aqTicketDialogLoading"); + const aqTicketDialogEmpty = $("#aqTicketDialogEmpty"); + const aqTicketDialogError = $("#aqTicketDialogError"); + const aqTicketReply = $("#aqTicketReply"); + const aqTicketReplyForm = $("#aqTicketReplyForm"); + const aqTicketReplyInput = $("#aqTicketReplyInput"); + const aqTicketReplySubmit = $("#aqTicketReplySubmit"); + const aqTicketReplyError = $("#aqTicketReplyError"); + const aqTicketReplyNotice = $("#aqTicketReplyNotice"); + const f7ReportsList = $("#f7ReportsList"); + const f7ReportsLoading = $("#f7ReportsLoading"); + const f7ReportsEmpty = $("#f7ReportsEmpty"); + const f7ReportsError = $("#f7ReportsError"); + const f7ReportsCount = $("#f7ReportsCount"); + const f7ReportsSearch = $("#f7ReportsSearch"); + const f7ReportPlaceholder = $("#f7ReportPlaceholder"); + const f7ReportDetail = $("#f7ReportDetail"); + const f7ReportDetailTime = $("#f7ReportDetailTime"); + const f7ReportTarget = $("#f7ReportTarget"); + const f7ReportReporter = $("#f7ReportReporter"); + const f7ReportCategory = $("#f7ReportCategory"); + const f7ReportId = $("#f7ReportId"); + const f7ReportMessage = $("#f7ReportMessage"); + const f7ReportTargetSummary = $("#f7ReportTargetSummary"); + const f7ReportSummaryTotal = $("#f7ReportSummaryTotal"); + const f7ReportSummaryRecent = $("#f7ReportSummaryRecent"); + const f7ReportSummaryRecentLabel = $("#f7ReportSummaryRecentLabel"); + const f7ReportSummaryReporters = $("#f7ReportSummaryReporters"); + const f7ReportSummaryFirst = $("#f7ReportSummaryFirst"); + const f7ReportSummaryLast = $("#f7ReportSummaryLast"); + const f7ReportSummaryCategories = $("#f7ReportSummaryCategories"); + const f7ReportTargetProfile = $("#f7ReportTargetProfile"); + const f7ReportProfileAvatar = $("#f7ReportProfileAvatar"); + const f7ReportProfileName = $("#f7ReportProfileName"); + const f7ReportProfileSteam = $("#f7ReportProfileSteam"); + const f7ReportProfileCountry = $("#f7ReportProfileCountry"); + const f7ReportProfileVac = $("#f7ReportProfileVac"); + const f7ReportProfileGameBans = $("#f7ReportProfileGameBans"); + const f7ReportProfileLastBan = $("#f7ReportProfileLastBan"); + const f7ReportProfilePlaytime = $("#f7ReportProfilePlaytime"); + const f7ReportProfileStatus = $("#f7ReportProfileStatus"); + const f7ReportHistory = $("#f7ReportHistory"); + const f7ReportHistoryList = $("#f7ReportHistoryList"); + const f7ReportsFocus = $("#f7ReportsFocus"); + const f7ReportsFocusLabel = $("#f7ReportsFocusLabel"); + const f7ReportsClearFocus = $("#f7ReportsClearFocus"); + const f7ReportShowAll = $("#f7ReportShowAll"); + const f7ReportOpenProfile = $("#f7ReportOpenProfile"); + const f7ScopeButtons = Array.from(document.querySelectorAll(".reports-scope-btn")); + const regionDisplay = typeof Intl !== "undefined" && typeof Intl.DisplayNames === "function" ? new Intl.DisplayNames(["en"], { type: "region" }) : null; + const COUNTRY_FALLBACKS = { UK: "United Kingdom", EU: "European Union" }; + const killFeedList = $("#killFeedList"); + const killFeedEmpty = $("#killFeedEmpty"); + const killFeedLoading = $("#killFeedLoading"); + const killFeedNotice = $("#killFeedNotice"); + const btnBackToDashboard = $("#btnBackToDashboard"); + const profileUsername = $("#profileUsername"); + const profileRole = $("#profileRole"); + const moduleFallback = $("#moduleFallback"); + const userDetailsOverlay = $("#userDetailsOverlay"); + const userDetailsPanel = $("#userDetailsPanel"); + const userDetailsClose = $("#userDetailsClose"); + const userDetailsName = $("#userDetailsName"); + const userDetailsSubtitle = $("#userDetailsSubtitle"); + const userDetailsRole = $("#userDetailsRole"); + const userDetailsRoleBadge = $("#userDetailsRoleBadge"); + const userDetailsCreated = $("#userDetailsCreated"); + const userDetailsId = $("#userDetailsId"); + const userDetailsRoleSelect = $("#userDetailsRoleSelect"); + const userDetailsSaveRole = $("#userDetailsSaveRole"); + const userDetailsResetPassword = $("#userDetailsResetPassword"); + const userDetailsDelete = $("#userDetailsDelete"); + const userDetailsSelfNotice = $("#userDetailsSelfNotice"); + const userDetailsRoleStatus = $("#userDetailsRoleStatus"); + const dialogOverlay = $("#dialogOverlay"); + const dialogModal = $("#dialogModal"); + const dialogTitle = $("#dialogTitle"); + const dialogMessage = $("#dialogMessage"); + const dialogInputWrap = $("#dialogInputWrap"); + const dialogInputLabel = $("#dialogInputLabel"); + const dialogInput = $("#dialogInput"); + const dialogError = $("#dialogError"); + const dialogConfirmBtn = $("#dialogConfirm"); + const dialogCancelBtn = $("#dialogCancel"); + const workspaceViewSections = Array.from(document.querySelectorAll(".workspace-view")); + const workspaceViewSectionMap = new Map(workspaceViewSections.map((section) => [section.dataset.view, section])); + const workspaceViewButtons = workspaceMenu ? Array.from(workspaceMenu.querySelectorAll(".menu-tab")) : []; + const chatFilterButtons = Array.from(document.querySelectorAll(".chat-filter-btn")); + const CHAT_REFRESH_INTERVAL_MS = 5e3; + const KILL_FEED_REFRESH_INTERVAL_MS = 3e4; + const KILL_FEED_RETENTION_MS = 24 * 60 * 60 * 1e3; + const DEFAULT_TEAM_CHAT_COLOR = "#3b82f6"; + const workspaceViewDefault = "players"; + let activeWorkspaceView = workspaceViewDefault; + const chatState = { + filter: "all", + cache: /* @__PURE__ */ new Map(), + lastFetched: /* @__PURE__ */ new Map(), + loading: false, + error: null, + profileCache: /* @__PURE__ */ new Map(), + profileRequests: /* @__PURE__ */ new Map(), + teamColors: /* @__PURE__ */ new Map(), + teamPalettes: /* @__PURE__ */ new Map(), + playerTeams: /* @__PURE__ */ new Map() + }; + const killFeedState = { + cache: /* @__PURE__ */ new Map(), + lastFetched: /* @__PURE__ */ new Map(), + renderSignatures: /* @__PURE__ */ new Map(), + loading: false, + error: null + }; + let killFeedRefreshTimer = null; + const f7State = { + serverId: null, + scope: "new", + list: [], + loading: false, + error: null, + activeId: null, + detailCache: /* @__PURE__ */ new Map(), + listRequestToken: null, + detailRequests: /* @__PURE__ */ new Map(), + filter: "", + filterActive: false, + totalCount: 0, + visibleCount: 0, + focusTarget: null, + focusLabel: "" + }; + let f7FilterTimer = null; + const f7ProfileCache = /* @__PURE__ */ new Map(); + const f7ProfileRequests = /* @__PURE__ */ new Map(); + const aqState = { + serverId: null, + list: [], + loadingList: false, + listError: null, + selectedId: null, + detailCache: /* @__PURE__ */ new Map(), + listRequestToken: null, + detailRequests: /* @__PURE__ */ new Map(), + detailLoading: false, + detailError: null, + replying: false, + replyError: null + }; + function emitWorkspaceEvent(name, detail) { + if (typeof window === "undefined" || typeof window.dispatchEvent !== "function") return; + try { + window.dispatchEvent(new CustomEvent(name, { detail })); + } catch (err) { } } - if (identical) return false; - } - storage.set(key, nextMap); - return true; - } - - function updatePlayerTeamMapping(serverId, players) { - const numeric = Number(serverId); - if (!Number.isFinite(numeric)) return false; - const next = new Map(); - for (const player of Array.isArray(players) ? players : []) { - const steamId = resolvePlayerSteamIdForMapping(player); - if (!steamId) continue; - const teamId = resolvePlayerTeamIdForMapping(player); - if (teamId == null) continue; - next.set(steamId, teamId); - } - return assignMapIfChanged(chatState.playerTeams, numeric, next); - } - - function updateServerTeamPalette(serverId, colors) { - const numeric = Number(serverId); - if (!Number.isFinite(numeric)) return false; - const next = new Map(); - for (const entry of Array.isArray(colors) ? colors : []) { - const teamId = normalizeTeamId(entry?.teamId ?? entry?.team_id ?? entry?.id); - const color = pickString(entry?.color ?? entry?.value ?? entry?.hex); - if (teamId == null || !color) continue; - next.set(teamId, color); - } - return assignMapIfChanged(chatState.teamPalettes, numeric, next); - } - - const TEAM_ID_PATTERNS = [ - /\bteam\s*(?:chat)?\s*(?:#|id[:=])\s*(\d+)\b/i, - /\bteam\s*(\d+)\b/i - ]; - - function parseTeamIdFromText(text) { - if (typeof text !== 'string') return null; - for (const pattern of TEAM_ID_PATTERNS) { - const match = text.match(pattern); - if (match && match[1]) { - const teamId = normalizeTeamId(match[1]); - if (teamId != null) return teamId; - } - } - return null; - } - - function resolveTeamIdForEntry(serverId, entry) { - if (!entry || entry.channel !== 'team') return null; - const numericServerId = Number(serverId); - const entrySteamId = pickString(entry.steamId); - if (entrySteamId && Number.isFinite(numericServerId)) { - const teamMap = chatState.playerTeams.get(numericServerId); - const mapped = teamMap?.get(entrySteamId); - if (mapped != null) return mapped; - } - const sources = []; - if (entry.raw) sources.push(entry.raw); - if (entry.message) sources.push(entry.message); - for (const source of sources) { - const teamId = parseTeamIdFromText(source); - if (teamId != null) return teamId; - } - return null; - } - - function resolveTeamMessageColor(serverId, entry) { - const numericServerId = Number(serverId); - const baseColor = getTeamChatColor(numericServerId, entry?.color); - if (!entry || entry.channel !== 'team') { - return pickString(baseColor, DEFAULT_TEAM_CHAT_COLOR); - } - const teamId = resolveTeamIdForEntry(numericServerId, entry); - if (teamId != null) { - const palette = chatState.teamPalettes.get(numericServerId); - const paletteColor = palette?.get(teamId); - const normalized = pickString(paletteColor); - if (normalized) return normalized; - } - const direct = pickString(entry?.color); - return pickString(direct, baseColor, DEFAULT_TEAM_CHAT_COLOR); - } - - function handlePlayersListUpdate(payload = {}) { - const players = Array.isArray(payload.players) ? payload.players : []; - const explicitServerId = payload.serverId; - let serverId = normalizeServerId(explicitServerId); - if (serverId == null && explicitServerId === undefined) { - serverId = normalizeServerId(state.currentServerId); - } - if (serverId == null) return; - const changed = updatePlayerTeamMapping(serverId, players); - const currentServerId = normalizeServerId(state.currentServerId); - if (changed && currentServerId != null && currentServerId === serverId) { - renderChatMessages(); - } - } - - function handleTeamColorsUpdate(payload = {}) { - const colors = Array.isArray(payload.colors) ? payload.colors : []; - const explicitServerId = payload.serverId; - let serverId = normalizeServerId(explicitServerId); - if (serverId == null && explicitServerId === undefined) { - serverId = normalizeServerId(state.currentServerId); - } - if (serverId == null) return; - const changed = updateServerTeamPalette(serverId, colors); - const currentServerId = normalizeServerId(state.currentServerId); - if (changed && currentServerId != null && currentServerId === serverId) { - renderChatMessages(); - } - } - - function messageSignature(entry) { - if (!entry) return ''; - if (entry.id != null) return `id:${entry.id}`; - return `ts:${entry.createdAt}:${entry.message}`; - } - - function updateChatFilterButtons() { - chatFilterButtons.forEach((btn) => { - const value = normalizeChatFilter(btn.dataset.channel || 'all'); - const active = value === chatState.filter; - btn.classList.toggle('active', active); - btn.setAttribute('aria-pressed', active ? 'true' : 'false'); - }); - } - - function setChatFilter(next) { - const normalized = normalizeChatFilter(next); - if (chatState.filter === normalized) return; - chatState.filter = normalized; - updateChatFilterButtons(); - renderChatMessages(); - } - - function normalizeChatMessage(payload, fallbackServerId) { - if (!payload) return null; - const serverId = Number(payload?.serverId ?? payload?.server_id ?? fallbackServerId); - if (!Number.isFinite(serverId)) return null; - const messageText = pickString( - payload?.message, - payload?.Message, - payload?.text, - payload?.Text, - payload?.body - ); - const trimmedMessage = messageText ? messageText.trim() : ''; - if (!trimmedMessage) return null; - const timestampCandidates = [ - payload?.createdAt, - payload?.created_at, - payload?.timestamp, - payload?.Timestamp, - payload?.Time, - payload?.time - ]; - let createdAt = null; - for (const candidate of timestampCandidates) { - const normalized = normalizeChatTimestamp(candidate); - if (normalized) { - createdAt = normalized; - break; - } - } - if (!createdAt) createdAt = new Date().toISOString(); - const idValue = payload?.id - ?? payload?.Id - ?? payload?.messageId - ?? payload?.message_id - ?? payload?.MessageId - ?? payload?.MessageID - ?? null; - let id = null; - if (idValue != null) { - const numeric = Number(idValue); - if (Number.isFinite(numeric)) id = numeric; - else { - const trimmedId = String(idValue).trim(); - id = trimmedId || null; - } - } - const channel = normalizeChatChannel(pickString( - payload?.channel, - payload?.Channel, - payload?.scope, - payload?.Scope, - payload?.type, - payload?.Type - )); - const username = pickString( - payload?.username, - payload?.Username, - payload?.user, - payload?.User, - payload?.name, - payload?.Name, - payload?.displayName, - payload?.DisplayName - ); - const steamId = pickString( - payload?.steamId, - payload?.SteamId, - payload?.steamid, - payload?.SteamID, - payload?.userId, - payload?.UserId, - payload?.userid, - payload?.playerId, - payload?.PlayerId - ); - const raw = pickString(payload?.raw, payload?.Raw); - const color = normalizeChatColor(pickString(payload?.color, payload?.Color)); - return { - id, - serverId, - channel, - steamId: steamId || null, - username: username || null, - message: trimmedMessage.length > 4000 ? trimmedMessage.slice(0, 4000) : trimmedMessage, - createdAt, - raw: raw || null, - color: color || null - }; - } - - function normalizeChatProfile(profile, steamId, fallbackName = null) { - const resolvedId = pickString(profile?.steamid, profile?.SteamId, steamId); - const displayName = pickString( - profile?.forced_display_name, - profile?.forcedDisplayName, - profile?.display_name, - profile?.displayName, - profile?.persona, - profile?.personaName, - profile?.personaname, - profile?.username, - fallbackName, - resolvedId - ); - const avatar = pickString( - profile?.avatarfull, - profile?.avatarFull, - profile?.avatar, - profile?.avatar_medium, - profile?.avatarMedium, - profile?.avatar_url, - profile?.avatarUrl - ); - const profileUrl = pickString( - profile?.profileurl, - profile?.profile_url, - profile?.profileUrl - ); - return { - steamId: resolvedId || null, - displayName: displayName || null, - avatar: avatar || null, - profileUrl: profileUrl || null - }; - } - - function normalizeChatTimestamp(value) { - if (value == null) return null; - if (value instanceof Date) { - return Number.isNaN(value.getTime()) ? null : value.toISOString(); - } - const numeric = Number(value); - if (Number.isFinite(numeric)) { - const ms = numeric > 1e12 ? numeric : numeric * 1000; - const fromNumber = new Date(ms); - if (!Number.isNaN(fromNumber.getTime())) return fromNumber.toISOString(); - } - const text = String(value).trim(); - if (!text) return null; - const parsed = new Date(text); - return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString(); - } - - function queueChatProfileFetch(steamId, { username } = {}) { - const key = String(steamId || '').trim(); - if (!key || chatState.profileCache.has(key) || chatState.profileRequests.has(key)) return; - if (!state.TOKEN) return; - const request = (async () => { - try { - const data = await api(`/players/${encodeURIComponent(key)}`); - chatState.profileCache.set(key, normalizeChatProfile(data, key, username)); - } catch (err) { - if (errorCode(err) === 'unauthorized') { - handleUnauthorized(); - } else { - chatState.profileCache.set(key, normalizeChatProfile(null, key, username)); + const moduleSlots = /* @__PURE__ */ new Map(); + document.querySelectorAll("[data-module-card]").forEach((card) => { + var _a2; + const id = (_a2 = card == null ? void 0 : card.dataset) == null ? void 0 : _a2.moduleCard; + if (!id) return; + const body = card.querySelector("[data-module-slot]"); + if (!body) return; + const header = card.querySelector(".card-header") || null; + const actions = card.querySelector("[data-module-actions]") || null; + const titleEl = (header == null ? void 0 : header.querySelector("[data-module-title]")) || (header == null ? void 0 : header.querySelector("h3")) || null; + moduleSlots.set(id, { card, body, header, actions, titleEl }); + }); + function setWorkspaceView(nextView = workspaceViewDefault) { + var _a2, _b2; + const visibleButtons = workspaceViewButtons.filter((btn) => !btn.classList.contains("permission-hidden") && !btn.disabled); + if (visibleButtons.length === 0) { + activeWorkspaceView = workspaceViewDefault; + workspaceViewSections.forEach((section) => { + section.classList.remove("active"); + section.setAttribute("aria-hidden", "true"); + }); + return; + } + const available = new Set(visibleButtons.map((btn) => btn.dataset.view)); + let target = available.has(nextView) ? nextView : ((_a2 = visibleButtons[0]) == null ? void 0 : _a2.dataset.view) || workspaceViewDefault; + if (!available.has(target)) target = ((_b2 = visibleButtons[0]) == null ? void 0 : _b2.dataset.view) || workspaceViewDefault; + activeWorkspaceView = target; + workspaceViewButtons.forEach((btn) => { + const allowed = !btn.classList.contains("permission-hidden") && !btn.disabled; + const match = allowed && btn.dataset.view === target; + btn.classList.toggle("active", match); + btn.setAttribute("aria-pressed", match ? "true" : "false"); + }); + workspaceViewSections.forEach((section) => { + const allowed = !section.classList.contains("permission-hidden"); + const match = allowed && section.dataset.view === target; + section.classList.toggle("active", match); + section.setAttribute("aria-hidden", match ? "false" : "true"); + }); + if (target === "settings") { + ensureDiscordStatusSelection(); } - } finally { - chatState.profileRequests.delete(key); - renderChatMessages(); } - })(); - chatState.profileRequests.set(key, request); - } - - function chatAvatarInitial(name = '') { - const text = String(name || '').trim(); - if (!text) return '?'; - const code = text.codePointAt(0); - if (code == null) return '?'; - return String.fromCodePoint(code).toUpperCase(); - } - - function formatChatTimestamp(value) { - if (!value) return null; - const date = value instanceof Date ? value : new Date(value); - if (Number.isNaN(date.getTime())) return null; - const iso = date.toISOString(); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - return { - iso, - label: `${hours}:${minutes}:${seconds}`, - title: date.toLocaleString() - }; - } - - function storeChatMessages(serverId, entries, { replace = false } = {}) { - const key = Number(serverId); - if (!Number.isFinite(key)) return; - const previous = chatState.cache.get(key) || []; - const list = replace ? [] : [...previous]; - if (replace) { - chatState.teamColors.delete(key); - } - const seen = new Set(list.map((item) => messageSignature(item))); - for (const raw of Array.isArray(entries) ? entries : []) { - const normalized = normalizeChatMessage(raw, key); - if (!normalized) continue; - const signature = messageSignature(normalized); - if (seen.has(signature)) continue; - list.push(normalized); - seen.add(signature); - if (normalized.steamId) { - queueChatProfileFetch(normalized.steamId, { username: normalized.username }); - } - } - list.sort((a, b) => { - const aTime = new Date(a.createdAt).getTime(); - const bTime = new Date(b.createdAt).getTime(); - if (aTime === bTime) return messageSignature(a).localeCompare(messageSignature(b)); - return aTime - bTime; - }); - const latestTeamColor = findLatestTeamColor(list); - if (latestTeamColor) { - chatState.teamColors.set(key, latestTeamColor); - } else if (replace) { - chatState.teamColors.delete(key); - } - chatState.cache.set(key, list); - chatState.lastFetched.set(key, Date.now()); - if (key === state.currentServerId) renderChatMessages(); - } - - function renderChatMessages() { - if (!workspaceChatList) return; - updateChatFilterButtons(); - const serverId = Number(state.currentServerId); - const hasServer = Number.isFinite(serverId); - const records = hasServer ? (chatState.cache.get(serverId) || []) : []; - const filtered = chatState.filter === 'all' - ? records - : records.filter((entry) => entry.channel === chatState.filter); - const loadingVisible = chatState.loading && hasServer; - if (workspaceChatLoading) { - workspaceChatLoading.classList.toggle('hidden', !loadingVisible); - } - if (chatState.error && workspaceChatNotice) { - showNotice(workspaceChatNotice, chatState.error, 'error'); - } else { - hideNotice(workspaceChatNotice); - } - if (!filtered.length) { - workspaceChatList.innerHTML = ''; - workspaceChatList.classList.add('hidden'); - if (workspaceChatEmpty) { - if (!hasServer) { - workspaceChatEmpty.textContent = 'Select a server to view chat.'; - workspaceChatEmpty.classList.remove('hidden'); - } else if (!loadingVisible) { - workspaceChatEmpty.textContent = 'No chat messages yet.'; - workspaceChatEmpty.classList.remove('hidden'); - } else { - workspaceChatEmpty.classList.add('hidden'); - } - } - return; - } - workspaceChatEmpty?.classList.add('hidden'); - workspaceChatList.classList.remove('hidden'); - workspaceChatList.innerHTML = ''; - filtered.forEach((entry) => { - const li = document.createElement('li'); - li.className = 'chat-entry'; - if (entry.channel === 'team') li.classList.add('team'); - if (entry.id != null) li.dataset.messageId = String(entry.id); - li.dataset.channel = entry.channel; - if (entry.steamId) li.dataset.steamId = entry.steamId; - const channelNumber = entry.channel === 'team' ? 1 : 0; - li.dataset.channelNumber = String(channelNumber); - - const avatar = document.createElement('div'); - avatar.className = 'chat-entry-avatar'; - const profile = entry.steamId ? chatState.profileCache.get(entry.steamId) : null; - if (!profile && entry.steamId) queueChatProfileFetch(entry.steamId, { username: entry.username }); - const displayName = (profile?.displayName || entry.username || entry.steamId || 'Unknown').trim(); - const avatarUrl = profile?.avatar || null; - if (avatarUrl) { - avatar.classList.add('has-image'); - const img = document.createElement('img'); - img.src = avatarUrl; - img.alt = displayName ? `${displayName} avatar` : 'Player avatar'; - avatar.appendChild(img); - } else { - const avatarLabel = document.createElement('span'); - avatarLabel.className = 'chat-entry-avatar-label'; - avatarLabel.textContent = chatAvatarInitial(displayName); - avatar.appendChild(avatarLabel); + workspaceViewButtons.forEach((btn) => { + btn.addEventListener("click", () => { + if (btn.disabled || btn.classList.contains("permission-hidden")) return; + setWorkspaceView(btn.dataset.view); + }); + }); + chatFilterButtons.forEach((btn) => { + btn.addEventListener("click", () => { + if (btn.disabled) return; + setChatFilter(btn.dataset.channel || "all"); + }); + }); + f7ScopeButtons.forEach((btn) => { + btn.addEventListener("click", () => { + if (btn.disabled) return; + setF7Scope(btn.dataset.scope || "new"); + }); + }); + if (f7ReportShowAll) { + f7ReportShowAll.addEventListener("click", () => { + var _a2; + const detail = f7State.activeId != null ? f7State.detailCache.get(f7State.activeId) : null; + const targetSteamId = (_a2 = pickString(detail == null ? void 0 : detail.targetSteamId)) == null ? void 0 : _a2.trim(); + if (!targetSteamId) return; + const label = pickString(detail == null ? void 0 : detail.targetName) || targetSteamId; + if (f7State.focusTarget === targetSteamId) { + clearF7FocusTarget(); + } else { + focusF7ReportsOnTarget(targetSteamId, label); + } + if (f7ReportDetail == null ? void 0 : f7ReportDetail.scrollIntoView) { + try { + f7ReportDetail.scrollIntoView({ behavior: "smooth", block: "start" }); + } catch (err) { + } + } + }); } - - const content = document.createElement('div'); - content.className = 'chat-entry-content'; - const meta = document.createElement('div'); - meta.className = 'chat-entry-meta'; - const infoRow = document.createElement('div'); - infoRow.className = 'chat-entry-info'; - const nameEl = profile?.profileUrl ? document.createElement('a') : document.createElement('span'); - nameEl.className = 'chat-entry-name'; - nameEl.textContent = displayName; - if (profile?.profileUrl) { - nameEl.href = profile.profileUrl; - nameEl.target = '_blank'; - nameEl.rel = 'noopener noreferrer'; - } - infoRow.appendChild(nameEl); - - const channelEl = document.createElement('span'); - channelEl.className = 'chat-entry-channel'; - channelEl.dataset.channelNumber = String(channelNumber); - channelEl.textContent = channelNumber === 1 ? 'Team (1)' : 'Global (0)'; - infoRow.appendChild(channelEl); - - const timeInfo = formatChatTimestamp(entry.createdAt); - if (timeInfo) { - const timeEl = document.createElement('time'); - timeEl.className = 'chat-entry-timestamp'; - timeEl.dateTime = timeInfo.iso; - timeEl.textContent = timeInfo.label; - timeEl.title = timeInfo.title; - infoRow.appendChild(timeEl); + if (f7ReportsClearFocus) { + f7ReportsClearFocus.addEventListener("click", () => { + clearF7FocusTarget(); + if (f7ReportDetail == null ? void 0 : f7ReportDetail.scrollIntoView) { + try { + f7ReportDetail.scrollIntoView({ behavior: "smooth", block: "start" }); + } catch (err) { + } + } + }); } - - const hasAvatarImage = !!avatarUrl; - if (entry.channel === 'team') { - const teamColor = resolveTeamMessageColor(serverId, entry) || DEFAULT_TEAM_CHAT_COLOR; - const nameColor = rgbaFromColor(teamColor, 0.95) || teamColor; - const badgeColor = rgbaFromColor(teamColor, 0.2) || teamColor; - const borderColor = rgbaFromColor(teamColor, 0.45) || teamColor; - const avatarColor = rgbaFromColor(teamColor, 0.35) || teamColor; - if (!hasAvatarImage) { - avatar.style.backgroundColor = avatarColor || ''; - } else { - avatar.style.backgroundColor = ''; - } - nameEl.style.color = nameColor || ''; - channelEl.style.backgroundColor = badgeColor || ''; - channelEl.style.color = nameColor || ''; - li.style.borderLeft = `3px solid ${borderColor || teamColor}`; - } else { - if (!hasAvatarImage) { - avatar.style.backgroundColor = entry.color || ''; - } else { - avatar.style.backgroundColor = ''; - } - nameEl.style.color = entry.color || ''; - channelEl.style.backgroundColor = ''; - channelEl.style.color = ''; - li.style.removeProperty('border-left'); + if (f7ReportsSearch) { + const commitFilter = () => { + const value = f7ReportsSearch.value || ""; + if (value === f7State.filter) return; + f7State.filter = value; + renderF7Reports(); + }; + const onSearchInput = () => { + if (f7FilterTimer) clearTimeout(f7FilterTimer); + f7FilterTimer = setTimeout(() => { + f7FilterTimer = null; + commitFilter(); + }, 200); + }; + f7ReportsSearch.addEventListener("input", onSearchInput); + f7ReportsSearch.addEventListener("search", commitFilter); } - - meta.appendChild(infoRow); - if (entry.steamId) { - const idEl = document.createElement('span'); - idEl.className = 'chat-entry-id'; - idEl.textContent = `Steam ID: ${entry.steamId}`; - meta.appendChild(idEl); + if (f7ReportOpenProfile) { + f7ReportOpenProfile.addEventListener("click", () => { + const detail = f7State.activeId != null ? f7State.detailCache.get(f7State.activeId) : null; + const payload = buildF7PlayerPayload(detail); + if (!(payload == null ? void 0 : payload.steamid)) return; + window.dispatchEvent(new CustomEvent("players:open-profile", { + detail: { + steamId: payload.steamid, + player: payload, + source: "f7-reports" + } + })); + }); } - - const messageEl = document.createElement('p'); - messageEl.className = 'chat-entry-message'; - messageEl.textContent = entry.message; - - content.append(meta, messageEl); - li.append(avatar, content); - workspaceChatList.appendChild(li); - }); - } - - async function refreshChatForServer(serverId, { force = false } = {}) { - const numeric = Number(serverId); - if (!Number.isFinite(numeric)) return; - if (!hasServerCapability('console')) return; - const now = Date.now(); - const last = chatState.lastFetched.get(numeric) || 0; - if (!force && chatState.cache.has(numeric) && now - last < CHAT_REFRESH_INTERVAL_MS) { - chatState.loading = false; - if (numeric === state.currentServerId) renderChatMessages(); - return; - } - chatState.loading = true; - if (numeric === state.currentServerId) renderChatMessages(); - try { - const data = await api(`/servers/${numeric}/chat?limit=200`); - const messages = Array.isArray(data?.messages) ? data.messages : []; - storeChatMessages(numeric, messages, { replace: true }); - chatState.error = null; - chatState.lastFetched.set(numeric, now); - } catch (err) { - chatState.error = describeError(err); - if (errorCode(err) === 'unauthorized') { - handleUnauthorized(); - } else { - ui.log('Failed to load chat history: ' + describeError(err)); - } - } finally { - chatState.loading = false; - if (numeric === state.currentServerId) renderChatMessages(); - } - } - - function clearChatRefreshTimer() { - if (chatRefreshTimer) { - clearInterval(chatRefreshTimer); - chatRefreshTimer = null; - } - } - - function scheduleChatRefresh(serverId) { - clearChatRefreshTimer(); - const numeric = Number(serverId); - if (!Number.isFinite(numeric)) return; - if (!hasServerCapability('console')) return; - chatRefreshTimer = setInterval(() => { - if (state.currentServerId !== numeric) { - clearChatRefreshTimer(); - return; + if (workspaceViewSections.length) { + setWorkspaceView(workspaceViewDefault); } - if (!hasServerCapability('console')) { - clearChatRefreshTimer(); - return; + function normalizeChatFilter(value) { + const text2 = typeof value === "string" ? value.trim().toLowerCase() : ""; + if (text2 === "team") return "team"; + if (text2 === "global") return "global"; + return "all"; } - refreshChatForServer(numeric).catch(() => {}); - }, CHAT_REFRESH_INTERVAL_MS); - } - - function ingestChatMessage(serverId, payload) { - if (!payload) return; - if (Array.isArray(payload)) { - storeChatMessages(serverId, payload); - } else { - storeChatMessages(serverId, [payload]); - } - } - - function pickString(...values) { - for (const value of values) { - if (value == null) continue; - if (typeof value === 'string') { + function normalizeChatChannel(value) { + if (typeof value === "number") { + return value === 1 ? "team" : "global"; + } + if (typeof value === "string") { + const trimmed = value.trim(); + if (!trimmed) return "global"; + const numeric = Number(trimmed); + if (Number.isFinite(numeric)) return numeric === 1 ? "team" : "global"; + const lower = trimmed.toLowerCase(); + if (lower === "team") return "team"; + if (lower === "global") return "global"; + if (lower === "1") return "team"; + if (lower === "0") return "global"; + } + return "global"; + } + function normalizeChatColor(value) { + const text2 = typeof value === "string" ? value.trim() : ""; + if (!text2) return null; + const hexMatch = text2.match(/^#?([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i); + if (hexMatch) { + return `#${hexMatch[1].toLowerCase()}`; + } + const compact = text2.replace(/\s+/g, ""); + const rgbMatch = compact.match(/^rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3})(?:,(0|1|0?\.\d+))?\)$/i); + if (rgbMatch) { + const numeric = rgbMatch.slice(1, 4).map((part) => { + const parsed = Number(part); + return Number.isFinite(parsed) ? parsed : null; + }); + if (numeric.some((part) => part == null || part < 0 || part > 255)) return null; + const alphaRaw = rgbMatch[4]; + if (typeof alphaRaw === "string") { + const alpha = Number(alphaRaw); + if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) return null; + return `rgba(${numeric[0]}, ${numeric[1]}, ${numeric[2]}, ${alpha})`; + } + return `rgb(${numeric[0]}, ${numeric[1]}, ${numeric[2]})`; + } + return null; + } + function parseCssColor(value) { + if (typeof value !== "string") return null; const trimmed = value.trim(); - if (trimmed) return trimmed; - } else if (typeof value === 'number' && Number.isFinite(value)) { - return String(value); + if (!trimmed) return null; + const hexMatch = trimmed.match(/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i); + if (hexMatch) { + const hex = hexMatch[1]; + if (hex.length === 3 || hex.length === 4) { + const r = parseInt(hex[0] + hex[0], 16); + const g = parseInt(hex[1] + hex[1], 16); + const b = parseInt(hex[2] + hex[2], 16); + const a = hex.length === 4 ? parseInt(hex[3] + hex[3], 16) / 255 : 1; + return { r, g, b, a }; + } + if (hex.length === 6 || hex.length === 8) { + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1; + return { r, g, b, a }; + } + } + const rgbMatch = trimmed.match(/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(0|1|0?\.\d+))?\s*\)$/i); + if (rgbMatch) { + const r = Number(rgbMatch[1]); + const g = Number(rgbMatch[2]); + const b = Number(rgbMatch[3]); + const a = rgbMatch[4] != null ? Number(rgbMatch[4]) : 1; + if ([r, g, b].some((n) => !Number.isFinite(n) || n < 0 || n > 255)) return null; + if (!Number.isFinite(a) || a < 0 || a > 1) return null; + return { r, g, b, a }; + } + return null; + } + function rgbaFromColor(value, alpha = 1) { + const parsed = parseCssColor(value) || parseCssColor(DEFAULT_TEAM_CHAT_COLOR); + if (!parsed) return null; + const clampChannel = (n) => Math.max(0, Math.min(255, Math.round(n))); + const clampAlpha = (n) => Math.max(0, Math.min(1, Number.isFinite(n) ? n : 1)); + const a = Math.round(clampAlpha(alpha) * 1e3) / 1e3; + return `rgba(${clampChannel(parsed.r)}, ${clampChannel(parsed.g)}, ${clampChannel(parsed.b)}, ${a})`; + } + function findLatestTeamColor(entries) { + if (!Array.isArray(entries)) return null; + for (let i = entries.length - 1; i >= 0; i -= 1) { + const entry = entries[i]; + if (!entry) continue; + if (entry.channel === "team" && typeof entry.color === "string" && entry.color) { + return entry.color; + } + } + return null; } - } - return null; - } - - const COMBAT_LOG_HEADER_REGEX = /^\s*time\s+attacker\s+id\s+target\s+id\s+weapon\s+ammo\s+area\s+distance\s+old[_\s]*hp\s+new[_\s]*hp\s+info\s+hits\s+integrity\s+travel\s+mismatch\s+desync\s*$/i; - const COMBAT_LOG_ENTRY_REGEX = new RegExp( - [ - '^(?