README covers the Server-based Zoho client, the new .env settings, deployment (image, compose, Nginx Proxy Manager), signing in and the security model. NOTES records the Zoho OAuth gotchas and the decisions behind the rebuild. ROADMAP describes the current architecture and the planned per-browser settings panel. CLAUDE.md and SESSION.md are brought up to date. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.5 KiB
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.
All of this is uncommitted on branch v1.1.0. None of it has been run against live Zoho or built with Docker yet. It was verified with stubbed Zoho and SDP calls only (see "Verified" below).
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 URIPUBLIC_URL/auth/callback; the old Non-Browser client is no longer used. Scopes:SDPOnDemand.requests.READandAaaServer.profile.READ(for the name in the header). - Sessions: the Zoho tokens live in an AES-256-GCM encrypted,
HttpOnly,SameSite=Laxcookie (Secureover https), keyed bySESSION_SECRET. Each sign-in lastsSESSION_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_KEYanddata/token.json(phase 1 was superseded). The page runs the checks itself (/api/closedevery minute, with 12 hours on first load;/api/workloadeveryWORKLOAD_MINUTES) and keeps the closed feed. All/apiroutes, weather included, need a sign-in./healthzis public for the Docker healthcheck. - Docker:
Dockerfile(node:24-alpine, runs as thenodeuser, with a healthcheck) and.dockerignore. The exampledocker-compose.ymlpublishes no ports, because Docker's port publishing bypasses ufw and would skip NPM's access list. It joins NPM's network instead (NPM_NETWORK, defaultnpm_default). - CI:
.gitea/workflows/docker.ymlruns onv*-prod*tag pushes on theself-hostedrunner. It logs in with theDHUB_UNAMEandDHUB_PASSsecrets, 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) anddocs/ROADMAP.md(new architecture, plus the planned design for per-browser settings).
Verified
- End to end against the real
server.jsover 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 pastSESSION_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. - Not verified: a real Zoho sign-in, the
user/inforesponse shape, the Docker build, the Gitea workflow, NPM.
Earlier the same day (dashboard work)
- 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 everyWORKLOAD_MINUTES(default 5). The closed watcher still checks every minute. - Central region scoping:
SITE_NAMESandGROUP_NAMESin.envare applied to every SDP query, and each ticket is also re-checked locally. - Unassigned requests counter:
UNASSIGNED_EXCLUDED_STATUSESmirrors the ManageEngine "Central Region Unassigned" view. Verified to match the view exactly. The debug list is kept but hidden (SHOW_UNASSIGNED_LIST = falseinindex.html). - Weather card (
src/weather.js,/api/weather): OpenWeatherMap current weather and 5-day forecast with icons, usingOWM_API_KEYandOWM_ZIP_CODE, cached for 10 minutes. Working. - 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.
- Table layout: totals moved to the left so they don't get cut off; the auth panel text is wider.
- 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.
- Small fixes:
[hidden]CSS bug (Refresh now showed when disconnected); a misplaced comment inserver.js. - README rewritten, with new screenshots in
images/.1.pngand2.pngare rendered with sample data;4.pngis from the live server;3.pngis unchanged. - Credits: page footer, README,
package.jsoncontributors,server.jsheader. Isaac Godin keeps the design credit. - License: MIT (LICENSE copyright Skylar Grant; package.json now says MIT, not ISC).
- 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.
images/1.pngandimages/2.pngshow the old Connect screens and are no longer used in the README.- 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
- Zoho client: create the Server-based Application with the production redirect URI (and localhost for development). Put its ID and secret,
PUBLIC_URLand a newSESSION_SECRETinto the VPS.env. - Gitea and Docker Hub: add the
DHUB_UNAMEandDHUB_PASSsecrets, make sure the runner has theself-hostedlabel and Docker, then push av*-prod*tag. - VPS: check NPM's network name, then
docker compose up -d. Set up the NPM proxy host (forward tosdp-dashboard:3000, SSL, Force SSL) and the MCCS access list. - Check live: sign in; go more than an hour without being asked to sign in again; sign out; confirm an outside IP is blocked.
- 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 Zoho sign-in and Docker setup are built but not yet tested live. Help me get it deployed and checked, then we'll plan the per-browser settings panel."