From 34becc8f49402e827bc28c4e7c2f3bd760fbf7d0 Mon Sep 17 00:00:00 2001 From: Martin Porwoll Date: Sun, 15 Feb 2026 00:14:53 +0000 Subject: [PATCH] feat(monitoring): add 4 monitoring collections (Snapshots, Logs, AlertRules, AlertHistory) Add monitoring access controls to centralized access module and create four new system-wide collections for the monitoring dashboard: - MonitoringSnapshots: historical system metrics for trend charts - MonitoringLogs: structured logs for business events (WORM) - MonitoringAlertRules: configurable alert rule definitions - MonitoringAlertHistory: alert log with acknowledge support Collections are registered in payload.config.ts but intentionally excluded from multi-tenant plugin since they are system-wide. Co-Authored-By: Claude Opus 4.6 --- src/collections/MonitoringAlertHistory.ts | 77 ++++++++++++++ src/collections/MonitoringAlertRules.ts | 120 ++++++++++++++++++++++ src/collections/MonitoringLogs.ts | 76 ++++++++++++++ src/collections/MonitoringSnapshots.ts | 81 +++++++++++++++ src/lib/access/index.ts | 32 ++++++ src/payload.config.ts | 11 ++ 6 files changed, 397 insertions(+) create mode 100644 src/collections/MonitoringAlertHistory.ts create mode 100644 src/collections/MonitoringAlertRules.ts create mode 100644 src/collections/MonitoringLogs.ts create mode 100644 src/collections/MonitoringSnapshots.ts diff --git a/src/collections/MonitoringAlertHistory.ts b/src/collections/MonitoringAlertHistory.ts new file mode 100644 index 0000000..5a359cc --- /dev/null +++ b/src/collections/MonitoringAlertHistory.ts @@ -0,0 +1,77 @@ +import type { CollectionConfig } from 'payload' +import { monitoringAlertHistoryAccess } from '../lib/access' + +export const MonitoringAlertHistory: CollectionConfig = { + slug: 'monitoring-alert-history', + admin: { + useAsTitle: 'message', + group: 'Monitoring', + description: 'Alert-Log (WORM - Write Once)', + defaultColumns: ['severity', 'metric', 'message', 'createdAt', 'acknowledgedBy'], + }, + access: monitoringAlertHistoryAccess, + fields: [ + { + name: 'rule', + type: 'relationship', + relationTo: 'monitoring-alert-rules', + admin: { readOnly: true }, + }, + { + name: 'metric', + type: 'text', + required: true, + admin: { readOnly: true }, + }, + { + name: 'value', + type: 'number', + required: true, + admin: { readOnly: true }, + }, + { + name: 'threshold', + type: 'number', + required: true, + admin: { readOnly: true }, + }, + { + name: 'severity', + type: 'select', + required: true, + options: [ + { label: 'Warnung', value: 'warning' }, + { label: 'Fehler', value: 'error' }, + { label: 'Kritisch', value: 'critical' }, + ], + admin: { readOnly: true }, + }, + { + name: 'message', + type: 'text', + required: true, + admin: { readOnly: true }, + }, + { + name: 'channelsSent', + type: 'select', + hasMany: true, + options: [ + { label: 'E-Mail', value: 'email' }, + { label: 'Slack', value: 'slack' }, + { label: 'Discord', value: 'discord' }, + ], + admin: { readOnly: true }, + }, + { + name: 'resolvedAt', + type: 'date', + admin: { date: { pickerAppearance: 'dayAndTime' } }, + }, + { + name: 'acknowledgedBy', + type: 'relationship', + relationTo: 'users', + }, + ], +} diff --git a/src/collections/MonitoringAlertRules.ts b/src/collections/MonitoringAlertRules.ts new file mode 100644 index 0000000..4b3e467 --- /dev/null +++ b/src/collections/MonitoringAlertRules.ts @@ -0,0 +1,120 @@ +import type { CollectionConfig } from 'payload' +import { monitoringAlertRulesAccess } from '../lib/access' + +export const MonitoringAlertRules: CollectionConfig = { + slug: 'monitoring-alert-rules', + admin: { + useAsTitle: 'name', + group: 'Monitoring', + description: 'Konfigurierbare Alert-Regeln', + defaultColumns: ['name', 'metric', 'condition', 'threshold', 'severity', 'enabled'], + }, + access: monitoringAlertRulesAccess, + fields: [ + { + name: 'name', + type: 'text', + required: true, + label: 'Regelname', + }, + { + name: 'metric', + type: 'text', + required: true, + label: 'Metrik-Pfad', + admin: { + description: 'z.B. system.cpuUsagePercent, services.redis.memoryUsedMB', + }, + }, + { + name: 'condition', + type: 'select', + required: true, + options: [ + { label: 'Größer als (>)', value: 'gt' }, + { label: 'Kleiner als (<)', value: 'lt' }, + { label: 'Gleich (=)', value: 'eq' }, + { label: 'Größer gleich (>=)', value: 'gte' }, + { label: 'Kleiner gleich (<=)', value: 'lte' }, + ], + }, + { + name: 'threshold', + type: 'number', + required: true, + label: 'Schwellenwert', + }, + { + name: 'severity', + type: 'select', + required: true, + options: [ + { label: 'Warnung', value: 'warning' }, + { label: 'Fehler', value: 'error' }, + { label: 'Kritisch', value: 'critical' }, + ], + }, + { + name: 'channels', + type: 'select', + hasMany: true, + required: true, + options: [ + { label: 'E-Mail', value: 'email' }, + { label: 'Slack', value: 'slack' }, + { label: 'Discord', value: 'discord' }, + ], + label: 'Benachrichtigungskanäle', + }, + { + name: 'recipients', + type: 'group', + label: 'Empfänger', + fields: [ + { + name: 'emails', + type: 'array', + label: 'E-Mail-Empfänger', + fields: [ + { name: 'email', type: 'email', required: true }, + ], + }, + { + name: 'slackWebhook', + type: 'text', + label: 'Slack Webhook URL', + }, + { + name: 'discordWebhook', + type: 'text', + label: 'Discord Webhook URL', + }, + ], + }, + { + name: 'cooldownMinutes', + type: 'number', + defaultValue: 15, + min: 1, + label: 'Cooldown (Minuten)', + admin: { + description: 'Minimaler Abstand zwischen gleichen Alerts', + }, + }, + { + name: 'enabled', + type: 'checkbox', + defaultValue: true, + label: 'Aktiv', + }, + { + name: 'tenant', + type: 'relationship', + relationTo: 'tenants', + label: 'Tenant', + admin: { + description: 'Optional: Tenant-spezifische Regel', + }, + }, + ], +} diff --git a/src/collections/MonitoringLogs.ts b/src/collections/MonitoringLogs.ts new file mode 100644 index 0000000..da26893 --- /dev/null +++ b/src/collections/MonitoringLogs.ts @@ -0,0 +1,76 @@ +import type { CollectionConfig } from 'payload' +import { monitoringLogsAccess } from '../lib/access' + +export const MonitoringLogs: CollectionConfig = { + slug: 'monitoring-logs', + admin: { + useAsTitle: 'message', + group: 'Monitoring', + description: 'Structured Logs für Business-Events', + defaultColumns: ['level', 'source', 'message', 'createdAt'], + }, + access: monitoringLogsAccess, + fields: [ + { + name: 'level', + type: 'select', + required: true, + options: [ + { label: 'Debug', value: 'debug' }, + { label: 'Info', value: 'info' }, + { label: 'Warn', value: 'warn' }, + { label: 'Error', value: 'error' }, + { label: 'Fatal', value: 'fatal' }, + ], + admin: { readOnly: true }, + }, + { + name: 'source', + type: 'select', + required: true, + options: [ + { label: 'Payload', value: 'payload' }, + { label: 'Queue Worker', value: 'queue-worker' }, + { label: 'Cron', value: 'cron' }, + { label: 'Email', value: 'email' }, + { label: 'OAuth', value: 'oauth' }, + { label: 'Sync', value: 'sync' }, + ], + admin: { readOnly: true }, + }, + { + name: 'message', + type: 'text', + required: true, + admin: { readOnly: true }, + }, + { + name: 'context', + type: 'json', + admin: { readOnly: true, description: 'Strukturierte Metadaten' }, + }, + { + name: 'requestId', + type: 'text', + admin: { readOnly: true, description: 'Korrelations-ID' }, + }, + { + name: 'userId', + type: 'relationship', + relationTo: 'users', + admin: { readOnly: true }, + }, + { + name: 'tenant', + type: 'relationship', + relationTo: 'tenants', + admin: { readOnly: true }, + }, + { + name: 'duration', + type: 'number', + min: 0, + admin: { readOnly: true, description: 'Dauer in ms' }, + }, + ], +} diff --git a/src/collections/MonitoringSnapshots.ts b/src/collections/MonitoringSnapshots.ts new file mode 100644 index 0000000..5c84a2b --- /dev/null +++ b/src/collections/MonitoringSnapshots.ts @@ -0,0 +1,81 @@ +import type { CollectionConfig } from 'payload' +import { monitoringSnapshotsAccess } from '../lib/access' + +export const MonitoringSnapshots: CollectionConfig = { + slug: 'monitoring-snapshots', + admin: { + useAsTitle: 'timestamp', + group: 'Monitoring', + description: 'Historische System-Metriken für Trend-Charts', + defaultColumns: ['timestamp', 'system_cpuUsagePercent', 'system_memoryUsagePercent', 'createdAt'], + }, + access: monitoringSnapshotsAccess, + fields: [ + { + name: 'timestamp', + type: 'date', + required: true, + index: true, + admin: { + readOnly: true, + date: { pickerAppearance: 'dayAndTime' }, + }, + }, + { + name: 'system', + type: 'group', + admin: { description: 'System-Ressourcen (CPU, RAM, Disk)' }, + fields: [ + { name: 'cpuUsagePercent', type: 'number', admin: { readOnly: true } }, + { name: 'memoryUsedMB', type: 'number', admin: { readOnly: true } }, + { name: 'memoryTotalMB', type: 'number', admin: { readOnly: true } }, + { name: 'memoryUsagePercent', type: 'number', admin: { readOnly: true } }, + { name: 'diskUsedGB', type: 'number', admin: { readOnly: true } }, + { name: 'diskTotalGB', type: 'number', admin: { readOnly: true } }, + { name: 'diskUsagePercent', type: 'number', admin: { readOnly: true } }, + { name: 'loadAvg1', type: 'number', admin: { readOnly: true } }, + { name: 'loadAvg5', type: 'number', admin: { readOnly: true } }, + { + name: 'uptime', + type: 'number', + admin: { readOnly: true, description: 'Uptime in Sekunden' }, + }, + ], + }, + { + name: 'services', + type: 'group', + admin: { description: 'Service-Status (PM2-Prozesse, Datenbank, Cache)' }, + fields: [ + { name: 'payload', type: 'json', admin: { readOnly: true } }, + { name: 'queueWorker', type: 'json', admin: { readOnly: true } }, + { name: 'postgresql', type: 'json', admin: { readOnly: true } }, + { name: 'pgbouncer', type: 'json', admin: { readOnly: true } }, + { name: 'redis', type: 'json', admin: { readOnly: true } }, + ], + }, + { + name: 'external', + type: 'group', + admin: { description: 'Externe Services (SMTP, OAuth, Cron)' }, + fields: [ + { name: 'smtp', type: 'json', admin: { readOnly: true } }, + { name: 'metaOAuth', type: 'json', admin: { readOnly: true } }, + { name: 'youtubeOAuth', type: 'json', admin: { readOnly: true } }, + { name: 'cronJobs', type: 'json', admin: { readOnly: true } }, + ], + }, + { + name: 'performance', + type: 'group', + admin: { description: 'Performance-Metriken' }, + fields: [ + { name: 'avgResponseTimeMs', type: 'number', admin: { readOnly: true } }, + { name: 'p95ResponseTimeMs', type: 'number', admin: { readOnly: true } }, + { name: 'p99ResponseTimeMs', type: 'number', admin: { readOnly: true } }, + { name: 'errorRate', type: 'number', admin: { readOnly: true } }, + { name: 'requestsPerMinute', type: 'number', admin: { readOnly: true } }, + ], + }, + ], +} diff --git a/src/lib/access/index.ts b/src/lib/access/index.ts index 07d515e..be4ae71 100644 --- a/src/lib/access/index.ts +++ b/src/lib/access/index.ts @@ -115,6 +115,38 @@ export function createApiKeyAccess(envKey: string): Access { export const consentLogsCreateAccess = createApiKeyAccess('CONSENT_LOGGING_API_KEY') +// ============================================================================ +// Monitoring Access Functions +// ============================================================================ + +export const monitoringSnapshotsAccess = { + read: superAdminOnly, + create: superAdminOnly, + update: denyAll, + delete: superAdminOnly, +} + +export const monitoringLogsAccess = { + read: superAdminOnly, + create: superAdminOnly, + update: denyAll, + delete: superAdminOnly, +} + +export const monitoringAlertRulesAccess = { + read: superAdminOnly, + create: superAdminOnly, + update: superAdminOnly, + delete: superAdminOnly, +} + +export const monitoringAlertHistoryAccess = { + read: superAdminOnly, + create: superAdminOnly, + update: superAdminOnly, + delete: superAdminOnly, +} + // ============================================================================ // Field-Level Access Functions // ============================================================================ diff --git a/src/payload.config.ts b/src/payload.config.ts index 2750768..8f43277 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -120,6 +120,12 @@ import { EmailLogs } from './collections/EmailLogs' // Audit Logs import { AuditLogs } from './collections/AuditLogs' +// Monitoring Collections +import { MonitoringSnapshots } from './collections/MonitoringSnapshots' +import { MonitoringLogs } from './collections/MonitoringLogs' +import { MonitoringAlertRules } from './collections/MonitoringAlertRules' +import { MonitoringAlertHistory } from './collections/MonitoringAlertHistory' + const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -262,6 +268,11 @@ export default buildConfig({ // System EmailLogs, AuditLogs, + // Monitoring + MonitoringSnapshots, + MonitoringLogs, + MonitoringAlertRules, + MonitoringAlertHistory, // Tenant-specific Settings (converted from Globals) SiteSettings, Navigations,