- 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
127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
import { Search } from 'lucide-react';
|
|
|
|
interface ComplianceMismatch {
|
|
id: number;
|
|
company_id: number | null;
|
|
company_name: string | null;
|
|
configuration_item_id: number | null;
|
|
veeam_workload_uid: string | null;
|
|
mismatch_type: string;
|
|
backup_type_udf: string | null;
|
|
device_name: string;
|
|
contract_name: string | null;
|
|
veeam_workload_name: string | null;
|
|
}
|
|
|
|
interface ComplianceDetailTableProps {
|
|
mismatches: ComplianceMismatch[];
|
|
}
|
|
|
|
export function ComplianceDetailTable({ mismatches }: ComplianceDetailTableProps) {
|
|
const [search, setSearch] = useState('');
|
|
const [typeFilter, setTypeFilter] = useState<string>('all');
|
|
|
|
const filtered = mismatches.filter((m) => {
|
|
const matchesSearch =
|
|
(m.device_name || '').toLowerCase().includes(search.toLowerCase()) ||
|
|
(m.company_name || '').toLowerCase().includes(search.toLowerCase());
|
|
const matchesType =
|
|
typeFilter === 'all' || m.mismatch_type === typeFilter;
|
|
return matchesSearch && matchesType;
|
|
});
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-4">
|
|
<div className="relative flex-1 max-w-sm">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
<Input
|
|
placeholder="Search devices or companies..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
className="pl-9"
|
|
/>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
{[
|
|
{ key: 'all', label: 'All' },
|
|
{ key: 'contracted_not_backed_up', label: 'Missing Backup' },
|
|
{ key: 'backed_up_not_contracted', label: 'No Contract' },
|
|
].map((f) => (
|
|
<Button
|
|
key={f.key}
|
|
variant={typeFilter === f.key ? 'default' : 'outline'}
|
|
size="sm"
|
|
onClick={() => setTypeFilter(f.key)}
|
|
>
|
|
{f.label}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-md border">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Company</TableHead>
|
|
<TableHead>Device</TableHead>
|
|
<TableHead>Issue</TableHead>
|
|
<TableHead>Backup UDF</TableHead>
|
|
<TableHead>Contract</TableHead>
|
|
<TableHead>Veeam Workload</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{filtered.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={6} className="text-center text-muted-foreground py-8">
|
|
{mismatches.length === 0
|
|
? 'No compliance issues found — all clear!'
|
|
: 'No matching results'}
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
filtered.map((m) => (
|
|
<TableRow key={m.id}>
|
|
<TableCell className="font-medium">{m.company_name || '-'}</TableCell>
|
|
<TableCell>{m.device_name}</TableCell>
|
|
<TableCell>
|
|
<Badge
|
|
className={
|
|
m.mismatch_type === 'contracted_not_backed_up'
|
|
? 'bg-red-500/10 text-red-500'
|
|
: 'bg-yellow-500/10 text-yellow-500'
|
|
}
|
|
>
|
|
{m.mismatch_type === 'contracted_not_backed_up'
|
|
? 'Missing Backup'
|
|
: 'No Contract'}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-sm">{m.backup_type_udf || '-'}</TableCell>
|
|
<TableCell className="text-sm">{m.contract_name || '-'}</TableCell>
|
|
<TableCell className="text-sm">{m.veeam_workload_name || '-'}</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|