|
@@ -1,6 +1,6 @@
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
-import { AutoComplete, Button, Form, Input, InputNumber, Modal, Select, Space, Switch, message } from 'antd';
|
|
|
|
|
|
|
+import { AutoComplete, Button, Form, Input, InputNumber, Modal, Select, Space, Switch, Tooltip, message } from 'antd';
|
|
|
import { ReloadOutlined } from '@ant-design/icons';
|
|
import { ReloadOutlined } from '@ant-design/icons';
|
|
|
import dayjs from 'dayjs';
|
|
import dayjs from 'dayjs';
|
|
|
import type { Dayjs } from 'dayjs';
|
|
import type { Dayjs } from 'dayjs';
|
|
@@ -10,6 +10,7 @@ import { formatInboundLabel } from '@/lib/inbounds/label';
|
|
|
import { TLS_FLOW_CONTROL } from '@/schemas/primitives';
|
|
import { TLS_FLOW_CONTROL } from '@/schemas/primitives';
|
|
|
import { DateTimePicker, SelectAllClearButtons } from '@/components/form';
|
|
import { DateTimePicker, SelectAllClearButtons } from '@/components/form';
|
|
|
import { useClients, type InboundOption } from '@/hooks/useClients';
|
|
import { useClients, type InboundOption } from '@/hooks/useClients';
|
|
|
|
|
+import { useFail2banStatusQuery, getLimitIpNotice } from '@/api/queries/useFail2banStatusQuery';
|
|
|
import { ClientBulkAddFormSchema, type ClientBulkAddFormValues } from '@/schemas/client';
|
|
import { ClientBulkAddFormSchema, type ClientBulkAddFormValues } from '@/schemas/client';
|
|
|
|
|
|
|
|
const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
|
|
const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
|
|
@@ -62,6 +63,9 @@ export default function ClientBulkAddModal({
|
|
|
const [form, setForm] = useState<FormState>(emptyForm);
|
|
const [form, setForm] = useState<FormState>(emptyForm);
|
|
|
const [delayedStart, setDelayedStart] = useState(false);
|
|
const [delayedStart, setDelayedStart] = useState(false);
|
|
|
const [saving, setSaving] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
|
+ const fail2ban = useFail2banStatusQuery();
|
|
|
|
|
+ const limitIpDisabled = !fail2ban.usable;
|
|
|
|
|
+ const limitIpNotice = getLimitIpNotice(fail2ban, t);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (!open) return;
|
|
if (!open) return;
|
|
@@ -311,7 +315,13 @@ export default function ClientBulkAddModal({
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
<Form.Item label={t('pages.clients.limitIp')}>
|
|
<Form.Item label={t('pages.clients.limitIp')}>
|
|
|
- <InputNumber value={form.limitIp} min={0} onChange={(v) => update('limitIp', Number(v) || 0)} />
|
|
|
|
|
|
|
+ <Tooltip title={limitIpNotice || undefined}>
|
|
|
|
|
+ <span style={{ display: 'inline-flex' }}>
|
|
|
|
|
+ <InputNumber value={form.limitIp} min={0} disabled={limitIpDisabled}
|
|
|
|
|
+ style={limitIpDisabled ? { pointerEvents: 'none' } : undefined}
|
|
|
|
|
+ onChange={(v) => update('limitIp', Number(v) || 0)} />
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </Tooltip>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
|
|
|
|
|
<Form.Item label={t('pages.clients.totalGB')}>
|
|
<Form.Item label={t('pages.clients.totalGB')}>
|