From db11548414e71615d6f0eec179c202026b6bba95 Mon Sep 17 00:00:00 2001 From: enzo Date: Wed, 22 Jul 2026 19:54:44 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20visual=20fixes=20=E2=80=94=20dark=20them?= =?UTF-8?q?e=20default,=20sidebar,=20dashboard=20cards,=20timeline=20dots,?= =?UTF-8?q?=20header=20counts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set data-theme=dark as default in index.html, useTheme defaults to 'dark' (issues 1, 10) - Rename "Cron" to "Cron 管理" in sidebar (issue 2) - Set card grid gap to 14px per design spec (issue 3) - Show D-grade profile names below health bar chart (issue 4) - Remove R/F/H/OK text from timeline, use colored dots with hover tooltip (issue 5) - Wire live online/running/offline counts via usePolling in header, add offline badge (issue 9) - Fix search placeholder to "搜索 Profile / 任务 / Cron / 事件..." (issue 9) Co-Authored-By: Claude --- frontend/index.html | 2 +- frontend/src/components/layout/AppLayout.tsx | 26 ++++- frontend/src/components/layout/Header.tsx | 5 +- frontend/src/components/layout/Sidebar.tsx | 2 +- frontend/src/components/ui/Timeline.tsx | 8 +- frontend/src/hooks/useTheme.ts | 11 +- frontend/src/pages/DashboardPage.tsx | 103 ++++++++++--------- frontend/src/pages/GatewayPage.tsx | 7 +- 8 files changed, 94 insertions(+), 70 deletions(-) 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 (
@@ -120,8 +141,9 @@ export default function AppLayout({
any onSearchNavigate?: (value: string) => void theme: string @@ -17,6 +18,7 @@ export default function Header({ breadcrumb, onlineCount = 0, runningCount = 0, + offlineCount = 0, onSearch, onSearchNavigate, theme, @@ -56,12 +58,13 @@ export default function Header({ {/* Search */} {onSearch && ( - + )} {/* Status badges */} 在线 {onlineCount} 运行中 {runningCount} + 离线 {offlineCount} {/* Theme toggle */}
-
{evt.label}
{!isLast && (
(() => { - return (localStorage.getItem('hive-theme') as Theme) || 'system' + return (localStorage.getItem('hive-theme') as Theme) || 'dark' }) useEffect(() => { const root = document.documentElement - if (theme === 'system') { - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches - root.setAttribute('data-theme', prefersDark ? 'dark' : 'light') - } else { - root.setAttribute('data-theme', theme) - } + root.setAttribute('data-theme', theme) localStorage.setItem('hive-theme', theme) }, [theme]) diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 0cbaf29..1d9e03a 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -62,37 +62,49 @@ function HealthBarChart({ const maxVal = Math.max(dist.A, dist.B, dist.C, dist.D, 1) return ( -
- {GRADE_META.map(({ key, color }) => { - const val = dist[key as keyof typeof dist] as number - const pct = (val / maxVal) * 100 - return ( -
onBarClick(key)} - > - +
+ {GRADE_META.map(({ key, color }) => { + const val = dist[key as keyof typeof dist] as number + const pct = (val / maxVal) * 100 + return ( +
onBarClick(key)} > - {key} - -
-
+ + {key} + +
+
+
+ + {val} +
- - {val} + ) + })} +
+ {dist.d_profiles && dist.d_profiles.length > 0 && ( +
+ D 级 Profile: + {dist.d_profiles.map((name) => ( + + {name} -
- ) - })} + ))} +
+ )}
) } @@ -186,28 +198,27 @@ export default function DashboardPage() { } /* ---------- trend -> timeline events ---------- */ - const timelineEvents: TimelineEvent[] = trend.map((item) => ({ - time: hourLabel(item.hour), - type: item.heartbeat_timeouts > 0 ? 'red' : item.feishu_disconnects > 0 ? 'orange' : item.restarts > 0 ? 'orange' : 'green', - label: ( - - {item.restarts > 0 && R:{item.restarts}} - {item.feishu_disconnects > 0 && F:{item.feishu_disconnects}} - {item.heartbeat_timeouts > 0 && H:{item.heartbeat_timeouts}} - {item.restarts === 0 && item.feishu_disconnects === 0 && item.heartbeat_timeouts === 0 && ( - OK - )} - - ), - color: trendColor(item), - })) + const timelineEvents: TimelineEvent[] = trend.map((item) => { + const details: string[] = [] + if (item.heartbeat_timeouts > 0) details.push(`心跳超时 ${item.heartbeat_timeouts}`) + if (item.feishu_disconnects > 0) details.push(`飞书断连 ${item.feishu_disconnects}`) + if (item.restarts > 0) details.push(`重启 ${item.restarts}`) + const tooltipText = details.length > 0 ? details.join(' / ') : '正常' + + return { + time: hourLabel(item.hour), + type: item.heartbeat_timeouts > 0 ? 'red' : item.feishu_disconnects > 0 ? 'orange' : item.restarts > 0 ? 'orange' : 'green', + tooltip: tooltipText, + color: trendColor(item), + } + }) return (

蜂群总览

{/* ---- summary cards ---- */} -
+
{/* ---- health + trend ---- */} -
+
{/* health distribution */}
{/* ---- kanban + cron ---- */} -
+
{/* kanban overview */}
{ return { time: e.time, type: e.type, - label: ( -
- {labelMap[e.type] || e.type} - {e.profile} -
- ), + tooltip: `${labelMap[e.type] || e.type} — ${e.profile}`, color: colorMap[e.type], } }