mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
Keep Machines limited to Pulse Agent resources
This commit is contained in:
parent
3561803d76
commit
1e16cf34f5
10 changed files with 73 additions and 83 deletions
|
|
@ -240,11 +240,11 @@ pause, test, edit, and remove.
|
|||
An availability target classified as `machine` is still agentless unless a
|
||||
Pulse Agent actually registers for that host. That classification exists so
|
||||
servers, desktops, laptops, and comparable computers such as a Mac mini can
|
||||
appear in Standalone Machines when monitored by reachability probes; it must
|
||||
not create lifecycle authority, install state, agent profile membership, or
|
||||
host-command capability. A `targetKind=machine` add-dialog handoff is only a
|
||||
preselected availability-resource creation path, not an agent install or
|
||||
lifecycle onboarding state.
|
||||
use machine-specific reachability copy in Availability checks; it must not
|
||||
create Standalone Machines membership, lifecycle authority, install state,
|
||||
agent profile membership, or host-command capability. A `targetKind=machine`
|
||||
add-dialog handoff is only a preselected availability-resource creation path,
|
||||
not an agent install or lifecycle onboarding state.
|
||||
The lifecycle-owned onboarding presentation helper must consume the governed
|
||||
platform support manifest for readiness stage, primary mode, canonical
|
||||
projections, and support-floor posture.
|
||||
|
|
|
|||
|
|
@ -436,12 +436,13 @@ the canonical monitored-system blocked payload.
|
|||
`frontend-modern/src/api/availabilityTargets.ts`, and the unified-resource
|
||||
availability payload together. The bounded `targetKind` vocabulary is
|
||||
`machine`, `service`, and `device`; missing legacy values default to
|
||||
`service`, and browser consumers may place only explicitly classified
|
||||
`machine` targets into Standalone Machines. Browser add-dialog routes may
|
||||
carry a `targetKind` query value to preselect that bounded kind, but the
|
||||
persisted contract remains the `AvailabilityTarget.targetKind` payload.
|
||||
API clients must not infer that classification from protocol, hostname,
|
||||
port, or row label.
|
||||
`service`. Browser add-dialog routes may carry a `targetKind` query value
|
||||
to preselect that bounded kind, but the persisted contract remains the
|
||||
`AvailabilityTarget.targetKind` payload. Browser consumers must not place
|
||||
availability targets into Standalone Machines from that classification;
|
||||
Machines membership requires Pulse Agent resource evidence. API clients
|
||||
must not infer that classification from protocol, hostname, port, or row
|
||||
label.
|
||||
3. Add dedicated contract tests for new stable payloads
|
||||
Unified resource type-filter and organization-share resource type additions
|
||||
must route through `internal/api/resources.go`, `internal/api/org_handlers.go`,
|
||||
|
|
|
|||
|
|
@ -2836,7 +2836,9 @@ availability settings at `/settings/monitoring/availability`, with
|
|||
Machine entry points must add `targetKind=machine`, while the focused
|
||||
availability checks entry points for services and devices must add
|
||||
`targetKind=service`, so deep links open the same owned dialog with the correct
|
||||
bounded target kind already selected.
|
||||
bounded target kind already selected. That target kind only scopes the
|
||||
availability form copy and payload; it must not make agentless reachability
|
||||
targets eligible for the Machines table.
|
||||
Former nested aliases such as `/settings/infrastructure/install`,
|
||||
`/settings/infrastructure/platforms/proxmox/pbs`,
|
||||
`/settings/infrastructure/api/pve`, and `/settings/workloads/docker` must fail
|
||||
|
|
@ -3590,16 +3592,16 @@ 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 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
|
||||
frontend-primitives-owned Machines surface as a focused Availability checks tab
|
||||
rather than a new primary nav item. The page may show availability checks beside
|
||||
standalone Pulse Agent machines, but Settings remains the add/edit owner and
|
||||
the app shell must not add a separate top-level Availability destination.
|
||||
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
|
||||
the default tab is `Machines`, the Machines table is only for Pulse Agent-backed
|
||||
resources with host telemetry, 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
|
||||
machines when the availability target is classified as `machine`; service and
|
||||
device checks stay in the focused availability tab and may only trigger a
|
||||
compact handoff from Machines. The Machines empty and handoff actions must use
|
||||
the machine-targeted availability add route, while Availability checks actions
|
||||
must use the service/device-targeted route.
|
||||
computers monitored only by agentless reachability checks may use
|
||||
`targetKind=machine` in the availability form, but they stay in Availability
|
||||
checks until a Pulse Agent registers and supplies CPU, memory, disk, and network
|
||||
telemetry. Machines empty and handoff actions must lead to Pulse Agent install
|
||||
or the Availability checks tab, not to an agentless machine row in Machines.
|
||||
|
|
|
|||
|
|
@ -801,9 +801,10 @@ recovery scope, or a storage/recovery-owned secret source.
|
|||
32. Keep agentless availability endpoints neutral on the shared unified-resource and API contracts. When `internal/api/availability_handlers.go`, `internal/api/connections_handlers.go`, `internal/api/platform_mock_connections.go`, or `frontend-modern/src/hooks/useUnifiedResources.ts` surface `network-endpoint` availability resources, storage and recovery may consume their liveness as infrastructure context only; they must not reinterpret ping/TCP/HTTP endpoints as storage providers, backup targets, recovery repositories, or protected-workload evidence.
|
||||
That neutrality includes availability targets whose `targetKind` is
|
||||
`machine`. A Linux server, desktop, laptop, or Mac mini monitored by an
|
||||
agentless reachability check may appear in Standalone Machines, but storage
|
||||
and recovery must still treat the row as liveness context unless a separate
|
||||
storage/recovery-owned relationship ties it to backup or restore evidence.
|
||||
agentless reachability check still belongs to Availability checks rather
|
||||
than Standalone Machines; storage and recovery must treat the row as
|
||||
liveness context unless a separate storage/recovery-owned relationship ties
|
||||
it to backup or restore evidence.
|
||||
33. Keep infrastructure summary chart bucketing and short response caching presentation-only on the adjacent shared API boundary. When `internal/api/router.go` normalizes mixed-cadence infrastructure history into equal-time summary buckets or serves a cached summary payload for repeated operator-facing summary-card requests, storage and recovery may consume the resulting visual context only; they must not reinterpret those normalized chart samples, cached timestamps, or cache hits as recovery freshness windows, backup cadence, or restore evidence.
|
||||
34. Keep workload chart downsampling and short response caching presentation-only on that same adjacent shared API boundary. When `internal/api/router.go` caps mixed-cadence workload history into equal-time buckets or serves a cached workload-summary payload for repeated operator-facing workload-card requests, storage and recovery may consume the resulting visual context only; they must not reinterpret those shaped chart samples, cached timestamps, or cache hits as recovery freshness windows, backup cadence, or restore evidence.
|
||||
The same adjacent chart boundary now covers compact storage capacity
|
||||
|
|
@ -1363,10 +1364,10 @@ storage or recovery inventory. Storage/recovery consumers may receive
|
|||
they must not fold those endpoints into protected-item counts, storage health
|
||||
rollups, recovery evidence, or storage/recovery licensing or readiness
|
||||
messages unless a separately governed storage/recovery relationship is added.
|
||||
The agentless `machine` target kind does not change that boundary: it is a
|
||||
Standalone presentation classification for computer-shaped reachability targets,
|
||||
not storage ownership, repository membership, backup coverage, or restore
|
||||
authority.
|
||||
The agentless `machine` target kind does not change that boundary: it is
|
||||
availability presentation vocabulary for computer-shaped reachability targets,
|
||||
not Standalone Machines membership, storage ownership, repository membership,
|
||||
backup coverage, or restore authority.
|
||||
That same owned summary path now also runs through
|
||||
`useStorageSummaryCharts.ts`: the storage page owns one page-scoped summary
|
||||
range and one shared storage-summary history fetch, and both the sticky
|
||||
|
|
|
|||
|
|
@ -742,8 +742,9 @@ AI-only summary payloads, or page-local heuristics.
|
|||
add handoffs must include `targetKind=machine`; service and device check
|
||||
handoffs must include `targetKind=service`. Saved availability targets carry
|
||||
a `targetKind` so agentless servers, laptops, desktops, and comparable
|
||||
computers can participate in the Machines inventory without reclassifying
|
||||
service or device checks as machines.
|
||||
computers can use machine-specific reachability copy in Availability checks
|
||||
without reclassifying service or device checks as machines. Machines
|
||||
inventory membership still requires Pulse Agent resource evidence.
|
||||
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
|
||||
|
|
@ -2804,8 +2805,9 @@ 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 Machines
|
||||
consumer. Future infrastructure work must extend those owners rather than
|
||||
standalone agent machines for the Machines consumer and agentless endpoint
|
||||
checks for the Availability checks consumer. Future infrastructure work must
|
||||
extend those owners rather than
|
||||
recreating
|
||||
`frontend-modern/src/features/infrastructure/`, a `/infrastructure` route, or
|
||||
a separate top-level availability route.
|
||||
|
|
|
|||
|
|
@ -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 Pulse Agent or agentless machines."
|
||||
description="Adjust the search or status filter to see more Pulse Agent machines."
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { Show, createEffect, createMemo, createSignal } from 'solid-js';
|
||||
import { A, useLocation, useNavigate } from '@solidjs/router';
|
||||
import ActivityIcon from 'lucide-solid/icons/activity';
|
||||
import PlusIcon from 'lucide-solid/icons/plus';
|
||||
import ServerIcon from 'lucide-solid/icons/server';
|
||||
import SettingsIcon from 'lucide-solid/icons/settings';
|
||||
import { buildAvailabilityTargetAddPath } from '@/components/Settings/availabilitySettingsModel';
|
||||
import { buildInfrastructureOnboardingPath } from '@/components/Settings/infrastructureWorkspaceModel';
|
||||
import {
|
||||
PlatformErrorState,
|
||||
|
|
@ -89,7 +87,7 @@ export function StandalonePageSurface() {
|
|||
fallback={
|
||||
<PlatformTableLoadingState
|
||||
title="Loading machines"
|
||||
description="Pulse is loading Pulse Agent machines, agentless computers, and availability checks."
|
||||
description="Pulse is loading Pulse Agent machines and availability checks."
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
|
@ -118,8 +116,8 @@ export function StandalonePageSurface() {
|
|||
fallback={
|
||||
<PlatformTableEmptyState
|
||||
icon={machineIcon()}
|
||||
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."
|
||||
title="No Pulse Agent machines"
|
||||
description="Install Pulse Agent on servers, laptops, and desktops for CPU, memory, disk, and network telemetry. Agentless reachability checks live in Availability checks."
|
||||
actions={
|
||||
<div class="flex flex-wrap items-center justify-center gap-2">
|
||||
<button
|
||||
|
|
@ -130,12 +128,9 @@ export function StandalonePageSurface() {
|
|||
<SettingsIcon class="h-3.5 w-3.5" />
|
||||
Add agent
|
||||
</button>
|
||||
<A
|
||||
href={buildAvailabilityTargetAddPath('machine')}
|
||||
class="inline-flex items-center gap-2 rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content shadow-sm hover:bg-slate-50"
|
||||
>
|
||||
<PlusIcon class="h-3.5 w-3.5" />
|
||||
Add machine check
|
||||
<A href={buildStandalonePath('availability')} class={overviewActionClass}>
|
||||
<ActivityIcon class="h-3.5 w-3.5" />
|
||||
View checks
|
||||
</A>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -149,10 +144,18 @@ export function StandalonePageSurface() {
|
|||
<Show when={model().availabilityChecks.length > 0}>
|
||||
<PlatformTableEmptyState
|
||||
icon={machineIcon()}
|
||||
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."
|
||||
title="No Pulse Agent machines"
|
||||
description="Availability checks, including ICMP checks for computers, stay in the Availability checks tab. Install Pulse Agent when you need full machine telemetry."
|
||||
actions={
|
||||
<div class="flex flex-wrap items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(buildInfrastructureOnboardingPath('pick'))}
|
||||
class={overviewActionClass}
|
||||
>
|
||||
<SettingsIcon class="h-3.5 w-3.5" />
|
||||
Add agent
|
||||
</button>
|
||||
<A
|
||||
href={buildStandalonePath('availability')}
|
||||
class={overviewActionClass}
|
||||
|
|
@ -160,13 +163,6 @@ export function StandalonePageSurface() {
|
|||
<ActivityIcon class="h-3.5 w-3.5" />
|
||||
View checks
|
||||
</A>
|
||||
<A
|
||||
href={buildAvailabilityTargetAddPath('machine')}
|
||||
class={overviewActionClass}
|
||||
>
|
||||
<PlusIcon class="h-3.5 w-3.5" />
|
||||
Add machine check
|
||||
</A>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
@ -177,7 +173,7 @@ export function StandalonePageSurface() {
|
|||
resources={model().machines}
|
||||
emptyIcon={machineIcon()}
|
||||
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."
|
||||
emptyDescription="Install Pulse Agent on Linux, macOS, Windows, or Unraid systems for full CPU, memory, disk, and network telemetry."
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
describe('StandalonePageSurface', () => {
|
||||
it('keeps overview focused on standalone machines, including agentless machines only', () => {
|
||||
it('keeps overview focused on Pulse Agent machines only', () => {
|
||||
render(() => <StandalonePageSurface />);
|
||||
|
||||
expect(mocks.useUnifiedResources).toHaveBeenCalledWith(
|
||||
|
|
@ -124,7 +124,7 @@ describe('StandalonePageSurface', () => {
|
|||
'data-tabs',
|
||||
'machines,availability',
|
||||
);
|
||||
expect(screen.getByTestId('agents-machines-table')).toHaveAttribute('data-resource-count', '2');
|
||||
expect(screen.getByTestId('agents-machines-table')).toHaveAttribute('data-resource-count', '1');
|
||||
expect(screen.queryByTestId('availability-checks-table')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
@ -176,18 +176,16 @@ describe('StandalonePageSurface', () => {
|
|||
|
||||
expect(screen.queryByTestId('agents-machines-table')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('availability-checks-table')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('No machines')).toBeInTheDocument();
|
||||
expect(screen.getByText('No Pulse Agent machines')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Add agent' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'View checks' })).toHaveAttribute(
|
||||
'href',
|
||||
'/standalone/availability',
|
||||
);
|
||||
expect(screen.getByRole('link', { name: 'Add machine check' })).toHaveAttribute(
|
||||
'href',
|
||||
'/settings/monitoring/availability?add=target&targetKind=machine',
|
||||
);
|
||||
expect(screen.queryByRole('link', { name: 'Add machine check' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the machines table when only an agentless machine is present', () => {
|
||||
it('keeps agentless machine checks in the availability handoff', () => {
|
||||
mocks.useUnifiedResources.mockReturnValue({
|
||||
resources: () => [
|
||||
resource({
|
||||
|
|
@ -205,7 +203,11 @@ describe('StandalonePageSurface', () => {
|
|||
|
||||
render(() => <StandalonePageSurface />);
|
||||
|
||||
expect(screen.getByTestId('agents-machines-table')).toHaveAttribute('data-resource-count', '1');
|
||||
expect(screen.queryByText('No machines')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('agents-machines-table')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('No Pulse Agent machines')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'View checks' })).toHaveAttribute(
|
||||
'href',
|
||||
'/standalone/availability',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ describe('standalonePageModel', () => {
|
|||
expect(model.availabilityChecks).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps agentless availability checks beside standalone machines', () => {
|
||||
it('keeps agentless availability checks out of standalone machines', () => {
|
||||
const model = buildStandalonePageModel([
|
||||
resource({ id: 'mac-mini', platformType: 'agent', type: 'agent', sources: ['agent'] }),
|
||||
resource({
|
||||
|
|
@ -85,8 +85,8 @@ describe('standalonePageModel', () => {
|
|||
}),
|
||||
]);
|
||||
|
||||
expect(model.machines.map((item) => item.id)).toEqual(['mac-mini', 'router-ping']);
|
||||
expect(model.machines.map((item) => item.id)).toEqual(['mac-mini']);
|
||||
expect(model.availabilityChecks.map((item) => item.id)).toEqual(['router-ping', 'endpoint-1']);
|
||||
expect(model.resources.map((item) => item.id)).toEqual(['mac-mini', 'router-ping']);
|
||||
expect(model.resources.map((item) => item.id)).toEqual(['mac-mini']);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,22 +15,8 @@ export const isAgentlessAvailabilityResource = (resource: Resource): boolean =>
|
|||
resource.platformType === 'availability' ||
|
||||
resource.sources?.includes('availability') === true;
|
||||
|
||||
const availabilityTargetKindFor = (resource: Resource): string =>
|
||||
String(
|
||||
resource.availability?.targetKind ??
|
||||
(resource.platformData?.availability as { targetKind?: string } | undefined)?.targetKind ??
|
||||
'',
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
export const isAgentlessMachineResource = (resource: Resource): boolean =>
|
||||
isAgentlessAvailabilityResource(resource) && availabilityTargetKindFor(resource) === 'machine';
|
||||
|
||||
export function buildStandalonePageModel(resources: readonly Resource[]): StandalonePageModel {
|
||||
const machines = resources.filter(
|
||||
(resource) => isStandaloneMachineResource(resource) || isAgentlessMachineResource(resource),
|
||||
);
|
||||
const machines = resources.filter(isStandaloneMachineResource);
|
||||
const availabilityChecks = resources.filter(isAgentlessAvailabilityResource);
|
||||
return {
|
||||
machines,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue