Ship gated eye polish, fabric levels, and collection UI refresh.

Topology MCP adds pull/compact/bundle/suggest-hubs with a no-template skill path; API gains fabric level and NE collection policy; web list pages get paging and denser collect/network workflows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-12 16:13:05 +08:00
parent b1aee86701
commit b81e5869a6
91 changed files with 10395 additions and 1513 deletions

View file

@ -9,6 +9,13 @@ const en = {
nextPage: "Next",
perPage: "{{n}}/page",
pagerMeta: "{{total}} items · page {{page}}/{{pages}}",
jumpPage: "Go to page",
pageSize: "Page size",
exportCsv: "Export CSV",
exporting: "Exporting…",
exportOk: "Exported {{count}} rows",
exportTruncated: "Exported first {{count}} of {{total}} rows (truncated)",
exportFailed: "Export failed",
opFailed: "Operation failed",
empty: "-",
cancel: "Cancel",
@ -100,7 +107,7 @@ const en = {
devices: "Device list",
topology: "Topology",
lldpLinks: "LLDP",
topoClassify: "Classify / slices",
topoClassify: "Classify",
alarms: "Alarms",
configs: "Configurations",
webcrt: "WebCRT",
@ -130,10 +137,15 @@ const en = {
statusRunning: "Running",
statusPaused: "Paused",
statusStopped: "Stopped",
statusDraft: "Draft",
collecting: "Collecting",
deleted: "Deleted",
confirmDelete: "Delete this device monitoring and its samples?",
empty: "No devices monitored yet. Pick a NE and interfaces to start.",
keywordPh: "Name / IP / note / ID",
allStatus: "All statuses",
exportDevicesName: "port-traffic-devices",
exportEventsName: "port-traffic-events",
wall: "Ops wall",
wallTitle: "Port traffic wall",
toDevices: "Manage devices",
@ -354,6 +366,14 @@ const en = {
edgeStatusAll: "All statuses",
edgeStatusActive: "Active",
edgeStatusMissing: "Missing",
edgeSourceAll: "All sources",
exportEdgesName: "lldp-fabric-edges",
exportJobsName: "lldp-collect-jobs",
exportItemsName: "lldp-job-items",
statusAll: "All statuses",
jobKeywordPh: "Job ID / error / scope",
itemKeywordPh: "NE name / IP / error",
itemResultAll: "All results",
missCount: "Miss cycles",
replacedBy: "Replaced",
kpi: {
@ -392,6 +412,7 @@ const en = {
done: "Done",
failed: "Failed",
cancelled: "Cancelled",
stopped: "Stopped",
},
triggerMode: {
manual: "Manual",
@ -444,7 +465,11 @@ const en = {
cyclesEmpty: "No sync cycles yet",
tasksTitle: "Cycle tasks",
taskKeywordPh: "Name / IP / message",
keywordPh: "ID / trigger / message",
statusAll: "All statuses",
allStatus: "All statuses",
exportCyclesName: "config-sync-cycles",
exportTasksName: "config-sync-tasks",
expand: "Details",
collapse: "Collapse",
kpi: {
@ -472,12 +497,14 @@ const en = {
keywordPh: "Name / IP / ID",
allSource: "All sources",
empty: "No network elements.",
exportName: "network-devices",
col: {
source: "Source",
name: "Name",
vendor: "Vendor",
deviceType: "Device type",
connect: "Connect",
actions: "Actions",
},
},
networkConfigs: {
@ -490,6 +517,7 @@ const en = {
export: "Export",
exportBoth: "Export all",
exportOk: "Config exported",
exportListName: "network-configs",
close: "Close",
tabSet: "Set format",
tabHier: "Hierarchical",
@ -626,6 +654,28 @@ const en = {
passwordMustChange: "New password must differ from the default/old password",
},
collect: {
statusAll: "All statuses",
keywordPh: "Job title / ID",
exportJobsName: "collect-jobs",
exportRunsName: "collect-runs",
policyTitle: "Collect policy",
policyHint:
"Periodic schedule is off by default (one-shot / manual). Enable it to auto-create and run jobs on an interval. Keeps the last 3 finished jobs by default.",
policySaved: "Policy saved",
enabled: "Enable scheduled collect",
interval: "Collect interval",
intervalUnitDays: "days",
intervalUnitHours: "hours",
historyKeep: "Jobs to keep",
scope: "Scope",
scopeAll: "All NEs (managed + UME)",
scopeSelected: "Selected NEs only",
savePolicy: "Save policy",
collectNow: "Collect now",
selectedCount: "{{count}} selected",
colSource: "Source",
policyCommandsRequired: "Add commands and save the policy before enabling schedule or collecting now",
policyTargetsRequired: "No eligible NEs in scope; adjust scope or select NEs",
create: {
title: "New collection job",
hint: "Configure commands and NEs, then create the job. Click Start in the job list to run collection.",
@ -676,6 +726,7 @@ const en = {
confirmDelete: "Delete this collection job and its log files?",
jobs: {
title: "Collection jobs",
listTitle: "Job list",
col: {
title: "Job",
status: "Status",
@ -705,6 +756,7 @@ const en = {
idle: "Idle",
last: "Last job",
active: "Active jobs",
nextDue: "Next due",
},
runs: {
status: "Status",
@ -1152,10 +1204,20 @@ const en = {
keywordPh: "keyword(alarm key/cause/ne_name/host_name/ip…)",
hostNamePh: "host_name (contains)",
allSeverity: "All severities",
clearedAll: "is_cleared: all",
clearedAll: "Cleared: all",
clearedYes: "Cleared",
clearedNo: "Active",
clearTitle: "Clear filters and reset to page 1",
filterByHost: "Filter by this host",
noHostName: "No host_name (sync inventory first)",
col: {
time: "Time",
severity: "Severity",
neId: "NE ID",
hostName: "Host",
neType: "NE type",
cause: "Cause",
},
},
},
webcrt: {
@ -1397,6 +1459,7 @@ const en = {
treeLoadFailed: "Failed to load topology tree",
treeRetry: "Retry",
graphLoading: "Loading topology…",
graphLoadFailed: "Failed to load topology",
graphRefreshing: "Refreshing…",
canvasEmpty: "No NEs on this map yet",
canvasEmptyHint: "Use Add NE, or pick a region that already has devices.",
@ -1476,8 +1539,8 @@ const en = {
"Remove selected link(s) from the canvas? They are deleted from Fabric only when you Save. LLDP may recreate them on the next collect.",
edgeDeleted: "Queued {{count}} link delete(s) — Save to apply",
discoverCancelled: "Discovery cancelled",
truncatedMembership: "Membership cap reached; some neighbors were not placed. Use classify/slices or a new map.",
truncatedFrozen: "This map membership is frozen; neighbors cannot be projected. Unfreeze in classify/slices or create a new map.",
truncatedMembership: "Membership cap reached; some neighbors were not placed. Use classify or a new map.",
truncatedFrozen: "This map membership is frozen; neighbors cannot be projected. Unfreeze via classify workflow or create a new map.",
truncatedNodes: "Too many view nodes; display truncated. Narrow membership or create another map.",
truncatedEdges: "Too many edges; display truncated. Filter status or create another map.",
truncatedGeneric: "Graph data truncated. Narrow scope or create another map.",
@ -1506,6 +1569,11 @@ const en = {
unsavedConfirm: "You have unsaved changes. Discard and switch maps?",
saving: "Saving…",
saved: "Topology saved",
saveFailed: "Save failed: {{detail}}",
readOnlyBanner: "Read-only (ne:write scope required to edit)",
readOnlyHint: "Read-only — no write permission",
discoverTimeout: "Discovery timed out; check LLDP tasks for progress",
policySyncFailed: "Failed to sync LLDP policy; local toggle still applies to canvas discovery",
discover: "Discover links",
discovering: "Discovering…",
discoverOne: "Discover links",
@ -1697,16 +1765,16 @@ const en = {
connectHint: "Connect mode: drag from the NE center anchor to the target (Esc or Select to exit)",
},
topoClassify: {
title: "Classify & slices",
title: "Classify",
openTopo: "Topology",
inventory: "NE inventory",
keywordPh: "Filter by name/IP",
filterRoleAll: "All roles",
filterLevelAll: "All levels",
filterRegionAll: "All regions",
filterUnmatchedOff: "No unmatched filter",
kindAny: "Role or region empty",
kindRole: "Role empty",
kindRegion: "Region empty",
kindAny: "Missing level or region",
kindLevel: "Missing level",
kindRegion: "Missing region",
filterLinkAll: "All link states",
filterLinkLinked: "Linked inventory",
filterLinkOrphaned: "Detached",
@ -1715,10 +1783,14 @@ const en = {
linkUme: "UME",
linkBoth: "Managed+UME",
linkOrphaned: "Detached",
roleCore: "Core",
roleAggregation: "Aggregation",
roleAccess: "Access",
roleUnknown: "Unknown",
levelExternal: "L0 external",
levelCore: "L1 core",
levelCoreSub: "L1.1 core′",
levelAgg: "L2 aggregation",
levelAggSub: "L2.1 agg′",
levelAccess: "L3 access",
levelAccessSub: "L3.1 deeper access",
levelInvalid: "Invalid level (0–99.9, one decimal)",
sourceManual: "Manual",
sourceUmeSync: "UME sync",
sourceLldp: "LLDP placeholder",
@ -1731,9 +1803,9 @@ const en = {
findMatches: "Find matches",
matchOk: "Matched {{count}}; selected",
matchHint: "{{count}} matched",
assignRole: "Role only",
assignLevel: "Level only",
assignRegion: "Region only",
assignBoth: "Role + region",
assignBoth: "Level + region",
clearRegion: "Clear region",
confirmAssign: "Confirm assign",
bulkConfirm: "Write tags to {{count}} selected/matched NE(s)?",
@ -1741,7 +1813,7 @@ const en = {
selectedCount: "{{count}} selected",
selectPage: "Select page",
colNe: "NE",
colRole: "Role",
colLevel: "Level",
colRegion: "Region",
colActions: "Actions",
edit: "Edit",
@ -1758,20 +1830,6 @@ const en = {
rowSaved: "Saved",
loading: "Loading…",
empty: "No NEs.",
showSlices: "Show slice maps",
hideSlices: "Hide slice maps",
slices: "Slice maps",
pickRegion: "Pick region",
tplCore: "Core only",
tplCoreAgg: "Core + neighbor Agg",
tplAggAcc: "Agg + neighbor Access",
seedPhysical: "Also place Cores on physical overview",
slicePreview: "Preview slices",
sliceApply: "Generate maps",
sliceConfirm: "Create custom maps and freeze placements?",
sliceOk: "Created {{count}} slice map(s)",
sliceStats: "{{maps}} map(s) planned, {{overlap}} NE(s) on multiple maps",
nodeCount: "{{count}} NE(s)",
},
audit: {
title: "Audit & ops",

View file

@ -9,6 +9,13 @@ const zh = {
nextPage: "下一页",
perPage: "{{n}}/页",
pagerMeta: "共 {{total}} 条 · 第 {{page}}/{{pages}} 页",
jumpPage: "跳转到页",
pageSize: "每页条数",
exportCsv: "导出 CSV",
exporting: "导出中…",
exportOk: "已导出 {{count}} 条",
exportTruncated: "已导出前 {{count}} 条(共 {{total}},已截断)",
exportFailed: "导出失败",
opFailed: "操作失败",
empty: "-",
cancel: "取消",
@ -100,7 +107,7 @@ const zh = {
devices: "设备列表",
topology: "拓扑信息",
lldpLinks: "LLDP",
topoClassify: "分类切片",
topoClassify: "分类",
alarms: "告警信息",
configs: "配置信息",
webcrt: "WebCRT",
@ -130,10 +137,15 @@ const zh = {
statusRunning: "运行中",
statusPaused: "已暂停",
statusStopped: "已停止",
statusDraft: "草稿",
collecting: "采集中",
deleted: "已删除",
confirmDelete: "删除该设备监控及其采样数据?",
empty: "暂无设备监控。请先选择网元并勾选接口。",
keywordPh: "名称 / IP / 备注 / ID",
allStatus: "全部状态",
exportDevicesName: "port-traffic-devices",
exportEventsName: "port-traffic-events",
wall: "监控大屏",
wallTitle: "端口流量大屏",
toDevices: "设备管理",
@ -350,6 +362,14 @@ const zh = {
edgeStatusAll: "全部状态",
edgeStatusActive: "活跃",
edgeStatusMissing: "未发现",
edgeSourceAll: "全部来源",
exportEdgesName: "lldp-fabric-edges",
exportJobsName: "lldp-collect-jobs",
exportItemsName: "lldp-job-items",
statusAll: "全部状态",
jobKeywordPh: "任务 ID / 错误 / 范围",
itemKeywordPh: "网元名称 / IP / 错误",
itemResultAll: "全部结果",
missCount: "未发现周期",
replacedBy: "已被替换",
kpi: {
@ -388,6 +408,7 @@ const zh = {
done: "已完成",
failed: "失败",
cancelled: "已取消",
stopped: "已停止",
},
triggerMode: {
manual: "手动",
@ -440,7 +461,11 @@ const zh = {
cyclesEmpty: "暂无同步周期",
tasksTitle: "周期任务",
taskKeywordPh: "名称 / IP / 消息",
keywordPh: "ID / 触发 / 消息",
statusAll: "全部状态",
allStatus: "全部状态",
exportCyclesName: "config-sync-cycles",
exportTasksName: "config-sync-tasks",
expand: "详情",
collapse: "收起",
kpi: {
@ -468,12 +493,14 @@ const zh = {
keywordPh: "名称 / IP / ID",
allSource: "全部来源",
empty: "暂无网元。",
exportName: "network-devices",
col: {
source: "来源",
name: "名称",
vendor: "厂商",
deviceType: "设备类型",
connect: "连通状态",
actions: "操作",
},
},
networkConfigs: {
@ -486,6 +513,7 @@ const zh = {
export: "导出",
exportBoth: "导出全部",
exportOk: "配置已导出",
exportListName: "network-configs",
close: "关闭",
tabSet: "Set 格式",
tabHier: "层级格式",
@ -621,6 +649,27 @@ const zh = {
passwordMustChange: "新密码不能与默认/旧密码相同",
},
collect: {
statusAll: "全部状态",
keywordPh: "任务名称 / ID",
exportJobsName: "collect-jobs",
exportRunsName: "collect-runs",
policyTitle: "采集策略",
policyHint: "默认不启用周期(只采一次);勾选后按周期自动创建并执行。默认保留最近 3 次任务数据。",
policySaved: "策略已保存",
enabled: "启用周期调度",
interval: "采集周期",
intervalUnitDays: "天",
intervalUnitHours: "小时",
historyKeep: "任务保留数",
scope: "范围",
scopeAll: "全部网元(纳管+UME)",
scopeSelected: "仅选中网元",
savePolicy: "保存策略",
collectNow: "立即采集",
selectedCount: "已选 {{count}} 台",
colSource: "来源",
policyCommandsRequired: "启用周期或立即采集前,请先填写采集命令并保存策略",
policyTargetsRequired: "选定范围内没有可用网元,请调整范围或勾选网元",
create: {
title: "新建采集任务",
hint: "配置命令与网元后创建任务;创建后需在任务列表中点击「开始」执行采集。",
@ -671,6 +720,7 @@ const zh = {
confirmDelete: "确定删除该采集任务?相关日志文件将一并删除。",
jobs: {
title: "采集任务",
listTitle: "任务列表",
col: {
title: "任务",
status: "状态",
@ -700,6 +750,7 @@ const zh = {
idle: "空闲",
last: "最近任务",
active: "活跃任务",
nextDue: "下次周期",
},
runs: {
status: "状态",
@ -1145,10 +1196,20 @@ const zh = {
keywordPh: "keyword(告警键/原因/ne_name/host_name/ip 等)",
hostNamePh: "host_name(含匹配)",
allSeverity: "全部级别",
clearedAll: "is_cleared: all",
clearedAll: "清除状态:全部",
clearedYes: "已清除",
clearedNo: "未清除",
clearTitle: "清空 keyword、host_name、级别、is_cleared,回到第 1 页",
filterByHost: "按该主机名筛选",
noHostName: "无 host_name(需先同步网元)",
col: {
time: "告警时间",
severity: "级别",
neId: "网元 ID",
hostName: "主机名",
neType: "网元类型",
cause: "原因",
},
},
},
webcrt: {
@ -1388,6 +1449,7 @@ const zh = {
treeLoadFailed: "拓扑树加载失败",
treeRetry: "重试",
graphLoading: "正在加载拓扑图…",
graphLoadFailed: "拓扑图加载失败",
graphRefreshing: "刷新中…",
canvasEmpty: "这张图上还没有网元",
canvasEmptyHint: "可用「添加网元」上图,或从左侧换到有设备的区域。",
@ -1464,8 +1526,8 @@ const zh = {
deleteEdgeConfirm: "从画布移除选中链路?仅在点击「保存」后才会从 Fabric 删除;LLDP 下次采集若仍存在可能再次出现。",
edgeDeleted: "已标记删除 {{count}} 条链路(保存后生效)",
discoverCancelled: "发现已取消",
truncatedMembership: "视图已达成员上限,部分邻居未上图。可去分类/切片或新建视图。",
truncatedFrozen: "当前视图已冻结成员,无法投影邻居。请在分类/切片中解冻或新建视图。",
truncatedMembership: "视图已达成员上限,部分邻居未上图。可去分类页或新建视图。",
truncatedFrozen: "当前视图已冻结成员,无法投影邻居。请在分类流程中处理或新建视图。",
truncatedNodes: "画布节点过多已截断显示。建议新建视图或收紧成员范围。",
truncatedEdges: "画布链路过多已截断显示。建议筛选状态或新建视图。",
truncatedGeneric: "图数据已截断显示。建议收紧范围或新建视图。",
@ -1494,6 +1556,11 @@ const zh = {
unsavedConfirm: "有未保存的更改,确定丢弃并切换吗?",
saving: "保存中…",
saved: "拓扑已保存",
saveFailed: "保存失败:{{detail}}",
readOnlyBanner: "只读模式(需要 ne:write 权限才能编辑)",
readOnlyHint: "只读模式,无编辑权限",
discoverTimeout: "发现任务超时,请稍后在 LLDP 任务页查看进度",
policySyncFailed: "同步 LLDP 策略失败,本地开关仍对本页发现生效",
discover: "发现链路",
discovering: "发现中…",
discoverOne: "发现链路",
@ -1680,15 +1747,15 @@ const zh = {
connectHint: "连线模式:从网元中心锚点拖到目标网元(Esc 或切回选择退出)",
},
topoClassify: {
title: "分类与切片",
title: "分类",
openTopo: "拓扑画布",
inventory: "网元清单",
keywordPh: "关键字筛选名称/IP",
filterRoleAll: "全部角色",
filterLevelAll: "全部层级",
filterRegionAll: "全部区域",
filterUnmatchedOff: "不过滤未分类",
kindAny: "角色或区域未填",
kindRole: "角色未填",
kindAny: "层级或区域未填",
kindLevel: "层级未填",
kindRegion: "区域未填",
filterLinkAll: "全部关联",
filterLinkLinked: "已关联库存",
@ -1698,10 +1765,14 @@ const zh = {
linkUme: "UME",
linkBoth: "托管+UME",
linkOrphaned: "已解绑",
roleCore: "核心",
roleAggregation: "汇聚",
roleAccess: "接入",
roleUnknown: "未知",
levelExternal: "L0 外部",
levelCore: "L1 核心",
levelCoreSub: "L1.1 次核心",
levelAgg: "L2 汇聚",
levelAggSub: "L2.1 二级汇聚",
levelAccess: "L3 接入",
levelAccessSub: "L3.1 更深接入",
levelInvalid: "层级无效(0–99.9,一位小数)",
sourceManual: "手动",
sourceUmeSync: "UME 同步",
sourceLldp: "LLDP 占位",
@ -1714,9 +1785,9 @@ const zh = {
findMatches: "查找匹配",
matchOk: "匹配 {{count}} 条,已勾选",
matchHint: "当前匹配 {{count}} 条",
assignRole: "只改角色",
assignLevel: "只改层级",
assignRegion: "只改区域",
assignBoth: "角色+区域",
assignBoth: "层级+区域",
clearRegion: "清空区域",
confirmAssign: "确认写入",
bulkConfirm: "将给已选/匹配的 {{count}} 条网元写入分类,确定?",
@ -1724,7 +1795,7 @@ const zh = {
selectedCount: "已选 {{count}}",
selectPage: "全选本页",
colNe: "网元",
colRole: "角色",
colLevel: "层级",
colRegion: "区域",
colActions: "操作",
edit: "编辑",
@ -1739,20 +1810,6 @@ const zh = {
rowSaved: "已保存",
loading: "加载中…",
empty: "没有网元。",
showSlices: "展开切片建图",
hideSlices: "收起切片建图",
slices: "切片建图",
pickRegion: "选择区域",
tplCore: "仅 Core",
tplCoreAgg: "Core + 邻接 Agg",
tplAggAcc: "Agg + 邻接 Access",
seedPhysical: "同时给物理总览上 Core",
slicePreview: "预览切片",
sliceApply: "生成切片图",
sliceConfirm: "将创建自定义拓扑图并上图冻结,确定?",
sliceOk: "已创建 {{count}} 张切片图",
sliceStats: "计划 {{maps}} 张图,重复上图网元 {{overlap}} 个",
nodeCount: "{{count}} 个网元",
},
audit: {
title: "操作审计",