web-app/src/app/routes/alert/alert-center/alert-center.component.ts (226 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN } from '@delon/theme';
import { NzModalService } from 'ng-zorro-antd/modal';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { GroupAlert } from '../../../pojo/GroupAlert';
import { AlertService } from '../../../service/alert.service';
interface ExtendedGroupAlert extends GroupAlert {
isNew?: boolean;
}
@Component({
selector: 'app-alert-center',
templateUrl: './alert-center.component.html',
styleUrl: './alert-center.component.less'
})
export class AlertCenterComponent implements OnInit, OnDestroy {
constructor(
private notifySvc: NzNotificationService,
private modal: NzModalService,
private alertSvc: AlertService,
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService
) {}
pageIndex: number = 1;
pageSize: number = 8;
total: number = 0;
groupAlerts: ExtendedGroupAlert[] = [];
tableLoading: boolean = false;
checkedAlertIds = new Set<number>();
filterStatus!: string;
filterContent: string | undefined;
private eventSource!: EventSource;
ngOnInit(): void {
this.loadAlertsTable();
this.initSSESubscription();
}
ngOnDestroy(): void {
if (this.eventSource) {
this.eventSource.close();
}
}
// Initialize SSE subscription for real-time alerts
private initSSESubscription(): void {
this.eventSource = new EventSource('/api/alert/sse/subscribe');
this.eventSource.addEventListener('ALERT_EVENT', (evt: MessageEvent) => {
try {
const newAlert: GroupAlert = JSON.parse(evt.data);
this.updateAlertList(newAlert);
} catch (error) {
console.error('Error parsing SSE data:', error);
}
});
// Handle SSE errors
this.eventSource.onerror = error => {
console.error('SSE connection error:', error);
this.eventSource.close();
};
}
private updateAlertList(newAlert: GroupAlert): void {
const extendedAlert: ExtendedGroupAlert = {
...newAlert,
isNew: true
};
if (!extendedAlert.alerts) {
extendedAlert.alerts = [];
}
const matchesFilter = this.checkAlertMatchesFilter(extendedAlert);
if (!matchesFilter) {
return;
}
const existingIndex = this.groupAlerts.findIndex(a => a.id === extendedAlert.id);
if (existingIndex === -1) {
this.groupAlerts = [extendedAlert, ...this.groupAlerts];
this.total += 1;
setTimeout(() => {
const index = this.groupAlerts.findIndex(a => a.id === extendedAlert.id);
if (index !== -1) {
this.groupAlerts[index].isNew = false;
// 触发变更检测
this.groupAlerts = [...this.groupAlerts];
}
}, 1000);
} else {
this.groupAlerts[existingIndex] = {
...extendedAlert,
isNew: true
};
setTimeout(() => {
if (this.groupAlerts[existingIndex]) {
this.groupAlerts[existingIndex].isNew = false;
this.groupAlerts = [...this.groupAlerts];
}
}, 1000);
this.groupAlerts = [...this.groupAlerts];
}
}
private checkAlertMatchesFilter(alert: ExtendedGroupAlert): boolean {
if (this.filterStatus && alert.status !== this.filterStatus) {
return false;
}
if (this.filterContent) {
const searchContent = this.filterContent.toLowerCase();
const hasMatchingContent = alert.alerts?.some(singleAlert => singleAlert.content?.toLowerCase().includes(searchContent));
const hasMatchingLabels = Object.entries(alert.groupLabels || {}).some(
([key, value]) => key.toLowerCase().includes(searchContent) || value.toLowerCase().includes(searchContent)
);
if (!hasMatchingContent && !hasMatchingLabels) {
return false;
}
}
return true;
}
loadAlertsTable() {
this.tableLoading = true;
let alertsInit$ = this.alertSvc.loadGroupAlerts(this.filterStatus, this.filterContent, this.pageIndex - 1, this.pageSize).subscribe(
message => {
if (message.code === 0) {
let page = message.data;
this.groupAlerts = page.content;
this.groupAlerts.forEach(alert => {
if (alert.alerts == undefined) {
alert.alerts = [];
}
});
this.pageIndex = page.number + 1;
this.total = page.totalElements;
} else {
console.warn(message.msg);
}
this.tableLoading = false;
alertsInit$.unsubscribe();
},
error => {
this.tableLoading = false;
alertsInit$.unsubscribe();
console.error(error.msg);
}
);
}
onDeleteAlerts() {
if (this.checkedAlertIds == null || this.checkedAlertIds.size === 0) {
this.notifySvc.warning(this.i18nSvc.fanyi('common.notify.no-select-delete'), '');
return;
}
this.modal.confirm({
nzTitle: this.i18nSvc.fanyi('common.confirm.delete-batch'),
nzOkText: this.i18nSvc.fanyi('common.button.ok'),
nzCancelText: this.i18nSvc.fanyi('common.button.cancel'),
nzOkDanger: true,
nzOkType: 'primary',
nzClosable: false,
nzOnOk: () => this.deleteAlerts(this.checkedAlertIds)
});
}
onMarkReadAlerts() {
if (this.checkedAlertIds == null || this.checkedAlertIds.size === 0) {
this.notifySvc.warning(this.i18nSvc.fanyi('alert.center.notify.no-mark'), '');
return;
}
this.modal.confirm({
nzTitle: this.i18nSvc.fanyi('alert.center.confirm.mark-done-batch'),
nzOkText: this.i18nSvc.fanyi('common.button.ok'),
nzCancelText: this.i18nSvc.fanyi('common.button.cancel'),
nzOkDanger: true,
nzOkType: 'primary',
nzClosable: false,
nzOnOk: () => this.updateAlertsStatus(this.checkedAlertIds, 'resolved')
});
}
onMarkUnReadAlerts() {
if (this.checkedAlertIds == null || this.checkedAlertIds.size === 0) {
this.notifySvc.warning(this.i18nSvc.fanyi('alert.center.notify.no-mark'), '');
return;
}
this.modal.confirm({
nzTitle: this.i18nSvc.fanyi('alert.center.confirm.mark-no-batch'),
nzOkText: this.i18nSvc.fanyi('common.button.ok'),
nzCancelText: this.i18nSvc.fanyi('common.button.cancel'),
nzOkDanger: true,
nzOkType: 'primary',
nzClosable: false,
nzOnOk: () => this.updateAlertsStatus(this.checkedAlertIds, 'firing')
});
}
onDeleteOneAlert(alertId: number) {
let alerts = new Set<number>();
alerts.add(alertId);
this.modal.confirm({
nzTitle: this.i18nSvc.fanyi('common.confirm.delete'),
nzOkText: this.i18nSvc.fanyi('common.button.ok'),
nzCancelText: this.i18nSvc.fanyi('common.button.cancel'),
nzOkDanger: true,
nzOkType: 'primary',
nzClosable: false,
nzOnOk: () => this.deleteAlerts(alerts)
});
}
onMarkReadOneAlert(alertId: number) {
let alerts = new Set<number>();
alerts.add(alertId);
this.updateAlertsStatus(alerts, 'resolved');
}
onMarkUnReadOneAlert(alertId: number) {
let alerts = new Set<number>();
alerts.add(alertId);
this.updateAlertsStatus(alerts, 'firing');
}
deleteAlerts(alertIds: Set<number>) {
this.tableLoading = true;
const deleteAlerts$ = this.alertSvc.deleteGroupAlerts(alertIds).subscribe(
message => {
deleteAlerts$.unsubscribe();
if (message.code === 0) {
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.delete-success'), '');
this.updatePageIndex(alertIds.size);
this.loadAlertsTable();
} else {
this.tableLoading = false;
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'), message.msg);
}
},
error => {
this.tableLoading = false;
deleteAlerts$.unsubscribe();
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.delete-fail'), error.msg);
}
);
}
updatePageIndex(delSize: number) {
const lastPage = Math.max(1, Math.ceil((this.total - delSize) / this.pageSize));
this.pageIndex = this.pageIndex > lastPage ? lastPage : this.pageIndex;
}
updateAlertsStatus(alertIds: Set<number>, status: string) {
this.tableLoading = true;
const markAlertsStatus$ = this.alertSvc.applyGroupAlertsStatus(alertIds, status).subscribe(
message => {
markAlertsStatus$.unsubscribe();
if (message.code === 0) {
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.mark-success'), '');
this.loadAlertsTable();
} else {
this.tableLoading = false;
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.mark-fail'), message.msg);
}
},
error => {
this.tableLoading = false;
markAlertsStatus$.unsubscribe();
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.mark-fail'), error.msg);
}
);
}
}