This is an automated email from the ASF dual-hosted git repository.

gongchao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/master by this push:
     new b9e569a1e [fix]: fix bugs in frontend for monitoring (#3806)
b9e569a1e is described below

commit b9e569a1e075441a7c0e49c4626adde3cb8f1303
Author: zhou yong kang <[email protected]>
AuthorDate: Mon Oct 13 23:54:11 2025 +0800

    [fix]: fix bugs in frontend for monitoring (#3806)
    
    Co-authored-by: aias00 <[email protected]>
    Co-authored-by: Tomsun28 <[email protected]>
---
 web-app/src/app/pojo/Monitor.ts                    |   4 +
 .../monitor/monitor-edit/monitor-edit.component.ts |  10 +-
 .../monitor-list/monitor-list.component.html       |  35 ++++--
 .../monitor-list/monitor-list.component.less       |  33 ++++++
 .../monitor/monitor-list/monitor-list.component.ts | 124 +++++++++++++++++++--
 web-app/src/assets/i18n/en-US.json                 |   3 +
 web-app/src/assets/i18n/zh-CN.json                 |   3 +
 7 files changed, 192 insertions(+), 20 deletions(-)

diff --git a/web-app/src/app/pojo/Monitor.ts b/web-app/src/app/pojo/Monitor.ts
index 619604034..03af662e6 100644
--- a/web-app/src/app/pojo/Monitor.ts
+++ b/web-app/src/app/pojo/Monitor.ts
@@ -39,4 +39,8 @@ export class Monitor {
   modifier!: string;
   gmtCreate!: number;
   gmtUpdate!: number;
+
+  _displayStatus?: 'ACTIVE' | 'DISAPPEARED' | 'GRACE_PERIOD';
+  _graceTimer?: any;
+  _disappearTime?: number;
 }
diff --git 
a/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts 
b/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
index 25f631737..73387aedc 100644
--- a/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
+++ b/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
@@ -104,7 +104,15 @@ export class MonitorEditComponent implements OnInit {
             }
           } else {
             console.warn(message.msg);
-            this.notifySvc.error(this.i18nSvc.fanyi('monitor.not-found'), 
message.msg);
+            if (message.code === 3) {
+              // MONITOR_NOT_EXIST_CODE = 0x03
+              
this.notifySvc.warning(this.i18nSvc.fanyi('monitor.item.unavailable'), '');
+              setTimeout(() => {
+                this.router.navigateByUrl('/monitors');
+              }, 1500);
+            } else {
+              this.notifySvc.error(this.i18nSvc.fanyi('monitor.not-found'), 
message.msg);
+            }
             return throwError(this.i18nSvc.fanyi('monitor.not-found'));
           }
           return this.appDefineSvc.getAppParamsDefine(this.monitor.app);
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 575a9a49c..da7afe0f2 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
@@ -155,19 +155,32 @@
 
   <nz-spin [nzSpinning]="tableLoading">
     <div class="monitor-card-list-content">
-      <div class="monitor-card" *ngFor="let data of monitors">
+      <div
+        class="monitor-card"
+        *ngFor="let data of monitors"
+        [ngClass]="getMonitorDisplayClass(data)"
+        [class.monitor-unavailable]="isMonitorDisabled(data)"
+      >
         <div class="monitor-card-checkbox">
-          <label nz-checkbox [ngModel]="checkedMonitorIds.has(data.id)" 
(ngModelChange)="onItemChecked(data.id, $event)"></label>
+          <label
+            nz-checkbox
+            [ngModel]="checkedMonitorIds.has(data.id)"
+            [nzDisabled]="isMonitorDisabled(data)"
+            (ngModelChange)="onItemChecked(data.id, $event)"
+          ></label>
         </div>
         <div class="monitor-card-content">
           <div class="monitor-card-header">
             <div class="monitor-card-title">
-              <button nz-button nzType="link" [routerLink]="['/monitors/' + 
data.id]">
+              <button nz-button nzType="link" [routerLink]="['/monitors/' + 
data.id]" [disabled]="isMonitorDisabled(data)">
                 <span nz-icon nzType="area-chart"></span>
                 {{ data.name }}
+                <nz-tag *ngIf="isMonitorDisabled(data)" nzColor="orange" 
style="margin-left: 8px">
+                  {{ 'monitor.status.unavailable' | i18n }}
+                </nz-tag>
               </button>
             </div>
-            <div class="monitor-card-status">
+            <div class="monitor-card-status" 
[class.status-faded]="isMonitorDisabled(data)">
               <nz-tag *ngIf="data.status == 0" [nzColor]="'#b2b2b2'">
                 <i nz-icon nzType="meh" nzTheme="outline"></i>
                 <span>{{ 'monitor.status.paused' | i18n }}</span>
@@ -202,7 +215,7 @@
                 {{ 'monitor.scrape.type.' + data.scrape | i18n }}
               </button>
 
-              <button nz-button nz-dropdown [nzDropdownMenu]="actionMenu" 
class="action-button">
+              <button nz-button nz-dropdown [nzDropdownMenu]="actionMenu" 
class="action-button" [disabled]="isMonitorDisabled(data)">
                 <span nz-icon nzType="ellipsis"></span>
               </button>
             </div>
@@ -237,39 +250,39 @@
           <nz-dropdown-menu #actionMenu="nzDropdownMenu">
             <ul nz-menu>
               <li nz-menu-item>
-                <button nz-button [routerLink]="['/monitors/' + data.id]">
+                <button nz-button [routerLink]="['/monitors/' + data.id]" 
[disabled]="isMonitorDisabled(data)">
                   <i nz-icon nzType="area-chart"></i>
                   {{ 'monitor.detail' | i18n }}
                 </button>
               </li>
               <li nz-menu-item>
-                <button nz-button (click)="onEditOneMonitor(data.id)">
+                <button nz-button (click)="onEditOneMonitor(data.id)" 
[disabled]="isMonitorDisabled(data)">
                   <i nz-icon nzType="edit"></i>
                   {{ 'monitor.edit-monitor' | i18n }}
                 </button>
               </li>
               <li nz-menu-item>
-                <button nz-button (click)="copyMonitor(data.id)">
+                <button nz-button (click)="copyMonitor(data.id)" 
[disabled]="isMonitorDisabled(data)">
                   <i nz-icon nzType="copy"></i>
                   {{ 'monitor.copy' | i18n }}
                 </button>
               </li>
               <li nz-menu-divider></li>
               <li nz-menu-item *ngIf="data.status == 0">
-                <button nz-button (click)="onEnableManageOneMonitor(data.id)">
+                <button nz-button (click)="onEnableManageOneMonitor(data.id)" 
[disabled]="isMonitorDisabled(data)">
                   <i nz-icon nzType="play-circle"></i>
                   {{ 'monitor.enable' | i18n }}
                 </button>
               </li>
               <li nz-menu-item *ngIf="data.status != 0">
-                <button nz-button (click)="onCancelManageOneMonitor(data.id)">
+                <button nz-button (click)="onCancelManageOneMonitor(data.id)" 
[disabled]="isMonitorDisabled(data)">
                   <i nz-icon nzType="pause-circle"></i>
                   {{ 'monitor.cancel' | i18n }}
                 </button>
               </li>
               <li nz-menu-divider></li>
               <li nz-menu-item>
-                <button nz-button nzDanger 
(click)="onDeleteOneMonitor(data.id)">
+                <button nz-button nzDanger 
(click)="onDeleteOneMonitor(data.id)" [disabled]="isMonitorDisabled(data)">
                   <i nz-icon nzType="delete"></i>
                   {{ 'monitor.delete-monitor' | i18n }}
                 </button>
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 49b73f78d..7817988a0 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
@@ -313,3 +313,36 @@
     }
   }
 }
+
+
+.monitor-unavailable {
+  opacity: 0.6;
+  transition: opacity 0.3s ease;
+  
+  .monitor-card-content {
+    filter: grayscale(50%);
+  }
+}
+
+.monitor-disappeared {
+  opacity: 0.7;
+  background-color: #f5f5f5;
+  border: 1px dashed #d9d9d9;
+  transition: all 0.3s ease;
+}
+
+.status-faded {
+  opacity: 0.5;
+}
+
+.monitor-card.monitor-unavailable {
+  .action-button[disabled] {
+    opacity: 0.3;
+    cursor: not-allowed;
+  }
+  
+  button[disabled] {
+    opacity: 0.4;
+    cursor: not-allowed;
+  }
+}
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 cd22449cd..56135b37d 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
@@ -73,6 +73,9 @@ export class MonitorListComponent implements OnInit, 
OnDestroy {
   currentSortField: string | null = null;
   currentSortOrder: string | null = null;
 
+  private previousMonitors: Monitor[] = [];
+  private readonly GRACE_PERIOD_MS = 5000;
+
   switchExportTypeModalFooter: ModalButtonOptions[] = [
     { label: this.i18nSvc.fanyi('common.button.cancel'), type: 'default', 
onClick: () => (this.isSwitchExportTypeModalVisible = false) }
   ];
@@ -110,6 +113,14 @@ export class MonitorListComponent implements OnInit, 
OnDestroy {
     if (this.intervalId) {
       clearInterval(this.intervalId);
     }
+
+    if (this.previousMonitors) {
+      this.previousMonitors.forEach(monitor => {
+        if (monitor._graceTimer) {
+          clearTimeout(monitor._graceTimer);
+        }
+      });
+    }
   }
 
   onAppChanged(): void {
@@ -178,7 +189,7 @@ export class MonitorListComponent implements OnInit, 
OnDestroy {
           this.checkedMonitorIds.clear();
           if (message.code === 0) {
             let page = message.data;
-            this.monitors = page.content;
+            this.monitors = this.reconcileMonitorStates(page.content);
             this.pageIndex = page.number + 1;
             this.total = page.totalElements;
           } else {
@@ -203,7 +214,7 @@ export class MonitorListComponent implements OnInit, 
OnDestroy {
           this.checkedMonitorIds.clear();
           if (message.code === 0) {
             let page = message.data;
-            this.monitors = page.content;
+            this.monitors = this.reconcileMonitorStates(page.content);
             this.pageIndex = page.number + 1;
             this.total = page.totalElements;
           } else {
@@ -446,13 +457,23 @@ export class MonitorListComponent implements OnInit, 
OnDestroy {
           this.loadMonitorTable();
         } else {
           this.tableLoading = false;
-          
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.cancel-fail'), 
message.msg);
+          if (message.code === 3) {
+            
this.notifySvc.warning(this.i18nSvc.fanyi('monitor.item.unavailable.refresh'), 
'');
+            this.loadMonitorTable();
+          } else {
+            
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.cancel-fail'), 
message.msg);
+          }
         }
       },
       error => {
         this.tableLoading = false;
         cancelManage$.unsubscribe();
-        this.notifySvc.error(this.i18nSvc.fanyi('common.notify.cancel-fail'), 
error.msg);
+        if (error.status === 404) {
+          
this.notifySvc.warning(this.i18nSvc.fanyi('monitor.item.unavailable.refresh'), 
'');
+          this.loadMonitorTable();
+        } else {
+          
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.cancel-fail'), 
error.msg);
+        }
       }
     );
   }
@@ -497,13 +518,24 @@ export class MonitorListComponent implements OnInit, 
OnDestroy {
           this.loadMonitorTable();
         } else {
           this.tableLoading = false;
-          
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.enable-fail'), 
message.msg);
+          if (message.code === 3) {
+            
this.notifySvc.warning(this.i18nSvc.fanyi('monitor.item.unavailable.refresh'), 
'');
+            this.loadMonitorTable();
+          } else {
+            
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.enable-fail'), 
message.msg);
+          }
         }
       },
       error => {
         this.tableLoading = false;
         enableManage$.unsubscribe();
-        this.notifySvc.error(this.i18nSvc.fanyi('common.notify.enable-fail'), 
error.msg);
+        // 检查是否是404错误
+        if (error.status === 404) {
+          
this.notifySvc.warning(this.i18nSvc.fanyi('monitor.item.unavailable.refresh'), 
'');
+          this.loadMonitorTable();
+        } else {
+          
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.enable-fail'), 
error.msg);
+        }
       }
     );
   }
@@ -614,12 +646,88 @@ export class MonitorListComponent implements OnInit, 
OnDestroy {
           this.notifySvc.success(this.i18nSvc.fanyi('monitor.copy.success'), 
'');
           this.loadMonitorTable();
         } else {
-          this.notifySvc.error(this.i18nSvc.fanyi('monitor.copy.failed'), 
message.msg);
+          if (message.code === 3) {
+            
this.notifySvc.warning(this.i18nSvc.fanyi('monitor.item.unavailable.refresh'), 
'');
+            this.loadMonitorTable();
+          } else {
+            this.notifySvc.error(this.i18nSvc.fanyi('monitor.copy.failed'), 
message.msg);
+          }
         }
       },
       error => {
-        this.notifySvc.error(this.i18nSvc.fanyi('monitor.copy.failed'), 
error.msg);
+        if (error.status === 404) {
+          
this.notifySvc.warning(this.i18nSvc.fanyi('monitor.item.unavailable.refresh'), 
'');
+          this.loadMonitorTable();
+        } else {
+          this.notifySvc.error(this.i18nSvc.fanyi('monitor.copy.failed'), 
error.msg);
+        }
       }
     );
   }
+
+  private reconcileMonitorStates(newMonitors: Monitor[]): Monitor[] {
+    if (!this.previousMonitors || this.previousMonitors.length === 0) {
+      const processedMonitors = newMonitors.map(monitor => ({
+        ...monitor,
+        _displayStatus: 'ACTIVE' as const
+      }));
+      this.previousMonitors = [...processedMonitors];
+      return processedMonitors;
+    }
+    const newMonitorMap = new Map(newMonitors.map(m => [m.id, m]));
+    const previousMonitorMap = new Map(this.previousMonitors.map(m => [m.id, 
m]));
+    const reconciledMonitors: Monitor[] = [];
+
+    newMonitors.forEach(newMonitor => {
+      const previousMonitor = previousMonitorMap.get(newMonitor.id);
+      if (previousMonitor) {
+        if (previousMonitor._graceTimer) {
+          clearTimeout(previousMonitor._graceTimer);
+        }
+        reconciledMonitors.push({
+          ...newMonitor,
+          _displayStatus: 'ACTIVE' as const
+        });
+      } else {
+        reconciledMonitors.push({
+          ...newMonitor,
+          _displayStatus: 'ACTIVE' as const
+        });
+      }
+    });
+
+    this.previousMonitors.forEach(previousMonitor => {
+      if (!newMonitorMap.has(previousMonitor.id)) {
+        if (previousMonitor._displayStatus === 'DISAPPEARED') {
+          reconciledMonitors.push(previousMonitor);
+        } else {
+          const disappearedMonitor = {
+            ...previousMonitor,
+            _displayStatus: 'DISAPPEARED' as const,
+            _disappearTime: Date.now()
+          };
+
+          disappearedMonitor._graceTimer = setTimeout(() => {
+            this.monitors = this.monitors.filter(m => m.id !== 
disappearedMonitor.id);
+          }, this.GRACE_PERIOD_MS);
+
+          reconciledMonitors.push(disappearedMonitor);
+        }
+      }
+    });
+
+    this.previousMonitors = [...reconciledMonitors];
+    return reconciledMonitors;
+  }
+
+  isMonitorDisabled(monitor: Monitor): boolean {
+    return monitor._displayStatus === 'DISAPPEARED';
+  }
+
+  getMonitorDisplayClass(monitor: Monitor): string {
+    if (monitor._displayStatus === 'DISAPPEARED') {
+      return 'monitor-disappeared';
+    }
+    return '';
+  }
 }
diff --git a/web-app/src/assets/i18n/en-US.json 
b/web-app/src/assets/i18n/en-US.json
index 70cb93e73..e05785581 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -877,6 +877,8 @@
   "monitor.new.notify.change-to-sftp": "SFTP has been enabled, and the port 
number has been automatically changed to 22. Please take note.",
   "monitor.new.success": "New Monitor Success",
   "monitor.not-found": "This Monitor Not Found",
+  "monitor.item.unavailable": "This monitor item is no longer available, 
possibly due to service being offline. Returning to list page...",
+  "monitor.item.unavailable.refresh": "This monitor item is no longer 
available, the list will refresh automatically",
   "monitor.path.tip": "Exporter Url Endpoint Path",
   "monitor.payload.tip": "Available When POST PUT",
   "monitor.privateKey.tip": "BEGIN RSA PRIVATE KEY",
@@ -891,6 +893,7 @@
   "monitor.status.paused": "Paused",
   "monitor.status.unreachable": "Unreachable",
   "monitor.status.up": "Up",
+  "monitor.status.unavailable": "Unavailable",
   "monitor.total": "Total",
   "monitor.uri.tip": "Website uri path(no ip port) EG:/console",
   "monitor.url.tip": "service:jmx:rmi:///jndi/rmi://host:port/jmxrmi",
diff --git a/web-app/src/assets/i18n/zh-CN.json 
b/web-app/src/assets/i18n/zh-CN.json
index 7f36e84a8..aea4f10c9 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -880,6 +880,8 @@
   "monitor.new.notify.change-to-sftp": "已开启SFTP,端口号自动更改为 22,请留意。",
   "monitor.new.success": "新增监控成功",
   "monitor.not-found": "查询异常,此监控不存在",
+  "monitor.item.unavailable": "该监控项已不可用,可能是由于服务已下线。正在返回列表页面...",
+  "monitor.item.unavailable.refresh": "该监控项已不可用,列表将自动刷新",
   "monitor.path.tip": "导出器Url端点路径",
   "monitor.payload.tip": "可用于 POST PUT",
   "monitor.privateKey.tip": "启动RSA私钥",
@@ -894,6 +896,7 @@
   "monitor.status.paused": "暂停",
   "monitor.status.unreachable": "不可达",
   "monitor.status.up": "正常",
+  "monitor.status.unavailable": "暂不可用",
   "monitor.total": "总量",
   "monitor.uri.tip": "网站 uri 路径(无 ip 端口) EG:/console",
   "monitor.url.tip": "服务:jmx:rmi:///jndi/rmi://host:port/jmxrmi",


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to