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]