diff --git a/frontend/src/pages/inbounds/ClientBulkModal.vue b/frontend/src/pages/inbounds/ClientBulkModal.vue
new file mode 100644
index 00000000..ffe034c8
--- /dev/null
+++ b/frontend/src/pages/inbounds/ClientBulkModal.vue
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+ Random
+ Random + Prefix
+ Random + Prefix + Num
+ Random + Prefix + Num + Postfix
+ Prefix + Num + Postfix
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ key }}
+
+
+
+
+
+ none
+ {{ key }}
+
+
+
+
+
+
+ Subscription
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total traffic (GB)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Expiry date
+
+
+
+
+
+
+
+ Renewal cycle (days)
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/inbounds/ClientFormModal.vue b/frontend/src/pages/inbounds/ClientFormModal.vue
new file mode 100644
index 00000000..1f00497a
--- /dev/null
+++ b/frontend/src/pages/inbounds/ClientFormModal.vue
@@ -0,0 +1,444 @@
+
+
+
+
+
+ Account is (expired | traffic ended) and disabled
+
+
+
+
+
+
+
+
+
+
+ Email
+
+
+
+
+
+
+
+
+ Password
+
+
+
+
+
+
+
+
+ Auth password
+
+
+
+
+
+
+
+
+ ID
+
+
+
+
+
+
+
+
+ {{ key }}
+
+
+
+
+
+
+
+ Subscription
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clear
+
+
+
+
+
+ none
+
+ {{ key }}
+
+
+
+
+
+
+
+ Reverse tag
+
+
+
+
+
+
+
+ Total traffic (GB)
+
+
+
+
+
+
+ {{ SizeFormatter.sizeFormat(clientStats.up) }} /
+ {{ SizeFormatter.sizeFormat(clientStats.down) }}
+ ({{ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Expiry date
+
+
+ Expired
+
+
+
+
+
+ Renewal cycle (days)
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/inbounds/InboundsPage.vue b/frontend/src/pages/inbounds/InboundsPage.vue
index f33a1b7a..c951091a 100644
--- a/frontend/src/pages/inbounds/InboundsPage.vue
+++ b/frontend/src/pages/inbounds/InboundsPage.vue
@@ -17,6 +17,8 @@ import AppSidebar from '@/components/AppSidebar.vue';
import CustomStatistic from '@/components/CustomStatistic.vue';
import InboundList from './InboundList.vue';
import InboundFormModal from './InboundFormModal.vue';
+import ClientFormModal from './ClientFormModal.vue';
+import ClientBulkModal from './ClientBulkModal.vue';
import { useInbounds } from './useInbounds.js';
const antdThemeConfig = computed(() => ({
@@ -34,6 +36,8 @@ const {
trafficDiff,
pageSize,
subSettings,
+ tgBotEnable,
+ ipLimitEnable,
refresh,
fetchDefaultSettings,
} = useInbounds();
@@ -52,6 +56,15 @@ const formOpen = ref(false);
const formMode = ref('add');
const formDbInbound = ref(null);
+// === Client modal (single + bulk) =====================================
+const clientOpen = ref(false);
+const clientMode = ref('add');
+const clientDbInbound = ref(null);
+const clientIndex = ref(null);
+
+const bulkOpen = ref(false);
+const bulkDbInbound = ref(null);
+
function onAddInbound() {
formMode.value = 'add';
formDbInbound.value = null;
@@ -64,6 +77,18 @@ function openEdit(dbInbound) {
formOpen.value = true;
}
+function openAddClient(dbInbound) {
+ clientMode.value = 'add';
+ clientDbInbound.value = dbInbound;
+ clientIndex.value = null;
+ clientOpen.value = true;
+}
+
+function openAddBulkClient(dbInbound) {
+ bulkDbInbound.value = dbInbound;
+ bulkOpen.value = true;
+}
+
// Per-row destructive actions go through Modal.confirm (matches legacy).
function confirmDelete(dbInbound) {
Modal.confirm({
@@ -173,6 +198,12 @@ function onRowAction({ key, dbInbound }) {
case 'edit':
openEdit(dbInbound);
break;
+ case 'addClient':
+ openAddClient(dbInbound);
+ break;
+ case 'addBulkClient':
+ openAddBulkClient(dbInbound);
+ break;
case 'delete':
confirmDelete(dbInbound);
break;
@@ -295,6 +326,25 @@ function onRowAction({ key, dbInbound }) {
:db-inbound="formDbInbound"
@saved="refresh"
/>
+
+