🐛 fix: Implement encryption and obfuscation features in protocol configuration
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
import { generatePassword, generateRealityKeyPair, generateRealityShortId } from './generate';
|
||||
import { generateVlessX25519Pair } from './generate/mlkem768x25519plus';
|
||||
|
||||
export const protocols = [
|
||||
'shadowsocks',
|
||||
@@ -25,9 +27,12 @@ export type FieldConfig = {
|
||||
max?: number;
|
||||
step?: number;
|
||||
suffix?: string;
|
||||
password?: number;
|
||||
generate?: {
|
||||
function: () => any;
|
||||
updateFields?: Record<string, string>;
|
||||
};
|
||||
condition?: (protocol: any, values: any) => boolean;
|
||||
group?: 'basic' | 'transport' | 'security' | 'reality' | 'plugin';
|
||||
group?: 'basic' | 'transport' | 'security' | 'reality' | 'obfs' | 'encryption';
|
||||
gridSpan?: 1 | 2;
|
||||
};
|
||||
|
||||
@@ -55,13 +60,9 @@ export const LABELS = {
|
||||
'360': '360',
|
||||
'qq': 'QQ',
|
||||
// multiplex
|
||||
'off': 'Off',
|
||||
'low': 'Low',
|
||||
'middle': 'Middle',
|
||||
'high': 'High',
|
||||
// ss plugins
|
||||
'v2ray-plugin': 'V2Ray Plugin',
|
||||
'simple-obfs': 'Simple Obfs',
|
||||
} as const;
|
||||
|
||||
// Flat arrays for enum-like sets
|
||||
@@ -75,8 +76,6 @@ export const SS_CIPHERS = [
|
||||
'2022-blake3-chacha20-poly1305',
|
||||
] as const;
|
||||
|
||||
export const SS_PLUGINS = ['none', 'simple-obfs', 'v2ray-plugin'] as const;
|
||||
|
||||
export const TRANSPORTS = {
|
||||
vmess: ['tcp', 'websocket', 'grpc'] as const,
|
||||
vless: ['tcp', 'websocket', 'grpc', 'mkcp', 'httpupgrade', 'xhttp'] as const,
|
||||
@@ -101,6 +100,10 @@ export const FLOWS = {
|
||||
|
||||
export const TUIC_UDP_RELAY_MODES = ['native', 'quic'] as const;
|
||||
export const TUIC_CONGESTION = ['bbr', 'cubic', 'new_reno'] as const;
|
||||
export const XHTTP_MODES = ['auto', 'packet-up', 'stream-up', 'stream-one'] as const;
|
||||
export const ENCRYPTION_TYPES = ['none', 'mlkem768x25519plus'] as const;
|
||||
export const ENCRYPTION_MODES = ['native', 'xorpub', 'random'] as const;
|
||||
export const ENCRYPTION_RTT = ['0rtt', '1rtt'] as const;
|
||||
export const FINGERPRINTS = [
|
||||
'chrome',
|
||||
'firefox',
|
||||
@@ -112,7 +115,7 @@ export const FINGERPRINTS = [
|
||||
'qq',
|
||||
] as const;
|
||||
|
||||
export const multiplexLevels = ['off', 'low', 'middle', 'high'] as const;
|
||||
export const multiplexLevels = ['none', 'low', 'middle', 'high'] as const;
|
||||
|
||||
export function getLabel(value: string): string {
|
||||
const label = (LABELS as Record<string, string>)[value];
|
||||
@@ -129,8 +132,9 @@ const ss = z.object({
|
||||
port: nullablePort,
|
||||
cipher: z.enum(SS_CIPHERS as any).nullish(),
|
||||
server_key: nullableString,
|
||||
plugin: z.enum(SS_PLUGINS as any).nullish(),
|
||||
plugin_options: nullableString,
|
||||
obfs: z.enum(['none', 'http', 'tls'] as const).nullish(),
|
||||
obfs_host: nullableString,
|
||||
obfs_path: nullableString,
|
||||
});
|
||||
|
||||
const vmess = z.object({
|
||||
@@ -163,6 +167,16 @@ const vless = z.object({
|
||||
reality_private_key: nullableString,
|
||||
reality_public_key: nullableString,
|
||||
reality_short_id: nullableString,
|
||||
mode: nullableString,
|
||||
extra: nullableString,
|
||||
encryption: z.enum(ENCRYPTION_TYPES as any).nullish(),
|
||||
encryption_mode: z.enum(ENCRYPTION_MODES as any).nullish(),
|
||||
encryption_rtt: z.enum(ENCRYPTION_RTT as any).nullish(),
|
||||
encryption_ticket: nullableString,
|
||||
encryption_server_padding: nullableString,
|
||||
encryption_private_key: nullableString,
|
||||
encryption_client_padding: nullableString,
|
||||
encryption_password: nullableString,
|
||||
});
|
||||
|
||||
const trojan = z.object({
|
||||
@@ -183,6 +197,7 @@ const hysteria2 = z.object({
|
||||
hop_ports: nullableString,
|
||||
hop_interval: z.number().nullish(),
|
||||
obfs_password: nullableString,
|
||||
obfs: z.enum(['none', 'salamander'] as const).nullish(),
|
||||
port: nullablePort,
|
||||
security: z.enum(SECURITY.hysteria2 as any).nullish(),
|
||||
sni: nullableString,
|
||||
@@ -281,8 +296,9 @@ export function getProtocolDefaultConfig(proto: ProtocolType) {
|
||||
port: null,
|
||||
cipher: 'chacha20-ietf-poly1305',
|
||||
server_key: null,
|
||||
plugin: 'none',
|
||||
plugin_opts: null,
|
||||
obfs: 'none',
|
||||
obfs_host: null,
|
||||
obfs_path: null,
|
||||
} as any;
|
||||
case 'vmess':
|
||||
return { type: 'vmess', port: null, transport: 'tcp', security: 'none' } as any;
|
||||
@@ -296,6 +312,7 @@ export function getProtocolDefaultConfig(proto: ProtocolType) {
|
||||
port: null,
|
||||
hop_ports: null,
|
||||
hop_interval: null,
|
||||
obfs: 'none',
|
||||
obfs_password: null,
|
||||
security: 'tls',
|
||||
up_mbps: null,
|
||||
@@ -335,7 +352,7 @@ export function getProtocolDefaultConfig(proto: ProtocolType) {
|
||||
return {
|
||||
type: 'meru',
|
||||
port: null,
|
||||
multiplex: 'off',
|
||||
multiplex: 'none',
|
||||
transport: 'tcp',
|
||||
} as any;
|
||||
case 'anytls':
|
||||
@@ -367,7 +384,7 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
{
|
||||
name: 'cipher',
|
||||
type: 'select',
|
||||
label: 'encryption_method',
|
||||
label: 'cipher',
|
||||
options: SS_CIPHERS,
|
||||
defaultValue: 'chacha20-ietf-poly1305',
|
||||
group: 'basic',
|
||||
@@ -376,7 +393,9 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
name: 'server_key',
|
||||
type: 'input',
|
||||
label: 'server_key',
|
||||
password: 32,
|
||||
generate: {
|
||||
function: () => generatePassword(32),
|
||||
},
|
||||
group: 'basic',
|
||||
condition: (p) =>
|
||||
[
|
||||
@@ -386,29 +405,28 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
].includes(p.cipher),
|
||||
},
|
||||
{
|
||||
name: 'plugin',
|
||||
name: 'obfs',
|
||||
type: 'select',
|
||||
label: 'plugin',
|
||||
options: SS_PLUGINS,
|
||||
label: 'obfs',
|
||||
options: ['none', 'http', 'tls'],
|
||||
defaultValue: 'none',
|
||||
group: 'plugin',
|
||||
group: 'obfs',
|
||||
},
|
||||
{
|
||||
name: 'plugin_opts',
|
||||
type: 'textarea',
|
||||
label: 'plugin_opts',
|
||||
placeholder: (t: (key: string) => string, p: any) => {
|
||||
switch (p.plugin) {
|
||||
case 'simple-obfs':
|
||||
return 'obfs=http;obfs-host=www.bing.com;path=/';
|
||||
case 'v2ray-plugin':
|
||||
return 'WebSocket: mode=websocket;host=mydomain.me;path=/;tls=true\n\nQUIC: mode=quic;host=mydomain.me';
|
||||
default:
|
||||
return 'key=value;key2=value2';
|
||||
}
|
||||
},
|
||||
group: 'plugin',
|
||||
condition: (p) => ['simple-obfs', 'v2ray-plugin'].includes(p.plugin),
|
||||
name: 'obfs_host',
|
||||
type: 'input',
|
||||
label: 'obfs_host',
|
||||
placeholder: 'e.g. www.bing.com',
|
||||
group: 'obfs',
|
||||
condition: (p) => p.obfs && p.obfs !== 'none',
|
||||
},
|
||||
{
|
||||
name: 'obfs_path',
|
||||
type: 'input',
|
||||
label: 'obfs_path',
|
||||
placeholder: 'e.g. /path/to/obfs',
|
||||
group: 'obfs',
|
||||
condition: (p) => p.obfs && p.obfs !== 'none',
|
||||
},
|
||||
],
|
||||
vmess: [
|
||||
@@ -441,6 +459,7 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
name: 'host',
|
||||
type: 'input',
|
||||
label: 'host',
|
||||
placeholder: 'e.g. www.bing.com',
|
||||
group: 'transport',
|
||||
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
|
||||
},
|
||||
@@ -448,6 +467,7 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
name: 'path',
|
||||
type: 'input',
|
||||
label: 'path',
|
||||
placeholder: 'e.g. /path/to/obfs',
|
||||
group: 'transport',
|
||||
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
|
||||
},
|
||||
@@ -492,14 +512,6 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
placeholder: '1-65535',
|
||||
group: 'basic',
|
||||
},
|
||||
{
|
||||
name: 'flow',
|
||||
type: 'select',
|
||||
label: 'flow',
|
||||
options: FLOWS.vless,
|
||||
defaultValue: 'none',
|
||||
group: 'basic',
|
||||
},
|
||||
{
|
||||
name: 'transport',
|
||||
type: 'select',
|
||||
@@ -508,6 +520,15 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
defaultValue: 'tcp',
|
||||
group: 'transport',
|
||||
},
|
||||
{
|
||||
name: 'flow',
|
||||
type: 'select',
|
||||
label: 'flow',
|
||||
options: FLOWS.vless,
|
||||
defaultValue: 'none',
|
||||
group: 'transport',
|
||||
condition: (p) => p.transport === 'tcp',
|
||||
},
|
||||
{
|
||||
name: 'security',
|
||||
type: 'select',
|
||||
@@ -520,6 +541,7 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
name: 'host',
|
||||
type: 'input',
|
||||
label: 'host',
|
||||
placeholder: 'e.g. www.bing.com',
|
||||
group: 'transport',
|
||||
condition: (p) => ['websocket', 'mkcp', 'httpupgrade', 'xhttp'].includes(p.transport),
|
||||
},
|
||||
@@ -527,6 +549,7 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
name: 'path',
|
||||
type: 'input',
|
||||
label: 'path',
|
||||
placeholder: 'e.g. /path/to/obfs',
|
||||
group: 'transport',
|
||||
condition: (p) => ['websocket', 'mkcp', 'httpupgrade', 'xhttp'].includes(p.transport),
|
||||
},
|
||||
@@ -537,6 +560,23 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
group: 'transport',
|
||||
condition: (p) => p.transport === 'grpc',
|
||||
},
|
||||
{
|
||||
name: 'mode',
|
||||
type: 'select',
|
||||
label: 'mode',
|
||||
options: XHTTP_MODES,
|
||||
defaultValue: 'auto',
|
||||
group: 'transport',
|
||||
condition: (p) => p.transport === 'xhttp',
|
||||
},
|
||||
{
|
||||
name: 'extra',
|
||||
type: 'textarea',
|
||||
label: 'extra',
|
||||
placeholder: '{}',
|
||||
group: 'transport',
|
||||
condition: (p) => p.transport === 'xhttp',
|
||||
},
|
||||
{
|
||||
name: 'sni',
|
||||
type: 'input',
|
||||
@@ -584,6 +624,13 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
label: 'security_private_key',
|
||||
placeholder: (t) => t('security_private_key_placeholder'),
|
||||
group: 'reality',
|
||||
generate: {
|
||||
function: generateRealityKeyPair,
|
||||
updateFields: {
|
||||
reality_private_key: 'privateKey',
|
||||
reality_public_key: 'publicKey',
|
||||
},
|
||||
},
|
||||
condition: (p) => p.security === 'reality',
|
||||
},
|
||||
{
|
||||
@@ -599,8 +646,84 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
type: 'input',
|
||||
label: 'security_short_id',
|
||||
group: 'reality',
|
||||
generate: {
|
||||
function: generateRealityShortId,
|
||||
},
|
||||
condition: (p) => p.security === 'reality',
|
||||
},
|
||||
{
|
||||
name: 'encryption',
|
||||
type: 'select',
|
||||
label: 'encryption',
|
||||
options: ENCRYPTION_TYPES,
|
||||
defaultValue: 'none',
|
||||
group: 'encryption',
|
||||
},
|
||||
{
|
||||
name: 'encryption_mode',
|
||||
type: 'select',
|
||||
label: 'encryption_mode',
|
||||
options: ENCRYPTION_MODES,
|
||||
defaultValue: 'native',
|
||||
group: 'encryption',
|
||||
condition: (p) => p.encryption === 'mlkem768x25519plus',
|
||||
},
|
||||
{
|
||||
name: 'encryption_rtt',
|
||||
type: 'select',
|
||||
label: 'encryption_rtt',
|
||||
options: ENCRYPTION_RTT,
|
||||
defaultValue: '1rtt',
|
||||
group: 'encryption',
|
||||
condition: (p) => p.encryption === 'mlkem768x25519plus',
|
||||
},
|
||||
{
|
||||
name: 'encryption_ticket',
|
||||
type: 'input',
|
||||
label: 'encryption_ticket',
|
||||
placeholder: 'e.g. 600s',
|
||||
group: 'encryption',
|
||||
condition: (p) => p.encryption === 'mlkem768x25519plus' && p.encryption_rtt === '0rtt',
|
||||
},
|
||||
{
|
||||
name: 'encryption_server_padding',
|
||||
type: 'input',
|
||||
label: 'encryption_server_padding',
|
||||
placeholder: 'e.g. 100-111-1111.75-0-111.50-0-3333',
|
||||
group: 'encryption',
|
||||
condition: (p) => p.encryption === 'mlkem768x25519plus',
|
||||
},
|
||||
{
|
||||
name: 'encryption_private_key',
|
||||
type: 'input',
|
||||
label: 'encryption_private_key',
|
||||
placeholder: (t) => t('encryption_private_key_placeholder'),
|
||||
group: 'encryption',
|
||||
generate: {
|
||||
function: () => generateVlessX25519Pair(),
|
||||
updateFields: {
|
||||
encryption_private_key: 'privateKeyB64',
|
||||
encryption_password: 'passwordB64',
|
||||
},
|
||||
},
|
||||
condition: (p) => p.encryption === 'mlkem768x25519plus',
|
||||
},
|
||||
{
|
||||
name: 'encryption_client_padding',
|
||||
type: 'input',
|
||||
label: 'encryption_client_padding',
|
||||
placeholder: 'e.g. 100-111-1111.75-0-111.50-0-3333',
|
||||
group: 'encryption',
|
||||
condition: (p) => p.encryption === 'mlkem768x25519plus',
|
||||
},
|
||||
{
|
||||
name: 'encryption_password',
|
||||
type: 'input',
|
||||
label: 'encryption_password',
|
||||
placeholder: (t) => t('encryption_password_placeholder'),
|
||||
group: 'encryption',
|
||||
condition: (p) => p.encryption === 'mlkem768x25519plus',
|
||||
},
|
||||
],
|
||||
trojan: [
|
||||
{
|
||||
@@ -632,6 +755,7 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
name: 'host',
|
||||
type: 'input',
|
||||
label: 'host',
|
||||
placeholder: 'e.g. www.bing.com',
|
||||
group: 'transport',
|
||||
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
|
||||
},
|
||||
@@ -639,6 +763,7 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
name: 'path',
|
||||
type: 'input',
|
||||
label: 'path',
|
||||
placeholder: 'e.g. /path/to/obfs',
|
||||
group: 'transport',
|
||||
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
|
||||
},
|
||||
@@ -699,13 +824,24 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
|
||||
suffix: 'S',
|
||||
group: 'basic',
|
||||
},
|
||||
{
|
||||
name: 'obfs',
|
||||
type: 'select',
|
||||
label: 'obfs',
|
||||
options: ['none', 'salamander'],
|
||||
defaultValue: 'none',
|
||||
group: 'obfs',
|
||||
},
|
||||
{
|
||||
name: 'obfs_password',
|
||||
type: 'input',
|
||||
label: 'obfs_password',
|
||||
placeholder: (t) => t('obfs_password_placeholder'),
|
||||
password: 16,
|
||||
group: 'basic',
|
||||
generate: {
|
||||
function: () => generatePassword(16),
|
||||
},
|
||||
group: 'obfs',
|
||||
condition: (p) => p.obfs && p.obfs !== 'none',
|
||||
},
|
||||
{
|
||||
name: 'up_mbps',
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export { generatePassword } from './random';
|
||||
export {
|
||||
generateRealityKeyPair,
|
||||
generateRealityShortId,
|
||||
publicKeyFromPrivate,
|
||||
} from './reality-key';
|
||||
@@ -0,0 +1,21 @@
|
||||
import { x25519 } from '@noble/curves/ed25519';
|
||||
|
||||
const toB64 = (u8: Uint8Array): string => {
|
||||
if (typeof Buffer !== 'undefined') return Buffer.from(u8).toString('base64');
|
||||
let s = '';
|
||||
for (const b of u8) s += String.fromCharCode(b);
|
||||
return btoa(s);
|
||||
};
|
||||
|
||||
export type VlessX25519Pair = {
|
||||
passwordB64: string;
|
||||
privateKeyB64: string;
|
||||
};
|
||||
|
||||
export function generateVlessX25519Pair(): VlessX25519Pair {
|
||||
const { secretKey, publicKey } = x25519.keygen();
|
||||
return {
|
||||
passwordB64: toB64(publicKey),
|
||||
privateKeyB64: toB64(secretKey),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { uid } from 'radash';
|
||||
|
||||
/**
|
||||
* Generate a random password
|
||||
* @param length Length of the password
|
||||
* @param charset Character set to use (defaults to alphanumeric)
|
||||
* @returns Randomly generated password
|
||||
*/
|
||||
export function generatePassword(length = 16, charset?: string) {
|
||||
return uid(length, charset).toLowerCase();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { x25519 } from '@noble/curves/ed25519.js';
|
||||
|
||||
function toB64Url(bytes: Uint8Array) {
|
||||
return btoa(String.fromCharCode(...bytes))
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/g, '');
|
||||
}
|
||||
function fromB64Url(s: string) {
|
||||
const b64 = s
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/')
|
||||
.padEnd(Math.ceil(s.length / 4) * 4, '=');
|
||||
const bin = atob(b64);
|
||||
return new Uint8Array([...bin].map((c) => c.charCodeAt(0)));
|
||||
}
|
||||
/**
|
||||
* Generate a Reality key pair
|
||||
* @returns An object containing the private and public keys in base64url format
|
||||
*/
|
||||
export function generateRealityKeyPair() {
|
||||
const { secretKey, publicKey } = x25519.keygen();
|
||||
return { privateKey: toB64Url(secretKey), publicKey: toB64Url(publicKey) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive public key from private key
|
||||
* @param privateKeyB64Url Private key in base64url format
|
||||
* @returns Public key in base64url format
|
||||
*/
|
||||
export function publicKeyFromPrivate(privateKeyB64Url: string) {
|
||||
return toB64Url(x25519.getPublicKey(fromB64Url(privateKeyB64Url)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a short ID for Reality
|
||||
* @returns A random hexadecimal string of length 2, 4, 6, 8, 10, 12, 14, or 16
|
||||
*/
|
||||
export function generateRealityShortId() {
|
||||
const hex = '0123456789abcdef';
|
||||
const lengths = [2, 4, 6, 8, 10, 12, 14, 16];
|
||||
const idx = Math.floor(Math.random() * lengths.length);
|
||||
const len = lengths[idx] ?? 16;
|
||||
let out = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
out += hex.charAt(Math.floor(Math.random() * hex.length));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -38,7 +38,6 @@ import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { uid } from 'radash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
@@ -55,12 +54,14 @@ import {
|
||||
function DynamicField({
|
||||
field,
|
||||
control,
|
||||
form,
|
||||
protocolIndex,
|
||||
protocolData,
|
||||
t,
|
||||
}: {
|
||||
field: FieldConfig;
|
||||
control: any;
|
||||
form: any;
|
||||
protocolIndex: number;
|
||||
protocolData: any;
|
||||
t: (key: string) => string;
|
||||
@@ -97,17 +98,29 @@ function DynamicField({
|
||||
}
|
||||
onValueChange={(v) => fieldProps.onChange(v)}
|
||||
suffix={
|
||||
field.password ? (
|
||||
field.generate ? (
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
onClick={() => {
|
||||
const length = field.password || 16;
|
||||
const result = uid(length).toLowerCase();
|
||||
fieldProps.onChange(result);
|
||||
const result = field.generate!.function();
|
||||
if (typeof result === 'string') {
|
||||
fieldProps.onChange(result);
|
||||
} else if (field.generate!.updateFields) {
|
||||
Object.entries(field.generate!.updateFields).forEach(
|
||||
([fieldName, resultKey]) => {
|
||||
const fullFieldName = `protocols.${protocolIndex}.${fieldName}`;
|
||||
form.setValue(fullFieldName, (result as any)[resultKey]);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (result.privateKey) {
|
||||
fieldProps.onChange(result.privateKey);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon='mdi:refresh' className='h-4 w-4' />
|
||||
<Icon icon='mdi:key' className='h-4 w-4' />
|
||||
</Button>
|
||||
) : (
|
||||
field.suffix
|
||||
@@ -225,7 +238,7 @@ function DynamicField({
|
||||
field.placeholder
|
||||
? typeof field.placeholder === 'function'
|
||||
? field.placeholder(t, protocolData)
|
||||
: t(field.placeholder)
|
||||
: field.placeholder
|
||||
: undefined
|
||||
}
|
||||
onChange={(e) => fieldProps.onChange(e.target.value)}
|
||||
@@ -246,6 +259,7 @@ function renderFieldsByGroup(
|
||||
fields: FieldConfig[],
|
||||
group: string,
|
||||
control: any,
|
||||
form: any,
|
||||
protocolIndex: number,
|
||||
protocolData: any,
|
||||
t: (key: string) => string,
|
||||
@@ -260,6 +274,7 @@ function renderFieldsByGroup(
|
||||
key={field.name}
|
||||
field={field}
|
||||
control={control}
|
||||
form={form}
|
||||
protocolIndex={protocolIndex}
|
||||
protocolData={protocolData}
|
||||
t={t}
|
||||
@@ -274,6 +289,7 @@ function renderGroupCard(
|
||||
fields: FieldConfig[],
|
||||
group: string,
|
||||
control: any,
|
||||
form: any,
|
||||
protocolIndex: number,
|
||||
protocolData: any,
|
||||
t: (key: string) => string,
|
||||
@@ -294,7 +310,7 @@ function renderGroupCard(
|
||||
{t(title)}
|
||||
</legend>
|
||||
<div className='p-4 pt-2'>
|
||||
{renderFieldsByGroup(fields, group, control, protocolIndex, protocolData, t)}
|
||||
{renderFieldsByGroup(fields, group, control, form, protocolIndex, protocolData, t)}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
@@ -541,19 +557,48 @@ export default function ServerForm(props: {
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className='px-4 pb-4 pt-0'>
|
||||
<div className='-mx-4 space-y-4 rounded-b-lg border-t px-4 pt-4'>
|
||||
{renderGroupCard('basic', fields, 'basic', control, i, current, t)}
|
||||
{renderGroupCard('plugin', fields, 'plugin', control, i, current, t)}
|
||||
{renderGroupCard('basic', fields, 'basic', control, form, i, current, t)}
|
||||
{renderGroupCard('obfs', fields, 'obfs', control, form, i, current, t)}
|
||||
{renderGroupCard(
|
||||
'transport',
|
||||
fields,
|
||||
'transport',
|
||||
control,
|
||||
form,
|
||||
i,
|
||||
current,
|
||||
t,
|
||||
)}
|
||||
{renderGroupCard(
|
||||
'security',
|
||||
fields,
|
||||
'security',
|
||||
control,
|
||||
form,
|
||||
i,
|
||||
current,
|
||||
t,
|
||||
)}
|
||||
{renderGroupCard(
|
||||
'reality',
|
||||
fields,
|
||||
'reality',
|
||||
control,
|
||||
form,
|
||||
i,
|
||||
current,
|
||||
t,
|
||||
)}
|
||||
{renderGroupCard(
|
||||
'encryption',
|
||||
fields,
|
||||
'encryption',
|
||||
control,
|
||||
form,
|
||||
i,
|
||||
current,
|
||||
t,
|
||||
)}
|
||||
{renderGroupCard('security', fields, 'security', control, i, current, t)}
|
||||
{renderGroupCard('reality', fields, 'reality', control, i, current, t)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
Reference in New Issue
Block a user