1717<template >
1818 <div class =" __container_app_topology" >
1919 <a-flex >
20- <a-card class =" topology-warpper" > <div id =" topology" ></div > </a-card >
20+ <a-card class =" topology-warpper" >
21+ <!-- G6 mount point (service call topology) -->
22+ <div id =" topology" ></div >
23+ </a-card >
2124 </a-flex >
25+ <!-- Right drawer: show service/application details after node click -->
2226 <a-drawer v-model:open =" detailDrawerOpen" :title =" detailTitle" placement =" right" width =" 520" >
2327 <a-spin :spinning =" detailLoading" >
2428 <a-typography-text v-if =" detailError" type =" danger" >{{ detailError }}</a-typography-text >
@@ -52,17 +56,25 @@ import { useRoute } from 'vue-router'
5256import { ExtensionCategory , register , Graph , NodeEvent } from ' @antv/g6'
5357const route = useRoute ()
5458
59+ // G6 graph instance for this view (resize/destroy lifecycle)
5560const graphRef = shallowRef <Graph | null >(null )
5661register (ExtensionCategory .NODE , ' vue-node' , VueNode )
62+
63+ // Right-side detail drawer state
5764const detailDrawerOpen = ref (false )
5865const detailLoading = ref (false )
5966const detailError = ref (' ' )
6067const detailData = shallowRef <Record <string , unknown >>({})
68+
69+ // Detail cache to avoid refetching the same node
6170const detailCache = new Map <string , Record <string , unknown >>()
71+
72+ // Current selected node info (for title and API selection)
6273const currentDetailKey = ref (' ' )
6374const currentDetailType = ref (' ' )
6475const selectedNodeId = ref (' ' )
6576
77+ // Clear selection when the drawer closes to avoid stale highlight
6678const clearSelectedNode = () => {
6779 const id = selectedNodeId .value
6880 if (id && graphRef .value ) {
@@ -94,6 +106,7 @@ const resolveNodeIconClass = (type: unknown) => {
94106 return ' icon-jiekouzhushou'
95107}
96108
109+ // Node renderer: choose icon by type and highlight for selected/active state
97110const StatefulNode = defineComponent ({
98111 props: {
99112 data: { type: Object as PropType <VueNodeViewData >, required: true }
@@ -157,13 +170,15 @@ const detailTitle = computed(() => {
157170 return currentDetailKey .value ? ` ${base }:${currentDetailKey .value } ` : base
158171})
159172
173+ // Expand detail object into description entries and filter empty values
160174const detailEntries = computed (() => {
161175 const data = detailData .value ?? {}
162176 return Object .entries (data )
163177 .filter (([, v ]) => v !== undefined && v !== null && String (v ) !== ' ' )
164178 .map (([key , value ]) => ({ key , value }))
165179})
166180
181+ // Format values for display (primitives/arrays/objects)
167182const formatValueForDisplay = (v : unknown ) => {
168183 if (v === null || v === undefined ) return ' '
169184 if (typeof v === ' string' || typeof v === ' number' || typeof v === ' boolean' ) return String (v )
@@ -182,6 +197,7 @@ const formatValueForDisplay = (v: unknown) => {
182197 return String (v )
183198}
184199
200+ // Convert backend topology payload into G6 nodes/edges
185201const buildGraphData = (raw : any ) => {
186202 const nodes = Array .isArray (raw ?.nodes )
187203 ? raw .nodes .map ((n : any ) => ({
@@ -202,6 +218,8 @@ const buildGraphData = (raw: any) => {
202218
203219 return { nodes , edges }
204220}
221+
222+ // Render topology: create Graph, configure layout/styles/behaviors, bind node click
205223const renderTopology = (graphData : any ) => {
206224 const root = document .getElementById (' topology' )
207225 if (! root ) return
@@ -244,6 +262,7 @@ const renderTopology = (graphData: any) => {
244262 ]
245263 })
246264
265+ // On node click, choose detail API by node type and show it in the drawer
247266 const handleNodeClick = async (e : any ) => {
248267 const serviceName = String (e ?.target ?.id ?? ' ' )
249268 const nodeData: any = serviceName ? graphRef .value ?.getNodeData (serviceName ) : undefined
@@ -260,6 +279,7 @@ const renderTopology = (graphData: any) => {
260279 : ' service'
261280 detailError .value = ' '
262281
282+ // Return immediately on cache hit
263283 const cacheKey = ` ${serviceName } `
264284 const cached = detailCache .get (cacheKey )
265285 if (cached ) {
@@ -271,6 +291,7 @@ const renderTopology = (graphData: any) => {
271291 detailLoading .value = true
272292 try {
273293 let res
294+ // application: use application detail API; service: split 'service:version:group' for service detail API
274295 if (nodeData .type === ' application' ) {
275296 res = await getApplicationDetail (nodeData .id )
276297 } else if (nodeData .type === ' service' ) {
@@ -311,13 +332,15 @@ watch(detailEntries, () => {
311332})
312333onMounted (async () => {
313334 try {
335+ // Fetch service graph by route param and render with force layout
314336 const serviceName = String (route .params ?.pathId ?? ' ' )
315337 console .log (' topology' , serviceName )
316338 const res = await getServiceGraph (serviceName )
317339 if (res ?.code !== HTTP_STATUS .SUCCESS ) return
318340 const graphData = buildGraphData (res ?.data )
319341 renderTopology (graphData )
320342
343+ // Resize canvas on window resize (keep container width responsive)
321344 resizeHandler = () => {
322345 const root = document .getElementById (' topology' )
323346 if (! root || ! graphRef .value ) return
@@ -329,6 +352,7 @@ onMounted(async () => {
329352
330353onBeforeUnmount (() => {
331354 if (resizeHandler ) window .removeEventListener (' resize' , resizeHandler )
355+ // Destroy Graph on unmount to release events/resources
332356 graphRef .value ?.destroy ()
333357 graphRef .value = null
334358})
0 commit comments