diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index a999aabb2a..e97aa67fd7 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -16,48 +16,76 @@ import { Injectable } from '@angular/core'; import { + HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, - HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; -import { Observable, throwError, of } from 'rxjs'; +import { Observable, of, throwError } from 'rxjs'; import { catchError, switchMap } from 'rxjs/operators'; import { MatDialog } from '@angular/material/dialog'; -import { EntityConflictDialogComponent } from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component'; -import { EntityId } from '@shared/models/id/entity-id'; - -interface ConflictedEntity { version: number; id: EntityId } +import { + EntityConflictDialogComponent +} from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component'; +import { InterceptorConfigService } from '@core/services/interceptor-config.service'; +import { HasId } from '@shared/models/base-data'; +import { HasVersion } from '@shared/models/entity.models'; @Injectable() export class EntityConflictInterceptor implements HttpInterceptor { - constructor(private dialog: MatDialog) {} - intercept(request: HttpRequest, next: HttpHandler): Observable> { + constructor( + private dialog: MatDialog, + private interceptorConfigService: InterceptorConfigService + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + if (!request.url.startsWith('/api/')) { + return next.handle(request); + } + return next.handle(request).pipe( catchError((error: HttpErrorResponse) => { - if (error.status === HttpStatusCode.Conflict) { - return this.resolveConflictRequest(request, error.error.message) - .pipe(switchMap(httpRequest => next.handle(httpRequest))); - } else { + if (error.status !== HttpStatusCode.Conflict) { return throwError(() => error); } + + return this.handleConflictError(request, next, error); }) ); } - private resolveConflictRequest(request: HttpRequest, message: string): Observable> { - const dialogRef = this.dialog.open(EntityConflictDialogComponent, {data: {message, entityId: request.body.id}}); + private handleConflictError( + request: HttpRequest, + next: HttpHandler, + error: HttpErrorResponse + ): Observable> { + if (this.interceptorConfigService.getInterceptorConfig(request).ignoreVersionConflict) { + return next.handle(this.updateRequestVersion(request)); + } - return dialogRef.afterClosed().pipe( + return this.openConflictDialog(request, error.error.message).pipe( switchMap(result => { if (result) { - request.body.version = null; + return next.handle(this.updateRequestVersion(request)); } - return of(request); + return of(null); }) ); } + + private updateRequestVersion(request: HttpRequest): HttpRequest { + const body = { ...request.body, version: null }; + return request.clone({ body }); + } + + private openConflictDialog(request: HttpRequest, message: string): Observable { + const dialogRef = this.dialog.open(EntityConflictDialogComponent, { + data: { message, entity: request.body } + }); + + return dialogRef.afterClosed(); + } } diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 502df634f3..6023267ff9 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -16,10 +16,9 @@ import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Observable } from 'rxjs/internal/Observable'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { AuthService } from '@core/auth/auth.service'; import { Constants } from '@shared/models/constants'; -import { InterceptorHttpParams } from './interceptor-http-params'; import { catchError, delay, finalize, mergeMap, switchMap } from 'rxjs/operators'; import { of, throwError } from 'rxjs'; import { InterceptorConfig } from './interceptor-config'; @@ -30,6 +29,7 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { parseHttpErrorMessage } from '@core/utils'; +import { InterceptorConfigService } from '@core/services/interceptor-config.service'; const tmpHeaders = {}; @@ -39,22 +39,19 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private AUTH_SCHEME = 'Bearer '; private AUTH_HEADER_NAME = 'X-Authorization'; - private internalUrlPrefixes = [ - '/api/auth/token', - '/api/rpc' - ]; - private activeRequests = 0; - constructor(@Inject(Store) private store: Store, - @Inject(DialogService) private dialogService: DialogService, - @Inject(TranslateService) private translate: TranslateService, - @Inject(AuthService) private authService: AuthService) { - } + constructor( + private store: Store, + private dialogService: DialogService, + private translate: TranslateService, + private authService: AuthService, + private interceptorConfigService: InterceptorConfigService + ) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { if (req.url.startsWith('/api/')) { - const config = this.getInterceptorConfig(req); + const config = this.interceptorConfigService.getInterceptorConfig(req); this.updateLoadingState(config, true); let observable$: Observable>; if (this.isTokenBasedAuthEntryPoint(req.url)) { @@ -98,7 +95,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } private handleResponseError(req: HttpRequest, next: HttpHandler, errorResponse: HttpErrorResponse): Observable> { - const config = this.getInterceptorConfig(req); + const config = this.interceptorConfigService.getInterceptorConfig(req); let unhandled = false; const ignoreErrors = config.ignoreErrors; const resendRequest = config.resendRequest; @@ -171,15 +168,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } - private isInternalUrlPrefix(url: string): boolean { - for (const index in this.internalUrlPrefixes) { - if (url.startsWith(this.internalUrlPrefixes[index])) { - return true; - } - } - return false; - } - private isTokenBasedAuthEntryPoint(url: string): boolean { return url.startsWith('/api/') && !url.startsWith(Constants.entryPoints.login) && @@ -202,19 +190,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } - private getInterceptorConfig(req: HttpRequest): InterceptorConfig { - let config: InterceptorConfig; - if (req.params && req.params instanceof InterceptorHttpParams) { - config = (req.params as InterceptorHttpParams).interceptorConfig; - } else { - config = new InterceptorConfig(false, false); - } - if (this.isInternalUrlPrefix(req.url)) { - config.ignoreLoading = true; - } - return config; - } - private showError(error: string, timeout: number = 0) { setTimeout(() => { this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); diff --git a/ui-ngx/src/app/core/interceptors/interceptor-config.ts b/ui-ngx/src/app/core/interceptors/interceptor-config.ts index 5a83d34c6b..62500cbefc 100644 --- a/ui-ngx/src/app/core/interceptors/interceptor-config.ts +++ b/ui-ngx/src/app/core/interceptors/interceptor-config.ts @@ -17,5 +17,6 @@ export class InterceptorConfig { constructor(public ignoreLoading: boolean = false, public ignoreErrors: boolean = false, + public ignoreVersionConflict: boolean = false, public resendRequest: boolean = false) {} } diff --git a/ui-ngx/src/app/core/services/interceptor-config.service.ts b/ui-ngx/src/app/core/services/interceptor-config.service.ts new file mode 100644 index 0000000000..a5e4d39cec --- /dev/null +++ b/ui-ngx/src/app/core/services/interceptor-config.service.ts @@ -0,0 +1,53 @@ +/// +/// 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. +/// + +import { Injectable } from '@angular/core'; +import { HttpRequest } from '@angular/common/http'; +import { InterceptorConfig } from '@core/interceptors/interceptor-config'; +import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; + +@Injectable({ + providedIn: 'root' +}) +export class InterceptorConfigService { + + private readonly internalUrlPrefixes = [ + '/api/auth/token', + '/api/rpc' + ]; + + getInterceptorConfig(req: HttpRequest): InterceptorConfig { + let config: InterceptorConfig; + if (req.params && req.params instanceof InterceptorHttpParams) { + config = (req.params as InterceptorHttpParams).interceptorConfig; + } else { + config = new InterceptorConfig(); + } + if (this.isInternalUrlPrefix(req.url)) { + config.ignoreLoading = true; + } + return config; + } + + private isInternalUrlPrefix(url: string): boolean { + for (const prefix of this.internalUrlPrefixes) { + if (url.startsWith(prefix)) { + return true; + } + } + return false; + } +} diff --git a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html index c500923723..502bc3381c 100644 --- a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html @@ -27,9 +27,9 @@
{{ data.message }}. - + {{ 'entity.version-conflict.link' | translate: - { entityType: (entityTypeTranslations.get(data.entityId.entityType).type | translate) } + { entityType: (entityTypeTranslations.get(data.entity.id.entityType).type | translate) } }} {{ 'entity.link' | translate }}. diff --git a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts index 32c0a45bdc..ec8c38e5d4 100644 --- a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts @@ -18,14 +18,13 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { SharedModule } from '@shared/shared.module'; import { ImportExportService } from '@shared/import-export/import-export.service'; -import { ExportableEntityTypes } from '@shared/import-export/import-export.models'; -import { EntityId } from '@shared/models/id/entity-id'; import { CommonModule } from '@angular/common'; -import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityInfoData } from '@shared/models/entity.models'; interface EntityConflictDialogData { message: string; - entityId: EntityId & {entityType: EntityType}; + entity: EntityInfoData; } @Component({ @@ -39,7 +38,6 @@ interface EntityConflictDialogData { ], }) export class EntityConflictDialogComponent { - readonly ExportableEntityTypes = ExportableEntityTypes; readonly entityTypeTranslations = entityTypeTranslations; constructor( @@ -58,6 +56,6 @@ export class EntityConflictDialogComponent { onLinkClick(event: MouseEvent): void { event.preventDefault(); - this.importExportService.exportEntity(this.data.entityId); + this.importExportService.exportEntity(this.data.entity); } } diff --git a/ui-ngx/src/app/shared/import-export/import-export.models.ts b/ui-ngx/src/app/shared/import-export/import-export.models.ts index 09c1bb3226..4933377d80 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.models.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.models.ts @@ -17,16 +17,6 @@ import { Widget, WidgetTypeDetails } from '@app/shared/models/widget.models'; import { DashboardLayoutId } from '@shared/models/dashboard.models'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { EntityType } from '@shared/models/entity-type.models'; - -export const ExportableEntityTypes = [ - EntityType.DEVICE_PROFILE, - EntityType.ASSET_PROFILE, - EntityType.RULE_CHAIN, - EntityType.DASHBOARD, - EntityType.WIDGET_TYPE, - EntityType.WIDGETS_BUNDLE -]; export interface ImportWidgetResult { widget: Widget; diff --git a/ui-ngx/src/app/shared/import-export/import-export.service.ts b/ui-ngx/src/app/shared/import-export/import-export.service.ts index c4fa2f4049..e3be8206a2 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.service.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.service.ts @@ -55,7 +55,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import { UtilsService } from '@core/services/utils.service'; import { WidgetService } from '@core/http/widget.service'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; +import { EntityInfoData, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; import { RequestConfig } from '@core/http/http-utils'; import { RuleChain, RuleChainImport, RuleChainMetaData, RuleChainType } from '@shared/models/rule-chain.models'; import { RuleChainService } from '@core/http/rule-chain.service'; @@ -79,7 +79,7 @@ import { ImageService } from '@core/http/image.service'; import { ImageExportData, ImageResourceInfo, ImageResourceType } from '@shared/models/resource.models'; import { selectUserSettingsProperty } from '@core/auth/auth.selectors'; import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions'; -import { ExportableEntity } from '@shared/models/base-data'; +import { ExportableEntity, HasId } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; export type editMissingAliasesFunction = (widgets: Array, isSingleWidget: boolean, @@ -380,29 +380,33 @@ export class ImportExportService { }); } - public exportEntity(entityId: EntityId): void { - switch (entityId.entityType) { + public exportEntity(entityData: EntityInfoData): void { + let preparedData; + switch (entityData.id.entityType) { case EntityType.DEVICE_PROFILE: - this.exportDeviceProfile(entityId.id); - break; case EntityType.ASSET_PROFILE: - this.exportAssetProfile(entityId.id); + preparedData = this.prepareProfileExport(entityData as DeviceProfile | AssetProfile); break; case EntityType.RULE_CHAIN: - this.exportRuleChain(entityId.id); - break; + this.ruleChainService.getRuleChainMetadata(entityData.id.id) + .pipe( + take(1), + map((ruleChainMetaData) => { + const ruleChainExport: RuleChainImport = { + ruleChain: this.prepareRuleChain(entityData as RuleChain), + metadata: this.prepareRuleChainMetaData(ruleChainMetaData) + }; + return ruleChainExport; + })) + .subscribe(ruleChainData => this.exportToPc(ruleChainData, entityData.name)); + return; case EntityType.DASHBOARD: - this.exportDashboard(entityId.id); - break; - case EntityType.WIDGET_TYPE: - this.exportWidgetType(entityId.id); - break; - case EntityType.WIDGETS_BUNDLE: - this.exportWidgetsBundle(entityId.id); + preparedData = this.prepareDashboardExport(entityData as Dashboard); break; default: - throwError(() => 'Not supported Entity Type'); + preparedData = this.prepareExport(entityData); } + this.exportToPc(preparedData, entityData.name); } private exportWidgetsBundleWithWidgetTypes(widgetsBundle: WidgetsBundle) { @@ -1133,6 +1137,9 @@ export class ImportExportService { if (isDefined(exportedData.externalId)) { delete exportedData.externalId; } + if (isDefined(exportedData.version)) { + delete exportedData.version; + } return exportedData; } diff --git a/ui-ngx/src/app/shared/models/asset.models.ts b/ui-ngx/src/app/shared/models/asset.models.ts index 6ba8bc8abb..7fc97f48e2 100644 --- a/ui-ngx/src/app/shared/models/asset.models.ts +++ b/ui-ngx/src/app/shared/models/asset.models.ts @@ -22,9 +22,9 @@ import { EntitySearchQuery } from '@shared/models/relation.models'; import { AssetProfileId } from '@shared/models/id/asset-profile-id'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { DashboardId } from '@shared/models/id/dashboard-id'; -import { EntityInfoData, HasTenantId } from '@shared/models/entity.models'; +import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface AssetProfile extends BaseData, HasTenantId, ExportableEntity { +export interface AssetProfile extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; name: string; description?: string; @@ -42,7 +42,7 @@ export interface AssetProfileInfo extends EntityInfoData { defaultDashboardId?: DashboardId; } -export interface Asset extends BaseData, HasTenantId, ExportableEntity { +export interface Asset extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; customerId?: CustomerId; name: string; diff --git a/ui-ngx/src/app/shared/models/customer.model.ts b/ui-ngx/src/app/shared/models/customer.model.ts index 3484dac633..2e8bf65334 100644 --- a/ui-ngx/src/app/shared/models/customer.model.ts +++ b/ui-ngx/src/app/shared/models/customer.model.ts @@ -18,9 +18,9 @@ import { CustomerId } from '@shared/models/id/customer-id'; import { ContactBased } from '@shared/models/contact-based.model'; import { TenantId } from './id/tenant-id'; import { ExportableEntity } from '@shared/models/base-data'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface Customer extends ContactBased, HasTenantId, ExportableEntity { +export interface Customer extends ContactBased, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; title: string; additionalInfo?: any; diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 36a14f59c6..fc42ccdc5f 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -23,9 +23,9 @@ import { Timewindow } from '@shared/models/time/time.models'; import { EntityAliases } from './alias.models'; import { Filters } from '@shared/models/query/query.models'; import { MatDialogRef } from '@angular/material/dialog'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface DashboardInfo extends BaseData, HasTenantId, ExportableEntity { +export interface DashboardInfo extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; title?: string; image?: string; diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index b38833bbaa..ba93568e17 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -22,7 +22,7 @@ import { DeviceCredentialsId } from '@shared/models/id/device-credentials-id'; import { EntitySearchQuery } from '@shared/models/relation.models'; import { DeviceProfileId } from '@shared/models/id/device-profile-id'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; -import { EntityInfoData, HasTenantId } from '@shared/models/entity.models'; +import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models'; import { FilterPredicateValue, KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; import * as _moment from 'moment'; @@ -584,7 +584,7 @@ export interface DeviceProfileData { provisionConfiguration?: DeviceProvisionConfiguration; } -export interface DeviceProfile extends BaseData, HasTenantId, ExportableEntity { +export interface DeviceProfile extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; name: string; description?: string; @@ -711,7 +711,7 @@ export interface DeviceData { transportConfiguration: DeviceTransportConfiguration; } -export interface Device extends BaseData, HasTenantId, ExportableEntity { +export interface Device extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId?: TenantId; customerId?: CustomerId; name: string; @@ -801,7 +801,7 @@ export const credentialTypesByTransportType = new Map { +export interface DeviceCredentials extends BaseData, HasTenantId { deviceId: DeviceId; credentialsType: DeviceCredentialsType; credentialsId: string; diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts index 569b2bec8a..6e1fe5dd70 100644 --- a/ui-ngx/src/app/shared/models/edge.models.ts +++ b/ui-ngx/src/app/shared/models/edge.models.ts @@ -22,9 +22,9 @@ import { EntitySearchQuery } from '@shared/models/relation.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { BaseEventBody } from '@shared/models/event.models'; import { EventId } from '@shared/models/id/event-id'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface Edge extends BaseData, HasTenantId { +export interface Edge extends BaseData, HasTenantId, HasVersion { tenantId?: TenantId; customerId?: CustomerId; name: string; diff --git a/ui-ngx/src/app/shared/models/entity-view.models.ts b/ui-ngx/src/app/shared/models/entity-view.models.ts index cb05dd3be5..f817456bbe 100644 --- a/ui-ngx/src/app/shared/models/entity-view.models.ts +++ b/ui-ngx/src/app/shared/models/entity-view.models.ts @@ -20,7 +20,7 @@ import { CustomerId } from '@shared/models/id/customer-id'; import { EntityViewId } from '@shared/models/id/entity-view-id'; import { EntityId } from '@shared/models/id/entity-id'; import { EntitySearchQuery } from '@shared/models/relation.models'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; export interface AttributesEntityView { cs: Array; @@ -33,7 +33,7 @@ export interface TelemetryEntityView { attributes: AttributesEntityView; } -export interface EntityView extends BaseData, HasTenantId, ExportableEntity { +export interface EntityView extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; customerId: CustomerId; entityId: EntityId; diff --git a/ui-ngx/src/app/shared/models/entity.models.ts b/ui-ngx/src/app/shared/models/entity.models.ts index cf8acd1c33..40ec570b13 100644 --- a/ui-ngx/src/app/shared/models/entity.models.ts +++ b/ui-ngx/src/app/shared/models/entity.models.ts @@ -187,3 +187,7 @@ export const entityFields: {[fieldName: string]: EntityField} = { export interface HasTenantId { tenantId?: TenantId; } + +export interface HasVersion { + version?: number; +} diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts index aa7ec47301..0bfdfbc459 100644 --- a/ui-ngx/src/app/shared/models/rule-chain.models.ts +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -20,9 +20,9 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { RuleNodeId } from '@shared/models/id/rule-node-id'; import { RuleNode, RuleNodeComponentDescriptor, RuleNodeType } from '@shared/models/rule-node.models'; import { ComponentClusteringMode, ComponentType } from '@shared/models/component-descriptor.models'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface RuleChain extends BaseData, HasTenantId, ExportableEntity { +export interface RuleChain extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; name: string; firstRuleNodeId: RuleNodeId; diff --git a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts index 9a6dbb5700..8bb34d9111 100644 --- a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts +++ b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts @@ -17,9 +17,9 @@ import { BaseData, ExportableEntity } from '@shared/models/base-data'; import { TenantId } from '@shared/models/id/tenant-id'; import { WidgetsBundleId } from '@shared/models/id/widgets-bundle-id'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -export interface WidgetsBundle extends BaseData, HasTenantId, ExportableEntity { +export interface WidgetsBundle extends BaseData, HasTenantId, HasVersion, ExportableEntity { tenantId: TenantId; alias: string; title: string;