mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
Localize LLDP and topology classify English leftovers.
Translate job status/trigger/scope, item result, edge labels, and classify role/source option text in zh/en. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
617d4245b0
commit
7cd89aaae9
4 changed files with 167 additions and 24 deletions
|
|
@ -389,6 +389,34 @@ const en = {
|
||||||
bPort: "Port B",
|
bPort: "Port B",
|
||||||
lastSeen: "Last seen",
|
lastSeen: "Last seen",
|
||||||
},
|
},
|
||||||
|
jobStatus: {
|
||||||
|
running: "Running",
|
||||||
|
pending: "Pending",
|
||||||
|
paused: "Paused",
|
||||||
|
done: "Done",
|
||||||
|
failed: "Failed",
|
||||||
|
cancelled: "Cancelled",
|
||||||
|
},
|
||||||
|
triggerMode: {
|
||||||
|
manual: "Manual",
|
||||||
|
schedule: "Scheduled",
|
||||||
|
topology: "Topology canvas",
|
||||||
|
},
|
||||||
|
jobScope: {
|
||||||
|
all: "All NEs",
|
||||||
|
ne_ids: "Selected NEs",
|
||||||
|
selected: "Selected NEs",
|
||||||
|
},
|
||||||
|
itemResult: {
|
||||||
|
ok: "OK",
|
||||||
|
warn: "Warn",
|
||||||
|
fail: "Fail",
|
||||||
|
},
|
||||||
|
edgeSource: {
|
||||||
|
lldp: "LLDP",
|
||||||
|
manual: "Manual",
|
||||||
|
stale: "Stale",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
configSync: {
|
configSync: {
|
||||||
title: "Config sync",
|
title: "Config sync",
|
||||||
|
|
@ -1601,6 +1629,15 @@ const en = {
|
||||||
linkUme: "UME",
|
linkUme: "UME",
|
||||||
linkBoth: "Managed+UME",
|
linkBoth: "Managed+UME",
|
||||||
linkOrphaned: "Detached",
|
linkOrphaned: "Detached",
|
||||||
|
roleCore: "Core",
|
||||||
|
roleAggregation: "Aggregation",
|
||||||
|
roleAccess: "Access",
|
||||||
|
roleUnknown: "Unknown",
|
||||||
|
sourceManual: "Manual",
|
||||||
|
sourceUmeSync: "UME sync",
|
||||||
|
sourceLldp: "LLDP placeholder",
|
||||||
|
sourceTopology: "Topology placeholder",
|
||||||
|
sourceWebcrt: "WebCRT",
|
||||||
regexTitle: "Regex find",
|
regexTitle: "Regex find",
|
||||||
patternPh: "Regex, e.g. ^CORE- or -BJ-",
|
patternPh: "Regex, e.g. ^CORE- or -BJ-",
|
||||||
fieldName: "Name",
|
fieldName: "Name",
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,34 @@ const zh = {
|
||||||
bPort: "对端口",
|
bPort: "对端口",
|
||||||
lastSeen: "最近看见",
|
lastSeen: "最近看见",
|
||||||
},
|
},
|
||||||
|
jobStatus: {
|
||||||
|
running: "运行中",
|
||||||
|
pending: "排队中",
|
||||||
|
paused: "已暂停",
|
||||||
|
done: "已完成",
|
||||||
|
failed: "失败",
|
||||||
|
cancelled: "已取消",
|
||||||
|
},
|
||||||
|
triggerMode: {
|
||||||
|
manual: "手动",
|
||||||
|
schedule: "周期",
|
||||||
|
topology: "拓扑画布",
|
||||||
|
},
|
||||||
|
jobScope: {
|
||||||
|
all: "全部网元",
|
||||||
|
ne_ids: "指定网元",
|
||||||
|
selected: "指定网元",
|
||||||
|
},
|
||||||
|
itemResult: {
|
||||||
|
ok: "成功",
|
||||||
|
warn: "警告",
|
||||||
|
fail: "失败",
|
||||||
|
},
|
||||||
|
edgeSource: {
|
||||||
|
lldp: "LLDP",
|
||||||
|
manual: "手动",
|
||||||
|
stale: "陈旧",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
configSync: {
|
configSync: {
|
||||||
title: "配置同步",
|
title: "配置同步",
|
||||||
|
|
@ -1586,10 +1614,19 @@ const zh = {
|
||||||
filterLinkLinked: "已关联库存",
|
filterLinkLinked: "已关联库存",
|
||||||
filterLinkOrphaned: "已解绑",
|
filterLinkOrphaned: "已解绑",
|
||||||
colLink: "关联",
|
colLink: "关联",
|
||||||
linkManaged: "Managed",
|
linkManaged: "托管",
|
||||||
linkUme: "UME",
|
linkUme: "UME",
|
||||||
linkBoth: "Managed+UME",
|
linkBoth: "托管+UME",
|
||||||
linkOrphaned: "已解绑",
|
linkOrphaned: "已解绑",
|
||||||
|
roleCore: "核心",
|
||||||
|
roleAggregation: "汇聚",
|
||||||
|
roleAccess: "接入",
|
||||||
|
roleUnknown: "未知",
|
||||||
|
sourceManual: "手动",
|
||||||
|
sourceUmeSync: "UME 同步",
|
||||||
|
sourceLldp: "LLDP 占位",
|
||||||
|
sourceTopology: "拓扑占位",
|
||||||
|
sourceWebcrt: "WebCRT",
|
||||||
regexTitle: "正则查找",
|
regexTitle: "正则查找",
|
||||||
patternPh: "正则,如 ^CORE- 或 -BJ-",
|
patternPh: "正则,如 ^CORE- 或 -BJ-",
|
||||||
fieldName: "名称",
|
fieldName: "名称",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,51 @@ const POLL_MS = 2500;
|
||||||
const TARGET_PAGE_SIZE = 20;
|
const TARGET_PAGE_SIZE = 20;
|
||||||
const SEP = " · ";
|
const SEP = " · ";
|
||||||
|
|
||||||
|
function lldpJobStatusLabel(t: (k: string) => string, status: string): string {
|
||||||
|
const s = String(status || "").trim().toLowerCase();
|
||||||
|
const key = `lldpLinks.jobStatus.${s}`;
|
||||||
|
const label = t(key);
|
||||||
|
return label === key ? status || "—" : label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lldpTriggerLabel(t: (k: string) => string, mode: string): string {
|
||||||
|
const s = String(mode || "").trim().toLowerCase();
|
||||||
|
const key = `lldpLinks.triggerMode.${s}`;
|
||||||
|
const label = t(key);
|
||||||
|
return label === key ? mode || "—" : label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lldpScopeLabel(t: (k: string) => string, scope: string): string {
|
||||||
|
const s = String(scope || "").trim().toLowerCase();
|
||||||
|
const key = `lldpLinks.jobScope.${s}`;
|
||||||
|
const label = t(key);
|
||||||
|
return label === key ? scope || "—" : label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lldpItemResultLabel(
|
||||||
|
t: (k: string) => string,
|
||||||
|
it: { ok?: boolean; parser_stub?: boolean | string | null; unmatched_count?: number; unmatched?: unknown[] },
|
||||||
|
): string {
|
||||||
|
if (!it.ok) return t("lldpLinks.itemResult.fail");
|
||||||
|
const unmatchedCount = it.unmatched_count ?? (it.unmatched?.length || 0);
|
||||||
|
if (it.parser_stub || unmatchedCount > 0) return t("lldpLinks.itemResult.warn");
|
||||||
|
return t("lldpLinks.itemResult.ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
function lldpEdgeStatusLabel(t: (k: string) => string, status: string): string {
|
||||||
|
const s = status === "stale" ? "missing" : String(status || "").trim().toLowerCase();
|
||||||
|
if (s === "active") return t("lldpLinks.edgeStatusActive");
|
||||||
|
if (s === "missing") return t("lldpLinks.edgeStatusMissing");
|
||||||
|
return status || "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
function lldpEdgeSourceLabel(t: (k: string) => string, source: string): string {
|
||||||
|
const s = String(source || "").trim().toLowerCase();
|
||||||
|
const key = `lldpLinks.edgeSource.${s}`;
|
||||||
|
const label = t(key);
|
||||||
|
return label === key ? source || "—" : label;
|
||||||
|
}
|
||||||
|
|
||||||
export function LldpLinksPage() {
|
export function LldpLinksPage() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { showOk, showError } = useToast();
|
const { showOk, showError } = useToast();
|
||||||
|
|
@ -545,7 +590,7 @@ export function LldpLinksPage() {
|
||||||
const st = e.status === "stale" ? "missing" : e.status;
|
const st = e.status === "stale" ? "missing" : e.status;
|
||||||
return (
|
return (
|
||||||
<tr key={e.id}>
|
<tr key={e.id}>
|
||||||
<td>{st}</td>
|
<td>{lldpEdgeStatusLabel(t, st)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div>{aLabel}</div>
|
<div>{aLabel}</div>
|
||||||
{e.a_ip && e.a_name ? <div className="muted">{e.a_ip}</div> : null}
|
{e.a_ip && e.a_name ? <div className="muted">{e.a_ip}</div> : null}
|
||||||
|
|
@ -556,7 +601,7 @@ export function LldpLinksPage() {
|
||||||
{e.b_ip && e.b_name ? <div className="muted">{e.b_ip}</div> : null}
|
{e.b_ip && e.b_name ? <div className="muted">{e.b_ip}</div> : null}
|
||||||
</td>
|
</td>
|
||||||
<td>{e.b_port || "—"}</td>
|
<td>{e.b_port || "—"}</td>
|
||||||
<td>{e.source}</td>
|
<td>{lldpEdgeSourceLabel(t, e.source)}</td>
|
||||||
<td>
|
<td>
|
||||||
{missCount || (st === "missing" ? 1 : 0)}
|
{missCount || (st === "missing" ? 1 : 0)}
|
||||||
{replaced ? (
|
{replaced ? (
|
||||||
|
|
@ -635,8 +680,8 @@ export function LldpLinksPage() {
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className="mono">{job.id.slice(0, 8)}</td>
|
<td className="mono">{job.id.slice(0, 8)}</td>
|
||||||
<td>{job.trigger_mode}</td>
|
<td>{lldpTriggerLabel(t, job.trigger_mode)}</td>
|
||||||
<td>{job.scope}</td>
|
<td>{lldpScopeLabel(t, job.scope)}</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
|
|
@ -647,7 +692,7 @@ export function LldpLinksPage() {
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{job.status}
|
{lldpJobStatusLabel(t, job.status)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
@ -744,7 +789,7 @@ export function LldpLinksPage() {
|
||||||
{(() => {
|
{(() => {
|
||||||
const job = jobDetailQuery.data;
|
const job = jobDetailQuery.data;
|
||||||
if (job) {
|
if (job) {
|
||||||
return `${job.id.slice(0, 8)} · ${job.status} · ${job.done}/${job.total}`;
|
return `${job.id.slice(0, 8)} · ${lldpJobStatusLabel(t, job.status)} · ${job.done}/${job.total}`;
|
||||||
}
|
}
|
||||||
if (jobDetailQuery.isLoading) {
|
if (jobDetailQuery.isLoading) {
|
||||||
return `${expandedJobId.slice(0, 8)} · …`;
|
return `${expandedJobId.slice(0, 8)} · …`;
|
||||||
|
|
@ -793,7 +838,7 @@ export function LldpLinksPage() {
|
||||||
<td>{it.ne_name || it.ne_id || "—"}</td>
|
<td>{it.ne_name || it.ne_id || "—"}</td>
|
||||||
<td>{it.ne_ip || "—"}</td>
|
<td>{it.ne_ip || "—"}</td>
|
||||||
<td>
|
<td>
|
||||||
{!it.ok ? "fail" : it.parser_stub || unmatchedCount > 0 ? "warn" : "ok"}
|
{lldpItemResultLabel(t, it)}
|
||||||
</td>
|
</td>
|
||||||
<td>{it.neighbors}</td>
|
<td>{it.neighbors}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,27 @@ function isFabricNodeDeletable(n: FabricNodeSearchHit): boolean {
|
||||||
return src === "lldp" || src === "topology" || src === "webcrt";
|
return src === "lldp" || src === "topology" || src === "webcrt";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ROLE_OPTIONS = ["core", "aggregation", "access", "unknown"] as const;
|
||||||
|
|
||||||
|
function roleLabel(t: (k: string) => string, role: string): string {
|
||||||
|
const s = String(role || "").trim().toLowerCase();
|
||||||
|
if (s === "core") return t("topoClassify.roleCore");
|
||||||
|
if (s === "aggregation") return t("topoClassify.roleAggregation");
|
||||||
|
if (s === "access") return t("topoClassify.roleAccess");
|
||||||
|
if (s === "unknown") return t("topoClassify.roleUnknown");
|
||||||
|
return role || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
function managedSourceLabel(t: (k: string) => string, source: string): string {
|
||||||
|
const s = String(source || "").trim().toLowerCase();
|
||||||
|
if (s === "manual") return t("topoClassify.sourceManual");
|
||||||
|
if (s === "ume_sync") return t("topoClassify.sourceUmeSync");
|
||||||
|
if (s === "lldp") return t("topoClassify.sourceLldp");
|
||||||
|
if (s === "topology") return t("topoClassify.sourceTopology");
|
||||||
|
if (s === "webcrt") return t("topoClassify.sourceWebcrt");
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
export function TopologyClassifyPage() {
|
export function TopologyClassifyPage() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { showOk, showError } = useToast();
|
const { showOk, showError } = useToast();
|
||||||
|
|
@ -285,10 +306,11 @@ export function TopologyClassifyPage() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="">{t("topoClassify.filterRoleAll")}</option>
|
<option value="">{t("topoClassify.filterRoleAll")}</option>
|
||||||
<option value="core">core</option>
|
{ROLE_OPTIONS.map((r) => (
|
||||||
<option value="aggregation">aggregation</option>
|
<option key={r} value={r}>
|
||||||
<option value="access">access</option>
|
{roleLabel(t, r)}
|
||||||
<option value="unknown">unknown</option>
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
<select
|
<select
|
||||||
className="input"
|
className="input"
|
||||||
|
|
@ -380,10 +402,11 @@ export function TopologyClassifyPage() {
|
||||||
value={assignRole}
|
value={assignRole}
|
||||||
onChange={(e) => setAssignRole(e.target.value)}
|
onChange={(e) => setAssignRole(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="core">core</option>
|
{ROLE_OPTIONS.map((r) => (
|
||||||
<option value="aggregation">aggregation</option>
|
<option key={r} value={r}>
|
||||||
<option value="access">access</option>
|
{roleLabel(t, r)}
|
||||||
<option value="unknown">unknown</option>
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
) : null}
|
) : null}
|
||||||
{assignWhat !== "role" ? (
|
{assignWhat !== "role" ? (
|
||||||
|
|
@ -476,7 +499,7 @@ export function TopologyClassifyPage() {
|
||||||
<td>{n.ip}</td>
|
<td>{n.ip}</td>
|
||||||
<td>
|
<td>
|
||||||
{linkLabel(n.link_status)}
|
{linkLabel(n.link_status)}
|
||||||
{n.managed_source ? ` · ${n.managed_source}` : ""}
|
{n.managed_source ? ` · ${managedSourceLabel(t, n.managed_source)}` : ""}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{editing ? (
|
{editing ? (
|
||||||
|
|
@ -486,13 +509,14 @@ export function TopologyClassifyPage() {
|
||||||
onChange={(e) => setEditRole(e.target.value)}
|
onChange={(e) => setEditRole(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="">-</option>
|
<option value="">-</option>
|
||||||
<option value="core">core</option>
|
{ROLE_OPTIONS.map((r) => (
|
||||||
<option value="aggregation">aggregation</option>
|
<option key={r} value={r}>
|
||||||
<option value="access">access</option>
|
{roleLabel(t, r)}
|
||||||
<option value="unknown">unknown</option>
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
) : (
|
) : (
|
||||||
n.role || "-"
|
roleLabel(t, n.role || "")
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
@ -681,7 +705,7 @@ export function TopologyClassifyPage() {
|
||||||
<li key={m.name}>
|
<li key={m.name}>
|
||||||
<strong>{m.name}</strong>
|
<strong>{m.name}</strong>
|
||||||
{" · "}
|
{" · "}
|
||||||
{m.role}
|
{roleLabel(t, m.role)}
|
||||||
{" · "}
|
{" · "}
|
||||||
{t("topoClassify.nodeCount").replace("{{count}}", String(m.node_count))}
|
{t("topoClassify.nodeCount").replace("{{count}}", String(m.node_count))}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue