sdp-request-watcher/SESSION.md
Skylar Grant 1a9de77dc6
All checks were successful
Build and push image / build (push) Successful in 53s
Note that the bare-metal test run uses port 3005
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:23:47 -04:00

7.8 KiB
Raw Permalink Blame History

Session summary: 2026-10-15

A Claude Code session that turned the closed-ticket board into a dashboard, then rebuilt sign-in so it can be hosted. Background is in CLAUDE.md, docs/ROADMAP.md and docs/NOTES.md.

Where things stand

The dashboard is now a stateless server meant to run as the Docker image v0idf1sh/sdp-dashboard on the RackNerd VPS, behind Nginx Proxy Manager. NPM's access list limits the site to MCCS' public IP blocks. Every user signs in with their own Zoho account. Filtering is still the central region from .env.

It's committed and pushed on branch v2.0.0. Tested live on 2026-10-15 as a bare-metal run on the VPS (Node, PORT=3005) behind dash-test in NPM: Zoho sign-in, live SDP data, Sign out, and the IP access list (mobile data gets 403 Forbidden) all work. Still to check: hourly token renewal, and the first Docker image build and deploy.

Auth rebuild and hosting (this session's last piece of work)

  • Sign-in: Zoho web OAuth (/auth/login → Zoho consent → /auth/callback). It needs a new Server-based Application client in the Zoho API console, with redirect URI PUBLIC_URL/auth/callback; the old Non-Browser client is no longer used. Scopes: SDPOnDemand.requests.READ and AaaServer.profile.READ (for the name in the header).
  • Sessions: the Zoho tokens live in an AES-256-GCM encrypted, HttpOnly, SameSite=Lax cookie (Secure over https), keyed by SESSION_SECRET. Each sign-in lasts SESSION_DAYS (default 7). The access token renews automatically; concurrent calls share one renewal. Sign out revokes the refresh token at Zoho. A rejected token, from Zoho or a 401 from SDP, sends the page back to the sign-in button.
  • Stateless server: removed boardService.js, tokenStore.js, the device-code flow, ADMIN_KEY and data/token.json (phase 1 was superseded). The page runs the checks itself (/api/closed every minute, with 12 hours on first load; /api/workload every WORKLOAD_MINUTES) and keeps the closed feed. All /api routes, weather included, need a sign-in. /healthz is public for the Docker healthcheck.
  • Docker: Dockerfile (node:24-alpine, runs as the node user, with a healthcheck) and .dockerignore. The example docker-compose.yml publishes no ports, because Docker's port publishing bypasses ufw and would skip NPM's access list. It joins NPM's network instead (NPM_NETWORK, default npm_default).
  • CI: .gitea/workflows/docker.yml runs on v*-prod* tag pushes on the self-hosted runner. It logs in with the DHUB_UNAME and DHUB_PASS secrets, then builds and pushes :<tag> and :latest. Restarting on the VPS is manual.
  • Docs: README (setup, deployment, NPM, signing in, security), env.example, CLAUDE.md, docs/NOTES.md (Zoho OAuth gotchas, decisions) and docs/ROADMAP.md (new architecture, plus the planned design for per-browser settings).

Verified

  • End to end against the real server.js over a unix socket, with axios stubbed: login redirect and state cookie; callback with a forged or missing state, a refused sign-in and a bad code; cookie flags and encryption; tokens never reach the page; a tampered cookie; three concurrent calls with an expired token renewing once; a revoked refresh token; a session past SESSION_DAYS; an SDP 401 signing the user out; sign-out revoking the token.
  • The page script with stubbed DOM and fetch: the signed-out panel, ?signin= messages, first-load backfill with no ding, a ding for a new close, and signing out mid-session.
  • Live, bare metal behind NPM (2026-10-15): Zoho sign-in, SDP data, Sign out, and the access list blocking mobile data.
  • Not verified yet: token renewal after an hour, the Docker build, the Gitea workflow, the production container behind sdp-dashboard:3000.

Earlier the same day (dashboard work)

  1. Dashboard redesign (index.html, tickets.js, boardService.js): stat tiles; a technician × status table with Total open and Closed today columns; the live closed feed in a side column; a "Sound on close" toggle, on by default; Refresh now. The table refreshes every WORKLOAD_MINUTES (default 5). The closed watcher still checks every minute.
  2. Central region scoping: SITE_NAMES and GROUP_NAMES in .env are applied to every SDP query, and each ticket is also re-checked locally.
  3. Unassigned requests counter: UNASSIGNED_EXCLUDED_STATUSES mirrors the ManageEngine "Central Region Unassigned" view. Verified to match the view exactly. The debug list is kept but hidden (SHOW_UNASSIGNED_LIST = false in index.html).
  4. Weather card (src/weather.js, /api/weather): OpenWeatherMap current weather and 5-day forecast with icons, using OWM_API_KEY and OWM_ZIP_CODE, cached for 10 minutes. Working.
  5. Sign-in changes: the Zoho page opens in a new tab, the button only appears once the device code exists, and the page checks quickly after Connect is clicked.
  6. Table layout: totals moved to the left so they don't get cut off; the auth panel text is wider.
  7. SDP query fixes:
    • greater_than → greater than. The wrong spelling made SDP ignore all the criteria and return about 124k tickets.
    • Paging now dedupes by ID and stops early when the total can't be reached.
    • The "partial" note now names which query was cut off.
    • Verified fixed: the "partial" note is gone.
  8. Small fixes: [hidden] CSS bug (Refresh now showed when disconnected); a misplaced comment in server.js.
  9. README rewritten. The screenshots it had then were removed after the auth rebuild; new ones are due.
  10. Credits: page footer, README, package.json contributors, server.js header. Isaac Godin keeps the design credit.
  11. License: MIT (LICENSE copyright Skylar Grant; package.json now says MIT, not ISC).
  12. Repo structure for working with Claude: CLAUDE.md, docs/ROADMAP.md, docs/NOTES.md.

Settings in .env

New: PUBLIC_URL, SESSION_SECRET, SESSION_DAYS (optional), TRUST_PROXY (optional). CLIENT_ID and CLIENT_SECRET must now come from the Server-based client. Gone: ADMIN_KEY, TOKEN_FILE. Unchanged: SITE_NAMES, GROUP_NAMES, TECHNICIAN_NAMES, STATUS_NAMES, UNASSIGNED_EXCLUDED_STATUSES, WORKLOAD_MINUTES, OWM_API_KEY, OWM_ZIP_CODE. All are documented in src/env.example and the README.

Known issues

  • On a typical screen the status table scrolls sideways ("Need Info" gets cut off), because the page is capped at 1,320px.
  • No screenshots in the README for now (images/ removed); Skylar is retaking them.
  • The README's reference links for Zoho's device flow were removed; links for the server-based (web) flow haven't been added.
  • Each open dashboard polls SDP with its own token. That's fine for a few viewers; watch SDP rate limits if wallboards multiply.

Next steps

Already done: Zoho Server-based client, NPM proxy hosts (dash and dash-test) with the MCCS access list, Cloudflare DNS, a wildcard certificate, and the bare-metal test.

  1. Token renewal: keep a tab open for more than an hour and confirm it stays signed in.
  2. First image: check the DHUB_UNAME and DHUB_PASS secrets and the runner's self-hosted label, then push a v*-prod* tag and watch the workflow.
  3. Production container: use a production .env with no PORT line and PUBLIC_URL=https:// the production subdomain, check NPM's network name, then docker compose up -d. Confirm docker ps shows it healthy and the production subdomain signs in.
  4. Screenshots for the README.
  5. Then: per-browser settings, as designed in docs/ROADMAP.md (not started).

To pick up

Start a Claude Code session in this repo and say something like:

"Read SESSION.md and docs/ROADMAP.md. The per-user sign-in works live on bare metal; next is the first Docker deploy. Help me check the production container, then we'll plan the per-browser settings panel."