Rename Standalone surface to Machines

This commit is contained in:
rcourtman
2026-05-25 21:51:31 +01:00
parent 7470b62a01
commit 7cf0a474ee
15 changed files with 86 additions and 82 deletions
@@ -145,16 +145,17 @@ runtime cost control, and shared AI transport surfaces.
the VMware page must not introduce VMware-local AI prompts, a provider
model picker, or a vSphere-specific chat/runtime route just because
networks are now rendered as a first-class API-native table.
The frontend-primitives-owned `Standalone` platform page follows the same
AI runtime boundary: route registration and primary-tab chrome may expose
the support-manifest `agent` platform plus agentless availability
endpoints, but the page must not add an agent-specific Assistant prompt
surface, AI launcher, model picker, or browser-owned model context.
Standalone rows may seed Assistant or Patrol only through the shared
unified resource handoff contracts. Its IA, primary shell position, and
landing eligibility belong to `frontend-primitives`; those changes must
not alter Patrol or Assistant utility-tab ordering, launcher visibility, or
shared keyboard handling in `frontend-modern/src/AppLayout.tsx`.
The frontend-primitives-owned Machines surface follows the same AI runtime
boundary while retaining the internal `standalone` route/id contract: route
registration and primary-tab chrome may expose the support-manifest `agent`
platform plus agentless availability endpoints, but the page must not add an
agent-specific Assistant prompt surface, AI launcher, model picker, or
browser-owned model context. Machine rows may seed Assistant or Patrol only
through the shared unified resource handoff contracts. Its IA, primary
shell position, and landing eligibility belong to `frontend-primitives`;
those changes must not alter Patrol or Assistant utility-tab ordering,
launcher visibility, or shared keyboard handling in
`frontend-modern/src/AppLayout.tsx`.
## Forbidden Paths
@@ -687,8 +688,10 @@ adding a platform tab through the same shell files must not fork Assistant or
Patrol shell state or smuggle in AI-owned platform reads.
Post-auth `/` and `/login` resolution follows the frontend-primitives-owned
provider-first platform landing contract, so the assistant-capable shell never
overrides Standalone eligibility or revives legacy Infrastructure as the
default estate surface.
overrides Machines-surface eligibility or revives legacy Infrastructure as the
default estate surface. The user-facing Machines label is an app-shell
presentation label for the existing `standalone` route/id and must not create a
separate AI handoff or prompt namespace.
The route-backed Proxmox platform tab is app-shell navigation only. Adding the
tab through `frontend-modern/src/App.tsx` and
@@ -500,7 +500,7 @@ or other self-hosted uncapped continuity plans.
Paid and hosted app shells must inherit the frontend-primitives-owned
platform/runtime primary navigation contract and canonical path constants
from `frontend-modern/src/routing/resourceLinks.ts`; cloud-paid must not
carry a second copy of the platform list, shell order, or Standalone
carry a second copy of the platform list, shell order, or Machines-surface
eligibility rules. Product navigation remains support-and-evidence gated:
`enabled`, `live`, and inclusion in the rendered primary navigation derive
from the governed support manifest plus canonical resource evidence in
@@ -521,9 +521,10 @@ or other self-hosted uncapped continuity plans.
bespoke per-family tables, synthetic placeholder data, or reintroduce
Infrastructure as a primary navigation entry without a governed contract
decision recorded by the frontend-primitives owner.
The presence of a `Standalone` tab is not a new commercial usage unit:
hosted and paid surfaces continue to meter the governed monitored-system
grouping result rather than counting primary navigation destinations.
The presence of the user-facing `Machines` tab, backed by the internal
`standalone` route/id, is not a new commercial usage unit: hosted and paid
surfaces continue to meter the governed monitored-system grouping result
rather than counting primary navigation destinations.
Landing behavior for paid and hosted shells must also defer to the
frontend-primitives-owned provider-first landing contract instead of
defining a cloud-paid-specific order.
@@ -1097,26 +1097,28 @@ not a replacement status card, CTA band, or page-local nested card.
that platform-first set only, so mobile and desktop navigation stay aligned
without reintroducing aggregate Workloads / Storage / Recovery workspace
tabs or the legacy Infrastructure entry.
Frontend primitives owns the sole user-facing `Standalone` IA contract for
the support-manifest `agent` platform and agentless availability endpoints;
adjacent subsystem contracts may reference this owner for dependencies but
must not restate the route, navigation, or landing semantics. Its primary
tab, mobile priority, command-palette destination, and keyboard shortcut
must all route through `buildStandalonePath()` and the
`PrimaryInfrastructureNavId` `standalone` evidence gate; they must not
create a generic Hosts, Nodes, Other, or mixed-systems bucket, and they
must not include provider-owned platform nodes that are not canonical
standalone resources. The Standalone page is a platform/runtime page, not a
legacy Infrastructure page: it must use the shared platform tab, toolbar,
table-card, and kind-aligned column primitives, and it must not reintroduce
the old top-of-page InfrastructureSummary chart strip. Standalone must also
remain secondary in the shell hierarchy when provider/runtime platform
evidence exists: `PRIMARY_INFRASTRUCTURE_NAV_IDS`, desktop primary tabs,
mobile primary priority, app-shell preload order, authenticated landing
fallback, and command-palette ordering must prefer Proxmox, Containers,
Kubernetes, TrueNAS, and vSphere ahead of Standalone. Standalone may win
those first/default positions only when the current estate has standalone
Pulse Agent machines or agentless availability endpoints and no
Frontend primitives owns the sole user-facing `Machines` IA contract for
the support-manifest `agent` platform and agentless availability endpoints.
The compatibility route, internal navigation id, and builders remain
`standalone` / `buildStandalonePath()`; adjacent subsystem contracts may
reference this owner for dependencies but must not restate the route,
navigation, or landing semantics. Its primary tab, mobile priority,
command-palette destination, and keyboard shortcut must all route through
`buildStandalonePath()` and the `PrimaryInfrastructureNavId` `standalone`
evidence gate; they must not create a generic Hosts, Nodes, Other, or
mixed-systems bucket, and they must not include provider-owned platform
nodes that are not canonical machine-page resources. The Machines page is a
platform/runtime page, not a legacy Infrastructure page: it must use the
shared platform tab, toolbar, table-card, and kind-aligned column
primitives, and it must not reintroduce the old top-of-page
InfrastructureSummary chart strip. The Machines surface must also remain
secondary in the shell hierarchy when provider/runtime platform evidence exists:
`PRIMARY_INFRASTRUCTURE_NAV_IDS`, desktop primary tabs, mobile primary
priority, app-shell preload order, authenticated landing fallback, and
command-palette ordering must prefer Proxmox, Containers, Kubernetes,
TrueNAS, and vSphere ahead of Machines. The Machines surface may win those
first/default positions only when the current estate has standalone Pulse
Agent machines or agentless availability endpoints and no
provider/runtime platform evidence.
## Forbidden Paths
@@ -1412,9 +1414,10 @@ not a replacement status card, CTA band, or page-local nested card.
rules.
`frontend-modern/src/App.tsx` must land authenticated `/` and `/login`
handoffs through this subsystem's provider-first platform landing contract:
the first visible provider/runtime platform wins, and Standalone is eligible
only when the current estate has standalone Pulse Agent machines or
agentless availability endpoints and no provider/runtime evidence. The
the first visible provider/runtime platform wins, and the Machines surface
is eligible only when the current estate has standalone Pulse Agent
machines or agentless availability endpoints and no provider/runtime
evidence. The
retired Infrastructure aggregate route and nested settings infrastructure
aliases are not compatibility commitments:
first-time operator setup must enter through the canonical Settings →
@@ -2564,7 +2567,7 @@ retired or unknown routes such as `/dashboard` must not be coerced into the
nearest platform tab just because the authenticated shell has a provider-first
landing fallback. Shared desktop and mobile navigation must tolerate a missing
active tab for those paths while still highlighting canonical active routes
such as Proxmox, Containers, Kubernetes, TrueNAS, vSphere, Standalone, Alerts,
such as Proxmox, Containers, Kubernetes, TrueNAS, vSphere, Machines, Alerts,
Patrol, and Settings.
The recovery feature shell now also depends on the shared
`frontend-modern/src/components/shared/Subtabs.tsx` primitive for its primary
@@ -3583,11 +3586,11 @@ metric slot, while keeping recent check timing and fuller failure context in
the tooltip or drawer so operators can understand what was measured without
duplicated row chrome.
Operational navigation for those agentless endpoints belongs to the
frontend-primitives-owned Standalone surface rather than a new primary nav item.
frontend-primitives-owned Machines surface rather than a new primary nav item.
The page may show availability checks beside standalone Pulse Agent machines and
provide a focused availability tab, but Settings remains the add/edit owner and
the app shell must not add a separate top-level Availability destination.
The Standalone page must not pretend its machine list is a generic overview:
The Machines page must not pretend its machine list is a generic overview:
the default tab is `Machines`, and the full availability-check row list belongs
to the `Availability checks` tab. Servers, laptops, desktops, and comparable
computers that are monitored by agentless reachability checks are still
@@ -688,11 +688,11 @@ AI-only summary payloads, or page-local heuristics.
derive every internal link from those builders so the canonical resource
URL vocabulary stays single-sourced; ad hoc string concatenation of
platform routes inside feature directories is not permitted.
Standalone's default resource route is the machines projection
The user-facing Machines surface's default resource route is the machines projection
(`/standalone/machines`); agentless endpoint rows use the
`/standalone/availability` projection and must not be collapsed into a
generic overview URL.
The frontend-primitives-owned Standalone IA contract consumes the
The frontend-primitives-owned Machines IA contract consumes the
unified-resource projection for Pulse-managed standalone agent rows and
agentless availability endpoint rows; this subsystem owns only the
membership rules for those projected rows. Agent membership must require
@@ -700,7 +700,7 @@ AI-only summary payloads, or page-local heuristics.
resource sources or source status, and no stronger provider-owner evidence
from Proxmox, VMware, TrueNAS, or Kubernetes. Source-less legacy snapshots
may fall back to a normalized `platformType === "agent"`, but
provider-owned nodes must not become Standalone-page members through
provider-owned nodes must not become machine-page members through
hostname, `agent` platform scope, or agent telemetry alone; those facts
surface as facets on the owning provider page.
The default tab for each platform path must point at a sub-tab whose
@@ -740,9 +740,8 @@ AI-only summary payloads, or page-local heuristics.
`/settings/monitoring/availability`, and the add flow for MQTT, ping, TCP,
and HTTP checks is `/settings/monitoring/availability?add=target`. Saved
availability targets carry a `targetKind` so agentless servers, laptops,
desktops, and comparable computers can participate in the Standalone
Machines inventory without reclassifying service or device checks as
machines.
desktops, and comparable computers can participate in the Machines inventory
without reclassifying service or device checks as machines.
6. Keep infrastructure source visibility on canonical unified-resource truth.
Settings infrastructure source filters and summaries must preserve known
sources such as `truenas` and `availability` through the shared source
@@ -1149,7 +1148,7 @@ or the live-state `platformData.availability` mirror: the System column uses
the probe protocol as the compact identity badge (`ICMP`, `TCP`, or `HTTP`),
while the metric cell shows only the target detail and latest latency or
failure result, such as `6053: 11 ms`, `/status: 503`, `3 ms`, or `timed out`.
Frontend primitives owns Standalone as the operational presentation for those
Frontend primitives owns Machines as the operational presentation for those
same agentless checks; unified resources owns the projection contract consumed
there. `StandalonePageSurface.tsx` must fetch both `agent` and
`network-endpoint` resources, keep standalone machines and availability checks
@@ -2803,7 +2802,7 @@ redirect. Canonical infrastructure management lives under
lives under `/settings/monitoring/availability`; day-to-day resource
inspection lives on the platform/runtime pages named by the
frontend-primitives-owned IA contract, with unified resources projecting
standalone agent machines and agentless endpoint checks for the Standalone
standalone agent machines and agentless endpoint checks for the Machines
consumer. Future infrastructure work must extend those owners rather than
recreating
`frontend-modern/src/features/infrastructure/`, a `/infrastructure` route, or
+3 -3
View File
@@ -213,7 +213,7 @@ export function AppLayout(props: AppLayoutProps) {
kubernetes: 'Kubernetes',
truenas: 'TrueNAS',
vmware: 'vSphere',
standalone: 'Standalone',
standalone: 'Machines',
alerts: 'Alerts',
ai: 'Patrol',
settings: 'Settings',
@@ -387,10 +387,10 @@ export function AppLayout(props: AppLayoutProps) {
},
{
id: 'standalone',
label: 'Standalone',
label: 'Machines',
route: ROOT_STANDALONE_PATH,
settingsRoute: ROOT_INFRASTRUCTURE_SETTINGS_PATH,
tooltip: 'Standalone Pulse Agent machines and agentless availability checks',
tooltip: 'Pulse Agent machines, agentless computers, and availability checks',
enabled: isVisible('standalone'),
live: isVisible('standalone'),
icon: ServerIcon,
@@ -67,9 +67,7 @@ describe('App architecture', () => {
expect(appRuntimeStateSource).not.toContain('fetchInfrastructureSummaryAndCache');
expect(appRuntimeStateSource).not.toContain('fetchWorkloadsSummaryAndCache');
expect(appRuntimeStateSource).not.toContain('requestIdleCallback');
expect(appSource).toContain(
"const StandalonePage = lazy(() => import('./pages/Standalone'));",
);
expect(appSource).toContain("const StandalonePage = lazy(() => import('./pages/Standalone'));");
expect(appSource).toContain('<Route path={STANDALONE_PATH} component={StandalonePage} />');
expect(appSource).toContain(
'<Route path={`${STANDALONE_PATH}/*`} component={StandalonePage} />',
@@ -103,7 +101,7 @@ describe('App architecture', () => {
"tooltip: 'VMware vSphere hosts, virtual machines, datastores, and networks'",
);
expect(appLayoutSource).toContain(
"tooltip: 'Standalone Pulse Agent machines and agentless availability checks'",
"tooltip: 'Pulse Agent machines, agentless computers, and availability checks'",
);
// Governed shell nav: Infrastructure, Workloads, Storage, and Recovery are
// not standalone shell tabs; platform/runtime pages own those workflows.
@@ -131,9 +131,9 @@ describe('AppLayout navigation icons', () => {
within(infrastructureGroup as HTMLElement)
.getAllByRole('tab')
.map((tab) => tab.getAttribute('aria-label')),
).toEqual(['Proxmox', 'Containers', 'vSphere', 'Standalone']);
).toEqual(['Proxmox', 'Containers', 'vSphere', 'Machines']);
expect(
within(infrastructureGroup as HTMLElement).getByRole('tab', { name: 'Standalone' }),
within(infrastructureGroup as HTMLElement).getByRole('tab', { name: 'Machines' }),
).toBeTruthy();
expect(
within(infrastructureGroup as HTMLElement).getByRole('tab', { name: 'Proxmox' }),
@@ -19,7 +19,7 @@ interface KeyboardShortcutsModalProps {
const UNIFIED_NAV_SHORTCUTS: ShortcutGroup = {
title: 'Navigation',
items: [
{ keys: 'g then s', description: 'Go to Standalone' },
{ keys: 'g then s', description: 'Go to Machines' },
{ keys: 'g then p', description: 'Go to Proxmox' },
{ keys: 'g then d', description: 'Go to Containers' },
{ keys: 'g then k', description: 'Go to Kubernetes' },
@@ -94,7 +94,7 @@ describe('CommandPaletteModal', () => {
/>
));
expect(screen.getByText('Go to Standalone')).toBeInTheDocument();
expect(screen.getByText('Go to Machines')).toBeInTheDocument();
expect(screen.getByText('/standalone/machines')).toBeInTheDocument();
expect(screen.getByText('Go to Proxmox')).toBeInTheDocument();
expect(screen.getByText('Go to Containers')).toBeInTheDocument();
@@ -112,7 +112,7 @@ describe('CommandPaletteModal', () => {
const commandLabels = screen.getAllByRole('button').map((button) => button.textContent ?? '');
expect(commandLabels.findIndex((label) => label.includes('Go to Proxmox'))).toBeLessThan(
commandLabels.findIndex((label) => label.includes('Go to Standalone')),
commandLabels.findIndex((label) => label.includes('Go to Machines')),
);
});
@@ -183,7 +183,7 @@ describe('CommandPaletteModal', () => {
expect(screen.queryByText('Go to Workloads')).not.toBeInTheDocument();
expect(screen.queryByText('Go to Storage')).not.toBeInTheDocument();
expect(screen.queryByText('Go to Recovery')).not.toBeInTheDocument();
expect(screen.queryByText('Go to Standalone')).not.toBeInTheDocument();
expect(screen.queryByText('Go to Machines')).not.toBeInTheDocument();
expect(screen.queryByText('Go to Containers')).not.toBeInTheDocument();
expect(screen.queryByText('Go to Kubernetes')).not.toBeInTheDocument();
expect(screen.queryByText('Go to TrueNAS')).not.toBeInTheDocument();
@@ -90,10 +90,10 @@ describe('MobileNavBar', () => {
primaryTabs={() => [
{
id: 'standalone',
label: 'Standalone',
label: 'Machines',
route: '/standalone/machines',
settingsRoute: '/settings/infrastructure',
tooltip: 'Standalone',
tooltip: 'Machines',
enabled: true,
live: true,
icon: StandaloneIcon,
@@ -135,10 +135,10 @@ describe('MobileNavBar', () => {
primaryTabs={() => [
{
id: 'standalone',
label: 'Standalone',
label: 'Machines',
route: '/standalone/machines',
settingsRoute: '/settings/infrastructure',
tooltip: 'Standalone',
tooltip: 'Machines',
enabled: true,
live: true,
icon: StandaloneIcon,
@@ -200,7 +200,7 @@ describe('MobileNavBar', () => {
expect(screen.getByText('3')).toBeInTheDocument();
expect(screen.getByText('Pro')).toBeInTheDocument();
fireEvent.click(screen.getByTitle('Standalone'));
fireEvent.click(screen.getByTitle('Machines'));
expect(onPrimaryClick).toHaveBeenCalledWith(expect.objectContaining({ id: 'standalone' }));
fireEvent.click(screen.getByTitle('Alerts'));
@@ -112,7 +112,7 @@ export function buildCommandPaletteCommands(options: {
if (primaryInfrastructureNavigationIsVisible(options.infrastructureVisibility, 'standalone')) {
commands.push({
id: 'nav-standalone',
label: 'Go to Standalone',
label: 'Go to Machines',
description: options.paths.standalonePath,
shortcut: 'g s',
keywords: [
@@ -189,13 +189,13 @@ export const AgentsMachinesTable: Component<{
<PlatformTableToolbar
search={tableState.search}
onSearchChange={tableState.setSearch}
searchPlaceholder="Search standalone machines"
searchPlaceholder="Search machines"
status={tableState.status()}
onStatusChange={tableState.setStatus}
statusOptions={PLATFORM_HEALTH_FILTER_OPTIONS}
visible={tableState.visible()}
total={tableState.total()}
rowNoun="standalone machines"
rowNoun="machines"
/>
<Show
@@ -204,7 +204,7 @@ export const AgentsMachinesTable: Component<{
<PlatformTableEmptyState
icon={props.emptyIcon}
title="No machines match current filters"
description="Adjust the search or status filter to see more standalone Pulse Agent machines."
description="Adjust the search or status filter to see more Pulse Agent or agentless machines."
/>
}
>
@@ -81,15 +81,15 @@ export function StandalonePageSurface() {
<PlatformSectionTabs
tabs={STANDALONE_TAB_SPECS}
active={activeTab()}
ariaLabel="Standalone sections"
ariaLabel="Machines sections"
/>
<Show
when={!showLoading()}
fallback={
<PlatformTableLoadingState
title="Loading standalone resources"
description="Pulse is loading standalone machines and availability checks."
title="Loading machines"
description="Pulse is loading Pulse Agent machines, agentless computers, and availability checks."
/>
}
>
@@ -97,7 +97,7 @@ export function StandalonePageSurface() {
when={!error()}
fallback={
<PlatformErrorState
title="Could not load standalone resources"
title="Could not load machines"
description="Refresh the resource snapshot or check the API connection state."
onRefresh={() => void refetch()}
/>
@@ -118,7 +118,7 @@ export function StandalonePageSurface() {
fallback={
<PlatformTableEmptyState
icon={machineIcon()}
title="No standalone monitored endpoints"
title="No monitored machines"
description="Install Pulse Agent on servers, laptops, and desktops for full telemetry, or add an agentless machine check when reachability is enough."
actions={
<div class="flex flex-wrap items-center justify-center gap-2">
@@ -149,7 +149,7 @@ export function StandalonePageSurface() {
<Show when={model().availabilityChecks.length > 0}>
<PlatformTableEmptyState
icon={machineIcon()}
title="No standalone machines"
title="No machines"
description="Service and device checks are monitored from the Availability checks tab. Mark an availability target as a machine when it represents a server, laptop, or desktop."
actions={
<A href={buildStandalonePath('availability')} class={overviewActionClass}>
@@ -164,7 +164,7 @@ export function StandalonePageSurface() {
<AgentsMachinesTable
resources={model().machines}
emptyIcon={machineIcon()}
emptyTitle="No standalone machines"
emptyTitle="No machines"
emptyDescription="Install the Pulse Agent on Linux, macOS, Windows, or Unraid systems for full telemetry, or add an agentless machine check when reachability is enough."
/>
</Show>
@@ -176,7 +176,7 @@ describe('StandalonePageSurface', () => {
expect(screen.queryByTestId('agents-machines-table')).not.toBeInTheDocument();
expect(screen.queryByTestId('availability-checks-table')).not.toBeInTheDocument();
expect(screen.getByText('No standalone machines')).toBeInTheDocument();
expect(screen.getByText('No machines')).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'View checks' })).toHaveAttribute(
'href',
'/standalone/availability',
@@ -202,6 +202,6 @@ describe('StandalonePageSurface', () => {
render(() => <StandalonePageSurface />);
expect(screen.getByTestId('agents-machines-table')).toHaveAttribute('data-resource-count', '1');
expect(screen.queryByText('No standalone machines')).not.toBeInTheDocument();
expect(screen.queryByText('No machines')).not.toBeInTheDocument();
});
});
@@ -40,7 +40,7 @@ describe('resource link routing contract', () => {
expect(buildProxmoxPath('')).toBe('/proxmox');
});
it('builds canonical Standalone, container runtime, Kubernetes, TrueNAS, and vSphere tab paths', () => {
it('builds canonical Machines, container runtime, Kubernetes, TrueNAS, and vSphere tab paths', () => {
expect(STANDALONE_PATH).toBe('/standalone');
expect(STANDALONE_DEFAULT_TAB).toBe('machines');
expect(buildStandalonePath()).toBe('/standalone/machines');