feat(frontend): sniffing tab on InboundFormModal.new.tsx (Pattern A)

Second section of the sibling-file rewrite. Wires the six sniffing
sub-fields to nested form paths ['sniffing', 'enabled'], ['sniffing',
'destOverride'], etc. Uses Form.useWatch on the enabled flag to drive
conditional rendering of the dependent fields — the same gate the
legacy modal expressed via `ib.sniffing.enabled &&`.

Checkbox.Group renders one Checkbox per SNIFFING_OPTION entry. The two
exclusion lists use Select mode="tags" so the user can paste comma-
separated IP/CIDR or domain rules.

No transient form state, no class methods — every field maps directly
to a wire-shape path in InboundFormValues.

Protocol tab is next.
This commit is contained in:
MHSanaei
2026-05-26 02:07:05 +02:00
parent bf70743589
commit 74a2813fb4

View File

@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import {
Checkbox,
Form,
Input,
InputNumber,
@@ -25,7 +26,7 @@ import {
type InboundFormValues,
} from '@/schemas/forms/inbound-form';
import { antdRule } from '@/utils/zodForm';
import { Protocols } from '@/schemas/primitives';
import { Protocols, SNIFFING_OPTION } from '@/schemas/primitives';
import DateTimePicker from '@/components/DateTimePicker';
import type { DBInbound } from '@/models/dbinbound';
import type { NodeRecord } from '@/api/queries/useNodesQuery';
@@ -87,6 +88,7 @@ export default function InboundFormModalNew({
const selectableNodes = (availableNodes || []).filter((n) => n.enable);
const protocol = Form.useWatch('protocol', form) ?? '';
const isNodeEligible = NODE_ELIGIBLE_PROTOCOLS.has(protocol);
const sniffingEnabled = Form.useWatch(['sniffing', 'enabled'], form) ?? false;
useEffect(() => {
if (!open) return;
@@ -271,6 +273,66 @@ export default function InboundFormModalNew({
</>
);
const sniffingTab = (
<>
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
<Switch />
</Form.Item>
{sniffingEnabled && (
<>
<Form.Item name={['sniffing', 'destOverride']} wrapperCol={{ span: 24 }}>
<Checkbox.Group>
{Object.entries(SNIFFING_OPTION).map(([key, value]) => (
<Checkbox key={key} value={value}>{key}</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
<Form.Item
name={['sniffing', 'metadataOnly']}
label={t('pages.inbounds.sniffingMetadataOnly')}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={['sniffing', 'routeOnly']}
label={t('pages.inbounds.sniffingRouteOnly')}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={['sniffing', 'ipsExcluded']}
label={t('pages.inbounds.sniffingIpsExcluded')}
>
<Select
mode="tags"
tokenSeparators={[',']}
placeholder="IP/CIDR/geoip:*/ext:*"
style={{ width: '100%' }}
/>
</Form.Item>
<Form.Item
name={['sniffing', 'domainsExcluded']}
label={t('pages.inbounds.sniffingDomainsExcluded')}
>
<Select
mode="tags"
tokenSeparators={[',']}
placeholder="domain:*/ext:*"
style={{ width: '100%' }}
/>
</Form.Item>
</>
)}
</>
);
return (
<>
{messageContextHolder}
@@ -293,7 +355,10 @@ export default function InboundFormModalNew({
wrapperCol={{ sm: { span: 14 } }}
onValuesChange={onValuesChange}
>
<Tabs items={[{ key: 'basic', label: t('pages.xray.basicTemplate'), children: basicTab }]} />
<Tabs items={[
{ key: 'basic', label: t('pages.xray.basicTemplate'), children: basicTab },
{ key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab },
]} />
</Form>
</Modal>
</>