diff --git a/ui/src/i18n/locales/en-US.ts b/ui/src/i18n/locales/en-US.ts index 33c076fe4..2ef74d89c 100644 --- a/ui/src/i18n/locales/en-US.ts +++ b/ui/src/i18n/locales/en-US.ts @@ -407,34 +407,87 @@ export const enUS = { // Nodes page nodes: { title: "Nodes", - desc: "Connected execution nodes and device pairings.", - noNodes: "No nodes connected.", + desc: "Paired devices and live links.", + noNodes: "No nodes found.", devices: "Devices", + devicesDesc: "Pairing requests + role tokens.", noDevices: "No paired devices.", approve: "Approve", reject: "Reject", revoke: "Revoke", rotate: "Rotate", pending: "Pending", + paired: "Paired", approved: "Approved", + offline: "offline", + tokens: "Tokens", + tokensNone: "Tokens: none", + role: "role", + requested: "requested", + repair: "repair", + active: "active", + revoked: "revoked", + scopes: "scopes", + roles: "roles", bindings: { - title: "Exec Bindings", - desc: "Bind agents to specific execution nodes.", - default: "Default Node", + title: "Exec node binding", + desc: "Pin agents to a specific node when using", + default: "Default binding", + defaultDesc: "Used when agents do not override a node binding.", agent: "Agent", node: "Node", - save: "Save Bindings", + anyNode: "Any node", + useDefault: "Use default", + save: "Save", + loadConfig: "Load config", + loadConfigNote: "Load config to edit bindings.", + noNodesAvailable: "No nodes with system.run available.", + defaultAgent: "default agent", + usesDefault: "uses default", + override: "override", + switchToForm: "Switch the Config tab to Form mode to edit bindings here.", }, approvals: { - title: "Exec Approvals", - desc: "Pre-approve commands for agent execution.", + title: "Exec approvals", + desc: "Allowlist and approval policy for", target: "Target", + targetDesc: "Gateway edits local approvals; node edits the selected node.", + host: "Host", gateway: "Gateway", - selectAgent: "Select agent", - addRule: "Add Rule", - noRules: "No approval rules configured.", + selectNode: "Select node", + noNodesYet: "No nodes advertise exec approvals yet.", + scope: "Scope", + defaults: "Defaults", + security: "Security", + securityDesc: "Default security mode.", + mode: "Mode", + deny: "Deny", + allowlist: "Allowlist", + full: "Full", + ask: "Ask", + askDesc: "Default prompt policy.", + off: "Off", + onMiss: "On miss", + always: "Always", + askFallback: "Ask fallback", + askFallbackDesc: "Applied when the UI prompt is unavailable.", + fallback: "Fallback", + autoAllowSkills: "Auto-allow skill CLIs", + autoAllowSkillsDesc: "Allow skill executables listed by the Gateway.", + usingDefault: "Using default", + addPattern: "Add pattern", + allowlistTitle: "Allowlist", + allowlistDesc: "Case-insensitive glob patterns.", + noAllowlist: "No allowlist entries yet.", + pattern: "Pattern", + newPattern: "New pattern", + lastUsed: "Last used", + never: "never", + remove: "Remove", + loadApprovals: "Load approvals", + loadApprovalsNote: "Load exec approvals to edit allowlists.", }, }, diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index 5516263db..9427a7fcc 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -414,34 +414,87 @@ export const zhTW = { // 節點頁面 nodes: { title: "節點", - desc: "已連線的執行節點與裝置配對。", - noNodes: "沒有已連線的節點。", + desc: "已配對的裝置與即時連結。", + noNodes: "找不到節點。", devices: "裝置", + devicesDesc: "配對請求與角色權杖。", noDevices: "沒有已配對的裝置。", approve: "核准", reject: "拒絕", revoke: "撤銷", rotate: "輪換", pending: "待處理", + paired: "已配對", approved: "已核准", + offline: "離線", + tokens: "權杖", + tokensNone: "權杖:無", + role: "角色", + requested: "請求於", + repair: "修復", + active: "使用中", + revoked: "已撤銷", + scopes: "範圍", + roles: "角色", bindings: { - title: "執行綁定", - desc: "將代理綁定到特定執行節點。", - default: "預設節點", + title: "執行節點綁定", + desc: "使用時將代理固定到特定節點", + default: "預設綁定", + defaultDesc: "當代理未覆寫節點綁定時使用。", agent: "代理", node: "節點", - save: "儲存綁定", + anyNode: "任意節點", + useDefault: "使用預設", + save: "儲存", + loadConfig: "載入組態", + loadConfigNote: "載入組態以編輯綁定。", + noNodesAvailable: "沒有支援 system.run 的節點。", + defaultAgent: "預設代理", + usesDefault: "使用預設", + override: "覆寫", + switchToForm: "請將「組態」分頁切換為「表單」模式以在此編輯綁定。", }, approvals: { title: "執行核准", - desc: "預先核准代理可執行的指令。", + desc: "許可清單與核准政策,適用於", target: "目標", + targetDesc: "閘道器編輯本地核准;節點編輯選定的節點。", + host: "主機", gateway: "閘道器", - selectAgent: "選擇代理", - addRule: "新增規則", - noRules: "尚未設定核准規則。", + selectNode: "選擇節點", + noNodesYet: "尚無節點公告執行核准。", + scope: "範圍", + defaults: "預設值", + security: "安全性", + securityDesc: "預設安全模式。", + mode: "模式", + deny: "拒絕", + allowlist: "許可清單", + full: "完整", + ask: "詢問", + askDesc: "預設提示政策。", + off: "關閉", + onMiss: "未命中時", + always: "總是", + askFallback: "詢問備援", + askFallbackDesc: "當 UI 提示不可用時套用。", + fallback: "備援", + autoAllowSkills: "自動允許技能 CLI", + autoAllowSkillsDesc: "允許閘道器列出的技能可執行檔。", + usingDefault: "使用預設", + addPattern: "新增模式", + allowlistTitle: "許可清單", + allowlistDesc: "不區分大小寫的 glob 模式。", + noAllowlist: "尚無許可清單項目。", + pattern: "模式", + newPattern: "新模式", + lastUsed: "上次使用", + never: "從未", + remove: "移除", + loadApprovals: "載入核准", + loadApprovalsNote: "載入執行核准以編輯許可清單。", }, }, diff --git a/ui/src/ui/views/nodes.ts b/ui/src/ui/views/nodes.ts index 31beca988..947bc543e 100644 --- a/ui/src/ui/views/nodes.ts +++ b/ui/src/ui/views/nodes.ts @@ -1,5 +1,6 @@ import { html, nothing } from "lit"; +import { t } from "../../i18n"; import { clampText, formatAgo, formatList } from "../format"; import type { ExecApprovalsAllowlistEntry, @@ -60,16 +61,16 @@ export function renderNodes(props: NodesProps) {
-
Nodes
-
Paired devices and live links.
+
${t("nodes.title")}
+
${t("nodes.desc")}
${props.nodes.length === 0 - ? html`
No nodes found.
` + ? html`
${t("nodes.noNodes")}
` : props.nodes.map((n) => renderNode(n))}
@@ -84,11 +85,11 @@ function renderDevices(props: NodesProps) {
-
Devices
-
Pairing requests + role tokens.
+
${t("nodes.devices")}
+
${t("nodes.devicesDesc")}
${props.devicesError @@ -97,18 +98,18 @@ function renderDevices(props: NodesProps) {
${pending.length > 0 ? html` -
Pending
+
${t("nodes.pending")}
${pending.map((req) => renderPendingDevice(req, props))} ` : nothing} ${paired.length > 0 ? html` -
Paired
+
${t("nodes.paired")}
${paired.map((device) => renderPairedDevice(device, props))} ` : nothing} ${pending.length === 0 && paired.length === 0 - ? html`
No paired devices.
` + ? html`
${t("nodes.noDevices")}
` : nothing}
@@ -117,9 +118,9 @@ function renderDevices(props: NodesProps) { function renderPendingDevice(req: PendingDevice, props: NodesProps) { const name = req.displayName?.trim() || req.deviceId; - const age = typeof req.ts === "number" ? formatAgo(req.ts) : "n/a"; - const role = req.role?.trim() ? `role: ${req.role}` : "role: -"; - const repair = req.isRepair ? " · repair" : ""; + const age = typeof req.ts === "number" ? formatAgo(req.ts) : t("common.na"); + const role = req.role?.trim() ? `${t("nodes.role")}: ${req.role}` : `${t("nodes.role")}: -`; + const repair = req.isRepair ? ` · ${t("nodes.repair")}` : ""; const ip = req.remoteIp ? ` · ${req.remoteIp}` : ""; return html`
@@ -127,16 +128,16 @@ function renderPendingDevice(req: PendingDevice, props: NodesProps) {
${name}
${req.deviceId}${ip}
- ${role} · requested ${age}${repair} + ${role} · ${t("nodes.requested")} ${age}${repair}
@@ -147,8 +148,8 @@ function renderPendingDevice(req: PendingDevice, props: NodesProps) { function renderPairedDevice(device: PairedDevice, props: NodesProps) { const name = device.displayName?.trim() || device.deviceId; const ip = device.remoteIp ? ` · ${device.remoteIp}` : ""; - const roles = `roles: ${formatList(device.roles)}`; - const scopes = `scopes: ${formatList(device.scopes)}`; + const roles = `${t("nodes.roles")}: ${formatList(device.roles)}`; + const scopes = `${t("nodes.scopes")}: ${formatList(device.scopes)}`; const tokens = Array.isArray(device.tokens) ? device.tokens : []; return html`
@@ -157,9 +158,9 @@ function renderPairedDevice(device: PairedDevice, props: NodesProps) {
${device.deviceId}${ip}
${roles} · ${scopes}
${tokens.length === 0 - ? html`
Tokens: none
` + ? html`
${t("nodes.tokensNone")}
` : html` -
Tokens
+
${t("nodes.tokens")}
${tokens.map((token) => renderTokenRow(device.deviceId, token, props))}
@@ -170,8 +171,8 @@ function renderPairedDevice(device: PairedDevice, props: NodesProps) { } function renderTokenRow(deviceId: string, token: DeviceTokenSummary, props: NodesProps) { - const status = token.revokedAtMs ? "revoked" : "active"; - const scopes = `scopes: ${formatList(token.scopes)}`; + const status = token.revokedAtMs ? t("nodes.revoked") : t("nodes.active"); + const scopes = `${t("nodes.scopes")}: ${formatList(token.scopes)}`; const when = formatAgo(token.rotatedAtMs ?? token.createdAtMs ?? token.lastUsedAtMs ?? null); return html`
@@ -181,7 +182,7 @@ function renderTokenRow(deviceId: string, token: DeviceTokenSummary, props: Node class="btn btn--sm" @click=${() => props.onDeviceRotate(deviceId, token.role, token.scopes)} > - Rotate + ${t("nodes.rotate")} ${token.revokedAtMs ? nothing @@ -190,7 +191,7 @@ function renderTokenRow(deviceId: string, token: DeviceTokenSummary, props: Node class="btn btn--sm danger" @click=${() => props.onDeviceRevoke(deviceId, token.role)} > - Revoke + ${t("nodes.revoke")} `}
@@ -436,9 +437,9 @@ function renderBindings(state: BindingState) {
-
Exec node binding
+
${t("nodes.bindings.title")}
- Pin agents to a specific node when using exec host=node. + ${t("nodes.bindings.desc")} exec host=node.
${state.formMode === "raw" ? html`
- Switch the Config tab to Form mode to edit bindings here. + ${t("nodes.bindings.switchToForm")}
` : nothing} ${!state.ready ? html`
-
Load config to edit bindings.
+
${t("nodes.bindings.loadConfigNote")}
` : html`
-
Default binding
-
Used when agents do not override a node binding.
+
${t("nodes.bindings.default")}
+
${t("nodes.bindings.defaultDesc")}
${state.target === "node" ? html`