mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-10 12:13:17 +08:00
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:
parent
909b5d61a7
commit
a4993b2407
18 changed files with 1329 additions and 840 deletions
899
lib/client.js
899
lib/client.js
File diff suppressed because it is too large
Load diff
111
lib/index.js
111
lib/index.js
|
|
@ -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"
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue