diff --git a/frontend/eslint.deprecated.config.js b/frontend/eslint.deprecated.config.js
new file mode 100644
index 00000000..5ac95dc5
--- /dev/null
+++ b/frontend/eslint.deprecated.config.js
@@ -0,0 +1,43 @@
+import tseslint from 'typescript-eslint';
+
+export default [
+ { ignores: ['node_modules/**', '../web/dist/**', 'src/generated/**'] },
+ ...tseslint.configs.recommendedTypeChecked.map((config) => ({
+ ...config,
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ...config.languageOptions,
+ parserOptions: {
+ ...config.languageOptions?.parserOptions,
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ })),
+ {
+ files: ['**/*.{ts,tsx}'],
+ rules: {
+ '@typescript-eslint/no-deprecated': 'warn',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unsafe-assignment': 'off',
+ '@typescript-eslint/no-unsafe-member-access': 'off',
+ '@typescript-eslint/no-unsafe-call': 'off',
+ '@typescript-eslint/no-unsafe-return': 'off',
+ '@typescript-eslint/no-unsafe-argument': 'off',
+ '@typescript-eslint/no-misused-promises': 'off',
+ '@typescript-eslint/no-floating-promises': 'off',
+ '@typescript-eslint/restrict-template-expressions': 'off',
+ '@typescript-eslint/no-unused-vars': 'off',
+ '@typescript-eslint/no-base-to-string': 'off',
+ '@typescript-eslint/no-redundant-type-constituents': 'off',
+ '@typescript-eslint/unbound-method': 'off',
+ '@typescript-eslint/require-await': 'off',
+ '@typescript-eslint/await-thenable': 'off',
+ '@typescript-eslint/no-empty-function': 'off',
+ '@typescript-eslint/prefer-promise-reject-errors': 'off',
+ '@typescript-eslint/only-throw-error': 'off',
+ '@typescript-eslint/no-unnecessary-type-assertion': 'off',
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+];
diff --git a/frontend/src/components/FinalMaskForm.tsx b/frontend/src/components/FinalMaskForm.tsx
index 23f2f02b..682eee75 100644
--- a/frontend/src/components/FinalMaskForm.tsx
+++ b/frontend/src/components/FinalMaskForm.tsx
@@ -1,4 +1,4 @@
-import { Button, Divider, Form, Input, InputNumber, Select, Switch } from 'antd';
+import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch } from 'antd';
import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
import type { FormInstance } from 'antd/es/form';
import type { NamePath } from 'antd/es/form/interface';
@@ -638,7 +638,7 @@ function ItemEditor({
if (type === 'base64') {
return (
-
+
@@ -646,7 +646,7 @@ function ItemEditor({
icon={}
onClick={() => form.setFieldValue([...absoluteItemPath, 'packet'], RandomUtil.randomBase64())}
/>
-
+
);
}
diff --git a/frontend/src/lib/xray/inbound-form-adapter.ts b/frontend/src/lib/xray/inbound-form-adapter.ts
index 596266a3..322744f1 100644
--- a/frontend/src/lib/xray/inbound-form-adapter.ts
+++ b/frontend/src/lib/xray/inbound-form-adapter.ts
@@ -179,7 +179,7 @@ export function pruneEmpty(value: unknown): unknown {
// those inside a vless inbound's settings.clients is confusing and rides
// dead weight in the wire payload. Parsing through the protocol's schema
// gives us the canonical projection.
-function clientSchemaForProtocol(protocol: string): z.ZodTypeAny | null {
+function clientSchemaForProtocol(protocol: string): z.ZodType | null {
switch (protocol) {
case 'vless': return VlessClientSchema;
case 'vmess': return VmessClientSchema;
diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx
index ba870274..3039d3bc 100644
--- a/frontend/src/pages/clients/ClientBulkAddModal.tsx
+++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx
@@ -62,10 +62,10 @@ export default function ClientBulkAddModal({
useEffect(() => {
if (!open) return;
-
+
setForm(emptyForm());
setDelayedStart(false);
-
+
}, [open]);
function update(key: K, value: FormState[K]) {
@@ -87,7 +87,7 @@ export default function ClientBulkAddModal({
useEffect(() => {
if (!showFlow && form.flow) {
-
+
update('flow', '');
}
}, [showFlow, form.flow]);
@@ -186,130 +186,131 @@ export default function ClientBulkAddModal({
open={open}
title={t('pages.clients.bulk')}
okText={t('create')}
- cancelText={t('close')}
- confirmLoading={saving}
- mask={{ closable: false }}
- width={640}
- onOk={submit}
- onCancel={() => onOpenChange(false)}
- >
-
-
-
-
-
-
- {form.emailMethod > 1 && (
- <>
-
- update('firstNum', Number(v) || 1)} />
-
-
- update('lastNum', Number(v) || 1)} />
-
- >
- )}
- {form.emailMethod > 0 && (
-
- update('emailPrefix', e.target.value)} />
-
- )}
- {form.emailMethod > 2 && (
-
- update('emailPostfix', e.target.value)} />
-
- )}
- {form.emailMethod < 2 && (
-
- update('quantity', Number(v) || 1)} />
-
- )}
-
-
- {t('subscription.title')}
- update('subId', RandomUtil.randomLowerAndNum(16))}
- />
- >
- }>
- update('subId', e.target.value)} />
-
-
-
- update('comment', e.target.value)} />
-
-
- {showFlow && (
-
+ cancelText={t('close')}
+ confirmLoading={saving}
+ mask={{ closable: false }}
+ width={640}
+ onOk={submit}
+ onCancel={() => onOpenChange(false)}
+ >
+
+
+
+
- )}
- {ipLimitEnable && (
-
- update('limitIp', Number(v) || 0)} />
+ {form.emailMethod > 1 && (
+ <>
+
+ update('firstNum', Number(v) || 1)} />
+
+
+ update('lastNum', Number(v) || 1)} />
+
+ >
+ )}
+ {form.emailMethod > 0 && (
+
+ update('emailPrefix', e.target.value)} />
+
+ )}
+ {form.emailMethod > 2 && (
+
+ update('emailPostfix', e.target.value)} />
+
+ )}
+ {form.emailMethod < 2 && (
+
+ update('quantity', Number(v) || 1)} />
+
+ )}
+
+
+ {t('subscription.title')}
+ update('subId', RandomUtil.randomLowerAndNum(16))}
+ />
+ >
+ }>
+ update('subId', e.target.value)} />
- )}
-
- update('totalGB', Number(v) || 0)} />
-
+
+ update('comment', e.target.value)} />
+
-
- { setDelayedStart(!delayedStart); update('expiryTime', 0); }}
- />
-
+ {showFlow && (
+
+
+ )}
- {delayedStart ? (
-
- update('expiryTime', -86400000 * (Number(v) || 0))}
+ {ipLimitEnable && (
+
+ update('limitIp', Number(v) || 0)} />
+
+ )}
+
+
+ update('totalGB', Number(v) || 0)} />
+
+
+
+ { setDelayedStart(!delayedStart); update('expiryTime', 0); }}
/>
- ) : (
-
- update('expiryTime', next ? next.valueOf() : 0)}
- />
-
- )}
-
+
+ {delayedStart ? (
+
+ update('expiryTime', -86400000 * (Number(v) || 0))}
+ />
+
+ ) : (
+
+ update('expiryTime', next ? next.valueOf() : 0)}
+ />
+
+ )}
+
>
);
diff --git a/frontend/src/pages/clients/ClientFormModal.css b/frontend/src/pages/clients/ClientFormModal.css
deleted file mode 100644
index 2b0a44c5..00000000
--- a/frontend/src/pages/clients/ClientFormModal.css
+++ /dev/null
@@ -1 +0,0 @@
-/* Client form modal — additional layout overrides if needed. */
diff --git a/frontend/src/pages/clients/ClientFormModal.tsx b/frontend/src/pages/clients/ClientFormModal.tsx
index 5a0f46dc..7761466f 100644
--- a/frontend/src/pages/clients/ClientFormModal.tsx
+++ b/frontend/src/pages/clients/ClientFormModal.tsx
@@ -22,7 +22,6 @@ import DateTimePicker from '@/components/DateTimePicker';
import { TLS_FLOW_CONTROL } from '@/schemas/primitives';
import type { ClientRecord, InboundOption } from '@/hooks/useClients';
import { ClientFormSchema, ClientCreateFormSchema } from '@/schemas/client';
-import './ClientFormModal.css';
const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
@@ -145,7 +144,7 @@ export default function ClientFormModal({
useEffect(() => {
if (!open) return;
-
+
if (isEdit && client) {
const et = Number(client.expiryTime) || 0;
const next: FormState = {
@@ -185,7 +184,7 @@ export default function ClientFormModal({
auth: RandomUtil.randomLowerAndNum(16),
});
}
-
+
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, isEdit]);
@@ -217,14 +216,14 @@ export default function ClientFormModal({
useEffect(() => {
if (!showFlow && form.flow) {
-
+
update('flow', '');
}
}, [showFlow, form.flow]);
useEffect(() => {
if (!showReverseTag && form.reverseTag) {
-
+
update('reverseTag', '');
}
}, [showReverseTag, form.reverseTag]);
@@ -347,193 +346,194 @@ export default function ClientFormModal({
open={open}
title={isEdit ? t('pages.clients.editTitle') : t('pages.clients.addTitle')}
destroyOnHidden
- okText={isEdit ? t('save') : t('create')}
- cancelText={t('cancel')}
- okButtonProps={{ loading: submitting }}
- width={720}
- onOk={onSubmit}
- onCancel={close}
- >
-
-
-
-
- update('enable', v)} />
- {t('enable')}
-
-
- {isEdit && ipLimitEnable && (
-
-
-
-
-
- {clientIps.length > 0 ? (
-
- {clientIps.map((ip, idx) => (
- {ip}
- ))}
-
- ) : (
- {t('tgbot.noIpRecord')}
+
+ {tgBotEnable && (
+
+
+ update('tgId', Number(v) || 0)} />
+
+
)}
+
+
+ update('comment', e.target.value)} />
+
+
+
+
+
+
- )}
-
+
+
+ update('enable', v)} />
+ {t('enable')}
+
+
+ {isEdit && ipLimitEnable && (
+
+
+
+
+
+ {clientIps.length > 0 ? (
+
+ {clientIps.map((ip, idx) => (
+ {ip}
+ ))}
+
+ ) : (
+ {t('tgbot.noIpRecord')}
+ )}
+
+ )}
+
>
);
diff --git a/frontend/src/pages/clients/ClientsPage.tsx b/frontend/src/pages/clients/ClientsPage.tsx
index 4b3a3bb9..04da6509 100644
--- a/frontend/src/pages/clients/ClientsPage.tsx
+++ b/frontend/src/pages/clients/ClientsPage.tsx
@@ -174,7 +174,7 @@ export default function ClientsPage() {
useEffect(() => {
if (pageSize > 0) {
-
+
setTablePageSize(pageSize);
}
}, [pageSize]);
@@ -744,8 +744,7 @@ export default function ClientsPage() {
value={inboundFilter}
onChange={(v) => setInboundFilter(v)}
allowClear
- showSearch
- optionFilterProp="label"
+ showSearch={{ optionFilterProp: 'label' }}
placeholder={t('inbounds')}
size={isMobile ? 'small' : 'middle'}
style={{ minWidth: 160, maxWidth: 240 }}
diff --git a/frontend/src/pages/inbounds/InboundFormModal.tsx b/frontend/src/pages/inbounds/InboundFormModal.tsx
index b96f25ef..689ba50b 100644
--- a/frontend/src/pages/inbounds/InboundFormModal.tsx
+++ b/frontend/src/pages/inbounds/InboundFormModal.tsx
@@ -858,18 +858,15 @@ export default function InboundFormModal({
disabled={mode === 'edit'}
placeholder={t('pages.inbounds.localPanel')}
allowClear
- >
- {t('pages.inbounds.localPanel')}
- {selectableNodes.map((n) => (
-
- {n.name}{n.status === 'offline' ? ' (offline)' : ''}
-
- ))}
-
+ options={[
+ { value: null, label: t('pages.inbounds.localPanel') },
+ ...selectableNodes.map((n) => ({
+ value: n.id,
+ label: `${n.name}${n.status === 'offline' ? ' (offline)' : ''}`,
+ disabled: n.status === 'offline',
+ })),
+ ]}
+ />
)}
@@ -924,13 +921,12 @@ export default function InboundFormModal({
-
+
- ((option?.label as string) || '').toLowerCase().includes(input.toLowerCase())
- }
+ showSearch={{
+ filterOption: (input, option) =>
+ ((option?.label as string) || '').toLowerCase().includes(input.toLowerCase()),
+ }}
style={{ width: '100%' }}
onChange={(v) => updateFallback(record.rowKey, { childId: v })}
/>
@@ -1258,11 +1254,13 @@ export default function InboundFormModal({
-
+
@@ -1326,10 +1324,12 @@ export default function InboundFormModal({
{protocol === Protocols.MIXED && (
<>
-
+
- {SSMethodSchema.options.map((m) => (
- {m}
- ))}
-
+ options={SSMethodSchema.options.map((m) => ({ value: m, label: m }))}
+ />
{isSSWith2022 && (
)}
-
+
- TCP (RAW)
- mKCP
- WebSocket
- gRPC
- HTTPUpgrade
- XHTTP
-
+ options={[
+ { value: 'tcp', label: 'TCP (RAW)' },
+ { value: 'kcp', label: 'mKCP' },
+ { value: 'ws', label: 'WebSocket' },
+ { value: 'grpc', label: 'gRPC' },
+ { value: 'httpupgrade', label: 'HTTPUpgrade' },
+ { value: 'xhttp', label: 'XHTTP' },
+ ]}
+ />
)}
@@ -1792,11 +1793,13 @@ export default function InboundFormModal({
-
+
{xhttpMode === 'packet-up' && (
<>
@@ -1838,14 +1841,18 @@ export default function InboundFormModal({
name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
label="Uplink HTTP Method"
>
-
+
-
+
-
+
>
)}
@@ -1896,13 +1907,15 @@ export default function InboundFormModal({
name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
label="Session Placement"
>
-
+
{xhttpSessionPlacement && xhttpSessionPlacement !== 'path' && (
-
+
{xhttpSeqPlacement && xhttpSeqPlacement !== 'path' && (
-
+
{xhttpUplinkPlacement && xhttpUplinkPlacement !== 'body' && (
-
+
@@ -2104,19 +2124,28 @@ export default function InboundFormModal({
-
+
-
+
);
@@ -2221,28 +2250,29 @@ export default function InboundFormModal({
name={['streamSettings', 'sockopt', 'domainStrategy']}
label="Domain Strategy"
>
-
+
-
+
-
+
@@ -2257,22 +2287,26 @@ export default function InboundFormModal({
name={['streamSettings', 'sockopt', 'trustedXForwardedFor']}
label="Trusted X-Forwarded-For"
>
-
+
-
+
{({ getFieldValue, setFieldValue }) => {
@@ -2442,28 +2476,26 @@ export default function InboundFormModal({
-
+
-
+
-
+
@@ -2471,19 +2503,20 @@ export default function InboundFormModal({
name={['streamSettings', 'tlsSettings', 'settings', 'fingerprint']}
label="uTLS"
>
-
+
-
+
-
+
-
+
-
-
+
+
diff --git a/frontend/src/pages/index/XrayLogModal.tsx b/frontend/src/pages/index/XrayLogModal.tsx
index b2ca34f4..a9284587 100644
--- a/frontend/src/pages/index/XrayLogModal.tsx
+++ b/frontend/src/pages/index/XrayLogModal.tsx
@@ -124,13 +124,19 @@ export default function XrayLogModal({ open, onClose }: XrayLogModalProps) {
>
-
+
setAlertVisible(false) }}
className="conf-alert"
- onClose={() => setAlertVisible(false)}
title={t('pages.settings.securityWarnings')}
description={(
<>
diff --git a/frontend/src/pages/xray/NordModal.tsx b/frontend/src/pages/xray/NordModal.tsx
index 5ef1a934..13660876 100644
--- a/frontend/src/pages/xray/NordModal.tsx
+++ b/frontend/src/pages/xray/NordModal.tsx
@@ -318,8 +318,7 @@ export default function NordModal({