@@ -32,6 +32,8 @@ export interface Host {
3232 port : number | string
3333 key : string
3434 ports ?: Array < number | string >
35+ /** Distinct IPs merged into this column (group-by-ip / group-by-alias). */
36+ ips ?: string [ ]
3537 /** Friendly name from IP alias enrichment (aliasSrc / aliasDst) */
3638 displayLabel ?: string
3739 /** Custom image URL from matched alias row (call flow header) */
@@ -72,7 +74,37 @@ export interface CallIdLegend {
7274 colors : CallIdColors
7375}
7476
75- export type HostGrouping = 'ungrouped' | 'group-by-ip'
77+ export type HostGrouping = 'ungrouped' | 'group-by-ip' | 'group-by-alias'
78+
79+ const ALIAS_KEY_PREFIX = 'alias:'
80+
81+ function rowOf ( msg : RawMessage ) : Record < string , unknown > {
82+ return msg as Record < string , unknown >
83+ }
84+
85+ /** Resolved alias label for an endpoint, or empty when enrichment has no alias. */
86+ export function endpointAlias ( row : Record < string , unknown > , side : 'src' | 'dst' ) : string {
87+ const ip = String ( side === 'src' ? row . src_ip ?? '' : row . dst_ip ?? '' )
88+ const lbl = side === 'src' ? displaySrcIp ( row ) : displayDstIp ( row )
89+ if ( lbl && lbl !== ip ) return lbl
90+ return ''
91+ }
92+
93+ function mergeHostEndpoint (
94+ host : Host ,
95+ ip : string ,
96+ port : number | string ,
97+ grouping : HostGrouping ,
98+ aliasLabel : string ,
99+ ) : void {
100+ if ( grouping === 'group-by-ip' || grouping === 'group-by-alias' ) {
101+ if ( ! host . ports ! . includes ( port ) ) host . ports ! . push ( port )
102+ if ( grouping === 'group-by-alias' && ip && ! host . ips ! . includes ( ip ) ) host . ips ! . push ( ip )
103+ }
104+ if ( grouping === 'group-by-alias' && aliasLabel && ! host . displayLabel ) {
105+ host . displayLabel = aliasLabel
106+ }
107+ }
76108
77109/**
78110 * Build the call-ID legend from a *raw* (un-filtered) message list so
@@ -151,35 +183,77 @@ export function shortcutIPv6(str: string): string {
151183 return str
152184}
153185
154- function hostKey ( ip : string , port : number | string , grouping : HostGrouping ) : string {
155- return grouping === 'group-by-ip' ? ip : `${ ip } :${ port } `
186+ export function hostKey (
187+ ip : string ,
188+ port : number | string ,
189+ grouping : HostGrouping ,
190+ row ?: Record < string , unknown > ,
191+ side ?: 'src' | 'dst' ,
192+ ) : string {
193+ if ( grouping === 'group-by-ip' ) return ip
194+ if ( grouping === 'group-by-alias' && row && side ) {
195+ const alias = endpointAlias ( row , side )
196+ if ( alias ) return `${ ALIAS_KEY_PREFIX } ${ alias } `
197+ return `${ ip } :${ port } `
198+ }
199+ return `${ ip } :${ port } `
200+ }
201+
202+ export function hostKeyFromMessage (
203+ msg : RawMessage ,
204+ side : 'src' | 'dst' ,
205+ grouping : HostGrouping ,
206+ ) : string {
207+ const row = rowOf ( msg )
208+ const ip = String ( side === 'src' ? row . src_ip ?? 'unknown' : row . dst_ip ?? 'unknown' )
209+ const port = side === 'src' ? ( row . src_port ?? 0 ) : ( row . dst_port ?? 0 )
210+ return hostKey ( ip , port as number | string , grouping , row , side )
156211}
157212
158213export function buildHosts ( items : RawMessage [ ] , grouping : HostGrouping ) : Host [ ] {
159214 const order : string [ ] = [ ]
160215 const map = new Map < string , Host > ( )
216+ const merges = grouping === 'group-by-ip' || grouping === 'group-by-alias'
217+
161218 items . forEach ( ( msg ) => {
219+ const row = rowOf ( msg )
162220 const srcIp = msg . src_ip || 'unknown'
163221 const dstIp = msg . dst_ip || 'unknown'
164222 const srcPort = msg . src_port ?? 0
165223 const dstPort = msg . dst_port ?? 0
166- const srcKey = hostKey ( srcIp , srcPort , grouping )
167- const dstKey = hostKey ( dstIp , dstPort , grouping )
224+ const srcAlias = grouping === 'group-by-alias' ? endpointAlias ( row , 'src' ) : ''
225+ const dstAlias = grouping === 'group-by-alias' ? endpointAlias ( row , 'dst' ) : ''
226+ const srcKey = hostKey ( srcIp , srcPort , grouping , row , 'src' )
227+ const dstKey = hostKey ( dstIp , dstPort , grouping , row , 'dst' )
168228
169229 if ( ! map . has ( srcKey ) ) {
170- map . set ( srcKey , { ip : srcIp , port : srcPort , key : srcKey , ports : [ srcPort ] } )
230+ const h : Host = {
231+ ip : srcIp ,
232+ port : srcPort ,
233+ key : srcKey ,
234+ ports : [ srcPort ] ,
235+ ips : merges ? [ srcIp ] : undefined ,
236+ }
237+ if ( srcAlias ) h . displayLabel = srcAlias
238+ map . set ( srcKey , h )
171239 order . push ( srcKey )
172- } else if ( grouping === 'group-by-ip' ) {
173- const h = map . get ( srcKey ) !
174- if ( ! h . ports ! . includes ( srcPort ) ) h . ports ! . push ( srcPort )
240+ } else if ( merges ) {
241+ mergeHostEndpoint ( map . get ( srcKey ) ! , srcIp , srcPort , grouping , srcAlias )
175242 }
176243
177244 if ( ! map . has ( dstKey ) ) {
178- map . set ( dstKey , { ip : dstIp , port : dstPort , key : dstKey , ports : [ dstPort ] } )
245+ const h : Host = {
246+ ip : dstIp ,
247+ port : dstPort ,
248+ key : dstKey ,
249+ ports : [ dstPort ] ,
250+ ips : merges ? [ dstIp ] : undefined ,
251+ }
252+ if ( dstAlias ) h . displayLabel = dstAlias
253+ map . set ( dstKey , h )
179254 order . push ( dstKey )
180- } else if ( grouping === 'group-by-ip' ) {
181- const h = map . get ( dstKey ) !
182- if ( ! h . ports ! . includes ( dstPort ) ) h . ports ! . push ( dstPort )
255+ } else if ( merges ) {
256+ mergeHostEndpoint ( map . get ( dstKey ) ! , dstIp , dstPort , grouping , dstAlias )
183257 }
184258 } )
185259 return order . map ( ( k ) => map . get ( k ) as Host )
@@ -192,15 +266,14 @@ export function resolveHostFlowMeta(
192266 grouping : HostGrouping ,
193267) : { displayLabel : string ; aliasImage : string ; aliasTags : string [ ] } {
194268 const empty = { displayLabel : '' , aliasImage : '' , aliasTags : [ ] as string [ ] }
195- const rec = ( m : RawMessage ) => m as Record < string , unknown >
196269 for ( const msg of items ) {
197270 const srcIp = msg . src_ip || 'unknown'
198271 const dstIp = msg . dst_ip || 'unknown'
199272 const sp = msg . src_port ?? 0
200273 const dp = msg . dst_port ?? 0
274+ const row = rowOf ( msg )
201275 if ( grouping === 'group-by-ip' ) {
202276 if ( srcIp === host . ip ) {
203- const row = rec ( msg )
204277 const lbl = displaySrcIp ( row )
205278 const en = aliasSrcEnrichment ( row )
206279 const hasAlias = lbl !== '' && lbl !== srcIp
@@ -209,7 +282,6 @@ export function resolveHostFlowMeta(
209282 }
210283 }
211284 if ( dstIp === host . ip ) {
212- const row = rec ( msg )
213285 const lbl = displayDstIp ( row )
214286 const en = aliasDstEnrichment ( row )
215287 const hasAlias = lbl !== '' && lbl !== dstIp
@@ -218,17 +290,15 @@ export function resolveHostFlowMeta(
218290 }
219291 }
220292 } else {
221- if ( hostKey ( srcIp , sp , grouping ) === host . key ) {
222- const row = rec ( msg )
293+ if ( hostKey ( srcIp , sp , grouping , row , 'src' ) === host . key ) {
223294 const lbl = displaySrcIp ( row )
224295 const en = aliasSrcEnrichment ( row )
225296 const hasAlias = lbl !== '' && lbl !== srcIp
226297 if ( hasAlias || en . image || en . tags . length > 0 ) {
227298 return { displayLabel : hasAlias ? lbl : '' , aliasImage : en . image , aliasTags : en . tags }
228299 }
229300 }
230- if ( hostKey ( dstIp , dp , grouping ) === host . key ) {
231- const row = rec ( msg )
301+ if ( hostKey ( dstIp , dp , grouping , row , 'dst' ) === host . key ) {
232302 const lbl = displayDstIp ( row )
233303 const en = aliasDstEnrichment ( row )
234304 const hasAlias = lbl !== '' && lbl !== dstIp
@@ -238,6 +308,9 @@ export function resolveHostFlowMeta(
238308 }
239309 }
240310 }
311+ if ( grouping === 'group-by-alias' && host . displayLabel ) {
312+ return { ...empty , displayLabel : host . displayLabel }
313+ }
241314 return empty
242315}
243316
@@ -252,11 +325,11 @@ export function resolveHostDisplayLabel(
252325
253326function indexOfHost (
254327 hosts : Host [ ] ,
255- ip : string ,
256- port : number | string ,
328+ msg : RawMessage ,
329+ side : 'src' | 'dst' ,
257330 grouping : HostGrouping ,
258331) : number {
259- const key = hostKey ( ip , port , grouping )
332+ const key = hostKeyFromMessage ( msg , side , grouping )
260333 return hosts . findIndex ( ( h ) => h . key === key )
261334}
262335
@@ -331,8 +404,8 @@ export function buildFlow(items: RawMessage[] | null | undefined, opts: BuildOpt
331404 let method = msg . sip_method || msg . method || msg . event || ''
332405 const proto = msg . protocol
333406
334- const srcIdx = indexOfHost ( hosts , srcIp , srcPort , grouping )
335- const dstIdx = indexOfHost ( hosts , dstIp , dstPort , grouping )
407+ const srcIdx = indexOfHost ( hosts , msg , 'src' , grouping )
408+ const dstIdx = indexOfHost ( hosts , msg , 'dst' , grouping )
336409
337410 const isRadial = srcIdx === dstIdx
338411 const isLastHost = isRadial && hosts . length > 1 && srcIdx === hosts . length - 1
0 commit comments