diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 8e30cf61f6..07295760aa 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -248,6 +248,18 @@ Without comparable identity (a SAS, SCSI or USB replacement with no WWN on one side) the row still matches. Report admission and the SMART wire contract are unchanged; the regression is `TestHostAgentSMARTRowForSwappedOutDiskDoesNotLendItsIdentity`. +A PVE disk that a still-reporting linked Agent's SMART row matches is marked +`AgentSMARTReported` (internal poll evidence on `models.PhysicalDisk`, never +serialized); disks the poller builds from that report when the Proxmox disk +query fails go through the same merge, and an Agent whose lease lapsed owns +none. The Agent then owns the disk's temperature alert +through `CheckHost`, and the PVE disk temperature check closes its own as +moved to the Agent, so one disk never alerts twice. Report admission, the SMART +wire contract and Agent authority are unchanged; +`TestMergeHostAgentSMARTIntoDisksMarksDisksTheAgentReports` in +`internal/monitoring/physical_disk_roundtrip_test.go` and +`TestPhysicalDiskAgentSMARTReportedStaysInternal` in +`internal/models/deepcopy_test.go` pin the marker. Assistant historical metric wiring uses the current monitor's retained store and registry metrics coordinates. Historical reads do not alter enrollment, diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md index 1b3fd083fd..4bf975c50d 100644 --- a/docs/release-control/v6/internal/subsystems/alerts.md +++ b/docs/release-control/v6/internal/subsystems/alerts.md @@ -1851,14 +1851,84 @@ A disk is hot from its trigger. Alerts and Patrol findings stay open until the reading falls to the clear value, or under the trigger when there is no band below it. The Physical Disks verdict and the TrueNAS Health cell judge only the current reading against the trigger. PDF reports colour disk temperatures by -the same thresholds. Judges that still differ: TrueNAS disk temperature alerts -use `TrueNASDiskDefaults.Temperature` (a flat 55/50), and the TrueNAS disk -drawer tones the reading from a fixed 55C. +the same thresholds. `TestDiskTemperatureThresholdMatchesCheckHostPolicy` in `internal/alerts/threshold_resolution_shared_test.go` pins per-type resolution, a raised NVMe trigger that `CheckHost` also honours, the copy, the disabled default and the nil manager. +TrueNAS disk temperature alerts follow the policy too: +`trueNASDiskTemperatureDefaultNoLock` +(`internal/alerts/truenas_disk_temperature.go`) is the type-default tier of +`effectiveAlertPolicyNoLock` for `truenas-disk`, so an unset +`TrueNASDiskDefaults.Temperature` resolves per disk type (the +`alertPolicyQuery.DiskType` the unified input carries from +`PhysicalDisk.DiskType`). A TrueNAS-wide value the user saved under TrueNAS +Disks replaces it for every TrueNAS disk, as a host Disk Temp override does for +an agent's disks, 0 meaning off, and a per-disk override beats both. The alert +records `diskType` so a config save re-judges it per type; one without it is +held to the lowest per-type trigger. A retained reading +(`diskinventory.TemperatureCollected` false) is no evidence and raises nothing. +The factory value is unset. `normalizeTrueNASDiskTemperature` drops a stored +55/50 only from a config without `TrueNASDiskTemperatureByType`, because the +thresholds page wrote that flat factory value back on every save; the page +writes the marker, so a 55 typed later is kept. Every config `GetConfig` +returns carries the marker, so a client that reads and writes back keeps it. +Without the marker (an old saved file, or a page loaded before the upgrade) +55/50 is read as that unchosen factory value; a client building a config from +scratch with an explicit TrueNAS-wide 55 has to send the marker. The TrueNAS +Disks Global Defaults cell reads `By type` while unset +(`globalDefaultFallbacks` on `ResourceTable`, +`resolveAlertResourceGlobalDefaultCell`), or Off when the agent Disk Temp +default switches the policy off, and switching it on then stages an explicit +TrueNAS-wide 55 because unset would stay off. Each disk row inherits its type's +trigger from the unsaved editor state (`resolveTrueNASDiskTemperatureDefault`). +The TrueNAS disk drawer tones a current reading from the same per-type trigger +(`getDiskTemperatureThresholds` passed into `buildTrueNASDetailSections`), so it +no longer judges heat from a fixed 55C. Judges that still differ: the TrueNAS +storage table and drawer do not apply a TrueNAS-wide value or per-disk +override. +`TestTrueNASDiskTemperatureAlertsFollowDiskTemperaturePolicy` and +`TestTrueNASDiskTemperatureAlertIgnoresRetainedReading` in +`internal/alerts/unified_eval_test.go` pin the tiers and retained readings; +`TestTrueNASDiskTemperatureAlertsReevaluatePerDiskType` and +`TestTrueNASDiskTemperatureAlertWithoutDiskTypeHeldAtLowestTrigger` in +`internal/alerts/alerts_test.go` pin re-judging on a config save; +`TestNormalizeTrueNASDiskTemperatureKeepsOnlyChosenValues` pins the +saved-config migration. + +Proxmox physical disks alert on temperature under the same policy. +`CheckProxmoxDiskTemperature` (`internal/alerts/proxmox_disk_temperature.go`) +raises a `diskTemperature` metric alert under the disk's PVE alert reference +(`ProxmoxPhysicalDiskAlertResourceID`, resource type `proxmox-disk`), judged by +`proxmoxDiskTemperatureThresholdNoLock`: the disk type's `DiskTempByType` entry +under the agent Disk Temp default, which switches it off for every type. It +carries the disk's serial, WWN and `diskType` like the health and wearout +alerts, so `ProxmoxPhysicalDiskAlertIdentifiers` lists its +`::metric-threshold:diskTemperature` identifier for history +ownership, and `reevaluateActiveAlertsLocked` re-judges it per disk type (the +lowest per-type trigger when the type was never recorded) instead of falling +through to guest thresholds that have no disk temperature. A disk a +still-reporting linked agent lists in its SMART report is the agent's: its +alert closes with the `moved_to_agent` resolution and `CheckHost` raises the +agent's own. The handover happens on the next disk poll, so for that one +interval an agent newly linked to a disk with an open PVE alert can hold its +own alert beside it. A lapsed agent owns nothing, so the node's own readings +keep alerting while its agent is silent. An +excluded disk, or a policy switched off, closes the alert; a poll without a +current reading holds it. Like the health and wearout alerts, a disk that +leaves the inventory keeps its alert until the 24-hour stale sweep. The +`proxmox-disk` alert delay defaults to the factory 5 seconds, so a warning +waits out the same 300-second noisy-gauge stability window agent and TrueNAS +disk temperature warnings do, and a critical reading bypasses it. +`TestProxmoxDiskTemperatureAlertsFollowDiskTemperaturePolicy`, +`TestProxmoxDiskTemperatureAlertOwnershipAndHolds`, +`TestProxmoxDiskTemperatureWarningWaitsOutStabilityWindow`, +`TestProxmoxDiskTemperatureAlertsReevaluatePerDiskType` and +`TestCheckDiskHealthAlertsCarryHistoryOwnershipIdentity` in +`internal/alerts/alerts_test.go` pin the policy, ownership, holds, re-judging +and identifiers. + ### Agent disk temperature alerts clear when their disk leaves the report `CheckHost` also clears a disk temperature alert, and drops its pending diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 7c3baeeb5e..ec775ca41c 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -2116,6 +2116,18 @@ the same record; the table renders defaults once, in both desktop table and narrow card layouts, and per-section metric columns must resolve to metric keys the shared column normalizer produces so the defaults editor reads and writes the same record keys the section persists. +A global default that another setting decides while it is unset passes a +`globalDefaultFallbacks` entry for that metric (label, title, whether the other +setting is itself off, and the value switching on stages when it is). The +TrueNAS Disks temperature uses it to read `By type`, following Disk +temperature by type, with `features/alerts/thresholds/trueNASDiskTemperature.ts` +giving each disk row its type's trigger from the unsaved editor state. +`resolveAlertResourceGlobalDefaultCell` decides the cell for both layouts: +unset follows the fallback, switching on from Off returns to unset unless the +fallback is off, and an unset default with no fallback stays Off as the engine +reads it. `ResourceTable.tsx` hands its props to the shared table state through +getters, so the Custom badge and reset control follow defaults that load after +the table mounts. Platform sub-routes that add native provider inventory must stay on the shared platform page and table primitives. The vSphere Networks surface routes through `/vmware/networks`, the shared platform tab model, the command palette diff --git a/docs/release-control/v6/internal/subsystems/monitoring.md b/docs/release-control/v6/internal/subsystems/monitoring.md index 796fc46ca0..67a6f98337 100644 --- a/docs/release-control/v6/internal/subsystems/monitoring.md +++ b/docs/release-control/v6/internal/subsystems/monitoring.md @@ -1825,6 +1825,29 @@ boundary around it: matches node names only, so it relies on fixture node names being unique across instances, which they are. +`checkPhysicalDiskAlerts` also hands each disk's temperature to +`CheckProxmoxDiskTemperature`, so a Proxmox disk that only the node's sensors +report alerts under the disk temperature policy, as its Running Hot verdict +already says it should. It passes the reading only when this poll collected it +(`collectedPhysicalDiskTemperature`, `diskinventory.TemperatureCollected`), so +a retained last-known value holds the alert rather than judging it, and it +passes `AgentSMARTReported`, which `mergeHostAgentSMARTIntoDisks` sets on a +disk a still-reporting linked agent lists in its SMART report (disks built +from that report when the Proxmox query fails go through the same merge). A +disk the agent lists is the agent's: `CheckHost` raises its temperature +alert, and the PVE disk check closes its own as moved to the agent, so no +disk alerts twice. Once the agent's lease lapses (`ExpireHostTelemetry` marks +the host offline) its retained rows still enrich the disk, but ownership +returns to the PVE check, which judges the node's own current readings; an +agent newly linked to a disk with an open PVE alert takes it over on the next +disk poll. An excluded device closes its temperature +alert with the health and wearout ones. Fixture disks carry no agent SMART +merge, so on a mock estate every hot disk on an online node alerts. +`TestMergeHostAgentSMARTIntoDisksMarksDisksTheAgentReports` and +`TestCheckPhysicalDiskAlertsRaisesProxmoxDiskTemperatureAlerts` in +`internal/monitoring/physical_disk_roundtrip_test.go` pin the marker, +the collected reading, agent ownership and exclusion. + Switching mock mode fences the alert evaluations that read mode-dependent data (`mockModeFence`, `internal/monitoring/mock_mode_fence.go`). `GetState`, the fixture graph, the unified read view, the recovery rollups and the connection diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 69b3cf77e3..7f476226af 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -6076,7 +6076,12 @@ the reference and is never retried. An owned row records its alert's reference as `alert_resource_id`. A read by the reference returns the rows journaled under it plus the rows owned away from it, matched through the indexed alert identifier (`ProxmoxPhysicalDiskAlertIdentifiers`) and that -recorded reference, and every count uses the same predicate. The Alerts +recorded reference, and every count uses the same predicate. Those +identifiers cover the health and wearout specs and the disk temperature +metric spec (`::metric-threshold:diskTemperature`), since PVE disks +also raise temperature alerts under the same reference +(`TestProxmoxDiskTemperatureAlertRowsFollowRecordedHardwareIdentity` in +`internal/unifiedresources/store_test.go`). The Alerts history Resource action therefore still lists every occurrence raised under the path, across the disks that held it, and alert-centric reads keep their occurrences (see the AI runtime contract's incident-history queries). Rows @@ -6232,6 +6237,13 @@ presentation. `frontend-modern/src/components/Infrastructure/` placement, signal, and snapshot context through the canonical resource drawer and debug/source sections rather than introducing a VMware-only detail route, drawer tab, or provider-local investigation shell. +The TrueNAS physical-disk drawer judges a current temperature reading by the +disk temperature policy, like the TrueNAS storage table: +`useResourceDetailDrawerDerivedState.ts` passes the alerts store's +`getDiskTemperatureThresholds` into `buildTrueNASDetailSections`, and the +Temperature row takes a warning tone only when `isPhysicalDiskRunningHot` +says the reading reached its disk type's alert trigger. A retained reading +keeps its muted last-known row, and without a resolver heat is not judged. That same infrastructure consumer boundary also owns source selection continuity. Settings infrastructure panels and platform/runtime pages must keep canonical sources such as `truenas` and `availability` present in their diff --git a/frontend-modern/browser-verification/26f085dd13bc21bf.json b/frontend-modern/browser-verification/26f085dd13bc21bf.json new file mode 100644 index 0000000000..fa004d8f83 --- /dev/null +++ b/frontend-modern/browser-verification/26f085dd13bc21bf.json @@ -0,0 +1,69 @@ +{ + "version": 1, + "base_sha": "bdf0f76b7bf61d550b1cb8574497eef75b354acb", + "verified_at": "2026-10-07T14:11:27Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx", + "frontend-modern/src/components/Alerts/AlertResourceTableMobile.tsx", + "frontend-modern/src/components/Alerts/ResourceTable.tsx", + "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx", + "frontend-modern/src/components/Alerts/ThresholdsTableTrueNASTab.tsx", + "frontend-modern/src/components/Alerts/alertResourceTableModel.ts", + "frontend-modern/src/components/Infrastructure/resourceDetailDrawerTrueNASModel.ts", + "frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDerivedState.ts", + "frontend-modern/src/features/alerts/alertsConfigurationModel.ts", + "frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsPlatformData.ts", + "frontend-modern/src/features/alerts/thresholds/trueNASDiskTemperature.ts", + "frontend-modern/src/types/alerts.ts", + "frontend-modern/src/utils/alertThresholdDefaults.ts" + ], + "content_sha256": { + "frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx": "4d09dd97adc2868889e256455c207b6b9089ea311713878f0d6ff00efb6b7123", + "frontend-modern/src/components/Alerts/AlertResourceTableMobile.tsx": "ceaf136da9361a5b1756350626a833da4dfebcb4f211c7269d7d89d37fb7e085", + "frontend-modern/src/components/Alerts/ResourceTable.tsx": "7e80a6c789b3069fcadcb86e0a6689e5319e7b0ac5d5bfa1a596520daec2d7e1", + "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx": "fc4e9f4d05c0bf5799acbbe64c37b901e104869ff120819342432563789ad0e9", + "frontend-modern/src/components/Alerts/ThresholdsTableTrueNASTab.tsx": "a22b86c28b65ec18ba81171fb1206a24107a0d97db19d7dd488bdfd0a689a956", + "frontend-modern/src/components/Alerts/alertResourceTableModel.ts": "5881aeaf7ee575589997bf67fb713a01784ce7ca16ecb5a5eaea77f36010837a", + "frontend-modern/src/components/Infrastructure/resourceDetailDrawerTrueNASModel.ts": "b60ba59688d6ea0cabfb2b413b8adcaa85c8d41524c1d1f0df9bdb9e6d85733c", + "frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDerivedState.ts": "63b07de3f354f8328298348a8892ad73fde2f11ec6768e9c0f6fc37eb4c43516", + "frontend-modern/src/features/alerts/alertsConfigurationModel.ts": "0e116e65c992a72e21d700835b0cb0db7173be9ec9c4980382958506d4e75976", + "frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsPlatformData.ts": "8525b0b012198ff87e29063908a9907b7fa1080cd84187202b1cd3cf198a3b46", + "frontend-modern/src/features/alerts/thresholds/trueNASDiskTemperature.ts": "80433d42dd4658a1449c0f0c378d7fa52cd6dfab117d626195099768e750352f", + "frontend-modern/src/types/alerts.ts": "d072175ab98e3785eae643d655be4c2d54cac0eddc8b55803a2841a312062e5a", + "frontend-modern/src/utils/alertThresholdDefaults.ts": "077e291d5c224063a373a5e9f5555d9b470f585801b0cfdd9dc1acacf233af28" + }, + "routes": [ + "/alerts/thresholds/truenas", + "/truenas/storage" + ], + "viewports": [ + { + "width": 1366, + "height": 900 + }, + { + "width": 375, + "height": 812 + } + ], + "states": [ + "Private mock stack (8 nodes, mock TrueNAS truenas-main) with a local-only generator overlay pinning TrueNAS disk temperatures: nvme0n1 60C, nvme1n1 72C, sda 57C (SATA), sdc 63C (SAS); after = this branch, before = main logic in the same tree", + "Thresholds > TrueNAS > Disks with no saved TrueNAS-wide value: header reads 'Defaults: NVMe 70\u00b0C \u00b7 SAS 65\u00b0C \u00b7 SATA 55\u00b0C', note 'Unless set here, each disk alerts at Disk temperature by type, set under Agents.', Global Defaults Temp cell empty with placeholder 'By type' (fits after widening to w-20) and On, disk rows nvme0n1/nvme1n1 70\u00b0C, sda/sdb 55\u00b0C, sdc-sdf 65\u00b0C", + "Same section at 375px: Global Defaults card shows TEMP \u00b0C 'By type' with On, disk cards show their type triggers, no horizontal overflow", + "Agent Disk Temp default switched off through the API: header 'Defaults: all metric alerts off', Global Defaults cell Off with title 'Off because the agent Disk Temp default under Agents is off...', disk rows Off", + "Saved TrueNAS-wide 62: header 'Defaults: Temp 62\u00b0C', every disk row 62\u00b0C, Custom badge and reset control after reload; saved 55 survives save and reload as 55 (stored with truenasDiskTemperatureByType)", + "Alerts with factory per-type thresholds: after stack raises nvme1n1 72 (trigger 70) and sda 57 (trigger 55) with diskType metadata, none for nvme0n1 60 or sdc 63; before (main) raised all four at a flat 55", + "TrueNAS storage table agrees: Attention 'Disk temperature is 72\u00b0C, at or above its 70\u00b0C alert threshold.' on nvme1n1 and '57\u00b0C ... 55\u00b0C' on sda, none on nvme0n1", + "TrueNAS disk drawer (Platform details > Show TrueNAS > Health): nvme0n1 Temperature 60\u00b0C in default text colour (main toned it amber from the fixed 55C); sda 57\u00b0C amber rgb(252,211,77) at 1366 and at 375 with no overflow" + ], + "interactions": [ + "Typed 62 into the TrueNAS Disks Global Defaults Temp cell, saved, reloaded: API truenasDiskDefaults {trigger 62, clear 57}, cell shows 62 with Custom", + "Triple-clicked and typed 55, saved, reloaded: API keeps {55, 50}, cell shows 55 with Custom and the reset control", + "Clicked Reset to factory defaults: cell returns to 'By type'; saved: API truenasDiskDefaults {}", + "On the 375px card, clicked On to switch the default Off, saved: API stores {trigger 0, clear 0} and the after stack's TrueNAS disk alerts resolve; clicked again: returns to 'By type', saved: {}", + "With the agent Disk Temp default off, clicked the Off badge: stages an explicit 55 (Custom, rows 55\u00b0C); discarded", + "Loaded an old saved config (flat 55/50, no marker) with the new binary: GET returns truenasDiskDefaults {} and truenasDiskTemperatureByType true", + "Expanded nvme0n1 and sda rows on /truenas/storage, opened Platform details and Show TrueNAS, read the Temperature row value and computed colour at 1366 and 375" + ] +} diff --git a/frontend-modern/browser-verification/d2df05cc61b44990.json b/frontend-modern/browser-verification/d2df05cc61b44990.json new file mode 100644 index 0000000000..6514b6055e --- /dev/null +++ b/frontend-modern/browser-verification/d2df05cc61b44990.json @@ -0,0 +1,39 @@ +{ + "version": 1, + "base_sha": "8e1a564d7e229bda51c021c7e6bde920e0ca0596", + "verified_at": "2026-10-07T14:35:42Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx" + ], + "content_sha256": { + "frontend-modern/src/components/Alerts/ThresholdsTableAgentsResourcesSection.tsx": "3052a190a76e885e98ed6601b3e83c8959e1c656d12270b87e074fffa95860ac" + }, + "routes": [ + "/alerts/thresholds/systems", + "/alerts/overview", + "/proxmox/storage?tab=disks" + ], + "viewports": [ + { + "width": 1366, + "height": 900 + }, + { + "width": 375, + "height": 812 + } + ], + "states": [ + "Private mock stack (8 PVE nodes) on this branch with a local-only generator overlay pinning PVE disks: pve1 NVMe 63C, pve2 SATA 56C, pve3 NVMe 72C, pve3 SAS 63C, pve3 SATA 66C", + "Alerts > Thresholds > Systems (Agents section): 'Disk temperature by type' card reads 'Alert trigger in \u00b0C for each disk type, for agent, TrueNAS and Proxmox disks. ... a TrueNAS Disks value replaces them for TrueNAS disks.' at 1366, and at 375 the card spans 34-341px with no horizontal overflow", + "Alerts overview: 'Samsung 870 EVO 1TB (/dev/nvme2) (Temperature) CRITICAL on West Production C \u2014 Temperature 66\u00b0C, above the 55\u00b0C alert level; Clears once it drops to 50\u00b0C or lower and stays there for 5 minutes', Alert level 55\u00b0C, Clear level 50\u00b0C", + "Warning-level PVE disk temperature alerts (SATA 56C trigger 55, NVMe 72C trigger 70) appear after the 300s stability window (first breach 15:26:32, firing by 15:31:46); the critical 66C fires at once; none for NVMe 63C or SAS 63C", + "Physical Disks filtered to West Production: exactly the three alerting disks read Running Hot (Crucial MX500 56\u00b0C, WD Black SN850X 72\u00b0C, Samsung 870 EVO 66\u00b0C); the 870 EVO drawer reads 'Running Hot \u2014 Disk temperature is 66\u00b0C, at or above its 55\u00b0C alert threshold.'" + ], + "interactions": [ + "Opened Alerts overview and read the PVE disk temperature alert card", + "Filtered Physical Disks by 'West Production C', expanded the Samsung 870 EVO row", + "Opened Thresholds > Systems, read the Disk temperature by type card at 1366 and at 375" + ] +} diff --git a/frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx b/frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx index 92593af32f..92232f4ee8 100644 --- a/frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx +++ b/frontend-modern/src/components/Alerts/AlertResourceTableDesktop.tsx @@ -43,12 +43,11 @@ import { ALERT_RESOURCE_METRIC_OFF_VALUE, getAlertResourceColumnKind, getAlertResourceColumnHeaderTooltip, - getAlertResourceEnabledDefault, getAlertResourceMetricBounds, getAlertResourceMetricDelayOverride, getAlertResourceMetricStep, - isAlertResourceMetricOff, normalizeAlertResourceMetricKey, + resolveAlertResourceGlobalDefaultCell, } from './alertResourceTableModel'; import type { OfflineState, Resource, ResourceTableProps } from './ResourceTable'; @@ -210,7 +209,10 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps) {(column) => ( {column} @@ -281,10 +283,14 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps) {(column) => { const metric = normalizeAlertResourceMetricKey(column); const bounds = getAlertResourceMetricBounds(metric); - const value = () => props.table.globalDefaults?.[metric] ?? 0; + const value = () => props.table.globalDefaults?.[metric]; + const fallback = () => props.table.globalDefaultFallbacks?.[metric]; // An unset default and a stored 0 are both disabled to the - // alert engine, so neither may render as On. - const isOff = () => isAlertResourceMetricOff(value()); + // alert engine, so neither may render as On, unless the + // metric's unset default follows another setting. + const cell = () => + resolveAlertResourceGlobalDefaultCell(metric, value(), fallback()); + const isOff = () => cell().isOff; return ( { const nextValue = parseFloat(e.currentTarget.value); @@ -318,12 +328,16 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps) })); props.table.setHasUnsavedChanges?.(true); }} - class={`w-16 px-2 py-0.5 text-sm text-center border rounded ${ + class={`${cell().follows && !isOff() ? 'w-20' : 'w-16'} px-2 py-0.5 text-sm text-center border rounded ${ isOff() ? 'border-border bg-surface-alt text-muted italic placeholder: dark:placeholder: placeholder:opacity-60 pointer-events-none' : 'border-border text-base-content focus:border-blue-500 focus:ring-1 focus:ring-blue-500' }`} - title={getAlertResourceTableMetricInputTitle(isOff())} + title={ + cell().follows + ? fallback()?.title + : getAlertResourceTableMetricInputTitle(isOff()) + } />