mirror of
https://github.com/LittleQuartZ/addmon.git
synced 2026-02-07 02:45:28 +07:00
167 lines
6.1 KiB
TypeScript
167 lines
6.1 KiB
TypeScript
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import type { KpiMetrics } from "@/lib/types/alerts";
|
|
import { KPI_THRESHOLDS } from "@/lib/types/alerts";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface KpiDashboardProps {
|
|
kpis: KpiMetrics | null;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export function KpiDashboard({ kpis, isLoading = false }: KpiDashboardProps) {
|
|
if (isLoading) {
|
|
return (
|
|
<div className="grid gap-4 md:grid-cols-4">
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
Alert Coverage Ratio
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Skeleton className="h-7 w-20 rounded-none" />
|
|
<Skeleton className="mt-1 h-4 w-32 rounded-none" />
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
False Positive Rate
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Skeleton className="h-7 w-20 rounded-none" />
|
|
<Skeleton className="mt-1 h-4 w-32 rounded-none" />
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Uptime Ratio</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Skeleton className="h-7 w-20 rounded-none" />
|
|
<Skeleton className="mt-1 h-4 w-32 rounded-none" />
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Verdict</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Skeleton className="h-7 w-20 rounded-none" />
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!kpis) {
|
|
return (
|
|
<Card className="rounded-none">
|
|
<CardContent className="py-6 text-center text-xs text-muted-foreground">
|
|
No KPI data available
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const alertCoverageRatio = kpis.errorCoverageRatio;
|
|
const falsePositiveRate = kpis.invalidAlertRatio;
|
|
const MONTHS_MS = 30 * 24 * 60 * 60 * 1000 * 3; // 3 months
|
|
const uptimeRatio = ((MONTHS_MS - kpis.overallDowntimeMs) / MONTHS_MS) * 100;
|
|
|
|
const coveragePassed =
|
|
alertCoverageRatio >= KPI_THRESHOLDS.alertCoverageRatio;
|
|
const fpPassed = falsePositiveRate <= KPI_THRESHOLDS.falsePositiveRate;
|
|
const uptimePassed = uptimeRatio >= KPI_THRESHOLDS.uptimeRatio;
|
|
const isEffective = coveragePassed && fpPassed && uptimePassed;
|
|
|
|
const coverageColor = coveragePassed ? "text-green-500" : "text-destructive";
|
|
const fpColor = fpPassed ? "text-green-500" : "text-destructive";
|
|
const uptimeColor = uptimePassed ? "text-green-500" : "text-destructive";
|
|
|
|
return (
|
|
<div className="grid gap-4 md:grid-cols-4">
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
Alert Coverage Ratio
|
|
</CardTitle>
|
|
<span className="text-[10px] text-muted-foreground">
|
|
≥{KPI_THRESHOLDS.alertCoverageRatio}%
|
|
</span>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={cn("text-2xl font-bold", coverageColor)}>
|
|
{alertCoverageRatio.toFixed(1)}%
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{kpis.coveredIncidents} of {kpis.totalIncidents} incidents
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
False Positive Rate
|
|
</CardTitle>
|
|
<span className="text-[10px] text-muted-foreground">
|
|
≤{KPI_THRESHOLDS.falsePositiveRate}%
|
|
</span>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={cn("text-2xl font-bold", fpColor)}>
|
|
{falsePositiveRate.toFixed(1)}%
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{kpis.invalidAlerts} of {kpis.totalFiringAlerts} alerts
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Uptime Ratio</CardTitle>
|
|
<span className="text-[10px] text-muted-foreground">
|
|
≥{KPI_THRESHOLDS.uptimeRatio}%
|
|
</span>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={cn("text-2xl font-bold", uptimeColor)}>
|
|
{uptimeRatio.toFixed(2)}%
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Downtime: {kpis.overallDowntimeFormatted}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card
|
|
className={cn(
|
|
"rounded-none border-2",
|
|
isEffective ? "border-green-500/50" : "border-destructive/50",
|
|
)}
|
|
>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Verdict</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div
|
|
className={cn(
|
|
"text-xl font-bold",
|
|
isEffective ? "text-green-500" : "text-destructive",
|
|
)}
|
|
>
|
|
{isEffective ? "EFFECTIVE" : "NOT EFFECTIVE"}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{[coveragePassed, fpPassed, uptimePassed].filter(Boolean).length}/3
|
|
KPIs passed
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|