Merge remote-tracking branch 'upstream/rc' into fix-edge-zombie-consumer-cleanup
This commit is contained in:
		
						commit
						85cfe19d14
					
				@ -300,6 +300,7 @@ public class ImageController extends BaseController {
 | 
			
		||||
        tbImageService.putETag(cacheKey, descriptor.getEtag());
 | 
			
		||||
        var result = ResponseEntity.ok()
 | 
			
		||||
                .header("Content-Type", descriptor.getMediaType())
 | 
			
		||||
                .header("Content-Security-Policy", "default-src 'none'")
 | 
			
		||||
                .eTag(descriptor.getEtag());
 | 
			
		||||
        if (!cacheKey.isPublic()) {
 | 
			
		||||
            result
 | 
			
		||||
 | 
			
		||||
@ -238,7 +238,7 @@ public class DefaultEntityQueryService implements EntityQueryService {
 | 
			
		||||
            entitiesSortOrder = sortOrder;
 | 
			
		||||
        }
 | 
			
		||||
        EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder);
 | 
			
		||||
        return new EntityDataQuery(query.getEntityFilter(), edpl, null, null, query.getKeyFilters());
 | 
			
		||||
        return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 | 
			
		||||
@ -519,6 +519,67 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
 | 
			
		||||
        Assert.assertEquals(1, filteredAssetAlamData.getTotalElements());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testFindAlarmsWithEntityFilterAndLatestValues() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
        List<Device> devices = new ArrayList<>();
 | 
			
		||||
        List<String> temps = new ArrayList<>();
 | 
			
		||||
        List<String> deviceNames = new ArrayList<>();
 | 
			
		||||
        for (int i = 0; i < 10; i++) {
 | 
			
		||||
            Device device = new Device();
 | 
			
		||||
            device.setCustomerId(customerId);
 | 
			
		||||
            device.setName("Device" + i);
 | 
			
		||||
            device.setType("default");
 | 
			
		||||
            device.setLabel("testLabel" + (int) (Math.random() * 1000));
 | 
			
		||||
            device = doPost("/api/device", device, Device.class);
 | 
			
		||||
            devices.add(device);
 | 
			
		||||
            deviceNames.add(device.getName());
 | 
			
		||||
 | 
			
		||||
            int temp = i * 10;
 | 
			
		||||
            temps.add(String.valueOf(temp));
 | 
			
		||||
            JsonNode content = JacksonUtil.toJsonNode("{\"temperature\": " + temp + "}");
 | 
			
		||||
            doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + device.getUuidId() + "/timeseries/SERVER_SCOPE", content)
 | 
			
		||||
                    .andExpect(status().isOk());
 | 
			
		||||
            Thread.sleep(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < devices.size(); i++) {
 | 
			
		||||
            Alarm alarm = new Alarm();
 | 
			
		||||
            alarm.setCustomerId(customerId);
 | 
			
		||||
            alarm.setOriginator(devices.get(i).getId());
 | 
			
		||||
            String type = "device alarm" + i;
 | 
			
		||||
            alarm.setType(type);
 | 
			
		||||
            alarm.setSeverity(AlarmSeverity.WARNING);
 | 
			
		||||
            doPost("/api/alarm", alarm, Alarm.class);
 | 
			
		||||
            Thread.sleep(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AlarmDataPageLink pageLink = new AlarmDataPageLink();
 | 
			
		||||
        pageLink.setPage(0);
 | 
			
		||||
        pageLink.setPageSize(100);
 | 
			
		||||
        pageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ALARM_FIELD, "created_time")));
 | 
			
		||||
 | 
			
		||||
        List<EntityKey> alarmFields = new ArrayList<>();
 | 
			
		||||
        alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "type"));
 | 
			
		||||
 | 
			
		||||
        List<EntityKey> entityFields = new ArrayList<>();
 | 
			
		||||
        entityFields.add(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
 | 
			
		||||
 | 
			
		||||
        List<EntityKey> latestValues = new ArrayList<>();
 | 
			
		||||
        latestValues.add(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
 | 
			
		||||
 | 
			
		||||
        EntityTypeFilter deviceTypeFilter = new EntityTypeFilter();
 | 
			
		||||
        deviceTypeFilter.setEntityType(EntityType.DEVICE);
 | 
			
		||||
        AlarmDataQuery deviceAlarmQuery =  new AlarmDataQuery(deviceTypeFilter, pageLink, entityFields, latestValues, null, alarmFields);
 | 
			
		||||
 | 
			
		||||
        PageData<AlarmData> alarmPageData = findAlarmsByQueryAndCheck(deviceAlarmQuery, 10);
 | 
			
		||||
        List<String> retrievedAlarmTemps = alarmPageData.getData().stream().map(alarmData -> alarmData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).toList();
 | 
			
		||||
        assertThat(retrievedAlarmTemps).containsExactlyInAnyOrderElementsOf(temps);
 | 
			
		||||
 | 
			
		||||
        List<String> retrievedDeviceNames = alarmPageData.getData().stream().map(alarmData -> alarmData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).toList();
 | 
			
		||||
        assertThat(retrievedDeviceNames).containsExactlyInAnyOrderElementsOf(deviceNames);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void testCountAlarmsByQuery(List<Alarm> alarms) throws Exception {
 | 
			
		||||
        AlarmCountQuery countQuery = new AlarmCountQuery();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,7 @@ public class UserFields extends AbstractEntityFields {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getName() {
 | 
			
		||||
        return super.getEmail();
 | 
			
		||||
        return getEmail();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public UserFields(UUID id, long createdTime, UUID tenantId, UUID customerId,
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.MobileAppId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.mobile.layout.MobileLayoutConfig;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.NoXss;
 | 
			
		||||
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@Data
 | 
			
		||||
@ -40,9 +41,11 @@ public class MobileAppBundle extends BaseData<MobileAppBundleId> implements HasT
 | 
			
		||||
    private TenantId tenantId;
 | 
			
		||||
    @Schema(description = "Application bundle title. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
 | 
			
		||||
    @NotBlank
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "title")
 | 
			
		||||
    private String title;
 | 
			
		||||
    @Schema(description = "Application bundle description.")
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "description")
 | 
			
		||||
    private String description;
 | 
			
		||||
    @Schema(description = "Android application id")
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
 | 
			
		||||
    @Valid
 | 
			
		||||
    private NotificationRuleRecipientsConfig recipientsConfig;
 | 
			
		||||
 | 
			
		||||
    @Valid
 | 
			
		||||
    private NotificationRuleConfig additionalConfig;
 | 
			
		||||
 | 
			
		||||
    private NotificationRuleId externalId;
 | 
			
		||||
 | 
			
		||||
@ -16,12 +16,14 @@
 | 
			
		||||
package org.thingsboard.server.common.data.notification.rule;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.NoXss;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class NotificationRuleConfig implements Serializable {
 | 
			
		||||
 | 
			
		||||
    @NoXss
 | 
			
		||||
    private String description;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ import { DialogService } from '@core/services/dialog.service';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { parseHttpErrorMessage } from '@core/utils';
 | 
			
		||||
import { getInterceptorConfig } from './interceptor.util';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
const tmpHeaders = {};
 | 
			
		||||
 | 
			
		||||
@ -46,6 +47,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
 | 
			
		||||
    private dialogService: DialogService,
 | 
			
		||||
    private translate: TranslateService,
 | 
			
		||||
    private authService: AuthService,
 | 
			
		||||
    private sanitizer: DomSanitizer
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 | 
			
		||||
@ -129,7 +131,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (unhandled && !ignoreErrors) {
 | 
			
		||||
      const errorMessageWithTimeout = parseHttpErrorMessage(errorResponse, this.translate, req.responseType);
 | 
			
		||||
      const errorMessageWithTimeout = parseHttpErrorMessage(errorResponse, this.translate, req.responseType, this.sanitizer);
 | 
			
		||||
      this.showError(errorMessageWithTimeout.message, errorMessageWithTimeout.timeout);
 | 
			
		||||
    }
 | 
			
		||||
    return throwError(() => errorResponse);
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,8 @@ import {
 | 
			
		||||
  isNotEmptyTbFunction,
 | 
			
		||||
  TbFunction
 | 
			
		||||
} from '@shared/models/js-function.models';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
import { SecurityContext } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
const varsRegex = /\${([^}]*)}/g;
 | 
			
		||||
 | 
			
		||||
@ -854,7 +856,7 @@ export function getEntityDetailsPageURL(id: string, entityType: EntityType): str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function parseHttpErrorMessage(errorResponse: HttpErrorResponse,
 | 
			
		||||
                                      translate: TranslateService, responseType?: string): {message: string; timeout: number} {
 | 
			
		||||
                                      translate: TranslateService, responseType?: string, sanitizer?:DomSanitizer): {message: string; timeout: number} {
 | 
			
		||||
  let error = null;
 | 
			
		||||
  let errorMessage: string;
 | 
			
		||||
  let timeout = 0;
 | 
			
		||||
@ -882,6 +884,9 @@ export function parseHttpErrorMessage(errorResponse: HttpErrorResponse,
 | 
			
		||||
    errorText += errorKey ? translate.instant(errorKey) : errorResponse.statusText;
 | 
			
		||||
    errorMessage = errorText;
 | 
			
		||||
  }
 | 
			
		||||
  if(sanitizer) {
 | 
			
		||||
    errorMessage = sanitizer.sanitize(SecurityContext.HTML,errorMessage);
 | 
			
		||||
  }
 | 
			
		||||
  return {message: errorMessage, timeout};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ import {
 | 
			
		||||
  NgZone,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  SecurityContext,
 | 
			
		||||
  ViewChild
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
@ -53,6 +54,7 @@ import {
 | 
			
		||||
import { deepClone } from '@core/utils';
 | 
			
		||||
import { hidePageSizePixelValue } from '@shared/models/constants';
 | 
			
		||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-manage-widget-actions',
 | 
			
		||||
@ -106,7 +108,8 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
 | 
			
		||||
              private dialogs: DialogService,
 | 
			
		||||
              private cd: ChangeDetectorRef,
 | 
			
		||||
              private elementRef: ElementRef,
 | 
			
		||||
              private zone: NgZone) {
 | 
			
		||||
              private zone: NgZone,
 | 
			
		||||
              private sanitizer: DomSanitizer) {
 | 
			
		||||
    super();
 | 
			
		||||
    const sortOrder: SortOrder = { property: 'actionSourceName', direction: Direction.ASC };
 | 
			
		||||
    this.pageLink = new PageLink(10, 0, null, sortOrder);
 | 
			
		||||
@ -289,7 +292,8 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
 | 
			
		||||
    }
 | 
			
		||||
    const title = this.translate.instant('widget-config.delete-action-title');
 | 
			
		||||
    const content = this.translate.instant('widget-config.delete-action-text', {actionName: action.name});
 | 
			
		||||
    this.dialogs.confirm(title, content,
 | 
			
		||||
    const safeContent = this.sanitizer.sanitize(SecurityContext.HTML, content);
 | 
			
		||||
    this.dialogs.confirm(title, safeContent,
 | 
			
		||||
      this.translate.instant('action.no'),
 | 
			
		||||
      this.translate.instant('action.yes'), true).subscribe(
 | 
			
		||||
      (res) => {
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ import {
 | 
			
		||||
  OnInit,
 | 
			
		||||
  QueryList,
 | 
			
		||||
  Renderer2,
 | 
			
		||||
  SecurityContext,
 | 
			
		||||
  SkipSelf,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
  ViewChildren,
 | 
			
		||||
@ -97,6 +98,7 @@ import { HttpStatusCode } from '@angular/common/http';
 | 
			
		||||
import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
 | 
			
		||||
import { EntityDebugSettings } from '@shared/models/entity.models';
 | 
			
		||||
import Timeout = NodeJS.Timeout;
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-rulechain-page',
 | 
			
		||||
@ -273,6 +275,7 @@ export class RuleChainPageComponent extends PageComponent
 | 
			
		||||
              private renderer: Renderer2,
 | 
			
		||||
              private viewContainerRef: ViewContainerRef,
 | 
			
		||||
              private changeDetector: ChangeDetectorRef,
 | 
			
		||||
              private sanitizer:DomSanitizer,
 | 
			
		||||
              public dialog: MatDialog,
 | 
			
		||||
              public dialogService: DialogService,
 | 
			
		||||
              public fb: FormBuilder) {
 | 
			
		||||
@ -1360,9 +1363,13 @@ export class RuleChainPageComponent extends PageComponent
 | 
			
		||||
        name = node.name;
 | 
			
		||||
        desc = this.translate.instant(ruleNodeTypeDescriptors.get(node.component.type).name) + ' - ' + node.component.name;
 | 
			
		||||
        if (node.additionalInfo) {
 | 
			
		||||
          details = node.additionalInfo.description;
 | 
			
		||||
          details = this.sanitizer.sanitize(SecurityContext.HTML, node.additionalInfo.description);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      name = this.sanitizer.sanitize(SecurityContext.HTML, name);
 | 
			
		||||
      desc = this.sanitizer.sanitize(SecurityContext.HTML, desc);
 | 
			
		||||
 | 
			
		||||
      let tooltipContent = '<div class="tb-rule-node-tooltip">' +
 | 
			
		||||
        '<div id="tb-node-content">' +
 | 
			
		||||
        '<div class="tb-node-title">' + name + '</div>' +
 | 
			
		||||
 | 
			
		||||
@ -206,7 +206,7 @@ export const HelpLinks = {
 | 
			
		||||
    mobileBundle: `${helpBaseUrl}/docs${docPlatformPrefix}/mobile-center/mobile-center/`,
 | 
			
		||||
    mobileQrCode: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/mobile-qr-code/`,
 | 
			
		||||
    calculatedField: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/calculated-fields/`,
 | 
			
		||||
    aiModels: `${helpBaseUrl}/docs${docPlatformPrefix}/ai-models`,
 | 
			
		||||
    aiModels: `${helpBaseUrl}/docs${docPlatformPrefix}/samples/analytics/ai-models/`,
 | 
			
		||||
    timewindowSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/dashboards/#time-window`,
 | 
			
		||||
    trendzSettings: `${helpBaseUrl}/docs/trendz/`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user