fix(vless): pair encryption/decryption correctly for TCP Vision

- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption
- Store both fields; strip encryption (client-only) from server Compile
- Form + capability + schema support encryption for subscription export
- Tests for export and compile stripping
This commit is contained in:
風雪 committed 2026-08-18 14:00:26 +00:00
1 parent ee7807e8f2
commit fc00113ea5
10 files changed
+104 -20

No files matched your search

+3 -6
View File
@@ -12,9 +12,9 @@ import (
"strings"
"github.com/kazeyukiro/3m-ui/backend/internal/config"
"golang.org/x/crypto/curve25519"
"github.com/kazeyukiro/3m-ui/backend/internal/database/models"
"github.com/kazeyukiro/3m-ui/backend/internal/user"
"golang.org/x/crypto/curve25519"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
)
@@ -209,12 +209,9 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
copyOption(p, opts, "packet-encoding")
copyOption(p, opts, "global-padding")
copyOption(p, opts, "authenticated-length")
// encryption (client) and decryption (server) are a generated pair from
// `mihomo generate vless-x25519` / `vless-mlkem768` — never copy decryption→encryption.
copyOption(p, opts, "encryption")
if p["encryption"] == nil {
if dec, ok := opts["decryption"].(string); ok && strings.TrimSpace(dec) != "" {
p["encryption"] = dec
}
}
if flow := userFieldFromOpts(opts, cred.UUID, "flow"); flow != nil {
p["flow"] = flow
} else {
@@ -0,0 +1,42 @@
package converter
import (
"testing"
"github.com/kazeyukiro/3m-ui/backend/internal/database/models"
"github.com/kazeyukiro/3m-ui/backend/internal/user"
)
func TestVLESSClientUsesEncryptionNotDecryption(t *testing.T) {
l := models.Listener{
Name: "vless-enc", Protocol: "vless", Port: "443", Enabled: true,
Config: `{"flow":"xtls-rprx-vision","decryption":"server-decryption-only","encryption":"client-encryption-pair"}`,
}
creds := []user.Credential{{Username: "u1", UUID: "9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68"}}
proxies, err := listenerToProxies(l, "1.2.3.4", creds)
if err != nil {
t.Fatal(err)
}
p := proxies[0]
if p["encryption"] != "client-encryption-pair" {
t.Fatalf("expected client encryption pair, got %#v", p["encryption"])
}
if p["flow"] != "xtls-rprx-vision" {
t.Fatalf("flow: %#v", p["flow"])
}
}
func TestVLESSClientDoesNotCopyDecryption(t *testing.T) {
l := models.Listener{
Name: "vless-enc", Protocol: "vless", Port: "443", Enabled: true,
Config: `{"flow":"xtls-rprx-vision","decryption":"server-decryption-only"}`,
}
creds := []user.Credential{{Username: "u1", UUID: "9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68"}}
proxies, err := listenerToProxies(l, "1.2.3.4", creds)
if err != nil {
t.Fatal(err)
}
if proxies[0]["encryption"] != nil {
t.Fatalf("must not copy decryption→encryption, got %#v", proxies[0]["encryption"])
}
}
@@ -101,7 +101,7 @@ var MihomoListenerSchemas = map[string]ListenerSchema{
},
"vless": {
Protocol: "vless",
Fields: listenerFields("users", "flow", "ws-path", "grpc-service-name", "xhttp-config", "decryption", "reality-config", "certificate", "private-key", "client-auth-type", "client-auth-cert", "ech-key", "allow-insecure", "jls-config", "shadow-tls", "res-tls", "mux-option"),
Fields: listenerFields("users", "flow", "ws-path", "grpc-service-name", "xhttp-config", "decryption", "encryption", "reality-config", "certificate", "private-key", "client-auth-type", "client-auth-cert", "ech-key", "allow-insecure", "jls-config", "shadow-tls", "res-tls", "mux-option"),
NestedFields: listenerNested(
"xhttp-config.path", "xhttp-config.host", "xhttp-config.mode", "xhttp-config.no-sse-header", "xhttp-config.x-padding-bytes", "xhttp-config.x-padding-obfs-mode", "xhttp-config.x-padding-key", "xhttp-config.x-padding-header", "xhttp-config.x-padding-placement", "xhttp-config.x-padding-method", "xhttp-config.uplink-http-method", "xhttp-config.session-placement", "xhttp-config.session-key", "xhttp-config.session-table", "xhttp-config.session-length", "xhttp-config.seq-placement", "xhttp-config.seq-key", "xhttp-config.uplink-data-placement", "xhttp-config.uplink-data-key", "xhttp-config.uplink-chunk-size", "xhttp-config.sc-max-buffered-posts", "xhttp-config.sc-stream-up-server-secs", "xhttp-config.sc-max-each-post-bytes",
"reality-config.dest", "reality-config.private-key", "reality-config.short-id", "reality-config.server-names", "reality-config.limit-fallback-upload.after-bytes", "reality-config.limit-fallback-upload.bytes-per-sec", "reality-config.limit-fallback-upload.burst-bytes-per-sec", "reality-config.limit-fallback-download.after-bytes", "reality-config.limit-fallback-download.bytes-per-sec", "reality-config.limit-fallback-download.burst-bytes-per-sec",
+3 -2
View File
@@ -190,8 +190,9 @@ func vlessURIs(name, host, port string, cfg map[string]interface{}) ([]string, e
if flow, _ := row["flow"].(string); flow != "" {
params["flow"] = flow
}
if decryption, _ := cfg["decryption"].(string); decryption != "" {
params["encryption"] = decryption
// Client URI uses encryption (not server-side decryption).
if encryption, _ := cfg["encryption"].(string); encryption != "" {
params["encryption"] = encryption
}
if reality, ok := cfg["reality-config"].(map[string]interface{}); ok {
params["security"] = "reality"
+7 -6
View File
@@ -165,11 +165,12 @@ func vlessCapability() ProtocolCapability {
comps := append(transportComponents(), securityComponents(true)...)
return ProtocolCapability{
Kind: "vless", Label: "VLESS",
Layers: transportSecurityLayers("raw", "reality"),
Layers: transportSecurityLayers("raw", "reality"),
Components: comps,
Fields: []FieldCapability{
{Path: "flow", Label: "Flow", Type: FieldString, Options: []string{"xtls-rprx-vision"}},
{Path: "decryption", Label: "Decryption", Type: FieldText, Advanced: true},
{Path: "decryption", Label: "Decryption", Type: FieldText, Advanced: true, Description: "Server-side VLESS decryption (mihomo generate vless-x25519 / vless-mlkem768)"},
{Path: "encryption", Label: "Encryption", Type: FieldText, Advanced: true, Description: "Client-side VLESS encryption pair (do not reuse decryption value)"},
},
UserFields: []FieldCapability{
{Path: "uuid", Label: "UUID", Type: FieldString, Required: true},
@@ -183,7 +184,7 @@ func vmessCapability() ProtocolCapability {
comps := append(transportComponents(), securityComponents(true)...)
return ProtocolCapability{
Kind: "vmess", Label: "VMess",
Layers: transportSecurityLayers("raw", "none"),
Layers: transportSecurityLayers("raw", "none"),
Components: comps,
Fields: []FieldCapability{
{Path: "alterId", Label: "Alter ID", Type: FieldInteger},
@@ -199,7 +200,7 @@ func trojanCapability() ProtocolCapability {
comps := append(transportComponents(), securityComponents(true)...)
return ProtocolCapability{
Kind: "trojan", Label: "Trojan",
Layers: transportSecurityLayers("raw", "tls"),
Layers: transportSecurityLayers("raw", "tls"),
Components: comps,
UserFields: []FieldCapability{
{Path: "password", Label: "Password", Type: FieldSecret, Required: true},
@@ -211,7 +212,7 @@ func trojanCapability() ProtocolCapability {
func shadowsocksCapability() ProtocolCapability {
return ProtocolCapability{
Kind: "shadowsocks", Label: "Shadowsocks",
Layers: []LayerCapability{},
Layers: []LayerCapability{},
Components: []ComponentCapability{},
Fields: []FieldCapability{
{Path: "cipher", Label: "Cipher", Type: FieldString, Required: true, Options: []string{
@@ -277,7 +278,7 @@ func tuicCapability() ProtocolCapability {
func shadowquicCapability() ProtocolCapability {
return ProtocolCapability{
Kind: "shadowquic", Label: "ShadowQUIC",
Layers: []LayerCapability{},
Layers: []LayerCapability{},
Components: []ComponentCapability{},
Fields: []FieldCapability{
{Path: "alpn", Label: "ALPN", Type: FieldStringList},
+3 -2
View File
@@ -124,8 +124,9 @@ func copyConfigPassthrough(dst, cfg map[string]interface{}, skip map[string]stru
if _, ok := skip[k]; ok {
continue
}
// strip panel-only keys
if strings.HasPrefix(k, "_") || k == "access_profile" {
// strip panel-only / client-export-only keys
if strings.HasPrefix(k, "_") || k == "access_profile" || k == "encryption" ||
k == "transport_layer" || k == "security_layer" {
continue
}
dst[k] = v
@@ -0,0 +1,34 @@
package protocol
import "testing"
func TestVLESSCompileStripsClientEncryption(t *testing.T) {
reg := DefaultCompileRegistry()
in := CompileInput{
Name: "vless-enc",
Protocol: "vless",
Listen: "0.0.0.0",
Port: 443,
Config: map[string]interface{}{
"flow": "xtls-rprx-vision",
"decryption": "server-decryption",
"encryption": "client-only-must-strip",
"transport_layer": "raw",
"security_layer": "none",
},
Users: []UserCred{{UUID: "9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68"}},
}
m, err := reg.Compile(in)
if err != nil {
t.Fatal(err)
}
if m["decryption"] != "server-decryption" {
t.Fatalf("decryption missing: %#v", m["decryption"])
}
if _, ok := m["encryption"]; ok {
t.Fatalf("encryption must not appear on server listener: %#v", m["encryption"])
}
if _, ok := m["transport_layer"]; ok {
t.Fatalf("transport_layer panel key leaked")
}
}
@@ -298,7 +298,7 @@ export function configToFormValues(raw: string | undefined | null): Record<strin
/** Keys the visual form fully owns. */
const FORM_OWNED_KEYS = new Set([
'cipher', 'password', 'psk', 'version', 'alterId', 'flow', 'decryption',
'cipher', 'password', 'psk', 'version', 'alterId', 'flow', 'decryption', 'encryption',
'ws-path', 'grpc-service-name', 'ss-option',
'up', 'down', 'ignore-client-bandwidth', 'obfs', 'obfs-password', 'obfs-min-packet-size', 'obfs-max-packet-size',
'masquerade', 'alpn', 'max-idle-time', 'handshake-timeout', 'token', 'congestion-controller',
@@ -386,6 +386,7 @@ export function formValuesToConfig(
set('ws-path', values['ws-path']);
set('grpc-service-name', values['grpc-service-name']);
set('decryption', values.decryption);
set('encryption', values.encryption);
break;
case 'trojan':
set('ws-path', values['ws-path']);
@@ -778,6 +779,9 @@ const ListenerConfigFields: React.FC<Props> = ({ protocol }) => {
<Form.Item name="decryption" label={t('listeners.decryption')} tooltip={t('listeners.decryptionHint')}>
<Input.TextArea rows={2} placeholder="mlkem768x25519plus...." />
</Form.Item>
<Form.Item name="encryption" label={t('listeners.encryption')} tooltip={t('listeners.encryptionHint')}>
<Input.TextArea rows={2} placeholder="mlkem768x25519plus...." />
</Form.Item>
</>
)}
+3 -1
View File
@@ -13,7 +13,9 @@ export default {
listeners: { title: 'Nodes', create: 'Create Node', edit: 'Edit Node', name: 'Name', protocol: 'Protocol', port: 'Port', bindAddress: 'Bind Address', status: 'Status', actions: 'Actions', config: 'Protocol Config', udp: 'UDP', tls: 'TLS', deleteConfirm: 'Delete this node?', created: 'Created', updated: 'Updated', deleted: 'Deleted', reloaded: 'Reloaded', copyURI: 'Copy subscription URI', urisTitle: 'Node subscription links', invalidJSON: 'Invalid JSON', selectProtocolFirst: 'Select a protocol first', usersHint: 'Create credentials under Users and bind them to this node. Only official Listener fields are configured here.', portHint: 'Single port (e.g. 443) or official ports syntax (e.g. 200,204,401-429)', sectionProtocol: 'Protocol options', transportLayer: 'Transport',
securityLayer: 'Security',
sectionSecurity: 'Security',
sectionTransport: 'Transport', sectionTLS: 'TLS certificate', sectionReality: 'Reality', sectionSSOption: 'Trojan SS option', cipher: 'Cipher', password: 'Password', passwordOptional: 'Password (optional)', uuid: 'UUID', privateKey: 'Private key', certificate: 'Certificate', alpn: 'ALPN', sni: 'SNI', clientFingerprint: 'Client fingerprint', allowInsecure: 'Allow insecure', muxOption: 'Mux', realityPrivateKey: 'Reality private key', realityShortID: 'Short ID', realityServerNames: 'Server names', realityDest: 'Dest', transport: 'Transport', path: 'Path', host: 'Host', serviceName: 'Service name', up: 'Up', down: 'Down', ignoreClientBandwidth: 'Ignore client bandwidth', masquerade: 'Masquerade', congestionControl: 'Congestion control', clone: 'Clone', saveTemplate: 'Save as template', versions: 'Versions', cloned: 'Cloned', templateCreated: 'Template created', instantiated: 'Listener created from template', batchDone: 'Batch operation completed', templates: 'Listener Templates', templateName: 'Template name', createdAt: 'Created at', instantiate: 'Instantiate', deleteTemplateConfirm: 'Delete this template?', templateDeleted: 'Template deleted', enableSelected: 'Enable selected', disableSelected: 'Disable selected', newPort: 'New port', version: 'Version', reason: 'Reason', diff: 'Diff', rollback: 'Rollback', rollbackConfirm: 'Rollback to this version?', rollbackDone: 'Rollback completed' , addUser: 'Add user', aeadMethod: 'AEAD method', allowInsecureHint: 'Allow insecure (debug only)', alterId: 'Alter ID', alterIdHint: 'VMess alterId, prefer 0', authenticationTimeout: 'Auth timeout', bandwidthHint: 'Bandwidth limit, e.g. 100 mbps', bbrProfile: 'BBR profile', clientAuthCert: 'Client cert', clientAuthType: 'Client auth type', congestionController: 'Congestion controller', createWizard: 'Create listener wizard', cwnd: 'CWND', decryption: 'Decryption', decryptionHint: 'VLESS decryption, usually none', echKey: 'ECH Key', enablePureDownlink: 'Pure downlink', flow: 'Flow', flowHint: 'e.g. xtls-rprx-vision', grpcHint: 'gRPC transport', grpcServiceName: 'gRPC service name', handshakeTimeout: 'Handshake timeout', jlsDest: 'JLS dest', jlsHint: 'JLS settings', jlsProxy: 'JLS proxy', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI hostname', jlsUpstreamAddr: 'JLS upstream', jlsUsers: 'JLS users', kcpTunConn: 'KCP connections', kcpTunCrypt: 'KCP crypt', kcpTunKey: 'KCP key', kcpTunMode: 'KCP mode', kcpTunNocomp: 'KCP no compress', kcpTunRcvwnd: 'KCP recv window', kcpTunSndwnd: 'KCP send window', maxIdleTime: 'Max idle time', maxUdpRelayPacketSize: 'Max UDP relay packet', mekyaHint: 'Mekya settings', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: 'Max simultaneous', mekyaMaxSimultaneousHint: 'Max concurrent connections', mekyaMaxWriteDuration: 'Max write duration', mekyaMaxWriteDurationHint: 'Write timeout', mekyaMaxWriteSize: 'Max write size', mekyaMaxWriteSizeHint: 'Single write limit', mekyaPacketBuffer: 'Packet buffer', mekyaPacketBufferHint: 'Packet buffer size', mkcpCongestion: 'mKCP congestion', mkcpDownlink: 'mKCP downlink', mkcpHeader: 'mKCP header', mkcpHint: 'mKCP transport', mkcpReadBuffer: 'mKCP read buffer', mkcpSeed: 'mKCP seed', mkcpUplink: 'mKCP uplink', mkcpWriteBuffer: 'mKCP write buffer', muxBrutal: 'Mux brutal', muxBrutalDown: 'Brutal down', muxBrutalUp: 'Brutal up', muxMaxConnections: 'Mux max connections', muxMaxStreams: 'Mux max streams', muxMinStreams: 'Mux min streams', muxOnlyTcp: 'Mux TCP only', muxPadding: 'Mux padding', muxProtocol: 'Mux protocol', muxStatistic: 'Mux statistics', network: 'Network', obfs: 'Obfuscation', obfsMaxPacketSize: 'Obfs max packet', obfsMinPacketSize: 'Obfs min packet', obfsOptsHost: 'Obfs host', obfsOptsMode: 'Obfs mode', obfsPassword: 'Obfs password', paddingMax: 'Padding max', paddingMin: 'Padding min', paddingScheme: 'Padding scheme', passwordOptionalHint: 'Optional; empty uses bound user password', passwordOptionalPlaceholder: 'Optional password', psk: 'PSK', quicVersions: 'QUIC versions', rateLimit: 'Rate limit', rateLimitHint: 'e.g. 10m', realityExclusiveHint: 'Reality and TLS cert are mutually exclusive', realityKeyHint: 'Reality private key', realityShortId: 'Reality short ID', realmId: 'Realm ID', realmProxy: 'Realm proxy', realmServerUrl: 'Realm server URL', realmSkipCert: 'Skip cert verify', realmStun: 'STUN', realmToken: 'Realm token', resTlsDest: 'ResTLS dest', resTlsHint: 'ResTLS settings', resTlsMinRecordLen: 'Min record length', resTlsPassword: 'ResTLS password', resTlsProxy: 'ResTLS proxy', resTlsScript: 'ResTLS script', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS upstream', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: 'Handshake dest', shadowTlsHandshakeProxy: 'Handshake proxy', shadowTlsHint: 'ShadowTLS settings', shadowTlsPassword: 'ShadowTLS passwLine truncated
sectionTransport: 'Transport', sectionTLS: 'TLS certificate', sectionReality: 'Reality', sectionSSOption: 'Trojan SS option', cipher: 'Cipher', password: 'Password', passwordOptional: 'Password (optional)', uuid: 'UUID', privateKey: 'Private key', certificate: 'Certificate', alpn: 'ALPN', sni: 'SNI', clientFingerprint: 'Client fingerprint', allowInsecure: 'Allow insecure', muxOption: 'Mux', realityPrivateKey: 'Reality private key', realityShortID: 'Short ID', realityServerNames: 'Server names', realityDest: 'Dest', transport: 'Transport', path: 'Path', host: 'Host', serviceName: 'Service name', up: 'Up', down: 'Down', ignoreClientBandwidth: 'Ignore client bandwidth', masquerade: 'Masquerade', congestionControl: 'Congestion control', clone: 'Clone', saveTemplate: 'Save as template', versions: 'Versions', cloned: 'Cloned', templateCreated: 'Template created', instantiated: 'Listener created from template', batchDone: 'Batch operation completed', templates: 'Listener Templates', templateName: 'Template name', createdAt: 'Created at', instantiate: 'Instantiate', deleteTemplateConfirm: 'Delete this template?', templateDeleted: 'Template deleted', enableSelected: 'Enable selected', disableSelected: 'Disable selected', newPort: 'New port', version: 'Version', reason: 'Reason', diff: 'Diff', rollback: 'Rollback', rollbackConfirm: 'Rollback to this version?', rollbackDone: 'Rollback completed' , addUser: 'Add user', aeadMethod: 'AEAD method', allowInsecureHint: 'Allow insecure (debug only)', alterId: 'Alter ID', alterIdHint: 'VMess alterId, prefer 0', authenticationTimeout: 'Auth timeout', bandwidthHint: 'Bandwidth limit, e.g. 100 mbps', bbrProfile: 'BBR profile', clientAuthCert: 'Client cert', clientAuthType: 'Client auth type', congestionController: 'Congestion controller', createWizard: 'Create listener wizard', cwnd: 'CWND', decryption: 'Decryption',
encryption: 'Encryption',
encryptionHint: 'Client-side VLESS encryption pair from generate output; do not reuse the decryption value', decryptionHint: 'Server-side VLESS decryption from mihomo generate vless-x25519 / vless-mlkem768', echKey: 'ECH Key', enablePureDownlink: 'Pure downlink', flow: 'Flow', flowHint: 'e.g. xtls-rprx-vision', grpcHint: 'gRPC transport', grpcServiceName: 'gRPC service name', handshakeTimeout: 'Handshake timeout', jlsDest: 'JLS dest', jlsHint: 'JLS settings', jlsProxy: 'JLS proxy', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI hostname', jlsUpstreamAddr: 'JLS upstream', jlsUsers: 'JLS users', kcpTunConn: 'KCP connections', kcpTunCrypt: 'KCP crypt', kcpTunKey: 'KCP key', kcpTunMode: 'KCP mode', kcpTunNocomp: 'KCP no compress', kcpTunRcvwnd: 'KCP recv window', kcpTunSndwnd: 'KCP send window', maxIdleTime: 'Max idle time', maxUdpRelayPacketSize: 'Max UDP relay packet', mekyaHint: 'Mekya settings', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: 'Max simultaneous', mekyaMaxSimultaneousHint: 'Max concurrent connections', mekyaMaxWriteDuration: 'Max write duration', mekyaMaxWriteDurationHint: 'Write timeout', mekyaMaxWriteSize: 'Max write size', mekyaMaxWriteSizeHint: 'Single write limit', mekyaPacketBuffer: 'Packet buffer', mekyaPacketBufferHint: 'Packet buffer size', mkcpCongestion: 'mKCP congestion', mkcpDownlink: 'mKCP downlink', mkcpHeader: 'mKCP header', mkcpHint: 'mKCP transport', mkcpReadBuffer: 'mKCP read buffer', mkcpSeed: 'mKCP seed', mkcpUplink: 'mKCP uplink', mkcpWriteBuffer: 'mKCP write buffer', muxBrutal: 'Mux brutal', muxBrutalDown: 'Brutal down', muxBrutalUp: 'Brutal up', muxMaxConnections: 'Mux max connections', muxMaxStreams: 'Mux max streams', muxMinStreams: 'Mux min streams', muxOnlyTcp: 'Mux TCP only', muxPadding: 'Mux padding', muxProtocol: 'Mux protocol', muxStatistic: 'Mux statistics', network: 'Network', obfs: 'Obfuscation', obfsMaxPacketSize: 'Obfs max packet', obfsMinPacketSize: 'Obfs min packet', obfsOptsHost: 'Obfs host', obfsOptsMode: 'Obfs mode', obfsPassword: 'Obfs password', paddingMax: 'Padding max', paddingMin: 'Padding min', paddingScheme: 'Padding scheme', passwordOptionalHint: 'Optional; empty uses bound user password', passwordOptionalPlaceholder: 'Optional password', psk: 'PSK', quicVersions: 'QUIC versions', rateLimit: 'Rate limit', rateLimitHint: 'e.g. 10m', realityExclusiveHint: 'Reality and TLS cert are mutually exclusive', realityKeyHint: 'Reality private key', realityShortId: 'Reality short ID', realmId: 'Realm ID', realmProxy: 'Realm proxy', realmServerUrl: 'Realm server URL', realmSkipCert: 'Skip cert verify', realmStun: 'STUN', realmToken: 'Realm token', resTlsDest: 'ResTLS dest', resTlsHint: 'ResTLS settings', resTlsMinRecordLen: 'Min record length', resTlsPassword: 'ResTLS password', resTlsProxy: 'ResTLS proxy', resTlsScript: 'ResTLS script', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS upstream', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: 'Handshake dest', shadowTlsHandshakeProxy: 'Handshake proxy', shadowTlsHint: 'ShadowTLS settings', shadowTlsPassword: 'ShadowTLS password', shadowTlsPasswordHint: 'v2/v3 password', shadowTlsUsers: 'ShadowTLS users', shadowTlsVersion: 'ShadowTLS version', simpleObfsMode: 'Simple Obfs mode', snellVersion: 'Snell version', ssOptionMethod: 'SS method', ssOptionPassword: 'SS password', sudokuKey: 'Sudoku key', tableType: 'Table type', tlsPairHint: 'Certificate and private key must both be set', token: 'Token', tokenHint: 'Auth token', tokenPlaceholder: 'Enter token', trafficPattern: 'Traffic pattern', userHintMandatory: 'At least one user required', wizardBasic: 'Basics', wizardConfig: 'Protocol', wizardConfigHint: 'Fill protocol-specific fields (TLS / Reality / users, etc.)', wizardReview: 'Review', wsPath: 'WebSocket path', wsPathHint: 'e.g. /ws', xhttpHint: 'XHTTP transport', xhttpHost: 'XHTTP host', xhttpMode: 'XHTTP mode', xhttpPath: 'XHTTP path', zeroRtt: '0-RTT'},
core: { title: 'Core', status: 'Status', running: 'Running', stoppedStatus: 'Stopped', version: 'Version', pid: 'PID', uptime: 'Uptime', start: 'Start', stop: 'Stop', restart: 'Restart', started: 'Started', stopped: 'Stopped', restarted: 'Restarted' , operationFailed: 'Operation failed', unavailable: 'Core unavailable'},
logs: { title: 'Logs', empty: 'No logs', level: 'Level', timestamp: 'Time', payload: 'Payload', autoRefresh: 'Auto refresh', clear: 'Clear' },
config: { title: 'Config Engine', subtitle: 'Visual Mihomo configuration', visual: 'Visual Config', yaml: 'YAML Config', proxies: 'Proxy Configuration', yamlPreview: 'YAML Preview', addProxy: 'Add Proxy', editProxy: 'Edit Proxy', deleteProxy: 'Delete Proxy', deleteConfirm: 'Delete this proxy?', generateSuccess: 'Configuration generated successfully', validateSuccess: 'Configuration is valid', validateFailed: 'Configuration validation failed', proxyName: 'Proxy Name', proxyType: 'Proxy Type', proxyServer: 'Proxy Server', proxyPort: 'Proxy Port', proxyPassword: 'Proxy Password', proxyUUID: 'Proxy UUID' , generate: 'Generate config', validate: 'Validate config'},
+3 -1
View File
@@ -13,7 +13,9 @@ export default {
listeners: { title: '节点管理', create: '创建节点', edit: '编辑节点', name: '名称', protocol: '协议', port: '端口', bindAddress: '绑定地址', status: '状态', actions: '操作', config: '协议配置', udp: 'UDP', tls: 'TLS', deleteConfirm: '确定要删除此节点吗?', created: '创建成功', updated: '更新成功', deleted: '删除成功', reloaded: '重载成功', copyURI: '复制订阅链接', urisTitle: '节点订阅链接', invalidJSON: '无效的 JSON', selectProtocolFirst: '请先选择协议', usersHint: '用户凭证请在「用户管理」中创建并绑定到本节点;此处仅配置官方 Listener 协议字段。', portHint: '支持单端口(如 443)或官方 ports 语法(如 200,204,401-429)', sectionProtocol: '协议选项', transportLayer: '传输层',
securityLayer: '安全层',
sectionSecurity: '安全层',
sectionTransport: '传输层', sectionTLS: 'TLS 证书', sectionReality: 'Reality', sectionSSOption: 'Trojan SS 混淆', cipher: '加密方法', password: '密码', passwordOptional: '密码(可选)', uuid: 'UUID', privateKey: '私钥', certificate: '证书', alpn: 'ALPN', sni: 'SNI', clientFingerprint: '客户端指纹', allowInsecure: '允许不安全', muxOption: '多路复用', realityPrivateKey: 'Reality 私钥', realityShortID: 'Short ID', realityServerNames: 'Server Names', realityDest: 'Dest', transport: '传输', path: '路径', host: 'Host', serviceName: 'Service Name', up: '上行', down: '下行', ignoreClientBandwidth: '忽略客户端带宽', masquerade: 'Masquerade', congestionControl: '拥塞控制', clone: '克隆', saveTemplate: '保存为模板', versions: '配置版本', cloned: '克隆成功', templateCreated: '模板创建成功', instantiated: '已从模板创建节点', batchDone: '批量操作完成', templates: '节点模板', templateName: '模板名称', createdAt: '创建时间', instantiate: '实例化', deleteTemplateConfirm: '确定删除此模板?', templateDeleted: '模板已删除', enableSelected: '启用选中', disableSelected: '禁用选中', newPort: '新端口', version: '版本', reason: '原因', diff: '配置差异', rollback: '回滚', rollbackConfirm: '确定回滚到此版本?', rollbackDone: '回滚完成' , addUser: '添加用户', aeadMethod: 'AEAD 加密方式', allowInsecureHint: '允许不安全连接(仅调试)', alterId: 'Alter ID', alterIdHint: 'VMess 额外 ID,建议 0', authenticationTimeout: '认证超时', bandwidthHint: '带宽限制,如 100 mbps', bbrProfile: 'BBR 配置', clientAuthCert: '客户端证书', clientAuthType: '客户端认证类型', congestionController: '拥塞控制', createWizard: '向导创建节点', cwnd: 'CWND', decryption: '解密', decryptionHint: 'VLESS 解密方式,通常 none', echKey: 'ECH Key', enablePureDownlink: '纯下行', flow: '流控', flowHint: '如 xtls-rprx-vision', grpcHint: 'gRPC 传输', grpcServiceName: 'gRPC Service Name', handshakeTimeout: '握手超时', jlsDest: 'JLS 目标', jlsHint: 'Joliz 相关配置', jlsProxy: 'JLS 代理', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI 主机名', jlsUpstreamAddr: 'JLS 上游地址', jlsUsers: 'JLS 用户', kcpTunConn: 'KCP 连接数', kcpTunCrypt: 'KCP 加密', kcpTunKey: 'KCP 密钥', kcpTunMode: 'KCP 模式', kcpTunNocomp: 'KCP 禁用压缩', kcpTunRcvwnd: 'KCP 接收窗口', kcpTunSndwnd: 'KCP 发送窗口', maxIdleTime: '最大空闲时间', maxUdpRelayPacketSize: '最大 UDP 中继包', mekyaHint: 'Mekya 相关配置', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: '最大并发', mekyaMaxSimultaneousHint: '最大同时连接数', mekyaMaxWriteDuration: '最大写入时长', mekyaMaxWriteDurationHint: '写入超时', mekyaMaxWriteSize: '最大写入大小', mekyaMaxWriteSizeHint: '单次写入上限', mekyaPacketBuffer: '包缓冲', mekyaPacketBufferHint: '数据包缓冲大小', mkcpCongestion: 'mKCP 拥塞控制', mkcpDownlink: 'mKCP 下行', mkcpHeader: 'mKCP 头类型', mkcpHint: 'mKCP 传输', mkcpReadBuffer: 'mKCP 读缓冲', mkcpSeed: 'mKCP Seed', mkcpUplink: 'mKCP 上行', mkcpWriteBuffer: 'mKCP 写缓冲', muxBrutal: 'Mux Brutal', muxBrutalDown: 'Brutal 下行', muxBrutalUp: 'Brutal 上行', muxMaxConnections: 'Mux 最大连接', muxMaxStreams: 'Mux 最大流', muxMinStreams: 'Mux 最小流', muxOnlyTcp: '仅 TCP Mux', muxPadding: 'Mux 填充', muxProtocol: 'Mux 协议', muxStatistic: 'Mux 统计', network: '传输网络', obfs: '混淆', obfsMaxPacketSize: '混淆最大包', obfsMinPacketSize: '混淆最小包', obfsOptsHost: '混淆 Host', obfsOptsMode: '混淆模式', obfsPassword: '混淆密码', paddingMax: '填充最大', paddingMin: '填充最小', paddingScheme: '填充方案', passwordOptionalHint: '可选,留空使用绑定用户密码', passwordOptionalPlaceholder: '可选密码', psk: 'PSK', quicVersions: 'QUIC 版本', rateLimit: '速率限制', rateLimitHint: '如 10m', realityExclusiveHint: 'Reality 与 TLS 证书互斥', realityKeyHint: 'Reality 私钥', realityShortId: 'Reality Short ID', realmId: 'Realm ID', realmProxy: 'Realm 代理', realmServerUrl: 'Realm 服务地址', realmSkipCert: '跳过证书校验', realmStun: 'STUN', realmToken: 'Realm Token', resTlsDest: 'ResTLS 目标', resTlsHint: 'ResTLS 配置', resTlsMinRecordLen: '最小记录长度', resTlsPassword: 'ResTLS 密码', resTlsProxy: 'ResTLS 代理', resTlsScript: 'ResTLS 脚本', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS 上游', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: '握手目标', shadowTlsHandshakeProxy: '握手代理', shadowTlsHint: 'ShadowTLS 配置', shadowTlsPassword: 'ShadowTLS 密码', shadowTlsPasswordHint: '版本 2/3 Line truncated
sectionTransport: '传输层', sectionTLS: 'TLS 证书', sectionReality: 'Reality', sectionSSOption: 'Trojan SS 混淆', cipher: '加密方法', password: '密码', passwordOptional: '密码(可选)', uuid: 'UUID', privateKey: '私钥', certificate: '证书', alpn: 'ALPN', sni: 'SNI', clientFingerprint: '客户端指纹', allowInsecure: '允许不安全', muxOption: '多路复用', realityPrivateKey: 'Reality 私钥', realityShortID: 'Short ID', realityServerNames: 'Server Names', realityDest: 'Dest', transport: '传输', path: '路径', host: 'Host', serviceName: 'Service Name', up: '上行', down: '下行', ignoreClientBandwidth: '忽略客户端带宽', masquerade: 'Masquerade', congestionControl: '拥塞控制', clone: '克隆', saveTemplate: '保存为模板', versions: '配置版本', cloned: '克隆成功', templateCreated: '模板创建成功', instantiated: '已从模板创建节点', batchDone: '批量操作完成', templates: '节点模板', templateName: '模板名称', createdAt: '创建时间', instantiate: '实例化', deleteTemplateConfirm: '确定删除此模板?', templateDeleted: '模板已删除', enableSelected: '启用选中', disableSelected: '禁用选中', newPort: '新端口', version: '版本', reason: '原因', diff: '配置差异', rollback: '回滚', rollbackConfirm: '确定回滚到此版本?', rollbackDone: '回滚完成' , addUser: '添加用户', aeadMethod: 'AEAD 加密方式', allowInsecureHint: '允许不安全连接(仅调试)', alterId: 'Alter ID', alterIdHint: 'VMess 额外 ID,建议 0', authenticationTimeout: '认证超时', bandwidthHint: '带宽限制,如 100 mbps', bbrProfile: 'BBR 配置', clientAuthCert: '客户端证书', clientAuthType: '客户端认证类型', congestionController: '拥塞控制', createWizard: '向导创建节点', cwnd: 'CWND', decryption: '解密',
encryption: 'Encryption',
encryptionHint: '客户端 VLESS Encryption:与 decryption 成对,取 generate 输出的 encryption 一侧,勿直接复制 decryption', decryptionHint: '服务端 VLESS Encryption:由 mihomo generate vless-x25519 / vless-mlkem768 生成,填 decryption 一侧', echKey: 'ECH Key', enablePureDownlink: '纯下行', flow: '流控', flowHint: '如 xtls-rprx-vision', grpcHint: 'gRPC 传输', grpcServiceName: 'gRPC Service Name', handshakeTimeout: '握手超时', jlsDest: 'JLS 目标', jlsHint: 'Joliz 相关配置', jlsProxy: 'JLS 代理', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI 主机名', jlsUpstreamAddr: 'JLS 上游地址', jlsUsers: 'JLS 用户', kcpTunConn: 'KCP 连接数', kcpTunCrypt: 'KCP 加密', kcpTunKey: 'KCP 密钥', kcpTunMode: 'KCP 模式', kcpTunNocomp: 'KCP 禁用压缩', kcpTunRcvwnd: 'KCP 接收窗口', kcpTunSndwnd: 'KCP 发送窗口', maxIdleTime: '最大空闲时间', maxUdpRelayPacketSize: '最大 UDP 中继包', mekyaHint: 'Mekya 相关配置', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: '最大并发', mekyaMaxSimultaneousHint: '最大同时连接数', mekyaMaxWriteDuration: '最大写入时长', mekyaMaxWriteDurationHint: '写入超时', mekyaMaxWriteSize: '最大写入大小', mekyaMaxWriteSizeHint: '单次写入上限', mekyaPacketBuffer: '包缓冲', mekyaPacketBufferHint: '数据包缓冲大小', mkcpCongestion: 'mKCP 拥塞控制', mkcpDownlink: 'mKCP 下行', mkcpHeader: 'mKCP 头类型', mkcpHint: 'mKCP 传输', mkcpReadBuffer: 'mKCP 读缓冲', mkcpSeed: 'mKCP Seed', mkcpUplink: 'mKCP 上行', mkcpWriteBuffer: 'mKCP 写缓冲', muxBrutal: 'Mux Brutal', muxBrutalDown: 'Brutal 下行', muxBrutalUp: 'Brutal 上行', muxMaxConnections: 'Mux 最大连接', muxMaxStreams: 'Mux 最大流', muxMinStreams: 'Mux 最小流', muxOnlyTcp: '仅 TCP Mux', muxPadding: 'Mux 填充', muxProtocol: 'Mux 协议', muxStatistic: 'Mux 统计', network: '传输网络', obfs: '混淆', obfsMaxPacketSize: '混淆最大包', obfsMinPacketSize: '混淆最小包', obfsOptsHost: '混淆 Host', obfsOptsMode: '混淆模式', obfsPassword: '混淆密码', paddingMax: '填充最大', paddingMin: '填充最小', paddingScheme: '填充方案', passwordOptionalHint: '可选,留空使用绑定用户密码', passwordOptionalPlaceholder: '可选密码', psk: 'PSK', quicVersions: 'QUIC 版本', rateLimit: '速率限制', rateLimitHint: '如 10m', realityExclusiveHint: 'Reality 与 TLS 证书互斥', realityKeyHint: 'Reality 私钥', realityShortId: 'Reality Short ID', realmId: 'Realm ID', realmProxy: 'Realm 代理', realmServerUrl: 'Realm 服务地址', realmSkipCert: '跳过证书校验', realmStun: 'STUN', realmToken: 'Realm Token', resTlsDest: 'ResTLS 目标', resTlsHint: 'ResTLS 配置', resTlsMinRecordLen: '最小记录长度', resTlsPassword: 'ResTLS 密码', resTlsProxy: 'ResTLS 代理', resTlsScript: 'ResTLS 脚本', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS 上游', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: '握手目标', shadowTlsHandshakeProxy: '握手代理', shadowTlsHint: 'ShadowTLS 配置', shadowTlsPassword: 'ShadowTLS 密码', shadowTlsPasswordHint: '版本 2/3 密码', shadowTlsUsers: 'ShadowTLS 用户', shadowTlsVersion: 'ShadowTLS 版本', simpleObfsMode: 'Simple Obfs 模式', snellVersion: 'Snell 版本', ssOptionMethod: 'SS 加密方式', ssOptionPassword: 'SS 密码', sudokuKey: 'Sudoku Key', tableType: '表类型', tlsPairHint: '证书与私钥需成对填写', token: 'Token', tokenHint: '认证 Token', tokenPlaceholder: '输入 Token', trafficPattern: '流量模式', userHintMandatory: '至少一个用户', wizardBasic: '基础信息', wizardConfig: '协议配置', wizardConfigHint: '填写协议相关字段(TLS / Reality / 用户等)', wizardReview: '确认创建', wsPath: 'WebSocket 路径', wsPathHint: '如 /ws', xhttpHint: 'XHTTP 传输', xhttpHost: 'XHTTP Host', xhttpMode: 'XHTTP 模式', xhttpPath: 'XHTTP 路径', zeroRtt: '0-RTT'},
core: { title: '核心管理', status: '核心状态', running: '运行中', stoppedStatus: '已停止', version: '版本', pid: '进程 ID', uptime: '运行时间', start: '启动', stop: '停止', restart: '重启', started: '已启动', stopped: '已停止', restarted: '已重启' , operationFailed: '操作失败', unavailable: '核心不可用'},
logs: { title: '运行日志', empty: '暂无日志', level: '级别', timestamp: '时间', payload: '内容', autoRefresh: '自动刷新', clear: '清空' },
config: { title: '配置引擎', subtitle: '可视化管理 Mihomo 配置', visual: '可视化配置', yaml: 'YAML 配置', proxies: '代理配置', yamlPreview: 'YAML 预览', addProxy: '添加代理', editProxy: '编辑代理', deleteProxy: '删除代理', deleteConfirm: '确定要删除此代理吗?', generateSuccess: '配置生成成功', validateSuccess: '配置验证通过', validateFailed: '配置验证失败', proxyName: '代理名称', proxyType: '代理类型', proxyServer: '代理服务器', proxyPort: '代理端口', proxyPassword: '代理密码', proxyUUID: '代理 UUID' , generate: '生成配置', validate: '校验配置'},