mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Turn the older-agent notice into one quiet line
Every Proxmox, Docker, Kubernetes, TrueNAS, vSphere and Machines tab opened with a three-line amber warning about hosts on an older Pulse agent, listing names and a Show all button above the monitoring data. An outdated agent is maintenance, not an incident, and the warning competed with real problems for attention on every visit. The notice is now one sentence in the info tone with the upgrade link inline. The affected names sit behind a "Which hosts?" toggle, so every name is still one click away and the inventory starts higher on the page.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
}
|
||||
+2
-2
@@ -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(
|
||||
|
||||
@@ -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(() => (
|
||||
<PlatformOutdatedAgentNotice
|
||||
hosts={[
|
||||
{ name: 'tower', version: 'v6.0.0-rc.5' },
|
||||
{ name: 'delly', version: 'v6.0.0-rc.5' },
|
||||
]}
|
||||
targetVersion="v6.0.0-rc.6"
|
||||
missingLabel="images, networks, and storage"
|
||||
/>
|
||||
));
|
||||
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(() => (
|
||||
<PlatformOutdatedAgentNotice
|
||||
hosts={[
|
||||
@@ -62,21 +46,31 @@ describe('PlatformOutdatedAgentNotice', () => {
|
||||
{ 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.');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
<InlineNotice
|
||||
role="status"
|
||||
data-testid="platform-outdated-agent-notice"
|
||||
tone="warning"
|
||||
icon={<AlertTriangle aria-hidden="true" />}
|
||||
actionHref={props.actionHref}
|
||||
actionLabel={actionLabel()}
|
||||
actionIcon={<ArrowRight aria-hidden="true" />}
|
||||
tone="info"
|
||||
icon={<Info aria-hidden="true" />}
|
||||
>
|
||||
<span>{message()}</span>{' '}
|
||||
<Show when={count() > 3}>
|
||||
<span>{message()}</span>
|
||||
<Show when={count() > 1}>
|
||||
{' '}
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 font-medium underline underline-offset-2 hover:no-underline focus-visible:rounded-xs focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2"
|
||||
class="font-medium underline underline-offset-2 hover:no-underline focus-visible:rounded-xs focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2"
|
||||
aria-expanded={showAllHosts()}
|
||||
onClick={() => setShowAllHosts((current) => !current)}
|
||||
>
|
||||
{showAllHosts() ? 'Hide affected names' : `Show all ${count()} ${subjectPlural()}`}
|
||||
{showAllHosts() ? 'Hide names' : `Which ${subjectPlural()}?`}
|
||||
</button>
|
||||
<Show when={showAllHosts()}>
|
||||
<p class="mt-1 wrap-break-word">Affected: {names()}.</p>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={props.actionHref}>
|
||||
{(href) => (
|
||||
<>
|
||||
{' '}
|
||||
<a
|
||||
href={href()}
|
||||
class="inline-flex items-center gap-1 whitespace-nowrap font-semibold underline-offset-2 hover:underline"
|
||||
>
|
||||
{actionLabel()}
|
||||
<ArrowRight aria-hidden="true" class="h-3.5 w-3.5" />
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={count() > 1 && showAllHosts()}>
|
||||
<p class="mt-1 wrap-break-word">Affected: {names()}.</p>
|
||||
</Show>
|
||||
</InlineNotice>
|
||||
</Show>
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
@@ -252,7 +252,7 @@ describe('StandalonePageSurface', () => {
|
||||
render(() => <StandalonePageSurface />);
|
||||
|
||||
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',
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user