wulf-pulse/components/backup/backup-summary-cards.tsx
lorentz 5dc7a7e66b feat: Veeam VSPC backup integration - sync, compliance, UI
- Database: 7 Veeam tables + backup_type_udf column on configuration_items
- API Client: VSPC REST API v3 client with pagination, rate limiting, Bearer auth
- Sync Service: full/incremental sync for orgs, servers, repos, jobs, agent jobs, workloads
- Scheduler: veeam-incremental (30min) and veeam-full (daily 2AM) schedules
- Compliance Engine: cross-references Autotask config items vs Veeam workloads
- API Endpoints: backup-status, companies, workloads, jobs, repos, compliance, sync
- UI: Backup Status page with Overview + Contract Compliance tabs
- Navigation: added Backup Status link with HardDrive icon
- Docker: added VEEAM_VSPC_URL and VEEAM_VSPC_API_KEY env vars to compose
2026-02-11 21:04:28 -05:00

78 lines
2.3 KiB
TypeScript

'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { HardDrive, ShieldAlert, CheckCircle, XCircle, AlertTriangle } from 'lucide-react';
interface BackupSummaryCardsProps {
totalProtectedWorkloads: number;
successRate24h: number;
failedJobs24h: number;
warningJobs24h: number;
totalBackupServerJobs: number;
totalBackupAgentJobs: number;
lastSyncAt: string | null;
}
export function BackupSummaryCards({
totalProtectedWorkloads,
successRate24h,
failedJobs24h,
warningJobs24h,
totalBackupServerJobs,
totalBackupAgentJobs,
lastSyncAt,
}: BackupSummaryCardsProps) {
const cards = [
{
title: 'Protected Workloads',
value: totalProtectedWorkloads,
icon: HardDrive,
color: 'text-blue-500',
},
{
title: 'Total Jobs',
value: totalBackupServerJobs + totalBackupAgentJobs,
icon: CheckCircle,
color: 'text-green-500',
subtitle: `${totalBackupServerJobs} server / ${totalBackupAgentJobs} agent`,
},
{
title: '24h Success Rate',
value: `${successRate24h}%`,
icon: successRate24h >= 95 ? CheckCircle : AlertTriangle,
color: successRate24h >= 95 ? 'text-green-500' : successRate24h >= 80 ? 'text-yellow-500' : 'text-red-500',
},
{
title: 'Failed Jobs (24h)',
value: failedJobs24h,
icon: XCircle,
color: failedJobs24h > 0 ? 'text-red-500' : 'text-green-500',
},
{
title: 'Warnings (24h)',
value: warningJobs24h,
icon: AlertTriangle,
color: warningJobs24h > 0 ? 'text-yellow-500' : 'text-green-500',
},
];
return (
<div className="grid gap-4 md:grid-cols-3 lg:grid-cols-5">
{cards.map((card) => (
<Card key={card.title}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{card.title}</CardTitle>
<card.icon className={`h-4 w-4 ${card.color}`} />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{card.value}</div>
{card.subtitle && (
<p className="text-xs text-muted-foreground mt-1">{card.subtitle}</p>
)}
</CardContent>
</Card>
))}
</div>
);
}