Plan showing an unreachable origin in the web UI (step 19)
A wrong git token made the watcher fail every fetch for 57 minutes while the branches view kept showing its last known list. WatcherState already records lastFetchError and /api/watcher already serves it; only the UI never renders it. The step also folds in the logging volume: one outage wrote 297 identical warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ef8b46b6e1
commit
18a41e9ced
@@ -0,0 +1,52 @@
|
|||||||
|
# Step 19: Show a stalled watcher in the web UI
|
||||||
|
|
||||||
|
Prerequisites: none — the data already exists, only nothing renders it.
|
||||||
|
Read `README.md` first.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
On 2026-08-30 the Gitea token in the machine config on vm4006 was replaced by a placeholder string.
|
||||||
|
For 57 minutes GitTally failed `git fetch --prune origin` every ten seconds and wrote 297 warnings to the journal.
|
||||||
|
The branches view showed a calm, ordinary list the whole time: every branch with its last build, nothing amiss.
|
||||||
|
The failure was noticed only because an expected build did not start, and it took reading the journal to see why.
|
||||||
|
|
||||||
|
A watcher that cannot reach origin means every branch row on the page is stale — the one thing the page exists to tell.
|
||||||
|
Silence is the wrong answer, and the journal is not the user interface.
|
||||||
|
|
||||||
|
## What Already Works
|
||||||
|
|
||||||
|
`Watcher.poll` records the failure: `state = state.copy(lastPollAt = …, lastFetchError = e.message ?: …)`, cleared again after a clean cycle.
|
||||||
|
`WatcherApiController` serves it at `/api/watcher` as `WatcherState`, which distinguishes three failure modes:
|
||||||
|
|
||||||
|
- `lastFetchError` — origin unreachable (credentials, network, gone remote);
|
||||||
|
- `lastPollError` — the cycle crashed after a successful fetch;
|
||||||
|
- `running: false` — the poll loop is not scheduled at all.
|
||||||
|
|
||||||
|
All three mean the same thing to a reader: what you see is not current.
|
||||||
|
The endpoint needs no change.
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
- `static/gittally.js`: fetch `/api/watcher` from the same polling cycle that refreshes the view, and show a banner while any of the three conditions holds.
|
||||||
|
Keep the existing discipline — a timeout on the fetch, and a failure of *this* request must never break the view's own refresh.
|
||||||
|
- `templates/fragments.html`: add a `watcher-banner` fragment to the `nav(view)` row so every view inherits it; hidden unless the script fills it.
|
||||||
|
- Wording says what is stale and since when, not just that something failed: the branch list is not updating, since `lastPollAt`, because `<error>`.
|
||||||
|
Timestamps go through the shared formatting — `UiFormats` and `gittally.js` must produce identical formats (invariant in `AGENTS.md`).
|
||||||
|
- Do not overload `live-indicator`: it reports whether the *browser* reaches the server.
|
||||||
|
This banner reports whether the *server* reaches origin. Two independent failures, two independent signals.
|
||||||
|
|
||||||
|
While in there, fix the logging volume: one outage produced 297 identical warnings.
|
||||||
|
Log the fetch failure when its message changes, not on every cycle, and log once more when the fetch succeeds again.
|
||||||
|
This is the same class as the `atTimes` warning that repeats every poll for an invalid slot — fix both or neither, but do not leave the new one behind.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- `server/WatcherApiControllerTest.kt` — assert that a state carrying `lastFetchError` reaches the JSON, so the field the UI depends on is covered.
|
||||||
|
- `watcher/WatcherTest.kt` — a failing `fetchOrigin` sets `lastFetchError` and a following clean cycle clears it; a repeated identical failure logs once.
|
||||||
|
- The JavaScript has no test harness; the banner is verified manually below.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Break it deliberately in a scratch repo install: set `git.token` to a wrong value, and watch the banner appear within one poll interval and disappear again after fixing it.
|
||||||
|
- Stop the watcher (`running: false`) and confirm the banner says so in its own words rather than reporting a fetch error.
|
||||||
|
- On a narrow viewport the banner must not push the table off screen.
|
||||||
@@ -82,6 +82,10 @@ Added after v0.9.19 replaced the per-branch settings with build definitions (202
|
|||||||
|
|
||||||
- [ ] `18-remove-branches-section.md` — delete the legacy `branches` section and its `autoBuild` schedule; run around 2026-09-05, after the precondition check in the step file
|
- [ ] `18-remove-branches-section.md` — delete the legacy `branches` section and its `autoBuild` schedule; run around 2026-09-05, after the precondition check in the step file
|
||||||
|
|
||||||
|
Added after a silent 57-minute fetch outage on vm4006 (2026-08-30):
|
||||||
|
|
||||||
|
- [ ] `19-watcher-health-in-ui.md` — show an unreachable origin in the web UI instead of only in the journal
|
||||||
|
|
||||||
Added for running GitTally on Hostsharing Managed Webspaces (2026-08-10):
|
Added for running GitTally on Hostsharing Managed Webspaces (2026-08-10):
|
||||||
|
|
||||||
- [ ] `17-bwrap-build-runtime.md` — GitTally on a Managed Webspace: bubblewrap user-namespace build sandbox with a prepared rootfs (precondition check first — see the step file), plus web access under a domain via the platform's Apache proxy and Let's Encrypt
|
- [ ] `17-bwrap-build-runtime.md` — GitTally on a Managed Webspace: bubblewrap user-namespace build sandbox with a prepared rootfs (precondition check first — see the step file), plus web access under a domain via the platform's Apache proxy and Let's Encrypt
|
||||||
@@ -94,3 +98,4 @@ Step 13 depends on 07, 11, and 12.
|
|||||||
Step 15 depends on 12 and 13 and revises the containerized-runtime sketch in `docs/bootstrapping.md` (ADR 0006 is written as part of the step; GraalVM native image was evaluated and rejected there).
|
Step 15 depends on 12 and 13 and revises the containerized-runtime sketch in `docs/bootstrapping.md` (ADR 0006 is written as part of the step; GraalVM native image was evaluated and rejected there).
|
||||||
Step 17 depends on 11, 15, and 16, and starts with a hard precondition check on the target webspace (ADR 0007 is written as part of the step).
|
Step 17 depends on 11, 15, and 16, and starts with a hard precondition check on the target webspace (ADR 0007 is written as part of the step).
|
||||||
Step 18 depends on nothing in code but on the watched repository having migrated — its precondition check is a hard gate, not a formality.
|
Step 18 depends on nothing in code but on the watched repository having migrated — its precondition check is a hard gate, not a formality.
|
||||||
|
Step 19 depends on nothing; `WatcherState` and `/api/watcher` already carry everything it needs to render.
|
||||||
|
|||||||
Reference in New Issue
Block a user