Web dashboard v2 step 3: dashboard restyle
Restyle every template under src/web/templates/dashboard/ plus the shared partials _candidate_row.html, _signals_table.html and _pagination.html with Tailwind utilities and the design system step 2 built, so the dashboard reads as the sans-serif instrument-panel half of the same publication as the reader pages: a page header on every page, stat tiles, compact tables with sticky heads inside .scroll-x, tinted badges, one inline filter form per page, settings groups with a sticky save bar, job cards, a scrolling journal block and a rating widget that fits inside a table cell. tailwind.css gains the dashboard vocabulary (.page-head/.page-desc/ .page-actions, .tiles/.tile*, .cell-wrap, .scroll-x.tall, .filters, .pager/.tabs, .setting*/.save-bar, .disclosure, .sparklines/.spark-figure, td .rating overrides, tr.superseded) plus a styled <meter> and a disabled control state. Renames class="inline" to form-inline: Tailwind emits an .inline display utility that was beating form.inline's layout. Three small backend changes: job cards show the status and time of their last run (attach_last_runs, no extra query, unit-tested); the Stats page passes "stats" as its nav key so the admin nav no longer highlights Overview; and the settings load-error banner drops its "! " prefix, with its assertion updated. cargo test: 477 passed. npm run css:check clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
1 parent
29c66da865
commit
a4c338a9f9
24 files changed
+665
-259
No files matched your search
@@ -1,28 +1,34 @@
|
||||
{% extends "layout.html" %}{% block content %}<section class="dashboard">
|
||||
<header class="page-head"><div>
|
||||
<h1>Jobs</h1>
|
||||
{% if jobs_enabled %}<p class="muted">Each job starts <code>daily-epub-job@<name>.service</code> through systemd; the unit runs <code>daily-epub job run <name></code> and records itself here. Jobs that take the run lock wait for nothing: a second <code>generate</code> while one is running fails immediately.</p>{% else %}<p class="notice">Jobs are disabled on this server (<code>server.jobs_enabled = false</code>); run the commands by hand instead.</p>{% endif %}
|
||||
<div class="cards job-cards">{% for card in cards %}<section class="card">
|
||||
<h2>{{ card.name }}</h2>
|
||||
<p>{{ card.description }}</p>
|
||||
{% if let Some(lock) = card.lock %}<p class="muted">Takes the <code>{{ lock }}</code> lock.</p>{% endif %}
|
||||
<form method="post" action="/dashboard/jobs/{{ card.name }}" class="inline"{% if card.dangerous %} data-confirm="Start {{ card.name }}? This republishes the issue of that date."{% endif %}>
|
||||
{% if card.dated %}<label>Date <input type="date" name="date" value="{{ today }}"></label>{% endif %}
|
||||
<button type="submit"{% if !jobs_enabled %} disabled{% endif %}>Start</button>
|
||||
<p class="page-desc">{% if jobs_enabled %}Each job starts <code>daily-epub-job@<name>.service</code> through systemd; the unit runs <code>daily-epub job run <name></code> and records itself here.{% else %}Jobs are disabled on this server; the catalogue below is read-only.{% endif %}</p>
|
||||
</div><div class="page-actions"><a class="btn" href="/dashboard/runs">Runs</a><a class="btn" href="/dashboard/settings">Settings</a></div></header>
|
||||
{% if jobs_enabled %}<p class="muted text-sm">Jobs that take the run lock wait for nothing: a second <code>generate</code> while one is running fails immediately.</p>{% else %}<p class="notice">Jobs are disabled on this server (<code>server.jobs_enabled = false</code>); run the commands by hand instead.</p>{% endif %}
|
||||
<div class="cards job-cards">{% for card in cards %}<section class="card flex flex-col">
|
||||
<div class="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1">
|
||||
<h2 class="font-mono text-base">{{ card.name }}</h2>
|
||||
{% if let Some(status) = card.last_status %}<span class="badge {{ status }}">{{ status }}</span>{% else %}<span class="muted text-xs uppercase tracking-[0.1em]">never run</span>{% endif %}
|
||||
</div>
|
||||
<p class="text-muted">{{ card.description }}</p>
|
||||
<p class="text-xs text-muted">{% if let Some(id) = card.last_id %}Last started <a href="/dashboard/jobs/{{ id }}">{% if let Some(when) = card.last_requested %}{{ when }}{% else %}job {{ id }}{% endif %}</a>{% else %}No run recorded yet.{% endif %}{% if let Some(lock) = card.lock %} · takes the <code class="whitespace-nowrap">{{ lock }}</code> lock{% endif %}</p>
|
||||
<form method="post" action="/dashboard/jobs/{{ card.name }}" class="form-inline mt-auto pt-1"{% if card.dangerous %} data-confirm="Start {{ card.name }}? This republishes the issue of that date."{% endif %}>
|
||||
{% if card.dated %}<label class="text-xs uppercase tracking-[0.1em] text-muted">Date <input type="date" name="date" value="{{ today }}" class="text-sm normal-case tracking-normal text-ink"></label>{% endif %}
|
||||
<button class="btn" type="submit"{% if !jobs_enabled %} disabled{% endif %}>Start</button>
|
||||
</form>
|
||||
</section>{% endfor %}</div>
|
||||
<h2>History</h2>
|
||||
{% if jobs.is_empty() %}<p class="muted">No jobs recorded yet.</p>{% else %}<div class="scroll-x"><table data-filter>
|
||||
<thead><tr><th>job</th><th>name</th><th>requested by</th><th>requested</th><th>started</th><th>finished</th><th class="num">duration</th><th>status</th><th>message</th><th>run</th></tr></thead>
|
||||
{% if jobs.is_empty() %}<p class="muted text-sm">No jobs recorded yet.</p>{% else %}<div class="scroll-x tall"><table data-filter>
|
||||
<thead><tr><th class="num">job</th><th>name</th><th>requested by</th><th>requested</th><th>started</th><th>finished</th><th class="num">duration</th><th>status</th><th>message</th><th class="num">run</th></tr></thead>
|
||||
<tbody>{% for job in jobs %}<tr>
|
||||
<td><a href="/dashboard/jobs/{{ job.id }}">{{ job.id }}</a></td>
|
||||
<td>{{ job.name }}</td>
|
||||
<td>{{ job.requested_by }}</td>
|
||||
<td>{{ job.requested }}</td>
|
||||
<td>{{ job.started }}</td>
|
||||
<td>{{ job.finished }}</td>
|
||||
<td class="num"><a href="/dashboard/jobs/{{ job.id }}">{{ job.id }}</a></td>
|
||||
<td class="cell-tight font-mono text-xs">{{ job.name }}</td>
|
||||
<td class="cell-tight text-muted">{{ job.requested_by }}</td>
|
||||
<td class="cell-tight text-muted">{{ job.requested }}</td>
|
||||
<td class="cell-tight text-muted">{{ job.started }}</td>
|
||||
<td class="cell-tight text-muted">{{ job.finished }}</td>
|
||||
<td class="num">{{ job.duration }}</td>
|
||||
<td><span class="badge {{ job.status }}">{{ job.status }}</span></td>
|
||||
<td class="message">{% if let Some(message) = job.message %}{{ message }}{% endif %}</td>
|
||||
<td>{% if let Some(run_id) = job.run_id %}<a href="/dashboard/runs/{{ run_id }}">{{ run_id }}</a>{% endif %}</td>
|
||||
<td class="cell-tight"><span class="badge {{ job.status }}">{{ job.status }}</span></td>
|
||||
<td class="message cell-wrap text-muted">{% if let Some(message) = job.message %}{{ message }}{% endif %}</td>
|
||||
<td class="num">{% if let Some(run_id) = job.run_id %}<a href="/dashboard/runs/{{ run_id }}">{{ run_id }}</a>{% endif %}</td>
|
||||
</tr>{% endfor %}</tbody></table></div>{% endif %}
|
||||
</section>{% endblock %}
|
||||
Reference in new issue
Block a user