diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index bd2fbe8a17..19ee8af773 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -8049,12 +8049,14 @@ backend deletion or destination delivery. ### Large platform notices keep the inventory in view -`PlatformOutdatedAgentNotice` previews at most three affected names and exposes -the full list through a keyboard-operable button when more hosts are outdated. -The count, update guidance and action link remain visible. This keeps a -large-estate stale-agent warning from pushing the platform inventory and -Storage search below the phone viewport while preserving every affected name -on demand. The component test pins collapsed, expanded and collapsed-again +`PlatformOutdatedAgentNotice` is maintenance guidance, not an incident, so it +renders as one sentence in the info tone: the count, what the update brings, +a keyboard-operable "Which hosts?" toggle (any count above one) that reveals +every affected name, and the upgrade link inline in the same line rather than +on an action row. This keeps the stale-agent notice from pushing the platform +inventory and Storage search below the fold at desktop and phone widths, and +from competing with real warnings for attention, while preserving every +affected name on demand. The component test pins collapsed, expanded and collapsed-again states; 1440px, 768px and 390px browser checks verify placement and overflow. The shared `InlineNotice` action text uses opaque 800-level colors for its four tones. The current Tailwind configuration overrides several 900-level palette diff --git a/frontend-modern/browser-verification/b7a057ac9a6f4dd8.json b/frontend-modern/browser-verification/b7a057ac9a6f4dd8.json new file mode 100644 index 0000000000..e2ec2c423c --- /dev/null +++ b/frontend-modern/browser-verification/b7a057ac9a6f4dd8.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "base_sha": "53e2d27ab420c505f54694b0ccbb7d843ff6eded", + "verified_at": "2026-10-05T00:02:30Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx" + ], + "content_sha256": { + "frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx": "b13db44a0fea2de8605bb79a1d1fd011f5023e9c8cba44f465c305eeac4f00b5" + }, + "routes": [ + "/proxmox/overview", + "/docker/overview", + "/kubernetes/overview" + ], + "viewports": [ + { + "width": 1440, + "height": 700 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "Mock estate with five Proxmox hosts, three Docker hosts and three Kubernetes nodes on an older agent. Before: a three-line amber warning listing three names plus \"and 2 more\", a Show all button and an action row above every platform tab.", + "After at 1440: one info-tone line on Proxmox (5 hosts run an older Pulse agent. Update them to v4.26.0 for the latest agent-contributed Proxmox node detail and command support. Which hosts? Open agent upgrade commands) with the node table directly below.", + "Phone 390x844 Kubernetes: the missing-data sentence, Which nodes? toggle and upgrade link wrap within the notice above the clusters table." + ], + "interactions": [ + "Loaded each route signed in and inspected screenshots; the component test exercises the Which hosts? toggle expanding and collapsing the affected names and checks the inline link href." + ] +} diff --git a/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx b/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx index bceef968f2..08f9a96b48 100644 --- a/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx +++ b/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx @@ -257,7 +257,7 @@ describe('KubernetesPageSurface contract', () => { renderSurface(); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('k8s-node-1 is running an older Pulse agent (v5.1.34).'); + expect(notice).toHaveTextContent('k8s-node-1 runs an older Pulse agent (v5.1.34), so'); expect(notice).toHaveTextContent('Kubernetes nodes, workloads, services, storage'); expect(screen.getByRole('link', { name: 'Open agent upgrade commands' })).toHaveAttribute( 'href', @@ -300,7 +300,7 @@ describe('KubernetesPageSurface contract', () => { renderSurface(); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('worker-1 is running an older Pulse agent (v5.1.34).'); + expect(notice).toHaveTextContent('worker-1 runs an older Pulse agent (v5.1.34), so'); expect(notice).toHaveTextContent('for this node'); expect(notice).toHaveTextContent('Kubernetes nodes, workloads, services, storage'); expect(screen.getByRole('link', { name: 'Open agent upgrade commands' })).toHaveAttribute( diff --git a/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.test.tsx b/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.test.tsx index 5117454821..c0b3a6ca0e 100644 --- a/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.test.tsx +++ b/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.test.tsx @@ -28,7 +28,7 @@ describe('PlatformOutdatedAgentNotice', () => { )); const notice = screen.getByTestId('platform-outdated-agent-notice'); expect(notice).toHaveTextContent( - 'tower is running an older Pulse agent (v6.0.0-rc.5). Update it to v6.0.0-rc.6 to see images, networks, and storage for this host.', + 'tower runs an older Pulse agent (v6.0.0-rc.5), so images, networks, and storage for this host may be missing.', ); expect(screen.getByRole('link', { name: 'Open agent upgrade commands' })).toHaveAttribute( 'href', @@ -36,23 +36,7 @@ describe('PlatformOutdatedAgentNotice', () => { ); }); - it('summarises multiple outdated hosts and lists them', () => { - render(() => ( - - )); - const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('2 hosts are running an older Pulse agent.'); - expect(notice).toHaveTextContent('Affected: tower, delly.'); - }); - - it('keeps a large affected-host list behind an expandable preview', () => { + it('summarises multiple outdated hosts in one line and keeps names behind a toggle', () => { render(() => ( { { name: 'remote', version: 'v6.0.0-rc.5' }, ]} targetVersion="v6.0.0-rc.6" - missingLabel="images" + missingLabel="images, networks, and storage" + actionHref="/settings/infrastructure" + actionLabel="Open agent upgrade commands" /> )); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('Affected: tower, delly, lab, and 1 more.'); - expect(notice).not.toHaveTextContent('Affected: tower, delly, lab, remote.'); + expect(notice).toHaveTextContent( + '4 hosts run an older Pulse agent, so images, networks, and storage may be missing.', + ); + // Maintenance, not an incident: the info tone, with names on demand. + expect(notice.className).toContain('bg-blue-50'); + expect(notice).not.toHaveTextContent('Affected:'); + expect(screen.getByRole('link', { name: 'Open agent upgrade commands' })).toHaveAttribute( + 'href', + '/settings/infrastructure', + ); - const toggle = screen.getByRole('button', { name: 'Show all 4 hosts' }); + const toggle = screen.getByRole('button', { name: 'Which hosts?' }); expect(toggle).toHaveAttribute('aria-expanded', 'false'); fireEvent.click(toggle); expect(toggle).toHaveAttribute('aria-expanded', 'true'); expect(notice).toHaveTextContent('Affected: tower, delly, lab, remote.'); - fireEvent.click(screen.getByRole('button', { name: 'Hide affected names' })); - expect(notice).not.toHaveTextContent('Affected: tower, delly, lab, remote.'); + fireEvent.click(screen.getByRole('button', { name: 'Hide names' })); + expect(notice).not.toHaveTextContent('Affected:'); }); it('uses latest-detail copy for hybrid platform pages', () => { @@ -90,7 +84,7 @@ describe('PlatformOutdatedAgentNotice', () => { )); expect(screen.getByTestId('platform-outdated-agent-notice')).toHaveTextContent( - 'delly is running an older Pulse agent (v5.1.34). Update it to v6.0.0-rc.6 for the latest agent-contributed platform detail on this host.', + 'delly runs an older Pulse agent (v5.1.34). Update it to v6.0.0-rc.6 for the latest agent-contributed platform detail.', ); }); @@ -135,10 +129,10 @@ describe('PlatformOutdatedAgentNotice', () => { )); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('2 VMs are running an older Pulse agent.'); expect(notice).toHaveTextContent( - 'Update them to v6.0.0-rc.6 for the latest in-guest telemetry and command support.', + '2 VMs run an older Pulse agent. Update them to v6.0.0-rc.6 for the latest in-guest telemetry and command support.', ); + fireEvent.click(screen.getByRole('button', { name: 'Which VMs?' })); expect(notice).toHaveTextContent('Affected: app-01, db-01.'); }); }); diff --git a/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx b/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx index dd2c53ad4d..9889de0422 100644 --- a/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx +++ b/frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx @@ -1,5 +1,5 @@ import { Show, createMemo, createSignal } from 'solid-js'; -import { AlertTriangle, ArrowRight } from 'lucide-solid'; +import { ArrowRight, Info } from 'lucide-solid'; import { InlineNotice } from '@/components/shared/InlineNotice'; import { presentationPolicyIsReadOnly } from '@/stores/sessionPresentationPolicy'; import type { OutdatedAgentHost } from './agentVersion'; @@ -26,18 +26,14 @@ type PlatformOutdatedAgentNoticeProps = { // is rendered only when there is an actually-outdated resource, so the page // stays clean in the healthy case. This is the breadcrumb that distinguishes a // genuinely-empty detail tab from one hidden by a stale agent. +// +// It is maintenance, not an incident, so it is one quiet line in the info tone +// rather than a multi-line warning above every tab: the affected names sit +// behind a toggle and the update action stays one click away. export function PlatformOutdatedAgentNotice(props: PlatformOutdatedAgentNoticeProps) { const count = createMemo(() => props.hosts.length); const names = createMemo(() => props.hosts.map((host) => host.name).join(', ')); const [showAllHosts, setShowAllHosts] = createSignal(false); - const preview = createMemo(() => { - const firstNames = props.hosts - .slice(0, 3) - .map((host) => host.name) - .join(', '); - const remaining = count() - 3; - return remaining > 0 ? `${firstNames}, and ${remaining} more` : firstNames; - }); const actionLabel = createMemo(() => props.actionLabel || 'Open Infrastructure settings'); const subjectSingular = createMemo(() => props.subjectSingular || 'host'); const subjectPlural = createMemo(() => props.subjectPlural || 'hosts'); @@ -49,14 +45,14 @@ export function PlatformOutdatedAgentNotice(props: PlatformOutdatedAgentNoticePr if (count() === 1) { const host = props.hosts[0]; if (copyVariant === 'latest-detail') { - return `${host.name} is running an older Pulse agent (${host.version}). Update it${target} for the latest ${props.missingLabel} on this ${subjectSingular()}.`; + return `${host.name} runs an older Pulse agent (${host.version}). Update it${target} for the latest ${props.missingLabel}.`; } - return `${host.name} is running an older Pulse agent (${host.version}). Update it${target} to see ${props.missingLabel} for this ${subjectSingular()}.`; + return `${host.name} runs an older Pulse agent (${host.version}), so ${props.missingLabel} for this ${subjectSingular()} may be missing.`; } if (copyVariant === 'latest-detail') { - return `${count()} ${subjectPlural()} are running an older Pulse agent. Update them${target} for the latest ${props.missingLabel}. Affected: ${preview()}.`; + return `${count()} ${subjectPlural()} run an older Pulse agent. Update them${target} for the latest ${props.missingLabel}.`; } - return `${count()} ${subjectPlural()} are running an older Pulse agent. Update them${target} to see ${props.missingLabel}. Affected: ${preview()}.`; + return `${count()} ${subjectPlural()} run an older Pulse agent, so ${props.missingLabel} may be missing.`; }); return ( @@ -64,25 +60,37 @@ export function PlatformOutdatedAgentNotice(props: PlatformOutdatedAgentNoticePr diff --git a/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx b/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx index 3c8e76bb57..73a1dee144 100644 --- a/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx +++ b/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx @@ -246,7 +246,7 @@ describe('ProxmoxPageSurface contract', () => { expect(screen.getByTestId('platform-section-tabs')).toHaveAttribute('data-active', 'overview'); expect(screen.getByTestId('nodes-table')).toHaveAttribute('data-rows', '1'); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('delly is running an older Pulse agent (v5.1.34).'); + expect(notice).toHaveTextContent('delly runs an older Pulse agent (v5.1.34).'); expect(notice).toHaveTextContent( 'latest agent-contributed Proxmox node detail and command support', ); diff --git a/frontend-modern/src/features/standalone/__tests__/StandalonePageSurface.test.tsx b/frontend-modern/src/features/standalone/__tests__/StandalonePageSurface.test.tsx index 326d253609..c2afa09e5a 100644 --- a/frontend-modern/src/features/standalone/__tests__/StandalonePageSurface.test.tsx +++ b/frontend-modern/src/features/standalone/__tests__/StandalonePageSurface.test.tsx @@ -252,7 +252,7 @@ describe('StandalonePageSurface', () => { render(() => ); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('tower is running an older Pulse agent (v5.1.34).'); + expect(notice).toHaveTextContent('tower runs an older Pulse agent (v5.1.34).'); expect(notice).toHaveTextContent( 'latest agent command support and agent-managed platform detail', ); diff --git a/frontend-modern/src/features/truenas/__tests__/TrueNASPageSurface.contract.test.tsx b/frontend-modern/src/features/truenas/__tests__/TrueNASPageSurface.contract.test.tsx index 6fd68a146a..8220220c63 100644 --- a/frontend-modern/src/features/truenas/__tests__/TrueNASPageSurface.contract.test.tsx +++ b/frontend-modern/src/features/truenas/__tests__/TrueNASPageSurface.contract.test.tsx @@ -179,7 +179,7 @@ describe('TrueNASPageSurface contract', () => { ); expect(screen.getByTestId('systems-table')).toHaveAttribute('data-rows', '1'); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('truenas-scale is running an older Pulse agent (v5.1.34).'); + expect(notice).toHaveTextContent('truenas-scale runs an older Pulse agent (v5.1.34).'); expect(notice).toHaveTextContent( 'latest agent-contributed TrueNAS system detail and command support', ); diff --git a/frontend-modern/src/features/vmware/__tests__/VmwarePageSurface.contract.test.tsx b/frontend-modern/src/features/vmware/__tests__/VmwarePageSurface.contract.test.tsx index cc7d5952cc..66733ca225 100644 --- a/frontend-modern/src/features/vmware/__tests__/VmwarePageSurface.contract.test.tsx +++ b/frontend-modern/src/features/vmware/__tests__/VmwarePageSurface.contract.test.tsx @@ -178,8 +178,8 @@ describe('VmwarePageSurface contract', () => { expect(screen.getByTestId('platform-section-tabs')).toHaveAttribute('data-active', 'overview'); expect(screen.getByTestId('hosts-table')).toHaveAttribute('data-rows', '1'); const notice = screen.getByTestId('platform-outdated-agent-notice'); - expect(notice).toHaveTextContent('app-01 is running an older Pulse agent (v5.1.34).'); - expect(notice).toHaveTextContent('latest in-guest telemetry and command support on this VM'); + expect(notice).toHaveTextContent('app-01 runs an older Pulse agent (v5.1.34).'); + expect(notice).toHaveTextContent('latest in-guest telemetry and command support.'); expect(screen.getByRole('link', { name: 'Open agent upgrade commands' })).toHaveAttribute( 'href', '/settings/infrastructure/agent-doctor?agents=agent%3Aagent-app-01',