Skip to content

feat(server): browser status dashboard at /teamclaude/dashboard - #188

Open
Frozen666 wants to merge 2 commits into
KarpelesLab:masterfrom
Frozen666:feat/status-dashboard
Open

feat(server): browser status dashboard at /teamclaude/dashboard#188
Frozen666 wants to merge 2 commits into
KarpelesLab:masterfrom
Frozen666:feat/status-dashboard

Conversation

@Frozen666

@Frozen666 Frozen666 commented Aug 24, 2026

Copy link
Copy Markdown

Motivation

The existing read surfaces — the TUI, teamclaude status, teamclaude attach — all require shell access to the machine running the proxy. For a proxy shared by several key holders, a URL anyone with a key can open is the friendlier way to check quota and rotation state.

What this adds

GET /teamclaude/dashboard — one self-contained HTML page rendering what teamclaude status shows: per-account cards with quota bars (session 5h / weekly 7d, plus Sonnet/Fable buckets when present, with countdown and absolute reset time), status badges, current-account marker, usage totals, active-session counts. Auto-refreshes every 5s.

If the status payload carries a clients section (#186), a per-client usage table renders too — without it the section simply stays hidden, so this PR is independent of #186.

Design notes

  • The asset is served before the key gate on purpose: the page contains no data — everything it shows comes from /teamclaude/status, which stays gated and is fetched by the page's script with the key (asked once, kept in localStorage, re-prompted on 401). A browser address bar cannot send x-api-key, so gating the asset would 401 every remote browser without protecting anything. The route is exact-path GET only; a POST to the same path flows down the normal pipeline (covered by test).
  • Self-contained: no external scripts, styles, or fonts — nothing to leak the localStorage-held key to, and it works on air-gapped deployments. All rendering uses textContent, so account/client names never reach innerHTML.
  • Status timestamps arrive in both shapes (epoch-ms numbers for quota resets and account lastUsed, ISO strings elsewhere); the page parses both.
  • Loopback stays key-exempt as everywhere else (a local browser sees status without entering a key).

Tests

test/dashboard.test.js: the page is self-contained (no external src/href/@import) and fetches the gated status endpoint; GET serves HTML without a key while a POST to the same path is proxied normally. Full suite passes, lint clean.

🤖 Generated with Claude Code

Frozen666 and others added 2 commits August 24, 2026 17:18
A self-contained HTML page rendering the same data as `teamclaude status`:
per-account quota bars (session/weekly + Sonnet/Fable buckets), rotation
state, active sessions, auto-refreshing. The asset is served without the key
on purpose — it contains no data; its script fetches the gated
/teamclaude/status with the proxy key (asked once, kept in localStorage,
re-prompted on 401). No external scripts/styles/fonts; all rendering via
textContent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L7DVJAvCRWgtw1TvNgsq4b
Quota resets and account lastUsed arrive as epoch milliseconds, which
Date.parse() cannot read — the reset countdown silently never rendered.
Parse both shapes (ms numbers and ISO strings), and show the wall-clock
reset time next to the countdown: "93% · 2.1h · 17:30" ("Wed 09:00"
beyond 24h).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L7DVJAvCRWgtw1TvNgsq4b
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