Improve connections table scalability headroom

This commit is contained in:
rcourtman
2026-05-13 21:02:58 +01:00
parent ceb9b87cfb
commit 0d727b3ff9
5 changed files with 442 additions and 14 deletions
@@ -1,4 +1,12 @@
import { Component, For, Show, type Accessor } from 'solid-js';
import {
Component,
createEffect,
createMemo,
createSignal,
For,
Show,
type Accessor,
} from 'solid-js';
import { Card } from '@/components/shared/Card';
import {
Table,
@@ -9,7 +17,13 @@ import {
TableRow,
} from '@/components/shared/Table';
import type { Connection } from '@/api/connections';
import { fleetSignalClassName, type InfrastructureSystemRow } from './connectionsTableModel';
import {
CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS,
connectionsTableVisibilityState,
fleetSignalClassName,
type InfrastructureSystemRow,
visibleConnectionsTableRows,
} from './connectionsTableModel';
import type { ConnectionRowActions } from './useConnectionRowActions';
import { getInfrastructureEmptyStateSummary } from '@/utils/infrastructureOnboardingPresentation';
@@ -46,9 +60,32 @@ const removeConfirmClass =
'inline-flex items-center rounded-md bg-rose-600 px-2.5 py-1 text-xs font-medium text-white transition-colors hover:bg-rose-700 disabled:cursor-not-allowed disabled:opacity-60';
export const ConnectionsTable: Component<ConnectionsTableProps> = (props) => {
const [visibleLimit, setVisibleLimit] = createSignal(CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS);
const hasActions = () => Boolean(props.actions) || Boolean(props.onEdit);
const colSpan = () => (hasActions() ? 6 : 5);
const rows = createMemo(() => props.rows());
const visibility = createMemo(() =>
connectionsTableVisibilityState(rows().length, visibleLimit()),
);
const visibleRows = createMemo(() =>
visibleConnectionsTableRows(rows(), visibility().visibleLimit),
);
const shouldShowVisibilityStatus = () =>
visibility().totalRows > CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS;
const showMoreRows = () => {
setVisibleLimit(visibility().nextVisibleLimit);
};
createEffect(() => {
const totalRows = rows().length;
setVisibleLimit((current) => {
if (totalRows <= CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS) {
return CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS;
}
return Math.min(Math.max(current, CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS), totalRows);
});
});
return (
<Card padding="none" tone="card" class="rounded-md">
@@ -79,7 +116,7 @@ export const ConnectionsTable: Component<ConnectionsTableProps> = (props) => {
</div>
<Show
when={props.rows().length > 0}
when={rows().length > 0}
fallback={
<div class="space-y-3 px-4 py-10 text-center">
<div class="text-base font-semibold text-base-content">
@@ -106,7 +143,21 @@ export const ConnectionsTable: Component<ConnectionsTableProps> = (props) => {
</div>
}
>
<Table class="w-full table-fixed divide-y divide-border text-sm !whitespace-normal">
<Show when={shouldShowVisibilityStatus()}>
<div
id="connections-table-visibility-status"
class="border-b border-border px-4 py-2 text-xs text-muted"
aria-live="polite"
>
{visibility().statusText}
</div>
</Show>
<Table
aria-describedby={
shouldShowVisibilityStatus() ? 'connections-table-visibility-status' : undefined
}
class="w-full table-fixed divide-y divide-border text-sm !whitespace-normal"
>
<TableHeader class="bg-surface-alt">
<TableRow>
<TableHead class="w-[22%] py-2 pl-4 pr-3 text-left text-xs font-semibold uppercase tracking-wide text-muted whitespace-nowrap 2xl:w-[18%]">
@@ -130,7 +181,7 @@ export const ConnectionsTable: Component<ConnectionsTableProps> = (props) => {
</TableRow>
</TableHeader>
<TableBody class="divide-y divide-border bg-surface">
<For each={props.rows()}>
<For each={visibleRows()}>
{(row) => {
const pauseLabel = () => (row.enabled ? 'Pause' : 'Resume');
const isPauseBusy = () => props.actions?.pendingAction(row.id) === 'pause';
@@ -331,6 +382,19 @@ export const ConnectionsTable: Component<ConnectionsTableProps> = (props) => {
</For>
</TableBody>
</Table>
<Show when={visibility().isBounded}>
<div class="flex flex-col gap-2 border-t border-border px-4 py-3 text-xs text-muted sm:flex-row sm:items-center sm:justify-between">
<span>{visibility().statusText}</span>
<button
type="button"
onClick={showMoreRows}
aria-label={visibility().showMoreAriaLabel}
class={inlineButtonClass}
>
{visibility().showMoreLabel}
</button>
</div>
</Show>
</Show>
</Card>
);
@@ -1,8 +1,11 @@
import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { ConnectionsTable } from '../ConnectionsTable';
import type { InfrastructureSystemRow } from '../connectionsTableModel';
import { connectionAgentIdentitySummary } from '../connectionsTableModel';
import {
CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS,
connectionAgentIdentitySummary,
type InfrastructureSystemRow,
} from '../connectionsTableModel';
import type { Connection, ConnectionAgentIdentity } from '@/api/connections';
import type { ConnectionRowActions } from '../useConnectionRowActions';
@@ -165,6 +168,18 @@ describe('ConnectionsTable', () => {
detail: 'Commands are disabled by policy.',
tone: 'info',
},
{
key: 'config-drift',
label: 'Config pending',
detail: 'Waiting for applied configuration confirmation.',
tone: 'warning',
},
{
key: 'config-drift',
label: 'Config unknown',
detail: 'No desired/applied config fingerprint has been reported yet.',
tone: 'warning',
},
],
}),
]}
@@ -180,6 +195,49 @@ describe('ConnectionsTable', () => {
'title',
'Commands are disabled by policy.',
);
expect(screen.getByText('Config pending')).toHaveAttribute(
'title',
'Waiting for applied configuration confirmation.',
);
expect(screen.getByText('Config unknown')).toHaveAttribute(
'title',
'No desired/applied config fingerprint has been reported yet.',
);
});
it('keeps large row sets bounded behind an explicit show-more path', () => {
const rows = Array.from({ length: CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS + 5 }, (_, index) =>
row({
id: `system-${index}`,
name: `system-${index}`,
host: undefined,
connection: connectionFixture({
id: `system-${index}`,
name: `system-${index}`,
}),
}),
);
render(() => <ConnectionsTable rows={() => rows} />);
expect(screen.getByText('system-0')).toBeInTheDocument();
expect(
screen.getByText(`system-${CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS - 1}`),
).toBeInTheDocument();
expect(screen.queryByText(`system-${CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS}`)).toBeNull();
expect(
screen.getAllByText(
`Showing ${CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS} of ${rows.length} monitored systems. 5 more systems available.`,
),
).toHaveLength(2);
fireEvent.click(screen.getByRole('button', { name: 'Show remaining 5 monitored systems.' }));
expect(
screen.getByText(`system-${CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS}`),
).toBeInTheDocument();
expect(screen.getByText(`Showing all ${rows.length} monitored systems.`)).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /Show remaining/i })).toBeNull();
});
it('renders Edit / Pause / Remove buttons when actions and onEdit are provided', () => {
@@ -71,6 +71,61 @@ describe('useConnectionsLedger', () => {
]);
});
it('reuses large stable row models across equivalent ledger refreshes', async () => {
const connections: Connection[] = Array.from({ length: 120 }, (_, index) => ({
id: `agent:stable-${index}`,
type: 'agent',
name: `stable-${index}`,
address: `stable-${index}`,
state: 'active',
stateReason: '',
enabled: true,
surfaces: ['host'],
scope: { host: true },
lastSeen: null,
lastError: null,
source: 'agent',
fleet: {
enrollmentState: 'enrolled',
livenessState: 'active',
versionDrift: 'current',
adapterHealth: 'healthy',
configRollout: 'reported',
credentialStatus: 'verified',
updateStatus: 'current',
remoteControl: 'disabled',
configDrift: { status: 'current' },
rollout: { status: 'current' },
credentialHealth: { status: 'verified', kind: 'agent-token' },
commandPolicy: { status: 'disabled' },
},
capabilities: { supportsPause: false, supportsScope: false, supportsTest: false },
}));
const listSpy = vi.spyOn(ConnectionsAPI, 'list').mockImplementation(async () => ({
connections: connections.map((connection) => ({
...connection,
scope: { ...(connection.scope ?? {}) },
fleet: connection.fleet ? { ...connection.fleet } : undefined,
capabilities: { ...connection.capabilities },
})),
systems: [],
}));
const { result } = renderHook(() => useConnectionsLedger());
await waitFor(() => expect(result.rows()).toHaveLength(120));
const firstRows = result.rows();
result.reload();
await waitFor(() => expect(listSpy).toHaveBeenCalledTimes(2));
await waitFor(() => expect(result.loading()).toBe(false));
expect(result.rows()).toHaveLength(120);
expect(result.rows()[0]).toBe(firstRows[0]);
expect(result.rows()[75]).toBe(firstRows[75]);
expect(result.rows()[119]).toBe(firstRows[119]);
});
it('prioritizes explicit rollout drift, credential, and command-policy posture', async () => {
const connections: Connection[] = [
{
@@ -203,12 +258,70 @@ describe('useConnectionsLedger', () => {
},
capabilities: { supportsPause: false, supportsScope: false, supportsTest: false },
},
{
id: 'agent:config-pending',
type: 'agent',
name: 'config-pending',
address: 'config-pending',
state: 'active',
stateReason: '',
enabled: true,
surfaces: ['host'],
scope: { host: true },
lastSeen: '2026-04-23T12:00:00Z',
lastError: null,
source: 'agent',
fleet: {
enrollmentState: 'enrolled',
livenessState: 'active',
versionDrift: 'current',
adapterHealth: 'healthy',
configRollout: 'reported',
credentialStatus: 'verified',
updateStatus: 'current',
remoteControl: 'enabled',
configDrift: { status: 'pending' },
rollout: { status: 'current' },
credentialHealth: { status: 'verified', kind: 'agent-token' },
commandPolicy: { status: 'enabled' },
},
capabilities: { supportsPause: false, supportsScope: false, supportsTest: false },
},
{
id: 'agent:config-unknown',
type: 'agent',
name: 'config-unknown',
address: 'config-unknown',
state: 'active',
stateReason: '',
enabled: true,
surfaces: ['host'],
scope: { host: true },
lastSeen: '2026-04-23T12:00:00Z',
lastError: null,
source: 'agent',
fleet: {
enrollmentState: 'enrolled',
livenessState: 'active',
versionDrift: 'current',
adapterHealth: 'healthy',
configRollout: 'reported',
credentialStatus: 'verified',
updateStatus: 'current',
remoteControl: 'enabled',
configDrift: { status: 'unknown' },
rollout: { status: 'current' },
credentialHealth: { status: 'verified', kind: 'agent-token' },
commandPolicy: { status: 'enabled' },
},
capabilities: { supportsPause: false, supportsScope: false, supportsTest: false },
},
];
vi.spyOn(ConnectionsAPI, 'list').mockResolvedValue({ connections, systems: [] });
const { result } = renderHook(() => useConnectionsLedger());
await waitFor(() => expect(result.rows()).toHaveLength(4));
await waitFor(() => expect(result.rows()).toHaveLength(6));
const byID = new Map(result.rows().map((row) => [row.id, row]));
expect(byID.get('agent:drifted')?.fleetHighlights.map((signal) => signal.label)).toEqual([
@@ -227,6 +340,12 @@ describe('useConnectionsLedger', () => {
expect(
byID.get('agent:remote-disabled')?.fleetHighlights.map((signal) => signal.label),
).toEqual(['Remote control disabled']);
expect(byID.get('agent:config-pending')?.fleetHighlights.map((signal) => signal.label)).toEqual(
['Config pending', 'Remote control enabled'],
);
expect(byID.get('agent:config-unknown')?.fleetHighlights.map((signal) => signal.label)).toEqual(
['Config unknown', 'Remote control enabled'],
);
});
it('renders a Proxmox cluster row from the canonical system metadata', async () => {
@@ -257,6 +257,71 @@ export interface FleetGovernanceSignal {
tone: FleetGovernanceSignalTone;
}
export const CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS = 80;
export const CONNECTIONS_TABLE_VISIBLE_ROWS_INCREMENT = 80;
export interface ConnectionsTableVisibilityState {
totalRows: number;
visibleLimit: number;
visibleRows: number;
hiddenRows: number;
isBounded: boolean;
statusText: string;
showMoreLabel: string;
showMoreAriaLabel: string;
nextVisibleLimit: number;
}
export const connectionsTableVisibilityState = (
totalRows: number,
visibleLimit: number,
): ConnectionsTableVisibilityState => {
const normalizedTotal = Math.max(0, Math.floor(totalRows));
const normalizedLimit = Math.max(
CONNECTIONS_TABLE_INITIAL_VISIBLE_ROWS,
Math.floor(visibleLimit),
);
const visibleRows = Math.min(normalizedTotal, normalizedLimit);
const hiddenRows = Math.max(0, normalizedTotal - visibleRows);
const nextBatch = Math.min(CONNECTIONS_TABLE_VISIBLE_ROWS_INCREMENT, hiddenRows);
const nextVisibleLimit = Math.min(
normalizedTotal,
visibleRows + CONNECTIONS_TABLE_VISIBLE_ROWS_INCREMENT,
);
const systemLabel = normalizedTotal === 1 ? 'system' : 'systems';
const remainingLabel = hiddenRows === 1 ? 'system' : 'systems';
const statusText =
hiddenRows > 0
? `Showing ${visibleRows} of ${normalizedTotal} monitored ${systemLabel}. ${hiddenRows} more ${remainingLabel} available.`
: `Showing all ${normalizedTotal} monitored ${systemLabel}.`;
return {
totalRows: normalizedTotal,
visibleLimit: normalizedLimit,
visibleRows,
hiddenRows,
isBounded: hiddenRows > 0,
statusText,
showMoreLabel:
hiddenRows > CONNECTIONS_TABLE_VISIBLE_ROWS_INCREMENT
? `Show next ${CONNECTIONS_TABLE_VISIBLE_ROWS_INCREMENT}`
: `Show remaining ${hiddenRows}`,
showMoreAriaLabel:
hiddenRows > CONNECTIONS_TABLE_VISIBLE_ROWS_INCREMENT
? `Show next ${nextBatch} monitored systems. ${hiddenRows} currently hidden.`
: `Show remaining ${hiddenRows} monitored ${remainingLabel}.`,
nextVisibleLimit,
};
};
export const visibleConnectionsTableRows = <TRow>(
rows: readonly TRow[],
visibleLimit: number,
): readonly TRow[] => {
if (rows.length <= visibleLimit) return rows;
return rows.slice(0, visibleLimit);
};
const DEFAULT_FLEET_GOVERNANCE: ConnectionFleetGovernance = {
enrollmentState: 'pending',
livenessState: 'pending',
@@ -600,9 +665,7 @@ const rolloutSignal = (state: ConnectionFleetRolloutState): FleetGovernanceSigna
}
};
const credentialHealthSignal = (
state: ConnectionFleetCredentialHealth,
): FleetGovernanceSignal => {
const credentialHealthSignal = (state: ConnectionFleetCredentialHealth): FleetGovernanceSignal => {
switch (state.status) {
case 'verified':
return {
@@ -175,6 +175,78 @@ interface ClusterRollup {
lastSeen?: string;
}
interface CachedInfrastructureSystemRow {
signature: string;
row: InfrastructureSystemRow;
}
const stableRecordEntries = (record?: Record<string, boolean> | null): [string, boolean][] =>
Object.entries(record ?? {}).sort(([left], [right]) => left.localeCompare(right));
const connectionRowSignature = (connection: Connection): string =>
JSON.stringify({
id: connection.id,
type: connection.type,
name: connection.name,
address: connection.address,
state: connection.state,
stateReason: connection.stateReason,
enabled: connection.enabled,
surfaces: connection.surfaces,
scope: stableRecordEntries(connection.scope),
lastSeen: connection.lastSeen,
lastActivityText: connectionLastActivityText(connection),
lastError: connection.lastError,
source: connection.source,
capabilities: connection.capabilities,
agentVersion: connection.agentVersion,
expectedAgentVersion: connection.expectedAgentVersion,
agentUpdateAvailable: connection.agentUpdateAvailable,
agentIdentity: connection.agentIdentity,
hostAliases: connection.hostAliases,
fleet: connection.fleet,
});
const connectionRowSignatureForID = (
connectionsByID: Map<string, Connection>,
id?: string | null,
): string | null => {
if (!id) return null;
const connection = connectionsByID.get(id);
return connection ? connectionRowSignature(connection) : null;
};
const systemRowSignature = (
system: ConnectionSystem,
connectionsByID: Map<string, Connection>,
): string =>
JSON.stringify({
id: system.id,
type: system.type,
clusterName: system.clusterName,
components: system.components,
members: (system.members ?? []).map((member) => ({
id: member.id,
name: member.name,
endpoint: member.endpoint,
hostAliases: member.hostAliases,
state: member.state,
lastSeen: member.lastSeen,
lastActivityText: lastActivityTextFromLastSeen(member.lastSeen),
primary: member.primary,
agentConnectionId: member.agentConnectionId,
agentConnectionSignature: connectionRowSignatureForID(
connectionsByID,
member.agentConnectionId,
),
})),
componentConnections: system.components.map((component) => ({
connectionId: component.connectionId,
signature: connectionRowSignatureForID(connectionsByID, component.connectionId),
})),
primaryConnectionSignature: connectionRowSignatureForID(connectionsByID, system.id),
});
const memberSubtitleFor = (member: ConnectionSystemMember): string =>
member.primary ? 'Primary node' : 'Cluster member';
@@ -361,6 +433,36 @@ interface ConnectionsLedgerSnapshot {
}
export const useConnectionsLedger = (): ConnectionsLedger => {
const rowCache = new Map<string, CachedInfrastructureSystemRow>();
const cacheRow = (
activeKeys: Set<string>,
key: string,
signature: string,
buildRow: () => InfrastructureSystemRow | null,
): InfrastructureSystemRow | null => {
activeKeys.add(key);
const cached = rowCache.get(key);
if (cached?.signature === signature) {
return cached.row;
}
const row = buildRow();
if (!row) {
rowCache.delete(key);
return null;
}
rowCache.set(key, { signature, row });
return row;
};
const pruneRowCache = (activeKeys: Set<string>) => {
for (const key of rowCache.keys()) {
if (!activeKeys.has(key)) {
rowCache.delete(key);
}
}
};
const [resource, { refetch }] = createResource<ConnectionsLedgerSnapshot>(
async () => {
const response = await ConnectionsAPI.list();
@@ -389,19 +491,41 @@ export const useConnectionsLedger = (): ConnectionsLedger => {
const rows = createMemo<InfrastructureSystemRow[]>(() => {
const allConnections = connections();
const systems = snapshot().systems ?? [];
const activeCacheKeys = new Set<string>();
const standaloneRows = () =>
allConnections
.map((connection) =>
cacheRow(
activeCacheKeys,
`connection:${connection.id}`,
connectionRowSignature(connection),
() => connectionToRow(connection),
),
)
.filter((row): row is InfrastructureSystemRow => Boolean(row));
if (systems.length === 0) {
return allConnections.map(connectionToRow);
const rows = standaloneRows();
pruneRowCache(activeCacheKeys);
return rows;
}
const byID = new Map(allConnections.map((connection) => [connection.id, connection]));
const groupedRows = systems
.map((system) => systemToRow(system, byID))
.map((system) =>
cacheRow(activeCacheKeys, `system:${system.id}`, systemRowSignature(system, byID), () =>
systemToRow(system, byID),
),
)
.filter((row): row is InfrastructureSystemRow => Boolean(row));
if (groupedRows.length === 0) {
return allConnections.map(connectionToRow);
const rows = standaloneRows();
pruneRowCache(activeCacheKeys);
return rows;
}
pruneRowCache(activeCacheKeys);
return groupedRows;
});
const findById = (id: string) => connections().find((conn) => conn.id === id);