mirror of
https://github.com/LittleQuartZ/addmon.git
synced 2026-02-07 02:45:28 +07:00
refactor: rename app crate and binary names to addmon for consistency
This commit is contained in:
parent
d19a7bdaa0
commit
cb30287fbd
5 changed files with 127 additions and 62 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "addmon",
|
"productName": "addmon",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"identifier": "com.tauri.dev",
|
"identifier": "com.littlequartz.addmon",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
"devUrl": "http://localhost:3001",
|
"devUrl": "http://localhost:3001",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import * as React from "react";
|
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Plus, Link, Link2Off } from "lucide-react";
|
import { Link, Link2Off, Plus } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import type { Incident, ProcessedAlert } from "@/lib/types/alerts";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -15,6 +13,8 @@ import {
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import type { Incident, ProcessedAlert } from "@/lib/types/alerts";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface IncidentManagerProps {
|
interface IncidentManagerProps {
|
||||||
incidents: Incident[];
|
incidents: Incident[];
|
||||||
|
|
@ -137,7 +137,7 @@ export function IncidentManager({
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative transition-colors",
|
"relative transition-colors",
|
||||||
isCurrentAlertAttached &&
|
isCurrentAlertAttached &&
|
||||||
"border-primary/50 bg-primary/5",
|
"border-primary/50 bg-primary/5",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
|
|
@ -166,7 +166,7 @@ export function IncidentManager({
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0",
|
"shrink-0",
|
||||||
isCurrentAlertAttached &&
|
isCurrentAlertAttached &&
|
||||||
"bg-destructive/10 text-destructive hover:bg-destructive/20",
|
"bg-destructive/10 text-destructive hover:bg-destructive/20",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isCurrentAlertAttached ? (
|
{isCurrentAlertAttached ? (
|
||||||
|
|
@ -199,7 +199,7 @@ export function IncidentManager({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="pt-0 pb-3 text-[10px] text-muted-foreground">
|
<CardFooter className="pt-3 pb-3 text-[10px] text-muted-foreground">
|
||||||
<div className="flex items-center gap-1.5 w-full">
|
<div className="flex items-center gap-1.5 w-full">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { KpiMetrics } from "@/lib/types/alerts";
|
import type { KpiMetrics } from "@/lib/types/alerts";
|
||||||
|
import { KPI_THRESHOLDS } from "@/lib/types/alerts";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface KpiDashboardProps {
|
interface KpiDashboardProps {
|
||||||
|
|
@ -11,11 +12,11 @@ interface KpiDashboardProps {
|
||||||
export function KpiDashboard({ kpis, isLoading = false }: KpiDashboardProps) {
|
export function KpiDashboard({ kpis, isLoading = false }: KpiDashboardProps) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-4">
|
||||||
<Card className="rounded-none">
|
<Card className="rounded-none">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">
|
<CardTitle className="text-sm font-medium">
|
||||||
Error Coverage
|
Alert Coverage Ratio
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -26,17 +27,7 @@ export function KpiDashboard({ kpis, isLoading = false }: KpiDashboardProps) {
|
||||||
<Card className="rounded-none">
|
<Card className="rounded-none">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">
|
<CardTitle className="text-sm font-medium">
|
||||||
Total Downtime
|
False Positive Rate
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Skeleton className="h-7 w-20 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">
|
|
||||||
Invalid Alerts
|
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -44,6 +35,23 @@ export function KpiDashboard({ kpis, isLoading = false }: KpiDashboardProps) {
|
||||||
<Skeleton className="mt-1 h-4 w-32 rounded-none" />
|
<Skeleton className="mt-1 h-4 w-32 rounded-none" />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -58,29 +66,35 @@ export function KpiDashboard({ kpis, isLoading = false }: KpiDashboardProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const coverageColor =
|
const alertCoverageRatio = kpis.errorCoverageRatio;
|
||||||
kpis.errorCoverageRatio > 80
|
const falsePositiveRate = kpis.invalidAlertRatio;
|
||||||
? "text-green-500"
|
const MONTHS_MS = 30 * 24 * 60 * 60 * 1000 * 3; // 3 months
|
||||||
: kpis.errorCoverageRatio > 50
|
const uptimeRatio = ((MONTHS_MS - kpis.overallDowntimeMs) / MONTHS_MS) * 100;
|
||||||
? "text-yellow-500"
|
|
||||||
: "text-destructive";
|
|
||||||
|
|
||||||
const invalidColor =
|
const coveragePassed =
|
||||||
kpis.invalidAlertRatio < 10
|
alertCoverageRatio >= KPI_THRESHOLDS.alertCoverageRatio;
|
||||||
? "text-green-500"
|
const fpPassed = falsePositiveRate <= KPI_THRESHOLDS.falsePositiveRate;
|
||||||
: kpis.invalidAlertRatio < 25
|
const uptimePassed = uptimeRatio >= KPI_THRESHOLDS.uptimeRatio;
|
||||||
? "text-yellow-500"
|
const isEffective = coveragePassed && fpPassed && uptimePassed;
|
||||||
: "text-destructive";
|
|
||||||
|
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 (
|
return (
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-4">
|
||||||
<Card className="rounded-none">
|
<Card className="rounded-none">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">Error Coverage</CardTitle>
|
<CardTitle className="text-sm font-medium">
|
||||||
|
Alert Coverage Ratio
|
||||||
|
</CardTitle>
|
||||||
|
<span className="text-[10px] text-muted-foreground">
|
||||||
|
≥{KPI_THRESHOLDS.alertCoverageRatio}%
|
||||||
|
</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={cn("text-2xl font-bold", coverageColor)}>
|
<div className={cn("text-2xl font-bold", coverageColor)}>
|
||||||
{kpis.errorCoverageRatio.toFixed(1)}%
|
{alertCoverageRatio.toFixed(1)}%
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{kpis.coveredIncidents} of {kpis.totalIncidents} incidents
|
{kpis.coveredIncidents} of {kpis.totalIncidents} incidents
|
||||||
|
|
@ -90,28 +104,64 @@ export function KpiDashboard({ kpis, isLoading = false }: KpiDashboardProps) {
|
||||||
|
|
||||||
<Card className="rounded-none">
|
<Card className="rounded-none">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">Total Downtime</CardTitle>
|
<CardTitle className="text-sm font-medium">
|
||||||
|
False Positive Rate
|
||||||
|
</CardTitle>
|
||||||
|
<span className="text-[10px] text-muted-foreground">
|
||||||
|
≤{KPI_THRESHOLDS.falsePositiveRate}%
|
||||||
|
</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">
|
<div className={cn("text-2xl font-bold", fpColor)}>
|
||||||
{kpis.overallDowntimeFormatted}
|
{falsePositiveRate.toFixed(1)}%
|
||||||
</div>
|
|
||||||
</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">Invalid Alerts</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className={cn("text-2xl font-bold", invalidColor)}>
|
|
||||||
{kpis.invalidAlertRatio.toFixed(1)}%
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{kpis.invalidAlerts} of {kpis.totalFiringAlerts} alerts
|
{kpis.invalidAlerts} of {kpis.totalFiringAlerts} alerts
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,19 @@ export interface KpiMetrics {
|
||||||
invalidAlerts: number;
|
invalidAlerts: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const KPI_THRESHOLDS = {
|
||||||
|
alertCoverageRatio: 99,
|
||||||
|
falsePositiveRate: 20,
|
||||||
|
uptimeRatio: 99.9,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export interface EffectivenessVerdict {
|
||||||
|
isEffective: boolean;
|
||||||
|
alertCoveragePassed: boolean;
|
||||||
|
falsePositivePassed: boolean;
|
||||||
|
uptimePassed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MonitorState {
|
export interface MonitorState {
|
||||||
alerts: ProcessedAlert[];
|
alerts: ProcessedAlert[];
|
||||||
incidents: Incident[];
|
incidents: Incident[];
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { ArrowDownWideNarrow, FileUp, Filter, Search } from "lucide-react";
|
import { ArrowDownWideNarrow, FileUp, Filter, Search } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { ModeToggle } from "@/components/mode-toggle";
|
||||||
import { AlertList } from "@/components/monitor/alert-list";
|
import { AlertList } from "@/components/monitor/alert-list";
|
||||||
import { IncidentManager } from "@/components/monitor/incident-manager";
|
import { IncidentManager } from "@/components/monitor/incident-manager";
|
||||||
import { KpiDashboard } from "@/components/monitor/kpi-dashboard";
|
import { KpiDashboard } from "@/components/monitor/kpi-dashboard";
|
||||||
|
|
@ -218,15 +219,15 @@ function MonitorPage() {
|
||||||
);
|
);
|
||||||
const updatedIncidents = incidentId
|
const updatedIncidents = incidentId
|
||||||
? incidents.map((i) =>
|
? incidents.map((i) =>
|
||||||
i.id === incidentId
|
i.id === incidentId
|
||||||
? {
|
? {
|
||||||
...i,
|
...i,
|
||||||
attachedAlertIds: i.attachedAlertIds.filter(
|
attachedAlertIds: i.attachedAlertIds.filter(
|
||||||
(aid) => aid !== alertId,
|
(aid) => aid !== alertId,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
: i,
|
: i,
|
||||||
)
|
)
|
||||||
: incidents;
|
: incidents;
|
||||||
|
|
||||||
setAlerts(updatedAlerts);
|
setAlerts(updatedAlerts);
|
||||||
|
|
@ -249,9 +250,10 @@ function MonitorPage() {
|
||||||
onChange={handleFileInputChange}
|
onChange={handleFileInputChange}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
/>
|
/>
|
||||||
<header className="flex shrink-0 items-center justify-between border-b px-4 py-3">
|
<header className="flex shrink-0 items-center border-b px-4 py-3">
|
||||||
<h1 className="text-sm font-semibold">Alert Monitor</h1>
|
<h1 className="text-sm font-semibold mr-auto">Alert Monitor</h1>
|
||||||
<Button onClick={handleLoadFile} disabled={isLoading} size="sm">
|
<ModeToggle />
|
||||||
|
<Button className="ml-2" onClick={handleLoadFile} disabled={isLoading}>
|
||||||
<FileUp className="mr-2 size-4" />
|
<FileUp className="mr-2 size-4" />
|
||||||
{isLoading ? "Loading..." : "Load Alerts JSON"}
|
{isLoading ? "Loading..." : "Load Alerts JSON"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue