mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-03 08:57: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": "オンラインで有効なノードを少なくとも1つ選択してください"
|
||||
"updateNoneEligible": "オンラインで有効なノードを少なくとも1つ選択してください",
|
||||
"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": "アウトバウンドを少なくとも1つ選んでください",
|
||||
@@ -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 }}ms",
|
||||
"eventErrorDetail": "エラー: {{ .Error }}",
|
||||
"eventLoginFallback": "{{ .Source }} からのログインに失敗しました",
|
||||
"eventOutboundDown": "アウトバウンド {{ .Tag }} がダウンしています",
|
||||
"eventOutboundUp": "アウトバウンド {{ .Tag }} が復旧しました",
|
||||
"eventErrorDetail": "エラー: {{ .Error }}",
|
||||
"eventDelayDetail": "遅延: {{ .Delay }}ms",
|
||||
"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