mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-04 01:17:15 +00:00
feat(node): node hardening — mTLS, hashed+zstd reconcile transport, per-node net metrics (#5382)
* fix(api-docs): document clientIpsByGuid route
Restores a green `go test ./...` baseline: TestAPIRoutesDocumented
flagged POST /panel/api/clients/clientIpsByGuid (added in 9385b6c6)
as undocumented in endpoints.ts.
* test(node): characterize current node TLS + API auth behavior
Phase 0 regression net for the mTLS work. These pass on unchanged
production code and lock the pre-mTLS contracts so later phases can be
proven additive:
- tlsConfigForNode: skip -> InsecureSkipVerify (no VerifyConnection);
pin -> VerifyConnection installed.
- checkAPIAuth: bearer match -> Next + api_authed; unauthenticated ->
401 (XHR) / 404; valid session -> Next.
- panel HTTPS listener with no ClientAuth accepts a client that presents
no client certificate (the browsers-keep-working invariant).
* feat(crypto): node-auth CA + client-cert minting (TDD)
Stdlib-only ECDSA P-256 helpers for the node mTLS work:
- GenerateNodeCA: self-signed CA (IsCA, CertSign, path len 0)
- IssueClientCert: client-auth leaf (ExtKeyUsageClientAuth) signed by CA
- LoadCAFromPEM: parse a CA cert+key for issuing / trust-pool building
Tests assert the contract (leaf verifies against the issuing CA with
ExtKeyUsageClientAuth), seen failing on the assertion before impl.
* feat(node): lazy node mTLS CA + client cert in settings (TDD)
SettingService gains opt-in mTLS material, all stored as Setting rows
with empty defaults and kept out of entity.AllSetting (so private keys
never reach the settings UI/export):
- EnsureNodeMtlsCA: mint+persist the node-auth CA once, reuse thereafter
- EnsureMasterClientCert: issue the master client cert from the CA, idempotent
- NodeMtlsClientCAPool: ClientCAs trust pool for the listener; nil when
unconfigured so the no-mTLS path is unchanged
Tests assert idempotency and that the client cert verifies against the CA
for client auth; seen failing on the assertion before impl.
* feat(node): mtls client TLS config + master-cert provider (TDD)
tlsConfigForNode gains an 'mtls' branch that presents the master client
certificate and verifies the node server against system roots (no
InsecureSkipVerify, no custom RootCAs). The cert is supplied via an
injected MasterClientCertProvider so runtime need not import service;
it fails closed when unconfigured. skip/pin contracts unchanged.
* feat(node): allow tokenless mtls nodes in remote do() (TDD)
mtls nodes authenticate with a client certificate, so the bearer token
becomes optional for them: do() no longer rejects an empty ApiToken when
TlsVerifyMode is mtls, and the Authorization header is omitted when no
token is set. Every other mode still requires a token (regression kept).
* feat(node): authenticate verified client certs in checkAPIAuth (TDD)
A completed mTLS handshake (non-empty r.TLS.VerifiedChains) now
authenticates an API request, equivalent to a valid bearer token, and
sets api_authed so the CSRF middleware lets cert-authed mutations
through. Bearer/session/reject paths unchanged. The accept-path assert
was mutation-checked (guard flipped -> test red -> reverted).
* feat(node): opt-in mTLS on the panel listener (TDD; mutation-checked)
web.go now applies VerifyClientCertIfGiven + ClientCAs to the HTTPS
listener when a node trust CA is configured, and wires the master client
cert provider for outbound mtls calls. With no CA the listener is
byte-identical to before (browsers unaffected).
applyNodeMtls is covered end-to-end: no-cert client handshakes (browsers
keep working), a CA-signed client cert verifies, a foreign-CA cert is
rejected at the handshake. Mutation-checked:
- RequireAndVerifyClientCert -> no-cert client rejected (red) -> reverted
- drop ClientCAs -> master cert no longer trusted (red) -> reverted
* feat(node): accept mtls verify-mode + CA reveal endpoint (TDD)
- model.Node.TlsVerifyMode validator now accepts 'mtls'
- normalize() preserves mtls and requires the node scheme to be https
(fail closed), instead of clamping mtls back to verify
- NodeService.NodeMtlsCaCert + POST /panel/api/nodes/mtls/ca return this
panel's node-auth CA cert (public) to paste into a node, minting the CA
+ master client cert on first call
- endpoints.ts documents the new route (doc-sync test)
No model column added (enum is a string), so no migration/codegen.
* feat(node): node mTLS UI + trust-CA setter (TDD)
Backend:
- NodeService.SetNodeMtlsTrustCA + POST /panel/api/nodes/mtls/trustCA
store the CA this panel trusts for incoming node-API client certs
(validates PEM, empty clears); applied on next restart
- endpoints.ts + regenerated openapi.json document both mtls routes
Frontend:
- node form: 'mtls' TLS-verify option + setup hint (zod enum updated)
- Nodes page 'Node mTLS' card: copy this panel's CA, and paste/save the
trusted parent CA
- en-US i18n keys (other locales fall back to en-US)
Gates green: go build (native+windows), vet, go test ./...; frontend
typecheck, lint, vitest (541).
* style(node): gofmt web_mtls_test doc comment
* feat(node): hashed+zstd reconcile transport (TDD, negotiated, mixed-version safe)
Adds an integrity + compression envelope to node config pushes:
- internal/util/wirecodec: shared zstd codec (bomb-capped decode) +
SHA-256 hashing + the header/capability constants
- Remote.do(): always attaches X-Config-Sha256 of the uncompressed body;
zstd-compresses only when the node advertised support (learned from its
X-3x-Node-Caps response header) and the body is >=1KiB
- ConfigEnvelopeMiddleware on /panel/api: advertises the cap, decompresses
and verifies the hash (handler not invoked on mismatch) before binding
Mixed-version safe: old nodes never advertise the cap -> plain bodies;
the hash header is verify-if-present so any panel/node mix interoperates
(existing reconcile tests stay green). klauspost/compress promoted to a
direct dep. Hash-mismatch reject was mutation-checked (compare defeated
-> test red -> reverted).
* feat(node): per-node network throughput metrics (TDD)
The node status response already carries gopsutil netIO.up/down (summed
non-virtual interfaces), so no node-side change is needed:
- probe() parses netIO.up/down into HeartbeatPatch.NetUp/NetDown
- Node gains net_up/net_down columns (AutoMigrate); UpdateHeartbeat
persists them and appends netUp/netDown to the per-node metric history
- NodeMetricKeys whitelists netUp/netDown so the history endpoint serves them
- NodeHistoryPanel renders Net Up/Down sparklines (KB/s, no 0-100 clamp)
- regenerated frontend types + openapi.json for the new Node fields
* feat(node): move node mTLS controls into a toolbar button + modal
The Node mTLS panel was an always-visible card cluttering the nodes
page. Replace it with a 'Node mTLS' button beside 'Add node' that opens
a modal with the same copy-CA + trusted-parent-CA controls; the modal
closes on a successful save. No backend/i18n changes.
* i18n(node): translate mTLS + net-metrics keys for all locales
Adds the node mTLS strings (tlsMtls, mtlsFormHint, mtls.* dialog + the
saveMtls toast) and the netUp/netDown chart labels to all 12 non-English
catalogs (ar, es, fa, id, ja, pt, ru, tr, uk, vi, zh-CN, zh-TW), matching
each catalog's existing terminology. Technical tokens (mTLS/TLS/CA/API/
KB/s) kept verbatim.
* fix(node): address Copilot review on node-hardening PR
- setting_mtls: fail closed on a half-present CA/master-cert pair instead of
silently regenerating (which would rotate the CA and break fleet trust).
- config_envelope: reject non-zstd Content-Encoding on the envelope path
rather than hashing/forwarding a still-encoded body to the handler.
- node mTLS: support tokenless mTLS end-to-end — apiToken is now
required_unless tlsVerifyMode=mtls (model) with matching conditional
validation in NodeFormSchema, so the runtime allowance is actually reachable.
- NodesPage: add a catch block to onSaveTrustCa so save failures surface.
This commit is contained in:
@@ -446,6 +446,7 @@
|
||||
"inboundClientAddSuccess": "已新增入站客戶端",
|
||||
"inboundClientDeleteSuccess": "入站客戶端已刪除",
|
||||
"inboundClientUpdateSuccess": "入站客戶端已更新",
|
||||
"savedNodeOfflineWillSync": "已在本機儲存。某個支撐節點離線或已停用——重新連線後將同步此變更。",
|
||||
"delDepletedClientsSuccess": "所有耗盡客戶端已刪除",
|
||||
"resetAllClientTrafficSuccess": "客戶端所有流量已重置",
|
||||
"resetAllTrafficSuccess": "所有流量已重置",
|
||||
@@ -912,6 +913,8 @@
|
||||
"status": "狀態",
|
||||
"cpu": "CPU",
|
||||
"mem": "記憶體",
|
||||
"netUp": "網路上行 (KB/s)",
|
||||
"netDown": "網路下行 (KB/s)",
|
||||
"uptime": "運行時間",
|
||||
"latency": "延遲",
|
||||
"lastHeartbeat": "上次心跳",
|
||||
@@ -953,13 +956,29 @@
|
||||
"probeFailed": "探測失敗",
|
||||
"updateStarted": "已開始更新面板",
|
||||
"updateResult": "已在 {ok} 個節點上觸發更新,{failed} 個失敗",
|
||||
"updateNoneEligible": "請至少選擇一個在線且已啟用的節點"
|
||||
"updateNoneEligible": "請至少選擇一個在線且已啟用的節點",
|
||||
"saveMtls": "儲存節點 mTLS"
|
||||
},
|
||||
"tlsVerifyMode": "TLS 驗證",
|
||||
"tlsVerifyModeHint": "面板如何驗證節點的 HTTPS 憑證。釘選或略過用於自簽憑證(僅 https 節點)。",
|
||||
"tlsVerify": "驗證(預設 CA)",
|
||||
"tlsPin": "釘選憑證(SHA-256)",
|
||||
"tlsSkip": "略過驗證",
|
||||
"tlsMtls": "雙向 TLS(用戶端憑證)",
|
||||
"mtlsFormHint": "此節點使用用戶端憑證對面板進行驗證。請從「節點 mTLS」區域複製本面板的 CA 到該節點,設定其受信任的 CA,然後重新啟動該節點。",
|
||||
"mtls": {
|
||||
"title": "節點 mTLS",
|
||||
"intro": "雙向 TLS 在節點間呼叫的 API 權杖之上增加用戶端憑證驗證。此為選用項目:留空則僅使用權杖驗證。",
|
||||
"copyCa": "複製此面板的 CA",
|
||||
"copyCaHint": "將此 CA 提供給本面板管理的節點,然後將它們的 TLS 驗證設定為雙向 TLS。",
|
||||
"caCopied": "CA 憑證已複製到剪貼簿",
|
||||
"caFailed": "取得 CA 憑證失敗",
|
||||
"trustLabel": "受信任的上層 CA",
|
||||
"trustHint": "當本面板自身作為節點時,將管理它的面板的 CA 貼到此處以要求其用戶端憑證。重新啟動面板後生效。",
|
||||
"trustPlaceholder": "-----BEGIN CERTIFICATE-----",
|
||||
"save": "儲存受信任的 CA",
|
||||
"saved": "受信任的 CA 已儲存 — 重新啟動面板後生效"
|
||||
},
|
||||
"tlsSkipWarning": "略過驗證會失去對中間人攻擊的防護,API 權杖可能被攔截。建議改用釘選憑證。",
|
||||
"pinnedCert": "釘選憑證的 SHA-256",
|
||||
"pinnedCertHint": "節點憑證的 SHA-256(base64 或 hex)。點選「取得」可立即從節點讀取。",
|
||||
@@ -1210,55 +1229,60 @@
|
||||
"getOutboundTrafficError": "取得出站流量錯誤",
|
||||
"resetOutboundTrafficError": "重設出站流量錯誤"
|
||||
},
|
||||
"emailNotifications": "通知",
|
||||
"smtpSettings": "SMTP 設定",
|
||||
"smtpEnable": "啟用電子郵件通知",
|
||||
"smtpEnableDesc": "透過 SMTP 啟用電子郵件通知",
|
||||
"smtpHost": "SMTP 主機",
|
||||
"smtpHostDesc": "SMTP 伺服器主機名稱(例如 smtp.gmail.com)",
|
||||
"smtpPort": "SMTP 連接埠",
|
||||
"smtpPortDesc": "SMTP 伺服器連接埠(預設:587)",
|
||||
"smtpUsername": "SMTP 使用者名稱",
|
||||
"smtpUsernameDesc": "SMTP 驗證使用者名稱",
|
||||
"smtpPassword": "SMTP 密碼",
|
||||
"smtpPasswordDesc": "SMTP 驗證密碼",
|
||||
"smtpTo": "收件人",
|
||||
"smtpToDesc": "以逗號分隔的收件人電子郵件地址",
|
||||
"emailSettings": "電子郵件",
|
||||
"eventCPUHigh": "CPU 偏高(%)",
|
||||
"emailNotifications": "通知",
|
||||
"smtpEventBusNotify": "電子郵件事件通知",
|
||||
"smtpEventBusNotifyDesc": "選擇觸發電子郵件通知的事件",
|
||||
"tgEventBusNotify": "Telegram 事件通知",
|
||||
"tgEventBusNotifyDesc": "選擇觸發 Telegram 通知的事件",
|
||||
"testSmtp": "傳送測試郵件",
|
||||
"testTgBot": "傳送測試訊息",
|
||||
"eventGroupOutbound": "出站",
|
||||
"eventGroupSecurity": "安全性",
|
||||
"eventGroupSystem": "系統",
|
||||
"eventGroupXray": "Xray 核心",
|
||||
"eventLoginAttempt": "登入嘗試",
|
||||
"eventGroupSystem": "系統",
|
||||
"eventGroupSecurity": "安全性",
|
||||
"eventGroupNode": "節點",
|
||||
"eventOutboundDown": "中斷",
|
||||
"eventOutboundUp": "恢復",
|
||||
"eventXrayCrash": "當機",
|
||||
"eventNodeDown": "離線",
|
||||
"eventNodeUp": "上線",
|
||||
"eventCPUHigh": "CPU 偏高(%)",
|
||||
"requestFailed": "請求失敗",
|
||||
"smtpEnable": "啟用電子郵件通知",
|
||||
"smtpEnableDesc": "透過 SMTP 啟用電子郵件通知",
|
||||
"smtpEncryption": "加密",
|
||||
"smtpEncryptionDesc": "SMTP 連線加密方式",
|
||||
"smtpEncryptionNone": "無(純文字)",
|
||||
"smtpEncryptionStartTLS": "STARTTLS",
|
||||
"smtpEncryptionTLS": "TLS(隱含)",
|
||||
"smtpEventBusNotify": "電子郵件事件通知",
|
||||
"smtpEventBusNotifyDesc": "選擇觸發電子郵件通知的事件",
|
||||
"smtpHost": "SMTP 主機",
|
||||
"smtpHostDesc": "SMTP 伺服器主機名稱(例如 smtp.gmail.com)",
|
||||
"smtpHostNotConfigured": "尚未設定 SMTP 主機",
|
||||
"smtpNoRecipients": "尚未設定收件人",
|
||||
"smtpNotInitialized": "SMTP 尚未初始化",
|
||||
"smtpPassword": "SMTP 密碼",
|
||||
"smtpPasswordDesc": "SMTP 驗證密碼",
|
||||
"smtpPort": "SMTP 連接埠",
|
||||
"smtpPortDesc": "SMTP 伺服器連接埠(預設:587)",
|
||||
"smtpSettings": "SMTP 設定",
|
||||
"smtpStageAuth": "驗證",
|
||||
"smtpStageConnect": "連線",
|
||||
"smtpStageAuth": "驗證",
|
||||
"smtpStageSend": "傳送",
|
||||
"smtpTestSuccess": "測試郵件已成功傳送",
|
||||
"smtpTo": "收件人",
|
||||
"smtpToDesc": "以逗號分隔的收件人電子郵件地址",
|
||||
"smtpUsername": "SMTP 使用者名稱",
|
||||
"smtpUsernameDesc": "SMTP 驗證使用者名稱",
|
||||
"smtpHostNotConfigured": "尚未設定 SMTP 主機",
|
||||
"smtpNoRecipients": "尚未設定收件人",
|
||||
"eventLoginAttempt": "登入嘗試",
|
||||
"telegramTokenConfigured": "已設定;留空以保留目前的權杖。",
|
||||
"telegramTokenPlaceholder": "已設定 - 輸入新權杖以取代",
|
||||
"testSmtp": "傳送測試郵件",
|
||||
"testTgBot": "傳送測試訊息",
|
||||
"smtpPasswordConfigured": "已設定;留空以保留目前的密碼。",
|
||||
"smtpPasswordPlaceholder": "已設定 - 輸入新密碼以取代",
|
||||
"smtpNotInitialized": "SMTP 尚未初始化",
|
||||
"tgBotNotEnabled": "Telegram 機器人未啟用",
|
||||
"tgBotNotRunning": "Telegram 機器人未執行",
|
||||
"tgEventBusNotify": "Telegram 事件通知",
|
||||
"tgEventBusNotifyDesc": "選擇觸發 Telegram 通知的事件",
|
||||
"tgTestFailed": "Telegram 測試失敗",
|
||||
"tgTestSuccess": "測試訊息已傳送至 Telegram",
|
||||
"tgBotNotRunning": "Telegram 機器人未執行",
|
||||
"smtpErrorAuth": "驗證失敗 — 請檢查使用者名稱和密碼",
|
||||
"smtpErrorStarttls": "伺服器需要 STARTTLS — 請變更加密類型",
|
||||
"smtpErrorTls": "伺服器需要 TLS — 請變更加密類型",
|
||||
@@ -1266,12 +1290,7 @@
|
||||
"smtpErrorTimeout": "連線逾時 — 無法連線至主機",
|
||||
"smtpErrorRelay": "伺服器拒絕從此地址傳送",
|
||||
"smtpErrorEof": "連線已被伺服器關閉",
|
||||
"smtpErrorUnknown": "SMTP 錯誤:{{ .Error }}",
|
||||
"eventGroupNode": "節點",
|
||||
"eventNodeDown": "離線",
|
||||
"eventNodeUp": "上線",
|
||||
"smtpPasswordConfigured": "已設定;留空以保留目前的密碼。",
|
||||
"smtpPasswordPlaceholder": "已設定 - 輸入新密碼以取代"
|
||||
"smtpErrorUnknown": "SMTP 錯誤:{{ .Error }}"
|
||||
},
|
||||
"xray": {
|
||||
"title": "Xray 配置",
|
||||
@@ -1319,6 +1338,8 @@
|
||||
"Inbounds": "入站",
|
||||
"InboundsDesc": "接受來自特定客戶端的流量",
|
||||
"Outbounds": "出站",
|
||||
"OutboundSubscriptions": "出站訂閱",
|
||||
"OutboundSubscriptionsDesc": "從遠端訂閱 URL(vmess/vless/trojan/ss/...)匯入出站。標籤會保持穩定,以便在負載均衡與路由規則中使用。系統會自動更新。",
|
||||
"Balancers": "負載均衡",
|
||||
"balancerTagRequired": "標籤為必填",
|
||||
"balancerSelectorRequired": "至少選擇一個出站",
|
||||
@@ -1496,8 +1517,6 @@
|
||||
"privateKey": "私密金鑰",
|
||||
"load": "負載"
|
||||
},
|
||||
"OutboundSubscriptions": "出站訂閱",
|
||||
"OutboundSubscriptionsDesc": "從遠端訂閱 URL(vmess/vless/trojan/ss/...)匯入出站。標籤會保持穩定,以便在負載均衡與路由規則中使用。系統會自動更新。",
|
||||
"outboundSub": {
|
||||
"manage": "訂閱",
|
||||
"title": "出站訂閱",
|
||||
@@ -1775,17 +1794,17 @@
|
||||
"SuccessResetTraffic": "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ✅ 成功",
|
||||
"FailedResetTraffic": "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ 錯誤: [ {{ .ErrorMessage }} ]",
|
||||
"FinishProcess": "🔚 所有客戶的流量重置已完成。",
|
||||
"eventCPUHigh": "CPU 偏高",
|
||||
"eventCPUHighDetail": "CPU:{{ .Detail }}",
|
||||
"eventDelayDetail": "延遲:{{ .Delay }} 毫秒",
|
||||
"eventErrorDetail": "錯誤:{{ .Error }}",
|
||||
"eventLoginFallback": "來自 {{ .Source }} 的登入失敗",
|
||||
"eventOutboundDown": "出站 {{ .Tag }} 已中斷",
|
||||
"eventOutboundUp": "出站 {{ .Tag }} 已恢復",
|
||||
"eventErrorDetail": "錯誤:{{ .Error }}",
|
||||
"eventDelayDetail": "延遲:{{ .Delay }} 毫秒",
|
||||
"eventXrayCrash": "Xray 已當機",
|
||||
"eventXrayCrashError": "錯誤:{{ .Error }}",
|
||||
"eventNodeDown": "節點 {{ .Name }} 已離線",
|
||||
"eventNodeUp": "節點 {{ .Name }} 已上線"
|
||||
"eventNodeUp": "節點 {{ .Name }} 已上線",
|
||||
"eventCPUHigh": "CPU 偏高",
|
||||
"eventCPUHighDetail": "CPU:{{ .Detail }}",
|
||||
"eventLoginFallback": "來自 {{ .Source }} 的登入失敗"
|
||||
},
|
||||
"buttons": {
|
||||
"closeKeyboard": "❌ 關閉鍵盤",
|
||||
@@ -1857,55 +1876,35 @@
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"labelDelay": "延遲",
|
||||
"labelDetail": "詳細資訊",
|
||||
"labelError": "錯誤",
|
||||
"labelIP": "IP",
|
||||
"labelOutbound": "出站",
|
||||
"labelReason": "原因",
|
||||
"labelSource": "來源",
|
||||
"labelStatus": "狀態",
|
||||
"labelTime": "時間",
|
||||
"labelUsername": "使用者名稱",
|
||||
"statusBanned": "BANNED",
|
||||
"statusCrashed": "已當機",
|
||||
"statusDown": "中斷",
|
||||
"statusFailed": "失敗",
|
||||
"statusFull": "FULL",
|
||||
"statusHigh": "偏高",
|
||||
"statusOffline": "OFFLINE",
|
||||
"statusOnline": "ONLINE",
|
||||
"statusRunning": "執行中",
|
||||
"statusSuccess": "成功",
|
||||
"statusUp": "恢復",
|
||||
"statusXrayDown": "Xray DOWN",
|
||||
"statusXrayUp": "Xray UP",
|
||||
"subjectCPUHigh": "CPU 偏高",
|
||||
"subjectDiskFull": "Disk full",
|
||||
"subjectIPBanned": "IP banned: {{ .IP }}",
|
||||
"subjectLoginFailed": "登入失敗",
|
||||
"subjectLoginSuccess": "登入成功",
|
||||
"subjectNodeOffline": "Node {{ .Node }} is OFFLINE",
|
||||
"subjectNodeOnline": "Node {{ .Node }} is ONLINE",
|
||||
"subjectNodeXrayDown": "Node {{ .Node }} Xray is DOWN",
|
||||
"subjectNodeXrayUp": "Node {{ .Node }} Xray is UP",
|
||||
"subjectOutboundDown": "出站 {{ .Tag }} 已中斷",
|
||||
"subjectOutboundUp": "出站 {{ .Tag }} 已恢復",
|
||||
"subjectXrayCrash": "Xray 已當機",
|
||||
"subjectXrayUp": "Xray is UP",
|
||||
"titleCPUHigh": "CPU 偏高",
|
||||
"titleDiskFull": "Disk full",
|
||||
"titleIPBanned": "IP banned",
|
||||
"titleLoginFailed": "登入失敗",
|
||||
"titleLoginSuccess": "登入成功",
|
||||
"titleNodeOffline": "Node OFFLINE",
|
||||
"titleNodeOnline": "Node ONLINE",
|
||||
"titleNodeXrayDown": "Node Xray DOWN",
|
||||
"titleNodeXrayUp": "Node Xray UP",
|
||||
"subjectCPUHigh": "CPU 偏高",
|
||||
"subjectLoginSuccess": "登入成功",
|
||||
"subjectLoginFailed": "登入失敗",
|
||||
"titleOutboundDown": "出站中斷",
|
||||
"titleOutboundUp": "出站恢復",
|
||||
"titleXrayCrash": "Xray 已當機",
|
||||
"titleXrayUp": "Xray UP",
|
||||
"labelNode": "節點"
|
||||
"titleCPUHigh": "CPU 偏高",
|
||||
"titleLoginSuccess": "登入成功",
|
||||
"titleLoginFailed": "登入失敗",
|
||||
"labelStatus": "狀態",
|
||||
"labelOutbound": "出站",
|
||||
"labelNode": "節點",
|
||||
"labelError": "錯誤",
|
||||
"labelDelay": "延遲",
|
||||
"labelDetail": "詳細資訊",
|
||||
"labelUsername": "使用者名稱",
|
||||
"labelIP": "IP",
|
||||
"labelReason": "原因",
|
||||
"labelSource": "來源",
|
||||
"labelTime": "時間",
|
||||
"statusCrashed": "已當機",
|
||||
"statusRunning": "執行中",
|
||||
"statusHigh": "偏高",
|
||||
"statusSuccess": "成功",
|
||||
"statusFailed": "失敗",
|
||||
"statusDown": "中斷",
|
||||
"statusUp": "恢復"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user