import React, { useEffect, useMemo, useState } from 'react'; import { AlertTriangle, RefreshCw, Route, ServerCrash, Workflow } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useAppContext } from '../context/AppContext'; import { FixedButton } from '../components/Button'; type EKGKV = { key?: string; score?: number; count?: number }; function StatCard({ title, value, subtitle, accent, icon, }: { title: string; value: string | number; subtitle?: string; accent: string; icon: React.ReactNode; }) { return (
{title}
{value}
{subtitle &&
{subtitle}
}
{icon}
); } function KVDistributionCard({ title, data, }: { title: string; data: Record; }) { const entries = useMemo(() => ( Object.entries(data).sort((a, b) => b[1] - a[1]) ), [data]); const maxValue = entries.length > 0 ? Math.max(...entries.map(([, value]) => value)) : 0; return (
{title}
{entries.length === 0 ? (
-
) : entries.map(([key, value]) => (
{key}
{value}
0 ? (value / maxValue) * 100 : 0}%` }} />
))}
); } function RankingCard({ title, items, valueMode, }: { title: string; items: EKGKV[]; valueMode: 'score' | 'count'; }) { return (
{title}
{items.length === 0 ? (
-
) : items.map((item, index) => (
{index + 1}
{item.key || '-'}
{valueMode === 'score' ? Number(item.score || 0).toFixed(2) : `x${item.count || 0}`}
))}
); } const EKG: React.FC = () => { const { t } = useTranslation(); const { q } = useAppContext(); const [loading, setLoading] = useState(false); const [ekgWindow, setEkgWindow] = useState<'6h' | '24h' | '7d'>(() => { const saved = typeof window !== 'undefined' ? window.localStorage.getItem('taskAudit.ekgWindow') : null; return saved === '6h' || saved === '24h' || saved === '7d' ? saved : '24h'; }); const [providerTop, setProviderTop] = useState([]); const [providerTopWorkload, setProviderTopWorkload] = useState([]); const [errsigTop, setErrsigTop] = useState([]); const [errsigTopWorkload, setErrsigTopWorkload] = useState([]); const [sourceStats, setSourceStats] = useState>({}); const [channelStats, setChannelStats] = useState>({}); const [escalationCount, setEscalationCount] = useState(0); const fetchData = async () => { setLoading(true); try { const ekgJoin = q ? `${q}&window=${encodeURIComponent(ekgWindow)}` : `?window=${encodeURIComponent(ekgWindow)}`; const er = await fetch(`/webui/api/ekg_stats${ekgJoin}`); if (!er.ok) throw new Error(await er.text()); const ej = await er.json(); setProviderTop(Array.isArray(ej.provider_top) ? ej.provider_top : []); setProviderTopWorkload(Array.isArray(ej.provider_top_workload) ? ej.provider_top_workload : []); setErrsigTop(Array.isArray(ej.errsig_top) ? ej.errsig_top : []); setErrsigTopWorkload(Array.isArray(ej.errsig_top_workload) ? ej.errsig_top_workload : []); setSourceStats(ej.source_stats && typeof ej.source_stats === 'object' ? ej.source_stats : {}); setChannelStats(ej.channel_stats && typeof ej.channel_stats === 'object' ? ej.channel_stats : {}); setEscalationCount(Number(ej.escalation_count || 0)); } catch (e) { console.error(e); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, [q, ekgWindow]); useEffect(() => { if (typeof window !== 'undefined') window.localStorage.setItem('taskAudit.ekgWindow', ekgWindow); }, [ekgWindow]); const sourceCount = Object.keys(sourceStats).length; const channelCount = Object.keys(channelStats).length; const totalErrorHits = errsigTop.reduce((sum, item) => sum + Number(item.count || 0), 0); const topWorkloadProvider = providerTopWorkload[0]?.key || '-'; return (

{t('ekg')}

{t('ekgOverviewHint')}
} /> } /> } /> } />
); }; export default EKG;