Clarify cluster-member rows in the connected-systems table

Two adjacent UX wrinkles on Settings → Infrastructure:

1. The primary cluster member's subtitle read "API contact" while the
Source column rendered an "Agent" badge directly beside it. Two unrelated
axes (role within cluster vs how Pulse reads this row) shared visually
adjacent space and parsed as a contradiction. Rename the subtitle to
"Primary node" so it no longer collides with the Source badge.

2. A clustered PVE member without an attached agent fell back to
Source = 'unknown' (rendered as "— No source attached"). That row IS
read via the cluster API, so 'unknown' is incorrect. Default the
fallback to 'api'; agent-installed members keep 'agent'. Invisible in
the current single-cluster homelab setup but accurate for mixed-agent
clusters going forward.
This commit is contained in:
rcourtman
2026-05-10 23:37:18 +01:00
parent 0d386d32a2
commit 9063e7ec32
4 changed files with 7 additions and 7 deletions
@@ -562,7 +562,7 @@ describe('InfrastructureWorkspace', () => {
{
id: 'node-pi',
name: 'pi',
subtitle: 'API contact',
subtitle: 'Primary node',
source: 'agent',
host: 'https://pi:8006',
hostAliases: ['pi', '192.168.0.2'],
@@ -936,7 +936,7 @@ describe('InfrastructureWorkspace', () => {
{
id: 'node-delly',
name: 'delly',
subtitle: 'API contact',
subtitle: 'Primary node',
source: 'agent',
host: 'https://delly:8006',
coverageLabels: ['Host telemetry'],
@@ -970,7 +970,7 @@ describe('InfrastructureWorkspace', () => {
await waitFor(() => expect(screen.getByText('homelab')).toBeInTheDocument());
expect(screen.getByText('Cluster · 2 nodes')).toBeInTheDocument();
expect(screen.getByText('API contact')).toBeInTheDocument();
expect(screen.getByText('Primary node')).toBeInTheDocument();
expect(screen.getAllByText('Agent').length).toBeGreaterThan(0);
expect(screen.getByText('delly')).toBeInTheDocument();
expect(screen.getByText('minipc')).toBeInTheDocument();
@@ -42,7 +42,7 @@ describe('filterRepresentedDiscoveredServers', () => {
{
id: 'node-pi',
name: 'pi',
subtitle: 'API contact',
subtitle: 'Primary node',
source: 'agent',
host: 'https://pi:8006',
hostAliases: ['pi', '192.168.0.2'],
@@ -175,7 +175,7 @@ describe('useConnectionsLedger', () => {
{
id: 'node-delly',
name: 'delly',
subtitle: 'API contact',
subtitle: 'Primary node',
source: 'agent',
host: 'https://delly:8006',
hostAliases: ['delly', '192.168.0.10'],
@@ -176,7 +176,7 @@ interface ClusterRollup {
}
const memberSubtitleFor = (member: ConnectionSystemMember): string =>
member.primary ? 'API contact' : 'Cluster member';
member.primary ? 'Primary node' : 'Cluster member';
const buildMemberRow = (
member: ConnectionSystemMember,
@@ -188,7 +188,7 @@ const buildMemberRow = (
const agentConnection = member.agentConnectionId
? connectionsByID.get(member.agentConnectionId)
: undefined;
const source: InfrastructureSourceKind = agentConnection ? 'agent' : 'unknown';
const source: InfrastructureSourceKind = agentConnection ? 'agent' : 'api';
const state = moreSevereState(member.state, agentConnection?.state);
const presentation = STATE_PRESENTATION[state] ?? STATE_PRESENTATION.pending;
const lastSeen =