mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 20:51:49 +00:00
105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { fetchGroups, fetchAllSignals, Group, Signal } from "../lib/api";
|
|
|
|
export default function AgentStats() {
|
|
const [groups, setGroups] = useState<Group[]>([]);
|
|
const [signals, setSignals] = useState<Signal[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function load() {
|
|
try {
|
|
const [grps, all] = await Promise.all([fetchGroups(), fetchAllSignals()]);
|
|
setGroups(grps);
|
|
setSignals(all.flatMap((g) => g.signals));
|
|
} catch {
|
|
// ignore
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
load();
|
|
}, []);
|
|
|
|
const totalSignals = groups.reduce((acc, g) => acc + g.signal_count, 0);
|
|
const activeGroups = groups.filter((g) => g.signal_count > 0).length;
|
|
const criticalSignals = signals.filter(
|
|
(s) => s.metadata.severity === "critical" || s.metadata.severity === "high"
|
|
).length;
|
|
const errorRate =
|
|
totalSignals > 0
|
|
? `${((criticalSignals / totalSignals) * 100).toFixed(2)}%`
|
|
: "0.00%";
|
|
|
|
const stats = [
|
|
{
|
|
title: "Active Groups",
|
|
value: loading ? "—" : `${activeGroups} / ${groups.length}`,
|
|
icon: "memory",
|
|
iconColor: "#a88cfb",
|
|
},
|
|
{
|
|
title: "Total Signals",
|
|
value: loading ? "—" : totalSignals >= 1000 ? `${(totalSignals / 1000).toFixed(1)}k` : String(totalSignals),
|
|
icon: "speed",
|
|
iconColor: "#00daf3",
|
|
},
|
|
{
|
|
title: "High Priority",
|
|
value: loading ? "—" : `${criticalSignals}`,
|
|
icon: "warning",
|
|
iconColor: criticalSignals > 0 ? "#ff6f78" : "#10b981",
|
|
},
|
|
{
|
|
title: "Lens Coverage",
|
|
value: loading ? "—" : `${[...new Set(groups.map((g) => g.lens).filter(Boolean))].length} types`,
|
|
icon: "verified",
|
|
iconColor: "#10b981",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 animate-fade-in-scale mb-8">
|
|
{stats.map((stat, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="glass p-5 rounded-2xl border border-white/5 relative overflow-hidden group card-interactive flex flex-col justify-between"
|
|
style={{
|
|
minHeight: "100px",
|
|
background: "rgba(20,20,25,0.4)",
|
|
backdropFilter: "blur(12px)",
|
|
}}
|
|
>
|
|
<p className="text-[10px] uppercase tracking-widest text-zinc-500 font-mono-data mb-3">
|
|
{stat.title}
|
|
</p>
|
|
<div className="flex flex-row items-end justify-between">
|
|
<h3 className="text-2xl font-light tracking-wide text-zinc-200 font-mono-data drop-shadow">
|
|
{stat.value.split(" ").map((part, i) => (
|
|
<span
|
|
key={i}
|
|
className={
|
|
i % 2 !== 0 && part.match(/[a-zA-Z]/)
|
|
? "text-sm ml-1 text-zinc-500"
|
|
: ""
|
|
}
|
|
>
|
|
{part}{" "}
|
|
</span>
|
|
))}
|
|
</h3>
|
|
<span
|
|
className="material-symbols-outlined text-[24px] opacity-90 drop-shadow-md"
|
|
style={{ color: stat.iconColor }}
|
|
>
|
|
{stat.icon}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|