mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 22:04:10 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
214e2ddde8
commit
34becc8f49
6 changed files with 397 additions and 0 deletions
77
src/collections/MonitoringAlertHistory.ts
Normal file
77
src/collections/MonitoringAlertHistory.ts
Normal file
|
|
@ -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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
120
src/collections/MonitoringAlertRules.ts
Normal file
120
src/collections/MonitoringAlertRules.ts
Normal file
|
|
@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
76
src/collections/MonitoringLogs.ts
Normal file
76
src/collections/MonitoringLogs.ts
Normal file
|
|
@ -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' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
81
src/collections/MonitoringSnapshots.ts
Normal file
81
src/collections/MonitoringSnapshots.ts
Normal file
|
|
@ -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 } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -115,6 +115,38 @@ export function createApiKeyAccess(envKey: string): Access {
|
||||||
|
|
||||||
export const consentLogsCreateAccess = createApiKeyAccess('CONSENT_LOGGING_API_KEY')
|
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
|
// Field-Level Access Functions
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,12 @@ import { EmailLogs } from './collections/EmailLogs'
|
||||||
// Audit Logs
|
// Audit Logs
|
||||||
import { AuditLogs } from './collections/AuditLogs'
|
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 filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
|
|
@ -262,6 +268,11 @@ export default buildConfig({
|
||||||
// System
|
// System
|
||||||
EmailLogs,
|
EmailLogs,
|
||||||
AuditLogs,
|
AuditLogs,
|
||||||
|
// Monitoring
|
||||||
|
MonitoringSnapshots,
|
||||||
|
MonitoringLogs,
|
||||||
|
MonitoringAlertRules,
|
||||||
|
MonitoringAlertHistory,
|
||||||
// Tenant-specific Settings (converted from Globals)
|
// Tenant-specific Settings (converted from Globals)
|
||||||
SiteSettings,
|
SiteSettings,
|
||||||
Navigations,
|
Navigations,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue