wulf-pulse/app/api/zabbix/sync-hosts/route.ts

123 lines
4.6 KiB
TypeScript
Raw Normal View History

/**
* POST /api/zabbix/sync-hosts
* Pulls all hosts from the live Zabbix API and caches them in zabbix_wan_hosts.
* Also fetches current open problems and stamps last_problem_at on affected hosts.
*/
import { NextResponse } from 'next/server';
import { ZabbixClient } from '@/lib/services/zabbix-client';
import { postgresClient } from '@/lib/services/postgres-client';
export const maxDuration = 120;
export async function POST() {
if (!process.env.ZABBIX_API_URL || !process.env.ZABBIX_API_TOKEN) {
return NextResponse.json({ error: 'Zabbix not configured' }, { status: 500 });
}
const zabbix = new ZabbixClient({
apiUrl: process.env.ZABBIX_API_URL,
apiToken: process.env.ZABBIX_API_TOKEN,
});
try {
const hosts = await zabbix.getHosts();
// Fetch open problems to stamp last_problem_at
const problems = await zabbix.getOpenProblems(2);
const problemByHostId = new Map<string, { name: string; clock: string }>();
for (const p of problems) {
for (const h of (p.hosts ?? [])) {
if (!problemByHostId.has(h.hostid) || Number(p.clock) > Number(problemByHostId.get(h.hostid)!.clock)) {
problemByHostId.set(h.hostid, { name: p.name, clock: p.clock });
}
}
}
let upserted = 0;
let skipped = 0;
for (const host of hosts) {
// Only process ICMP/WAN hosts — they have an agent interface with an IP
const iface = (host.interfaces ?? []).find(i => Number(i.useip) === 1 && i.ip && i.ip !== '127.0.0.1');
if (!iface) { skipped++; continue; }
const getMacro = (name: string) =>
(host.macros ?? []).find(m => m.macro === name)?.value ?? null;
const getTag = (name: string) =>
(host.tags ?? []).find(t => t.tag === name)?.value ?? null;
const rmmSiteUid = getMacro('{$RMM_SITE_UID}');
const autotaskCompanyId = getMacro('{$AUTOTASK_COMPANY_ID}');
const autotaskCompanyName = getMacro('{$AUTOTASK_COMPANY_NAME}');
const ispName = getMacro('{$ISP_NAME}');
const asn = getMacro('{$ASN}');
const isMultiWan = getTag('multi-wan') === 'true';
const source = getTag('source') ?? 'datto-rmm';
const problem = problemByHostId.get(host.hostid);
await postgresClient.query(
`INSERT INTO zabbix_wan_hosts (
hostid, host_name, display_name, wan_ip, status,
rmm_site_uid, autotask_company_id, autotask_company_name,
isp_name, asn, is_multi_wan, source, tags,
last_problem_at, last_problem_name,
last_synced_at, updated_at
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,NOW(),NOW())
ON CONFLICT (hostid) DO UPDATE SET
host_name = EXCLUDED.host_name,
display_name = EXCLUDED.display_name,
wan_ip = EXCLUDED.wan_ip,
status = EXCLUDED.status,
rmm_site_uid = EXCLUDED.rmm_site_uid,
autotask_company_id = EXCLUDED.autotask_company_id,
autotask_company_name = EXCLUDED.autotask_company_name,
isp_name = EXCLUDED.isp_name,
asn = EXCLUDED.asn,
is_multi_wan = EXCLUDED.is_multi_wan,
source = EXCLUDED.source,
tags = EXCLUDED.tags,
last_problem_at = EXCLUDED.last_problem_at,
last_problem_name = EXCLUDED.last_problem_name,
last_synced_at = NOW(),
updated_at = NOW()`,
[
host.hostid,
host.host,
host.name ?? host.host,
iface.ip,
Number(host.status ?? 0),
rmmSiteUid,
autotaskCompanyId ? Number(autotaskCompanyId) : null,
autotaskCompanyName,
ispName,
asn,
isMultiWan,
source,
JSON.stringify(host.tags ?? []),
problem ? new Date(Number(problem.clock) * 1000).toISOString() : null,
problem?.name ?? null,
]
);
upserted++;
}
// Remove hosts no longer in Zabbix
const liveIds = hosts.map(h => h.hostid);
if (liveIds.length > 0) {
const deleted = await postgresClient.query(
'DELETE FROM zabbix_wan_hosts WHERE hostid <> ALL($1) RETURNING hostid',
[liveIds]
);
return NextResponse.json({ upserted, skipped, removed: deleted.rowCount });
}
return NextResponse.json({ upserted, skipped, removed: 0 });
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return NextResponse.json({ error: msg }, { status: 500 });
}
}