Commit 43ed086023dbabd9a0b299e542f471614377b770

Authored by Andrii Shvaika
2 parents bd9ec316 349909be

Merge remote-tracking branch 'origin/master'

@@ -72,6 +72,7 @@ public class EntityKeyMapping { @@ -72,6 +72,7 @@ public class EntityKeyMapping {
72 private static final String PHONE = "phone"; 72 private static final String PHONE = "phone";
73 73
74 public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); 74 public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME);
  75 + public static final List<String> dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE);
75 public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL); 76 public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL);
76 public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE); 77 public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE);
77 78
@@ -89,7 +90,7 @@ public class EntityKeyMapping { @@ -89,7 +90,7 @@ public class EntityKeyMapping {
89 90
90 allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL))); 91 allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL)));
91 92
92 - allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(commonEntityFields)); 93 + allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(dashboardEntityFields));
93 allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); 94 allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields));
94 allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); 95 allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields));
95 allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields)); 96 allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields));
@@ -118,12 +119,12 @@ public class EntityKeyMapping { @@ -118,12 +119,12 @@ public class EntityKeyMapping {
118 contactBasedAliases.put(LABEL, TITLE); 119 contactBasedAliases.put(LABEL, TITLE);
119 aliases.put(EntityType.TENANT, contactBasedAliases); 120 aliases.put(EntityType.TENANT, contactBasedAliases);
120 aliases.put(EntityType.CUSTOMER, contactBasedAliases); 121 aliases.put(EntityType.CUSTOMER, contactBasedAliases);
  122 + aliases.put(EntityType.DASHBOARD, contactBasedAliases);
121 Map<String, String> commonEntityAliases = new HashMap<>(); 123 Map<String, String> commonEntityAliases = new HashMap<>();
122 commonEntityAliases.put(TITLE, NAME); 124 commonEntityAliases.put(TITLE, NAME);
123 aliases.put(EntityType.DEVICE, commonEntityAliases); 125 aliases.put(EntityType.DEVICE, commonEntityAliases);
124 aliases.put(EntityType.ASSET, commonEntityAliases); 126 aliases.put(EntityType.ASSET, commonEntityAliases);
125 aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases); 127 aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases);
126 - aliases.put(EntityType.DASHBOARD, commonEntityAliases);  
127 aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases); 128 aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases);
128 129
129 Map<String, String> userEntityAliases = new HashMap<>(); 130 Map<String, String> userEntityAliases = new HashMap<>();
@@ -8997,10 +8997,10 @@ @@ -8997,10 +8997,10 @@
8997 "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw==" 8997 "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw=="
8998 }, 8998 },
8999 "ngx-flowchart": { 8999 "ngx-flowchart": {
9000 - "version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f", 9000 + "version": "git://github.com/thingsboard/ngx-flowchart.git#7a02f4748b5e7821a883c903107af5f20415d026",
9001 "from": "git://github.com/thingsboard/ngx-flowchart.git#master", 9001 "from": "git://github.com/thingsboard/ngx-flowchart.git#master",
9002 "requires": { 9002 "requires": {
9003 - "tslib": "^1.10.0" 9003 + "tslib": "^1.13.0"
9004 }, 9004 },
9005 "dependencies": { 9005 "dependencies": {
9006 "tslib": { 9006 "tslib": {
@@ -25,6 +25,10 @@ const PROXY_CONFIG = { @@ -25,6 +25,10 @@ const PROXY_CONFIG = {
25 "target": `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`, 25 "target": `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`,
26 "secure": false, 26 "secure": false,
27 }, 27 },
  28 + "/static": {
  29 + "target": "http://localhost:8080",
  30 + "secure": false,
  31 + },
28 "/api/ws": { 32 "/api/ws": {
29 "target": "ws://localhost:8080", 33 "target": "ws://localhost:8080",
30 "ws": true, 34 "ws": true,
@@ -337,6 +337,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -337,6 +337,7 @@ export class WidgetSubscription implements IWidgetSubscription {
337 } 337 }
338 338
339 private initDataSubscription(): Observable<any> { 339 private initDataSubscription(): Observable<any> {
  340 + this.notifyDataLoading();
340 const initDataSubscriptionSubject = new ReplaySubject(1); 341 const initDataSubscriptionSubject = new ReplaySubject(1);
341 this.loadStDiff().subscribe(() => { 342 this.loadStDiff().subscribe(() => {
342 if (!this.ctx.aliasController) { 343 if (!this.ctx.aliasController) {
@@ -431,6 +432,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -431,6 +432,7 @@ export class WidgetSubscription implements IWidgetSubscription {
431 }); 432 });
432 this.configureLoadedData(); 433 this.configureLoadedData();
433 this.hasResolvedData = this.datasources.length > 0; 434 this.hasResolvedData = this.datasources.length > 0;
  435 + this.updateDataTimewindow();
434 this.notifyDataLoaded(); 436 this.notifyDataLoaded();
435 this.onDataUpdated(true); 437 this.onDataUpdated(true);
436 }) 438 })
@@ -731,6 +733,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -731,6 +733,7 @@ export class WidgetSubscription implements IWidgetSubscription {
731 if (this.type === widgetType.alarm) { 733 if (this.type === widgetType.alarm) {
732 this.updateAlarmDataSubscription(); 734 this.updateAlarmDataSubscription();
733 } else { 735 } else {
  736 + this.notifyDataLoading();
734 this.dataSubscribe(); 737 this.dataSubscribe();
735 } 738 }
736 } 739 }
@@ -831,13 +834,21 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -831,13 +834,21 @@ export class WidgetSubscription implements IWidgetSubscription {
831 } 834 }
832 } 835 }
833 836
834 - private dataSubscribe() { 837 + private updateDataTimewindow() {
835 if (!this.hasDataPageLink) { 838 if (!this.hasDataPageLink) {
836 if (this.type === widgetType.timeseries && this.timeWindowConfig) { 839 if (this.type === widgetType.timeseries && this.timeWindowConfig) {
837 this.updateRealtimeSubscription(); 840 this.updateRealtimeSubscription();
838 if (this.comparisonEnabled) { 841 if (this.comparisonEnabled) {
839 this.updateSubscriptionForComparison(); 842 this.updateSubscriptionForComparison();
840 } 843 }
  844 + }
  845 + }
  846 + }
  847 +
  848 + private dataSubscribe() {
  849 + if (!this.hasDataPageLink) {
  850 + if (this.type === widgetType.timeseries && this.timeWindowConfig) {
  851 + this.updateDataTimewindow();
841 if (this.subscriptionTimewindow.fixedWindow) { 852 if (this.subscriptionTimewindow.fixedWindow) {
842 this.onDataUpdated(); 853 this.onDataUpdated();
843 } 854 }
@@ -1025,6 +1036,11 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1025,6 +1036,11 @@ export class WidgetSubscription implements IWidgetSubscription {
1025 } 1036 }
1026 } 1037 }
1027 1038
  1039 + private notifyDataLoading() {
  1040 + this.loadingData = true;
  1041 + this.callbacks.dataLoading(this);
  1042 + }
  1043 +
1028 private notifyDataLoaded() { 1044 private notifyDataLoaded() {
1029 this.loadingData = false; 1045 this.loadingData = false;
1030 this.callbacks.dataLoading(this); 1046 this.callbacks.dataLoading(this);
@@ -1268,6 +1284,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1268,6 +1284,7 @@ export class WidgetSubscription implements IWidgetSubscription {
1268 if (this.caulculateLegendData) { 1284 if (this.caulculateLegendData) {
1269 this.updateLegend(index, data.data, detectChanges); 1285 this.updateLegend(index, data.data, detectChanges);
1270 } 1286 }
  1287 + this.notifyDataLoaded();
1271 this.onDataUpdated(detectChanges); 1288 this.onDataUpdated(detectChanges);
1272 } 1289 }
1273 } 1290 }
@@ -239,7 +239,7 @@ export class RuleChainService { @@ -239,7 +239,7 @@ export class RuleChainService {
239 }); 239 });
240 } 240 }
241 if (moduleResource) { 241 if (moduleResource) {
242 - tasks.push(this.resourcesService.loadModule(moduleResource, ruleNodeConfigResourcesModulesMap).pipe( 242 + tasks.push(this.resourcesService.loadFactories(moduleResource, ruleNodeConfigResourcesModulesMap).pipe(
243 map((res) => { 243 map((res) => {
244 if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) { 244 if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) {
245 const selector = snakeCase(nodeDefinition.configDirective, '-'); 245 const selector = snakeCase(nodeDefinition.configDirective, '-');
@@ -25,6 +25,8 @@ import { @@ -25,6 +25,8 @@ import {
25 } from '@angular/core'; 25 } from '@angular/core';
26 import { DOCUMENT } from '@angular/common'; 26 import { DOCUMENT } from '@angular/common';
27 import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs'; 27 import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs';
  28 +import { HttpClient } from '@angular/common/http';
  29 +import { objToBase64 } from '@core/utils';
28 30
29 declare const SystemJS; 31 declare const SystemJS;
30 32
@@ -34,12 +36,14 @@ declare const SystemJS; @@ -34,12 +36,14 @@ declare const SystemJS;
34 export class ResourcesService { 36 export class ResourcesService {
35 37
36 private loadedResources: { [url: string]: ReplaySubject<any> } = {}; 38 private loadedResources: { [url: string]: ReplaySubject<any> } = {};
37 - private loadedModules: { [url: string]: ReplaySubject<ComponentFactory<any>[]> } = {}; 39 + private loadedModules: { [url: string]: ReplaySubject<Type<any>[]> } = {};
  40 + private loadedFactories: { [url: string]: ReplaySubject<ComponentFactory<any>[]> } = {};
38 41
39 private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; 42 private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0];
40 43
41 constructor(@Inject(DOCUMENT) private readonly document: any, 44 constructor(@Inject(DOCUMENT) private readonly document: any,
42 private compiler: Compiler, 45 private compiler: Compiler,
  46 + private http: HttpClient,
43 private injector: Injector) {} 47 private injector: Injector) {}
44 48
45 public loadResource(url: string): Observable<any> { 49 public loadResource(url: string): Observable<any> {
@@ -60,12 +64,12 @@ export class ResourcesService { @@ -60,12 +64,12 @@ export class ResourcesService {
60 return this.loadResourceByType(fileType, url); 64 return this.loadResourceByType(fileType, url);
61 } 65 }
62 66
63 - public loadModule(url: string, modulesMap: {[key: string]: any}): Observable<ComponentFactory<any>[]> {  
64 - if (this.loadedModules[url]) {  
65 - return this.loadedModules[url].asObservable(); 67 + public loadFactories(url: string, modulesMap: {[key: string]: any}): Observable<ComponentFactory<any>[]> {
  68 + if (this.loadedFactories[url]) {
  69 + return this.loadedFactories[url].asObservable();
66 } 70 }
67 const subject = new ReplaySubject<ComponentFactory<any>[]>(); 71 const subject = new ReplaySubject<ComponentFactory<any>[]>();
68 - this.loadedModules[url] = subject; 72 + this.loadedFactories[url] = subject;
69 if (modulesMap) { 73 if (modulesMap) {
70 for (const moduleId of Object.keys(modulesMap)) { 74 for (const moduleId of Object.keys(modulesMap)) {
71 SystemJS.set(moduleId, modulesMap[moduleId]); 75 SystemJS.set(moduleId, modulesMap[moduleId]);
@@ -86,19 +90,76 @@ export class ResourcesService { @@ -86,19 +90,76 @@ export class ResourcesService {
86 c.ngModuleFactory.create(this.injector); 90 c.ngModuleFactory.create(this.injector);
87 componentFactories.push(...c.componentFactories); 91 componentFactories.push(...c.componentFactories);
88 } 92 }
89 - this.loadedModules[url].next(componentFactories);  
90 - this.loadedModules[url].complete(); 93 + this.loadedFactories[url].next(componentFactories);
  94 + this.loadedFactories[url].complete();
91 } catch (e) { 95 } catch (e) {
92 - this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`));  
93 - delete this.loadedModules[url]; 96 + this.loadedFactories[url].error(new Error(`Unable to init module from url: ${url}`));
  97 + delete this.loadedFactories[url];
94 } 98 }
95 }, 99 },
96 (e) => { 100 (e) => {
97 - this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`));  
98 - delete this.loadedModules[url]; 101 + this.loadedFactories[url].error(new Error(`Unable to compile module from url: ${url}`));
  102 + delete this.loadedFactories[url];
99 }); 103 });
100 } else { 104 } else {
101 - this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export!`)); 105 + this.loadedFactories[url].error(new Error(`Module '${url}' doesn't have default export!`));
  106 + delete this.loadedFactories[url];
  107 + }
  108 + },
  109 + (e) => {
  110 + this.loadedFactories[url].error(new Error(`Unable to load module from url: ${url}`));
  111 + delete this.loadedFactories[url];
  112 + }
  113 + );
  114 + return subject.asObservable();
  115 + }
  116 +
  117 + public loadModules(url: string, modulesMap: {[key: string]: any}): Observable<Type<any>[]> {
  118 + if (this.loadedModules[url]) {
  119 + return this.loadedModules[url].asObservable();
  120 + }
  121 + const subject = new ReplaySubject<Type<any>[]>();
  122 + this.loadedModules[url] = subject;
  123 + if (modulesMap) {
  124 + for (const moduleId of Object.keys(modulesMap)) {
  125 + SystemJS.set(moduleId, modulesMap[moduleId]);
  126 + }
  127 + }
  128 + SystemJS.import(url).then(
  129 + (module) => {
  130 + try {
  131 + let modules;
  132 + try {
  133 + modules = this.extractNgModules(module);
  134 + } catch (e) {
  135 + }
  136 + if (modules && modules.length) {
  137 + const tasks: Promise<ModuleWithComponentFactories<any>>[] = [];
  138 + for (const m of modules) {
  139 + tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m));
  140 + }
  141 + forkJoin(tasks).subscribe((compiled) => {
  142 + try {
  143 + for (const c of compiled) {
  144 + c.ngModuleFactory.create(this.injector);
  145 + }
  146 + this.loadedModules[url].next(modules);
  147 + this.loadedModules[url].complete();
  148 + } catch (e) {
  149 + this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`));
  150 + delete this.loadedModules[url];
  151 + }
  152 + },
  153 + (e) => {
  154 + this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`));
  155 + delete this.loadedModules[url];
  156 + });
  157 + } else {
  158 + this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export or not NgModule!`));
  159 + delete this.loadedModules[url];
  160 + }
  161 + } catch (e) {
  162 + this.loadedModules[url].error(new Error(`Unable to load module from url: ${url}`));
102 delete this.loadedModules[url]; 163 delete this.loadedModules[url];
103 } 164 }
104 }, 165 },
@@ -330,7 +330,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -330,7 +330,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
330 this.defaultPageSize = pageSize; 330 this.defaultPageSize = pageSize;
331 } 331 }
332 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; 332 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
333 - this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; 333 + this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024;
334 334
335 this.pageLink.searchPropagatedAlarms = isDefined(this.widgetConfig.searchPropagatedAlarms) 335 this.pageLink.searchPropagatedAlarms = isDefined(this.widgetConfig.searchPropagatedAlarms)
336 ? this.widgetConfig.searchPropagatedAlarms : true; 336 ? this.widgetConfig.searchPropagatedAlarms : true;
@@ -535,10 +535,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -535,10 +535,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
535 } else { 535 } else {
536 this.pageLink.page = 0; 536 this.pageLink.page = 0;
537 } 537 }
538 - this.pageLink.sortOrder = {  
539 - key: findEntityKeyByColumnDef(this.sort.active, this.columns),  
540 - direction: Direction[this.sort.direction.toUpperCase()]  
541 - }; 538 + const key = findEntityKeyByColumnDef(this.sort.active, this.columns);
  539 + if (key) {
  540 + this.pageLink.sortOrder = {
  541 + key,
  542 + direction: Direction[this.sort.direction.toUpperCase()]
  543 + };
  544 + } else {
  545 + this.pageLink.sortOrder = null;
  546 + }
542 const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns); 547 const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns);
543 const keyFilters: KeyFilter[] = null; // TODO: 548 const keyFilters: KeyFilter[] = null; // TODO:
544 this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters); 549 this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters);
@@ -240,7 +240,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -240,7 +240,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
240 this.defaultPageSize = pageSize; 240 this.defaultPageSize = pageSize;
241 } 241 }
242 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; 242 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
243 - this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; 243 + this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024;
244 244
245 const cssString = constructTableCssString(this.widgetConfig); 245 const cssString = constructTableCssString(this.widgetConfig);
246 const cssParser = new cssjs(); 246 const cssParser = new cssjs();
@@ -460,10 +460,15 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -460,10 +460,15 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
460 } else { 460 } else {
461 this.pageLink.page = 0; 461 this.pageLink.page = 0;
462 } 462 }
463 - this.pageLink.sortOrder = {  
464 - key: findEntityKeyByColumnDef(this.sort.active, this.columns),  
465 - direction: Direction[this.sort.direction.toUpperCase()]  
466 - }; 463 + const key = findEntityKeyByColumnDef(this.sort.active, this.columns);
  464 + if (key) {
  465 + this.pageLink.sortOrder = {
  466 + key,
  467 + direction: Direction[this.sort.direction.toUpperCase()]
  468 + };
  469 + } else {
  470 + this.pageLink.sortOrder = null;
  471 + }
467 const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns); 472 const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns);
468 const keyFilters: KeyFilter[] = null; // TODO: 473 const keyFilters: KeyFilter[] = null; // TODO:
469 this.entityDatasource.loadEntities(this.pageLink, sortOrderLabel, keyFilters); 474 this.entityDatasource.loadEntities(this.pageLink, sortOrderLabel, keyFilters);
@@ -159,7 +159,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: @@ -159,7 +159,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
159 } 159 }
160 template = createLabelFromDatasource(data.$datasource, template); 160 template = createLabelFromDatasource(data.$datasource, template);
161 161
162 - let match = varsRegex.exec(template); 162 + let match = /\${([^}]*)}/g.exec(template);
163 while (match !== null) { 163 while (match !== null) {
164 const variable = match[0]; 164 const variable = match[0];
165 let label = match[1]; 165 let label = match[1];
@@ -186,7 +186,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: @@ -186,7 +186,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
186 textValue = value; 186 textValue = value;
187 } 187 }
188 template = template.split(variable).join(textValue); 188 template = template.split(variable).join(textValue);
189 - match = varsRegex.exec(template); 189 + match = /\${([^}]*)}/g.exec(template);
190 } 190 }
191 191
192 let actionTags: string; 192 let actionTags: string;
@@ -96,7 +96,10 @@ export function entityDataSortOrderFromString(strSortOrder: string, columns: Ent @@ -96,7 +96,10 @@ export function entityDataSortOrderFromString(strSortOrder: string, columns: Ent
96 if (!property && !property.length) { 96 if (!property && !property.length) {
97 return null; 97 return null;
98 } 98 }
99 - const column = findColumnByLabel(property, columns); 99 + let column = findColumnByLabel(property, columns);
  100 + if (!column) {
  101 + column = findColumnByName(property, columns);
  102 + }
100 if (column && column.entityKey) { 103 if (column && column.entityKey) {
101 return {key: column.entityKey, direction}; 104 return {key: column.entityKey, direction};
102 } 105 }
@@ -113,13 +116,22 @@ export function findColumnByEntityKey(key: EntityKey, columns: EntityColumn[]): @@ -113,13 +116,22 @@ export function findColumnByEntityKey(key: EntityKey, columns: EntityColumn[]):
113 } 116 }
114 117
115 export function findEntityKeyByColumnDef(def: string, columns: EntityColumn[]): EntityKey { 118 export function findEntityKeyByColumnDef(def: string, columns: EntityColumn[]): EntityKey {
116 - return findColumnByDef(def, columns).entityKey; 119 + if (def) {
  120 + const column = findColumnByDef(def, columns);
  121 + return column ? column.entityKey : null;
  122 + } else {
  123 + return null;
  124 + }
117 } 125 }
118 126
119 export function findColumn(searchProperty: string, searchValue: string, columns: EntityColumn[]): EntityColumn { 127 export function findColumn(searchProperty: string, searchValue: string, columns: EntityColumn[]): EntityColumn {
120 return columns.find(theColumn => theColumn[searchProperty] === searchValue); 128 return columns.find(theColumn => theColumn[searchProperty] === searchValue);
121 } 129 }
122 130
  131 +export function findColumnByName(name: string, columns: EntityColumn[]): EntityColumn {
  132 + return findColumn('name', name, columns);
  133 +}
  134 +
123 export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn { 135 export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn {
124 let column: EntityColumn; 136 let column: EntityColumn;
125 const alarmColumns = columns.filter(c => c.type === DataKeyType.alarm); 137 const alarmColumns = columns.filter(c => c.type === DataKeyType.alarm);
@@ -41,6 +41,43 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -41,6 +41,43 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
41 import { WidgetTypeId } from '@app/shared/models/id/widget-type-id'; 41 import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
42 import { TenantId } from '@app/shared/models/id/tenant-id'; 42 import { TenantId } from '@app/shared/models/id/tenant-id';
43 import { SharedModule } from '@shared/shared.module'; 43 import { SharedModule } from '@shared/shared.module';
  44 +import * as AngularCore from '@angular/core';
  45 +import * as AngularCommon from '@angular/common';
  46 +import * as AngularForms from '@angular/forms';
  47 +import * as AngularRouter from '@angular/router';
  48 +import * as AngularCdkKeycodes from '@angular/cdk/keycodes';
  49 +import * as AngularCdkCoercion from '@angular/cdk/coercion';
  50 +import * as AngularMaterialChips from '@angular/material/chips';
  51 +import * as AngularMaterialAutocomplete from '@angular/material/autocomplete';
  52 +import * as AngularMaterialDialog from '@angular/material/dialog';
  53 +import * as NgrxStore from '@ngrx/store';
  54 +import * as RxJs from 'rxjs';
  55 +import * as RxJsOperators from 'rxjs/operators';
  56 +import * as TranslateCore from '@ngx-translate/core';
  57 +import * as TbCore from '@core/public-api';
  58 +import * as TbShared from '@shared/public-api';
  59 +import * as _moment from 'moment';
  60 +
  61 +declare const SystemJS;
  62 +
  63 +const widgetResourcesModulesMap = {
  64 + '@angular/core': SystemJS.newModule(AngularCore),
  65 + '@angular/common': SystemJS.newModule(AngularCommon),
  66 + '@angular/forms': SystemJS.newModule(AngularForms),
  67 + '@angular/router': SystemJS.newModule(AngularRouter),
  68 + '@angular/cdk/keycodes': SystemJS.newModule(AngularCdkKeycodes),
  69 + '@angular/cdk/coercion': SystemJS.newModule(AngularCdkCoercion),
  70 + '@angular/material/chips': SystemJS.newModule(AngularMaterialChips),
  71 + '@angular/material/autocomplete': SystemJS.newModule(AngularMaterialAutocomplete),
  72 + '@angular/material/dialog': SystemJS.newModule(AngularMaterialDialog),
  73 + '@ngrx/store': SystemJS.newModule(NgrxStore),
  74 + rxjs: SystemJS.newModule(RxJs),
  75 + 'rxjs/operators': SystemJS.newModule(RxJsOperators),
  76 + '@ngx-translate/core': SystemJS.newModule(TranslateCore),
  77 + '@core/public-api': SystemJS.newModule(TbCore),
  78 + '@shared/public-api': SystemJS.newModule(TbShared),
  79 + moment: SystemJS.newModule(_moment)
  80 +};
44 81
45 // @dynamic 82 // @dynamic
46 @Injectable() 83 @Injectable()
@@ -105,8 +142,8 @@ export class WidgetComponentService { @@ -105,8 +142,8 @@ export class WidgetComponentService {
105 const initSubject = new ReplaySubject(); 142 const initSubject = new ReplaySubject();
106 this.init$ = initSubject.asObservable(); 143 this.init$ = initSubject.asObservable();
107 const loadDefaultWidgetInfoTasks = [ 144 const loadDefaultWidgetInfoTasks = [
108 - this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule]),  
109 - this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule]), 145 + this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
  146 + this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
110 ]; 147 ];
111 forkJoin(loadDefaultWidgetInfoTasks).subscribe( 148 forkJoin(loadDefaultWidgetInfoTasks).subscribe(
112 () => { 149 () => {
@@ -218,31 +255,71 @@ export class WidgetComponentService { @@ -218,31 +255,71 @@ export class WidgetComponentService {
218 this.cssParser.cssPreviewNamespace = widgetNamespace; 255 this.cssParser.cssPreviewNamespace = widgetNamespace;
219 this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); 256 this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss);
220 const resourceTasks: Observable<string>[] = []; 257 const resourceTasks: Observable<string>[] = [];
  258 + const modulesTasks: Observable<Type<any>[] | string>[] = [];
221 if (widgetInfo.resources.length > 0) { 259 if (widgetInfo.resources.length > 0) {
222 - widgetInfo.resources.forEach((resource) => { 260 + widgetInfo.resources.filter(r => r.isModule).forEach(
  261 + (resource) => {
  262 + modulesTasks.push(
  263 + this.resources.loadModules(resource.url, widgetResourcesModulesMap).pipe(
  264 + catchError((e: Error) => of(e?.message ? e.message : `Failed to load widget resource module: '${resource.url}'`))
  265 + )
  266 + );
  267 + }
  268 + );
  269 + }
  270 + widgetInfo.resources.filter(r => !r.isModule).forEach(
  271 + (resource) => {
223 resourceTasks.push( 272 resourceTasks.push(
224 this.resources.loadResource(resource.url).pipe( 273 this.resources.loadResource(resource.url).pipe(
225 catchError(e => of(`Failed to load widget resource: '${resource.url}'`)) 274 catchError(e => of(`Failed to load widget resource: '${resource.url}'`))
226 ) 275 )
227 ); 276 );
228 - }); 277 + }
  278 + );
  279 +
  280 + let modulesObservable: Observable<string | Type<any>[]>;
  281 + if (modulesTasks.length) {
  282 + modulesObservable = forkJoin(modulesTasks).pipe(
  283 + map(res => {
  284 + const msg = res.find(r => typeof r === 'string');
  285 + if (msg) {
  286 + return msg as string;
  287 + } else {
  288 + let resModules = (res as Type<any>[][]).flat();
  289 + if (modules && modules.length) {
  290 + resModules = resModules.concat(modules);
  291 + }
  292 + return resModules;
  293 + }
  294 + })
  295 + );
  296 + } else {
  297 + modulesObservable = modules && modules.length ? of(modules) : of([]);
229 } 298 }
  299 +
230 resourceTasks.push( 300 resourceTasks.push(
231 - this.dynamicComponentFactoryService.createDynamicComponentFactory(  
232 - class DynamicWidgetComponentInstance extends DynamicWidgetComponent {},  
233 - widgetInfo.templateHtml,  
234 - modules  
235 - ).pipe(  
236 - map((factory) => {  
237 - widgetInfo.componentFactory = factory;  
238 - return null;  
239 - }),  
240 - catchError(e => {  
241 - const details = this.utils.parseException(e);  
242 - const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`;  
243 - return of(errorMessage);  
244 - })  
245 - ) 301 + modulesObservable.pipe(
  302 + mergeMap((resolvedModules) => {
  303 + if (typeof resolvedModules === 'string') {
  304 + return of(resolvedModules);
  305 + } else {
  306 + return this.dynamicComponentFactoryService.createDynamicComponentFactory(
  307 + class DynamicWidgetComponentInstance extends DynamicWidgetComponent {},
  308 + widgetInfo.templateHtml,
  309 + resolvedModules
  310 + ).pipe(
  311 + map((factory) => {
  312 + widgetInfo.componentFactory = factory;
  313 + return null;
  314 + }),
  315 + catchError(e => {
  316 + const details = this.utils.parseException(e);
  317 + const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`;
  318 + return of(errorMessage);
  319 + })
  320 + )
  321 + }
  322 + }))
246 ); 323 );
247 return forkJoin(resourceTasks).pipe( 324 return forkJoin(resourceTasks).pipe(
248 switchMap(msgs => { 325 switchMap(msgs => {
@@ -794,16 +794,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -794,16 +794,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
794 options.useDashboardTimewindow = true; 794 options.useDashboardTimewindow = true;
795 } 795 }
796 } 796 }
797 - let datasource: Datasource;  
798 if (options.type === widgetType.alarm) { 797 if (options.type === widgetType.alarm) {
799 - datasource = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]); 798 + options.alarmSource = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]);
800 } else { 799 } else {
801 - datasource = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo);  
802 - }  
803 - if (options.type === widgetType.alarm) {  
804 - options.alarmSource = datasource;  
805 - } else {  
806 - options.datasources = [datasource]; 800 + options.datasources = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo);
807 } 801 }
808 this.createSubscription(options, subscribe).subscribe( 802 this.createSubscription(options, subscribe).subscribe(
809 (subscription) => { 803 (subscription) => {
@@ -129,6 +129,10 @@ @@ -129,6 +129,10 @@
129 (ngModelChange)="isDirty = true" 129 (ngModelChange)="isDirty = true"
130 placeholder="{{ 'widget.resource-url' | translate }}"/> 130 placeholder="{{ 'widget.resource-url' | translate }}"/>
131 </mat-form-field> 131 </mat-form-field>
  132 + <mat-checkbox [(ngModel)]="resource.isModule"
  133 + (ngModelChange)="isDirty = true">
  134 + {{ 'widget.resource-is-module' | translate }}
  135 + </mat-checkbox>
132 <button mat-icon-button color="primary" 136 <button mat-icon-button color="primary"
133 [disabled]="isLoading$ | async" 137 [disabled]="isLoading$ | async"
134 (click)="removeResource(i)" 138 (click)="removeResource(i)"
@@ -53,6 +53,7 @@ export interface NodeScriptTestDialogData { @@ -53,6 +53,7 @@ export interface NodeScriptTestDialogData {
53 msgType?: string; 53 msgType?: string;
54 } 54 }
55 55
  56 +// @dynamic
56 @Component({ 57 @Component({
57 selector: 'tb-node-script-test-dialog', 58 selector: 'tb-node-script-test-dialog',
58 templateUrl: './node-script-test-dialog.component.html', 59 templateUrl: './node-script-test-dialog.component.html',
@@ -114,6 +114,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( @@ -114,6 +114,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
114 114
115 export interface WidgetResource { 115 export interface WidgetResource {
116 url: string; 116 url: string;
  117 + isModule?: boolean;
117 } 118 }
118 119
119 export interface WidgetActionSource { 120 export interface WidgetActionSource {
@@ -1787,6 +1787,7 @@ @@ -1787,6 +1787,7 @@
1787 "type": "Widget type", 1787 "type": "Widget type",
1788 "resources": "Resources", 1788 "resources": "Resources",
1789 "resource-url": "JavaScript/CSS URL", 1789 "resource-url": "JavaScript/CSS URL",
  1790 + "resource-is-module": "Is module",
1790 "remove-resource": "Remove resource", 1791 "remove-resource": "Remove resource",
1791 "add-resource": "Add resource", 1792 "add-resource": "Add resource",
1792 "html": "HTML", 1793 "html": "HTML",