Refactor dashboard.js: streamline state management, improve layouts, optimize role and command handlers.
This commit is contained in:
+7
-6
@@ -121,7 +121,7 @@ const initIndexPage = async () => {
|
|||||||
try {
|
try {
|
||||||
const data = await fetchJson('/api/me');
|
const data = await fetchJson('/api/me');
|
||||||
setCsrfToken(data.csrfToken);
|
setCsrfToken(data.csrfToken);
|
||||||
sessionStatus.textContent = `Signed in as ${formatUserDisplay(data.user)}.`;
|
sessionStatus.textContent = formatUserDisplay(data.user);
|
||||||
loginButton.hidden = true;
|
loginButton.hidden = true;
|
||||||
dashboardButton.hidden = false;
|
dashboardButton.hidden = false;
|
||||||
logoutButton.hidden = false;
|
logoutButton.hidden = false;
|
||||||
@@ -130,26 +130,27 @@ const initIndexPage = async () => {
|
|||||||
banner.style.display = 'none';
|
banner.style.display = 'none';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sessionStatus.textContent = 'You are not signed in yet.';
|
sessionStatus.textContent = '';
|
||||||
loginButton.hidden = false;
|
loginButton.hidden = false;
|
||||||
dashboardButton.hidden = true;
|
dashboardButton.hidden = true;
|
||||||
logoutButton.hidden = true;
|
logoutButton.hidden = true;
|
||||||
if (banner) {
|
if (banner) {
|
||||||
banner.textContent = 'Log in with Discord to access the dashboard.';
|
const sessionExpired = error.status === 401 || error.status === 403;
|
||||||
banner.style.display = 'block';
|
banner.textContent = sessionExpired ? '' : 'Unable to check your session. Try again.';
|
||||||
|
banner.style.display = sessionExpired ? 'none' : 'block';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logoutButton?.addEventListener('click', async () => {
|
logoutButton?.addEventListener('click', async () => {
|
||||||
try {
|
try {
|
||||||
await fetch('/auth/logout', {
|
await fetchJson('/auth/logout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
...(csrfToken ? {'X-CSRF-Token': csrfToken} : {}),
|
...(csrfToken ? {'X-CSRF-Token': csrfToken} : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sessionStatus.textContent = 'Logged out successfully.';
|
sessionStatus.textContent = '';
|
||||||
loginButton.hidden = false;
|
loginButton.hidden = false;
|
||||||
dashboardButton.hidden = true;
|
dashboardButton.hidden = true;
|
||||||
logoutButton.hidden = true;
|
logoutButton.hidden = true;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 380" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="brass" x1="141" y1="160" x2="344" y2="251" gradientUnits="userSpaceOnUse"><stop stop-color="#7b5d30"/><stop offset=".22" stop-color="#c6a366"/><stop offset=".43" stop-color="#f3ddb0"/><stop offset=".63" stop-color="#d4b77b"/><stop offset=".86" stop-color="#9f7a40"/><stop offset="1" stop-color="#6d4a23"/></linearGradient>
|
||||||
|
<linearGradient id="rim" x1="127" y1="258" x2="364" y2="278" gradientUnits="userSpaceOnUse"><stop stop-color="#806031"/><stop offset=".4" stop-color="#ead3a6"/><stop offset=".7" stop-color="#b7955a"/><stop offset="1" stop-color="#74512a"/></linearGradient>
|
||||||
|
<linearGradient id="arch" x1="260" y1="22" x2="260" y2="345" gradientUnits="userSpaceOnUse"><stop stop-color="#e8dec9"/><stop offset="1" stop-color="#e8dec9" stop-opacity=".1"/></linearGradient>
|
||||||
|
<radialGradient id="shadow"><stop stop-color="#483728" stop-opacity=".25"/><stop offset="1" stop-color="#483728" stop-opacity="0"/></radialGradient>
|
||||||
|
</defs>
|
||||||
|
<path d="M115 342V162a135 135 0 0 1 270 0v180" fill="url(#arch)"/>
|
||||||
|
<g stroke="#b69b6a" stroke-opacity=".45"><path d="M99 342V158a151 151 0 0 1 302 0v184"/><path d="M109 342V158a141 141 0 0 1 282 0v184"/><path d="M121 342V160a129 129 0 0 1 258 0v182"/><path d="M133 342V162a117 117 0 0 1 234 0v180"/><path d="M79 342h342"/></g>
|
||||||
|
<circle cx="250" cy="190" r="94" stroke="#b69b6a" stroke-opacity=".3" stroke-dasharray="2 5"/>
|
||||||
|
<ellipse cx="252" cy="292" rx="151" ry="24" fill="url(#shadow)"/>
|
||||||
|
<path d="M128 275h245l8 11H120z" fill="#493032"/>
|
||||||
|
<path d="M135 270h231l7 8H128z" fill="#6e4e45"/>
|
||||||
|
<rect x="135" y="259" width="231" height="13" rx="4" fill="url(#rim)"/>
|
||||||
|
<path d="M148 254c0-51 39-91 102-91s102 40 102 91z" fill="url(#brass)" stroke="#a07e44" stroke-width="1"/>
|
||||||
|
<path d="M157 249c2-42 31-73 76-78" stroke="#f6e5be" stroke-opacity=".65" stroke-width="2"/>
|
||||||
|
<path d="M143 253h214v8H143z" fill="url(#rim)"/>
|
||||||
|
<path d="M245 146h10v19h-10z" fill="url(#rim)"/>
|
||||||
|
<ellipse cx="250" cy="145" rx="18" ry="5" fill="url(#brass)" stroke="#ad8e54"/>
|
||||||
|
<path d="M239 213h22m-11-11v22" stroke="#755627" stroke-opacity=".48"/>
|
||||||
|
<g fill="#b09462"><path d="m411 93 3 9 9 3-9 3-3 9-3-9-9-3 9-3z"/><path d="m88 238 2 6 6 2-6 2-2 6-2-6-6-2 6-2z"/></g>
|
||||||
|
<path d="M433 215v65m-5-65h10m-10 65h10" stroke="#b69b6a" stroke-opacity=".6"/>
|
||||||
|
<text x="250" y="322" text-anchor="middle" fill="#8f7953" font-size="9" font-family="Georgia,serif" letter-spacing="4">THE STEWARD</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
+402
-282
@@ -1,287 +1,407 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Discord Bot Dashboard</title>
|
<meta name="theme-color" content="#302224" />
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<title>Overview · The Steward</title>
|
||||||
</head>
|
<link rel="icon" href="mark.svg" type="image/svg+xml" />
|
||||||
<body>
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<div class="app-shell">
|
</head>
|
||||||
<header class="navbar">
|
<body>
|
||||||
<div class="navbar__brand">
|
<a class="skip-link" href="#mainContent">Skip to content</a>
|
||||||
<div class="brand-mark">DB</div>
|
<div class="app-shell">
|
||||||
<div>
|
<aside class="sidebar" aria-label="Dashboard navigation">
|
||||||
<p class="muted">Discord Bot Dashboard</p>
|
<a class="brand" href="/" aria-label="The Steward home">
|
||||||
<strong id="userDisplay">Loading session...</strong>
|
<img src="mark.svg" alt="" />
|
||||||
<p class="muted" id="sessionExpiry"></p>
|
<span
|
||||||
</div>
|
><small>THE</small>Steward<span class="brand-period">.</span></span
|
||||||
</div>
|
>
|
||||||
<div class="navbar__actions">
|
</a>
|
||||||
<a class="button button--ghost" href="/">Home</a>
|
<nav class="tabs" aria-label="Server management">
|
||||||
<button class="button button--primary" id="logoutButton">Log out</button>
|
<button class="tab active" data-tab="settings" aria-current="page">
|
||||||
</div>
|
<svg><use href="icons.svg#grid" /></svg>Overview
|
||||||
</header>
|
</button>
|
||||||
|
<button class="tab" data-tab="permissions">
|
||||||
<div class="error-banner" id="errorBanner" hidden>
|
<svg><use href="icons.svg#key" /></svg>Permissions
|
||||||
<div>
|
<span class="nav-count" id="navCommandCount">–</span>
|
||||||
<strong>Something went wrong.</strong>
|
</button>
|
||||||
<p data-role="error-message"></p>
|
<button class="tab" data-tab="warnings">
|
||||||
</div>
|
<svg><use href="icons.svg#shield" /></svg>Warnings
|
||||||
<button class="button button--ghost" data-role="error-copy">Copy error details</button>
|
</button>
|
||||||
</div>
|
</nav>
|
||||||
|
<div class="sidebar__section-label server-label">
|
||||||
<div class="toast-container" id="toastContainer"></div>
|
SERVERS <span id="guildCount">–</span>
|
||||||
|
|
||||||
<div class="layout">
|
|
||||||
<aside class="sidebar">
|
|
||||||
<div class="sidebar__header">
|
|
||||||
<div>
|
|
||||||
<h2>Guilds</h2>
|
|
||||||
<p class="muted">Pick a server to manage.</p>
|
|
||||||
</div>
|
|
||||||
<input type="search" id="guildSearch" placeholder="Search guilds" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="guild-list" id="guildList"></div>
|
|
||||||
<div class="skeleton-list" id="guildListSkeleton" hidden>
|
|
||||||
<div class="skeleton-line"></div>
|
|
||||||
<div class="skeleton-line"></div>
|
|
||||||
<div class="skeleton-line"></div>
|
|
||||||
<div class="skeleton-line"></div>
|
|
||||||
</div>
|
|
||||||
<div class="empty-state" id="guildEmpty" hidden>
|
|
||||||
No manageable guilds found. Make sure you have Manage Server permissions.
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="content">
|
|
||||||
<section class="guild-header">
|
|
||||||
<div class="guild-header__info">
|
|
||||||
<img id="guildHeaderIcon" alt="" />
|
|
||||||
<div>
|
|
||||||
<h1 id="guildHeaderName">Select a guild</h1>
|
|
||||||
<div class="guild-header__status">
|
|
||||||
<span class="status-badge" id="guildStatusBadge"></span>
|
|
||||||
<span class="muted" id="guildStatusNote"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="guild-header__actions">
|
|
||||||
<button class="button button--primary" id="inviteButton" hidden>Invite bot</button>
|
|
||||||
<button class="button button--ghost" id="refreshBotButton" hidden>Refresh status</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="tabs">
|
|
||||||
<button class="tab active" data-tab="settings">Settings</button>
|
|
||||||
<button class="tab" data-tab="permissions">Permissions</button>
|
|
||||||
<button class="tab" data-tab="warnings">Warnings</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="panel" id="settingsPanel">
|
|
||||||
<div class="panel__header">
|
|
||||||
<div>
|
|
||||||
<h2>Admin access</h2>
|
|
||||||
<p class="muted">Control who can edit bot configuration and permissions.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="callout" id="botInactiveCallout" hidden>
|
|
||||||
<strong>Bot not added yet.</strong>
|
|
||||||
<p class="muted">The bot is not in this server yet. Invite it to configure settings.</p>
|
|
||||||
<p class="muted">You can review saved settings, but changes will stay inactive until the bot joins.</p>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<span>
|
|
||||||
Admin roles
|
|
||||||
<span class="tooltip" title="Members with these roles can edit dashboard settings.">?</span>
|
|
||||||
</span>
|
|
||||||
<div id="adminRolesPicker"></div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<span>
|
|
||||||
Admin user IDs
|
|
||||||
<span class="tooltip" title="Paste one or more user IDs separated by commas or spaces.">?</span>
|
|
||||||
</span>
|
|
||||||
<div class="input-row">
|
|
||||||
<input type="text" id="adminUserInput" placeholder="Paste user IDs" />
|
|
||||||
<button class="button" id="addAdminUserButton">Add</button>
|
|
||||||
</div>
|
|
||||||
<div class="chip-list" id="adminUserChips"></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="panel" id="permissionsPanel" hidden>
|
|
||||||
<div class="panel__header panel__header--split">
|
|
||||||
<div>
|
|
||||||
<h2>Command permissions</h2>
|
|
||||||
<p class="muted">Review and edit restrictions faster with search, filters, and live summaries.</p>
|
|
||||||
</div>
|
|
||||||
<div class="button-row">
|
|
||||||
<button class="button button--ghost" id="clearAllCommands">Clear all restrictions</button>
|
|
||||||
<button class="button" id="copyFromGuild">Copy from another guild</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stats-grid">
|
|
||||||
<article class="stat-card">
|
|
||||||
<span class="stat-card__label">Commands</span>
|
|
||||||
<strong class="stat-card__value" id="permissionTotalCount">0</strong>
|
|
||||||
<span class="muted">Available moderation commands</span>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<span class="stat-card__label">Restricted</span>
|
|
||||||
<strong class="stat-card__value" id="permissionRestrictedCount">0</strong>
|
|
||||||
<span class="muted">Commands limited to specific roles or users</span>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<span class="stat-card__label">Open</span>
|
|
||||||
<strong class="stat-card__value" id="permissionOpenCount">0</strong>
|
|
||||||
<span class="muted">Commands currently available to everyone</span>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<span class="stat-card__label">Unsaved edits</span>
|
|
||||||
<strong class="stat-card__value" id="permissionChangedCount">0</strong>
|
|
||||||
<span class="muted">Cards changed since the last save</span>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toolbar">
|
|
||||||
<div class="field toolbar__field">
|
|
||||||
<label for="commandSearch">Find command</label>
|
|
||||||
<input type="search" id="commandSearch" placeholder="Search by command name" />
|
|
||||||
</div>
|
|
||||||
<div class="field toolbar__field">
|
|
||||||
<label for="commandFilter">Show</label>
|
|
||||||
<select id="commandFilter">
|
|
||||||
<option value="all">All commands</option>
|
|
||||||
<option value="restricted">Restricted only</option>
|
|
||||||
<option value="open">Open only</option>
|
|
||||||
<option value="changed">Changed only</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<span>
|
|
||||||
Add command
|
|
||||||
<span class="tooltip" title="Create new command overrides for custom commands.">?</span>
|
|
||||||
</span>
|
|
||||||
<div class="input-row">
|
|
||||||
<input type="text" id="newCommandInput" placeholder="command-name" />
|
|
||||||
<button class="button" id="addCommandButton">Add</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="command-list" id="commandsList"></div>
|
|
||||||
<div class="empty-state" id="commandsEmpty" hidden>
|
|
||||||
No commands match the current filter. Try clearing the search or add a custom command override.
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="panel" id="warningsPanel" hidden>
|
|
||||||
<div class="warnings-layout">
|
|
||||||
<div class="warnings-pane">
|
|
||||||
<div class="panel__header">
|
|
||||||
<div>
|
|
||||||
<h2>User lookup</h2>
|
|
||||||
<p class="muted">Search members or paste an ID, then manage all warnings from one target card.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="warningsUserSearch">Find member</label>
|
|
||||||
<input type="search" id="warningsUserSearch" placeholder="Type at least 2 characters" />
|
|
||||||
<span class="muted" id="warningsSearchStatus">Start typing to search this guild.</span>
|
|
||||||
</div>
|
|
||||||
<div class="member-list" id="warningsUserResults"></div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="warningsUserInput">Manual user ID</label>
|
|
||||||
<input type="text" id="warningsUserInput" placeholder="123456789" />
|
|
||||||
</div>
|
|
||||||
<div class="selected-user-card" id="warningsSelectedCard">
|
|
||||||
<span class="selected-user-card__label">Current target</span>
|
|
||||||
<strong id="warningsSelectedUser">No user selected.</strong>
|
|
||||||
<span class="muted" id="warningsSelectedHint">Pick a member from search results or paste a Discord user ID.</span>
|
|
||||||
</div>
|
|
||||||
<div class="button-row">
|
|
||||||
<button class="button" id="fetchWarningsButton">Load history</button>
|
|
||||||
<button class="button button--ghost" id="clearWarningsButton">Clear warnings</button>
|
|
||||||
</div>
|
|
||||||
<div class="spinner" id="warningsSpinner" hidden></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="warnings-pane">
|
|
||||||
<div class="panel__header panel__header--split">
|
|
||||||
<div>
|
|
||||||
<h2>Warnings</h2>
|
|
||||||
<p class="muted">See warning history and add new entries for the selected target.</p>
|
|
||||||
</div>
|
|
||||||
<div class="warning-status-pill" id="warningsHistoryState">No history loaded</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stats-grid stats-grid--compact">
|
|
||||||
<article class="stat-card stat-card--compact">
|
|
||||||
<span class="stat-card__label">Total warnings</span>
|
|
||||||
<strong class="stat-card__value" id="warningsTotalCount">0</strong>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card stat-card--compact">
|
|
||||||
<span class="stat-card__label">Latest warning</span>
|
|
||||||
<strong class="stat-card__value stat-card__value--small" id="warningsLatestDate">None</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="empty-state" id="warningsEmpty" hidden>No warnings found for this user.</div>
|
|
||||||
<ul id="warningsList"></ul>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<h3>Add warning</h3>
|
|
||||||
<p class="muted">The warning will be added to the currently selected target.</p>
|
|
||||||
<div class="field">
|
|
||||||
<label for="warningReasonInput">Reason</label>
|
|
||||||
<textarea
|
|
||||||
id="warningReasonInput"
|
|
||||||
placeholder="Describe what happened and why the warning is being issued."
|
|
||||||
maxlength="500"
|
|
||||||
></textarea>
|
|
||||||
<div class="field__meta">
|
|
||||||
<span class="muted" id="warningCharCount">0/500</span>
|
|
||||||
<span class="muted" id="warningStatus"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="button button--primary" id="addWarningButton">Add warning</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="save-bar" id="saveBar" hidden>
|
|
||||||
<span class="save-indicator" id="dirtyIndicator">Unsaved changes</span>
|
|
||||||
<div class="button-row">
|
|
||||||
<button class="button button--ghost" id="discardChangesButton">Discard</button>
|
|
||||||
<button class="button button--primary" id="savePermissionsButton">Save changes</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel skeleton-panel" id="contentSkeleton" hidden>
|
|
||||||
<div class="skeleton-line"></div>
|
|
||||||
<div class="skeleton-line"></div>
|
|
||||||
<div class="skeleton-line"></div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="search-field sidebar-search">
|
||||||
<div class="modal" id="copyModal" hidden>
|
<svg><use href="icons.svg#search" /></svg>
|
||||||
<div class="modal__content">
|
<input
|
||||||
<h3>Copy permissions</h3>
|
type="search"
|
||||||
<p class="muted">Select another guild to copy permissions from.</p>
|
id="guildSearch"
|
||||||
<select id="copyGuildSelect"></select>
|
aria-label="Search servers"
|
||||||
<div class="button-row">
|
placeholder="Search servers"
|
||||||
<button class="button button--ghost" id="cancelCopyButton">Cancel</button>
|
/>
|
||||||
<button class="button button--primary" id="confirmCopyButton">Copy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="guild-list" id="guildList"></div>
|
||||||
<script src="app.js"></script>
|
<div class="skeleton-list" id="guildListSkeleton" hidden>
|
||||||
<script src="dashboard.js"></script>
|
<div class="skeleton-line"></div>
|
||||||
</body>
|
<div class="skeleton-line"></div>
|
||||||
|
<div class="skeleton-line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="empty-state" id="guildEmpty" hidden>
|
||||||
|
No servers available.
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-bottom">
|
||||||
|
<div class="profile">
|
||||||
|
<div class="profile-avatar" id="userInitial">S</div>
|
||||||
|
<div class="profile__text">
|
||||||
|
<strong id="userDisplay">Loading…</strong
|
||||||
|
><span id="sessionExpiry">Discord account</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="icon-button"
|
||||||
|
id="logoutButton"
|
||||||
|
aria-label="Log out"
|
||||||
|
title="Log out"
|
||||||
|
>
|
||||||
|
<svg><use href="icons.svg#logout" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<div class="workspace">
|
||||||
|
<header class="topbar">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
Dashboard <span>/</span><strong id="currentPage">Overview</strong>
|
||||||
|
</div>
|
||||||
|
<a class="icon-button" href="/" aria-label="Home" title="Home"
|
||||||
|
><svg><use href="icons.svg#home" /></svg
|
||||||
|
></a>
|
||||||
|
</header>
|
||||||
|
<main class="content" id="mainContent" tabindex="-1">
|
||||||
|
<div class="error-banner" id="errorBanner" role="alert" hidden>
|
||||||
|
<p data-role="error-message"></p>
|
||||||
|
<button class="button button--ghost" data-role="error-copy">
|
||||||
|
Copy details
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<section class="page-heading">
|
||||||
|
<h1 id="pageTitle">Overview</h1>
|
||||||
|
<div class="guild-header">
|
||||||
|
<span class="guild-avatar" id="guildHeaderFallback">S</span
|
||||||
|
><img id="guildHeaderIcon" alt="" hidden /><strong
|
||||||
|
id="guildHeaderName"
|
||||||
|
>Select a server</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section
|
||||||
|
class="overview-stats"
|
||||||
|
id="overviewStats"
|
||||||
|
aria-label="Server summary"
|
||||||
|
>
|
||||||
|
<article class="overview-stat">
|
||||||
|
<div class="stat-heading">
|
||||||
|
<span>Bot status</span
|
||||||
|
><button
|
||||||
|
class="icon-button"
|
||||||
|
id="refreshBotButton"
|
||||||
|
aria-label="Refresh bot status"
|
||||||
|
title="Refresh bot status"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
↻
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<strong
|
||||||
|
class="stat-value stat-value--status"
|
||||||
|
id="overviewBotStatus"
|
||||||
|
>–</strong
|
||||||
|
>
|
||||||
|
</article>
|
||||||
|
<article class="overview-stat">
|
||||||
|
<div class="stat-heading">
|
||||||
|
<button class="text-link" data-open-tab="permissions">
|
||||||
|
Commands ↗</button
|
||||||
|
><svg><use href="icons.svg#terminal" /></svg>
|
||||||
|
</div>
|
||||||
|
<strong class="stat-value" id="overviewCommandCount">–</strong>
|
||||||
|
</article>
|
||||||
|
<article class="overview-stat">
|
||||||
|
<div class="stat-heading">
|
||||||
|
<span>Admin roles</span
|
||||||
|
><svg><use href="icons.svg#users" /></svg>
|
||||||
|
</div>
|
||||||
|
<strong class="stat-value" id="overviewRoleCount">–</strong>
|
||||||
|
</article>
|
||||||
|
<article class="overview-stat">
|
||||||
|
<div class="stat-heading">
|
||||||
|
<span>Restricted commands</span
|
||||||
|
><svg><use href="icons.svg#key" /></svg>
|
||||||
|
</div>
|
||||||
|
<strong class="stat-value" id="overviewRestrictedCount">–</strong>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
<div class="callout" id="botInactiveCallout" hidden>
|
||||||
|
<svg><use href="icons.svg#info" /></svg>
|
||||||
|
<p>Invite the bot to edit settings.</p>
|
||||||
|
<button class="button button--primary" id="inviteButton" hidden>
|
||||||
|
Invite bot ↗
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="empty-state panel" id="workspaceEmpty" hidden>
|
||||||
|
Select a server to get started.
|
||||||
|
</div>
|
||||||
|
<section
|
||||||
|
class="settings-layout"
|
||||||
|
id="settingsPanel"
|
||||||
|
aria-labelledby="adminTitle"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="panel admin-panel">
|
||||||
|
<div class="panel__header">
|
||||||
|
<div class="section-title">
|
||||||
|
<span class="section-icon"
|
||||||
|
><svg><use href="icons.svg#users" /></svg
|
||||||
|
></span>
|
||||||
|
<h2 id="adminTitle">Admin access</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="admin-fields">
|
||||||
|
<div class="field">
|
||||||
|
<span id="adminRolesLabel">Roles</span>
|
||||||
|
<div id="adminRolesPicker"></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="adminUserInput">Members</label>
|
||||||
|
<div class="input-row">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="adminUserInput"
|
||||||
|
placeholder="Discord user IDs, comma-separated"
|
||||||
|
/><button
|
||||||
|
class="button button--soft"
|
||||||
|
id="addAdminUserButton"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="chip-list" id="adminUserChips"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footnote">
|
||||||
|
<svg><use href="icons.svg#info" /></svg>Admins bypass command
|
||||||
|
restrictions.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section
|
||||||
|
class="panel"
|
||||||
|
id="permissionsPanel"
|
||||||
|
aria-labelledby="permissionsTitle"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="panel__header panel__header--split">
|
||||||
|
<h2 id="permissionsTitle">Commands</h2>
|
||||||
|
<div class="button-row">
|
||||||
|
<button class="button button--ghost" id="clearAllCommands">
|
||||||
|
Reset restrictions</button
|
||||||
|
><button class="button" id="copyFromGuild">
|
||||||
|
<svg><use href="icons.svg#copy" /></svg>Copy from server
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>Total</span><strong id="permissionTotalCount">0</strong>
|
||||||
|
</article>
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>Restricted</span
|
||||||
|
><strong id="permissionRestrictedCount">0</strong>
|
||||||
|
</article>
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>Default access</span
|
||||||
|
><strong id="permissionOpenCount">0</strong>
|
||||||
|
</article>
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>Unsaved</span
|
||||||
|
><strong id="permissionChangedCount">0</strong>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="search-field">
|
||||||
|
<svg><use href="icons.svg#search" /></svg
|
||||||
|
><input
|
||||||
|
type="search"
|
||||||
|
id="commandSearch"
|
||||||
|
aria-label="Search commands"
|
||||||
|
placeholder="Search commands"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<select id="commandFilter" aria-label="Filter commands">
|
||||||
|
<option value="all">All commands</option>
|
||||||
|
<option value="restricted">Restricted</option>
|
||||||
|
<option value="open">Default access</option>
|
||||||
|
<option value="changed">Unsaved</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="field-hint">Discord permissions still apply.</p>
|
||||||
|
<div class="command-list" id="commandsList"></div>
|
||||||
|
<div class="empty-state" id="commandsEmpty" hidden>
|
||||||
|
No matching commands.
|
||||||
|
</div>
|
||||||
|
<div class="custom-command">
|
||||||
|
<label for="newCommandInput">Custom command</label>
|
||||||
|
<div class="input-row">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="newCommandInput"
|
||||||
|
placeholder="command-name"
|
||||||
|
maxlength="32"
|
||||||
|
/><button class="button" id="addCommandButton">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section
|
||||||
|
class="warnings-layout"
|
||||||
|
id="warningsPanel"
|
||||||
|
aria-label="Member warnings"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="panel warnings-pane">
|
||||||
|
<div class="panel__header"><h2>Member</h2></div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="warningsUserSearch">Search</label>
|
||||||
|
<div class="search-field">
|
||||||
|
<svg><use href="icons.svg#search" /></svg
|
||||||
|
><input
|
||||||
|
type="search"
|
||||||
|
id="warningsUserSearch"
|
||||||
|
placeholder="Member name"
|
||||||
|
maxlength="64"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="field-hint"
|
||||||
|
id="warningsSearchStatus"
|
||||||
|
role="status"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div class="member-list" id="warningsUserResults"></div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="warningsUserInput">Or enter a user ID</label
|
||||||
|
><input
|
||||||
|
type="text"
|
||||||
|
id="warningsUserInput"
|
||||||
|
placeholder="123456789012345678"
|
||||||
|
inputmode="numeric"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="selected-user-card" id="warningsSelectedCard" hidden>
|
||||||
|
<strong id="warningsSelectedUser"></strong
|
||||||
|
><span class="field-hint" id="warningsSelectedHint"></span>
|
||||||
|
</div>
|
||||||
|
<div class="button-row warning-actions">
|
||||||
|
<button class="button button--dark" id="fetchWarningsButton">
|
||||||
|
Load history</button
|
||||||
|
><button class="button button--danger" id="clearWarningsButton">
|
||||||
|
Clear warnings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="spinner"
|
||||||
|
id="warningsSpinner"
|
||||||
|
aria-label="Loading warnings"
|
||||||
|
hidden
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="panel warnings-pane">
|
||||||
|
<div class="panel__header"><h2>History</h2></div>
|
||||||
|
<div class="stats-grid stats-grid--compact">
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>Warnings</span
|
||||||
|
><strong id="warningsTotalCount">–</strong>
|
||||||
|
</article>
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>Latest</span
|
||||||
|
><strong class="stat-value--small" id="warningsLatestDate"
|
||||||
|
>–</strong
|
||||||
|
>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="empty-state" id="warningsEmpty">
|
||||||
|
Select a member to view warnings.
|
||||||
|
</div>
|
||||||
|
<ul id="warningsList"></ul>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="warningReasonInput">Reason</label
|
||||||
|
><textarea
|
||||||
|
id="warningReasonInput"
|
||||||
|
placeholder="Reason for warning"
|
||||||
|
maxlength="500"
|
||||||
|
rows="4"
|
||||||
|
></textarea>
|
||||||
|
<div class="field__meta">
|
||||||
|
<span id="warningStatus" role="status"></span
|
||||||
|
><span class="field-hint" id="warningCharCount">0/500</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="button button--primary" id="addWarningButton">
|
||||||
|
Add warning
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="save-bar" id="saveBar" role="status" hidden>
|
||||||
|
<div>
|
||||||
|
<span class="save-dot"></span><strong>Unsaved changes</strong>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<button class="button button--ghost" id="discardChangesButton">
|
||||||
|
Discard</button
|
||||||
|
><button
|
||||||
|
class="button button--primary"
|
||||||
|
id="savePermissionsButton"
|
||||||
|
>
|
||||||
|
Save changes <svg><use href="icons.svg#check" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="panel skeleton-panel"
|
||||||
|
id="contentSkeleton"
|
||||||
|
aria-label="Loading server"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="skeleton-line"></div>
|
||||||
|
<div class="skeleton-line"></div>
|
||||||
|
<div class="skeleton-line"></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="toast-container"
|
||||||
|
id="toastContainer"
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
|
></div>
|
||||||
|
<dialog class="modal" id="copyModal" aria-labelledby="copyTitle">
|
||||||
|
<div class="modal__content">
|
||||||
|
<h2 id="copyTitle">Copy permissions</h2>
|
||||||
|
<label for="copyGuildSelect">From server</label
|
||||||
|
><select id="copyGuildSelect"></select>
|
||||||
|
<p class="field-hint">Replace unavailable roles before saving.</p>
|
||||||
|
<div class="button-row">
|
||||||
|
<button class="button button--ghost" id="cancelCopyButton">
|
||||||
|
Cancel</button
|
||||||
|
><button class="button button--primary" id="confirmCopyButton">
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
<script src="dashboard.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1060
-1053
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<symbol id="grid" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></symbol>
|
||||||
|
<symbol id="key" viewBox="0 0 24 24"><circle cx="8" cy="8" r="5"/><path d="m12 12 9 9m-6-6 3-3m0 6 3-3"/></symbol>
|
||||||
|
<symbol id="shield" viewBox="0 0 24 24"><path d="M12 3l8 3v6c0 5-8 9-8 9s-8-4-8-9V6zM9 12l2 2 4-4"/></symbol>
|
||||||
|
<symbol id="search" viewBox="0 0 24 24"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M16 16l5 5"/></symbol>
|
||||||
|
<symbol id="spark" viewBox="0 0 24 24"><path d="m12 2 2.8 7.2L22 12l-7.2 2.8L12 22l-2.8-7.2L2 12l7.2-2.8z"/></symbol>
|
||||||
|
<symbol id="bell" viewBox="0 0 24 24"><path d="M3 18h18M5 15a7 7 0 0 1 14 0zm7-7V4m-2 0h4M2 21h20"/></symbol>
|
||||||
|
<symbol id="logout" viewBox="0 0 24 24"><path d="M9 4H4v16h5m5-12 4 4-4 4m-5-4h12"/></symbol>
|
||||||
|
<symbol id="home" viewBox="0 0 24 24"><path d="M3 10l9-7 9 7M5 9v12h14V9M9 21v-8h6v8"/></symbol>
|
||||||
|
<symbol id="activity" viewBox="0 0 24 24"><path d="M2 12h5l3-8 4 16 3-8h5"/></symbol>
|
||||||
|
<symbol id="terminal" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="m7 9 3 3-3 3m7 0h3"/></symbol>
|
||||||
|
<symbol id="users" viewBox="0 0 24 24"><circle cx="9" cy="8" r="3"/><path d="M3 21v-3a6 6 0 0 1 12 0v3M16 5a3 3 0 0 1 0 6m2 4a5 5 0 0 1 3 5"/></symbol>
|
||||||
|
<symbol id="info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6m0-10v.1"/></symbol>
|
||||||
|
<symbol id="copy" viewBox="0 0 24 24"><rect x="8" y="8" width="12" height="13" rx="2"/><path d="M16 8V3H3v13h5"/></symbol>
|
||||||
|
<symbol id="check" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6"/></symbol>
|
||||||
|
<symbol id="discord" viewBox="0 0 24 24"><path d="M8 5 4 6C2 9 1 13 2 17l5 2 1-2m8-12 4 1c2 3 3 7 2 11l-5 2-1-2M7 7c3-1 7-1 10 0M6 16c4 2 8 2 12 0"/><circle cx="8" cy="12" r="1.5"/><circle cx="16" cy="12" r="1.5"/></symbol>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
+49
-32
@@ -1,36 +1,53 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Discord Bot Dashboard</title>
|
<meta name="theme-color" content="#302224" />
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<title>The Steward</title>
|
||||||
</head>
|
<link rel="icon" href="mark.svg" type="image/svg+xml" />
|
||||||
<body>
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<div class="app">
|
</head>
|
||||||
<header class="topbar">
|
<body class="landing-body">
|
||||||
<div>
|
<div class="landing-shell">
|
||||||
<div class="badge">Discord Bot Dashboard</div>
|
<header class="landing-nav">
|
||||||
<h1>Manage your moderation settings</h1>
|
<a class="brand" href="/" aria-label="The Steward home"
|
||||||
<p class="muted">Configure permissions and warnings without touching JSON.</p>
|
><img src="mark.svg" alt="" /><span
|
||||||
</div>
|
><small>THE</small>Steward<span class="brand-period">.</span></span
|
||||||
<div class="button-row">
|
></a
|
||||||
<a class="button button--primary" href="/auth/discord" id="loginButton">Log in with Discord</a>
|
>
|
||||||
<a class="button" href="/dashboard.html" id="dashboardButton" hidden>Open dashboard</a>
|
</header>
|
||||||
</div>
|
<main class="landing-main">
|
||||||
</header>
|
<div class="landing-copy">
|
||||||
|
<h1>Welcome.</h1>
|
||||||
<div class="banner" id="banner"></div>
|
<p class="landing-description">Manage your Discord servers.</p>
|
||||||
|
<div class="button-row">
|
||||||
<section class="panel">
|
<a
|
||||||
<h2>Session status</h2>
|
class="button button--primary"
|
||||||
<p class="muted" id="sessionStatus">Checking your session...</p>
|
href="/auth/discord"
|
||||||
<div class="button-row" style="margin-top: 16px;">
|
id="loginButton"
|
||||||
<button class="button button--ghost" id="logoutButton" hidden>Log out</button>
|
><svg><use href="icons.svg#discord" /></svg>Sign in with Discord
|
||||||
</div>
|
<span>↗</span></a
|
||||||
</section>
|
>
|
||||||
|
<a
|
||||||
|
class="button button--dark"
|
||||||
|
href="/dashboard.html"
|
||||||
|
id="dashboardButton"
|
||||||
|
hidden
|
||||||
|
>Open dashboard <span>↗</span></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="landing-session">
|
||||||
|
<span id="sessionStatus">Checking session…</span
|
||||||
|
><button class="text-link" id="logoutButton" hidden>Log out</button>
|
||||||
|
</div>
|
||||||
|
<div class="banner" id="banner" role="status"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="landing-art">
|
||||||
<script src="app.js"></script>
|
<img src="concierge.svg" alt="Brass service bell" />
|
||||||
</body>
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><rect width="48" height="48" rx="5" fill="#bd9b61"/><g stroke="#302224" stroke-width="1.8" stroke-linecap="round" fill="none"><path d="M10 32h28M13 29a11 11 0 0 1 22 0zM24 18v-4m-3 0h6M8 36h32"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 266 B |
+1818
-982
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user