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
This commit is contained in:
parent
19605f82aa
commit
c518eefdb2
61 changed files with 11236 additions and 237 deletions
|
|
@ -6,6 +6,8 @@ import {
|
|||
ZabbixHostCreateParams,
|
||||
ZabbixHostUpdateParams,
|
||||
ZabbixRpcResponse,
|
||||
ZabbixProblem,
|
||||
ZabbixEvent,
|
||||
} from '@/lib/types/zabbix';
|
||||
|
||||
export type { ZabbixHostTag } from '@/lib/types/zabbix';
|
||||
|
|
@ -116,6 +118,119 @@ export class ZabbixClient {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all hosts with full detail: interfaces, tags, macros, groups.
|
||||
*/
|
||||
async getHosts(): Promise<ZabbixHost[]> {
|
||||
return this.rpc<ZabbixHost[]>('host.get', {
|
||||
output: 'extend',
|
||||
selectInterfaces: 'extend',
|
||||
selectTags: 'extend',
|
||||
selectMacros: 'extend',
|
||||
selectGroups: 'extend',
|
||||
selectParentTemplates: ['templateid', 'host', 'name'],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete one or more hosts by their hostids.
|
||||
*/
|
||||
async deleteHosts(hostids: string[]): Promise<void> {
|
||||
await this.rpc<{ hostids: string[] }>('host.delete', hostids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get open problems with hosts inline.
|
||||
* Only returns problems whose trigger is linked to at least one enabled host.
|
||||
* severities: 2=Warning,3=Average,4=High,5=Disaster
|
||||
*/
|
||||
async getOpenProblems(minSeverity = 2): Promise<ZabbixProblem[]> {
|
||||
return this.rpc<ZabbixProblem[]>('problem.get', {
|
||||
output: 'extend',
|
||||
selectAcknowledges: 'extend',
|
||||
selectSuppressionData: 'extend',
|
||||
severities: [2, 3, 4, 5].filter(s => s >= minSeverity),
|
||||
recent: true,
|
||||
sortfield: 'eventid',
|
||||
sortorder: 'DESC',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get problems that were resolved within the given window.
|
||||
* Uses problem.get with time_from/time_till (filters on problem clock) and
|
||||
* r_eventid IS SET (meaning the problem has a recovery event = resolved).
|
||||
* Returns problems only — r_clock is the resolution timestamp.
|
||||
*/
|
||||
async getResolvedEvents(from: Date, to: Date): Promise<ZabbixEvent[]> {
|
||||
const fromSec = Math.floor(from.getTime() / 1000);
|
||||
const toSec = Math.floor(to.getTime() / 1000);
|
||||
|
||||
// event.get with value:1 returns PROBLEM trigger events.
|
||||
// time_from/time_till filter on event creation (problem start) time.
|
||||
// We then keep only those that have an r_clock (= resolved) within the window.
|
||||
const events = await this.rpc<ZabbixEvent[]>('event.get', {
|
||||
output: 'extend',
|
||||
source: 0,
|
||||
object: 0,
|
||||
value: 1,
|
||||
time_from: fromSec,
|
||||
time_till: toSec,
|
||||
severities: [2, 3, 4, 5],
|
||||
sortfield: 'eventid',
|
||||
sortorder: 'DESC',
|
||||
limit: 500,
|
||||
});
|
||||
|
||||
return events.filter(e => e.r_eventid && e.r_eventid !== '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch group names for a specific list of hostids.
|
||||
* Returns a Map<hostid, groupName[]> for client name resolution.
|
||||
*/
|
||||
async getHostGroupMap(hostids: string[]): Promise<Map<string, string[]>> {
|
||||
if (hostids.length === 0) return new Map();
|
||||
const hosts = await this.rpc<Array<{
|
||||
hostid: string;
|
||||
groups: Array<{ groupid: string; name: string }>;
|
||||
}>>('host.get', {
|
||||
output: ['hostid'],
|
||||
hostids,
|
||||
selectGroups: ['groupid', 'name'],
|
||||
});
|
||||
|
||||
const map = new Map<string, string[]>();
|
||||
for (const h of hosts) {
|
||||
map.set(h.hostid, (h.groups ?? []).map((g: { name: string }) => g.name));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a list of triggerids, return a map of triggerid → { hostid, hostName }
|
||||
* filtered to only enabled hosts (status '0').
|
||||
* Triggerids with no enabled host are omitted from the map.
|
||||
*/
|
||||
async getTriggerEnabledHosts(triggerids: string[]): Promise<Map<string, { hostid: string; hostName: string }>> {
|
||||
if (triggerids.length === 0) return new Map();
|
||||
const triggers = await this.rpc<Array<{
|
||||
triggerid: string;
|
||||
hosts: Array<{ hostid: string; name: string; status: string }>;
|
||||
}>>('trigger.get', {
|
||||
output: ['triggerid'],
|
||||
triggerids,
|
||||
selectHosts: ['hostid', 'name', 'status'],
|
||||
});
|
||||
|
||||
const map = new Map<string, { hostid: string; hostName: string }>();
|
||||
for (const t of triggers) {
|
||||
const enabled = (t.hosts ?? []).find(h => h.status === '0');
|
||||
if (enabled) map.set(t.triggerid, { hostid: enabled.hostid, hostName: enabled.name });
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a Zabbix host. Idempotent — looks up by host name first.
|
||||
* Returns the hostid and whether the host was created or updated.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue