e536fdd2c0
Six tabs (Queue, On-call, Alerts, Team, Admin, Account) had already
outgrown the bottom bar once: 56b8191 let it auto-size its columns to
fit however many there were, but on a phone that only left each tab
55-65px wide. Squeezing further wasn't an option, so the bar is gone
on mobile and a hamburger button in the topbar opens a menu instead.
The menu reuses the sheet + menu-item pattern already used for the
snooze and assign-to actions in incident.js, rather than a new overlay
component. It lists the same sections the sidebar does, including
hiding Admin for non-admins, and the triggered-incident badge that
used to sit on the Queue tab icon now shows on the hamburger button.
Desktop (>=900px) is untouched: the sidebar is the same markup, CSS
alone hides it below 900px and shows it above, so nothing there
changed behaviourally.
Claude-Session: https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7
146 lines
7.1 KiB
HTML
146 lines
7.1 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<meta name="color-scheme" content="light dark">
|
|
<meta name="theme-color" content="#f5f6f8" media="(prefers-color-scheme: light)">
|
|
<meta name="theme-color" content="#0f1115" media="(prefers-color-scheme: dark)">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
<meta name="apple-mobile-web-app-title" content="terdut">
|
|
<title>terdut</title>
|
|
<link rel="manifest" href="/manifest.webmanifest">
|
|
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
<link rel="stylesheet" href="/app.css">
|
|
<script type="module" src="/js/app.js"></script>
|
|
</head>
|
|
<body>
|
|
<div id="boot" class="boot" aria-busy="true"><span class="spinner"></span></div>
|
|
|
|
<main id="login" class="login" hidden>
|
|
<form id="login-form" class="login-card" autocomplete="on">
|
|
<div class="login-brand">
|
|
<img src="/icon.svg" alt="" width="40" height="40">
|
|
<h1>terdut</h1>
|
|
</div>
|
|
<label>
|
|
<span>Username</span>
|
|
<input name="username" autocomplete="username" autocapitalize="none" spellcheck="false" required>
|
|
</label>
|
|
<label>
|
|
<span>Password</span>
|
|
<input name="password" type="password" autocomplete="current-password" required>
|
|
</label>
|
|
<p class="form-error" role="alert" hidden></p>
|
|
<button class="btn btn-primary btn-block" type="submit">Sign in</button>
|
|
<p class="login-hint">No password yet? Ask an admin to set one, or run
|
|
<code>PUT /api/users/{id}/password</code> with your API key.</p>
|
|
<p class="login-hint" id="signup-link" hidden>
|
|
No account? <a href="/signup">Create one</a>.</p>
|
|
</form>
|
|
|
|
<!-- Sign-up. Shown instead of the login card at /signup, and only offers
|
|
what the server allows: an invite link, or open sign-up. -->
|
|
<form id="signup-form" class="login-card" autocomplete="on" hidden>
|
|
<div class="login-brand">
|
|
<img src="/icon.svg" alt="" width="40" height="40">
|
|
<h1>terdut</h1>
|
|
</div>
|
|
<p class="signup-intro" id="signup-intro"></p>
|
|
<label>
|
|
<span>Username</span>
|
|
<input name="username" autocomplete="username" autocapitalize="none" spellcheck="false" required>
|
|
</label>
|
|
<label>
|
|
<span>Email</span>
|
|
<input name="email" type="email" autocomplete="email" required>
|
|
</label>
|
|
<label>
|
|
<span>Password</span>
|
|
<input name="password" type="password" autocomplete="new-password" minlength="10" required>
|
|
</label>
|
|
<label id="signup-team-label" hidden>
|
|
<span>Team name</span>
|
|
<input name="team_name" autocomplete="off">
|
|
</label>
|
|
<p class="form-error" role="alert" hidden></p>
|
|
<button class="btn btn-primary btn-block" type="submit">Create account</button>
|
|
<p class="login-hint">Already have one? <a href="/">Sign in</a>.</p>
|
|
</form>
|
|
</main>
|
|
|
|
<div id="app" class="app" hidden>
|
|
<nav class="nav" aria-label="Sections">
|
|
<a class="nav-brand" href="/">
|
|
<img src="/icon.svg" alt="" width="28" height="28">
|
|
<span>terdut</span>
|
|
</a>
|
|
<a class="nav-link" href="/" data-section="queue" aria-label="Queue">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h10"/></svg>
|
|
<span class="nav-label">Queue</span>
|
|
<span class="nav-badge" data-badge hidden></span>
|
|
</a>
|
|
<a class="nav-link" href="/oncall" data-section="oncall" aria-label="On-call">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 10h17M8 3v4M16 3v4"/></svg>
|
|
<span class="nav-label">On-call</span>
|
|
</a>
|
|
<a class="nav-link" href="/alerts" data-section="alerts" aria-label="Alerts">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 16V11a6 6 0 0 1 12 0v5l1.5 2h-15z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></svg>
|
|
<span class="nav-label">Alerts</span>
|
|
</a>
|
|
<a class="nav-link" href="/team" data-section="team" aria-label="Team">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="9" cy="8" r="3"/><circle cx="17" cy="9" r="2.5"/><path d="M3 19a6 6 0 0 1 12 0M15 19a5 5 0 0 1 6-4"/></svg>
|
|
<span class="nav-label">Team</span>
|
|
</a>
|
|
<!-- Hidden unless the signed-in user is a system administrator; app.js
|
|
unhides it once /api/me says so. The server refuses every admin
|
|
endpoint regardless, so this is a courtesy and not a gate. -->
|
|
<a class="nav-link" href="/admin" data-section="admin" aria-label="Admin" id="nav-admin" hidden>
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3l7 3v6c0 4-3 7-7 9-4-2-7-5-7-9V6z"/></svg>
|
|
<span class="nav-label">Admin</span>
|
|
</a>
|
|
<a class="nav-link" href="/more" data-section="more" aria-label="Account">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="3.5"/><path d="M5 20a7 7 0 0 1 14 0"/></svg>
|
|
<span class="nav-label">Account</span>
|
|
</a>
|
|
</nav>
|
|
|
|
<header class="topbar">
|
|
<div class="topbar-left">
|
|
<button class="btn btn-ghost btn-icon" id="menu-btn" type="button" aria-label="Menu" aria-haspopup="menu">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
|
|
<span class="nav-badge menu-btn-badge" data-badge hidden></span>
|
|
</button>
|
|
<h1 class="topbar-title" id="topbar-title">Queue</h1>
|
|
</div>
|
|
<span class="open-pill" id="open-pill" hidden></span>
|
|
</header>
|
|
|
|
<section id="view-queue" class="view view-queue" data-view="queue">
|
|
<div class="pane pane-list">
|
|
<div class="chips" id="queue-filters" role="tablist" aria-label="Filter"></div>
|
|
<div id="queue-list" class="list"></div>
|
|
</div>
|
|
<div class="pane pane-detail" id="detail" aria-live="polite"></div>
|
|
</section>
|
|
|
|
<section id="view-oncall" class="view view-page" data-view="oncall" hidden></section>
|
|
<section id="view-alerts" class="view view-page" data-view="alerts" hidden></section>
|
|
<section id="view-team" class="view view-page" data-view="team" hidden></section>
|
|
<section id="view-admin" class="view view-page" data-view="admin" hidden></section>
|
|
<!-- One person, at /admin/users/{id}: reached from the Admin tab's user
|
|
list, and a section of its own so a deep link survives a reload. -->
|
|
<section id="view-adminuser" class="view view-page" data-view="adminuser" hidden></section>
|
|
<!-- One team, at /admin/teams/{id}: who is in it and the invites into it,
|
|
which the Team tab cannot show for a team you are not a member of. -->
|
|
<section id="view-adminteam" class="view view-page" data-view="adminteam" hidden></section>
|
|
<section id="view-more" class="view view-page" data-view="more" hidden></section>
|
|
</div>
|
|
|
|
<dialog id="sheet" class="sheet"></dialog>
|
|
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
|
|
</body>
|
|
</html>
|