mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-13 17:20:31 +08:00
webui layout: add dedicated EKG menu/page and declutter task-audit layout
This commit is contained in:
@@ -2,8 +2,6 @@ import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
|
||||
type EKGKV = { key?: string; score?: number; count?: number };
|
||||
|
||||
type TaskAuditItem = {
|
||||
task_id?: string;
|
||||
time?: string;
|
||||
@@ -39,18 +37,6 @@ const TaskAudit: React.FC = () => {
|
||||
const [dailyReport, setDailyReport] = useState<string>('');
|
||||
const [reportDate, setReportDate] = useState<string>(new Date().toISOString().slice(0,10));
|
||||
const [showDailyReport, setShowDailyReport] = useState(false);
|
||||
const [ekgProviderTop, setEkgProviderTop] = useState<EKGKV[]>([]);
|
||||
const [ekgProviderTopWorkload, setEkgProviderTopWorkload] = useState<EKGKV[]>([]);
|
||||
const [ekgErrsigTop, setEkgErrsigTop] = useState<EKGKV[]>([]);
|
||||
const [ekgErrsigTopHeartbeat, setEkgErrsigTopHeartbeat] = useState<EKGKV[]>([]);
|
||||
const [ekgErrsigTopWorkload, setEkgErrsigTopWorkload] = useState<EKGKV[]>([]);
|
||||
const [ekgSourceStats, setEkgSourceStats] = useState<Record<string, number>>({});
|
||||
const [ekgChannelStats, setEkgChannelStats] = useState<Record<string, number>>({});
|
||||
const [ekgEscalationCount, setEkgEscalationCount] = useState<number>(0);
|
||||
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 fetchData = async () => {
|
||||
setLoading(true);
|
||||
@@ -71,20 +57,6 @@ const TaskAudit: React.FC = () => {
|
||||
} else {
|
||||
setDailyReport('');
|
||||
}
|
||||
const ekgJoin = q ? `${q}&window=${encodeURIComponent(ekgWindow)}` : `?window=${encodeURIComponent(ekgWindow)}`;
|
||||
const ekgUrl = `/webui/api/ekg_stats${ekgJoin}`;
|
||||
const er = await fetch(ekgUrl);
|
||||
if (er.ok) {
|
||||
const ej = await er.json();
|
||||
setEkgProviderTop(Array.isArray(ej.provider_top) ? ej.provider_top : []);
|
||||
setEkgProviderTopWorkload(Array.isArray(ej.provider_top_workload) ? ej.provider_top_workload : []);
|
||||
setEkgErrsigTop(Array.isArray(ej.errsig_top) ? ej.errsig_top : []);
|
||||
setEkgErrsigTopHeartbeat(Array.isArray(ej.errsig_top_heartbeat) ? ej.errsig_top_heartbeat : []);
|
||||
setEkgErrsigTopWorkload(Array.isArray(ej.errsig_top_workload) ? ej.errsig_top_workload : []);
|
||||
setEkgSourceStats(ej.source_stats && typeof ej.source_stats === 'object' ? ej.source_stats : {});
|
||||
setEkgChannelStats(ej.channel_stats && typeof ej.channel_stats === 'object' ? ej.channel_stats : {});
|
||||
setEkgEscalationCount(Number(ej.escalation_count || 0));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setItems([]);
|
||||
@@ -94,13 +66,8 @@ const TaskAudit: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { fetchData(); }, [q, reportDate, ekgWindow]);
|
||||
useEffect(() => { fetchData(); }, [q, reportDate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem('taskAudit.ekgWindow', ekgWindow);
|
||||
}
|
||||
}, [ekgWindow]);
|
||||
|
||||
|
||||
|
||||
@@ -159,83 +126,6 @@ const TaskAudit: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-zinc-800 rounded-xl bg-zinc-900/40 p-3 text-sm">
|
||||
<div className="flex items-center justify-between gap-2 mb-2">
|
||||
<div className="text-xs text-zinc-400 uppercase tracking-wider">EKG Insights</div>
|
||||
<select value={ekgWindow} onChange={(e)=>setEkgWindow(e.target.value as '6h' | '24h' | '7d')} className="bg-zinc-900 border border-zinc-700 rounded px-2 py-1 text-xs">
|
||||
<option value="6h">6h</option>
|
||||
<option value="24h">24h</option>
|
||||
<option value="7d">7d</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs">
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Escalations</div>
|
||||
<div className="text-zinc-100 text-base font-semibold">{ekgEscalationCount}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Source Stats</div>
|
||||
<div className="space-y-1">
|
||||
{Object.keys(ekgSourceStats).length === 0 ? <div className="text-zinc-500">-</div> : Object.entries(ekgSourceStats).map(([k, v]) => (
|
||||
<div key={k} className="text-zinc-200">{k}: <span className="text-zinc-400">{v}</span></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Channel Stats</div>
|
||||
<div className="space-y-1">
|
||||
{Object.keys(ekgChannelStats).length === 0 ? <div className="text-zinc-500">-</div> : Object.entries(ekgChannelStats).map(([k, v]) => (
|
||||
<div key={k} className="text-zinc-200">{k}: <span className="text-zinc-400">{v}</span></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 grid grid-cols-1 md:grid-cols-2 gap-2 text-xs">
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Top Providers (workload)</div>
|
||||
<div className="space-y-1">
|
||||
{ekgProviderTopWorkload.length === 0 ? <div className="text-zinc-500">-</div> : ekgProviderTopWorkload.map((x, i) => (
|
||||
<div key={`${x.key}-${i}`} className="text-zinc-200">{x.key} <span className="text-zinc-500">({Number(x.score || 0).toFixed(2)})</span></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Top Providers (all)</div>
|
||||
<div className="space-y-1">
|
||||
{ekgProviderTop.length === 0 ? <div className="text-zinc-500">-</div> : ekgProviderTop.map((x, i) => (
|
||||
<div key={`${x.key}-${i}`} className="text-zinc-200">{x.key} <span className="text-zinc-500">({Number(x.score || 0).toFixed(2)})</span></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 grid grid-cols-1 md:grid-cols-3 gap-2 text-xs">
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Top Error Signatures (workload)</div>
|
||||
<div className="space-y-1 max-h-24 overflow-y-auto">
|
||||
{ekgErrsigTopWorkload.length === 0 ? <div className="text-zinc-500">-</div> : ekgErrsigTopWorkload.map((x, i) => (
|
||||
<div key={`${x.key}-${i}`} className="text-zinc-200 truncate">{x.key} <span className="text-zinc-500">(x{x.count || 0})</span></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Top Error Signatures (heartbeat)</div>
|
||||
<div className="space-y-1 max-h-24 overflow-y-auto">
|
||||
{ekgErrsigTopHeartbeat.length === 0 ? <div className="text-zinc-500">-</div> : ekgErrsigTopHeartbeat.map((x, i) => (
|
||||
<div key={`${x.key}-${i}`} className="text-zinc-200 truncate">{x.key} <span className="text-zinc-500">(x{x.count || 0})</span></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/40 p-2">
|
||||
<div className="text-zinc-500 mb-1">Top Error Signatures (all)</div>
|
||||
<div className="space-y-1 max-h-24 overflow-y-auto">
|
||||
{ekgErrsigTop.length === 0 ? <div className="text-zinc-500">-</div> : ekgErrsigTop.map((x, i) => (
|
||||
<div key={`${x.key}-${i}`} className="text-zinc-200 truncate">{x.key} <span className="text-zinc-500">(x{x.count || 0})</span></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-zinc-800 rounded-xl bg-zinc-900/40 p-3 text-sm">
|
||||
<div className="flex items-center justify-between gap-2 flex-wrap">
|
||||
<div className="text-xs text-zinc-400 uppercase tracking-wider">{t('dailySummary')}</div>
|
||||
|
||||
Reference in New Issue
Block a user