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 && (