182 lines
6.1 KiB
TypeScript
182 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import { usePoll } from "@/lib/use-poll";
|
|
import type { EmailStats } from "@/lib/types";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { StatusBadge } from "@/components/status-badge";
|
|
|
|
interface BackupResult {
|
|
host: string;
|
|
status: string;
|
|
last_run: string;
|
|
size?: string;
|
|
}
|
|
|
|
interface DriftResult {
|
|
stack: string;
|
|
drifted: boolean;
|
|
details?: string;
|
|
}
|
|
|
|
interface StackRestart {
|
|
stack: string;
|
|
status: string;
|
|
timestamp: string;
|
|
}
|
|
|
|
export default function AutomationsPage() {
|
|
const { data: emails } = usePoll<EmailStats>(
|
|
"/api/automations/email",
|
|
60000
|
|
);
|
|
const { data: backups } = usePoll<Record<string, unknown>>(
|
|
"/api/automations/backup",
|
|
120000
|
|
);
|
|
const { data: drift } = usePoll<Record<string, unknown>>(
|
|
"/api/automations/drift",
|
|
120000
|
|
);
|
|
const { data: restartsData } = usePoll<{ entries: StackRestart[] }>(
|
|
"/api/automations/restarts",
|
|
60000
|
|
);
|
|
const restarts = restartsData?.entries ?? [];
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<h1 className="text-lg font-semibold">Automations</h1>
|
|
|
|
{/* Email Organizers */}
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
Email Organizers
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{!emails ? (
|
|
<p className="text-xs text-muted-foreground">Loading...</p>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{emails.accounts.map((acct: Record<string, unknown>) => {
|
|
const name = String(acct.account ?? acct.name ?? "?");
|
|
const today = Number(acct.today ?? acct.today_total ?? 0);
|
|
const cats = (acct.categories ?? acct.today_categories ?? {}) as Record<string, number>;
|
|
return (
|
|
<div key={name} className="space-y-2 rounded-lg px-3 py-2 hover:bg-white/[0.02] transition-colors">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm font-medium text-foreground">{name}</span>
|
|
<span className="text-xs text-muted-foreground/70">{today} today</span>
|
|
</div>
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{Object.entries(cats).map(([cat, count]) => (
|
|
<Badge key={cat} variant="secondary" className="text-[10px] bg-white/[0.04] border border-white/[0.06]">
|
|
{cat}: {count}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Row 2: Backups, Drift, Restarts */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
Backup Status
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{!backups ? (
|
|
<p className="text-xs text-muted-foreground">Loading...</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
<StatusBadge
|
|
color={String(backups.status) === "ok" ? "green" : "red"}
|
|
label={String(backups.status ?? "unknown")}
|
|
/>
|
|
{backups.has_errors ? (
|
|
<p className="text-xs text-red-400">Errors detected in backup</p>
|
|
) : null}
|
|
<p className="text-[10px] text-muted-foreground/60">
|
|
{String(backups.entries ?? 0)} log entries today
|
|
</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
Config Drift
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{!drift ? (
|
|
<p className="text-xs text-muted-foreground">Loading...</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
<StatusBadge
|
|
color={String(drift.status) === "clean" || String(drift.status) === "no_log" ? "green" : "amber"}
|
|
label={String(drift.status ?? "unknown")}
|
|
/>
|
|
<p className="text-[10px] text-muted-foreground/60">
|
|
{String(drift.last_result ?? "No scan results yet")}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
Stack Restarts
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{!restarts ? (
|
|
<p className="text-xs text-muted-foreground">Loading...</p>
|
|
) : restarts.length === 0 ? (
|
|
<p className="text-xs text-muted-foreground/60">
|
|
No recent restarts
|
|
</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{restarts.map((r, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center justify-between text-xs"
|
|
>
|
|
<span className="text-foreground">{r.stack}</span>
|
|
<div className="flex items-center gap-2">
|
|
<StatusBadge
|
|
color={r.status === "success" ? "green" : "red"}
|
|
label={r.status}
|
|
/>
|
|
<span className="text-muted-foreground/70">
|
|
{new Date(r.timestamp).toLocaleTimeString("en-US", {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
})}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|