From 3e8a0eb93eebc657fe0a10f7c49888ce286c06ee Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 11 May 2026 09:05:47 +0200 Subject: [PATCH] fix(inbounds): paginate expanded client list, restore ID column, hide empty Remark - ClientRowTable now applies the General-Settings pageSize to its expanded client list. The 3.0 rewrite dropped pagination, so users with thousands of clients per inbound hit a 30-60s browser hang on expand (#4233). - ID column was marked responsive: ['xs'] so it was hidden on desktop; removed the restriction so it shows as the first column everywhere. - Remark column is now omitted entirely when no inbound has a non-empty remark, matching the existing Node-column pattern. --- .../src/pages/inbounds/ClientRowTable.vue | 29 +++++++++++++++++-- frontend/src/pages/inbounds/InboundList.vue | 14 +++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/inbounds/ClientRowTable.vue b/frontend/src/pages/inbounds/ClientRowTable.vue index 3659d643..67b19966 100644 --- a/frontend/src/pages/inbounds/ClientRowTable.vue +++ b/frontend/src/pages/inbounds/ClientRowTable.vue @@ -31,6 +31,7 @@ const props = defineProps({ onlineClients: { type: Array, default: () => [] }, lastOnlineMap: { type: Object, default: () => ({}) }, isDarkTheme: { type: Boolean, default: false }, + pageSize: { type: Number, default: 0 }, }); const emit = defineEmits([ @@ -46,6 +47,20 @@ const emit = defineEmits([ const inbound = computed(() => props.dbInbound.toInbound()); const clients = computed(() => inbound.value?.clients || []); +const currentPage = ref(1); +const paginatedClients = computed(() => { + if (!props.pageSize || props.pageSize <= 0) return clients.value; + const start = (currentPage.value - 1) * props.pageSize; + return clients.value.slice(start, start + props.pageSize); +}); + +watch([clients, () => props.pageSize], () => { + const total = clients.value.length; + const size = props.pageSize > 0 ? props.pageSize : (total || 1); + const maxPage = Math.max(1, Math.ceil(total / size)); + if (currentPage.value > maxPage) currentPage.value = maxPage; +}); + // === Per-client stats lookup ======================================= const statsMap = computed(() => { const m = new Map(); @@ -246,7 +261,7 @@ function confirmBulkDelete() {
{{ t('pages.inbounds.expireDate') }}
-
+ +
@@ -687,6 +706,12 @@ function confirmBulkDelete() { padding: 0 !important; } +.client-list-pagination { + display: flex; + justify-content: center; + padding: 10px 16px 4px; +} + /* ===== Mobile card list =========================================== */ .client-list.is-mobile { display: flex; diff --git a/frontend/src/pages/inbounds/InboundList.vue b/frontend/src/pages/inbounds/InboundList.vue index 6841ed3a..88b39bc8 100644 --- a/frontend/src/pages/inbounds/InboundList.vue +++ b/frontend/src/pages/inbounds/InboundList.vue @@ -122,13 +122,19 @@ const visibleInbounds = computed(() => { // `key`-driven so we can render via the body-cell slot below. AD-Vue 4's // `responsive` array still works on column defs. Computed so column // labels react to live locale switches. +const hasAnyRemark = computed(() => + props.dbInbounds.some((i) => typeof i?.remark === 'string' && i.remark.trim() !== ''), +); + const desktopColumns = computed(() => { const cols = [ - { title: 'ID', dataIndex: 'id', key: 'id', align: 'right', width: 30, responsive: ['xs'] }, + { title: 'ID', dataIndex: 'id', key: 'id', align: 'right', width: 30 }, { title: t('pages.inbounds.operate'), key: 'action', align: 'center', width: 30 }, { title: t('pages.inbounds.enable'), key: 'enable', align: 'center', width: 35 }, - { title: t('pages.inbounds.remark'), dataIndex: 'remark', key: 'remark', align: 'center', width: 60 }, ]; + if (hasAnyRemark.value) { + cols.push({ title: t('pages.inbounds.remark'), dataIndex: 'remark', key: 'remark', align: 'center', width: 60 }); + } if (props.nodesById.size > 0) { cols.push({ title: t('pages.inbounds.node'), key: 'node', align: 'center', width: 60 }); } @@ -401,6 +407,7 @@ function showQrCodeMenu(dbInbound) {