docs(admin): dashboard page — stats, watcher, Quick Actions, scan panel

Second page of the admin tree. Documents the four-card stats grid with
what each number actually counts (instance-wide totals; Books = every
media item including podcast episodes), the File Watcher card's data
source (/api/scanner/watch/status, armed by Auto-Scan) and its
zero-libraries meaning, Force Rescan All Libraries with its real
mechanics (one job per library, skip-on-active, backfill tool vs.
routine shallow refresh), and the Scanning panel's live progress +
Cancel all scans. Links only to pages that exist (scanning.md); the
Settings reference stays prose until that page lands.

The real-tree nav test's Administration assertion loosens further: guide
leads, everything else strictly follows usabilityOrder — durable across
the remaining page landings. Also fixes the amend this commit replaces:
its first version briefly carried a forward link to the unwritten
settings.md (caught by the link gate, now removed) — the gate worked;
the && chain around go test let the bad commit through, which is exactly
why this amend exists.
This commit is contained in:
John O'Keefe committed 2026-11-02 13:49:23 -05:00
1 parent 89fc479bdb
commit ed550fa914
2 files changed
+76 -4

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
<!-- Ground truth: templates/admin.templ (stats grid, File Watcher card,
Quick Actions, Scanning panel), internal/router/frontend.go (/admin
handler), internal/router/admin_library.go (getAdminStats),
web/src/admin.ts (scanAllLibraries one-job-per-library, watch status
fetch, progress rows), internal/handlers/scanner.go (watch status,
coalescing 409). Re-verify against these when the page changes. -->
# Admin Dashboard
The dashboard at `/admin` is the admin landing page: a four-number health
check, the file-watcher status, and the place where library-wide scans are
launched and watched.
## The stats grid
Four counts, straight from the database, refreshed on every page load:
| Card | What the number counts |
| --- | --- |
| **Libraries** | Every library — book, comic, audiobook, and podcast libraries alike |
| **Books** | Every item across all of them: books, comics, audiobooks, and podcast episodes |
| **Users** | Every account on the instance |
| **Devices** | Every registered sync device (Android, KOReader, Kobo) |
These are instance-wide totals — they don't filter by what any individual
user can see.
## File Watcher
The **File Watcher** card shows how many libraries are currently under
live watch for file changes. The watcher exists when **Auto-Scan** is
enabled on the Settings page (Settings → Scanning); the number updates on
page load.
- Watching **0** libraries usually means auto-scan is off — turn it on in
Settings → Scanning, and the watcher arms on the next server start.
- Watched libraries get shallow scans as files change, plus a periodic
poll on the scan interval — see [Scanning](scanning.md) for exactly what
those do.
## Quick Actions
### Force Rescan All Libraries
The big one. This is a **force rescan of every library**: each file is
re-read and re-extracted with the scanner's current parsing, and hash
identifiers are recomputed — nothing unchanged is skipped. Use it as the
backfill tool after mass edits or scanner improvements; don't use it as a
routine refresh (that's what the watcher and [per-library
scans](scanning.md) are for, which skip unchanged files).
Mechanically it starts **one scan job per library** and reports per
library: started, or skipped because that library already had a scan
running (only one scan per library can run at a time).
### Manage Libraries
A shortcut to [Libraries](libraries.md), where folders, access, and
per-library scans live.
## The Scanning panel
Whenever scans are running, a **Scanning Libraries** panel appears with a
live progress row per library (progress bar, current file counts, and a
per-library cancel button), plus:
- **Cancel all scans** — stops every queued or running scan job
- The close button — hides the panel; scans keep running without it
The panel appears for any scan — the dashboard's own force rescan, or one
started from the Libraries page. Progress updates live while the page is
open; the jobs themselves run on the server either way.
+4 -4
View File
@@ -243,18 +243,18 @@ func TestBuildNavigationUsabilityOrder(t *testing.T) {
}
// Administration grows as its pages are written; pin its shape, not
// its size: the guide leads, Libraries follows, and every other item
// its size: the guide leads (pinned landing), and every other item
// must appear in usabilityOrder's declared relative order.
admin := find("Administration")
if len(admin.Items) < 2 || admin.Items[0].Title != "Admin Guide" || admin.Items[1].Title != "Libraries" {
t.Fatalf("Administration must lead with Admin Guide then Libraries, got %+v", admin.Items)
if len(admin.Items) < 1 || admin.Items[0].Title != "Admin Guide" {
t.Fatalf("Administration must lead with the Admin Guide, got %+v", admin.Items)
}
pos := map[string]int{}
for i, p := range usabilityOrder["Administration"] {
pos[docURL(p)] = i
}
last := -1
for _, item := range admin.Items[2:] {
for _, item := range admin.Items[1:] {
r, ok := pos[item.URL]
if !ok {
t.Errorf("Administration item %q (%s) is not in usabilityOrder", item.Title, item.URL)