{#each dashboards as d (d.id)}
@@ -96,24 +105,31 @@
diff --git a/web/src/routes/dashboards/[id]/+page.svelte b/web/src/routes/dashboards/[id]/+page.svelte
index 55a521c..ef449d6 100644
--- a/web/src/routes/dashboards/[id]/+page.svelte
+++ b/web/src/routes/dashboards/[id]/+page.svelte
@@ -2,13 +2,13 @@
import { page } from '$app/state';
import { GridStack, type GridStackNode } from 'gridstack';
import 'gridstack/dist/gridstack.min.css';
- import QueryBar from '$lib/QueryBar.svelte';
import PanelViz from '$lib/PanelViz.svelte';
+ import PanelEditor from '$lib/components/PanelEditor.svelte';
+ import { Button, Card, EmptyState, Skeleton } from '$lib/components/ui';
import {
getDashboard,
updateDashboard,
deleteDashboard as apiDeleteDashboard,
- addPanel,
deletePanel as apiDeletePanel,
updatePanel as apiUpdatePanel,
exportDashboard,
@@ -17,8 +17,6 @@
injectTimeRange,
type Dashboard,
type Panel,
- type VizType,
- type Language,
type QueryResult
} from '$lib/api';
@@ -37,11 +35,29 @@
let gridEl: HTMLDivElement | undefined = $state();
let grid: GridStack | undefined;
- let showAddPanel = $state(false);
- let newTitle = $state('');
- let newQuery = $state('');
- let newLanguage = $state('');
- let newVizType = $state('table');
+ let editorOpen = $state(false);
+ let editingPanel = $state(null);
+
+ function openNewPanel() {
+ editingPanel = null;
+ editorOpen = true;
+ }
+ function openEditPanel(panel: Panel) {
+ editingPanel = panel;
+ editorOpen = true;
+ }
+
+ // Zoom on a time-series panel becomes the dashboard's new global
+ // range -- the brief's "zoomed range able to feed back into the
+ // global dashboard time-range picker" requirement. Reuses the exact
+ // same applyTimeRange() path the manual earliest/latest inputs use,
+ // so a zoom and a typed range behave identically (persisted, re-runs
+ // every panel), not two divergent code paths.
+ async function onPanelZoom(range: { earliest: string; latest: string }) {
+ earliestInput = range.earliest;
+ latestInput = range.latest;
+ await applyTimeRange();
+ }
async function load() {
loading = true;
@@ -99,30 +115,6 @@
return Math.max(...dashboard.panels.map((p) => p.position_y + p.height));
}
- async function submitAddPanel() {
- if (!newQuery.trim()) return;
- try {
- await addPanel(dashboardId, {
- title: newTitle,
- query: newQuery,
- query_language: newLanguage,
- viz_type: newVizType,
- position_x: 0,
- position_y: nextY(),
- width: 6,
- height: 4
- });
- showAddPanel = false;
- newTitle = '';
- newQuery = '';
- newLanguage = '';
- newVizType = 'table';
- await load();
- } catch (e) {
- error = e instanceof Error ? e.message : String(e);
- }
- }
-
async function removePanel(panelId: string) {
try {
await apiDeletePanel(dashboardId, panelId);
@@ -193,15 +185,23 @@
{#if loading}
-
Loading…
+
+
+
+
+
+ {#each Array(4) as _, i (i)}
+
+ {/each}
+
{:else if !dashboard}
-
Error: {error}
+
{:else}
{dashboard.name}
-
-
+
+
{#if dashboard.description}
{dashboard.description}
{/if}
@@ -210,8 +210,8 @@
-
- Per-panel overrides win over this default -- see the panel editor.
+
+ Per-panel overrides win over this default, and a time-series panel's zoom updates this automatically.