/** * POST /api/zabbix/sync-events * Polls Zabbix for WAN problem events (past N days) and caches them in zabbix_events. * Joins against local zabbix_wan_hosts to enrich with company/site context. * Body: { days?: number } — defaults to 30 */ import { NextRequest, NextResponse } from 'next/server'; import { ZabbixClient } from '@/lib/services/zabbix-client'; import { postgresClient } from '@/lib/services/postgres-client'; export const maxDuration = 300; export async function POST(request: NextRequest) { if (!process.env.ZABBIX_API_URL || !process.env.ZABBIX_API_TOKEN) { return NextResponse.json({ error: 'Zabbix not configured' }, { status: 500 }); } const body = await request.json().catch(() => ({})); const days: number = Number(body.days ?? 30); const zabbix = new ZabbixClient({ apiUrl: process.env.ZABBIX_API_URL, apiToken: process.env.ZABBIX_API_TOKEN, }); try { // Load local WAN host cache for enrichment (hostid → context) const hostRows = await postgresClient.query<{ hostid: string; display_name: string; wan_ip: string; autotask_company_id: number | null; autotask_company_name: string | null; rmm_site_uid: string | null; isp_name: string | null; }>('SELECT hostid, display_name, wan_ip, autotask_company_id, autotask_company_name, rmm_site_uid, isp_name FROM zabbix_wan_hosts'); const hostMap = new Map(hostRows.rows.map(r => [r.hostid, r])); if (hostMap.size === 0) { return NextResponse.json({ error: 'No Zabbix hosts cached — run Sync Hosts first' }, { status: 400 }); } const from = new Date(Date.now() - days * 86400 * 1000); const to = new Date(); // Pull WAN-host-only events by scoping to our known hostids const hostIds = [...hostMap.keys()]; const events = await zabbix.getEvents({ hostIds, from, to, limit: 10000 }); let upserted = 0; let noHost = 0; for (const ev of events) { const hostid = ev.hosts?.[0]?.hostid ?? null; const host = hostid ? hostMap.get(hostid) ?? null : null; if (!host) { noHost++; continue; } const clockTs = new Date(Number(ev.clock) * 1000); const rClockTs = ev.r_clock && ev.r_clock !== '0' ? new Date(Number(ev.r_clock) * 1000) : null; const duration = rClockTs ? Math.round((rClockTs.getTime() - clockTs.getTime()) / 1000) : null; await postgresClient.query( `INSERT INTO zabbix_events ( eventid, objectid, name, severity, clock, r_eventid, r_clock, duration_seconds, hostid, host_name, wan_ip, autotask_company_id, autotask_company_name, rmm_site_uid, isp_name, last_synced_at ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,NOW()) ON CONFLICT (eventid) DO UPDATE SET r_eventid = EXCLUDED.r_eventid, r_clock = EXCLUDED.r_clock, duration_seconds = EXCLUDED.duration_seconds, host_name = EXCLUDED.host_name, wan_ip = EXCLUDED.wan_ip, autotask_company_id = EXCLUDED.autotask_company_id, autotask_company_name = EXCLUDED.autotask_company_name, rmm_site_uid = EXCLUDED.rmm_site_uid, isp_name = EXCLUDED.isp_name, last_synced_at = NOW()`, [ ev.eventid, ev.objectid, ev.name, Number(ev.severity), clockTs, ev.r_eventid && ev.r_eventid !== '0' ? ev.r_eventid : null, rClockTs, duration, hostid, host.display_name, host.wan_ip, host.autotask_company_id, host.autotask_company_name, host.rmm_site_uid, host.isp_name, ] ); upserted++; } const stats = await postgresClient.query<{ total: string; open: string; oldest: string; newest: string }>(` SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE r_eventid IS NULL) AS open, MIN(clock) AS oldest, MAX(clock) AS newest FROM zabbix_events `); return NextResponse.json({ upserted, no_host: noHost, days, ...stats.rows[0] }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); return NextResponse.json({ error: msg }, { status: 500 }); } }