| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 |
- import { useEffect, useMemo, useState } from 'react';
- import { useTranslation } from 'react-i18next';
- import { Alert, Modal, Select, Typography, message } from 'antd';
- import type { InboundOption } from '@/hooks/useClients';
- import type { BulkAttachResult } from '@/schemas/client';
- const MULTI_USER_PROTOCOLS = new Set(['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks']);
- interface BulkAttachInboundsModalProps {
- open: boolean;
- count: number;
- inbounds: InboundOption[];
- onOpenChange: (open: boolean) => void;
- onSubmit: (inboundIds: number[]) => Promise<BulkAttachResult | null>;
- }
- export default function BulkAttachInboundsModal({
- open,
- count,
- inbounds,
- onOpenChange,
- onSubmit,
- }: BulkAttachInboundsModalProps) {
- const { t } = useTranslation();
- const [messageApi, messageContextHolder] = message.useMessage();
- const [targetIds, setTargetIds] = useState<number[]>([]);
- const [submitting, setSubmitting] = useState(false);
- useEffect(() => {
- if (open) setTargetIds([]);
- }, [open]);
- const targetOptions = useMemo(() => {
- return (inbounds || [])
- .filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase()))
- .map((ib) => ({
- value: ib.id,
- label: `${ib.remark ?? ''} (${ib.protocol ?? ''}@${ib.port ?? ''})`,
- }));
- }, [inbounds]);
- async function submit() {
- if (targetIds.length === 0 || count === 0) return;
- setSubmitting(true);
- try {
- const result = await onSubmit(targetIds);
- if (!result) return;
- const attached = result.attached?.length ?? 0;
- const skipped = result.skipped?.length ?? 0;
- const errors = result.errors?.length ?? 0;
- if (errors > 0) {
- messageApi.warning(
- t('pages.inbounds.attachClientsResultMixed', { attached, skipped, errors }),
- );
- } else {
- messageApi.success(t('pages.inbounds.attachClientsResult', { attached, skipped }));
- }
- onOpenChange(false);
- } finally {
- setSubmitting(false);
- }
- }
- return (
- <>
- {messageContextHolder}
- <Modal
- open={open}
- title={t('pages.clients.attachToInboundsTitle', { count })}
- okText={t('pages.inbounds.attachClients')}
- cancelText={t('cancel')}
- okButtonProps={{ disabled: targetIds.length === 0, loading: submitting }}
- onCancel={() => onOpenChange(false)}
- onOk={submit}
- destroyOnHidden
- >
- <Typography.Paragraph type="secondary">
- {t('pages.clients.attachToInboundsDesc', { count })}
- </Typography.Paragraph>
- {targetOptions.length === 0 ? (
- <Alert type="info" showIcon message={t('pages.clients.attachToInboundsNoTargets')} />
- ) : (
- <Select
- mode="multiple"
- style={{ width: '100%' }}
- value={targetIds}
- onChange={setTargetIds}
- options={targetOptions}
- placeholder={t('pages.clients.attachToInboundsTargets')}
- optionFilterProp="label"
- autoFocus
- />
- )}
- </Modal>
- </>
- );
- }
|