diff --git a/pkg/nodes/registry_server.go b/pkg/nodes/registry_server.go index 52d8aeb..810d52e 100644 --- a/pkg/nodes/registry_server.go +++ b/pkg/nodes/registry_server.go @@ -2496,6 +2496,25 @@ func (s *RegistryServer) handleWebUIOfficeState(w http.ResponseWriter, r *http.R } return time.Time{} } + ipv4Pattern := regexp.MustCompile(`\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b`) + maskIPv4 := func(text string) string { + if strings.TrimSpace(text) == "" { + return text + } + return ipv4Pattern.ReplaceAllStringFunc(text, func(ip string) string { + parts := strings.Split(ip, ".") + if len(parts) != 4 { + return ip + } + for _, p := range parts { + n, err := strconv.Atoi(p) + if err != nil || n < 0 || n > 255 { + return ip + } + } + return parts[0] + "." + parts[1] + ".**.**" + }) + } latestByTask := map[string]map[string]interface{}{} latestTimeByTask := map[string]time.Time{} @@ -2663,7 +2682,7 @@ func (s *RegistryServer) handleWebUIOfficeState(w http.ResponseWriter, r *http.R nodeDetail := func(n NodeInfo) string { parts := make([]string, 0, 4) if ep := strings.TrimSpace(n.Endpoint); ep != "" { - parts = append(parts, ep) + parts = append(parts, maskIPv4(ep)) } switch { case strings.TrimSpace(n.OS) != "" && strings.TrimSpace(n.Arch) != "": @@ -2680,9 +2699,9 @@ func (s *RegistryServer) handleWebUIOfficeState(w http.ResponseWriter, r *http.R parts = append(parts, "seen:"+n.LastSeenAt.UTC().Format(time.RFC3339)) } if len(parts) == 0 { - return "node " + strings.TrimSpace(n.ID) + return maskIPv4("node " + strings.TrimSpace(n.ID)) } - return strings.Join(parts, " · ") + return maskIPv4(strings.Join(parts, " · ")) } allNodes := []NodeInfo{} @@ -2748,6 +2767,7 @@ func (s *RegistryServer) handleWebUIOfficeState(w http.ResponseWriter, r *http.R if name == "" { name = id } + name = maskIPv4(name) updatedAt := "" if !n.LastSeenAt.IsZero() { updatedAt = n.LastSeenAt.UTC().Format(time.RFC3339) @@ -2773,6 +2793,7 @@ func (s *RegistryServer) handleWebUIOfficeState(w http.ResponseWriter, r *http.R mainDetailOut = mainDetailOut + " · " + nodeInfo } } + mainDetailOut = maskIPv4(mainDetailOut) ekgErr5m := 0 cutoff := now.Add(-5 * time.Minute) @@ -2793,7 +2814,7 @@ func (s *RegistryServer) handleWebUIOfficeState(w http.ResponseWriter, r *http.R "time": now.Format(time.RFC3339), "main": map[string]interface{}{ "id": mainNode.ID, - "name": mainNode.Name, + "name": maskIPv4(mainNode.Name), "state": mainState, "detail": mainDetailOut, "zone": mainZone, diff --git a/webui/src/components/office/OfficeScene.tsx b/webui/src/components/office/OfficeScene.tsx index a65dc17..73c920a 100644 --- a/webui/src/components/office/OfficeScene.tsx +++ b/webui/src/components/office/OfficeScene.tsx @@ -2,6 +2,8 @@ import React, { useEffect, useMemo, useState } from 'react'; import { OFFICE_CANVAS, OFFICE_ZONE_POINT, OFFICE_ZONE_SLOTS, OfficeZone } from './officeLayout'; export type OfficeMainState = { + id?: string; + name?: string; state?: string; detail?: string; zone?: string; diff --git a/webui/src/pages/Office.tsx b/webui/src/pages/Office.tsx index c9f569e..76d07df 100644 --- a/webui/src/pages/Office.tsx +++ b/webui/src/pages/Office.tsx @@ -4,6 +4,22 @@ import { useTranslation } from 'react-i18next'; import { useAppContext } from '../context/AppContext'; import OfficeScene, { OfficeMainState, OfficeNodeState } from '../components/office/OfficeScene'; +const IPV4_PATTERN = /\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/g; + +function maskIPv4(text: string | undefined): string { + const raw = String(text || ''); + return raw.replace(IPV4_PATTERN, (ip) => { + const parts = ip.split('.'); + if (parts.length !== 4) return ip; + const valid = parts.every((p) => { + const n = Number(p); + return Number.isInteger(n) && n >= 0 && n <= 255; + }); + if (!valid) return ip; + return `${parts[0]}.${parts[1]}.**.**`; + }); +} + type OfficeStats = { running?: number; waiting?: number; @@ -52,6 +68,26 @@ const Office: React.FC = () => { const main = payload.main || {}; const nodes = Array.isArray(payload.nodes) ? payload.nodes : []; const stats = payload.stats || {}; + const safeMain = useMemo( + () => ({ + ...main, + id: maskIPv4(main.id), + name: maskIPv4(main.name), + detail: maskIPv4(main.detail), + task_id: maskIPv4(main.task_id), + }), + [main] + ); + const safeNodes = useMemo( + () => + nodes.map((n) => ({ + ...n, + id: maskIPv4(n.id), + name: maskIPv4(n.name), + detail: maskIPv4(n.detail), + })), + [nodes] + ); const cards = useMemo( () => [ @@ -71,7 +107,7 @@ const Office: React.FC = () => {

{t('office')}

- {t('officeMainState')}: {main.state || 'idle'} {main.task_id ? `· ${main.task_id}` : ''} + {t('officeMainState')}: {safeMain.state || 'idle'} {safeMain.task_id ? `· ${safeMain.task_id}` : ''}