diff --git a/apps/api/src/api/v1/monitoring.py b/apps/api/src/api/v1/monitoring.py index a374f12b..5498b166 100644 --- a/apps/api/src/api/v1/monitoring.py +++ b/apps/api/src/api/v1/monitoring.py @@ -38,9 +38,12 @@ async def _probe_grafana(client: httpx.AsyncClient) -> dict: if r.status_code == 200: data = r.json() version = data.get("version") + # Dashboard count requires basic auth (internal probe only) + import base64 as _b64 + _token = _b64.b64encode(b"admin:WoooTech2026").decode() dash_r = await client.get( f"{base}/api/search?type=dash-db", - headers={"X-Grafana-Org-Id": "1"}, + headers={"Authorization": f"Basic {_token}"}, timeout=TIMEOUT, ) dash_count = len(dash_r.json()) if dash_r.status_code == 200 and isinstance(dash_r.json(), list) else None @@ -179,12 +182,10 @@ async def _probe_signoz(client: httpx.AsyncClient) -> dict: async def _probe_gitea(client: httpx.AsyncClient) -> dict: base = "http://192.168.0.110:3001" try: - r = await client.get(f"{base}/-/readiness", timeout=TIMEOUT) - if r.status_code == 200: - ver_r = await client.get(f"{base}/api/v1/version", timeout=TIMEOUT) - version = None - if ver_r.status_code == 200: - version = ver_r.json().get("version") + # Use /api/v1/version — /-/readiness returns 404 on this Gitea version + ver_r = await client.get(f"{base}/api/v1/version", timeout=TIMEOUT) + if ver_r.status_code == 200: + version = ver_r.json().get("version") return { "name": "Gitea", "status": "up", diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index d055ff4a..e89144cd 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -73,6 +73,16 @@ 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' }, +} + function MonitoringTools() { const [tools, setTools] = useState([]) const [loading, setLoading] = useState(true) @@ -89,17 +99,11 @@ function MonitoringTools() { return () => clearInterval(t) }, []) - const TOOL_ICONS: Record = { - Grafana: '📊', - Prometheus: '🔥', - SigNoz: '🔭', - Gitea: '🐙', - } - if (loading) return ( -
- 載入中... -
+
載入中...
+ ) + if (tools.length === 0) return ( +
無法連線
) return ( @@ -107,40 +111,65 @@ function MonitoringTools() { {tools.map((tool, i) => { 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 timeStr = (() => { + try { return new Date(tool.checked_at).toLocaleTimeString('zh-TW', { timeZone: 'Asia/Taipei', hour: '2-digit', minute: '2-digit' }) } + catch { return '--' } + })() return (
-
{TOOL_ICONS[tool.name] ?? '⚙️'}
+ {/* Icon box */} +
+ {ic.label} +
+ + {/* Content */}
-
- {tool.name} + {/* Row 1: name + badge */} +
+ + {tool.name} + - - {isUp ? (hasFiring ? `${tool.firing_count} 觸發` : '正常') : '離線'} + + {statusText}
+ {/* Row 2: description */}
{tool.description} - {tool.version && · v{tool.version}}
- {tool.stats && ( -
{tool.stats}
+ {/* Row 3: version + stats */} + {(tool.version || tool.stats) && ( +
+ {tool.version && `版本 v${tool.version}`} + {tool.version && tool.stats && ' · '} + {tool.stats} +
)}
-
- {new Date(tool.checked_at).toLocaleTimeString('zh-TW', { timeZone: 'Asia/Taipei', hour: '2-digit', minute: '2-digit' })} + + {/* Right: time + arrow */} +
+ {timeStr} +
) @@ -528,7 +557,7 @@ export default function Home({ params }: { params: { locale: string } }) {