Commit 87874c3eb66c0eada6c432edd10381ded4cbaeec

Authored by nickAS21
2 parents 6fbd34d6 6cd7e3df

Merge branch 'master' into lwm2m_create_coap_resource

# Conflicts:
#	ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html
Showing 28 changed files with 509 additions and 353 deletions
... ... @@ -16,16 +16,17 @@
16 16
17 17 import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models';
18 18 import {
19   - AggregationType, calculateIntervalComparisonEndTime,
20   - calculateIntervalEndTime, calculateIntervalStartEndTime,
  19 + AggregationType,
  20 + calculateIntervalComparisonEndTime,
  21 + calculateIntervalEndTime,
  22 + calculateIntervalStartEndTime,
21 23 getCurrentTime,
22   - getCurrentTimeForComparison, getTime,
  24 + getTime,
23 25 SubscriptionTimewindow
24 26 } from '@shared/models/time/time.models';
25 27 import { UtilsService } from '@core/services/utils.service';
26   -import { deepClone } from '@core/utils';
  28 +import { deepClone, isNumeric } from '@core/utils';
27 29 import Timeout = NodeJS.Timeout;
28   -import * as moment_ from 'moment';
29 30
30 31 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void;
31 32
... ... @@ -407,24 +408,11 @@ export class DataAggregator {
407 408 }
408 409 }
409 410
410   - private isNumeric(val: any): boolean {
411   - return (val - parseFloat( val ) + 1) >= 0;
412   - }
413   -
414 411 private convertValue(val: string): any {
415   - if (!this.noAggregation || val && this.isNumeric(val)) {
  412 + if (!this.noAggregation || val && isNumeric(val) && Number(val).toString() === val) {
416 413 return Number(val);
417   - } else {
418   - return val;
419   - }
420   - }
421   -
422   - private getCurrentTime() {
423   - if (this.subsTw.timeForComparison) {
424   - return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone);
425   - } else {
426   - return getCurrentTime(this.subsTw.timezone);
427 414 }
  415 + return val;
428 416 }
429 417
430 418 }
... ...
... ... @@ -38,7 +38,7 @@ import {
38 38 } from '@shared/models/telemetry/telemetry.models';
39 39 import { UtilsService } from '@core/services/utils.service';
40 40 import { EntityDataListener, EntityDataLoadResult } from '@core/api/entity-data.service';
41   -import { deepClone, isDefined, isDefinedAndNotNull, isObject, objectHashCode } from '@core/utils';
  41 +import { deepClone, isDefined, isDefinedAndNotNull, isNumeric, isObject, objectHashCode } from '@core/utils';
42 42 import { PageData } from '@shared/models/page/page-data';
43 43 import { DataAggregator } from '@core/api/data-aggregator';
44 44 import { NULL_UUID } from '@shared/models/id/has-uuid';
... ... @@ -667,8 +667,7 @@ export class EntityDataSubscription {
667 667 if (prevDataCb) {
668 668 dataAggregator.updateOnDataCb(prevDataCb);
669 669 }
670   - }
671   - if (!this.history && !isUpdate) {
  670 + } else if (!this.history && !isUpdate) {
672 671 this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, dataUpdatedCb);
673 672 }
674 673 }
... ... @@ -743,16 +742,11 @@ export class EntityDataSubscription {
743 742 }
744 743 }
745 744
746   - private isNumeric(val: any): boolean {
747   - return (val - parseFloat( val ) + 1) >= 0;
748   - }
749   -
750 745 private convertValue(val: string): any {
751   - if (val && this.isNumeric(val) && Number(val).toString() === val) {
  746 + if (val && isNumeric(val) && Number(val).toString() === val) {
752 747 return Number(val);
753   - } else {
754   - return val;
755 748 }
  749 + return val;
756 750 }
757 751
758 752 private toSubscriptionData(sourceData: {[key: string]: TsValue | TsValue[]}, isTs: boolean): SubscriptionData {
... ...
... ... @@ -231,7 +231,6 @@ export class DashboardUtilsService {
231 231 private createDefaultGridSettings(): GridSettings {
232 232 return {
233 233 backgroundColor: '#eeeeee',
234   - color: 'rgba(0,0,0,0.870588)',
235 234 columns: 24,
236 235 margin: 10,
237 236 backgroundSizeMode: '100%'
... ...
... ... @@ -27,6 +27,7 @@ import { AuthService } from '@core/auth/auth.service';
27 27
28 28 const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler';
29 29 const dashboardLoadedHandler = 'tbMobileDashboardLoadedHandler';
  30 +const dashboardLayoutHandler = 'tbMobileDashboardLayoutHandler';
30 31 const navigationHandler = 'tbMobileNavigationHandler';
31 32 const mobileHandler = 'tbMobileHandler';
32 33
... ... @@ -43,6 +44,7 @@ export class MobileService {
43 44
44 45 private reloadUserObservable: Observable<boolean>;
45 46 private lastDashboardId: string;
  47 + private toggleLayoutFunction: () => void;
46 48
47 49 constructor(@Inject(WINDOW) private window: Window,
48 50 private router: Router,
... ... @@ -65,12 +67,26 @@ export class MobileService {
65 67 }
66 68 }
67 69
68   - public onDashboardLoaded() {
  70 + public onDashboardLoaded(hasRightLayout: boolean, rightLayoutOpened: boolean) {
69 71 if (this.mobileApp) {
70   - this.mobileChannel.callHandler(dashboardLoadedHandler);
  72 + this.mobileChannel.callHandler(dashboardLoadedHandler, hasRightLayout, rightLayoutOpened);
71 73 }
72 74 }
73 75
  76 + public onDashboardRightLayoutChanged(opened: boolean) {
  77 + if (this.mobileApp) {
  78 + this.mobileChannel.callHandler(dashboardLayoutHandler, opened);
  79 + }
  80 + }
  81 +
  82 + public registerToggleLayoutFunction(toggleLayoutFunction: () => void) {
  83 + this.toggleLayoutFunction = toggleLayoutFunction;
  84 + }
  85 +
  86 + public unregisterToggleLayoutFunction() {
  87 + this.toggleLayoutFunction = null;
  88 + }
  89 +
74 90 public handleWidgetMobileAction<T extends MobileActionResult>(type: WidgetMobileActionType, ...args: any[]):
75 91 Observable<WidgetMobileActionResult<T>> {
76 92 if (this.mobileApp) {
... ... @@ -110,6 +126,11 @@ export class MobileService {
110 126 const reloadUserMessage: ReloadUserMessage = message.data;
111 127 this.reloadUser(reloadUserMessage);
112 128 break;
  129 + case 'toggleDashboardLayout':
  130 + if (this.toggleLayoutFunction) {
  131 + this.toggleLayoutFunction();
  132 + }
  133 + break;
113 134 }
114 135 }
115 136 }
... ...
... ... @@ -149,8 +149,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
149 149 @Input()
150 150 currentState: string;
151 151
  152 + private hideToolbarValue = false;
  153 +
152 154 @Input()
153   - hideToolbar: boolean;
  155 + set hideToolbar(hideToolbar: boolean) {
  156 + this.hideToolbarValue = hideToolbar;
  157 + }
  158 +
  159 + get hideToolbar(): boolean {
  160 + return (this.hideToolbarValue || this.hideToolbarSetting()) && !this.isEdit;
  161 + }
154 162
155 163 @Input()
156 164 syncStateWithQueryParam = true;
... ... @@ -269,6 +277,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
269 277 return !this.widgetEditMode && !this.hideToolbar &&
270 278 (this.toolbarAlwaysOpen() || this.isToolbarOpened || this.isEdit || this.showRightLayoutSwitch());
271 279 }
  280 +
272 281 set toolbarOpened(toolbarOpened: boolean) {
273 282 }
274 283
... ... @@ -329,7 +338,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
329 338 this.dashboardCtx.aliasController.updateAliases();
330 339 setTimeout(() => {
331 340 this.mobileService.handleDashboardStateName(this.dashboardCtx.stateController.getCurrentStateName());
332   - this.mobileService.onDashboardLoaded();
  341 + this.mobileService.onDashboardLoaded(this.layouts.right.show, this.isRightLayoutOpened);
333 342 });
334 343 }
335 344 }
... ... @@ -340,6 +349,14 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
340 349 this.isMobile = !state.matches;
341 350 }
342 351 ));
  352 + if (this.isMobileApp) {
  353 + this.mobileService.registerToggleLayoutFunction(() => {
  354 + setTimeout(() => {
  355 + this.toggleLayouts();
  356 + this.cd.detectChanges();
  357 + });
  358 + });
  359 + }
343 360 }
344 361
345 362 private init(data: any) {
... ... @@ -430,6 +447,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
430 447 }
431 448
432 449 ngOnDestroy(): void {
  450 + if (this.isMobileApp) {
  451 + this.mobileService.unregisterToggleLayoutFunction();
  452 + }
433 453 this.rxSubscriptions.forEach((subscription) => {
434 454 subscription.unsubscribe();
435 455 });
... ... @@ -469,6 +489,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
469 489 }
470 490 }
471 491
  492 + private hideToolbarSetting(): boolean {
  493 + if (this.dashboard.configuration.settings &&
  494 + isDefined(this.dashboard.configuration.settings.hideToolbar)) {
  495 + return this.dashboard.configuration.settings.hideToolbar;
  496 + } else {
  497 + return false;
  498 + }
  499 + }
  500 +
472 501 public displayTitle(): boolean {
473 502 if (this.dashboard.configuration.settings &&
474 503 isDefined(this.dashboard.configuration.settings.showTitle)) {
... ... @@ -546,15 +575,17 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
546 575 }
547 576
548 577 public showRightLayoutSwitch(): boolean {
549   - return this.isMobile && this.layouts.right.show;
  578 + return this.isMobile && !this.isMobileApp && this.layouts.right.show;
550 579 }
551 580
552 581 public toggleLayouts() {
553 582 this.isRightLayoutOpened = !this.isRightLayoutOpened;
  583 + this.mobileService.onDashboardRightLayoutChanged(this.isRightLayoutOpened);
554 584 }
555 585
556 586 public openRightLayout() {
557 587 this.isRightLayoutOpened = true;
  588 + this.mobileService.onDashboardRightLayoutChanged(this.isRightLayoutOpened);
558 589 }
559 590
560 591 public mainLayoutWidth(): string {
... ... @@ -782,7 +813,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
782 813 this.updateLayouts(layoutsData);
783 814 }
784 815 setTimeout(() => {
785   - this.mobileService.onDashboardLoaded();
  816 + this.mobileService.onDashboardLoaded(this.layouts.right.show, this.isRightLayoutOpened);
786 817 });
787 818 }
788 819
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<form (ngSubmit)="save()">
  18 +<form (ngSubmit)="save()" style="width: 750px;">
19 19 <mat-toolbar color="primary">
20 20 <h2 translate>{{settings ? 'dashboard.settings' : 'layout.settings'}}</h2>
21 21 <span fxFlex></span>
... ... @@ -28,8 +28,8 @@
28 28 <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
29 29 </mat-progress-bar>
30 30 <div mat-dialog-content>
31   - <fieldset [disabled]="isLoading$ | async" fxLayout="column" fxLayoutGap="16px">
32   - <div *ngIf="settings" [formGroup]="settingsFormGroup">
  31 + <fieldset [disabled]="isLoading$ | async">
  32 + <div *ngIf="settings" [formGroup]="settingsFormGroup" fxLayout="column">
33 33 <mat-form-field class="mat-block">
34 34 <mat-label translate>dashboard.state-controller</mat-label>
35 35 <mat-select required formControlName="stateControllerId">
... ... @@ -38,112 +38,116 @@
38 38 </mat-option>
39 39 </mat-select>
40 40 </mat-form-field>
41   - <div fxLayout="row" fxLayoutAlign="start center" fxLayout.lt-md="column" fxLayoutAlign.lt-md fxLayoutGap="8px">
42   - <mat-checkbox fxFlex formControlName="toolbarAlwaysOpen">
43   - {{ 'dashboard.toolbar-always-open' | translate }}
44   - </mat-checkbox>
45   - <mat-checkbox fxFlex formControlName="showTitle">
  41 + <fieldset class="fields-group" fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start center" fxLayoutGap="8px">
  42 + <legend class="group-title" translate>dashboard.title-settings</legend>
  43 + <mat-slide-toggle fxFlex formControlName="showTitle">
46 44 {{ 'dashboard.display-title' | translate }}
47   - </mat-checkbox>
  45 + </mat-slide-toggle>
48 46 <tb-color-input fxFlex
49 47 label="{{'dashboard.title-color' | translate}}"
50 48 icon="format_color_fill"
51 49 openOnInput
52 50 formControlName="titleColor">
53 51 </tb-color-input>
54   - </div>
55   - <div fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 8px;"
56   - fxLayout.lt-md="column" fxLayoutAlign.lt-md fxLayoutGap="8px">
57   - <mat-checkbox fxFlex formControlName="showDashboardsSelect">
  52 + </fieldset>
  53 + <fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px">
  54 + <legend class="group-title" translate>dashboard.dashboard-logo-settings</legend>
  55 + <mat-slide-toggle formControlName="showDashboardLogo">
  56 + {{ 'dashboard.display-dashboard-logo' | translate }}
  57 + </mat-slide-toggle>
  58 + <tb-image-input fxFlex
  59 + label="{{'dashboard.dashboard-logo-image' | translate}}"
  60 + formControlName="dashboardLogoUrl">
  61 + </tb-image-input>
  62 + </fieldset>
  63 + <fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px">
  64 + <legend class="group-title" translate>dashboard.toolbar-settings</legend>
  65 + <mat-slide-toggle formControlName="hideToolbar">
  66 + {{ 'dashboard.hide-toolbar' | translate }}
  67 + </mat-slide-toggle>
  68 + <mat-slide-toggle formControlName="toolbarAlwaysOpen">
  69 + {{ 'dashboard.toolbar-always-open' | translate }}
  70 + </mat-slide-toggle>
  71 + <mat-slide-toggle formControlName="showDashboardsSelect">
58 72 {{ 'dashboard.display-dashboards-selection' | translate }}
59   - </mat-checkbox>
60   - <mat-checkbox fxFlex formControlName="showEntitiesSelect">
  73 + </mat-slide-toggle>
  74 + <mat-slide-toggle formControlName="showEntitiesSelect">
61 75 {{ 'dashboard.display-entities-selection' | translate }}
62   - </mat-checkbox>
63   - <mat-checkbox fxFlex formControlName="showFilters">
  76 + </mat-slide-toggle>
  77 + <mat-slide-toggle formControlName="showFilters">
64 78 {{ 'dashboard.display-filters' | translate }}
65   - </mat-checkbox>
66   - <mat-checkbox fxFlex formControlName="showDashboardTimewindow">
  79 + </mat-slide-toggle>
  80 + <mat-slide-toggle formControlName="showDashboardTimewindow">
67 81 {{ 'dashboard.display-dashboard-timewindow' | translate }}
68   - </mat-checkbox>
69   - <mat-checkbox fxFlex formControlName="showDashboardExport">
  82 + </mat-slide-toggle>
  83 + <mat-slide-toggle formControlName="showDashboardExport">
70 84 {{ 'dashboard.display-dashboard-export' | translate }}
71   - </mat-checkbox>
72   - <mat-checkbox fxFlex formControlName="showUpdateDashboardImage">
  85 + </mat-slide-toggle>
  86 + <mat-slide-toggle formControlName="showUpdateDashboardImage">
73 87 {{ 'dashboard.display-update-dashboard-image' | translate }}
74   - </mat-checkbox>
75   - </div>
76   - <mat-checkbox formControlName="showDashboardLogo">
77   - {{ 'dashboard.display-dashboard-logo' | translate }}
78   - </mat-checkbox>
79   - <tb-image-input fxFlex *ngIf="settingsFormGroup.get('showDashboardLogo').value"
80   - label="{{'dashboard.dashboard-logo-image' | translate}}"
81   - formControlName="dashboardLogoUrl">
82   - </tb-image-input>
  88 + </mat-slide-toggle>
  89 + </fieldset>
83 90 </div>
84   - <div *ngIf="gridSettings" [formGroup]="gridSettingsFormGroup">
85   - <tb-color-input fxFlex
86   - label="{{'layout.color' | translate}}"
87   - icon="format_color_fill"
88   - openOnInput
89   - formControlName="color">
90   - </tb-color-input>
91   - <mat-form-field class="mat-block">
92   - <mat-label translate>dashboard.columns-count</mat-label>
93   - <input matInput formControlName="columns" type="number" step="any" min="10"
94   - max="1000" required>
95   - <mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('required')">
96   - {{ 'dashboard.columns-count-required' | translate }}
97   - </mat-error>
98   - <mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('min')">
99   - {{ 'dashboard.min-columns-count-message' | translate }}
100   - </mat-error>
101   - <mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('max')">
102   - {{ 'dashboard.max-columns-count-message' | translate }}
103   - </mat-error>
104   - </mat-form-field>
105   - <mat-form-field fxFlex class="mat-block">
106   - <mat-label translate>dashboard.widgets-margins</mat-label>
107   - <input matInput formControlName="margin" type="number" step="any" min="0"
108   - max="50" required>
109   - <mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('required')">
110   - {{ 'dashboard.margin-required' | translate }}
111   - </mat-error>
112   - <mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('min')">
113   - {{ 'dashboard.min-margin-message' | translate }}
114   - </mat-error>
115   - <mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('max')">
116   - {{ 'dashboard.max-margin-message' | translate }}
117   - </mat-error>
118   - </mat-form-field>
119   - <mat-checkbox fxFlex formControlName="autoFillHeight" style="display: block; padding-bottom: 12px;">
120   - {{ 'dashboard.autofill-height' | translate }}
121   - </mat-checkbox>
122   - <tb-color-input fxFlex
123   - label="{{'dashboard.background-color' | translate}}"
124   - icon="format_color_fill"
125   - openOnInput
126   - formControlName="backgroundColor">
127   - </tb-color-input>
128   - <tb-image-input fxFlex
129   - label="{{'dashboard.background-image' | translate}}"
130   - formControlName="backgroundImageUrl">
131   - </tb-image-input>
132   - <mat-form-field class="mat-block">
133   - <mat-label translate>dashboard.background-size-mode</mat-label>
134   - <mat-select formControlName="backgroundSizeMode">
135   - <mat-option value="100%">Fit width</mat-option>
136   - <mat-option value="auto 100%">Fit height</mat-option>
137   - <mat-option value="cover">Cover</mat-option>
138   - <mat-option value="contain">Contain</mat-option>
139   - <mat-option value="auto">Original size</mat-option>
140   - </mat-select>
141   - </mat-form-field>
142   - <small translate>dashboard.mobile-layout</small>
143   - <div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs fxLayoutGap="8px" style="margin-top: 8px">
144   - <mat-checkbox fxFlex formControlName="mobileAutoFillHeight">
  91 + <div *ngIf="gridSettings" [formGroup]="gridSettingsFormGroup" fxLayout="column">
  92 + <fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px">
  93 + <legend class="group-title" translate>dashboard.layout-settings</legend>
  94 + <mat-form-field class="mat-block">
  95 + <mat-label translate>dashboard.columns-count</mat-label>
  96 + <input matInput formControlName="columns" type="number" step="any" min="10"
  97 + max="1000" required>
  98 + <mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('required')">
  99 + {{ 'dashboard.columns-count-required' | translate }}
  100 + </mat-error>
  101 + <mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('min')">
  102 + {{ 'dashboard.min-columns-count-message' | translate }}
  103 + </mat-error>
  104 + <mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('max')">
  105 + {{ 'dashboard.max-columns-count-message' | translate }}
  106 + </mat-error>
  107 + </mat-form-field>
  108 + <mat-form-field fxFlex class="mat-block">
  109 + <mat-label translate>dashboard.widgets-margins</mat-label>
  110 + <input matInput formControlName="margin" type="number" step="any" min="0"
  111 + max="50" required>
  112 + <mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('required')">
  113 + {{ 'dashboard.margin-required' | translate }}
  114 + </mat-error>
  115 + <mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('min')">
  116 + {{ 'dashboard.min-margin-message' | translate }}
  117 + </mat-error>
  118 + <mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('max')">
  119 + {{ 'dashboard.max-margin-message' | translate }}
  120 + </mat-error>
  121 + </mat-form-field>
  122 + <mat-slide-toggle fxFlex formControlName="autoFillHeight" style="display: block; padding-bottom: 12px;">
  123 + {{ 'dashboard.autofill-height' | translate }}
  124 + </mat-slide-toggle>
  125 + <tb-color-input fxFlex
  126 + label="{{'dashboard.background-color' | translate}}"
  127 + icon="format_color_fill"
  128 + openOnInput
  129 + formControlName="backgroundColor">
  130 + </tb-color-input>
  131 + <tb-image-input fxFlex
  132 + label="{{'dashboard.background-image' | translate}}"
  133 + formControlName="backgroundImageUrl">
  134 + </tb-image-input>
  135 + <mat-form-field class="mat-block">
  136 + <mat-label translate>dashboard.background-size-mode</mat-label>
  137 + <mat-select formControlName="backgroundSizeMode">
  138 + <mat-option value="100%">Fit width</mat-option>
  139 + <mat-option value="auto 100%">Fit height</mat-option>
  140 + <mat-option value="cover">Cover</mat-option>
  141 + <mat-option value="contain">Contain</mat-option>
  142 + <mat-option value="auto">Original size</mat-option>
  143 + </mat-select>
  144 + </mat-form-field>
  145 + </fieldset>
  146 + <fieldset class="fields-group" fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start center" fxLayoutGap="8px">
  147 + <legend class="group-title" translate>dashboard.mobile-layout</legend>
  148 + <mat-slide-toggle fxFlex formControlName="mobileAutoFillHeight">
145 149 {{ 'dashboard.autofill-height' | translate }}
146   - </mat-checkbox>
  150 + </mat-slide-toggle>
147 151 <mat-form-field fxFlex class="mat-block">
148 152 <mat-label translate>dashboard.mobile-row-height</mat-label>
149 153 <input matInput formControlName="mobileRowHeight" type="number" step="any" min="5"
... ... @@ -158,7 +162,7 @@
158 162 {{ 'dashboard.max-mobile-row-height-message' | translate }}
159 163 </mat-error>
160 164 </mat-form-field>
161   - </div>
  165 + </fieldset>
162 166 </div>
163 167 </fieldset>
164 168 </div>
... ...
... ... @@ -14,13 +14,19 @@
14 14 * limitations under the License.
15 15 */
16 16 :host {
17   - small {
18   - white-space: normal;
  17 + .fields-group {
  18 + padding: 0 8px 8px;
  19 + margin: 10px 0;
  20 + border: 1px groove rgba(0, 0, 0, .25);
  21 + border-radius: 4px;
  22 + legend {
  23 + color: rgba(0, 0, 0, .7);
  24 + }
19 25 }
20 26 }
21 27
22 28 :host ::ng-deep {
23   - .mat-checkbox-label {
  29 + .mat-slide-toggle-content {
24 30 white-space: normal;
25 31 }
26 32 }
... ...
... ... @@ -71,19 +71,33 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
71 71 this.gridSettings = this.data.gridSettings;
72 72
73 73 if (this.settings) {
  74 + const showTitle = isUndefined(this.settings.showTitle) ? true : this.settings.showTitle;
  75 + const showDashboardLogo = isUndefined(this.settings.showDashboardLogo) ? false : this.settings.showDashboardLogo;
  76 + const hideToolbar = isUndefined(this.settings.hideToolbar) ? false : this.settings.hideToolbar;
74 77 this.settingsFormGroup = this.fb.group({
75 78 stateControllerId: [isUndefined(this.settings.stateControllerId) ? 'entity' : this.settings.stateControllerId, []],
76   - toolbarAlwaysOpen: [isUndefined(this.settings.toolbarAlwaysOpen) ? true : this.settings.toolbarAlwaysOpen, []],
77   - showTitle: [isUndefined(this.settings.showTitle) ? true : this.settings.showTitle, []],
78   - titleColor: [isUndefined(this.settings.titleColor) ? 'rgba(0,0,0,0.870588)' : this.settings.titleColor, []],
79   - showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []],
80   - showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []],
81   - showFilters: [isUndefined(this.settings.showFilters) ? true : this.settings.showFilters, []],
82   - showDashboardLogo: [isUndefined(this.settings.showDashboardLogo) ? false : this.settings.showDashboardLogo, []],
83   - dashboardLogoUrl: [isUndefined(this.settings.dashboardLogoUrl) ? null : this.settings.dashboardLogoUrl, []],
84   - showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []],
85   - showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []],
86   - showUpdateDashboardImage: [isUndefined(this.settings.showUpdateDashboardImage) ? true : this.settings.showUpdateDashboardImage, []]
  79 + showTitle: [showTitle, []],
  80 + titleColor: [{value: isUndefined(this.settings.titleColor) ? 'rgba(0,0,0,0.870588)' : this.settings.titleColor,
  81 + disabled: !showTitle}, []],
  82 + showDashboardLogo: [showDashboardLogo, []],
  83 + dashboardLogoUrl: [{value: isUndefined(this.settings.dashboardLogoUrl) ? null : this.settings.dashboardLogoUrl,
  84 + disabled: !showDashboardLogo}, []],
  85 + hideToolbar: [hideToolbar, []],
  86 + toolbarAlwaysOpen: [{value: isUndefined(this.settings.toolbarAlwaysOpen) ? true : this.settings.toolbarAlwaysOpen,
  87 + disabled: hideToolbar}, []],
  88 + showDashboardsSelect: [{value: isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect,
  89 + disabled: hideToolbar}, []],
  90 + showEntitiesSelect: [{value: isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect,
  91 + disabled: hideToolbar}, []],
  92 + showFilters: [{value: isUndefined(this.settings.showFilters) ? true : this.settings.showFilters,
  93 + disabled: hideToolbar}, []],
  94 + showDashboardTimewindow: [{value: isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow,
  95 + disabled: hideToolbar}, []],
  96 + showDashboardExport: [{value: isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport,
  97 + disabled: hideToolbar}, []],
  98 + showUpdateDashboardImage: [
  99 + {value: isUndefined(this.settings.showUpdateDashboardImage) ? true : this.settings.showUpdateDashboardImage,
  100 + disabled: hideToolbar}, []]
87 101 });
88 102 this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe(
89 103 (stateControllerId: StateControllerId) => {
... ... @@ -92,13 +106,52 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
92 106 }
93 107 }
94 108 );
  109 + this.settingsFormGroup.get('showTitle').valueChanges.subscribe(
  110 + (showTitleValue: boolean) => {
  111 + if (showTitleValue) {
  112 + this.settingsFormGroup.get('titleColor').enable();
  113 + } else {
  114 + this.settingsFormGroup.get('titleColor').disable();
  115 + }
  116 + }
  117 + );
  118 + this.settingsFormGroup.get('showDashboardLogo').valueChanges.subscribe(
  119 + (showDashboardLogoValue: boolean) => {
  120 + if (showDashboardLogoValue) {
  121 + this.settingsFormGroup.get('dashboardLogoUrl').enable();
  122 + } else {
  123 + this.settingsFormGroup.get('dashboardLogoUrl').disable();
  124 + }
  125 + }
  126 + );
  127 + this.settingsFormGroup.get('hideToolbar').valueChanges.subscribe(
  128 + (hideToolbarValue: boolean) => {
  129 + if (hideToolbarValue) {
  130 + this.settingsFormGroup.get('toolbarAlwaysOpen').disable();
  131 + this.settingsFormGroup.get('showDashboardsSelect').disable();
  132 + this.settingsFormGroup.get('showEntitiesSelect').disable();
  133 + this.settingsFormGroup.get('showFilters').disable();
  134 + this.settingsFormGroup.get('showDashboardTimewindow').disable();
  135 + this.settingsFormGroup.get('showDashboardExport').disable();
  136 + this.settingsFormGroup.get('showUpdateDashboardImage').disable();
  137 + } else {
  138 + this.settingsFormGroup.get('toolbarAlwaysOpen').enable();
  139 + this.settingsFormGroup.get('showDashboardsSelect').enable();
  140 + this.settingsFormGroup.get('showEntitiesSelect').enable();
  141 + this.settingsFormGroup.get('showFilters').enable();
  142 + this.settingsFormGroup.get('showDashboardTimewindow').enable();
  143 + this.settingsFormGroup.get('showDashboardExport').enable();
  144 + this.settingsFormGroup.get('showUpdateDashboardImage').enable();
  145 + }
  146 + }
  147 + );
95 148 } else {
96 149 this.settingsFormGroup = this.fb.group({});
97 150 }
98 151
99 152 if (this.gridSettings) {
  153 + const mobileAutoFillHeight = isUndefined(this.gridSettings.mobileAutoFillHeight) ? false : this.gridSettings.mobileAutoFillHeight;
100 154 this.gridSettingsFormGroup = this.fb.group({
101   - color: [this.gridSettings.color || 'rgba(0,0,0,0.870588)', []],
102 155 columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1000)]],
103 156 margin: [isDefined(this.gridSettings.margin) ? this.gridSettings.margin : 10,
104 157 [Validators.required, Validators.min(0), Validators.max(50)]],
... ... @@ -106,10 +159,19 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
106 159 backgroundColor: [this.gridSettings.backgroundColor || 'rgba(0,0,0,0)', []],
107 160 backgroundImageUrl: [this.gridSettings.backgroundImageUrl, []],
108 161 backgroundSizeMode: [this.gridSettings.backgroundSizeMode || '100%', []],
109   - mobileAutoFillHeight: [isUndefined(this.gridSettings.mobileAutoFillHeight) ? false : this.gridSettings.mobileAutoFillHeight, []],
110   - mobileRowHeight: [isUndefined(this.gridSettings.mobileRowHeight) ? 70 : this.gridSettings.mobileRowHeight,
111   - [Validators.required, Validators.min(5), Validators.max(200)]]
  162 + mobileAutoFillHeight: [mobileAutoFillHeight, []],
  163 + mobileRowHeight: [{ value: isUndefined(this.gridSettings.mobileRowHeight) ? 70 : this.gridSettings.mobileRowHeight,
  164 + disabled: mobileAutoFillHeight}, [Validators.required, Validators.min(5), Validators.max(200)]]
112 165 });
  166 + this.gridSettingsFormGroup.get('mobileAutoFillHeight').valueChanges.subscribe(
  167 + (mobileAutoFillHeightValue: boolean) => {
  168 + if (mobileAutoFillHeightValue) {
  169 + this.gridSettingsFormGroup.get('mobileRowHeight').disable();
  170 + } else {
  171 + this.gridSettingsFormGroup.get('mobileRowHeight').enable();
  172 + }
  173 + }
  174 + );
113 175 } else {
114 176 this.gridSettingsFormGroup = this.fb.group({});
115 177 }
... ... @@ -133,10 +195,10 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
133 195 let settings: DashboardSettings = null;
134 196 let gridSettings: GridSettings = null;
135 197 if (this.settings) {
136   - settings = {...this.settings, ...this.settingsFormGroup.value};
  198 + settings = {...this.settings, ...this.settingsFormGroup.getRawValue()};
137 199 }
138 200 if (this.gridSettings) {
139   - gridSettings = {...this.gridSettings, ...this.gridSettingsFormGroup.value};
  201 + gridSettings = {...this.gridSettings, ...this.gridSettingsFormGroup.getRawValue()};
140 202 }
141 203 this.dialogRef.close({settings, gridSettings});
142 204 }
... ...
... ... @@ -26,7 +26,6 @@
26 26 [style.backgroundImage]="backgroundImage"
27 27 [ngStyle]="dashboardStyle">
28 28 <section *ngIf="layoutCtx.widgets.isEmpty()" fxLayoutAlign="center center"
29   - [ngStyle]="{'color': layoutCtx.gridSettings.color}"
30 29 style="display: flex; z-index: 1; pointer-events: none;"
31 30 class="mat-headline tb-absolute-fill">
32 31 <span *ngIf="!isEdit">
... ...
1   -/**
2   - * Copyright © 2016-2021 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -:host ::ng-deep {
17   - textarea.mat-input-element.cdk-textarea-autosize {
18   - box-sizing: content-box;
19   - }
20   -}
... ... @@ -41,7 +41,7 @@ import { Subject } from 'rxjs';
41 41 @Component({
42 42 selector: 'tb-security-config-lwm2m-server',
43 43 templateUrl: './security-config-lwm2m-server.component.html',
44   - styleUrls: ['./security-config-lwm2m-server.component.scss'],
  44 + styleUrls: [],
45 45 providers: [
46 46 {
47 47 provide: NG_VALUE_ACCESSOR,
... ...
... ... @@ -27,8 +27,4 @@
27 27 .mat-tab-body {
28 28 padding: 16px 0;
29 29 }
30   -
31   - textarea.mat-input-element.cdk-textarea-autosize {
32   - box-sizing: content-box;
33   - }
34 30 }
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, forwardRef, Input, OnInit } from '@angular/core';
  17 +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
18 18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@app/core/core.state';
... ... @@ -33,6 +33,8 @@ import {
33 33 transportPayloadTypeTranslationMap,
34 34 } from '@shared/models/device.models';
35 35 import { isDefinedAndNotNull } from '@core/utils';
  36 +import { Subject } from 'rxjs';
  37 +import { takeUntil } from 'rxjs/operators';
36 38
37 39 @Component({
38 40 selector: 'tb-coap-device-profile-transport-configuration',
... ... @@ -44,7 +46,7 @@ import { isDefinedAndNotNull } from '@core/utils';
44 46 multi: true
45 47 }]
46 48 })
47   -export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
  49 +export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy {
48 50
49 51 coapTransportDeviceTypes = Object.keys(CoapTransportDeviceType);
50 52
... ... @@ -56,6 +58,7 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control
56 58
57 59 coapDeviceProfileTransportConfigurationFormGroup: FormGroup;
58 60
  61 + private destroy$ = new Subject();
59 62 private requiredValue: boolean;
60 63
61 64 private transportPayloadTypeConfiguration = this.fb.group({
... ... @@ -99,15 +102,23 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control
99 102 })
100 103 }
101 104 );
102   - this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType')
103   - .valueChanges.subscribe(coapDeviceType => {
  105 + this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').valueChanges.pipe(
  106 + takeUntil(this.destroy$)
  107 + ).subscribe(coapDeviceType => {
104 108 this.updateCoapDeviceTypeBasedControls(coapDeviceType, true);
105 109 });
106   - this.coapDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
  110 + this.coapDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe(
  111 + takeUntil(this.destroy$)
  112 + ).subscribe(() => {
107 113 this.updateModel();
108 114 });
109 115 }
110 116
  117 + ngOnDestroy() {
  118 + this.destroy$.next();
  119 + this.destroy$.complete();
  120 + }
  121 +
111 122 get coapDeviceTypeDefault(): boolean {
112 123 const coapDeviceType = this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').value;
113 124 return coapDeviceType === CoapTransportDeviceType.DEFAULT;
... ...
... ... @@ -14,13 +14,15 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, forwardRef, Input, OnInit } from '@angular/core';
  17 +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
18 18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@app/core/core.state';
21 21 import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 22 import { DeviceProfileConfiguration, DeviceProfileType } from '@shared/models/device.models';
23 23 import { deepClone } from '@core/utils';
  24 +import { Subject } from 'rxjs';
  25 +import { takeUntil } from 'rxjs/operators';
24 26
25 27 @Component({
26 28 selector: 'tb-device-profile-configuration',
... ... @@ -32,12 +34,14 @@ import { deepClone } from '@core/utils';
32 34 multi: true
33 35 }]
34 36 })
35   -export class DeviceProfileConfigurationComponent implements ControlValueAccessor, OnInit {
  37 +export class DeviceProfileConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy {
36 38
37 39 deviceProfileType = DeviceProfileType;
38 40
39 41 deviceProfileConfigurationFormGroup: FormGroup;
40 42
  43 + private destroy$ = new Subject();
  44 +
41 45 private requiredValue: boolean;
42 46 get required(): boolean {
43 47 return this.requiredValue;
... ... @@ -69,11 +73,18 @@ export class DeviceProfileConfigurationComponent implements ControlValueAccessor
69 73 this.deviceProfileConfigurationFormGroup = this.fb.group({
70 74 configuration: [null, Validators.required]
71 75 });
72   - this.deviceProfileConfigurationFormGroup.valueChanges.subscribe(() => {
  76 + this.deviceProfileConfigurationFormGroup.valueChanges.pipe(
  77 + takeUntil(this.destroy$)
  78 + ).subscribe(() => {
73 79 this.updateModel();
74 80 });
75 81 }
76 82
  83 + ngOnDestroy() {
  84 + this.destroy$.next();
  85 + this.destroy$.complete();
  86 + }
  87 +
77 88 setDisabledState(isDisabled: boolean): void {
78 89 this.disabled = isDisabled;
79 90 if (this.disabled) {
... ...
... ... @@ -15,112 +15,107 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section [formGroup]="serverFormGroup" style="min-width: 400px;">
19   - <div class="mat-padding">
20   - <div fxLayout="column">
21   - <div fxLayout="row" fxLayoutGap="8px">
22   - <mat-form-field class="mat-block">
23   - <mat-label>{{ 'device-profile.lwm2m.mode' | translate }}</mat-label>
24   - <mat-select formControlName="securityMode">
25   - <mat-option *ngFor="let securityMode of securityConfigLwM2MTypes"
26   - [value]="securityMode">
27   - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }}
28   - </mat-option>
29   - </mat-select>
30   - </mat-form-field>
31   - <mat-form-field class="mat-block">
32   - <mat-label>{{ 'device-profile.lwm2m.server-host' | translate }}</mat-label>
33   - <input matInput type="text" formControlName="host" required
34   - matTooltip="{{'device-profile.lwm2m.server-host-tip' | translate}}"
35   - matTooltipPosition="above">
36   - <mat-error *ngIf="serverFormGroup.get('host').hasError('required')">
37   - {{ 'device-profile.lwm2m.server-host' | translate }}
38   - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
39   - </mat-error>
40   - </mat-form-field>
41   - <mat-form-field class="mat-block">
42   - <mat-label>{{ 'device-profile.lwm2m.server-port' | translate }}</mat-label>
43   - <input matInput type="number" formControlName="port" required
44   - matTooltip="{{'device-profile.lwm2m.server-port-tip' | translate}}"
45   - matTooltipPosition="above">
46   - <mat-error *ngIf="serverFormGroup.get('port').hasError('required')">
47   - {{ 'device-profile.lwm2m.server-port' | translate }}
48   - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
49   - </mat-error>
50   - </mat-form-field>
51   - <mat-form-field class="mat-block">
52   - <mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label>
53   - <input matInput type="number" formControlName="serverId" required
54   - matTooltip="{{'device-profile.lwm2m.short-id-tip' | translate}}"
55   - matTooltipPosition="above">
56   - <mat-error *ngIf="serverFormGroup.get('serverId').hasError('required')">
57   - {{ 'device-profile.lwm2m.short-id' | translate }}
58   - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
59   - </mat-error>
60   - </mat-form-field>
61   - </div>
62   - </div>
63   - <div fxLayout="column">
64   - <div fxLayout="row" fxLayoutGap="10px">
65   - <mat-form-field class="mat-block">
66   - <mat-label>{{ 'device-profile.lwm2m.client-hold-off-time' | translate }}</mat-label>
67   - <input matInput type="number" formControlName="clientHoldOffTime" required
68   - matTooltip="{{'device-profile.lwm2m.client-hold-off-time-tip' | translate}}"
69   - matTooltipPosition="above">
70   - <mat-error *ngIf="serverFormGroup.get('clientHoldOffTime').hasError('required')">
71   - {{ 'device-profile.lwm2m.client-hold-off-time' | translate }}
72   - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
73   - </mat-error>
74   - </mat-form-field>
75   - <mat-form-field class="mat-block">
76   - <mat-label>{{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }}</mat-label>
77   - <input matInput type="number" formControlName="bootstrapServerAccountTimeout" required
78   - matTooltip="{{'device-profile.lwm2m.bootstrap-server-account-timeout-tip' | translate}}"
79   - matTooltipPosition="above">
80   - <mat-error *ngIf="serverFormGroup.get('bootstrapServerAccountTimeout').hasError('required')">
81   - {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }}
82   - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
83   - </mat-error>
84   - </mat-form-field>
85   - <mat-checkbox formControlName="bootstrapServerIs" color="primary">
86   - {{ 'device-profile.lwm2m.bootstrap-server' | translate }}
87   - </mat-checkbox>
88   - </div>
89   - <div *ngIf="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
90   - serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509">
91   - <mat-form-field class="mat-block">
92   - <mat-label>{{ 'device-profile.lwm2m.server-public-key' | translate }}</mat-label>
93   - <textarea matInput
94   - #serverPublicKey
95   - maxlength="{{lenMaxServerPublicKey}}"
96   - cdkTextareaAutosize
97   - cdkAutosizeMinRows="1"
98   - cols="1" required
99   - style="overflow:hidden"
100   - formControlName="serverPublicKey"
101   - matTooltip="{{'device-profile.lwm2m.server-public-key-tip' | translate}}"
102   - ></textarea>
103   - <mat-hint align="end">{{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}}</mat-hint>
104   - <mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('required')">
105   - {{ 'device-profile.lwm2m.server-public-key' | translate }}
106   - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
107   - </mat-error>
108   - <mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('pattern') &&
109   - (serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
110   - serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509)">
111   - {{ 'device-profile.lwm2m.server-public-key' | translate }}
112   - <strong>{{ 'device-profile.lwm2m.pattern_hex_dec' | translate: {
113   - count: 0} }}</strong>
114   - </mat-error>
115   - <mat-error *ngIf="(serverFormGroup.get('serverPublicKey').hasError('maxlength') ||
116   - serverFormGroup.get('serverPublicKey').hasError('minlength')) &&
117   - serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK">
118   - {{ 'device-profile.lwm2m.server-public-key' | translate }}
119   - <strong>{{ 'device-profile.lwm2m.pattern_hex_dec' | translate: {
120   - count: lenMaxServerPublicKey } }}</strong>
121   - </mat-error>
122   - </mat-form-field>
123   - </div>
124   - </div>
  18 +<section [formGroup]="serverFormGroup">
  19 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px" fxLayoutGap.xs="0px">
  20 + <mat-form-field fxFlex>
  21 + <mat-label>{{ 'device-profile.lwm2m.mode' | translate }}</mat-label>
  22 + <mat-select formControlName="securityMode">
  23 + <mat-option *ngFor="let securityMode of securityConfigLwM2MTypes"
  24 + [value]="securityMode">
  25 + {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }}
  26 + </mat-option>
  27 + </mat-select>
  28 + </mat-form-field>
  29 + <mat-form-field fxFlex>
  30 + <mat-label>{{ 'device-profile.lwm2m.server-host' | translate }}</mat-label>
  31 + <input matInput type="text" formControlName="host" required
  32 + matTooltip="{{'device-profile.lwm2m.server-host-tip' | translate}}"
  33 + matTooltipPosition="above">
  34 + <mat-error *ngIf="serverFormGroup.get('host').hasError('required')">
  35 + {{ 'device-profile.lwm2m.server-host' | translate }}
  36 + <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
  37 + </mat-error>
  38 + </mat-form-field>
  39 + <mat-form-field fxFlex>
  40 + <mat-label>{{ 'device-profile.lwm2m.server-port' | translate }}</mat-label>
  41 + <input matInput type="number" formControlName="port" required
  42 + matTooltip="{{'device-profile.lwm2m.server-port-tip' | translate}}"
  43 + matTooltipPosition="above">
  44 + <mat-error *ngIf="serverFormGroup.get('port').hasError('required')">
  45 + {{ 'device-profile.lwm2m.server-port' | translate }}
  46 + <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
  47 + </mat-error>
  48 + </mat-form-field>
  49 + <mat-form-field fxFlex>
  50 + <mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label>
  51 + <input matInput type="number" formControlName="serverId" required
  52 + matTooltip="{{'device-profile.lwm2m.short-id-tip' | translate}}"
  53 + matTooltipPosition="above">
  54 + <mat-error *ngIf="serverFormGroup.get('serverId').hasError('required')">
  55 + {{ 'device-profile.lwm2m.short-id' | translate }}
  56 + <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
  57 + </mat-error>
  58 + </mat-form-field>
  59 + </div>
  60 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px" fxLayoutGap.xs="0px">
  61 + <mat-form-field fxFlex>
  62 + <mat-label>{{ 'device-profile.lwm2m.client-hold-off-time' | translate }}</mat-label>
  63 + <input matInput type="number" formControlName="clientHoldOffTime" required
  64 + matTooltip="{{'device-profile.lwm2m.client-hold-off-time-tip' | translate}}"
  65 + matTooltipPosition="above">
  66 + <mat-error *ngIf="serverFormGroup.get('clientHoldOffTime').hasError('required')">
  67 + {{ 'device-profile.lwm2m.client-hold-off-time' | translate }}
  68 + <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
  69 + </mat-error>
  70 + </mat-form-field>
  71 + <mat-form-field fxFlex>
  72 + <mat-label>{{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }}</mat-label>
  73 + <input matInput type="number" formControlName="bootstrapServerAccountTimeout" required
  74 + matTooltip="{{'device-profile.lwm2m.bootstrap-server-account-timeout-tip' | translate}}"
  75 + matTooltipPosition="above">
  76 + <mat-error *ngIf="serverFormGroup.get('bootstrapServerAccountTimeout').hasError('required')">
  77 + {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }}
  78 + <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
  79 + </mat-error>
  80 + </mat-form-field>
  81 + <mat-checkbox fxFlex formControlName="bootstrapServerIs" color="primary">
  82 + {{ 'device-profile.lwm2m.bootstrap-server' | translate }}
  83 + </mat-checkbox>
  84 + <div fxFlex></div>
  85 + </div>
  86 + <div *ngIf="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
  87 + serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509">
  88 + <mat-form-field class="mat-block">
  89 + <mat-label>{{ 'device-profile.lwm2m.server-public-key' | translate }}</mat-label>
  90 + <textarea matInput
  91 + #serverPublicKey
  92 + maxlength="{{lenMaxServerPublicKey}}"
  93 + cdkTextareaAutosize
  94 + cdkAutosizeMinRows="1"
  95 + cols="1" required
  96 + style="overflow:hidden"
  97 + formControlName="serverPublicKey"
  98 + matTooltip="{{'device-profile.lwm2m.server-public-key-tip' | translate}}"
  99 + ></textarea>
  100 + <mat-hint align="end">{{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}}</mat-hint>
  101 + <mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('required')">
  102 + {{ 'device-profile.lwm2m.server-public-key' | translate }}
  103 + <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
  104 + </mat-error>
  105 + <mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('pattern') &&
  106 + (serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
  107 + serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509)">
  108 + {{ 'device-profile.lwm2m.server-public-key' | translate }}
  109 + <strong>{{ 'device-profile.lwm2m.pattern_hex_dec' | translate: {
  110 + count: 0} }}</strong>
  111 + </mat-error>
  112 + <mat-error *ngIf="(serverFormGroup.get('serverPublicKey').hasError('maxlength') ||
  113 + serverFormGroup.get('serverPublicKey').hasError('minlength')) &&
  114 + serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK">
  115 + {{ 'device-profile.lwm2m.server-public-key' | translate }}
  116 + <strong>{{ 'device-profile.lwm2m.pattern_hex_dec' | translate: {
  117 + count: lenMaxServerPublicKey } }}</strong>
  118 + </mat-error>
  119 + </mat-form-field>
125 120 </div>
126 121 </section>
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import { DeviceProfileTransportConfiguration } from '@shared/models/device.models';
18   -import { Component, forwardRef, Input } from '@angular/core';
  18 +import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
19 19 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
20 20 import { coerceBooleanProperty } from '@angular/cdk/coercion';
21 21 import {
... ... @@ -39,6 +39,8 @@ import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/util
39 39 import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point';
40 40 import { Direction } from '@shared/models/page/sort-order';
41 41 import _ from 'lodash';
  42 +import { Subject } from 'rxjs';
  43 +import { takeUntil } from 'rxjs/operators';
42 44
43 45 @Component({
44 46 selector: 'tb-profile-lwm2m-device-transport-configuration',
... ... @@ -49,11 +51,12 @@ import _ from 'lodash';
49 51 multi: true
50 52 }]
51 53 })
52   -export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators {
  54 +export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators, OnDestroy {
53 55
54 56 private configurationValue: Lwm2mProfileConfigModels;
55 57 private requiredValue: boolean;
56 58 private disabled = false;
  59 + private destroy$ = new Subject();
57 60
58 61 bindingModeType = BINDING_MODE;
59 62 bindingModeTypes = Object.keys(BINDING_MODE);
... ... @@ -86,7 +89,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
86 89 lifetime: [null, Validators.required],
87 90 defaultMinPeriod: [null, Validators.required],
88 91 notifIfDisabled: [true, []],
89   - binding:[],
  92 + binding: [],
90 93 bootstrapServer: [null, Validators.required],
91 94 lwm2mServer: [null, Validators.required],
92 95 clientStrategy: [1, []],
... ... @@ -96,10 +99,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
96 99 this.lwm2mDeviceConfigFormGroup = this.fb.group({
97 100 configurationJson: [null, Validators.required]
98 101 });
99   - this.lwm2mDeviceProfileFormGroup.valueChanges.subscribe((value) => {
  102 + this.lwm2mDeviceProfileFormGroup.valueChanges.pipe(
  103 + takeUntil(this.destroy$)
  104 + ).subscribe((value) => {
100 105 this.updateDeviceProfileValue(value);
101 106 });
102   - this.lwm2mDeviceConfigFormGroup.valueChanges.subscribe(() => {
  107 + this.lwm2mDeviceConfigFormGroup.valueChanges.pipe(
  108 + takeUntil(this.destroy$)
  109 + ).subscribe(() => {
103 110 this.updateModel();
104 111 });
105 112 this.sortFunction = this.sortObjectKeyPathJson;
... ... @@ -112,6 +119,11 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
112 119 registerOnTouched(fn: any): void {
113 120 }
114 121
  122 + ngOnDestroy() {
  123 + this.destroy$.next();
  124 + this.destroy$.complete();
  125 + }
  126 +
115 127 setDisabledState(isDisabled: boolean): void {
116 128 this.disabled = isDisabled;
117 129 if (isDisabled) {
... ... @@ -124,11 +136,17 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
124 136 }
125 137
126 138 writeValue(value: Lwm2mProfileConfigModels | null): void {
127   - this.configurationValue = (Object.keys(value).length === 0) ? getDefaultProfileConfig() : value;
128   - this.lwm2mDeviceConfigFormGroup.patchValue({
129   - configurationJson: this.configurationValue
130   - }, {emitEvent: false});
131   - this.initWriteValue();
  139 + if (isDefinedAndNotNull(value)) {
  140 + if (Object.keys(value).length !== 0 && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) {
  141 + this.configurationValue = value;
  142 + } else {
  143 + this.configurationValue = getDefaultProfileConfig();
  144 + }
  145 + this.lwm2mDeviceConfigFormGroup.patchValue({
  146 + configurationJson: this.configurationValue
  147 + }, {emitEvent: false});
  148 + this.initWriteValue();
  149 + }
132 150 }
133 151
134 152 private initWriteValue = (): void => {
... ... @@ -257,7 +275,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
257 275 instanceUpdate.id = instanceId;
258 276 instanceUpdate.resources.forEach(resource => {
259 277 resource.keyName = _.camelCase(resource.name + instanceUpdate.id);
260   - })
  278 + });
261 279 return instanceUpdate;
262 280 }
263 281
... ...
... ... @@ -15,10 +15,19 @@
15 15 ///
16 16
17 17 import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core';
18   -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormGroup,
  22 + NG_VALIDATORS,
  23 + NG_VALUE_ACCESSOR,
  24 + ValidationErrors,
  25 + Validator,
  26 + Validators
  27 +} from '@angular/forms';
19 28 import { coerceBooleanProperty } from '@angular/cdk/coercion';
20 29 import { Observable } from 'rxjs';
21   -import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators';
  30 +import { distinctUntilChanged, filter, mergeMap, share, tap } from 'rxjs/operators';
22 31 import { ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT } from './lwm2m-profile-config.models';
23 32 import { DeviceProfileService } from '@core/http/device-profile.service';
24 33 import { Direction } from '@shared/models/page/sort-order';
... ... @@ -33,13 +42,18 @@ import { PageLink } from '@shared/models/page/page-link';
33 42 provide: NG_VALUE_ACCESSOR,
34 43 useExisting: forwardRef(() => Lwm2mObjectListComponent),
35 44 multi: true
36   - }]
  45 + },
  46 + {
  47 + provide: NG_VALIDATORS,
  48 + useExisting: forwardRef(() => Lwm2mObjectListComponent),
  49 + multi: true
  50 + }
  51 + ]
37 52 })
38   -export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validators {
  53 +export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validator {
39 54
40 55 private requiredValue: boolean;
41 56 private dirty = false;
42   - private lw2mModels: Observable<Array<ObjectLwM2M>>;
43 57 private modelValue: Array<string> = [];
44 58
45 59 lwm2mListFormGroup: FormGroup;
... ... @@ -78,8 +92,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
78 92 }
79 93
80 94 private updateValidators = (): void => {
81   - this.lwm2mListFormGroup.get('objectLwm2m').setValidators(this.required ? [Validators.required] : []);
82   - this.lwm2mListFormGroup.get('objectLwm2m').updateValueAndValidity();
  95 + this.lwm2mListFormGroup.get('objectsList').setValidators(this.required ? [Validators.required] : []);
  96 + this.lwm2mListFormGroup.get('objectsList').updateValueAndValidity();
83 97 }
84 98
85 99 registerOnChange(fn: any): void {
... ... @@ -92,6 +106,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
92 106 ngOnInit() {
93 107 this.filteredObjectsList = this.lwm2mListFormGroup.get('objectLwm2m').valueChanges
94 108 .pipe(
  109 + distinctUntilChanged(),
95 110 tap((value) => {
96 111 if (value && typeof value !== 'string') {
97 112 this.add(value);
... ... @@ -100,7 +115,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
100 115 }
101 116 }),
102 117 filter(searchText => isString(searchText)),
103   - mergeMap(searchText => this.fetchListObjects(searchText))
  118 + mergeMap(searchText => this.fetchListObjects(searchText)),
  119 + share()
104 120 );
105 121 }
106 122
... ... @@ -131,6 +147,12 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
131 147 }
132 148 }
133 149
  150 + validate(): ValidationErrors | null {
  151 + return this.lwm2mListFormGroup.valid ? null : {
  152 + lwm2mListObj: false
  153 + };
  154 + }
  155 +
134 156 private add(object: ObjectLwM2M): void {
135 157 if (isDefinedAndNotNull(this.modelValue) && this.modelValue.indexOf(object.keyId) === -1) {
136 158 this.modelValue.push(object.keyId);
... ... @@ -157,23 +179,13 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
157 179 return object ? object.name : undefined;
158 180 }
159 181
160   - private fetchListObjects = (searchText?: string): Observable<Array<ObjectLwM2M>> => {
  182 + private fetchListObjects = (searchText: string): Observable<Array<ObjectLwM2M>> => {
161 183 this.searchText = searchText;
162   - return this.getLwM2mModelsPage().pipe(
163   - map(objectLwM2Ms => objectLwM2Ms)
164   - );
165   - }
166   -
167   - private getLwM2mModelsPage(): Observable<Array<ObjectLwM2M>> {
168 184 const pageLink = new PageLink(PAGE_SIZE_LIMIT, 0, this.searchText, {
169 185 property: 'id',
170 186 direction: Direction.ASC
171 187 });
172   - this.lw2mModels = this.deviceProfileService.getLwm2mObjectsPage(pageLink).pipe(
173   - publishReplay(1),
174   - refCount()
175   - );
176   - return this.lw2mModels;
  188 + return this.deviceProfileService.getLwm2mObjectsPage(pageLink);
177 189 }
178 190
179 191 onFocus = (): void => {
... ... @@ -183,10 +195,9 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
183 195 }
184 196 }
185 197
186   - private clear = (value: string = ''): void => {
187   - this.objectInput.nativeElement.value = value;
  198 + private clear = (): void => {
188 199 this.searchText = '';
189   - this.lwm2mListFormGroup.get('objectLwm2m').patchValue(value);
  200 + this.lwm2mListFormGroup.get('objectLwm2m').patchValue(null);
190 201 setTimeout(() => {
191 202 this.objectInput.nativeElement.blur();
192 203 this.objectInput.nativeElement.focus();
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section [formGroup]="resourceFormGroup" class="mat-padding">
  18 +<section [formGroup]="resourceFormGroup">
19 19 <div fxLayout="row" fxFill formArrayName="resources"
20 20 *ngFor="let resourceLwM2M of resourceFormArray.controls; let i = index; trackBy: trackByParams">
21 21 <div class="vertical-padding" fxLayout="column" fxFill [formGroupName]="i">
... ... @@ -46,14 +46,14 @@
46 46 </div>
47 47 <div fxFlex="10" fxLayoutAlign="center center">
48 48 <mat-checkbox formControlName="attribute" color="warn"
49   - [checked]="updateObserve(i)"
  49 + (change)="updateObserve(i)"
50 50 matTooltip="{{'device-profile.lwm2m.is-attr-tip' | translate}}"
51 51 matTooltipPosition="above">
52 52 </mat-checkbox>
53 53 </div>
54 54 <div fxFlex="10" fxLayoutAlign="center center">
55 55 <mat-checkbox formControlName="telemetry" color="primary"
56   - [checked]="updateObserve(i)"
  56 + (change)="updateObserve(i)"
57 57 matTooltip="{{'device-profile.lwm2m.is-telemetry-tip' | translate}}"
58 58 matTooltipPosition="above">
59 59 </mat-checkbox>
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 -->
18 18 <section [formGroup]="observeAttrTelemetryFormGroup">
19   - <mat-accordion multi="true" class="mat-body-1" formArrayName="clientLwM2M">
  19 + <mat-accordion multi="true" formArrayName="clientLwM2M">
20 20 <mat-expansion-panel
21 21 *ngFor="let objectLwM2M of clientLwM2MFormArray.controls; let i = index;"
22 22 [formGroupName]="i">
... ...
... ... @@ -24,6 +24,9 @@
24 24 }
25 25
26 26 :host{
  27 + section {
  28 + padding: 2px;
  29 + }
27 30 .instance-list {
28 31 mat-expansion-panel-header {
29 32 color: inherit;
... ...
... ... @@ -64,7 +64,7 @@ export const BINDING_MODE_NAMES = new Map<BINDING_MODE, string>(
64 64 [BINDING_MODE.UQ, 'UQ: UDP connection in queue mode'],
65 65 [BINDING_MODE.US, 'US: both UDP and SMS connections active, both in standard mode'],
66 66 [BINDING_MODE.UQS, 'UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode'],
67   - [BINDING_MODE.T,'T: TCP connection in standard mode'],
  67 + [BINDING_MODE.T, 'T: TCP connection in standard mode'],
68 68 [BINDING_MODE.TQ, 'TQ: TCP connection in queue mode'],
69 69 [BINDING_MODE.TS, 'TS: both TCP and SMS connections active, both in standard mode'],
70 70 [BINDING_MODE.TQS, 'TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode'],
... ... @@ -162,7 +162,6 @@ export interface Lwm2mProfileConfigModels {
162 162 clientLwM2mSettings: ClientLwM2mSettings;
163 163 observeAttr: ObservableAttributes;
164 164 bootstrap: BootstrapSecurityConfig;
165   -
166 165 }
167 166
168 167 export interface ClientLwM2mSettings {
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, forwardRef, Input, OnInit } from '@angular/core';
  17 +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
18 18 import {
19 19 ControlValueAccessor,
20 20 FormBuilder,
... ... @@ -39,6 +39,8 @@ import {
39 39 transportPayloadTypeTranslationMap
40 40 } from '@shared/models/device.models';
41 41 import { isDefinedAndNotNull } from '@core/utils';
  42 +import { Subject } from 'rxjs';
  43 +import { takeUntil } from 'rxjs/operators';
42 44
43 45 @Component({
44 46 selector: 'tb-mqtt-device-profile-transport-configuration',
... ... @@ -50,7 +52,7 @@ import { isDefinedAndNotNull } from '@core/utils';
50 52 multi: true
51 53 }]
52 54 })
53   -export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
  55 +export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy {
54 56
55 57 transportPayloadTypes = Object.keys(TransportPayloadType);
56 58
... ... @@ -58,6 +60,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
58 60
59 61 mqttDeviceProfileTransportConfigurationFormGroup: FormGroup;
60 62
  63 + private destroy$ = new Subject();
61 64 private requiredValue: boolean;
62 65
63 66 get required(): boolean {
... ... @@ -98,15 +101,23 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
98 101 })
99 102 }, {validator: this.uniqueDeviceTopicValidator}
100 103 );
101   - this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType')
102   - .valueChanges.subscribe(payloadType => {
  104 + this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').valueChanges.pipe(
  105 + takeUntil(this.destroy$)
  106 + ).subscribe(payloadType => {
103 107 this.updateTransportPayloadBasedControls(payloadType, true);
104 108 });
105   - this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
  109 + this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe(
  110 + takeUntil(this.destroy$)
  111 + ).subscribe(() => {
106 112 this.updateModel();
107 113 });
108 114 }
109 115
  116 + ngOnDestroy() {
  117 + this.destroy$.next();
  118 + this.destroy$.complete();
  119 + }
  120 +
110 121 setDisabledState(isDisabled: boolean): void {
111 122 this.disabled = isDisabled;
112 123 if (this.disabled) {
... ... @@ -192,8 +203,8 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
192 203 }
193 204
194 205 private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null {
195   - if (control.value) {
196   - const formValue = control.value as MqttDeviceProfileTransportConfiguration;
  206 + if (control.getRawValue()) {
  207 + const formValue = control.getRawValue() as MqttDeviceProfileTransportConfiguration;
197 208 if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) {
198 209 return {unique: true};
199 210 }
... ...
... ... @@ -14,17 +14,19 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import {Component, forwardRef, Input, OnInit} from '@angular/core';
18   -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms';
19   -import {Store} from '@ngrx/store';
20   -import {AppState} from '@app/core/core.state';
21   -import {coerceBooleanProperty} from '@angular/cdk/coercion';
  17 +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@app/core/core.state';
  21 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 22 import {
23 23 DeviceProfileTransportConfiguration,
24 24 DeviceTransportType,
25 25 SnmpDeviceProfileTransportConfiguration
26 26 } from '@shared/models/device.models';
27   -import {isDefinedAndNotNull} from "@core/utils";
  27 +import { isDefinedAndNotNull } from '@core/utils';
  28 +import { Subject } from 'rxjs';
  29 +import { takeUntil } from 'rxjs/operators';
28 30
29 31 export interface OidMappingConfiguration {
30 32 isAttribute: boolean;
... ... @@ -44,8 +46,11 @@ export interface OidMappingConfiguration {
44 46 multi: true
45 47 }]
46 48 })
47   -export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
  49 +export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy {
  50 +
48 51 snmpDeviceProfileTransportConfigurationFormGroup: FormGroup;
  52 +
  53 + private destroy$ = new Subject();
49 54 private requiredValue: boolean;
50 55 private configuration = [];
51 56
... ... @@ -71,11 +76,18 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control
71 76 this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({
72 77 configuration: [null, Validators.required]
73 78 });
74   - this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
  79 + this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe(
  80 + takeUntil(this.destroy$)
  81 + ).subscribe(() => {
75 82 this.updateModel();
76 83 });
77 84 }
78 85
  86 + ngOnDestroy() {
  87 + this.destroy$.next();
  88 + this.destroy$.complete();
  89 + }
  90 +
79 91 registerOnChange(fn: any): void {
80 92 this.propagateChange = fn;
81 93 }
... ...
... ... @@ -32,7 +32,7 @@ import {
32 32 })
33 33 export class DeviceProfileTabsComponent extends EntityTabsComponent<DeviceProfile> {
34 34
35   - deviceTransportTypes = Object.keys(DeviceTransportType);
  35 + deviceTransportTypes = Object.values(DeviceTransportType);
36 36
37 37 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
38 38
... ...
... ... @@ -20,7 +20,7 @@
20 20 <mat-icon *ngIf="icon">{{icon}}</mat-icon>
21 21 <span *ngIf="label">{{label}}</span>
22 22 </mat-label>
23   - <div matPrefix class="tb-color-preview" (click)="showColorPicker()" style="margin-right: 5px;">
  23 + <div matPrefix class="tb-color-preview" (click)="!disabled && showColorPicker()" style="margin-right: 5px;">
24 24 <div class="tb-color-result" [ngStyle]="{background: colorFormGroup.get('color').value}"></div>
25 25 </div>
26 26 <input matInput formControlName="color" (mousedown)="openOnInput && showColorPicker()" [required]="required">
... ...
... ... @@ -45,7 +45,6 @@ export interface WidgetLayouts {
45 45
46 46 export interface GridSettings {
47 47 backgroundColor?: string;
48   - color?: string;
49 48 columns?: number;
50 49 margin?: number;
51 50 backgroundSizeMode?: string;
... ... @@ -93,6 +92,7 @@ export interface DashboardSettings {
93 92 showDashboardExport?: boolean;
94 93 showUpdateDashboardImage?: boolean;
95 94 toolbarAlwaysOpen?: boolean;
  95 + hideToolbar?: boolean;
96 96 titleColor?: string;
97 97 }
98 98
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated' | 'openDashboardMessage' | 'reloadUserMessage';
  17 +export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated' | 'openDashboardMessage' | 'reloadUserMessage' | 'toggleDashboardLayout';
18 18
19 19 export interface WindowMessage {
20 20 type: WindowMessageType;
... ...
... ... @@ -721,6 +721,7 @@
721 721 "maximum-upload-file-size": "Maximum upload file size: {{ size }}",
722 722 "cannot-upload-file": "Cannot upload file",
723 723 "settings": "Settings",
  724 + "layout-settings": "Layout settings",
724 725 "columns-count": "Columns count",
725 726 "columns-count-required": "Columns count is required.",
726 727 "min-columns-count-message": "Only 10 minimum column count is allowed.",
... ... @@ -743,15 +744,19 @@
743 744 "mobile-row-height-required": "Mobile row height value is required.",
744 745 "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.",
745 746 "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.",
  747 + "title-settings": "Title settings",
746 748 "display-title": "Display dashboard title",
747   - "toolbar-always-open": "Keep toolbar opened",
748 749 "title-color": "Title color",
  750 + "toolbar-settings": "Toolbar settings",
  751 + "hide-toolbar": "Hide toolbar",
  752 + "toolbar-always-open": "Keep toolbar opened",
749 753 "display-dashboards-selection": "Display dashboards selection",
750 754 "display-entities-selection": "Display entities selection",
751 755 "display-filters": "Display filters",
752 756 "display-dashboard-timewindow": "Display timewindow",
753 757 "display-dashboard-export": "Display export",
754 758 "display-update-dashboard-image": "Display update dashboard image",
  759 + "dashboard-logo-settings": "Dashboard logo settings",
755 760 "display-dashboard-logo": "Display logo in dashboard fullscreen mode",
756 761 "dashboard-logo-image": "Dashboard logo image",
757 762 "import": "Import dashboard",
... ...