mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-11 01:40:46 +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
|
|
@ -7,6 +7,7 @@ import {
|
|||
publishAlarmPushStatus,
|
||||
type AlarmPushPhase,
|
||||
} from './alarm-push-status.ts'
|
||||
import { alarmActionLabel, tHost } from './i18n.ts'
|
||||
|
||||
export function alarmSubscribeUrl(apiUrl: string): string {
|
||||
const trimmed = apiUrl.trim().replace(/\/$/, '')
|
||||
|
|
@ -42,16 +43,6 @@ export interface KeyAlarmPayload {
|
|||
/** Human-readable prompt for a sticky Netx Ops DSH session. */
|
||||
export function formatAlarmPrompt(payload: KeyAlarmPayload, lang = 'zh'): string {
|
||||
const action = String(payload.action ?? '').trim().toLowerCase()
|
||||
const actionZh: Record<string, string> = {
|
||||
inserted: '告警产生',
|
||||
updated: '告警更新',
|
||||
deleted: '告警清除',
|
||||
}
|
||||
const actionEn: Record<string, string> = {
|
||||
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()
|
||||
|
|
@ -59,32 +50,22 @@ export function formatAlarmPrompt(payload: KeyAlarmPayload, lang = 'zh'): string
|
|||
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('\n')
|
||||
}
|
||||
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('\n')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import { homedir } from 'node:os'
|
|||
import { join } from 'node:path'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { formatAlarmPrompt, type KeyAlarmPayload } from './alarm-push.ts'
|
||||
import { tHost } from './i18n.ts'
|
||||
|
||||
const PRESET_ID = 'netxops'
|
||||
const PERMISSION_PRESET = 'default'
|
||||
const TITLE = 'Netx 关键告警'
|
||||
|
||||
interface StickyHandle {
|
||||
sessionId: string
|
||||
|
|
@ -75,7 +75,7 @@ export async function deliverAlarmToSession(
|
|||
}
|
||||
}
|
||||
|
||||
await createStickySession(ctx, prompt)
|
||||
await createStickySession(ctx, prompt, lang)
|
||||
}
|
||||
|
||||
const next = deliveryChain.then(run, run)
|
||||
|
|
@ -121,7 +121,7 @@ async function followup(ctx: Context, agent: { followup: (msg: unknown) => unkno
|
|||
}
|
||||
}
|
||||
|
||||
async function createStickySession(ctx: Context, prompt: string): Promise<void> {
|
||||
async function createStickySession(ctx: Context, prompt: string, lang = 'zh'): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const c = ctx as any
|
||||
const agents = c.agents
|
||||
|
|
@ -179,7 +179,7 @@ async function createStickySession(ctx: Context, prompt: string): Promise<void>
|
|||
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 = {
|
||||
|
|
|
|||
90
src/netx/i18n.ts
Normal file
90
src/netx/i18n.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Host-plane Netx Ops i18n (alarm prompts / session titles).
|
||||
* UI copy lives in `src/client/locales.ts` via dsh-client-locale.
|
||||
*/
|
||||
|
||||
export type NetxLocale = 'zh' | 'en'
|
||||
|
||||
export type NetxHostMessageKey =
|
||||
| 'alarm.sessionTitle'
|
||||
| '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'
|
||||
|
||||
const ZH: Record<NetxHostMessageKey, string> = {
|
||||
'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': '请分析这条关键告警并给出下一步运维建议。',
|
||||
}
|
||||
|
||||
const EN: Record<NetxHostMessageKey, string> = {
|
||||
'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.',
|
||||
}
|
||||
|
||||
const TABLES: Record<NetxLocale, Record<NetxHostMessageKey, string>> = {
|
||||
zh: ZH,
|
||||
en: EN,
|
||||
}
|
||||
|
||||
/** Map plugin `lang` / Accept-Language-ish values to zh|en (default zh). */
|
||||
export function normalizeNetxLocale(lang: string | undefined | null): NetxLocale {
|
||||
const normalized = String(lang ?? '').trim().toLowerCase()
|
||||
if (normalized === 'english' || /^en(?:[-_].*)?$/u.test(normalized)) return 'en'
|
||||
return 'zh'
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a host-plane message key.
|
||||
* @param key - stable message id.
|
||||
* @param lang - plugin lang or locale tag.
|
||||
* @param vars - `{name}` replacements.
|
||||
*/
|
||||
export function tHost(
|
||||
key: NetxHostMessageKey,
|
||||
lang: string | undefined | null = 'zh',
|
||||
vars?: Record<string, string | number>,
|
||||
): string {
|
||||
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: string) => (
|
||||
Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : match
|
||||
))
|
||||
}
|
||||
|
||||
/** Alarm action verb for prompt headers. */
|
||||
export function alarmActionLabel(action: string, lang: string | undefined | null): string {
|
||||
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)
|
||||
}
|
||||
|
|
@ -132,6 +132,7 @@ export async function getSessionsExportStatus(
|
|||
available: false,
|
||||
sessionCount: 0,
|
||||
supportsRawArtifacts: false,
|
||||
reasonCode: 'no_persistence',
|
||||
reason: 'sessionPersistence unavailable — mount a JSONL session backend (web profile default)',
|
||||
}
|
||||
}
|
||||
|
|
@ -140,6 +141,7 @@ export async function getSessionsExportStatus(
|
|||
available: false,
|
||||
sessionCount: 0,
|
||||
supportsRawArtifacts: false,
|
||||
reasonCode: 'no_raw_artifacts',
|
||||
reason: 'persistence backend does not expose per-session raw artifacts (SQLite export unsupported)',
|
||||
}
|
||||
}
|
||||
|
|
@ -156,6 +158,7 @@ export async function getSessionsExportStatus(
|
|||
available: false,
|
||||
sessionCount: 0,
|
||||
supportsRawArtifacts: true,
|
||||
reasonCode: 'list_failed',
|
||||
reason: error instanceof Error ? error.message : String(error),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue