Add NavMenu settings/presets UI and profile HTTP routes (ticket 011)
Adds thin HTTP routes for profile CRUD and colour-scheme updates (GET/PATCH /api/settings, GET/POST /api/settings/profiles, DELETE /api/settings/profiles/:name, POST /api/settings/profiles/:name/activate) following the existing CSS-route pattern in api.rs, with activate hot-swapping the running engine's declared params/metrics so switching profiles needs no restart. Builds out the NavMenu frontend with Settings (colour scheme picker, CSS import/list/delete) and Presets (list/create/switch/delete profiles with declaration counts) panels, and a useSettingsPanel hook that applies the active colour scheme and imported CSS to the document live. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
57d23c62e9
commit
801ac36b78
@@ -43,6 +43,29 @@ export interface Stats {
|
||||
last_timestamp_ms: number | null
|
||||
}
|
||||
|
||||
export type ColorScheme = 'light' | 'dark' | 'system'
|
||||
|
||||
export interface Settings {
|
||||
color_scheme: ColorScheme
|
||||
active_profile: string | null
|
||||
}
|
||||
|
||||
export type CssEntryKind = 'imported' | 'linked'
|
||||
|
||||
export interface CssEntry {
|
||||
id: string
|
||||
kind: CssEntryKind
|
||||
filename: string | null
|
||||
path_or_url: string | null
|
||||
}
|
||||
|
||||
export interface ProfileSummary {
|
||||
name: string
|
||||
param_count: number
|
||||
metric_count: number
|
||||
dashboard_count: number
|
||||
}
|
||||
|
||||
async function getJson<T>(path: string): Promise<T> {
|
||||
const res = await fetch(path)
|
||||
if (!res.ok) {
|
||||
@@ -51,6 +74,26 @@ async function getJson<T>(path: string): Promise<T> {
|
||||
return res.json() as Promise<T>
|
||||
}
|
||||
|
||||
async function sendJson<T>(path: string, method: string, body?: unknown): Promise<T> {
|
||||
const res = await fetch(path, {
|
||||
method,
|
||||
headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
if (!res.ok) {
|
||||
let message = `${path} failed: ${res.status}`
|
||||
try {
|
||||
const data = await res.json()
|
||||
if (data?.error) message = data.error
|
||||
} catch {
|
||||
// response body wasn't JSON (or was empty) — fall back to the generic message
|
||||
}
|
||||
throw new Error(message)
|
||||
}
|
||||
if (res.status === 204) return undefined as T
|
||||
return res.json() as Promise<T>
|
||||
}
|
||||
|
||||
export function fetchLogs(params: {
|
||||
sinceSequence?: number
|
||||
startMs?: number
|
||||
@@ -84,3 +127,43 @@ export function fetchStats(): Promise<Stats> {
|
||||
export function fetchParamDeclarations(): Promise<ParamDeclaration[]> {
|
||||
return getJson('/api/params')
|
||||
}
|
||||
|
||||
export function fetchSettings(): Promise<Settings> {
|
||||
return getJson('/api/settings')
|
||||
}
|
||||
|
||||
export function updateSettings(update: { colorScheme?: ColorScheme }): Promise<Settings> {
|
||||
return sendJson('/api/settings', 'PATCH', { color_scheme: update.colorScheme })
|
||||
}
|
||||
|
||||
export function fetchCssEntries(): Promise<CssEntry[]> {
|
||||
return getJson('/api/settings/css')
|
||||
}
|
||||
|
||||
export function importCss(filename: string, content: string): Promise<CssEntry> {
|
||||
return sendJson('/api/settings/css', 'POST', { filename, content })
|
||||
}
|
||||
|
||||
export function deleteCssEntry(id: string): Promise<void> {
|
||||
return sendJson(`/api/settings/css/${encodeURIComponent(id)}`, 'DELETE')
|
||||
}
|
||||
|
||||
export function cssContentUrl(id: string): string {
|
||||
return `/api/settings/css/${encodeURIComponent(id)}/content`
|
||||
}
|
||||
|
||||
export function fetchProfiles(): Promise<ProfileSummary[]> {
|
||||
return getJson('/api/settings/profiles')
|
||||
}
|
||||
|
||||
export function createProfile(name: string): Promise<ProfileSummary> {
|
||||
return sendJson('/api/settings/profiles', 'POST', { name })
|
||||
}
|
||||
|
||||
export function deleteProfile(name: string): Promise<void> {
|
||||
return sendJson(`/api/settings/profiles/${encodeURIComponent(name)}`, 'DELETE')
|
||||
}
|
||||
|
||||
export function activateProfile(name: string): Promise<Settings> {
|
||||
return sendJson(`/api/settings/profiles/${encodeURIComponent(name)}/activate`, 'POST')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user