Add Dashboards UI: PromQL-backed bar/line/pie panels saved per profile (ticket 014)
Backend: replace Profile.dashboards' opaque JSON stub with typed Dashboard/Panel structs, add ProfileStore CRUD (create/save/list/delete dashboard) and matching /api/settings/profiles/:name/dashboards[/:id] routes, with tests covering the HTTP round trip and persistence across a simulated restart. Frontend: add recharts (lightweight, React 19-compatible) and a Dashboards component with a dashboard builder (create dashboards, add/remove/edit panels with a PromQL query + chart type), live per-panel previews that respect the active time range and show malformed-query errors inline instead of crashing, and load/save wired to the new profile-backed persistence. 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
ae21a7a0ce
commit
41362aa3d8
@@ -98,6 +98,29 @@ export interface ProfileSummary {
|
||||
dashboard_count: number
|
||||
}
|
||||
|
||||
/// One chart-ready time series returned by `GET /api/query` (ticket 013):
|
||||
/// a label combination plus its `(timestamp_ms, value)` points, sorted by
|
||||
/// timestamp. See `PromqlSeriesDto` on the backend.
|
||||
export interface PromqlSeries {
|
||||
labels: Record<string, string>
|
||||
points: [number, number][]
|
||||
}
|
||||
|
||||
export type ChartType = 'bar' | 'line' | 'pie'
|
||||
|
||||
export interface Panel {
|
||||
id: string
|
||||
title: string
|
||||
promql: string
|
||||
chart_type: ChartType
|
||||
}
|
||||
|
||||
export interface Dashboard {
|
||||
id: string
|
||||
name: string
|
||||
panels: Panel[]
|
||||
}
|
||||
|
||||
async function getJson<T>(path: string): Promise<T> {
|
||||
const res = await fetch(path)
|
||||
if (!res.ok) {
|
||||
@@ -265,3 +288,46 @@ export function deleteProfile(name: string): Promise<void> {
|
||||
export function activateProfile(name: string): Promise<Settings> {
|
||||
return sendJson(`/api/settings/profiles/${encodeURIComponent(name)}/activate`, 'POST')
|
||||
}
|
||||
|
||||
/// Evaluates a PromQL query (ticket 013) via `GET /api/query`. Throws on a
|
||||
/// malformed query (`400` with `{ "error": "..." }`), so callers should catch
|
||||
/// and surface the message inline (e.g. per-panel) rather than letting it
|
||||
/// crash the page.
|
||||
export function fetchPromqlSeries(params: {
|
||||
promql: string
|
||||
startMs?: number
|
||||
endMs?: number
|
||||
}): Promise<PromqlSeries[]> {
|
||||
const search = new URLSearchParams()
|
||||
search.set('promql', params.promql)
|
||||
if (params.startMs !== undefined) search.set('start_ms', String(params.startMs))
|
||||
if (params.endMs !== undefined) search.set('end_ms', String(params.endMs))
|
||||
return getJson(`/api/query?${search.toString()}`)
|
||||
}
|
||||
|
||||
export function fetchDashboards(profileName: string): Promise<Dashboard[]> {
|
||||
return getJson(`/api/settings/profiles/${encodeURIComponent(profileName)}/dashboards`)
|
||||
}
|
||||
|
||||
export function createDashboard(profileName: string, name: string): Promise<Dashboard> {
|
||||
return sendJson(`/api/settings/profiles/${encodeURIComponent(profileName)}/dashboards`, 'POST', { name })
|
||||
}
|
||||
|
||||
export function saveDashboard(
|
||||
profileName: string,
|
||||
dashboardId: string,
|
||||
update: { name: string; panels: Panel[] },
|
||||
): Promise<Dashboard> {
|
||||
return sendJson(
|
||||
`/api/settings/profiles/${encodeURIComponent(profileName)}/dashboards/${encodeURIComponent(dashboardId)}`,
|
||||
'PUT',
|
||||
update,
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteDashboard(profileName: string, dashboardId: string): Promise<void> {
|
||||
return sendJson(
|
||||
`/api/settings/profiles/${encodeURIComponent(profileName)}/dashboards/${encodeURIComponent(dashboardId)}`,
|
||||
'DELETE',
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user