diff --git a/docs/API.md b/docs/API.md index b8645ea..ee902c5 100644 --- a/docs/API.md +++ b/docs/API.md @@ -657,6 +657,7 @@ Alert configuration responses model Pulse's hysteresis thresholds and advanced b - `guestDefaults`, `nodeDefaults`, `storageDefault`, `dockerDefaults`, `pmgThresholds` expose the baseline trigger/clear values applied globally. Each metric uses `{ "trigger": 90, "clear": 85 }`, so fractional thresholds (e.g. `12.5`) are supported. - `overrides` is keyed by resource ID for bespoke thresholds. Setting a threshold to `-1` disables that signal for that resource. - `timeThresholds` and `metricTimeThresholds` provide per-resource/per-metric grace periods, reducing alert noise on bursty workloads. +- `dockerIgnoredContainerPrefixes` suppresses alerts for ephemeral containers whose name or ID begins with a listed prefix. Matching is case-insensitive and controlled through the Alerts UI. - `aggregation`, `flapping`, `schedule` configure deduplication, cooldown, and quiet hours. These values are shared with the notification pipeline. - Active and historical alerts include `metadata.clearThreshold`, `resourceType`, and other context so UIs can render the trigger/clear pair and supply timeline explanations. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 566cf69..af15401 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -200,6 +200,10 @@ PROXY_AUTH_LOGOUT_URL=/logout # URL for SSO logout "restartCount": 3, "restartWindow": 300 }, + "dockerIgnoredContainerPrefixes": [ + "runner-", + "ci-temp-" + ], "pmgThresholds": { "queueTotalWarning": 500, "oldestMessageWarnMins": 30 @@ -249,6 +253,7 @@ PROXY_AUTH_LOGOUT_URL=/logout # URL for SSO logout - Set a metric to `-1` to disable it globally or per-resource (the UI shows “Off” and adds a **Custom** badge). - `timeThresholds` apply a grace period before an alert fires; `metricTimeThresholds` allow per-metric overrides (e.g., delay network alerts longer than CPU). - `overrides` are indexed by the stable resource ID returned from `/api/state` (VMs: `instance/qemu/vmid`, containers: `instance/lxc/ctid`, nodes: `instance/node`). +- `dockerIgnoredContainerPrefixes` lets you silence state/metric/restart alerts for ephemeral containers whose names or IDs share a common, case-insensitive prefix. The Docker tab in the UI keeps this list in sync. - Quiet hours, escalation, deduplication, and restart loop detection are all managed here, and the UI keeps the JSON in sync automatically. > Tip: Back up `alerts.json` alongside `.env` during exports. Restoring it preserves all overrides, quiet-hour schedules, and webhook routing. diff --git a/docs/DOCKER_MONITORING.md b/docs/DOCKER_MONITORING.md index 00fccd2..18399ec 100644 --- a/docs/DOCKER_MONITORING.md +++ b/docs/DOCKER_MONITORING.md @@ -137,6 +137,10 @@ docker run -d \ The agent automatically discovers the Docker socket via the usual environment variables. To use SSH tunnels or TCP sockets, export `DOCKER_HOST` as you would for the Docker CLI. +### Suppressing ephemeral containers + +CI runners and short-lived build containers can generate noisy state alerts when they exit on schedule. In Pulse v4.24.0 and later you can provide a list of prefixes to ignore under **Alerts → Thresholds → Docker → Ignored container prefixes**. Any container whose name *or* ID begins with a configured prefix is skipped for state, health, metric, restart-loop, and OOM alerts. Matching is case-insensitive and the list is saved as `dockerIgnoredContainerPrefixes` inside `alerts.json`. Use one entry per family of ephemeral containers (for example, `runner-` or `gitlab-job-`). + ## Testing and troubleshooting - Run with `--interval 15s --insecure` in a terminal to see log output while testing. diff --git a/frontend-modern/package-lock.json b/frontend-modern/package-lock.json index 100f1d4..2f62f0a 100644 Binary files a/frontend-modern/package-lock.json and b/frontend-modern/package-lock.json differ diff --git a/frontend-modern/package.json b/frontend-modern/package.json index 4cfaf96..f5219bc 100644 --- a/frontend-modern/package.json +++ b/frontend-modern/package.json @@ -18,6 +18,7 @@ "build": "vite build", "preview": "vite preview", "generate-types": "cd ../scripts && go run generate-types.go", + "test": "vitest run", "type-check": "tsc --noEmit", "lint": "eslint \"src/**/*.{ts,tsx}\"", "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix", @@ -39,11 +40,15 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-solid": "^0.14.0", + "@solidjs/testing-library": "^0.8.5", + "@testing-library/jest-dom": "^6.5.0", + "jsdom": "^24.1.0", "postcss": "^8.4.0", "prettier": "^3.3.0", "tailwindcss": "^3.4.0", "typescript": "^5.3.0", "vite": "^6.3.5", - "vite-plugin-solid": "^2.8.0" + "vite-plugin-solid": "^2.8.0", + "vitest": "^2.1.9" } } diff --git a/frontend-modern/src/components/Alerts/ThresholdsTable.tsx b/frontend-modern/src/components/Alerts/ThresholdsTable.tsx index 4e82813..990addd 100644 --- a/frontend-modern/src/components/Alerts/ThresholdsTable.tsx +++ b/frontend-modern/src/components/Alerts/ThresholdsTable.tsx @@ -97,6 +97,12 @@ const PMG_KEY_TO_NORMALIZED = new Map( PMG_THRESHOLD_COLUMNS.map((column) => [column.key, column.normalized]), ); +export const normalizeDockerIgnoredInput = (value: string): string[] => + value + .split('\n') + .map((entry) => entry.trim()) + .filter((entry) => entry.length > 0); + // Simple threshold object for the UI interface SimpleThresholds { cpu?: number; @@ -143,11 +149,14 @@ interface ThresholdsTableProps { setDockerDefaults: ( value: { cpu: number; memory: number; restartCount: number; restartWindow: number; memoryWarnPct: number; memoryCriticalPct: number } | ((prev: { cpu: number; memory: number; restartCount: number; restartWindow: number; memoryWarnPct: number; memoryCriticalPct: number }) => { cpu: number; memory: number; restartCount: number; restartWindow: number; memoryWarnPct: number; memoryCriticalPct: number }), ) => void; + dockerIgnoredPrefixes: () => string[]; + setDockerIgnoredPrefixes: (value: string[] | ((prev: string[]) => string[])) => void; storageDefault: () => number; setStorageDefault: (value: number) => void; resetGuestDefaults?: () => void; resetNodeDefaults?: () => void; resetDockerDefaults?: () => void; + resetDockerIgnoredPrefixes?: () => void; resetStorageDefault?: () => void; factoryGuestDefaults?: Record; factoryNodeDefaults?: Record; @@ -202,6 +211,13 @@ export function ThresholdsTable(props: ThresholdsTableProps) { >({}); const [activeTab, setActiveTab] = createSignal<'proxmox' | 'pmg' | 'docker'>('proxmox'); let searchInputRef: HTMLInputElement | undefined; + const [dockerIgnoredInput, setDockerIgnoredInput] = createSignal( + props.dockerIgnoredPrefixes().join('\n'), + ); + + createEffect(() => { + setDockerIgnoredInput(props.dockerIgnoredPrefixes().join('\n')); + }); // Determine active tab from URL const getActiveTabFromRoute = (): 'proxmox' | 'pmg' | 'docker' => { @@ -235,6 +251,23 @@ export function ThresholdsTable(props: ThresholdsTableProps) { navigate(tabRoutes[tab]); }; + const handleDockerIgnoredChange = (value: string) => { + setDockerIgnoredInput(value); + const normalized = normalizeDockerIgnoredInput(value); + props.setDockerIgnoredPrefixes(normalized); + props.setHasUnsavedChanges(true); + }; + + const handleResetDockerIgnored = () => { + if (props.resetDockerIgnoredPrefixes) { + props.resetDockerIgnoredPrefixes(); + } else { + props.setDockerIgnoredPrefixes([]); + } + setDockerIgnoredInput(''); + props.setHasUnsavedChanges(true); + }; + // Set up keyboard shortcuts onMount(() => { const isEditableElement = (el: HTMLElement | null | undefined): boolean => { @@ -1952,6 +1985,36 @@ const dockerContainersGroupedByHost = createMemo>((pr +
+
+
+

+ Ignored container prefixes +

+

+ Containers whose name or ID starts with any prefix below are skipped for Docker + alerts. Enter one prefix per line; matching is case-insensitive. +

+
+ 0}> + + +
+