mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Flag an outdated agent once on the Infrastructure page (#2531)
Each outdated Pulse Agent on Settings › Infrastructure was flagged twice: an amber **Agent update** badge, plus a second-line **Version behind** note. The band also counted every outdated agent as needing attention. On a mock estate it read "11 systems needs attention", and all 11 were version lag. - **Version lag is maintenance.** Version behind and Update available are now maintenance signals. The badge, which opens Agent Doctor, carries them, so they never become the row's problem line and don't count toward needs attention. The Manage drawer still lists every fleet signal. - **Real update problems still show.** A failed update and auto-update switched off remain row problems, since each needs a different action. - **Problems can't hide behind the cap.** A row's problem is now picked from all its visible signals before the three-highlight cap, and problems rank ahead of maintenance. A second attached agent's failed update can no longer fall behind another agent's version lag. - **The band** says how many agent updates are available, in a neutral tone. "Systems need" and "hosts have" now agree in number. - **Column widths.** The Actions column is wide only when a row shows an install button beside Manage, and Health gets 27% of the table. The Health cell now keeps the status, the badge and the last-seen time on one line. On main, the last-seen time was cut to "18s a…". Verification: - At 1280, 1440 and 1920px the table has 16 rows and no clipped cells. Main has 27 rows (including 11 problem lines), with 11 and 6 clipped cells at 1280 and 1440px. - The phone card layout shows no Version behind lines. - Re-measured after rebasing onto the shared Table change in #2524. The receipt is included.
This commit is contained in:
@@ -5127,6 +5127,20 @@ API and Pulse Agent`) from the shared ledger contract. The compact landing may
|
||||
render its API / Agent / API + Agent badge beside the system name, with the full
|
||||
phrase available through accessible metadata and the detail flow, instead of
|
||||
spending a separate table column on collection method.
|
||||
An agent that is behind the Pulse Agent target or has an update waiting is
|
||||
maintenance, not a problem: the row's `Agent update` badge says so once and
|
||||
opens Agent Doctor, so `primaryRowProblem` skips signals marked `maintenance`
|
||||
(Version behind, Update available) and the row adds no `Version behind` line
|
||||
beneath itself. Those rows therefore do not count toward the band's `needs
|
||||
attention` total, which counts them separately and neutrally as agent updates
|
||||
available. A failed update and auto-update switched off remain row problems,
|
||||
because each asks for a different action, and the Manage drawer still lists
|
||||
every fleet signal. A row's problem is picked from all of its visible signals
|
||||
before the three-highlight cap, and problems rank ahead of maintenance in that
|
||||
list, so a second attached agent's failed update cannot fall behind another
|
||||
agent's version lag. The Actions column is only as wide as an install button
|
||||
beside Manage needs when a row in view shows one, so Health keeps its state,
|
||||
the update badge and the last-seen time on one line.
|
||||
Source badge class selection may use semantic gray treatment for API-only rows
|
||||
and typed non-gray tones for agent, probe, or combined sources, but the source
|
||||
identity remains the API/Agent/Probe label and subtitle from the shared ledger
|
||||
|
||||
@@ -7343,6 +7343,11 @@ agent-applied setting, `fleet.configDrift` must remain `not-applicable` and
|
||||
diagnostics, but must not count that passive state as source setup attention.
|
||||
Only a real managed desired config fingerprint can create pending rollout
|
||||
attention when an applied fingerprint is absent or mismatched.
|
||||
Likewise `fleet.versionDrift: behind` and `fleet.updateStatus:
|
||||
update-available` are maintenance, not source setup attention:
|
||||
`useConnectionsLedger.ts` keeps them out of a row's problem and the band's
|
||||
needs-attention count, which the row's agent-update count and badge carry
|
||||
instead, while `updateStatus: failed` and `disabled` remain row problems.
|
||||
The `fleet.commandPolicy` object is the canonical desired/applied convergence
|
||||
contract for remote command enablement. It must carry the desired server
|
||||
policy, the applied agent-reported truth when available, the effective
|
||||
|
||||
@@ -2633,7 +2633,9 @@ Agent`), with the plain-language source phrase available through accessible
|
||||
always-on version column for Pulse Agent; exact version text belongs in the
|
||||
edit/detail surfaces, while the landing table only surfaces a compact
|
||||
warning badge when an attached or standalone agent actually has an update
|
||||
available. That same table boundary must reuse the `System` cell for compact
|
||||
available. That badge is the row's only version flag: the row adds no
|
||||
`Version behind` problem line beneath it, and the source band counts those
|
||||
rows as agent updates available rather than as needing attention. That same table boundary must reuse the `System` cell for compact
|
||||
standalone-agent identity such as `Unraid 7.1.0`; raw reported addresses
|
||||
belong in the governed Manage detail or an explicitly expanded
|
||||
cluster-member row, not an always-on diagnostics column.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "2218ab6c18cecde801a1a4e920071089032d46a8",
|
||||
"verified_at": "2026-10-05T16:35:39Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx",
|
||||
"frontend-modern/src/components/Settings/connectionsTableModel.ts",
|
||||
"frontend-modern/src/components/Settings/useConnectionsLedger.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Settings/InfrastructureSourceManager.tsx": "0e631a2eaf7ba72e2a70e99ceca0639c82372b8700b831f48d5b93f9dce35ab1",
|
||||
"frontend-modern/src/components/Settings/connectionsTableModel.ts": "1ee02ad9c25795e44f766d7d4dff615cd2323e3691c30e844a3d5dfa4973c64d",
|
||||
"frontend-modern/src/components/Settings/useConnectionsLedger.ts": "4761b1a14a6af04225af07036ecf800334a12ab3b1aa418e400ddf4e2bb841ec"
|
||||
},
|
||||
"routes": [
|
||||
"/settings/infrastructure"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 390,
|
||||
"height": 1600
|
||||
},
|
||||
{
|
||||
"width": 1100,
|
||||
"height": 1000
|
||||
},
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 1000
|
||||
},
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 1100
|
||||
},
|
||||
{
|
||||
"width": 1920,
|
||||
"height": 1000
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Mock estate with 13 connected systems, 11 of them Pulse Agent hosts behind the agent target with an update available",
|
||||
"Each outdated agent row shows Active, one Agent update badge (opens Agent Doctor) and its last-seen time on one line; main added a second italic Version behind line under every one of the 11 rows",
|
||||
"The band reads 13 systems connected \u00b7 All active \u00b7 11 agent updates available in a neutral tone and Open Agent Doctor is a secondary button; main read 11 systems needs attention in amber with a primary button although every one was only version lag",
|
||||
"Health column at 1280 / 1440 / 1920: 228 / 267 / 390px with no clipped cell (main 176 / 216 / 332px with 11 and 6 clipped cells, the last-seen time cut to 18s a...); no cell wraps",
|
||||
"Phone card layout at 390: neutral band, no Version behind line on any card, 11 Agent update badges; below 1100 the page uses the card layout as before",
|
||||
"Re-measured after rebasing onto 2218ab6c18 (shared Table row-click change from #2524): same widths, no clipped cells, phone layout unchanged"
|
||||
],
|
||||
"interactions": [
|
||||
"Measured column widths and clipped cells on main (reverse-applied patch) and on the change at 1100, 1280, 1440 and 1920",
|
||||
"Counted Version behind lines and Agent update badges in the live phone layout"
|
||||
]
|
||||
}
|
||||
@@ -502,6 +502,15 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
|
||||
!props.readOnly && Boolean(props.onOpenConnection) && (row.canEdit || row.isAgent);
|
||||
|
||||
const actionColumnVisible = () => !props.readOnly;
|
||||
// Actions holds Manage (about 96px with padding) and, on a row missing agent
|
||||
// coverage, an install button beside it. Reserving the wide column only when
|
||||
// a row shows that button leaves Health room for its state, the Agent update
|
||||
// badge and the last-seen time on one line.
|
||||
const actionColumnShowsInstall = createMemo(
|
||||
() =>
|
||||
Boolean(props.onAddSourceStep || props.onAddSource) &&
|
||||
infrastructureRows().some((row) => rowNeedsAgentCoverage(row)),
|
||||
);
|
||||
const lastDiscoveryResultText = createMemo(() =>
|
||||
formatRelativeTimestamp(props.discoveryScanStatus().lastResultAt),
|
||||
);
|
||||
@@ -543,6 +552,11 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
|
||||
(row) => Boolean(row.problem) || row.members.some((member) => member.problem),
|
||||
).length,
|
||||
);
|
||||
// Outdated agents are maintenance rather than attention (see
|
||||
// FleetGovernanceSignal.maintenance); the band still counts them once.
|
||||
const agentUpdateTotal = createMemo(() =>
|
||||
infrastructureRows().reduce((total, row) => total + row.agentUpdateCount, 0),
|
||||
);
|
||||
const discoveryMonitorTitle = createMemo(() => {
|
||||
const status = props.discoveryScanStatus();
|
||||
if (status.scanning) return 'Scanning';
|
||||
@@ -815,7 +829,16 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
|
||||
·
|
||||
</span>
|
||||
<span class="font-medium text-amber-700 dark:text-amber-300">
|
||||
{formatCount(fleetAttentionSystemCount(), 'system')} needs attention
|
||||
{formatCount(fleetAttentionSystemCount(), 'system')}{' '}
|
||||
{fleetAttentionSystemCount() === 1 ? 'needs' : 'need'} attention
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={agentUpdateTotal() > 0}>
|
||||
<span aria-hidden="true" class="text-muted">
|
||||
·
|
||||
</span>
|
||||
<span class="text-muted" data-infrastructure-agent-updates>
|
||||
{formatCount(agentUpdateTotal(), 'agent update')} available
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={uncoveredAgentTargetCount() > 0}>
|
||||
@@ -823,7 +846,8 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
|
||||
·
|
||||
</span>
|
||||
<span class="font-medium text-amber-700 dark:text-amber-300">
|
||||
{formatCount(uncoveredAgentTargetCount(), 'host')} has limited coverage
|
||||
{formatCount(uncoveredAgentTargetCount(), 'host')}{' '}
|
||||
{uncoveredAgentTargetCount() === 1 ? 'has' : 'have'} limited coverage
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -897,17 +921,19 @@ export const InfrastructureSourceManager: Component<InfrastructureSourceManagerP
|
||||
<Table class="w-full min-w-[820px] table-fixed text-sm">
|
||||
<TableHeader class="">
|
||||
<TableRow>
|
||||
<TableHead class="w-[30%] py-1.5 pl-3 pr-3 text-left text-[11px] font-medium text-muted whitespace-nowrap">
|
||||
<TableHead class="w-[29%] py-1.5 pl-3 pr-3 text-left text-[11px] font-medium text-muted whitespace-nowrap">
|
||||
System
|
||||
</TableHead>
|
||||
<TableHead class="w-[32%] px-3 py-1.5 text-left text-[11px] font-medium text-muted whitespace-nowrap">
|
||||
<TableHead class="w-[29%] px-3 py-1.5 text-left text-[11px] font-medium text-muted whitespace-nowrap">
|
||||
Coverage
|
||||
</TableHead>
|
||||
<TableHead class="w-[23%] px-3 py-1.5 text-left text-[11px] font-medium text-muted whitespace-nowrap">
|
||||
<TableHead class="w-[27%] px-3 py-1.5 text-left text-[11px] font-medium text-muted whitespace-nowrap">
|
||||
Health
|
||||
</TableHead>
|
||||
<Show when={actionColumnVisible()}>
|
||||
<TableHead class="w-48 px-3 py-1.5 text-right text-[11px] font-medium text-muted whitespace-nowrap">
|
||||
<TableHead
|
||||
class={`${actionColumnShowsInstall() ? 'w-48' : 'w-28'} px-3 py-1.5 text-right text-[11px] font-medium text-muted whitespace-nowrap`}
|
||||
>
|
||||
Actions
|
||||
</TableHead>
|
||||
</Show>
|
||||
|
||||
+46
@@ -6,7 +6,9 @@ import {
|
||||
agentConnectionIDsForInfrastructureRow,
|
||||
} from '../InfrastructureSourceManager';
|
||||
import {
|
||||
fleetGovernanceSignalsForConnection,
|
||||
primaryRowProblem,
|
||||
visibleFleetGovernanceSignals,
|
||||
type FleetGovernanceSignal,
|
||||
type InfrastructureSystemMemberRow,
|
||||
type InfrastructureSystemRow,
|
||||
@@ -398,6 +400,50 @@ describe('InfrastructureSourceManager setup summary', () => {
|
||||
expect(onOpenAgentDoctor).toHaveBeenCalledWith(['agent:behind-host']);
|
||||
});
|
||||
|
||||
it('flags an outdated agent once, as an update rather than as attention', () => {
|
||||
const outdated = connectionFixture({
|
||||
id: 'agent:outdated-host',
|
||||
name: 'outdated-host',
|
||||
agentUpdateAvailable: true,
|
||||
fleet: {
|
||||
enrollmentState: 'enrolled',
|
||||
livenessState: 'active',
|
||||
versionDrift: 'behind',
|
||||
adapterHealth: 'healthy',
|
||||
configRollout: 'reported',
|
||||
credentialStatus: 'verified',
|
||||
updateStatus: 'update-available',
|
||||
remoteControl: 'disabled',
|
||||
},
|
||||
});
|
||||
const fleetSignals = fleetGovernanceSignalsForConnection(outdated);
|
||||
const outdatedRow = row({
|
||||
connection: outdated,
|
||||
agentUpdateCount: 1,
|
||||
fleetSignals,
|
||||
fleetHighlights: visibleFleetGovernanceSignals(fleetSignals),
|
||||
problem: primaryRowProblem(visibleFleetGovernanceSignals(fleetSignals, Infinity)),
|
||||
});
|
||||
|
||||
render(() => (
|
||||
<InfrastructureSourceManager
|
||||
rows={() => [outdatedRow]}
|
||||
discoveredNodes={() => []}
|
||||
discoveryEnabled={false}
|
||||
discoveryScanStatus={() => ({ scanning: false })}
|
||||
readOnly={false}
|
||||
onOpenAgentDoctor={vi.fn()}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Open Agent Doctor for outdated-host' }),
|
||||
).toHaveTextContent('Agent update');
|
||||
expect(screen.queryByText('Version behind')).toBeNull();
|
||||
expect(screen.queryByText(/needs? attention/i)).toBeNull();
|
||||
expect(screen.getByText('1 agent update available')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('still counts actionable member posture when the cluster parent is healthy', () => {
|
||||
render(() => (
|
||||
<InfrastructureSourceManager
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Connection, ConnectionAgentIdentity } from '@/api/connections';
|
||||
import {
|
||||
connectionAgentIdentitySummary,
|
||||
fleetGovernanceSignalsForConnection,
|
||||
primaryRowProblem,
|
||||
visibleFleetGovernanceSignals,
|
||||
type FleetGovernanceSignal,
|
||||
} from '../connectionsTableModel';
|
||||
@@ -296,6 +297,70 @@ describe('visibleFleetGovernanceSignals', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('primaryRowProblem', () => {
|
||||
const outdatedFleet = (updateStatus: 'update-available' | 'failed' | 'disabled') =>
|
||||
connectionFixture({
|
||||
agentUpdate:
|
||||
updateStatus === 'failed'
|
||||
? { state: 'error', autoUpdate: true, lastError: 'signature verification failed' }
|
||||
: undefined,
|
||||
fleet: {
|
||||
enrollmentState: 'enrolled',
|
||||
livenessState: 'active',
|
||||
versionDrift: 'behind',
|
||||
adapterHealth: 'healthy',
|
||||
configRollout: 'reported',
|
||||
credentialStatus: 'verified',
|
||||
updateStatus,
|
||||
remoteControl: 'disabled',
|
||||
},
|
||||
});
|
||||
|
||||
it('leaves an outdated agent to its Agent update badge instead of naming a problem', () => {
|
||||
const highlights = visibleFleetGovernanceSignals(
|
||||
fleetGovernanceSignalsForConnection(outdatedFleet('update-available')),
|
||||
);
|
||||
// The Manage drawer keeps the structured signals.
|
||||
expect(highlights.map((signal) => signal.label)).toEqual([
|
||||
'Version behind',
|
||||
'Update available',
|
||||
]);
|
||||
expect(primaryRowProblem(highlights)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("finds a failed update on a second attached agent behind another agent's version lag", () => {
|
||||
const signals = [
|
||||
...fleetGovernanceSignalsForConnection(outdatedFleet('update-available')),
|
||||
...fleetGovernanceSignalsForConnection({
|
||||
...outdatedFleet('failed'),
|
||||
id: 'agent:host-2',
|
||||
name: 'host-2',
|
||||
}),
|
||||
];
|
||||
expect(primaryRowProblem(visibleFleetGovernanceSignals(signals, Infinity))).toMatchObject({
|
||||
label: 'Agent update failed',
|
||||
tone: 'critical',
|
||||
});
|
||||
// The capped highlight list leads with the problem, not the maintenance.
|
||||
expect(visibleFleetGovernanceSignals(signals)[0]?.label).toBe('Agent update failed');
|
||||
});
|
||||
|
||||
it('still names a failed update or disabled auto-update, which need a different action', () => {
|
||||
expect(
|
||||
primaryRowProblem(
|
||||
visibleFleetGovernanceSignals(fleetGovernanceSignalsForConnection(outdatedFleet('failed'))),
|
||||
),
|
||||
).toMatchObject({ label: 'Agent update failed', tone: 'critical' });
|
||||
expect(
|
||||
primaryRowProblem(
|
||||
visibleFleetGovernanceSignals(
|
||||
fleetGovernanceSignalsForConnection(outdatedFleet('disabled')),
|
||||
),
|
||||
),
|
||||
).toMatchObject({ label: 'Auto-update off', tone: 'warning' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('fleetGovernanceSignalsForConnection connection-type gating', () => {
|
||||
it('omits agent-fleet governance signals for pull-based API sources', () => {
|
||||
const signals = fleetGovernanceSignalsForConnection(
|
||||
|
||||
@@ -1665,8 +1665,12 @@ describe('settings architecture guardrails', () => {
|
||||
expect(infrastructureSourceManagerSource).toContain('Connection posture');
|
||||
expect(infrastructureSourceManagerSource).toContain("'system')} connected");
|
||||
expect(infrastructureSourceManagerSource).toContain('All active');
|
||||
expect(infrastructureSourceManagerSource).toContain('needs attention');
|
||||
expect(infrastructureSourceManagerSource).toContain('has limited coverage');
|
||||
expect(infrastructureSourceManagerSource).toContain(
|
||||
"{fleetAttentionSystemCount() === 1 ? 'needs' : 'need'} attention",
|
||||
);
|
||||
expect(infrastructureSourceManagerSource).toContain(
|
||||
"{uncoveredAgentTargetCount() === 1 ? 'has' : 'have'} limited coverage",
|
||||
);
|
||||
expect(infrastructureSourceManagerSource).toContain('setupConfidenceAction');
|
||||
expect(infrastructureSourceManagerSource).not.toContain('Infrastructure coverage');
|
||||
expect(infrastructureSourceManagerSource).not.toContain('Fleet governance');
|
||||
|
||||
@@ -552,6 +552,8 @@ describe('useConnectionsLedger', () => {
|
||||
'Rollout pending',
|
||||
'Version behind',
|
||||
]);
|
||||
// Version lag rides on the Agent update badge; the problem line names the drift.
|
||||
expect(byID.get('agent:drifted')?.problem?.label).toBe('Config drift');
|
||||
// Pull-based API sources (PVE/PBS/etc.) have no Pulse Agent, so agent-fleet
|
||||
// governance (rollout/config/version/command-policy) must not surface on
|
||||
// them. Only source-agnostic posture like credential health applies; an
|
||||
|
||||
@@ -256,6 +256,11 @@ export interface FleetGovernanceSignal {
|
||||
label: string;
|
||||
detail: string;
|
||||
tone: FleetGovernanceSignalTone;
|
||||
// An agent that is behind or has an update waiting is maintenance, not a
|
||||
// problem: the row's Agent update badge already says so and opens Agent
|
||||
// Doctor, so the row does not repeat it as its problem line or count it as
|
||||
// needing attention. The Manage drawer still lists the signal.
|
||||
maintenance?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_FLEET_GOVERNANCE: ConnectionFleetGovernance = {
|
||||
@@ -462,6 +467,7 @@ const versionSignal = (state: ConnectionFleetVersionDrift): FleetGovernanceSigna
|
||||
label: 'Version behind',
|
||||
detail: 'This agent is behind the current Pulse Agent target.',
|
||||
tone: 'warning',
|
||||
maintenance: true,
|
||||
};
|
||||
case 'current':
|
||||
return {
|
||||
@@ -686,6 +692,7 @@ const updateSignal = (
|
||||
label: 'Update available',
|
||||
detail: 'A newer Pulse Agent binary is available for this system.',
|
||||
tone: 'warning',
|
||||
maintenance: true,
|
||||
};
|
||||
case 'checking':
|
||||
return {
|
||||
@@ -906,8 +913,12 @@ export const fleetGovernanceSignalsForConnection = (
|
||||
return signals;
|
||||
};
|
||||
|
||||
// The row shows at most three highlights. A row's problem is picked from the
|
||||
// whole visible list (limit Infinity) so a critical signal from a second
|
||||
// attached agent cannot fall behind the cap.
|
||||
export const visibleFleetGovernanceSignals = (
|
||||
signals: readonly FleetGovernanceSignal[],
|
||||
limit = 3,
|
||||
): FleetGovernanceSignal[] => {
|
||||
const hasPassiveAgentConfigConfirmation = signals.some(isPassiveAgentConfigConfirmationSignal);
|
||||
const visibleSignals = signals.filter((signal) => {
|
||||
@@ -929,11 +940,14 @@ export const visibleFleetGovernanceSignals = (
|
||||
if (signal.key === 'command-policy' && signal.tone === 'info') return false;
|
||||
return true;
|
||||
});
|
||||
// Problems lead; maintenance (an agent behind or with an update waiting)
|
||||
// follows them, since the Agent update badge already carries it.
|
||||
const attention = visibleSignals.filter(
|
||||
(signal) => signal.tone === 'critical' || signal.tone === 'warning',
|
||||
(signal) => !signal.maintenance && (signal.tone === 'critical' || signal.tone === 'warning'),
|
||||
);
|
||||
const control = visibleSignals.filter((signal) => signal.tone === 'info');
|
||||
return [...attention, ...control].slice(0, 3);
|
||||
const maintenance = visibleSignals.filter((signal) => signal.maintenance);
|
||||
const control = visibleSignals.filter((signal) => !signal.maintenance && signal.tone === 'info');
|
||||
return [...attention, ...maintenance, ...control].slice(0, limit);
|
||||
};
|
||||
|
||||
const isPassiveAgentConfigConfirmationSignal = (signal: FleetGovernanceSignal): boolean => {
|
||||
@@ -1027,9 +1041,11 @@ export interface InfrastructureSystemRow {
|
||||
// Pick the single most important problem to surface in the row's status
|
||||
// column. Critical beats warning; otherwise first wins — the upstream
|
||||
// builder has already prioritised highlights (attention before info).
|
||||
// Maintenance signals never become the problem (see FleetGovernanceSignal).
|
||||
export const primaryRowProblem = (
|
||||
signals: readonly FleetGovernanceSignal[],
|
||||
allSignals: readonly FleetGovernanceSignal[],
|
||||
): InfrastructureRowProblem | undefined => {
|
||||
const signals = allSignals.filter((signal) => !signal.maintenance);
|
||||
const critical = signals.find((signal) => signal.tone === 'critical');
|
||||
if (critical) {
|
||||
return { label: critical.label, detail: critical.detail, tone: 'critical' };
|
||||
|
||||
@@ -307,7 +307,7 @@ const buildMemberRow = (
|
||||
// still rolls up from the prioritised highlight list.
|
||||
const problem =
|
||||
(agentConnection && agentAttachmentProblem(agentConnection)) ??
|
||||
primaryRowProblem(fleetHighlights);
|
||||
primaryRowProblem(visibleFleetGovernanceSignals(fleetSignals, Infinity));
|
||||
return {
|
||||
id: member.id,
|
||||
nodeIdentity: member.nodeIdentity?.trim() || undefined,
|
||||
@@ -423,7 +423,8 @@ const buildRow = (
|
||||
lastErrorMessage,
|
||||
fleetSignals,
|
||||
fleetHighlights,
|
||||
problem: attachmentProblem ?? primaryRowProblem(fleetHighlights),
|
||||
problem:
|
||||
attachmentProblem ?? primaryRowProblem(visibleFleetGovernanceSignals(fleetSignals, Infinity)),
|
||||
enabled: primaryConnection.enabled,
|
||||
canEdit: EDITABLE_CONNECTION_TYPES.includes(primaryConnection.type),
|
||||
canPause: primaryConnection.capabilities.supportsPause,
|
||||
|
||||
Reference in New Issue
Block a user