mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-12 16:15:02 +03:00
1bdb517b88
Ant Design's Spin wraps its content in aria-live="polite", so every WebSocket tick on the dashboard, inbounds, clients and nodes pages was announced, and only the changed number was read, with no label. Pass aria-live="off" on every Spin that wraps content; aria-busy still signals loading. Each vital tile is now a group named by its metric, so "47.2 %" is read as CPU. rc-select renders each tag's remove "x" as an aria-hidden span with no tabIndex, so screen-reader users could only Backspace the last value away, never remove one SNI or inbound from the middle. selectTagRender swaps in a real button named "Remove <value>" and moves focus to the next tag after removal. It is applied to every tags/multiple Select. maxTagCount="responsive" is dropped because it hid selected inbounds behind "+N", where no screen reader could reach them. Two oxlint rules (antd-a11y/spin-content-not-live and antd-a11y/select-tag-remove) keep new code from regressing either fix.
722 lines
27 KiB
TypeScript
722 lines
27 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Input, InputNumber, Select, Switch, Tabs } from 'antd';
|
|
import {
|
|
ApartmentOutlined,
|
|
BellOutlined,
|
|
ClockCircleOutlined,
|
|
GlobalOutlined,
|
|
SafetyCertificateOutlined,
|
|
SettingOutlined,
|
|
} from '@ant-design/icons';
|
|
import { selectTagRender } from '@/components/form';
|
|
import type { AllSetting } from '@/models/setting';
|
|
import { isOutboundProtocol } from '@/schemas/primitives';
|
|
import { HttpUtil, LanguageManager } from '@/utils';
|
|
import { onNumber } from '@/utils/onNumber';
|
|
import { DefaultSettingTag, SettingListItem } from '@/components/ui';
|
|
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
import { catTabLabel } from './catTabLabel';
|
|
import { sanitizePath } from './uriPath';
|
|
import SecretInput from './SecretInput';
|
|
|
|
interface ApiMsg<T = unknown> {
|
|
success?: boolean;
|
|
obj?: T;
|
|
}
|
|
|
|
interface GeneralTabProps {
|
|
allSetting: AllSetting;
|
|
updateSetting: (patch: Partial<AllSetting>) => void;
|
|
}
|
|
|
|
export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProps) {
|
|
const { t } = useTranslation();
|
|
const { isMobile } = useMediaQuery();
|
|
|
|
const [lang, setLang] = useState<string>(() => LanguageManager.getLanguage());
|
|
const [inboundOptions, setInboundOptions] = useState<{ label: string; value: string }[]>([]);
|
|
const [outboundTagList, setOutboundTagList] = useState<string[]>([]);
|
|
const [balancerTagList, setBalancerTagList] = useState<string[]>([]);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
(async () => {
|
|
// /options is the slim picker-shaped endpoint — it skips the heavy
|
|
// per-client settings and clientStats payloads that /list ships.
|
|
const msg = (await HttpUtil.get('/panel/api/inbounds/options')) as ApiMsg<
|
|
{
|
|
tag: string;
|
|
protocol: string;
|
|
port: number;
|
|
}[]
|
|
>;
|
|
if (cancelled) return;
|
|
if (msg?.success && Array.isArray(msg.obj)) {
|
|
setInboundOptions(
|
|
msg.obj.map((ib) => ({
|
|
label: `${ib.tag} (${ib.protocol}@${ib.port})`,
|
|
value: ib.tag,
|
|
})),
|
|
);
|
|
} else {
|
|
setInboundOptions([]);
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
(async () => {
|
|
// Candidates for the panel egress picker: template outbounds plus
|
|
// subscription-derived outbounds, and routing balancers. The panel egress
|
|
// is injected as a routing rule, so a balancer tag is a valid target
|
|
// (it load-balances the panel's own traffic). The geodata picker, by
|
|
// contrast, dials a forced tag and can only use a concrete outbound.
|
|
const msg = (await HttpUtil.post('/panel/api/xray/', undefined, {
|
|
silent: true,
|
|
})) as ApiMsg<string>;
|
|
if (cancelled || !msg?.success || typeof msg.obj !== 'string') return;
|
|
try {
|
|
const payload = JSON.parse(msg.obj) as Record<string, unknown>;
|
|
const template = (payload.xraySetting || {}) as Record<string, unknown>;
|
|
const tags = new Set<string>();
|
|
const outbounds = Array.isArray(template.outbounds) ? template.outbounds : [];
|
|
for (const o of outbounds) {
|
|
if (!o || typeof o !== 'object') continue;
|
|
const rec = o as Record<string, unknown>;
|
|
if (isOutboundProtocol(rec, 'blackhole')) continue; // never a useful egress
|
|
const tag = rec.tag;
|
|
if (typeof tag === 'string' && tag) tags.add(tag);
|
|
}
|
|
const subTags = Array.isArray(payload.subscriptionOutboundTags)
|
|
? payload.subscriptionOutboundTags
|
|
: [];
|
|
for (const tag of subTags) {
|
|
if (typeof tag === 'string' && tag) tags.add(tag);
|
|
}
|
|
const balancerTags: string[] = [];
|
|
const routing = (template.routing || {}) as Record<string, unknown>;
|
|
const balancers = Array.isArray(routing.balancers) ? routing.balancers : [];
|
|
for (const b of balancers) {
|
|
if (!b || typeof b !== 'object') continue;
|
|
const tag = (b as Record<string, unknown>).tag;
|
|
if (typeof tag === 'string' && tag && !tags.has(tag)) balancerTags.push(tag);
|
|
}
|
|
setOutboundTagList([...tags]);
|
|
setBalancerTagList(balancerTags);
|
|
} catch {
|
|
setOutboundTagList([]);
|
|
setBalancerTagList([]);
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
// Outbound tags and balancer tags share one picker. When balancers exist they
|
|
// get their own labeled group so it's clear the selection routes through a
|
|
// balancer rather than a single outbound.
|
|
const outboundOptions = useMemo<
|
|
(
|
|
| { label: string; value: string }
|
|
| { label: string; options: { label: string; value: string }[] }
|
|
)[]
|
|
>(() => {
|
|
const outOpts = outboundTagList.map((tag) => ({ label: tag, value: tag }));
|
|
if (balancerTagList.length === 0) return outOpts;
|
|
return [
|
|
{ label: t('pages.xray.Outbounds'), options: outOpts },
|
|
{
|
|
label: t('pages.xray.Balancers'),
|
|
options: balancerTagList.map((tag) => ({ label: tag, value: tag })),
|
|
},
|
|
];
|
|
}, [outboundTagList, balancerTagList, t]);
|
|
|
|
const ldapInboundTagList = useMemo(() => {
|
|
const csv = allSetting.ldapInboundTags || '';
|
|
return csv.length
|
|
? csv
|
|
.split(',')
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)
|
|
: [];
|
|
}, [allSetting.ldapInboundTags]);
|
|
|
|
function setLdapInboundTagList(list: string[]) {
|
|
updateSetting({ ldapInboundTags: Array.isArray(list) ? list.join(',') : '' });
|
|
}
|
|
|
|
function onLangChange(value: string) {
|
|
setLang(value);
|
|
LanguageManager.setLanguage(value);
|
|
}
|
|
|
|
const langOptions = useMemo(
|
|
() =>
|
|
LanguageManager.supportedLanguages.map(
|
|
(l: { value: string; name: string; icon: string }) => ({
|
|
value: l.value,
|
|
label: (
|
|
<>
|
|
<span role="img" aria-label={l.name}>
|
|
{l.icon}
|
|
</span>
|
|
<span>{l.name}</span>
|
|
</>
|
|
),
|
|
}),
|
|
),
|
|
[],
|
|
);
|
|
|
|
return (
|
|
<Tabs
|
|
defaultActiveKey="1"
|
|
items={[
|
|
{
|
|
key: '1',
|
|
label: catTabLabel(
|
|
<SettingOutlined aria-hidden />,
|
|
t('pages.settings.panelSettings'),
|
|
isMobile,
|
|
),
|
|
children: (
|
|
<>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.panelListeningIP')}
|
|
description={t('pages.settings.panelListeningIPDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.webListen}
|
|
onChange={(e) => updateSetting({ webListen: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.panelListeningDomain')}
|
|
description={t('pages.settings.panelListeningDomainDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.webDomain}
|
|
onChange={(e) => updateSetting({ webDomain: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.panelPort')}
|
|
badge={<DefaultSettingTag settingKey="webPort" value={allSetting.webPort} />}
|
|
description={t('pages.settings.panelPortDesc')}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.webPort}
|
|
min={1}
|
|
max={65535}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ webPort: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.panelUrlPath')}
|
|
description={t('pages.settings.panelUrlPathDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.webBasePath}
|
|
onChange={(e) => updateSetting({ webBasePath: sanitizePath(e.target.value) })}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.sessionMaxAge')}
|
|
badge={
|
|
<DefaultSettingTag settingKey="sessionMaxAge" value={allSetting.sessionMaxAge} />
|
|
}
|
|
description={t('pages.settings.sessionMaxAgeDesc')}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.sessionMaxAge}
|
|
min={60}
|
|
max={525600}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ sessionMaxAge: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.trustedProxyCidrs')}
|
|
description={t('pages.settings.trustedProxyCidrsDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.trustedProxyCIDRs}
|
|
placeholder="127.0.0.1/32,::1/128"
|
|
onChange={(e) => updateSetting({ trustedProxyCIDRs: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.realityScanCandidates')}
|
|
description={t('pages.settings.realityScanCandidatesDesc')}
|
|
badge={
|
|
<DefaultSettingTag
|
|
settingKey="realityScanCandidates"
|
|
value={allSetting.realityScanCandidates}
|
|
/>
|
|
}
|
|
>
|
|
<Input.TextArea
|
|
rows={3}
|
|
value={allSetting.realityScanCandidates}
|
|
placeholder="www.cloudflare.com:443,www.microsoft.com:443"
|
|
onChange={(e) => updateSetting({ realityScanCandidates: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ipLimitAllowlist')}
|
|
description={t('pages.settings.ipLimitAllowlistDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.ipLimitAllowlist}
|
|
placeholder="203.0.113.10,198.51.100.0/24"
|
|
onChange={(e) => updateSetting({ ipLimitAllowlist: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.panelOutbound')}
|
|
description={t('pages.settings.panelOutboundDesc')}
|
|
>
|
|
<Select
|
|
style={{ width: '100%' }}
|
|
allowClear
|
|
showSearch
|
|
value={allSetting.panelOutbound || undefined}
|
|
placeholder={t('pages.settings.panelOutboundPh')}
|
|
options={outboundOptions}
|
|
onChange={(v) =>
|
|
updateSetting({ panelOutbound: (v as string | undefined) || '' })
|
|
}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.pageSize')}
|
|
badge={<DefaultSettingTag settingKey="pageSize" value={allSetting.pageSize} />}
|
|
description={t('pages.settings.pageSizeDesc')}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.pageSize}
|
|
min={0}
|
|
max={1000}
|
|
step={5}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ pageSize: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.restartXrayOnClientDisable')}
|
|
description={t('pages.settings.restartXrayOnClientDisableDesc')}
|
|
>
|
|
<Switch
|
|
checked={allSetting.restartXrayOnClientDisable}
|
|
onChange={(v) => updateSetting({ restartXrayOnClientDisable: v })}
|
|
/>
|
|
</SettingListItem>
|
|
|
|
<SettingListItem paddings="small" title={t('pages.settings.language')}>
|
|
<Select
|
|
value={lang}
|
|
onChange={onLangChange}
|
|
style={{ width: '100%' }}
|
|
options={langOptions}
|
|
/>
|
|
</SettingListItem>
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
key: '2',
|
|
label: catTabLabel(
|
|
<BellOutlined aria-hidden />,
|
|
t('pages.settings.notifications'),
|
|
isMobile,
|
|
),
|
|
children: (
|
|
<>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.expireTimeDiff')}
|
|
badge={<DefaultSettingTag settingKey="expireDiff" value={allSetting.expireDiff} />}
|
|
description={t('pages.settings.expireTimeDiffDesc')}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.expireDiff}
|
|
min={0}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ expireDiff: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.trafficDiff')}
|
|
badge={
|
|
<DefaultSettingTag settingKey="trafficDiff" value={allSetting.trafficDiff} />
|
|
}
|
|
description={t('pages.settings.trafficDiffDesc')}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.trafficDiff}
|
|
min={0}
|
|
max={100}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ trafficDiff: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
key: '3',
|
|
label: catTabLabel(
|
|
<SafetyCertificateOutlined aria-hidden />,
|
|
t('pages.settings.certs'),
|
|
isMobile,
|
|
),
|
|
children: (
|
|
<>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.publicKeyPath')}
|
|
description={t('pages.settings.publicKeyPathDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.webCertFile}
|
|
onChange={(e) => updateSetting({ webCertFile: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.privateKeyPath')}
|
|
description={t('pages.settings.privateKeyPathDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.webKeyFile}
|
|
onChange={(e) => updateSetting({ webKeyFile: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
key: '4',
|
|
label: catTabLabel(
|
|
<GlobalOutlined aria-hidden />,
|
|
t('pages.settings.externalTraffic'),
|
|
isMobile,
|
|
),
|
|
children: (
|
|
<>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.externalTrafficInformEnable')}
|
|
description={t('pages.settings.externalTrafficInformEnableDesc')}
|
|
>
|
|
<Switch
|
|
checked={allSetting.externalTrafficInformEnable}
|
|
onChange={(v) => updateSetting({ externalTrafficInformEnable: v })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.externalTrafficInformURI')}
|
|
description={t('pages.settings.externalTrafficInformURIDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.externalTrafficInformURI}
|
|
placeholder="(http|https)://domain[:port]/path/"
|
|
onChange={(e) => updateSetting({ externalTrafficInformURI: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
key: '5',
|
|
label: catTabLabel(
|
|
<ClockCircleOutlined aria-hidden />,
|
|
t('pages.settings.dateAndTime'),
|
|
isMobile,
|
|
),
|
|
children: (
|
|
<>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.timeZone')}
|
|
description={t('pages.settings.timeZoneDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.timeLocation}
|
|
onChange={(e) => updateSetting({ timeLocation: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.datepicker')}
|
|
description={t('pages.settings.datepickerDescription')}
|
|
>
|
|
<Select
|
|
value={allSetting.datepicker || 'gregorian'}
|
|
onChange={(v) => updateSetting({ datepicker: v as 'gregorian' | 'jalalian' })}
|
|
style={{ width: '100%' }}
|
|
options={[
|
|
{ value: 'gregorian', label: t('pages.settings.calendarGregorian') },
|
|
{ value: 'jalalian', label: t('pages.settings.calendarJalalian') },
|
|
]}
|
|
/>
|
|
</SettingListItem>
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
key: '6',
|
|
label: catTabLabel(<ApartmentOutlined aria-hidden />, 'LDAP', isMobile),
|
|
children: (
|
|
<>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.enable')}>
|
|
<Switch
|
|
checked={allSetting.ldapEnable}
|
|
onChange={(v) => updateSetting({ ldapEnable: v })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.host')}>
|
|
<Input
|
|
value={allSetting.ldapHost}
|
|
onChange={(e) => updateSetting({ ldapHost: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.port')}
|
|
badge={<DefaultSettingTag settingKey="ldapPort" value={allSetting.ldapPort} />}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.ldapPort}
|
|
min={1}
|
|
max={65535}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ ldapPort: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.useTls')}>
|
|
<Switch
|
|
checked={allSetting.ldapUseTLS}
|
|
onChange={(v) => updateSetting({ ldapUseTLS: v })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.skipTlsVerify')}
|
|
description={t('pages.settings.ldap.skipTlsVerifyDesc')}
|
|
>
|
|
<Switch
|
|
checked={allSetting.ldapInsecureSkipVerify}
|
|
disabled={!allSetting.ldapUseTLS}
|
|
onChange={(v) => updateSetting({ ldapInsecureSkipVerify: v })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.bindDn')}>
|
|
<Input
|
|
value={allSetting.ldapBindDN}
|
|
onChange={(e) => updateSetting({ ldapBindDN: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('password')}
|
|
description={
|
|
allSetting.hasLdapPassword && !allSetting.clearLdapPassword
|
|
? t('pages.settings.ldap.passwordConfigured')
|
|
: t('pages.settings.ldap.passwordUnconfigured')
|
|
}
|
|
>
|
|
<SecretInput
|
|
value={allSetting.ldapPassword}
|
|
configured={allSetting.hasLdapPassword}
|
|
clearArmed={allSetting.clearLdapPassword}
|
|
placeholder={t('pages.settings.ldap.passwordPlaceholder')}
|
|
onChange={(v) => updateSetting({ ldapPassword: v })}
|
|
onClearArmedChange={(armed) => updateSetting({ clearLdapPassword: armed })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.baseDn')}>
|
|
<Input
|
|
value={allSetting.ldapBaseDN}
|
|
onChange={(e) => updateSetting({ ldapBaseDN: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.userFilter')}>
|
|
<Input
|
|
value={allSetting.ldapUserFilter}
|
|
onChange={(e) => updateSetting({ ldapUserFilter: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.userAttr')}>
|
|
<Input
|
|
value={allSetting.ldapUserAttr}
|
|
onChange={(e) => updateSetting({ ldapUserAttr: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.vlessField')}>
|
|
<Input
|
|
value={allSetting.ldapVlessField}
|
|
onChange={(e) => updateSetting({ ldapVlessField: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.flagField')}
|
|
description={t('pages.settings.ldap.flagFieldDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.ldapFlagField}
|
|
onChange={(e) => updateSetting({ ldapFlagField: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.truthyValues')}
|
|
description={t('pages.settings.ldap.truthyValuesDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.ldapTruthyValues}
|
|
onChange={(e) => updateSetting({ ldapTruthyValues: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.invertFlag')}
|
|
description={t('pages.settings.ldap.invertFlagDesc')}
|
|
>
|
|
<Switch
|
|
checked={allSetting.ldapInvertFlag}
|
|
onChange={(v) => updateSetting({ ldapInvertFlag: v })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.syncSchedule')}
|
|
description={t('pages.settings.ldap.syncScheduleDesc')}
|
|
>
|
|
<Input
|
|
value={allSetting.ldapSyncCron}
|
|
onChange={(e) => updateSetting({ ldapSyncCron: e.target.value })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.inboundTags')}
|
|
description={t('pages.settings.ldap.inboundTagsDesc')}
|
|
>
|
|
<>
|
|
<Select
|
|
mode="multiple"
|
|
tagRender={selectTagRender}
|
|
value={ldapInboundTagList}
|
|
onChange={setLdapInboundTagList}
|
|
style={{ width: '100%' }}
|
|
options={inboundOptions}
|
|
/>
|
|
{inboundOptions.length === 0 && (
|
|
<div className="ldap-no-inbounds">{t('pages.settings.ldap.noInbounds')}</div>
|
|
)}
|
|
</>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.autoCreate')}>
|
|
<Switch
|
|
checked={allSetting.ldapAutoCreate}
|
|
onChange={(v) => updateSetting({ ldapAutoCreate: v })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem paddings="small" title={t('pages.settings.ldap.autoDelete')}>
|
|
<Switch
|
|
checked={allSetting.ldapAutoDelete}
|
|
onChange={(v) => updateSetting({ ldapAutoDelete: v })}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.defaultTotalGb')}
|
|
badge={
|
|
<DefaultSettingTag
|
|
settingKey="ldapDefaultTotalGB"
|
|
value={allSetting.ldapDefaultTotalGB}
|
|
/>
|
|
}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.ldapDefaultTotalGB}
|
|
min={0}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ ldapDefaultTotalGB: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.defaultExpiryDays')}
|
|
badge={
|
|
<DefaultSettingTag
|
|
settingKey="ldapDefaultExpiryDays"
|
|
value={allSetting.ldapDefaultExpiryDays}
|
|
/>
|
|
}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.ldapDefaultExpiryDays}
|
|
min={0}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ ldapDefaultExpiryDays: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
<SettingListItem
|
|
paddings="small"
|
|
title={t('pages.settings.ldap.defaultIpLimit')}
|
|
badge={
|
|
<DefaultSettingTag
|
|
settingKey="ldapDefaultLimitIP"
|
|
value={allSetting.ldapDefaultLimitIP}
|
|
/>
|
|
}
|
|
>
|
|
<InputNumber
|
|
value={allSetting.ldapDefaultLimitIP}
|
|
min={0}
|
|
style={{ width: '100%' }}
|
|
onChange={onNumber((v) => updateSetting({ ldapDefaultLimitIP: v }))}
|
|
/>
|
|
</SettingListItem>
|
|
</>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
);
|
|
}
|