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()}