diff --git a/frontend/src/components/form/SelectAllClearButtons.tsx b/frontend/src/components/form/SelectAllClearButtons.tsx
new file mode 100644
index 000000000..2c3c71c04
--- /dev/null
+++ b/frontend/src/components/form/SelectAllClearButtons.tsx
@@ -0,0 +1,51 @@
+import { useTranslation } from 'react-i18next';
+import { Button } from 'antd';
+
+interface Option {
+ value: number;
+}
+
+interface SelectAllClearButtonsProps {
+ options: Option[];
+ value: number[];
+ onChange: (value: number[]) => void;
+ /** Override the default "Select all" label (defaults to the inbound copy). */
+ selectAllLabel?: string;
+ /** Override the default "Clear all" label (defaults to the inbound copy). */
+ clearLabel?: string;
+}
+
+export default function SelectAllClearButtons({
+ options,
+ value,
+ onChange,
+ selectAllLabel,
+ clearLabel,
+}: SelectAllClearButtonsProps) {
+ const { t } = useTranslation();
+
+ const optionValues = options.map((o) => o.value);
+ // Treat as "all selected" when every option is chosen, rather than comparing
+ // lengths — this stays correct even if `value` holds ids outside `options`.
+ const allSelected = options.length > 0 && optionValues.every((v) => value.includes(v));
+
+ return (
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/form/index.ts b/frontend/src/components/form/index.ts
index 9f3e37137..0a5785541 100644
--- a/frontend/src/components/form/index.ts
+++ b/frontend/src/components/form/index.ts
@@ -1,3 +1,4 @@
export { default as DateTimePicker } from './DateTimePicker';
export { default as JsonEditor } from './JsonEditor';
export { default as HeaderMapEditor } from './HeaderMapEditor';
+export { default as SelectAllClearButtons } from './SelectAllClearButtons';
diff --git a/frontend/src/pages/clients/BulkAttachInboundsModal.tsx b/frontend/src/pages/clients/BulkAttachInboundsModal.tsx
index 800e15747..1fd397a3e 100644
--- a/frontend/src/pages/clients/BulkAttachInboundsModal.tsx
+++ b/frontend/src/pages/clients/BulkAttachInboundsModal.tsx
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Alert, Modal, Select, Typography, message } from 'antd';
+import { SelectAllClearButtons } from '@/components/form';
import type { InboundOption } from '@/hooks/useClients';
import { formatInboundLabel } from '@/lib/inbounds/label';
import type { BulkAttachResult } from '@/schemas/client';
@@ -82,16 +83,23 @@ export default function BulkAttachInboundsModal({
{targetOptions.length === 0 ? (
) : (
-
+ <>
+
+
+ >
)}
>
diff --git a/frontend/src/pages/clients/BulkDetachInboundsModal.tsx b/frontend/src/pages/clients/BulkDetachInboundsModal.tsx
index 2c120d47d..d3851f573 100644
--- a/frontend/src/pages/clients/BulkDetachInboundsModal.tsx
+++ b/frontend/src/pages/clients/BulkDetachInboundsModal.tsx
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Alert, Modal, Select, Typography, message } from 'antd';
+import { SelectAllClearButtons } from '@/components/form';
import type { InboundOption } from '@/hooks/useClients';
import { formatInboundLabel } from '@/lib/inbounds/label';
import type { BulkDetachResult } from '@/schemas/client';
@@ -82,16 +83,23 @@ export default function BulkDetachInboundsModal({
{targetOptions.length === 0 ? (
) : (
-
+ <>
+
+
+ >
)}
>
diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx
index 20dedc792..8d25d850c 100644
--- a/frontend/src/pages/clients/ClientBulkAddModal.tsx
+++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx
@@ -8,7 +8,7 @@ import type { Dayjs } from 'dayjs';
import { RandomUtil, SizeFormatter } from '@/utils';
import { formatInboundLabel } from '@/lib/inbounds/label';
import { TLS_FLOW_CONTROL } from '@/schemas/primitives';
-import { DateTimePicker } from '@/components/form';
+import { DateTimePicker, SelectAllClearButtons } from '@/components/form';
import { useClients, type InboundOption } from '@/hooks/useClients';
import { ClientBulkAddFormSchema, type ClientBulkAddFormValues } from '@/schemas/client';
@@ -213,6 +213,11 @@ export default function ClientBulkAddModal({
>
+ update('inboundIds', v)}
+ />