Browse Source

Merge remote-tracking branch 'upstream/master' into feature/extend-table-widget-settings

pull/4235/head
Chantsova Ekaterina 5 years ago
parent
commit
63a0421b89
  1. 4
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  2. 22
      application/src/main/data/json/system/widget_bundles/analogue_gauges.json
  3. 18
      application/src/main/data/json/system/widget_bundles/cards.json
  4. 34
      application/src/main/data/json/system/widget_bundles/charts.json
  5. 18
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  6. 4
      application/src/main/data/json/system/widget_bundles/date.json
  7. 42
      application/src/main/data/json/system/widget_bundles/digital_gauges.json
  8. 6
      application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
  9. 8
      application/src/main/data/json/system/widget_bundles/gateway_widgets.json
  10. 10
      application/src/main/data/json/system/widget_bundles/gpio_widgets.json
  11. 52
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  12. 20
      application/src/main/data/json/system/widget_bundles/maps.json
  13. 10
      application/src/main/data/json/system/widget_bundles/navigation_widgets.json
  14. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java
  15. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java
  16. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java
  17. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java
  18. 2
      ui-ngx/src/app/core/api/data-aggregator.ts
  19. 14
      ui-ngx/src/app/core/http/widget.service.ts
  20. 20
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  21. 31
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  22. 38
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html
  23. 7
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss
  24. 78
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  25. 12
      ui-ngx/src/app/modules/home/components/widget/data-keys.component.html
  26. 42
      ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts
  27. 9
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  28. 5
      ui-ngx/src/app/shared/components/entity/entity-select.component.ts
  29. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/src/main/data/json/system/widget_bundles/alarm_widgets.json

File diff suppressed because one or more lines are too long

22
application/src/main/data/json/system/widget_bundles/analogue_gauges.json

File diff suppressed because one or more lines are too long

18
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

34
application/src/main/data/json/system/widget_bundles/charts.json

File diff suppressed because one or more lines are too long

18
application/src/main/data/json/system/widget_bundles/control_widgets.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_bundles/date.json

@ -3,14 +3,14 @@
"alias": "date",
"title": "Date",
"image": "data:image/png;base64,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",
"description": null
"description": "Contains widgets to change the data range for other widgets on the dashboard."
},
"widgetTypes": [
{
"alias": "date_range_navigator",
"name": "Date-range-navigator",
"image": "data:image/png;base64,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",
"description": null,
"description": "Allows to change the data range for other widgets on the dashboard.",
"descriptor": {
"type": "static",
"sizeX": 5,

42
application/src/main/data/json/system/widget_bundles/digital_gauges.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json

@ -3,14 +3,14 @@
"alias": "entity_admin_widgets",
"title": "Entity admin widgets",
"image": "data:image/png;base64,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",
"description": null
"description": "Templates of complex widgets that allow to list and create/update/delete devices and assets."
},
"widgetTypes": [
{
"alias": "device_admin_table",
"name": "Device admin table",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAAmQSURBVHja7d39V1NHGsBx/7Lgafd47FH2BhAEDIFoyIL1pb5gC4upcqwW0aJUDNpSGmFXAxVQV+silUUsqQVsFvAFRF6EKEFISSAvJDf3uz8ELOtaCZCzKp35AThzZ+7cz5lnbubkCbmrCI49GXzHy5OxAKuCwy6Zd7zIruHgqjEXK6C4xlY9kVcCRH6yapAVUQYFREAEREAEREDeGsiMbWpR7bsd4d9BmyvKkOajR4+e7VBe0bC8NoKzjUrdL9WUXXlde03d7E5JalvENdvtC0Mqky9aipIPzfxvw5obS4Kcb3ppzgxty4aYTBFAMgBHWnl4I/aK9sG5Tdr8i3tRNwcJACH5Fd3wSy3z+mjqwkdmIXKUIdQm+cC2Q9JVK+60OxDKrqPgDHhLEuNyHoFSm6beYQv3CFVqpM03gP6d0haL1M2Vj6rTpT3204lx+U7Ir8CfemW3pGkMz2uqlLQXrFuljaUB0Hz5cXzKxVnIqDEu9YQ/qpBe6QH98RbHrQ2tFByDh+pn5JbAoexux8k0F9+l3nF8lRJenxc0tsk6dR/BzE96H+VL3dRK5SO29KSSYVt6Kewpwydl3Bn5coMLYGpIuvKcXnXdZGeKBTSpt0fOS7/gktrw6j+339OXRxUyIVkpygNOGmlOnqFiH+SWMCLdheC29mBqHYTSw8v43kNQkq9wW+2AEamb2lQFvt4ow5nds5BGcEhdv4XW01ag6FPQXADyDuOS2ria4ocbScrr17nJZDLt328ymUz2CCDD0l0MO0pLS/ca8CdbybwGuSX8KIVvrk+kQ6Wlpelfhbt0nS4wJtRi0c0u9to04LweOJc9C7n923IOrxFH5WGjLm92sX+7HZfUxgltaWnpIWkimhCr9IwtH5vNZrMFTh5/HO+C3BJapXAE90snzGaz+UcAvt948a4tpZaqrMghT1JK7tgOzEEqs3BJbRzLNJvNZrMriqGlHNgN+4/OVv07peIQkFvCgPQYaBzxq+fdU3eYgbRaGjd4IoZUbgdO5oGmFjhsxCW1cX5LKLq3X1/P0cReuKFuJXS+HkKbE1rCkNCHB2doVj/hcKadqc97AdjzV3+wRqpmOvW0HDyzICQUXw+WFDu21H2gyXZgi2vEJbUxGP9tCOsXSpQgkiSpDwwAijkuXWN4DJhTZsIQhrITdUmN4MpX6xMLvOFdhmZD4sHMUmhJTNz4xYIQiuI+YmqXOjnzYAZoDqdskg6Hwkdvpabokm9FaUamRkcdgdm/PQ8ehQD8EwATLiDU2+0B4Hnn6FwX770RxekEXJ1jyugM0w5gagyYeg4TLkKjfsI/AEIPBiDU1xvwjCo4fFOdA8wdDfTd80driyJ2vwIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiICsOoti94LMv8VwTdrvdbve9DRBZtQ9aVUv8lOP+dWti1q2zvh0Q1S1aVTKBniGYdD7vkX33fwXsDwKRnO37D2DCiWL3jXke2QFv1/M3BNmZ4GlVyfbkTPVJTiVo123Xpq59SllsmnY6Qsi3qTyIGdUlad+rZkC9+YPrbwbSrD/RqpLv/YObf1JOZSntql4l4dJgzJCSWRMhxB4zUL4V3RkurQ19XMgP8W8GcrvnvXKVPJmfnqqST+XQq5okw/JDTHLymuIIIWSd21KLzsKAanxDbHKCyvtmIJx6XyWXfRjqmAe5u/qZ2+mLFPJd8monOgttMTMGk9vtVN4QxJuokqvXfZ2hmnwBkQ3Z5oy2SCHO1btBt6Ei7VMa1pwp3PsmQitUNQL3qhT5ytnOqomOJpxVfq4+wFd7OpJ0K/3VANqroDN9Y/FD51ffOd/VV3Znxdop0Fne+S3K45w24HSr2GsJiIAIiIAIiIAIiIAIiIAIyIKQoRVRRGgJiIAIiIAIiIAIiID8YSG2c6/4r6uh4uLi4qF3CmLLMhS9JOkxvCg9C0OUQJSuZHnfS/R8PM9gOPdS3cUX5flrIZdSwKLN3OcH2N+mrAd7Rr81Qa/LebrAuO5Yvebg+Pya5uPLcXRkN4/nZdleqrUWzxbra0PLkRHL8KYgB64C5FiVGJwaKy25UJO7wMCu9XBNJ4N3fiJXdgJ4/It3ZBn+0jz+soPLc5F1+bWQT6yxTPZB4fU5iDfzKrTkQm96BBB2dFChzzzri/dz1tJQSEdajn6MM4Yt5sU7DIat4ywNcuOYJxbgsd43B9mjV6Alu71l298jgRy/1JMphzYPH7mFdryhUEke5NrZzq2KnOZYZFwZDIasn1ka5FfdtCcWmNA9ZhaiMu2wQEtK2d4CIoF8du1iwq5d6tsdBQO7aSh0qQH+lrhr15/bo+KIDFKXpt+8OgtPVjt0/wI7O5QYJhK6aMnFFT8WASSQNHj9iNvtDoa0Z6/TUCivD+F31h93u91yVByRrhE8sQR3lvX0DLSnP/4pzqXEwN2kyZZcqDqyEGRtj3XvSZyJbcPFbsrWe2goxFg1dqR+LLFj6LgnKo6IITOncBYVFRVV0Gg80otyDLjW+KgO/CcW+FSJt6iopBUYOJrfBMM1cP863nLjZegrzL+9yHWe3cGyIG9DOfb78/HOQX5vPqBxDtL49kNqiot/14HvXPiFvdIntvECIiACIiACIiACIiACsmiIyLOL0BIQAREQAREQAREQAXmnISLPfmcahupvht8fnrx63QNg/yWyYYNNTa3//QXKo13LcowvPc+u7ee6tvqMdgpwbqquzAiCvHlnZOO61lSdSrk5v+bhteU4OrYuPc+u7Q/EPoPj1UBXPWT1QVV+pJD1MB43iedSvdt7Awa6Rmwot853w1T9Jc+iHdnLyLNr+3sygEA4tkL3N3kY2tq5CAif/hDQX6jRyRoHBS0NhZiMNzNbfbqaC4bQoh3LyLNr++9uD0/g5SbIi/+G0M7ersVATtT9K9/t3ttlrg5sDDYUyut9DDT/s8Dt3ta7eMeS8+xo+4e0gNNhvdwEyPr7dduazmnbI4fkNddojEZj99Ot1uM0FLoSAKq0RqOxNyqOiCFy4iB8Vg9YayDv5+aqqi+Sv48YMqCetuaAB3bs7aShkDgXfT825YMnOo6IIfykKTu0fQZwpplOZAeAiEPr/aL9qR2Ecg6WbXZzeaNCQyFXsiv0D+Wdn5Xp/6959i4v/GrtDq9L388dMsB0T2TDy+3tfTIQ6mr1gLcfJgZhxDoJcmerd5EOkWd/uyAizy628QIiIAIiIALyB4esmAcEr4xHNk+OrQqsjIdoy6tWxmPNZf4DJqTD+Gup8cgAAAAASUVORK5CYII=",
"description": null,
"description": "Customized entity table widget with preconfigured actions to create update and delete devices.",
"descriptor": {
"type": "latest",
"sizeX": 7.5,
@ -28,7 +28,7 @@
"alias": "asset_admin_table",
"name": "Asset admin table",
"image": "data:image/png;base64,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",
"description": null,
"description": "Customized entity table widget with preconfigured actions to create update and delete assets.",
"descriptor": {
"type": "latest",
"sizeX": 7.5,

8
application/src/main/data/json/system/widget_bundles/gateway_widgets.json

File diff suppressed because one or more lines are too long

10
application/src/main/data/json/system/widget_bundles/gpio_widgets.json

File diff suppressed because one or more lines are too long

52
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

20
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

10
application/src/main/data/json/system/widget_bundles/navigation_widgets.json

File diff suppressed because one or more lines are too long

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java

@ -32,7 +32,7 @@ public class EntitiesAlarmOriginatorIdAsyncLoader {
case ALARM:
return getAlarmOriginatorAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) original));
default:
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original));
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType()));
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java

@ -42,7 +42,7 @@ public class EntitiesCustomerIdAsyncLoader {
case DEVICE:
return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) original));
default:
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original));
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType()));
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java

@ -63,7 +63,7 @@ public class EntitiesFieldsAsyncLoader {
return getAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), (EntityViewId) original),
EntityFieldsData::new);
default:
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original));
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType()));
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java

@ -50,7 +50,7 @@ public class EntitiesTenantIdAsyncLoader {
case RULE_CHAIN:
return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync(ctx.getTenantId(), (RuleChainId) original));
default:
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original));
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType()));
}
}

2
ui-ngx/src/app/core/api/data-aggregator.ts

@ -155,6 +155,7 @@ export class DataAggregator {
}
public onData(data: SubscriptionDataHolder, update: boolean, history: boolean, detectChanges: boolean) {
this.updatedData = true;
if (!this.dataReceived || this.resetPending) {
let updateIntervalScheduledTime = true;
if (!this.dataReceived) {
@ -183,7 +184,6 @@ export class DataAggregator {
this.onInterval(history, detectChanges);
}
}
this.updatedData = true;
}
private onInterval(history?: boolean, detectChanges?: boolean) {

14
ui-ngx/src/app/core/http/widget.service.ts

@ -55,6 +55,8 @@ export class WidgetService {
private systemWidgetsBundles: Array<WidgetsBundle>;
private tenantWidgetsBundles: Array<WidgetsBundle>;
private widgetTypeInfosCache = new Map<string, Array<WidgetTypeInfo>>();
private loadWidgetsBundleCacheSubject: ReplaySubject<any>;
constructor(
@ -137,8 +139,15 @@ export class WidgetService {
public getBundleWidgetTypeInfos(bundleAlias: string, isSystem: boolean,
config?: RequestConfig): Observable<Array<WidgetTypeInfo>> {
return this.http.get<Array<WidgetTypeInfo>>(`/api/widgetTypesInfos?isSystem=${isSystem}&bundleAlias=${bundleAlias}`,
defaultHttpOptionsFromConfig(config));
const key = bundleAlias + (isSystem ? '_sys' : '');
if (this.widgetTypeInfosCache.has(key)) {
return of(this.widgetTypeInfosCache.get(key));
} else {
return this.http.get<Array<WidgetTypeInfo>>(`/api/widgetTypesInfos?isSystem=${isSystem}&bundleAlias=${bundleAlias}`,
defaultHttpOptionsFromConfig(config)).pipe(
tap((res) => this.widgetTypeInfosCache.set(key, res) )
);
}
}
public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean,
@ -305,6 +314,7 @@ export class WidgetService {
this.systemWidgetsBundles = undefined;
this.tenantWidgetsBundles = undefined;
this.loadWidgetsBundleCacheSubject = undefined;
this.widgetTypeInfosCache.clear();
}
}

20
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -242,8 +242,9 @@
</tb-edit-widget>
</tb-details-panel>
<tb-details-panel *ngIf="!isAddingWidgetClosed && !widgetEditMode" fxFlex
headerTitle="{{
(!widgetsBundle?.title ? 'widget.select-widgets-bundle' : 'dashboard.select-widget-value') | translate: widgetsBundle
headerTitle="{{ isAddingWidget ?
((!dashboardWidgetSelectComponent?.widgetsBundle ?
'widget.select-widgets-bundle' : 'dashboard.select-widget-value') | translate: dashboardWidgetSelectComponent?.widgetsBundle) : ''
}}"
headerHeightPx="64"
[isShowSearch]="true"
@ -252,34 +253,33 @@
backgroundColor="#cfd8dc"
(closeDetails)="onAddWidgetClosed()"
(closeSearch)="onCloseSearchBundle()">
<div class="prefix-title-buttons" [fxHide]="!widgetsBundle?.title" style="height: 28px; margin-right: 12px">
<button class="tb-mat-28" mat-icon-button type="button" (click)="widgetBundleSelected(null)">
<div class="prefix-title-buttons" [fxShow]="(isAddingWidget && dashboardWidgetSelectComponent?.widgetsBundle) ? true : false" style="height: 28px; margin-right: 12px">
<button class="tb-mat-28" mat-icon-button type="button" (click)="clearSelectedWidgetBundle()">
<mat-icon>arrow_back</mat-icon>
</button>
</div>
<div class="search-pane" *ngIf="isAddingWidget" fxLayout="row">
<tb-widgets-bundle-search fxFlex
[(ngModel)]="searchBundle"
placeholder="{{ (!widgetsBundle?.title ? 'widgets-bundle.search' : 'widget.search') | translate }}"
placeholder="{{ (!dashboardWidgetSelectComponent?.widgetsBundle ? 'widgets-bundle.search' : 'widget.search') | translate }}"
(ngModelChange)="searchBundle = $event">
</tb-widgets-bundle-search>
</div>
<div class="details-buttons" *ngIf="isAddingWidget">
<button mat-button mat-icon-button type="button"
*ngIf="widgetTypes.length > 1"
*ngIf="dashboardWidgetSelectComponent?.widgetTypes.size > 1"
(click)="editWidgetsTypesToDisplay($event)"
matTooltip="{{ 'widget.filter' | translate }}"
matTooltipPosition="above">
<mat-icon>filter_list</mat-icon>
</button>
</div>
<tb-dashboard-widget-select *ngIf="isAddingWidget"
<tb-dashboard-widget-select #dashboardWidgetSelect
*ngIf="isAddingWidget"
[aliasController]="dashboardCtx.aliasController"
[widgetsBundle]="widgetsBundle"
[searchBundle]="searchBundle"
[filterWidgetTypes]="filterWidgetTypes"
(widgetsTypes)="updateWidgetsTypes($event)"
(widgetsBundleSelected)="widgetBundleSelected($event)"
(widgetsBundleSelected)="widgetBundleSelected()"
(widgetSelected)="addWidgetFromType($event)">
</tb-dashboard-widget-select>
</tb-details-panel>

31
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -47,7 +47,7 @@ import {
} from '@app/shared/models/dashboard.models';
import { WINDOW } from '@core/services/window.service';
import { WindowMessage } from '@shared/models/window-message.model';
import { deepClone, isDefined } from '@app/core/utils';
import { deepClone, isDefined, isDefinedAndNotNull } from '@app/core/utils';
import {
DashboardContext,
DashboardPageLayout,
@ -62,6 +62,7 @@ import { MediaBreakpoints } from '@shared/models/constants';
import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthState } from '@core/auth/auth.selectors';
import {
DatasourceType,
Widget,
WidgetConfig,
WidgetInfo,
@ -120,6 +121,7 @@ import {
DisplayWidgetTypesPanelData,
WidgetTypes
} from '@home/components/dashboard-page/widget-types-panel.component';
import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component';
// @dynamic
@Component({
@ -166,9 +168,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
forceDashboardMobileMode = false;
isAddingWidget = false;
isAddingWidgetClosed = true;
widgetsBundle: WidgetsBundle = null;
searchBundle = '';
widgetTypes: WidgetTypes[] = [];
filterWidgetTypes: widgetType[] = null;
isToolbarOpened = false;
@ -266,6 +266,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
@ViewChild('tbEditWidget') editWidgetComponent: EditWidgetComponent;
@ViewChild('dashboardWidgetSelect') dashboardWidgetSelectComponent: DashboardWidgetSelectComponent;
constructor(protected store: Store<AppState>,
@Inject(WINDOW) private window: Window,
private breakpointObserver: BreakpointObserver,
@ -366,7 +368,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.forceDashboardMobileMode = false;
this.isAddingWidget = false;
this.isAddingWidgetClosed = true;
this.widgetsBundle = null;
this.isToolbarOpened = false;
this.isToolbarOpenedAnimate = false;
@ -884,13 +885,18 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
addWidgetFromType(widget: WidgetInfo) {
this.onAddWidgetClosed();
this.widgetTypes = [];
this.searchBundle = '';
this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe(
(widgetTypeInfo) => {
const config: WidgetConfig = JSON.parse(widgetTypeInfo.defaultConfig);
config.title = 'New ' + widgetTypeInfo.widgetName;
config.datasources = [];
if (isDefinedAndNotNull(config.alarmSource)) {
config.alarmSource = {
type: DatasourceType.entity,
dataKeys: config.alarmSource.dataKeys || []
};
}
const newWidget: Widget = {
isSystemType: widget.isSystemType,
bundleAlias: widget.bundleAlias,
@ -1146,16 +1152,13 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
return widgetContextActions;
}
widgetBundleSelected(bundle: WidgetsBundle){
this.widgetsBundle = bundle;
this.widgetTypes = [];
widgetBundleSelected(){
this.searchBundle = '';
}
updateWidgetsTypes(types: Set<widgetType>) {
this.widgetTypes = Array.from(types.values()).map(type => {
return {type, display: true};
});
clearSelectedWidgetBundle() {
this.searchBundle = '';
this.dashboardWidgetSelectComponent.widgetsBundle = null;
}
editWidgetsTypesToDisplay($event: Event) {
@ -1184,7 +1187,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
{
provide: DISPLAY_WIDGET_TYPES_PANEL_DATA,
useValue: {
types: this.widgetTypes,
types: Array.from(this.dashboardWidgetSelectComponent.widgetTypes.values()).map(type => {
return {type, display: true};
}),
typesUpdated: (newTypes) => {
this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type);
}

38
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html

@ -17,7 +17,7 @@
-->
<div class="widget-select">
<div *ngIf="widgetsBundle; else bundles">
<div *ngIf="(widgets$ | async)?.length; else emptyBundle" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngIf="(widgets$ | async)?.length; else loadingWidgets" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngFor="let widget of widgets$ | async" class="mat-card-container">
<mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, widget)">
<div class="preview-container" fxFlex="45">
@ -33,15 +33,24 @@
</mat-card>
</div>
</div>
<ng-template #emptyBundle>
<span translate
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
<ng-template #loadingWidgets>
<div *ngIf="loadingWidgets$ | async; else emptyBundle" fxLayout="column"
fxLayoutAlign="center center" class="tb-absolute-fill">
<span class="mat-headline" style="padding-bottom: 20px;">
{{ 'widget.loading-widgets' | translate }}
</span>
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
</div>
<ng-template #emptyBundle>
<span translate
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
</ng-template>
</ng-template>
</div>
<ng-template #bundles>
<div fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngIf="(widgetsBundles$ | async)?.length; else loadingWidgetBundles" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngFor="let widgetsBundle of widgetsBundles$ | async" class="mat-card-container">
<mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="selectBundle($event, widgetsBundle)">
<div class="preview-container" fxFlex="45">
@ -57,5 +66,20 @@
</mat-card>
</div>
</div>
<ng-template #loadingWidgetBundles>
<div *ngIf="loadingWidgetBundles$ | async; else noWidgetBundles" fxLayout="column"
fxLayoutAlign="center center" class="tb-absolute-fill">
<span class="mat-headline" style="padding-bottom: 20px;">
{{ 'widgets-bundle.loading-widgets-bundles' | translate }}
</span>
<mat-spinner strokeWidth="5"></mat-spinner>
</div>
<ng-template #noWidgetBundles>
<span translate
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widgets-bundle.no-widgets-bundles-text</span>
</ng-template>
</ng-template>
</ng-template>
</div>

7
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss

@ -29,8 +29,15 @@
flex: 0 0 100%;
max-width: 100%;
&:hover {
.mat-card {
box-shadow: 0 2px 6px 6px rgb(0 0 0 / 20%), 0 1px 4px 2px rgb(0 0 0 / 14%), 0 1px 6px 0 rgb(0 0 0 / 12%)
}
}
.mat-card {
cursor: pointer;
transition: box-shadow 0.2s;
.preview-container {
text-align: center;

78
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts

@ -20,8 +20,7 @@ import { IAliasController } from '@core/api/widget-api.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetService } from '@core/http/widget.service';
import { WidgetInfo, widgetType } from '@shared/models/widget.models';
import { toWidgetInfo } from '@home/models/widget-component.models';
import { distinctUntilChanged, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators';
import { distinctUntilChanged, map, publishReplay, refCount, share, switchMap, tap } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { isDefinedAndNotNull } from '@core/utils';
@ -37,18 +36,28 @@ export class DashboardWidgetSelectComponent implements OnInit {
private filterWidgetTypes$ = new BehaviorSubject<Array<widgetType>>(null);
private widgetsInfo: Observable<Array<WidgetInfo>>;
private widgetsBundleValue: WidgetsBundle;
private widgetsType = new Set<widgetType>();
widgetTypes = new Set<widgetType>();
widgets$: Observable<Array<WidgetInfo>>;
loadingWidgetsSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
loadingWidgets$ = this.loadingWidgetsSubject.pipe(
share()
);
widgetsBundles$: Observable<Array<WidgetsBundle>>;
loadingWidgetBundlesSubject: BehaviorSubject<boolean> = new BehaviorSubject(true);
loadingWidgetBundles$ = this.loadingWidgetBundlesSubject.pipe(
share()
);
@Input()
set widgetsBundle(widgetBundle: WidgetsBundle) {
this.widgetsInfo = null;
this.widgetsType.clear();
this.widgetsTypes.emit(this.widgetsType);
this.filterWidgetTypes$.next(null);
this.widgetsBundleValue = widgetBundle;
if (this.widgetsBundleValue !== widgetBundle) {
this.widgetsBundleValue = widgetBundle;
if (widgetBundle === null) {
this.widgetTypes.clear();
}
this.filterWidgetTypes$.next(null);
this.widgetsInfo = null;
}
}
get widgetsBundle(): WidgetsBundle {
@ -74,14 +83,8 @@ export class DashboardWidgetSelectComponent implements OnInit {
@Output()
widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>();
@Output()
widgetsTypes: EventEmitter<Set<widgetType>> = new EventEmitter<Set<widgetType>>();
constructor(private widgetsService: WidgetService,
private sanitizer: DomSanitizer) {
}
ngOnInit(): void {
this.widgetsBundles$ = this.search$.asObservable().pipe(
distinctUntilChanged(),
switchMap(search => this.fetchWidgetBundle(search))
@ -92,27 +95,41 @@ export class DashboardWidgetSelectComponent implements OnInit {
);
}
ngOnInit(): void {
}
private getWidgets(): Observable<Array<WidgetInfo>> {
if (!this.widgetsInfo) {
if (this.widgetsBundle !== null) {
const bundleAlias = this.widgetsBundle.alias;
const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;
this.loadingWidgetsSubject.next(true);
this.widgetsInfo = this.widgetsService.getBundleWidgetTypeInfos(bundleAlias, isSystem).pipe(
map(widgets => widgets.sort((a, b) => b.createdTime - a.createdTime)),
map(widgets => widgets.map((widgetTypeInfo) => {
this.widgetsType.add(widgetTypeInfo.widgetType);
const widget: WidgetInfo = {
isSystemType: isSystem,
bundleAlias,
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.widgetType,
title: widgetTypeInfo.name,
image: widgetTypeInfo.image,
description: widgetTypeInfo.description
};
return widget;
})),
tap(() => this.widgetsTypes.emit(this.widgetsType)),
map(widgets => {
widgets = widgets.sort((a, b) => b.createdTime - a.createdTime);
const widgetTypes = new Set<widgetType>();
const widgetInfos = widgets.map((widgetTypeInfo) => {
widgetTypes.add(widgetTypeInfo.widgetType);
const widget: WidgetInfo = {
isSystemType: isSystem,
bundleAlias,
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.widgetType,
title: widgetTypeInfo.name,
image: widgetTypeInfo.image,
description: widgetTypeInfo.description
};
return widget;
}
);
setTimeout(() => {
this.widgetTypes = widgetTypes;
});
return widgetInfos;
}),
tap(() => {
this.loadingWidgetsSubject.next(false);
}),
publishReplay(1),
refCount()
);
@ -147,6 +164,7 @@ export class DashboardWidgetSelectComponent implements OnInit {
private getWidgetsBundle(): Observable<Array<WidgetsBundle>> {
return this.widgetsService.getAllWidgetsBundles().pipe(
tap(() => this.loadingWidgetBundlesSubject.next(false)),
publishReplay(1),
refCount()
);

12
ui-ngx/src/app/modules/home/components/widget/data-keys.component.html

@ -20,7 +20,7 @@
<mat-chip
*ngFor="let key of keys; let $index = index"
[selectable]="!disabled"
[removable]="!disabled && datasourceType !== datasourceTypes.entityCount"
[removable]="!disabled && !isEntityCountDatasource"
(removed)="remove(key)">
<div fxLayout="row" fxLayoutAlign="start center" class="tb-attribute-chip">
<div *ngIf="!disabled" class="tb-chip-drag-handle" style="margin-right: 5px;">
@ -31,7 +31,7 @@
</div>
<div class="tb-chip-labels">
<div class="tb-chip-label">
<span *ngIf="datasourceType !== datasourceTypes.function && datasourceType !== datasourceTypes.entityCount">
<span *ngIf="datasourceType === datasourceTypes.entity">
<span *ngIf="key.type === dataKeyTypes.alarm"
matTooltip="{{'datakey.alarm' | translate }}"
matTooltipPosition="above">
@ -70,7 +70,7 @@
(click)="editDataKey(key, $index)" mat-button mat-icon-button class="tb-mat-32">
<mat-icon class="tb-mat-20">edit</mat-icon>
</button>
<mat-icon matChipRemove *ngIf="!disabled && datasourceType !== datasourceTypes.entityCount">close</mat-icon>
<mat-icon matChipRemove *ngIf="!disabled && !isEntityCountDatasource">close</mat-icon>
</div>
</mat-chip>
<input matInput type="text" placeholder="{{ !disabled ? placeholder : '' }}"
@ -78,10 +78,12 @@
#keyInput
formControlName="key"
matAutocompleteOrigin
[readonly]="isEntityCountDatasource"
#origin="matAutocompleteOrigin"
[matAutocompleteConnectedTo]="origin"
(focusin)="onFocus()"
[matAutocomplete]="keyAutocomplete"
[matAutocompleteDisabled]="isEntityCountDatasource"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="add($event)">
@ -91,7 +93,7 @@
[displayWith]="displayKeyFn">
<mat-option *ngFor="let key of filteredKeys | async" [value]="key">
<span style="white-space: nowrap;">
<span *ngIf="datasourceType !== datasourceTypes.function && datasourceType !== datasourceTypes.entityCount">
<span *ngIf="datasourceType === datasourceTypes.entity">
<span *ngIf="key.type === dataKeyTypes.alarm"
matTooltip="{{'datakey.alarm' | translate }}"
matTooltipPosition="above">
@ -128,7 +130,7 @@
{{ translate.get('entity.no-key-matching',
{key: truncate.transform(searchText, true, 6, &apos;...&apos;)}) | async }}
</span>
<span *ngIf="datasourceType === datasourceTypes.function || datasourceType === datasourceTypes.entityCount; else createEntityKey">
<span *ngIf="datasourceType !== datasourceTypes.entity; else createEntityKey">
<a translate (click)="createKey(searchText)">entity.create-new-key</a>
</span>
<ng-template #createEntityKey>

42
ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts

@ -121,7 +121,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue || !this.optDataKeys || this.datasourceType === DatasourceType.entityCount;
return this.requiredValue || !this.optDataKeys || this.isEntityCountDatasource;
}
@Input()
set required(value: boolean) {
@ -217,7 +217,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
if (this.maxDataKeys !== null && this.maxDataKeys > -1) {
if (this.datasourceType === DatasourceType.function) {
return this.translate.instant('datakey.maximum-function-types', {count: this.maxDataKeys});
} else if (this.datasourceType !== DatasourceType.entityCount) {
} else if (!this.isEntityCountDatasource) {
return this.translate.instant('datakey.maximum-timeseries-or-attributes', {count: this.maxDataKeys});
} else {
return '';
@ -250,7 +250,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
private reset() {
if (this.widgetType === widgetType.alarm) {
this.keys = this.utils.getDefaultAlarmDataKeys();
} else if (this.datasourceType === DatasourceType.entityCount) {
} else if (this.isEntityCountDatasource) {
this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count)];
} else {
this.keys = [];
@ -271,11 +271,10 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'entityAliasId') {
this.searchText = '';
this.fetchObservable$ = null;
this.latestSearchTextResult = null;
this.clearSearchCache();
this.dirty = true;
} else if (['widgetType', 'datasourceType'].includes(propName)) {
this.clearSearchCache();
this.updateParams();
setTimeout(() => {
this.reset();
@ -436,20 +435,18 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
if (this.datasourceType === DatasourceType.function) {
const targetKeysList = this.widgetType === widgetType.alarm ? this.alarmKeys : this.functionTypeKeys;
fetchObservable = of(targetKeysList);
} else {
if (this.datasourceType !== DatasourceType.entityCount && this.entityAliasId) {
const dataKeyTypes = [DataKeyType.timeseries];
if (this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) {
dataKeyTypes.push(DataKeyType.attribute);
dataKeyTypes.push(DataKeyType.entityField);
if (this.widgetType === widgetType.alarm) {
dataKeyTypes.push(DataKeyType.alarm);
}
} else if (this.datasourceType === DatasourceType.entity && this.entityAliasId) {
const dataKeyTypes = [DataKeyType.timeseries];
if (this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) {
dataKeyTypes.push(DataKeyType.attribute);
dataKeyTypes.push(DataKeyType.entityField);
if (this.widgetType === widgetType.alarm) {
dataKeyTypes.push(DataKeyType.alarm);
}
fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, dataKeyTypes);
} else {
fetchObservable = of([]);
}
fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, dataKeyTypes);
} else {
fetchObservable = of([]);
}
this.fetchObservable$ = fetchObservable.pipe(
publishReplay(1),
@ -477,4 +474,13 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
}, 0);
}
get isEntityCountDatasource(): boolean {
return this.datasourceType === DatasourceType.entityCount;
}
private clearSearchCache() {
this.searchText = '';
this.fetchObservable$ = null;
this.latestSearchTextResult = null;
}
}

9
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -263,15 +263,6 @@
</mat-select>
</mat-form-field>
<section class="tb-datasource" [ngSwitch]="alarmSourceSettings.get('type').value">
<ng-template [ngSwitchCase]="datasourceType.function">
<mat-form-field floatLabel="always" [fxShow]="widgetType !== widgetTypes.alarm"
class="tb-datasource-name" style="min-width: 200px;">
<mat-label></mat-label>
<input matInput
placeholder="{{ 'datasource.name' | translate }}"
formControlName="name">
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="datasourceType.entity">
<tb-entity-alias-select
[showLabel]="true"

5
ui-ngx/src/app/shared/components/entity/entity-select.component.ts

@ -122,6 +122,9 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
writeValue(value: EntityId | null): void {
if (value != null) {
if (value.id === NULL_UUID) {
value.id = null;
}
this.modelValue = value;
this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: true});
this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: true});
@ -146,6 +149,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
|| this.modelValue.entityType === AliasEntityType.CURRENT_USER
|| this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) {
this.modelValue.id = NULL_UUID;
} else if (this.modelValue.entityType === AliasEntityType.CURRENT_CUSTOMER && !this.modelValue.id) {
this.modelValue.id = NULL_UUID;
}
if (this.modelValue.entityType && this.modelValue.id) {

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

@ -2266,7 +2266,8 @@
"data-overflow": "Widget displays {{count}} out of {{total}} entities",
"alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities",
"search": "Search widget",
"filter": "Widget filter type"
"filter": "Widget filter type",
"loading-widgets": "Loading widgets..."
},
"widget-action": {
"header-button": "Widget header button",
@ -2318,7 +2319,8 @@
"invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure.",
"search": "Search widget bundles",
"selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selected",
"open-widgets-bundle": "Open widgets bundle"
"open-widgets-bundle": "Open widgets bundle",
"loading-widgets-bundles": "Loading widgets bundles..."
},
"widget-config": {
"data": "Data",

Loading…
Cancel
Save