SPB Git

spb/findata

Public
3.5 KB

# FinData OS — web console

Private operator console for the FinData OS lakehouse. Next.js 15 (App Router) + TypeScript + Tailwind CSS v4, built as a static export and served by the Python API — no Node process in production.

# Develop

bash
cd apps/web
pnpm install
pnpm dev            # http://localhost:3000 — /api/* is proxied to http://127.0.0.1:8600 (next.config.ts rewrites, dev only)
pnpm typecheck      # tsc --noEmit

Start the API locally (uv run uvicorn findata_api.main:app --port 8600) before using the dev server; the console authenticates with the API's cookie session and sends X-CSRF-Token on every non-GET request.

# Build & serve

bash
pnpm build          # → apps/web/out/  (index.html + one directory per route, trailing slashes)

services/api/src/findata_api/main.py mounts apps/web/out (or FINDATA_WEB_DIST) with StaticFiles(html=True), so /datasets/ serves out/datasets/index.html, and the console is same-origin with /api/v1. The CSP is default-src 'self' (inline styles allowed, inline scripts not); the bundle contains no inline scripts, external fonts or CDNs.

# CSP and the post-build step

Next's App Router emits its React Server Components payload as inline <script>self.__next_f.push(…)</script> tags, which script-src 'self' blocks. pnpm build therefore runs scripts/externalize-inline-scripts.mjs after next build: it moves those pushes, per page, into out/_next/static/flight/<hash>.js and replaces them with one blocking <script src>. The exported HTML contains no inline scripts (the script fails the build if it ever meets one that is not a __next_f push).

# Smoke test without the backend

node scripts/mock-server.mjs [port] [--signed-out] [--mfa] serves out/ with the API's exact CSP header plus fixture answers for every /api/v1 endpoint the console uses — handy for layout checks (375 px / desktop, light / dark) and for verifying hydration under CSP. Not used in production.

Detail pages use query strings instead of dynamic segments (static export cannot pre-render unknown ids): /datasets/detail/?id=…, /symbols/detail/?symbol=….

# Layout

text
src/lib/api.ts        fetch wrapper (same-origin cookies, CSRF header, error normalisation) + API types
src/lib/auth.tsx      /auth/me context, redirects to /login/ (401) or the MFA step
src/lib/hooks.ts      useAsync, useDebounced, useQueryParam, useInterval, useLocalStorage
src/lib/format.ts     bytes, thin-space numbers, YYYY-MM-DD HH:mm:ssZ, CSV
src/components/       Shell (sidebar ≥1024px, bottom tabs + drawer on mobile), DataTable (cards <640px), charts, dialogs, toast
src/app/<route>/page.tsx   one client page per route

Routes: / overview · /login/ · /datasets/ (+detail/) · /symbols/ (+detail/) · /coverage/ · /bars/ · /ingestion/ · /quality/ · /storage/ · /keys/ · /playground/ · /exports/ · /fundamentals/ · /docs/ · /audit/ · /settings/.

Owner-only pages (ingestion, storage, keys, audit, environment settings) are hidden from the navigation for other roles and show a notice if opened directly; the API enforces the admin scope regardless.

# Design

Brand-neutral palette validated with the dataviz skill (categorical blue/orange/aqua, sequential blue ramp for the coverage heatmap, fixed status colours with icon + label), light and dark via prefers-color-scheme, system font stack. Charts: recharts (one axis per chart, 2 px lines, ≤24 px bars, hover tooltips) and plain SVG for the heatmap.

Files · 0467d449e66a5e62fb10ba4fcbf2f09fc3d1ef38
Menu

Theme