Files
3x-ui/frontend/src/pages/inbounds/list/RowActions.tsx
T
MHSanaei c4a1139d3f feat(frontend): treat wireguard inbounds as multi-user in client actions
WireGuard has been first-class multi-client on the backend for a while (key generation, tunnel address allocation, attach/detach/delete all flow through the shared client apply path), but isInboundMultiUser still excluded it, so wireguard rows only offered Export Inbound / Reset Traffic / Clone / Delete. Adding it to the multi-user set surfaces Export All URLs (per-client .conf blocks), the subscription export, and the attach/detach/group/delete-all client actions, and makes wireguard inbounds valid targets in the attach-clients picker. The now-dead isWireguard guard on the inbound-info branch is dropped. The clients-page bulk attach/detach modals carried the same stale protocol set, also missing mtproto, so both now match the single-client form's inbound picker.
2026-07-12 15:31:27 +02:00

85 lines
3.4 KiB
TypeScript

import { useTranslation } from 'react-i18next';
import { Button, Dropdown, type MenuProps } from 'antd';
import {
MoreOutlined,
EditOutlined,
QrcodeOutlined,
CopyOutlined,
ExportOutlined,
RetweetOutlined,
BlockOutlined,
DeleteOutlined,
InfoCircleOutlined,
TagsOutlined,
UsergroupAddOutlined,
UsergroupDeleteOutlined,
} from '@ant-design/icons';
import { isInboundMultiUser, showQrCodeMenu } from './helpers';
import type { DBInboundRecord, RowAction } from './types';
interface RowActionsMenuProps {
record: DBInboundRecord;
subEnable: boolean;
hasClients: boolean;
onClick: (key: RowAction) => void;
isMobile?: boolean;
}
export function buildRowActionsMenu({ record, subEnable, t, isMobile, hasClients }: { record: DBInboundRecord; subEnable: boolean; t: (k: string) => string; isMobile?: boolean; hasClients?: boolean }): MenuProps['items'] {
const items: MenuProps['items'] = [];
if (isMobile) {
items.push({ key: 'edit', icon: <EditOutlined />, label: t('edit') });
}
if (showQrCodeMenu(record)) {
items.push({ key: 'qrcode', icon: <QrcodeOutlined />, label: t('qrCode') });
}
if (isInboundMultiUser(record)) {
items.push({ key: 'export', icon: <ExportOutlined />, label: t('pages.inbounds.export') });
if (subEnable) {
items.push({
key: 'subs',
icon: <ExportOutlined />,
label: `${t('pages.inbounds.export')} — ${t('pages.settings.subSettings')}`,
});
}
} else {
items.push({ key: 'showInfo', icon: <InfoCircleOutlined />, label: t('pages.inbounds.inboundInfo') });
}
items.push({ key: 'clipboard', icon: <CopyOutlined />, label: t('pages.inbounds.exportInbound') });
items.push({ key: 'resetTraffic', icon: <RetweetOutlined />, label: t('pages.inbounds.resetTraffic') });
items.push({ key: 'clone', icon: <BlockOutlined />, label: t('pages.inbounds.clone') });
if (isInboundMultiUser(record)) {
items.push({ key: 'attachExisting', icon: <UsergroupAddOutlined />, label: t('pages.inbounds.attachExistingClients') });
}
if (isInboundMultiUser(record) && hasClients) {
items.push({ key: 'attachClients', icon: <UsergroupAddOutlined />, label: t('pages.inbounds.attachClients') });
items.push({ key: 'detachClients', icon: <UsergroupDeleteOutlined />, label: t('pages.inbounds.detachClients') });
items.push({ key: 'addToGroup', icon: <TagsOutlined />, label: t('pages.inbounds.addClientsToGroup') });
items.push({ type: 'divider' });
items.push({ key: 'delAllClients', icon: <UsergroupDeleteOutlined />, danger: true, label: t('pages.inbounds.delAllClients') });
} else {
items.push({ type: 'divider' });
}
items.push({ key: 'delete', icon: <DeleteOutlined />, danger: true, label: t('delete') });
return items;
}
export function RowActionsCell({ record, subEnable, hasClients, onClick }: RowActionsMenuProps) {
const { t } = useTranslation();
return (
<div className="action-buttons">
<Button type="text" size="small" style={{ fontSize: 16 }} icon={<EditOutlined />} aria-label={t('edit')} onClick={() => onClick('edit')} />
<Dropdown
trigger={['click']}
menu={{
items: buildRowActionsMenu({ record, subEnable, t, hasClients }),
onClick: ({ key }) => onClick(key as RowAction),
}}
>
<Button type="text" size="small" style={{ fontSize: 16 }} icon={<MoreOutlined />} aria-label={t('more')} />
</Dropdown>
</div>
);
}