|
|
@@ -30,12 +30,15 @@ import dayjs from 'dayjs';
|
|
|
import type { Dayjs } from 'dayjs';
|
|
|
import { Controller, FormProvider, useForm, useWatch, useFieldArray } from 'react-hook-form';
|
|
|
|
|
|
-import { HttpUtil, RandomUtil, Wireguard } from '@/utils';
|
|
|
+import { HttpUtil, IntlUtil, RandomUtil, Wireguard } from '@/utils';
|
|
|
import { formatInboundLabel } from '@/lib/inbounds/label';
|
|
|
import { generateMtprotoSecret } from '@/lib/xray/inbound-defaults';
|
|
|
import { normalizeClientIps, type ClientIpInfo } from '@/lib/clients/ip-log';
|
|
|
+import { useDatepicker } from '@/hooks/useDatepicker';
|
|
|
+import { useClientHwids } from '@/hooks/useClientHwids';
|
|
|
import { DateTimePicker, SelectAllClearButtons } from '@/components/form';
|
|
|
import { FormField } from '@/components/form/rhf';
|
|
|
+import ClientHwidListModal from '@/components/clients/ClientHwidList';
|
|
|
import { TLS_FLOW_CONTROL, TRAFFIC_RESETS } from '@/schemas/primitives';
|
|
|
import type {
|
|
|
ClientRecord,
|
|
|
@@ -79,16 +82,6 @@ interface ApiMsg<T = unknown> {
|
|
|
obj?: T;
|
|
|
}
|
|
|
|
|
|
-interface ClientHwidInfo {
|
|
|
- id: number;
|
|
|
- firstSeen: number;
|
|
|
- lastSeen: number;
|
|
|
- userAgent: string;
|
|
|
- deviceOs: string;
|
|
|
- osVersion: string;
|
|
|
- deviceModel: string;
|
|
|
-}
|
|
|
-
|
|
|
type Mode = 'add' | 'edit';
|
|
|
|
|
|
interface SaveMetaEdit {
|
|
|
@@ -252,10 +245,19 @@ export default function ClientFormModal({
|
|
|
const [ipsLoading, setIpsLoading] = useState(false);
|
|
|
const [ipsClearing, setIpsClearing] = useState(false);
|
|
|
const [ipsModalOpen, setIpsModalOpen] = useState(false);
|
|
|
- const [clientHwids, setClientHwids] = useState<ClientHwidInfo[]>([]);
|
|
|
- const [hwidsLoading, setHwidsLoading] = useState(false);
|
|
|
- const [hwidsClearing, setHwidsClearing] = useState(false);
|
|
|
+ const {
|
|
|
+ clientHwids,
|
|
|
+ hwidsLoading,
|
|
|
+ hwidsClearing,
|
|
|
+ deletingHwidId,
|
|
|
+ loadHwids,
|
|
|
+ clearHwids,
|
|
|
+ deleteHwid,
|
|
|
+ } = useClientHwids(client?.email);
|
|
|
const [hwidsModalOpen, setHwidsModalOpen] = useState(false);
|
|
|
+ const { datepicker } = useDatepicker();
|
|
|
+ const hwidDateLabel = (ts: number) =>
|
|
|
+ !ts || ts <= 0 ? '-' : IntlUtil.formatDate(ts, datepicker);
|
|
|
const fail2ban = useFail2banStatusQuery();
|
|
|
const limitIpDisabled = !fail2ban.usable;
|
|
|
const limitIpNotice = getLimitIpNotice(fail2ban, t);
|
|
|
@@ -535,46 +537,11 @@ export default function ClientFormModal({
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async function loadHwids() {
|
|
|
- if (!isEdit || !client?.email) return;
|
|
|
- setHwidsLoading(true);
|
|
|
- try {
|
|
|
- const msg = (await HttpUtil.post(
|
|
|
- `/panel/api/clients/hwids/${encodeURIComponent(client.email)}`,
|
|
|
- )) as ApiMsg<unknown[]>;
|
|
|
- if (!msg?.success || !Array.isArray(msg.obj)) {
|
|
|
- setClientHwids([]);
|
|
|
- return;
|
|
|
- }
|
|
|
- setClientHwids(
|
|
|
- msg.obj.filter(
|
|
|
- (x): x is ClientHwidInfo =>
|
|
|
- !!x && typeof x === 'object' && typeof (x as ClientHwidInfo).id === 'number',
|
|
|
- ),
|
|
|
- );
|
|
|
- } finally {
|
|
|
- setHwidsLoading(false);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
function openHwidsModal() {
|
|
|
setHwidsModalOpen(true);
|
|
|
if (clientHwids.length === 0) void loadHwids();
|
|
|
}
|
|
|
|
|
|
- async function clearHwids() {
|
|
|
- if (!isEdit || !client?.email) return;
|
|
|
- setHwidsClearing(true);
|
|
|
- try {
|
|
|
- const msg = (await HttpUtil.delete(
|
|
|
- `/panel/api/clients/hwids/${encodeURIComponent(client.email)}`,
|
|
|
- )) as ApiMsg;
|
|
|
- if (msg?.success) setClientHwids([]);
|
|
|
- } finally {
|
|
|
- setHwidsClearing(false);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
function close() {
|
|
|
onOpenChange(false);
|
|
|
}
|
|
|
@@ -1431,77 +1398,20 @@ export default function ClientFormModal({
|
|
|
)}
|
|
|
</Modal>
|
|
|
|
|
|
- <Modal
|
|
|
+ <ClientHwidListModal
|
|
|
open={hwidsModalOpen}
|
|
|
- title={`${t('pages.clients.hwidLog')}${client?.email ? ` — ${client.email}` : ''}`}
|
|
|
- width={520}
|
|
|
+ email={client?.email}
|
|
|
zIndex={CLIENT_IP_LOG_MODAL_Z_INDEX}
|
|
|
- onCancel={() => setHwidsModalOpen(false)}
|
|
|
- footer={[
|
|
|
- <Button
|
|
|
- key="refresh"
|
|
|
- icon={<ReloadOutlined />}
|
|
|
- loading={hwidsLoading}
|
|
|
- onClick={loadHwids}
|
|
|
- >
|
|
|
- {t('refresh')}
|
|
|
- </Button>,
|
|
|
- <Button
|
|
|
- key="clear"
|
|
|
- danger
|
|
|
- loading={hwidsClearing}
|
|
|
- disabled={clientHwids.length === 0}
|
|
|
- onClick={clearHwids}
|
|
|
- >
|
|
|
- {t('pages.clients.clearAll')}
|
|
|
- </Button>,
|
|
|
- <Button key="close" type="primary" onClick={() => setHwidsModalOpen(false)}>
|
|
|
- {t('close')}
|
|
|
- </Button>,
|
|
|
- ]}
|
|
|
- >
|
|
|
- {clientHwids.length > 0 ? (
|
|
|
- <div style={{ maxHeight: 360, overflowY: 'auto' }}>
|
|
|
- {clientHwids.map((entry) => (
|
|
|
- <div
|
|
|
- key={entry.id}
|
|
|
- style={{
|
|
|
- borderBottom: '1px solid var(--ant-color-border-secondary)',
|
|
|
- padding: '8px 0',
|
|
|
- }}
|
|
|
- >
|
|
|
- <Typography.Text strong>
|
|
|
- {entry.deviceModel || entry.userAgent || t('pages.clients.hwidDevice')}
|
|
|
- </Typography.Text>
|
|
|
- <br />
|
|
|
- <Typography.Text type="secondary">
|
|
|
- {[entry.deviceOs, entry.osVersion].filter(Boolean).join(' ')}
|
|
|
- </Typography.Text>
|
|
|
- <br />
|
|
|
- <Typography.Text type="secondary">
|
|
|
- {t('pages.clients.firstSeen')}:{' '}
|
|
|
- {entry.firstSeen ? dayjs(entry.firstSeen).format('YYYY-MM-DD HH:mm') : '-'}
|
|
|
- </Typography.Text>
|
|
|
- <br />
|
|
|
- <Typography.Text type="secondary">
|
|
|
- {t('pages.clients.lastSeen')}:{' '}
|
|
|
- {entry.lastSeen ? dayjs(entry.lastSeen).format('YYYY-MM-DD HH:mm') : '-'}
|
|
|
- </Typography.Text>
|
|
|
- {entry.userAgent && (
|
|
|
- <>
|
|
|
- <br />
|
|
|
- <Typography.Text type="secondary" style={{ wordBreak: 'break-all' }}>
|
|
|
- {entry.userAgent}
|
|
|
- </Typography.Text>
|
|
|
- </>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- ) : (
|
|
|
- <Tag>{t('pages.clients.noHwids')}</Tag>
|
|
|
- )}
|
|
|
- </Modal>
|
|
|
+ hwids={clientHwids}
|
|
|
+ loading={hwidsLoading}
|
|
|
+ clearing={hwidsClearing}
|
|
|
+ deletingId={deletingHwidId}
|
|
|
+ formatDate={hwidDateLabel}
|
|
|
+ onRefresh={loadHwids}
|
|
|
+ onClearAll={clearHwids}
|
|
|
+ onDelete={deleteHwid}
|
|
|
+ onClose={() => setHwidsModalOpen(false)}
|
|
|
+ />
|
|
|
</>
|
|
|
);
|
|
|
}
|