Merge branch 'hotfix/3.6.3' into release-3.6

This commit is contained in:
Igor Kulikov 2024-04-11 16:10:31 +03:00
commit fc6a0d8efe
73 changed files with 972 additions and 449 deletions

View File

@ -0,0 +1,27 @@
--
-- Copyright © 2016-2024 The Thingsboard Authors
--
-- Licensed 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.
--
-- NOTIFICATIONS UPDATE START
ALTER TABLE notification ADD COLUMN IF NOT EXISTS delivery_method VARCHAR(50) NOT NULL default 'WEB';
DROP INDEX IF EXISTS idx_notification_recipient_id_created_time;
DROP INDEX IF EXISTS idx_notification_recipient_id_unread;
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_created_time ON notification(delivery_method, recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_unread ON notification(delivery_method, recipient_id) WHERE status <> 'READ';
-- NOTIFICATIONS UPDATE END

View File

@ -105,6 +105,8 @@ public class NotificationController extends BaseController {
private final NotificationCenter notificationCenter;
private final NotificationSettingsService notificationSettingsService;
private static final String DELIVERY_METHOD_ALLOWABLE_VALUES = "WEB,MOBILE_APP";
@ApiOperation(value = "Get notifications (getNotifications)",
notes = "Returns the page of notifications for current user." + NEW_LINE +
PAGE_DATA_PARAMETERS +
@ -172,10 +174,23 @@ public class NotificationController extends BaseController {
@RequestParam(required = false) String sortOrder,
@ApiParam(value = "To search for unread notifications only")
@RequestParam(defaultValue = "false") boolean unreadOnly,
@ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES)
@RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// no permissions
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink);
return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), deliveryMethod, user.getId(), unreadOnly, pageLink);
}
@ApiOperation(value = "Get unread notifications count (getUnreadNotificationsCount)",
notes = "Returns unread notifications count for chosen delivery method." +
AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@GetMapping("/notifications/unread/count")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public Integer getUnreadNotificationsCount(@ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES)
@RequestParam(defaultValue = "MOBILE_APP") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) {
return notificationService.countUnreadNotificationsByRecipientId(user.getTenantId(), deliveryMethod, user.getId());
}
@ApiOperation(value = "Mark notification as read (markNotificationAsRead)",
@ -195,9 +210,11 @@ public class NotificationController extends BaseController {
AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PutMapping("/notifications/read")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public void markAllNotificationsAsRead(@AuthenticationPrincipal SecurityUser user) {
public void markAllNotificationsAsRead(@ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES)
@RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) {
// no permissions
notificationCenter.markAllNotificationsAsRead(user.getTenantId(), user.getId());
notificationCenter.markAllNotificationsAsRead(user.getTenantId(), deliveryMethod, user.getId());
}
@ApiOperation(value = "Delete notification (deleteNotification)",

View File

@ -33,13 +33,13 @@ public class ThingsboardErrorResponse {
// Error code
private final ThingsboardErrorCode errorCode;
private final Date timestamp;
private final long timestamp;
protected ThingsboardErrorResponse(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) {
this.message = message;
this.errorCode = errorCode;
this.status = status;
this.timestamp = new java.util.Date();
this.timestamp = System.currentTimeMillis();
}
public static ThingsboardErrorResponse of(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) {
@ -75,7 +75,7 @@ public class ThingsboardErrorResponse {
}
@ApiModelProperty(position = 4, value = "Timestamp", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Date getTimestamp() {
public long getTimestamp() {
return timestamp;
}
}

View File

@ -16,6 +16,8 @@
package org.thingsboard.server.exception;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -28,7 +30,9 @@ import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.context.request.WebRequest;
@ -43,16 +47,21 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
import org.thingsboard.server.service.security.exception.UserPasswordNotValidException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
@Slf4j
@Controller
@RestControllerAdvice
public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler {
public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler, ErrorController {
private static final Map<HttpStatus, ThingsboardErrorCode> statusToErrorCodeMap = new HashMap<>();
static {
@ -90,6 +99,17 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
return errorCodeToStatusMap.getOrDefault(errorCode, HttpStatus.INTERNAL_SERVER_ERROR);
}
@RequestMapping("/error")
public ResponseEntity<Object> handleError(HttpServletRequest request) {
HttpStatus httpStatus = Optional.ofNullable(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE))
.map(status -> HttpStatus.resolve(Integer.parseInt(status.toString())))
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
String errorMessage = Optional.ofNullable(request.getAttribute(ERROR_EXCEPTION))
.map(e -> (ExceptionUtils.getMessage((Throwable) e)))
.orElse(httpStatus.getReasonPhrase());
return new ResponseEntity<>(ThingsboardErrorResponse.of(errorMessage, statusToErrorCode(httpStatus), httpStatus), httpStatus);
}
@Override
@ExceptionHandler(AccessDeniedException.class)
public void handle(HttpServletRequest request, HttpServletResponse response,

View File

@ -78,6 +78,24 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor {
@Override
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
tenantProfile = JacksonUtil.clone(tenantProfile);
// clear all config
var tenantProfileData = tenantProfile.getProfileData();
var configuration = tenantProfile.getDefaultProfileConfiguration();
configuration.setRpcTtlDays(0);
configuration.setMaxJSExecutions(0);
configuration.setMaxREExecutions(0);
configuration.setMaxDPStorageDays(0);
configuration.setMaxTbelExecutions(0);
configuration.setQueueStatsTtlDays(0);
configuration.setMaxTransportMessages(0);
configuration.setDefaultStorageTtlDays(0);
configuration.setMaxTransportDataPoints(0);
configuration.setRuleEngineExceptionsTtlDays(0);
configuration.setMaxRuleNodeExecutionsPerMessage(0);
tenantProfileData.setConfiguration(configuration);
tenantProfile.setProfileData(tenantProfileData);
ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_2) ?
ByteString.empty() : ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData()));
TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder()
@ -93,4 +111,5 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor {
}
return builder.build();
}
}

View File

@ -19,6 +19,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
@ -36,6 +37,23 @@ public class TenantMsgConstructorV2 implements TenantMsgConstructor {
@Override
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
tenantProfile = JacksonUtil.clone(tenantProfile);
// clear all config
var configuration = tenantProfile.getDefaultProfileConfiguration();
configuration.setRpcTtlDays(0);
configuration.setMaxJSExecutions(0);
configuration.setMaxREExecutions(0);
configuration.setMaxDPStorageDays(0);
configuration.setMaxTbelExecutions(0);
configuration.setQueueStatsTtlDays(0);
configuration.setMaxTransportMessages(0);
configuration.setDefaultStorageTtlDays(0);
configuration.setMaxTransportDataPoints(0);
configuration.setRuleEngineExceptionsTtlDays(0);
configuration.setMaxRuleNodeExecutionsPerMessage(0);
tenantProfile.getProfileData().setConfiguration(configuration);
return TenantProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenantProfile)).build();
}
}

View File

@ -25,10 +25,9 @@ import org.springframework.context.annotation.Lazy;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
@ -72,11 +71,11 @@ public abstract class AbstractTbEntityService {
@Lazy
private EntitiesVersionControlService vcService;
protected void removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) {
PageData<AlarmInfo> alarms =
alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, null, false));
protected void removeAlarmsByOriginatorId(TenantId tenantId, EntityId entityId) {
PageData<AlarmId> alarms =
alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, new TimePageLink(Integer.MAX_VALUE));
alarms.getData().stream().map(AlarmInfo::getId).forEach(alarmId -> alarmService.delAlarm(tenantId, alarmId));
alarms.getData().forEach(alarmId -> alarmService.delAlarm(tenantId, alarmId));
}
protected <T> T checkNotNull(T reference) throws ThingsboardException {

View File

@ -78,7 +78,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
TenantId tenantId = asset.getTenantId();
AssetId assetId = asset.getId();
try {
removeAlarmsByEntityId(tenantId, assetId);
removeAlarmsByOriginatorId(tenantId, assetId);
assetService.deleteAsset(tenantId, assetId);
notificationEntityService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString());
tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED);

View File

@ -97,7 +97,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
TenantId tenantId = device.getTenantId();
DeviceId deviceId = device.getId();
try {
removeAlarmsByEntityId(tenantId, deviceId);
removeAlarmsByOriginatorId(tenantId, deviceId);
deviceService.deleteDevice(tenantId, deviceId);
notificationEntityService.notifyDeleteDevice(tenantId, deviceId, device.getCustomerId(), device,
user, deviceId.toString());

View File

@ -54,23 +54,35 @@ public class RefreshTokenExpCheckService {
AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
if (settings != null && settings.getJsonValue().has("enableOauth2") && settings.getJsonValue().get("enableOauth2").asBoolean()) {
JsonNode jsonValue = settings.getJsonValue();
if (OFFICE_365.name().equals(jsonValue.get("providerId").asText()) && jsonValue.has("refreshTokenExpires")) {
long expiresIn = jsonValue.get("refreshTokenExpires").longValue();
if ((expiresIn - System.currentTimeMillis()) < 604800000L) { //less than 7 days
log.info("Trying to refresh refresh token.");
if (OFFICE_365.name().equals(jsonValue.get("providerId").asText()) && jsonValue.has("refreshToken")
&& jsonValue.has("refreshTokenExpires")) {
try {
long expiresIn = jsonValue.get("refreshTokenExpires").longValue();
long tokenLifeDuration = expiresIn - System.currentTimeMillis();
if (tokenLifeDuration < 0) {
((ObjectNode) jsonValue).put("tokenGenerated", false);
((ObjectNode) jsonValue).remove("refreshToken");
((ObjectNode) jsonValue).remove("refreshTokenExpires");
String clientId = jsonValue.get("clientId").asText();
String clientSecret = jsonValue.get("clientSecret").asText();
String refreshToken = jsonValue.get("refreshToken").asText();
String tokenUri = jsonValue.get("tokenUri").asText();
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings);
} else if (tokenLifeDuration < 604800000L) { //less than 7 days
log.info("Trying to refresh refresh token.");
TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(),
new GenericUrl(tokenUri), refreshToken)
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret))
.execute();
((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken());
((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli());
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings);
String clientId = jsonValue.get("clientId").asText();
String clientSecret = jsonValue.get("clientSecret").asText();
String refreshToken = jsonValue.get("refreshToken").asText();
String tokenUri = jsonValue.get("tokenUri").asText();
TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(),
new GenericUrl(tokenUri), refreshToken)
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret))
.execute();
((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken());
((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli());
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings);
}
} catch (Exception e) {
log.error("Error occurred while checking token", e);
}
}
}

View File

@ -82,6 +82,8 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.WEB;
@Service
@Slf4j
@RequiredArgsConstructor
@ -192,7 +194,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId)
.request(notificationRequest)
.deliveryMethods(Set.of(NotificationDeliveryMethod.WEB))
.deliveryMethods(Set.of(WEB))
.template(template)
.build();
@ -323,6 +325,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
.requestId(request.getId())
.recipientId(recipient.getId())
.type(ctx.getNotificationType())
.deliveryMethod(WEB)
.subject(processedTemplate.getSubject())
.text(processedTemplate.getBody())
.additionalConfig(processedTemplate.getAdditionalConfig())
@ -348,19 +351,22 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId);
if (updated) {
log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.notificationId(notificationId.getId())
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);
Notification notification = notificationService.findNotificationById(tenantId, notificationId);
if (notification.getDeliveryMethod() == WEB) {
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.notificationId(notificationId.getId())
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);
}
}
}
@Override
public void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) {
int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, recipientId);
if (updatedCount > 0) {
public void markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, deliveryMethod, recipientId);
if (updatedCount > 0 && deliveryMethod == WEB) {
log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
@ -375,7 +381,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
public void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) {
Notification notification = notificationService.findNotificationById(tenantId, notificationId);
boolean deleted = notificationService.deleteNotification(tenantId, recipientId, notificationId);
if (deleted) {
if (deleted && notification.getDeliveryMethod() == WEB) {
NotificationUpdate update = NotificationUpdate.builder()
.deleted(true)
.notification(notification)
@ -455,7 +461,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.WEB;
return WEB;
}
@Override
@ -466,7 +472,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Autowired
public void setChannels(List<NotificationChannel> channels, NotificationCenter webNotificationChannel) {
this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c));
this.channels.put(NotificationDeliveryMethod.WEB, (NotificationChannel) webNotificationChannel);
this.channels.put(WEB, (NotificationChannel) webNotificationChannel);
}
}

View File

@ -16,6 +16,7 @@
package org.thingsboard.server.service.notification.channels;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.MessagingErrorCode;
@ -26,11 +27,15 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.notification.FirebaseService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
@ -41,6 +46,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.MOBILE_APP;
@Component
@RequiredArgsConstructor
@Slf4j
@ -48,25 +55,54 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
private final FirebaseService firebaseService;
private final UserService userService;
private final NotificationService notificationService;
private final NotificationSettingsService notificationSettingsService;
@Override
public void sendNotification(User recipient, MobileAppDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) throws Exception {
NotificationRequest request = ctx.getRequest();
NotificationInfo info = request.getInfo();
if (info != null && info.getDashboardId() != null) {
ObjectNode additionalConfig = JacksonUtil.asObject(processedTemplate.getAdditionalConfig());
ObjectNode onClick = JacksonUtil.asObject(additionalConfig.get("onClick"));
if (onClick.get("enabled") == null || !Boolean.parseBoolean(onClick.get("enabled").asText())) {
onClick.put("enabled", true);
onClick.put("linkType", "DASHBOARD");
onClick.put("setEntityIdInState", true);
onClick.put("dashboardId", info.getDashboardId().toString());
additionalConfig.set("onClick", onClick);
}
processedTemplate.setAdditionalConfig(additionalConfig);
}
Notification notification = Notification.builder()
.requestId(request.getId())
.recipientId(recipient.getId())
.type(ctx.getNotificationType())
.deliveryMethod(MOBILE_APP)
.subject(processedTemplate.getSubject())
.text(processedTemplate.getBody())
.additionalConfig(processedTemplate.getAdditionalConfig())
.info(info)
.status(NotificationStatus.SENT)
.build();
notificationService.saveNotification(recipient.getTenantId(), notification);
var mobileSessions = userService.findMobileSessions(recipient.getTenantId(), recipient.getId());
if (mobileSessions.isEmpty()) {
throw new IllegalArgumentException("User doesn't use the mobile app");
}
MobileAppNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.MOBILE_APP);
MobileAppNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(MOBILE_APP);
String credentials = config.getFirebaseServiceAccountCredentials();
Set<String> validTokens = new HashSet<>(mobileSessions.keySet());
String subject = processedTemplate.getSubject();
String body = processedTemplate.getBody();
Map<String, String> data = getNotificationData(processedTemplate, ctx);
int unreadCount = notificationService.countUnreadNotificationsByRecipientId(ctx.getTenantId(), MOBILE_APP, recipient.getId());
for (String token : mobileSessions.keySet()) {
try {
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data);
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data, unreadCount);
} catch (FirebaseMessagingException e) {
MessagingErrorCode errorCode = e.getMessagingErrorCode();
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT) {
@ -92,12 +128,6 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
Optional.ofNullable(info.getStateEntityId()).ifPresent(stateEntityId -> {
data.put("stateEntityId", stateEntityId.getId().toString());
data.put("stateEntityType", stateEntityId.getEntityType().name());
if (!"true".equals(data.get("onClick.enabled")) && info.getDashboardId() != null) {
data.put("onClick.enabled", "true");
data.put("onClick.linkType", "DASHBOARD");
data.put("onClick.setEntityIdInState", "true");
data.put("onClick.dashboardId", info.getDashboardId().toString());
}
});
data.put("notificationType", ctx.getNotificationType().name());
switch (ctx.getNotificationType()) {
@ -116,14 +146,14 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
@Override
public void check(TenantId tenantId) throws Exception {
NotificationSettings systemSettings = notificationSettingsService.findNotificationSettings(TenantId.SYS_TENANT_ID);
if (!systemSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.MOBILE_APP)) {
if (!systemSettings.getDeliveryMethodsConfigs().containsKey(MOBILE_APP)) {
throw new RuntimeException("Push-notifications to mobile are not configured");
}
}
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.MOBILE_APP;
return MOBILE_APP;
}
}

View File

@ -54,7 +54,8 @@ public class DefaultFirebaseService implements FirebaseService {
.build();
@Override
public void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map<String, String> data) throws FirebaseMessagingException {
public void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body,
Map<String, String> data, Integer badge) throws FirebaseMessagingException {
FirebaseContext firebaseContext = contexts.asMap().compute(tenantId.toString(), (key, context) -> {
if (context == null) {
return new FirebaseContext(key, credentials);
@ -64,6 +65,12 @@ public class DefaultFirebaseService implements FirebaseService {
}
});
Aps.Builder apsConfig = Aps.builder()
.setSound("default");
if (badge != null) {
apsConfig.setBadge(badge);
}
Message message = Message.builder()
.setToken(fcmToken)
.setNotification(Notification.builder()
@ -74,14 +81,17 @@ public class DefaultFirebaseService implements FirebaseService {
.setPriority(AndroidConfig.Priority.HIGH)
.build())
.setApnsConfig(ApnsConfig.builder()
.setAps(Aps.builder()
.setContentAvailable(true)
.build())
.setAps(apsConfig.build())
.build())
.putAllData(data)
.build();
firebaseContext.getMessaging().send(message);
log.trace("[{}] Sent message for FCM token {}", tenantId, fcmToken);
try {
firebaseContext.getMessaging().send(message);
log.trace("[{}] Sent message for FCM token {}", tenantId, fcmToken);
} catch (Throwable t) {
log.debug("[{}] Failed to send message for FCM token {}", tenantId, fcmToken, t);
throw t;
}
}
public static class FirebaseContext {

View File

@ -114,9 +114,17 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
if (partitionService.isManagedByCurrentService(queueKey.getTenantId())) {
var consumer = getConsumer(queueKey).orElseGet(() -> {
Queue config = queueService.findQueueByTenantIdAndName(queueKey.getTenantId(), queueKey.getQueueName());
if (config == null) {
if (!partitions.isEmpty()) {
log.error("[{}] Queue configuration is missing", queueKey, new RuntimeException("stacktrace"));
}
return null;
}
return createConsumer(queueKey, config);
});
consumer.update(partitions);
if (consumer != null) {
consumer.update(partitions);
}
}
});
consumers.keySet().stream()

View File

@ -18,7 +18,6 @@ package org.thingsboard.server.service.queue;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
@ -45,6 +44,7 @@ public class TbRuleEngineConsumerStats {
public static final String FAILED_MSGS = "failedMsgs";
public static final String SUCCESSFUL_ITERATIONS = "successfulIterations";
public static final String FAILED_ITERATIONS = "failedIterations";
public static final String TENANT_ID_TAG = "tenantId";
private final StatsFactory statsFactory;
@ -73,14 +73,15 @@ public class TbRuleEngineConsumerStats {
this.statsFactory = statsFactory;
String statsKey = StatsType.RULE_ENGINE.getName() + "." + queueName;
this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS);
this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS);
this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS);
this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS);
this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT);
this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED);
this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS);
this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS);
String tenant = tenantId == null || tenantId.isSysTenantId() ? "system" : tenantId.toString();
this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS, TENANT_ID_TAG, tenant);
this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS, TENANT_ID_TAG, tenant);
this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS, TENANT_ID_TAG, tenant);
this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS, TENANT_ID_TAG, tenant);
this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT, TENANT_ID_TAG, tenant);
this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED, TENANT_ID_TAG, tenant);
this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS, TENANT_ID_TAG, tenant);
this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS, TENANT_ID_TAG, tenant);
counters.add(totalMsgCounter);
counters.add(successMsgCounter);
@ -93,7 +94,7 @@ public class TbRuleEngineConsumerStats {
counters.add(failedIterationsCounter);
}
public Timer getTimer(TenantId tenantId, String status){
public Timer getTimer(TenantId tenantId, String status) {
return tenantMsgProcessTimers.computeIfAbsent(tenantId,
id -> statsFactory.createTimer(StatsType.RULE_ENGINE.getName() + "." + queueName,
"tenantId", tenantId.getId().toString(),

View File

@ -68,7 +68,7 @@ public class RpcCleanUpService {
long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getRpcTtlDays());
long expirationTime = System.currentTimeMillis() - ttl;
long totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime);
int totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime);
if (totalRemoved > 0) {
log.info("Removed {} outdated rpc(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));

View File

@ -51,6 +51,8 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.WEB;
@Service
@TbCoreComponent
@RequiredArgsConstructor
@ -104,7 +106,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void fetchUnreadNotifications(NotificationsSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId());
PageData<Notification> notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(),
(UserId) subscription.getEntityId(), subscription.getLimit());
WEB, (UserId) subscription.getEntityId(), subscription.getLimit());
subscription.getLatestUnreadNotifications().clear();
notifications.getData().forEach(notification -> {
subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification);
@ -114,7 +116,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId());
int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), (UserId) subscription.getEntityId());
int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), WEB, (UserId) subscription.getEntityId());
subscription.getTotalUnreadCounter().set(unreadCount);
}
@ -235,7 +237,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
@Override
public void handleMarkAllAsReadCmd(WebSocketSessionRef sessionRef, MarkAllNotificationsAsReadCmd cmd) {
SecurityUser securityCtx = sessionRef.getSecurityCtx();
notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), securityCtx.getId());
notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), WEB, securityCtx.getId());
}
@Override

View File

@ -29,6 +29,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.StringUtils;
@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -55,6 +57,7 @@ import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -327,6 +330,53 @@ public class AssetControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarm.getId().getId().toString()))));
}
@Test
public void testDeleteAssetWithPropagatedAlarm() throws Exception {
Device device = new Device();
device.setTenantId(savedTenant.getTenantId());
device.setName("Test device");
device.setLabel("Label");
device.setType("default");
device = doPost("/api/device", device, Device.class);
Asset asset = new Asset();
asset.setName("My asset");
asset.setType("default");
asset = doPost("/api/asset", asset, Asset.class);
EntityRelation entityRelation = new EntityRelation(asset.getId(), device.getId(), "CONTAINS");
doPost("/api/relation", entityRelation);
//create alarm
Alarm alarm = Alarm.builder()
.tenantId(savedTenant.getTenantId())
.originator(device.getId())
.severity(AlarmSeverity.CRITICAL)
.type("test_type")
.propagate(true)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
PageData<AlarmInfo> deviceAlarms = doGetTyped("/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=10", new TypeReference<>() {
});
assertThat(deviceAlarms.getData()).hasSize(1);
PageData<AlarmInfo> assetAlarms = doGetTyped("/api/alarm/ASSET/" + asset.getUuidId() + "?page=0&pageSize=10", new TypeReference<>() {
});
assertThat(assetAlarms.getData()).hasSize(1);
//delete asset
doDelete("/api/asset/" + asset.getId().getId().toString())
.andExpect(status().isOk());
//check device alarms
PageData<AlarmInfo> deviceAlarmsAfterAssetDeletion = doGetTyped("/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=10", new TypeReference<PageData<AlarmInfo>>() {
});
assertThat(deviceAlarmsAfterAssetDeletion.getData()).hasSize(1);
}
@Test
public void testDeleteAssetAssignedToEntityView() throws Exception {
Asset asset1 = new Asset();

View File

@ -145,7 +145,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
if (deliveryMethods.length == 0) {
deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB};
deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP};
}
NotificationTemplate notificationTemplate = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods);
return submitNotificationRequest(targets, notificationTemplate.getId(), delayInSec);
@ -247,8 +247,12 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
}
protected List<Notification> getMyNotifications(boolean unreadOnly, int limit) throws Exception {
return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&", new TypeReference<PageData<Notification>>() {},
new PageLink(limit, 0), unreadOnly).getData();
return getMyNotifications(NotificationDeliveryMethod.WEB, unreadOnly, limit);
}
protected List<Notification> getMyNotifications(NotificationDeliveryMethod deliveryMethod, boolean unreadOnly, int limit) throws Exception {
return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&deliveryMethod={deliveryMethod}&", new TypeReference<PageData<Notification>>() {},
new PageLink(limit, 0), unreadOnly, deliveryMethod).getData();
}
protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) {

View File

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
@ -52,6 +53,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestPrevie
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmCommentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig;
@ -81,7 +83,6 @@ import org.thingsboard.server.common.data.notification.template.WebDeliveryMetho
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.notification.DefaultNotifications;
import org.thingsboard.server.dao.notification.NotificationDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.notification.channels.MicrosoftTeamsNotificationChannel;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate;
@ -116,17 +117,25 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Autowired
private NotificationCenter notificationCenter;
@Autowired
private NotificationDao notificationDao;
@Autowired
private MicrosoftTeamsNotificationChannel microsoftTeamsNotificationChannel;
@MockBean
private FirebaseService firebaseService;
private static final String TEST_MOBILE_TOKEN = "tenantFcmToken";
@Before
public void beforeEach() throws Exception {
loginCustomerUser();
wsClient = getWsClient();
loginSysAdmin();
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
saveNotificationSettings(config);
loginTenantAdmin();
mobileToken = TEST_MOBILE_TOKEN;
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
}
@Test
@ -242,7 +251,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
int notificationsCount = 20;
wsClient.registerWaitForUpdate(notificationsCount);
for (int i = 1; i <= notificationsCount; i++) {
submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB);
submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP);
}
wsClient.waitForUpdate(true);
assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(notificationsCount);
@ -306,7 +315,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTarget target = createNotificationTarget(savedDifferentTenantUser.getId());
int notificationsCount = 20;
for (int i = 0; i < notificationsCount; i++) {
NotificationRequest request = submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB);
NotificationRequest request = submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP);
awaitNotificationRequest(request.getId());
}
List<NotificationRequest> requests = notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(tenantId, EntityType.USER, new PageLink(100)).getData();
@ -500,7 +509,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testNotificationRequestInfo() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = new NotificationDeliveryMethod[]{
NotificationDeliveryMethod.WEB
NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP
};
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Test subject", "Test text", deliveryMethods);
NotificationTarget target = createNotificationTarget(tenantAdminUserId);
@ -525,7 +534,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
}
@ -727,17 +735,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testMobileAppNotifications() throws Exception {
loginSysAdmin();
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
saveNotificationSettings(config);
loginCustomerUser();
mobileToken = "customerFcmToken";
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
loginTenantAdmin();
mobileToken = "tenantFcmToken1";
mobileToken = TEST_MOBILE_TOKEN;
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
mobileToken = "tenantFcmToken2";
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
@ -746,7 +749,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
loginTenantAdmin();
NotificationTarget target = createNotificationTarget(new AllUsersFilter());
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Title", "Message", NotificationDeliveryMethod.MOBILE_APP);
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Title", "Message", NotificationDeliveryMethod.MOBILE_APP, NotificationDeliveryMethod.WEB);
((MobileAppDeliveryMethodNotificationTemplate) template.getConfiguration().getDeliveryMethodsTemplates().get(NotificationDeliveryMethod.MOBILE_APP))
.setAdditionalConfig(JacksonUtil.newObjectNode().set("test", JacksonUtil.newObjectNode().put("test", "test")));
saveNotificationTemplate(template);
@ -758,11 +761,19 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
.contains("doesn't use the mobile app");
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken1"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))));
eq(TEST_MOBILE_TOKEN), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1));
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken2"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))));
eq("tenantFcmToken2"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1));
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("customerFcmToken"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))));
eq("customerFcmToken"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1));
assertThat(getMyNotifications(NotificationDeliveryMethod.MOBILE_APP, true, 10)).singleElement().satisfies(notification -> {
assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.MOBILE_APP);
assertThat(notification.getText()).isEqualTo("Message");
assertThat(notification.getSubject()).isEqualTo("Title");
});
assertThat(getMyNotifications(true, 10)).singleElement().satisfies(notification -> {
assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.WEB);
});
verifyNoMoreInteractions(firebaseService);
clearInvocations(firebaseService);
@ -770,26 +781,24 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
request = submitNotificationRequest(List.of(target.getId()), template.getId(), 0);
awaitNotificationRequest(request.getId());
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken1"), eq("Title"), eq("Message"), anyMap());
eq(TEST_MOBILE_TOKEN), eq("Title"), eq("Message"), anyMap(), eq(2));
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("customerFcmToken"), eq("Title"), eq("Message"), anyMap());
eq("customerFcmToken"), eq("Title"), eq("Message"), anyMap(), eq(2));
verifyNoMoreInteractions(firebaseService);
Integer unreadCount = doGet("/api/notifications/unread/count", Integer.class);
assertThat(unreadCount).isEqualTo(2);
}
@Test
public void testMobileAppNotifications_ruleBased() throws Exception {
loginSysAdmin();
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
saveNotificationSettings(config);
loginTenantAdmin();
mobileToken = "tenantFcmToken";
mobileToken = TEST_MOBILE_TOKEN;
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
createNotificationRule(AlarmCommentNotificationRuleTriggerConfig.builder().onlyUserComments(true).build(),
DefaultNotifications.alarmComment.getSubject(), DefaultNotifications.alarmComment.getText(),
List.of(createNotificationTarget(tenantAdminUserId).getId()), NotificationDeliveryMethod.MOBILE_APP);
List.of(createNotificationTarget(tenantAdminUserId).getId()), NotificationDeliveryMethod.MOBILE_APP, NotificationDeliveryMethod.WEB);
Device device = createDevice("test", "test");
UUID alarmDashboardId = UUID.randomUUID();
@ -802,18 +811,21 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
.put("dashboardId", alarmDashboardId.toString()))
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
AlarmId alarmId = alarm.getId();
AlarmComment comment = new AlarmComment();
comment.setComment(JacksonUtil.newObjectNode()
.put("text", "text"));
doPost("/api/alarm/" + alarm.getId() + "/comment", comment, AlarmComment.class);
doPost("/api/alarm/" + alarmId + "/comment", comment, AlarmComment.class);
String expectedSubject = "Comment on 'test' alarm";
String expectedBody = TENANT_ADMIN_EMAIL + " added comment: text";
ArgumentCaptor<Map<String, String>> msgCaptor = ArgumentCaptor.forClass(Map.class);
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken"), eq("Comment on 'test' alarm"),
eq(TENANT_ADMIN_EMAIL + " added comment: text"),
msgCaptor.capture());
eq(TEST_MOBILE_TOKEN), eq(expectedSubject),
eq(expectedBody),
msgCaptor.capture(), eq(1));
});
Map<String, String> firebaseMessageData = msgCaptor.getValue();
assertThat(firebaseMessageData.keySet()).doesNotContainNull().doesNotContain("");
@ -823,6 +835,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(firebaseMessageData.get("onClick.enabled")).isEqualTo("true");
assertThat(firebaseMessageData.get("onClick.linkType")).isEqualTo("DASHBOARD");
assertThat(firebaseMessageData.get("onClick.dashboardId")).isEqualTo(alarmDashboardId.toString());
assertThat(getMyNotifications(NotificationDeliveryMethod.MOBILE_APP, true, 10)).singleElement().satisfies(notification -> {
assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.MOBILE_APP);
assertThat(notification.getSubject()).isEqualTo(expectedSubject);
assertThat(notification.getText()).isEqualTo(expectedBody);
assertThat(notification.getInfo()).asInstanceOf(type(AlarmCommentNotificationInfo.class))
.matches(info -> info.getAlarmId().equals(alarmId.getId()) && info.getDashboardId().getId().equals(alarmDashboardId));
});
}
@Test

View File

@ -106,6 +106,8 @@ public interface AlarmService extends EntityDaoService {
PageData<AlarmId> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, PageLink pageLink);
PageData<AlarmId> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink);
void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId);
void deleteEntityAlarmRecordsByTenantId(TenantId tenantId);

View File

@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -30,13 +31,13 @@ public interface NotificationService {
boolean markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);
int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId);
int markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink);
PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink);
PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit);
PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit);
int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId);
int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId);

View File

@ -36,13 +36,12 @@ public class Notification extends BaseData<NotificationId> {
private NotificationRequestId requestId;
private UserId recipientId;
private NotificationType type;
private NotificationDeliveryMethod deliveryMethod;
private String subject;
private String text;
private JsonNode additionalConfig;
private NotificationInfo info;
private NotificationStatus status;
}

View File

@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
.setConnectRequestMsg(ConnectRequestMsg.newBuilder()
.setEdgeRoutingKey(edgeKey)
.setEdgeSecret(edgeSecret)
.setEdgeVersion(EdgeVersion.V_3_6_2)
.setEdgeVersion(EdgeVersion.V_3_6_4)
.setMaxInboundMessageSize(maxInboundMessageSize)
.build())
.build());

View File

@ -37,6 +37,7 @@ enum EdgeVersion {
V_3_6_0 = 3;
V_3_6_1 = 4;
V_3_6_2 = 5;
V_3_6_4 = 6;
}
/**

View File

@ -19,6 +19,7 @@ import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@ -61,12 +62,17 @@ public class DefaultStatsFactory implements StatsFactory {
@Override
public StatsCounter createStatsCounter(String key, String statsName) {
public StatsCounter createStatsCounter(String key, String statsName, String... otherTags) {
String[] tags = new String[]{STATS_NAME_TAG, statsName};
if (otherTags.length > 0) {
if (otherTags.length % 2 != 0) {
throw new IllegalArgumentException("Invalid tags array size");
}
tags = ArrayUtils.addAll(tags, otherTags);
}
return new StatsCounter(
new AtomicInteger(0),
metricsEnabled ?
meterRegistry.counter(key, STATS_NAME_TAG, statsName)
: STUB_COUNTER,
metricsEnabled ? meterRegistry.counter(key, tags) : STUB_COUNTER,
statsName
);
}

View File

@ -18,7 +18,8 @@ package org.thingsboard.server.common.stats;
import io.micrometer.core.instrument.Timer;
public interface StatsFactory {
StatsCounter createStatsCounter(String key, String statsName);
StatsCounter createStatsCounter(String key, String statsName, String... otherTags);
DefaultCounter createDefaultCounter(String key, String... tags);
@ -27,4 +28,5 @@ public interface StatsFactory {
MessagesStats createMessagesStats(String key);
Timer createTimer(String key, String... tags);
}

View File

@ -231,6 +231,10 @@ public class JacksonUtil {
return node;
}
public static ObjectNode asObject(JsonNode node) {
return node != null && node.isObject() ? ((ObjectNode) node) : newObjectNode();
}
public static void replaceUuidsRecursively(JsonNode node, Set<String> skippedRootFields, Pattern includedFieldsPattern, UnaryOperator<UUID> replacer, boolean root) {
if (node == null) {
return;

View File

@ -73,7 +73,7 @@ public class SslUtil {
@SneakyThrows
public static PrivateKey readPrivateKey(String fileContent, String passStr) {
char[] password = StringUtils.isEmpty(passStr) ? EMPTY_PASS : passStr.toCharArray();
char[] password = getPassword(passStr);
PrivateKey privateKey = null;
JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter();
@ -102,4 +102,8 @@ public class SslUtil {
return privateKey;
}
public static char[] getPassword(String passStr) {
return StringUtils.isEmpty(passStr) ? EMPTY_PASS : passStr.toCharArray();
}
}

View File

@ -157,12 +157,10 @@ public class DefaultGitRepositoryService implements GitRepositoryService {
@Override
public void fetch(TenantId tenantId) throws GitAPIException {
var repository = repositories.get(tenantId);
if (repository != null) {
log.debug("[{}] Fetching tenant repository.", tenantId);
repository.fetch();
log.debug("[{}] Fetched tenant repository.", tenantId);
}
var repository = checkRepository(tenantId);
log.debug("[{}] Fetching tenant repository.", tenantId);
repository.fetch();
log.debug("[{}] Fetched tenant repository.", tenantId);
}
@Override
@ -195,8 +193,17 @@ public class DefaultGitRepositoryService implements GitRepositoryService {
}
private GitRepository checkRepository(TenantId tenantId) {
return Optional.ofNullable(repositories.get(tenantId))
GitRepository gitRepository = Optional.ofNullable(repositories.get(tenantId))
.orElseThrow(() -> new IllegalStateException("Repository is not initialized"));
if (!Files.exists(Path.of(gitRepository.getDirectory()))) {
try {
return cloneRepository(tenantId, gitRepository.getSettings());
} catch (Exception e) {
throw new IllegalStateException("Could not initialize the repository: " + e.getMessage(), e);
}
}
return gitRepository;
}
@Override
@ -229,17 +236,7 @@ public class DefaultGitRepositoryService implements GitRepositoryService {
testRepository(tenantId, settings);
clearRepository(tenantId);
log.debug("[{}] Init tenant repository started.", tenantId);
Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString());
GitRepository repository;
if (Files.exists(repositoryDirectory)) {
FileUtils.forceDelete(repositoryDirectory.toFile());
}
Files.createDirectories(repositoryDirectory);
repository = GitRepository.clone(settings, repositoryDirectory.toFile());
repositories.put(tenantId, repository);
log.debug("[{}] Init tenant repository completed.", tenantId);
cloneRepository(tenantId, settings);
}
@Override
@ -276,4 +273,18 @@ public class DefaultGitRepositoryService implements GitRepositoryService {
String entityId = StringUtils.substringBetween(path, "/", ".json");
return EntityIdFactory.getByTypeAndUuid(entityType, entityId);
}
private GitRepository cloneRepository(TenantId tenantId, RepositorySettings settings) throws Exception {
log.debug("[{}] Init tenant repository started.", tenantId);
Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString());
if (Files.exists(repositoryDirectory)) {
FileUtils.forceDelete(repositoryDirectory.toFile());
}
Files.createDirectories(repositoryDirectory);
GitRepository repository = GitRepository.clone(settings, repositoryDirectory.toFile());
repositories.put(tenantId, repository);
log.debug("[{}] Init tenant repository completed.", tenantId);
return repository;
}
}

View File

@ -80,6 +80,8 @@ public interface AlarmDao extends Dao<Alarm> {
PageData<AlarmId> findAlarmIdsByAssigneeId(TenantId tenantId, UUID userId, PageLink pageLink);
PageData<AlarmId> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink);
void createEntityAlarmRecord(EntityAlarm entityAlarm);
List<EntityAlarm> findEntityAlarmRecords(TenantId tenantId, AlarmId id);

View File

@ -306,6 +306,12 @@ public class BaseAlarmService extends AbstractCachedEntityService<TenantId, Page
return alarmDao.findAlarmIdsByAssigneeId(tenantId, userId.getId(), pageLink);
}
@Override
public PageData<AlarmId> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink) {
log.trace("[{}] Executing findAlarmsByOriginatorId [{}]", tenantId, originatorId);
return alarmDao.findAlarmIdsByOriginatorId(tenantId, originatorId, pageLink);
}
@Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
AlarmStatus alarmStatus, String assigneeId) {

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.entity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.HasCustomerId;
@ -60,6 +61,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
private EntityQueryDao entityQueryDao;
@Autowired
@Lazy
EntityServiceRegistry entityServiceRegistry;
@Override

View File

@ -17,15 +17,12 @@ package org.thingsboard.server.dao.entity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@ -33,14 +30,13 @@ import java.util.Map;
@Slf4j
public class DefaultEntityServiceRegistry implements EntityServiceRegistry {
private final ApplicationContext applicationContext;
private final List<EntityDaoService> entityDaoServices;
private final Map<EntityType, EntityDaoService> entityDaoServicesMap = new HashMap<>();
@EventListener(ContextRefreshedEvent.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
@PostConstruct
public void init() {
log.debug("Initializing EntityServiceRegistry on ContextRefreshedEvent");
applicationContext.getBeansOfType(EntityDaoService.class).values().forEach(entityDaoService -> {
entityDaoServices.forEach(entityDaoService -> {
EntityType entityType = entityDaoService.getEntityType();
entityDaoServicesMap.put(entityType, entityDaoService);
if (EntityType.RULE_CHAIN.equals(entityType)) {

View File

@ -620,6 +620,7 @@ public class ModelConstants {
public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id";
public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id";
public static final String NOTIFICATION_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_DELIVERY_METHOD_PROPERTY = "delivery_method";
public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject";
public static final String NOTIFICATION_TEXT_PROPERTY = "body";
public static final String NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY = "additional_config";

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@ -56,6 +57,10 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
@Column(name = ModelConstants.NOTIFICATION_TYPE_PROPERTY, nullable = false)
private NotificationType type;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_DELIVERY_METHOD_PROPERTY, nullable = false)
private NotificationDeliveryMethod deliveryMethod;
@Column(name = ModelConstants.NOTIFICATION_SUBJECT_PROPERTY)
private String subject;
@ -82,6 +87,7 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
setRequestId(getUuid(notification.getRequestId()));
setRecipientId(getUuid(notification.getRecipientId()));
setType(notification.getType());
setDeliveryMethod(notification.getDeliveryMethod());
setSubject(notification.getSubject());
setText(notification.getText());
setAdditionalConfig(notification.getAdditionalConfig());
@ -97,6 +103,7 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
notification.setRequestId(getEntityId(requestId, NotificationRequestId::new));
notification.setRecipientId(getEntityId(recipientId, UserId::new));
notification.setType(type);
notification.setDeliveryMethod(deliveryMethod);
notification.setSubject(subject);
notification.setText(text);
notification.setAdditionalConfig(additionalConfig);

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -57,29 +58,29 @@ public class DefaultNotificationService implements NotificationService, EntityDa
}
@Override
public int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) {
return notificationDao.updateStatusByRecipientId(tenantId, recipientId, NotificationStatus.READ);
public int markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
return notificationDao.updateStatusByDeliveryMethodAndRecipientId(tenantId, deliveryMethod, recipientId, NotificationStatus.READ);
}
@Override
public PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink) {
public PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink) {
if (unreadOnly) {
return notificationDao.findUnreadByRecipientIdAndPageLink(tenantId, recipientId, pageLink);
return notificationDao.findUnreadByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink);
} else {
return notificationDao.findByRecipientIdAndPageLink(tenantId, recipientId, pageLink);
return notificationDao.findByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink);
}
}
@Override
public PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit) {
public PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit) {
SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC);
PageLink pageLink = new PageLink(limit, 0, null, sortOrder);
return findNotificationsByRecipientIdAndReadStatus(tenantId, recipientId, true, pageLink);
return findNotificationsByRecipientIdAndReadStatus(tenantId, deliveryMethod, recipientId, true, pageLink);
}
@Override
public int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId) {
return notificationDao.countUnreadByRecipientId(tenantId, recipientId);
public int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
return notificationDao.countUnreadByDeliveryMethodAndRecipientId(tenantId, deliveryMethod, recipientId);
}
@Override

View File

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -27,19 +28,17 @@ import org.thingsboard.server.dao.Dao;
public interface NotificationDao extends Dao<Notification> {
PageData<Notification> findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink);
PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink);
PageData<Notification> findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink);
PageData<Notification> findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink);
boolean updateStatusByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId, NotificationStatus status);
int countUnreadByRecipientId(TenantId tenantId, UserId recipientId);
PageData<Notification> findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink);
int countUnreadByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
boolean deleteByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId);
int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status);
int updateStatusByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, NotificationStatus status);
void deleteByRequestId(TenantId tenantId, NotificationRequestId requestId);

View File

@ -30,5 +30,6 @@ public interface RpcDao extends Dao<Rpc> {
PageData<Rpc> findAllRpcByTenantId(TenantId tenantId, PageLink pageLink);
Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime);
int deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime);
}

View File

@ -321,6 +321,9 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.assigneeId = :assigneeId")
Page<UUID> findAlarmIdsByAssigneeId(@Param("tenantId") UUID tenantId, @Param("assigneeId") UUID assigneeId, Pageable pageable);
@Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.originatorId = :originatorId")
Page<UUID> findAlarmIdsByOriginatorId(@Param("tenantId") UUID tenantId, @Param("originatorId") UUID originatorId, Pageable pageable);
@Query(value = "SELECT create_or_update_active_alarm(:t_id, :c_id, :a_id, :a_created_ts, :a_o_id, :a_o_type, :a_type, :a_severity, " +
":a_start_ts, :a_end_ts, :a_details, :a_propagate, :a_propagate_to_owner, " +
":a_propagate_to_tenant, :a_propagation_types, :a_creation_enabled)", nativeQuery = true)

View File

@ -296,6 +296,12 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
.mapData(AlarmId::new);
}
@Override
public PageData<AlarmId> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink) {
return DaoUtil.pageToPageData(alarmRepository.findAlarmIdsByOriginatorId(tenantId.getId(), originatorId.getId(), DaoUtil.toPageable(pageLink)))
.mapData(AlarmId::new);
}
@Override
public void createEntityAlarmRecord(EntityAlarm entityAlarm) {
log.debug("Saving entity {}", entityAlarm);

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -51,14 +52,14 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao<NotificationEn
private int partitionSizeInHours;
@Override
public PageData<Notification> findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndStatusNot(recipientId.getId(), NotificationStatus.READ,
pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
public PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod,
recipientId.getId(), NotificationStatus.READ, pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<Notification> findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByRecipientId(recipientId.getId(),
public PageData<Notification> findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientId(deliveryMethod, recipientId.getId(),
pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
}
@ -71,13 +72,8 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao<NotificationEn
* For this hot method, the partial index `idx_notification_recipient_id_unread` was introduced since 3.6.0
* */
@Override
public int countUnreadByRecipientId(TenantId tenantId, UserId recipientId) {
return notificationRepository.countByRecipientIdAndStatusNot(recipientId.getId(), NotificationStatus.READ);
}
@Override
public PageData<Notification> findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByRequestId(notificationRequestId.getId(), DaoUtil.toPageable(pageLink)));
public int countUnreadByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
return notificationRepository.countByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod, recipientId.getId(), NotificationStatus.READ);
}
@Override
@ -86,8 +82,8 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao<NotificationEn
}
@Override
public int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status) {
return notificationRepository.updateStatusByRecipientId(recipientId.getId(), status);
public int updateStatusByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, NotificationStatus status) {
return notificationRepository.updateStatusByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod, recipientId.getId(), status);
}
@Override

View File

@ -23,6 +23,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.dao.model.sql.NotificationEntity;
@ -31,20 +32,23 @@ import java.util.UUID;
@Repository
public interface NotificationRepository extends JpaRepository<NotificationEntity, UUID> {
@Query("SELECT n FROM NotificationEntity n WHERE n.recipientId = :recipientId AND n.status <> :status " +
@Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod " +
"AND n.recipientId = :recipientId AND n.status <> :status " +
"AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " +
"OR ilike(n.text, concat('%', :searchText, '%')) = true)")
Page<NotificationEntity> findByRecipientIdAndStatusNot(@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status,
@Param("searchText") String searchText,
Pageable pageable);
Page<NotificationEntity> findByDeliveryMethodAndRecipientIdAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,
@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT n FROM NotificationEntity n WHERE n.recipientId = :recipientId " +
@Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod AND n.recipientId = :recipientId " +
"AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " +
"OR ilike(n.text, concat('%', :searchText, '%')) = true)")
Page<NotificationEntity> findByRecipientId(@Param("recipientId") UUID recipientId,
@Param("searchText") String searchText,
Pageable pageable);
Page<NotificationEntity> findByDeliveryMethodAndRecipientId(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,
@Param("recipientId") UUID recipientId,
@Param("searchText") String searchText,
Pageable pageable);
@Modifying
@Transactional
@ -54,9 +58,7 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status);
int countByRecipientIdAndStatusNot(UUID recipientId, NotificationStatus status);
Page<NotificationEntity> findByRequestId(UUID requestId, Pageable pageable);
int countByDeliveryMethodAndRecipientIdAndStatusNot(NotificationDeliveryMethod deliveryMethod, UUID recipientId, NotificationStatus status);
@Transactional
@Modifying
@ -76,8 +78,9 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
@Modifying
@Transactional
@Query("UPDATE NotificationEntity n SET n.status = :status " +
"WHERE n.recipientId = :recipientId AND n.status <> :status")
int updateStatusByRecipientId(@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status);
"WHERE n.deliveryMethod = :deliveryMethod AND n.recipientId = :recipientId AND n.status <> :status")
int updateStatusByDeliveryMethodAndRecipientIdAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,
@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status);
}

View File

@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -67,8 +68,9 @@ public class JpaRpcDao extends JpaAbstractDao<RpcEntity, Rpc> implements RpcDao
return DaoUtil.toPageData(rpcRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Transactional
@Override
public Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime) {
public int deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime) {
return rpcRepository.deleteOutdatedRpcByTenantId(tenantId.getId(), expirationTime);
}

View File

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.rpc;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.rpc.RpcStatus;
@ -32,7 +33,8 @@ public interface RpcRepository extends JpaRepository<RpcEntity, UUID> {
Page<RpcEntity> findAllByTenantId(UUID tenantId, Pageable pageable);
@Query(value = "WITH deleted AS (DELETE FROM rpc WHERE (tenant_id = :tenantId AND created_time < :expirationTime) IS TRUE RETURNING *) SELECT count(*) FROM deleted",
@Modifying
@Query(value = "DELETE FROM rpc WHERE tenant_id = :tenantId AND created_time < :expirationTime",
nativeQuery = true)
Long deleteOutdatedRpcByTenantId(@Param("tenantId") UUID tenantId, @Param("expirationTime") Long expirationTime);
int deleteOutdatedRpcByTenantId(@Param("tenantId") UUID tenantId, @Param("expirationTime") Long expirationTime);
}

View File

@ -118,9 +118,9 @@ CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id);
CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_created_time ON notification(delivery_method, recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_unread ON notification(delivery_method, recipient_id) WHERE status <> 'READ';
CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);

View File

@ -864,6 +864,7 @@ CREATE TABLE IF NOT EXISTS notification (
request_id UUID,
recipient_id UUID NOT NULL,
type VARCHAR(50) NOT NULL,
delivery_method VARCHAR(50) NOT NULL,
subject VARCHAR(255),
body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000),

View File

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed 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.thingsboard.server.dao.sql.rpc;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.dao.AbstractJpaDaoTest;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
public class JpaRpcDaoTest extends AbstractJpaDaoTest {
@Autowired
JpaRpcDao rpcDao;
@Test
public void deleteOutdated() {
Rpc rpc = new Rpc();
rpc.setTenantId(TenantId.SYS_TENANT_ID);
rpc.setDeviceId(new DeviceId(UUID.randomUUID()));
rpc.setStatus(RpcStatus.QUEUED);
rpc.setRequest(JacksonUtil.toJsonNode("{}"));
rpcDao.saveAndFlush(rpc.getTenantId(), rpc);
rpc.setId(null);
rpcDao.saveAndFlush(rpc.getTenantId(), rpc);
TenantId tenantId = TenantId.fromUUID(UUID.fromString("3d193a7a-774b-4c05-84d5-f7fdcf7a37cf"));
rpc.setId(null);
rpc.setTenantId(tenantId);
rpc.setDeviceId(new DeviceId(UUID.randomUUID()));
rpcDao.saveAndFlush(rpc.getTenantId(), rpc);
assertThat(rpcDao.deleteOutdatedRpcByTenantId(TenantId.SYS_TENANT_ID, 0L)).isEqualTo(0);
assertThat(rpcDao.deleteOutdatedRpcByTenantId(TenantId.SYS_TENANT_ID, Long.MAX_VALUE)).isEqualTo(2);
assertThat(rpcDao.deleteOutdatedRpcByTenantId(tenantId, System.currentTimeMillis() + 1)).isEqualTo(1);
}
}

View File

@ -10,6 +10,9 @@
<logger name="org.thingsboard.server.dao" level="WARN"/>
<logger name="org.testcontainers" level="INFO" />
<!-- Log Hibernate SQL queries -->
<!-- <logger name="org.hibernate.SQL" level="DEBUG"/> -->
<root level="WARN">
<appender-ref ref="console"/>
</root>

View File

@ -15,7 +15,7 @@
"dependencies": {
"@aws-sdk/client-sqs": "^3.121.0",
"@azure/service-bus": "^7.5.1",
"@google-cloud/pubsub": "^3.0.1",
"@google-cloud/pubsub": "^4.3.3",
"amqplib": "^0.10.0",
"config": "^3.3.7",
"express": "^4.18.1",

View File

@ -797,68 +797,68 @@
enabled "2.0.x"
kuler "^2.0.0"
"@google-cloud/paginator@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-4.0.0.tgz#9c3e01544717aecb9a922b4269ff298f30a0f1bb"
integrity sha512-wNmCZl+2G2DmgT/VlF+AROf80SoaC/CwS8trwmjNaq26VRNK8yPbU5F/Vy+R9oDAGKWQU2k8+Op5H4kFJVXFaQ==
"@google-cloud/paginator@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-5.0.0.tgz#b8cc62f151685095d11467402cbf417c41bf14e6"
integrity sha512-87aeg6QQcEPxGCOthnpUjvw4xAZ57G7pL8FS0C4e/81fr3FjkpUpibf1s2v5XGyGhUVGF4Jfg7yEcxqn2iUw1w==
dependencies:
arrify "^2.0.0"
extend "^3.0.2"
"@google-cloud/precise-date@^2.0.0":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-2.0.3.tgz#14f6f28ce35dabf3882e7aeab1c9d51bd473faed"
integrity sha512-+SDJ3ZvGkF7hzo6BGa8ZqeK3F6Z4+S+KviC9oOK+XCs3tfMyJCh/4j93XIWINgMMDIh9BgEvlw4306VxlXIlYA==
"@google-cloud/precise-date@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-4.0.0.tgz#e179893a3ad628b17a6fabdfcc9d468753aac11a"
integrity sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==
"@google-cloud/projectify@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-2.0.1.tgz#13350ee609346435c795bbfe133a08dfeab78d65"
integrity sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==
"@google-cloud/projectify@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-4.0.0.tgz#d600e0433daf51b88c1fa95ac7f02e38e80a07be"
integrity sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==
"@google-cloud/promisify@^2.0.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-2.0.2.tgz#81d654b4cb227c65c7ad2f9a7715262febd409ed"
integrity sha512-EvuabjzzZ9E2+OaYf+7P9OAiiwbTxKYL0oGLnREQd+Su2NTQBpomkdlkBowFvyWsaV0d1sSGxrKpSNcrhPqbxg==
"@google-cloud/promisify@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-4.0.0.tgz#a906e533ebdd0f754dca2509933334ce58b8c8b1"
integrity sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==
"@google-cloud/pubsub@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-3.0.1.tgz#3a6bb9649a1b4309d82d8670a9c01375e622692f"
integrity sha512-dznNbRd/Y8J0C0xvdvCPi3B1msK/dj/Nya+NQZ2doUOLT6eoa261tBwk9umOQs5L5GKcdlqQKbBjrNjDYVbzQA==
"@google-cloud/pubsub@^4.3.3":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-4.3.3.tgz#3d3f947ae8fca1694388592f22f77fdc3008ee8d"
integrity sha512-vJKh9L4dHf1XGSDKS1SB0IpqP/sUajQh4/QwhYasuq/NjzfHSxqSt+CuhrFGb5/gioTWE4gce0sn7h1SW7qESg==
dependencies:
"@google-cloud/paginator" "^4.0.0"
"@google-cloud/precise-date" "^2.0.0"
"@google-cloud/projectify" "^2.0.0"
"@google-cloud/promisify" "^2.0.0"
"@opentelemetry/api" "^1.0.0"
"@opentelemetry/semantic-conventions" "^1.0.0"
"@google-cloud/paginator" "^5.0.0"
"@google-cloud/precise-date" "^4.0.0"
"@google-cloud/projectify" "^4.0.0"
"@google-cloud/promisify" "^4.0.0"
"@opentelemetry/api" "^1.6.0"
"@opentelemetry/semantic-conventions" "~1.21.0"
"@types/duplexify" "^3.6.0"
"@types/long" "^4.0.0"
arrify "^2.0.0"
extend "^3.0.2"
google-auth-library "^8.0.2"
google-gax "^3.0.1"
google-auth-library "^9.3.0"
google-gax "^4.3.1"
heap-js "^2.2.0"
is-stream-ended "^0.1.4"
lodash.snakecase "^4.1.1"
p-defer "^3.0.0"
"@grpc/grpc-js@~1.6.0":
version "1.6.7"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.7.tgz#4c4fa998ff719fe859ac19fe977fdef097bb99aa"
integrity sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==
"@grpc/grpc-js@~1.10.0":
version "1.10.6"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.6.tgz#1e3eb1af911dc888fbef7452f56a7573b8284d54"
integrity sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==
dependencies:
"@grpc/proto-loader" "^0.6.4"
"@types/node" ">=12.12.47"
"@grpc/proto-loader" "^0.7.10"
"@js-sdsl/ordered-map" "^4.4.2"
"@grpc/proto-loader@^0.6.12", "@grpc/proto-loader@^0.6.4":
version "0.6.13"
resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc"
integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==
"@grpc/proto-loader@^0.7.0", "@grpc/proto-loader@^0.7.10":
version "0.7.12"
resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.12.tgz#787b58e3e3771df30b1567c057b6ab89e3a42911"
integrity sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q==
dependencies:
"@types/long" "^4.0.1"
lodash.camelcase "^4.3.0"
long "^4.0.0"
protobufjs "^6.11.3"
yargs "^16.2.0"
long "^5.0.0"
protobufjs "^7.2.4"
yargs "^17.7.2"
"@jridgewell/resolve-uri@^3.0.3":
version "3.0.8"
@ -878,6 +878,11 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@js-sdsl/ordered-map@^4.4.2":
version "4.4.2"
resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c"
integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
"@nodelib/fs.scandir@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
@ -899,15 +904,20 @@
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"
"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.0.1":
"@opentelemetry/api@^1.0.1":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.1.0.tgz#563539048255bbe1a5f4f586a4a10a1bb737f44a"
integrity sha512-hf+3bwuBwtXsugA2ULBc95qxrOqP2pOekLz34BJhcAKawt94vfeNyUKpYc0lZQ/3sCP6LqRa7UAdHA7i5UODzQ==
"@opentelemetry/semantic-conventions@^1.0.0":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.3.1.tgz#ba07b864a3c955f061aa30ea3ef7f4ae4449794a"
integrity sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA==
"@opentelemetry/api@^1.6.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec"
integrity sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==
"@opentelemetry/semantic-conventions@~1.21.0":
version "1.21.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.21.0.tgz#83f7479c524ab523ac2df702ade30b9724476c72"
integrity sha512-lkC8kZYntxVKr7b8xmjCVUgE0a8xgDakPyDo9uSWavXPyYqLgYYGdEd2j8NxihRyb6UwpX3G/hFUF4/9q2V+/g==
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
@ -974,6 +984,11 @@
dependencies:
defer-to-connect "^1.0.1"
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
"@tsconfig/node10@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
@ -1015,6 +1030,11 @@
"@types/connect" "*"
"@types/node" "*"
"@types/caseless@*":
version "0.12.5"
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5"
integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -1088,7 +1108,7 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.1.tgz#fdf6f6c6c73d3d8eee9c98a9a0485bc524b048d7"
integrity sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==
"@types/node@>=12.12.47", "@types/node@>=13.7.0":
"@types/node@>=13.7.0":
version "17.0.42"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.42.tgz#d7e8f22700efc94d125103075c074396b5f41f9b"
integrity sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==
@ -1108,6 +1128,16 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/request@^2.48.8":
version "2.48.12"
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30"
integrity sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==
dependencies:
"@types/caseless" "*"
"@types/node" "*"
"@types/tough-cookie" "*"
form-data "^2.5.0"
"@types/serve-static@*":
version "1.13.10"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
@ -1116,6 +1146,11 @@
"@types/mime" "^1"
"@types/node" "*"
"@types/tough-cookie@*":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304"
integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==
"@types/tunnel@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9"
@ -1167,6 +1202,13 @@ agent-base@6:
dependencies:
debug "4"
agent-base@^7.0.2:
version "7.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317"
integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==
dependencies:
debug "^4.3.4"
amqplib@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.10.0.tgz#766d696f8ceae097ee9eb73e6796999e5d40a1db"
@ -1462,6 +1504,15 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
clone-response@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
@ -1522,7 +1573,7 @@ colorspace@1.1.x:
color "3.0.x"
text-hex "1.0.x"
combined-stream@^1.0.8:
combined-stream@^1.0.6, combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -1609,7 +1660,7 @@ debug@2.6.9, debug@~2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.1:
debug@4:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@ -1623,6 +1674,13 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decompress-response@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
@ -1921,11 +1979,6 @@ fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
fast-xml-parser@3.19.0:
version "3.19.0"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz#cb637ec3f3999f51406dd8ff0e6fc4d83e520d01"
@ -1982,6 +2035,15 @@ for-each@^0.3.3:
dependencies:
is-callable "^1.1.3"
form-data@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
@ -2081,34 +2143,23 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
gaxios@^4.0.0:
version "4.3.3"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.3.3.tgz#d44bdefe52d34b6435cc41214fdb160b64abfc22"
integrity sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==
gaxios@^6.0.0, gaxios@^6.1.1:
version "6.4.0"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.4.0.tgz#08a42cb44d5123a72efaaf9f786c266e7f18be70"
integrity sha512-apAloYrY4dlBGlhauDAYSZveafb5U6+L9titing1wox6BvWM0TSXBp603zTrLpyLMGkrcFgohnUN150dFN/zOA==
dependencies:
abort-controller "^3.0.0"
extend "^3.0.2"
https-proxy-agent "^5.0.0"
https-proxy-agent "^7.0.1"
is-stream "^2.0.0"
node-fetch "^2.6.7"
node-fetch "^2.6.9"
uuid "^9.0.1"
gaxios@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.0.0.tgz#df11e5d0a45831dd39eb5fbbba0d6a6b09815e70"
integrity sha512-VD/yc5ln6XU8Ch1hyYY6kRMBE0Yc2np3fPyeJeYHhrPs1i8rgnsApPMWyrugkl7LLoSqpOJVBWlQIa87OAvt8Q==
gcp-metadata@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.0.tgz#9b0dd2b2445258e7597f2024332d20611cbd6b8c"
integrity sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==
dependencies:
abort-controller "^3.0.0"
extend "^3.0.2"
https-proxy-agent "^5.0.0"
is-stream "^2.0.0"
node-fetch "^2.6.7"
gcp-metadata@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-5.0.0.tgz#a00f999f60a4461401e7c515f8a3267cfb401ee7"
integrity sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==
dependencies:
gaxios "^5.0.0"
gaxios "^6.0.0"
json-bigint "^1.0.0"
get-caller-file@^2.0.5:
@ -2178,46 +2229,35 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
google-auth-library@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-8.0.2.tgz#5fa0f2d3795c3e4019d2bb315ade4454cc9c30b5"
integrity sha512-HoG+nWFAThLovKpvcbYzxgn+nBJPTfAwtq0GxPN821nOO+21+8oP7MoEHfd1sbDulUFFGfcjJr2CnJ4YssHcyg==
google-auth-library@^9.3.0:
version "9.7.0"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.7.0.tgz#dd99a08e2e3f70778de8be4ed8556460e237550a"
integrity sha512-I/AvzBiUXDzLOy4iIZ2W+Zq33W4lcukQv1nl7C8WUA6SQwyQwUwu3waNmWNAvzds//FG8SZ+DnKnW/2k6mQS8A==
dependencies:
arrify "^2.0.0"
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
fast-text-encoding "^1.0.0"
gaxios "^5.0.0"
gcp-metadata "^5.0.0"
gtoken "^5.3.2"
gaxios "^6.1.1"
gcp-metadata "^6.1.0"
gtoken "^7.0.0"
jws "^4.0.0"
lru-cache "^6.0.0"
google-gax@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-3.1.0.tgz#a6fea06bfd0157969ddc0a6d04c47980978ead02"
integrity sha512-OHKVHZtaYHwxrjiI4BH+IzRWH2Q75sZg+q0/RatxLicbeeCCIug99+NKQiE39B5rLnGENtcbUjmXiqJHbSrkSg==
google-gax@^4.3.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-4.3.2.tgz#417cbee97f2e68d78f641af19c0f15234c0dbd9c"
integrity sha512-2mw7qgei2LPdtGrmd1zvxQviOcduTnsvAWYzCxhOWXK4IQKmQztHnDQwD0ApB690fBQJemFKSU7DnceAy3RLzw==
dependencies:
"@grpc/grpc-js" "~1.6.0"
"@grpc/proto-loader" "^0.6.12"
"@grpc/grpc-js" "~1.10.0"
"@grpc/proto-loader" "^0.7.0"
"@types/long" "^4.0.0"
abort-controller "^3.0.0"
duplexify "^4.0.0"
fast-text-encoding "^1.0.3"
google-auth-library "^8.0.2"
is-stream-ended "^0.1.4"
google-auth-library "^9.3.0"
node-fetch "^2.6.1"
object-hash "^3.0.0"
proto3-json-serializer "^1.0.0"
protobufjs "6.11.3"
retry-request "^5.0.0"
google-p12-pem@^3.1.3:
version "3.1.4"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.1.4.tgz#123f7b40da204de4ed1fbf2fd5be12c047fc8b3b"
integrity sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==
dependencies:
node-forge "^1.3.1"
proto3-json-serializer "^2.0.0"
protobufjs "7.2.6"
retry-request "^7.0.0"
uuid "^9.0.1"
got@^9.6.0:
version "9.6.0"
@ -2241,13 +2281,12 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
gtoken@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.2.tgz#deb7dc876abe002178e0515e383382ea9446d58f"
integrity sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==
gtoken@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26"
integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==
dependencies:
gaxios "^4.0.0"
google-p12-pem "^3.1.3"
gaxios "^6.0.0"
jws "^4.0.0"
has-bigints@^1.0.1, has-bigints@^1.0.2:
@ -2301,6 +2340,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
heap-js@^2.2.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/heap-js/-/heap-js-2.5.0.tgz#487e268b1733b187ca04eccf52f8387be92b46cb"
integrity sha512-kUGoI3p7u6B41z/dp33G6OaL7J4DRqRYwVmeIlwLClx7yaaAy7hoDExnuejTKtuDwfcatGmddHDEOjf6EyIxtQ==
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
@ -2317,6 +2361,15 @@ http-errors@2.0.0:
statuses "2.0.1"
toidentifier "1.0.1"
http-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
dependencies:
"@tootallnate/once" "2"
agent-base "6"
debug "4"
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
@ -2325,6 +2378,14 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
https-proxy-agent@^7.0.1:
version "7.0.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168"
integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==
dependencies:
agent-base "^7.0.2"
debug "4"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -2728,6 +2789,11 @@ long@^4.0.0:
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
long@^5.0.0:
version "5.2.3"
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
long@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61"
@ -2868,16 +2934,16 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.2, ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
multistream@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/multistream/-/multistream-4.1.0.tgz#7bf00dfd119556fbc153cff3de4c6d477909f5a8"
@ -2903,18 +2969,13 @@ node-abi@^2.21.0:
dependencies:
semver "^5.4.1"
node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7:
node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7, node-fetch@^2.6.9:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-forge@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
nodemon@^2.0.16:
version "2.0.16"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.16.tgz#d71b31bfdb226c25de34afea53486c8ef225fdef"
@ -3164,17 +3225,17 @@ progress@^2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
proto3-json-serializer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-1.0.1.tgz#5d2b0b3c85568edb62984c94ce2e726985de44be"
integrity sha512-jtnGKL8EE1mTOl2qgMZJQXbS22dlb2K07i4b5vp8yMGMJ6mFSgBg4Ossu/g9NCuamdYXHjp3XxyWw4tJ0G/uKw==
proto3-json-serializer@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-2.0.1.tgz#da0b510f6d6e584b1b5c271f045c26728abe71e0"
integrity sha512-8awBvjO+FwkMd6gNoGFZyqkHZXCFd54CIYTb6De7dPaufGJ2XNW+QUNqbMr8MaAocMdb+KpsD4rxEOaTBDCffA==
dependencies:
protobufjs "^6.11.3"
protobufjs "^7.2.5"
protobufjs@6.11.3, protobufjs@^6.11.3:
version "6.11.3"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74"
integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==
protobufjs@7.2.6, protobufjs@^7.2.4, protobufjs@^7.2.5:
version "7.2.6"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215"
integrity sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
@ -3186,9 +3247,8 @@ protobufjs@6.11.3, protobufjs@^6.11.3:
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/long" "^4.0.1"
"@types/node" ">=13.7.0"
long "^4.0.0"
long "^5.0.0"
proxy-addr@~2.0.7:
version "2.0.7"
@ -3363,13 +3423,14 @@ responselike@^1.0.2:
dependencies:
lowercase-keys "^1.0.0"
retry-request@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-5.0.0.tgz#886ff8ec0e77fffbe66a4d5e90fd8f6646b6eae4"
integrity sha512-vBZdBxUordje9253imlmGtppC5gdcwZmNz7JnU2ui+KKFPk25keR+0c020AVV20oesYxIFOI0Kh3HE88/59ieg==
retry-request@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-7.0.2.tgz#60bf48cfb424ec01b03fca6665dee91d06dd95f3"
integrity sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==
dependencies:
debug "^4.1.1"
"@types/request" "^2.48.8"
extend "^3.0.2"
teeny-request "^9.0.0"
reusify@^1.0.4:
version "1.0.4"
@ -3547,6 +3608,13 @@ statuses@2.0.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
stream-events@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5"
integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==
dependencies:
stubs "^3.0.0"
stream-meter@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d"
@ -3594,7 +3662,7 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
string-width@^4.2.2:
string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -3680,6 +3748,11 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
stubs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"
integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==
supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -3720,6 +3793,17 @@ tar-stream@^2.1.4:
inherits "^2.0.3"
readable-stream "^3.1.1"
teeny-request@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-9.0.0.tgz#18140de2eb6595771b1b02203312dfad79a4716d"
integrity sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==
dependencies:
http-proxy-agent "^5.0.0"
https-proxy-agent "^5.0.0"
node-fetch "^2.6.9"
stream-events "^1.0.5"
uuid "^9.0.0"
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
@ -3973,6 +4057,11 @@ uuid@^8.3.0, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^9.0.0, uuid@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
@ -4138,6 +4227,11 @@ yargs-parser@^20.2.2:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
@ -4151,6 +4245,19 @@ yargs@^16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.7.2:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.1.1"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"

View File

@ -75,7 +75,7 @@
<freemarker.version>2.3.30</freemarker.version>
<mail.version>1.6.2</mail.version>
<curator.version>5.5.0</curator.version>
<zookeeper.version>3.8.1</zookeeper.version>
<zookeeper.version>3.9.2</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version>
<tbel.version>1.1.5</tbel.version>
@ -94,7 +94,7 @@
<jts.version>1.18.2</jts.version>
<bouncycastle.version>1.69</bouncycastle.version>
<winsw.version>2.0.1</winsw.version>
<postgresql.driver.version>42.5.0</postgresql.driver.version>
<postgresql.driver.version>42.7.3</postgresql.driver.version>
<sonar.exclusions>org/thingsboard/server/gen/**/*,
org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
</sonar.exclusions>

View File

@ -38,7 +38,7 @@ public interface NotificationCenter {
void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);
void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId);
void markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId);

View File

@ -21,6 +21,6 @@ import java.util.Map;
public interface FirebaseService {
void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map<String, String> data) throws Exception;
void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map<String, String> data, Integer badge) throws Exception;
}

View File

@ -87,7 +87,7 @@ public class CertPemCredentials implements ClientCredentials {
private KeyManagerFactory createAndInitKeyManagerFactory() throws Exception {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(loadKeyStore(), password.toCharArray());
kmf.init(loadKeyStore(), SslUtil.getPassword(password));
return kmf;
}
@ -107,7 +107,7 @@ public class CertPemCredentials implements ClientCredentials {
CertPath certPath = factory.generateCertPath(certificates);
List<? extends Certificate> path = certPath.getCertificates();
Certificate[] x509Certificates = path.toArray(new Certificate[0]);
keyStore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, password.toCharArray(), x509Certificates);
keyStore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, SslUtil.getPassword(password), x509Certificates);
}
return keyStore;
}

View File

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import javax.annotation.Nullable;
import java.util.Objects;
@Slf4j
@RuleNode(
@ -57,7 +58,7 @@ public class TbCheckAlarmStatusNode implements TbNode {
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
try {
Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class);
Objects.requireNonNull(alarm, "alarm is null");
ListenableFuture<Alarm> latest = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId());
Futures.addCallback(latest, new FutureCallback<>() {
@ -78,7 +79,11 @@ public class TbCheckAlarmStatusNode implements TbNode {
}
}, ctx.getDbCallbackExecutor());
} catch (Exception e) {
log.error("Failed to parse alarm: [{}]", msg.getData());
if (e instanceof IllegalArgumentException || e instanceof NullPointerException) {
log.debug("[{}][{}] Failed to parse alarm: [{}] error [{}]", ctx.getTenantId(), ctx.getRuleChainName(), msg.getData(), e.getMessage());
} else {
log.error("[{}][{}] Failed to parse alarm: [{}]", ctx.getTenantId(), ctx.getRuleChainName(), msg.getData(), e);
}
throw new TbNodeException(e);
}
}

View File

@ -38,8 +38,10 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@ -158,6 +160,19 @@ class TbCheckAlarmStatusNodeTest {
assertThat(value).isInstanceOf(TbNodeException.class).hasMessage("No such alarm found.");
}
@Test
void givenUnparseableAlarm_whenOnMsg_then_Failure() {
String msgData = "{\"Number\":1113718,\"id\":8.1}";
TbMsg msg = getTbMsg(msgData);
willReturn("Default Rule Chain").given(ctx).getRuleChainName();
assertThatThrownBy(() -> node.onMsg(ctx, msg))
.as("onMsg")
.isInstanceOf(TbNodeException.class)
.hasCauseInstanceOf(IllegalArgumentException.class)
.hasMessage("java.lang.IllegalArgumentException: The given string value cannot be transformed to Json object: {\"Number\":1113718,\"id\":8.1}");
}
private TbMsg getTbMsg(String msgData) {
return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, msgData);
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNode" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>

View File

@ -51,7 +51,8 @@
"import/order": "off",
"@typescript-eslint/member-ordering": "off",
"no-underscore-dangle": "off",
"@typescript-eslint/naming-convention": "off"
"@typescript-eslint/naming-convention": "off",
"jsdoc/newline-after-description": 0
}
},
{

View File

@ -30,7 +30,7 @@
"@date-io/date-fns": "1.3.7",
"@flowjs/flow.js": "^2.14.1",
"@flowjs/ngx-flow": "~0.6.0",
"@geoman-io/leaflet-geoman-free": "^2.13.0",
"@geoman-io/leaflet-geoman-free": "2.14.2",
"@iplab/ngx-color-picker": "^15.0.2",
"@juggle/resize-observer": "^3.4.0",
"@mat-datetimepicker/core": "~11.0.3",
@ -67,11 +67,11 @@
"jstree": "^3.3.15",
"jstree-bootstrap-theme": "^1.0.1",
"jszip": "^3.10.1",
"leaflet": "~1.8.0",
"leaflet-polylinedecorator": "^1.6.0",
"leaflet-providers": "^1.13.0",
"leaflet.gridlayer.googlemutant": "^0.13.5",
"leaflet.markercluster": "^1.5.3",
"leaflet": "1.8.0",
"leaflet-polylinedecorator": "1.6.0",
"leaflet-providers": "1.13.0",
"leaflet.gridlayer.googlemutant": "0.14.1",
"leaflet.markercluster": "1.5.3",
"libphonenumber-js": "^1.10.4",
"marked": "^4.0.17",
"moment": "^2.29.4",
@ -130,11 +130,11 @@
"@types/jasminewd2": "^2.0.10",
"@types/jquery": "^3.5.16",
"@types/js-beautify": "^1.13.3",
"@types/leaflet": "~1.8.0",
"@types/leaflet-polylinedecorator": "^1.6.1",
"@types/leaflet-providers": "^1.2.1",
"@types/leaflet.gridlayer.googlemutant": "^0.4.6",
"@types/leaflet.markercluster": "^1.5.1",
"@types/leaflet": "1.8.0",
"@types/leaflet-polylinedecorator": "1.6.4",
"@types/leaflet-providers": "1.2.4",
"@types/leaflet.gridlayer.googlemutant": "0.4.9",
"@types/leaflet.markercluster": "1.5.4",
"@types/lodash": "^4.14.192",
"@types/marked": "^4.0.8",
"@types/node": "~18.15.11",

View File

@ -139,8 +139,9 @@ export class TbTimeSeriesChart {
this.settings = mergeDeep({} as TimeSeriesChartSettings,
timeSeriesChartDefaultSettings,
this.inputSettings as TimeSeriesChartSettings);
const dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page');
this.darkMode = this.settings.darkMode || dashboardPageElement.hasClass('dark');
const $dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page');
const dashboardPageElement = $dashboardPageElement.length ? $($dashboardPageElement[$dashboardPageElement.length-1]) : null;
this.darkMode = this.settings.darkMode || dashboardPageElement?.hasClass('dark');
this.setupYAxes();
this.setupData();
this.setupThresholds();
@ -154,15 +155,17 @@ export class TbTimeSeriesChart {
});
this.shapeResize$.observe(this.chartElement);
}
this.darkModeObserver = new MutationObserver(mutations => {
for(let mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const darkMode = dashboardPageElement.hasClass('dark');
this.setDarkMode(darkMode);
if (dashboardPageElement) {
this.darkModeObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const darkMode = dashboardPageElement.hasClass('dark');
this.setDarkMode(darkMode);
}
}
}
});
this.darkModeObserver.observe(dashboardPageElement[0], { attributes: true });
});
this.darkModeObserver.observe(dashboardPageElement[0], {attributes: true});
}
}
public update(): void {
@ -269,7 +272,7 @@ export class TbTimeSeriesChart {
}
this.yMinSubject.complete();
this.yMaxSubject.complete();
this.darkModeObserver.disconnect();
this.darkModeObserver?.disconnect();
}
public resize(): void {

View File

@ -63,7 +63,9 @@
height: 18px;
background: #305680;
mask-image: url(/assets/copy-code-icon.svg);
-webkit-mask-image: url(/assets/copy-code-icon.svg);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
}
}
}

View File

@ -65,6 +65,8 @@
.tb-battery-level-box {
display: flex;
align-items: center;
min-height: 0;
max-height: 100%;
.tb-battery-level-rectangle {
width: 100%;
height: 100%;
@ -73,8 +75,11 @@
position: absolute;
inset: 0;
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: cover;
-webkit-mask-size: cover;
mask-position: center;
-webkit-mask-position: center;
}
.tb-battery-level-container {
position: absolute;
@ -95,6 +100,7 @@
&.vertical {
.tb-battery-level-shape {
mask-image: url(/assets/widget/battery-level/battery-shape-vertical.svg);
-webkit-mask-image: url(/assets/widget/battery-level/battery-shape-vertical.svg);
}
.tb-battery-level-container {
flex-direction: column-reverse;
@ -122,6 +128,7 @@
&.horizontal {
.tb-battery-level-shape {
mask-image: url(/assets/widget/battery-level/battery-shape-horizontal.svg);
-webkit-mask-image: url(/assets/widget/battery-level/battery-shape-horizontal.svg);
}
.tb-battery-level-container {
inset: 6.25% 8.85% 6.25% 3.54%;

View File

@ -307,7 +307,7 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
const newWidth = height * horizontalBatteryDimensions.shapeAspectRatio;
this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', newWidth + 'px');
} else {
this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', null);
this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', width + 'px');
}
if (this.batteryLevelValue) {
const ratios = horizontalBatteryDimensions.heightRatio;

View File

@ -444,7 +444,7 @@ class InnerShadowCircle {
add.x('-50%').y('-50%').width('200%').height('200%');
let effect: Effect = add.componentTransfer(components => {
components.funcA({ type: 'table', tableValues: '1 0' });
}).in(add.$fill);
});
effect = effect.gaussianBlur(this.blur, this.blur).attr({stdDeviation: this.blur});
this.blurEffect = effect;
effect = effect.offset(this.dx, this.dy);
@ -454,7 +454,7 @@ class InnerShadowCircle {
effect = effect.composite(this.offsetEffect, 'in');
effect.composite(add.$sourceAlpha, 'in');
add.merge(m => {
m.mergeNode(add.$fill);
m.mergeNode();
m.mergeNode();
});
});
@ -538,14 +538,14 @@ class DefaultPowerButtonShape extends PowerButtonShape {
this.pressedTimeline.finish();
const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}});
this.pressedShadow.animate(6, 0.6);
}
protected onPressEnd() {
this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}});
this.pressedShadow.animateRestore();
}
@ -602,14 +602,14 @@ class SimplifiedPowerButtonShape extends PowerButtonShape {
this.pressedTimeline.finish();
const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}});
this.pressedShadow.animate(6, 0.6);
}
protected onPressEnd() {
this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}});
this.pressedShadow.animateRestore();
}
}
@ -674,7 +674,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape {
const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98, origin: {x: cx, y: cy}});
this.pressedShadow.animate(6, 0.6);
}
@ -682,7 +682,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape {
this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onCenterGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}});
this.pressedShadow.animateRestore();
}
}
@ -774,14 +774,14 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape {
this.innerShadow.show();
const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}});
this.innerShadow.animate(6, 0.6);
}
protected onPressEnd() {
this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}});
this.innerShadow.animateRestore().after(() => {
if (this.disabled) {
this.innerShadow.hide();
@ -849,14 +849,14 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape {
this.backgroundShape.removeClass('tb-shadow');
}
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onCenterGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onCenterGroup).transform({scale: pressedScale, origin: {x: cx, y: cy}});
this.pressedShadow.animate(8, 0.4);
}
protected onPressEnd() {
this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onCenterGroup).transform({scale: 1});
powerButtonAnimation(this.onCenterGroup).transform({scale: 1, origin: {x: cx, y: cy}});
this.pressedShadow.animateRestore().after(() => {
if (!this.value) {
this.backgroundShape.addClass('tb-shadow');
@ -938,7 +938,7 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape {
const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98, origin: {x: cx, y: cy}});
this.pressedShadow.animate(6, 0.6);
}
@ -946,7 +946,7 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape {
this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onCenterGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}});
this.pressedShadow.animateRestore();
}

View File

@ -155,7 +155,9 @@
height: 18px;
background: #305680;
mask-image: url(/assets/copy-code-icon.svg);
-webkit-mask-image: url(/assets/copy-code-icon.svg);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
}
}
}

View File

@ -92,7 +92,9 @@
height: 18px;
background: $tb-primary-color;
mask-image: url(/assets/copy-code-icon.svg);
-webkit-mask-image: url(/assets/copy-code-icon.svg);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
}
}
&.multiline {

View File

@ -85,12 +85,11 @@
</div>
</div>
<div *ngIf="linkType === ImageLinkType.none" class="tb-image-select-buttons-container">
<button #browseGalleryButton
mat-stroked-button
<button mat-stroked-button
type="button"
color="primary"
class="tb-image-select-button"
(click)="toggleGallery($event, browseGalleryButton)">
(click)="toggleGallery($event)">
<tb-icon matButtonIcon>filter</tb-icon>
<span translate>image.browse-from-gallery</span>
</button>

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -24,10 +24,13 @@ import { DndDropEvent } from 'ngx-drag-drop';
import { isUndefined } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ImageLinkType } from '@shared/components/image/gallery-image-input.component';
import { TbPopoverService } from '@shared/components/popover.service';
import { MatButton } from '@angular/material/button';
import { ImageGalleryComponent } from '@shared/components/image/image-gallery.component';
import { prependTbImagePrefixToUrls, removeTbImagePrefixFromUrls } from '@shared/models/resource.models';
import {
ImageResourceInfo,
prependTbImagePrefixToUrls,
removeTbImagePrefixFromUrls
} from '@shared/models/resource.models';
import { MatDialog } from '@angular/material/dialog';
import { ImageGalleryDialogComponent } from '@shared/components/image/image-gallery-dialog.component';
@Component({
selector: 'tb-multiple-gallery-image-input',
@ -66,10 +69,8 @@ export class MultipleGalleryImageInputComponent extends PageComponent implements
private propagateChange = null;
constructor(protected store: Store<AppState>,
private cd: ChangeDetectorRef,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private popoverService: TbPopoverService) {
private dialog: MatDialog,
private cd: ChangeDetectorRef) {
super(store);
}
@ -130,31 +131,21 @@ export class MultipleGalleryImageInputComponent extends PageComponent implements
this.updateModel();
}
toggleGallery($event: Event, browseGalleryButton: MatButton) {
toggleGallery($event: Event) {
if ($event) {
$event.stopPropagation();
}
const trigger = browseGalleryButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
pageMode: false,
popoverMode: true,
mode: 'grid',
selectionMode: true
};
const imageGalleryPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ImageGalleryComponent, 'top', true, null,
ctx,
{},
{}, {}, true);
imageGalleryPopover.tbComponentRef.instance.imageSelected.subscribe((image) => {
imageGalleryPopover.hide();
this.dialog.open<ImageGalleryDialogComponent, any,
ImageResourceInfo>(ImageGalleryDialogComponent, {
autoFocus: false,
disableClose: false,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed().subscribe((image) => {
if (image) {
this.imageUrls.push(image.link);
this.updateModel();
});
}
}
});
}
imageDragStart(index: number) {

View File

@ -21,6 +21,7 @@ import { coerceNumberProperty } from '@angular/cdk/coercion';
import { SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
import { Interval, IntervalMath, TimeInterval } from '@shared/models/time/time.models';
import { isDefined } from '@core/utils';
@Component({
selector: 'tb-timeinterval',
@ -90,14 +91,17 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
secs = 0;
interval: Interval = 0;
modelValue: Interval;
advanced = false;
rendered = false;
intervals: Array<TimeInterval>;
private propagateChange = (_: any) => {};
advanced = false;
private modelValue: Interval;
private rendered = false;
private propagateChangeValue: any;
private propagateChange = (value: any) => {
this.propagateChangeValue = value;
};
constructor(private timeService: TimeService) {
}
@ -108,6 +112,9 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
registerOnChange(fn: any): void {
this.propagateChange = fn;
if (isDefined(this.propagateChangeValue)) {
this.propagateChange(this.propagateChangeValue);
}
}
registerOnTouched(fn: any): void {
@ -132,7 +139,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
}
}
setInterval(interval: Interval) {
private setInterval(interval: Interval) {
if (!this.advanced) {
this.interval = interval;
}
@ -143,7 +150,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
this.secs = intervalSeconds % 60;
}
boundInterval(updateToPreferred = false) {
private boundInterval(updateToPreferred = false) {
const min = this.timeService.boundMinInterval(this.minValue);
const max = this.timeService.boundMaxInterval(this.maxValue);
this.intervals = this.timeService.getIntervals(this.minValue, this.maxValue, this.useCalendarIntervals);
@ -165,7 +172,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
}
}
updateView(updateToPreferred = false) {
private updateView(updateToPreferred = false) {
if (!this.rendered) {
return;
}
@ -187,7 +194,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
this.boundInterval(updateToPreferred);
}
calculateIntervalMs(): number {
private calculateIntervalMs(): number {
return (this.days * 86400 +
this.hours * 3600 +
this.mins * 60 +
@ -232,7 +239,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
}
}
onSecsChange() {
private onSecsChange() {
if (typeof this.secs === 'undefined') {
return;
}
@ -252,7 +259,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
this.updateView();
}
onMinsChange() {
private onMinsChange() {
if (typeof this.mins === 'undefined') {
return;
}
@ -272,7 +279,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
this.updateView();
}
onHoursChange() {
private onHoursChange() {
if (typeof this.hours === 'undefined') {
return;
}
@ -292,7 +299,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
this.updateView();
}
onDaysChange() {
private onDaysChange() {
if (typeof this.days === 'undefined') {
return;
}

View File

@ -20,7 +20,6 @@ import {
AggregationType,
DAY,
HistoryWindowType,
QuickTimeInterval,
quickTimeIntervalPeriod,
RealtimeWindowType,
Timewindow,

View File

@ -588,9 +588,13 @@
bottom: 0;
background: $tb-primary-color;
mask-image: url(/assets/home/no_data_folder_bg.svg);
-webkit-mask-image: url(/assets/home/no_data_folder_bg.svg);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-size: contain;
mask-position: center;
-webkit-mask-position: center;
}
}

View File

@ -1582,10 +1582,10 @@
dependencies:
tslib "^2.2.0"
"@geoman-io/leaflet-geoman-free@^2.13.0":
version "2.16.0"
resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.16.0.tgz#c8a92fcc2cdd5770ebf43f8ef9f03cc8ef842359"
integrity sha512-BnKAAoTXraWVFfhX/0gT/iBgAz1BPfpbdQ9dJamEFI4lIku9UNXXluu/E0k7YkZETq0tENX2GPnKLB4p+VgrSw==
"@geoman-io/leaflet-geoman-free@2.14.2":
version "2.14.2"
resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.14.2.tgz#c84c2115c263f34d11dc0b43859551639fe3d56b"
integrity sha512-6lIyG8RvSVdFjVjiQgBPyNASjymSyqzsiUeBW0pA+q41lB5fAg4SDC6SfJvWdEyDHa81Jb5FWjUkCc9O+u0gbg==
dependencies:
"@turf/boolean-contains" "^6.5.0"
"@turf/kinks" "^6.5.0"
@ -3079,28 +3079,28 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/leaflet-polylinedecorator@^1.6.1":
"@types/leaflet-polylinedecorator@1.6.4":
version "1.6.4"
resolved "https://registry.yarnpkg.com/@types/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.4.tgz#2270b84585bd28bbf1fef237be8480196c44ac06"
integrity sha512-meH/jC58Drt/9aqLhuzMCUQjHgMTud+UuQBK5gT6E8M6OfM/rftz/VgvdxOCCAQC9isrp4pqahDgEswesb5X3A==
dependencies:
"@types/leaflet" "*"
"@types/leaflet-providers@^1.2.1":
"@types/leaflet-providers@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@types/leaflet-providers/-/leaflet-providers-1.2.4.tgz#284e8a197d4c2dbfeda436842e03b2adb502be16"
integrity sha512-4wYEpreixp+G5t510s202eQ5eubOmxHevIfCNrzUZbzp50XQsC9lSetX7MnPl3ANRJnUBbCWOzZ9EQFiH0Jm+g==
dependencies:
"@types/leaflet" "*"
"@types/leaflet.gridlayer.googlemutant@^0.4.6":
"@types/leaflet.gridlayer.googlemutant@0.4.9":
version "0.4.9"
resolved "https://registry.yarnpkg.com/@types/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.4.9.tgz#9090ddf4578ce631d22b8aafc5ed12525b4fbf90"
integrity sha512-u/5Avs1KKkeABRDixGbGp2ldFs67+fYIATpkvvFgN+LQPNfq7dFd5adkksshiQBfYJ4SaQg1VJgN2dNirIC0aQ==
dependencies:
"@types/leaflet" "*"
"@types/leaflet.markercluster@^1.5.1":
"@types/leaflet.markercluster@1.5.4":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.4.tgz#2ab43417cf3f6a42d0f1baf4e1c8f659cf1dc3a1"
integrity sha512-tfMP8J62+wfsVLDLGh5Zh1JZxijCaBmVsMAX78MkLPwvPitmZZtSin5aWOVRhZrCS+pEOZwNzexbfWXlY+7yjg==
@ -3114,7 +3114,7 @@
dependencies:
"@types/geojson" "*"
"@types/leaflet@~1.8.0":
"@types/leaflet@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.8.0.tgz#dc92d3e868fb6d5067b4b59fa08cd4441f84fabe"
integrity sha512-+sXFmiJTFdhaXXIGFlV5re9AdqtAODoXbGAvxx02e5SHXL3ir7ClP5J7pahO8VmzKY3dth4RUS1nf2BTT+DW1A==
@ -7750,14 +7750,14 @@ lcov-parse@1.0.0:
resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0"
integrity sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==
leaflet-polylinedecorator@^1.6.0:
leaflet-polylinedecorator@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.0.tgz#9ef79fd1b5302d67b72efe959a8ecd2553f27266"
integrity sha512-kn3krmZRetgvN0wjhgYL8kvyLS0tUogAl0vtHuXQnwlYNjbl7aLQpkoFUo8UB8gVZoB0dhI4Tb55VdTJAcYzzQ==
dependencies:
leaflet-rotatedmarker "^0.2.0"
leaflet-providers@^1.13.0:
leaflet-providers@1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/leaflet-providers/-/leaflet-providers-1.13.0.tgz#10c843a23d5823a65096d40ad53f27029e13434b"
integrity sha512-f/sN5wdgBbVA2jcCYzScIfYNxKdn2wBJP9bu+5cRX9Xj6g8Bt1G9Sr8WgJAt/ckIFIc3LVVxCBNFpSCfTuUElg==
@ -7767,17 +7767,17 @@ leaflet-rotatedmarker@^0.2.0:
resolved "https://registry.yarnpkg.com/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz#4467f49f98d1bfd56959bd9c6705203dd2601277"
integrity sha512-yc97gxLXwbZa+Gk9VCcqI0CkvIBC9oNTTjFsHqq4EQvANrvaboib4UdeQLyTnEqDpaXHCqzwwVIDHtvz2mUiDg==
leaflet.gridlayer.googlemutant@^0.13.5:
version "0.13.5"
resolved "https://registry.yarnpkg.com/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.13.5.tgz#7182c26f479e726bff8b85a7ebf9d3f44dd3ea57"
integrity sha512-DHUEXpo1t0WZ9tpdLUHHkrTK7LldCXr/gqkV5E/4hHidDJB2srceLLCZj4PV/pl0jPdTkVBT+Lr8nL9EZDB5hw==
leaflet.gridlayer.googlemutant@0.14.1:
version "0.14.1"
resolved "https://registry.yarnpkg.com/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.14.1.tgz#c282209aa1a39eb2f87d8aaa4e9894181c9e20a0"
integrity sha512-/OYxEjmgxO1U1KOhTzg+m8c0b95J0943LU8DXQmdJu/x2f+1Ur78rvEPO2QCS0cmwZ3m6FvE5I3zXnBzJNWRCA==
leaflet.markercluster@^1.5.3:
leaflet.markercluster@1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz#9cdb52a4eab92671832e1ef9899669e80efc4056"
integrity sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==
leaflet@~1.8.0:
leaflet@1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e"
integrity sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==