diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 1ae50c1..73ae78b 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1398,8 +1398,9 @@ const en = { termConnect: { title: "Complete login", titleCreate: "Create NE and connect", - hint: "This node is not managed or lacks credentials. Enter IP and protocol to connect (same as creating/completing an NE).", + hint: "This node is not managed or lacks credentials. Enter IP, vendor, and protocol to connect (same as creating/completing an NE).", name: "Name", + vendor: "Vendor", ip: "IP address", port: "Port", protocol: "Protocol", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 8916a68..27d8c34 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1392,8 +1392,9 @@ const zh = { termConnect: { title: "补全登录信息", titleCreate: "新建网元并登录", - hint: "该网元尚未纳管或缺少登录信息,请填写 IP 与协议后连接(等同新建/补全网元)。", + hint: "该网元尚未纳管或缺少登录信息,请填写 IP、厂家与协议后连接(等同新建/补全网元)。", name: "名称", + vendor: "厂家", ip: "IP 地址", port: "端口", protocol: "协议", diff --git a/web/src/pages/TopologyPage.tsx b/web/src/pages/TopologyPage.tsx index d82722f..f6faa30 100644 --- a/web/src/pages/TopologyPage.tsx +++ b/web/src/pages/TopologyPage.tsx @@ -42,6 +42,7 @@ import { fetchLldpDiscoverJob, fetchManagedNe, fetchManagedNeById, + fetchManagedNeMeta, fetchTopologyGraph, fetchTopologyTree, fetchUmeNe, @@ -298,6 +299,7 @@ type CtxMenu = type TermConnectDialog = { neId: string; name: string; + vendor: string; ip_address: string; port: number; protocol: "ssh" | "telnet"; @@ -836,6 +838,16 @@ export function TopologyPage() { }); }, [edges, expandPhysicalLinks, hidePorts, edgeFlow, edgeDefaults, selectedEdgeId]); + const neMetaQuery = useQuery({ + queryKey: queryKeys.managedNeMeta, + queryFn: fetchManagedNeMeta, + enabled: Boolean(termDialog), + staleTime: 60_000, + }); + const termVendors = neMetaQuery.data?.vendors?.length + ? neMetaQuery.data.vendors + : ["ZTE", "Huawei", "Cisco", "H3C", "Juniper", "Nokia", "Other"]; + const treeQuery = useQuery({ queryKey: queryKeys.topologyTree, queryFn: fetchTopologyTree, @@ -1893,6 +1905,7 @@ export function TopologyPage() { setTermDialog({ neId: managedId, name: String(ne.name || node.data.label || "").trim(), + vendor: String(ne.vendor || node.data.vendor || "Other").trim() || "Other", ip_address: hintIp, port: Number(ne.port) || (proto === "telnet" ? 23 : 22), protocol: proto, @@ -1918,6 +1931,7 @@ export function TopologyPage() { setTermDialog({ neId: "", name: String(node.data.label || "").trim(), + vendor: String(node.data.vendor || "Other").trim() || "Other", ip_address: String(node.data.ne_ip || "").trim(), port: 22, protocol: "ssh", @@ -1945,12 +1959,14 @@ export function TopologyPage() { } const port = Number(termDialog.port) || (protocol === "telnet" ? 23 : 22); const name = termDialog.name.trim() || ip; + const vendor = String(termDialog.vendor || "Other").trim() || "Other"; setTermBusy(true); try { let neId = termDialog.neId; if (neId) { await updateManagedNe(neId, { name, + vendor, ip_address: ip, port, protocol, @@ -1960,14 +1976,22 @@ export function TopologyPage() { setNodes((ns) => ns.map((n) => String(n.data.managed_ne_id || "") === neId - ? { ...n, data: { ...n.data, ne_ip: ip, label: name || n.data.label } } + ? { + ...n, + data: { + ...n.data, + ne_ip: ip, + vendor, + label: name || n.data.label, + }, + } : n, ), ); } else { const created = await createManagedNe({ name, - vendor: "Other", + vendor, device_type: "generic", ip_address: ip, port, @@ -1979,6 +2003,7 @@ export function TopologyPage() { } setTermDialog(null); showOk(t("topology.termConnect.saved")); + void queryClient.invalidateQueries({ queryKey: queryKeys.managedNeAll }); openOrFocusModule({ moduleId: "webcrt", path: `/webcrt?ne_id=${encodeURIComponent(neId)}`, @@ -3909,6 +3934,31 @@ export function TopologyPage() { onChange={(e) => setTermDialog({ ...termDialog, name: e.target.value })} /> +