From aea4d5151838136856674caea6d18a9a218ac762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Mon, 27 Jul 2026 07:04:57 -0600 Subject: [PATCH] feat[frontend](dashboard): added refresh interval --- .../dashboard/components/DashboardGrid.tsx | 4 +- .../components/DashboardRefreshInterval.tsx | 72 +++++++++++++++++++ .../dashboard/components/WidgetRenderer.tsx | 4 +- .../dashboard/hooks/useVisualizationData.ts | 4 +- .../dashboard/pages/DashboardPage.tsx | 4 ++ 5 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 frontend/src/features/dashboard/components/DashboardRefreshInterval.tsx diff --git a/frontend/src/features/dashboard/components/DashboardGrid.tsx b/frontend/src/features/dashboard/components/DashboardGrid.tsx index 0306870f7..603a4ce51 100644 --- a/frontend/src/features/dashboard/components/DashboardGrid.tsx +++ b/frontend/src/features/dashboard/components/DashboardGrid.tsx @@ -15,6 +15,7 @@ export function DashboardGrid({ visualizations, time, filters, + refreshMs, editing, onLayoutChange, onEditItem, @@ -24,6 +25,7 @@ export function DashboardGrid({ visualizations: Visualization[] time: TimeRange filters?: FilterType[] + refreshMs?: number | null editing: boolean onLayoutChange?: (items: GridLayoutItem[]) => void onEditItem?: (id: number) => void @@ -88,7 +90,7 @@ export function DashboardGrid({ onRemove={viz ? () => onRemoveItem?.(viz.id) : undefined} > {viz ? ( - + ) : (
{t('dashboards.grid.missingVisualization')} diff --git a/frontend/src/features/dashboard/components/DashboardRefreshInterval.tsx b/frontend/src/features/dashboard/components/DashboardRefreshInterval.tsx new file mode 100644 index 000000000..3c3434fdb --- /dev/null +++ b/frontend/src/features/dashboard/components/DashboardRefreshInterval.tsx @@ -0,0 +1,72 @@ +import { useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { ChevronDown, RefreshCw } from 'lucide-react' +import { cn } from '@/shared/lib/utils' +import { Button } from '@/shared/components/ui/button' + +const OPTIONS: { id: string; ms: number | null }[] = [ + { id: 'off', ms: null }, + { id: '10s', ms: 10_000 }, + { id: '30s', ms: 30_000 }, + { id: '1m', ms: 60_000 }, + { id: '5m', ms: 300_000 }, + { id: '15m', ms: 900_000 }, +] + +export function DashboardRefreshInterval({ + value, + onChange, +}: { + value: number | null + onChange: (next: number | null) => void +}) { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const ref = useRef(null) + const current = OPTIONS.find((o) => o.ms === value) ?? OPTIONS[0] + + useEffect(() => { + const h = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) + } + window.addEventListener('mousedown', h) + return () => window.removeEventListener('mousedown', h) + }, []) + + return ( +
+ + + {open && ( +
+ {OPTIONS.map((o) => ( + + ))} +
+ )} +
+ ) +} diff --git a/frontend/src/features/dashboard/components/WidgetRenderer.tsx b/frontend/src/features/dashboard/components/WidgetRenderer.tsx index c5199f3f7..015f4d5f6 100644 --- a/frontend/src/features/dashboard/components/WidgetRenderer.tsx +++ b/frontend/src/features/dashboard/components/WidgetRenderer.tsx @@ -18,10 +18,12 @@ export function WidgetRenderer({ visualization, time, filters, + refreshMs, }: { visualization: Visualization time: TimeRange filters?: FilterType[] + refreshMs?: number | null }) { const { t } = useTranslation() @@ -35,7 +37,7 @@ export function WidgetRenderer({ : 'echarts' const hasSql = !!visualization.sqlQuery?.trim() - const query = useVisualizationData(hasSql ? visualization : null, time, filters) + const query = useVisualizationData(hasSql ? visualization : null, time, filters, refreshMs ?? null) if (renderer === 'echarts' && (parsed.error || !parsed.option)) { return ( diff --git a/frontend/src/features/dashboard/hooks/useVisualizationData.ts b/frontend/src/features/dashboard/hooks/useVisualizationData.ts index 22ff64aeb..f85f1ada3 100644 --- a/frontend/src/features/dashboard/hooks/useVisualizationData.ts +++ b/frontend/src/features/dashboard/hooks/useVisualizationData.ts @@ -22,7 +22,8 @@ export interface VisualizationData { export function useVisualizationData( visualization: Visualization | null, time: TimeRange, - filters: FilterType[] = EMPTY_FILTERS + filters: FilterType[] = EMPTY_FILTERS, + refetchIntervalMs: number | null = null ) { const service = useMemo(() => createOpenSearchService(), []) @@ -50,5 +51,6 @@ export function useVisualizationData( }, enabled: visualization != null && resolvedQuery != null, staleTime: 30_000, + refetchInterval: refetchIntervalMs ?? false, }) } diff --git a/frontend/src/features/dashboard/pages/DashboardPage.tsx b/frontend/src/features/dashboard/pages/DashboardPage.tsx index 3f7ebccf9..975cd4b73 100644 --- a/frontend/src/features/dashboard/pages/DashboardPage.tsx +++ b/frontend/src/features/dashboard/pages/DashboardPage.tsx @@ -13,6 +13,7 @@ import { DashboardGrid } from '@/features/dashboard/components/DashboardGrid' import { DashboardEditorBar } from '@/features/dashboard/components/DashboardEditorBar' import { DashboardFormDialog } from '@/features/dashboard/components/DashboardFormDialog' import { DashboardTimeRange } from '@/features/dashboard/components/DashboardTimeRange' +import { DashboardRefreshInterval } from '@/features/dashboard/components/DashboardRefreshInterval' import { DashboardFilterBar, type ChipValueMap, @@ -28,6 +29,7 @@ export function DashboardPage() { const [selectedId, setSelectedId] = useState(null) const [search, setSearch] = useState('') const [time, setTime] = useState(() => presetRange('24h')) + const [refreshMs, setRefreshMs] = useState(null) const [formOpen, setFormOpen] = useState( null ) @@ -284,6 +286,7 @@ export function DashboardPage() { right={
+ {editor.editing && (