Skip to content

feat(web): read-only queue and job views - #6

Merged
fahimfaisaal merged 13 commits into
mainfrom
claude/queue-views-edccab
Nov 5, 2026
Merged

fahimfaisaal merged 13 commits into
mainfrom
claude/queue-views-edccab

Conversation

@fahimfaisaal

@fahimfaisaal fahimfaisaal commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Summary

Read-only queue and job views on varmq queue routes (goptics/varmq#173, merged): GET /queues, GET /queues/{id}/jobs?state&offset&limit. Job types from github.com/goptics/varmq/contract v0.1.0 (zero-dep module, nothing else from varmq). Merged with main (#7 a11y fixes).

 go.mod                          # + github.com/goptics/varmq/contract v0.1.0
 internal/
 ├── api/
+│   └── queues.go          # ListQueues, ListJobs; Queue ID escaped as one path segment; 404/405 = no queue data
 ├── fleet/
+│   ├── queues.go          # FetchQueues from every instance, GroupQueues by Queue ID (one copy, never summed)
 │   └── fleettest/fakevarmq.go   # serves spec-shaped queue fixtures
 └── web/
+    ├── queues.go          # /queues list, /queues/{id} job page
+    └── templates/queue.html, queues.html
     templates/layout.html  # Queues nav link
     static/app.css         # queue/job styles; queues table stacks on phones

Request flow on page load:

GET /queues
  guard: current snapshot, this env's lease, same source (as actions)
  FetchQueues            # one /queues call per instance
  GroupQueues            # by Queue ID; read copy beats error copy; On = instances reporting it
GET /queues/{id}?state=failed&offset=0
  FetchJobs              # first instance; next instance on 404/error
  render one row per job: ID, short time, final error or one-line payload, attempts
  open row (<details>): to-scale timeline with step icons, errors per attempt, payload (indented, cut at 64 KiB)
  • Types: contract.JobRecord, JobError, JobCounts (int64), JobTotals, JobState. Local only api.QueueSummary: /queues wire entry (ID, error, optional counts/totals); contract has no type for it.
  • Job page: state tabs with counts, sticky on scroll (one compact row on phones); jobs as dense rows in one panel; header shows range and state count (1–50 of 10000); short times (clock today, day this year, else date) with full time in title and <time datetime>; timeline steps as inline SVG icons (enqueued, started, finished or failed), hollow when not reached.
  • Errors: one line per failed attempt (errors[], oldest first, max 10): attempt no., <time>, text. Job summary shows final error.
  • a11y as fix(web): audit P2 a11y fixes: headings, down units, partial dots, tips, phone table #7: page h1 (sr-only "Queues"; Queue ID on job page), queues table stacks at <= 640px with data-label, state tabs aria-current, <time datetime>, payload <pre tabindex="0">.
  • Instances without queue routes (older varmq, storage without QueueInspector) named in a note. Worker views unchanged.
  • Collector stores no queue data: no queue history. Job page no auto-refresh (would close an open job).
  • No CLI view: vartop has no worker list command to mirror.

Not covered

  • No single-job route in spec: job has no permalink; pending job with no ID has no key.
  • Paging: spec answer has no total. "Next" hidden when state count (read with the page) says page is last; count can be a moment stale.

Evidence

  • internal/api/queues_test.go: Queue ID escaping ({, }, /), 404/405 fallback, payload bytes kept as sent, errors decode.
  • internal/fleet/queues_test.go: grouping does not sum, unsupported instances, error copies, jobs fallback to next instance.
  • internal/web/queues_test.go: both pages against fake varmq, incl. Queue ID with /, errors list oldest first.
  • go vet ./..., go test -race ./... pass.
  • Browser: dashboard, /queues, /queues/{id} at 375px dark and 800px light, fake fleet.
  • Real fleet: varmq 8cf7a4a (#173) + redisq feat/job-records-inspector, 2 instances on one Redis queue orders/eu (Queue ID with /). List, failed tab, errors, paging checked; found + fixed: same-host instances (same instance name) both labelled instance-1 in "Reported by".
  • GitNexus detect-changes vs main: shared symbols touched are routes (+2 routes), parseTemplates (+2 sets), Page (+2 nil-able fields); callers covered by tests.

Merge Danger

Door: two-way

Additive: new routes, templates, nav link, one zero-dep module. Revert removes it cleanly.

Blast Radius: web

  • Open /queues tab sends one /queues request per instance per poll interval.
  • Pages show "no queue data" until instances run varmq with #173 and a storage with QueueInspector.

Screenshots (before / after)

Fake fleet, 3 instances: instance-1 and instance-3 share two queues; instance-2 older varmq (no queue routes). Images on branch pr-assets/queue-views.

Dashboard nav, 375px, dark: new "Queues" link.

Before After

Dashboard nav, 800px, light

Before After

New pages, no before (main answers 404).

/queues: one row per Queue ID, counts not summed, "Reported by" instances; note names instance-2 (no queue routes). Phone: rows stack, cells labelled.

375px, dark 800px, light

/queues/{id}, failed tab, job expanded: sticky state tabs with counts; list header with range; row with final error; open job: timeline with step icons, 3 errors entries oldest first, indented payload.

375px, dark 800px, light

/queues/{id}, pending tab: jobs with no ID yet; one-line payload preview per row.

375px, dark 800px, light

Per varmq spec/openapi.yaml 1.0.0 (/queues, /queues/{id}/jobs).

- api: ListQueues, ListJobs. Queue ID path-escaped as one segment.
  404/405 = no queue data (older varmq), not error.
- fleet: FetchQueues from every unit, GroupQueues by Queue ID. One
  copy kept, never summed; read copy beats error copy. On = units
  reporting it. FetchJobs falls back to next unit on 404/error.
- web: /queues list + /queues/{id} job page (state tabs, paging,
  <details> per job: payload, times, attempts, last error). Read live
  on GET, same guards as actions: current snapshot, this env's lease,
  same source. No poller on job page.
- fleettest fake serves spec-shaped queue fixtures.

No CLI view: vartop has no worker list command to mirror.
varmq spec b9b5801 drops Job.last_error for errors[]{attempt, at,
error}, oldest first, last 10. Job page lists each failed attempt with
its time; summary keeps the final error.
Unit order is not fixed, so the jobs read may go to b, not a. Old
assertion failed ~half the runs.
Conflict: render.go funcmap; keep queuesURL, jobsURL and domID.

Follow P2 heading change (#7, h2 styles now on h1):
- queue.html: queue ID h2 -> h1, keeps page-title style.
- queues.html: hidden h1 "Queues", like dashboard/grid.
Drop local Job, JobError, QueueCounts, QueueTotals copies for
contract.JobRecord, JobError, JobCounts, JobTotals, JobState. Keep
QueueSummary: /queues wire entry (ID, error, optional counts), no
contract type for it. Counts now int64.
As .workers since #7: rows stack at <= 640px, cells labelled via
data-label, no sideways scroll. Queue ID breaks only when it cannot fit
whole. Failed count red only when > 0.
Jobs answer has no total; a full page last in its state linked to an
empty one. Hide Next when the count read with the page says it ends.
Instances on one host share an instance name; both got the first unit's
label in "Reported by". Endpoint key is exact; instance match is the
fallback for balanced units.
State tabs as count cards with state dots. Each job a card: ID, attempts
pill, time, final error on one line. Open job: timeline, one block per
failed attempt, payload beside them on wide screens. Pager as buttons.
One panel, two-line row per job: ID + short time, then final error
(failed) or one-line payload + attempts. Header shows range and state
count (1–50 of 10000). Full time in title and <time datetime>.
Timeline: inline SVG per step (enqueued, started, finished/failed),
state colored; step not reached hollow. State tabs sticky on scroll;
one compact row on phones.
@fahimfaisaal
fahimfaisaal merged commit 27c5da6 into main Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant