fix: visual fixes — dark theme default, sidebar, dashboard cards, timeline dots, header counts

- 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 <noreply@anthropic.com>
This commit is contained in:
enzo
2026-07-22 19:54:44 +08:00
co-authored by Claude
parent e5bb7ec49a
commit db11548414
8 changed files with 94 additions and 70 deletions
+57 -46
View File
@@ -62,37 +62,49 @@ function HealthBarChart({
const maxVal = Math.max(dist.A, dist.B, dist.C, dist.D, 1)
return (
<div className="space-y-3 mt-2">
{GRADE_META.map(({ key, color }) => {
const val = dist[key as keyof typeof dist] as number
const pct = (val / maxVal) * 100
return (
<div
key={key}
className="flex items-center gap-3 cursor-pointer group"
onClick={() => onBarClick(key)}
>
<span
className="w-5 text-center text-[13px] font-bold shrink-0"
style={{ color }}
<div>
<div className="space-y-3 mt-2">
{GRADE_META.map(({ key, color }) => {
const val = dist[key as keyof typeof dist] as number
const pct = (val / maxVal) * 100
return (
<div
key={key}
className="flex items-center gap-3 cursor-pointer group"
onClick={() => onBarClick(key)}
>
{key}
</span>
<div className="flex-1 h-6 rounded-[var(--radius-sm)] bg-[var(--bg-hover)] overflow-hidden relative">
<div
className="h-full rounded-[var(--radius-sm)] transition-all duration-500 group-hover:opacity-80"
style={{
width: `${Math.max(pct, 4)}%`,
backgroundColor: color,
}}
/>
<span
className="w-5 text-center text-[13px] font-bold shrink-0"
style={{ color }}
>
{key}
</span>
<div className="flex-1 h-6 rounded-[var(--radius-sm)] bg-[var(--bg-hover)] overflow-hidden relative">
<div
className="h-full rounded-[var(--radius-sm)] transition-all duration-500 group-hover:opacity-80"
style={{
width: `${Math.max(pct, 4)}%`,
backgroundColor: color,
}}
/>
</div>
<span className="w-8 text-right text-[13px] shrink-0" style={{ color: 'var(--text-dim)' }}>
{val}
</span>
</div>
<span className="w-8 text-right text-[13px] shrink-0" style={{ color: 'var(--text-dim)' }}>
{val}
)
})}
</div>
{dist.d_profiles && dist.d_profiles.length > 0 && (
<div className="mt-3 pt-3 border-t" style={{ borderColor: 'var(--border)' }}>
<span className="text-[12px]" style={{ color: 'var(--text-dim)' }}>D 级 Profile: </span>
{dist.d_profiles.map((name) => (
<span key={name} className="inline-block text-[12px] px-2 py-0.5 mr-1 mb-1 rounded-[var(--radius-sm)]" style={{ background: 'var(--bg-hover)', color: 'var(--red)' }}>
{name}
</span>
</div>
)
})}
))}
</div>
)}
</div>
)
}
@@ -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: (
<span className="flex gap-2 text-[11px]">
{item.restarts > 0 && <span style={{ color: 'var(--yellow)' }}>R:{item.restarts}</span>}
{item.feishu_disconnects > 0 && <span style={{ color: 'var(--orange)' }}>F:{item.feishu_disconnects}</span>}
{item.heartbeat_timeouts > 0 && <span style={{ color: 'var(--red)' }}>H:{item.heartbeat_timeouts}</span>}
{item.restarts === 0 && item.feishu_disconnects === 0 && item.heartbeat_timeouts === 0 && (
<span style={{ color: 'var(--green)' }}>OK</span>
)}
</span>
),
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 (
<div>
<h2 style={{ color: 'var(--text)', fontSize: 18, fontWeight: 600 }}>蜂群总览</h2>
{/* ---- summary cards ---- */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mt-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-[14px] mt-4">
<Card
label="在线 Gateway"
value={summary.online_count}
@@ -237,7 +248,7 @@ export default function DashboardPage() {
</div>
{/* ---- health + trend ---- */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-[14px] mt-4">
{/* health distribution */}
<div
className="rounded-[var(--radius)] p-4"
@@ -281,7 +292,7 @@ export default function DashboardPage() {
</div>
{/* ---- kanban + cron ---- */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-[14px] mt-4">
{/* kanban overview */}
<div
className="rounded-[var(--radius)] p-4"