diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 4187baeba7..4930ba0eb8 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -6557,6 +6557,20 @@ Patrol scope payloads. API-backed TrueNAS systems may still keep `truenas` platform metadata and separate run-history coverage counts, but AI resource type fields must normalize to canonical `agent` once they cross the governed runtime boundary. +Patrol scope resolution registers the alert subsystem's Docker references as +aliases, built by the same `internal/alerts` builders rather than formatted +locally: `DockerHostResourceID` on the Docker host record, and on each +container record `dockerAlertScopeAlias`, which passes the container's name so +a container reported without an ID answers to its marked name reference +(`docker:/name:`) and never to its host's reference. +`dockerServiceAlertScopeAliases` registers service references, including +`docker:/service/name:` for a service without an ID, on the +owning host. The legacy snapshot record of a container without an ID has no ID +to scope by, so only the unified read state can scope its alerts. Proof: +`TestResolvePatrolScopeResolvesDockerAlertResourceIDs` in +`internal/ai/patrol_run_test.go` and +`TestDockerServiceAlertScopeAliasesDeriveIDFromName` in +`internal/ai/patrol_state_scope_alias_test.go`. The same governed-context rule also applies to the main unified AI resource overview: infrastructure, workload, alert-label, and top-consumer summaries must not leak raw resource names, cluster labels, IP addresses, or unresolved diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md index c0c94f7673..b2849c1d76 100644 --- a/docs/release-control/v6/internal/subsystems/alerts.md +++ b/docs/release-control/v6/internal/subsystems/alerts.md @@ -1532,6 +1532,45 @@ short-ID, unified-hash, and slash-tail forms as trailing lookup candidates). Container override work must not reintroduce a runtime-container-ID persistence key. +Docker alert references come only from the builders in +`internal/alerts/docker.go`: a host's own alert `docker:` +(`DockerHostResourceID`, used only by its connectivity alert), a container +`docker:/` (`DockerContainerResourceID`), and a Swarm +service `docker:/service/` (`DockerServiceResourceID`). +A container or service reported without an ID alerts under its name, marked in +the ID position (`docker:/name:`, +`docker:/service/name:`). Docker and Swarm IDs never +contain a colon, so a name reference never takes its host's reference or +another service's ID reference, and one with neither an ID nor a name raises +no alert. Report cleanup, host removal and the host's offline alert firing +clear children by the `docker:/` prefix; the container and service +policy switches classify by the first path segment after the host +(`dockerAlertResourcePath`), so a container name containing `/service/` stays +a container; and Patrol's scope aliases and resource history parse the same +forms. Service overrides key on the same reference, so an override written for +a service without an ID under its old unmarked name no longer applies. A +reference can contain the canonical state separator (a Docker host +disambiguated as `::`), so `splitCanonicalStateID` splits a +state ID before the alert's recorded spec ID, or else after its resource ID, +and only without either at the first `::`. Splitting at the first `::` gave +such a host's alerts the reference of the host named ``; an alert saved +that way recovers its reference on restore because its spec ID is intact. +Before name references existed, a container without an ID alerted under its +host's reference. Only the connectivity alert belongs there, so the host's +next report, or its offline alert firing, clears any such alert +left active whose recorded host matches and whose container ID is empty. A +pending image update recorded there keeps its age under the container's new +reference when that first report still shows the update pending; a first +report with missing or failed update evidence clears it, and the delay starts +again on the next positive report. Proof: +`TestDockerAlertsWithoutIDsUseTheirOwnReferences`, +`TestDockerHostIDWithSeparatorKeepsItsAlertReferences`, +`TestDockerAlertsRestoredWithCutShortReferencesRecover`, +`TestDockerContainerWithoutIDKeepsPendingUpdateAgeAcrossUpgrade` and +`TestDockerContainerResourceID` in `internal/alerts/alerts_test.go`, and +`TestOwnerAlertTimelinesUseCanonicalHistoryIdentity` in +`internal/monitoring/monitor_alert_handling_test.go`. + Backup orphan evaluation is also inventory-scoped. The alerts runtime may evaluate recovery rollups for backup age, but unresolved Proxmox PVE backup subjects must not be treated as orphaned until monitoring has supplied the diff --git a/docs/release-control/v6/internal/subsystems/monitoring.md b/docs/release-control/v6/internal/subsystems/monitoring.md index 9b79344156..166af64ef8 100644 --- a/docs/release-control/v6/internal/subsystems/monitoring.md +++ b/docs/release-control/v6/internal/subsystems/monitoring.md @@ -1201,14 +1201,15 @@ events (ZFS pools and devices, host filesystems, disks, RAID arrays, sensors and the Unraid array), pass through the same writer, which binds them to the Docker host, Swarm service, storage or host named in the unified-resources history identity clause. Container names and shortened container IDs still -bind nothing. `CheckDockerHost` gives a container without an ID its host's -reference, so that container's events join the host's history. A producer +bind nothing, and neither does the marked name a container or Swarm service +reported without an ID alerts under, so its events join neither its host's +history nor that of a service whose ID it is named like. A producer that changes one of these reference shapes must change `dockerHostHistoryReference` or `historySubResourceOwner` with it. `TestOwnerAlertTimelinesUseCanonicalHistoryIdentity` in `internal/monitoring/monitor_alert_handling_test.go` pins the Docker host, -Swarm service, ZFS pool and device, and host filesystem shapes against the -real alert manager. +Swarm service, ID-less container and service, ZFS pool and device, and host +filesystem shapes against the real alert manager. TrueNAS native alert projection preserves the trimmed, uppercase provider level in ResourceIncident.NativeSeverity. INFO and NOTICE retain the same canonical monitor risk; consumers must not lose their distinct actionability when projecting provider evidence. Native CRITICAL, ALERT, and EMERGENCY all project to canonical critical severity; EMERGENCY must not be discarded as unknown or make a still-active condition appear recovered. WARNING remains warning, and INFO and NOTICE remain informational at this projection boundary. diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 8ebe9ec0a6..140564c828 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -1990,6 +1990,15 @@ mode, row-hover mode, history range, and first-use hint on the same state owner for the generic surface and the Proxmox and vSphere platform compositions; provider pages must not recreate a partial metric prop list that can enable prefetching without exposing the matching control or discovery state. +That first-use hint clears while the pointer is over a guest row, because the +first populated preview marks it seen. Clearing it must not unmount it or +change the toolbar height: wherever the hint wraps the action row onto its own +line, removing it moved the table up under the pointer and the history lens +retargeted the next guest. `WorkloadsFilter.tsx` therefore keeps the cleared +hint mounted but `invisible` wherever the desktop History controls render, +for as long as row hover stays in bars + history mode. Leaving that mode or +remounting the filter releases the box. `WorkloadsFilter.test.tsx` pins the +retained node and its release on a switch to Trends. The alert-bridge patrol-trigger callback wired in `internal/api/router.go` now short-circuits before queuing a scoped patrol when a firing alert does not meet diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 91ea20e4f9..210427db08 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -5858,17 +5858,20 @@ Docker host alerts (`docker:`) and Swarm service alerts Docker source identities: the host ID is the Docker host's source ID, and a service reference names the service with that exact ID in the host's Swarm cluster, whichever manager reported it. A hostname in the host position is -only ever looked up as a Docker host source ID. A service without an ID is -referenced by its normalized name in the same place, so while its cluster has -such a service every service reference there is a conflict. Alert references -do not record whether they came from an ID, so a row journaled by an ID-less -service can still join another service's history if a later generation or -event binds the same reference; Swarm always reports service IDs, so that needs a malformed -report naming a service exactly like another service's ID. Hostless -`docker-service:` references, container names and shortened container -IDs never bind and are never retried against later inventory. A container -without an ID alerts under its host's reference (`alerts.DockerResourceID`), so -its events join that host's history, never another container's. +only ever looked up as a Docker host source ID. The registry keys a service +without an ID by its raw name, so the service entry must also carry exactly +the referenced ID. A container or service reported without an ID alerts under +its name, marked in the ID position (`docker:/name:` and +`docker:/service/name:`, from `alerts.DockerContainerResourceID` +and `alerts.DockerServiceResourceID`). Docker and Swarm IDs never contain a +colon, so such a reference never equals its host's reference or another +service's ID reference, and `dockerHostHistoryReference` treats a service slot +with a colon as a name. These name references, hostless `docker-service:` +references, container names and shortened container IDs never bind and are +never retried against later inventory. Rows journaled before name references +existed keep the reference they were written under: an ID-less container's +rows stay in its host's history, and an ID-less service's rows bind only to a +service in that cluster whose ID equals its normalized name. Sub-resource alert references name no resource of their own and bind to the owner whose durable ID they carry (`historySubResourceOwner`): ZFS pool and device alerts (`/zfs-pool:[/device:]`) to the diff --git a/frontend-modern/browser-verification/758bbf731f948f33.json b/frontend-modern/browser-verification/758bbf731f948f33.json new file mode 100644 index 0000000000..0aa4ba26b4 --- /dev/null +++ b/frontend-modern/browser-verification/758bbf731f948f33.json @@ -0,0 +1,56 @@ +{ + "version": 1, + "base_sha": "0fb5ccb62b2c8b07637c09547cc7e397e82925a7", + "verified_at": "2026-10-06T21:51:39Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/components/Workloads/WorkloadsFilter.tsx" + ], + "content_sha256": { + "frontend-modern/src/components/Workloads/WorkloadsFilter.tsx": "382e16198535227445f6cf823a1390502bcb24409ed2f12e19e028fa325793bf" + }, + "routes": [ + "/proxmox", + "/vmware" + ], + "viewports": [ + { + "width": 1440, + "height": 1000 + }, + { + "width": 1280, + "height": 1000 + }, + { + "width": 1600, + "height": 1000 + }, + { + "width": 768, + "height": 1000 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "Private mock stack (backend + Vite on 7797/5307) built from main 0fb5ccb62b (including the Net and Disk I/O row history lens) plus this change, re-run after rebasing from 3ac5664d95, compared with the same stack on unchanged main; large mock estate (50 nodes, 10 VMs + 8 LXCs each) so the guest list matches the reported repro", + "Fresh browser context so the first-use 'Hover a guest to preview history' hint is unseen; bars display mode with history row hover (the defaults)", + "Before, /proxmox at 1440 and 1280 with the page unscrolled: the hint wraps the filter action row onto its own line (filter bar 138px); after hovering artifact-cache-310 (675) the hint unmounted, the bar shrank to 102px, the row moved up 36px, pointer events went enter 675, leave 675, enter 679, and the history lens ended on auth-service-314 (679)", + "Before, /proxmox at 1600: hint inline on one row, bar 102px both ways, no shift", + "After, /proxmox at 1024, 1280, 1366, 1440, 1600 and 1920, both unscrolled and with a mid-list row scrolled to the centre: row y shift 0, filter bar height unchanged (138 or 102), a single pointerenter on the hovered row, and data-history-lens-active only on the hovered guest; the hint is still mounted with visibility hidden", + "After, /vmware at 1024 (wrapped, 138px) and 1440 (inline): row shift 0 and the lens stays on the hovered VM", + "After, 768: the hint span is display none so it has no box either way; bar 174px unchanged and the lens stays on the hovered row. 390: the mobile toolbar never renders the hint (54px before and after reload)", + "After the first preview, a reload shows no hint and the compact one-line toolbar (102px at 1440)", + "After the first preview, switching View > Metrics to Trends removes the hidden span instead of leaving a gap, and switching back to Bars does not bring it back" + ], + "interactions": [ + "Moved the real mouse onto a guest row's name cell (clear of the expand chevron) and waited for the populated history preview, recording the row bounding box, filter bar height, scroll position, pointerenter/pointerleave events and [data-history-lens-active] rows before and after", + "Repeated the hover with the page unscrolled and with the target row scrolled to the viewport centre", + "Reloaded the page after the first preview to check the next-visit toolbar", + "Opened View and switched Metrics between Trends and Bars after the hint had cleared", + "Captured screenshots of the toolbar and first rows before and after hover on main and on this change" + ] +} diff --git a/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx b/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx index 75814e225a..34d226b784 100644 --- a/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx +++ b/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx @@ -294,6 +294,20 @@ export const WorkloadsFilter: Component = (props) => { buildWorkloadSearchSuggestions(props.searchSuggestionWorkloads?.() ?? []), ); + // The history hint clears on the first populated row preview, while the + // pointer is over the table. Once shown, it keeps its box for as long as row + // hover previews history: removing it could unwrap the action row and slide + // the hovered guest out from under the pointer, retargeting the lens to the + // next row. Leaving bars + history hover, or remounting, releases the box. + const historyHintSlotReserved = createMemo( + (reserved) => + props.metricHistoryHintVisible?.() === true || + (reserved && + props.metricDisplayMode?.() === 'bars' && + (props.metricHoverMode?.() ?? 'history') === 'history'), + false, + ); + return ( = (props) => { } >
- +