Add Netx Ops i18n and promote settings to a dedicated section.

Localize alarm prompts and UI reason codes for zh/en, and restyle the settings page like uds-auth as its own settings.section.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-09 22:09:47 +08:00
parent 909b5d61a7
commit a4993b2407
18 changed files with 1329 additions and 840 deletions

File diff suppressed because it is too large Load diff

View file

@ -50,6 +50,63 @@ function resetAlarmPushStatus() {
publishAlarmPushStatus(emptyStatus({ phase: "disabled", enabled: false }));
}
// src/netx/i18n.ts
var ZH = {
"alarm.sessionTitle": "Netx 关键告警",
"alarm.defaultLabel": "关键告警",
"alarm.action.inserted": "告警产生",
"alarm.action.updated": "告警更新",
"alarm.action.deleted": "告警清除",
"alarm.action.fallback": "告警",
"alarm.field.device": "设备",
"alarm.field.object": "对象",
"alarm.field.severity": "级别",
"alarm.field.cause": "原因",
"alarm.field.time": "时间",
"alarm.analyzeHint": "请分析这条关键告警并给出下一步运维建议。"
};
var EN = {
"alarm.sessionTitle": "Netx key alarm",
"alarm.defaultLabel": "Key alarm",
"alarm.action.inserted": "Alarm Raised",
"alarm.action.updated": "Alarm Updated",
"alarm.action.deleted": "Alarm Cleared",
"alarm.action.fallback": "Alarm",
"alarm.field.device": "Device",
"alarm.field.object": "Object",
"alarm.field.severity": "Severity",
"alarm.field.cause": "Cause",
"alarm.field.time": "Time",
"alarm.analyzeHint": "Please analyze this key alarm and suggest next ops steps."
};
var TABLES = {
zh: ZH,
en: EN
};
function normalizeNetxLocale(lang) {
const normalized = String(lang ?? "").trim().toLowerCase();
if (normalized === "english" || /^en(?:[-_].*)?$/u.test(normalized))
return "en";
return "zh";
}
function tHost(key, lang = "zh", vars) {
const locale = normalizeNetxLocale(lang);
const template = TABLES[locale][key] ?? TABLES.zh[key] ?? key;
if (vars === undefined)
return template;
return template.replace(/\{(\w+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : match);
}
function alarmActionLabel(action, lang) {
const normalized = String(action ?? "").trim().toLowerCase();
if (normalized === "inserted")
return tHost("alarm.action.inserted", lang);
if (normalized === "updated")
return tHost("alarm.action.updated", lang);
if (normalized === "deleted")
return tHost("alarm.action.deleted", lang);
return normalized || tHost("alarm.action.fallback", lang);
}
// src/netx/alarm-push.ts
function alarmSubscribeUrl(apiUrl) {
const trimmed = apiUrl.trim().replace(/\/$/, "");
@ -69,16 +126,6 @@ function alarmSubscribeUrl(apiUrl) {
}
function formatAlarmPrompt(payload, lang = "zh") {
const action = String(payload.action ?? "").trim().toLowerCase();
const actionZh = {
inserted: "告警产生",
updated: "告警更新",
deleted: "告警清除"
};
const actionEn = {
inserted: "Alarm Raised",
updated: "Alarm Updated",
deleted: "Alarm Cleared"
};
const ne = payload.ne && typeof payload.ne === "object" ? payload.ne : {};
const host = String(ne.host_name ?? "").trim();
const ip = String(ne.ip_address ?? "").trim();
@ -86,33 +133,18 @@ function formatAlarmPrompt(payload, lang = "zh") {
let device = host || neName || String(payload.ne_id ?? "").trim() || "-";
if (ip)
device = device === "-" ? ip : `${device} (${ip})`;
const label = String(payload.rule_label ?? payload.native_probable_cause ?? "关键告警").trim();
if (lang.startsWith("en")) {
return [
`[NMS ${actionEn[action] ?? (action || "Alarm")}] ${label}`,
`Device: ${device}`,
`Object: ${String(payload.object_name ?? "-").trim()}`,
`Severity: ${String(payload.perceived_severity ?? "-").trim()}`,
`Cause: ${String(payload.native_probable_cause ?? "-").trim()}`,
`Time: ${String(payload.time_created ?? "-").trim()}`,
`notificationId: ${String(payload.notification_id ?? "-").trim()}`,
`alarm_key: ${String(payload.alarm_key ?? "-").trim()}`,
"",
"Please analyze this key alarm and suggest next ops steps."
].join(`
`);
}
const label = String(payload.rule_label ?? payload.native_probable_cause ?? tHost("alarm.defaultLabel", lang)).trim();
return [
`[NMS ${actionZh[action] ?? (action || "告警")}] ${label}`,
`设备: ${device}`,
`对象: ${String(payload.object_name ?? "-").trim()}`,
`级别: ${String(payload.perceived_severity ?? "-").trim()}`,
`原因: ${String(payload.native_probable_cause ?? "-").trim()}`,
`时间: ${String(payload.time_created ?? "-").trim()}`,
`[NMS ${alarmActionLabel(action, lang)}] ${label}`,
`${tHost("alarm.field.device", lang)}: ${device}`,
`${tHost("alarm.field.object", lang)}: ${String(payload.object_name ?? "-").trim()}`,
`${tHost("alarm.field.severity", lang)}: ${String(payload.perceived_severity ?? "-").trim()}`,
`${tHost("alarm.field.cause", lang)}: ${String(payload.native_probable_cause ?? "-").trim()}`,
`${tHost("alarm.field.time", lang)}: ${String(payload.time_created ?? "-").trim()}`,
`notificationId: ${String(payload.notification_id ?? "-").trim()}`,
`alarm_key: ${String(payload.alarm_key ?? "-").trim()}`,
"",
"请分析这条关键告警并给出下一步运维建议。"
tHost("alarm.analyzeHint", lang)
].join(`
`);
}
@ -368,7 +400,6 @@ import { homedir } from "node:os";
import { join } from "node:path";
var PRESET_ID = "netxops";
var PERMISSION_PRESET = "default";
var TITLE = "Netx 关键告警";
var sticky = null;
var deliveryChain = Promise.resolve();
function resolveWorkspacePath() {
@ -402,7 +433,7 @@ async function deliverAlarmToSession(ctx, payload, lang = "zh") {
await disposeSticky(previous);
}
}
await createStickySession(ctx, prompt);
await createStickySession(ctx, prompt, lang);
};
const next = deliveryChain.then(run, run);
deliveryChain = next.then(() => {
@ -440,7 +471,7 @@ async function followup(ctx, agent, prompt) {
throw error;
}
}
async function createStickySession(ctx, prompt) {
async function createStickySession(ctx, prompt, lang = "zh") {
const c = ctx;
const agents = c.agents;
const agentPresets = c.agentPresets;
@ -485,7 +516,7 @@ async function createStickySession(ctx, prompt) {
permissionPresets.set(handle.agent.session, PERMISSION_PRESET);
}
if (sessionTitle && typeof sessionTitle.rename === "function") {
sessionTitle.rename(handle.agent.session, TITLE);
sessionTitle.rename(handle.agent.session, tHost("alarm.sessionTitle", lang));
}
await followup(ctx, handle.agent, prompt);
sticky = {
@ -777,7 +808,7 @@ function watchNetxConnection(listener) {
// src/netx/session-export.ts
import { homedir as homedir2, hostname as osHostname } from "node:os";
// node_modules/fflate/esm/index.mjs
// node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
import { createRequire as createRequire2 } from "module";
var require2 = createRequire2("/");
var _a;
@ -1629,6 +1660,7 @@ async function getSessionsExportStatus(ctx, signal) {
available: false,
sessionCount: 0,
supportsRawArtifacts: false,
reasonCode: "no_persistence",
reason: "sessionPersistence unavailable — mount a JSONL session backend (web profile default)"
};
}
@ -1637,6 +1669,7 @@ async function getSessionsExportStatus(ctx, signal) {
available: false,
sessionCount: 0,
supportsRawArtifacts: false,
reasonCode: "no_raw_artifacts",
reason: "persistence backend does not expose per-session raw artifacts (SQLite export unsupported)"
};
}
@ -1653,6 +1686,7 @@ async function getSessionsExportStatus(ctx, signal) {
available: false,
sessionCount: 0,
supportsRawArtifacts: true,
reasonCode: "list_failed",
reason: error instanceof Error ? error.message : String(error)
};
}
@ -3571,6 +3605,7 @@ function apply(ctx, config = Config({})) {
value: {
available: false,
options: [],
reasonCode: "im_catalog_unavailable",
hint: "dsh-im-ops missing or outdated — install ≥ops.24 for delivery picker"
}
};