diff --git a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html index 38fa55f5489..0990366e091 100644 --- a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html +++ b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html @@ -140,19 +140,6 @@
-
-
- - -
-
-
+ +
{{ 'common.total' | i18n }} {{ total }} diff --git a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.less b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.less index 9a288de84bc..a47dbb8485d 100644 --- a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.less +++ b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.less @@ -86,19 +86,27 @@ position: relative; } -.monitor-list-header { +.monitor-selected-count { + color: rgba(0, 0, 0, 0.45); + font-size: 12px; +} + +.monitor-list-footer { display: flex; - align-items: center; justify-content: space-between; - margin-bottom: 16px; + align-items: center; + margin-top: 16px; padding: 8px 0; flex-wrap: wrap; gap: 8px; } -.monitor-header-actions { +.monitor-footer-actions { display: flex; align-items: center; + gap: 8px; + // Align the select-all checkbox with the per-row checkboxes, which sit at the card's 12px padding. + padding-left: 12px; } .monitor-card-list-content { diff --git a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts index ef58fd6afd3..2ab34431793 100644 --- a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts +++ b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts @@ -53,7 +53,8 @@ export class MonitorListComponent implements OnInit, OnDestroy { app!: string | undefined; labels!: string | undefined; pageIndex: number = 1; - pageSize: number = 8; + pageSize: number = 10; + pageSizeOptions: number[] = [10, 20, 50]; total: number = 0; monitors!: Monitor[]; tableLoading: boolean = true; @@ -98,7 +99,7 @@ export class MonitorListComponent implements OnInit, OnDestroy { this.app = undefined; } this.pageIndex = 1; - this.pageSize = 8; + this.pageSize = 10; this.checkedMonitorIds = new Set(); this.tableLoading = true; this.loadMonitorTable(); @@ -134,7 +135,7 @@ export class MonitorListComponent implements OnInit, OnDestroy { message => { filter$.unsubscribe(); this.tableLoading = false; - this.checkedAll = false; + // Filtering changes the result set, so the previous selection no longer applies. this.checkedMonitorIds.clear(); if (message.code === 0) { let page = message.data; @@ -180,8 +181,7 @@ export class MonitorListComponent implements OnInit, OnDestroy { .subscribe( message => { this.tableLoading = false; - this.checkedAll = false; - this.checkedMonitorIds.clear(); + // Keep the selection across reloads (pagination, auto-refresh, post-CRUD) so multi-page checks persist (#4164). if (message.code === 0) { let page = message.data; this.monitors = reconcile ? this.reconcileMonitorStates(page.content) : this.resetMonitorStates(page.content); @@ -205,8 +205,7 @@ export class MonitorListComponent implements OnInit, OnDestroy { .subscribe( message => { this.tableLoading = false; - this.checkedAll = false; - this.checkedMonitorIds.clear(); + // Keep the selection across pagination so checks made on other pages persist (#4164). if (message.code === 0) { let page = message.data; // Pagination changes the result set, so reconcile here would flag the previous page as "disappeared" (#4156). @@ -305,6 +304,7 @@ export class MonitorListComponent implements OnInit, OnDestroy { deleteMonitors$.unsubscribe(); if (message.code === 0) { this.notifySvc.success(this.i18nSvc.fanyi('common.notify.delete-success'), ''); + monitors.forEach(id => this.checkedMonitorIds.delete(id)); this.updatePageIndex(monitors.size); this.loadMonitorTable(); } else { @@ -450,6 +450,7 @@ export class MonitorListComponent implements OnInit, OnDestroy { cancelManage$.unsubscribe(); if (message.code === 0) { this.notifySvc.success(this.i18nSvc.fanyi('common.notify.cancel-success'), ''); + monitors.forEach(id => this.checkedMonitorIds.delete(id)); this.loadMonitorTable(); } else { this.tableLoading = false; @@ -511,6 +512,7 @@ export class MonitorListComponent implements OnInit, OnDestroy { enableManage$.unsubscribe(); if (message.code === 0) { this.notifySvc.success(this.i18nSvc.fanyi('common.notify.enable-success'), ''); + monitors.forEach(id => this.checkedMonitorIds.delete(id)); this.loadMonitorTable(); } else { this.tableLoading = false; @@ -537,14 +539,28 @@ export class MonitorListComponent implements OnInit, OnDestroy { } // begin: List multiple choice paging - checkedAll: boolean = false; + // Reflects only the current page: checked when every selectable monitor on this page is selected. + get checkedAll(): boolean { + const selectable = this.monitors?.filter(monitor => !this.isMonitorDisabled(monitor)) ?? []; + return selectable.length > 0 && selectable.every(monitor => this.checkedMonitorIds.has(monitor.id)); + } onAllChecked(checked: boolean) { - if (checked) { - this.monitors.forEach(monitor => this.checkedMonitorIds.add(monitor.id)); - } else { - this.checkedMonitorIds.clear(); - } + // Only toggle the current page's selectable monitors, leaving selections on other pages untouched (#4164). + this.monitors + ?.filter(monitor => !this.isMonitorDisabled(monitor)) + .forEach(monitor => { + if (checked) { + this.checkedMonitorIds.add(monitor.id); + } else { + this.checkedMonitorIds.delete(monitor.id); + } + }); + } + + // Clear the whole cross-page selection in one click, so users aren't forced to page back to deselect (#4164). + clearSelection() { + this.checkedMonitorIds.clear(); } onItemChecked(monitorId: number, checked: boolean) { @@ -566,6 +582,12 @@ export class MonitorListComponent implements OnInit, OnDestroy { this.changeMonitorTable(this.currentSortField, this.currentSortOrder); } + onPageSizeChange(pageSize: number) { + this.pageSize = pageSize; + this.pageIndex = 1; + this.changeMonitorTable(this.currentSortField, this.currentSortOrder); + } + // begin: app type search filter onSearchAppClicked() { diff --git a/web-app/src/assets/i18n/en-US.json b/web-app/src/assets/i18n/en-US.json index f3a8781df1f..b4ad4a66770 100644 --- a/web-app/src/assets/i18n/en-US.json +++ b/web-app/src/assets/i18n/en-US.json @@ -863,6 +863,9 @@ "monitor.edit.success": "Update Monitor Success", "monitor.enable": "Enable", "monitor.export": "Export Selected", + "monitor.selected-count": "{{count}} selected", + "monitor.clear-selection": "Clear", + "monitor.select-current-page": "Select this page", "monitor.export-all": "Export All", "monitor.export.switch-type": "Please select the export file format", "monitor.export.use-type": "Export selected monitors in {{type}} format", diff --git a/web-app/src/assets/i18n/ja-JP.json b/web-app/src/assets/i18n/ja-JP.json index 1d0fc787796..ad171604595 100644 --- a/web-app/src/assets/i18n/ja-JP.json +++ b/web-app/src/assets/i18n/ja-JP.json @@ -822,6 +822,9 @@ "monitor.edit.success": "モニターの更新に成功しました", "monitor.enable": "モニターを再開", "monitor.export": "モニターをエクスポート", + "monitor.selected-count": "{{count}} 件選択中", + "monitor.clear-selection": "クリア", + "monitor.select-current-page": "現在のページを全選択", "monitor.export.switch-type": "エクスポートファイル形式を選択してください!", "monitor.export.use-type": "{{type}}ファイル形式でモニターをエクスポート", "monitor.grafana.enabled.label": "Grafanaを有効化", diff --git a/web-app/src/assets/i18n/pt-BR.json b/web-app/src/assets/i18n/pt-BR.json index c6d1e0da487..72bff765b1a 100644 --- a/web-app/src/assets/i18n/pt-BR.json +++ b/web-app/src/assets/i18n/pt-BR.json @@ -634,6 +634,9 @@ "monitor.edit.success": "Monitor modificado com sucesso", "monitor.enable": "Retomar monitoramento", "monitor.export": "Exportar monitor", + "monitor.selected-count": "{{count}} selecionado(s)", + "monitor.clear-selection": "Limpar", + "monitor.select-current-page": "Selecionar esta página", "monitor.export.switch-type": "Selecione o formato do arquivo de exportação!", "monitor.export.use-type": "Exportar monitor no formato {{type}}", "monitor.grafana.enabled.label": "Habilitar Grafana", diff --git a/web-app/src/assets/i18n/zh-CN.json b/web-app/src/assets/i18n/zh-CN.json index 6d4a71744d4..768473d64fe 100644 --- a/web-app/src/assets/i18n/zh-CN.json +++ b/web-app/src/assets/i18n/zh-CN.json @@ -866,6 +866,9 @@ "monitor.edit.success": "修改监控成功", "monitor.enable": "恢复监控", "monitor.export": "导出所选", + "monitor.selected-count": "已选 {{count}} 项", + "monitor.clear-selection": "清空", + "monitor.select-current-page": "全选当前页", "monitor.export-all": "导出全部", "monitor.export.switch-type": "请选择导出文件格式!", "monitor.export.use-type": "以 {{type}} 文件格式导出所选监控", diff --git a/web-app/src/assets/i18n/zh-TW.json b/web-app/src/assets/i18n/zh-TW.json index ce8ee447ffc..a05dc1b7c5a 100644 --- a/web-app/src/assets/i18n/zh-TW.json +++ b/web-app/src/assets/i18n/zh-TW.json @@ -826,6 +826,9 @@ "monitor.edit.success": "修改監控成功", "monitor.enable": "恢復監控", "monitor.export": "匯出監控", + "monitor.selected-count": "已選 {{count}} 項", + "monitor.clear-selection": "清空", + "monitor.select-current-page": "全選當前頁", "monitor.export.switch-type": "請選擇匯出文件格式!", "monitor.export.use-type": "以 {{type}} 檔案格式匯出監控", "monitor.grafana.enabled.label": "啓用Grafana監控",