wulf-pulse/app/api/veeam/compliance/route.ts
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

76 lines
2.4 KiB
TypeScript

import { NextResponse } from 'next/server';
import postgresClient from '@/lib/services/postgres-client';
export async function GET() {
try {
// Get compliance summary
const summaryResult = await postgresClient.query(`
SELECT
mismatch_type,
COUNT(*) as count
FROM veeam_compliance_results
GROUP BY mismatch_type
`);
let contractedNotBackedUp = 0;
let backedUpNotContracted = 0;
for (const row of summaryResult.rows) {
if (row.mismatch_type === 'contracted_not_backed_up') {
contractedNotBackedUp = parseInt(row.count);
} else if (row.mismatch_type === 'backed_up_not_contracted') {
backedUpNotContracted = parseInt(row.count);
}
}
// Get total contracted devices (config items with backup UDF + company has active contract)
const contractedResult = await postgresClient.query(`
SELECT COUNT(DISTINCT ci.id) as count
FROM configuration_items ci
JOIN contracts ct ON ct.company_id = ci.company_id AND ct.status = 1
WHERE ci.backup_type_udf IS NOT NULL
AND ci.backup_type_udf != ''
AND ci.is_active = true
AND ci.is_deleted = false
`);
const totalContracted = parseInt(contractedResult.rows[0]?.count || '0');
const matched = totalContracted - contractedNotBackedUp;
// Get last computed time
const lastComputed = await postgresClient.query(
'SELECT MAX(computed_at) as computed_at FROM veeam_compliance_results'
);
// Get mismatch details
const mismatches = await postgresClient.query(`
SELECT
cr.*,
c.company_name
FROM veeam_compliance_results cr
LEFT JOIN companies c ON c.id = cr.company_id
ORDER BY cr.mismatch_type, c.company_name, cr.device_name
`);
return NextResponse.json({
summary: {
totalContractedDevices: totalContracted,
matchedDevices: Math.max(0, matched),
contractedNotBackedUp,
backedUpNotContracted,
computedAt: lastComputed.rows[0]?.computed_at || null,
},
mismatches: mismatches.rows,
});
} catch (error) {
console.error('[VEEAM-API] compliance error:', error);
return NextResponse.json({
summary: {
totalContractedDevices: 0,
matchedDevices: 0,
contractedNotBackedUp: 0,
backedUpNotContracted: 0,
computedAt: null,
},
mismatches: [],
});
}
}