mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-18 10:17:36 +07:00
refactor(frontend): stack client credential fields and use label hints on inbound form
Stack UUID/password/subId/auth/flow/security fields vertically in the client modal instead of two-column rows, and replace the inbound form's 'extra' help lines with hover tooltip hints on field labels.
This commit is contained in:
@ -3,6 +3,7 @@ export type OnlineAPISupport = number;
|
||||
export type ProcessState = string;
|
||||
export type Protocol = string;
|
||||
export type SubLinkProvider = unknown;
|
||||
export type staticEgressResolver = string;
|
||||
export type transportBits = number;
|
||||
|
||||
export interface AllSetting {
|
||||
|
||||
@ -12,6 +12,9 @@ export type Protocol = z.infer<typeof ProtocolSchema>;
|
||||
export const SubLinkProviderSchema = z.unknown();
|
||||
export type SubLinkProvider = z.infer<typeof SubLinkProviderSchema>;
|
||||
|
||||
export const staticEgressResolverSchema = z.string();
|
||||
export type staticEgressResolver = z.infer<typeof staticEgressResolverSchema>;
|
||||
|
||||
export const transportBitsSchema = z.number().int();
|
||||
export type transportBits = z.infer<typeof transportBitsSchema>;
|
||||
|
||||
|
||||
@ -678,71 +678,55 @@ export default function ClientFormModal({
|
||||
label: t('pages.clients.tabCredentials'),
|
||||
children: (
|
||||
<>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t('pages.clients.uuid')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.uuid} style={{ flex: 1 }} onChange={(e) => update('uuid', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={() => update('uuid', RandomUtil.randomUUID())} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t('pages.clients.password')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.password} style={{ flex: 1 }} onChange={(e) => update('password', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={regeneratePassword} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item label={t('pages.clients.uuid')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.uuid} style={{ flex: 1 }} onChange={(e) => update('uuid', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={() => update('uuid', RandomUtil.randomUUID())} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t('pages.clients.subId')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.subId} style={{ flex: 1 }} onChange={(e) => update('subId', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={() => update('subId', RandomUtil.randomLowerAndNum(16))} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t('pages.clients.hysteriaAuth')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.auth} style={{ flex: 1 }} onChange={(e) => update('auth', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={() => update('auth', RandomUtil.randomLowerAndNum(16))} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item label={t('pages.clients.password')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.password} style={{ flex: 1 }} onChange={(e) => update('password', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={regeneratePassword} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
{showFlow && (
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item label={t('pages.clients.flow')}>
|
||||
<Select
|
||||
value={form.flow}
|
||||
onChange={(v) => update('flow', v)}
|
||||
options={[
|
||||
{ value: '', label: t('none') },
|
||||
...FLOW_OPTIONS.map((k) => ({ value: k, label: k })),
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</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>
|
||||
<Form.Item label={t('pages.clients.subId')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.subId} style={{ flex: 1 }} onChange={(e) => update('subId', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={() => update('subId', RandomUtil.randomLowerAndNum(16))} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('pages.clients.hysteriaAuth')}>
|
||||
<Space.Compact style={{ display: 'flex' }}>
|
||||
<Input value={form.auth} style={{ flex: 1 }} onChange={(e) => update('auth', e.target.value)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={() => update('auth', RandomUtil.randomLowerAndNum(16))} />
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
{showFlow && (
|
||||
<Form.Item label={t('pages.clients.flow')}>
|
||||
<Select
|
||||
value={form.flow}
|
||||
onChange={(v) => update('flow', v)}
|
||||
options={[
|
||||
{ value: '', label: t('none') },
|
||||
...FLOW_OPTIONS.map((k) => ({ value: k, label: k })),
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
{showSecurity && (
|
||||
<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>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
Alert,
|
||||
@ -83,6 +84,16 @@ import type { DBInbound } from '@/models/dbinbound';
|
||||
import type { NodeRecord } from '@/api/queries/useNodesQuery';
|
||||
|
||||
|
||||
// Render a field label with a hover tooltip icon instead of an `extra` help line below.
|
||||
const labelWithHint = (label: string, hint: string) => (
|
||||
<span>
|
||||
{label}
|
||||
<Tooltip title={hint}>
|
||||
<QuestionCircleOutlined style={{ marginInlineStart: 4, color: 'rgba(128,128,128,0.65)' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
|
||||
const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p }));
|
||||
const TRAFFIC_RESETS = ['never', 'hourly', 'daily', 'weekly', 'monthly'] as const;
|
||||
const SHARE_ADDR_STRATEGIES = ['node', 'listen', 'custom'] as const;
|
||||
@ -538,16 +549,14 @@ export default function InboundFormModal({
|
||||
|
||||
<Form.Item
|
||||
name="listen"
|
||||
label={t('pages.inbounds.address')}
|
||||
extra={t('pages.inbounds.form.listenHelp')}
|
||||
label={labelWithHint(t('pages.inbounds.address'), t('pages.inbounds.form.listenHelp'))}
|
||||
>
|
||||
<Input placeholder={t('pages.inbounds.monitorDesc')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="shareAddrStrategy"
|
||||
label={t('pages.inbounds.form.shareAddrStrategy')}
|
||||
extra={t('pages.inbounds.form.shareAddrStrategyHelp')}
|
||||
label={labelWithHint(t('pages.inbounds.form.shareAddrStrategy'), t('pages.inbounds.form.shareAddrStrategyHelp'))}
|
||||
>
|
||||
<Select
|
||||
options={SHARE_ADDR_STRATEGIES
|
||||
@ -562,8 +571,7 @@ export default function InboundFormModal({
|
||||
{shareAddrStrategy === 'custom' && (
|
||||
<Form.Item
|
||||
name="shareAddr"
|
||||
label={t('pages.inbounds.form.shareAddr')}
|
||||
extra={t('pages.inbounds.form.shareAddrHelp')}
|
||||
label={labelWithHint(t('pages.inbounds.form.shareAddr'), t('pages.inbounds.form.shareAddrHelp'))}
|
||||
rules={[{
|
||||
validator: (_, value) => (
|
||||
isValidShareAddrInput(String(value ?? ''))
|
||||
@ -578,8 +586,7 @@ export default function InboundFormModal({
|
||||
|
||||
<Form.Item
|
||||
name="subSortIndex"
|
||||
label={t('pages.inbounds.form.subSortIndex')}
|
||||
extra={t('pages.inbounds.form.subSortIndexHelp')}
|
||||
label={labelWithHint(t('pages.inbounds.form.subSortIndex'), t('pages.inbounds.form.subSortIndexHelp'))}
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
|
||||
Reference in New Issue
Block a user