Commit fd602dec7f2fd754fde946b0727adebb3c2a093e
1 parent
1f5f411b
UI: Added device profile schedule setting for alarm setting
Showing
16 changed files
with
848 additions
and
10 deletions
... | ... | @@ -478,7 +478,7 @@ spring: |
478 | 478 | database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" |
479 | 479 | datasource: |
480 | 480 | driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" |
481 | - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" | |
481 | + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard_32}" | |
482 | 482 | username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
483 | 483 | password: "${SPRING_DATASOURCE_PASSWORD:postgres}" |
484 | 484 | hikari: | ... | ... |
... | ... | @@ -137,7 +137,8 @@ |
137 | 137 | "react-is", |
138 | 138 | "hoist-non-react-statics", |
139 | 139 | "classnames", |
140 | - "raf" | |
140 | + "raf", | |
141 | + "moment-timezone" | |
141 | 142 | ] |
142 | 143 | }, |
143 | 144 | "configurations": { |
... | ... | @@ -248,4 +249,4 @@ |
248 | 249 | "cli": { |
249 | 250 | "packageManager": "yarn" |
250 | 251 | } |
251 | -} | |
\ No newline at end of file | ||
252 | +} | ... | ... |
... | ... | @@ -63,6 +63,7 @@ |
63 | 63 | "material-design-icons": "^3.0.1", |
64 | 64 | "messageformat": "^2.3.0", |
65 | 65 | "moment": "^2.27.0", |
66 | + "moment-timezone": "^0.5.31", | |
66 | 67 | "ngx-clipboard": "^13.0.1", |
67 | 68 | "ngx-color-picker": "^10.0.1", |
68 | 69 | "ngx-daterangepicker-material": "^4.0.1", |
... | ... | @@ -109,6 +110,7 @@ |
109 | 110 | "@types/leaflet-polylinedecorator": "^1.6.0", |
110 | 111 | "@types/leaflet.markercluster": "^1.4.2", |
111 | 112 | "@types/lodash": "^4.14.159", |
113 | + "@types/moment-timezone": "^0.5.30", | |
112 | 114 | "@types/raphael": "^2.3.0", |
113 | 115 | "@types/react": "^16.9.46", |
114 | 116 | "@types/react-dom": "^16.9.8", | ... | ... |
... | ... | @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k |
107 | 107 | import { FilterTextComponent } from './filter/filter-text.component'; |
108 | 108 | import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; |
109 | 109 | import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; |
110 | +import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; | |
110 | 111 | |
111 | 112 | @NgModule({ |
112 | 113 | declarations: |
... | ... | @@ -196,7 +197,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp |
196 | 197 | DeviceProfileComponent, |
197 | 198 | DeviceProfileDialogComponent, |
198 | 199 | AddDeviceProfileDialogComponent, |
199 | - RuleChainAutocompleteComponent | |
200 | + RuleChainAutocompleteComponent, | |
201 | + AlarmScheduleComponent | |
200 | 202 | ], |
201 | 203 | imports: [ |
202 | 204 | CommonModule, |
... | ... | @@ -275,7 +277,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp |
275 | 277 | DeviceProfileComponent, |
276 | 278 | DeviceProfileDialogComponent, |
277 | 279 | AddDeviceProfileDialogComponent, |
278 | - RuleChainAutocompleteComponent | |
280 | + RuleChainAutocompleteComponent, | |
281 | + AlarmScheduleComponent | |
279 | 282 | ], |
280 | 283 | providers: [ |
281 | 284 | WidgetComponentService, | ... | ... |
... | ... | @@ -93,7 +93,9 @@ |
93 | 93 | </section> |
94 | 94 | </mat-tab> |
95 | 95 | <mat-tab label="{{ 'device-profile.schedule' | translate }}"> |
96 | - <div class="row">{{ 'device-profile.schedule' | translate }}</div> | |
96 | + <tb-alarm-schedule fxFlex class="row" | |
97 | + formControlName="schedule"> | |
98 | + </tb-alarm-schedule> | |
97 | 99 | </mat-tab> |
98 | 100 | <mat-tab label="{{ 'device-profile.alarm-rule-details' | translate }}"> |
99 | 101 | <mat-form-field class="mat-block row"> | ... | ... |
... | ... | @@ -95,6 +95,7 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat |
95 | 95 | count: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]] |
96 | 96 | }) |
97 | 97 | }, Validators.required), |
98 | + schedule: [null], | |
98 | 99 | alarmDetails: [null] |
99 | 100 | }); |
100 | 101 | this.alarmRuleFormGroup.get('condition.spec.type').valueChanges.subscribe((type) => { | ... | ... |
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 | +<section [formGroup]="alarmScheduleForm" fxLayout="column"> | |
19 | + <mat-form-field class="mat-block" hideRequiredMarker floatLabel="always"> | |
20 | + <mat-label> </mat-label> | |
21 | + <mat-select formControlName="type" required placeholder="{{ 'device-profile.schedule-type' | translate }}"> | |
22 | + <mat-option *ngFor="let alarmScheduleType of alarmScheduleTypes" [value]="alarmScheduleType"> | |
23 | + {{ alarmScheduleTypeTranslate.get(alarmScheduleType) | translate }} | |
24 | + </mat-option> | |
25 | + </mat-select> | |
26 | + <mat-error *ngIf="alarmScheduleForm.get('type').hasError('required')"> | |
27 | + {{ 'device-profile.schedule-type-required' | translate }} | |
28 | + </mat-error> | |
29 | + </mat-form-field> | |
30 | + <div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME"> | |
31 | + <tb-timezone-select | |
32 | + [defaultTimezone]="defaultTimezone" | |
33 | + required | |
34 | + formControlName="timezone"> | |
35 | + </tb-timezone-select> | |
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> | |
38 | + <div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" style="padding-bottom: 16px;"> | |
39 | + <div fxLayout="row" fxLayoutGap="16px"> | |
40 | + <mat-checkbox [formControl]="weeklyRepeatControl(0)"> | |
41 | + {{ 'device-profile.schedule-day.monday' | translate }} | |
42 | + </mat-checkbox> | |
43 | + <mat-checkbox [formControl]="weeklyRepeatControl(1)"> | |
44 | + {{ 'device-profile.schedule-day.tuesday' | translate }} | |
45 | + </mat-checkbox> | |
46 | + <mat-checkbox [formControl]="weeklyRepeatControl(2)"> | |
47 | + {{ 'device-profile.schedule-day.wednesday' | translate }} | |
48 | + </mat-checkbox> | |
49 | + <mat-checkbox [formControl]="weeklyRepeatControl(3)"> | |
50 | + {{ 'device-profile.schedule-day.thursday' | translate }} | |
51 | + </mat-checkbox> | |
52 | + </div> | |
53 | + <div fxLayout="row" fxLayoutGap="16px"> | |
54 | + <mat-checkbox [formControl]="weeklyRepeatControl(4)"> | |
55 | + {{ 'device-profile.schedule-day.friday' | translate }} | |
56 | + </mat-checkbox> | |
57 | + <mat-checkbox [formControl]="weeklyRepeatControl(5)"> | |
58 | + {{ 'device-profile.schedule-day.saturday' | translate }} | |
59 | + </mat-checkbox> | |
60 | + <mat-checkbox [formControl]="weeklyRepeatControl(6)"> | |
61 | + {{ 'device-profile.schedule-day.sunday' | translate }} | |
62 | + </mat-checkbox> | |
63 | + </div> | |
64 | + </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"> | |
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> | |
79 | + </div> | |
80 | + </section> | |
81 | + <section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.CUSTOM"> | |
82 | + <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> | |
103 | + </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> | |
122 | + </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> | |
141 | + </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> | |
160 | + </div> | |
161 | + </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> | |
181 | + </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> | |
200 | + </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> | |
219 | + </div> | |
220 | + </div> | |
221 | + </div> | |
222 | + </section> | |
223 | + </div> | |
224 | +</section> | ... | ... |
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 { | |
19 | + ControlValueAccessor, | |
20 | + FormArray, | |
21 | + FormBuilder, | |
22 | + FormControl, | |
23 | + FormGroup, | |
24 | + NG_VALIDATORS, | |
25 | + NG_VALUE_ACCESSOR, | |
26 | + ValidationErrors, | |
27 | + Validator, | |
28 | + Validators | |
29 | +} from '@angular/forms'; | |
30 | +import { AlarmSchedule, AlarmScheduleType, AlarmScheduleTypeTranslationMap } from '@shared/models/device.models'; | |
31 | +import { isDefined, isDefinedAndNotNull } from '@core/utils'; | |
32 | +import * as _moment from 'moment-timezone'; | |
33 | +import { MatCheckboxChange } from '@angular/material/checkbox'; | |
34 | + | |
35 | +@Component({ | |
36 | + selector: 'tb-alarm-schedule', | |
37 | + templateUrl: './alarm-schedule.component.html', | |
38 | + providers: [{ | |
39 | + provide: NG_VALUE_ACCESSOR, | |
40 | + useExisting: forwardRef(() => AlarmScheduleComponent), | |
41 | + multi: true | |
42 | + }, { | |
43 | + provide: NG_VALIDATORS, | |
44 | + useExisting: forwardRef(() => AlarmScheduleComponent), | |
45 | + multi: true | |
46 | + }] | |
47 | +}) | |
48 | +export class AlarmScheduleComponent implements ControlValueAccessor, Validator, OnInit { | |
49 | + @Input() | |
50 | + disabled: boolean; | |
51 | + | |
52 | + alarmScheduleForm: FormGroup; | |
53 | + | |
54 | + defaultTimezone = _moment.tz.guess(); | |
55 | + | |
56 | + alarmScheduleTypes = Object.keys(AlarmScheduleType); | |
57 | + alarmScheduleType = AlarmScheduleType; | |
58 | + alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; | |
59 | + | |
60 | + private modelValue: AlarmSchedule; | |
61 | + | |
62 | + private defaultItems = Array.from({length: 7}, (value, i) => ({ | |
63 | + enabled: true, | |
64 | + dayOfWeek: i | |
65 | + })); | |
66 | + | |
67 | + private propagateChange = (v: any) => { }; | |
68 | + | |
69 | + constructor(private fb: FormBuilder) { | |
70 | + } | |
71 | + | |
72 | + ngOnInit(): void { | |
73 | + this.alarmScheduleForm = this.fb.group({ | |
74 | + type: [AlarmScheduleType.ANY_TIME, Validators.required], | |
75 | + timezone: [null, Validators.required], | |
76 | + daysOfWeek: this.fb.array(new Array(7).fill(false)), | |
77 | + startsOn: [0, Validators.required], | |
78 | + endsOn: [0, Validators.required], | |
79 | + items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i))) | |
80 | + }); | |
81 | + this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { | |
82 | + this.alarmScheduleForm.reset({type, items: this.defaultItems}, {emitEvent: false}); | |
83 | + this.updateValidators(type, true); | |
84 | + this.alarmScheduleForm.updateValueAndValidity(); | |
85 | + }); | |
86 | + this.alarmScheduleForm.valueChanges.subscribe(() => { | |
87 | + this.updateModel(); | |
88 | + }); | |
89 | + } | |
90 | + | |
91 | + registerOnChange(fn: any): void { | |
92 | + this.propagateChange = fn; | |
93 | + } | |
94 | + | |
95 | + registerOnTouched(fn: any): void { | |
96 | + } | |
97 | + | |
98 | + setDisabledState(isDisabled: boolean): void { | |
99 | + this.disabled = isDisabled; | |
100 | + if (this.disabled) { | |
101 | + this.alarmScheduleForm.disable({emitEvent: false}); | |
102 | + } else { | |
103 | + this.alarmScheduleForm.enable({emitEvent: false}); | |
104 | + } | |
105 | + } | |
106 | + | |
107 | + writeValue(value: AlarmSchedule): void { | |
108 | + this.modelValue = value; | |
109 | + if (!isDefinedAndNotNull(this.modelValue)) { | |
110 | + this.modelValue = { | |
111 | + type: AlarmScheduleType.ANY_TIME | |
112 | + }; | |
113 | + } | |
114 | + switch (this.modelValue.type) { | |
115 | + case AlarmScheduleType.SPECIFIC_TIME: | |
116 | + let daysOfWeek = new Array(7).fill(false); | |
117 | + if (isDefined(this.modelValue.daysOfWeek)) { | |
118 | + daysOfWeek = daysOfWeek.map((item, index) => this.modelValue.daysOfWeek.indexOf(index + 1) > -1); | |
119 | + } | |
120 | + this.alarmScheduleForm.patchValue({ | |
121 | + type: this.modelValue.type, | |
122 | + timezone: this.modelValue.timezone, | |
123 | + daysOfWeek, | |
124 | + startsOn: this.timestampToTime(this.modelValue.startsOn), | |
125 | + endsOn: this.timestampToTime(this.modelValue.endsOn) | |
126 | + }, {emitEvent: false}); | |
127 | + break; | |
128 | + case AlarmScheduleType.CUSTOM: | |
129 | + if (this.modelValue.items) { | |
130 | + const alarmDays = []; | |
131 | + this.modelValue.items | |
132 | + .sort((a, b) => a.dayOfWeek - b.dayOfWeek) | |
133 | + .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 | + } | |
141 | + alarmDays.push({ | |
142 | + enabled: item.enabled, | |
143 | + startsOn: this.timestampToTime(item.startsOn), | |
144 | + endsOn: this.timestampToTime(item.endsOn) | |
145 | + }); | |
146 | + }); | |
147 | + this.alarmScheduleForm.patchValue({ | |
148 | + type: this.modelValue.type, | |
149 | + timezone: this.modelValue.timezone, | |
150 | + items: alarmDays | |
151 | + }, {emitEvent: false}); | |
152 | + } | |
153 | + break; | |
154 | + default: | |
155 | + this.alarmScheduleForm.patchValue(this.modelValue || undefined, {emitEvent: false}); | |
156 | + } | |
157 | + this.updateValidators(this.modelValue.type); | |
158 | + } | |
159 | + | |
160 | + validate(control: FormGroup): ValidationErrors | null { | |
161 | + return this.alarmScheduleForm.valid ? null : { | |
162 | + alarmScheduler: { | |
163 | + valid: false | |
164 | + } | |
165 | + }; | |
166 | + } | |
167 | + | |
168 | + weeklyRepeatControl(index: number): FormControl { | |
169 | + return (this.alarmScheduleForm.get('daysOfWeek') as FormArray).at(index) as FormControl; | |
170 | + } | |
171 | + | |
172 | + private updateValidators(type: AlarmScheduleType, changedType = false){ | |
173 | + switch (type){ | |
174 | + case AlarmScheduleType.ANY_TIME: | |
175 | + this.alarmScheduleForm.get('timezone').disable({emitEvent: false}); | |
176 | + this.alarmScheduleForm.get('daysOfWeek').disable({emitEvent: false}); | |
177 | + this.alarmScheduleForm.get('startsOn').disable({emitEvent: false}); | |
178 | + this.alarmScheduleForm.get('endsOn').disable({emitEvent: false}); | |
179 | + this.alarmScheduleForm.get('items').disable({emitEvent: false}); | |
180 | + break; | |
181 | + case AlarmScheduleType.SPECIFIC_TIME: | |
182 | + this.alarmScheduleForm.get('timezone').enable({emitEvent: false}); | |
183 | + this.alarmScheduleForm.get('daysOfWeek').enable({emitEvent: false}); | |
184 | + this.alarmScheduleForm.get('startsOn').enable({emitEvent: false}); | |
185 | + this.alarmScheduleForm.get('endsOn').enable({emitEvent: false}); | |
186 | + this.alarmScheduleForm.get('items').disable({emitEvent: false}); | |
187 | + break; | |
188 | + case AlarmScheduleType.CUSTOM: | |
189 | + this.alarmScheduleForm.get('timezone').enable({emitEvent: false}); | |
190 | + this.alarmScheduleForm.get('daysOfWeek').disable({emitEvent: false}); | |
191 | + this.alarmScheduleForm.get('startsOn').disable({emitEvent: false}); | |
192 | + this.alarmScheduleForm.get('endsOn').disable({emitEvent: false}); | |
193 | + if (changedType) { | |
194 | + this.alarmScheduleForm.get('items').enable({emitEvent: false}); | |
195 | + } | |
196 | + break; | |
197 | + } | |
198 | + } | |
199 | + | |
200 | + private updateModel() { | |
201 | + const value = this.alarmScheduleForm.value; | |
202 | + if (this.modelValue) { | |
203 | + if (isDefined(value.daysOfWeek)) { | |
204 | + value.daysOfWeek = value.daysOfWeek | |
205 | + .map((day: boolean, index: number) => day ? index + 1 : null) | |
206 | + .filter(day => !!day); | |
207 | + } | |
208 | + if (isDefined(value.startsOn) && value.startsOn !== 0) { | |
209 | + value.startsOn = this.timeToTimestamp(value.startsOn); | |
210 | + } | |
211 | + if (isDefined(value.endsOn) && value.endsOn !== 0) { | |
212 | + value.endsOn = this.timeToTimestamp(value.endsOn); | |
213 | + } | |
214 | + if (isDefined(value.items)){ | |
215 | + value.items = this.alarmScheduleForm.getRawValue().items; | |
216 | + value.items = value.items.map((item) => { | |
217 | + return { ...item, startsOn: this.timeToTimestamp(item.startsOn), endsOn: this.timeToTimestamp(item.endsOn)}; | |
218 | + }); | |
219 | + } | |
220 | + this.modelValue = value; | |
221 | + this.propagateChange(this.modelValue); | |
222 | + } | |
223 | + } | |
224 | + | |
225 | + private timeToTimestamp(date: Date | number): number { | |
226 | + if (typeof date === 'number' || date === null) { | |
227 | + return 0; | |
228 | + } | |
229 | + return _moment.utc([1970, 0, 1, date.getHours(), date.getMinutes(), date.getSeconds(), 0]).valueOf(); | |
230 | + } | |
231 | + | |
232 | + private timestampToTime(time = 0): Date { | |
233 | + return new Date(time + new Date().getTimezoneOffset() * 60 * 1000); | |
234 | + } | |
235 | + | |
236 | + private defaultItemsScheduler(index): FormGroup { | |
237 | + return this.fb.group({ | |
238 | + enabled: [true], | |
239 | + dayOfWeek: [index], | |
240 | + startsOn: [0, Validators.required], | |
241 | + endsOn: [0, Validators.required] | |
242 | + }); | |
243 | + } | |
244 | + | |
245 | + changeCustomScheduler($event: MatCheckboxChange, index: number) { | |
246 | + const value = $event.checked; | |
247 | + if (value) { | |
248 | + this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false}); | |
249 | + this.itemsSchedulerForm.at(index).get('endsOn').enable(); | |
250 | + } else { | |
251 | + this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false}); | |
252 | + this.itemsSchedulerForm.at(index).get('endsOn').disable(); | |
253 | + } | |
254 | + } | |
255 | + | |
256 | + private get itemsSchedulerForm(): FormArray { | |
257 | + return this.alarmScheduleForm.get('items') as FormArray; | |
258 | + } | |
259 | +} | ... | ... |
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 | +<mat-form-field [formGroup]="selectTimezoneFormGroup" fxFlex class="mat-block"> | |
19 | + <mat-label translate>timezone.timezone</mat-label> | |
20 | + <input matInput type="text" placeholder="{{ 'timezone.select-timezone' | translate }}" | |
21 | + #timezoneInput | |
22 | + formControlName="timezone" | |
23 | + (focusin)="onFocus()" | |
24 | + [required]="required" | |
25 | + [matAutocomplete]="timezoneAutocomplete"> | |
26 | + <button *ngIf="selectTimezoneFormGroup.get('timezone').value && !disabled" | |
27 | + type="button" style="margin-right: 1px" | |
28 | + matSuffix mat-button mat-icon-button aria-label="Clear" | |
29 | + (mousedown)="ignoreClosePanel = true" | |
30 | + (click)="clear()"> | |
31 | + <mat-icon class="material-icons">close</mat-icon> | |
32 | + </button> | |
33 | + <mat-autocomplete class="tb-autocomplete" | |
34 | + #timezoneAutocomplete="matAutocomplete" | |
35 | + (closed)="onPanelClosed()" | |
36 | + (optionSelected)="ignoreClosePanel = true" | |
37 | + [displayWith]="displayTimezoneFn"> | |
38 | + <mat-option *ngFor="let timezone of filteredTimezones | async" [value]="timezone"> | |
39 | + <span [innerHTML]="displayTimezoneFn(timezone) | highlight:searchText"></span> | |
40 | + </mat-option> | |
41 | + <mat-option *ngIf="!(filteredTimezones | async)?.length" [value]="null"> | |
42 | + <span> | |
43 | + {{ translate.get('timezone.no-timezones-matching', {timezone: searchText}) | async }} | |
44 | + </span> | |
45 | + </mat-option> | |
46 | + </mat-autocomplete> | |
47 | + <mat-error *ngIf="selectTimezoneFormGroup.get('timezone').hasError('required')"> | |
48 | + {{ 'timezone.timezone-required' | translate }} | |
49 | + </mat-error> | |
50 | +</mat-form-field> | ... | ... |
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 { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; | |
18 | +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; | |
19 | +import { Observable, of } from 'rxjs'; | |
20 | +import { map, mergeMap, share, tap } from 'rxjs/operators'; | |
21 | +import { Store } from '@ngrx/store'; | |
22 | +import { AppState } from '@app/core/core.state'; | |
23 | +import { TranslateService } from '@ngx-translate/core'; | |
24 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
25 | +import * as _moment from 'moment-timezone'; | |
26 | +import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; | |
27 | + | |
28 | +interface TimezoneInfo { | |
29 | + id: string; | |
30 | + name: string; | |
31 | + offset: string; | |
32 | + nOffset: number; | |
33 | +} | |
34 | + | |
35 | +@Component({ | |
36 | + selector: 'tb-timezone-select', | |
37 | + templateUrl: './timezone-select.component.html', | |
38 | + styleUrls: [], | |
39 | + providers: [{ | |
40 | + provide: NG_VALUE_ACCESSOR, | |
41 | + useExisting: forwardRef(() => TimezoneSelectComponent), | |
42 | + multi: true | |
43 | + }] | |
44 | +}) | |
45 | +export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { | |
46 | + | |
47 | + selectTimezoneFormGroup: FormGroup; | |
48 | + | |
49 | + modelValue: string | null; | |
50 | + | |
51 | + defaultTimezoneId: string = null; | |
52 | + | |
53 | + defaultTimezoneInfo: TimezoneInfo = null; | |
54 | + | |
55 | + timezones: TimezoneInfo[] = _moment.tz.names().map((zoneName) => { | |
56 | + const tz = _moment.tz(zoneName); | |
57 | + return { | |
58 | + id: zoneName, | |
59 | + name: zoneName.replace(/_/g, ' '), | |
60 | + offset: `UTC${tz.format('Z')}`, | |
61 | + nOffset: tz.utcOffset() | |
62 | + } | |
63 | + }); | |
64 | + | |
65 | + @Input() | |
66 | + set defaultTimezone(timezone: string) { | |
67 | + if (this.defaultTimezoneId !== timezone) { | |
68 | + this.defaultTimezoneId = timezone; | |
69 | + if (this.defaultTimezoneId) { | |
70 | + this.defaultTimezoneInfo = | |
71 | + this.timezones.find((timezoneInfo) => timezoneInfo.id === this.defaultTimezoneId); | |
72 | + } else { | |
73 | + this.defaultTimezoneInfo = null; | |
74 | + } | |
75 | + } | |
76 | + } | |
77 | + | |
78 | + private requiredValue: boolean; | |
79 | + get required(): boolean { | |
80 | + return this.requiredValue; | |
81 | + } | |
82 | + @Input() | |
83 | + set required(value: boolean) { | |
84 | + this.requiredValue = coerceBooleanProperty(value); | |
85 | + } | |
86 | + | |
87 | + @Input() | |
88 | + disabled: boolean; | |
89 | + | |
90 | + @ViewChild('timezoneInput', {static: true, read: MatAutocompleteTrigger}) timezoneInputTrigger: MatAutocompleteTrigger; | |
91 | + | |
92 | + filteredTimezones: Observable<Array<TimezoneInfo>>; | |
93 | + | |
94 | + searchText = ''; | |
95 | + | |
96 | + ignoreClosePanel = false; | |
97 | + | |
98 | + private dirty = false; | |
99 | + | |
100 | + private propagateChange = (v: any) => { }; | |
101 | + | |
102 | + constructor(private store: Store<AppState>, | |
103 | + public translate: TranslateService, | |
104 | + private ngZone: NgZone, | |
105 | + private fb: FormBuilder) { | |
106 | + this.selectTimezoneFormGroup = this.fb.group({ | |
107 | + timezone: [null] | |
108 | + }); | |
109 | + } | |
110 | + | |
111 | + registerOnChange(fn: any): void { | |
112 | + this.propagateChange = fn; | |
113 | + } | |
114 | + | |
115 | + registerOnTouched(fn: any): void { | |
116 | + } | |
117 | + | |
118 | + ngOnInit() { | |
119 | + this.filteredTimezones = this.selectTimezoneFormGroup.get('timezone').valueChanges | |
120 | + .pipe( | |
121 | + tap(value => { | |
122 | + let modelValue; | |
123 | + if (typeof value === 'string' || !value) { | |
124 | + modelValue = null; | |
125 | + } else { | |
126 | + modelValue = value.id; | |
127 | + } | |
128 | + this.updateView(modelValue); | |
129 | + if (value === null) { | |
130 | + this.clear(); | |
131 | + } | |
132 | + }), | |
133 | + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), | |
134 | + mergeMap(name => this.fetchTimezones(name) ), | |
135 | + share() | |
136 | + ); | |
137 | + } | |
138 | + | |
139 | + ngAfterViewInit(): void { | |
140 | + } | |
141 | + | |
142 | + setDisabledState(isDisabled: boolean): void { | |
143 | + this.disabled = isDisabled; | |
144 | + if (this.disabled) { | |
145 | + this.selectTimezoneFormGroup.disable({emitEvent: false}); | |
146 | + } else { | |
147 | + this.selectTimezoneFormGroup.enable({emitEvent: false}); | |
148 | + } | |
149 | + } | |
150 | + | |
151 | + writeValue(value: string | null): void { | |
152 | + this.searchText = ''; | |
153 | + let foundTimezone: TimezoneInfo = null; | |
154 | + if (value !== null) { | |
155 | + foundTimezone = this.timezones.find(timezoneInfo => timezoneInfo.id === value); | |
156 | + } | |
157 | + if (foundTimezone !== null) { | |
158 | + this.modelValue = value; | |
159 | + this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false}); | |
160 | + } else { | |
161 | + if (this.defaultTimezoneInfo) { | |
162 | + this.selectTimezoneFormGroup.get('timezone').patchValue(this.defaultTimezoneInfo, {emitEvent: false}); | |
163 | + setTimeout(() => { | |
164 | + this.updateView(this.defaultTimezoneInfo.id); | |
165 | + }, 0); | |
166 | + } else { | |
167 | + this.modelValue = null; | |
168 | + this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); | |
169 | + } | |
170 | + } | |
171 | + this.dirty = true; | |
172 | + } | |
173 | + | |
174 | + onFocus() { | |
175 | + if (this.dirty) { | |
176 | + this.selectTimezoneFormGroup.get('timezone').updateValueAndValidity({onlySelf: true, emitEvent: true}); | |
177 | + this.dirty = false; | |
178 | + } | |
179 | + } | |
180 | + | |
181 | + onPanelClosed() { | |
182 | + if (this.ignoreClosePanel) { | |
183 | + this.ignoreClosePanel = false; | |
184 | + } else { | |
185 | + if (!this.modelValue && this.defaultTimezoneInfo) { | |
186 | + this.ngZone.run(() => { | |
187 | + this.selectTimezoneFormGroup.get('timezone').reset(this.defaultTimezoneInfo, {emitEvent: true}); | |
188 | + }); | |
189 | + } | |
190 | + } | |
191 | + } | |
192 | + | |
193 | + updateView(value: string | null) { | |
194 | + if (this.modelValue !== value) { | |
195 | + this.modelValue = value; | |
196 | + this.propagateChange(this.modelValue); | |
197 | + } | |
198 | + } | |
199 | + | |
200 | + displayTimezoneFn(timezone?: TimezoneInfo): string | undefined { | |
201 | + return timezone ? `${timezone.name} (${timezone.offset})` : undefined; | |
202 | + } | |
203 | + | |
204 | + fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { | |
205 | + this.searchText = searchText; | |
206 | + let result = this.timezones; | |
207 | + if (searchText && searchText.length) { | |
208 | + result = this.timezones.filter((timezoneInfo) => | |
209 | + timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())); | |
210 | + } | |
211 | + return of(result); | |
212 | + } | |
213 | + | |
214 | + clear() { | |
215 | + this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: true}); | |
216 | + setTimeout(() => { | |
217 | + this.timezoneInputTrigger.openPanel(); | |
218 | + }, 0); | |
219 | + } | |
220 | + | |
221 | +} | ... | ... |
... | ... | @@ -236,9 +236,40 @@ export interface AlarmCondition { |
236 | 236 | spec?: AlarmConditionSpec; |
237 | 237 | } |
238 | 238 | |
239 | +export enum AlarmScheduleType { | |
240 | + ANY_TIME = 'ANY_TIME', | |
241 | + SPECIFIC_TIME = 'SPECIFIC_TIME', | |
242 | + CUSTOM = 'CUSTOM' | |
243 | +} | |
244 | + | |
245 | +export const AlarmScheduleTypeTranslationMap = new Map<AlarmScheduleType, string>( | |
246 | + [ | |
247 | + [AlarmScheduleType.ANY_TIME, 'device-profile.schedule-any-time'], | |
248 | + [AlarmScheduleType.SPECIFIC_TIME, 'device-profile.schedule-specific-time'], | |
249 | + [AlarmScheduleType.CUSTOM, 'device-profile.schedule-custom'] | |
250 | + ] | |
251 | +); | |
252 | + | |
253 | +export interface AlarmSchedule{ | |
254 | + type: AlarmScheduleType; | |
255 | + timezone?: string; | |
256 | + daysOfWeek?: number[]; | |
257 | + startsOn?: number; | |
258 | + endsOn?: number; | |
259 | + items?: CustomTimeSchedulerItem[]; | |
260 | +} | |
261 | + | |
262 | +export interface CustomTimeSchedulerItem{ | |
263 | + enabled: boolean; | |
264 | + dayOfWeek: number; | |
265 | + startsOn: number; | |
266 | + endsOn: number; | |
267 | +} | |
268 | + | |
239 | 269 | export interface AlarmRule { |
240 | 270 | condition: AlarmCondition; |
241 | 271 | alarmDetails?: string; |
272 | + schedule?: AlarmSchedule; | |
242 | 273 | } |
243 | 274 | |
244 | 275 | export interface DeviceProfileAlarm { | ... | ... |
... | ... | @@ -467,7 +467,6 @@ export const defaultTimeIntervals = new Array<TimeInterval>( |
467 | 467 | ); |
468 | 468 | |
469 | 469 | export enum TimeUnit { |
470 | - MILLISECONDS = 'MILLISECONDS', | |
471 | 470 | SECONDS = 'SECONDS', |
472 | 471 | MINUTES = 'MINUTES', |
473 | 472 | HOURS = 'HOURS', |
... | ... | @@ -476,7 +475,6 @@ export enum TimeUnit { |
476 | 475 | |
477 | 476 | export const timeUnitTranslationMap = new Map<TimeUnit, string>( |
478 | 477 | [ |
479 | - [TimeUnit.MILLISECONDS, 'timeunit.milliseconds'], | |
480 | 478 | [TimeUnit.SECONDS, 'timeunit.seconds'], |
481 | 479 | [TimeUnit.MINUTES, 'timeunit.minutes'], |
482 | 480 | [TimeUnit.HOURS, 'timeunit.hours'], | ... | ... |
... | ... | @@ -134,6 +134,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his |
134 | 134 | import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-gateway-select.component'; |
135 | 135 | import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list.component'; |
136 | 136 | import { ContactComponent } from '@shared/components/contact.component'; |
137 | +import { TimezoneSelectComponent } from '@shared/components/time/timezone-select.component'; | |
137 | 138 | |
138 | 139 | @NgModule({ |
139 | 140 | providers: [ |
... | ... | @@ -172,6 +173,7 @@ import { ContactComponent } from '@shared/components/contact.component'; |
172 | 173 | DashboardSelectPanelComponent, |
173 | 174 | DatetimePeriodComponent, |
174 | 175 | DatetimeComponent, |
176 | + TimezoneSelectComponent, | |
175 | 177 | ValueInputComponent, |
176 | 178 | DashboardAutocompleteComponent, |
177 | 179 | EntitySubTypeAutocompleteComponent, |
... | ... | @@ -292,6 +294,7 @@ import { ContactComponent } from '@shared/components/contact.component'; |
292 | 294 | DashboardSelectComponent, |
293 | 295 | DatetimePeriodComponent, |
294 | 296 | DatetimeComponent, |
297 | + TimezoneSelectComponent, | |
295 | 298 | DashboardAutocompleteComponent, |
296 | 299 | EntitySubTypeAutocompleteComponent, |
297 | 300 | EntitySubTypeSelectComponent, | ... | ... |
... | ... | @@ -856,7 +856,25 @@ |
856 | 856 | "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.", |
857 | 857 | "condition-repeating-value-pattern": "Count of events should be integers.", |
858 | 858 | "condition-repeating-value-required": "Count of events is required.", |
859 | - "schedule": "Schedule" | |
859 | + "schedule-type": "Scheduler type", | |
860 | + "schedule-type-required": "Scheduler type is required.", | |
861 | + "schedule": "Schedule", | |
862 | + "schedule-any-time": "Active all the time", | |
863 | + "schedule-specific-time": "Active at a specific time", | |
864 | + "schedule-custom": "Custom", | |
865 | + "schedule-day": { | |
866 | + "monday": "Monday", | |
867 | + "tuesday": "Tuesday", | |
868 | + "wednesday": "Wednesday", | |
869 | + "thursday": "Thursday", | |
870 | + "friday": "Friday", | |
871 | + "saturday": "Saturday", | |
872 | + "sunday": "Sunday" | |
873 | + }, | |
874 | + "schedule-days": "Days", | |
875 | + "schedule-time": "Time", | |
876 | + "schedule-time-from": "From", | |
877 | + "schedule-time-to": "To" | |
860 | 878 | }, |
861 | 879 | "dialog": { |
862 | 880 | "close": "Close dialog" |
... | ... | @@ -1742,6 +1760,12 @@ |
1742 | 1760 | "help": "Help", |
1743 | 1761 | "reset-debug-mode": "Reset debug mode in all nodes" |
1744 | 1762 | }, |
1763 | + "timezone": { | |
1764 | + "timezone": "Timezone", | |
1765 | + "select-timezone": "Select timezone", | |
1766 | + "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", | |
1767 | + "timezone-required": "Timezone is required." | |
1768 | + }, | |
1745 | 1769 | "queue": { |
1746 | 1770 | "select_name": "Select queue name", |
1747 | 1771 | "name": "Queue Name", |
... | ... | @@ -1821,7 +1845,6 @@ |
1821 | 1845 | "advanced": "Advanced" |
1822 | 1846 | }, |
1823 | 1847 | "timeunit": { |
1824 | - "milliseconds": "Milliseconds", | |
1825 | 1848 | "seconds": "Seconds", |
1826 | 1849 | "minutes": "Minutes", |
1827 | 1850 | "hours": "Hours", | ... | ... |
... | ... | @@ -1428,6 +1428,13 @@ |
1428 | 1428 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" |
1429 | 1429 | integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== |
1430 | 1430 | |
1431 | +"@types/moment-timezone@^0.5.30": | |
1432 | + version "0.5.30" | |
1433 | + resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.30.tgz#340ed45fe3e715f4a011f5cfceb7cb52aad46fc7" | |
1434 | + integrity sha512-aDVfCsjYnAQaV/E9Qc24C5Njx1CoDjXsEgkxtp9NyXDpYu4CCbmclb6QhWloS9UTU/8YROUEEdEkWI0D7DxnKg== | |
1435 | + dependencies: | |
1436 | + moment-timezone "*" | |
1437 | + | |
1431 | 1438 | "@types/mousetrap@^1.6.0": |
1432 | 1439 | version "1.6.3" |
1433 | 1440 | resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.3.tgz#3159a01a2b21c9155a3d8f85588885d725dc987d" |
... | ... | @@ -6289,6 +6296,18 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: |
6289 | 6296 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" |
6290 | 6297 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== |
6291 | 6298 | |
6299 | +moment-timezone@*, moment-timezone@^0.5.31: | |
6300 | + version "0.5.31" | |
6301 | + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05" | |
6302 | + integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA== | |
6303 | + dependencies: | |
6304 | + moment ">= 2.9.0" | |
6305 | + | |
6306 | +"moment@>= 2.9.0": | |
6307 | + version "2.29.0" | |
6308 | + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.0.tgz#fcbef955844d91deb55438613ddcec56e86a3425" | |
6309 | + integrity sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA== | |
6310 | + | |
6292 | 6311 | moment@^2.27.0: |
6293 | 6312 | version "2.27.0" |
6294 | 6313 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" | ... | ... |