This is an automated email from the ASF dual-hosted git repository.

zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit 0e284d69c30ef4b59e15067c3f9086ecedd8d2f9
Author: Logic <[email protected]>
AuthorDate: Fri May 29 21:58:31 2026 +0800

    feat(web-next): expose source-backed topology node legend
---
 web-next/app/topology/page.test.tsx               | 54 ++++++++++++++---------
 web-next/app/topology/topology-page.tsx           | 23 +++++++++-
 web-next/app/ui-lab/page.test.tsx                 | 42 +++++++++++-------
 web-next/app/ui-lab/page.tsx                      | 24 +++++++++-
 web-next/lib/i18n-runtime-messages.ts             | 22 ++++++++-
 web-next/packages/hertzbeat-ui/src/index.test.tsx | 34 ++++++++++----
 web-next/packages/hertzbeat-ui/src/index.tsx      | 30 +++++++++++--
 web-next/test/i18n-test-helper.ts                 | 22 ++++++++-
 8 files changed, 193 insertions(+), 58 deletions(-)

diff --git a/web-next/app/topology/page.test.tsx 
b/web-next/app/topology/page.test.tsx
index 3b9495c28f..5f5f377db7 100644
--- a/web-next/app/topology/page.test.tsx
+++ b/web-next/app/topology/page.test.tsx
@@ -947,31 +947,41 @@ describe('topology page', () => {
     
expect(html).toContain('data-hz-topology-legend-header-owner="hertzbeat-ui-legend-header"');
     
expect(html).toContain('data-hz-topology-legend-title-owner="hertzbeat-ui-legend-title"');
     
expect(html).toContain('data-hz-topology-legend-summary-owner="hertzbeat-ui-legend-summary"');
-    expect(html).toContain('data-hz-topology-legend-section="status"');
-    
expect(html).toContain('data-hz-topology-legend-section-owner="hertzbeat-ui-legend-section"');
-    
expect(html).toContain('data-hz-topology-legend-section-label-owner="hertzbeat-ui-legend-section-label"');
-    expect(html).toContain('data-hz-topology-legend-section="interaction"');
-    
expect(html).not.toContain('data-hz-topology-legend-section="source-kind"');
-    expect(html).not.toContain('data-hz-topology-legend-section="confidence"');
-    expect(html).toContain('data-hz-topology-legend-item="healthy-node"');
-    expect(html).toContain('data-hz-topology-legend-item="warning-node"');
-    expect(html).toContain('data-hz-topology-legend-item="critical-node"');
+           expect(html).toContain('data-hz-topology-legend-section="status"');
+           
expect(html).toContain('data-hz-topology-legend-section-owner="hertzbeat-ui-legend-section"');
+           
expect(html).toContain('data-hz-topology-legend-section-label-owner="hertzbeat-ui-legend-section-label"');
+           
expect(html).toContain('data-hz-topology-legend-section="node-type"');
+           
expect(html).toContain('data-hz-topology-legend-section="interaction"');
+           
expect(html).not.toContain('data-hz-topology-legend-section="source-kind"');
+           
expect(html).not.toContain('data-hz-topology-legend-section="confidence"');
+           
expect(html).toContain('data-hz-topology-legend-item="node-type-service"');
+           
expect(html).toContain('data-hz-topology-legend-item="node-type-database"');
+           
expect(html).toContain('data-hz-topology-legend-icon-library="lucide-react"');
+           
expect(html).toContain('data-hz-topology-legend-icon-source="entity-type-catalog"');
+           
expect(html).toContain('data-hz-topology-legend-icon-name="blocks"');
+           
expect(html).toContain('data-hz-topology-legend-icon-name="database"');
+           
expect(html).toContain('data-hz-topology-legend-icon-no-handdrawn="true"');
+           
expect(html).toContain('data-hz-topology-legend-item="healthy-node"');
+           
expect(html).toContain('data-hz-topology-legend-item="warning-node"');
+           
expect(html).toContain('data-hz-topology-legend-item="critical-node"');
     expect(html).toContain('data-hz-topology-legend-item="selected-node"');
     expect(html).toContain('data-hz-topology-legend-item="directional-edge"');
     expect(html).toContain('data-hz-topology-legend-item="dimmed-edge"');
-    expect(html).toContain('data-hz-topology-legend-color="#22c55e"');
-    expect(html).toContain('data-hz-topology-legend-color="#f59e0b"');
-    expect(html).toContain('data-hz-topology-legend-color="#ef4444"');
-    expect(html).toContain('data-hz-topology-legend-swatch-shape="line"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-status-token"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-interaction-token"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-edge-token"');
-    expect(html).toContain('data-hz-topology-legend-no-handdrawn-icon="true"');
-    
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="node-ring"');
-    
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="selected-ring"');
-    
expect(html).toContain('data-hz-topology-legend-item-owner="hertzbeat-ui-legend-item"');
-    
expect(html).toContain('data-hz-topology-legend-swatch-owner="hertzbeat-ui-legend-swatch"');
-    
expect(html).toContain('data-hz-topology-legend-item-label-owner="hertzbeat-ui-legend-item-label"');
+           expect(html).toContain('data-hz-topology-legend-color="#22c55e"');
+           expect(html).toContain('data-hz-topology-legend-color="#f59e0b"');
+           expect(html).toContain('data-hz-topology-legend-color="#ef4444"');
+           
expect(html).toContain('data-hz-topology-legend-visual-mode="source-backed-text"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="lucide-react"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-status-token"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-interaction-token"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-edge-token"');
+           
expect(html).toContain('data-hz-topology-legend-no-handdrawn-icon="true"');
+           expect(html).not.toContain('data-hz-topology-legend-swatch-owner=');
+           expect(html).not.toContain('data-hz-topology-legend-swatch-shape=');
+           
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="node-ring"');
+           
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="selected-ring"');
+           
expect(html).toContain('data-hz-topology-legend-item-owner="hertzbeat-ui-legend-item"');
+           
expect(html).toContain('data-hz-topology-legend-item-label-owner="hertzbeat-ui-legend-item-label"');
     
expect(html).toContain('data-hz-topology-legend-item-value-owner="hertzbeat-ui-legend-item-value"');
     expect(html).not.toContain('data-hz-topology-legend-pattern="dashed"');
     
expect(html).toContain('data-topology-detail-drawer-owner="hertzbeat-ui-detail-drawer"');
diff --git a/web-next/app/topology/topology-page.tsx 
b/web-next/app/topology/topology-page.tsx
index 5dd9ec494b..f40c4c4d33 100644
--- a/web-next/app/topology/topology-page.tsx
+++ b/web-next/app/topology/topology-page.tsx
@@ -42,6 +42,7 @@ import {
 } from '@hertzbeat/ui';
 import {
   HzTopologyG6Canvas,
+  HZ_TOPOLOGY_G6_NODE_ICON_CATALOG,
   buildHzTopologyG6LargeGraphStrategy,
   buildHzTopologyG6RenderWindow,
   type HzTopologyG6GraphInput,
@@ -1122,8 +1123,28 @@ export default function TopologyPage({
     : topologyEmptyStateKind === 'degraded'
       ? t('topology.degraded.api.source')
       : 'API';
+  const topologyLegendNodeTypeItems = React.useMemo(
+    () =>
+      HZ_TOPOLOGY_G6_NODE_ICON_CATALOG.filter(icon => icon.kind !== 
'unknown').map(icon => ({
+        id: `node-type-${icon.kind}`,
+        label: t(`topology.legend.node-type.${icon.kind}`),
+        value: icon.iconName,
+        iconSrc: icon.iconSrc,
+        iconAlt: '',
+        iconLibrary: icon.iconLibrary,
+        iconName: icon.iconName,
+        iconSource: icon.iconSource,
+        visualSource: 'lucide-react' as const
+      })),
+    [t]
+  );
   const topologyLegendSections = React.useMemo(
     () => [
+      {
+        id: 'node-type',
+        label: t('topology.legend.node-type'),
+        items: topologyLegendNodeTypeItems
+      },
       {
         id: 'status',
         label: t('topology.legend.status'),
@@ -1181,7 +1202,7 @@ export default function TopologyPage({
         ]
       }
     ],
-    [t]
+    [t, topologyLegendNodeTypeItems]
   );
   const topologyEdgeIds = React.useMemo(
     () => new Set(map.edges.map(edge => edge.id)),
diff --git a/web-next/app/ui-lab/page.test.tsx 
b/web-next/app/ui-lab/page.test.tsx
index 0297d90fcf..6b600075e3 100644
--- a/web-next/app/ui-lab/page.test.tsx
+++ b/web-next/app/ui-lab/page.test.tsx
@@ -2688,23 +2688,31 @@ describe('HertzBeat UI lab page', () => {
     expect(html).toContain('data-hz-topology-primitive="legend"');
     expect(html).toContain('data-hz-topology-legend-boundary="flush"');
     
expect(html).toContain('data-hz-topology-legend-boundary-owner="hertzbeat-ui-legend-boundary"');
-    
expect(html).toContain('data-hz-topology-legend-header-owner="hertzbeat-ui-legend-header"');
-    expect(html).toContain('data-hz-topology-legend-section="status"');
-    
expect(html).toContain('data-hz-topology-legend-section-owner="hertzbeat-ui-legend-section"');
-    
expect(html).toContain('data-hz-topology-legend-section-label-owner="hertzbeat-ui-legend-section-label"');
-    expect(html).toContain('data-hz-topology-legend-section="interaction"');
-    expect(html).not.toContain('data-hz-topology-legend-section="confidence"');
-    expect(html).toContain('data-hz-topology-legend-swatch-shape="line"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-status-token"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-interaction-token"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-edge-token"');
-    expect(html).toContain('data-hz-topology-legend-no-handdrawn-icon="true"');
-    
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="node-ring"');
-    
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="selected-ring"');
-    expect(html).toContain('data-hz-topology-legend-color="#22c55e"');
-    
expect(html).toContain('data-hz-topology-legend-item-owner="hertzbeat-ui-legend-item"');
-    
expect(html).toContain('data-hz-topology-legend-swatch-owner="hertzbeat-ui-legend-swatch"');
-    
expect(html).toContain('data-hz-topology-legend-item-label-owner="hertzbeat-ui-legend-item-label"');
+           
expect(html).toContain('data-hz-topology-legend-header-owner="hertzbeat-ui-legend-header"');
+           expect(html).toContain('data-hz-topology-legend-section="status"');
+           
expect(html).toContain('data-hz-topology-legend-section-owner="hertzbeat-ui-legend-section"');
+           
expect(html).toContain('data-hz-topology-legend-section-label-owner="hertzbeat-ui-legend-section-label"');
+           
expect(html).toContain('data-hz-topology-legend-section="node-type"');
+           
expect(html).toContain('data-hz-topology-legend-section="interaction"');
+           
expect(html).not.toContain('data-hz-topology-legend-section="confidence"');
+           
expect(html).toContain('data-hz-topology-legend-item="node-type-service"');
+           
expect(html).toContain('data-hz-topology-legend-item="node-type-database"');
+           
expect(html).toContain('data-hz-topology-legend-icon-library="lucide-react"');
+           
expect(html).toContain('data-hz-topology-legend-icon-source="entity-type-catalog"');
+           
expect(html).toContain('data-hz-topology-legend-icon-no-handdrawn="true"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="lucide-react"');
+           
expect(html).toContain('data-hz-topology-legend-visual-mode="source-backed-text"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-status-token"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-interaction-token"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-edge-token"');
+           
expect(html).toContain('data-hz-topology-legend-no-handdrawn-icon="true"');
+           expect(html).not.toContain('data-hz-topology-legend-swatch-owner=');
+           expect(html).not.toContain('data-hz-topology-legend-swatch-shape=');
+           
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="node-ring"');
+           
expect(html).not.toContain('data-hz-topology-legend-swatch-shape="selected-ring"');
+           expect(html).toContain('data-hz-topology-legend-color="#22c55e"');
+           
expect(html).toContain('data-hz-topology-legend-item-owner="hertzbeat-ui-legend-item"');
+           
expect(html).toContain('data-hz-topology-legend-item-label-owner="hertzbeat-ui-legend-item-label"');
     expect(html).not.toContain('data-hz-topology-legend-pattern="dashed"');
     expect(html).toContain('data-hz-ui-lab-topology-detail-drawer="shared"');
     expect(html).toContain('data-hz-ui="topology-detail-drawer"');
diff --git a/web-next/app/ui-lab/page.tsx b/web-next/app/ui-lab/page.tsx
index f49f175e4c..6d4aa16b02 100644
--- a/web-next/app/ui-lab/page.tsx
+++ b/web-next/app/ui-lab/page.tsx
@@ -204,6 +204,7 @@ import {
 } from '@hertzbeat/ui';
 import {
   HzTopologyG6Canvas,
+  HZ_TOPOLOGY_G6_NODE_ICON_CATALOG,
   buildHzTopologyG6LargeGraphStrategy,
   buildHzTopologyG6RenderWindow,
   buildHzTopologyG6ScaleFixture,
@@ -494,6 +495,17 @@ const topologyG6LargeGraphStrategy500 = 
buildHzTopologyG6LargeGraphStrategy(topo
 const topologyG6ScaleLabRenderWindow500 = 
buildHzTopologyG6RenderWindow(topologyG6ScaleLabGraph500, 
topologyG6LargeGraphStrategy500, {
   priorityNodeIds: ['scale-svc-420']
 });
+const topologyG6NodeTypeLegendItems = 
HZ_TOPOLOGY_G6_NODE_ICON_CATALOG.filter(icon => icon.kind !== 
'unknown').map(icon => ({
+  id: `node-type-${icon.kind}`,
+  label: icon.label,
+  value: icon.iconName,
+  iconSrc: icon.iconSrc,
+  iconAlt: '',
+  iconLibrary: icon.iconLibrary,
+  iconName: icon.iconName,
+  iconSource: icon.iconSource,
+  visualSource: 'lucide-react' as const
+}));
 
 const monitorRows = [
   { name: 'mysql-prod-01', app: 'MySQL', collector: 'collector-a', signal: 
'metrics', status: '可用', latency: '38 ms', tone: 'success' as const },
@@ -7000,6 +7012,11 @@ export default function HertzBeatUiLabPage() {
                   boundary="flush"
                   density="canvas-dock"
                   sections={[
+                        {
+                          id: 'node-type',
+                          label: 'Node type',
+                          items: topologyG6NodeTypeLegendItems
+                        },
                         {
                           id: 'status',
                           label: 'Status',
@@ -7262,9 +7279,14 @@ export default function HertzBeatUiLabPage() {
                   <HzTopologyLegend
                     data-hz-ui-lab-topology-legend="shared"
                     title="Topology legend"
-                    summaryLabel="2 groups"
+                    summaryLabel="3 groups"
                     boundary="flush"
                     sections={[
+                      {
+                        id: 'node-type',
+                        label: 'Node type',
+                        items: topologyG6NodeTypeLegendItems
+                      },
                       {
                         id: 'status',
                         label: 'Status',
diff --git a/web-next/lib/i18n-runtime-messages.ts 
b/web-next/lib/i18n-runtime-messages.ts
index 84396b91be..74fe659faf 100644
--- a/web-next/lib/i18n-runtime-messages.ts
+++ b/web-next/lib/i18n-runtime-messages.ts
@@ -984,7 +984,7 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'topology.alert-impact.open': 'Open alert impact',
     'topology.alert-impact.copy': 'Keep the selected edge, entity, and signal 
scope.',
     'topology.legend.title': 'Legend',
-    'topology.legend.summary': '2 groups',
+    'topology.legend.summary': '3 groups',
     'topology.legend.health': 'Health',
     'topology.legend.health.healthy': 'Healthy',
     'topology.legend.health.healthy-value': 'good',
@@ -1016,6 +1016,15 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'topology.legend.confidence': 'Confidence',
     'topology.legend.confidence.live': 'Current evidence',
     'topology.legend.confidence.stale': 'Low confidence / stale',
+    'topology.legend.node-type': 'Node type',
+    'topology.legend.node-type.application': 'Application',
+    'topology.legend.node-type.service': 'Service',
+    'topology.legend.node-type.database': 'Database',
+    'topology.legend.node-type.middleware': 'Middleware',
+    'topology.legend.node-type.k8s-workload': 'Workload',
+    'topology.legend.node-type.monitor': 'Monitor',
+    'topology.legend.node-type.resource': 'Resource',
+    'topology.legend.node-type.alert': 'Alert',
     'topology.timeline.title': 'Impact timeline',
     'topology.timeline.copy': 'Recent entity, relationship, and monitor 
evidence that changed this topology view.',
     'topology.timeline.detail.fallback': 'Topology evidence changed',
@@ -4492,7 +4501,7 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'topology.alert-impact.open': '打开告警影响面',
     'topology.alert-impact.copy': '保留当前边、实体和三信号范围。',
     'topology.legend.title': '图例',
-    'topology.legend.summary': '2 组',
+    'topology.legend.summary': '3 组',
     'topology.legend.health': '健康状态',
     'topology.legend.health.healthy': '健康',
     'topology.legend.health.healthy-value': '正常',
@@ -4524,6 +4533,15 @@ export const SUPPLEMENTAL_MESSAGES: 
Partial<Record<LocaleCode, Messages>> = {
     'topology.legend.confidence': '可信度',
     'topology.legend.confidence.live': '当前窗口证据',
     'topology.legend.confidence.stale': '低可信 / 过期',
+    'topology.legend.node-type': '节点类型',
+    'topology.legend.node-type.application': '应用',
+    'topology.legend.node-type.service': '服务',
+    'topology.legend.node-type.database': '数据库',
+    'topology.legend.node-type.middleware': '中间件',
+    'topology.legend.node-type.k8s-workload': '工作负载',
+    'topology.legend.node-type.monitor': '监控',
+    'topology.legend.node-type.resource': '资源',
+    'topology.legend.node-type.alert': '告警',
     'topology.timeline.title': '影响时间线',
     'topology.timeline.copy': '最近改变当前拓扑视图的实体、关系和监控对象证据。',
     'topology.timeline.detail.fallback': '拓扑证据已变更',
diff --git a/web-next/packages/hertzbeat-ui/src/index.test.tsx 
b/web-next/packages/hertzbeat-ui/src/index.test.tsx
index a36e11afc9..e045fa1fc7 100644
--- a/web-next/packages/hertzbeat-ui/src/index.test.tsx
+++ b/web-next/packages/hertzbeat-ui/src/index.test.tsx
@@ -5202,11 +5202,20 @@ describe('@hertzbeat/ui', () => {
           {
             id: 'status',
             label: 'Status',
-            items: [
-              { id: 'healthy-node', label: 'Healthy node', color: '#22c55e', 
visualSource: 'hertzbeat-status-token' },
-              { id: 'critical-node', label: 'Critical node', color: '#ef4444', 
visualSource: 'hertzbeat-status-token' }
-            ]
-          },
+                   items: [
+                     {
+                       id: 'service-node',
+                       label: 'Service node',
+                       iconSrc: 
'data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%3E%3C/svg%3E',
+                       iconLibrary: 'lucide-react',
+                       iconName: 'blocks',
+                       iconSource: 'entity-type-catalog',
+                       visualSource: 'lucide-react'
+                     },
+                     { id: 'healthy-node', label: 'Healthy node', color: 
'#22c55e', visualSource: 'hertzbeat-status-token' },
+                     { id: 'critical-node', label: 'Critical node', color: 
'#ef4444', visualSource: 'hertzbeat-status-token' }
+                   ]
+                 },
           {
             id: 'interaction',
             label: 'Interaction',
@@ -5227,10 +5236,17 @@ describe('@hertzbeat/ui', () => {
     
expect(html).toContain('data-hz-topology-legend-summary-visibility="hidden"');
     expect(html).toContain('data-hz-topology-legend-section="status"');
     expect(html).toContain('data-hz-topology-legend-section="interaction"');
-    
expect(html).toContain('data-hz-topology-legend-visual-mode="source-backed-text"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-status-token"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-interaction-token"');
-    
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-edge-token"');
+           
expect(html).toContain('data-hz-topology-legend-visual-mode="source-backed-text"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="lucide-react"');
+           
expect(html).toContain('data-hz-topology-legend-source-label="lucide-react"');
+           
expect(html).toContain('data-hz-topology-legend-icon-owner="hertzbeat-ui-legend-source-icon"');
+           
expect(html).toContain('data-hz-topology-legend-icon-library="lucide-react"');
+           
expect(html).toContain('data-hz-topology-legend-icon-name="blocks"');
+           
expect(html).toContain('data-hz-topology-legend-icon-source="entity-type-catalog"');
+           
expect(html).toContain('data-hz-topology-legend-icon-no-handdrawn="true"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-status-token"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-interaction-token"');
+           
expect(html).toContain('data-hz-topology-legend-visual-source="hertzbeat-edge-token"');
     expect(html).toContain('data-hz-topology-legend-source-label="status 
token"');
     expect(html).toContain('data-hz-topology-legend-source-label="interaction 
token"');
     expect(html).toContain('data-hz-topology-legend-source-label="edge 
token"');
diff --git a/web-next/packages/hertzbeat-ui/src/index.tsx 
b/web-next/packages/hertzbeat-ui/src/index.tsx
index b3f3fd412a..f9cfcc8d04 100644
--- a/web-next/packages/hertzbeat-ui/src/index.tsx
+++ b/web-next/packages/hertzbeat-ui/src/index.tsx
@@ -10493,7 +10493,12 @@ export type HzTopologyLegendItem = {
   color?: string;
   fill?: string;
   swatch?: 'line';
-  visualSource?: 'hertzbeat-status-token' | 'hertzbeat-interaction-token' | 
'hertzbeat-edge-token';
+  visualSource?: 'hertzbeat-status-token' | 'hertzbeat-interaction-token' | 
'hertzbeat-edge-token' | 'lucide-react';
+  iconSrc?: string;
+  iconAlt?: string;
+  iconLibrary?: 'lucide-react';
+  iconName?: string;
+  iconSource?: 'entity-type-catalog';
 };
 
 export type HzTopologyLegendSection = {
@@ -10523,7 +10528,8 @@ const topologyLegendBoundaryClassName: 
Record<HzTopologyLegendBoundary, string>
 const topologyLegendVisualSourceLabel: 
Record<NonNullable<HzTopologyLegendItem['visualSource']>, string> = {
   'hertzbeat-status-token': 'status token',
   'hertzbeat-interaction-token': 'interaction token',
-  'hertzbeat-edge-token': 'edge token'
+  'hertzbeat-edge-token': 'edge token',
+  'lucide-react': 'lucide-react'
 };
 
 export type HzTopologyHoverTooltipKind = 'node' | 'edge';
@@ -11288,8 +11294,8 @@ export function HzTopologyLegend({
                   <div
                     key={item.id}
                     className={cn(
-                      'grid min-w-0 items-center text-[11px]',
-                      isCanvasDock ? 'min-h-4 grid-cols-[auto] gap-1' : 
'min-h-5 grid-cols-[minmax(0,1fr)_auto] gap-2'
+                      'flex min-w-0 items-center text-[11px]',
+                      isCanvasDock ? 'min-h-4 gap-1' : 'min-h-5 gap-2'
                     )}
                     data-hz-topology-legend-item={item.id}
                     
data-hz-topology-legend-item-owner="hertzbeat-ui-legend-item"
@@ -11302,6 +11308,22 @@ export function HzTopologyLegend({
                     data-hz-topology-legend-source-label={sourceLabel}
                     data-hz-topology-legend-no-handdrawn-icon="true"
                   >
+                    {item.iconSrc ? (
+                      <span
+                        aria-label={item.iconAlt}
+                        role={item.iconAlt ? 'img' : undefined}
+                        className={cn(
+                          'h-3.5 w-3.5 shrink-0 bg-contain bg-center 
bg-no-repeat opacity-80',
+                          isCanvasDock ? 'mr-1 inline-block' : 'mr-1.5 
inline-block'
+                        )}
+                        style={{ backgroundImage: `url("${item.iconSrc}")` }}
+                        
data-hz-topology-legend-icon-owner="hertzbeat-ui-legend-source-icon"
+                        data-hz-topology-legend-icon-library={item.iconLibrary}
+                        data-hz-topology-legend-icon-name={item.iconName}
+                        data-hz-topology-legend-icon-source={item.iconSource}
+                        data-hz-topology-legend-icon-no-handdrawn="true"
+                      />
+                    ) : null}
                     <span
                       className="min-w-0 truncate text-[#cbd3df]"
                       
data-hz-topology-legend-item-label-owner="hertzbeat-ui-legend-item-label"
diff --git a/web-next/test/i18n-test-helper.ts 
b/web-next/test/i18n-test-helper.ts
index ed498db025..1f955f4fdd 100644
--- a/web-next/test/i18n-test-helper.ts
+++ b/web-next/test/i18n-test-helper.ts
@@ -585,7 +585,7 @@ const SUPPLEMENTAL_MESSAGES: Partial<Record<LocaleCode, 
Messages>> = {
     'topology.alert-impact.open': 'Open alert impact',
     'topology.alert-impact.copy': 'Keep the selected edge, entity, and signal 
scope.',
     'topology.legend.title': 'Legend',
-    'topology.legend.summary': '2 groups',
+    'topology.legend.summary': '3 groups',
     'topology.legend.health': 'Health',
     'topology.legend.health.healthy': 'Healthy',
     'topology.legend.health.healthy-value': 'good',
@@ -617,6 +617,15 @@ const SUPPLEMENTAL_MESSAGES: Partial<Record<LocaleCode, 
Messages>> = {
     'topology.legend.confidence': 'Confidence',
     'topology.legend.confidence.live': 'Current evidence',
     'topology.legend.confidence.stale': 'Low confidence / stale',
+    'topology.legend.node-type': 'Node type',
+    'topology.legend.node-type.application': 'Application',
+    'topology.legend.node-type.service': 'Service',
+    'topology.legend.node-type.database': 'Database',
+    'topology.legend.node-type.middleware': 'Middleware',
+    'topology.legend.node-type.k8s-workload': 'Workload',
+    'topology.legend.node-type.monitor': 'Monitor',
+    'topology.legend.node-type.resource': 'Resource',
+    'topology.legend.node-type.alert': 'Alert',
     'topology.timeline.title': 'Impact timeline',
     'topology.timeline.copy': 'Recent entity, relationship, and monitor 
evidence that changed this topology view.',
     'topology.timeline.detail.fallback': 'Topology evidence changed',
@@ -3366,7 +3375,7 @@ const SUPPLEMENTAL_MESSAGES: Partial<Record<LocaleCode, 
Messages>> = {
     'topology.alert-impact.open': '打开告警影响面',
     'topology.alert-impact.copy': '保留当前边、实体和三信号范围。',
     'topology.legend.title': '图例',
-    'topology.legend.summary': '2 组',
+    'topology.legend.summary': '3 组',
     'topology.legend.health': '健康状态',
     'topology.legend.health.healthy': '健康',
     'topology.legend.health.healthy-value': '正常',
@@ -3398,6 +3407,15 @@ const SUPPLEMENTAL_MESSAGES: Partial<Record<LocaleCode, 
Messages>> = {
     'topology.legend.confidence': '可信度',
     'topology.legend.confidence.live': '当前窗口证据',
     'topology.legend.confidence.stale': '低可信 / 过期',
+    'topology.legend.node-type': '节点类型',
+    'topology.legend.node-type.application': '应用',
+    'topology.legend.node-type.service': '服务',
+    'topology.legend.node-type.database': '数据库',
+    'topology.legend.node-type.middleware': '中间件',
+    'topology.legend.node-type.k8s-workload': '工作负载',
+    'topology.legend.node-type.monitor': '监控',
+    'topology.legend.node-type.resource': '资源',
+    'topology.legend.node-type.alert': '告警',
     'topology.timeline.title': '影响时间线',
     'topology.timeline.copy': '最近改变当前拓扑视图的实体、关系和监控对象证据。',
     'topology.timeline.detail.fallback': '拓扑证据已变更',


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to