/** * Admin 后台 - 监控面板(报刊风) * * 功能: * - /health 存活检查(自动:30 秒一轮;可手动刷新) * - /health/ready 数据库就绪检查 * - 服务名 / 版本 / 运行时间 / 检查项(后端返回什么就展示什么) */ import { useCallback, useEffect, useState } from 'react' import { fetchHealth } from '../dal' import { api } from '../api' import { Card, CardBody, CardHeader, SectionHeader, Alert, Spinner, Badge } from '../components' export default function MonitoringPage() { const [health, setHealth] = useState(null) const [ready, setReady] = useState<'ready' | 'not_ready' | null>(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [lastCheck, setLastCheck] = useState('') const refresh = useCallback(async () => { setLoading(true) setError(null) try { const [h, r] = await Promise.allSettled([ fetchHealth(), api.get<{ status: string }>('/health/ready'), ]) setHealth(h.status === 'fulfilled' ? h.value : null) setReady(r.status === 'fulfilled' ? (r.value?.status as 'ready' | 'not_ready') : null) if (h.status === 'rejected') { setError(h.reason instanceof Error ? h.reason.message : '无法连接到后端') } setLastCheck(new Date().toLocaleTimeString('zh-CN', { hour12: false })) } finally { setLoading(false) } }, []) useEffect(() => { refresh() const t = setInterval(refresh, 30_000) return () => clearInterval(t) }, [refresh]) const alive = health?.status === 'healthy' || health?.status === 'ok' return (
{lastCheck && `最近巡检 ${lastCheck}`}
{error && ( )}
{/* 存活状态 */}
存活状态
{/* 数据库就绪 */}
数据库就绪
{/* 服务名 */}
服务
{health?.service || 'profeto'}
{/* 版本 */} {health?.version && (
版本
{health.version}
)} {/* 运行时间 */} {health?.uptime_seconds != null && (
运行时间
{Math.floor(health.uptime_seconds / 3600)}h{' '} {Math.floor((health.uptime_seconds % 3600) / 60)}m
)} {/* 检查项 */} {health?.checks && Object.keys(health.checks).length > 0 && (
健康检查
{Object.entries(health.checks).map(([key, val]) => (
{key} {String(val)}
))}
)}
) }