From 09f9cf2bf08e6dd5f29673eb94969d41b8bf6cf1 Mon Sep 17 00:00:00 2001 From: wangwei Date: Fri, 22 May 2026 00:02:38 +0800 Subject: [PATCH] feat(status): loading/error states, refresh button, auto-poll, health panel, config truncation, doc status highlights --- frontend/src/pages/Status/StatusPage.tsx | 240 ++++++++++++++++++++--- 1 file changed, 216 insertions(+), 24 deletions(-) diff --git a/frontend/src/pages/Status/StatusPage.tsx b/frontend/src/pages/Status/StatusPage.tsx index b4b5ef4..462b714 100644 --- a/frontend/src/pages/Status/StatusPage.tsx +++ b/frontend/src/pages/Status/StatusPage.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useTheme } from '../../contexts'; import { Content } from '../../components/layout/Content'; import { TPattern } from '../../components/common/TPattern'; -import { getSystemStats, getSystemConfig, type SystemStats, type SystemConfig } from '../../api/status'; +import { getSystemStats, getSystemConfig, getSystemHealth, type SystemStats, type SystemConfig, type SystemHealth } from '../../api/status'; import { getDocumentList, type DocInfo } from '../../api/docs'; const StatsCard = ({ label, value, accent = false }: { @@ -36,6 +36,40 @@ const StatsCard = ({ label, value, accent = false }: { ); }; +const ServiceBadge = ({ + label, + status, + detail, +}: { + label: string; + status: 'ok' | 'error' | 'unknown' | boolean; + detail?: string; +}) => { + const { theme } = useTheme(); + const isOk = status === 'ok' || status === true; + const isUnknown = status === 'unknown'; + const color = isUnknown ? theme.text3 : isOk ? theme.green : '#d64545'; + return ( +
+
+ + {label} +
+ + {detail ?? (isUnknown ? '—' : isOk ? 'OK' : 'ERROR')} + +
+ ); +}; + export const StatusPage: React.FC = () => { const { theme, isDark } = useTheme(); const [stats, setStats] = useState({ @@ -46,33 +80,110 @@ export const StatusPage: React.FC = () => { }); const [config, setConfig] = useState(null); const [docs, setDocs] = useState([]); + const [health, setHealth] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - async function loadData() { + const loadData = useCallback(async () => { + setLoading(true); + setError(null); try { - const [statsRes, configRes, docsRes] = await Promise.all([ + const [statsRes, configRes, docsRes, healthRes] = await Promise.all([ getSystemStats(), getSystemConfig(), getDocumentList(), + getSystemHealth(), ]); setStats(statsRes); setConfig(configRes); setDocs(docsRes.docs); - } catch (error) { - console.error('Failed to load status data:', error); + setHealth(healthRes); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load status data'); + } finally { + setLoading(false); } - } - - useEffect(() => { - const timerId = window.setTimeout(() => { - void loadData(); - }, 0); - return () => window.clearTimeout(timerId); }, []); + // Initial load + useEffect(() => { + void loadData(); + }, [loadData]); + + // Auto-poll every 5 s while any document is still processing + useEffect(() => { + const hasProcessing = docs.some(d => d.status === 'parsing' || d.status === 'pending'); + if (!hasProcessing) return; + const id = window.setInterval(() => void loadData(), 5000); + return () => window.clearInterval(id); + }, [docs, loadData]); + return ( + + + {/* Loading indicator */} + {loading && ( +
+ + LOADING... +
+ )} + + {/* Error banner */} + {error && ( +
+ {error} + +
+ )} + + {/* Stats section */}
+
+

+ DOCUMENT STATISTICS +

+ +
{
+ {/* Service health section */} +
+

+ SERVICE HEALTH +

+
+ + + +
+
+ + + +
+
+ + {/* System configuration section */}

{ borderRadius: 10, border: `1px solid ${theme.border}`, boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none', + overflow: 'hidden', }}> - {k} - {v} + {k} + + {v} + ))} @@ -145,15 +313,31 @@ export const StatusPage: React.FC = () => { borderRadius: 10, border: `1px solid ${theme.border}`, boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none', + overflow: 'hidden', }}> - {k} - {v} + {k} + + {v} + ))}

+ {/* Document index section */}

{ background: theme.bgCard, borderRadius: 10, marginBottom: 10, - border: `1px solid ${theme.border}`, + border: `1px solid ${ + d.status === 'failed' ? '#d64545' : + d.status === 'parsing' || d.status === 'pending' ? theme.accent + '80' : + theme.border + }`, }}> -
- {d.name} - +
+ {d.name} + {d.updated_at ? new Date(d.updated_at).toLocaleString() : d.status}
-
+
{d.chunks} chunks
- {d.status.toUpperCase()} + {d.status === 'parsing' ? '⟳ ' : ''}{d.status.toUpperCase()}