A module that sees something a tech must act on now (a timer overrun, a meter past its limit, timecode gone) raises an alert on a channel, 1–6, and every dashboard listening to that channel flashes: the menu bar — brought out for the duration if it was hidden — and a frame round the screen alternate between the level's two colours, with the alert's text bold across the bar. Low fades slowly, medium faster, critical snaps with no fade and floods the board behind the tiles too, in a muted cut that shows faintly through the tile panels without costing their content any contrast. Server: the hub. One alert per channel (a new one replaces the last), timed out server-side (1–600 s, 15 by default, 0 = held until cleared), broadcast over the shell stream as `alert` / `alert-clear`; GET /api/alerts for a dashboard that opens or reconnects mid-alert; POST /api/alerts and DELETE /api/alerts/<n>, open like a layout save. Modules raise from their server entry with shell.alert() / shell.clearAlert() or from a tile with moduleApi.alert() / moduleApi.clearAlert(); a module's alerts end when it stops. Colours live as alerts.colors in the data directory's proddash.json. Dashboard: the Layouts menu gains a folded Alerts row with six numbered toggles; the channel list saves with the layout, local and named (the server keeps it in a saved layout). Admin: a folded Alerts section with two colour pickers per level, a swatch running each level's cadence, and a Test row. Docs: docs/ALERTS.md (the feature), a "Raising alerts" section in the module guide, README. Tools: tools/alert.js raises/clears/lists from the command line; tools/alerts.test.js drives it all end to end. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
129 lines
5.6 KiB
HTML
129 lines
5.6 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>ProdDash Admin</title>
|
|
<meta name="theme-color" content="#0b0e12">
|
|
<link rel="icon" href="/icons/icon-192.png">
|
|
<link rel="stylesheet" href="/style.css">
|
|
<link rel="stylesheet" href="/admin/admin.css">
|
|
</head>
|
|
<body>
|
|
<header id="topbar">
|
|
<span id="brand">Prod<span class="accent">Dash</span> <span class="admin-tag">Admin</span> <span class="admin-version" id="shell-version"></span></span>
|
|
<span class="spacer"></span>
|
|
<a class="tb-btn" href="/">← Dashboard</a>
|
|
</header>
|
|
|
|
<main id="admin-main">
|
|
<section class="admin-card" id="login-card" hidden>
|
|
<h2>Admin PIN</h2>
|
|
<p class="admin-copy">This page changes the server-wide configuration for every dashboard in the building.</p>
|
|
<div class="login-row">
|
|
<input type="password" id="passcode" class="pin-input" inputmode="numeric" autocomplete="off"
|
|
maxlength="6" placeholder="••••••" aria-label="Admin PIN">
|
|
<button class="btn primary" id="login-btn">Unlock</button>
|
|
</div>
|
|
<div class="admin-status" id="login-status"></div>
|
|
</section>
|
|
|
|
<section class="admin-card update-card" id="update-card" hidden>
|
|
<div class="update-head">
|
|
<span class="dot connecting"></span>
|
|
<strong id="update-title">Update available</strong>
|
|
<span class="spacer"></span>
|
|
<a class="btn small" id="update-changes" href="#" target="_blank" rel="noopener">What changed</a>
|
|
<button class="btn small primary" id="update-btn">Update now</button>
|
|
</div>
|
|
<p class="admin-copy" id="update-copy"></p>
|
|
<div class="admin-status" id="update-status"></div>
|
|
</section>
|
|
|
|
<section id="theme-section" hidden>
|
|
<details class="admin-fold">
|
|
<summary class="admin-h1 admin-fold-title">Theme</summary>
|
|
<div class="theme-swatches" id="theme-swatches" role="radiogroup" aria-label="Theme"></div>
|
|
<div class="admin-status" id="theme-status"></div>
|
|
</details>
|
|
</section>
|
|
|
|
<section id="modules-section" hidden>
|
|
<h1 class="admin-h1">Modules</h1>
|
|
<p class="admin-copy admin-storage" id="storage-note" hidden>Saved on the server at
|
|
<code id="storage-path"></code> — outside the app folder, so updating ProdDash keeps them.</p>
|
|
<div id="modules-list"></div>
|
|
</section>
|
|
|
|
<section id="available-section" hidden>
|
|
<h1 class="admin-h1">Available modules</h1>
|
|
<div class="catalog-toolbar">
|
|
<button class="btn small primary" id="install-selected" hidden>Install selected</button>
|
|
<button class="btn small" id="catalog-refresh">Check again</button>
|
|
<span class="admin-status" id="catalog-status"></span>
|
|
</div>
|
|
<div id="available-list"></div>
|
|
</section>
|
|
|
|
<section id="layouts-section" hidden>
|
|
<h1 class="admin-h1">Saved layouts</h1>
|
|
<div id="layouts-list"></div>
|
|
</section>
|
|
|
|
<section id="alerts-section" hidden>
|
|
<details class="admin-fold">
|
|
<summary class="admin-h1 admin-fold-title">Alerts</summary>
|
|
<p class="admin-copy">A module that sees something a tech must act on raises an alert on one of
|
|
six channels, and every dashboard listening to that channel flashes — menu bar and screen
|
|
edge — with the alert's text across the bar. Each level alternates between two colours;
|
|
pick them here. A dashboard chooses its channels in its Layouts menu, under Alerts.</p>
|
|
<div class="admin-card">
|
|
<div class="alert-levels" id="alert-levels"></div>
|
|
<div class="alert-test">
|
|
<span class="alert-test-label">Test on channel</span>
|
|
<select id="alert-test-channel" class="alert-test-select" aria-label="Channel to test on"></select>
|
|
<button class="btn small alert-test-btn" data-level="low">Low</button>
|
|
<button class="btn small alert-test-btn" data-level="medium">Medium</button>
|
|
<button class="btn small alert-test-btn" data-level="critical">Critical</button>
|
|
</div>
|
|
<div class="admin-status" id="alerts-status"></div>
|
|
</div>
|
|
</details>
|
|
</section>
|
|
|
|
<section id="security-section" hidden>
|
|
<h1 class="admin-h1">Admin PIN</h1>
|
|
<p class="admin-copy" id="pin-state"></p>
|
|
<div class="admin-card">
|
|
<div class="pin-form">
|
|
<label class="pin-field" id="pin-current-field" hidden>
|
|
<span>Current PIN</span>
|
|
<input type="password" id="pin-current" class="pin-input" inputmode="numeric"
|
|
autocomplete="off" maxlength="6" placeholder="••••••">
|
|
</label>
|
|
<label class="pin-field">
|
|
<span id="pin-new-label">New PIN</span>
|
|
<input type="password" id="pin-new" class="pin-input" inputmode="numeric"
|
|
autocomplete="off" maxlength="6" placeholder="6 digits">
|
|
</label>
|
|
<label class="pin-field">
|
|
<span>Repeat</span>
|
|
<input type="password" id="pin-repeat" class="pin-input" inputmode="numeric"
|
|
autocomplete="off" maxlength="6" placeholder="6 digits">
|
|
</label>
|
|
<div class="pin-actions">
|
|
<button class="btn primary" id="pin-save">Set PIN</button>
|
|
<button class="btn" id="pin-clear" hidden>Turn off</button>
|
|
</div>
|
|
</div>
|
|
<div class="admin-status" id="pin-status"></div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<div id="toast" hidden></div>
|
|
|
|
<script type="module" src="/admin/admin.js"></script>
|
|
</body>
|
|
</html>
|