Compare commits

...
Author SHA1 Message Date
tomsun28 696432b59c Merge branch 'master' into fix-memory 2025-03-10 00:34:40 +08:00
01be0c7a1d [improve] Optimize the progress display of monitoring imports (#3120)
Signed-off-by: Sherlock Yin <sherlock.yin1994@gmail.com>
Co-authored-by: yinyijun <yinyijun6@mgtv.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: yinyijun <yingey2011>
2025-03-10 00:25:31 +08:00
0fdd76c0de [feature] add smslocal sms notification (#3135)
Signed-off-by: 淞筱 <105542329+a-little-fool@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: yunfan24 <yunfan24@outlook.com>
2025-03-09 21:51:53 +08:00
tomsun28 299f41b769 Merge branch 'master' into fix-memory 2025-03-09 16:10:49 +08:00
tomsun28 0776480256 [improve] fix potential memory leakage and content length issues.
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-03-06 00:46:44 +08:00
48 changed files with 910 additions and 67 deletions
@@ -54,4 +54,8 @@ public class SmsConfig {
*/
private UniSmsProperties unisms;
/**
* Smslocal SMS configuration
*/
private SmslocalSmsProperties smslocal;
}
@@ -0,0 +1,33 @@
/*
* 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.
*/
package org.apache.hertzbeat.alert.config;
import lombok.Data;
/**
* Smslocal SMS Properties
*/
@Data
public class SmslocalSmsProperties {
/**
* SmsLocal account api key
*/
private String apiKey;
}
@@ -20,6 +20,7 @@ package org.apache.hertzbeat.alert.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.SmsConfig;
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
@@ -33,6 +34,7 @@ import org.springframework.stereotype.Component;
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;
/**
* SMS client factory
@@ -49,9 +51,7 @@ public class SmsClientFactory {
private volatile SmsClient currentSmsClient;
public SmsClientFactory(GeneralConfigDao generalConfigDao,
ObjectMapper objectMapper,
SmsConfig yamlSmsConfig) {
public SmsClientFactory(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper, SmsConfig yamlSmsConfig) {
this.generalConfigDao = generalConfigDao;
this.objectMapper = objectMapper;
this.yamlSmsConfig = yamlSmsConfig;
@@ -133,6 +133,9 @@ public class SmsClientFactory {
case ALIBABA:
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
break;
case SMSLOCAL:
currentSmsClient = new SmsLocalSmsClientImpl(smsConfig.getSmslocal());
break;
default:
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
break;
@@ -0,0 +1,146 @@
/*
* 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.
*/
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
import org.apache.hertzbeat.common.constants.SmsConstants;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.support.exception.SendMessageException;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Smslocal SMS Client Implement
*/
@Slf4j
public class SmsLocalSmsClientImpl implements SmsClient {
private static final String HOST = "secure.smslocal.com";
private static final String PATH = "/api/service/enterprise-service/external/sms";
private static final String FROM = "Hertzbeat";
private static final String SUCCESS_CODE = "200";
private final SmslocalSmsProperties config;
public SmsLocalSmsClientImpl(SmslocalSmsProperties smslocalSmsProperties) {
this.config = smslocalSmsProperties;
}
@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
if (Objects.isNull(receiver) || Objects.isNull(alert)) {
log.warn("receiver and alert can not be null! receiver: {}, alert:{}", receiver, alert);
return;
}
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String content = alert.getCommonAnnotations().get("summary");
if (Objects.isNull(content) || Objects.isNull(alert.getCommonAnnotations().get("description"))) {
content = alert.getAlerts().get(0).getContent();
}
SmsMessage smsMessage = new SmsMessage(FROM, receiver.getPhone(), content);
String payload = JsonUtil.toJson(smsMessage);
HttpPost httpPost = new HttpPost("https://" + HOST + PATH);
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
httpPost.setHeader("Token", config.getApiKey());
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload);
// send http request and handle response
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
log.debug("SMS response status: {}, body: {}", statusCode, responseBody);
if (statusCode != 200) {
throw new SendMessageException("HTTP request failed with status code: " + statusCode);
}
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
JsonNode jsonNode = jsonResponse.get(0);
if (Objects.isNull(jsonNode)) {
log.warn("jsonResponse parse errorCode failed: {}", jsonResponse);
return;
}
String errorCode = jsonNode.get("errorCode").asText();
if (!SUCCESS_CODE.equals(errorCode)) {
String msgid = jsonNode.get("id").asText();
throw new SendMessageException(errorCode + ":" + msgid);
}
log.info("Successfully sent SMS to phone: {}", receiver.getPhone());
}
} catch (Exception e) {
log.error("Failed to send SMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
@Override
public String getType() {
return SmsConstants.SMSLOCAL;
}
@Override
public boolean checkConfig() {
if (Objects.isNull(config) || Objects.isNull(config.getApiKey()) || config.getApiKey().isBlank()) {
log.warn("smslocal properties can not be null: {}", config);
return false;
}
return true;
}
@Getter
@Setter
private static class SmsMessage {
String from;
String to;
String content;
final int datacoding = 0;
final String direction = "mt";
public SmsMessage(String from, String to, String content) {
this.from = from;
this.to = to;
this.content = content;
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.
*/
package org.apache.hertzbeat.common.constants;
/**
* Import/Export task constants
*/
public interface ImExportTaskConstant {
/**
* If the number of tasks exceeds 100, progress information will broadcast
*/
Integer IMPORT_TASK_PROCESS_THRESHOLD = 100;
}
@@ -0,0 +1,49 @@
/*
* 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.
*/
package org.apache.hertzbeat.common.constants;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* Import/Export task status
*/
@Getter
@AllArgsConstructor
@ToString
public enum ImportTaskStatusEnum {
/**
* In progress
*/
IN_PROGRESS("IN_PROGRESS"),
/**
* Completed
*/
COMPLETED("COMPLETED"),
/**
* Failed
*/
FAILED("FAILED");
private final String value;
}
@@ -0,0 +1,39 @@
/*
* 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.
*/
package org.apache.hertzbeat.common.constants;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* Manager Event Type Enum
*/
@Getter
@AllArgsConstructor
@ToString
public enum ManagerEventTypeEnum {
/**
* IMPORT_TASK_EVENT
*/
IMPORT_TASK_EVENT("IMPORT_TASK_EVENT");
private final String value;
}
@@ -0,0 +1,40 @@
/*
* 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.
*/
package org.apache.hertzbeat.common.constants;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* Notify Level Enum
*/
@Getter
@AllArgsConstructor
@ToString
public enum NotifyLevelEnum {
SUCCESS("SUCCESS"),
ERROR("ERROR"),
INFO("INFO"),
WARNING("WARNING"),
BLANK("BLANK");
private final String value;
}
@@ -23,10 +23,13 @@ package org.apache.hertzbeat.common.constants;
public interface SmsConstants {
// Tencent cloud SMS
String TENCENT = "tencent";
// Alibaba Cloud SMS
String ALIBABA = "alibaba";
// UniSMS
String UNISMS = "unisms";
// Smslocal SMS
String SMSLOCAL = "smslocal";
}
@@ -63,6 +63,7 @@ public class GroupAlert {
private Long id;
@Schema(title = "Group Key", example = "HighCPUUsage{alertname=\"HighCPUUsage\", instance=\"server1\"}")
@Column(length = 2048)
private String groupKey;
@Schema(title = "Status", example = "resolved")
@@ -59,8 +59,9 @@ public class SingleAlert {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(title = "Threshold Id", example = "87584674384", accessMode = READ_ONLY)
private Long id;
@Schema(title = "Fingerprint", example = "dxsdfdsf")
@Schema(title = "Fingerprint", example = "alertname:demo")
@Column(length = 2048)
private String fingerprint;
@Schema(title = "Labels", example = "{\"alertname\": \"HighCPUUsage\", \"priority\": \"critical\", \"instance\": \"343483943\"}")
@@ -74,6 +75,7 @@ public class SingleAlert {
private Map<String, String> annotations;
@Schema(title = "Content", example = "CPU usage is above 80% for the last 5 minutes on instance server1.example.com.")
@Column(length = 4096)
private String content;
@Schema(title = "Status", example = "firing|resolved")
@@ -0,0 +1,79 @@
/*
* 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.
*/
package org.apache.hertzbeat.common.entity.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.hertzbeat.common.constants.ImportTaskStatusEnum;
import org.apache.hertzbeat.common.constants.ManagerEventTypeEnum;
import org.apache.hertzbeat.common.constants.NotifyLevelEnum;
import org.springframework.lang.Nullable;
/**
* Import task message
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ImportTaskMessage extends ManagerMessage {
/**
* Task name
*/
@NotNull
private String taskName;
/**
* Progress, expressed as a percentage
*/
@Nullable
private Integer progress;
/**
* Task Status,
* @see ImportTaskStatusEnum
*/
@NotNull
private String status;
/**
* If Fail, the error message
*/
@Nullable
private String errMsg;
public ImportTaskMessage(String notifyLevel, String managerEventType, String taskName, @Nullable Integer progress, String status, @Nullable String errMsg){
super(notifyLevel, managerEventType);
this.taskName = taskName;
this.progress = progress;
this.status = status;
this.errMsg = errMsg;
}
public static ManagerMessage createInProgressMessage(String taskName, Integer process){
return new ImportTaskMessage(NotifyLevelEnum.INFO.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, process, ImportTaskStatusEnum.IN_PROGRESS.getValue(), null);
}
public static ManagerMessage createCompletedMessage(String taskName){
return new ImportTaskMessage(NotifyLevelEnum.SUCCESS.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, null, ImportTaskStatusEnum.COMPLETED.getValue(), null);
}
public static ManagerMessage createFailedMessage(String taskName, String errMsg){
return new ImportTaskMessage(NotifyLevelEnum.ERROR.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, null, ImportTaskStatusEnum.FAILED.getValue(), errMsg);
}
}
@@ -0,0 +1,42 @@
/*
* 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.
*/
package org.apache.hertzbeat.common.entity.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Manager Message Entity
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ManagerMessage {
/**
* Notify Level
* @see org.apache.hertzbeat.common.constants.NotifyLevelEnum
*/
private String notifyLevel;
/**
* Manager Event Type
* @see org.apache.hertzbeat.common.constants.ManagerEventTypeEnum
*/
private String managerEventType;
}
@@ -0,0 +1,88 @@
/*
* 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.
*/
package org.apache.hertzbeat.manager.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.ManagerEventTypeEnum;
import org.apache.hertzbeat.common.entity.dto.ImportTaskMessage;
import org.apache.hertzbeat.common.entity.dto.ManagerMessage;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manager SSE
*/
@Slf4j
@Component
public class ManagerSseManager {
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
public SseEmitter createEmitter(Long clientId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitter.onCompletion(() -> removeEmitter(clientId));
emitter.onTimeout(() -> removeEmitter(clientId));
emitters.put(clientId, emitter);
return emitter;
}
@Async
public void broadcast(String eventName, String data) {
emitters.forEach((clientId, emitter) -> {
try {
emitter.send(SseEmitter.event()
.id(String.valueOf(System.currentTimeMillis()))
.name(eventName)
.data(data));
} catch (IOException | IllegalStateException e) {
emitter.complete();
removeEmitter(clientId);
} catch (Exception exception) {
log.error("Failed to broadcast manager message data to client: {}", exception.getMessage());
emitter.complete();
removeEmitter(clientId);
}
});
}
public void broadcastImportTaskInProgress(String taskName, Integer progress){
ManagerMessage managerMessage = ImportTaskMessage.createInProgressMessage(taskName, progress);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
public void broadcastImportTaskSuccess(String taskName){
ManagerMessage managerMessage = ImportTaskMessage.createCompletedMessage(taskName);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
public void broadcastImportTaskFail(String taskName, String errMsg){
ManagerMessage managerMessage = ImportTaskMessage.createFailedMessage(taskName, errMsg);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
private void removeEmitter(Long clientId) {
emitters.remove(clientId);
}
}
@@ -0,0 +1,50 @@
/*
* 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.
*/
package org.apache.hertzbeat.manager.controller;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
/**
* SSE controller for manager
*/
@RestController
@RequestMapping(path = "/api/manager/sse", produces = {TEXT_EVENT_STREAM_VALUE})
public class ManagerSseController {
private final ManagerSseManager emitterManager;
public ManagerSseController(ManagerSseManager emitterManager) {
this.emitterManager = emitterManager;
}
@GetMapping(path = "/subscribe")
public SseEmitter subscribe() {
Long clientId = SnowFlakeIdGenerator.generateId();
return emitterManager.createEmitter(clientId);
}
}
@@ -39,5 +39,7 @@ public class SmsNoticeSender {
private SmsUniSmsConfig unisms;
private SmslocalConfig smslocal;
private boolean enable = true;
}
@@ -0,0 +1,38 @@
/*
* 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.
*/
package org.apache.hertzbeat.manager.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Smslocal SMS configuration
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmslocalConfig {
/**
* Smslocal api key
*/
private String apiKey;
}
@@ -29,9 +29,10 @@ public interface ImExportService {
/**
* Import Configuration
* @param taskName task name
* @param is input stream
*/
void importConfig(InputStream is);
void importConfig(String taskName, InputStream is);
/**
* Export Configuration
@@ -22,17 +22,12 @@ import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.annotation.Resource;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.ImExportTaskConstant;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
@@ -41,6 +36,14 @@ import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* class AbstractImExportServiceImpl
*/
@@ -54,27 +57,30 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
@Resource
private TagService tagService;
@Resource
private ManagerSseManager managerSseManager;
@Override
public void importConfig(InputStream is) {
var formList = parseImport(is)
.stream()
.map(this::convert)
.toList();
public void importConfig(String taskName, InputStream is) {
var formList = parseImport(is).stream().map(this::convert).toList();
if (!CollectionUtils.isEmpty(formList)) {
formList.forEach(monitorDto -> {
int totalElements = formList.size();
int progressInterval = Math.max(1, totalElements / 10);
for (int i = 0; i < totalElements; i++) {
MonitorDto monitorDto = formList.get(i);
monitorService.validate(monitorDto, false);
monitorService.addMonitor(monitorDto.getMonitor(), monitorDto.getParams(), monitorDto.getCollector(), monitorDto.getGrafanaDashboard());
});
if (totalElements >= ImExportTaskConstant.IMPORT_TASK_PROCESS_THRESHOLD && ((i + 1) % progressInterval == 0) && (i + 1 < totalElements)) {
managerSseManager.broadcastImportTaskInProgress(taskName, (int) ((i + 1) * 100.0 / totalElements));
}
}
managerSseManager.broadcastImportTaskSuccess(taskName);
}
}
@Override
public void exportConfig(OutputStream os, List<Long> configList) {
var monitorList = configList.stream()
.map(it -> monitorService.getMonitorDto(it))
.filter(Objects::nonNull)
.map(this::convert)
.toList();
var monitorList = configList.stream().map(it -> monitorService.getMonitorDto(it)).filter(Objects::nonNull).map(this::convert).toList();
writeOs(monitorList, os);
}
@@ -99,15 +105,13 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
var monitor = new MonitorDTO();
BeanUtils.copyProperties(dto.getMonitor(), monitor);
exportMonitor.setMonitor(monitor);
exportMonitor.setParams(dto.getParams().stream()
.map(it -> {
var param = new ParamDTO();
param.setField(it.getField());
param.setType(it.getType());
param.setValue(it.getParamValue());
return param;
})
.toList());
exportMonitor.setParams(dto.getParams().stream().map(it -> {
var param = new ParamDTO();
param.setField(it.getField());
param.setType(it.getType());
param.setValue(it.getParamValue());
return param;
}).toList());
exportMonitor.getMonitor().setCollector(dto.getCollector());
return exportMonitor;
}
@@ -120,7 +124,7 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
var monitorDto = new MonitorDto();
var monitor = new Monitor();
log.debug("exportMonitor.monitor{}", exportMonitor.monitor);
if (exportMonitor.monitor != null) {
if (exportMonitor.monitor != null) {
// Add one more null check
BeanUtils.copyProperties(exportMonitor.monitor, monitor);
}
@@ -129,15 +133,13 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
monitorDto.setCollector(exportMonitor.getMonitor().getCollector());
}
if (exportMonitor.params != null) {
monitorDto.setParams(exportMonitor.params.stream()
.map(it -> {
var param = new Param();
param.setField(it.field);
param.setType(it.type);
param.setParamValue(it.value);
return param;
})
.toList());
monitorDto.setParams(exportMonitor.params.stream().map(it -> {
var param = new Param();
param.setField(it.field);
param.setType(it.type);
param.setParamValue(it.value);
return param;
}).toList());
} else {
monitorDto.setParams(Collections.emptyList());
}
@@ -203,5 +205,4 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
@Excel(name = "Value")
private String value;
}
}
@@ -54,6 +54,7 @@ import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.SdMonitorOperator;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.grafana.service.DashboardService;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.dao.CollectorDao;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
@@ -138,6 +139,8 @@ public class MonitorServiceImpl implements MonitorService {
private WarehouseService warehouseService;
@Autowired
private DashboardService dashboardService;
@Autowired
private ManagerSseManager managerSseManager;
public MonitorServiceImpl(List<ImExportService> imExportServiceList) {
imExportServiceList.forEach(it -> imExportServiceMap.put(it.type(), it));
@@ -183,17 +186,21 @@ public class MonitorServiceImpl implements MonitorService {
@Override
public void importConfig(MultipartFile file) throws Exception {
var fileName = FileUtil.getFileName(file);
var type = FileUtil.getFileType(file);
if (!imExportServiceMap.containsKey(type)) {
throw new RuntimeException(ExportFileConstants.FILE + " " + fileName + " is not supported.");
try {
if (!imExportServiceMap.containsKey(type)) {
String errMsg = ExportFileConstants.FILE + " " + fileName + " is not supported.";
throw new RuntimeException(errMsg);
}
var imExportService = imExportServiceMap.get(type);
imExportService.importConfig(fileName, file.getInputStream());
} catch (Exception e){
managerSseManager.broadcastImportTaskFail(fileName, e.getMessage());
throw e;
}
var imExportService = imExportServiceMap.get(type);
imExportService.importConfig(file.getInputStream());
}
@Override
@Transactional(readOnly = true)
public void validate(MonitorDto monitorDto, Boolean isModify) throws IllegalArgumentException {
@@ -225,6 +225,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -247,4 +249,4 @@ ai:
# api key
api-key:
#At present, only IFLYTEK large model needs to be filled in
api-secret:
api-secret:
@@ -78,6 +78,7 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -73,10 +73,13 @@ public class DataStorageDispatch {
if (metricsData == null) {
continue;
}
calculateMonitorStatus(metricsData);
historyDataWriter.ifPresent(dataWriter -> dataWriter.saveData(metricsData));
pluginRunner.pluginExecute(PostCollectPlugin.class, ((postCollectPlugin, pluginContext) -> postCollectPlugin.execute(metricsData, pluginContext)));
realTimeDataWriter.saveData(metricsData);
try {
calculateMonitorStatus(metricsData);
historyDataWriter.ifPresent(dataWriter -> dataWriter.saveData(metricsData));
pluginRunner.pluginExecute(PostCollectPlugin.class, ((postCollectPlugin, pluginContext) -> postCollectPlugin.execute(metricsData, pluginContext)));
} finally {
realTimeDataWriter.saveData(metricsData);
}
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
} catch (Exception e) {
@@ -70,6 +70,7 @@ public class MemoryDataStorage extends AbstractRealTimeDataStorage {
Long monitorId = metricsData.getId();
String metrics = metricsData.getMetrics();
if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
metricsData.close();
return;
}
Map<String, CollectRep.MetricsData> metricsDataMap =
@@ -63,6 +63,7 @@ public class RedisDataStorage extends AbstractRealTimeDataStorage {
String hashKey = metricsData.getMetrics();
if (metricsData.getCode() == CollectRep.Code.SUCCESS) {
redisCommandDelegate.operate().hset(key, hashKey, metricsData, future -> future.thenAccept(response -> {
metricsData.close();
if (response) {
log.debug("[warehouse] redis add new data {}:{}.", key, hashKey);
} else {
+27
View File
@@ -156,6 +156,33 @@ alerter:
Now you can configure this information in your hertzbeat application.
### Smslocal SMS Configuration
SMSLocal is an all-in-one SMS service for businesses, with features like multi-way sending, strong security, and 24/7 support. You can refer to smslocal's [Developer Documentation](https://www.smslocal.com/developer/) for configuration.
Add/Fill in the following Smslocal configuration to `application.yml` (replace parameters with your own SMS server configuration):
```yaml
alerter:
sms:
enable: true # Whether to enable
type: smslocal # SMS provider type, set to smslocal
smslocal: # Smslocal configuration
api-key: YOUR_API_KEY_HERE
```
1. Register smslocal account
- Visit [Smslocal Website](https://www.smslocal.com/)
2. Obtain `api-key`
- Log in to [Smslocal Api Access](https://secure.smslocal.com/cpaas/pages/profile/settings/api-reference)
- Go to "API Access" page
- Click the eye button
- Copy the displayed access key
- Then you can configure the `application.yml` file
Now you can configure this information in your hertzbeat application.
## Operation steps
1. **【Alarm notification】->【Add new recipient】 ->【Select SMS notification method】**
@@ -155,6 +155,33 @@ alerter:
现在您可以把这些信息配置到您的hertzbeat应用中。
### smslocal短信配置
smslocal是一款面向企业的一体化短信服务平台,具备诸如多种发送方式、强大的安全性以及全天候支持等特性。你可以参考 smslocal 的[开发者文档](https://www.smslocal.com/developer/)来进行配置。
在 `application.yml` 中添加/填写以下 smslocal 配置内容(请用你自己的短信服务器配置参数替换相关参数):
```yaml
alerter:
sms:
enable: true # 是否启用
type: smslocal # 短信服务提供商类型,设置为smslocal
smslocal: # smslocal配置
api-key: 在此处填入你的API密钥
```
1. 注册 smslocal 账号
- 访问 [smslocal官网](https://www.smslocal.com/)
2. 获取 `api-key`
- 登录 [smslocal API accessKey访问页面](https://secure.smslocal.com/cpaas/pages/profile/settings/api-reference)
- 进入 “API 访问” 页面
- 点击眼睛图标按钮
- 复制显示的访问密钥
- 然后你就可以配置 `application.yml` 文件了
现在你可以在你的 Hertzbeat 应用程序中配置这些信息。
## 操作步骤
1. **【告警通知】->【新增接收人】 ->【选择短信通知方式】**
+2
View File
@@ -224,6 +224,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -191,6 +191,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,6 +78,7 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -187,6 +187,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,6 +78,7 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -185,6 +185,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,6 +78,7 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -184,6 +184,8 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,6 +78,7 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
+1
View File
@@ -78,6 +78,7 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -156,7 +156,8 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
}
);
this.loadData();
this.initSSEConnection();
this.initAlertSSEConnection();
this.initManagerSSEConnection();
}
ngOnDestroy() {
@@ -271,7 +272,7 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
});
}
private initSSEConnection(): void {
private initAlertSSEConnection(): void {
const sseUrl = '/api/alert/sse/subscribe';
this.eventSource = new EventSource(sseUrl);
@@ -302,4 +303,34 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
this.eventSource.close();
};
}
private initManagerSSEConnection(): void {
const sseUrl = '/api/manager/sse/subscribe';
this.eventSource = new EventSource(sseUrl);
this.eventSource.addEventListener('IMPORT_TASK_EVENT', (evt: MessageEvent) => {
let msg = JSON.parse(evt.data);
if (msg.notifyLevel === 'SUCCESS') {
this.notifySvc.success(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-success-detail', { taskName: msg.taskName })
);
} else if (msg.notifyLevel === 'ERROR') {
this.notifySvc.error(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-fail-detail', { taskName: msg.taskName, errMsg: msg.errMsg })
);
} else if (msg.notifyLevel === 'INFO') {
this.notifySvc.info(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-progress', { taskName: msg.taskName, progress: msg.progress })
);
} else {
console.error('Parse message error, msg:', evt.data);
}
});
this.eventSource.onerror = error => {
console.error('Manager SSE connection error:', error);
this.eventSource.close();
};
}
}
+2
View File
@@ -18,6 +18,7 @@
*/
import { AlibabaSmsConfig } from './AlibabaSmsConfig';
import { SmslocalSmsConfig } from './SmslocalSmsConfig';
import { TencentSmsConfig } from './TencentSmsConfig';
import { UniSmsConfig } from './UniSmsConfig';
import { SmsType } from './enums/sms-type.enum';
@@ -28,6 +29,7 @@ export class SmsNoticeSender {
tencent: TencentSmsConfig = new TencentSmsConfig();
alibaba: AlibabaSmsConfig = new AlibabaSmsConfig();
unisms: UniSmsConfig = new UniSmsConfig();
smslocal: SmslocalSmsConfig = new SmslocalSmsConfig();
enable: boolean = false;
creator!: string;
modifier!: string;
+22
View File
@@ -0,0 +1,22 @@
/*
* 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.
*/
export class SmslocalSmsConfig {
apiKey!: string;
}
+2 -1
View File
@@ -20,7 +20,8 @@
export enum SmsType {
TENCENT = 'tencent',
ALIBABA = 'alibaba',
UNISMS = 'unisms'
UNISMS = 'unisms',
SMSLOCAL = 'smslocal'
}
export enum UniSmsAuthMode {
@@ -265,15 +265,20 @@ export class MonitorListComponent implements OnInit, OnDestroy {
}
onImportMonitors(info: NzUploadChangeParam): void {
if (info.file.response) {
console.log(info.type);
if (info.type === 'start') {
this.notifySvc.info(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-submitted', { taskName: info.file.name })
);
}
if (info.type === 'success' && info.file.response) {
this.tableLoading = true;
const message = info.file.response;
if (message.code === 0) {
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.import-success'), '');
this.loadMonitorTable();
} else {
this.tableLoading = false;
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.import-fail'), message.msg);
}
}
}
@@ -139,7 +139,7 @@
</div>
</nz-modal>
<!-- sms server modal -->
<!-- sms server model -->
<nz-modal
[(nzVisible)]="isSmsServerModalVisible"
[nzTitle]="'settings.server.sms.setting' | i18n"
@@ -158,9 +158,12 @@
<nz-option [nzValue]="SmsType.TENCENT" nzLabel="{{ 'alert.notice.sender.sms.type.tencent' | i18n }}"></nz-option>
<nz-option [nzValue]="SmsType.ALIBABA" nzLabel="{{ 'alert.notice.sender.sms.type.alibaba' | i18n }}"></nz-option>
<nz-option [nzValue]="SmsType.UNISMS" nzLabel="{{ 'alert.notice.sender.sms.type.unisms' | i18n }}"></nz-option>
<nz-option [nzValue]="SmsType.SMSLOCAL" nzLabel="{{ 'alert.notice.sender.sms.type.smslocal' | i18n }}"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<!-- Tencent SMS -->
<ng-container *ngIf="smsType === SmsType.TENCENT">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="secretId" nzRequired="true">
@@ -201,6 +204,8 @@
</nz-form-control>
</nz-form-item>
</ng-container>
<!-- Alibaba SMS -->
<ng-container *ngIf="smsType === SmsType.ALIBABA">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="accessKeyId" nzRequired="true">
@@ -256,6 +261,8 @@
</nz-form-control>
</nz-form-item>
</ng-container>
<!-- UniSMS -->
<ng-container *ngIf="smsType === SmsType.UNISMS">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="accessKeyId" nzRequired="true">
@@ -317,6 +324,18 @@
</nz-form-control>
</nz-form-item>
</ng-container>
<!-- Smslocal SMS -->
<ng-container *ngIf="smsType === SmsType.SMSLOCAL">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="apiKey" nzRequired="true">
{{ 'alert.notice.sender.sms.smslocal.apiKey' | i18n }}
</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<input [(ngModel)]="smsNoticeSender.smslocal.apiKey" nz-input required name="apiKey" type="password" id="apiKey" />
</nz-form-control>
</nz-form-item>
</ng-container>
<nz-form-item>
<nz-form-label nzSpan="7" nzFor="smsEnable" nzRequired="true">{{ 'common.enable' | i18n }}</nz-form-label>
<nz-form-control nzSpan="12">
@@ -31,6 +31,7 @@ import { UniSmsConfig } from 'src/app/pojo/UniSmsConfig';
import { SmsType, UniSmsAuthMode } from 'src/app/pojo/enums/sms-type.enum';
import { EmailNoticeSender } from '../../../../pojo/EmailNoticeSender';
import { SmslocalSmsConfig } from '../../../../pojo/SmslocalSmsConfig';
import { GeneralConfigService } from '../../../../service/general-config.service';
@Component({
@@ -142,6 +143,7 @@ export class MessageServerComponent implements OnInit {
this.smsNoticeSender.tencent = { ...new TencentSmsConfig(), ...message.data.tencent };
this.smsNoticeSender.alibaba = { ...new AlibabaSmsConfig(), ...message.data.alibaba };
this.smsNoticeSender.unisms = { ...new UniSmsConfig(), ...message.data.unisms };
this.smsNoticeSender.smslocal = { ...new SmslocalSmsConfig(), ...message.data.smslocal };
this.smsType = message.data.type || 'tencent';
} else {
this.smsNoticeSender = new SmsNoticeSender();
@@ -165,7 +167,8 @@ export class MessageServerComponent implements OnInit {
...this.smsNoticeSender,
tencent: { ...this.smsNoticeSender.tencent },
alibaba: { ...this.smsNoticeSender.alibaba },
unisms: { ...this.smsNoticeSender.unisms }
unisms: { ...this.smsNoticeSender.unisms },
smslocal: { ...this.smsNoticeSender.smslocal }
};
this.isSmsServerModalVisible = true;
}
@@ -176,7 +179,8 @@ export class MessageServerComponent implements OnInit {
...this.tempSmsNoticeSender,
tencent: { ...this.tempSmsNoticeSender.tencent },
alibaba: { ...this.tempSmsNoticeSender.alibaba },
unisms: { ...this.tempSmsNoticeSender.unisms }
unisms: { ...this.tempSmsNoticeSender.unisms },
smslocal: { ...this.tempSmsNoticeSender.smslocal }
};
this.isSmsServerModalVisible = false;
}
+6
View File
@@ -153,10 +153,12 @@
"alert.notice.sender.sms.unisms.signature": "UniSMS Signature",
"alert.notice.sender.sms.unisms.templateId": "UniSMS TemplateId",
"alert.notice.sender.sms.unisms.authMode": "UniSMS Authentication Mode",
"alert.notice.sender.sms.smslocal.apiKey": "Smslocal ApiKey",
"alert.notice.sender.sms.type": "Sms Type",
"alert.notice.sender.sms.type.alibaba": "Alibaba Sms",
"alert.notice.sender.sms.type.tencent": "Tencent Sms",
"alert.notice.sender.sms.type.unisms": "UniSMS",
"alert.notice.sender.sms.type.smslocal": "Smslocal Sms",
"alert.notice.template": "Notice Template",
"alert.notice.template.content": "Template Content",
"alert.notice.template.delete": "Delete Template",
@@ -460,6 +462,10 @@
"common.notify.export-success": "Export Success!",
"common.notify.import-fail": "Import Failed!",
"common.notify.import-success": "Import Success!",
"common.notify.import-fail-detail": "Import [{{taskName}}] Failed: [{{errMsg}}]",
"common.notify.import-success-detail": "Import [{{taskName}}] Success!",
"common.notify.import-submitted": "Import [{{taskName}}] Submitted!",
"common.notify.import-progress": "Importing [{{taskName}}], progress: {{progress}}%",
"common.notify.mark-fail": "Mark Failed!",
"common.notify.mark-success": "Mark Success!",
"common.notify.new-fail": "Add Failed!",
+6
View File
@@ -153,10 +153,12 @@
"alert.notice.sender.sms.unisms.signature": "UniSMS Signature",
"alert.notice.sender.sms.unisms.templateId": "UniSMS TemplateId",
"alert.notice.sender.sms.unisms.authMode": "UniSMS認証モード",
"alert.notice.sender.sms.smslocal.apiKey": "Smslocal ApiKey",
"alert.notice.sender.sms.type": "SMSタイプ",
"alert.notice.sender.sms.type.alibaba": "Alibaba Sms",
"alert.notice.sender.sms.type.tencent": "Tencent Sms",
"alert.notice.sender.sms.type.unisms": "UniSMS",
"alert.notice.sender.sms.type.smslocal": "Smslocal Sms",
"alert.notice.template": "通知テンプレート",
"alert.notice.template.content": "テンプレート内容",
"alert.notice.template.delete": "テンプレートを削除",
@@ -460,6 +462,10 @@
"common.notify.export-success": "エクスポートに成功しました!",
"common.notify.import-fail": "インポートに失敗しました!",
"common.notify.import-success": "インポートに成功しました!",
"common.notify.import-fail-detail": "[{{taskName}}]のインポートに失敗しました: [{{errMsg}}]",
"common.notify.import-success-detail": "[{{taskName}}]のインポートが成功しました!",
"common.notify.import-submitted": "インポートタスク[{{taskName}}]が送信されました!",
"common.notify.import-progress": "[{{taskName}}]のインポート中、進捗状況: {{progress}}%",
"common.notify.mark-fail": "マークに失敗しました!",
"common.notify.mark-success": "マークに成功しました!",
"common.notify.new-fail": "追加に失敗しました!",
+6
View File
@@ -153,10 +153,12 @@
"alert.notice.sender.sms.unisms.signature": "合一短信Signature",
"alert.notice.sender.sms.unisms.templateId": "合一短信TemplateId",
"alert.notice.sender.sms.unisms.authMode": "合一短信鉴权方式",
"alert.notice.sender.sms.smslocal.apiKey": "Smslocal短信鉴权方式",
"alert.notice.sender.sms.type": "短信类型",
"alert.notice.sender.sms.type.alibaba": "阿里短信",
"alert.notice.sender.sms.type.tencent": "腾讯短信",
"alert.notice.sender.sms.type.unisms": "合一短信(UniSMS",
"alert.notice.sender.sms.type.smslocal": "当地短信(Smslocal",
"alert.notice.template": "通知模板",
"alert.notice.template.content": "模板内容",
"alert.notice.template.delete": "删除通知模板",
@@ -460,6 +462,10 @@
"common.notify.export-success": "导出成功!",
"common.notify.import-fail": "导入失败!",
"common.notify.import-success": "导入成功!",
"common.notify.import-fail-detail": "导入 [{{taskName}}] 失败: [{{errMsg}}]",
"common.notify.import-success-detail": "导入 [{{taskName}}] 成功!",
"common.notify.import-submitted": "导入任务 [{{taskName}}] 已提交!",
"common.notify.import-progress": "正在导入 [{{taskName}}], 进度: {{progress}}%",
"common.notify.mark-fail": "标记失败!",
"common.notify.mark-success": "标记成功!",
"common.notify.new-fail": "新增失败!",
+6
View File
@@ -153,10 +153,12 @@
"alert.notice.sender.sms.unisms.signature": "合一簡訊Signature",
"alert.notice.sender.sms.unisms.templateId": "合一簡訊TemplateId",
"alert.notice.sender.sms.unisms.authMode": "合一簡訊驗證方式",
"alert.notice.sender.sms.smslocal.authMode": "Smslocal短訊ApiKey",
"alert.notice.sender.sms.type": "騰訊類型",
"alert.notice.sender.sms.type.alibaba": "阿裏短訊",
"alert.notice.sender.sms.type.tencent": "騰訊短訊",
"alert.notice.sender.sms.type.unisms": "合一簡訊(UniSMS",
"alert.notice.sender.sms.type.smslocal": "当地短訊(Smslocal",
"alert.notice.template": "通知模板",
"alert.notice.template.content": "模板内容",
"alert.notice.template.delete": "刪除通知模板",
@@ -460,6 +462,10 @@
"common.notify.export-success": "導出成功!",
"common.notify.import-fail": "導入失敗!",
"common.notify.import-success": "導入成功!",
"common.notify.import-fail-detail": "導入 [{{taskName}}] 失敗: [{{errMsg}}]",
"common.notify.import-success-detail": "導入 [{{taskName}}] 成功!",
"common.notify.import-submitted": "導入任務 [{{taskName}}] 已提交!",
"common.notify.import-progress": "正在導入 [{{taskName}}], 進度: {{progress}}%",
"common.notify.mark-fail": "標記失敗!",
"common.notify.mark-success": "標記成功!",
"common.notify.new-fail": "新增失敗!",