thingsboard/ui-ngx/src/app/modules/home/models/widget-component.models.ts

612 lines
20 KiB
TypeScript
Raw Normal View History

2019-08-14 19:55:24 +03:00
///
2022-01-17 14:07:46 +02:00
/// Copyright © 2016-2022 The Thingsboard Authors
2019-08-14 19:55:24 +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 { IDashboardComponent } from '@home/models/dashboard-component.models';
2019-09-06 20:17:45 +03:00
import {
2019-09-10 15:12:10 +03:00
DataSet,
Datasource,
DatasourceData,
FormattedData,
JsonSettingsSchema,
Widget,
2019-09-06 20:17:45 +03:00
WidgetActionDescriptor,
WidgetActionSource,
WidgetConfig,
WidgetControllerDescriptor,
WidgetType,
widgetType,
WidgetTypeDescriptor,
WidgetTypeDetails,
WidgetTypeParameters
2019-09-06 20:17:45 +03:00
} from '@shared/models/widget.models';
import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models';
import {
IAliasController,
2019-09-06 20:17:45 +03:00
IStateController,
IWidgetSubscription,
IWidgetUtils,
RpcApi,
StateParams,
SubscriptionEntityInfo,
2019-09-06 20:17:45 +03:00
TimewindowFunctions,
WidgetActionsApi,
WidgetSubscriptionApi
} from '@core/api/widget-api.models';
import { ChangeDetectorRef, ComponentFactory, Injector, NgZone, Type } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { RafService } from '@core/services/raf.service';
import { WidgetTypeId } from '@shared/models/id/widget-type-id';
import { TenantId } from '@shared/models/id/tenant-id';
2019-09-25 19:37:29 +03:00
import { WidgetLayout } from '@shared/models/dashboard.models';
import { formatValue, isDefined } from '@core/utils';
2020-02-28 19:49:14 +02:00
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {
NotificationHorizontalPosition,
NotificationType,
NotificationVerticalPosition
} from '@core/notification/notification.models';
import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { DeviceService } from '@core/http/device.service';
import { AssetService } from '@core/http/asset.service';
import { EntityViewService } from '@core/http/entity-view.service';
import { CustomerService } from '@core/http/customer.service';
import { DashboardService } from '@core/http/dashboard.service';
import { UserService } from '@core/http/user.service';
import { AttributeService } from '@core/http/attribute.service';
import { EntityRelationService } from '@core/http/entity-relation.service';
import { EntityService } from '@core/http/entity.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { AuthService } from '@core/auth/auth.service';
import { ResourceService } from '@core/http/resource.service';
import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { PageLink, TimePageLink } from '@shared/models/page/page-link';
2020-06-05 15:36:28 +03:00
import { SortOrder } from '@shared/models/page/sort-order';
import { DomSanitizer } from '@angular/platform-browser';
2020-08-31 16:39:54 +03:00
import { Router } from '@angular/router';
2022-06-22 16:51:40 +03:00
import { EdgeService } from '@core/http/edge.service';
2022-06-16 11:17:59 +05:00
import * as RxJS from 'rxjs';
import * as RxJSOperators from 'rxjs/operators';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { EntityId } from '@shared/models/id/entity-id';
import { AlarmQuery, AlarmSearchStatus, AlarmStatus} from '@app/shared/models/alarm.models';
2019-09-03 19:31:16 +03:00
export interface IWidgetAction {
name: string;
2019-09-03 19:31:16 +03:00
icon: string;
onAction: ($event: Event) => void;
}
2019-08-14 19:55:24 +03:00
export type ShowWidgetHeaderActionFunction = (ctx: WidgetContext, data: FormattedData[]) => boolean;
2019-09-03 19:31:16 +03:00
export interface WidgetHeaderAction extends IWidgetAction {
displayName: string;
descriptor: WidgetActionDescriptor;
useShowWidgetHeaderActionFunction: boolean;
showWidgetHeaderActionFunction: ShowWidgetHeaderActionFunction;
2019-08-14 19:55:24 +03:00
}
2019-09-03 19:31:16 +03:00
export interface WidgetAction extends IWidgetAction {
show: boolean;
}
export interface IDashboardWidget {
updateWidgetParams();
}
2019-11-04 15:47:36 +02:00
export class WidgetContext {
constructor(public dashboard: IDashboardComponent,
private dashboardWidget: IDashboardWidget,
private widget: Widget,
public parentDashboard?: IDashboardComponent,
public popoverComponent?: TbPopoverComponent) {}
2019-11-04 15:47:36 +02:00
get stateController(): IStateController {
return this.parentDashboard ? this.parentDashboard.stateController : this.dashboard.stateController;
2019-11-04 15:47:36 +02:00
}
get aliasController(): IAliasController {
return this.dashboard.aliasController;
}
get dashboardTimewindow(): Timewindow {
return this.dashboard.dashboardTimewindow;
}
get widgetConfig(): WidgetConfig {
return this.widget.config;
}
get settings(): any {
return this.widget.config.settings;
}
get units(): string {
return this.widget.config.units || '';
}
get decimals(): number {
return isDefined(this.widget.config.decimals) ? this.widget.config.decimals : 2;
}
set changeDetector(cd: ChangeDetectorRef) {
this.changeDetectorValue = cd;
}
set containerChangeDetector(cd: ChangeDetectorRef) {
this.containerChangeDetectorValue = cd;
}
get currentUser(): AuthUser {
if (this.store) {
return getCurrentAuthUser(this.store);
} else {
return null;
}
}
authService: AuthService;
deviceService: DeviceService;
assetService: AssetService;
entityViewService: EntityViewService;
2022-06-22 16:51:40 +03:00
edgeService: EdgeService;
customerService: CustomerService;
dashboardService: DashboardService;
userService: UserService;
attributeService: AttributeService;
entityRelationService: EntityRelationService;
entityService: EntityService;
dialogs: DialogService;
customDialog: CustomDialogService;
resourceService: ResourceService;
date: DatePipe;
translate: TranslateService;
http: HttpClient;
sanitizer: DomSanitizer;
2020-08-31 16:39:54 +03:00
router: Router;
private changeDetectorValue: ChangeDetectorRef;
private containerChangeDetectorValue: ChangeDetectorRef;
2020-02-03 19:38:28 +02:00
2019-11-04 15:47:36 +02:00
inited = false;
destroyed = false;
2019-11-04 15:47:36 +02:00
subscriptions: {[id: string]: IWidgetSubscription} = {};
defaultSubscription: IWidgetSubscription = null;
timewindowFunctions: TimewindowFunctions = {
onUpdateTimewindow: (startTimeMs, endTimeMs, interval) => {
if (this.defaultSubscription) {
this.defaultSubscription.onUpdateTimewindow(startTimeMs, endTimeMs, interval);
}
},
onResetTimewindow: () => {
if (this.defaultSubscription) {
this.defaultSubscription.onResetTimewindow();
}
}
};
controlApi: RpcApi = {
sendOneWayCommand: (method, params, timeout, persistent,
retries, additionalInfo, requestUUID) => {
2019-11-04 15:47:36 +02:00
if (this.defaultSubscription) {
return this.defaultSubscription.sendOneWayCommand(method, params, timeout, persistent, retries, additionalInfo, requestUUID);
2019-11-04 15:47:36 +02:00
} else {
2022-06-16 11:17:59 +05:00
return RxJS.of(null);
2019-11-04 15:47:36 +02:00
}
},
sendTwoWayCommand: (method, params, timeout, persistent,
retries, additionalInfo, requestUUID) => {
2019-11-04 15:47:36 +02:00
if (this.defaultSubscription) {
return this.defaultSubscription.sendTwoWayCommand(method, params, timeout, persistent, retries, additionalInfo, requestUUID);
2019-11-04 15:47:36 +02:00
} else {
2022-06-16 11:17:59 +05:00
return RxJS.of(null);
2019-11-04 15:47:36 +02:00
}
},
completedCommand: () => {
if (this.defaultSubscription) {
return this.defaultSubscription.completedCommand();
} else {
2022-06-16 11:17:59 +05:00
return RxJS.of(null);
}
2019-11-04 15:47:36 +02:00
}
};
utils: IWidgetUtils = {
formatValue
};
2020-02-28 19:49:14 +02:00
$container: JQuery<HTMLElement>;
$containerParent: JQuery<HTMLElement>;
2019-11-04 15:47:36 +02:00
width: number;
height: number;
$scope: IDynamicWidgetComponent;
isEdit: boolean;
isMobile: boolean;
toastTargetId: string;
2019-11-04 15:47:36 +02:00
2020-02-10 13:04:56 +02:00
widgetNamespace?: string;
subscriptionApi?: WidgetSubscriptionApi;
2019-11-04 15:47:36 +02:00
actionsApi?: WidgetActionsApi;
activeEntityInfo?: SubscriptionEntityInfo;
2019-09-10 15:12:10 +03:00
datasources?: Array<Datasource>;
data?: Array<DatasourceData>;
latestData?: Array<DatasourceData>;
2019-09-10 15:12:10 +03:00
hiddenData?: Array<{data: DataSet}>;
timeWindow?: WidgetTimewindow;
2019-10-24 19:52:19 +03:00
2019-11-04 15:47:36 +02:00
hideTitlePanel = false;
2019-10-24 19:52:19 +03:00
widgetTitle?: string;
2020-02-25 19:11:25 +02:00
widgetTitleTooltip?: string;
2019-10-24 19:52:19 +03:00
customHeaderActions?: Array<WidgetHeaderAction>;
widgetActions?: Array<WidgetAction>;
2019-10-25 17:50:12 +03:00
servicesMap?: Map<string, Type<any>>;
$injector?: Injector;
2020-02-03 17:29:01 +02:00
ngZone?: NgZone;
2020-02-28 19:49:14 +02:00
store?: Store<AppState>;
private popoverComponents: TbPopoverComponent[] = [];
rxjs = {
2022-06-16 11:17:59 +05:00
...RxJS,
...RxJSOperators
};
registerPopoverComponent(popoverComponent: TbPopoverComponent) {
this.popoverComponents.push(popoverComponent);
popoverComponent.tbDestroy.subscribe(() => {
const index = this.popoverComponents.indexOf(popoverComponent, 0);
if (index > -1) {
this.popoverComponents.splice(index, 1);
}
});
}
updatePopoverPositions() {
this.popoverComponents.forEach(comp => {
comp.updatePosition();
});
}
setPopoversHidden(hidden: boolean) {
this.popoverComponents.forEach(comp => {
comp.tbHidden = hidden;
});
}
2020-02-28 19:49:14 +02:00
showSuccessToast(message: string, duration: number = 1000,
verticalPosition: NotificationVerticalPosition = 'bottom',
horizontalPosition: NotificationHorizontalPosition = 'left',
target: string = 'dashboardRoot') {
2020-02-28 19:49:14 +02:00
this.showToast('success', message, duration, verticalPosition, horizontalPosition, target);
}
showInfoToast(message: string,
2020-08-14 13:08:17 +03:00
verticalPosition: NotificationVerticalPosition = 'bottom',
horizontalPosition: NotificationHorizontalPosition = 'left',
target: string = 'dashboardRoot') {
this.showToast('info', message, undefined, verticalPosition, horizontalPosition, target);
}
showWarnToast(message: string,
verticalPosition: NotificationVerticalPosition = 'bottom',
horizontalPosition: NotificationHorizontalPosition = 'left',
target: string = 'dashboardRoot') {
this.showToast('warn', message, undefined, verticalPosition, horizontalPosition, target);
}
2020-02-28 19:49:14 +02:00
showErrorToast(message: string,
verticalPosition: NotificationVerticalPosition = 'bottom',
horizontalPosition: NotificationHorizontalPosition = 'left',
target: string = 'dashboardRoot') {
2020-02-28 19:49:14 +02:00
this.showToast('error', message, undefined, verticalPosition, horizontalPosition, target);
}
showToast(type: NotificationType, message: string, duration: number,
verticalPosition: NotificationVerticalPosition = 'bottom',
horizontalPosition: NotificationHorizontalPosition = 'left',
target: string = 'dashboardRoot') {
2020-02-28 19:49:14 +02:00
this.store.dispatch(new ActionNotificationShow(
{
message,
type,
duration,
verticalPosition,
horizontalPosition,
target,
panelClass: this.widgetNamespace,
forceDismiss: true
}));
}
hideToast(target?: string) {
this.store.dispatch(new ActionNotificationHide(
{
target,
}));
}
2020-02-28 19:49:14 +02:00
detectChanges(updateWidgetParams: boolean = false) {
if (!this.destroyed) {
if (updateWidgetParams) {
this.dashboardWidget.updateWidgetParams();
}
2020-02-14 16:46:02 +02:00
try {
this.changeDetectorValue.detectChanges();
} catch (e) {
// console.log(e);
}
}
}
detectContainerChanges() {
if (!this.destroyed) {
try {
this.containerChangeDetectorValue.detectChanges();
} catch (e) {
// console.log(e);
}
}
}
updateWidgetParams() {
if (!this.destroyed) {
setTimeout(() => {
this.dashboardWidget.updateWidgetParams();
}, 0);
}
}
updateAliases(aliasIds?: Array<string>) {
this.aliasController.updateAliases(aliasIds);
}
reset() {
this.destroyed = false;
this.hideTitlePanel = false;
this.widgetTitle = undefined;
this.widgetActions = undefined;
}
2020-06-05 15:36:28 +03:00
closeDialog(resultData: any = null) {
const dialogRef = this.$scope.dialogRef || this.stateController.dashboardCtrl.dashboardCtx.getDashboard().dialogRef;
if (dialogRef) {
dialogRef.close(resultData);
}
}
2020-06-05 15:36:28 +03:00
pageLink(pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null): PageLink {
return new PageLink(pageSize, page, textSearch, sortOrder);
2020-08-14 13:08:17 +03:00
}
timePageLink(startTime: number, endTime: number, pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null) {
return new TimePageLink(pageSize, page, textSearch, sortOrder, startTime, endTime);
}
alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, fetchOriginator: boolean) {
return new AlarmQuery(entityId, pageLink, searchStatus, status, fetchOriginator);
}
2019-08-14 19:55:24 +03:00
}
2019-09-06 20:17:45 +03:00
export interface IDynamicWidgetComponent {
readonly ctx: WidgetContext;
readonly errorMessages: string[];
2020-02-13 18:38:11 +02:00
readonly $injector: Injector;
2019-09-10 15:12:10 +03:00
executingRpcRequest: boolean;
rpcEnabled: boolean;
rpcErrorText: string;
rpcRejection: HttpErrorResponse;
raf: RafService;
2019-09-06 20:17:45 +03:00
[key: string]: any;
}
export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor {
widgetName: string;
alias: string;
2019-09-25 19:37:29 +03:00
typeSettingsSchema?: string | any;
typeDataKeySettingsSchema?: string | any;
typeLatestDataKeySettingsSchema?: string | any;
2021-03-05 17:08:46 +02:00
image?: string;
description?: string;
2019-09-06 20:17:45 +03:00
componentFactory?: ComponentFactory<IDynamicWidgetComponent>;
}
2019-09-25 19:37:29 +03:00
export interface WidgetConfigComponentData {
config: WidgetConfig;
layout: WidgetLayout;
widgetType: widgetType;
2019-10-21 19:57:18 +03:00
typeParameters: WidgetTypeParameters;
2019-10-24 19:52:19 +03:00
actionSources: {[actionSourceId: string]: WidgetActionSource};
2019-10-21 19:57:18 +03:00
isDataEnabled: boolean;
2020-02-24 17:16:02 +02:00
settingsSchema: JsonSettingsSchema;
dataKeySettingsSchema: JsonSettingsSchema;
latestDataKeySettingsSchema: JsonSettingsSchema;
settingsDirective: string;
dataKeySettingsDirective: string;
2022-03-30 11:14:54 +03:00
latestDataKeySettingsDirective: string;
2019-09-25 19:37:29 +03:00
}
2019-09-06 20:17:45 +03:00
export const MissingWidgetType: WidgetInfo = {
type: widgetType.latest,
widgetName: 'Widget type not found',
alias: 'undefined',
sizeX: 8,
sizeY: 6,
resources: [],
templateHtml: '<div class="tb-widget-error-container">' +
'<div class="tb-widget-error-msg" innerHTML="{{\'widget.widget-type-not-found\' | translate }}"></div>' +
2019-09-06 20:17:45 +03:00
'</div>',
templateCss: '',
controllerScript: 'self.onInit = function() {}',
settingsSchema: '{}\n',
dataKeySettingsSchema: '{}\n',
image: null,
description: null,
2019-09-06 20:17:45 +03:00
defaultConfig: '{\n' +
'"title": "Widget type not found",\n' +
'"datasources": [],\n' +
'"settings": {}\n' +
2019-09-10 15:12:10 +03:00
'}\n',
typeParameters: {}
2019-09-06 20:17:45 +03:00
};
export const ErrorWidgetType: WidgetInfo = {
type: widgetType.latest,
widgetName: 'Error loading widget',
alias: 'error',
sizeX: 8,
sizeY: 6,
resources: [],
templateHtml: '<div class="tb-widget-error-container">' +
'<div translate class="tb-widget-error-msg">widget.widget-type-load-error</div>' +
'<div *ngFor="let error of errorMessages" class="tb-widget-error-msg">{{ error }}</div>' +
'</div>',
templateCss: '',
controllerScript: 'self.onInit = function() {}',
settingsSchema: '{}\n',
dataKeySettingsSchema: '{}\n',
image: null,
description: null,
2019-09-06 20:17:45 +03:00
defaultConfig: '{\n' +
'"title": "Widget failed to load",\n' +
'"datasources": [],\n' +
'"settings": {}\n' +
2019-09-10 15:12:10 +03:00
'}\n',
typeParameters: {}
2019-09-06 20:17:45 +03:00
};
export interface WidgetTypeInstance {
getSettingsSchema?: () => string;
getDataKeySettingsSchema?: () => string;
getLatestDataKeySettingsSchema?: () => string;
2019-09-06 20:17:45 +03:00
typeParameters?: () => WidgetTypeParameters;
useCustomDatasources?: () => boolean;
2019-10-24 19:52:19 +03:00
actionSources?: () => {[actionSourceId: string]: WidgetActionSource};
2019-09-06 20:17:45 +03:00
onInit?: () => void;
onDataUpdated?: () => void;
onLatestDataUpdated?: () => void;
2019-09-06 20:17:45 +03:00
onResize?: () => void;
onEditModeChanged?: () => void;
onMobileModeChanged?: () => void;
onDestroy?: () => void;
}
2021-03-05 17:08:46 +02:00
export function detailsToWidgetInfo(widgetTypeDetailsEntity: WidgetTypeDetails): WidgetInfo {
const widgetInfo = toWidgetInfo(widgetTypeDetailsEntity);
widgetInfo.image = widgetTypeDetailsEntity.image;
widgetInfo.description = widgetTypeDetailsEntity.description;
return widgetInfo;
}
2019-09-06 20:17:45 +03:00
export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {
return {
widgetName: widgetTypeEntity.name,
alias: widgetTypeEntity.alias,
type: widgetTypeEntity.descriptor.type,
sizeX: widgetTypeEntity.descriptor.sizeX,
sizeY: widgetTypeEntity.descriptor.sizeY,
resources: widgetTypeEntity.descriptor.resources,
templateHtml: widgetTypeEntity.descriptor.templateHtml,
templateCss: widgetTypeEntity.descriptor.templateCss,
controllerScript: widgetTypeEntity.descriptor.controllerScript,
settingsSchema: widgetTypeEntity.descriptor.settingsSchema,
dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema,
latestDataKeySettingsSchema: widgetTypeEntity.descriptor.latestDataKeySettingsSchema,
settingsDirective: widgetTypeEntity.descriptor.settingsDirective,
dataKeySettingsDirective: widgetTypeEntity.descriptor.dataKeySettingsDirective,
2022-03-30 11:14:54 +03:00
latestDataKeySettingsDirective: widgetTypeEntity.descriptor.latestDataKeySettingsDirective,
2019-09-06 20:17:45 +03:00
defaultConfig: widgetTypeEntity.descriptor.defaultConfig
};
}
2021-03-05 17:08:46 +02:00
export function toWidgetTypeDetails(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
bundleAlias: string, createdTime: number): WidgetTypeDetails {
const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, bundleAlias, createdTime);
const widgetTypeDetails: WidgetTypeDetails = {...widgetTypeEntity,
description: widgetInfo.description,
image: widgetInfo.image
};
return widgetTypeDetails;
}
export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
bundleAlias: string, createdTime: number): WidgetType {
const descriptor: WidgetTypeDescriptor = {
type: widgetInfo.type,
sizeX: widgetInfo.sizeX,
sizeY: widgetInfo.sizeY,
resources: widgetInfo.resources,
templateHtml: widgetInfo.templateHtml,
templateCss: widgetInfo.templateCss,
controllerScript: widgetInfo.controllerScript,
settingsSchema: widgetInfo.settingsSchema,
dataKeySettingsSchema: widgetInfo.dataKeySettingsSchema,
latestDataKeySettingsSchema: widgetInfo.latestDataKeySettingsSchema,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,
2022-03-30 11:14:54 +03:00
latestDataKeySettingsDirective: widgetInfo.latestDataKeySettingsDirective,
defaultConfig: widgetInfo.defaultConfig
};
return {
id,
tenantId,
createdTime,
bundleAlias,
alias: widgetInfo.alias,
name: widgetInfo.widgetName,
descriptor
};
}
export function updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
entityName?: string, entityLabel?: string) {
if (targetEntityId) {
let targetEntityParams: StateParams;
if (targetEntityParamName && targetEntityParamName.length) {
targetEntityParams = params[targetEntityParamName];
if (!targetEntityParams) {
targetEntityParams = {};
params[targetEntityParamName] = targetEntityParams;
params.targetEntityParamName = targetEntityParamName;
}
} else {
targetEntityParams = params;
}
targetEntityParams.entityId = targetEntityId;
if (entityName) {
targetEntityParams.entityName = entityName;
}
if (entityLabel) {
targetEntityParams.entityLabel = entityLabel;
}
}
}