mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 18:44:53 +00:00
Compute attached-agent problem directly, drop synthesized signal
The agent-offline detail line was being produced by synthesizing a fake FleetGovernanceSignal with key 'agent-attachment' and laundering it through visibleFleetGovernanceSignals so the chip filter would pick it up. With the chip stack gone and the row exposing a single InfrastructureRowProblem, the synthesis is dead weight — it pretended to be server-emitted governance state when it was really derived from the agent's connection state. - Replace agentAttachmentSignal with agentAttachmentProblem, returning InfrastructureRowProblem directly. The row builder applies it ahead of the fleet-highlights problem, so an offline agent always wins over a downstream credential/config drift signal on the same row. - Remove 'agent-attachment' from FleetGovernanceSignalKey now that no signal carries it. - Simplify the setup-summary Live/Needs attention counters to read off the user-visible row state (statusLabel, problem) instead of re-deriving via signal predicates. The previous path could in principle disagree with what the row actually rendered. - Drop rowFleetSignals, rowHasFleetTone, rowHasVisibleFleetTone — all now unused.
This commit is contained in:
parent
41349be7d8
commit
a48a1acd8d
3 changed files with 36 additions and 67 deletions
|
|
@ -26,7 +26,6 @@ import {
|
|||
connectionAgentVersionPresentation,
|
||||
infrastructureSourcePresentation,
|
||||
surfaceLabel,
|
||||
type FleetGovernanceSignal,
|
||||
type InfrastructureSystemRow,
|
||||
} from './connectionsTableModel';
|
||||
import type { DiscoveredServer, DiscoveryScanStatus } from './infrastructureSettingsModel';
|
||||
|
|
@ -185,24 +184,6 @@ const rowHasAgentCoverage = (row: InfrastructureSystemRow): boolean =>
|
|||
(member) => member.source === 'agent' || member.source === 'both' || member.agentConnection,
|
||||
);
|
||||
|
||||
const rowFleetSignals = (row: InfrastructureSystemRow): FleetGovernanceSignal[] => [
|
||||
...row.fleetSignals,
|
||||
...row.members.flatMap((member) => member.fleetSignals),
|
||||
];
|
||||
|
||||
const rowHasFleetTone = (
|
||||
row: InfrastructureSystemRow,
|
||||
predicate: (signal: FleetGovernanceSignal) => boolean,
|
||||
): boolean => rowFleetSignals(row).some(predicate);
|
||||
|
||||
const rowHasVisibleFleetTone = (
|
||||
row: InfrastructureSystemRow,
|
||||
predicate: (signal: FleetGovernanceSignal) => boolean,
|
||||
): boolean =>
|
||||
[...row.fleetHighlights, ...row.members.flatMap((member) => member.fleetHighlights)].some(
|
||||
predicate,
|
||||
);
|
||||
|
||||
const agentHostProfileRouteStep = (
|
||||
row: InfrastructureSystemRow,
|
||||
): InfrastructureSourcePickerRouteStep | null => {
|
||||
|
|
@ -393,24 +374,19 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
|
|||
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
||||
return null;
|
||||
});
|
||||
// Counters read directly off the row state the user sees: a row is live
|
||||
// when its status is "Active" and needs attention when its derived
|
||||
// problem (or any member's) is non-empty. Going via signal predicates
|
||||
// duplicated the rollup logic and could disagree with what was rendered.
|
||||
const liveFleetSystemCount = createMemo(
|
||||
() =>
|
||||
props
|
||||
.rows()
|
||||
.filter((row) =>
|
||||
rowHasFleetTone(row, (signal) => signal.key === 'liveness' && signal.tone === 'ok'),
|
||||
).length,
|
||||
() => props.rows().filter((row) => row.statusLabel === 'Active').length,
|
||||
);
|
||||
const fleetAttentionSystemCount = createMemo(
|
||||
() =>
|
||||
props
|
||||
.rows()
|
||||
.filter((row) =>
|
||||
rowHasVisibleFleetTone(
|
||||
row,
|
||||
(signal) => signal.tone === 'warning' || signal.tone === 'critical',
|
||||
),
|
||||
).length,
|
||||
.filter((row) => Boolean(row.problem) || row.members.some((member) => member.problem))
|
||||
.length,
|
||||
);
|
||||
const discoveryReadinessLabel = createMemo(() => {
|
||||
if (props.discoveryScanStatus().scanning) return 'Scanning now';
|
||||
|
|
|
|||
|
|
@ -246,8 +246,7 @@ export type FleetGovernanceSignalKey =
|
|||
| 'credential-health'
|
||||
| 'updates'
|
||||
| 'remote-control'
|
||||
| 'command-policy'
|
||||
| 'agent-attachment';
|
||||
| 'command-policy';
|
||||
|
||||
export type FleetGovernanceSignalTone = 'ok' | 'info' | 'warning' | 'critical' | 'muted';
|
||||
|
||||
|
|
@ -824,19 +823,18 @@ const commandPolicySignal = (state: ConnectionFleetCommandPolicy): FleetGovernan
|
|||
}
|
||||
};
|
||||
|
||||
// Builds a plain-English chip when an attached Pulse Agent isn't reporting,
|
||||
// to be rendered alongside a primary connection that *is* healthy. Avoids
|
||||
// the prior pattern of stacking server-side terms ("Adapter degraded",
|
||||
// liveness "Stale") that didn't tell the user which collection path failed.
|
||||
export const agentAttachmentSignal = (
|
||||
// Builds the row-level problem line when an attached Pulse Agent isn't
|
||||
// reporting. Derived directly from connection state — not laundered
|
||||
// through the fleet-signal pipeline — so the row builder owns the user
|
||||
// story for "API works, agent doesn't" without a synthesized chip hack.
|
||||
export const agentAttachmentProblem = (
|
||||
agent: Connection,
|
||||
): FleetGovernanceSignal | null => {
|
||||
): InfrastructureRowProblem | undefined => {
|
||||
switch (agent.state) {
|
||||
case 'stale': {
|
||||
const ago = lastActivityTextFromLastSeen(agent.lastSeen);
|
||||
const suffix = ago && ago !== 'No activity yet' && ago !== 'Unknown' ? ` · ${ago}` : '';
|
||||
return {
|
||||
key: 'agent-attachment',
|
||||
label: `Agent offline${suffix}`,
|
||||
detail:
|
||||
agent.stateReason ||
|
||||
|
|
@ -846,27 +844,24 @@ export const agentAttachmentSignal = (
|
|||
}
|
||||
case 'unreachable':
|
||||
return {
|
||||
key: 'agent-attachment',
|
||||
label: 'Agent unreachable',
|
||||
detail: agent.stateReason || 'Pulse cannot currently reach the agent on this host.',
|
||||
tone: 'critical',
|
||||
};
|
||||
case 'unauthorized':
|
||||
return {
|
||||
key: 'agent-attachment',
|
||||
label: 'Agent unauthorized',
|
||||
detail: agent.stateReason || 'The Pulse Agent token is being rejected.',
|
||||
tone: 'critical',
|
||||
};
|
||||
case 'pending':
|
||||
return {
|
||||
key: 'agent-attachment',
|
||||
label: 'Agent pending first report',
|
||||
detail: agent.stateReason || 'The Pulse Agent has not reported yet.',
|
||||
tone: 'warning',
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
type ConnectionType,
|
||||
} from '@/api/connections';
|
||||
import {
|
||||
agentAttachmentSignal,
|
||||
agentAttachmentProblem,
|
||||
connectionAgentEndpointDisplay,
|
||||
connectionAgentIdentitySummary,
|
||||
connectionLastActivityText,
|
||||
|
|
@ -18,7 +18,7 @@ import {
|
|||
lastActivityTextFromLastSeen,
|
||||
primaryRowProblem,
|
||||
surfaceLabel,
|
||||
type FleetGovernanceSignal,
|
||||
type InfrastructureRowProblem,
|
||||
type InfrastructureSourceKind,
|
||||
type InfrastructureSystemMemberRow,
|
||||
type InfrastructureSystemRow,
|
||||
|
|
@ -291,13 +291,14 @@ const buildMemberRow = (
|
|||
const state = member.state;
|
||||
const presentation = STATE_PRESENTATION[state] ?? STATE_PRESENTATION.pending;
|
||||
const lastSeen = member.lastSeen ?? agentConnection?.lastSeen;
|
||||
const attachmentSignal = agentConnection ? agentAttachmentSignal(agentConnection) : null;
|
||||
const fleetSignals: FleetGovernanceSignal[] = [
|
||||
...(attachmentSignal ? [attachmentSignal] : []),
|
||||
...(agentConnection ? fleetGovernanceSignalsForConnection(agentConnection) : []),
|
||||
];
|
||||
|
||||
const fleetSignals = agentConnection ? fleetGovernanceSignalsForConnection(agentConnection) : [];
|
||||
const fleetHighlights = visibleFleetGovernanceSignals(fleetSignals);
|
||||
// Attachment problems (agent offline/unreachable/etc.) are derived from
|
||||
// connection state directly; everything else (credentials, config, rollout)
|
||||
// still rolls up from the prioritised highlight list.
|
||||
const problem =
|
||||
(agentConnection && agentAttachmentProblem(agentConnection)) ??
|
||||
primaryRowProblem(fleetHighlights);
|
||||
return {
|
||||
id: member.id,
|
||||
name,
|
||||
|
|
@ -311,7 +312,7 @@ const buildMemberRow = (
|
|||
lastActivityText: lastActivityTextFromLastSeen(lastSeen),
|
||||
fleetSignals,
|
||||
fleetHighlights,
|
||||
problem: primaryRowProblem(fleetHighlights),
|
||||
problem,
|
||||
primary: Boolean(member.primary),
|
||||
agentConnection,
|
||||
};
|
||||
|
|
@ -373,12 +374,15 @@ const buildRow = (
|
|||
connection.id === primaryConnection.id || !memberAgentConnectionIds.has(connection.id),
|
||||
)
|
||||
: componentConnections;
|
||||
// For non-agent primaries (PVE, PBS, etc.), surface a clearly-named chip
|
||||
// when an attached agent is not reporting. We avoid double-injecting the
|
||||
// chip for cluster member agents — those are tracked on the member rows.
|
||||
const attachmentSignals: FleetGovernanceSignal[] =
|
||||
const fleetSignals = rowFleetSignalConnections.flatMap((connection) =>
|
||||
fleetGovernanceSignalsForConnection(connection),
|
||||
);
|
||||
// For non-agent primaries (PVE, PBS, etc.), surface the first unhealthy
|
||||
// attached-agent connection as the row's problem line. Cluster member
|
||||
// agents are tracked on the member rows, not duplicated here.
|
||||
const attachmentProblem: InfrastructureRowProblem | undefined =
|
||||
primaryConnection.type === 'agent'
|
||||
? []
|
||||
? undefined
|
||||
: rowFleetSignalConnections
|
||||
.filter(
|
||||
(connection) =>
|
||||
|
|
@ -386,14 +390,8 @@ const buildRow = (
|
|||
connection.type === 'agent' &&
|
||||
!memberAgentConnectionIds.has(connection.id),
|
||||
)
|
||||
.map((connection) => agentAttachmentSignal(connection))
|
||||
.filter((signal): signal is FleetGovernanceSignal => Boolean(signal));
|
||||
const fleetSignals: FleetGovernanceSignal[] = [
|
||||
...attachmentSignals,
|
||||
...rowFleetSignalConnections.flatMap((connection) =>
|
||||
fleetGovernanceSignalsForConnection(connection),
|
||||
),
|
||||
];
|
||||
.map((connection) => agentAttachmentProblem(connection))
|
||||
.find((problem): problem is InfrastructureRowProblem => Boolean(problem));
|
||||
|
||||
const fleetHighlights = visibleFleetGovernanceSignals(fleetSignals);
|
||||
return {
|
||||
|
|
@ -414,7 +412,7 @@ const buildRow = (
|
|||
lastErrorMessage,
|
||||
fleetSignals,
|
||||
fleetHighlights,
|
||||
problem: primaryRowProblem(fleetHighlights),
|
||||
problem: attachmentProblem ?? primaryRowProblem(fleetHighlights),
|
||||
enabled: primaryConnection.enabled,
|
||||
canEdit: EDITABLE_CONNECTION_TYPES.includes(primaryConnection.type),
|
||||
canPause: primaryConnection.capabilities.supportsPause,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue