Commit 93fc099ee3622d6c47aecec76f91aa34d483ea56

Authored by Igor Kulikov
2 parents 20c90c8a 7640d32d

Merge branch 'vvlladd28-improvement/device-profile/refactoring'

Showing 28 changed files with 581 additions and 542 deletions
@@ -387,3 +387,19 @@ export function sortObjectKeys<T>(obj: T): T { @@ -387,3 +387,19 @@ export function sortObjectKeys<T>(obj: T): T {
387 return acc; 387 return acc;
388 }, {} as T); 388 }, {} as T);
389 } 389 }
  390 +
  391 +export function deepTrim<T>(obj: T): T {
  392 + if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null) {
  393 + return obj;
  394 + }
  395 + return Object.keys(obj).reduce((acc, curr) => {
  396 + if (isString(obj[curr])) {
  397 + acc[curr] = obj[curr].trim();
  398 + } else if (isObject(obj[curr])) {
  399 + acc[curr] = deepTrim(obj[curr]);
  400 + } else {
  401 + acc[curr] = obj[curr];
  402 + }
  403 + return acc;
  404 + }, (Array.isArray(obj) ? [] : {}) as T);
  405 +}
@@ -23,7 +23,7 @@ import { AppState } from '@core/core.state'; @@ -23,7 +23,7 @@ import { AppState } from '@core/core.state';
23 import { EntityAction } from '@home/models/entity/entity-component.models'; 23 import { EntityAction } from '@home/models/entity/entity-component.models';
24 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; 24 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
25 import { PageLink } from '@shared/models/page/page-link'; 25 import { PageLink } from '@shared/models/page/page-link';
26 -import { isObject, isString } from '@core/utils'; 26 +import { deepTrim } from '@core/utils';
27 27
28 // @dynamic 28 // @dynamic
29 @Directive() 29 @Directive()
@@ -115,20 +115,7 @@ export abstract class EntityComponent<T extends BaseData<HasId>, @@ -115,20 +115,7 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
115 } 115 }
116 116
117 prepareFormValue(formValue: any): any { 117 prepareFormValue(formValue: any): any {
118 - return this.deepTrim(formValue);  
119 - }  
120 -  
121 - private deepTrim(obj: object): object {  
122 - return Object.keys(obj).reduce((acc, curr) => {  
123 - if (isString(obj[curr])) {  
124 - acc[curr] = obj[curr].trim();  
125 - } else if (isObject(obj[curr])) {  
126 - acc[curr] = this.deepTrim(obj[curr]);  
127 - } else {  
128 - acc[curr] = obj[curr];  
129 - }  
130 - return acc;  
131 - }, Array.isArray(obj) ? [] : {}); 118 + return deepTrim(formValue);
132 } 119 }
133 120
134 protected setEntitiesTableConfig(entitiesTableConfig: C) { 121 protected setEntitiesTableConfig(entitiesTableConfig: C) {
@@ -76,7 +76,6 @@ @@ -76,7 +76,6 @@
76 type="button" 76 type="button"
77 matTooltip="{{ (dynamicMode ? 'filter.switch-to-default-value' : 'filter.switch-to-dynamic-value') | translate }}" 77 matTooltip="{{ (dynamicMode ? 'filter.switch-to-default-value' : 'filter.switch-to-dynamic-value') | translate }}"
78 matTooltipPosition="above" 78 matTooltipPosition="above"
79 - *ngIf="allow"  
80 (click)="dynamicMode = !dynamicMode"> 79 (click)="dynamicMode = !dynamicMode">
81 <mat-icon class="tb-mat-20" [svgIcon]="dynamicMode ? 'mdi:numeric' : 'mdi:variable'"></mat-icon> 80 <mat-icon class="tb-mat-20" [svgIcon]="dynamicMode ? 'mdi:numeric' : 'mdi:variable'"></mat-icon>
82 </button> 81 </button>
@@ -54,7 +54,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn @@ -54,7 +54,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
54 if (allow) { 54 if (allow) {
55 this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER); 55 this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER);
56 } else { 56 } else {
57 - this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE); 57 + this.dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_DEVICE];
58 } 58 }
59 } 59 }
60 60
@@ -90,7 +90,6 @@ import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.co @@ -90,7 +90,6 @@ import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.co
90 import { TenantProfileDataComponent } from './profile/tenant-profile-data.component'; 90 import { TenantProfileDataComponent } from './profile/tenant-profile-data.component';
91 import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component'; 91 import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component';
92 import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component'; 92 import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component';
93 -import { DeviceProfileDataComponent } from './profile/device-profile-data.component';  
94 import { DeviceProfileComponent } from './profile/device-profile.component'; 93 import { DeviceProfileComponent } from './profile/device-profile.component';
95 import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component'; 94 import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component';
96 import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component'; 95 import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component';
@@ -195,7 +194,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen @@ -195,7 +194,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen
195 AlarmRuleConditionComponent, 194 AlarmRuleConditionComponent,
196 DeviceProfileAlarmComponent, 195 DeviceProfileAlarmComponent,
197 DeviceProfileAlarmsComponent, 196 DeviceProfileAlarmsComponent,
198 - DeviceProfileDataComponent,  
199 DeviceProfileComponent, 197 DeviceProfileComponent,
200 DeviceProfileDialogComponent, 198 DeviceProfileDialogComponent,
201 AddDeviceProfileDialogComponent, 199 AddDeviceProfileDialogComponent,
@@ -277,7 +275,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen @@ -277,7 +275,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen
277 AlarmRuleConditionComponent, 275 AlarmRuleConditionComponent,
278 DeviceProfileAlarmComponent, 276 DeviceProfileAlarmComponent,
279 DeviceProfileAlarmsComponent, 277 DeviceProfileAlarmsComponent,
280 - DeviceProfileDataComponent,  
281 DeviceProfileComponent, 278 DeviceProfileComponent,
282 DeviceProfileDialogComponent, 279 DeviceProfileDialogComponent,
283 AddDeviceProfileDialogComponent, 280 AddDeviceProfileDialogComponent,
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 </mat-progress-bar> 29 </mat-progress-bar>
30 <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> 30 <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
31 <div mat-dialog-content> 31 <div mat-dialog-content>
32 - <mat-horizontal-stepper [linear]="true" #addDeviceProfileStepper (selectionChange)="selectedIndex = $event.selectedIndex"> 32 + <mat-horizontal-stepper [linear]="true" #addDeviceProfileStepper (selectionChange)="changeStep($event)">
33 <mat-step [stepControl]="deviceProfileDetailsFormGroup"> 33 <mat-step [stepControl]="deviceProfileDetailsFormGroup">
34 <form [formGroup]="deviceProfileDetailsFormGroup" style="padding-bottom: 16px;"> 34 <form [formGroup]="deviceProfileDetailsFormGroup" style="padding-bottom: 16px;">
35 <ng-template matStepLabel>{{ 'device-profile.device-profile-details' | translate }}</ng-template> 35 <ng-template matStepLabel>{{ 'device-profile.device-profile-details' | translate }}</ng-template>
@@ -45,7 +45,7 @@ @@ -45,7 +45,7 @@
45 labelText="device-profile.default-rule-chain" 45 labelText="device-profile.default-rule-chain"
46 formControlName="defaultRuleChainId"> 46 formControlName="defaultRuleChainId">
47 </tb-rule-chain-autocomplete> 47 </tb-rule-chain-autocomplete>
48 - <mat-form-field class="mat-block"> 48 + <mat-form-field fxHide class="mat-block">
49 <mat-label translate>device-profile.type</mat-label> 49 <mat-label translate>device-profile.type</mat-label>
50 <mat-select formControlName="type" required> 50 <mat-select formControlName="type" required>
51 <mat-option *ngFor="let type of deviceProfileTypes" [value]="type"> 51 <mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 </fieldset> 63 </fieldset>
64 </form> 64 </form>
65 </mat-step> 65 </mat-step>
66 - <mat-step [stepControl]="transportConfigFormGroup"> 66 + <mat-step [stepControl]="transportConfigFormGroup" [optional]="true">
67 <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;"> 67 <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
68 <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template> 68 <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template>
69 <mat-form-field class="mat-block"> 69 <mat-form-field class="mat-block">
@@ -73,6 +73,9 @@ @@ -73,6 +73,9 @@
73 {{deviceTransportTypeTranslations.get(type) | translate}} 73 {{deviceTransportTypeTranslations.get(type) | translate}}
74 </mat-option> 74 </mat-option>
75 </mat-select> 75 </mat-select>
  76 + <mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
  77 + {{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
  78 + </mat-hint>
76 <mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')"> 79 <mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
77 {{ 'device-profile.transport-type-required' | translate }} 80 {{ 'device-profile.transport-type-required' | translate }}
78 </mat-error> 81 </mat-error>
@@ -83,7 +86,7 @@ @@ -83,7 +86,7 @@
83 </tb-device-profile-transport-configuration> 86 </tb-device-profile-transport-configuration>
84 </form> 87 </form>
85 </mat-step> 88 </mat-step>
86 - <mat-step [stepControl]="alarmRulesFormGroup"> 89 + <mat-step [stepControl]="alarmRulesFormGroup" [optional]="true">
87 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> 90 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
88 <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate: 91 <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
89 {count: alarmRulesFormGroup.get('alarms').value ? 92 {count: alarmRulesFormGroup.get('alarms').value ?
@@ -95,19 +98,28 @@ @@ -95,19 +98,28 @@
95 </mat-step> 98 </mat-step>
96 </mat-horizontal-stepper> 99 </mat-horizontal-stepper>
97 </div> 100 </div>
98 - <div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center">  
99 - <button mat-button *ngIf="selectedIndex > 0"  
100 - [disabled]="(isLoading$ | async)"  
101 - (click)="previousStep()">{{ 'action.back' | translate }}</button>  
102 - <span *ngIf="selectedIndex <= 0"></span>  
103 - <div fxLayout="row wrap" fxLayoutGap="20px"> 101 + <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;">
  102 + <div fxFlex fxLayout="row" fxLayoutAlign="end">
  103 + <button mat-raised-button
  104 + *ngIf="showNext"
  105 + [disabled]="(isLoading$ | async)"
  106 + (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
  107 + </div>
  108 + <div fxFlex fxLayout="row">
104 <button mat-button 109 <button mat-button
  110 + color="primary"
105 [disabled]="(isLoading$ | async)" 111 [disabled]="(isLoading$ | async)"
106 (click)="cancel()">{{ 'action.cancel' | translate }}</button> 112 (click)="cancel()">{{ 'action.cancel' | translate }}</button>
107 - <button mat-raised-button  
108 - [disabled]="(isLoading$ | async) || selectedForm().invalid"  
109 - color="primary"  
110 - (click)="nextStep()">{{ (selectedIndex === 2 ? 'action.add' : 'action.continue') | translate }}</button> 113 + <span fxFlex></span>
  114 + <div fxLayout="row wrap" fxLayoutGap="8px">
  115 + <button mat-raised-button *ngIf="selectedIndex > 0"
  116 + [disabled]="(isLoading$ | async)"
  117 + (click)="previousStep()">{{ 'action.back' | translate }}</button>
  118 + <button mat-raised-button
  119 + [disabled]="(isLoading$ | async)"
  120 + color="primary"
  121 + (click)="add()">{{ 'action.add' | translate }}</button>
  122 + </div>
111 </div> 123 </div>
112 </div> 124 </div>
113 </div> 125 </div>
@@ -13,25 +13,51 @@ @@ -13,25 +13,51 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -:host {  
17 - .mat-dialog-content {  
18 - display: flex;  
19 - flex-direction: column;  
20 - overflow: hidden; 16 +@import "../../../../../scss/constants";
21 17
22 - .mat-stepper-horizontal {  
23 - display: flex;  
24 - flex-direction: column;  
25 - overflow: hidden; 18 +:host-context(.tb-fullscreen-dialog .mat-dialog-container) {
  19 + @media #{$mat-lt-sm} {
  20 + .mat-dialog-content {
  21 + max-height: 75vh;
26 } 22 }
27 } 23 }
28 } 24 }
29 25
30 :host ::ng-deep { 26 :host ::ng-deep {
31 .mat-dialog-content { 27 .mat-dialog-content {
  28 + display: flex;
  29 + flex-direction: column;
  30 + height: 100%;
  31 + padding: 24px 24px 8px !important;
  32 +
32 .mat-stepper-horizontal { 33 .mat-stepper-horizontal {
  34 + display: flex;
  35 + flex-direction: column;
  36 + height: 100%;
  37 + overflow: hidden;
  38 + @media #{$mat-lt-sm} {
  39 + .mat-step-label {
  40 + white-space: normal;
  41 + overflow: visible;
  42 + .mat-step-text-label {
  43 + overflow: visible;
  44 + }
  45 + }
  46 + }
33 .mat-horizontal-content-container { 47 .mat-horizontal-content-container {
34 - overflow: auto; 48 + height: 350px;
  49 + max-height: 100%;
  50 + width: 100%;;
  51 + overflow-y: auto;
  52 + @media #{$mat-gt-sm} {
  53 + min-width: 800px;
  54 + }
  55 + }
  56 + .mat-horizontal-stepper-content[aria-expanded=true] {
  57 + height: 100%;
  58 + form {
  59 + height: 100%;
  60 + }
35 } 61 }
36 } 62 }
37 } 63 }
@@ -36,13 +36,15 @@ import { @@ -36,13 +36,15 @@ import {
36 DeviceProfile, 36 DeviceProfile,
37 DeviceProfileType, 37 DeviceProfileType,
38 deviceProfileTypeTranslationMap, 38 deviceProfileTypeTranslationMap,
39 - DeviceTransportType, 39 + DeviceTransportType, deviceTransportTypeHintMap,
40 deviceTransportTypeTranslationMap 40 deviceTransportTypeTranslationMap
41 } from '@shared/models/device.models'; 41 } from '@shared/models/device.models';
42 import { DeviceProfileService } from '@core/http/device-profile.service'; 42 import { DeviceProfileService } from '@core/http/device-profile.service';
43 import { EntityType } from '@shared/models/entity-type.models'; 43 import { EntityType } from '@shared/models/entity-type.models';
44 import { MatHorizontalStepper } from '@angular/material/stepper'; 44 import { MatHorizontalStepper } from '@angular/material/stepper';
45 import { RuleChainId } from '@shared/models/id/rule-chain-id'; 45 import { RuleChainId } from '@shared/models/id/rule-chain-id';
  46 +import { StepperSelectionEvent } from '@angular/cdk/stepper';
  47 +import { deepTrim } from '@core/utils';
46 48
47 export interface AddDeviceProfileDialogData { 49 export interface AddDeviceProfileDialogData {
48 deviceProfileName: string; 50 deviceProfileName: string;
@@ -62,12 +64,16 @@ export class AddDeviceProfileDialogComponent extends @@ -62,12 +64,16 @@ export class AddDeviceProfileDialogComponent extends
62 64
63 selectedIndex = 0; 65 selectedIndex = 0;
64 66
  67 + showNext = true;
  68 +
65 entityType = EntityType; 69 entityType = EntityType;
66 70
67 deviceProfileTypes = Object.keys(DeviceProfileType); 71 deviceProfileTypes = Object.keys(DeviceProfileType);
68 72
69 deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; 73 deviceProfileTypeTranslations = deviceProfileTypeTranslationMap;
70 74
  75 + deviceTransportTypeHints = deviceTransportTypeHintMap;
  76 +
71 deviceTransportTypes = Object.keys(DeviceTransportType); 77 deviceTransportTypes = Object.keys(DeviceTransportType);
72 78
73 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; 79 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
@@ -150,25 +156,63 @@ export class AddDeviceProfileDialogComponent extends @@ -150,25 +156,63 @@ export class AddDeviceProfileDialogComponent extends
150 } 156 }
151 } 157 }
152 158
153 - private add(): void {  
154 - const deviceProfile: DeviceProfile = {  
155 - name: this.deviceProfileDetailsFormGroup.get('name').value,  
156 - type: this.deviceProfileDetailsFormGroup.get('type').value,  
157 - transportType: this.transportConfigFormGroup.get('transportType').value,  
158 - description: this.deviceProfileDetailsFormGroup.get('description').value,  
159 - profileData: {  
160 - configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),  
161 - transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value,  
162 - alarms: this.alarmRulesFormGroup.get('alarms').value 159 + add(): void {
  160 + if (this.allValid()) {
  161 + const deviceProfile: DeviceProfile = {
  162 + name: this.deviceProfileDetailsFormGroup.get('name').value,
  163 + type: this.deviceProfileDetailsFormGroup.get('type').value,
  164 + transportType: this.transportConfigFormGroup.get('transportType').value,
  165 + description: this.deviceProfileDetailsFormGroup.get('description').value,
  166 + profileData: {
  167 + configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
  168 + transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value,
  169 + alarms: this.alarmRulesFormGroup.get('alarms').value
  170 + }
  171 + };
  172 + if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) {
  173 + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value);
163 } 174 }
164 - };  
165 - if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) {  
166 - deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value); 175 + this.deviceProfileService.saveDeviceProfile(deepTrim(deviceProfile)).subscribe(
  176 + (savedDeviceProfile) => {
  177 + this.dialogRef.close(savedDeviceProfile);
  178 + }
  179 + );
  180 + }
  181 + }
  182 +
  183 + getFormLabel(index: number): string {
  184 + switch (index) {
  185 + case 0:
  186 + return 'device-profile.device-profile-details';
  187 + case 1:
  188 + return 'device-profile.transport-configuration';
  189 + case 2:
  190 + return 'device-profile.alarm-rules';
  191 + }
  192 + }
  193 +
  194 + changeStep($event: StepperSelectionEvent): void {
  195 + this.selectedIndex = $event.selectedIndex;
  196 + if (this.selectedIndex === this.maxStepperIndex) {
  197 + this.showNext = false;
  198 + } else {
  199 + this.showNext = true;
167 } 200 }
168 - this.deviceProfileService.saveDeviceProfile(deviceProfile).subscribe(  
169 - (savedDeviceProfile) => {  
170 - this.dialogRef.close(savedDeviceProfile); 201 + }
  202 +
  203 + private get maxStepperIndex(): number {
  204 + return this.addDeviceProfileStepper?._steps?.length - 1;
  205 + }
  206 +
  207 + allValid(): boolean {
  208 + return !this.addDeviceProfileStepper.steps.find((item, index) => {
  209 + if (item.stepControl.invalid) {
  210 + item.interacted = true;
  211 + this.addDeviceProfileStepper.selectedIndex = index;
  212 + return true;
  213 + } else {
  214 + return false;
171 } 215 }
172 - ); 216 + });
173 } 217 }
174 } 218 }
@@ -21,74 +21,72 @@ @@ -21,74 +21,72 @@
21 <tb-alarm-rule-condition fxFlex class="row" 21 <tb-alarm-rule-condition fxFlex class="row"
22 formControlName="condition"> 22 formControlName="condition">
23 </tb-alarm-rule-condition> 23 </tb-alarm-rule-condition>
24 - <section class="row">  
25 - <div formGroupName="spec">  
26 - <mat-form-field class="mat-block" hideRequiredMarker>  
27 - <mat-label translate>device-profile.condition-type</mat-label>  
28 - <mat-select formControlName="type" required>  
29 - <mat-option *ngFor="let alarmConditionType of alarmConditionTypes" [value]="alarmConditionType">  
30 - {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} 24 + <section formGroupName="spec" class="row">
  25 + <mat-form-field class="mat-block" hideRequiredMarker>
  26 + <mat-label translate>device-profile.condition-type</mat-label>
  27 + <mat-select formControlName="type" required>
  28 + <mat-option *ngFor="let alarmConditionType of alarmConditionTypes" [value]="alarmConditionType">
  29 + {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }}
  30 + </mat-option>
  31 + </mat-select>
  32 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.type').hasError('required')">
  33 + {{ 'device-profile.condition-type-required' | translate }}
  34 + </mat-error>
  35 + </mat-form-field>
  36 + <div fxLayout="row" fxLayoutGap="8px" *ngIf="alarmRuleFormGroup.get('condition.spec.type').value == AlarmConditionType.DURATION">
  37 + <mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
  38 + <mat-label></mat-label>
  39 + <input type="number" required
  40 + step="1" min="1" max="2147483647" matInput
  41 + placeholder="{{ 'device-profile.condition-duration-value' | translate }}"
  42 + formControlName="value">
  43 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('required')">
  44 + {{ 'device-profile.condition-duration-value-required' | translate }}
  45 + </mat-error>
  46 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('min')">
  47 + {{ 'device-profile.condition-duration-value-range' | translate }}
  48 + </mat-error>
  49 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('max')">
  50 + {{ 'device-profile.condition-duration-value-range' | translate }}
  51 + </mat-error>
  52 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('pattern')">
  53 + {{ 'device-profile.condition-duration-value-pattern' | translate }}
  54 + </mat-error>
  55 + </mat-form-field>
  56 + <mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
  57 + <mat-label></mat-label>
  58 + <mat-select formControlName="unit"
  59 + required
  60 + placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
  61 + <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
  62 + {{ timeUnitTranslations.get(timeUnit) | translate }}
31 </mat-option> 63 </mat-option>
32 </mat-select> 64 </mat-select>
33 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.type').hasError('required')">  
34 - {{ 'device-profile.condition-type-required' | translate }} 65 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.unit').hasError('required')">
  66 + {{ 'device-profile.condition-duration-time-unit-required' | translate }}
  67 + </mat-error>
  68 + </mat-form-field>
  69 + </div>
  70 + <div fxLayout="row" fxLayoutGap="8px" *ngIf="alarmRuleFormGroup.get('condition.spec.type').value == AlarmConditionType.REPEATING">
  71 + <mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
  72 + <mat-label></mat-label>
  73 + <input type="number" required
  74 + step="1" min="1" max="2147483647" matInput
  75 + placeholder="{{ 'device-profile.condition-repeating-value' | translate }}"
  76 + formControlName="count">
  77 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('required')">
  78 + {{ 'device-profile.condition-repeating-value-required' | translate }}
  79 + </mat-error>
  80 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('min')">
  81 + {{ 'device-profile.condition-repeating-value-range' | translate }}
  82 + </mat-error>
  83 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('max')">
  84 + {{ 'device-profile.condition-repeating-value-range' | translate }}
  85 + </mat-error>
  86 + <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('pattern')">
  87 + {{ 'device-profile.condition-repeating-value-pattern' | translate }}
35 </mat-error> 88 </mat-error>
36 </mat-form-field> 89 </mat-form-field>
37 - <div fxLayout="row" fxLayoutGap="8px" *ngIf="alarmRuleFormGroup.get('condition.spec.type').value == AlarmConditionType.DURATION">  
38 - <mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">  
39 - <mat-label></mat-label>  
40 - <input type="number" required  
41 - step="1" min="1" max="2147483647" matInput  
42 - placeholder="{{ 'device-profile.condition-duration-value' | translate }}"  
43 - formControlName="value">  
44 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('required')">  
45 - {{ 'device-profile.condition-duration-value-required' | translate }}  
46 - </mat-error>  
47 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('min')">  
48 - {{ 'device-profile.condition-duration-value-range' | translate }}  
49 - </mat-error>  
50 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('max')">  
51 - {{ 'device-profile.condition-duration-value-range' | translate }}  
52 - </mat-error>  
53 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.value').hasError('pattern')">  
54 - {{ 'device-profile.condition-duration-value-pattern' | translate }}  
55 - </mat-error>  
56 - </mat-form-field>  
57 - <mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">  
58 - <mat-label></mat-label>  
59 - <mat-select formControlName="unit"  
60 - required  
61 - placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">  
62 - <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">  
63 - {{ timeUnitTranslations.get(timeUnit) | translate }}  
64 - </mat-option>  
65 - </mat-select>  
66 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.unit').hasError('required')">  
67 - {{ 'device-profile.condition-duration-time-unit-required' | translate }}  
68 - </mat-error>  
69 - </mat-form-field>  
70 - </div>  
71 - <div fxLayout="row" fxLayoutGap="8px" *ngIf="alarmRuleFormGroup.get('condition.spec.type').value == AlarmConditionType.REPEATING">  
72 - <mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">  
73 - <mat-label></mat-label>  
74 - <input type="number" required  
75 - step="1" min="1" max="2147483647" matInput  
76 - placeholder="{{ 'device-profile.condition-repeating-value' | translate }}"  
77 - formControlName="count">  
78 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('required')">  
79 - {{ 'device-profile.condition-repeating-value-required' | translate }}  
80 - </mat-error>  
81 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('min')">  
82 - {{ 'device-profile.condition-repeating-value-range' | translate }}  
83 - </mat-error>  
84 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('max')">  
85 - {{ 'device-profile.condition-repeating-value-range' | translate }}  
86 - </mat-error>  
87 - <mat-error *ngIf="alarmRuleFormGroup.get('condition.spec.count').hasError('pattern')">  
88 - {{ 'device-profile.condition-repeating-value-pattern' | translate }}  
89 - </mat-error>  
90 - </mat-form-field>  
91 - </div>  
92 </div> 90 </div>
93 </section> 91 </section>
94 </mat-tab> 92 </mat-tab>
@@ -29,6 +29,7 @@ import { AlarmConditionType, AlarmConditionTypeTranslationMap, AlarmRule } from @@ -29,6 +29,7 @@ import { AlarmConditionType, AlarmConditionTypeTranslationMap, AlarmRule } from
29 import { MatDialog } from '@angular/material/dialog'; 29 import { MatDialog } from '@angular/material/dialog';
30 import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; 30 import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models';
31 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 31 import { coerceBooleanProperty } from '@angular/cdk/coercion';
  32 +import { isUndefined } from '@core/utils';
32 33
33 @Component({ 34 @Component({
34 selector: 'tb-alarm-rule', 35 selector: 'tb-alarm-rule',
@@ -117,10 +118,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat @@ -117,10 +118,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
117 118
118 writeValue(value: AlarmRule): void { 119 writeValue(value: AlarmRule): void {
119 this.modelValue = value; 120 this.modelValue = value;
120 - if (this.modelValue?.condition?.spec === null) {  
121 - this.modelValue.condition.spec = {  
122 - type: AlarmConditionType.SIMPLE  
123 - }; 121 + if (this.modelValue !== null && isUndefined(this.modelValue?.condition?.spec)) {
  122 + this.modelValue = Object.assign(this.modelValue, {condition: {spec: {type: AlarmConditionType.SIMPLE}}});
124 } 123 }
125 this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); 124 this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false});
126 this.updateValidators(this.modelValue?.condition?.spec?.type); 125 this.updateValidators(this.modelValue?.condition?.spec?.type);
@@ -35,7 +35,7 @@ @@ -35,7 +35,7 @@
35 </tb-timezone-select> 35 </tb-timezone-select>
36 <section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.SPECIFIC_TIME"> 36 <section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.SPECIFIC_TIME">
37 <div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div> 37 <div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div>
38 - <div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" style="padding-bottom: 16px;"> 38 + <div fxLayout="column" fxLayout.gt-md="row" fxLayoutGap="16px" style="padding-bottom: 16px;">
39 <div fxLayout="row" fxLayoutGap="16px"> 39 <div fxLayout="row" fxLayoutGap="16px">
40 <mat-checkbox [formControl]="weeklyRepeatControl(0)"> 40 <mat-checkbox [formControl]="weeklyRepeatControl(0)">
41 {{ 'device-profile.schedule-day.monday' | translate }} 41 {{ 'device-profile.schedule-day.monday' | translate }}
@@ -64,158 +64,189 @@ @@ -64,158 +64,189 @@
64 </div> 64 </div>
65 <div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-time</div> 65 <div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-time</div>
66 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> 66 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
67 - <mat-form-field fxFlex>  
68 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
69 - <mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>  
70 - <mat-datetimepicker #startTimePicker type="time" openOnFocus="true"></mat-datetimepicker>  
71 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker">  
72 - </mat-form-field>  
73 - <mat-form-field fxFlex>  
74 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
75 - <mat-datetimepicker-toggle [for]="endTimePicker" matPrefix></mat-datetimepicker-toggle>  
76 - <mat-datetimepicker #endTimePicker type="time" openOnFocus="true"></mat-datetimepicker>  
77 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker">  
78 - </mat-form-field> 67 + <div fxLayout="row" fxLayoutGap="8px" fxFlex.gt-md>
  68 + <mat-form-field fxFlex.xs fxFlex.sm="150px" fxFlex.md="150px" fxFlex.gt-md>
  69 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  70 + <mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>
  71 + <mat-datetimepicker #startTimePicker type="time" openOnFocus="true"></mat-datetimepicker>
  72 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker">
  73 + </mat-form-field>
  74 + <mat-form-field fxFlex.xs fxFlex.sm="150px" fxFlex.md="150px" fxFlex.gt-md>
  75 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  76 + <mat-datetimepicker-toggle [for]="endTimePicker" matPrefix></mat-datetimepicker-toggle>
  77 + <mat-datetimepicker #endTimePicker type="time" openOnFocus="true"></mat-datetimepicker>
  78 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker">
  79 + </mat-form-field>
  80 + </div>
  81 + <div fxFlex fxLayoutAlign="center center" style="margin: auto">
  82 + <div style="text-align: center"
  83 + [innerHTML]="getSchedulerRangeText(alarmScheduleForm)">
  84 + </div>
  85 + </div>
79 </div> 86 </div>
80 </section> 87 </section>
81 <section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.CUSTOM"> 88 <section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.CUSTOM">
82 <div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div> 89 <div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div>
83 - <div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap.gt-sm="16px" formArrayName="items">  
84 - <div fxLayout="column" fxFlex fxFlex.gt-sm="50">  
85 - <div fxLayout="row" fxLayoutGap="8px" formGroupName="0" fxLayoutAlign="start center">  
86 - <mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 0)">  
87 - {{ 'device-profile.schedule-day.monday' | translate }}  
88 - </mat-checkbox>  
89 - <div fxLayout="row" fxLayoutGap="8px" fxFlex>  
90 - <mat-form-field fxFlex="100px">  
91 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
92 - <mat-datetimepicker-toggle [for]="startTimePicker1" matPrefix></mat-datetimepicker-toggle>  
93 - <mat-datetimepicker #startTimePicker1 type="time" openOnFocus="true"></mat-datetimepicker>  
94 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker1">  
95 - </mat-form-field>  
96 - <mat-form-field fxFlex="100px">  
97 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
98 - <mat-datetimepicker-toggle [for]="endTimePicker1" matPrefix></mat-datetimepicker-toggle>  
99 - <mat-datetimepicker #endTimePicker1 type="time" openOnFocus="true"></mat-datetimepicker>  
100 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker1">  
101 - </mat-form-field>  
102 - </div> 90 + <div fxLayout="column" formArrayName="items" fxLayoutGap="1em">
  91 + <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" formGroupName="0" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
  92 + <mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, 0)">
  93 + {{ 'device-profile.schedule-day.monday' | translate }}
  94 + </mat-checkbox>
  95 + <div fxLayout="row" fxLayoutGap="8px" fxFlex>
  96 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  97 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  98 + <mat-datetimepicker-toggle [for]="startTimePicker1" matPrefix></mat-datetimepicker-toggle>
  99 + <mat-datetimepicker #startTimePicker1 type="time" openOnFocus="true"></mat-datetimepicker>
  100 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker1">
  101 + </mat-form-field>
  102 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  103 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  104 + <mat-datetimepicker-toggle [for]="endTimePicker1" matPrefix></mat-datetimepicker-toggle>
  105 + <mat-datetimepicker #endTimePicker1 type="time" openOnFocus="true"></mat-datetimepicker>
  106 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker1">
  107 + </mat-form-field>
103 </div> 108 </div>
104 - <div fxLayout="row" fxLayoutGap="8px" formGroupName="1" fxLayoutAlign="start center">  
105 - <mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 1)">  
106 - {{ 'device-profile.schedule-day.tuesday' | translate }}  
107 - </mat-checkbox>  
108 - <div fxLayout="row" fxLayoutGap="8px" fxFlex>  
109 - <mat-form-field fxFlex="100px">  
110 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
111 - <mat-datetimepicker-toggle [for]="startTimePicker2" matPrefix></mat-datetimepicker-toggle>  
112 - <mat-datetimepicker #startTimePicker2 type="time" openOnFocus="true"></mat-datetimepicker>  
113 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker2">  
114 - </mat-form-field>  
115 - <mat-form-field fxFlex="100px">  
116 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
117 - <mat-datetimepicker-toggle [for]="endTimePicker2" matPrefix></mat-datetimepicker-toggle>  
118 - <mat-datetimepicker #endTimePicker2 type="time" openOnFocus="true"></mat-datetimepicker>  
119 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker2">  
120 - </mat-form-field>  
121 - </div> 109 + <div fxFlex fxLayoutAlign="center center"
  110 + style="text-align: center"
  111 + [innerHTML]="getSchedulerRangeText(itemsSchedulerForm.at(0))">
122 </div> 112 </div>
123 - <div fxLayout="row" fxLayoutGap="8px" formGroupName="2" fxLayoutAlign="start center">  
124 - <mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 2)">  
125 - {{ 'device-profile.schedule-day.wednesday' | translate }}  
126 - </mat-checkbox>  
127 - <div fxLayout="row" fxLayoutGap="8px" fxFlex>  
128 - <mat-form-field fxFlex="100px">  
129 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
130 - <mat-datetimepicker-toggle [for]="startTimePicker3" matPrefix></mat-datetimepicker-toggle>  
131 - <mat-datetimepicker #startTimePicker3 type="time" openOnFocus="true"></mat-datetimepicker>  
132 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker3">  
133 - </mat-form-field>  
134 - <mat-form-field fxFlex="100px">  
135 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
136 - <mat-datetimepicker-toggle [for]="endTimePicker3" matPrefix></mat-datetimepicker-toggle>  
137 - <mat-datetimepicker #endTimePicker3 type="time" openOnFocus="true"></mat-datetimepicker>  
138 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker3">  
139 - </mat-form-field>  
140 - </div> 113 + </div>
  114 + <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" formGroupName="1" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
  115 + <mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, 1)">
  116 + {{ 'device-profile.schedule-day.tuesday' | translate }}
  117 + </mat-checkbox>
  118 + <div fxLayout="row" fxLayoutGap="8px" fxFlex>
  119 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  120 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  121 + <mat-datetimepicker-toggle [for]="startTimePicker2" matPrefix></mat-datetimepicker-toggle>
  122 + <mat-datetimepicker #startTimePicker2 type="time" openOnFocus="true"></mat-datetimepicker>
  123 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker2">
  124 + </mat-form-field>
  125 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  126 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  127 + <mat-datetimepicker-toggle [for]="endTimePicker2" matPrefix></mat-datetimepicker-toggle>
  128 + <mat-datetimepicker #endTimePicker2 type="time" openOnFocus="true"></mat-datetimepicker>
  129 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker2">
  130 + </mat-form-field>
141 </div> 131 </div>
142 - <div fxLayout="row" fxLayoutGap="8px" formGroupName="3" fxLayoutAlign="start center">  
143 - <mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 3)">  
144 - {{ 'device-profile.schedule-day.thursday' | translate }}  
145 - </mat-checkbox>  
146 - <div fxLayout="row" fxLayoutGap="8px" fxFlex>  
147 - <mat-form-field fxFlex="100px">  
148 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
149 - <mat-datetimepicker-toggle [for]="startTimePicker4" matPrefix></mat-datetimepicker-toggle>  
150 - <mat-datetimepicker #startTimePicker4 type="time" openOnFocus="true"></mat-datetimepicker>  
151 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker4">  
152 - </mat-form-field>  
153 - <mat-form-field fxFlex="100px">  
154 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
155 - <mat-datetimepicker-toggle [for]="endTimePicker4" matPrefix></mat-datetimepicker-toggle>  
156 - <mat-datetimepicker #endTimePicker4 type="time" openOnFocus="true"></mat-datetimepicker>  
157 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker4">  
158 - </mat-form-field>  
159 - </div> 132 + <div fxFlex fxLayoutAlign="center center"
  133 + style="text-align: center"
  134 + [innerHTML]="getSchedulerRangeText(itemsSchedulerForm.at(1))">
160 </div> 135 </div>
161 </div> 136 </div>
162 - <div fxLayout="column" fxFlex fxFlex.gt-sm="50">  
163 - <div fxLayout="row" fxLayoutGap="8px" formGroupName="4" fxLayoutAlign="start center">  
164 - <mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 4)">  
165 - {{ 'device-profile.schedule-day.friday' | translate }}  
166 - </mat-checkbox>  
167 - <div fxLayout="row" fxLayoutGap="8px" fxFlex>  
168 - <mat-form-field fxFlex="100px">  
169 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
170 - <mat-datetimepicker-toggle [for]="startTimePicker5" matPrefix></mat-datetimepicker-toggle>  
171 - <mat-datetimepicker #startTimePicker5 type="time" openOnFocus="true"></mat-datetimepicker>  
172 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker5">  
173 - </mat-form-field>  
174 - <mat-form-field fxFlex="100px">  
175 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
176 - <mat-datetimepicker-toggle [for]="endTimePicker5" matPrefix></mat-datetimepicker-toggle>  
177 - <mat-datetimepicker #endTimePicker5 type="time" openOnFocus="true"></mat-datetimepicker>  
178 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker5">  
179 - </mat-form-field>  
180 - </div> 137 + <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" formGroupName="2" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
  138 + <mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, 2)">
  139 + {{ 'device-profile.schedule-day.wednesday' | translate }}
  140 + </mat-checkbox>
  141 + <div fxLayout="row" fxLayoutGap="8px" fxFlex>
  142 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  143 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  144 + <mat-datetimepicker-toggle [for]="startTimePicker3" matPrefix></mat-datetimepicker-toggle>
  145 + <mat-datetimepicker #startTimePicker3 type="time" openOnFocus="true"></mat-datetimepicker>
  146 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker3">
  147 + </mat-form-field>
  148 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  149 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  150 + <mat-datetimepicker-toggle [for]="endTimePicker3" matPrefix></mat-datetimepicker-toggle>
  151 + <mat-datetimepicker #endTimePicker3 type="time" openOnFocus="true"></mat-datetimepicker>
  152 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker3">
  153 + </mat-form-field>
  154 + </div>
  155 + <div fxFlex fxLayoutAlign="center center"
  156 + style="text-align: center"
  157 + [innerHTML]="getSchedulerRangeText(itemsSchedulerForm.at(2))">
  158 + </div>
  159 + </div>
  160 + <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" formGroupName="3" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
  161 + <mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, 3)">
  162 + {{ 'device-profile.schedule-day.thursday' | translate }}
  163 + </mat-checkbox>
  164 + <div fxLayout="row" fxLayoutGap="8px" fxFlex>
  165 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  166 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  167 + <mat-datetimepicker-toggle [for]="startTimePicker4" matPrefix></mat-datetimepicker-toggle>
  168 + <mat-datetimepicker #startTimePicker4 type="time" openOnFocus="true"></mat-datetimepicker>
  169 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker4">
  170 + </mat-form-field>
  171 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  172 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  173 + <mat-datetimepicker-toggle [for]="endTimePicker4" matPrefix></mat-datetimepicker-toggle>
  174 + <mat-datetimepicker #endTimePicker4 type="time" openOnFocus="true"></mat-datetimepicker>
  175 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker4">
  176 + </mat-form-field>
181 </div> 177 </div>
182 - <div fxLayout="row" fxLayoutGap="8px" formGroupName="5" fxLayoutAlign="start center">  
183 - <mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 5)">  
184 - {{ 'device-profile.schedule-day.saturday' | translate }}  
185 - </mat-checkbox>  
186 - <div fxLayout="row" fxLayoutGap="8px" fxFlex>  
187 - <mat-form-field fxFlex="100px">  
188 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
189 - <mat-datetimepicker-toggle [for]="startTimePicker6" matPrefix></mat-datetimepicker-toggle>  
190 - <mat-datetimepicker #startTimePicker6 type="time" openOnFocus="true"></mat-datetimepicker>  
191 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker6">  
192 - </mat-form-field>  
193 - <mat-form-field fxFlex="100px">  
194 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
195 - <mat-datetimepicker-toggle [for]="endTimePicker6" matPrefix></mat-datetimepicker-toggle>  
196 - <mat-datetimepicker #endTimePicker6 type="time" openOnFocus="true"></mat-datetimepicker>  
197 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker6">  
198 - </mat-form-field>  
199 - </div> 178 + <div fxFlex fxLayoutAlign="center center"
  179 + style="text-align: center"
  180 + [innerHTML]="getSchedulerRangeText(itemsSchedulerForm.at(3))">
  181 + </div>
  182 + </div>
  183 + <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" formGroupName="4" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
  184 + <mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, 4)">
  185 + {{ 'device-profile.schedule-day.friday' | translate }}
  186 + </mat-checkbox>
  187 + <div fxLayout="row" fxLayoutGap="8px" fxFlex>
  188 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  189 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  190 + <mat-datetimepicker-toggle [for]="startTimePicker5" matPrefix></mat-datetimepicker-toggle>
  191 + <mat-datetimepicker #startTimePicker5 type="time" openOnFocus="true"></mat-datetimepicker>
  192 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker5">
  193 + </mat-form-field>
  194 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  195 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  196 + <mat-datetimepicker-toggle [for]="endTimePicker5" matPrefix></mat-datetimepicker-toggle>
  197 + <mat-datetimepicker #endTimePicker5 type="time" openOnFocus="true"></mat-datetimepicker>
  198 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker5">
  199 + </mat-form-field>
  200 + </div>
  201 + <div fxFlex fxLayoutAlign="center center"
  202 + style="text-align: center"
  203 + [innerHTML]="getSchedulerRangeText(itemsSchedulerForm.at(4))">
  204 + </div>
  205 + </div>
  206 + <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" formGroupName="5" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
  207 + <mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, 5)">
  208 + {{ 'device-profile.schedule-day.saturday' | translate }}
  209 + </mat-checkbox>
  210 + <div fxLayout="row" fxLayoutGap="8px" fxFlex>
  211 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  212 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  213 + <mat-datetimepicker-toggle [for]="startTimePicker6" matPrefix></mat-datetimepicker-toggle>
  214 + <mat-datetimepicker #startTimePicker6 type="time" openOnFocus="true"></mat-datetimepicker>
  215 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker6">
  216 + </mat-form-field>
  217 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  218 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  219 + <mat-datetimepicker-toggle [for]="endTimePicker6" matPrefix></mat-datetimepicker-toggle>
  220 + <mat-datetimepicker #endTimePicker6 type="time" openOnFocus="true"></mat-datetimepicker>
  221 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker6">
  222 + </mat-form-field>
  223 + </div>
  224 + <div fxFlex fxLayoutAlign="center center"
  225 + style="text-align: center"
  226 + [innerHTML]="getSchedulerRangeText(itemsSchedulerForm.at(5))">
  227 + </div>
  228 + </div>
  229 + <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" formGroupName="6" fxLayoutAlign="start center" fxLayoutAlign.xs="center start">
  230 + <mat-checkbox formControlName="enabled" fxFlex="17" (change)="changeCustomScheduler($event, 6)">
  231 + {{ 'device-profile.schedule-day.sunday' | translate }}
  232 + </mat-checkbox>
  233 + <div fxLayout="row" fxLayoutGap="8px" fxFlex>
  234 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  235 + <mat-label translate>device-profile.schedule-time-from</mat-label>
  236 + <mat-datetimepicker-toggle [for]="startTimePicker7" matPrefix></mat-datetimepicker-toggle>
  237 + <mat-datetimepicker #startTimePicker7 type="time" openOnFocus="true"></mat-datetimepicker>
  238 + <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker7">
  239 + </mat-form-field>
  240 + <mat-form-field fxFlex.xs fxFlex.sm="100px" fxFlex.md="100px">
  241 + <mat-label translate>device-profile.schedule-time-to</mat-label>
  242 + <mat-datetimepicker-toggle [for]="endTimePicker7" matPrefix></mat-datetimepicker-toggle>
  243 + <mat-datetimepicker #endTimePicker7 type="time" openOnFocus="true"></mat-datetimepicker>
  244 + <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker7">
  245 + </mat-form-field>
200 </div> 246 </div>
201 - <div fxLayout="row" fxLayoutGap="8px" formGroupName="6" fxLayoutAlign="start center">  
202 - <mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 6)">  
203 - {{ 'device-profile.schedule-day.sunday' | translate }}  
204 - </mat-checkbox>  
205 - <div fxLayout="row" fxLayoutGap="8px" fxFlex>  
206 - <mat-form-field fxFlex="100px">  
207 - <mat-label translate>device-profile.schedule-time-from</mat-label>  
208 - <mat-datetimepicker-toggle [for]="startTimePicker7" matPrefix></mat-datetimepicker-toggle>  
209 - <mat-datetimepicker #startTimePicker7 type="time" openOnFocus="true"></mat-datetimepicker>  
210 - <input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker7">  
211 - </mat-form-field>  
212 - <mat-form-field fxFlex="100px">  
213 - <mat-label translate>device-profile.schedule-time-to</mat-label>  
214 - <mat-datetimepicker-toggle [for]="endTimePicker7" matPrefix></mat-datetimepicker-toggle>  
215 - <mat-datetimepicker #endTimePicker7 type="time" openOnFocus="true"></mat-datetimepicker>  
216 - <input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker7">  
217 - </mat-form-field>  
218 - </div> 247 + <div fxFlex fxLayoutAlign="center center"
  248 + style="text-align: center"
  249 + [innerHTML]="getSchedulerRangeText(itemsSchedulerForm.at(6))">
219 </div> 250 </div>
220 </div> 251 </div>
221 </div> 252 </div>
  1 +/**
  2 + * Copyright © 2016-2020 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 +::ng-deep {
  17 + .nowrap {
  18 + white-space: nowrap;
  19 + }
  20 +}
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 16
17 import { Component, forwardRef, Input, OnInit } from '@angular/core'; 17 import { Component, forwardRef, Input, OnInit } from '@angular/core';
18 import { 18 import {
  19 + AbstractControl,
19 ControlValueAccessor, 20 ControlValueAccessor,
20 FormArray, 21 FormArray,
21 FormBuilder, 22 FormBuilder,
@@ -35,6 +36,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox'; @@ -35,6 +36,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox';
35 @Component({ 36 @Component({
36 selector: 'tb-alarm-schedule', 37 selector: 'tb-alarm-schedule',
37 templateUrl: './alarm-schedule.component.html', 38 templateUrl: './alarm-schedule.component.html',
  39 + styleUrls: ['./alarm-schedule.component.scss'],
38 providers: [{ 40 providers: [{
39 provide: NG_VALUE_ACCESSOR, 41 provide: NG_VALUE_ACCESSOR,
40 useExisting: forwardRef(() => AlarmScheduleComponent), 42 useExisting: forwardRef(() => AlarmScheduleComponent),
@@ -79,7 +81,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -79,7 +81,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
79 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i))) 81 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)))
80 }); 82 });
81 this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { 83 this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
82 - this.alarmScheduleForm.reset({type, items: this.defaultItems}, {emitEvent: false}); 84 + this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: this.defaultTimezone}, {emitEvent: false});
83 this.updateValidators(type, true); 85 this.updateValidators(type, true);
84 this.alarmScheduleForm.updateValueAndValidity(); 86 this.alarmScheduleForm.updateValueAndValidity();
85 }); 87 });
@@ -131,13 +133,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -131,13 +133,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
131 this.modelValue.items 133 this.modelValue.items
132 .sort((a, b) => a.dayOfWeek - b.dayOfWeek) 134 .sort((a, b) => a.dayOfWeek - b.dayOfWeek)
133 .forEach((item, index) => { 135 .forEach((item, index) => {
134 - if (item.enabled) {  
135 - this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false});  
136 - this.itemsSchedulerForm.at(index).get('endsOn').enable({emitEvent: false});  
137 - } else {  
138 - this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false});  
139 - this.itemsSchedulerForm.at(index).get('endsOn').disable({emitEvent: false});  
140 - } 136 + this.disabledSelectedTime(item.enabled, index);
141 alarmDays.push({ 137 alarmDays.push({
142 enabled: item.enabled, 138 enabled: item.enabled,
143 startsOn: this.timestampToTime(item.startsOn), 139 startsOn: this.timestampToTime(item.startsOn),
@@ -206,15 +202,15 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -206,15 +202,15 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
206 .filter(day => !!day); 202 .filter(day => !!day);
207 } 203 }
208 if (isDefined(value.startsOn) && value.startsOn !== 0) { 204 if (isDefined(value.startsOn) && value.startsOn !== 0) {
209 - value.startsOn = this.timeToTimestamp(value.startsOn); 205 + value.startsOn = this.timeToTimestampUTC(value.startsOn);
210 } 206 }
211 if (isDefined(value.endsOn) && value.endsOn !== 0) { 207 if (isDefined(value.endsOn) && value.endsOn !== 0) {
212 - value.endsOn = this.timeToTimestamp(value.endsOn); 208 + value.endsOn = this.timeToTimestampUTC(value.endsOn);
213 } 209 }
214 if (isDefined(value.items)){ 210 if (isDefined(value.items)){
215 value.items = this.alarmScheduleForm.getRawValue().items; 211 value.items = this.alarmScheduleForm.getRawValue().items;
216 value.items = value.items.map((item) => { 212 value.items = value.items.map((item) => {
217 - return { ...item, startsOn: this.timeToTimestamp(item.startsOn), endsOn: this.timeToTimestamp(item.endsOn)}; 213 + return { ...item, startsOn: this.timeToTimestampUTC(item.startsOn), endsOn: this.timeToTimestampUTC(item.endsOn)};
218 }); 214 });
219 } 215 }
220 this.modelValue = value; 216 this.modelValue = value;
@@ -222,7 +218,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -222,7 +218,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
222 } 218 }
223 } 219 }
224 220
225 - private timeToTimestamp(date: Date | number): number { 221 + private timeToTimestampUTC(date: Date | number): number {
226 if (typeof date === 'number' || date === null) { 222 if (typeof date === 'number' || date === null) {
227 return 0; 223 return 0;
228 } 224 }
@@ -244,16 +240,39 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -244,16 +240,39 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
244 240
245 changeCustomScheduler($event: MatCheckboxChange, index: number) { 241 changeCustomScheduler($event: MatCheckboxChange, index: number) {
246 const value = $event.checked; 242 const value = $event.checked;
247 - if (value) { 243 + this.disabledSelectedTime(value, index, true);
  244 + }
  245 +
  246 + private disabledSelectedTime(enable: boolean, index: number, emitEvent = false) {
  247 + if (enable) {
248 this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false}); 248 this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false});
249 - this.itemsSchedulerForm.at(index).get('endsOn').enable(); 249 + this.itemsSchedulerForm.at(index).get('endsOn').enable({emitEvent});
250 } else { 250 } else {
251 this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false}); 251 this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false});
252 - this.itemsSchedulerForm.at(index).get('endsOn').disable(); 252 + this.itemsSchedulerForm.at(index).get('endsOn').disable({emitEvent});
  253 + }
  254 + }
  255 +
  256 + private timeToMoment(date: Date | number): _moment.Moment {
  257 + if (typeof date === 'number' || date === null) {
  258 + return _moment([1970, 0, 1, 0, 0, 0, 0]);
  259 + }
  260 + return _moment([1970, 0, 1, date.getHours(), date.getMinutes(), 0, 0]);
  261 + }
  262 +
  263 + getSchedulerRangeText(control: FormGroup | AbstractControl): string {
  264 + const start = this.timeToMoment(control.get('startsOn').value);
  265 + const end = this.timeToMoment(control.get('endsOn').value);
  266 + if (start < end) {
  267 + return `<span><span class="nowrap">${start.format('hh:mm A')}</span> – <span class="nowrap">${end.format('hh:mm A')}</span></span>`;
  268 + } else if (start.valueOf() === 0 && end.valueOf() === 0 || start.isSame(_moment([1970, 0])) && end.isSame(_moment([1970, 0]))) {
  269 + return '<span><span class="nowrap">12:00 AM</span> – <span class="nowrap">12:00 PM</span></span>';
253 } 270 }
  271 + return `<span><span class="nowrap">12:00 AM</span> – <span class="nowrap">${end.format('hh:mm A')}</span>` +
  272 + ` and <span class="nowrap">${start.format('hh:mm A')}</span> – <span class="nowrap">12:00 PM</span></span>`;
254 } 273 }
255 274
256 - private get itemsSchedulerForm(): FormArray { 275 + get itemsSchedulerForm(): FormArray {
257 return this.alarmScheduleForm.get('items') as FormArray; 276 return this.alarmScheduleForm.get('items') as FormArray;
258 } 277 }
259 } 278 }
@@ -48,7 +48,7 @@ @@ -48,7 +48,7 @@
48 </button> 48 </button>
49 </div> 49 </div>
50 <div *ngIf="!createAlarmRulesFormArray().controls.length"> 50 <div *ngIf="!createAlarmRulesFormArray().controls.length">
51 - <span translate fxLayoutAlign="center center" 51 + <span translate fxLayoutAlign="center center" style="margin: 16px 0"
52 class="tb-prompt">device-profile.no-create-alarm-rules</span> 52 class="tb-prompt">device-profile.no-create-alarm-rules</span>
53 </div> 53 </div>
54 <div fxLayout="row" *ngIf="!disabled"> 54 <div fxLayout="row" *ngIf="!disabled">
@@ -57,7 +57,7 @@ @@ -57,7 +57,7 @@
57 (click)="addCreateAlarmRule()" 57 (click)="addCreateAlarmRule()"
58 matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}" 58 matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}"
59 matTooltipPosition="above"> 59 matTooltipPosition="above">
60 - <mat-icon>add_circle_outline</mat-icon> 60 + <mat-icon class="button-icon">add_circle_outline</mat-icon>
61 {{ 'device-profile.add-create-alarm-rule' | translate }} 61 {{ 'device-profile.add-create-alarm-rule' | translate }}
62 </button> 62 </button>
63 </div> 63 </div>
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 :host { 17 :host {
18 .create-alarm-rule { 18 .create-alarm-rule {
19 - border: 1px groove rgba(0, 0, 0, .25); 19 + border: 2px groove rgba(0, 0, 0, .45);
20 border-radius: 4px; 20 border-radius: 4px;
21 padding: 8px; 21 padding: 8px;
22 } 22 }
@@ -28,4 +28,9 @@ @@ -28,4 +28,9 @@
28 width: 160px; 28 width: 160px;
29 } 29 }
30 } 30 }
  31 + .button-icon{
  32 + font-size: 20px;
  33 + width: 20px;
  34 + height: 20px;
  35 + }
31 } 36 }
@@ -18,24 +18,11 @@ @@ -18,24 +18,11 @@
18 <mat-expansion-panel class="device-profile-alarm" fxFlex [formGroup]="alarmFormGroup" [(expanded)]="expanded"> 18 <mat-expansion-panel class="device-profile-alarm" fxFlex [formGroup]="alarmFormGroup" [(expanded)]="expanded">
19 <mat-expansion-panel-header> 19 <mat-expansion-panel-header>
20 <div fxFlex fxLayout="row" fxLayoutAlign="start center"> 20 <div fxFlex fxLayout="row" fxLayoutAlign="start center">
21 - <mat-panel-title [fxShow]="!expanded"> 21 + <mat-panel-title>
22 <div fxLayout="row" fxFlex fxLayoutAlign="start center"> 22 <div fxLayout="row" fxFlex fxLayoutAlign="start center">
23 {{ alarmFormGroup.get('alarmType').value }} 23 {{ alarmFormGroup.get('alarmType').value }}
24 </div> 24 </div>
25 </mat-panel-title> 25 </mat-panel-title>
26 - <mat-form-field floatLabel="always"  
27 - style="width: 600px;"  
28 - [fxShow]="expanded"  
29 - (keydown)="!disabled ? $event.stopPropagation() : null;"  
30 - (click)="!disabled ? $event.stopPropagation() : null;">  
31 - <mat-label>{{'device-profile.alarm-type' | translate}}</mat-label>  
32 - <input required matInput formControlName="alarmType" placeholder="Enter alarm type">  
33 - <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">  
34 - {{ 'device-profile.alarm-type-required' | translate }}  
35 - </mat-error>  
36 - <mat-hint *ngIf="!disabled"  
37 - innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>  
38 - </mat-form-field>  
39 <span fxFlex></span> 26 <span fxFlex></span>
40 <button *ngIf="!disabled" mat-icon-button style="min-width: 40px;" 27 <button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
41 type="button" 28 type="button"
@@ -46,6 +33,50 @@ @@ -46,6 +33,50 @@
46 </button> 33 </button>
47 </div> 34 </div>
48 </mat-expansion-panel-header> 35 </mat-expansion-panel-header>
  36 + <div fxLayout="column" fxLayoutGap="0.5em">
  37 + <mat-divider></mat-divider>
  38 + <mat-form-field fxFlex floatLabel="always">
  39 + <mat-label>{{'device-profile.alarm-type' | translate}}</mat-label>
  40 + <input required matInput formControlName="alarmType" placeholder="Enter alarm type">
  41 + <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
  42 + {{ 'device-profile.alarm-type-required' | translate }}
  43 + </mat-error>
  44 + <mat-hint *ngIf="!disabled"
  45 + innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>
  46 + </mat-form-field>
  47 + </div>
  48 + <mat-expansion-panel class="advanced-settings" [expanded]="false">
  49 + <mat-expansion-panel-header>
  50 + <mat-panel-title>
  51 + <div fxFlex fxLayout="row" fxLayoutAlign="end center">
  52 + <div class="tb-small" translate>device-profile.advanced-settings</div>
  53 + </div>
  54 + </mat-panel-title>
  55 + </mat-expansion-panel-header>
  56 + <mat-checkbox formControlName="propagate" style="display: block; padding-bottom: 16px;">
  57 + {{ 'device-profile.propagate-alarm' | translate }}
  58 + </mat-checkbox>
  59 + <section *ngIf="alarmFormGroup.get('propagate').value === true">
  60 + <mat-form-field floatLabel="always" class="mat-block">
  61 + <mat-label translate>device-profile.alarm-rule-relation-types-list</mat-label>
  62 + <mat-chip-list #relationTypesChipList [disabled]="disabled">
  63 + <mat-chip
  64 + *ngFor="let key of alarmFormGroup.get('propagateRelationTypes').value;"
  65 + (removed)="removeRelationType(key)">
  66 + {{key}}
  67 + <mat-icon matChipRemove>close</mat-icon>
  68 + </mat-chip>
  69 + <input matInput type="text" placeholder="{{'device-profile.alarm-rule-relation-types-list' | translate}}"
  70 + style="max-width: 200px;"
  71 + [matChipInputFor]="relationTypesChipList"
  72 + [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
  73 + (matChipInputTokenEnd)="addRelationType($event)"
  74 + [matChipInputAddOnBlur]="true">
  75 + </mat-chip-list>
  76 + <mat-hint innerHTML="{{ 'device-profile.alarm-rule-relation-types-list-hint' | translate }}"></mat-hint>
  77 + </mat-form-field>
  78 + </section>
  79 + </mat-expansion-panel>
49 <div fxFlex fxLayout="column"> 80 <div fxFlex fxLayout="column">
50 <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.create-alarm-rules</div> 81 <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.create-alarm-rules</div>
51 <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;"> 82 <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
@@ -68,7 +99,7 @@ @@ -68,7 +99,7 @@
68 </button> 99 </button>
69 </div> 100 </div>
70 <div *ngIf="!alarmFormGroup.get('clearRule').value"> 101 <div *ngIf="!alarmFormGroup.get('clearRule').value">
71 - <span translate fxLayoutAlign="center center" 102 + <span translate fxLayoutAlign="center center" style="margin: 16px 0"
72 class="tb-prompt">device-profile.no-clear-alarm-rule</span> 103 class="tb-prompt">device-profile.no-clear-alarm-rule</span>
73 </div> 104 </div>
74 <div fxLayout="row" *ngIf="!disabled" 105 <div fxLayout="row" *ngIf="!disabled"
@@ -78,41 +109,9 @@ @@ -78,41 +109,9 @@
78 (click)="addClearAlarmRule()" 109 (click)="addClearAlarmRule()"
79 matTooltip="{{ 'device-profile.add-clear-alarm-rule' | translate }}" 110 matTooltip="{{ 'device-profile.add-clear-alarm-rule' | translate }}"
80 matTooltipPosition="above"> 111 matTooltipPosition="above">
81 - <mat-icon>add_circle_outline</mat-icon> 112 + <mat-icon class="button-icon">add_circle_outline</mat-icon>
82 {{ 'device-profile.add-clear-alarm-rule' | translate }} 113 {{ 'device-profile.add-clear-alarm-rule' | translate }}
83 </button> 114 </button>
84 </div> 115 </div>
85 </div> 116 </div>
86 - <mat-expansion-panel class="advanced-settings" [expanded]="false">  
87 - <mat-expansion-panel-header>  
88 - <mat-panel-title>  
89 - <div fxFlex fxLayout="row" fxLayoutAlign="end center">  
90 - <div class="tb-small" translate>device-profile.advanced-settings</div>  
91 - </div>  
92 - </mat-panel-title>  
93 - </mat-expansion-panel-header>  
94 - <mat-checkbox formControlName="propagate" style="display: block; padding-bottom: 16px;">  
95 - {{ 'device-profile.propagate-alarm' | translate }}  
96 - </mat-checkbox>  
97 - <section *ngIf="alarmFormGroup.get('propagate').value === true">  
98 - <mat-form-field floatLabel="always" class="mat-block">  
99 - <mat-label translate>device-profile.alarm-rule-relation-types-list</mat-label>  
100 - <mat-chip-list #relationTypesChipList [disabled]="disabled">  
101 - <mat-chip  
102 - *ngFor="let key of alarmFormGroup.get('propagateRelationTypes').value;"  
103 - (removed)="removeRelationType(key)">  
104 - {{key}}  
105 - <mat-icon matChipRemove>close</mat-icon>  
106 - </mat-chip>  
107 - <input matInput type="text" placeholder="{{'device-profile.alarm-rule-relation-types-list' | translate}}"  
108 - style="max-width: 200px;"  
109 - [matChipInputFor]="relationTypesChipList"  
110 - [matChipInputSeparatorKeyCodes]="separatorKeysCodes"  
111 - (matChipInputTokenEnd)="addRelationType($event)"  
112 - [matChipInputAddOnBlur]="true">  
113 - </mat-chip-list>  
114 - <mat-hint innerHTML="{{ 'device-profile.alarm-rule-relation-types-list-hint' | translate }}"></mat-hint>  
115 - </mat-form-field>  
116 - </section>  
117 - </mat-expansion-panel>  
118 </mat-expansion-panel> 117 </mat-expansion-panel>
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 :host { 17 :host {
18 display: block; 18 display: block;
19 .clear-alarm-rule { 19 .clear-alarm-rule {
20 - border: 1px groove rgba(0, 0, 0, .25); 20 + border: 2px groove rgba(0, 0, 0, .45);
21 border-radius: 4px; 21 border-radius: 4px;
22 padding: 8px; 22 padding: 8px;
23 } 23 }
@@ -28,13 +28,16 @@ @@ -28,13 +28,16 @@
28 .mat-expansion-panel-header { 28 .mat-expansion-panel-header {
29 padding: 0 24px 0 8px; 29 padding: 0 24px 0 8px;
30 &.mat-expanded { 30 &.mat-expanded {
31 - height: 80px; 31 + height: 48px;
32 } 32 }
33 } 33 }
34 } 34 }
35 &.advanced-settings { 35 &.advanced-settings {
36 border: none; 36 border: none;
37 padding: 0; 37 padding: 0;
  38 + .mat-expansion-panel-header {
  39 + padding: 0 8px;
  40 + }
38 } 41 }
39 } 42 }
40 } 43 }
@@ -43,7 +46,7 @@ @@ -43,7 +46,7 @@
43 .mat-expansion-panel { 46 .mat-expansion-panel {
44 &.device-profile-alarm { 47 &.device-profile-alarm {
45 .mat-expansion-panel-body { 48 .mat-expansion-panel-body {
46 - padding: 0 8px; 49 + padding: 0 8px 8px;
47 } 50 }
48 } 51 }
49 &.advanced-settings { 52 &.advanced-settings {
@@ -51,5 +54,10 @@ @@ -51,5 +54,10 @@
51 padding: 0; 54 padding: 0;
52 } 55 }
53 } 56 }
  57 + .button-icon{
  58 + font-size: 20px;
  59 + width: 20px;
  60 + height: 20px;
  61 + }
54 } 62 }
55 } 63 }
@@ -25,4 +25,8 @@ @@ -25,4 +25,8 @@
25 } 25 }
26 } 26 }
27 } 27 }
  28 +
  29 + .tb-prompt{
  30 + margin: 30px 0;
  31 + }
28 } 32 }
1 -<!--  
2 -  
3 - Copyright © 2016-2020 The Thingsboard Authors  
4 -  
5 - Licensed under the Apache License, Version 2.0 (the "License");  
6 - you may not use this file except in compliance with the License.  
7 - You may obtain a copy of the License at  
8 -  
9 - http://www.apache.org/licenses/LICENSE-2.0  
10 -  
11 - Unless required by applicable law or agreed to in writing, software  
12 - distributed under the License is distributed on an "AS IS" BASIS,  
13 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 - See the License for the specific language governing permissions and  
15 - limitations under the License.  
16 -  
17 --->  
18 -<div [formGroup]="deviceProfileDataFormGroup" style="padding-bottom: 16px;">  
19 - <mat-accordion multi="true">  
20 - <mat-expansion-panel *ngIf="displayProfileConfiguration" [expanded]="true">  
21 - <mat-expansion-panel-header>  
22 - <mat-panel-title>  
23 - <div translate>device-profile.profile-configuration</div>  
24 - </mat-panel-title>  
25 - </mat-expansion-panel-header>  
26 - <tb-device-profile-configuration  
27 - formControlName="configuration"  
28 - required>  
29 - </tb-device-profile-configuration>  
30 - </mat-expansion-panel>  
31 - <mat-expansion-panel *ngIf="displayTransportConfiguration" [expanded]="true">  
32 - <mat-expansion-panel-header>  
33 - <mat-panel-title>  
34 - <div translate>device-profile.transport-configuration</div>  
35 - </mat-panel-title>  
36 - </mat-expansion-panel-header>  
37 - <tb-device-profile-transport-configuration  
38 - formControlName="transportConfiguration"  
39 - required>  
40 - </tb-device-profile-transport-configuration>  
41 - </mat-expansion-panel>  
42 - <mat-expansion-panel [expanded]="true">  
43 - <mat-expansion-panel-header>  
44 - <mat-panel-title>  
45 - <div>{{'device-profile.alarm-rules-with-count' | translate:  
46 - {count: deviceProfileDataFormGroup.get('alarms').value ?  
47 - deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div>  
48 - </mat-panel-title>  
49 - </mat-expansion-panel-header>  
50 - <tb-device-profile-alarms  
51 - formControlName="alarms">  
52 - </tb-device-profile-alarms>  
53 - </mat-expansion-panel>  
54 - </mat-accordion>  
55 -</div>  
1 -///  
2 -/// Copyright © 2016-2020 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 -  
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';  
22 -import {  
23 - DeviceProfileData,  
24 - DeviceProfileType,  
25 - deviceProfileTypeConfigurationInfoMap,  
26 - DeviceTransportType, deviceTransportTypeConfigurationInfoMap  
27 -} from '@shared/models/device.models';  
28 -  
29 -@Component({  
30 - selector: 'tb-device-profile-data',  
31 - templateUrl: './device-profile-data.component.html',  
32 - styleUrls: [],  
33 - providers: [{  
34 - provide: NG_VALUE_ACCESSOR,  
35 - useExisting: forwardRef(() => DeviceProfileDataComponent),  
36 - multi: true  
37 - }]  
38 -})  
39 -export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit {  
40 -  
41 - deviceProfileDataFormGroup: FormGroup;  
42 -  
43 - private requiredValue: boolean;  
44 - get required(): boolean {  
45 - return this.requiredValue;  
46 - }  
47 - @Input()  
48 - set required(value: boolean) {  
49 - this.requiredValue = coerceBooleanProperty(value);  
50 - }  
51 -  
52 - @Input()  
53 - disabled: boolean;  
54 -  
55 - displayProfileConfiguration: boolean;  
56 - displayTransportConfiguration: boolean;  
57 -  
58 - private propagateChange = (v: any) => { };  
59 -  
60 - constructor(private store: Store<AppState>,  
61 - private fb: FormBuilder) {  
62 - }  
63 -  
64 - registerOnChange(fn: any): void {  
65 - this.propagateChange = fn;  
66 - }  
67 -  
68 - registerOnTouched(fn: any): void {  
69 - }  
70 -  
71 - ngOnInit() {  
72 - this.deviceProfileDataFormGroup = this.fb.group({  
73 - configuration: [null, Validators.required],  
74 - transportConfiguration: [null, Validators.required],  
75 - alarms: [null]  
76 - });  
77 - this.deviceProfileDataFormGroup.valueChanges.subscribe(() => {  
78 - this.updateModel();  
79 - });  
80 - }  
81 -  
82 - setDisabledState(isDisabled: boolean): void {  
83 - this.disabled = isDisabled;  
84 - if (this.disabled) {  
85 - this.deviceProfileDataFormGroup.disable({emitEvent: false});  
86 - } else {  
87 - this.deviceProfileDataFormGroup.enable({emitEvent: false});  
88 - }  
89 - }  
90 -  
91 - writeValue(value: DeviceProfileData | null): void {  
92 - const deviceProfileType = value?.configuration?.type;  
93 - this.displayProfileConfiguration = deviceProfileType &&  
94 - deviceProfileTypeConfigurationInfoMap.get(deviceProfileType).hasProfileConfiguration;  
95 - const deviceTransportType = value?.transportConfiguration?.type;  
96 - this.displayTransportConfiguration = deviceTransportType &&  
97 - deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasProfileConfiguration;  
98 - this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false});  
99 - this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});  
100 - this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false});  
101 - }  
102 -  
103 - private updateModel() {  
104 - let deviceProfileData: DeviceProfileData = null;  
105 - if (this.deviceProfileDataFormGroup.valid) {  
106 - deviceProfileData = this.deviceProfileDataFormGroup.getRawValue();  
107 - }  
108 - this.propagateChange(deviceProfileData);  
109 - }  
110 -}  
@@ -53,7 +53,7 @@ @@ -53,7 +53,7 @@
53 labelText="device-profile.default-rule-chain" 53 labelText="device-profile.default-rule-chain"
54 formControlName="defaultRuleChainId"> 54 formControlName="defaultRuleChainId">
55 </tb-rule-chain-autocomplete> 55 </tb-rule-chain-autocomplete>
56 - <mat-form-field class="mat-block"> 56 + <mat-form-field fxHide class="mat-block">
57 <mat-label translate>device-profile.type</mat-label> 57 <mat-label translate>device-profile.type</mat-label>
58 <mat-select formControlName="type" required> 58 <mat-select formControlName="type" required>
59 <mat-option *ngFor="let type of deviceProfileTypes" [value]="type"> 59 <mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
@@ -65,21 +65,6 @@ @@ -65,21 +65,6 @@
65 </mat-error> 65 </mat-error>
66 </mat-form-field> 66 </mat-form-field>
67 <mat-form-field class="mat-block"> 67 <mat-form-field class="mat-block">
68 - <mat-label translate>device-profile.transport-type</mat-label>  
69 - <mat-select formControlName="transportType" required>  
70 - <mat-option *ngFor="let type of deviceTransportTypes" [value]="type">  
71 - {{deviceTransportTypeTranslations.get(type) | translate}}  
72 - </mat-option>  
73 - </mat-select>  
74 - <mat-error *ngIf="entityForm.get('transportType').hasError('required')">  
75 - {{ 'device-profile.transport-type-required' | translate }}  
76 - </mat-error>  
77 - </mat-form-field>  
78 - <tb-device-profile-data  
79 - formControlName="profileData"  
80 - required>  
81 - </tb-device-profile-data>  
82 - <mat-form-field class="mat-block">  
83 <mat-label translate>device-profile.description</mat-label> 68 <mat-label translate>device-profile.description</mat-label>
84 <textarea matInput formControlName="description" rows="2"></textarea> 69 <textarea matInput formControlName="description" rows="2"></textarea>
85 </mat-form-field> 70 </mat-form-field>
@@ -77,7 +77,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -77,7 +77,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
77 name: [entity ? entity.name : '', [Validators.required]], 77 name: [entity ? entity.name : '', [Validators.required]],
78 type: [entity ? entity.type : null, [Validators.required]], 78 type: [entity ? entity.type : null, [Validators.required]],
79 transportType: [entity ? entity.transportType : null, [Validators.required]], 79 transportType: [entity ? entity.transportType : null, [Validators.required]],
80 - profileData: [entity && !this.isAdd ? entity.profileData : {}, []], 80 + profileData: this.fb.group({
  81 + configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required],
  82 + transportConfiguration: [entity && !this.isAdd ? entity.profileData?.transportConfiguration : {}, Validators.required],
  83 + alarms: [entity && !this.isAdd ? entity.profileData?.alarms : []]
  84 + }),
81 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], 85 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
82 description: [entity ? entity.description : '', []], 86 description: [entity ? entity.description : '', []],
83 } 87 }
@@ -138,7 +142,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -138,7 +142,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
138 if (formValue.defaultRuleChainId) { 142 if (formValue.defaultRuleChainId) {
139 formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId); 143 formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId);
140 } 144 }
141 - return formValue; 145 + return super.prepareFormValue(formValue);
142 } 146 }
143 147
144 onDeviceProfileIdCopied(event) { 148 onDeviceProfileIdCopied(event) {
@@ -16,9 +16,5 @@ @@ -16,9 +16,5 @@
16 16
17 --> 17 -->
18 <form [formGroup]="defaultDeviceProfileConfigurationFormGroup" style="padding-bottom: 16px;"> 18 <form [formGroup]="defaultDeviceProfileConfigurationFormGroup" style="padding-bottom: 16px;">
19 - <tb-json-object-edit  
20 - [required]="required"  
21 - label="{{ 'device-profile.type-default' | translate }}"  
22 - formControlName="configuration">  
23 - </tb-json-object-edit> 19 +
24 </form> 20 </form>
@@ -16,9 +16,5 @@ @@ -16,9 +16,5 @@
16 16
17 --> 17 -->
18 <form [formGroup]="defaultDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;"> 18 <form [formGroup]="defaultDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;">
19 - <tb-json-object-edit  
20 - [required]="required"  
21 - label="{{ 'device-profile.transport-type-default' | translate }}"  
22 - formControlName="configuration">  
23 - </tb-json-object-edit> 19 +
24 </form> 20 </form>
@@ -16,6 +16,52 @@ @@ -16,6 +16,52 @@
16 16
17 --> 17 -->
18 <mat-tab *ngIf="entity" 18 <mat-tab *ngIf="entity"
  19 + label="{{ 'device-profile.transport-configuration' | translate }}" #transportType="matTab">
  20 + <div class="mat-padding" [formGroup]="detailsForm">
  21 + <mat-form-field class="mat-block">
  22 + <mat-label translate>device-profile.transport-type</mat-label>
  23 + <mat-select formControlName="transportType" required>
  24 + <mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
  25 + {{deviceTransportTypeTranslations.get(type) | translate}}
  26 + </mat-option>
  27 + </mat-select>
  28 + <mat-hint *ngIf="detailsForm.get('transportType').value">
  29 + {{deviceTransportTypeHints.get(detailsForm.get('transportType').value) | translate}}
  30 + </mat-hint>
  31 + <mat-error *ngIf="detailsForm.get('transportType').hasError('required')">
  32 + {{ 'device-profile.transport-type-required' | translate }}
  33 + </mat-error>
  34 + </mat-form-field>
  35 + <div formGroupName="profileData">
  36 + <tb-device-profile-transport-configuration
  37 + formControlName="transportConfiguration"
  38 + required>
  39 + </tb-device-profile-transport-configuration>
  40 + </div>
  41 + </div>
  42 +</mat-tab>
  43 +<mat-tab *ngIf="entity"
  44 + label="{{'device-profile.alarm-rules' | translate:
  45 + {count: entity.profileData.alarms && entity.profileData.alarms.length ?
  46 + entity.profileData.alarms.length : 0} }}" #alarmRules="matTab">
  47 + <div class="mat-padding" [formGroup]="detailsForm">
  48 + <div formGroupName="profileData">
  49 + <tb-device-profile-alarms formControlName="alarms"></tb-device-profile-alarms>
  50 + </div>
  51 + </div>
  52 +</mat-tab>
  53 +<mat-tab *ngIf="false"
  54 + label="{{'device-profile.profile-configuration' | translate }}" #deviceProfile="matTab">
  55 + <div class="mat-padding" [formGroup]="detailsForm">
  56 + <div formGroupName="profileData">
  57 + <tb-device-profile-configuration
  58 + formControlName="configuration"
  59 + required>
  60 + </tb-device-profile-configuration>
  61 + </div>
  62 + </div>
  63 +</mat-tab>
  64 +<mat-tab *ngIf="entity && !isEdit"
19 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> 65 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE" 66 <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"
21 [active]="attributesTab.isActive" 67 [active]="attributesTab.isActive"
@@ -23,7 +69,7 @@ @@ -23,7 +69,7 @@
23 [entityName]="entity.name"> 69 [entityName]="entity.name">
24 </tb-attribute-table> 70 </tb-attribute-table>
25 </mat-tab> 71 </mat-tab>
26 -<mat-tab *ngIf="entity" 72 +<mat-tab *ngIf="entity && !isEdit"
27 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> 73 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
28 <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" 74 <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
29 disableAttributeScopeSelection 75 disableAttributeScopeSelection
@@ -32,11 +78,11 @@ @@ -32,11 +78,11 @@
32 [entityName]="entity.name"> 78 [entityName]="entity.name">
33 </tb-attribute-table> 79 </tb-attribute-table>
34 </mat-tab> 80 </mat-tab>
35 -<mat-tab *ngIf="entity" 81 +<mat-tab *ngIf="entity && !isEdit"
36 label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab"> 82 label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
37 <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table> 83 <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
38 </mat-tab> 84 </mat-tab>
39 -<mat-tab *ngIf="entity" 85 +<mat-tab *ngIf="entity && !isEdit"
40 label="{{ 'tenant.events' | translate }}" #eventsTab="matTab"> 86 label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
41 <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="nullUid" 87 <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="nullUid"
42 [entityId]="entity.id"></tb-event-table> 88 [entityId]="entity.id"></tb-event-table>
@@ -18,7 +18,12 @@ import { Component } from '@angular/core'; @@ -18,7 +18,12 @@ import { Component } from '@angular/core';
18 import { Store } from '@ngrx/store'; 18 import { Store } from '@ngrx/store';
19 import { AppState } from '@core/core.state'; 19 import { AppState } from '@core/core.state';
20 import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; 20 import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
21 -import { DeviceProfile } from '@shared/models/device.models'; 21 +import {
  22 + DeviceProfile,
  23 + DeviceTransportType,
  24 + deviceTransportTypeHintMap,
  25 + deviceTransportTypeTranslationMap
  26 +} from '@shared/models/device.models';
22 27
23 @Component({ 28 @Component({
24 selector: 'tb-device-profile-tabs', 29 selector: 'tb-device-profile-tabs',
@@ -27,6 +32,12 @@ import { DeviceProfile } from '@shared/models/device.models'; @@ -27,6 +32,12 @@ import { DeviceProfile } from '@shared/models/device.models';
27 }) 32 })
28 export class DeviceProfileTabsComponent extends EntityTabsComponent<DeviceProfile> { 33 export class DeviceProfileTabsComponent extends EntityTabsComponent<DeviceProfile> {
29 34
  35 + deviceTransportTypes = Object.keys(DeviceTransportType);
  36 +
  37 + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
  38 +
  39 + deviceTransportTypeHints = deviceTransportTypeHintMap;
  40 +
30 constructor(protected store: Store<AppState>) { 41 constructor(protected store: Store<AppState>) {
31 super(store); 42 super(store);
32 } 43 }
@@ -59,6 +59,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -59,6 +59,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
59 this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE_PROFILE); 59 this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE_PROFILE);
60 this.config.entityResources = entityTypeResources.get(EntityType.DEVICE_PROFILE); 60 this.config.entityResources = entityTypeResources.get(EntityType.DEVICE_PROFILE);
61 61
  62 + this.config.hideDetailsTabsOnEdit = false;
  63 +
62 this.config.addDialogStyle = {width: '1000px'}; 64 this.config.addDialogStyle = {width: '1000px'};
63 65
64 this.config.columns.push( 66 this.config.columns.push(
@@ -33,7 +33,7 @@ export enum DeviceProfileType { @@ -33,7 +33,7 @@ export enum DeviceProfileType {
33 export enum DeviceTransportType { 33 export enum DeviceTransportType {
34 DEFAULT = 'DEFAULT', 34 DEFAULT = 'DEFAULT',
35 MQTT = 'MQTT', 35 MQTT = 'MQTT',
36 -// LWM2M = 'LWM2M' 36 + // LWM2M = 'LWM2M'
37 } 37 }
38 38
39 export enum MqttTransportPayloadType { 39 export enum MqttTransportPayloadType {
@@ -68,7 +68,7 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st @@ -68,7 +68,7 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
68 [ 68 [
69 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default'], 69 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default'],
70 [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt'], 70 [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt'],
71 - // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m'] 71 + // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m']
72 ] 72 ]
73 ); 73 );
74 74
@@ -76,7 +76,7 @@ export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>( @@ -76,7 +76,7 @@ export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>(
76 [ 76 [
77 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], 77 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'],
78 [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], 78 [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'],
79 - // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] 79 + // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint']
80 ] 80 ]
81 ); 81 );
82 82