a8dc89e23d
Secondary text was too dim to read: --faint sat at about 4.0:1 in the dark theme and 3.3:1 in the light one, and it carries row ages, hints and labels. It now clears 4.5:1 in both. The dark surfaces and borders are a step further apart so cards stand out from the page, and the light borders a touch stronger. Account has an Appearance section with System, Light and Dark. System is the old behaviour. The choice is per browser, kept in localStorage, and is applied by a small js/theme.js loaded from <head> so there is no flash of the other theme; the CSP allows no inline script, hence a file of its own. On-call is redesigned: a hero card for who is on call now, with when the shift ends for the selected team, and the week as seven day cells instead of grouped rows. Today is marked with a neutral tint and a bar rather than the accent colour, which now means only things you can act on; your own days are marked by the "you" badge, not a fill. On a wide screen the hero and week sit beside your shifts under a page title. The page stays read-only: shifts are still edited in the TUI. Smaller fixes: the team switcher no longer appears in the phone's bottom bar as well as the top bar (a shared rule overrode the one that hides it); the Team and Admin tab strips fade at the edge and scroll the open section into view, so "Sources" is no longer cut off; and "All clear" is a green status badge with a check instead of a grey pill that looked like a button. Web UI only: no endpoint or JSON shape changed, so nothing to mirror in terdut-tui.
174 lines
9.1 KiB
HTML
174 lines
9.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 src="/js/theme.js"></script>
|
|
<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>
|
|
<!-- Why a sign-in failed, when the identity provider sent the browser back
|
|
here with ?sso_error=. Kept apart from the password form's own error. -->
|
|
<p class="form-error" id="sso-error" role="alert" hidden></p>
|
|
<!-- A plain link, not a fetch: the browser has to navigate to the provider,
|
|
and the page's CSP allows no connection to anywhere else. -->
|
|
<a class="btn btn-primary btn-block" id="sso-link" href="/api/oidc/login" hidden>Sign in with SSO</a>
|
|
<p class="login-divider" id="login-or" hidden><span>or</span></p>
|
|
<div class="login-password" id="password-login">
|
|
<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>
|
|
</div>
|
|
</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>
|
|
<!-- Which team the app is scoped to. Hidden unless the signed-in user is
|
|
in more than one; teamselector.js fills it in and wires the click. -->
|
|
<button class="nav-team-selector" id="team-selector" type="button" hidden></button>
|
|
<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>
|
|
<!-- Secondary: full-width in the desktop sidebar, folded into the
|
|
"More" tab's sheet on the phone-width bottom bar instead (see
|
|
.nav-link-secondary in app.css and openNavMenu in app.js). -->
|
|
<a class="nav-link nav-link-secondary" href="/stats" data-section="stats" aria-label="Stats">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20h16M7 20v-7M12 20V6M17 20v-10"/></svg>
|
|
<span class="nav-label">Stats</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 nav-link-secondary" 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 nav-link-secondary" 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>
|
|
<!-- Phone-width only (see .nav-more-btn in app.css): opens the same
|
|
sheet the old hamburger button did, for the sections the bottom
|
|
bar has no room for. Not shown on the desktop sidebar, which lists
|
|
every section already. -->
|
|
<button class="nav-link nav-more-btn" id="nav-more-btn" type="button" aria-label="More sections" aria-haspopup="menu">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h.01M12 12h.01M19 12h.01"/></svg>
|
|
<span class="nav-label">More</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<header class="topbar">
|
|
<div class="topbar-left">
|
|
<button class="team-selector-mobile" id="team-selector-mobile" type="button" hidden></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-stats" class="view view-page" data-view="stats" 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>
|
|
<!-- Approve a terminal's sign-in, at /device?code=...: the page its prompt links to. -->
|
|
<section id="view-device" class="view view-page" data-view="device" hidden></section>
|
|
</div>
|
|
|
|
<dialog id="sheet" class="sheet"></dialog>
|
|
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
|
|
</body>
|
|
</html>
|