ekg stats: add 6h/24h/7d window filter and webui selector for time-windowed insights

This commit is contained in:
DBT
2026-03-01 05:44:20 +00:00
parent 11a0005645
commit d65116f11c
2 changed files with 36 additions and 3 deletions

View File

@@ -1639,6 +1639,21 @@ func (s *RegistryServer) handleWebUIEKGStats(w http.ResponseWriter, r *http.Requ
}
workspace := strings.TrimSpace(s.workspacePath)
ekgPath := filepath.Join(workspace, "memory", "ekg-events.jsonl")
window := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("window")))
windowDur := 24 * time.Hour
switch window {
case "6h":
windowDur = 6 * time.Hour
case "24h", "":
windowDur = 24 * time.Hour
case "7d":
windowDur = 7 * 24 * time.Hour
}
selectedWindow := window
if selectedWindow == "" {
selectedWindow = "24h"
}
cutoff := time.Now().UTC().Add(-windowDur)
b, _ := os.ReadFile(ekgPath)
lines := strings.Split(string(b), "\n")
if len(lines) > 0 && lines[len(lines)-1] == "" {
@@ -1667,6 +1682,14 @@ func (s *RegistryServer) handleWebUIEKGStats(w http.ResponseWriter, r *http.Requ
if json.Unmarshal([]byte(ln), &row) != nil {
continue
}
ts := strings.TrimSpace(fmt.Sprintf("%v", row["time"]))
if ts != "" {
if tm, err := time.Parse(time.RFC3339, ts); err == nil {
if tm.Before(cutoff) {
continue
}
}
}
provider := strings.TrimSpace(fmt.Sprintf("%v", row["provider"]))
status := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", row["status"])))
errSig := strings.TrimSpace(fmt.Sprintf("%v", row["errsig"]))
@@ -1731,6 +1754,7 @@ func (s *RegistryServer) handleWebUIEKGStats(w http.ResponseWriter, r *http.Requ
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
"window": selectedWindow,
"provider_top": toTopScore(providerScore, 5),
"provider_top_workload": toTopScore(providerScoreWorkload, 5),
"errsig_top": toTopCount(errSigCount, 5),

View File

@@ -47,6 +47,7 @@ const TaskAudit: React.FC = () => {
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'>('24h');
const fetchData = async () => {
setLoading(true);
@@ -67,7 +68,8 @@ const TaskAudit: React.FC = () => {
} else {
setDailyReport('');
}
const ekgUrl = `/webui/api/ekg_stats${q || ''}`;
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();
@@ -89,7 +91,7 @@ const TaskAudit: React.FC = () => {
}
};
useEffect(() => { fetchData(); }, [q, reportDate]);
useEffect(() => { fetchData(); }, [q, reportDate, ekgWindow]);
@@ -149,7 +151,14 @@ const TaskAudit: React.FC = () => {
</div>
<div className="border border-zinc-800 rounded-xl bg-zinc-900/40 p-3 text-sm">
<div className="text-xs text-zinc-400 uppercase tracking-wider mb-2">EKG Insights</div>
<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>