@@ -13,16 +13,13 @@ import type { ClabApiClient } from "./clabApiClient.js";
1313import { ClabApiFileSystemAdapter } from "./clabApiFileSystem.js" ;
1414import { getTokenFromRequest } from "./middleware.js" ;
1515import { 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" ;
2116import 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
2724interface 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+
62109interface SnapshotRequest {
63110 path : string ;
64111 mode ?: "edit" | "view" ;
@@ -119,128 +166,6 @@ function extractLabName(filePath: string): string {
119166 return basename . replace ( / \. c l a b \. y a ? m l $ / 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-
244169async 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