wulf-pulse/app/api/zabbix/create-host/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

126 lines
3.3 KiB
TypeScript

/**
* Manual Zabbix Host Creation API
* POST /api/zabbix/create-host
*
* Creates a Zabbix host with ICMP monitoring using the same logic as the
* RMM discovery flow, but with a user-supplied IP and site name instead
* of auto-discovered WAN IPs.
*/
import { NextRequest, NextResponse } from 'next/server';
import { ZabbixClient } from '@/lib/services/zabbix-client';
import { postgresClient } from '@/lib/services/postgres-client';
import {
lookupIsp,
buildHostParams,
discoverIcmpTemplate,
} from '@/lib/services/zabbix-wan-utils';
interface CreateHostBody {
ip: string;
siteName: string;
companyId?: number;
dryRun?: boolean;
}
export async function POST(request: NextRequest) {
try {
const body: CreateHostBody = await request.json();
const { ip, siteName, companyId, dryRun = false } = body;
// Validate required fields
if (!ip || !siteName) {
return NextResponse.json(
{ error: 'ip and siteName are required' },
{ status: 400 }
);
}
// Basic IPv4 validation
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipv4Regex.test(ip)) {
return NextResponse.json(
{ error: 'Invalid IPv4 address format' },
{ status: 400 }
);
}
if (!process.env.ZABBIX_API_URL || !process.env.ZABBIX_API_TOKEN) {
return NextResponse.json(
{ error: 'Zabbix is not configured. Add ZABBIX_API_URL and ZABBIX_API_TOKEN to your environment.' },
{ status: 500 }
);
}
// Resolve company name if companyId provided
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;
}
// ISP lookup
const ispInfo = await lookupIsp(ip);
// Dry-run: return what would happen without writing to Zabbix
if (dryRun) {
return NextResponse.json({
action: 'skipped',
dryRun: true,
siteName,
ip,
companyId: companyId ?? null,
companyName: companyName ?? null,
isp: ispInfo?.isp ?? null,
asn: ispInfo?.asn ?? null,
hostId: null,
});
}
const zabbix = new ZabbixClient({
apiUrl: process.env.ZABBIX_API_URL!,
apiToken: process.env.ZABBIX_API_TOKEN!,
});
// Ensure host group + discover ICMP template
const globalGroupId = await zabbix.ensureHostGroup('Datto RMM Sites');
const icmpTemplateId = await discoverIcmpTemplate(zabbix);
// Build host params using the shared utility
const hostParams = await buildHostParams({
siteName,
wanIp: ip,
companyId,
companyName,
ispInfo,
source: 'manual',
icmpTemplateId,
globalGroupId,
zabbix,
});
// Create or update the host
const { action, hostid } = await zabbix.upsertHost(hostParams);
return NextResponse.json({
action,
dryRun: false,
siteName,
ip,
companyId: companyId ?? null,
companyName: companyName ?? null,
isp: ispInfo?.isp ?? null,
asn: ispInfo?.asn ?? null,
hostId: hostid,
});
} catch (error) {
console.error('[create-host] error:', error);
return NextResponse.json(
{ error: String(error) },
{ status: 500 }
);
}
}