diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index e89144cd..19bed9ab 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -10,6 +10,8 @@ * @updated 2026-04-02 Claude Code — Metrics Strip 7指標視覺強化 * @updated 2026-04-03 Claude Code — 監控工具區塊 (Grafana/Prometheus/SigNoz/Gitea) * 串接: incidents(count/P0/MTTR/autoRemediation) + dashboard(serviceHealth/pendingApprovals/podHealth) + * @updated 2026-04-03 Claude Code — 完整對齊 figma-v2 設計 + * figma-v2 重點: 7指標(含今日事件) + 監控工具左彩色條 + 可點擊連結 + meta行 */ import { useTranslations } from 'next-intl' @@ -35,6 +37,7 @@ interface MetricItem { badge?: { text: string; color: string; bg: string } sparkline?: { values: number[]; color: string } valueColor?: string + extra?: React.ReactNode } // ============================================================================= @@ -60,7 +63,8 @@ function MiniSparkline({ values, color }: { values: number[]; color: string }) { } // ============================================================================= -// Monitoring Tools Component +// Monitoring Tools Component — figma-v2 style +// Left 3px accent bar + clickable links + meta row // ============================================================================= interface MonitoringTool { @@ -73,14 +77,34 @@ interface MonitoringTool { checked_at: string } -// icon SVG paths (inline, no emoji — consistent cross-platform) -const TOOL_ICON_COLOR: Record = { - Grafana: { bg: '#fff4e6', color: '#f46800', label: 'G' }, - Prometheus: { bg: '#fff0eb', color: '#e6522c', label: 'P' }, - Sentry: { bg: '#f3eeff', color: '#7b52bf', label: 'S' }, - Langfuse: { bg: '#eaf5ff', color: '#0077cc', label: 'L' }, - SigNoz: { bg: '#eefaf2', color: '#199058', label: 'N' }, - Gitea: { bg: '#fff0f3', color: '#cc2d40', label: 'T' }, +// 各工具連結(內網,在同網段才能開啟) +const TOOL_LINKS: Record = { + Grafana: 'http://192.168.0.110:3002', + Prometheus: 'http://192.168.0.110:9090', + Sentry: 'http://192.168.0.110:9000', + Langfuse: 'http://192.168.0.110:3100', + SigNoz: 'http://192.168.0.188:3301', + Gitea: 'http://192.168.0.110:3001', +} + +// figma-v2 左側彩色條顏色 +const TOOL_ACCENT_COLOR: Record = { + Grafana: '#F59E0B', + Prometheus: '#E85530', + Sentry: '#7B52BF', + Langfuse: '#0077CC', + SigNoz: '#4A90D9', + Gitea: '#22C55E', +} + +// 圖示 emoji +const TOOL_EMOJI: Record = { + Grafana: '📊', + Prometheus: '🔥', + Sentry: '🔭', + Langfuse: '🧪', + SigNoz: '🔭', + Gitea: '🐙', } function MonitoringTools() { @@ -107,71 +131,108 @@ function MonitoringTools() { ) return ( -
- {tools.map((tool, i) => { +
+ {tools.map((tool) => { const isUp = tool.status === 'up' const hasFiring = (tool.firing_count ?? 0) > 0 const statusColor = isUp ? (hasFiring ? '#F59E0B' : '#22C55E') : '#cc2200' - const statusBg = isUp ? (hasFiring ? 'rgba(245,158,11,0.08)' : 'rgba(34,197,94,0.08)') : 'rgba(204,34,0,0.08)' const statusText = isUp ? (hasFiring ? `${tool.firing_count} 觸發` : '正常') : '離線' - const ic = TOOL_ICON_COLOR[tool.name] ?? { bg: '#f5f4ed', color: '#87867f', label: '?' } + const accentColor = TOOL_ACCENT_COLOR[tool.name] ?? '#b0ad9f' + const emoji = TOOL_EMOJI[tool.name] ?? '🔧' + const link = TOOL_LINKS[tool.name] ?? '#' const timeStr = (() => { try { return new Date(tool.checked_at).toLocaleTimeString('zh-TW', { timeZone: 'Asia/Taipei', hour: '2-digit', minute: '2-digit' }) } catch { return '--' } })() + return ( -
- {/* Icon box */} + + {/* 左側彩色條 */}
- {ic.label} + position: 'absolute', left: 0, top: 0, bottom: 0, width: 3, + background: accentColor, + }} /> + + {/* 主行 */} +
+ {emoji} +
+
+ {tool.name} +
+
{tool.description}
+
+
+
+ + {statusText} +
+ {hasFiring ? ( + + {tool.firing_count} 告警 + + ) : ( + + 0 告警 + + )} +
+
- {/* Content */} -
@@ -317,6 +378,13 @@ export default function Home({ params }: { params: { locale: string } }) { return `${((resolved / incidents.length) * 100).toFixed(0)}%` })() + // 自動處置率數值 (for progress bar) + const autoRemediationPct = (() => { + if (!incidents?.length) return 0 + const resolved = incidents.filter(i => i.status === 'resolved' || i.status === 'closed').length + return Math.round((resolved / incidents.length) * 100) + })() + // MTTR 均值 const mttrAvg = (() => { if (!incidents?.length) return '--' @@ -338,22 +406,34 @@ export default function Home({ params }: { params: { locale: string } }) { const podAllRunning = totalServices > 0 && healthyServices === totalServices // ── 7 Metrics Strip ───────────────────────────────────────────────────────── + // figma-v2 順序: 活躍事件 | 服務健康 | 待處理授權 | 今日事件 | 自動處置率 | MTTR 均值 | Pod 健康 const hasPendingApprovals = pendingApprovals !== null && pendingApprovals !== undefined && pendingApprovals > 0 + const todayIncidentCount = incidents?.length ?? 0 + const metrics: MetricItem[] = [ { label: tDashboard('activeIncidents'), value: incidents?.length ?? '--', sub: p0Count > 0 ? undefined : tDashboard('stable'), badge: p0Count > 0 ? { text: `P0 ×${p0Count}`, color: '#cc2200', bg: 'rgba(204,34,0,0.08)' } : undefined, - valueColor: p0Count > 0 ? '#cc2200' : undefined, + valueColor: p0Count > 0 ? '#d97757' : undefined, }, { label: tDashboard('serviceHealth'), value: totalServices > 0 ? `${healthyServices}/${totalServices}` : '--', - sub: tDashboard('normal'), - sparkline: rpsMetric?.trend ? { values: rpsMetric.trend, color: '#22C55E' } : undefined, + valueColor: '#22C55E', + extra: totalServices > 0 ? ( +
+ {Array.from({ length: Math.min(totalServices, 4) }).map((_, idx) => ( + + ))} +
+ ) : undefined, }, { label: tDashboard('pendingApprovals'), @@ -362,14 +442,39 @@ export default function Home({ params }: { params: { locale: string } }) { badge: hasPendingApprovals ? { text: `⏳ ${pendingApprovals}`, color: '#F59E0B', bg: 'rgba(245,158,11,0.08)' } : undefined, valueColor: hasPendingApprovals ? '#F59E0B' : undefined, }, + { + // figma-v2 新增: 今日事件 (與 activeIncidents 同值,但加趨勢箭頭) + label: tDashboard('todayIncidents'), + value: todayIncidentCount, + extra: ( + + + + + ), + }, { label: tDashboard('autoRemediationRate'), value: autoRemediationRate, + extra: ( +
+
+
+ ), sparkline: errorRateMetric?.trend ? { values: errorRateMetric.trend.map(v => 100 - v), color: '#4A90D9' } : undefined, }, { label: tDashboard('mttrAvg'), value: mttrAvg, + extra: ( + + + + + ), }, { label: tDashboard('podHealth'), @@ -442,7 +547,7 @@ export default function Home({ params }: { params: { locale: string } }) {
- {/* Metrics Row */} + {/* Metrics Row — figma-v2: 7 metrics */}
{metrics.map((m, i) => (
{/* Label */} - + {m.label} {/* Value row */} -
+
)}
- {/* Sub row */} -
- {m.badge ? ( + {/* Sub / badge / extra row */} +
+ {m.extra ? m.extra : m.badge ? ( ) : m.sub ? ( - {m.sub} + {m.sub} ) : null}
@@ -622,7 +727,7 @@ export default function Home({ params }: { params: { locale: string } }) { })()} />
- {/* 監控工具 */} + {/* 監控工具 — figma-v2 style: 左彩色條 + 可點擊 + meta行 */}