diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md
index d441026bb5..0a133cf8fe 100644
--- a/docs/release-control/v6/internal/subsystems/alerts.md
+++ b/docs/release-control/v6/internal/subsystems/alerts.md
@@ -2901,6 +2901,30 @@ severity and the triggered-in-24h total; threshold override counts are
configuration and do not appear on the overview. None of this changes alert
lifecycle, acknowledgement, snooze, or delivery evaluation.
+### Thresholds lead with each group's default limits
+
+The Thresholds page answers "what will alert me, and at what limits" before
+any group is opened. Every collapsed threshold group whose table edits global
+defaults states them in its header through
+`getAlertThresholdsDefaultsSummary` in
+`frontend-modern/src/utils/alertThresholdsSectionPresentation.ts`. The helper
+reads the same column list and defaults object the group's resource table
+edits (each section hoists its columns into one module constant), and drops
+every metric `isAlertResourceMetricOff` reports, so the header can never show
+a limit the engine is not evaluating. Rules whose zero means a fallback rather
+than Off are stated as whole rules with their effective values: Docker's
+restart loop (count within its window) and memory-limit warn/critical, which
+`NormalizeDockerDefaults` always resolves to positive values. Backup age is
+summarized by its warning and critical days only, because fresh and stale
+hours colour the dashboard and never alert. A group switched off as a whole
+shows its Disabled badge and no defaults line. The CPU averaging line counts
+every configured profile override, including ones only the API sets. CPU averaging is tuning, not the page's
+answer. It is one collapsed disclosure stating the effective window and the
+number of platform overrides, with the same per-profile selects inside. Docker
+and Machines list their resource groups before Docker-only rules and the
+SMART card. Threshold values, inheritance, metric evaluation windows and
+persistence are unchanged.
+
### External watchdog is Pulse-availability evidence, not notification delivery
The Alerts destinations surface owns the operator contract for an external
diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index 8224593ad9..2fad960654 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -7453,6 +7453,11 @@ presentation. The compact row remains actionable: plain-language rationale and
provider history/permission limitations live one disclosure deeper beside
bounded restore evidence. A posture fetch failure must keep the evidence
inspectable and show unknown, never a locally inferred healthy fallback.
+The bounded restore evidence keeps the newest point and the newest completed
+point of each source before filling with the newest overall, so a run of guest
+snapshots or a running job cannot push the latest usable backup out of the
+disclosure. A coverage strip footnote is
+plain muted text under the legend, not another counter or badge row.
Protection table controls continue to compose shared filter, table, status,
counter, loading, error, disclosure, and inline-detail primitives. The bounded
@@ -8372,3 +8377,11 @@ regressions preserve their original missing-value and fallback obligations.
drawer with synthetic APIs in Chromium/WebKit desktop and 390/320-pixel light
and dark views. This is presentation acceptance, not native backup/QGA safety,
provider outage attribution, installed recovery or release availability.
+
+### Threshold group headers carry a defaults line
+
+`CollapsibleSection` takes an optional `defaultsSummary` rendered under the
+title as a muted line (`data-thresholds-defaults-summary`) that wraps only
+between items on phones and truncates from `sm` up, and is hidden while
+`isGloballyDisabled` is set, where it would promise alerts that cannot fire. Callers pass the shared summary helper's output rather than
+composing per-section strings.
diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md
index ccc9bf860a..890572cc5c 100644
--- a/docs/release-control/v6/internal/subsystems/storage-recovery.md
+++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md
@@ -171,6 +171,16 @@ backup files, never guest-local snapshots. Guest snapshots remain listed as
restore evidence but cannot make an old independent backup look current.
Archive detail uses its format (or filename fallback), preserving the full
provider volid for hover and search in both Coverage and By date.
+Coverage rows carry that one counted backup age at every width; per-source
+PBS, PVE-file and guest-snapshot age columns are not row columns, because a
+fresh snapshot beside a stale backup read as protection that posture does not
+grant. The row expansion keeps the newest point and the newest completed point
+of every source inside its bounded evidence list, so neither recent snapshots
+nor a running or failed job hide the latest usable backup. The Backup health
+strip states what its attention, unprotected and unknown counts mean from the
+server posture policy (`freshnessWindowSeconds`) using the causes
+`internal/recovery/posture.go` evaluates, and renders no rule until that policy
+has loaded rather than a guessed window.
Phone pool/disk rows preserve health and identity while using weighted widths,
consistent gutters and compact capacity/health labels. Full disk risk reasons
diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md
index 208cee1e54..591f8565d5 100644
--- a/docs/release-control/v6/internal/subsystems/unified-resources.md
+++ b/docs/release-control/v6/internal/subsystems/unified-resources.md
@@ -3313,7 +3313,9 @@ metadata.
### Protection posture identity consumer
`ProxmoxCoverageTable` remains a unified-resource identity consumer while
-storage/recovery owns protection truth. Live VM/LXC rows carry the exact
+storage/recovery owns protection truth. Dropping the per-source age columns
+from its rows changes no identity input: rows still key posture by the exact
+canonical `Resource.id`. Live VM/LXC rows carry the exact
canonical `Resource.id` into one bounded posture batch; the table must not parse
its presentation key, VMID, name, node, or instance to mint a replacement
resource identity. Host and orphaned backup artifacts have no canonical live
diff --git a/frontend-modern/browser-tests/operator-tables-consent.tsx b/frontend-modern/browser-tests/operator-tables-consent.tsx
index 17b99932c1..e156be1fe7 100644
--- a/frontend-modern/browser-tests/operator-tables-consent.tsx
+++ b/frontend-modern/browser-tests/operator-tables-consent.tsx
@@ -319,9 +319,6 @@ function Fixture() {
: new Set([...current, key]),
)
}
- showPbsColumn
- showArchiveColumn
- showSnapshotColumn
showTaskColumn={false}
/>
By date
diff --git a/frontend-modern/browser-verification/368f58089b70d7bf.json b/frontend-modern/browser-verification/368f58089b70d7bf.json
new file mode 100644
index 0000000000..064551caa0
--- /dev/null
+++ b/frontend-modern/browser-verification/368f58089b70d7bf.json
@@ -0,0 +1,81 @@
+{
+ "version": 1,
+ "base_sha": "1d4c3aeb6e529af3b63992ef22152e6b8b8a33d8",
+ "verified_at": "2026-10-05T01:34:30Z",
+ "result": "passed",
+ "changed_paths": [
+ "frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableAgentDisksSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableAgentsTab.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableDockerContainersSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableDockerTab.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableKubernetesTab.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxBackupsSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxGuestDisksSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxGuestsSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxNodesSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxPBSSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxPMGSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxSnapshotsSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxStorageSection.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableTrueNASTab.tsx",
+ "frontend-modern/src/components/Alerts/ThresholdsTableVMwareTab.tsx",
+ "frontend-modern/src/features/alerts/tabs/ThresholdsTab.tsx",
+ "frontend-modern/src/utils/alertThresholdsSectionPresentation.ts"
+ ],
+ "content_sha256": {
+ "frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx": "0a993dded673dbd2eb0b3919af2586a0fe4579ce10465e495710f526cf7ca1bd",
+ "frontend-modern/src/components/Alerts/ThresholdsTableAgentDisksSection.tsx": "d54ba2278c35ca5748cc0a310bb738dd9128454b1283d547bed7938568f92221",
+ "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx": "3b5cb92fb9b684d5655c6453b7e2738fb776d4c91bd566c319cc6d135301e095",
+ "frontend-modern/src/components/Alerts/ThresholdsTableAgentsTab.tsx": "463bba443bc9dfc52e9cfcb83505640d5f8e91f5c731a8ec8cab1542f43e81a4",
+ "frontend-modern/src/components/Alerts/ThresholdsTableDockerContainersSection.tsx": "3ee73f2b61ed5da9532dce15637e9803c4465f7a70241edf16e1692439c55fa5",
+ "frontend-modern/src/components/Alerts/ThresholdsTableDockerTab.tsx": "6226defd1e8adba0d28bae0998abd832993fc1e6b5b63f80024798c92684ecbd",
+ "frontend-modern/src/components/Alerts/ThresholdsTableKubernetesTab.tsx": "7e793356d6488169f8307ada64db35d9c15c9eacf1bddd30144e193f53345051",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxBackupsSection.tsx": "8b89d2d4e3ffcbfe1dceb6d4a5d309c1d424d1d58059110e51b9e73a93fcd670",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxGuestDisksSection.tsx": "7849b5ed2d8c2674915aa98bcbd4bf348881efc155f00dbcbe9febeabcc38458",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxGuestsSection.tsx": "6f1694090e0022d1fff0653b06d1cf136f5fcebd59c88c41a4f703125d555ec2",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxNodesSection.tsx": "c48d40917928e0ca8564d4eb90cb0e6987c8c9313494a444fa98209a3eee5dc1",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxPBSSection.tsx": "d6801951e3d0ec6eaa7b267ff68e0f13ba3838be9c697b58822b94c57d719f06",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxPMGSection.tsx": "2606881fdc7a68b747c34793330756b41513c89f07cbebcc5a3f69c6039d6610",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxSnapshotsSection.tsx": "3a1bfafe2b44c724a5b351d37d721bf5992db31d66e761a0b782ad6f0a57cc7c",
+ "frontend-modern/src/components/Alerts/ThresholdsTableProxmoxStorageSection.tsx": "c4da879c91150d4c8d32c2068a4a0988ac3934f9d7297bff45d63b2ee1f4f6e2",
+ "frontend-modern/src/components/Alerts/ThresholdsTableTrueNASTab.tsx": "2d78009148e366e1546d52127ea1b0d0ee372037ca9b627a0ff3605854a1ca53",
+ "frontend-modern/src/components/Alerts/ThresholdsTableVMwareTab.tsx": "a628c3a17f16cb3ba74ca15bf53c0f8afa6d10390386b5b42f932c60105e167e",
+ "frontend-modern/src/features/alerts/tabs/ThresholdsTab.tsx": "ea46c8920f008f1904fe6e51dbfc4c5fa755e5af92341fe223fe75428efdf077",
+ "frontend-modern/src/utils/alertThresholdsSectionPresentation.ts": "75c2c8d6e0728b0ecec30b02c92185af63a32f03c43cede6a7068cba29943112"
+ },
+ "routes": [
+ "/alerts/thresholds/proxmox",
+ "/alerts/thresholds/docker",
+ "/alerts/thresholds/kubernetes",
+ "/alerts/thresholds/truenas",
+ "/alerts/thresholds/vmware",
+ "/alerts/thresholds/systems"
+ ],
+ "viewports": [
+ {
+ "width": 1440,
+ "height": 900
+ },
+ {
+ "width": 390,
+ "height": 844
+ }
+ ],
+ "states": [
+ "Proxmox tab: every collapsed group states its default limits (Virtualization Hosts CPU 80% \u00b7 Memory 85% \u00b7 Disk 90% \u00b7 Temp 80\u00b0C, VMs & Containers, Guest Filesystems, Storage Devices, PBS Servers, Mail Gateway with +12 more)",
+ "Recovery and Snapshot Age groups disabled: Disabled badge and no defaults line",
+ "CPU averaging collapsed to one line: 5-minute average for every resource",
+ "Docker tab: Container Runtimes and Containers lead (Restart loop 3 in 5 min \u00b7 Memory limit 90% warn, 95% critical), Docker-only rules follow",
+ "Kubernetes, TrueNAS, vSphere tabs: every group with defaults states them",
+ "Machines tab: Machines and Machine Disks lead with defaults, SMART alert rules card follows",
+ "Phone 390: defaults lines and CPU averaging summary wrap between items without breaking a value",
+ "No console errors"
+ ],
+ "interactions": [
+ "Opened CPU averaging disclosure: ten profile selects shown",
+ "Changed Virtual machines CPU averaging to 15-minute: summary updated to 1 platform override and the unsaved-changes banner appeared (not saved)",
+ "Expanded VMs & Containers to compare header defaults with the Global Defaults row"
+ ]
+}
diff --git a/frontend-modern/browser-verification/dbe3b19ac8e27fa0.json b/frontend-modern/browser-verification/dbe3b19ac8e27fa0.json
new file mode 100644
index 0000000000..7f3b64628b
--- /dev/null
+++ b/frontend-modern/browser-verification/dbe3b19ac8e27fa0.json
@@ -0,0 +1,44 @@
+{
+ "version": 1,
+ "base_sha": "4c266391c3753eaa1e6bea1cfd8339c5d40c01de",
+ "verified_at": "2026-10-05T01:10:06Z",
+ "result": "passed",
+ "changed_paths": [
+ "frontend-modern/src/features/proxmox/ProxmoxBackupsCoverageStrip.tsx",
+ "frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx",
+ "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx",
+ "frontend-modern/src/features/proxmox/proxmoxBackupsTableModel.ts",
+ "frontend-modern/src/features/proxmox/proxmoxBackupsTablePresentation.ts"
+ ],
+ "content_sha256": {
+ "frontend-modern/src/features/proxmox/ProxmoxBackupsCoverageStrip.tsx": "97d3f7a23a959406dd8cb9e92a8921fd38118194d5f757e31b6e0e2eec74f27f",
+ "frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx": "2cd4cfb0c470ba51efcdb0beafef0b630acac34373b92dd9b77de03f99b16743",
+ "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx": "2568f261531b4b601df7da9e1b8c1826bacc70e8a28420bbe909f2eb3524c6eb",
+ "frontend-modern/src/features/proxmox/proxmoxBackupsTableModel.ts": "8f7f8a80bb994404c21cce7b75c621bdf844f80b38a9def79d6dc61fb42b8dd6",
+ "frontend-modern/src/features/proxmox/proxmoxBackupsTablePresentation.ts": "55dd4787cd47e9ccbf056ba67809fd45638b1e3f21889c6a31ce192ce87b8e67"
+ },
+ "routes": [
+ "/proxmox/backups"
+ ],
+ "viewports": [
+ {
+ "width": 1440,
+ "height": 900
+ },
+ {
+ "width": 390,
+ "height": 844
+ }
+ ],
+ "states": [
+ "Coverage workspace, mock fleet of 68 targets: Backup health strip footnote explains attention, unprotected and unknown with the 7-day window from the server policy",
+ "Coverage table at 1440: Workload / Type / Target ID / Node / Posture / Last backup / Task, no PBS snapshot, PVE file or Guest snapshot columns",
+ "Coverage table at 390: Workload / Posture / Age / Job, footnote wraps under the legend without a reserved column",
+ "Row expansion for a Needs attention guest with a failed newer job: posture explanation, Provider evidence and Restore evidence listing PVE file and Snapshot sources",
+ "No console errors on either viewport"
+ ],
+ "interactions": [
+ "Expanded and collapsed coverage rows to inspect provider and restore evidence",
+ "Compared before/after screenshots at both viewports"
+ ]
+}
diff --git a/frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx b/frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx
index d57c44a562..3723f82490 100644
--- a/frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx
+++ b/frontend-modern/src/components/Alerts/Thresholds/sections/CollapsibleSection.tsx
@@ -5,7 +5,7 @@
* Used to organize resource groups in the thresholds page.
*/
-import { Component, Show, createSignal, createEffect, JSX } from 'solid-js';
+import { Component, For, Show, createSignal, createEffect, JSX } from 'solid-js';
import ChevronRight from 'lucide-solid/icons/chevron-right';
import ChevronDown from 'lucide-solid/icons/chevron-down';
import { StatusDot } from '@/components/shared/StatusDot';
@@ -34,6 +34,12 @@ export interface CollapsibleSectionProps {
icon?: JSX.Element;
/** Subtitle or description */
subtitle?: string;
+ /**
+ * What the group alerts on by default, shown under the title so the page
+ * answers "what will alert me" before the group is opened. Hidden while the
+ * group is disabled, where it would promise alerts that cannot fire.
+ */
+ defaultsSummary?: string;
/** Message to show when section is empty */
emptyMessage?: string;
/** Whether to show a visual indicator for global disable state */
@@ -133,6 +139,23 @@ export const CollapsibleSection: Component = (props) =>
{props.subtitle}
+
+
+ {/* Break between items only, never inside "Disk 90%". */}
+
+ {(item, index) => (
+ <>
+ {index() > 0 ? ' · ' : ''}
+ {item}
+ >
+ )}
+
+
+
diff --git a/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx b/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx
index 40f9cab7fa..710f053f07 100644
--- a/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx
+++ b/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx
@@ -26,6 +26,29 @@ describe('CollapsibleSection', () => {
expect(screen.getByText('Child content')).toBeInTheDocument();
});
+ it('states the default limits in the collapsed header, except while disabled', () => {
+ const [disabled, setDisabled] = createSignal(false);
+ const { container } = render(() => (
+
+ Rows
+
+ ));
+
+ const summary = () => container.querySelector('[data-thresholds-defaults-summary]');
+ expect(summary()?.textContent).toBe('Defaults: CPU 80% · Memory 85% · Disk 90%');
+ expect(summary()?.closest('button')?.getAttribute('aria-expanded')).toBe('false');
+
+ setDisabled(true);
+ expect(summary()).toBeNull();
+ expect(screen.getByText(ALERT_THRESHOLDS_SECTION_DISABLED_LABEL)).toBeInTheDocument();
+ });
+
it('uses default data-testid from id prop', () => {
const { container } = render(() => (
diff --git a/frontend-modern/src/components/Alerts/ThresholdsTableAgentDisksSection.tsx b/frontend-modern/src/components/Alerts/ThresholdsTableAgentDisksSection.tsx
index 80eef07708..f737de1003 100644
--- a/frontend-modern/src/components/Alerts/ThresholdsTableAgentDisksSection.tsx
+++ b/frontend-modern/src/components/Alerts/ThresholdsTableAgentDisksSection.tsx
@@ -5,6 +5,9 @@ import { ResourceTable } from './ResourceTable';
import { CollapsibleSection } from './Thresholds/sections/CollapsibleSection';
import { formatMetricValue } from '@/features/alerts/thresholds/helpers';
import type { ThresholdsTableSectionProps } from '@/features/alerts/thresholds/thresholdsTableSectionProps';
+import { getAlertThresholdsDefaultsSummary } from '@/utils/alertThresholdsSectionPresentation';
+
+const AGENT_DISK_THRESHOLD_COLUMNS = ['Disk %'];
export function ThresholdsTableAgentDisksSection(props: ThresholdsTableSectionProps) {
const { state, tableProps } = props;
@@ -14,6 +17,9 @@ export function ThresholdsTableAgentDisksSection(props: ThresholdsTableSectionPr
state.toggleSection('agentDisks')}
@@ -27,7 +33,7 @@ export function ThresholdsTableAgentDisksSection(props: ThresholdsTableSectionPr
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
groupedResources={state.agentDisksGroupedByAgent()}
groupHeaderMeta={state.agentGroupHeaderMeta()}
- columns={['Disk %']}
+ columns={AGENT_DISK_THRESHOLD_COLUMNS}
activeAlerts={tableProps.activeAlerts}
emptyMessage={state.AGENT_DISKS_FILTER_EMPTY_STATE}
onEdit={state.startEditing}
diff --git a/frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx b/frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx
index b4d77dbf87..5429e1e806 100644
--- a/frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx
+++ b/frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx
@@ -6,6 +6,9 @@ import { ResourceTable } from './ResourceTable';
import { CollapsibleSection } from './Thresholds/sections/CollapsibleSection';
import { formatMetricValue } from '@/features/alerts/thresholds/helpers';
import type { ThresholdsTableSectionProps } from '@/features/alerts/thresholds/thresholdsTableSectionProps';
+import { getAlertThresholdsDefaultsSummary } from '@/utils/alertThresholdsSectionPresentation';
+
+const AGENT_THRESHOLD_COLUMNS = ['CPU %', 'Memory %', 'Disk %', 'Disk Temp °C'];
const DISK_TEMP_TYPE_FIELDS: readonly { key: string; label: string }[] = [
{ key: 'nvme', label: 'NVMe' },
@@ -21,6 +24,10 @@ export function ThresholdsTableAgentsResourcesSection(props: ThresholdsTableSect
state.toggleSection('agents')}
@@ -33,7 +40,7 @@ export function ThresholdsTableAgentsResourcesSection(props: ThresholdsTableSect
title=""
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
resources={state.agentsWithOverrides()}
- columns={['CPU %', 'Memory %', 'Disk %', 'Disk Temp °C']}
+ columns={AGENT_THRESHOLD_COLUMNS}
activeAlerts={tableProps.activeAlerts}
emptyMessage={state.AGENT_THRESHOLDS_FILTER_EMPTY_STATE}
onEdit={state.startEditing}
diff --git a/frontend-modern/src/components/Alerts/ThresholdsTableAgentsTab.tsx b/frontend-modern/src/components/Alerts/ThresholdsTableAgentsTab.tsx
index 55eb3c0167..b860b18953 100644
--- a/frontend-modern/src/components/Alerts/ThresholdsTableAgentsTab.tsx
+++ b/frontend-modern/src/components/Alerts/ThresholdsTableAgentsTab.tsx
@@ -6,9 +6,10 @@ import type { ThresholdsTableSectionProps } from '@/features/alerts/thresholds/t
export function ThresholdsTableAgentsTab(props: ThresholdsTableSectionProps) {
return (
<>
-
+ {/* The groups and their default limits lead; disk health rules follow. */}
+
>
);
}
diff --git a/frontend-modern/src/components/Alerts/ThresholdsTableDockerContainersSection.tsx b/frontend-modern/src/components/Alerts/ThresholdsTableDockerContainersSection.tsx
index 5078cf0441..a96305a6d5 100644
--- a/frontend-modern/src/components/Alerts/ThresholdsTableDockerContainersSection.tsx
+++ b/frontend-modern/src/components/Alerts/ThresholdsTableDockerContainersSection.tsx
@@ -5,15 +5,46 @@ import { ResourceTable } from './ResourceTable';
import { CollapsibleSection } from './Thresholds/sections/CollapsibleSection';
import { formatMetricValue } from '@/features/alerts/thresholds/helpers';
import type { ThresholdsTableSectionProps } from '@/features/alerts/thresholds/thresholdsTableSectionProps';
+import {
+ getAlertThresholdsDefaultsSummary,
+ getDockerContainerRuleSummaryItems,
+} from '@/utils/alertThresholdsSectionPresentation';
+
+const DOCKER_CONTAINER_THRESHOLD_COLUMNS = [
+ 'CPU %',
+ 'Memory %',
+ 'Disk %',
+ 'Restart Count',
+ 'Restart Window (s)',
+ 'Memory Warn %',
+ 'Memory Critical %',
+];
+
+// Restart and memory-limit columns are summarized as whole rules instead.
+const DOCKER_CONTAINER_METRIC_SUMMARY_COLUMNS = ['CPU %', 'Memory %', 'Disk %'];
export function ThresholdsTableDockerContainersSection(props: ThresholdsTableSectionProps) {
const { state, tableProps } = props;
+ const containerDefaults = () => ({
+ cpu: tableProps.dockerDefaults.cpu,
+ memory: tableProps.dockerDefaults.memory,
+ disk: tableProps.dockerDefaults.disk,
+ restartCount: tableProps.dockerDefaults.restartCount,
+ restartWindow: tableProps.dockerDefaults.restartWindow,
+ memoryWarnPct: tableProps.dockerDefaults.memoryWarnPct,
+ memoryCriticalPct: tableProps.dockerDefaults.memoryCriticalPct,
+ });
return (
state.toggleSection('dockerContainers')}
@@ -27,15 +58,7 @@ export function ThresholdsTableDockerContainersSection(props: ThresholdsTableSec
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
groupedResources={state.dockerContainersGroupedByHost()}
groupHeaderMeta={state.dockerHostGroupMeta()}
- columns={[
- 'CPU %',
- 'Memory %',
- 'Disk %',
- 'Restart Count',
- 'Restart Window (s)',
- 'Memory Warn %',
- 'Memory Critical %',
- ]}
+ columns={DOCKER_CONTAINER_THRESHOLD_COLUMNS}
activeAlerts={tableProps.activeAlerts}
emptyMessage={state.CONTAINERS_FILTER_EMPTY_STATE}
onEdit={state.startEditing}
@@ -54,15 +77,7 @@ export function ThresholdsTableDockerContainersSection(props: ThresholdsTableSec
}
formatMetricValue={formatMetricValue}
hasActiveAlert={state.hasActiveAlert}
- globalDefaults={{
- cpu: tableProps.dockerDefaults.cpu,
- memory: tableProps.dockerDefaults.memory,
- disk: tableProps.dockerDefaults.disk,
- restartCount: tableProps.dockerDefaults.restartCount,
- restartWindow: tableProps.dockerDefaults.restartWindow,
- memoryWarnPct: tableProps.dockerDefaults.memoryWarnPct,
- memoryCriticalPct: tableProps.dockerDefaults.memoryCriticalPct,
- }}
+ globalDefaults={containerDefaults()}
setGlobalDefaults={(value) => {
const current = {
cpu: tableProps.dockerDefaults.cpu,
diff --git a/frontend-modern/src/components/Alerts/ThresholdsTableDockerTab.tsx b/frontend-modern/src/components/Alerts/ThresholdsTableDockerTab.tsx
index 9bd6aad526..60ba345a55 100644
--- a/frontend-modern/src/components/Alerts/ThresholdsTableDockerTab.tsx
+++ b/frontend-modern/src/components/Alerts/ThresholdsTableDockerTab.tsx
@@ -10,13 +10,14 @@ import type { ThresholdsTableSectionProps } from '@/features/alerts/thresholds/t
export function ThresholdsTableDockerTab(props: ThresholdsTableSectionProps) {
return (
<>
+ {/* The groups and their default limits lead; Docker-only rules follow. */}
+
+
-
-
>
);
}
diff --git a/frontend-modern/src/components/Alerts/ThresholdsTableKubernetesTab.tsx b/frontend-modern/src/components/Alerts/ThresholdsTableKubernetesTab.tsx
index 34876a7331..d2c2310b61 100644
--- a/frontend-modern/src/components/Alerts/ThresholdsTableKubernetesTab.tsx
+++ b/frontend-modern/src/components/Alerts/ThresholdsTableKubernetesTab.tsx
@@ -10,6 +10,7 @@ import { CollapsibleSection } from './Thresholds/sections/CollapsibleSection';
import { formatMetricValue } from '@/features/alerts/thresholds/helpers';
import type { ThresholdsTableSectionProps } from '@/features/alerts/thresholds/thresholdsTableSectionProps';
import type { Resource } from '@/features/alerts/thresholds/tableTypes';
+import { getAlertThresholdsDefaultsSummary } from '@/utils/alertThresholdsSectionPresentation';
const KUBERNETES_WORKLOAD_COLUMNS = [
'CPU %',
@@ -40,6 +41,7 @@ function KubernetesResourceSection(
state.toggleSection(props.id)}
diff --git a/frontend-modern/src/components/Alerts/ThresholdsTableProxmoxBackupsSection.tsx b/frontend-modern/src/components/Alerts/ThresholdsTableProxmoxBackupsSection.tsx
index e5461c875f..c4a547d7b3 100644
--- a/frontend-modern/src/components/Alerts/ThresholdsTableProxmoxBackupsSection.tsx
+++ b/frontend-modern/src/components/Alerts/ThresholdsTableProxmoxBackupsSection.tsx
@@ -15,6 +15,11 @@ import {
reconcileWarningCriticalEdit,
} from '@/features/alerts/thresholds/helpers';
import type { ThresholdsTableSectionProps } from '@/features/alerts/thresholds/thresholdsTableSectionProps';
+import { getAlertThresholdsDefaultsSummary } from '@/utils/alertThresholdsSectionPresentation';
+
+const BACKUP_THRESHOLD_COLUMNS = ['Fresh Hours', 'Stale Hours', 'Warning Days', 'Critical Days'];
+// Fresh and stale hours only colour the dashboard; the alert fires on days.
+const BACKUP_ALERT_SUMMARY_COLUMNS = ['Warning Days', 'Critical Days'];
export function ThresholdsTableProxmoxBackupsSection(props: ThresholdsTableSectionProps) {
const { state, tableProps } = props;
@@ -24,6 +29,10 @@ export function ThresholdsTableProxmoxBackupsSection(props: ThresholdsTableSecti
state.toggleSection('backups')}
icon={}
@@ -33,7 +42,7 @@ export function ThresholdsTableProxmoxBackupsSection(props: ThresholdsTableSecti