diff --git a/frontend/src/lib/xray/inbound-form-adapter.ts b/frontend/src/lib/xray/inbound-form-adapter.ts index 322744f1..62f3966b 100644 --- a/frontend/src/lib/xray/inbound-form-adapter.ts +++ b/frontend/src/lib/xray/inbound-form-adapter.ts @@ -227,15 +227,32 @@ export function dropLegacyOptionalEmpties( const fb = settings.fallbacks; if (Array.isArray(fb) && fb.length === 0) delete settings.fallbacks; - // StreamSettings emits `finalmask` only when at least one transport - // mask exists (legacy `hasFinalMask`). Otherwise drop the whole block. if (stream) { + // StreamSettings emits `finalmask` only when at least one transport + // mask exists (legacy `hasFinalMask`). Drop the whole block when all + // sub-fields are empty; otherwise drop only the empty sub-arrays so + // the wire payload doesn't carry a stray `"tcp": []` next to a + // populated UDP mask list (and vice versa). const fm = stream.finalmask as { tcp?: unknown[]; udp?: unknown[]; quicParams?: unknown } | undefined; if (fm && typeof fm === 'object') { const hasTcp = Array.isArray(fm.tcp) && fm.tcp.length > 0; const hasUdp = Array.isArray(fm.udp) && fm.udp.length > 0; const hasQuic = fm.quicParams != null; - if (!hasTcp && !hasUdp && !hasQuic) delete stream.finalmask; + if (!hasTcp && !hasUdp && !hasQuic) { + delete stream.finalmask; + } else { + if (!hasTcp) delete fm.tcp; + if (!hasUdp) delete fm.udp; + } + } + + // Hysteria's per-client auth lives in settings.clients[*].auth; the + // streamSettings.hysteriaSettings.auth slot is a holdover from older + // hysteria builds and serves no purpose on the inbound side, so an + // empty value shouldn't ride along in the JSON payload. + const hs = stream.hysteriaSettings as { auth?: string } | undefined; + if (hs && typeof hs === 'object' && (hs.auth === '' || hs.auth == null)) { + delete hs.auth; } } } diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx index 3039d3bc..59d05940 100644 --- a/frontend/src/pages/clients/ClientBulkAddModal.tsx +++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Form, Input, InputNumber, Modal, Select, Switch, message } from 'antd'; -import { SyncOutlined } from '@ant-design/icons'; +import { Button, Form, Input, InputNumber, Modal, Select, Space, Switch, message } from 'antd'; +import { ReloadOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import type { Dayjs } from 'dayjs'; @@ -41,6 +41,7 @@ function emptyForm(): FormState { limitIp: 0, totalGB: 0, expiryTime: 0, + reset: 0, inboundIds: [], }; } @@ -154,6 +155,7 @@ export default function ClientBulkAddModal({ flow: showFlow ? (form.flow || '') : '', totalGB: Math.round((form.totalGB || 0) * SizeFormatter.ONE_GB), expiryTime: form.expiryTime, + reset: Number(form.reset) || 0, limitIp: Number(form.limitIp) || 0, comment: form.comment, enable: true, @@ -247,16 +249,18 @@ export default function ClientBulkAddModal({ )} -