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",