Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ Dashboards are **generated by the embedded DeepSQL Agent acting as a coding agen
- **Rendering + data access**: `DashboardArtifact.jsx` renders the HTML in a **sandboxed iframe** (`sandbox="allow-scripts"`, opaque origin + a strict CSP — no external network). The artifact fetches data only through an injected `deepsql.query(sql)` bridge that `postMessage`s to the parent; the parent calls **`POST /api/dashboards/query`** (`DashboardQueryController`), which is **read-only twice over** (`McpSqlGuardService.validateReadOnlySql` + `QueryExecutionContext.api` = `READ_ONLY_ONLY`) and access-scoped via `assertCanReadConnectionContent`. So the agent's code has full creative freedom while every query stays guarded and sandboxed. The bridge also auto-sizes the iframe and forwards runtime errors.
- Generation endpoints unchanged (`POST /api/dashboards/generate` + `/generate/stream`). `DashboardBuilder.js`/`DashboardInputs.js` remain only because `tabs/Core/PreviewTab.js` still uses them — the dashboard *creation* path no longer touches them.
- **Sharing**: both share types render a standalone read-only `DashboardViewer` (title + `DashboardArtifact` with an injected `queryFn`). Internal link `/dashboard-view/:id` (auth) uses the authed broker; public link `/share/dashboard/:token` (permitAll) uses `PublicDashboardController` (`GET /api/public/dashboards/{token}` + `/query`), which resolves only while `saved_dashboards.is_public` is true (revoke = flip it) and runs read-only + connection-scoped. `share_token`/`is_public` are set only via `POST|DELETE /api/saved-dashboards/{id}/share` (access-checked), never a general update. `ShareMenu.jsx` drives the UI. The public query path has its own nginx `dashq` limiter.
- **Organization** (search/folders/favorites): `SavedDashboardController`'s search/folder/favorite endpoints existed for a while with no UI consumer. `DashboardsHome.jsx` now wires all of it — a search box (client-side filter over name/description), folder chips derived from `GET /connection/{id}/folders` with a per-card "move to folder" popover (`PUT /saved-dashboards/{id}` with `folder: ""` to clear — `updateDashboard` treats `null` as "field omitted" so blank is the explicit clear signal, same convention as `setSharePassword`), and a favorite star toggle (`POST /{id}/favorite`) with optimistic UI update.
- **Clone**: `POST /saved-dashboards/{id}/clone` (`SavedDashboardService.cloneDashboard`) duplicates a dashboard's config/chat/tags/folder into a fresh row — not shared, not favorited. Exposed as a copy icon on each `DashboardsHome.jsx` card.
- **Version history**: every real overwrite of `dashboardConfig` (agent build via `completeBuildTurn`, manual Source-tab edit via `updateDashboard`, or a restore) snapshots the *previous* config into `dashboard_versions` (`V113__create_dashboard_versions.sql`) before overwriting, tagged with a trigger (`AGENT_BUILD`/`MANUAL_EDIT`/`RESTORE`) — capped at 50 snapshots per dashboard, oldest pruned first. `GET /{id}/versions` lists them newest-first; `POST /{id}/versions/{versionId}/restore` swaps a snapshot back in as current (itself snapshotting whatever was live, so a restore is undoable too) and **dedupes**: after a restore, the restored row plus any other row with byte-identical `dashboard_config` are deleted, since that content is now "Current," not history — otherwise a restore-edit-restore cycle piles up an alternating chain of duplicate snapshots. `DashboardWorkspace.jsx`'s History panel shows a lightweight diff summary per entry (title/widget-count/size delta computed client-side, not a real line diff — the agent rewrites large chunks even for small logical changes) plus a Preview modal that renders that version's HTML live via `DashboardArtifact`.
- **Refresh**: `DashboardArtifact`'s `useImperativeHandle` exposes `reload()`, which bumps an internal `reloadEpoch` state used as the `<iframe>`'s `key` — forcing a genuine remount (and re-running every widget's `deepsql.query()` call) even when `html` is referentially unchanged, which changing `html`/`srcDoc` alone can't guarantee. `DashboardWorkspace.jsx`'s canvas toolbar has a manual Refresh button plus an auto-refresh interval dropdown (Off/30s/5m/1h) that calls it on a timer, paused while a build is in flight (a completing build already replaces the iframe). `DashboardViewer.jsx` (both share surfaces) takes the same `autoRefreshMs` optionally, plus `hideChrome` for kiosk mode.
- **TV/kiosk mode**: `PublicDashboardPage.jsx` reads `?kiosk=1&refresh=<seconds>` (chrome-less + auto-refresh, floor 10s) and `?tokens=tokA,tokB&advance=<seconds>` (cycles through multiple public share tokens, dwelling `advance` seconds each — the route's own `:token` is always the first slide). A password-protected dashboard mid-cycle is skipped (there's no one there to type a password) rather than parking the whole kiosk on a gate. `ShareMenu.jsx` surfaces a ready-made kiosk link (`?kiosk=1&refresh=60`) once a dashboard is public and unprotected.
- **Alerts**: `dashboard_alerts` (`V114__create_dashboard_alerts.sql`) holds a natural-language condition per dashboard (e.g. "alert if the error rate exceeds 5% in the last hour"), evaluated on a schedule by `DashboardAlertService.evaluate()` — a **bounded agent session** (fresh `ensureSession`, no tools beyond `execute_sql`/schema lookups, a short task prompt asking for exactly `YES`/`NO` + a one-sentence reason grounded in a real query result) reusing the same agent plumbing as dashboard generation, just for a one-line answer instead of a whole HTML document. `DashboardAlertTaskConfig` registers one db-scheduler recurring task (`dashboard-alert-tick`, every minute) that evaluates whichever alerts are actually due per `DashboardAlertRepository.findDue` (each alert has its own `checkIntervalMinutes`) rather than one scheduled task per alert. A fired alert dispatches through `EmailService.sendDashboardAlert`/`WebhookService.sendDashboardAlert` (new methods, same pattern as the existing growth/slow-query alert methods) gated by a per-alert `cooldownMinutes` so a condition that stays true doesn't re-fire every tick. The alert runs **as whoever created it** (`createdByUsername`, captured at creation time) — there's no ambient "system" identity for a background job, and running every alert as an arbitrary admin would let one user's alert read data through someone else's access grant. `DashboardAlertController` is the CRUD surface (`/saved-dashboards/{id}/alerts`); `DashboardWorkspace.jsx`'s toolbar has an Alerts panel (composer + per-alert enable/disable/delete, last-check verdict shown inline).

## LLM Providers

Expand Down
Loading
Loading