wulf-pulse/app/api/zabbix/hosts/[hostid]/route.ts
lorentz c518eefdb2 feat: Morning NOC Summary adaptive card for Teams
- Add MorningSummaryService with Zabbix aggregation and adaptive card builder
- Add webhook delivery system with Teams incoming webhooks
- Add admin UI at /admin/morning-summary for webhook/config management
- Add API routes: /send, /test, /webhooks, /webhooks/[id], /config, /history
- Register morning-summary cron job in SyncScheduler (Mon-Fri 6:30 AM)
- Add outages_only filter (Unavailable triggers only)
- Fix host resolution: use getTriggerEnabledHosts to exclude disabled hosts
- Fix resolved events: event.get value:1 scoped to window with r_eventid filter
- Remove emojis from fact rows and section headers in card
- Remove Open Zabbix button (duplicate of View Problems)
- Add migrations: morning_summary_config + morning_summaries tables
- Add outages_only column to morning_summary_config
2026-03-11 09:34:51 -04:00

142 lines
4.6 KiB
TypeScript

/**
* PUT /api/zabbix/hosts/[hostid] — full update of an existing Zabbix host
*
* If companyId is provided (or changed), rebuilds host groups / macros / tags
* via buildHostParams (same as creation flow). Otherwise applies fields directly.
*/
import { NextRequest, NextResponse } from 'next/server';
import { ZabbixClient } from '@/lib/services/zabbix-client';
import { postgresClient } from '@/lib/services/postgres-client';
import { ZabbixHostMacro, ZabbixHostTag } from '@/lib/types/zabbix';
import {
sanitizeHostname,
lookupIsp,
buildHostParams,
discoverIcmpTemplate,
} from '@/lib/services/zabbix-wan-utils';
interface UpdateBody {
name: string;
ip: string;
description?: string;
companyId?: number | null;
tags?: ZabbixHostTag[];
macros?: ZabbixHostMacro[];
rebuildFromClient?: boolean; // if true, fully re-run buildHostParams
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ hostid: string }> }
) {
try {
const { hostid } = await params;
const body: UpdateBody = await request.json();
const { name, ip, description, companyId, tags, macros, rebuildFromClient } = body;
if (!name || !ip) {
return NextResponse.json({ error: 'name and ip are required' }, { status: 400 });
}
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipv4Regex.test(ip)) {
return NextResponse.json({ error: 'Invalid IPv4 address' }, { status: 400 });
}
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!,
});
if (rebuildFromClient) {
// Full rebuild: re-resolve ISP, rebuild groups/macros/tags from scratch
let companyName: string | undefined;
if (companyId) {
const res = await postgresClient.query<{ company_name: string }>(
'SELECT company_name FROM companies WHERE id = $1 LIMIT 1',
[companyId]
);
companyName = res.rows[0]?.company_name;
}
const ispInfo = await lookupIsp(ip);
const globalGroupId = await zabbix.ensureHostGroup('Datto RMM Sites');
const icmpTemplateId = await discoverIcmpTemplate(zabbix);
const hostParams = await buildHostParams({
siteName: name,
wanIp: ip,
companyId: companyId ?? undefined,
companyName,
ispInfo,
source: 'manual',
icmpTemplateId,
globalGroupId,
zabbix,
});
await zabbix['rpc']('host.update', {
hostid,
host: sanitizeHostname(name),
name,
description: description ?? hostParams.description,
groups: hostParams.groups,
templates: hostParams.templates,
macros: hostParams.macros,
tags: hostParams.tags,
});
// Update IP interface separately
const existingHost = await zabbix['rpc']<Array<{ interfaces: Array<{ interfaceid: string; main: number }> }>>('host.get', {
output: ['hostid'],
hostids: [hostid],
selectInterfaces: ['interfaceid', 'main', 'type'],
});
const mainIface = existingHost[0]?.interfaces?.find((i: any) => i.main === 1 || i.main === '1');
if (mainIface) {
await zabbix['rpc']('hostinterface.update', {
interfaceid: mainIface.interfaceid,
ip,
useip: 1,
dns: '',
});
}
} else {
// Direct update — apply exactly what was sent
await zabbix['rpc']('host.update', {
hostid,
host: sanitizeHostname(name),
name,
...(description !== undefined ? { description } : {}),
...(tags !== undefined ? { tags } : {}),
...(macros !== undefined ? { macros } : {}),
});
// Update IP interface
const existingHost = await zabbix['rpc']<Array<{ interfaces: Array<{ interfaceid: string; main: number }> }>>('host.get', {
output: ['hostid'],
hostids: [hostid],
selectInterfaces: ['interfaceid', 'main', 'type'],
});
const mainIface = existingHost[0]?.interfaces?.find((i: any) => i.main === 1 || i.main === '1');
if (mainIface) {
await zabbix['rpc']('hostinterface.update', {
interfaceid: mainIface.interfaceid,
ip,
useip: 1,
dns: '',
});
}
}
return NextResponse.json({ updated: true, hostid });
} catch (error) {
console.error('[PUT /api/zabbix/hosts/[hostid]]', error);
return NextResponse.json({ error: String(error) }, { status: 500 });
}
}