2019-08-29 20:04:59 +03:00
|
|
|
///
|
2023-01-31 10:43:56 +02:00
|
|
|
/// Copyright © 2016-2023 The Thingsboard Authors
|
2019-08-29 20:04:59 +03:00
|
|
|
///
|
|
|
|
|
/// 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 {
|
|
|
|
|
AfterViewInit,
|
|
|
|
|
ChangeDetectionStrategy,
|
2021-08-31 18:57:07 +03:00
|
|
|
ChangeDetectorRef,
|
2019-08-29 20:04:59 +03:00
|
|
|
Component,
|
|
|
|
|
ElementRef,
|
2021-02-02 15:20:43 +02:00
|
|
|
Injector,
|
2020-01-22 20:05:30 +02:00
|
|
|
Input,
|
|
|
|
|
NgZone,
|
2019-08-29 20:04:59 +03:00
|
|
|
OnInit,
|
2021-02-02 15:20:43 +02:00
|
|
|
StaticProvider,
|
2019-08-29 20:04:59 +03:00
|
|
|
ViewChild,
|
|
|
|
|
ViewContainerRef
|
|
|
|
|
} from '@angular/core';
|
|
|
|
|
import { PageComponent } from '@shared/components/page.component';
|
|
|
|
|
import { PageLink } from '@shared/models/page/page-link';
|
|
|
|
|
import { MatPaginator } from '@angular/material/paginator';
|
|
|
|
|
import { MatSort } from '@angular/material/sort';
|
|
|
|
|
import { Store } from '@ngrx/store';
|
|
|
|
|
import { AppState } from '@core/core.state';
|
|
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
|
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
|
|
|
import { DialogService } from '@core/services/dialog.service';
|
|
|
|
|
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
2023-07-13 15:24:45 +03:00
|
|
|
import { fromEvent, merge, Observable } from 'rxjs';
|
2019-08-29 20:04:59 +03:00
|
|
|
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
|
|
|
|
|
import { EntityId } from '@shared/models/id/entity-id';
|
|
|
|
|
import {
|
|
|
|
|
AttributeData,
|
|
|
|
|
AttributeScope,
|
2019-11-15 12:22:14 +02:00
|
|
|
DataKeyType,
|
|
|
|
|
isClientSideTelemetryType,
|
|
|
|
|
LatestTelemetry,
|
2019-08-29 20:04:59 +03:00
|
|
|
TelemetryType,
|
2023-07-13 15:24:45 +03:00
|
|
|
telemetryTypeTranslations, TimeseriesDeleteStrategy,
|
2020-04-09 17:53:24 +03:00
|
|
|
toTelemetryType
|
2019-08-29 20:04:59 +03:00
|
|
|
} from '@shared/models/telemetry/telemetry.models';
|
|
|
|
|
import { AttributeDatasource } from '@home/models/datasource/attribute-datasource';
|
|
|
|
|
import { AttributeService } from '@app/core/http/attribute.service';
|
|
|
|
|
import { EntityType } from '@shared/models/entity-type.models';
|
|
|
|
|
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
|
|
|
|
import {
|
|
|
|
|
AddAttributeDialogComponent,
|
|
|
|
|
AddAttributeDialogData
|
|
|
|
|
} from '@home/components/attribute/add-attribute-dialog.component';
|
|
|
|
|
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
|
|
|
|
import {
|
|
|
|
|
EDIT_ATTRIBUTE_VALUE_PANEL_DATA,
|
|
|
|
|
EditAttributeValuePanelComponent,
|
|
|
|
|
EditAttributeValuePanelData
|
|
|
|
|
} from './edit-attribute-value-panel.component';
|
2021-02-02 15:20:43 +02:00
|
|
|
import { ComponentPortal } from '@angular/cdk/portal';
|
2019-08-30 19:19:45 +03:00
|
|
|
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
|
2019-11-15 12:22:14 +02:00
|
|
|
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
|
|
|
|
|
import { DataKey, Datasource, DatasourceType, Widget, widgetType } from '@shared/models/widget.models';
|
|
|
|
|
import { IAliasController, IStateController, StateParams } from '@core/api/widget-api.models';
|
2020-01-22 20:05:30 +02:00
|
|
|
import { AliasController } from '@core/api/alias-controller';
|
2019-11-15 12:22:14 +02:00
|
|
|
import { EntityAlias, EntityAliases } from '@shared/models/alias.models';
|
|
|
|
|
import { UtilsService } from '@core/services/utils.service';
|
|
|
|
|
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
|
|
|
|
|
import { NULL_UUID } from '@shared/models/id/has-uuid';
|
|
|
|
|
import { WidgetService } from '@core/http/widget.service';
|
|
|
|
|
import { toWidgetInfo } from '../../models/widget-component.models';
|
|
|
|
|
import { EntityService } from '@core/http/entity.service';
|
|
|
|
|
import {
|
|
|
|
|
AddWidgetToDashboardDialogComponent,
|
|
|
|
|
AddWidgetToDashboardDialogData
|
|
|
|
|
} from '@home/components/attribute/add-widget-to-dashboard-dialog.component';
|
2023-07-13 15:24:45 +03:00
|
|
|
import { deepClone, isUndefinedOrNull } from '@core/utils';
|
2020-06-30 19:37:50 +03:00
|
|
|
import { Filters } from '@shared/models/query/query.models';
|
2021-12-02 13:53:00 +02:00
|
|
|
import { hidePageSizePixelValue } from '@shared/models/constants';
|
|
|
|
|
import { ResizeObserver } from '@juggle/resize-observer';
|
2023-07-13 15:24:45 +03:00
|
|
|
import {
|
|
|
|
|
DELETE_TIMESERIES_PANEL_DATA,
|
|
|
|
|
DeleteTimeseriesPanelComponent, DeleteTimeseriesPanelData
|
|
|
|
|
} from '@home/components/attribute/delete-timeseries-panel.component';
|
2019-08-29 20:04:59 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'tb-attribute-table',
|
|
|
|
|
templateUrl: './attribute-table.component.html',
|
|
|
|
|
styleUrls: ['./attribute-table.component.scss'],
|
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
|
|
|
})
|
|
|
|
|
export class AttributeTableComponent extends PageComponent implements AfterViewInit, OnInit {
|
|
|
|
|
|
|
|
|
|
telemetryTypeTranslationsMap = telemetryTypeTranslations;
|
|
|
|
|
isClientSideTelemetryTypeMap = isClientSideTelemetryType;
|
|
|
|
|
|
|
|
|
|
latestTelemetryTypes = LatestTelemetry;
|
|
|
|
|
|
|
|
|
|
mode: 'default' | 'widget' = 'default';
|
|
|
|
|
|
|
|
|
|
attributeScopes: Array<string> = [];
|
|
|
|
|
attributeScope: TelemetryType;
|
2020-02-04 15:14:17 +02:00
|
|
|
toTelemetryTypeFunc = toTelemetryType;
|
2019-08-29 20:04:59 +03:00
|
|
|
|
|
|
|
|
displayedColumns = ['select', 'lastUpdateTs', 'key', 'value'];
|
|
|
|
|
pageLink: PageLink;
|
|
|
|
|
textSearchMode = false;
|
|
|
|
|
dataSource: AttributeDatasource;
|
2021-12-14 12:40:18 +02:00
|
|
|
hidePageSize = false;
|
2019-08-29 20:04:59 +03:00
|
|
|
|
|
|
|
|
activeValue = false;
|
|
|
|
|
dirtyValue = false;
|
|
|
|
|
entityIdValue: EntityId;
|
|
|
|
|
|
|
|
|
|
attributeScopeSelectionReadonly = false;
|
|
|
|
|
|
|
|
|
|
viewsInited = false;
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
selectedWidgetsBundleAlias: string = null;
|
2021-08-31 18:57:07 +03:00
|
|
|
widgetBundleSet = false;
|
2019-11-15 12:22:14 +02:00
|
|
|
widgetsLoaded = false;
|
|
|
|
|
widgetsCarouselIndex = 0;
|
|
|
|
|
widgetsList: Array<Array<Widget>> = [];
|
|
|
|
|
widgetsListCache: Array<Array<Widget>> = [];
|
|
|
|
|
aliasController: IAliasController;
|
|
|
|
|
private widgetDatasource: Datasource;
|
|
|
|
|
|
2021-12-14 12:40:18 +02:00
|
|
|
private widgetResize$: ResizeObserver;
|
|
|
|
|
|
2019-08-29 20:04:59 +03:00
|
|
|
private disableAttributeScopeSelectionValue: boolean;
|
2021-12-14 12:40:18 +02:00
|
|
|
|
2019-08-29 20:04:59 +03:00
|
|
|
get disableAttributeScopeSelection(): boolean {
|
|
|
|
|
return this.disableAttributeScopeSelectionValue;
|
|
|
|
|
}
|
2021-12-14 12:40:18 +02:00
|
|
|
|
2019-08-29 20:04:59 +03:00
|
|
|
@Input()
|
|
|
|
|
set disableAttributeScopeSelection(value: boolean) {
|
|
|
|
|
this.disableAttributeScopeSelectionValue = coerceBooleanProperty(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
defaultAttributeScope: TelemetryType;
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
set active(active: boolean) {
|
|
|
|
|
if (this.activeValue !== active) {
|
|
|
|
|
this.activeValue = active;
|
|
|
|
|
if (this.activeValue && this.dirtyValue) {
|
|
|
|
|
this.dirtyValue = false;
|
|
|
|
|
if (this.viewsInited) {
|
|
|
|
|
this.updateData(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
set entityId(entityId: EntityId) {
|
|
|
|
|
if (this.entityIdValue !== entityId) {
|
|
|
|
|
this.entityIdValue = entityId;
|
|
|
|
|
this.resetSortAndFilter(this.activeValue);
|
|
|
|
|
if (!this.activeValue) {
|
|
|
|
|
this.dirtyValue = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
@Input()
|
|
|
|
|
entityName: string;
|
|
|
|
|
|
2020-02-10 13:10:14 +02:00
|
|
|
@ViewChild('searchInput') searchInputField: ElementRef;
|
2019-08-29 20:04:59 +03:00
|
|
|
|
2020-02-10 13:10:14 +02:00
|
|
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
|
|
|
|
@ViewChild(MatSort) sort: MatSort;
|
2019-08-29 20:04:59 +03:00
|
|
|
|
|
|
|
|
constructor(protected store: Store<AppState>,
|
|
|
|
|
private attributeService: AttributeService,
|
2019-08-30 19:19:45 +03:00
|
|
|
private telemetryWsService: TelemetryWebsocketService,
|
2019-08-29 20:04:59 +03:00
|
|
|
public translate: TranslateService,
|
|
|
|
|
public dialog: MatDialog,
|
|
|
|
|
private overlay: Overlay,
|
|
|
|
|
private viewContainerRef: ViewContainerRef,
|
2019-11-15 12:22:14 +02:00
|
|
|
private dialogService: DialogService,
|
|
|
|
|
private entityService: EntityService,
|
|
|
|
|
private utils: UtilsService,
|
|
|
|
|
private dashboardUtils: DashboardUtilsService,
|
|
|
|
|
private widgetService: WidgetService,
|
2021-08-31 18:57:07 +03:00
|
|
|
private zone: NgZone,
|
2021-12-14 12:40:18 +02:00
|
|
|
private cd: ChangeDetectorRef,
|
|
|
|
|
private elementRef: ElementRef) {
|
2019-08-29 20:04:59 +03:00
|
|
|
super(store);
|
|
|
|
|
this.dirtyValue = !this.activeValue;
|
|
|
|
|
const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC };
|
|
|
|
|
this.pageLink = new PageLink(10, 0, null, sortOrder);
|
2019-11-15 12:22:14 +02:00
|
|
|
this.dataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.zone, this.translate);
|
2019-08-29 20:04:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnInit() {
|
2021-12-02 13:53:00 +02:00
|
|
|
this.widgetResize$ = new ResizeObserver(() => {
|
2021-12-14 12:40:18 +02:00
|
|
|
const showHidePageSize = this.elementRef.nativeElement.offsetWidth < hidePageSizePixelValue;
|
2021-12-02 13:53:00 +02:00
|
|
|
if (showHidePageSize !== this.hidePageSize) {
|
|
|
|
|
this.hidePageSize = showHidePageSize;
|
2021-12-14 12:40:18 +02:00
|
|
|
this.cd.markForCheck();
|
2021-12-02 13:53:00 +02:00
|
|
|
}
|
|
|
|
|
});
|
2021-12-14 12:40:18 +02:00
|
|
|
this.widgetResize$.observe(this.elementRef.nativeElement);
|
2021-12-01 11:32:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnDestroy() {
|
2021-12-02 13:53:00 +02:00
|
|
|
if (this.widgetResize$) {
|
|
|
|
|
this.widgetResize$.disconnect();
|
2021-12-01 11:32:32 +02:00
|
|
|
}
|
2019-08-29 20:04:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attributeScopeChanged(attributeScope: TelemetryType) {
|
|
|
|
|
this.attributeScope = attributeScope;
|
|
|
|
|
this.mode = 'default';
|
2021-09-30 16:45:57 +03:00
|
|
|
this.paginator.pageIndex = 0;
|
2019-08-29 20:04:59 +03:00
|
|
|
this.updateData(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngAfterViewInit() {
|
|
|
|
|
|
|
|
|
|
fromEvent(this.searchInputField.nativeElement, 'keyup')
|
|
|
|
|
.pipe(
|
|
|
|
|
debounceTime(150),
|
|
|
|
|
distinctUntilChanged(),
|
|
|
|
|
tap(() => {
|
|
|
|
|
this.paginator.pageIndex = 0;
|
|
|
|
|
this.updateData();
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.subscribe();
|
|
|
|
|
|
|
|
|
|
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
|
|
|
|
|
|
|
|
|
merge(this.sort.sortChange, this.paginator.page)
|
|
|
|
|
.pipe(
|
|
|
|
|
tap(() => this.updateData())
|
|
|
|
|
)
|
|
|
|
|
.subscribe();
|
|
|
|
|
|
|
|
|
|
this.viewsInited = true;
|
|
|
|
|
if (this.activeValue && this.entityIdValue) {
|
|
|
|
|
this.updateData(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateData(reload: boolean = false) {
|
|
|
|
|
this.pageLink.page = this.paginator.pageIndex;
|
|
|
|
|
this.pageLink.pageSize = this.paginator.pageSize;
|
|
|
|
|
this.pageLink.sortOrder.property = this.sort.active;
|
|
|
|
|
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
|
|
|
|
|
this.dataSource.loadAttributes(this.entityIdValue, this.attributeScope, this.pageLink, reload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enterFilterMode() {
|
|
|
|
|
this.textSearchMode = true;
|
|
|
|
|
this.pageLink.textSearch = '';
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.searchInputField.nativeElement.focus();
|
|
|
|
|
this.searchInputField.nativeElement.setSelectionRange(0, 0);
|
|
|
|
|
}, 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exitFilterMode() {
|
|
|
|
|
this.textSearchMode = false;
|
|
|
|
|
this.pageLink.textSearch = null;
|
|
|
|
|
this.paginator.pageIndex = 0;
|
|
|
|
|
this.updateData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resetSortAndFilter(update: boolean = true) {
|
|
|
|
|
const entityType = this.entityIdValue.entityType;
|
|
|
|
|
if (entityType === EntityType.DEVICE || entityType === EntityType.ENTITY_VIEW) {
|
|
|
|
|
this.attributeScopes = Object.keys(AttributeScope);
|
|
|
|
|
this.attributeScopeSelectionReadonly = false;
|
|
|
|
|
} else {
|
|
|
|
|
this.attributeScopes = [AttributeScope.SERVER_SCOPE];
|
|
|
|
|
this.attributeScopeSelectionReadonly = true;
|
|
|
|
|
}
|
|
|
|
|
this.mode = 'default';
|
2022-05-26 11:11:58 +03:00
|
|
|
this.textSearchMode = false;
|
2021-11-12 15:52:58 +02:00
|
|
|
this.selectedWidgetsBundleAlias = null;
|
2019-08-29 20:04:59 +03:00
|
|
|
this.attributeScope = this.defaultAttributeScope;
|
|
|
|
|
this.pageLink.textSearch = null;
|
|
|
|
|
if (this.viewsInited) {
|
|
|
|
|
this.paginator.pageIndex = 0;
|
|
|
|
|
const sortable = this.sort.sortables.get('key');
|
|
|
|
|
this.sort.active = sortable.id;
|
|
|
|
|
this.sort.direction = 'asc';
|
|
|
|
|
if (update) {
|
|
|
|
|
this.updateData(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reloadAttributes() {
|
|
|
|
|
this.updateData(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addAttribute($event: Event) {
|
|
|
|
|
if ($event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
this.dialog.open<AddAttributeDialogComponent, AddAttributeDialogData, boolean>(AddAttributeDialogComponent, {
|
|
|
|
|
disableClose: true,
|
|
|
|
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
|
|
|
|
data: {
|
|
|
|
|
entityId: this.entityIdValue,
|
|
|
|
|
attributeScope: this.attributeScope as AttributeScope
|
|
|
|
|
}
|
|
|
|
|
}).afterClosed().subscribe(
|
|
|
|
|
(res) => {
|
|
|
|
|
if (res) {
|
|
|
|
|
this.reloadAttributes();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
editAttribute($event: Event, attribute: AttributeData) {
|
|
|
|
|
if ($event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
}
|
2020-04-09 18:06:10 +03:00
|
|
|
if (this.isClientSideTelemetryTypeMap.get(this.attributeScope)) {
|
2020-04-09 17:53:24 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2019-08-29 20:04:59 +03:00
|
|
|
const target = $event.target || $event.srcElement || $event.currentTarget;
|
|
|
|
|
const config = new OverlayConfig();
|
|
|
|
|
config.backdropClass = 'cdk-overlay-transparent-backdrop';
|
|
|
|
|
config.hasBackdrop = true;
|
|
|
|
|
const connectedPosition: ConnectedPosition = {
|
|
|
|
|
originX: 'end',
|
|
|
|
|
originY: 'center',
|
|
|
|
|
overlayX: 'end',
|
|
|
|
|
overlayY: 'center'
|
|
|
|
|
};
|
|
|
|
|
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
|
|
|
|
|
.withPositions([connectedPosition]);
|
|
|
|
|
|
|
|
|
|
const overlayRef = this.overlay.create(config);
|
|
|
|
|
overlayRef.backdropClick().subscribe(() => {
|
|
|
|
|
overlayRef.dispose();
|
|
|
|
|
});
|
2021-02-02 15:20:43 +02:00
|
|
|
const providers: StaticProvider[] = [
|
|
|
|
|
{
|
|
|
|
|
provide: EDIT_ATTRIBUTE_VALUE_PANEL_DATA,
|
|
|
|
|
useValue: {
|
|
|
|
|
attributeValue: attribute.value
|
|
|
|
|
} as EditAttributeValuePanelData
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
provide: OverlayRef,
|
|
|
|
|
useValue: overlayRef
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
|
2019-08-29 20:04:59 +03:00
|
|
|
const componentRef = overlayRef.attach(new ComponentPortal(EditAttributeValuePanelComponent,
|
|
|
|
|
this.viewContainerRef, injector));
|
|
|
|
|
componentRef.onDestroy(() => {
|
|
|
|
|
if (componentRef.instance.result !== null) {
|
|
|
|
|
const attributeValue = componentRef.instance.result;
|
|
|
|
|
const updatedAttribute = {...attribute};
|
|
|
|
|
updatedAttribute.value = attributeValue;
|
|
|
|
|
this.attributeService.saveEntityAttributes(this.entityIdValue,
|
|
|
|
|
this.attributeScope as AttributeScope, [updatedAttribute]).subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.reloadAttributes();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-13 15:24:45 +03:00
|
|
|
deleteTimeseries($event: Event, attribute?: AttributeData) {
|
|
|
|
|
if ($event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
}
|
2023-07-14 12:37:40 +03:00
|
|
|
const isMultipleDeletion = isUndefinedOrNull(attribute) && this.dataSource.selection.selected.length > 1;
|
2023-07-13 15:24:45 +03:00
|
|
|
const target = $event.target || $event.srcElement || $event.currentTarget;
|
|
|
|
|
const config = new OverlayConfig();
|
|
|
|
|
config.backdropClass = 'cdk-overlay-transparent-backdrop';
|
|
|
|
|
config.hasBackdrop = true;
|
|
|
|
|
const connectedPosition: ConnectedPosition = {
|
|
|
|
|
originX: 'start',
|
|
|
|
|
originY: 'top',
|
|
|
|
|
overlayX: 'end',
|
|
|
|
|
overlayY: 'top'
|
|
|
|
|
};
|
|
|
|
|
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
|
|
|
|
|
.withPositions([connectedPosition]);
|
|
|
|
|
config.maxWidth = '488px';
|
|
|
|
|
config.width = '100%';
|
|
|
|
|
const overlayRef = this.overlay.create(config);
|
|
|
|
|
overlayRef.backdropClick().subscribe(() => {
|
|
|
|
|
overlayRef.dispose();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const providers: StaticProvider[] = [
|
|
|
|
|
{
|
|
|
|
|
provide: DELETE_TIMESERIES_PANEL_DATA,
|
|
|
|
|
useValue: {
|
|
|
|
|
isMultipleDeletion: isMultipleDeletion
|
|
|
|
|
} as DeleteTimeseriesPanelData
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
provide: OverlayRef,
|
|
|
|
|
useValue: overlayRef
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
|
|
|
|
|
const componentRef = overlayRef.attach(new ComponentPortal(DeleteTimeseriesPanelComponent,
|
|
|
|
|
this.viewContainerRef, injector));
|
|
|
|
|
componentRef.onDestroy(() => {
|
|
|
|
|
if (componentRef.instance.result !== null) {
|
|
|
|
|
const strategy = componentRef.instance.result;
|
2023-07-14 12:37:40 +03:00
|
|
|
const timeseries = attribute ? [attribute]: this.dataSource.selection.selected;
|
2023-07-13 15:24:45 +03:00
|
|
|
let deleteAllDataForKeys = false;
|
|
|
|
|
let rewriteLatestIfDeleted = false;
|
|
|
|
|
let startTs = null;
|
|
|
|
|
let endTs = null;
|
2023-07-14 12:37:40 +03:00
|
|
|
let deleteLatest = true;
|
2023-07-13 15:24:45 +03:00
|
|
|
let task: Observable<any>;
|
|
|
|
|
if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY) {
|
|
|
|
|
deleteAllDataForKeys = true;
|
|
|
|
|
}
|
|
|
|
|
if (strategy === TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE) {
|
|
|
|
|
deleteAllDataForKeys = true;
|
2023-07-14 12:37:40 +03:00
|
|
|
deleteLatest = false;
|
2023-07-13 15:24:45 +03:00
|
|
|
}
|
|
|
|
|
if (strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE) {
|
2023-07-14 12:37:40 +03:00
|
|
|
rewriteLatestIfDeleted = componentRef.instance.rewriteLatestIfDeleted;
|
|
|
|
|
task = this.attributeService.deleteEntityLatestTimeseries(this.entityIdValue, timeseries, rewriteLatestIfDeleted);
|
2023-07-13 15:24:45 +03:00
|
|
|
}
|
|
|
|
|
if (strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD) {
|
|
|
|
|
startTs = componentRef.instance.startDateTime.getTime();
|
|
|
|
|
endTs = componentRef.instance.endDateTime.getTime();
|
|
|
|
|
rewriteLatestIfDeleted = componentRef.instance.rewriteLatestIfDeleted;
|
|
|
|
|
}
|
|
|
|
|
if (!task) {
|
|
|
|
|
task = this.attributeService.deleteEntityTimeseries(this.entityIdValue, timeseries, deleteAllDataForKeys,
|
|
|
|
|
startTs, endTs, rewriteLatestIfDeleted, deleteLatest);
|
|
|
|
|
}
|
|
|
|
|
task.subscribe(() => this.reloadAttributes());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 20:04:59 +03:00
|
|
|
deleteAttributes($event: Event) {
|
|
|
|
|
if ($event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
if (this.dataSource.selection.selected.length > 0) {
|
|
|
|
|
this.dialogService.confirm(
|
|
|
|
|
this.translate.instant('attribute.delete-attributes-title', {count: this.dataSource.selection.selected.length}),
|
|
|
|
|
this.translate.instant('attribute.delete-attributes-text'),
|
|
|
|
|
this.translate.instant('action.no'),
|
|
|
|
|
this.translate.instant('action.yes'),
|
|
|
|
|
true
|
|
|
|
|
).subscribe((result) => {
|
|
|
|
|
if (result) {
|
|
|
|
|
this.attributeService.deleteEntityAttributes(this.entityIdValue,
|
|
|
|
|
this.attributeScope as AttributeScope, this.dataSource.selection.selected).subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.reloadAttributes();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enterWidgetMode() {
|
|
|
|
|
this.mode = 'widget';
|
2019-11-15 12:22:14 +02:00
|
|
|
this.widgetsList = [];
|
|
|
|
|
this.widgetsListCache = [];
|
|
|
|
|
this.widgetsLoaded = false;
|
2021-08-31 18:57:07 +03:00
|
|
|
this.widgetBundleSet = false;
|
2019-11-15 12:22:14 +02:00
|
|
|
this.widgetsCarouselIndex = 0;
|
|
|
|
|
this.selectedWidgetsBundleAlias = 'cards';
|
|
|
|
|
|
|
|
|
|
const entityAlias: EntityAlias = {
|
|
|
|
|
id: this.utils.guid(),
|
|
|
|
|
alias: this.entityName,
|
|
|
|
|
filter: this.dashboardUtils.createSingleEntityFilter(this.entityIdValue)
|
|
|
|
|
};
|
|
|
|
|
const entitiAliases: EntityAliases = {};
|
|
|
|
|
entitiAliases[entityAlias.id] = entityAlias;
|
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
const stateController: IStateController = {
|
|
|
|
|
getStateParams(): StateParams {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-06-30 19:37:50 +03:00
|
|
|
const filters: Filters = {};
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
this.aliasController = new AliasController(this.utils,
|
|
|
|
|
this.entityService,
|
2021-03-02 12:04:45 +02:00
|
|
|
this.translate,
|
2020-06-30 19:37:50 +03:00
|
|
|
() => stateController, entitiAliases, filters);
|
2019-11-15 12:22:14 +02:00
|
|
|
|
|
|
|
|
const dataKeyType: DataKeyType = this.attributeScope === LatestTelemetry.LATEST_TELEMETRY ?
|
|
|
|
|
DataKeyType.timeseries : DataKeyType.attribute;
|
|
|
|
|
|
|
|
|
|
this.widgetDatasource = {
|
|
|
|
|
type: DatasourceType.entity,
|
|
|
|
|
entityAliasId: entityAlias.id,
|
|
|
|
|
dataKeys: []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < this.dataSource.selection.selected.length; i++) {
|
|
|
|
|
const attribute = this.dataSource.selection.selected[i];
|
|
|
|
|
const dataKey: DataKey = {
|
|
|
|
|
name: attribute.key,
|
|
|
|
|
label: attribute.key,
|
|
|
|
|
type: dataKeyType,
|
|
|
|
|
color: this.utils.getMaterialColor(i),
|
|
|
|
|
settings: {},
|
|
|
|
|
_hash: Math.random()
|
|
|
|
|
};
|
|
|
|
|
this.widgetDatasource.dataKeys.push(dataKey);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-29 20:04:59 +03:00
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
onWidgetsCarouselIndexChanged() {
|
|
|
|
|
if (this.mode === 'widget') {
|
|
|
|
|
for (let i = 0; i < this.widgetsList.length; i++) {
|
|
|
|
|
this.widgetsList[i].splice(0, this.widgetsList[i].length);
|
|
|
|
|
if (i === this.widgetsCarouselIndex) {
|
|
|
|
|
this.widgetsList[i].push(this.widgetsListCache[i][0]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-31 18:57:07 +03:00
|
|
|
onWidgetsBundleChanged(widgetsBundle: WidgetsBundle) {
|
2021-08-31 19:40:00 +03:00
|
|
|
this.widgetBundleSet = !!widgetsBundle;
|
2019-11-15 12:22:14 +02:00
|
|
|
if (this.mode === 'widget') {
|
|
|
|
|
this.widgetsList = [];
|
|
|
|
|
this.widgetsListCache = [];
|
2021-09-30 16:45:57 +03:00
|
|
|
this.widgetsCarouselIndex = 0;
|
2021-08-31 18:57:07 +03:00
|
|
|
if (widgetsBundle) {
|
2019-11-15 12:22:14 +02:00
|
|
|
this.widgetsLoaded = false;
|
2021-08-31 18:57:07 +03:00
|
|
|
const bundleAlias = widgetsBundle.alias;
|
|
|
|
|
const isSystem = widgetsBundle.tenantId.id === NULL_UUID;
|
2019-11-15 12:22:14 +02:00
|
|
|
this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe(
|
|
|
|
|
(widgetTypes) => {
|
|
|
|
|
widgetTypes = widgetTypes.sort((a, b) => {
|
|
|
|
|
let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
|
|
|
|
|
if (result === 0) {
|
|
|
|
|
result = b.createdTime - a.createdTime;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
});
|
|
|
|
|
for (const type of widgetTypes) {
|
|
|
|
|
const widgetInfo = toWidgetInfo(type);
|
|
|
|
|
if (widgetInfo.type !== widgetType.static) {
|
|
|
|
|
const sizeX = widgetInfo.sizeX * 2;
|
|
|
|
|
const sizeY = widgetInfo.sizeY * 2;
|
|
|
|
|
const col = Math.floor(Math.max(0, (20 - sizeX) / 2));
|
|
|
|
|
const widget: Widget = {
|
|
|
|
|
isSystemType: isSystem,
|
|
|
|
|
bundleAlias,
|
|
|
|
|
typeAlias: widgetInfo.alias,
|
|
|
|
|
type: widgetInfo.type,
|
|
|
|
|
title: widgetInfo.widgetName,
|
|
|
|
|
sizeX,
|
|
|
|
|
sizeY,
|
|
|
|
|
row: 0,
|
|
|
|
|
col,
|
|
|
|
|
config: JSON.parse(widgetInfo.defaultConfig)
|
|
|
|
|
};
|
|
|
|
|
widget.config.title = widgetInfo.widgetName;
|
|
|
|
|
widget.config.datasources = [this.widgetDatasource];
|
|
|
|
|
if ((this.attributeScope === LatestTelemetry.LATEST_TELEMETRY && widgetInfo.type !== widgetType.rpc) ||
|
|
|
|
|
widgetInfo.type === widgetType.latest) {
|
|
|
|
|
const length = this.widgetsListCache.push([widget]);
|
|
|
|
|
this.widgetsList.push(length === 1 ? [widget] : []);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.widgetsLoaded = true;
|
2021-08-31 18:57:07 +03:00
|
|
|
this.cd.markForCheck();
|
2019-11-15 12:22:14 +02:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addWidgetToDashboard() {
|
|
|
|
|
if (this.mode === 'widget' && this.widgetsListCache.length > 0) {
|
|
|
|
|
const widget = this.widgetsListCache[this.widgetsCarouselIndex][0];
|
|
|
|
|
this.dialog.open<AddWidgetToDashboardDialogComponent, AddWidgetToDashboardDialogData>
|
|
|
|
|
(AddWidgetToDashboardDialogComponent, {
|
|
|
|
|
disableClose: true,
|
|
|
|
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
|
|
|
|
data: {
|
|
|
|
|
entityId: this.entityIdValue,
|
|
|
|
|
entityName: this.entityName,
|
|
|
|
|
widget: deepClone(widget)
|
|
|
|
|
}
|
|
|
|
|
}).afterClosed();
|
|
|
|
|
}
|
2019-08-29 20:04:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exitWidgetMode() {
|
2019-11-15 12:22:14 +02:00
|
|
|
this.selectedWidgetsBundleAlias = null;
|
2019-08-29 20:04:59 +03:00
|
|
|
this.mode = 'default';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|