mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Improve connections table scalability headroom
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user