|
@@ -26,6 +26,7 @@ import type { ClientRecord, InboundOption } from '@/hooks/useClients';
|
|
|
import { ClientFormSchema, ClientCreateFormSchema } from '@/schemas/client';
|
|
import { ClientFormSchema, ClientCreateFormSchema } from '@/schemas/client';
|
|
|
|
|
|
|
|
const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
|
|
const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
|
|
|
|
|
+const VMESS_SECURITY_OPTIONS = ['auto', 'aes-128-gcm', 'chacha20-poly1305', 'none', 'zero'] as const;
|
|
|
|
|
|
|
|
const MULTI_CLIENT_PROTOCOLS = new Set([
|
|
const MULTI_CLIENT_PROTOCOLS = new Set([
|
|
|
'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria',
|
|
'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria',
|
|
@@ -77,6 +78,7 @@ interface FormState {
|
|
|
password: string;
|
|
password: string;
|
|
|
auth: string;
|
|
auth: string;
|
|
|
flow: string;
|
|
flow: string;
|
|
|
|
|
+ security: string;
|
|
|
reverseTag: string;
|
|
reverseTag: string;
|
|
|
totalGB: number;
|
|
totalGB: number;
|
|
|
expiryDate: Dayjs | null;
|
|
expiryDate: Dayjs | null;
|
|
@@ -99,6 +101,7 @@ function emptyForm(): FormState {
|
|
|
password: '',
|
|
password: '',
|
|
|
auth: '',
|
|
auth: '',
|
|
|
flow: '',
|
|
flow: '',
|
|
|
|
|
+ security: 'auto',
|
|
|
reverseTag: '',
|
|
reverseTag: '',
|
|
|
totalGB: 0,
|
|
totalGB: 0,
|
|
|
expiryDate: null,
|
|
expiryDate: null,
|
|
@@ -163,6 +166,7 @@ export default function ClientFormModal({
|
|
|
password: client.password || '',
|
|
password: client.password || '',
|
|
|
auth: client.auth || '',
|
|
auth: client.auth || '',
|
|
|
flow: client.flow || '',
|
|
flow: client.flow || '',
|
|
|
|
|
+ security: client.security || 'auto',
|
|
|
reverseTag: client.reverse?.tag || '',
|
|
reverseTag: client.reverse?.tag || '',
|
|
|
totalGB: bytesToGB(client.totalGB || 0),
|
|
totalGB: bytesToGB(client.totalGB || 0),
|
|
|
reset: Number(client.reset) || 0,
|
|
reset: Number(client.reset) || 0,
|
|
@@ -214,6 +218,14 @@ export default function ClientFormModal({
|
|
|
return ids;
|
|
return ids;
|
|
|
}, [inbounds]);
|
|
}, [inbounds]);
|
|
|
|
|
|
|
|
|
|
+ const vmessIds = useMemo(() => {
|
|
|
|
|
+ const ids = new Set<number>();
|
|
|
|
|
+ for (const row of inbounds || []) {
|
|
|
|
|
+ if (row && row.protocol === 'vmess') ids.add(row.id);
|
|
|
|
|
+ }
|
|
|
|
|
+ return ids;
|
|
|
|
|
+ }, [inbounds]);
|
|
|
|
|
+
|
|
|
const showFlow = useMemo(
|
|
const showFlow = useMemo(
|
|
|
() => (form.inboundIds || []).some((id) => flowCapableIds.has(id)),
|
|
() => (form.inboundIds || []).some((id) => flowCapableIds.has(id)),
|
|
|
[form.inboundIds, flowCapableIds],
|
|
[form.inboundIds, flowCapableIds],
|
|
@@ -224,6 +236,11 @@ export default function ClientFormModal({
|
|
|
[form.inboundIds, vlessLikeIds],
|
|
[form.inboundIds, vlessLikeIds],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
|
|
+ const showSecurity = useMemo(
|
|
|
|
|
+ () => (form.inboundIds || []).some((id) => vmessIds.has(id)),
|
|
|
|
|
+ [form.inboundIds, vmessIds],
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (!showFlow && form.flow) {
|
|
if (!showFlow && form.flow) {
|
|
|
|
|
|
|
@@ -286,6 +303,7 @@ export default function ClientFormModal({
|
|
|
password: form.password,
|
|
password: form.password,
|
|
|
auth: form.auth,
|
|
auth: form.auth,
|
|
|
flow: form.flow,
|
|
flow: form.flow,
|
|
|
|
|
+ security: form.security,
|
|
|
reverseTag: form.reverseTag,
|
|
reverseTag: form.reverseTag,
|
|
|
totalGB: form.totalGB,
|
|
totalGB: form.totalGB,
|
|
|
delayedStart: form.delayedStart,
|
|
delayedStart: form.delayedStart,
|
|
@@ -313,6 +331,7 @@ export default function ClientFormModal({
|
|
|
password: form.password,
|
|
password: form.password,
|
|
|
auth: form.auth,
|
|
auth: form.auth,
|
|
|
flow: showFlow ? (form.flow || '') : '',
|
|
flow: showFlow ? (form.flow || '') : '',
|
|
|
|
|
+ security: showSecurity ? (form.security || 'auto') : 'auto',
|
|
|
totalGB: gbToBytes(form.totalGB),
|
|
totalGB: gbToBytes(form.totalGB),
|
|
|
expiryTime,
|
|
expiryTime,
|
|
|
reset: Number(form.reset) || 0,
|
|
reset: Number(form.reset) || 0,
|
|
@@ -497,6 +516,17 @@ export default function ClientFormModal({
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
</Col>
|
|
</Col>
|
|
|
)}
|
|
)}
|
|
|
|
|
+ {showSecurity && (
|
|
|
|
|
+ <Col xs={24} md={12}>
|
|
|
|
|
+ <Form.Item label={t('pages.clients.vmessSecurity')}>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ value={form.security}
|
|
|
|
|
+ onChange={(v) => update('security', v)}
|
|
|
|
|
+ options={VMESS_SECURITY_OPTIONS.map((k) => ({ value: k, label: k }))}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </Col>
|
|
|
|
|
+ )}
|
|
|
</Row>
|
|
</Row>
|
|
|
|
|
|
|
|
<Row gutter={16}>
|
|
<Row gutter={16}>
|