Showing
16 changed files
with
416 additions
and
74 deletions
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 class="tb-filter-text" [ngClass]="{disabled: disabled, required: requiredClass}" | |
19 | + [innerHTML]="filterText"></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 | +:host { | |
17 | + .tb-filter-text { | |
18 | + overflow-y: auto; | |
19 | + &.disabled { | |
20 | + opacity: 0.7; | |
21 | + } | |
22 | + &.required { | |
23 | + color: #f44336; | |
24 | + } | |
25 | + } | |
26 | +} | |
27 | + | |
28 | +:host ::ng-deep { | |
29 | + .tb-filter-text { | |
30 | + line-height: 1.8em; | |
31 | + span { | |
32 | + display: inline-block; | |
33 | + vertical-align: middle; | |
34 | + line-height: 1.4em; | |
35 | + } | |
36 | + .tb-filter-predicate { | |
37 | + padding-right: 4px; | |
38 | + padding-left: 4px; | |
39 | + } | |
40 | + .tb-filter-entity-key, .tb-filter-value, .tb-filter-dynamic-source { | |
41 | + font-weight: bold; | |
42 | + border: 1px groove rgba(0, 0, 0, .25); | |
43 | + border-radius: 4px; | |
44 | + padding-left: 4px; | |
45 | + padding-right: 4px; | |
46 | + } | |
47 | + .tb-filter-entity-key, .tb-filter-value { | |
48 | + white-space: nowrap; | |
49 | + overflow: hidden; | |
50 | + text-overflow: ellipsis; | |
51 | + max-width: 150px; | |
52 | + } | |
53 | + .tb-filter-dynamic-source { | |
54 | + } | |
55 | + .tb-filter-entity-key { | |
56 | + color: #305680; | |
57 | + } | |
58 | + .tb-filter-value { | |
59 | + color: #ff5722; | |
60 | + } | |
61 | + .tb-filter-simple-operation { | |
62 | + font-size: 0.9em; | |
63 | + } | |
64 | + .tb-filter-complex-operation { | |
65 | + text-transform: uppercase; | |
66 | + font-weight: bold; | |
67 | + } | |
68 | + .tb-filter-dynamic-value { | |
69 | + .tb-filter-dynamic-source, .tb-filter-value { | |
70 | + color: #0c959c; | |
71 | + } | |
72 | + } | |
73 | + .tb-filter-bracket { | |
74 | + .tb-left-bracket, .tb-right-bracket { | |
75 | + font-size: 1.2em; | |
76 | + } | |
77 | + } | |
78 | + } | |
79 | +} | ... | ... |
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, NG_VALUE_ACCESSOR } from '@angular/forms'; | |
19 | +import { MatDialog } from '@angular/material/dialog'; | |
20 | +import { KeyFilter, keyFiltersToText } from '@shared/models/query/query.models'; | |
21 | +import { TranslateService } from '@ngx-translate/core'; | |
22 | +import { DatePipe } from '@angular/common'; | |
23 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
24 | + | |
25 | +@Component({ | |
26 | + selector: 'tb-filter-text', | |
27 | + templateUrl: './filter-text.component.html', | |
28 | + styleUrls: ['./filter-text.component.scss'], | |
29 | + providers: [ | |
30 | + { | |
31 | + provide: NG_VALUE_ACCESSOR, | |
32 | + useExisting: forwardRef(() => FilterTextComponent), | |
33 | + multi: true | |
34 | + } | |
35 | + ] | |
36 | +}) | |
37 | +export class FilterTextComponent implements ControlValueAccessor, OnInit { | |
38 | + | |
39 | + private requiredValue: boolean; | |
40 | + get required(): boolean { | |
41 | + return this.requiredValue; | |
42 | + } | |
43 | + @Input() | |
44 | + set required(value: boolean) { | |
45 | + this.requiredValue = coerceBooleanProperty(value); | |
46 | + } | |
47 | + | |
48 | + @Input() | |
49 | + disabled: boolean; | |
50 | + | |
51 | + @Input() | |
52 | + noFilterText = this.translate.instant('filter.no-filter-text'); | |
53 | + | |
54 | + @Input() | |
55 | + addFilterPrompt = this.translate.instant('filter.add-filter-prompt'); | |
56 | + | |
57 | + requiredClass = false; | |
58 | + | |
59 | + private filterText: string; | |
60 | + | |
61 | + private propagateChange = (v: any) => { }; | |
62 | + | |
63 | + constructor(private dialog: MatDialog, | |
64 | + private fb: FormBuilder, | |
65 | + private translate: TranslateService, | |
66 | + private datePipe: DatePipe) { | |
67 | + } | |
68 | + | |
69 | + registerOnChange(fn: any): void { | |
70 | + this.propagateChange = fn; | |
71 | + } | |
72 | + | |
73 | + registerOnTouched(fn: any): void { | |
74 | + } | |
75 | + | |
76 | + ngOnInit() { | |
77 | + } | |
78 | + | |
79 | + setDisabledState(isDisabled: boolean): void { | |
80 | + this.disabled = isDisabled; | |
81 | + } | |
82 | + | |
83 | + writeValue(value: Array<KeyFilter>): void { | |
84 | + this.updateFilterText(value); | |
85 | + } | |
86 | + | |
87 | + private updateFilterText(value: Array<KeyFilter>) { | |
88 | + this.requiredClass = false; | |
89 | + if (value && value.length) { | |
90 | + this.filterText = keyFiltersToText(this.translate, this.datePipe, value); | |
91 | + } else { | |
92 | + if (this.required && !this.disabled) { | |
93 | + this.filterText = this.addFilterPrompt; | |
94 | + this.requiredClass = true; | |
95 | + } else { | |
96 | + this.filterText = this.noFilterText; | |
97 | + } | |
98 | + } | |
99 | + } | |
100 | + | |
101 | +} | ... | ... |
... | ... | @@ -104,6 +104,7 @@ import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.co |
104 | 104 | import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; |
105 | 105 | import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; |
106 | 106 | import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component'; |
107 | +import { FilterTextComponent } from './filter/filter-text.component'; | |
107 | 108 | |
108 | 109 | @NgModule({ |
109 | 110 | declarations: |
... | ... | @@ -165,6 +166,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k |
165 | 166 | FilterDialogComponent, |
166 | 167 | FiltersDialogComponent, |
167 | 168 | FilterSelectComponent, |
169 | + FilterTextComponent, | |
168 | 170 | FiltersEditComponent, |
169 | 171 | FiltersEditPanelComponent, |
170 | 172 | UserFilterDialogComponent, |
... | ... | @@ -245,6 +247,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k |
245 | 247 | FilterDialogComponent, |
246 | 248 | FiltersDialogComponent, |
247 | 249 | FilterSelectComponent, |
250 | + FilterTextComponent, | |
248 | 251 | FiltersEditComponent, |
249 | 252 | UserFilterDialogComponent, |
250 | 253 | TenantProfileAutocompleteComponent, | ... | ... |
... | ... | @@ -15,16 +15,22 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<mat-form-field class="mat-block" (click)="openFilterDialog($event)" floatLabel="always" hideRequiredMarker> | |
19 | - <mat-label></mat-label> | |
20 | - <input readonly | |
21 | - required matInput [formControl]="alarmRuleConditionControl" | |
22 | - placeholder="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}"> | |
23 | - <a matSuffix mat-icon-button color="primary" | |
24 | - type="button" | |
25 | - (click)="openFilterDialog($event)" | |
26 | - matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}" | |
27 | - matTooltipPosition="above"> | |
28 | - <mat-icon>{{ disabled ? 'more_vert' : 'edit' }}</mat-icon> | |
29 | - </a> | |
30 | -</mat-form-field> | |
18 | +<div fxLayout="column" fxFlex> | |
19 | + <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> | |
20 | + <div class="tb-small" translate>device-profile.alarm-rule-condition</div> | |
21 | + <span fxFlex></span> | |
22 | + <a mat-button color="primary" | |
23 | + type="button" | |
24 | + (click)="openFilterDialog($event)" | |
25 | + matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}" | |
26 | + matTooltipPosition="above"> | |
27 | + {{ (disabled ? 'action.view' : (conditionSet() ? 'action.edit' : 'action.add')) | translate }} | |
28 | + </a> | |
29 | + </div> | |
30 | + <div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)"> | |
31 | + <tb-filter-text [formControl]="alarmRuleConditionControl" | |
32 | + required | |
33 | + addFilterPrompt="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}"> | |
34 | + </tb-filter-text> | |
35 | + </div> | |
36 | +</div> | ... | ... |
... | ... | @@ -14,9 +14,24 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | :host { |
17 | - a.mat-icon-button { | |
17 | + display: flex; | |
18 | + a.mat-button { | |
18 | 19 | &:hover, &:focus { |
19 | 20 | border-bottom: none; |
20 | 21 | } |
21 | 22 | } |
23 | + .tb-alarm-rule-condition { | |
24 | + padding: 8px; | |
25 | + border: 1px groove rgba(0, 0, 0, .25); | |
26 | + border-radius: 4px; | |
27 | + cursor: pointer; | |
28 | + } | |
29 | +} | |
30 | + | |
31 | +:host ::ng-deep { | |
32 | + .tb-alarm-rule-condition { | |
33 | + .tb-filter-text { | |
34 | + max-height: 200px; | |
35 | + } | |
36 | + } | |
22 | 37 | } | ... | ... |
... | ... | @@ -30,6 +30,8 @@ import { |
30 | 30 | AlarmRuleKeyFiltersDialogComponent, |
31 | 31 | AlarmRuleKeyFiltersDialogData |
32 | 32 | } from './alarm-rule-key-filters-dialog.component'; |
33 | +import { TranslateService } from '@ngx-translate/core'; | |
34 | +import { DatePipe } from '@angular/common'; | |
33 | 35 | |
34 | 36 | @Component({ |
35 | 37 | selector: 'tb-alarm-rule-condition', |
... | ... | @@ -60,7 +62,9 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit |
60 | 62 | private propagateChange = (v: any) => { }; |
61 | 63 | |
62 | 64 | constructor(private dialog: MatDialog, |
63 | - private fb: FormBuilder) { | |
65 | + private fb: FormBuilder, | |
66 | + private translate: TranslateService, | |
67 | + private datePipe: DatePipe) { | |
64 | 68 | } |
65 | 69 | |
66 | 70 | registerOnChange(fn: any): void { |
... | ... | @@ -76,6 +80,11 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit |
76 | 80 | |
77 | 81 | setDisabledState(isDisabled: boolean): void { |
78 | 82 | this.disabled = isDisabled; |
83 | + if (this.disabled) { | |
84 | + this.alarmRuleConditionControl.disable({emitEvent: false}); | |
85 | + } else { | |
86 | + this.alarmRuleConditionControl.enable({emitEvent: false}); | |
87 | + } | |
79 | 88 | } |
80 | 89 | |
81 | 90 | writeValue(value: Array<KeyFilter>): void { |
... | ... | @@ -83,8 +92,12 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit |
83 | 92 | this.updateConditionInfo(); |
84 | 93 | } |
85 | 94 | |
95 | + public conditionSet() { | |
96 | + return this.modelValue && this.modelValue.length; | |
97 | + } | |
98 | + | |
86 | 99 | public validate(c: FormControl) { |
87 | - return (this.modelValue && this.modelValue.length) ? null : { | |
100 | + return this.conditionSet() ? null : { | |
88 | 101 | alarmRuleCondition: { |
89 | 102 | valid: false, |
90 | 103 | }, |
... | ... | @@ -112,11 +125,7 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit |
112 | 125 | } |
113 | 126 | |
114 | 127 | private updateConditionInfo() { |
115 | - if (this.modelValue && this.modelValue.length) { | |
116 | - this.alarmRuleConditionControl.patchValue('Condition set'); | |
117 | - } else { | |
118 | - this.alarmRuleConditionControl.patchValue(null); | |
119 | - } | |
128 | + this.alarmRuleConditionControl.patchValue(this.modelValue); | |
120 | 129 | } |
121 | 130 | |
122 | 131 | private updateModel() { | ... | ... |
... | ... | @@ -16,56 +16,56 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 18 | <div fxLayout="column" [formGroup]="alarmRuleFormGroup"> |
19 | - <div formGroupName="condition" fxLayout="row" fxLayoutAlign="start" fxLayoutGap="8px" fxFlex> | |
20 | - <div fxLayout="column" fxFlex> | |
19 | + <div formGroupName="condition" fxLayout="row" fxLayoutGap="8px" fxFlex> | |
20 | + <tb-alarm-rule-condition fxFlex | |
21 | + formControlName="condition"> | |
22 | + </tb-alarm-rule-condition> | |
23 | + <div fxLayout="column"> | |
21 | 24 | <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> |
22 | - <label class="tb-small" translate>device-profile.alarm-rule-condition</label> | |
23 | - </div> | |
24 | - <tb-alarm-rule-condition fxFlex | |
25 | - formControlName="condition"> | |
26 | - </tb-alarm-rule-condition> | |
27 | - </div> | |
28 | - <div fxLayout="column" style="min-width: 250px;"> | |
29 | - <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> | |
30 | - <label fxFlex class="tb-small" translate>device-profile.condition-duration</label> | |
25 | + <div class="tb-small" translate>device-profile.condition-duration</div> | |
26 | + <span fxFlex></span> | |
31 | 27 | <mat-slide-toggle [disabled]="disabled" |
28 | + color="primary" | |
32 | 29 | [ngModelOptions]="{standalone: true}" |
33 | 30 | (ngModelChange)="enableDurationChanged($event)" |
34 | 31 | [ngModel]="enableDuration"> |
35 | 32 | </mat-slide-toggle> |
36 | 33 | </div> |
37 | - <div fxLayout="row" fxLayoutGap="8px" [fxShow]="enableDuration"> | |
38 | - <mat-form-field class="mat-block duration-value-field" hideRequiredMarker floatLabel="always"> | |
39 | - <mat-label></mat-label> | |
40 | - <input type="number" | |
41 | - [required]="enableDuration" | |
42 | - step="1" | |
43 | - min="1" max="2147483647" matInput | |
44 | - placeholder="{{ 'device-profile.condition-duration-value' | translate }}" | |
45 | - formControlName="durationValue"> | |
46 | - <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('required')"> | |
47 | - {{ 'device-profile.condition-duration-value-required' | translate }} | |
48 | - </mat-error> | |
49 | - <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('min')"> | |
50 | - {{ 'device-profile.condition-duration-value-range' | translate }} | |
51 | - </mat-error> | |
52 | - <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('max')"> | |
53 | - {{ 'device-profile.condition-duration-value-range' | translate }} | |
54 | - </mat-error> | |
55 | - </mat-form-field> | |
56 | - <mat-form-field class="mat-block duration-unit-field" hideRequiredMarker floatLabel="always"> | |
57 | - <mat-label></mat-label> | |
58 | - <mat-select formControlName="durationUnit" | |
59 | - [required]="enableDuration" | |
60 | - placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}"> | |
61 | - <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit"> | |
62 | - {{ timeUnitTranslations.get(timeUnit) | translate }} | |
63 | - </mat-option> | |
64 | - </mat-select> | |
65 | - <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationUnit').hasError('required')"> | |
66 | - {{ 'device-profile.condition-duration-time-unit-required' | translate }} | |
67 | - </mat-error> | |
68 | - </mat-form-field> | |
34 | + <div class="tb-condition-duration" fxFlex fxLayout="row" fxLayoutGap="8px"> | |
35 | + <span style="min-width: 250px;" [fxShow]="!enableDuration"></span> | |
36 | + <div style="min-width: 250px;" fxLayout="row" fxLayoutGap="8px" [fxShow]="enableDuration"> | |
37 | + <mat-form-field class="mat-block duration-value-field" hideRequiredMarker floatLabel="always"> | |
38 | + <mat-label></mat-label> | |
39 | + <input type="number" | |
40 | + [required]="enableDuration" | |
41 | + step="1" | |
42 | + min="1" max="2147483647" matInput | |
43 | + placeholder="{{ 'device-profile.condition-duration-value' | translate }}" | |
44 | + formControlName="durationValue"> | |
45 | + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('required')"> | |
46 | + {{ 'device-profile.condition-duration-value-required' | translate }} | |
47 | + </mat-error> | |
48 | + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('min')"> | |
49 | + {{ 'device-profile.condition-duration-value-range' | translate }} | |
50 | + </mat-error> | |
51 | + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('max')"> | |
52 | + {{ 'device-profile.condition-duration-value-range' | translate }} | |
53 | + </mat-error> | |
54 | + </mat-form-field> | |
55 | + <mat-form-field class="mat-block duration-unit-field" hideRequiredMarker floatLabel="always"> | |
56 | + <mat-label></mat-label> | |
57 | + <mat-select formControlName="durationUnit" | |
58 | + [required]="enableDuration" | |
59 | + placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}"> | |
60 | + <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit"> | |
61 | + {{ timeUnitTranslations.get(timeUnit) | translate }} | |
62 | + </mat-option> | |
63 | + </mat-select> | |
64 | + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationUnit').hasError('required')"> | |
65 | + {{ 'device-profile.condition-duration-time-unit-required' | translate }} | |
66 | + </mat-error> | |
67 | + </mat-form-field> | |
68 | + </div> | |
69 | 69 | </div> |
70 | 70 | </div> |
71 | 71 | </div> |
... | ... | @@ -73,7 +73,7 @@ |
73 | 73 | <mat-expansion-panel-header> |
74 | 74 | <mat-panel-title> |
75 | 75 | <div fxFlex fxLayout="row" fxLayoutAlign="end center"> |
76 | - <div class="tb-small" translate>device-profile.advanced-settings</div> | |
76 | + <div class="tb-small" translate>device-profile.alarm-rule-details</div> | |
77 | 77 | </div> |
78 | 78 | </mat-panel-title> |
79 | 79 | </mat-expansion-panel-header> | ... | ... |
... | ... | @@ -14,6 +14,11 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | :host { |
17 | + .tb-condition-duration { | |
18 | + padding: 8px; | |
19 | + border: 1px groove rgba(0, 0, 0, .25); | |
20 | + border-radius: 4px; | |
21 | + } | |
17 | 22 | .mat-expansion-panel.advanced-settings { |
18 | 23 | box-shadow: none; |
19 | 24 | border: none; | ... | ... |
... | ... | @@ -36,7 +36,7 @@ |
36 | 36 | <tb-alarm-rule formControlName="alarmRule" required fxFlex> |
37 | 37 | </tb-alarm-rule> |
38 | 38 | </div> |
39 | - <button *ngIf="!disabled && createAlarmRulesFormArray().controls.length > 1" | |
39 | + <button *ngIf="!disabled" | |
40 | 40 | mat-icon-button color="primary" style="min-width: 40px;" |
41 | 41 | type="button" |
42 | 42 | (click)="removeCreateAlarmRule($index)" |
... | ... | @@ -45,6 +45,10 @@ |
45 | 45 | <mat-icon>remove_circle_outline</mat-icon> |
46 | 46 | </button> |
47 | 47 | </div> |
48 | + <div *ngIf="disabled && !createAlarmRulesFormArray().controls.length"> | |
49 | + <span translate fxLayoutAlign="center center" | |
50 | + class="tb-prompt">device-profile.no-create-alarm-rules</span> | |
51 | + </div> | |
48 | 52 | <div fxLayout="row" *ngIf="!disabled"> |
49 | 53 | <button mat-stroked-button color="primary" |
50 | 54 | type="button" | ... | ... |
... | ... | @@ -142,7 +142,7 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, |
142 | 142 | } |
143 | 143 | |
144 | 144 | public validate(c: FormControl) { |
145 | - return (this.createAlarmRulesFormGroup.valid && this.createAlarmRulesFormGroup.get('createAlarmRules').value.length) ? null : { | |
145 | + return (this.createAlarmRulesFormGroup.valid) ? null : { | |
146 | 146 | createAlarmRules: { |
147 | 147 | valid: false, |
148 | 148 | }, | ... | ... |
... | ... | @@ -66,6 +66,10 @@ |
66 | 66 | <mat-icon>remove_circle_outline</mat-icon> |
67 | 67 | </button> |
68 | 68 | </div> |
69 | + <div *ngIf="disabled && !alarmFormGroup.get('clearRule').value"> | |
70 | + <span translate fxLayoutAlign="center center" | |
71 | + class="tb-prompt">device-profile.no-clear-alarm-rule</span> | |
72 | + </div> | |
69 | 73 | <div fxLayout="row" *ngIf="!disabled" |
70 | 74 | [fxShow]="!alarmFormGroup.get('clearRule').value"> |
71 | 75 | <button mat-stroked-button color="primary" | ... | ... |
... | ... | @@ -32,8 +32,7 @@ |
32 | 32 | (click)="addAlarm()" |
33 | 33 | matTooltip="{{ 'device-profile.add-alarm-rule' | translate }}" |
34 | 34 | matTooltipPosition="above"> |
35 | - <mat-icon>add</mat-icon> | |
36 | - <span translate>action.add</span> | |
35 | + <span translate>device-profile.add-alarm-rule</span> | |
37 | 36 | </button> |
38 | 37 | </div> |
39 | 38 | </div> | ... | ... |
... | ... | @@ -39,7 +39,7 @@ |
39 | 39 | required> |
40 | 40 | </tb-device-profile-transport-configuration> |
41 | 41 | </mat-expansion-panel> |
42 | - <mat-expansion-panel [expanded]="false"> | |
42 | + <mat-expansion-panel [expanded]="true"> | |
43 | 43 | <mat-expansion-panel-header> |
44 | 44 | <mat-panel-title> |
45 | 45 | <div>{{'device-profile.alarm-rules' | translate: | ... | ... |
... | ... | @@ -25,6 +25,8 @@ import { PageData } from '@shared/models/page/page-data'; |
25 | 25 | import { isDefined, isEqual } from '@core/utils'; |
26 | 26 | import { TranslateService } from '@ngx-translate/core'; |
27 | 27 | import { AlarmInfo, AlarmSearchStatus, AlarmSeverity } from '../alarm.models'; |
28 | +import { Filter } from '@material-ui/icons'; | |
29 | +import { DatePipe } from '@angular/common'; | |
28 | 30 | |
29 | 31 | export enum EntityKeyType { |
30 | 32 | ATTRIBUTE = 'ATTRIBUTE', |
... | ... | @@ -358,6 +360,98 @@ export interface FiltersInfo { |
358 | 360 | datasourceFilters: {[datasourceIndex: number]: FilterInfo}; |
359 | 361 | } |
360 | 362 | |
363 | +export function keyFiltersToText(translate: TranslateService, datePipe: DatePipe, keyFilters: Array<KeyFilter>): string { | |
364 | + const filtersText = keyFilters.map(keyFilter => | |
365 | + keyFilterToText(translate, datePipe, keyFilter, | |
366 | + keyFilters.length > 1 ? ComplexOperation.AND : undefined)); | |
367 | + let result: string; | |
368 | + if (filtersText.length > 1) { | |
369 | + const andText = translate.instant('filter.operation.and'); | |
370 | + result = filtersText.join(' <span class="tb-filter-complex-operation">' + andText + '</span> '); | |
371 | + } else { | |
372 | + result = filtersText[0]; | |
373 | + } | |
374 | + return result; | |
375 | +} | |
376 | + | |
377 | +export function keyFilterToText(translate: TranslateService, datePipe: DatePipe, keyFilter: KeyFilter, | |
378 | + parentComplexOperation?: ComplexOperation): string { | |
379 | + const keyFilterPredicate = keyFilter.predicate; | |
380 | + return keyFilterPredicateToText(translate, datePipe, keyFilter, keyFilterPredicate, parentComplexOperation); | |
381 | +} | |
382 | + | |
383 | +export function keyFilterPredicateToText(translate: TranslateService, | |
384 | + datePipe: DatePipe, | |
385 | + keyFilter: KeyFilter, | |
386 | + keyFilterPredicate: KeyFilterPredicate, | |
387 | + parentComplexOperation?: ComplexOperation): string { | |
388 | + if (keyFilterPredicate.type === FilterPredicateType.COMPLEX) { | |
389 | + const complexPredicate = keyFilterPredicate as ComplexFilterPredicate; | |
390 | + const complexOperation = complexPredicate.operation; | |
391 | + const complexPredicatesText = | |
392 | + complexPredicate.predicates.map(predicate => keyFilterPredicateToText(translate, datePipe, keyFilter, predicate, complexOperation)); | |
393 | + if (complexPredicatesText.length > 1) { | |
394 | + const operationText = translate.instant(complexOperationTranslationMap.get(complexOperation)); | |
395 | + let result = complexPredicatesText.join(' <span class="tb-filter-complex-operation">' + operationText + '</span> '); | |
396 | + if (complexOperation === ComplexOperation.OR && parentComplexOperation && ComplexOperation.OR !== parentComplexOperation) { | |
397 | + result = `<span class="tb-filter-bracket"><span class="tb-left-bracket">(</span>${result}<span class="tb-right-bracket">)</span></span>`; | |
398 | + } | |
399 | + return result; | |
400 | + } else { | |
401 | + return complexPredicatesText[0]; | |
402 | + } | |
403 | + } else { | |
404 | + return simpleKeyFilterPredicateToText(translate, datePipe, keyFilter, keyFilterPredicate); | |
405 | + } | |
406 | +} | |
407 | + | |
408 | +function simpleKeyFilterPredicateToText(translate: TranslateService, | |
409 | + datePipe: DatePipe, | |
410 | + keyFilter: KeyFilter, | |
411 | + keyFilterPredicate: StringFilterPredicate | | |
412 | + NumericFilterPredicate | | |
413 | + BooleanFilterPredicate): string { | |
414 | + const key = keyFilter.key.key; | |
415 | + let operation: string; | |
416 | + let value: string; | |
417 | + const val = keyFilterPredicate.value; | |
418 | + const dynamicValue = !!val.dynamicValue && !!val.dynamicValue.sourceType; | |
419 | + if (dynamicValue) { | |
420 | + value = '<span class="tb-filter-dynamic-value"><span class="tb-filter-dynamic-source">' + | |
421 | + translate.instant(dynamicValueSourceTypeTranslationMap.get(val.dynamicValue.sourceType)) + '</span>'; | |
422 | + value += '.<span class="tb-filter-value">' + val.dynamicValue.sourceAttribute + '</span></span>'; | |
423 | + } | |
424 | + switch (keyFilterPredicate.type) { | |
425 | + case FilterPredicateType.STRING: | |
426 | + operation = translate.instant(stringOperationTranslationMap.get(keyFilterPredicate.operation)); | |
427 | + if (keyFilterPredicate.ignoreCase) { | |
428 | + operation += ' ' + translate.instant('filter.ignore-case'); | |
429 | + } | |
430 | + if (!dynamicValue) { | |
431 | + value = `'${keyFilterPredicate.value.defaultValue}'`; | |
432 | + } | |
433 | + break; | |
434 | + case FilterPredicateType.NUMERIC: | |
435 | + operation = translate.instant(numericOperationTranslationMap.get(keyFilterPredicate.operation)); | |
436 | + if (!dynamicValue) { | |
437 | + if (keyFilter.valueType === EntityKeyValueType.DATE_TIME) { | |
438 | + value = datePipe.transform(keyFilterPredicate.value.defaultValue, 'yyyy-MM-dd HH:mm'); | |
439 | + } else { | |
440 | + value = keyFilterPredicate.value.defaultValue + ''; | |
441 | + } | |
442 | + } | |
443 | + break; | |
444 | + case FilterPredicateType.BOOLEAN: | |
445 | + operation = translate.instant(booleanOperationTranslationMap.get(keyFilterPredicate.operation)); | |
446 | + value = translate.instant(keyFilterPredicate.value.defaultValue ? 'value.true' : 'value.false'); | |
447 | + break; | |
448 | + } | |
449 | + if (!dynamicValue) { | |
450 | + value = `<span class="tb-filter-value">${value}</span>`; | |
451 | + } | |
452 | + return `<span class="tb-filter-predicate"><span class="tb-filter-entity-key">${key}</span> <span class="tb-filter-simple-operation">${operation}</span> ${value}</span>`; | |
453 | +} | |
454 | + | |
361 | 455 | export function keyFilterInfosToKeyFilters(keyFilterInfos: Array<KeyFilterInfo>): Array<KeyFilter> { |
362 | 456 | const keyFilters: Array<KeyFilter> = []; |
363 | 457 | for (const keyFilterInfo of keyFilterInfos) { | ... | ... |
... | ... | @@ -813,15 +813,16 @@ |
813 | 813 | "alarm-rules": "Alarm rules ({{count}})", |
814 | 814 | "add-alarm-rule": "Add alarm rule", |
815 | 815 | "edit-alarm-rule": "Edit alarm rule", |
816 | - "alarm-rule-details": "Alarm rule details", | |
817 | 816 | "alarm-type": "Alarm type", |
818 | 817 | "alarm-type-required": "Alarm type is required.", |
819 | 818 | "alarm-type-pattern-hint": "Alarm type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata", |
820 | 819 | "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm", |
821 | 820 | "create-alarm-rules": "Create alarm rules", |
821 | + "no-create-alarm-rules": "No create conditions configured", | |
822 | 822 | "clear-alarm-rule": "Clear alarm rule", |
823 | - "add-create-alarm-rule": "Add create alarm rule", | |
824 | - "add-clear-alarm-rule": "Add clear alarm rule", | |
823 | + "no-clear-alarm-rule": "No clear condition configured", | |
824 | + "add-create-alarm-rule": "Add create condition", | |
825 | + "add-clear-alarm-rule": "Add clear condition", | |
825 | 826 | "select-alarm-severity": "Select alarm severity", |
826 | 827 | "alarm-severity-required": "Alarm severity is required.", |
827 | 828 | "condition-duration": "Condition duration", |
... | ... | @@ -831,6 +832,7 @@ |
831 | 832 | "condition-duration-value-required": "Duration value is required.", |
832 | 833 | "condition-duration-time-unit-required": "Time unit is required.", |
833 | 834 | "advanced-settings": "Advanced settings", |
835 | + "alarm-rule-details": "Details", | |
834 | 836 | "propagate-alarm": "Propagate alarm", |
835 | 837 | "alarm-details": "Alarm details", |
836 | 838 | "alarm-rule-condition": "Alarm rule condition", |
... | ... | @@ -1277,6 +1279,8 @@ |
1277 | 1279 | "filter": "Filter", |
1278 | 1280 | "editable": "Editable", |
1279 | 1281 | "no-filters-found": "No filters found.", |
1282 | + "no-filter-text": "No filter specified", | |
1283 | + "add-filter-prompt": "Please add filter", | |
1280 | 1284 | "no-filter-matching": "'{{filter}}' not found.", |
1281 | 1285 | "create-new-filter": "Create a new one!", |
1282 | 1286 | "filter-required": "Filter is required.", |
... | ... | @@ -1295,7 +1299,7 @@ |
1295 | 1299 | "and": "and", |
1296 | 1300 | "or": "or" |
1297 | 1301 | }, |
1298 | - "ignore-case": "Ignore case", | |
1302 | + "ignore-case": "ignore case", | |
1299 | 1303 | "value": "Value", |
1300 | 1304 | "remove-filter": "Remove filter", |
1301 | 1305 | "no-filters": "No filters configured", | ... | ... |