Compare commits

...
10 changed files with 185 additions and 11 deletions
@@ -121,6 +121,14 @@ public class MonitorsController {
monitorService.export(ids, type, res);
}
@GetMapping("/export/all")
@Operation(summary = "export all monitor config", description = "export all monitor config")
public void exportAll(
@Parameter(description = "Export Type:JSON,EXCEL,YAML") @RequestParam(defaultValue = "JSON") String type,
HttpServletResponse res) throws Exception {
monitorService.exportAll(type, res);
}
@PostMapping("/import")
@Operation(summary = "import monitor config", description = "import monitor config")
public ResponseEntity<Message<Void>> export(MultipartFile file) throws Exception {
@@ -173,6 +173,15 @@ public interface MonitorService {
*/
void export(List<Long> ids, String type, HttpServletResponse res) throws Exception;
/**
* Export All Monitoring Configuration
*
* @param type file type
* @param res response
* @throws Exception This exception will be thrown if the export fails
*/
void exportAll(String type, HttpServletResponse res) throws Exception;
/**
* Import Monitoring Configuration
*
@@ -235,6 +235,18 @@ public class MonitorServiceImpl implements MonitorService {
imExportService.exportConfig(res.getOutputStream(), ids);
}
@Override
public void exportAll(String type, HttpServletResponse res) throws Exception {
// Get all monitor IDs from the database
List<Long> allMonitorIds = monitorDao.findAll()
.stream()
.map(Monitor::getId)
.collect(Collectors.toList());
// Use the existing export method to export all monitors
export(allMonitorIds, type, res);
}
@Override
public void importConfig(MultipartFile file) throws Exception {
var fileName = FileUtil.getFileName(file);
@@ -145,4 +145,18 @@ class MonitorsControllerTest {
.andExpect(jsonPath("$.code").value("0"))
.andExpect(jsonPath("$.msg").value("Import success"));
}
@Test
void exportAll() throws Exception {
String type = "JSON";
// Mock the behavior of monitorService.exportAll
doNothing().when(monitorService).exportAll(Mockito.anyString(), Mockito.any());
// Perform the request and verify the response
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/monitors/export/all")
.param("type", type))
.andExpect(status().isOk())
.andReturn();
}
}
@@ -745,6 +745,33 @@ class MonitorServiceTest {
when(monitorDao.findById(1L)).thenReturn(Optional.of(monitor));
when(paramDao.findParamsByMonitorId(1L)).thenReturn(params);
assertDoesNotThrow(() -> monitorService.copyMonitor(1L));
}
@Test
void exportAll() throws Exception {
// Create some test monitors
Monitor monitor1 = Monitor.builder().id(1L).name("test1").app("app1").build();
Monitor monitor2 = Monitor.builder().id(2L).name("test2").app("app2").build();
List<Monitor> allMonitors = List.of(monitor1, monitor2);
// Mock the behavior of monitorDao.findAll
when(monitorDao.findAll()).thenReturn(allMonitors);
// Create a mock HttpServletResponse
jakarta.servlet.http.HttpServletResponse mockResponse = org.mockito.Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
// Mock the ImExportService
org.apache.hertzbeat.manager.service.ImExportService mockImExportService = org.mockito.Mockito.mock(org.apache.hertzbeat.manager.service.ImExportService.class);
// Mock the getFileName method
when(mockImExportService.getFileName()).thenReturn("test.json");
// Set the field using reflection
java.lang.reflect.Field field = MonitorServiceImpl.class.getDeclaredField("imExportServiceMap");
field.setAccessible(true);
java.util.Map<String, org.apache.hertzbeat.manager.service.ImExportService> imExportServiceMap = new java.util.HashMap<>();
imExportServiceMap.put("JSON", mockImExportService);
field.set(monitorService, imExportServiceMap);
// Test the exportAll method
assertDoesNotThrow(() -> monitorService.exportAll("JSON", mockResponse));
}
}
@@ -77,6 +77,12 @@
{{ 'monitor.export' | i18n }}
</button>
</li>
<li nz-menu-item>
<button nz-button (click)="onExportAllMonitors()">
<i nz-icon nzType="export" nzTheme="outline"></i>
{{ 'monitor.export-all' | i18n }}
</button>
</li>
<li nz-menu-item>
<nz-upload nzAction="/monitors/import" [nzLimit]="1" [nzShowUploadList]="false" (nzChange)="onImportMonitors($event)">
<button nz-button>
@@ -287,7 +293,12 @@
>
<ng-container *nzModalContent>
<div class="export-type-container">
<div class="export-type-card" (click)="exportMonitors('JSON')" [class.loading]="exportJsonButtonLoading">
<div
class="export-type-card"
(click)="exportMonitors('JSON')"
[class.loading]="exportJsonButtonLoading"
*ngIf="checkedMonitorIds.size > 0"
>
<div class="export-type-icon">
<i nz-icon nzType="code" nzTheme="outline"></i>
</div>
@@ -296,7 +307,12 @@
<p>{{ 'monitor.export.use-type' | i18n : { type: 'JSON' } }}</p>
</div>
</div>
<div class="export-type-card" (click)="exportMonitors('EXCEL')" [class.loading]="exportExcelButtonLoading">
<div
class="export-type-card"
(click)="exportMonitors('EXCEL')"
[class.loading]="exportExcelButtonLoading"
*ngIf="checkedMonitorIds.size > 0"
>
<div class="export-type-icon">
<i nz-icon nzType="file-excel" nzTheme="outline"></i>
</div>
@@ -305,6 +321,34 @@
<p>{{ 'monitor.export.use-type' | i18n : { type: 'EXCEL' } }}</p>
</div>
</div>
<div
class="export-type-card"
(click)="exportAllMonitors('JSON')"
[class.loading]="exportJsonButtonLoading"
*ngIf="checkedMonitorIds.size === 0"
>
<div class="export-type-icon">
<i nz-icon nzType="code" nzTheme="outline"></i>
</div>
<div class="export-type-info">
<h3>JSON</h3>
<p>{{ 'monitor.export-all.use-type' | i18n : { type: 'JSON' } }}</p>
</div>
</div>
<div
class="export-type-card"
(click)="exportAllMonitors('EXCEL')"
[class.loading]="exportExcelButtonLoading"
*ngIf="checkedMonitorIds.size === 0"
>
<div class="export-type-icon">
<i nz-icon nzType="file-excel" nzTheme="outline"></i>
</div>
<div class="export-type-info">
<h3>EXCEL</h3>
<p>{{ 'monitor.export-all.use-type' | i18n : { type: 'EXCEL' } }}</p>
</div>
</div>
</div>
</ng-container>
</nz-modal>
@@ -264,6 +264,10 @@ export class MonitorListComponent implements OnInit, OnDestroy {
this.isSwitchExportTypeModalVisible = true;
}
onExportAllMonitors() {
this.isSwitchExportTypeModalVisible = true;
}
onImportMonitors(info: NzUploadChangeParam): void {
console.log(info.type);
if (info.type === 'start') {
@@ -362,6 +366,46 @@ export class MonitorListComponent implements OnInit, OnDestroy {
);
}
exportAllMonitors(type: string) {
switch (type) {
case 'JSON':
this.exportJsonButtonLoading = true;
break;
case 'EXCEL':
this.exportExcelButtonLoading = true;
break;
}
const exportAllMonitors$ = this.monitorSvc
.exportAllMonitors(type)
.pipe(
finalize(() => {
this.exportExcelButtonLoading = false;
this.exportJsonButtonLoading = false;
exportAllMonitors$.unsubscribe();
})
)
.subscribe(
response => {
const message = response.body!;
if (message.type == 'application/json') {
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.export-fail'), '');
} else {
const blob = new Blob([message], { type: response.headers.get('Content-Type')! });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.download = response.headers.get('Content-Disposition')!.split(';')[1].split('filename=')[1];
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
this.isSwitchExportTypeModalVisible = false;
}
},
error => {
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.export-fail'), error.msg);
}
);
}
onCancelManageMonitors() {
if (this.checkedMonitorIds == null || this.checkedMonitorIds.size === 0) {
this.notifySvc.warning(this.i18nSvc.fanyi('common.notify.no-select-cancel'), '');
@@ -30,6 +30,7 @@ const monitors_uri = '/monitors';
const detect_monitor_uri = '/monitor/detect';
const manage_monitors_uri = '/monitors/manage';
const export_monitors_uri = '/monitors/export';
const export_all_monitors_uri = '/monitors/export/all';
const summary_uri = '/summary';
const warehouse_storage_status_uri = '/warehouse/storage/status';
const grafana_dashboard_uri = '/grafana/dashboard';
@@ -74,6 +75,16 @@ export class MonitorService {
});
}
public exportAllMonitors(type: string): Observable<HttpResponse<Blob>> {
let httpParams = new HttpParams();
httpParams = httpParams.append('type', type);
return this.http.get(export_all_monitors_uri, {
params: httpParams,
observe: 'response',
responseType: 'blob'
});
}
public cancelManageMonitors(monitorIds: Set<number>): Observable<Message<any>> {
let httpParams = new HttpParams();
monitorIds.forEach(monitorId => {
+10 -7
View File
@@ -70,11 +70,11 @@
"alert.help.inhibit.link": "https://hertzbeat.apache.org/docs/help/alert_inhibit",
"alert.help.integration": "Unified management of alerts from different third-party platforms, integrating and receiving alert messages from third-party monitoring and observability systems, and performing actions such as grouping, aggregation, inhibition, silencing, and notification distribution.",
"alert.help.integration.link": "https://hertzbeat.apache.org",
"alert.help.notice": "Notification is used to config the receiver of alarm message and receiving method. The alarm message will be sent to the receiver by specified way(support email, discord, webhook etc). <a href='https://hertzbeat.apache.org/zh-cn/docs/help/alert_webhook'>Click here to see configuration steps.</a>.<br><i>Notice Template</i> is message content structure template. The built-in template is used by default or you can customize the template to customize the message notification structure.<br><span class='help_module_span'>Note⚠️: After configuring the <i>Receiver</i>, you also need to config the<i>Notice Policy</i>to specify which messages are sent to which receivers.</span><a href='https://hertzbeat.apache.org/docs/help/alert_email'> Click here to see potential issues</a>.",
"alert.help.notice": "Notification is used to config the receiver of alarm message and receiving method. The alarm message will be sent to the receiver by specified way(support email, discord, webhook etc). <a href='https://hertzbeat.apache.org/zh-cn/docs/help/alert_webhook'>Click here to see configuration steps.</a>.<br>\"<i>Notice Template</i>\" is message content structure template. The built-in template is used by default or you can customize the template to customize the message notification structure.<br><span class='help_module_span'>Note⚠️: After configuring the \"<i>Receiver</i>\", you also need to config the\"<i>Notice Policy</i>\"to specify which messages are sent to which receivers.</span><a href='https://hertzbeat.apache.org/docs/help/alert_email'> Click here to see potential issues</a>.",
"alert.help.notice.link": "https://hertzbeat.apache.org/docs/help/alert_email",
"alert.help.setting": "Threshold Rules are used for metrics alarm threshold rule management. Click the \"<i>New Threshold</i>\" to configure the alarm threshold for monitoring metrics. Hertzbeat will trigger alarms based on the threshold and metrics data.<br>Note⚠️: The alarm message that has been triggered can be checked in [Alter Center], and you can also set the notification method and personnel in [Notification].",
"alert.help.setting.link": "https://hertzbeat.apache.org/docs/help/alert_threshold",
"alert.help.silence": "Alarm Silence management is used when you dont want to be disturbed during system maintenance or on nights weekend. <br> Click \"<i>New Silence Strategy</i>\" and configure the time period to block messages so you would not get disturbed during breaks.",
"alert.help.silence": "Alarm Silence management is used when you don't want to be disturbed during system maintenance or on nights weekend. <br> Click \"<i>New Silence Strategy</i>\" and configure the time period to block messages so you would not get disturbed during breaks.",
"alert.help.silence.link": "https://hertzbeat.apache.org/docs",
"alert.inhibit.delete": "Delete Inhibit Rule",
"alert.inhibit.edit": "Edit Inhibit Rule",
@@ -701,10 +701,12 @@
"monitor.edit-monitor": "Edit Monitor",
"monitor.edit.failed": "Update Monitor Failed",
"monitor.edit.success": "Update Monitor Success",
"monitor.enable": "Resume Monitor",
"monitor.export": "Export Monitor",
"monitor.export.switch-type": "Please select the export file format!",
"monitor.export.use-type": "Export monitors in {{type}} file format",
"monitor.enable": "Enable",
"monitor.export": "Export Selected",
"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",
"monitor.export-all.use-type": "Export all monitors in {{type}} format",
"monitor.grafana.enabled.label": "Enable Grafana",
"monitor.grafana.enabled.tip": "is enabled, the monitoring data will be displayed in Grafana",
"monitor.grafana.upload.label": "Upload Grafana Template",
@@ -912,5 +914,6 @@
"ai.bot.greeting": "Hello! I am an AI assistant. How can I help you?",
"ai.bot.input.placeholder": "Please enter a question...",
"ai.bot.send": "Send",
"ai.bot.connect-fail": "Sorry, there was an issue connecting to the AI assistant. Please try again later."
"ai.bot.connect-fail": "Sorry, there was an issue connecting to the AI assistant. Please try again later.",
"monitor.help": "Monitoring and management page, you can check the metric data and manage monitoring tasks here. The status of normal service is"
}
+4 -2
View File
@@ -702,9 +702,11 @@
"monitor.edit.failed": "修改监控失败",
"monitor.edit.success": "修改监控成功",
"monitor.enable": "恢复监控",
"monitor.export": "导出监控",
"monitor.export": "导出所选",
"monitor.export-all": "导出全部",
"monitor.export.switch-type": "请选择导出文件格式!",
"monitor.export.use-type": "以 {{type}} 文件格式导出监控",
"monitor.export.use-type": "以 {{type}} 文件格式导出所选监控",
"monitor.export-all.use-type": "以 {{type}} 文件格式导出全部监控",
"monitor.grafana.enabled.label": "启用Grafana",
"monitor.grafana.enabled.tip": "是否启用Grafana",
"monitor.grafana.upload.label": "上传Grafana模板",