diff --git a/frontend/index.html b/frontend/index.html index 7dff459..9376d6b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,5 +1,5 @@ - +
diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index 23cb5b0..20167cc 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -1,6 +1,8 @@ import { useState, type ReactNode } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { useTheme } from '../../hooks/useTheme' +import { usePolling } from '../../hooks/usePolling' +import { getDashboardSummary } from '../../api/dashboard' import Sidebar from './Sidebar' import Header from './Header' import MobileNav from './MobileNav' @@ -27,6 +29,7 @@ export interface AppLayoutProps { breadcrumb?: string onlineCount?: number runningCount?: number + offlineCount?: number onSearch?: (query: string) => any onSearchNavigate?: (value: string) => void } @@ -36,6 +39,7 @@ export default function AppLayout({ breadcrumb, onlineCount = 0, runningCount = 0, + offlineCount = 0, onSearch, onSearchNavigate, }: AppLayoutProps) { @@ -52,6 +56,23 @@ export default function AppLayout({ const sidebarWidth = isTablet ? 180 : 220 + /* ---- live header counts ---- */ + const [liveOnline, setLiveOnline] = useState(0) + const [liveRunning, setLiveRunning] = useState(0) + const [liveOffline, setLiveOffline] = useState(0) + usePolling(async () => { + try { + const res = await getDashboardSummary() + setLiveOnline(res.data.online_count) + setLiveRunning(res.data.running_tasks) + setLiveOffline(res.data.offline_count) + } catch { /* ignore polling errors */ } + }, 30_000) + + const hOnline = onlineCount || liveOnline + const hRunning = runningCount || liveRunning + const hOffline = offlineCount || liveOffline + if (isMobile) { return (