Browse Source

Merge remote-tracking branch 'origin/master'

pull/3104/head
Andrii Shvaika 6 years ago
parent
commit
43ed086023
  1. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  2. 4
      ui-ngx/package-lock.json
  3. 4
      ui-ngx/proxy.conf.js
  4. 19
      ui-ngx/src/app/core/api/widget-subscription.ts
  5. 2
      ui-ngx/src/app/core/http/rule-chain.service.ts
  6. 85
      ui-ngx/src/app/core/services/resources.service.ts
  7. 15
      ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts
  8. 15
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts
  9. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts
  10. 16
      ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts
  11. 115
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  12. 10
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  13. 4
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
  14. 1
      ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts
  15. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  16. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

5
dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java

@ -72,6 +72,7 @@ public class EntityKeyMapping {
private static final String PHONE = "phone"; private static final String PHONE = "phone";
public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME);
public static final List<String> dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE);
public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL); public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL);
public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE); public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE);
@ -89,7 +90,7 @@ public class EntityKeyMapping {
allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL))); allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL)));
allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(dashboardEntityFields));
allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields));
@ -118,12 +119,12 @@ public class EntityKeyMapping {
contactBasedAliases.put(LABEL, TITLE); contactBasedAliases.put(LABEL, TITLE);
aliases.put(EntityType.TENANT, contactBasedAliases); aliases.put(EntityType.TENANT, contactBasedAliases);
aliases.put(EntityType.CUSTOMER, contactBasedAliases); aliases.put(EntityType.CUSTOMER, contactBasedAliases);
aliases.put(EntityType.DASHBOARD, contactBasedAliases);
Map<String, String> commonEntityAliases = new HashMap<>(); Map<String, String> commonEntityAliases = new HashMap<>();
commonEntityAliases.put(TITLE, NAME); commonEntityAliases.put(TITLE, NAME);
aliases.put(EntityType.DEVICE, commonEntityAliases); aliases.put(EntityType.DEVICE, commonEntityAliases);
aliases.put(EntityType.ASSET, commonEntityAliases); aliases.put(EntityType.ASSET, commonEntityAliases);
aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases); aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases);
aliases.put(EntityType.DASHBOARD, commonEntityAliases);
aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases); aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases);
Map<String, String> userEntityAliases = new HashMap<>(); Map<String, String> userEntityAliases = new HashMap<>();

4
ui-ngx/package-lock.json

@ -8997,10 +8997,10 @@
"integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw==" "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw=="
}, },
"ngx-flowchart": { "ngx-flowchart": {
"version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f", "version": "git://github.com/thingsboard/ngx-flowchart.git#7a02f4748b5e7821a883c903107af5f20415d026",
"from": "git://github.com/thingsboard/ngx-flowchart.git#master", "from": "git://github.com/thingsboard/ngx-flowchart.git#master",
"requires": { "requires": {
"tslib": "^1.10.0" "tslib": "^1.13.0"
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {

4
ui-ngx/proxy.conf.js

@ -25,6 +25,10 @@ const PROXY_CONFIG = {
"target": `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`, "target": `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`,
"secure": false, "secure": false,
}, },
"/static": {
"target": "http://localhost:8080",
"secure": false,
},
"/api/ws": { "/api/ws": {
"target": "ws://localhost:8080", "target": "ws://localhost:8080",
"ws": true, "ws": true,

19
ui-ngx/src/app/core/api/widget-subscription.ts

@ -337,6 +337,7 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
private initDataSubscription(): Observable<any> { private initDataSubscription(): Observable<any> {
this.notifyDataLoading();
const initDataSubscriptionSubject = new ReplaySubject(1); const initDataSubscriptionSubject = new ReplaySubject(1);
this.loadStDiff().subscribe(() => { this.loadStDiff().subscribe(() => {
if (!this.ctx.aliasController) { if (!this.ctx.aliasController) {
@ -431,6 +432,7 @@ export class WidgetSubscription implements IWidgetSubscription {
}); });
this.configureLoadedData(); this.configureLoadedData();
this.hasResolvedData = this.datasources.length > 0; this.hasResolvedData = this.datasources.length > 0;
this.updateDataTimewindow();
this.notifyDataLoaded(); this.notifyDataLoaded();
this.onDataUpdated(true); this.onDataUpdated(true);
}) })
@ -731,6 +733,7 @@ export class WidgetSubscription implements IWidgetSubscription {
if (this.type === widgetType.alarm) { if (this.type === widgetType.alarm) {
this.updateAlarmDataSubscription(); this.updateAlarmDataSubscription();
} else { } else {
this.notifyDataLoading();
this.dataSubscribe(); this.dataSubscribe();
} }
} }
@ -831,13 +834,21 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
} }
private dataSubscribe() { private updateDataTimewindow() {
if (!this.hasDataPageLink) { if (!this.hasDataPageLink) {
if (this.type === widgetType.timeseries && this.timeWindowConfig) { if (this.type === widgetType.timeseries && this.timeWindowConfig) {
this.updateRealtimeSubscription(); this.updateRealtimeSubscription();
if (this.comparisonEnabled) { if (this.comparisonEnabled) {
this.updateSubscriptionForComparison(); this.updateSubscriptionForComparison();
} }
}
}
}
private dataSubscribe() {
if (!this.hasDataPageLink) {
if (this.type === widgetType.timeseries && this.timeWindowConfig) {
this.updateDataTimewindow();
if (this.subscriptionTimewindow.fixedWindow) { if (this.subscriptionTimewindow.fixedWindow) {
this.onDataUpdated(); this.onDataUpdated();
} }
@ -1025,6 +1036,11 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
} }
private notifyDataLoading() {
this.loadingData = true;
this.callbacks.dataLoading(this);
}
private notifyDataLoaded() { private notifyDataLoaded() {
this.loadingData = false; this.loadingData = false;
this.callbacks.dataLoading(this); this.callbacks.dataLoading(this);
@ -1268,6 +1284,7 @@ export class WidgetSubscription implements IWidgetSubscription {
if (this.caulculateLegendData) { if (this.caulculateLegendData) {
this.updateLegend(index, data.data, detectChanges); this.updateLegend(index, data.data, detectChanges);
} }
this.notifyDataLoaded();
this.onDataUpdated(detectChanges); this.onDataUpdated(detectChanges);
} }
} }

2
ui-ngx/src/app/core/http/rule-chain.service.ts

@ -239,7 +239,7 @@ export class RuleChainService {
}); });
} }
if (moduleResource) { if (moduleResource) {
tasks.push(this.resourcesService.loadModule(moduleResource, ruleNodeConfigResourcesModulesMap).pipe( tasks.push(this.resourcesService.loadFactories(moduleResource, ruleNodeConfigResourcesModulesMap).pipe(
map((res) => { map((res) => {
if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) { if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) {
const selector = snakeCase(nodeDefinition.configDirective, '-'); const selector = snakeCase(nodeDefinition.configDirective, '-');

85
ui-ngx/src/app/core/services/resources.service.ts

@ -25,6 +25,8 @@ import {
} from '@angular/core'; } from '@angular/core';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs'; import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { objToBase64 } from '@core/utils';
declare const SystemJS; declare const SystemJS;
@ -34,12 +36,14 @@ declare const SystemJS;
export class ResourcesService { export class ResourcesService {
private loadedResources: { [url: string]: ReplaySubject<any> } = {}; private loadedResources: { [url: string]: ReplaySubject<any> } = {};
private loadedModules: { [url: string]: ReplaySubject<ComponentFactory<any>[]> } = {}; private loadedModules: { [url: string]: ReplaySubject<Type<any>[]> } = {};
private loadedFactories: { [url: string]: ReplaySubject<ComponentFactory<any>[]> } = {};
private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0];
constructor(@Inject(DOCUMENT) private readonly document: any, constructor(@Inject(DOCUMENT) private readonly document: any,
private compiler: Compiler, private compiler: Compiler,
private http: HttpClient,
private injector: Injector) {} private injector: Injector) {}
public loadResource(url: string): Observable<any> { public loadResource(url: string): Observable<any> {
@ -60,12 +64,12 @@ export class ResourcesService {
return this.loadResourceByType(fileType, url); return this.loadResourceByType(fileType, url);
} }
public loadModule(url: string, modulesMap: {[key: string]: any}): Observable<ComponentFactory<any>[]> { public loadFactories(url: string, modulesMap: {[key: string]: any}): Observable<ComponentFactory<any>[]> {
if (this.loadedModules[url]) { if (this.loadedFactories[url]) {
return this.loadedModules[url].asObservable(); return this.loadedFactories[url].asObservable();
} }
const subject = new ReplaySubject<ComponentFactory<any>[]>(); const subject = new ReplaySubject<ComponentFactory<any>[]>();
this.loadedModules[url] = subject; this.loadedFactories[url] = subject;
if (modulesMap) { if (modulesMap) {
for (const moduleId of Object.keys(modulesMap)) { for (const moduleId of Object.keys(modulesMap)) {
SystemJS.set(moduleId, modulesMap[moduleId]); SystemJS.set(moduleId, modulesMap[moduleId]);
@ -86,19 +90,76 @@ export class ResourcesService {
c.ngModuleFactory.create(this.injector); c.ngModuleFactory.create(this.injector);
componentFactories.push(...c.componentFactories); componentFactories.push(...c.componentFactories);
} }
this.loadedModules[url].next(componentFactories); this.loadedFactories[url].next(componentFactories);
this.loadedModules[url].complete(); this.loadedFactories[url].complete();
} catch (e) { } catch (e) {
this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); this.loadedFactories[url].error(new Error(`Unable to init module from url: ${url}`));
delete this.loadedModules[url]; delete this.loadedFactories[url];
} }
}, },
(e) => { (e) => {
this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); this.loadedFactories[url].error(new Error(`Unable to compile module from url: ${url}`));
delete this.loadedModules[url]; delete this.loadedFactories[url];
}); });
} else { } else {
this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export!`)); this.loadedFactories[url].error(new Error(`Module '${url}' doesn't have default export!`));
delete this.loadedFactories[url];
}
},
(e) => {
this.loadedFactories[url].error(new Error(`Unable to load module from url: ${url}`));
delete this.loadedFactories[url];
}
);
return subject.asObservable();
}
public loadModules(url: string, modulesMap: {[key: string]: any}): Observable<Type<any>[]> {
if (this.loadedModules[url]) {
return this.loadedModules[url].asObservable();
}
const subject = new ReplaySubject<Type<any>[]>();
this.loadedModules[url] = subject;
if (modulesMap) {
for (const moduleId of Object.keys(modulesMap)) {
SystemJS.set(moduleId, modulesMap[moduleId]);
}
}
SystemJS.import(url).then(
(module) => {
try {
let modules;
try {
modules = this.extractNgModules(module);
} catch (e) {
}
if (modules && modules.length) {
const tasks: Promise<ModuleWithComponentFactories<any>>[] = [];
for (const m of modules) {
tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m));
}
forkJoin(tasks).subscribe((compiled) => {
try {
for (const c of compiled) {
c.ngModuleFactory.create(this.injector);
}
this.loadedModules[url].next(modules);
this.loadedModules[url].complete();
} catch (e) {
this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`));
delete this.loadedModules[url];
}
},
(e) => {
this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`));
delete this.loadedModules[url];
});
} else {
this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export or not NgModule!`));
delete this.loadedModules[url];
}
} catch (e) {
this.loadedModules[url].error(new Error(`Unable to load module from url: ${url}`));
delete this.loadedModules[url]; delete this.loadedModules[url];
} }
}, },

15
ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts

@ -330,7 +330,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
this.defaultPageSize = pageSize; this.defaultPageSize = pageSize;
} }
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024;
this.pageLink.searchPropagatedAlarms = isDefined(this.widgetConfig.searchPropagatedAlarms) this.pageLink.searchPropagatedAlarms = isDefined(this.widgetConfig.searchPropagatedAlarms)
? this.widgetConfig.searchPropagatedAlarms : true; ? this.widgetConfig.searchPropagatedAlarms : true;
@ -535,10 +535,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
} else { } else {
this.pageLink.page = 0; this.pageLink.page = 0;
} }
this.pageLink.sortOrder = { const key = findEntityKeyByColumnDef(this.sort.active, this.columns);
key: findEntityKeyByColumnDef(this.sort.active, this.columns), if (key) {
direction: Direction[this.sort.direction.toUpperCase()] this.pageLink.sortOrder = {
}; key,
direction: Direction[this.sort.direction.toUpperCase()]
};
} else {
this.pageLink.sortOrder = null;
}
const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns); const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns);
const keyFilters: KeyFilter[] = null; // TODO: const keyFilters: KeyFilter[] = null; // TODO:
this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters); this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters);

15
ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts

@ -240,7 +240,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
this.defaultPageSize = pageSize; this.defaultPageSize = pageSize;
} }
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024;
const cssString = constructTableCssString(this.widgetConfig); const cssString = constructTableCssString(this.widgetConfig);
const cssParser = new cssjs(); const cssParser = new cssjs();
@ -460,10 +460,15 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
} else { } else {
this.pageLink.page = 0; this.pageLink.page = 0;
} }
this.pageLink.sortOrder = { const key = findEntityKeyByColumnDef(this.sort.active, this.columns);
key: findEntityKeyByColumnDef(this.sort.active, this.columns), if (key) {
direction: Direction[this.sort.direction.toUpperCase()] this.pageLink.sortOrder = {
}; key,
direction: Direction[this.sort.direction.toUpperCase()]
};
} else {
this.pageLink.sortOrder = null;
}
const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns); const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns);
const keyFilters: KeyFilter[] = null; // TODO: const keyFilters: KeyFilter[] = null; // TODO:
this.entityDatasource.loadEntities(this.pageLink, sortOrderLabel, keyFilters); this.entityDatasource.loadEntities(this.pageLink, sortOrderLabel, keyFilters);

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts

@ -159,7 +159,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
} }
template = createLabelFromDatasource(data.$datasource, template); template = createLabelFromDatasource(data.$datasource, template);
let match = varsRegex.exec(template); let match = /\${([^}]*)}/g.exec(template);
while (match !== null) { while (match !== null) {
const variable = match[0]; const variable = match[0];
let label = match[1]; let label = match[1];
@ -186,7 +186,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
textValue = value; textValue = value;
} }
template = template.split(variable).join(textValue); template = template.split(variable).join(textValue);
match = varsRegex.exec(template); match = /\${([^}]*)}/g.exec(template);
} }
let actionTags: string; let actionTags: string;

16
ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts

@ -96,7 +96,10 @@ export function entityDataSortOrderFromString(strSortOrder: string, columns: Ent
if (!property && !property.length) { if (!property && !property.length) {
return null; return null;
} }
const column = findColumnByLabel(property, columns); let column = findColumnByLabel(property, columns);
if (!column) {
column = findColumnByName(property, columns);
}
if (column && column.entityKey) { if (column && column.entityKey) {
return {key: column.entityKey, direction}; return {key: column.entityKey, direction};
} }
@ -113,13 +116,22 @@ export function findColumnByEntityKey(key: EntityKey, columns: EntityColumn[]):
} }
export function findEntityKeyByColumnDef(def: string, columns: EntityColumn[]): EntityKey { export function findEntityKeyByColumnDef(def: string, columns: EntityColumn[]): EntityKey {
return findColumnByDef(def, columns).entityKey; if (def) {
const column = findColumnByDef(def, columns);
return column ? column.entityKey : null;
} else {
return null;
}
} }
export function findColumn(searchProperty: string, searchValue: string, columns: EntityColumn[]): EntityColumn { export function findColumn(searchProperty: string, searchValue: string, columns: EntityColumn[]): EntityColumn {
return columns.find(theColumn => theColumn[searchProperty] === searchValue); return columns.find(theColumn => theColumn[searchProperty] === searchValue);
} }
export function findColumnByName(name: string, columns: EntityColumn[]): EntityColumn {
return findColumn('name', name, columns);
}
export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn { export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn {
let column: EntityColumn; let column: EntityColumn;
const alarmColumns = columns.filter(c => c.type === DataKeyType.alarm); const alarmColumns = columns.filter(c => c.type === DataKeyType.alarm);

115
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -41,6 +41,43 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetTypeId } from '@app/shared/models/id/widget-type-id'; import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
import { TenantId } from '@app/shared/models/id/tenant-id'; import { TenantId } from '@app/shared/models/id/tenant-id';
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
import * as AngularCore from '@angular/core';
import * as AngularCommon from '@angular/common';
import * as AngularForms from '@angular/forms';
import * as AngularRouter from '@angular/router';
import * as AngularCdkKeycodes from '@angular/cdk/keycodes';
import * as AngularCdkCoercion from '@angular/cdk/coercion';
import * as AngularMaterialChips from '@angular/material/chips';
import * as AngularMaterialAutocomplete from '@angular/material/autocomplete';
import * as AngularMaterialDialog from '@angular/material/dialog';
import * as NgrxStore from '@ngrx/store';
import * as RxJs from 'rxjs';
import * as RxJsOperators from 'rxjs/operators';
import * as TranslateCore from '@ngx-translate/core';
import * as TbCore from '@core/public-api';
import * as TbShared from '@shared/public-api';
import * as _moment from 'moment';
declare const SystemJS;
const widgetResourcesModulesMap = {
'@angular/core': SystemJS.newModule(AngularCore),
'@angular/common': SystemJS.newModule(AngularCommon),
'@angular/forms': SystemJS.newModule(AngularForms),
'@angular/router': SystemJS.newModule(AngularRouter),
'@angular/cdk/keycodes': SystemJS.newModule(AngularCdkKeycodes),
'@angular/cdk/coercion': SystemJS.newModule(AngularCdkCoercion),
'@angular/material/chips': SystemJS.newModule(AngularMaterialChips),
'@angular/material/autocomplete': SystemJS.newModule(AngularMaterialAutocomplete),
'@angular/material/dialog': SystemJS.newModule(AngularMaterialDialog),
'@ngrx/store': SystemJS.newModule(NgrxStore),
rxjs: SystemJS.newModule(RxJs),
'rxjs/operators': SystemJS.newModule(RxJsOperators),
'@ngx-translate/core': SystemJS.newModule(TranslateCore),
'@core/public-api': SystemJS.newModule(TbCore),
'@shared/public-api': SystemJS.newModule(TbShared),
moment: SystemJS.newModule(_moment)
};
// @dynamic // @dynamic
@Injectable() @Injectable()
@ -105,8 +142,8 @@ export class WidgetComponentService {
const initSubject = new ReplaySubject(); const initSubject = new ReplaySubject();
this.init$ = initSubject.asObservable(); this.init$ = initSubject.asObservable();
const loadDefaultWidgetInfoTasks = [ const loadDefaultWidgetInfoTasks = [
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule]), this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule]), this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
]; ];
forkJoin(loadDefaultWidgetInfoTasks).subscribe( forkJoin(loadDefaultWidgetInfoTasks).subscribe(
() => { () => {
@ -218,31 +255,71 @@ export class WidgetComponentService {
this.cssParser.cssPreviewNamespace = widgetNamespace; this.cssParser.cssPreviewNamespace = widgetNamespace;
this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss);
const resourceTasks: Observable<string>[] = []; const resourceTasks: Observable<string>[] = [];
const modulesTasks: Observable<Type<any>[] | string>[] = [];
if (widgetInfo.resources.length > 0) { if (widgetInfo.resources.length > 0) {
widgetInfo.resources.forEach((resource) => { widgetInfo.resources.filter(r => r.isModule).forEach(
(resource) => {
modulesTasks.push(
this.resources.loadModules(resource.url, widgetResourcesModulesMap).pipe(
catchError((e: Error) => of(e?.message ? e.message : `Failed to load widget resource module: '${resource.url}'`))
)
);
}
);
}
widgetInfo.resources.filter(r => !r.isModule).forEach(
(resource) => {
resourceTasks.push( resourceTasks.push(
this.resources.loadResource(resource.url).pipe( this.resources.loadResource(resource.url).pipe(
catchError(e => of(`Failed to load widget resource: '${resource.url}'`)) catchError(e => of(`Failed to load widget resource: '${resource.url}'`))
) )
); );
}); }
);
let modulesObservable: Observable<string | Type<any>[]>;
if (modulesTasks.length) {
modulesObservable = forkJoin(modulesTasks).pipe(
map(res => {
const msg = res.find(r => typeof r === 'string');
if (msg) {
return msg as string;
} else {
let resModules = (res as Type<any>[][]).flat();
if (modules && modules.length) {
resModules = resModules.concat(modules);
}
return resModules;
}
})
);
} else {
modulesObservable = modules && modules.length ? of(modules) : of([]);
} }
resourceTasks.push( resourceTasks.push(
this.dynamicComponentFactoryService.createDynamicComponentFactory( modulesObservable.pipe(
class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, mergeMap((resolvedModules) => {
widgetInfo.templateHtml, if (typeof resolvedModules === 'string') {
modules return of(resolvedModules);
).pipe( } else {
map((factory) => { return this.dynamicComponentFactoryService.createDynamicComponentFactory(
widgetInfo.componentFactory = factory; class DynamicWidgetComponentInstance extends DynamicWidgetComponent {},
return null; widgetInfo.templateHtml,
}), resolvedModules
catchError(e => { ).pipe(
const details = this.utils.parseException(e); map((factory) => {
const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`; widgetInfo.componentFactory = factory;
return of(errorMessage); return null;
}) }),
) catchError(e => {
const details = this.utils.parseException(e);
const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`;
return of(errorMessage);
})
)
}
}))
); );
return forkJoin(resourceTasks).pipe( return forkJoin(resourceTasks).pipe(
switchMap(msgs => { switchMap(msgs => {

10
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -794,16 +794,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
options.useDashboardTimewindow = true; options.useDashboardTimewindow = true;
} }
} }
let datasource: Datasource;
if (options.type === widgetType.alarm) { if (options.type === widgetType.alarm) {
datasource = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]); options.alarmSource = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]);
} else { } else {
datasource = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo); options.datasources = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo);
}
if (options.type === widgetType.alarm) {
options.alarmSource = datasource;
} else {
options.datasources = [datasource];
} }
this.createSubscription(options, subscribe).subscribe( this.createSubscription(options, subscribe).subscribe(
(subscription) => { (subscription) => {

4
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html

@ -129,6 +129,10 @@
(ngModelChange)="isDirty = true" (ngModelChange)="isDirty = true"
placeholder="{{ 'widget.resource-url' | translate }}"/> placeholder="{{ 'widget.resource-url' | translate }}"/>
</mat-form-field> </mat-form-field>
<mat-checkbox [(ngModel)]="resource.isModule"
(ngModelChange)="isDirty = true">
{{ 'widget.resource-is-module' | translate }}
</mat-checkbox>
<button mat-icon-button color="primary" <button mat-icon-button color="primary"
[disabled]="isLoading$ | async" [disabled]="isLoading$ | async"
(click)="removeResource(i)" (click)="removeResource(i)"

1
ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts

@ -53,6 +53,7 @@ export interface NodeScriptTestDialogData {
msgType?: string; msgType?: string;
} }
// @dynamic
@Component({ @Component({
selector: 'tb-node-script-test-dialog', selector: 'tb-node-script-test-dialog',
templateUrl: './node-script-test-dialog.component.html', templateUrl: './node-script-test-dialog.component.html',

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -114,6 +114,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
export interface WidgetResource { export interface WidgetResource {
url: string; url: string;
isModule?: boolean;
} }
export interface WidgetActionSource { export interface WidgetActionSource {

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1787,6 +1787,7 @@
"type": "Widget type", "type": "Widget type",
"resources": "Resources", "resources": "Resources",
"resource-url": "JavaScript/CSS URL", "resource-url": "JavaScript/CSS URL",
"resource-is-module": "Is module",
"remove-resource": "Remove resource", "remove-resource": "Remove resource",
"add-resource": "Add resource", "add-resource": "Add resource",
"html": "HTML", "html": "HTML",

Loading…
Cancel
Save