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 387 return acc;
388 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 23 import { EntityAction } from '@home/models/entity/entity-component.models';
24 24 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
25 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 28 // @dynamic
29 29 @Directive()
... ... @@ -115,20 +115,7 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
115 115 }
116 116
117 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 121 protected setEntitiesTableConfig(entitiesTableConfig: C) {
... ...
... ... @@ -76,7 +76,6 @@
76 76 type="button"
77 77 matTooltip="{{ (dynamicMode ? 'filter.switch-to-default-value' : 'filter.switch-to-dynamic-value') | translate }}"
78 78 matTooltipPosition="above"
79   - *ngIf="allow"
80 79 (click)="dynamicMode = !dynamicMode">
81 80 <mat-icon class="tb-mat-20" [svgIcon]="dynamicMode ? 'mdi:numeric' : 'mdi:variable'"></mat-icon>
82 81 </button>
... ...
... ... @@ -54,7 +54,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
54 54 if (allow) {
55 55 this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER);
56 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 90 import { TenantProfileDataComponent } from './profile/tenant-profile-data.component';
91 91 import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component';
92 92 import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component';
93   -import { DeviceProfileDataComponent } from './profile/device-profile-data.component';
94 93 import { DeviceProfileComponent } from './profile/device-profile.component';
95 94 import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component';
96 95 import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component';
... ... @@ -195,7 +194,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen
195 194 AlarmRuleConditionComponent,
196 195 DeviceProfileAlarmComponent,
197 196 DeviceProfileAlarmsComponent,
198   - DeviceProfileDataComponent,
199 197 DeviceProfileComponent,
200 198 DeviceProfileDialogComponent,
201 199 AddDeviceProfileDialogComponent,
... ... @@ -277,7 +275,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen
277 275 AlarmRuleConditionComponent,
278 276 DeviceProfileAlarmComponent,
279 277 DeviceProfileAlarmsComponent,
280   - DeviceProfileDataComponent,
281 278 DeviceProfileComponent,
282 279 DeviceProfileDialogComponent,
283 280 AddDeviceProfileDialogComponent,
... ...
... ... @@ -29,7 +29,7 @@
29 29 </mat-progress-bar>
30 30 <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
31 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 33 <mat-step [stepControl]="deviceProfileDetailsFormGroup">
34 34 <form [formGroup]="deviceProfileDetailsFormGroup" style="padding-bottom: 16px;">
35 35 <ng-template matStepLabel>{{ 'device-profile.device-profile-details' | translate }}</ng-template>
... ... @@ -45,7 +45,7 @@
45 45 labelText="device-profile.default-rule-chain"
46 46 formControlName="defaultRuleChainId">
47 47 </tb-rule-chain-autocomplete>
48   - <mat-form-field class="mat-block">
  48 + <mat-form-field fxHide class="mat-block">
49 49 <mat-label translate>device-profile.type</mat-label>
50 50 <mat-select formControlName="type" required>
51 51 <mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
... ... @@ -63,7 +63,7 @@
63 63 </fieldset>
64 64 </form>
65 65 </mat-step>
66   - <mat-step [stepControl]="transportConfigFormGroup">
  66 + <mat-step [stepControl]="transportConfigFormGroup" [optional]="true">
67 67 <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
68 68 <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template>
69 69 <mat-form-field class="mat-block">
... ... @@ -73,6 +73,9 @@
73 73 {{deviceTransportTypeTranslations.get(type) | translate}}
74 74 </mat-option>
75 75 </mat-select>
  76 + <mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
  77 + {{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
  78 + </mat-hint>
76 79 <mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
77 80 {{ 'device-profile.transport-type-required' | translate }}
78 81 </mat-error>
... ... @@ -83,7 +86,7 @@
83 86 </tb-device-profile-transport-configuration>
84 87 </form>
85 88 </mat-step>
86   - <mat-step [stepControl]="alarmRulesFormGroup">
  89 + <mat-step [stepControl]="alarmRulesFormGroup" [optional]="true">
87 90 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
88 91 <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
89 92 {count: alarmRulesFormGroup.get('alarms').value ?
... ... @@ -95,19 +98,28 @@
95 98 </mat-step>
96 99 </mat-horizontal-stepper>
97 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 109 <button mat-button
  110 + color="primary"
105 111 [disabled]="(isLoading$ | async)"
106 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 123 </div>
112 124 </div>
113 125 </div>
... ...
... ... @@ -13,25 +13,51 @@
13 13 * See the License for the specific language governing permissions and
14 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 26 :host ::ng-deep {
31 27 .mat-dialog-content {
  28 + display: flex;
  29 + flex-direction: column;
  30 + height: 100%;
  31 + padding: 24px 24px 8px !important;
  32 +
32 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 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 36 DeviceProfile,
37 37 DeviceProfileType,
38 38 deviceProfileTypeTranslationMap,
39   - DeviceTransportType,
  39 + DeviceTransportType, deviceTransportTypeHintMap,
40 40 deviceTransportTypeTranslationMap
41 41 } from '@shared/models/device.models';
42 42 import { DeviceProfileService } from '@core/http/device-profile.service';
43 43 import { EntityType } from '@shared/models/entity-type.models';
44 44 import { MatHorizontalStepper } from '@angular/material/stepper';
45 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 49 export interface AddDeviceProfileDialogData {
48 50 deviceProfileName: string;
... ... @@ -62,12 +64,16 @@ export class AddDeviceProfileDialogComponent extends
62 64
63 65 selectedIndex = 0;
64 66
  67 + showNext = true;
  68 +
65 69 entityType = EntityType;
66 70
67 71 deviceProfileTypes = Object.keys(DeviceProfileType);
68 72
69 73 deviceProfileTypeTranslations = deviceProfileTypeTranslationMap;
70 74
  75 + deviceTransportTypeHints = deviceTransportTypeHintMap;
  76 +
71 77 deviceTransportTypes = Object.keys(DeviceTransportType);
72 78
73 79 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
... ... @@ -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 21 <tb-alarm-rule-condition fxFlex class="row"
22 22 formControlName="condition">
23 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 63 </mat-option>
32 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 88 </mat-error>
36 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 90 </div>
93 91 </section>
94 92 </mat-tab>
... ...
... ... @@ -29,6 +29,7 @@ import { AlarmConditionType, AlarmConditionTypeTranslationMap, AlarmRule } from
29 29 import { MatDialog } from '@angular/material/dialog';
30 30 import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models';
31 31 import { coerceBooleanProperty } from '@angular/cdk/coercion';
  32 +import { isUndefined } from '@core/utils';
32 33
33 34 @Component({
34 35 selector: 'tb-alarm-rule',
... ... @@ -117,10 +118,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
117 118
118 119 writeValue(value: AlarmRule): void {
119 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 124 this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false});
126 125 this.updateValidators(this.modelValue?.condition?.spec?.type);
... ...
... ... @@ -35,7 +35,7 @@
35 35 </tb-timezone-select>
36 36 <section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.SPECIFIC_TIME">
37 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 39 <div fxLayout="row" fxLayoutGap="16px">
40 40 <mat-checkbox [formControl]="weeklyRepeatControl(0)">
41 41 {{ 'device-profile.schedule-day.monday' | translate }}
... ... @@ -64,158 +64,189 @@
64 64 </div>
65 65 <div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-time</div>
66 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 86 </div>
80 87 </section>
81 88 <section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.CUSTOM">
82 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 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 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 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 135 </div>
161 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 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 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 250 </div>
220 251 </div>
221 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 16
17 17 import { Component, forwardRef, Input, OnInit } from '@angular/core';
18 18 import {
  19 + AbstractControl,
19 20 ControlValueAccessor,
20 21 FormArray,
21 22 FormBuilder,
... ... @@ -35,6 +36,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox';
35 36 @Component({
36 37 selector: 'tb-alarm-schedule',
37 38 templateUrl: './alarm-schedule.component.html',
  39 + styleUrls: ['./alarm-schedule.component.scss'],
38 40 providers: [{
39 41 provide: NG_VALUE_ACCESSOR,
40 42 useExisting: forwardRef(() => AlarmScheduleComponent),
... ... @@ -79,7 +81,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
79 81 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)))
80 82 });
81 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 85 this.updateValidators(type, true);
84 86 this.alarmScheduleForm.updateValueAndValidity();
85 87 });
... ... @@ -131,13 +133,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
131 133 this.modelValue.items
132 134 .sort((a, b) => a.dayOfWeek - b.dayOfWeek)
133 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 137 alarmDays.push({
142 138 enabled: item.enabled,
143 139 startsOn: this.timestampToTime(item.startsOn),
... ... @@ -206,15 +202,15 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
206 202 .filter(day => !!day);
207 203 }
208 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 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 210 if (isDefined(value.items)){
215 211 value.items = this.alarmScheduleForm.getRawValue().items;
216 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 216 this.modelValue = value;
... ... @@ -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 222 if (typeof date === 'number' || date === null) {
227 223 return 0;
228 224 }
... ... @@ -244,16 +240,39 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
244 240
245 241 changeCustomScheduler($event: MatCheckboxChange, index: number) {
246 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 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 250 } else {
251 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 276 return this.alarmScheduleForm.get('items') as FormArray;
258 277 }
259 278 }
... ...
... ... @@ -48,7 +48,7 @@
48 48 </button>
49 49 </div>
50 50 <div *ngIf="!createAlarmRulesFormArray().controls.length">
51   - <span translate fxLayoutAlign="center center"
  51 + <span translate fxLayoutAlign="center center" style="margin: 16px 0"
52 52 class="tb-prompt">device-profile.no-create-alarm-rules</span>
53 53 </div>
54 54 <div fxLayout="row" *ngIf="!disabled">
... ... @@ -57,7 +57,7 @@
57 57 (click)="addCreateAlarmRule()"
58 58 matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}"
59 59 matTooltipPosition="above">
60   - <mat-icon>add_circle_outline</mat-icon>
  60 + <mat-icon class="button-icon">add_circle_outline</mat-icon>
61 61 {{ 'device-profile.add-create-alarm-rule' | translate }}
62 62 </button>
63 63 </div>
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 :host {
18 18 .create-alarm-rule {
19   - border: 1px groove rgba(0, 0, 0, .25);
  19 + border: 2px groove rgba(0, 0, 0, .45);
20 20 border-radius: 4px;
21 21 padding: 8px;
22 22 }
... ... @@ -28,4 +28,9 @@
28 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 18 <mat-expansion-panel class="device-profile-alarm" fxFlex [formGroup]="alarmFormGroup" [(expanded)]="expanded">
19 19 <mat-expansion-panel-header>
20 20 <div fxFlex fxLayout="row" fxLayoutAlign="start center">
21   - <mat-panel-title [fxShow]="!expanded">
  21 + <mat-panel-title>
22 22 <div fxLayout="row" fxFlex fxLayoutAlign="start center">
23 23 {{ alarmFormGroup.get('alarmType').value }}
24 24 </div>
25 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 26 <span fxFlex></span>
40 27 <button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
41 28 type="button"
... ... @@ -46,6 +33,50 @@
46 33 </button>
47 34 </div>
48 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 80 <div fxFlex fxLayout="column">
50 81 <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.create-alarm-rules</div>
51 82 <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
... ... @@ -68,7 +99,7 @@
68 99 </button>
69 100 </div>
70 101 <div *ngIf="!alarmFormGroup.get('clearRule').value">
71   - <span translate fxLayoutAlign="center center"
  102 + <span translate fxLayoutAlign="center center" style="margin: 16px 0"
72 103 class="tb-prompt">device-profile.no-clear-alarm-rule</span>
73 104 </div>
74 105 <div fxLayout="row" *ngIf="!disabled"
... ... @@ -78,41 +109,9 @@
78 109 (click)="addClearAlarmRule()"
79 110 matTooltip="{{ 'device-profile.add-clear-alarm-rule' | translate }}"
80 111 matTooltipPosition="above">
81   - <mat-icon>add_circle_outline</mat-icon>
  112 + <mat-icon class="button-icon">add_circle_outline</mat-icon>
82 113 {{ 'device-profile.add-clear-alarm-rule' | translate }}
83 114 </button>
84 115 </div>
85 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 117 </mat-expansion-panel>
... ...
... ... @@ -17,7 +17,7 @@
17 17 :host {
18 18 display: block;
19 19 .clear-alarm-rule {
20   - border: 1px groove rgba(0, 0, 0, .25);
  20 + border: 2px groove rgba(0, 0, 0, .45);
21 21 border-radius: 4px;
22 22 padding: 8px;
23 23 }
... ... @@ -28,13 +28,16 @@
28 28 .mat-expansion-panel-header {
29 29 padding: 0 24px 0 8px;
30 30 &.mat-expanded {
31   - height: 80px;
  31 + height: 48px;
32 32 }
33 33 }
34 34 }
35 35 &.advanced-settings {
36 36 border: none;
37 37 padding: 0;
  38 + .mat-expansion-panel-header {
  39 + padding: 0 8px;
  40 + }
38 41 }
39 42 }
40 43 }
... ... @@ -43,7 +46,7 @@
43 46 .mat-expansion-panel {
44 47 &.device-profile-alarm {
45 48 .mat-expansion-panel-body {
46   - padding: 0 8px;
  49 + padding: 0 8px 8px;
47 50 }
48 51 }
49 52 &.advanced-settings {
... ... @@ -51,5 +54,10 @@
51 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 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 53 labelText="device-profile.default-rule-chain"
54 54 formControlName="defaultRuleChainId">
55 55 </tb-rule-chain-autocomplete>
56   - <mat-form-field class="mat-block">
  56 + <mat-form-field fxHide class="mat-block">
57 57 <mat-label translate>device-profile.type</mat-label>
58 58 <mat-select formControlName="type" required>
59 59 <mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
... ... @@ -65,21 +65,6 @@
65 65 </mat-error>
66 66 </mat-form-field>
67 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 68 <mat-label translate>device-profile.description</mat-label>
84 69 <textarea matInput formControlName="description" rows="2"></textarea>
85 70 </mat-form-field>
... ...
... ... @@ -77,7 +77,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
77 77 name: [entity ? entity.name : '', [Validators.required]],
78 78 type: [entity ? entity.type : null, [Validators.required]],
79 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 85 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
82 86 description: [entity ? entity.description : '', []],
83 87 }
... ... @@ -138,7 +142,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
138 142 if (formValue.defaultRuleChainId) {
139 143 formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId);
140 144 }
141   - return formValue;
  145 + return super.prepareFormValue(formValue);
142 146 }
143 147
144 148 onDeviceProfileIdCopied(event) {
... ...
... ... @@ -16,9 +16,5 @@
16 16
17 17 -->
18 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 20 </form>
... ...
... ... @@ -16,9 +16,5 @@
16 16
17 17 -->
18 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 20 </form>
... ...
... ... @@ -16,6 +16,52 @@
16 16
17 17 -->
18 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 65 label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
20 66 <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"
21 67 [active]="attributesTab.isActive"
... ... @@ -23,7 +69,7 @@
23 69 [entityName]="entity.name">
24 70 </tb-attribute-table>
25 71 </mat-tab>
26   -<mat-tab *ngIf="entity"
  72 +<mat-tab *ngIf="entity && !isEdit"
27 73 label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
28 74 <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
29 75 disableAttributeScopeSelection
... ... @@ -32,11 +78,11 @@
32 78 [entityName]="entity.name">
33 79 </tb-attribute-table>
34 80 </mat-tab>
35   -<mat-tab *ngIf="entity"
  81 +<mat-tab *ngIf="entity && !isEdit"
36 82 label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
37 83 <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
38 84 </mat-tab>
39   -<mat-tab *ngIf="entity"
  85 +<mat-tab *ngIf="entity && !isEdit"
40 86 label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
41 87 <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="nullUid"
42 88 [entityId]="entity.id"></tb-event-table>
... ...
... ... @@ -18,7 +18,12 @@ import { Component } from '@angular/core';
18 18 import { Store } from '@ngrx/store';
19 19 import { AppState } from '@core/core.state';
20 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 28 @Component({
24 29 selector: 'tb-device-profile-tabs',
... ... @@ -27,6 +32,12 @@ import { DeviceProfile } from '@shared/models/device.models';
27 32 })
28 33 export class DeviceProfileTabsComponent extends EntityTabsComponent<DeviceProfile> {
29 34
  35 + deviceTransportTypes = Object.keys(DeviceTransportType);
  36 +
  37 + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
  38 +
  39 + deviceTransportTypeHints = deviceTransportTypeHintMap;
  40 +
30 41 constructor(protected store: Store<AppState>) {
31 42 super(store);
32 43 }
... ...
... ... @@ -59,6 +59,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
59 59 this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE_PROFILE);
60 60 this.config.entityResources = entityTypeResources.get(EntityType.DEVICE_PROFILE);
61 61
  62 + this.config.hideDetailsTabsOnEdit = false;
  63 +
62 64 this.config.addDialogStyle = {width: '1000px'};
63 65
64 66 this.config.columns.push(
... ...
... ... @@ -33,7 +33,7 @@ export enum DeviceProfileType {
33 33 export enum DeviceTransportType {
34 34 DEFAULT = 'DEFAULT',
35 35 MQTT = 'MQTT',
36   -// LWM2M = 'LWM2M'
  36 + // LWM2M = 'LWM2M'
37 37 }
38 38
39 39 export enum MqttTransportPayloadType {
... ... @@ -68,7 +68,7 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
68 68 [
69 69 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default'],
70 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 76 [
77 77 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'],
78 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
... ...