mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-12 07:18:49 +00:00
Extract alerts configuration model owner
This commit is contained in:
@@ -243,6 +243,9 @@ surface is now a shell that composes the destinations, schedule, and thresholds
|
||||
tabs. The canonical alert-policy runtime owner is now
|
||||
`frontend-modern/src/features/alerts/useAlertsConfigurationState.ts` for
|
||||
config transport, defaults, and save/load orchestration,
|
||||
`frontend-modern/src/features/alerts/alertsConfigurationModel.ts` for backend
|
||||
config normalization, factory defaults, docker-gap validation, and save-payload
|
||||
serialization,
|
||||
`frontend-modern/src/features/alerts/useAlertOverridesState.ts` for raw
|
||||
override normalization plus resource-backed override projection, and
|
||||
`frontend-modern/src/features/alerts/useAlertDestinationsState.ts` for
|
||||
@@ -251,9 +254,10 @@ notification destination reload and persistence.
|
||||
webhook load/mutate/test flow plus destination test actions, while
|
||||
`frontend-modern/src/features/alerts/tabs/DestinationsTab.tsx` stays the
|
||||
destinations render shell. Future config cleanup should extend the config
|
||||
transport hook, the override-projection hook, or the destinations runtime hook
|
||||
based on which subsystem actually owns the behavior instead of letting the
|
||||
broader configuration hook absorb all three concerns again.
|
||||
transport hook, the config model, the override-projection hook, or the
|
||||
destinations runtime hook based on which subsystem actually owns the behavior
|
||||
instead of letting the broader configuration hook absorb all three concerns
|
||||
again.
|
||||
|
||||
Alert filter metadata and grouped header consumers must also preserve the
|
||||
canonical `agent` and `node` header boundary when reusing shared filter
|
||||
|
||||
@@ -444,16 +444,19 @@ configuration orchestration. `frontend-modern/src/pages/Alerts.tsx` is the page
|
||||
shell, while `frontend-modern/src/features/alerts/AlertsConfigurationSurface.tsx`
|
||||
is the feature shell. The canonical runtime owner is now
|
||||
`frontend-modern/src/features/alerts/useAlertsConfigurationState.ts` for alert
|
||||
config transport, `frontend-modern/src/features/alerts/useAlertOverridesState.ts`
|
||||
config transport,
|
||||
`frontend-modern/src/features/alerts/alertsConfigurationModel.ts` for config
|
||||
normalization, factory defaults, docker-gap validation, and payload
|
||||
serialization, `frontend-modern/src/features/alerts/useAlertOverridesState.ts`
|
||||
for override projection and thresholds-facing resource selectors, and
|
||||
`frontend-modern/src/features/alerts/useAlertDestinationsState.ts` for
|
||||
notification destination reload and persistence.
|
||||
`frontend-modern/src/features/alerts/useAlertDestinationsTabState.ts` now owns
|
||||
webhook runtime and destination test actions while
|
||||
`frontend-modern/src/features/alerts/tabs/DestinationsTab.tsx` stays the
|
||||
render shell. Future cleanup should extend the transport hook, override hook,
|
||||
or destinations runtime hook based on the true owner, not move config control
|
||||
flow back into the top-level page shell.
|
||||
render shell. Future cleanup should extend the transport hook, config model,
|
||||
override hook, or destinations runtime hook based on the true owner, not move
|
||||
config control flow back into the top-level page shell.
|
||||
The same rule now also covers cross-tab incident timelines: the shared runtime
|
||||
owner is `frontend-modern/src/features/alerts/useAlertIncidentTimelineState.ts`,
|
||||
while `frontend-modern/src/features/alerts/OverviewTab.tsx` and
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { AlertConfig } from '@/types/alerts';
|
||||
|
||||
import {
|
||||
ALERT_DOCKER_GAP_VALIDATION_ERROR,
|
||||
buildAlertsConfigurationPayload,
|
||||
createDefaultAlertsConfigurationSnapshot,
|
||||
readAlertsConfigurationSnapshot,
|
||||
} from '../alertsConfigurationModel';
|
||||
|
||||
describe('alertsConfigurationModel', () => {
|
||||
it('creates the canonical default snapshot', () => {
|
||||
const snapshot = createDefaultAlertsConfigurationSnapshot();
|
||||
|
||||
expect(snapshot.guestDefaults.cpu).toBe(80);
|
||||
expect(snapshot.dockerDefaults.serviceWarnGapPercent).toBe(10);
|
||||
expect(snapshot.timeThresholds.guest).toBe(5);
|
||||
expect(snapshot.scheduleCooldown.enabled).toBe(true);
|
||||
expect(snapshot.backupDefaults.ignoreVMIDs).toEqual([]);
|
||||
});
|
||||
|
||||
it('normalizes alert config into the runtime snapshot', () => {
|
||||
const config = {
|
||||
enabled: true,
|
||||
guestDefaults: {
|
||||
cpu: { trigger: 91, clear: 86 },
|
||||
disableConnectivity: true,
|
||||
poweredOffSeverity: 'critical',
|
||||
},
|
||||
nodeDefaults: {},
|
||||
agentDefaults: {},
|
||||
storageDefault: { trigger: 88, clear: 83 },
|
||||
overrides: {},
|
||||
dockerDefaults: {
|
||||
serviceWarnGapPercent: 40,
|
||||
serviceCriticalGapPercent: 10,
|
||||
statePoweredOffSeverity: 'critical',
|
||||
},
|
||||
backupDefaults: {
|
||||
enabled: true,
|
||||
warningDays: 30,
|
||||
criticalDays: 10,
|
||||
ignoreVMIDs: [' 101 ', '101', ''],
|
||||
},
|
||||
snapshotDefaults: {
|
||||
enabled: true,
|
||||
warningDays: 25,
|
||||
criticalDays: 5,
|
||||
},
|
||||
schedule: {
|
||||
quietHours: {
|
||||
enabled: true,
|
||||
start: '21:00',
|
||||
end: '06:00',
|
||||
days: [1, 3, 5],
|
||||
},
|
||||
cooldown: 45,
|
||||
maxAlertsHour: 7,
|
||||
},
|
||||
} as AlertConfig;
|
||||
|
||||
const snapshot = readAlertsConfigurationSnapshot(config);
|
||||
|
||||
expect(snapshot.guestDefaults.cpu).toBe(91);
|
||||
expect(snapshot.guestDisableConnectivity).toBe(true);
|
||||
expect(snapshot.guestPoweredOffSeverity).toBe('critical');
|
||||
expect(snapshot.dockerDefaults.serviceWarnGapPercent).toBe(40);
|
||||
expect(snapshot.dockerDefaults.serviceCriticalGapPercent).toBe(40);
|
||||
expect(snapshot.backupDefaults.warningDays).toBe(10);
|
||||
expect(snapshot.backupDefaults.criticalDays).toBe(10);
|
||||
expect(snapshot.backupDefaults.ignoreVMIDs).toEqual(['101']);
|
||||
expect(snapshot.snapshotDefaults.warningDays).toBe(5);
|
||||
expect(snapshot.snapshotDefaults.criticalDays).toBe(5);
|
||||
expect(snapshot.scheduleQuietHours.days).toEqual({
|
||||
sunday: false,
|
||||
monday: true,
|
||||
tuesday: false,
|
||||
wednesday: true,
|
||||
thursday: false,
|
||||
friday: true,
|
||||
saturday: false,
|
||||
});
|
||||
expect(snapshot.scheduleCooldown.maxAlerts).toBe(7);
|
||||
});
|
||||
|
||||
it('builds the canonical save payload from the runtime snapshot', () => {
|
||||
const snapshot = createDefaultAlertsConfigurationSnapshot();
|
||||
snapshot.guestDefaults.cpu = 92;
|
||||
snapshot.dockerIgnoredPrefixes = [' web ', ''];
|
||||
snapshot.guestTagWhitelist = [' prod ', ''];
|
||||
snapshot.metricTimeThresholds = {
|
||||
Guest: { CPU: 17 },
|
||||
} as Record<string, Record<string, number>>;
|
||||
snapshot.scheduleCooldown = {
|
||||
enabled: true,
|
||||
minutes: 47,
|
||||
maxAlerts: 99,
|
||||
};
|
||||
snapshot.scheduleGrouping = {
|
||||
enabled: true,
|
||||
window: 2,
|
||||
byNode: true,
|
||||
byGuest: false,
|
||||
};
|
||||
snapshot.backupDefaults = {
|
||||
enabled: true,
|
||||
warningDays: 30,
|
||||
criticalDays: 10,
|
||||
freshHours: 12,
|
||||
staleHours: 48,
|
||||
alertOrphaned: true,
|
||||
ignoreVMIDs: [' 101 ', ''],
|
||||
};
|
||||
|
||||
const result = buildAlertsConfigurationPayload({
|
||||
snapshot,
|
||||
rawOverridesConfig: { 'guest-1': { disabled: true } },
|
||||
alertsActivationState: 'active',
|
||||
alertsActivationConfig: {
|
||||
enabled: true,
|
||||
activationTime: '2026-03-22T10:00:00Z',
|
||||
observationWindowHours: 24,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.dockerValidationError).toBeUndefined();
|
||||
expect(result.alertConfig).toBeDefined();
|
||||
expect(result.alertConfig?.guestDefaults.cpu).toEqual({ trigger: 92, clear: 87 });
|
||||
expect(result.alertConfig?.dockerIgnoredContainerPrefixes).toEqual(['web']);
|
||||
expect(result.alertConfig?.guestTagWhitelist).toEqual(['prod']);
|
||||
expect(result.alertConfig?.metricTimeThresholds).toEqual({ guest: { cpu: 17 } });
|
||||
expect(result.alertConfig?.schedule?.cooldown).toBe(47);
|
||||
expect(result.alertConfig?.schedule?.maxAlertsHour).toBe(10);
|
||||
expect(result.alertConfig?.schedule?.grouping).toEqual({
|
||||
enabled: true,
|
||||
window: 120,
|
||||
byNode: true,
|
||||
byGuest: false,
|
||||
});
|
||||
expect(result.alertConfig?.backupDefaults).toMatchObject({
|
||||
warningDays: 10,
|
||||
criticalDays: 10,
|
||||
ignoreVMIDs: ['101'],
|
||||
});
|
||||
expect(result.alertConfig?.overrides).toEqual({ 'guest-1': { disabled: true } });
|
||||
});
|
||||
|
||||
it('returns the canonical docker-gap validation error before save', () => {
|
||||
const snapshot = createDefaultAlertsConfigurationSnapshot();
|
||||
snapshot.dockerDefaults.serviceWarnGapPercent = 60;
|
||||
snapshot.dockerDefaults.serviceCriticalGapPercent = 40;
|
||||
|
||||
const result = buildAlertsConfigurationPayload({
|
||||
snapshot,
|
||||
rawOverridesConfig: {},
|
||||
alertsActivationState: null,
|
||||
alertsActivationConfig: null,
|
||||
});
|
||||
|
||||
expect(result.alertConfig).toBeUndefined();
|
||||
expect(result.dockerValidationError).toBe(ALERT_DOCKER_GAP_VALIDATION_ERROR);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,680 @@
|
||||
import type { AlertConfig, ActivationState, BackupAlertConfig, SnapshotAlertConfig } from '@/types/alerts';
|
||||
|
||||
import {
|
||||
clampMaxAlertsPerHour,
|
||||
createDefaultCooldown,
|
||||
createDefaultEscalation,
|
||||
createDefaultGrouping,
|
||||
createDefaultQuietHours,
|
||||
createDefaultResolveNotifications,
|
||||
DEFAULT_DELAY_SECONDS,
|
||||
fallbackMaxAlertsPerHour,
|
||||
getTriggerValue,
|
||||
normalizeMetricDelayMap,
|
||||
} from './helpers';
|
||||
import type {
|
||||
CooldownConfig,
|
||||
EscalationConfig,
|
||||
EscalationNotifyTarget,
|
||||
GroupingConfig,
|
||||
QuietHoursConfig,
|
||||
} from './types';
|
||||
import { GROUPING_WINDOW_DEFAULT_SECONDS, clampCooldownMinutes } from './types';
|
||||
|
||||
export const FACTORY_GUEST_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 90,
|
||||
diskRead: -1,
|
||||
diskWrite: -1,
|
||||
networkIn: -1,
|
||||
networkOut: -1,
|
||||
};
|
||||
|
||||
export const FACTORY_NODE_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 90,
|
||||
temperature: 80,
|
||||
};
|
||||
|
||||
export const FACTORY_PBS_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
};
|
||||
|
||||
export const FACTORY_AGENT_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 90,
|
||||
diskTemperature: 55,
|
||||
};
|
||||
|
||||
export const FACTORY_DOCKER_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 85,
|
||||
restartCount: 3,
|
||||
restartWindow: 300,
|
||||
memoryWarnPct: 90,
|
||||
memoryCriticalPct: 95,
|
||||
serviceWarnGapPercent: 10,
|
||||
serviceCriticalGapPercent: 50,
|
||||
};
|
||||
|
||||
export const FACTORY_DOCKER_STATE_DISABLE_CONNECTIVITY = false;
|
||||
export const FACTORY_DOCKER_STATE_SEVERITY: 'warning' | 'critical' = 'warning';
|
||||
export const FACTORY_STORAGE_DEFAULT = 85;
|
||||
export const FACTORY_SNAPSHOT_DEFAULTS: SnapshotAlertConfig = {
|
||||
enabled: false,
|
||||
warningDays: 30,
|
||||
criticalDays: 45,
|
||||
};
|
||||
export const FACTORY_BACKUP_DEFAULTS: BackupAlertConfig = {
|
||||
enabled: false,
|
||||
warningDays: 7,
|
||||
criticalDays: 14,
|
||||
freshHours: 24,
|
||||
staleHours: 72,
|
||||
alertOrphaned: true,
|
||||
ignoreVMIDs: [],
|
||||
};
|
||||
|
||||
export interface AlertsConfigurationSnapshot {
|
||||
scheduleQuietHours: QuietHoursConfig;
|
||||
scheduleCooldown: CooldownConfig;
|
||||
scheduleGrouping: GroupingConfig;
|
||||
scheduleEscalation: EscalationConfig;
|
||||
notifyOnResolve: boolean;
|
||||
guestDefaults: Record<string, number | undefined>;
|
||||
guestDisableConnectivity: boolean;
|
||||
guestPoweredOffSeverity: 'warning' | 'critical';
|
||||
nodeDefaults: Record<string, number | undefined>;
|
||||
pbsDefaults: Record<string, number | undefined>;
|
||||
agentDefaults: Record<string, number | undefined>;
|
||||
dockerDefaults: typeof FACTORY_DOCKER_DEFAULTS;
|
||||
dockerDisableConnectivity: boolean;
|
||||
dockerPoweredOffSeverity: 'warning' | 'critical';
|
||||
dockerIgnoredPrefixes: string[];
|
||||
ignoredGuestPrefixes: string[];
|
||||
guestTagWhitelist: string[];
|
||||
guestTagBlacklist: string[];
|
||||
storageDefault: number;
|
||||
backupDefaults: BackupAlertConfig;
|
||||
timeThresholds: {
|
||||
guest: number;
|
||||
node: number;
|
||||
storage: number;
|
||||
pbs: number;
|
||||
agent: number;
|
||||
};
|
||||
metricTimeThresholds: Record<string, Record<string, number>>;
|
||||
snapshotDefaults: SnapshotAlertConfig;
|
||||
pmgThresholds: {
|
||||
queueTotalWarning: number;
|
||||
queueTotalCritical: number;
|
||||
oldestMessageWarnMins: number;
|
||||
oldestMessageCritMins: number;
|
||||
deferredQueueWarn: number;
|
||||
deferredQueueCritical: number;
|
||||
holdQueueWarn: number;
|
||||
holdQueueCritical: number;
|
||||
quarantineSpamWarn: number;
|
||||
quarantineSpamCritical: number;
|
||||
quarantineVirusWarn: number;
|
||||
quarantineVirusCritical: number;
|
||||
quarantineGrowthWarnPct: number;
|
||||
quarantineGrowthWarnMin: number;
|
||||
quarantineGrowthCritPct: number;
|
||||
quarantineGrowthCritMin: number;
|
||||
};
|
||||
disableAllNodes: boolean;
|
||||
disableAllGuests: boolean;
|
||||
disableAllAgents: boolean;
|
||||
disableAllStorage: boolean;
|
||||
disableAllPBS: boolean;
|
||||
disableAllPMG: boolean;
|
||||
disableAllDockerHosts: boolean;
|
||||
disableAllDockerServices: boolean;
|
||||
disableAllDockerContainers: boolean;
|
||||
disableAllNodesOffline: boolean;
|
||||
disableAllGuestsOffline: boolean;
|
||||
disableAllAgentsOffline: boolean;
|
||||
disableAllPBSOffline: boolean;
|
||||
disableAllPMGOffline: boolean;
|
||||
disableAllDockerHostsOffline: boolean;
|
||||
}
|
||||
|
||||
export interface BuildAlertsConfigurationPayloadArgs {
|
||||
snapshot: AlertsConfigurationSnapshot;
|
||||
rawOverridesConfig: AlertConfig['overrides'];
|
||||
alertsActivationState: ActivationState | null;
|
||||
alertsActivationConfig: {
|
||||
enabled?: boolean;
|
||||
activationTime?: string | null;
|
||||
observationWindowHours?: number | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export const ALERT_DOCKER_GAP_VALIDATION_ERROR =
|
||||
'Swarm service critical gap must be greater than or equal to the warning gap when enabled.';
|
||||
|
||||
const cloneDays = (days: QuietHoursConfig['days']): QuietHoursConfig['days'] => ({ ...days });
|
||||
|
||||
const cloneBackupDefaults = (backupDefaults: BackupAlertConfig): BackupAlertConfig => ({
|
||||
...backupDefaults,
|
||||
ignoreVMIDs: [...(backupDefaults.ignoreVMIDs ?? [])],
|
||||
});
|
||||
|
||||
const createHysteresisThreshold = (trigger: number | undefined, clearMargin = 5) => {
|
||||
const normalized = typeof trigger === 'number' ? trigger : 0;
|
||||
return {
|
||||
trigger: normalized,
|
||||
clear: Math.max(0, normalized - clearMargin),
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeGap = (value: unknown, fallback: number) => {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.max(0, Math.min(100, numeric));
|
||||
};
|
||||
|
||||
const normalizeWarningCriticalPair = (
|
||||
warning: number | undefined,
|
||||
critical: number | undefined,
|
||||
): { warning: number; critical: number } => {
|
||||
const normalizedWarning = Math.max(0, warning ?? 0);
|
||||
const safeCritical = Math.max(0, critical ?? 0);
|
||||
const finalWarning =
|
||||
safeCritical > 0 && normalizedWarning > safeCritical ? safeCritical : normalizedWarning;
|
||||
const finalCritical = safeCritical > 0 ? Math.max(safeCritical, finalWarning) : finalWarning;
|
||||
return {
|
||||
warning: finalWarning,
|
||||
critical: finalCritical,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeStringList = (values: string[] | undefined): string[] =>
|
||||
(values ?? []).map((value) => value.trim()).filter((value) => value.length > 0);
|
||||
|
||||
export function createDefaultAlertsConfigurationSnapshot(): AlertsConfigurationSnapshot {
|
||||
return {
|
||||
scheduleQuietHours: createDefaultQuietHours(),
|
||||
scheduleCooldown: createDefaultCooldown(),
|
||||
scheduleGrouping: createDefaultGrouping(),
|
||||
scheduleEscalation: createDefaultEscalation(),
|
||||
notifyOnResolve: createDefaultResolveNotifications(),
|
||||
guestDefaults: { ...FACTORY_GUEST_DEFAULTS },
|
||||
guestDisableConnectivity: false,
|
||||
guestPoweredOffSeverity: 'warning',
|
||||
nodeDefaults: { ...FACTORY_NODE_DEFAULTS },
|
||||
pbsDefaults: { ...FACTORY_PBS_DEFAULTS },
|
||||
agentDefaults: { ...FACTORY_AGENT_DEFAULTS },
|
||||
dockerDefaults: { ...FACTORY_DOCKER_DEFAULTS },
|
||||
dockerDisableConnectivity: FACTORY_DOCKER_STATE_DISABLE_CONNECTIVITY,
|
||||
dockerPoweredOffSeverity: FACTORY_DOCKER_STATE_SEVERITY,
|
||||
dockerIgnoredPrefixes: [],
|
||||
ignoredGuestPrefixes: [],
|
||||
guestTagWhitelist: [],
|
||||
guestTagBlacklist: [],
|
||||
storageDefault: FACTORY_STORAGE_DEFAULT,
|
||||
backupDefaults: cloneBackupDefaults(FACTORY_BACKUP_DEFAULTS),
|
||||
timeThresholds: {
|
||||
guest: DEFAULT_DELAY_SECONDS,
|
||||
node: DEFAULT_DELAY_SECONDS,
|
||||
storage: DEFAULT_DELAY_SECONDS,
|
||||
pbs: DEFAULT_DELAY_SECONDS,
|
||||
agent: DEFAULT_DELAY_SECONDS,
|
||||
},
|
||||
metricTimeThresholds: {},
|
||||
snapshotDefaults: { ...FACTORY_SNAPSHOT_DEFAULTS },
|
||||
pmgThresholds: {
|
||||
queueTotalWarning: 500,
|
||||
queueTotalCritical: 1000,
|
||||
oldestMessageWarnMins: 30,
|
||||
oldestMessageCritMins: 60,
|
||||
deferredQueueWarn: 200,
|
||||
deferredQueueCritical: 500,
|
||||
holdQueueWarn: 100,
|
||||
holdQueueCritical: 300,
|
||||
quarantineSpamWarn: 2000,
|
||||
quarantineSpamCritical: 5000,
|
||||
quarantineVirusWarn: 2000,
|
||||
quarantineVirusCritical: 5000,
|
||||
quarantineGrowthWarnPct: 25,
|
||||
quarantineGrowthWarnMin: 250,
|
||||
quarantineGrowthCritPct: 50,
|
||||
quarantineGrowthCritMin: 500,
|
||||
},
|
||||
disableAllNodes: false,
|
||||
disableAllGuests: false,
|
||||
disableAllAgents: false,
|
||||
disableAllStorage: false,
|
||||
disableAllPBS: false,
|
||||
disableAllPMG: false,
|
||||
disableAllDockerHosts: false,
|
||||
disableAllDockerServices: false,
|
||||
disableAllDockerContainers: false,
|
||||
disableAllNodesOffline: false,
|
||||
disableAllGuestsOffline: false,
|
||||
disableAllAgentsOffline: false,
|
||||
disableAllPBSOffline: false,
|
||||
disableAllPMGOffline: false,
|
||||
disableAllDockerHostsOffline: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function readAlertsConfigurationSnapshot(config: AlertConfig): AlertsConfigurationSnapshot {
|
||||
const snapshot = createDefaultAlertsConfigurationSnapshot();
|
||||
|
||||
if (config.guestDefaults) {
|
||||
snapshot.guestDefaults = {
|
||||
cpu: getTriggerValue(config.guestDefaults.cpu) ?? FACTORY_GUEST_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.guestDefaults.memory) ?? FACTORY_GUEST_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.guestDefaults.disk) ?? FACTORY_GUEST_DEFAULTS.disk,
|
||||
diskRead: getTriggerValue(config.guestDefaults.diskRead) ?? FACTORY_GUEST_DEFAULTS.diskRead,
|
||||
diskWrite:
|
||||
getTriggerValue(config.guestDefaults.diskWrite) ?? FACTORY_GUEST_DEFAULTS.diskWrite,
|
||||
networkIn:
|
||||
getTriggerValue(config.guestDefaults.networkIn) ?? FACTORY_GUEST_DEFAULTS.networkIn,
|
||||
networkOut:
|
||||
getTriggerValue(config.guestDefaults.networkOut) ?? FACTORY_GUEST_DEFAULTS.networkOut,
|
||||
};
|
||||
snapshot.guestDisableConnectivity = Boolean(config.guestDefaults.disableConnectivity);
|
||||
snapshot.guestPoweredOffSeverity =
|
||||
config.guestDefaults.poweredOffSeverity === 'critical' ? 'critical' : 'warning';
|
||||
}
|
||||
|
||||
if (config.nodeDefaults) {
|
||||
snapshot.nodeDefaults = {
|
||||
cpu: getTriggerValue(config.nodeDefaults.cpu) ?? FACTORY_NODE_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.nodeDefaults.memory) ?? FACTORY_NODE_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.nodeDefaults.disk) ?? FACTORY_NODE_DEFAULTS.disk,
|
||||
temperature:
|
||||
getTriggerValue(config.nodeDefaults.temperature) ?? FACTORY_NODE_DEFAULTS.temperature,
|
||||
};
|
||||
}
|
||||
|
||||
if (config.pbsDefaults) {
|
||||
snapshot.pbsDefaults = {
|
||||
cpu: getTriggerValue(config.pbsDefaults.cpu) ?? FACTORY_PBS_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.pbsDefaults.memory) ?? FACTORY_PBS_DEFAULTS.memory,
|
||||
};
|
||||
}
|
||||
|
||||
if (config.agentDefaults) {
|
||||
snapshot.agentDefaults = {
|
||||
cpu: getTriggerValue(config.agentDefaults.cpu) ?? FACTORY_AGENT_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.agentDefaults.memory) ?? FACTORY_AGENT_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.agentDefaults.disk) ?? FACTORY_AGENT_DEFAULTS.disk,
|
||||
diskTemperature:
|
||||
getTriggerValue(config.agentDefaults.diskTemperature) ??
|
||||
FACTORY_AGENT_DEFAULTS.diskTemperature,
|
||||
};
|
||||
}
|
||||
|
||||
if (config.dockerDefaults) {
|
||||
const serviceWarnGap = normalizeGap(
|
||||
config.dockerDefaults.serviceWarnGapPercent,
|
||||
FACTORY_DOCKER_DEFAULTS.serviceWarnGapPercent,
|
||||
);
|
||||
let serviceCriticalGap = normalizeGap(
|
||||
config.dockerDefaults.serviceCriticalGapPercent,
|
||||
FACTORY_DOCKER_DEFAULTS.serviceCriticalGapPercent,
|
||||
);
|
||||
if (serviceCriticalGap > 0 && serviceWarnGap > serviceCriticalGap) {
|
||||
serviceCriticalGap = serviceWarnGap;
|
||||
}
|
||||
|
||||
snapshot.dockerDefaults = {
|
||||
cpu: getTriggerValue(config.dockerDefaults.cpu) ?? FACTORY_DOCKER_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.dockerDefaults.memory) ?? FACTORY_DOCKER_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.dockerDefaults.disk) ?? FACTORY_DOCKER_DEFAULTS.disk,
|
||||
restartCount: config.dockerDefaults.restartCount ?? FACTORY_DOCKER_DEFAULTS.restartCount,
|
||||
restartWindow: config.dockerDefaults.restartWindow ?? FACTORY_DOCKER_DEFAULTS.restartWindow,
|
||||
memoryWarnPct: config.dockerDefaults.memoryWarnPct ?? FACTORY_DOCKER_DEFAULTS.memoryWarnPct,
|
||||
memoryCriticalPct:
|
||||
config.dockerDefaults.memoryCriticalPct ?? FACTORY_DOCKER_DEFAULTS.memoryCriticalPct,
|
||||
serviceWarnGapPercent: serviceWarnGap,
|
||||
serviceCriticalGapPercent: serviceCriticalGap,
|
||||
};
|
||||
snapshot.dockerDisableConnectivity = Boolean(config.dockerDefaults.stateDisableConnectivity);
|
||||
snapshot.dockerPoweredOffSeverity =
|
||||
config.dockerDefaults.statePoweredOffSeverity === 'critical' ? 'critical' : 'warning';
|
||||
}
|
||||
|
||||
snapshot.dockerIgnoredPrefixes = [...(config.dockerIgnoredContainerPrefixes ?? [])];
|
||||
snapshot.ignoredGuestPrefixes = [...(config.ignoredGuestPrefixes ?? [])];
|
||||
snapshot.guestTagWhitelist = [...(config.guestTagWhitelist ?? [])];
|
||||
snapshot.guestTagBlacklist = [...(config.guestTagBlacklist ?? [])];
|
||||
|
||||
if (config.storageDefault) {
|
||||
snapshot.storageDefault = getTriggerValue(config.storageDefault) ?? FACTORY_STORAGE_DEFAULT;
|
||||
}
|
||||
if (config.timeThresholds) {
|
||||
snapshot.timeThresholds = {
|
||||
guest: config.timeThresholds.guest ?? DEFAULT_DELAY_SECONDS,
|
||||
node: config.timeThresholds.node ?? DEFAULT_DELAY_SECONDS,
|
||||
storage: config.timeThresholds.storage ?? DEFAULT_DELAY_SECONDS,
|
||||
pbs: config.timeThresholds.pbs ?? DEFAULT_DELAY_SECONDS,
|
||||
agent: config.timeThresholds.agent ?? DEFAULT_DELAY_SECONDS,
|
||||
};
|
||||
}
|
||||
if (config.metricTimeThresholds) {
|
||||
snapshot.metricTimeThresholds = normalizeMetricDelayMap(config.metricTimeThresholds);
|
||||
}
|
||||
|
||||
if (config.backupDefaults) {
|
||||
const normalizedPair = normalizeWarningCriticalPair(
|
||||
config.backupDefaults.warningDays,
|
||||
config.backupDefaults.criticalDays,
|
||||
);
|
||||
snapshot.backupDefaults = {
|
||||
enabled: Boolean(config.backupDefaults.enabled),
|
||||
warningDays: normalizedPair.warning,
|
||||
criticalDays: normalizedPair.critical,
|
||||
freshHours: config.backupDefaults.freshHours ?? FACTORY_BACKUP_DEFAULTS.freshHours,
|
||||
staleHours: config.backupDefaults.staleHours ?? FACTORY_BACKUP_DEFAULTS.staleHours,
|
||||
alertOrphaned:
|
||||
config.backupDefaults.alertOrphaned ?? FACTORY_BACKUP_DEFAULTS.alertOrphaned ?? true,
|
||||
ignoreVMIDs: Array.from(
|
||||
new Set(
|
||||
normalizeStringList(
|
||||
config.backupDefaults.ignoreVMIDs ?? FACTORY_BACKUP_DEFAULTS.ignoreVMIDs,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (config.snapshotDefaults) {
|
||||
const normalizedPair = normalizeWarningCriticalPair(
|
||||
config.snapshotDefaults.warningDays,
|
||||
config.snapshotDefaults.criticalDays,
|
||||
);
|
||||
snapshot.snapshotDefaults = {
|
||||
enabled: Boolean(config.snapshotDefaults.enabled),
|
||||
warningDays: normalizedPair.warning,
|
||||
criticalDays: normalizedPair.critical,
|
||||
};
|
||||
}
|
||||
|
||||
if (config.pmgDefaults) {
|
||||
snapshot.pmgThresholds = {
|
||||
queueTotalWarning: config.pmgDefaults.queueTotalWarning ?? 500,
|
||||
queueTotalCritical: config.pmgDefaults.queueTotalCritical ?? 1000,
|
||||
oldestMessageWarnMins: config.pmgDefaults.oldestMessageWarnMins ?? 30,
|
||||
oldestMessageCritMins: config.pmgDefaults.oldestMessageCritMins ?? 60,
|
||||
deferredQueueWarn: config.pmgDefaults.deferredQueueWarn ?? 200,
|
||||
deferredQueueCritical: config.pmgDefaults.deferredQueueCritical ?? 500,
|
||||
holdQueueWarn: config.pmgDefaults.holdQueueWarn ?? 100,
|
||||
holdQueueCritical: config.pmgDefaults.holdQueueCritical ?? 300,
|
||||
quarantineSpamWarn: config.pmgDefaults.quarantineSpamWarn ?? 2000,
|
||||
quarantineSpamCritical: config.pmgDefaults.quarantineSpamCritical ?? 5000,
|
||||
quarantineVirusWarn: config.pmgDefaults.quarantineVirusWarn ?? 2000,
|
||||
quarantineVirusCritical: config.pmgDefaults.quarantineVirusCritical ?? 5000,
|
||||
quarantineGrowthWarnPct: config.pmgDefaults.quarantineGrowthWarnPct ?? 25,
|
||||
quarantineGrowthWarnMin: config.pmgDefaults.quarantineGrowthWarnMin ?? 250,
|
||||
quarantineGrowthCritPct: config.pmgDefaults.quarantineGrowthCritPct ?? 50,
|
||||
quarantineGrowthCritMin: config.pmgDefaults.quarantineGrowthCritMin ?? 500,
|
||||
};
|
||||
}
|
||||
|
||||
snapshot.disableAllNodes = config.disableAllNodes ?? false;
|
||||
snapshot.disableAllGuests = config.disableAllGuests ?? false;
|
||||
snapshot.disableAllAgents = config.disableAllAgents ?? false;
|
||||
snapshot.disableAllStorage = config.disableAllStorage ?? false;
|
||||
snapshot.disableAllPBS = config.disableAllPBS ?? false;
|
||||
snapshot.disableAllPMG = config.disableAllPMG ?? false;
|
||||
snapshot.disableAllDockerHosts = config.disableAllDockerHosts ?? false;
|
||||
snapshot.disableAllDockerServices = config.disableAllDockerServices ?? false;
|
||||
snapshot.disableAllDockerContainers = config.disableAllDockerContainers ?? false;
|
||||
snapshot.disableAllNodesOffline = config.disableAllNodesOffline ?? false;
|
||||
snapshot.disableAllGuestsOffline = config.disableAllGuestsOffline ?? false;
|
||||
snapshot.disableAllAgentsOffline = config.disableAllAgentsOffline ?? false;
|
||||
snapshot.disableAllPBSOffline = config.disableAllPBSOffline ?? false;
|
||||
snapshot.disableAllPMGOffline = config.disableAllPMGOffline ?? false;
|
||||
snapshot.disableAllDockerHostsOffline = config.disableAllDockerHostsOffline ?? false;
|
||||
|
||||
if (config.schedule) {
|
||||
if (config.schedule.quietHours) {
|
||||
const quietHours = config.schedule.quietHours;
|
||||
const days = Array.isArray(quietHours.days)
|
||||
? {
|
||||
sunday: quietHours.days.includes(0),
|
||||
monday: quietHours.days.includes(1),
|
||||
tuesday: quietHours.days.includes(2),
|
||||
wednesday: quietHours.days.includes(3),
|
||||
thursday: quietHours.days.includes(4),
|
||||
friday: quietHours.days.includes(5),
|
||||
saturday: quietHours.days.includes(6),
|
||||
}
|
||||
: ((quietHours.days as Record<string, boolean>) || createDefaultQuietHours().days);
|
||||
snapshot.scheduleQuietHours = {
|
||||
enabled: quietHours.enabled || false,
|
||||
start: quietHours.start || '22:00',
|
||||
end: quietHours.end || '08:00',
|
||||
timezone:
|
||||
quietHours.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
|
||||
days,
|
||||
suppress: {
|
||||
performance: quietHours.suppress?.performance ?? false,
|
||||
storage: quietHours.suppress?.storage ?? false,
|
||||
offline: quietHours.suppress?.offline ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (config.schedule.cooldown !== undefined) {
|
||||
const rawCooldown = config.schedule.cooldown;
|
||||
const cooldownEnabled = rawCooldown > 0;
|
||||
snapshot.scheduleCooldown = {
|
||||
enabled: cooldownEnabled,
|
||||
minutes: cooldownEnabled ? clampCooldownMinutes(rawCooldown) : 0,
|
||||
maxAlerts: fallbackMaxAlertsPerHour(config.schedule.maxAlertsHour),
|
||||
};
|
||||
}
|
||||
|
||||
if (config.schedule.grouping) {
|
||||
const groupingConfig = config.schedule.grouping;
|
||||
const rawGroupingWindowSeconds =
|
||||
typeof groupingConfig.window === 'number'
|
||||
? groupingConfig.window
|
||||
: GROUPING_WINDOW_DEFAULT_SECONDS;
|
||||
const normalizedGroupingWindowSeconds = Math.max(0, rawGroupingWindowSeconds);
|
||||
snapshot.scheduleGrouping = {
|
||||
enabled:
|
||||
groupingConfig.enabled !== undefined
|
||||
? Boolean(groupingConfig.enabled)
|
||||
: normalizedGroupingWindowSeconds > 0,
|
||||
window: Math.round(normalizedGroupingWindowSeconds / 60),
|
||||
byNode: groupingConfig.byNode !== undefined ? groupingConfig.byNode : true,
|
||||
byGuest: groupingConfig.byGuest !== undefined ? groupingConfig.byGuest : false,
|
||||
};
|
||||
}
|
||||
|
||||
if (config.schedule.notifyOnResolve !== undefined) {
|
||||
snapshot.notifyOnResolve = Boolean(config.schedule.notifyOnResolve);
|
||||
}
|
||||
|
||||
if (config.schedule.escalation) {
|
||||
snapshot.scheduleEscalation = {
|
||||
enabled: Boolean(config.schedule.escalation.enabled),
|
||||
levels: (config.schedule.escalation.levels || []).map((level) => ({
|
||||
after: typeof level.after === 'number' ? level.after : 15,
|
||||
notify: (level.notify as EscalationNotifyTarget) || 'all',
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
export function buildAlertsConfigurationPayload({
|
||||
snapshot,
|
||||
rawOverridesConfig,
|
||||
alertsActivationState,
|
||||
alertsActivationConfig,
|
||||
}: BuildAlertsConfigurationPayloadArgs): {
|
||||
alertConfig?: AlertConfig;
|
||||
dockerValidationError?: string;
|
||||
} {
|
||||
if (
|
||||
snapshot.dockerDefaults.serviceCriticalGapPercent > 0 &&
|
||||
snapshot.dockerDefaults.serviceWarnGapPercent > snapshot.dockerDefaults.serviceCriticalGapPercent
|
||||
) {
|
||||
return {
|
||||
dockerValidationError: ALERT_DOCKER_GAP_VALIDATION_ERROR,
|
||||
};
|
||||
}
|
||||
|
||||
const normalizedSnapshotPair = normalizeWarningCriticalPair(
|
||||
snapshot.snapshotDefaults.warningDays,
|
||||
snapshot.snapshotDefaults.criticalDays,
|
||||
);
|
||||
const normalizedBackupPair = normalizeWarningCriticalPair(
|
||||
snapshot.backupDefaults.warningDays,
|
||||
snapshot.backupDefaults.criticalDays,
|
||||
);
|
||||
const normalizedCooldownMinutes = snapshot.scheduleCooldown.enabled
|
||||
? clampCooldownMinutes(snapshot.scheduleCooldown.minutes)
|
||||
: 0;
|
||||
const normalizedMaxAlertsHour = clampMaxAlertsPerHour(snapshot.scheduleCooldown.maxAlerts);
|
||||
const groupingWindowSeconds =
|
||||
snapshot.scheduleGrouping.enabled && snapshot.scheduleGrouping.window >= 0
|
||||
? snapshot.scheduleGrouping.window * 60
|
||||
: 0;
|
||||
const groupingEnabled = snapshot.scheduleGrouping.enabled && groupingWindowSeconds > 0;
|
||||
|
||||
return {
|
||||
alertConfig: {
|
||||
enabled: alertsActivationConfig?.enabled ?? true,
|
||||
activationState: alertsActivationState ?? undefined,
|
||||
activationTime: alertsActivationConfig?.activationTime ?? undefined,
|
||||
observationWindowHours: alertsActivationConfig?.observationWindowHours ?? undefined,
|
||||
disableAllNodes: snapshot.disableAllNodes,
|
||||
disableAllGuests: snapshot.disableAllGuests,
|
||||
disableAllAgents: snapshot.disableAllAgents,
|
||||
disableAllStorage: snapshot.disableAllStorage,
|
||||
disableAllPBS: snapshot.disableAllPBS,
|
||||
disableAllPMG: snapshot.disableAllPMG,
|
||||
disableAllDockerHosts: snapshot.disableAllDockerHosts,
|
||||
disableAllDockerContainers: snapshot.disableAllDockerContainers,
|
||||
disableAllDockerServices: snapshot.disableAllDockerServices,
|
||||
disableAllNodesOffline: snapshot.disableAllNodesOffline,
|
||||
disableAllGuestsOffline: snapshot.disableAllGuestsOffline,
|
||||
disableAllPBSOffline: snapshot.disableAllPBSOffline,
|
||||
disableAllAgentsOffline: snapshot.disableAllAgentsOffline,
|
||||
disableAllPMGOffline: snapshot.disableAllPMGOffline,
|
||||
disableAllDockerHostsOffline: snapshot.disableAllDockerHostsOffline,
|
||||
guestDefaults: {
|
||||
cpu: createHysteresisThreshold(snapshot.guestDefaults.cpu),
|
||||
memory: createHysteresisThreshold(snapshot.guestDefaults.memory),
|
||||
disk: createHysteresisThreshold(snapshot.guestDefaults.disk),
|
||||
diskRead: createHysteresisThreshold(snapshot.guestDefaults.diskRead),
|
||||
diskWrite: createHysteresisThreshold(snapshot.guestDefaults.diskWrite),
|
||||
networkIn: createHysteresisThreshold(snapshot.guestDefaults.networkIn),
|
||||
networkOut: createHysteresisThreshold(snapshot.guestDefaults.networkOut),
|
||||
disableConnectivity: snapshot.guestDisableConnectivity,
|
||||
poweredOffSeverity: snapshot.guestPoweredOffSeverity,
|
||||
},
|
||||
nodeDefaults: {
|
||||
cpu: createHysteresisThreshold(snapshot.nodeDefaults.cpu),
|
||||
memory: createHysteresisThreshold(snapshot.nodeDefaults.memory),
|
||||
disk: createHysteresisThreshold(snapshot.nodeDefaults.disk),
|
||||
temperature: createHysteresisThreshold(snapshot.nodeDefaults.temperature),
|
||||
},
|
||||
agentDefaults: {
|
||||
cpu: createHysteresisThreshold(snapshot.agentDefaults.cpu),
|
||||
memory: createHysteresisThreshold(snapshot.agentDefaults.memory),
|
||||
disk: createHysteresisThreshold(snapshot.agentDefaults.disk),
|
||||
diskTemperature: createHysteresisThreshold(snapshot.agentDefaults.diskTemperature),
|
||||
},
|
||||
pbsDefaults: {
|
||||
cpu: createHysteresisThreshold(snapshot.pbsDefaults.cpu),
|
||||
memory: createHysteresisThreshold(snapshot.pbsDefaults.memory),
|
||||
},
|
||||
dockerDefaults: {
|
||||
cpu: createHysteresisThreshold(snapshot.dockerDefaults.cpu),
|
||||
memory: createHysteresisThreshold(snapshot.dockerDefaults.memory),
|
||||
disk: createHysteresisThreshold(snapshot.dockerDefaults.disk),
|
||||
restartCount: snapshot.dockerDefaults.restartCount,
|
||||
restartWindow: snapshot.dockerDefaults.restartWindow,
|
||||
memoryWarnPct: snapshot.dockerDefaults.memoryWarnPct,
|
||||
memoryCriticalPct: snapshot.dockerDefaults.memoryCriticalPct,
|
||||
serviceWarnGapPercent: snapshot.dockerDefaults.serviceWarnGapPercent,
|
||||
serviceCriticalGapPercent: snapshot.dockerDefaults.serviceCriticalGapPercent,
|
||||
stateDisableConnectivity: snapshot.dockerDisableConnectivity,
|
||||
statePoweredOffSeverity: snapshot.dockerPoweredOffSeverity,
|
||||
},
|
||||
dockerIgnoredContainerPrefixes: normalizeStringList(snapshot.dockerIgnoredPrefixes),
|
||||
ignoredGuestPrefixes: normalizeStringList(snapshot.ignoredGuestPrefixes),
|
||||
guestTagWhitelist: normalizeStringList(snapshot.guestTagWhitelist),
|
||||
guestTagBlacklist: normalizeStringList(snapshot.guestTagBlacklist),
|
||||
storageDefault: createHysteresisThreshold(snapshot.storageDefault),
|
||||
minimumDelta: 2.0,
|
||||
suppressionWindow: 5,
|
||||
hysteresisMargin: 5.0,
|
||||
timeThresholds: { ...snapshot.timeThresholds },
|
||||
metricTimeThresholds: normalizeMetricDelayMap(snapshot.metricTimeThresholds),
|
||||
snapshotDefaults: {
|
||||
enabled: snapshot.snapshotDefaults.enabled,
|
||||
warningDays: normalizedSnapshotPair.warning,
|
||||
criticalDays: normalizedSnapshotPair.critical,
|
||||
},
|
||||
backupDefaults: {
|
||||
enabled: snapshot.backupDefaults.enabled,
|
||||
warningDays: normalizedBackupPair.warning,
|
||||
criticalDays: normalizedBackupPair.critical,
|
||||
freshHours: snapshot.backupDefaults.freshHours ?? 24,
|
||||
staleHours: snapshot.backupDefaults.staleHours ?? 72,
|
||||
alertOrphaned: snapshot.backupDefaults.alertOrphaned ?? true,
|
||||
ignoreVMIDs: normalizeStringList(snapshot.backupDefaults.ignoreVMIDs),
|
||||
},
|
||||
pmgDefaults: { ...snapshot.pmgThresholds },
|
||||
overrides: rawOverridesConfig,
|
||||
schedule: {
|
||||
quietHours: {
|
||||
...snapshot.scheduleQuietHours,
|
||||
days: cloneDays(snapshot.scheduleQuietHours.days),
|
||||
},
|
||||
cooldown: normalizedCooldownMinutes,
|
||||
notifyOnResolve: snapshot.notifyOnResolve,
|
||||
maxAlertsHour: normalizedMaxAlertsHour,
|
||||
escalation: {
|
||||
enabled: snapshot.scheduleEscalation.enabled,
|
||||
levels: snapshot.scheduleEscalation.levels.map((level) => ({ ...level })),
|
||||
},
|
||||
grouping: {
|
||||
enabled: groupingEnabled,
|
||||
window: groupingWindowSeconds,
|
||||
byNode: snapshot.scheduleGrouping.byNode,
|
||||
byGuest: snapshot.scheduleGrouping.byGuest,
|
||||
},
|
||||
},
|
||||
aggregation: {
|
||||
enabled: true,
|
||||
timeWindow: 10,
|
||||
countThreshold: 3,
|
||||
similarityWindow: 5.0,
|
||||
},
|
||||
flapping: {
|
||||
enabled: true,
|
||||
threshold: 5,
|
||||
window: 10,
|
||||
suppressionTime: 30,
|
||||
minStability: 0.8,
|
||||
},
|
||||
ioNormalization: {
|
||||
enabled: true,
|
||||
vmDiskMax: 500.0,
|
||||
containerDiskMax: 300.0,
|
||||
networkMax: 1000.0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -15,29 +15,32 @@ import {
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
import {
|
||||
clampMaxAlertsPerHour,
|
||||
createDefaultCooldown,
|
||||
createDefaultEscalation,
|
||||
createDefaultGrouping,
|
||||
createDefaultQuietHours,
|
||||
createDefaultResolveNotifications,
|
||||
DEFAULT_DELAY_SECONDS,
|
||||
fallbackMaxAlertsPerHour,
|
||||
getTriggerValue,
|
||||
normalizeMetricDelayMap,
|
||||
} from './helpers';
|
||||
ALERT_DOCKER_GAP_VALIDATION_ERROR,
|
||||
createDefaultAlertsConfigurationSnapshot,
|
||||
buildAlertsConfigurationPayload,
|
||||
FACTORY_AGENT_DEFAULTS,
|
||||
FACTORY_BACKUP_DEFAULTS,
|
||||
FACTORY_DOCKER_DEFAULTS,
|
||||
FACTORY_DOCKER_STATE_DISABLE_CONNECTIVITY,
|
||||
FACTORY_DOCKER_STATE_SEVERITY,
|
||||
FACTORY_GUEST_DEFAULTS,
|
||||
FACTORY_NODE_DEFAULTS,
|
||||
FACTORY_PBS_DEFAULTS,
|
||||
FACTORY_SNAPSHOT_DEFAULTS,
|
||||
FACTORY_STORAGE_DEFAULT,
|
||||
readAlertsConfigurationSnapshot,
|
||||
type AlertsConfigurationSnapshot,
|
||||
} from './alertsConfigurationModel';
|
||||
import { useAlertDestinationsState } from './useAlertDestinationsState';
|
||||
import { useAlertOverridesState } from './useAlertOverridesState';
|
||||
import type {
|
||||
AlertTab,
|
||||
CooldownConfig,
|
||||
EscalationConfig,
|
||||
EscalationNotifyTarget,
|
||||
GroupingConfig,
|
||||
Override,
|
||||
QuietHoursConfig,
|
||||
} from './types';
|
||||
import { GROUPING_WINDOW_DEFAULT_SECONDS, clampCooldownMinutes } from './types';
|
||||
|
||||
export interface AlertsConfigurationSurfaceProps {
|
||||
activeTab: Accessor<AlertTab>;
|
||||
@@ -57,155 +60,104 @@ export interface AlertsConfigurationSurfaceProps {
|
||||
} | null;
|
||||
}
|
||||
|
||||
export const FACTORY_GUEST_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 90,
|
||||
diskRead: -1,
|
||||
diskWrite: -1,
|
||||
networkIn: -1,
|
||||
networkOut: -1,
|
||||
};
|
||||
|
||||
export const FACTORY_NODE_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 90,
|
||||
temperature: 80,
|
||||
};
|
||||
|
||||
export const FACTORY_PBS_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
};
|
||||
|
||||
export const FACTORY_AGENT_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 90,
|
||||
diskTemperature: 55,
|
||||
};
|
||||
|
||||
export const FACTORY_DOCKER_DEFAULTS = {
|
||||
cpu: 80,
|
||||
memory: 85,
|
||||
disk: 85,
|
||||
restartCount: 3,
|
||||
restartWindow: 300,
|
||||
memoryWarnPct: 90,
|
||||
memoryCriticalPct: 95,
|
||||
serviceWarnGapPercent: 10,
|
||||
serviceCriticalGapPercent: 50,
|
||||
};
|
||||
|
||||
export const FACTORY_DOCKER_STATE_DISABLE_CONNECTIVITY = false;
|
||||
export const FACTORY_DOCKER_STATE_SEVERITY: 'warning' | 'critical' = 'warning';
|
||||
export const FACTORY_STORAGE_DEFAULT = 85;
|
||||
export const FACTORY_SNAPSHOT_DEFAULTS: SnapshotAlertConfig = {
|
||||
enabled: false,
|
||||
warningDays: 30,
|
||||
criticalDays: 45,
|
||||
};
|
||||
export const FACTORY_BACKUP_DEFAULTS: BackupAlertConfig = {
|
||||
enabled: false,
|
||||
warningDays: 7,
|
||||
criticalDays: 14,
|
||||
freshHours: 24,
|
||||
staleHours: 72,
|
||||
alertOrphaned: true,
|
||||
ignoreVMIDs: [],
|
||||
};
|
||||
|
||||
export function useAlertsConfigurationState(props: AlertsConfigurationSurfaceProps) {
|
||||
const defaultSnapshot = createDefaultAlertsConfigurationSnapshot();
|
||||
const [isReloadingConfig, setIsReloadingConfig] = createSignal(false);
|
||||
const [scheduleQuietHours, setScheduleQuietHours] =
|
||||
createSignal<QuietHoursConfig>(createDefaultQuietHours());
|
||||
const [scheduleCooldown, setScheduleCooldown] =
|
||||
createSignal<CooldownConfig>(createDefaultCooldown());
|
||||
const [scheduleGrouping, setScheduleGrouping] =
|
||||
createSignal<GroupingConfig>(createDefaultGrouping());
|
||||
const [scheduleEscalation, setScheduleEscalation] =
|
||||
createSignal<EscalationConfig>(createDefaultEscalation());
|
||||
const [notifyOnResolve, setNotifyOnResolve] = createSignal<boolean>(
|
||||
createDefaultResolveNotifications(),
|
||||
const [scheduleQuietHours, setScheduleQuietHours] = createSignal<QuietHoursConfig>(
|
||||
defaultSnapshot.scheduleQuietHours,
|
||||
);
|
||||
const [scheduleCooldown, setScheduleCooldown] = createSignal<CooldownConfig>(
|
||||
defaultSnapshot.scheduleCooldown,
|
||||
);
|
||||
const [scheduleGrouping, setScheduleGrouping] = createSignal<GroupingConfig>(
|
||||
defaultSnapshot.scheduleGrouping,
|
||||
);
|
||||
const [scheduleEscalation, setScheduleEscalation] = createSignal<EscalationConfig>(
|
||||
defaultSnapshot.scheduleEscalation,
|
||||
);
|
||||
const [notifyOnResolve, setNotifyOnResolve] = createSignal<boolean>(defaultSnapshot.notifyOnResolve);
|
||||
const [guestDefaults, setGuestDefaults] = createSignal<Record<string, number | undefined>>(
|
||||
defaultSnapshot.guestDefaults,
|
||||
);
|
||||
const [guestDisableConnectivity, setGuestDisableConnectivity] = createSignal(
|
||||
defaultSnapshot.guestDisableConnectivity,
|
||||
);
|
||||
const [guestDefaults, setGuestDefaults] = createSignal<Record<string, number | undefined>>({
|
||||
...FACTORY_GUEST_DEFAULTS,
|
||||
});
|
||||
const [guestDisableConnectivity, setGuestDisableConnectivity] = createSignal(false);
|
||||
const [guestPoweredOffSeverity, setGuestPoweredOffSeverity] = createSignal<
|
||||
'warning' | 'critical'
|
||||
>('warning');
|
||||
const [nodeDefaults, setNodeDefaults] = createSignal<Record<string, number | undefined>>({
|
||||
...FACTORY_NODE_DEFAULTS,
|
||||
});
|
||||
const [pbsDefaults, setPBSDefaults] = createSignal<Record<string, number | undefined>>({
|
||||
...FACTORY_PBS_DEFAULTS,
|
||||
});
|
||||
const [agentDefaults, setAgentDefaults] = createSignal<Record<string, number | undefined>>({
|
||||
...FACTORY_AGENT_DEFAULTS,
|
||||
});
|
||||
const [dockerDefaults, setDockerDefaults] = createSignal({ ...FACTORY_DOCKER_DEFAULTS });
|
||||
>(defaultSnapshot.guestPoweredOffSeverity);
|
||||
const [nodeDefaults, setNodeDefaults] = createSignal<Record<string, number | undefined>>(
|
||||
defaultSnapshot.nodeDefaults,
|
||||
);
|
||||
const [pbsDefaults, setPBSDefaults] = createSignal<Record<string, number | undefined>>(
|
||||
defaultSnapshot.pbsDefaults,
|
||||
);
|
||||
const [agentDefaults, setAgentDefaults] = createSignal<Record<string, number | undefined>>(
|
||||
defaultSnapshot.agentDefaults,
|
||||
);
|
||||
const [dockerDefaults, setDockerDefaults] = createSignal(defaultSnapshot.dockerDefaults);
|
||||
const [dockerDisableConnectivity, setDockerDisableConnectivity] = createSignal(
|
||||
FACTORY_DOCKER_STATE_DISABLE_CONNECTIVITY,
|
||||
defaultSnapshot.dockerDisableConnectivity,
|
||||
);
|
||||
const [dockerPoweredOffSeverity, setDockerPoweredOffSeverity] = createSignal<
|
||||
'warning' | 'critical'
|
||||
>(FACTORY_DOCKER_STATE_SEVERITY);
|
||||
const [dockerIgnoredPrefixes, setDockerIgnoredPrefixes] = createSignal<string[]>([]);
|
||||
const [ignoredGuestPrefixes, setIgnoredGuestPrefixes] = createSignal<string[]>([]);
|
||||
const [guestTagWhitelist, setGuestTagWhitelist] = createSignal<string[]>([]);
|
||||
const [guestTagBlacklist, setGuestTagBlacklist] = createSignal<string[]>([]);
|
||||
const [storageDefault, setStorageDefault] = createSignal(FACTORY_STORAGE_DEFAULT);
|
||||
const [backupDefaults, setBackupDefaults] = createSignal<BackupAlertConfig>({
|
||||
...FACTORY_BACKUP_DEFAULTS,
|
||||
});
|
||||
const [timeThresholds, setTimeThresholds] = createSignal({
|
||||
guest: DEFAULT_DELAY_SECONDS,
|
||||
node: DEFAULT_DELAY_SECONDS,
|
||||
storage: DEFAULT_DELAY_SECONDS,
|
||||
pbs: DEFAULT_DELAY_SECONDS,
|
||||
agent: DEFAULT_DELAY_SECONDS,
|
||||
});
|
||||
>(defaultSnapshot.dockerPoweredOffSeverity);
|
||||
const [dockerIgnoredPrefixes, setDockerIgnoredPrefixes] = createSignal<string[]>(
|
||||
defaultSnapshot.dockerIgnoredPrefixes,
|
||||
);
|
||||
const [ignoredGuestPrefixes, setIgnoredGuestPrefixes] = createSignal<string[]>(
|
||||
defaultSnapshot.ignoredGuestPrefixes,
|
||||
);
|
||||
const [guestTagWhitelist, setGuestTagWhitelist] = createSignal<string[]>(
|
||||
defaultSnapshot.guestTagWhitelist,
|
||||
);
|
||||
const [guestTagBlacklist, setGuestTagBlacklist] = createSignal<string[]>(
|
||||
defaultSnapshot.guestTagBlacklist,
|
||||
);
|
||||
const [storageDefault, setStorageDefault] = createSignal(defaultSnapshot.storageDefault);
|
||||
const [backupDefaults, setBackupDefaults] = createSignal<BackupAlertConfig>(
|
||||
defaultSnapshot.backupDefaults,
|
||||
);
|
||||
const [timeThresholds, setTimeThresholds] = createSignal(defaultSnapshot.timeThresholds);
|
||||
const [metricTimeThresholds, setMetricTimeThresholds] = createSignal<
|
||||
Record<string, Record<string, number>>
|
||||
>({});
|
||||
const [snapshotDefaults, setSnapshotDefaults] = createSignal<SnapshotAlertConfig>({
|
||||
...FACTORY_SNAPSHOT_DEFAULTS,
|
||||
});
|
||||
const [pmgThresholds, setPMGThresholds] = createSignal({
|
||||
queueTotalWarning: 500,
|
||||
queueTotalCritical: 1000,
|
||||
oldestMessageWarnMins: 30,
|
||||
oldestMessageCritMins: 60,
|
||||
deferredQueueWarn: 200,
|
||||
deferredQueueCritical: 500,
|
||||
holdQueueWarn: 100,
|
||||
holdQueueCritical: 300,
|
||||
quarantineSpamWarn: 2000,
|
||||
quarantineSpamCritical: 5000,
|
||||
quarantineVirusWarn: 2000,
|
||||
quarantineVirusCritical: 5000,
|
||||
quarantineGrowthWarnPct: 25,
|
||||
quarantineGrowthWarnMin: 250,
|
||||
quarantineGrowthCritPct: 50,
|
||||
quarantineGrowthCritMin: 500,
|
||||
});
|
||||
const [disableAllNodes, setDisableAllNodes] = createSignal(false);
|
||||
const [disableAllGuests, setDisableAllGuests] = createSignal(false);
|
||||
const [disableAllAgents, setDisableAllAgents] = createSignal(false);
|
||||
const [disableAllStorage, setDisableAllStorage] = createSignal(false);
|
||||
const [disableAllPBS, setDisableAllPBS] = createSignal(false);
|
||||
const [disableAllPMG, setDisableAllPMG] = createSignal(false);
|
||||
const [disableAllDockerHosts, setDisableAllDockerHosts] = createSignal(false);
|
||||
const [disableAllDockerServices, setDisableAllDockerServices] = createSignal(false);
|
||||
const [disableAllDockerContainers, setDisableAllDockerContainers] = createSignal(false);
|
||||
const [disableAllNodesOffline, setDisableAllNodesOffline] = createSignal(false);
|
||||
const [disableAllGuestsOffline, setDisableAllGuestsOffline] = createSignal(false);
|
||||
const [disableAllAgentsOffline, setDisableAllAgentsOffline] = createSignal(false);
|
||||
const [disableAllPBSOffline, setDisableAllPBSOffline] = createSignal(false);
|
||||
const [disableAllPMGOffline, setDisableAllPMGOffline] = createSignal(false);
|
||||
const [disableAllDockerHostsOffline, setDisableAllDockerHostsOffline] = createSignal(false);
|
||||
>(defaultSnapshot.metricTimeThresholds);
|
||||
const [snapshotDefaults, setSnapshotDefaults] = createSignal<SnapshotAlertConfig>(
|
||||
defaultSnapshot.snapshotDefaults,
|
||||
);
|
||||
const [pmgThresholds, setPMGThresholds] = createSignal(defaultSnapshot.pmgThresholds);
|
||||
const [disableAllNodes, setDisableAllNodes] = createSignal(defaultSnapshot.disableAllNodes);
|
||||
const [disableAllGuests, setDisableAllGuests] = createSignal(defaultSnapshot.disableAllGuests);
|
||||
const [disableAllAgents, setDisableAllAgents] = createSignal(defaultSnapshot.disableAllAgents);
|
||||
const [disableAllStorage, setDisableAllStorage] = createSignal(defaultSnapshot.disableAllStorage);
|
||||
const [disableAllPBS, setDisableAllPBS] = createSignal(defaultSnapshot.disableAllPBS);
|
||||
const [disableAllPMG, setDisableAllPMG] = createSignal(defaultSnapshot.disableAllPMG);
|
||||
const [disableAllDockerHosts, setDisableAllDockerHosts] = createSignal(
|
||||
defaultSnapshot.disableAllDockerHosts,
|
||||
);
|
||||
const [disableAllDockerServices, setDisableAllDockerServices] = createSignal(
|
||||
defaultSnapshot.disableAllDockerServices,
|
||||
);
|
||||
const [disableAllDockerContainers, setDisableAllDockerContainers] = createSignal(
|
||||
defaultSnapshot.disableAllDockerContainers,
|
||||
);
|
||||
const [disableAllNodesOffline, setDisableAllNodesOffline] = createSignal(
|
||||
defaultSnapshot.disableAllNodesOffline,
|
||||
);
|
||||
const [disableAllGuestsOffline, setDisableAllGuestsOffline] = createSignal(
|
||||
defaultSnapshot.disableAllGuestsOffline,
|
||||
);
|
||||
const [disableAllAgentsOffline, setDisableAllAgentsOffline] = createSignal(
|
||||
defaultSnapshot.disableAllAgentsOffline,
|
||||
);
|
||||
const [disableAllPBSOffline, setDisableAllPBSOffline] = createSignal(
|
||||
defaultSnapshot.disableAllPBSOffline,
|
||||
);
|
||||
const [disableAllPMGOffline, setDisableAllPMGOffline] = createSignal(
|
||||
defaultSnapshot.disableAllPMGOffline,
|
||||
);
|
||||
const [disableAllDockerHostsOffline, setDisableAllDockerHostsOffline] = createSignal(
|
||||
defaultSnapshot.disableAllDockerHostsOffline,
|
||||
);
|
||||
const destinationsState = useAlertDestinationsState({ activeTab: props.activeTab });
|
||||
const overridesState = useAlertOverridesState({
|
||||
allResources: props.allResources,
|
||||
@@ -215,6 +167,110 @@ export function useAlertsConfigurationState(props: AlertsConfigurationSurfacePro
|
||||
setOverviewOverrides: props.setOverviewOverrides,
|
||||
});
|
||||
|
||||
const applyConfigurationSnapshot = (snapshot: AlertsConfigurationSnapshot) => {
|
||||
setScheduleQuietHours({
|
||||
...snapshot.scheduleQuietHours,
|
||||
days: { ...snapshot.scheduleQuietHours.days },
|
||||
suppress: { ...snapshot.scheduleQuietHours.suppress },
|
||||
});
|
||||
setScheduleCooldown({ ...snapshot.scheduleCooldown });
|
||||
setScheduleGrouping({ ...snapshot.scheduleGrouping });
|
||||
setScheduleEscalation({
|
||||
enabled: snapshot.scheduleEscalation.enabled,
|
||||
levels: snapshot.scheduleEscalation.levels.map((level) => ({ ...level })),
|
||||
});
|
||||
setNotifyOnResolve(snapshot.notifyOnResolve);
|
||||
setGuestDefaults({ ...snapshot.guestDefaults });
|
||||
setGuestDisableConnectivity(snapshot.guestDisableConnectivity);
|
||||
setGuestPoweredOffSeverity(snapshot.guestPoweredOffSeverity);
|
||||
setNodeDefaults({ ...snapshot.nodeDefaults });
|
||||
setPBSDefaults({ ...snapshot.pbsDefaults });
|
||||
setAgentDefaults({ ...snapshot.agentDefaults });
|
||||
setDockerDefaults({ ...snapshot.dockerDefaults });
|
||||
setDockerDisableConnectivity(snapshot.dockerDisableConnectivity);
|
||||
setDockerPoweredOffSeverity(snapshot.dockerPoweredOffSeverity);
|
||||
setDockerIgnoredPrefixes([...snapshot.dockerIgnoredPrefixes]);
|
||||
setIgnoredGuestPrefixes([...snapshot.ignoredGuestPrefixes]);
|
||||
setGuestTagWhitelist([...snapshot.guestTagWhitelist]);
|
||||
setGuestTagBlacklist([...snapshot.guestTagBlacklist]);
|
||||
setStorageDefault(snapshot.storageDefault);
|
||||
setBackupDefaults({
|
||||
...snapshot.backupDefaults,
|
||||
ignoreVMIDs: [...(snapshot.backupDefaults.ignoreVMIDs ?? [])],
|
||||
});
|
||||
setTimeThresholds({ ...snapshot.timeThresholds });
|
||||
setMetricTimeThresholds(structuredClone(snapshot.metricTimeThresholds));
|
||||
setSnapshotDefaults({ ...snapshot.snapshotDefaults });
|
||||
setPMGThresholds({ ...snapshot.pmgThresholds });
|
||||
setDisableAllNodes(snapshot.disableAllNodes);
|
||||
setDisableAllGuests(snapshot.disableAllGuests);
|
||||
setDisableAllAgents(snapshot.disableAllAgents);
|
||||
setDisableAllStorage(snapshot.disableAllStorage);
|
||||
setDisableAllPBS(snapshot.disableAllPBS);
|
||||
setDisableAllPMG(snapshot.disableAllPMG);
|
||||
setDisableAllDockerHosts(snapshot.disableAllDockerHosts);
|
||||
setDisableAllDockerServices(snapshot.disableAllDockerServices);
|
||||
setDisableAllDockerContainers(snapshot.disableAllDockerContainers);
|
||||
setDisableAllNodesOffline(snapshot.disableAllNodesOffline);
|
||||
setDisableAllGuestsOffline(snapshot.disableAllGuestsOffline);
|
||||
setDisableAllAgentsOffline(snapshot.disableAllAgentsOffline);
|
||||
setDisableAllPBSOffline(snapshot.disableAllPBSOffline);
|
||||
setDisableAllPMGOffline(snapshot.disableAllPMGOffline);
|
||||
setDisableAllDockerHostsOffline(snapshot.disableAllDockerHostsOffline);
|
||||
};
|
||||
|
||||
const captureConfigurationSnapshot = (): AlertsConfigurationSnapshot => ({
|
||||
scheduleQuietHours: {
|
||||
...scheduleQuietHours(),
|
||||
days: { ...scheduleQuietHours().days },
|
||||
suppress: { ...scheduleQuietHours().suppress },
|
||||
},
|
||||
scheduleCooldown: { ...scheduleCooldown() },
|
||||
scheduleGrouping: { ...scheduleGrouping() },
|
||||
scheduleEscalation: {
|
||||
enabled: scheduleEscalation().enabled,
|
||||
levels: scheduleEscalation().levels.map((level) => ({ ...level })),
|
||||
},
|
||||
notifyOnResolve: notifyOnResolve(),
|
||||
guestDefaults: { ...guestDefaults() },
|
||||
guestDisableConnectivity: guestDisableConnectivity(),
|
||||
guestPoweredOffSeverity: guestPoweredOffSeverity(),
|
||||
nodeDefaults: { ...nodeDefaults() },
|
||||
pbsDefaults: { ...pbsDefaults() },
|
||||
agentDefaults: { ...agentDefaults() },
|
||||
dockerDefaults: { ...dockerDefaults() },
|
||||
dockerDisableConnectivity: dockerDisableConnectivity(),
|
||||
dockerPoweredOffSeverity: dockerPoweredOffSeverity(),
|
||||
dockerIgnoredPrefixes: [...dockerIgnoredPrefixes()],
|
||||
ignoredGuestPrefixes: [...ignoredGuestPrefixes()],
|
||||
guestTagWhitelist: [...guestTagWhitelist()],
|
||||
guestTagBlacklist: [...guestTagBlacklist()],
|
||||
storageDefault: storageDefault(),
|
||||
backupDefaults: {
|
||||
...backupDefaults(),
|
||||
ignoreVMIDs: [...(backupDefaults().ignoreVMIDs ?? [])],
|
||||
},
|
||||
timeThresholds: { ...timeThresholds() },
|
||||
metricTimeThresholds: structuredClone(metricTimeThresholds()),
|
||||
snapshotDefaults: { ...snapshotDefaults() },
|
||||
pmgThresholds: { ...pmgThresholds() },
|
||||
disableAllNodes: disableAllNodes(),
|
||||
disableAllGuests: disableAllGuests(),
|
||||
disableAllAgents: disableAllAgents(),
|
||||
disableAllStorage: disableAllStorage(),
|
||||
disableAllPBS: disableAllPBS(),
|
||||
disableAllPMG: disableAllPMG(),
|
||||
disableAllDockerHosts: disableAllDockerHosts(),
|
||||
disableAllDockerServices: disableAllDockerServices(),
|
||||
disableAllDockerContainers: disableAllDockerContainers(),
|
||||
disableAllNodesOffline: disableAllNodesOffline(),
|
||||
disableAllGuestsOffline: disableAllGuestsOffline(),
|
||||
disableAllAgentsOffline: disableAllAgentsOffline(),
|
||||
disableAllPBSOffline: disableAllPBSOffline(),
|
||||
disableAllPMGOffline: disableAllPMGOffline(),
|
||||
disableAllDockerHostsOffline: disableAllDockerHostsOffline(),
|
||||
});
|
||||
|
||||
const resetGuestDefaults = () => {
|
||||
setGuestDefaults({ ...FACTORY_GUEST_DEFAULTS });
|
||||
props.setHasUnsavedChanges(true);
|
||||
@@ -258,313 +314,14 @@ export function useAlertsConfigurationState(props: AlertsConfigurationSurfacePro
|
||||
setIsReloadingConfig(true);
|
||||
props.setHasUnsavedChanges(false);
|
||||
destinationsState.resetDestinations();
|
||||
|
||||
setGuestDefaults({ ...FACTORY_GUEST_DEFAULTS });
|
||||
setGuestDisableConnectivity(false);
|
||||
setGuestPoweredOffSeverity('warning');
|
||||
setNodeDefaults({ ...FACTORY_NODE_DEFAULTS });
|
||||
setPBSDefaults({ ...FACTORY_PBS_DEFAULTS });
|
||||
setAgentDefaults({ ...FACTORY_AGENT_DEFAULTS });
|
||||
setDockerDefaults({ ...FACTORY_DOCKER_DEFAULTS });
|
||||
setDockerDisableConnectivity(FACTORY_DOCKER_STATE_DISABLE_CONNECTIVITY);
|
||||
setDockerPoweredOffSeverity(FACTORY_DOCKER_STATE_SEVERITY);
|
||||
setDockerIgnoredPrefixes([]);
|
||||
setIgnoredGuestPrefixes([]);
|
||||
setGuestTagWhitelist([]);
|
||||
setGuestTagBlacklist([]);
|
||||
setStorageDefault(FACTORY_STORAGE_DEFAULT);
|
||||
setTimeThresholds({
|
||||
guest: DEFAULT_DELAY_SECONDS,
|
||||
node: DEFAULT_DELAY_SECONDS,
|
||||
storage: DEFAULT_DELAY_SECONDS,
|
||||
pbs: DEFAULT_DELAY_SECONDS,
|
||||
agent: DEFAULT_DELAY_SECONDS,
|
||||
});
|
||||
setMetricTimeThresholds({});
|
||||
setScheduleQuietHours(createDefaultQuietHours());
|
||||
setScheduleCooldown(createDefaultCooldown());
|
||||
setScheduleGrouping(createDefaultGrouping());
|
||||
setScheduleEscalation(createDefaultEscalation());
|
||||
setNotifyOnResolve(createDefaultResolveNotifications());
|
||||
setBackupDefaults({ ...FACTORY_BACKUP_DEFAULTS });
|
||||
setSnapshotDefaults({ ...FACTORY_SNAPSHOT_DEFAULTS });
|
||||
applyConfigurationSnapshot(createDefaultAlertsConfigurationSnapshot());
|
||||
|
||||
try {
|
||||
const config = await AlertsAPI.getConfig();
|
||||
|
||||
if (config.guestDefaults) {
|
||||
setGuestDefaults({
|
||||
cpu: getTriggerValue(config.guestDefaults.cpu) ?? FACTORY_GUEST_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.guestDefaults.memory) ?? FACTORY_GUEST_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.guestDefaults.disk) ?? FACTORY_GUEST_DEFAULTS.disk,
|
||||
diskRead:
|
||||
getTriggerValue(config.guestDefaults.diskRead) ?? FACTORY_GUEST_DEFAULTS.diskRead,
|
||||
diskWrite:
|
||||
getTriggerValue(config.guestDefaults.diskWrite) ?? FACTORY_GUEST_DEFAULTS.diskWrite,
|
||||
networkIn:
|
||||
getTriggerValue(config.guestDefaults.networkIn) ?? FACTORY_GUEST_DEFAULTS.networkIn,
|
||||
networkOut:
|
||||
getTriggerValue(config.guestDefaults.networkOut) ?? FACTORY_GUEST_DEFAULTS.networkOut,
|
||||
});
|
||||
setGuestDisableConnectivity(Boolean(config.guestDefaults.disableConnectivity));
|
||||
setGuestPoweredOffSeverity(
|
||||
config.guestDefaults.poweredOffSeverity === 'critical' ? 'critical' : 'warning',
|
||||
);
|
||||
}
|
||||
|
||||
if (config.nodeDefaults) {
|
||||
setNodeDefaults({
|
||||
cpu: getTriggerValue(config.nodeDefaults.cpu) ?? FACTORY_NODE_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.nodeDefaults.memory) ?? FACTORY_NODE_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.nodeDefaults.disk) ?? FACTORY_NODE_DEFAULTS.disk,
|
||||
temperature:
|
||||
getTriggerValue(config.nodeDefaults.temperature) ?? FACTORY_NODE_DEFAULTS.temperature,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.pbsDefaults) {
|
||||
setPBSDefaults({
|
||||
cpu: getTriggerValue(config.pbsDefaults.cpu) ?? FACTORY_PBS_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.pbsDefaults.memory) ?? FACTORY_PBS_DEFAULTS.memory,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.agentDefaults) {
|
||||
setAgentDefaults({
|
||||
cpu: getTriggerValue(config.agentDefaults.cpu) ?? FACTORY_AGENT_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.agentDefaults.memory) ?? FACTORY_AGENT_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.agentDefaults.disk) ?? FACTORY_AGENT_DEFAULTS.disk,
|
||||
diskTemperature:
|
||||
getTriggerValue(config.agentDefaults.diskTemperature) ??
|
||||
FACTORY_AGENT_DEFAULTS.diskTemperature,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.dockerDefaults) {
|
||||
const normalizeGap = (value: unknown, fallback: number) => {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.max(0, Math.min(100, numeric));
|
||||
};
|
||||
|
||||
const serviceWarnGap = normalizeGap(
|
||||
config.dockerDefaults.serviceWarnGapPercent,
|
||||
FACTORY_DOCKER_DEFAULTS.serviceWarnGapPercent,
|
||||
);
|
||||
let serviceCriticalGap = normalizeGap(
|
||||
config.dockerDefaults.serviceCriticalGapPercent,
|
||||
FACTORY_DOCKER_DEFAULTS.serviceCriticalGapPercent,
|
||||
);
|
||||
if (serviceCriticalGap > 0 && serviceWarnGap > serviceCriticalGap) {
|
||||
serviceCriticalGap = serviceWarnGap;
|
||||
}
|
||||
|
||||
setDockerDefaults({
|
||||
cpu: getTriggerValue(config.dockerDefaults.cpu) ?? FACTORY_DOCKER_DEFAULTS.cpu,
|
||||
memory: getTriggerValue(config.dockerDefaults.memory) ?? FACTORY_DOCKER_DEFAULTS.memory,
|
||||
disk: getTriggerValue(config.dockerDefaults.disk) ?? FACTORY_DOCKER_DEFAULTS.disk,
|
||||
restartCount:
|
||||
config.dockerDefaults.restartCount ?? FACTORY_DOCKER_DEFAULTS.restartCount,
|
||||
restartWindow:
|
||||
config.dockerDefaults.restartWindow ?? FACTORY_DOCKER_DEFAULTS.restartWindow,
|
||||
memoryWarnPct:
|
||||
config.dockerDefaults.memoryWarnPct ?? FACTORY_DOCKER_DEFAULTS.memoryWarnPct,
|
||||
memoryCriticalPct:
|
||||
config.dockerDefaults.memoryCriticalPct ??
|
||||
FACTORY_DOCKER_DEFAULTS.memoryCriticalPct,
|
||||
serviceWarnGapPercent: serviceWarnGap,
|
||||
serviceCriticalGapPercent: serviceCriticalGap,
|
||||
});
|
||||
setDockerDisableConnectivity(Boolean(config.dockerDefaults.stateDisableConnectivity));
|
||||
setDockerPoweredOffSeverity(
|
||||
config.dockerDefaults.statePoweredOffSeverity === 'critical' ? 'critical' : 'warning',
|
||||
);
|
||||
}
|
||||
|
||||
setDockerIgnoredPrefixes(config.dockerIgnoredContainerPrefixes ?? []);
|
||||
setIgnoredGuestPrefixes(config.ignoredGuestPrefixes ?? []);
|
||||
setGuestTagWhitelist(config.guestTagWhitelist ?? []);
|
||||
setGuestTagBlacklist(config.guestTagBlacklist ?? []);
|
||||
|
||||
if (config.storageDefault) {
|
||||
setStorageDefault(getTriggerValue(config.storageDefault) ?? FACTORY_STORAGE_DEFAULT);
|
||||
}
|
||||
if (config.timeThresholds) {
|
||||
setTimeThresholds({
|
||||
guest: config.timeThresholds.guest ?? DEFAULT_DELAY_SECONDS,
|
||||
node: config.timeThresholds.node ?? DEFAULT_DELAY_SECONDS,
|
||||
storage: config.timeThresholds.storage ?? DEFAULT_DELAY_SECONDS,
|
||||
pbs: config.timeThresholds.pbs ?? DEFAULT_DELAY_SECONDS,
|
||||
agent: config.timeThresholds.agent ?? DEFAULT_DELAY_SECONDS,
|
||||
});
|
||||
}
|
||||
if (config.metricTimeThresholds) {
|
||||
setMetricTimeThresholds(normalizeMetricDelayMap(config.metricTimeThresholds));
|
||||
}
|
||||
|
||||
if (config.backupDefaults) {
|
||||
const enabled = Boolean(config.backupDefaults.enabled);
|
||||
const rawWarning =
|
||||
config.backupDefaults.warningDays ?? FACTORY_BACKUP_DEFAULTS.warningDays;
|
||||
const rawCritical =
|
||||
config.backupDefaults.criticalDays ?? FACTORY_BACKUP_DEFAULTS.criticalDays;
|
||||
const safeCritical = Math.max(0, rawCritical);
|
||||
const normalizedWarning = Math.max(0, rawWarning);
|
||||
const warningDays =
|
||||
safeCritical > 0 && normalizedWarning > safeCritical ? safeCritical : normalizedWarning;
|
||||
const criticalDays = Math.max(safeCritical, warningDays);
|
||||
const freshHours =
|
||||
config.backupDefaults.freshHours ?? FACTORY_BACKUP_DEFAULTS.freshHours;
|
||||
const staleHours =
|
||||
config.backupDefaults.staleHours ?? FACTORY_BACKUP_DEFAULTS.staleHours;
|
||||
const alertOrphaned =
|
||||
config.backupDefaults.alertOrphaned ?? FACTORY_BACKUP_DEFAULTS.alertOrphaned ?? true;
|
||||
const ignoreVMIDs = Array.from(
|
||||
new Set(
|
||||
(config.backupDefaults.ignoreVMIDs ?? FACTORY_BACKUP_DEFAULTS.ignoreVMIDs ?? [])
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length > 0),
|
||||
),
|
||||
);
|
||||
setBackupDefaults({
|
||||
enabled,
|
||||
warningDays,
|
||||
criticalDays,
|
||||
freshHours,
|
||||
staleHours,
|
||||
alertOrphaned,
|
||||
ignoreVMIDs,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.snapshotDefaults) {
|
||||
const enabled = Boolean(config.snapshotDefaults.enabled);
|
||||
const rawWarning =
|
||||
config.snapshotDefaults.warningDays ?? FACTORY_SNAPSHOT_DEFAULTS.warningDays;
|
||||
const rawCritical =
|
||||
config.snapshotDefaults.criticalDays ?? FACTORY_SNAPSHOT_DEFAULTS.criticalDays;
|
||||
const safeCritical = Math.max(0, rawCritical);
|
||||
const normalizedWarning = Math.max(0, rawWarning);
|
||||
const warningDays =
|
||||
safeCritical > 0 && normalizedWarning > safeCritical ? safeCritical : normalizedWarning;
|
||||
const criticalDays = Math.max(safeCritical, warningDays);
|
||||
setSnapshotDefaults({ enabled, warningDays, criticalDays });
|
||||
}
|
||||
|
||||
if (config.pmgDefaults) {
|
||||
setPMGThresholds({
|
||||
queueTotalWarning: config.pmgDefaults.queueTotalWarning ?? 500,
|
||||
queueTotalCritical: config.pmgDefaults.queueTotalCritical ?? 1000,
|
||||
oldestMessageWarnMins: config.pmgDefaults.oldestMessageWarnMins ?? 30,
|
||||
oldestMessageCritMins: config.pmgDefaults.oldestMessageCritMins ?? 60,
|
||||
deferredQueueWarn: config.pmgDefaults.deferredQueueWarn ?? 200,
|
||||
deferredQueueCritical: config.pmgDefaults.deferredQueueCritical ?? 500,
|
||||
holdQueueWarn: config.pmgDefaults.holdQueueWarn ?? 100,
|
||||
holdQueueCritical: config.pmgDefaults.holdQueueCritical ?? 300,
|
||||
quarantineSpamWarn: config.pmgDefaults.quarantineSpamWarn ?? 2000,
|
||||
quarantineSpamCritical: config.pmgDefaults.quarantineSpamCritical ?? 5000,
|
||||
quarantineVirusWarn: config.pmgDefaults.quarantineVirusWarn ?? 2000,
|
||||
quarantineVirusCritical: config.pmgDefaults.quarantineVirusCritical ?? 5000,
|
||||
quarantineGrowthWarnPct: config.pmgDefaults.quarantineGrowthWarnPct ?? 25,
|
||||
quarantineGrowthWarnMin: config.pmgDefaults.quarantineGrowthWarnMin ?? 250,
|
||||
quarantineGrowthCritPct: config.pmgDefaults.quarantineGrowthCritPct ?? 50,
|
||||
quarantineGrowthCritMin: config.pmgDefaults.quarantineGrowthCritMin ?? 500,
|
||||
});
|
||||
}
|
||||
|
||||
setDisableAllNodes(config.disableAllNodes ?? false);
|
||||
setDisableAllGuests(config.disableAllGuests ?? false);
|
||||
setDisableAllAgents(config.disableAllAgents ?? false);
|
||||
setDisableAllStorage(config.disableAllStorage ?? false);
|
||||
setDisableAllPBS(config.disableAllPBS ?? false);
|
||||
setDisableAllPMG(config.disableAllPMG ?? false);
|
||||
setDisableAllDockerHosts(config.disableAllDockerHosts ?? false);
|
||||
setDisableAllDockerServices(config.disableAllDockerServices ?? false);
|
||||
setDisableAllDockerContainers(config.disableAllDockerContainers ?? false);
|
||||
setDisableAllNodesOffline(config.disableAllNodesOffline ?? false);
|
||||
setDisableAllGuestsOffline(config.disableAllGuestsOffline ?? false);
|
||||
setDisableAllAgentsOffline(config.disableAllAgentsOffline ?? false);
|
||||
setDisableAllPBSOffline(config.disableAllPBSOffline ?? false);
|
||||
setDisableAllPMGOffline(config.disableAllPMGOffline ?? false);
|
||||
setDisableAllDockerHostsOffline(config.disableAllDockerHostsOffline ?? false);
|
||||
applyConfigurationSnapshot(readAlertsConfigurationSnapshot(config));
|
||||
|
||||
overridesState.replaceRawOverridesConfig(config.overrides || {});
|
||||
|
||||
if (config.schedule) {
|
||||
if (config.schedule.quietHours) {
|
||||
const quietHours = config.schedule.quietHours;
|
||||
const days = Array.isArray(quietHours.days)
|
||||
? {
|
||||
sunday: quietHours.days.includes(0),
|
||||
monday: quietHours.days.includes(1),
|
||||
tuesday: quietHours.days.includes(2),
|
||||
wednesday: quietHours.days.includes(3),
|
||||
thursday: quietHours.days.includes(4),
|
||||
friday: quietHours.days.includes(5),
|
||||
saturday: quietHours.days.includes(6),
|
||||
}
|
||||
: ((quietHours.days as Record<string, boolean>) || createDefaultQuietHours().days);
|
||||
setScheduleQuietHours({
|
||||
enabled: quietHours.enabled || false,
|
||||
start: quietHours.start || '22:00',
|
||||
end: quietHours.end || '08:00',
|
||||
timezone:
|
||||
quietHours.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
|
||||
days,
|
||||
suppress: {
|
||||
performance: quietHours.suppress?.performance ?? false,
|
||||
storage: quietHours.suppress?.storage ?? false,
|
||||
offline: quietHours.suppress?.offline ?? false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (config.schedule.cooldown !== undefined) {
|
||||
const rawCooldown = config.schedule.cooldown;
|
||||
const cooldownEnabled = rawCooldown > 0;
|
||||
setScheduleCooldown({
|
||||
enabled: cooldownEnabled,
|
||||
minutes: cooldownEnabled ? clampCooldownMinutes(rawCooldown) : 0,
|
||||
maxAlerts: fallbackMaxAlertsPerHour(config.schedule.maxAlertsHour),
|
||||
});
|
||||
}
|
||||
|
||||
if (config.schedule.grouping) {
|
||||
const groupingConfig = config.schedule.grouping;
|
||||
const rawGroupingWindowSeconds =
|
||||
typeof groupingConfig.window === 'number'
|
||||
? groupingConfig.window
|
||||
: GROUPING_WINDOW_DEFAULT_SECONDS;
|
||||
const normalizedGroupingWindowSeconds = Math.max(0, rawGroupingWindowSeconds);
|
||||
setScheduleGrouping({
|
||||
enabled:
|
||||
groupingConfig.enabled !== undefined
|
||||
? Boolean(groupingConfig.enabled)
|
||||
: normalizedGroupingWindowSeconds > 0,
|
||||
window: Math.round(normalizedGroupingWindowSeconds / 60),
|
||||
byNode: groupingConfig.byNode !== undefined ? groupingConfig.byNode : true,
|
||||
byGuest: groupingConfig.byGuest !== undefined ? groupingConfig.byGuest : false,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.schedule.notifyOnResolve !== undefined) {
|
||||
setNotifyOnResolve(Boolean(config.schedule.notifyOnResolve));
|
||||
}
|
||||
|
||||
if (config.schedule.escalation) {
|
||||
const levels = (config.schedule.escalation.levels || []).map((level) => ({
|
||||
after: typeof level.after === 'number' ? level.after : 15,
|
||||
notify: (level.notify as EscalationNotifyTarget) || 'all',
|
||||
}));
|
||||
setScheduleEscalation({
|
||||
enabled: Boolean(config.schedule.escalation.enabled),
|
||||
levels,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await destinationsState.loadDestinations();
|
||||
|
||||
if (options.notify) {
|
||||
@@ -581,182 +338,18 @@ export function useAlertsConfigurationState(props: AlertsConfigurationSurfacePro
|
||||
};
|
||||
|
||||
const saveAlertConfiguration = async () => {
|
||||
const createHysteresisThreshold = (trigger: number | undefined, clearMargin = 5) => {
|
||||
const normalized = typeof trigger === 'number' ? trigger : 0;
|
||||
return {
|
||||
trigger: normalized,
|
||||
clear: Math.max(0, normalized - clearMargin),
|
||||
};
|
||||
};
|
||||
|
||||
const snapshotConfig = snapshotDefaults();
|
||||
const normalizedSnapshotWarning = Math.max(0, snapshotConfig.warningDays ?? 0);
|
||||
const normalizedSnapshotCritical = Math.max(0, snapshotConfig.criticalDays ?? 0);
|
||||
const finalSnapshotCritical =
|
||||
normalizedSnapshotCritical > 0
|
||||
? Math.max(normalizedSnapshotCritical, normalizedSnapshotWarning)
|
||||
: normalizedSnapshotWarning;
|
||||
|
||||
const backupConfig = backupDefaults();
|
||||
const normalizedBackupWarning = Math.max(0, backupConfig.warningDays ?? 0);
|
||||
const normalizedBackupCritical = Math.max(0, backupConfig.criticalDays ?? 0);
|
||||
const finalBackupCritical =
|
||||
normalizedBackupCritical > 0
|
||||
? Math.max(normalizedBackupCritical, normalizedBackupWarning)
|
||||
: normalizedBackupWarning;
|
||||
|
||||
const dockerDefaultsValue = dockerDefaults();
|
||||
if (
|
||||
dockerDefaultsValue.serviceCriticalGapPercent > 0 &&
|
||||
dockerDefaultsValue.serviceWarnGapPercent > dockerDefaultsValue.serviceCriticalGapPercent
|
||||
) {
|
||||
notificationStore.error(
|
||||
'Swarm service critical gap must be greater than or equal to the warning gap when enabled.',
|
||||
);
|
||||
const result = buildAlertsConfigurationPayload({
|
||||
snapshot: captureConfigurationSnapshot(),
|
||||
rawOverridesConfig: overridesState.rawOverridesConfig(),
|
||||
alertsActivationState: props.alertsActivationState(),
|
||||
alertsActivationConfig: props.alertsActivationConfig(),
|
||||
});
|
||||
if (result.dockerValidationError) {
|
||||
notificationStore.error(ALERT_DOCKER_GAP_VALIDATION_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedCooldownMinutes = scheduleCooldown().enabled
|
||||
? clampCooldownMinutes(scheduleCooldown().minutes)
|
||||
: 0;
|
||||
const normalizedMaxAlertsHour = clampMaxAlertsPerHour(scheduleCooldown().maxAlerts);
|
||||
const groupingState = scheduleGrouping();
|
||||
const groupingWindowSeconds =
|
||||
groupingState.enabled && groupingState.window >= 0 ? groupingState.window * 60 : 0;
|
||||
const groupingEnabled = groupingState.enabled && groupingWindowSeconds > 0;
|
||||
const activationConfig = props.alertsActivationConfig();
|
||||
|
||||
const alertConfig = {
|
||||
enabled: activationConfig?.enabled ?? true,
|
||||
activationState: props.alertsActivationState() ?? undefined,
|
||||
activationTime: activationConfig?.activationTime ?? undefined,
|
||||
observationWindowHours: activationConfig?.observationWindowHours ?? undefined,
|
||||
disableAllNodes: disableAllNodes(),
|
||||
disableAllGuests: disableAllGuests(),
|
||||
disableAllAgents: disableAllAgents(),
|
||||
disableAllStorage: disableAllStorage(),
|
||||
disableAllPBS: disableAllPBS(),
|
||||
disableAllPMG: disableAllPMG(),
|
||||
disableAllDockerHosts: disableAllDockerHosts(),
|
||||
disableAllDockerContainers: disableAllDockerContainers(),
|
||||
disableAllDockerServices: disableAllDockerServices(),
|
||||
disableAllNodesOffline: disableAllNodesOffline(),
|
||||
disableAllGuestsOffline: disableAllGuestsOffline(),
|
||||
disableAllPBSOffline: disableAllPBSOffline(),
|
||||
disableAllAgentsOffline: disableAllAgentsOffline(),
|
||||
disableAllPMGOffline: disableAllPMGOffline(),
|
||||
disableAllDockerHostsOffline: disableAllDockerHostsOffline(),
|
||||
guestDefaults: {
|
||||
cpu: createHysteresisThreshold(guestDefaults().cpu),
|
||||
memory: createHysteresisThreshold(guestDefaults().memory),
|
||||
disk: createHysteresisThreshold(guestDefaults().disk),
|
||||
diskRead: createHysteresisThreshold(guestDefaults().diskRead),
|
||||
diskWrite: createHysteresisThreshold(guestDefaults().diskWrite),
|
||||
networkIn: createHysteresisThreshold(guestDefaults().networkIn),
|
||||
networkOut: createHysteresisThreshold(guestDefaults().networkOut),
|
||||
disableConnectivity: guestDisableConnectivity(),
|
||||
poweredOffSeverity: guestPoweredOffSeverity(),
|
||||
},
|
||||
nodeDefaults: {
|
||||
cpu: createHysteresisThreshold(nodeDefaults().cpu),
|
||||
memory: createHysteresisThreshold(nodeDefaults().memory),
|
||||
disk: createHysteresisThreshold(nodeDefaults().disk),
|
||||
temperature: createHysteresisThreshold(nodeDefaults().temperature),
|
||||
},
|
||||
agentDefaults: {
|
||||
cpu: createHysteresisThreshold(agentDefaults().cpu),
|
||||
memory: createHysteresisThreshold(agentDefaults().memory),
|
||||
disk: createHysteresisThreshold(agentDefaults().disk),
|
||||
diskTemperature: createHysteresisThreshold(agentDefaults().diskTemperature),
|
||||
},
|
||||
pbsDefaults: {
|
||||
cpu: createHysteresisThreshold(pbsDefaults().cpu),
|
||||
memory: createHysteresisThreshold(pbsDefaults().memory),
|
||||
},
|
||||
dockerDefaults: {
|
||||
cpu: createHysteresisThreshold(dockerDefaultsValue.cpu),
|
||||
memory: createHysteresisThreshold(dockerDefaultsValue.memory),
|
||||
disk: createHysteresisThreshold(dockerDefaultsValue.disk),
|
||||
restartCount: dockerDefaultsValue.restartCount,
|
||||
restartWindow: dockerDefaultsValue.restartWindow,
|
||||
memoryWarnPct: dockerDefaultsValue.memoryWarnPct,
|
||||
memoryCriticalPct: dockerDefaultsValue.memoryCriticalPct,
|
||||
serviceWarnGapPercent: dockerDefaultsValue.serviceWarnGapPercent,
|
||||
serviceCriticalGapPercent: dockerDefaultsValue.serviceCriticalGapPercent,
|
||||
stateDisableConnectivity: dockerDisableConnectivity(),
|
||||
statePoweredOffSeverity: dockerPoweredOffSeverity(),
|
||||
},
|
||||
dockerIgnoredContainerPrefixes: dockerIgnoredPrefixes()
|
||||
.map((prefix) => prefix.trim())
|
||||
.filter((prefix) => prefix.length > 0),
|
||||
ignoredGuestPrefixes: ignoredGuestPrefixes()
|
||||
.map((prefix) => prefix.trim())
|
||||
.filter((prefix) => prefix.length > 0),
|
||||
guestTagWhitelist: guestTagWhitelist()
|
||||
.map((tag) => tag.trim())
|
||||
.filter((tag) => tag.length > 0),
|
||||
guestTagBlacklist: guestTagBlacklist()
|
||||
.map((tag) => tag.trim())
|
||||
.filter((tag) => tag.length > 0),
|
||||
storageDefault: createHysteresisThreshold(storageDefault()),
|
||||
minimumDelta: 2.0,
|
||||
suppressionWindow: 5,
|
||||
hysteresisMargin: 5.0,
|
||||
timeThresholds: timeThresholds(),
|
||||
metricTimeThresholds: normalizeMetricDelayMap(metricTimeThresholds()),
|
||||
snapshotDefaults: {
|
||||
enabled: snapshotConfig.enabled,
|
||||
warningDays: normalizedSnapshotWarning,
|
||||
criticalDays: finalSnapshotCritical,
|
||||
},
|
||||
backupDefaults: {
|
||||
enabled: backupConfig.enabled,
|
||||
warningDays: normalizedBackupWarning,
|
||||
criticalDays: finalBackupCritical,
|
||||
freshHours: backupConfig.freshHours ?? 24,
|
||||
staleHours: backupConfig.staleHours ?? 72,
|
||||
alertOrphaned: backupConfig.alertOrphaned ?? true,
|
||||
ignoreVMIDs: (backupConfig.ignoreVMIDs ?? [])
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length > 0),
|
||||
},
|
||||
pmgDefaults: pmgThresholds(),
|
||||
overrides: overridesState.rawOverridesConfig(),
|
||||
schedule: {
|
||||
quietHours: scheduleQuietHours(),
|
||||
cooldown: normalizedCooldownMinutes,
|
||||
notifyOnResolve: notifyOnResolve(),
|
||||
maxAlertsHour: normalizedMaxAlertsHour,
|
||||
escalation: scheduleEscalation(),
|
||||
grouping: {
|
||||
enabled: groupingEnabled,
|
||||
window: groupingWindowSeconds,
|
||||
byNode: groupingState.byNode,
|
||||
byGuest: groupingState.byGuest,
|
||||
},
|
||||
},
|
||||
aggregation: {
|
||||
enabled: true,
|
||||
timeWindow: 10,
|
||||
countThreshold: 3,
|
||||
similarityWindow: 5.0,
|
||||
},
|
||||
flapping: {
|
||||
enabled: true,
|
||||
threshold: 5,
|
||||
window: 10,
|
||||
suppressionTime: 30,
|
||||
minStability: 0.8,
|
||||
},
|
||||
ioNormalization: {
|
||||
enabled: true,
|
||||
vmDiskMax: 500.0,
|
||||
containerDiskMax: 300.0,
|
||||
networkMax: 1000.0,
|
||||
},
|
||||
};
|
||||
|
||||
await AlertsAPI.updateConfig(alertConfig);
|
||||
await AlertsAPI.updateConfig(result.alertConfig!);
|
||||
|
||||
await destinationsState.saveDestinations();
|
||||
props.setHasUnsavedChanges(false);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import alertsPageSource from '@/pages/Alerts.tsx?raw';
|
||||
import alertsConfigurationSurfaceSource from '@/features/alerts/AlertsConfigurationSurface.tsx?raw';
|
||||
import alertsConfigurationStateSource from '@/features/alerts/useAlertsConfigurationState.ts?raw';
|
||||
import alertsConfigurationModelSource from '@/features/alerts/alertsConfigurationModel.ts?raw';
|
||||
import alertOverridesStateSource from '@/features/alerts/useAlertOverridesState.ts?raw';
|
||||
import alertDestinationsStateSource from '@/features/alerts/useAlertDestinationsState.ts?raw';
|
||||
import alertDestinationsTabStateSource from '@/features/alerts/useAlertDestinationsTabState.ts?raw';
|
||||
@@ -196,16 +197,32 @@ describe('tab path helpers', () => {
|
||||
expect(alertsConfigurationSurfaceSource).not.toContain("eventBus.on('org_switched'");
|
||||
expect(alertsConfigurationStateSource).toContain('export function useAlertsConfigurationState');
|
||||
expect(alertsConfigurationStateSource).toContain('AlertsAPI.getConfig');
|
||||
expect(alertsConfigurationStateSource).toContain('createDefaultAlertsConfigurationSnapshot');
|
||||
expect(alertsConfigurationStateSource).toContain('readAlertsConfigurationSnapshot');
|
||||
expect(alertsConfigurationStateSource).toContain('buildAlertsConfigurationPayload');
|
||||
expect(alertsConfigurationStateSource).toContain('useAlertDestinationsState');
|
||||
expect(alertsConfigurationStateSource).toContain('useAlertOverridesState');
|
||||
expect(alertsConfigurationStateSource).not.toContain('NotificationsAPI.getEmailConfig');
|
||||
expect(alertsConfigurationStateSource).not.toContain('NotificationsAPI.updateEmailConfig');
|
||||
expect(alertsConfigurationStateSource).toContain("eventBus.on('org_switched'");
|
||||
expect(alertsConfigurationStateSource).not.toContain('const createHysteresisThreshold =');
|
||||
expect(alertsConfigurationStateSource).not.toContain('const normalizeGap =');
|
||||
expect(alertsConfigurationStateSource).not.toContain(
|
||||
'const [rawOverridesConfig, setRawOverridesConfig] = createSignal',
|
||||
);
|
||||
expect(alertsConfigurationStateSource).not.toContain('const hostOverrideIdCandidates =');
|
||||
expect(alertsConfigurationStateSource).not.toContain('const allGuests = createMemo');
|
||||
expect(alertsConfigurationModelSource).toContain(
|
||||
'export function createDefaultAlertsConfigurationSnapshot',
|
||||
);
|
||||
expect(alertsConfigurationModelSource).toContain(
|
||||
'export function readAlertsConfigurationSnapshot',
|
||||
);
|
||||
expect(alertsConfigurationModelSource).toContain(
|
||||
'export function buildAlertsConfigurationPayload',
|
||||
);
|
||||
expect(alertsConfigurationModelSource).toContain('const createHysteresisThreshold =');
|
||||
expect(alertsConfigurationModelSource).toContain('const normalizeGap =');
|
||||
expect(alertOverridesStateSource).toContain('export function useAlertOverridesState');
|
||||
expect(alertOverridesStateSource).toContain('getActionableAgentIdFromResource');
|
||||
expect(alertOverridesStateSource).toContain('pbsInstanceFromResource');
|
||||
|
||||
@@ -326,6 +326,7 @@ import alertWebhookPresentationSource from '@/utils/alertWebhookPresentation.ts?
|
||||
import alertOverviewTabSource from '@/features/alerts/OverviewTab.tsx?raw';
|
||||
import alertsConfigurationSurfaceSource from '@/features/alerts/AlertsConfigurationSurface.tsx?raw';
|
||||
import alertsConfigurationStateSource from '@/features/alerts/useAlertsConfigurationState.ts?raw';
|
||||
import alertsConfigurationModelSource from '@/features/alerts/alertsConfigurationModel.ts?raw';
|
||||
import alertOverridesStateSource from '@/features/alerts/useAlertOverridesState.ts?raw';
|
||||
import alertDestinationsStateSource from '@/features/alerts/useAlertDestinationsState.ts?raw';
|
||||
import alertDestinationsTabStateSource from '@/features/alerts/useAlertDestinationsTabState.ts?raw';
|
||||
@@ -2774,16 +2775,32 @@ describe('frontend resource type boundaries', () => {
|
||||
expect(alertsConfigurationSurfaceSource).toContain('useAlertsConfigurationState');
|
||||
expect(alertsConfigurationSurfaceSource).not.toContain('AlertsAPI.getConfig');
|
||||
expect(alertsConfigurationStateSource).toContain('AlertsAPI.getConfig');
|
||||
expect(alertsConfigurationStateSource).toContain('createDefaultAlertsConfigurationSnapshot');
|
||||
expect(alertsConfigurationStateSource).toContain('readAlertsConfigurationSnapshot');
|
||||
expect(alertsConfigurationStateSource).toContain('buildAlertsConfigurationPayload');
|
||||
expect(alertsConfigurationStateSource).toContain('useAlertDestinationsState');
|
||||
expect(alertsConfigurationStateSource).toContain('useAlertOverridesState');
|
||||
expect(alertsConfigurationStateSource).not.toContain('NotificationsAPI.getEmailConfig');
|
||||
expect(alertsConfigurationStateSource).not.toContain('NotificationsAPI.updateEmailConfig');
|
||||
expect(alertsConfigurationStateSource).toContain("eventBus.on('org_switched'");
|
||||
expect(alertsConfigurationStateSource).not.toContain('const createHysteresisThreshold =');
|
||||
expect(alertsConfigurationStateSource).not.toContain('const normalizeGap =');
|
||||
expect(alertsConfigurationStateSource).not.toContain(
|
||||
'const [rawOverridesConfig, setRawOverridesConfig] = createSignal',
|
||||
);
|
||||
expect(alertsConfigurationStateSource).not.toContain('const hostOverrideIdCandidates =');
|
||||
expect(alertsConfigurationStateSource).not.toContain('const allGuests = createMemo');
|
||||
expect(alertsConfigurationModelSource).toContain(
|
||||
'export function createDefaultAlertsConfigurationSnapshot',
|
||||
);
|
||||
expect(alertsConfigurationModelSource).toContain(
|
||||
'export function readAlertsConfigurationSnapshot',
|
||||
);
|
||||
expect(alertsConfigurationModelSource).toContain(
|
||||
'export function buildAlertsConfigurationPayload',
|
||||
);
|
||||
expect(alertsConfigurationModelSource).toContain('const createHysteresisThreshold =');
|
||||
expect(alertsConfigurationModelSource).toContain('const normalizeGap =');
|
||||
expect(alertOverridesStateSource).toContain('export function useAlertOverridesState');
|
||||
expect(alertOverridesStateSource).toContain('getActionableAgentIdFromResource');
|
||||
expect(alertOverridesStateSource).toContain('pbsInstanceFromResource');
|
||||
@@ -3858,6 +3875,7 @@ describe('frontend resource type boundaries', () => {
|
||||
"import { ScheduleTab } from './tabs/ScheduleTab';",
|
||||
);
|
||||
expect(alertsConfigurationSurfaceSource).toContain('useAlertsConfigurationState');
|
||||
expect(alertsConfigurationStateSource).toContain('buildAlertsConfigurationPayload');
|
||||
expect(alertsPageSource).not.toContain('function DestinationsTab(');
|
||||
expect(alertsPageSource).not.toContain('function ScheduleTab(');
|
||||
expect(alertDestinationsTabSource).toContain('useAlertDestinationsTabState');
|
||||
|
||||
Reference in New Issue
Block a user