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監控",