Skip to content

Commit a11a6d7

Browse files
committed
Refactor clab-ui host integration
1 parent c4c105f commit a11a6d7

33 files changed

+1944
-850
lines changed

apps/standalone/server/topologyProxy.ts

Lines changed: 58 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,13 @@ import type { ClabApiClient } from "./clabApiClient.js";
1313
import { ClabApiFileSystemAdapter } from "./clabApiFileSystem.js";
1414
import { getTokenFromRequest } from "./middleware.js";
1515
import { TopologyHostCore } from "@srl-labs/clab-ui/core/host/TopologyHostCore";
16-
import type {
17-
ContainerDataProvider,
18-
ContainerInfo,
19-
InterfaceInfo
20-
} from "@srl-labs/clab-ui/core/parsing/types";
2116
import type {
2217
TopologyHostCommand,
2318
TopologyHostResponseMessage,
2419
TopologySnapshot
2520
} from "@srl-labs/clab-ui/core/types/messages";
21+
import { createRuntimeContainerDataProvider } from "@srl-labs/clab-ui/topology/runtime";
22+
import type { HostRuntimeContainer, HostRuntimeInterface } from "@srl-labs/clab-ui/host";
2623

2724
interface RuntimeContainerPayload {
2825
name: string;
@@ -59,6 +56,56 @@ interface RuntimeInterfacePayload {
5956
stats?: RuntimeInterfaceStatsPayload;
6057
}
6158

59+
function toFiniteNumber(value: number | string | undefined): number | undefined {
60+
if (typeof value === "number") {
61+
return Number.isFinite(value) ? value : undefined;
62+
}
63+
if (typeof value === "string" && value.trim().length > 0) {
64+
const parsed = Number(value);
65+
return Number.isFinite(parsed) ? parsed : undefined;
66+
}
67+
return undefined;
68+
}
69+
70+
function toRuntimeInterface(iface: RuntimeInterfacePayload): HostRuntimeInterface {
71+
return {
72+
name: iface.name ?? "",
73+
alias: iface.alias ?? "",
74+
mac: iface.mac ?? "",
75+
mtu: toFiniteNumber(iface.mtu) ?? 0,
76+
state: iface.state ?? "",
77+
type: iface.type ?? "",
78+
ifIndex: toFiniteNumber(iface.ifIndex),
79+
stats: iface.stats
80+
? {
81+
rxBps: toFiniteNumber(iface.stats.rxBps),
82+
txBps: toFiniteNumber(iface.stats.txBps),
83+
rxPps: toFiniteNumber(iface.stats.rxPps),
84+
txPps: toFiniteNumber(iface.stats.txPps),
85+
rxBytes: toFiniteNumber(iface.stats.rxBytes),
86+
txBytes: toFiniteNumber(iface.stats.txBytes),
87+
rxPackets: toFiniteNumber(iface.stats.rxPackets),
88+
txPackets: toFiniteNumber(iface.stats.txPackets),
89+
statsIntervalSeconds: toFiniteNumber(iface.stats.statsIntervalSeconds)
90+
}
91+
: undefined
92+
};
93+
}
94+
95+
function toRuntimeContainers(containers: RuntimeContainerPayload[]): HostRuntimeContainer[] {
96+
return containers.map((container) => ({
97+
name: container.name ?? "",
98+
nodeName: container.nodeName ?? "",
99+
labName: container.labName ?? "",
100+
state: container.state ?? "",
101+
kind: container.kind ?? "",
102+
image: container.image ?? "",
103+
ipv4Address: container.ipv4Address ?? "",
104+
ipv6Address: container.ipv6Address ?? "",
105+
interfaces: (container.interfaces ?? []).map((iface) => toRuntimeInterface(iface))
106+
}));
107+
}
108+
62109
interface SnapshotRequest {
63110
path: string;
64111
mode?: "edit" | "view";
@@ -119,128 +166,6 @@ function extractLabName(filePath: string): string {
119166
return basename.replace(/\.clab\.ya?ml$/i, "");
120167
}
121168

122-
function normalizeLabName(labName: string): string {
123-
return labName.trim().toLowerCase();
124-
}
125-
126-
function shortContainerName(container: RuntimeContainerPayload): string {
127-
if (container.nodeName && container.nodeName.length > 0) {
128-
return container.nodeName;
129-
}
130-
const fullName = container.name ?? "";
131-
const prefix = container.labName ? `clab-${container.labName}-` : "";
132-
if (prefix && fullName.startsWith(prefix)) {
133-
return fullName.slice(prefix.length);
134-
}
135-
return fullName;
136-
}
137-
138-
function stripCidr(address: string | undefined): string {
139-
if (!address) {
140-
return "";
141-
}
142-
const [value] = address.split("/");
143-
return value ?? "";
144-
}
145-
146-
function toFiniteNumber(value: number | string | undefined): number | undefined {
147-
if (typeof value === "number") {
148-
return Number.isFinite(value) ? value : undefined;
149-
}
150-
if (typeof value === "string" && value.trim().length > 0) {
151-
const parsed = Number(value);
152-
return Number.isFinite(parsed) ? parsed : undefined;
153-
}
154-
return undefined;
155-
}
156-
157-
function toInterfaceInfo(iface: RuntimeInterfacePayload): InterfaceInfo {
158-
return {
159-
name: iface.name ?? "",
160-
alias: iface.alias ?? "",
161-
mac: iface.mac ?? "",
162-
mtu: toFiniteNumber(iface.mtu) ?? 0,
163-
state: iface.state ?? "",
164-
type: iface.type ?? "",
165-
ifIndex: toFiniteNumber(iface.ifIndex),
166-
stats: iface.stats
167-
? {
168-
rxBps: toFiniteNumber(iface.stats.rxBps),
169-
txBps: toFiniteNumber(iface.stats.txBps),
170-
rxPps: toFiniteNumber(iface.stats.rxPps),
171-
txPps: toFiniteNumber(iface.stats.txPps),
172-
rxBytes: toFiniteNumber(iface.stats.rxBytes),
173-
txBytes: toFiniteNumber(iface.stats.txBytes),
174-
rxPackets: toFiniteNumber(iface.stats.rxPackets),
175-
txPackets: toFiniteNumber(iface.stats.txPackets),
176-
statsIntervalSeconds: toFiniteNumber(iface.stats.statsIntervalSeconds)
177-
}
178-
: undefined
179-
};
180-
}
181-
182-
function createContainerDataProvider(containers: RuntimeContainerPayload[]): ContainerDataProvider {
183-
const containersByLab = new Map<string, RuntimeContainerPayload[]>();
184-
for (const container of containers) {
185-
const labName = normalizeLabName(container.labName ?? "");
186-
const existing = containersByLab.get(labName);
187-
if (existing) {
188-
existing.push(container);
189-
} else {
190-
containersByLab.set(labName, [container]);
191-
}
192-
}
193-
194-
const findContainerEntry = (
195-
containerName: string,
196-
labName: string
197-
): RuntimeContainerPayload | undefined => {
198-
const labContainers = containersByLab.get(normalizeLabName(labName)) ?? [];
199-
return labContainers.find((container) => {
200-
if (container.name === containerName) return true;
201-
if (container.nodeName === containerName) return true;
202-
return shortContainerName(container) === containerName;
203-
});
204-
};
205-
206-
const toContainerInfo = (container: RuntimeContainerPayload): ContainerInfo => ({
207-
name: container.name ?? "",
208-
name_short: shortContainerName(container),
209-
rootNodeName: container.nodeName ?? "",
210-
state: container.state ?? "",
211-
kind: container.kind ?? "",
212-
image: container.image ?? "",
213-
IPv4Address: stripCidr(container.ipv4Address),
214-
IPv6Address: stripCidr(container.ipv6Address),
215-
interfaces: (container.interfaces ?? []).map((iface) => toInterfaceInfo(iface)),
216-
label: container.nodeName || container.name
217-
});
218-
219-
return {
220-
findContainer(containerName: string, labName: string): ContainerInfo | undefined {
221-
const container = findContainerEntry(containerName, labName);
222-
return container ? toContainerInfo(container) : undefined;
223-
},
224-
findInterface(containerName: string, ifaceName: string, labName: string): InterfaceInfo | undefined {
225-
const container = findContainerEntry(containerName, labName);
226-
if (!container) {
227-
return undefined;
228-
}
229-
const needle = ifaceName.trim().toLowerCase();
230-
if (!needle) {
231-
return undefined;
232-
}
233-
234-
const iface = (container.interfaces ?? []).find((entry) => {
235-
const byName = entry.name?.trim().toLowerCase();
236-
const byAlias = entry.alias?.trim().toLowerCase();
237-
return byName === needle || byAlias === needle;
238-
});
239-
return iface ? toInterfaceInfo(iface) : undefined;
240-
}
241-
};
242-
}
243-
244169
async function getOrCreateHost(
245170
client: ClabApiClient,
246171
token: string,
@@ -320,7 +245,9 @@ export function registerTopologyProxy(app: FastifyInstance, getClient: ClientRes
320245
try {
321246
const deploymentState = body.deploymentState ?? "undeployed";
322247
const mode = body.mode ?? (deploymentState === "deployed" ? "view" : "edit");
323-
const containerDataProvider = createContainerDataProvider(body.runtimeContainers ?? []);
248+
const containerDataProvider = createRuntimeContainerDataProvider(
249+
toRuntimeContainers(body.runtimeContainers ?? [])
250+
);
324251
const host = await getOrCreateHost(
325252
client,
326253
token,
@@ -364,7 +291,9 @@ export function registerTopologyProxy(app: FastifyInstance, getClient: ClientRes
364291
try {
365292
const deploymentState = body.deploymentState ?? "undeployed";
366293
const mode = body.mode ?? (deploymentState === "deployed" ? "view" : "edit");
367-
const containerDataProvider = createContainerDataProvider(body.runtimeContainers ?? []);
294+
const containerDataProvider = createRuntimeContainerDataProvider(
295+
toRuntimeContainers(body.runtimeContainers ?? [])
296+
);
368297
const host = await getOrCreateHost(
369298
client,
370299
token,

0 commit comments

Comments
 (0)