Browse Source

UI: Refactoring alarm rule

pull/8046/head
Vladyslav_Prykhodko 4 years ago
parent
commit
034832ddbe
  1. 6
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts
  2. 64
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html
  3. 214
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts
  4. 36
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.html
  5. 132
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.ts
  6. 19
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalations.component.ts
  7. 219
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html
  8. 31
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.scss
  9. 299
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts
  10. 2
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts
  11. 2
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts
  12. 4
      ui-ngx/src/app/shared/models/notification.models.ts
  13. 100
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts

@ -48,6 +48,8 @@ import {
} from '@home/pages/notification-center/rule-table/rule-notification-dialog.component';
import { EscalationsComponent } from '@home/pages/notification-center/rule-table/escalations.component';
import { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component';
import { AlarmTypeListComponent } from '@home/pages/notification-center/rule-table/alarm-type-list.component';
import { AlarmSeveritiesListComponent } from '@home/pages/notification-center/rule-table/alarm-severities-list.component';
@NgModule({
declarations: [
@ -63,7 +65,9 @@ import { EscalationFormComponent } from '@home/pages/notification-center/rule-ta
RuleTableHeaderComponent,
RuleNotificationDialogComponent,
EscalationsComponent,
EscalationFormComponent
EscalationFormComponent,
AlarmTypeListComponent,
AlarmSeveritiesListComponent
],
imports: [
CommonModule,

64
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html

@ -0,0 +1,64 @@
<!--
Copyright © 2016-2022 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section [formGroup]="alarmSeveritiesForm">
<mat-form-field fxFlex class="mat-block" [floatLabel]="flotLabel">
<mat-label translate>alarm.alarm-severity-list</mat-label>
<mat-chip-list #severitiesChipList formControlName="alarmSeverities" [required]="required">
<mat-chip *ngFor="let severity of alarmSeveritiesForm.get('alarmSeverities').value"
[selectable]="!disabled"
[removable]="!disabled"
(removed)="onSeverityRemoved(severity)">
{{ alarmSeverityTranslationMap.get(severity) | translate }}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip>
<input matInput
type="text"
formControlName="alarmSeverity"
placeholder="{{ !alarmSeveritiesForm.get('alarmSeverities').value?.length ? ('alarm.any-severity' | translate) : '' }}"
#severityInput
(focusin)="onFocus()"
matAutocompleteOrigin
#origin="matAutocompleteOrigin"
[matAutocompleteConnectedTo]="origin"
[matAutocomplete]="severityAutocomplete"
[matChipInputFor]="severitiesChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addSeverityFromChipInput($event)">
</mat-chip-list>
<mat-autocomplete #severityAutocomplete="matAutocomplete"
class="tb-autocomplete"
(optionSelected)="addSeverityFromAutocomplete($event)"
[displayWith]="displaySeverityFn.bind(this)">
<mat-option *ngFor="let severity of filteredDisplaySeverities | async" [value]="severity">
<span [innerHTML]="alarmSeverityTranslationMap.get(alarmSeverity[severity]) | translate | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="(filteredDisplaySeverities | async)?.length === 0" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span translate>notification.no-severity-found</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ 'notification.no-severity-matching' | translate :{severity: truncate.transform(searchText, true, 6, &apos;...&apos;)} }}
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</section>

214
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts

@ -0,0 +1,214 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models';
import { TranslateService } from '@ngx-translate/core';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { Observable } from 'rxjs';
import { map, share } from 'rxjs/operators';
import { MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { FloatLabelType } from '@angular/material/form-field/form-field';
@Component({
selector: 'tb-alarm-severities-list',
templateUrl: './alarm-severities-list.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmSeveritiesListComponent),
multi: true
}
]
})
export class AlarmSeveritiesListComponent implements ControlValueAccessor{
@ViewChild('severityInput', {static: true}) severityInput: ElementRef<HTMLInputElement>;
@ViewChild('severityInput', {read: MatAutocompleteTrigger, static: true}) autocompleteTrigger: MatAutocompleteTrigger;
alarmSeveritiesForm: FormGroup;
alarmSeverityTranslationMap = alarmSeverityTranslations;
private alarmSeverities = Object.keys(AlarmSeverity);
alarmSeverity = AlarmSeverity;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
private modelValue: Array<string> | null;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
const newVal = coerceBooleanProperty(value);
if (this.requiredValue !== newVal) {
this.requiredValue = newVal;
this.updateValidators();
}
}
@Input()
disabled: boolean;
@Input()
flotLabel: FloatLabelType = 'auto';
searchText = '';
filteredDisplaySeverities: Observable<Array<string>>;
private dirty = false;
private propagateChange = (v: any) => { };
constructor(private fb: FormBuilder,
private translate: TranslateService,
public truncate: TruncatePipe) {
this.alarmSeveritiesForm = this.fb.group({
alarmSeverities: [null, this.required ? [Validators.required] : []],
alarmSeverity: ['']
});
this.filteredDisplaySeverities = this.alarmSeveritiesForm.get('alarmSeverity').valueChanges
.pipe(
map((value) => value ? value : ''),
map(name => this.fetchSeverities(name) ),
share()
);
}
updateValidators() {
this.alarmSeveritiesForm.get('alarmSeverities').setValidators(this.required ? [Validators.required] : []);
this.alarmSeveritiesForm.get('alarmSeverities').updateValueAndValidity();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.alarmSeveritiesForm.disable({emitEvent: false});
} else {
this.alarmSeveritiesForm.enable({emitEvent: false});
}
}
writeValue(value: Array<string> | null): void {
this.searchText = '';
if (value != null && value.length > 0) {
this.modelValue = [...value];
this.alarmSeveritiesForm.get('alarmSeverities').setValue(value);
} else {
this.alarmSeveritiesForm.get('alarmSeverities').setValue(null);
this.modelValue = null;
}
this.dirty = true;
}
addSeverityFromAutocomplete(event: MatAutocompleteSelectedEvent) {
this.addSeverity(event.option.value);
}
addSeverityFromChipInput(event: MatChipInputEvent): void {
const alarmSeverity = event.value || '';
const result = this.fetchSeverities(alarmSeverity.trim());
if (result.length === 1) {
this.addSeverity(result[0]);
}
}
private addSeverity(alarmSeverity: string) {
if (alarmSeverity) {
if (!this.modelValue || this.modelValue.indexOf(alarmSeverity) === -1) {
if (!this.modelValue) {
this.modelValue = [];
}
this.modelValue.push(alarmSeverity);
this.alarmSeveritiesForm.get('alarmSeverities').setValue(this.modelValue);
}
this.propagateChange(this.modelValue);
if (!this.fetchSeverities().length) {
this.clear('', true);
this.autocompleteTrigger.closePanel();
} else {
this.clear();
}
}
}
onSeverityRemoved(alarmSeverity: string) {
const index = this.modelValue.indexOf(alarmSeverity);
if (index >= 0) {
this.modelValue.splice(index, 1);
if (!this.modelValue.length) {
this.modelValue = null;
}
this.alarmSeveritiesForm.get('alarmSeverities').setValue(this.modelValue);
this.propagateChange(this.modelValue);
this.clear(this.severityInput.nativeElement.value, true);
this.autocompleteTrigger.closePanel();
}
}
displaySeverityFn(severity?: string): string | undefined {
return severity ? this.translate.instant(alarmSeverityTranslations.get(AlarmSeverity[severity])) : undefined;
}
onFocus() {
if (this.dirty) {
this.alarmSeveritiesForm.get('alarmSeverity').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
private fetchSeverities(searchText?: string): Array<string> {
this.searchText = searchText;
const allowAlarmSeverity = this.alarmSeverities.filter(severity => !this.modelValue?.includes(severity));
if (this.textIsNotEmpty(this.searchText)) {
const search = this.searchText.toUpperCase();
return allowAlarmSeverity.filter(severity => severity.toUpperCase().includes(search));
} else {
return allowAlarmSeverity;
}
}
private clear(value: string = '', ignoreFocus = false) {
this.severityInput.nativeElement.value = value;
this.alarmSeveritiesForm.get('alarmSeverity').patchValue(value);
if (!ignoreFocus) {
setTimeout(() => {
this.severityInput.nativeElement.blur();
this.severityInput.nativeElement.focus();
}, 0);
}
}
}

36
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.html

@ -0,0 +1,36 @@
<!--
Copyright © 2016-2022 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section [formGroup]="alarmTypeForm">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-type-list</mat-label>
<mat-chip-list #alarmTypeChipList formControlName="alarmTypes" [required]="required">
<mat-chip *ngFor="let type of alarmTypeList"
[selectable]="!disabled"
[removable]="!disabled"
(removed)="removeAlarmType(type)">
{{type}}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip>
<input placeholder="{{ !alarmTypeForm.get('alarmTypes').value?.length ? ('alarm.any-type' | translate) : '' }}"
[matChipInputFor]="alarmTypeChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addAlarmType($event)">
</mat-chip-list>
</mat-form-field>
</section>

132
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-type-list.component.ts

@ -0,0 +1,132 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
@Component({
selector: 'tb-alarm-type-list',
templateUrl: './alarm-type-list.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmTypeListComponent),
multi: true
}
]
})
export class AlarmTypeListComponent implements ControlValueAccessor{
alarmTypeForm: FormGroup;
private modelValue: Array<string> | null;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
const newVal = coerceBooleanProperty(value);
if (this.requiredValue !== newVal) {
this.requiredValue = newVal;
this.updateValidators();
}
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private fb: FormBuilder) {
this.alarmTypeForm = this.fb.group({
alarmTypes: [null, this.required ? [Validators.required] : []]
});
}
updateValidators() {
this.alarmTypeForm.get('alarmTypes').setValidators(this.required ? [Validators.required] : []);
this.alarmTypeForm.get('alarmTypes').updateValueAndValidity();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.alarmTypeForm.disable({emitEvent: false});
} else {
this.alarmTypeForm.enable({emitEvent: false});
}
}
writeValue(value: Array<string> | null): void {
if (value != null && value.length > 0) {
this.modelValue = [...value];
this.alarmTypeForm.get('alarmTypes').setValue(value);
} else {
this.alarmTypeForm.get('alarmTypes').setValue(null);
this.modelValue = null;
}
}
addAlarmType(event: MatChipInputEvent): void {
let alarmType = event.value || '';
const input = event.chipInput.inputElement;
alarmType = alarmType.trim();
if (alarmType) {
if (!this.modelValue || this.modelValue.indexOf(alarmType) === -1) {
if (!this.modelValue) {
this.modelValue = [];
}
this.modelValue.push(alarmType);
this.alarmTypeForm.get('alarmTypes').setValue(this.modelValue);
}
this.propagateChange(this.modelValue);
if (input) {
input.value = '';
}
}
}
removeAlarmType(alarmType: string) {
const index = this.modelValue.indexOf(alarmType);
if (index >= 0) {
this.modelValue.splice(index, 1);
if (!this.modelValue.length) {
this.modelValue = null;
}
this.alarmTypeForm.get('alarmTypes').setValue(this.modelValue);
this.propagateChange(this.modelValue);
}
}
get alarmTypeList(): string[] {
return this.alarmTypeForm.get('alarmTypes').value;
}
}

19
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalations.component.ts

@ -118,13 +118,17 @@ export class EscalationsComponent implements ControlValueAccessor, Validator, On
}
}
writeValue(escalations: Array<NonConfirmedNotificationEscalation> | null): void {
if (escalations?.length === this.escalationsFormArray.length) {
this.escalationsFormArray.patchValue(escalations, {emitEvent: false});
writeValue(escalations: {[key: string]: Array<string>} | null): void {
const escalationParse: Array<NonConfirmedNotificationEscalation> = [];
for (const escalation in escalations) {
escalationParse.push({delayInSec: Number(escalation), targets: escalations[escalation]});
}
if (escalationParse?.length === this.escalationsFormArray.length) {
this.escalationsFormArray.patchValue(escalationParse, {emitEvent: false});
} else {
const escalationsControls: Array<AbstractControl> = [];
if (escalations) {
escalations.forEach((escalation, index) => {
if (escalationParse) {
escalationParse.forEach((escalation, index) => {
escalationsControls.push(this.fb.control(escalation, [Validators.required]));
});
} else {
@ -166,7 +170,10 @@ export class EscalationsComponent implements ControlValueAccessor, Validator, On
}
private updateModel() {
const escalations = this.escalationsFormGroup.get('escalations').value;
const escalations = {};
this.escalationsFormGroup.get('escalations').value.forEach(
escalation => escalations[escalation.delayInSec] = escalation.targets
);
this.propagateChange(escalations);
}
}

219
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html

@ -67,94 +67,46 @@
*ngIf="ruleNotificationForm.get('triggerType').value === triggerType.ALARM">
<ng-template matStepLabel>{{ 'notification.alarm-trigger-settings' | translate }}</ng-template>
<form [formGroup]="alarmTemplateForm">
<fieldset class="fields-group">
<legend translate>notification.filter</legend>
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-type-list</mat-label>
<mat-chip-list #alarmTypeChipList formControlName="alarmTypes">
<mat-chip *ngFor="let type of alarmTypeList()" [selectable]="true"
[removable]="true" (removed)="removeAlarmType(type)">
{{type}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input placeholder="{{ !alarmTemplateForm.get('alarmTypes').value?.length ? ('alarm.any-type' | translate) : '' }}"
[matChipInputFor]="alarmTypeChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addAlarmType($event)">
</mat-chip-list>
</mat-form-field>
<section formGroupName="triggerConfig">
<fieldset class="fields-group">
<legend translate>notification.filter</legend>
<tb-alarm-type-list
formControlName="alarmTypes">
</tb-alarm-type-list>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>alarm.alarm-severity-list</mat-label>
<mat-chip-list #severitiesChipList formControlName="alarmSeverities"
required>
<mat-chip *ngFor="let severity of alarmTemplateForm.get('alarmSeverities').value"
[removable]="true" (removed)="onSeverityRemoved(severity)">
{{ alarmSeverityTranslationMap.get(severity) | translate }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input matInput
type="text"
placeholder="{{ !alarmTemplateForm.get('alarmSeverities').value?.length ? ('alarm.any-severity' | translate) : '' }}"
style="max-width: 200px;"
#severityInput
(focusin)="onSeverityInputFocus()"
matAutocompleteOrigin
#origin="matAutocompleteOrigin"
(input)="severityInputChange.next(severityInput.value)"
[matAutocompleteConnectedTo]="origin"
[matAutocomplete]="severityAutocomplete"
[matChipInputFor]="severitiesChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addSeverityFromChipInput($event)">
</mat-chip-list>
<mat-autocomplete #severityAutocomplete="matAutocomplete"
class="tb-autocomplete"
(optionSelected)="severitySelected($event)"
[displayWith]="displaySeverityFn.bind(this)">
<mat-option *ngFor="let severity of filteredDisplaySeverities | async" [value]="severity">
<span [innerHTML]="alarmSeverityTranslationMap.get(alarmSeverityEnum[severity]) | translate | highlight:severitySearchText"></span>
</mat-option>
<mat-option *ngIf="(filteredDisplaySeverities | async)?.length === 0" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(severitySearchText); else searchNotEmpty">
<span translate>notification.no-severity-found</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('notification.no-severity-matching',
{severity: truncate.transform(severitySearchText, true, 6, &apos;...&apos;)}) | async }}
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</fieldset>
<tb-alarm-severities-list formControlName="alarmSeverities"
flotLabel="always"
required>
</tb-alarm-severities-list>
</fieldset>
<fieldset class="fields-group" formGroupName="clearRule">
<legend translate>notification.clear-rule</legend>
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-status-list</mat-label>
<mat-select formControlName="alarmStatus"
placeholder="{{ !alarmTemplateForm.get('clearRule.alarmStatus').value?.length ? ('alarm.any-status' | translate) : '' }}">
<mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</fieldset>
<fieldset class="fields-group" formGroupName="clearRule">
<legend translate>notification.clear-rule</legend>
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-status-list</mat-label>
<mat-select formControlName="alarmStatus"
placeholder="{{ !alarmTemplateForm.get('triggerConfig.clearRule.alarmStatus').value ? ('alarm.any-status' | translate) : '' }}">
<mat-option [value]="null">{{ 'alarm.any-status' | translate }}</mat-option>
<mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</fieldset>
</section>
<fieldset class="fields-group">
<fieldset class="fields-group" formGroupName="recipientsConfig">
<legend translate>notification.hierarchy-of-receiving</legend>
<tb-escalations-component formControlName="escalationTable"></tb-escalations-component>
</fieldset>
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</form>
<form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig">
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form>
</mat-step>
@ -162,42 +114,51 @@
[stepControl]="deviceInactivityTemplateForm">
<ng-template matStepLabel>{{ 'notification.device-inactivity-trigger-settings' | translate }}</ng-template>
<form [formGroup]="deviceInactivityTemplateForm">
<span translate>notification.filter-by</span>
<div fxFlex fxLayoutAlign="center center">
<mat-button-toggle-group class="tb-notification-unread-toggle-group"
style="width: 250px;"
formControlName="filterByDevice">
<mat-button-toggle fxFlex [value]=true>{{ 'notification.device' | translate }}</mat-button-toggle>
<mat-button-toggle fxFlex [value]=false>{{ 'notification.device-profile' | translate }}</mat-button-toggle>
</mat-button-toggle-group>
</div>
<tb-entity-list
*ngIf="deviceInactivityTemplateForm.get('filterByDevice').value; else deviceProfile"
required
formControlName="devices"
labelText="{{ 'notification.devices' | translate }}"
placeholderText="{{ 'notification.device' | translate }}"
[entityType]="entityType.DEVICE">
</tb-entity-list>
<ng-template #deviceProfile>
<section formGroupName="triggerConfig">
<span translate>notification.filter-by</span>
<div fxFlex fxLayoutAlign="center center">
<mat-button-toggle-group class="tb-notification-unread-toggle-group"
style="width: 250px;"
formControlName="filterByDevice">
<mat-button-toggle fxFlex [value]=true>{{ 'notification.device' | translate }}</mat-button-toggle>
<mat-button-toggle fxFlex [value]=false>{{ 'notification.device-profile' | translate }}</mat-button-toggle>
</mat-button-toggle-group>
</div>
<ng-container *ngIf="deviceInactivityTemplateForm.get('triggerConfig.filterByDevice').value; else deviceProfile">
<tb-entity-list
formControlName="devices"
labelText="{{ 'notification.devices' | translate }}"
placeholderText="{{ 'notification.devices' | translate }}"
[entityType]="entityType.DEVICE">
</tb-entity-list>
<div class="tb-hint" translate>notification.device-list-rule-hint</div>
</ng-container>
<ng-template #deviceProfile>
<tb-entity-list
formControlName="deviceProfiles"
labelText="{{ 'notification.device-profiles' | translate }}"
placeholderText="{{ 'notification.device-profiles' | translate }}"
[entityType]="entityType.DEVICE_PROFILE">
</tb-entity-list>
<div class="tb-hint" translate>notification.device-profiles-list-rule-hint</div>
</ng-template>
</section>
<section formGroupName="recipientsConfig">
<tb-entity-list
required
formControlName="deviceProfiles"
labelText="{{ 'notification.device-profiles' | translate }}"
placeholderText="{{ 'notification.device-profile' | translate }}"
[entityType]="entityType.DEVICE_PROFILE">
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="{{ 'notification.target' | translate }}">
</tb-entity-list>
</ng-template>
<tb-entity-list
required
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="{{ 'notification.target' | translate }}">
</tb-entity-list>
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form>
<form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig">
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form>
</mat-step>
@ -205,7 +166,7 @@
[stepControl]="entityActionTemplateForm">
<ng-template matStepLabel>{{ 'notification.entity-action-trigger-settings' | translate }}</ng-template>
<form [formGroup]="entityActionTemplateForm">
<fieldset class="fields-group">
<fieldset class="fields-group" formGroupName="triggerConfig">
<legend translate>notification.filter</legend>
<tb-entity-type-select required
showLabel
@ -219,16 +180,22 @@
<mat-slide-toggle formControlName="deleted">{{ 'notification.deleted' | translate }}</mat-slide-toggle>
</section>
</fieldset>
<tb-entity-list
required
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="notification.target">
</tb-entity-list>
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
<section formGroupName="recipientsConfig">
<tb-entity-list
required
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="notification.target">
</tb-entity-list>
</section>
</form>
<form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig">
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form>
</mat-step>
</mat-horizontal-stepper>

31
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.scss

@ -24,6 +24,24 @@
}
}
.tb-hint {
margin-top: -1.25em;
padding: 0;
}
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
margin-bottom: 8px;
color: rgba(0, 0, 0, .7);
width: fit-content;
}
}
::ng-deep {
.mat-dialog-content {
@ -119,17 +137,4 @@
}
}
}
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
margin-bottom: 8px;
color: rgba(0, 0, 0, .7);
width: fit-content;
}
}
}

299
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts

@ -15,7 +15,7 @@
///
import { NotificationRule, TriggerType, TriggerTypeTranslationMap } from '@shared/models/notification.models';
import { Component, ElementRef, Inject, OnDestroy, ViewChild } from '@angular/core';
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -25,23 +25,14 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { EntityType } from '@shared/models/entity-type.models';
import { deepClone, deepTrim, isDefined } from '@core/utils';
import { Observable, of, Subject } from 'rxjs';
import { map, mergeMap, share, startWith, takeUntil } from 'rxjs/operators';
import { Observable, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
import { MatStepper } from '@angular/material/stepper';
import { MediaBreakpoints } from '@shared/models/constants';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MatChipInputEvent, MatChipList } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import {
AlarmSeverity,
alarmSeverityTranslations,
AlarmStatus,
alarmStatusTranslations
} from '@shared/models/alarm.models';
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { AlarmStatus, alarmStatusTranslations } from '@shared/models/alarm.models';
import { TranslateService } from '@ngx-translate/core';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
export interface RuleNotificationDialogData {
rule?: NotificationRule;
@ -59,12 +50,6 @@ export class RuleNotificationDialogComponent extends
@ViewChild('addNotificationRule', {static: true}) addNotificationRule: MatStepper;
@ViewChild('severitiesChipList') severitiesChipList: MatChipList;
@ViewChild('severityAutocomplete') severityAutocomplete: MatAutocomplete;
@ViewChild('severityInput') severityInput: ElementRef<HTMLInputElement>;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
stepperOrientation: Observable<StepperOrientation>;
ruleNotificationForm: FormGroup;
@ -76,10 +61,6 @@ export class RuleNotificationDialogComponent extends
triggerTypes: TriggerType[] = Object.values(TriggerType);
triggerTypeTranslationMap = TriggerTypeTranslationMap;
alarmSeverities = Object.keys(AlarmSeverity);
alarmSeverityEnum = AlarmSeverity;
alarmSeverityTranslationMap = alarmSeverityTranslations;
alarmSearchStatuses: AlarmStatus[] = Object.values(AlarmStatus);
alarmSearchStatusTranslationMap = alarmStatusTranslations;
@ -89,17 +70,14 @@ export class RuleNotificationDialogComponent extends
selectedIndex = 0;
filteredDisplaySeverities: Observable<Array<string>>;
severitySearchText = '';
dialogTitle = 'notification.edit-rule';
severityInputChange = new Subject<string>();
private destroy$ = new Subject();
private readonly ruleNotification: NotificationRule;
private triggerTypeFormsMap: Map<TriggerType, FormGroup>;
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<RuleNotificationDialogComponent, NotificationRule>,
@ -107,7 +85,6 @@ export class RuleNotificationDialogComponent extends
private breakpointObserver: BreakpointObserver,
private fb: FormBuilder,
public translate: TranslateService,
public truncate: TruncatePipe,
private notificationService: NotificationService) {
super(store, router, dialogRef);
@ -122,72 +99,66 @@ export class RuleNotificationDialogComponent extends
name: [null, Validators.required],
templateId: [null, Validators.required],
triggerType: [TriggerType.ALARM, Validators.required],
recipientsConfig: this.fb.group({
triggerType: [],
}),
triggerConfig: this.fb.group({
triggerType: []
}),
recipientsConfig: [null],
triggerConfig: [null],
additionalConfig: this.fb.group({
description: []
description: ['']
})
});
this.ruleNotificationForm.get('triggerType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(
value => {
this.ruleNotificationForm.get('triggerConfig').patchValue({triggerType: value}, {emitEvent: false});
this.ruleNotificationForm.get('recipientsConfig').patchValue({triggerType: value}, {emitEvent: false});
}
);
this.alarmTemplateForm = this.fb.group({
alarmTypes: [null],
alarmSeverities: [null, Validators.required],
clearRule: this.fb.group({
alarmStatus: []
triggerConfig: this.fb.group({
alarmTypes: [null],
alarmSeverities: [null, Validators.required],
clearRule: this.fb.group({
alarmStatus: [null]
})
}),
escalationTable: [],
description: ['']
recipientsConfig: this.fb.group({
escalationTable: []
})
});
this.deviceInactivityTemplateForm = this.fb.group({
filterByDevice: [true],
devices: [null, Validators.required],
deviceProfiles: [{value: null, disabled: true}, Validators.required],
targets: [[], Validators.required],
description: ['']
triggerConfig: this.fb.group({
filterByDevice: [true],
devices: [null],
deviceProfiles: [{value: null, disabled: true}]
}),
recipientsConfig: this.fb.group({
targets: [[], Validators.required]
})
});
this.deviceInactivityTemplateForm.get('filterByDevice').valueChanges.pipe(
this.deviceInactivityTemplateForm.get('triggerConfig.filterByDevice').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (value) {
this.deviceInactivityTemplateForm.get('devices').enable({emitEvent: false});
this.deviceInactivityTemplateForm.get('deviceProfiles').disable({emitEvent: false});
this.deviceInactivityTemplateForm.get('triggerConfig.devices').enable({emitEvent: false});
this.deviceInactivityTemplateForm.get('triggerConfig.deviceProfiles').disable({emitEvent: false});
} else {
this.deviceInactivityTemplateForm.get('deviceProfiles').enable({emitEvent: false});
this.deviceInactivityTemplateForm.get('devices').disable({emitEvent: false});
this.deviceInactivityTemplateForm.get('triggerConfig.deviceProfiles').enable({emitEvent: false});
this.deviceInactivityTemplateForm.get('triggerConfig.devices').disable({emitEvent: false});
}
});
this.entityActionTemplateForm = this.fb.group({
entityType: [EntityType.DEVICE],
created: [false],
updated: [false],
deleted: [false],
targets: [[], Validators.required],
description: ['']
triggerConfig: this.fb.group({
entityType: [EntityType.DEVICE],
created: [false],
updated: [false],
deleted: [false]
}),
recipientsConfig: this.fb.group({
targets: [[], Validators.required]
})
});
this.filteredDisplaySeverities = this.severityInputChange
.pipe(
startWith(''),
map((value) => value ? value : ''),
mergeMap(name => this.fetchSeverities(name) ),
share()
);
this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([
[TriggerType.ALARM, this.alarmTemplateForm],
[TriggerType.DEVICE_INACTIVITY, this.deviceInactivityTemplateForm],
[TriggerType.ENTITY_ACTION, this.entityActionTemplateForm]
]);
if (data.isAdd || data.isCopy) {
this.dialogTitle = 'notification.add-rule';
@ -200,145 +171,15 @@ export class RuleNotificationDialogComponent extends
}
this.ruleNotificationForm.reset({}, {emitEvent: false});
this.ruleNotificationForm.patchValue(this.ruleNotification, {emitEvent: false});
if (this.ruleNotification.triggerType === TriggerType.ALARM) {
const parsedEscalationTable = [];
const escalationTable = this.ruleNotification.recipientsConfig.escalationTable;
for (const escalation in escalationTable) {
parsedEscalationTable.push({delayInSec: escalation, targets: escalationTable[escalation]});
}
this.alarmTemplateForm.patchValue({
escalationTable: parsedEscalationTable,
description: this.ruleNotification.additionalConfig.description,
...this.ruleNotification.triggerConfig
}, {emitEvent: false});
} else if (this.ruleNotification.triggerType === TriggerType.DEVICE_INACTIVITY) {
this.deviceInactivityTemplateForm.patchValue({
filterByDevice: !!this.ruleNotification.triggerConfig.devices,
deviceProfiles: this.ruleNotification.triggerConfig.deviceProfiles,
devices: this.ruleNotification.triggerConfig.devices,
targets: this.ruleNotification.recipientsConfig.targets,
description: this.ruleNotification.additionalConfig.description
}, {emitEvent: false});
this.deviceInactivityTemplateForm.get('filterByDevice').updateValueAndValidity({onlySelf: true});
} else {
this.entityActionTemplateForm.patchValue({
targets: this.ruleNotification.recipientsConfig.targets,
description: this.ruleNotification.additionalConfig.description,
...this.ruleNotification.triggerConfig
}, {emitEvent: false});
}
}
}
onSeverityRemoved(severity: string): void {
const severities: string[] = this.alarmTemplateForm.get('alarmSeverities').value;
const index = severities.indexOf(severity);
if (index > -1) {
severities.splice(index, 1);
this.alarmTemplateForm.get('alarmSeverities').setValue(severities);
this.alarmTemplateForm.get('alarmSeverities').markAsDirty();
this.severitiesChipList.errorState = !severities.length;
}
}
onSeverityInputFocus() {
this.severityInputChange.next(this.severityInput.nativeElement.value);
}
addSeverityFromChipInput(event: MatChipInputEvent): void {
const value = event.value;
if ((value || '').trim()) {
const severityName = value.trim().toUpperCase();
const existingSeverity = this.alarmSeverities.find(severity => severity.toUpperCase() === severityName);
if (this.addSeverity(existingSeverity)) {
this.clearSeverityInput('');
const currentForm = this.triggerTypeFormsMap.get(this.ruleNotification.triggerType);
currentForm.patchValue(this.ruleNotification, {emitEvent: false});
if (this.ruleNotification.triggerType === TriggerType.DEVICE_INACTIVITY) {
this.deviceInactivityTemplateForm.get('triggerConfig.filterByDevice')
.patchValue(!!this.ruleNotification.triggerConfig.devices, {onlySelf: true});
}
}
}
private addSeverity(existingSeverity: string): boolean {
if (existingSeverity) {
const displaySeverities: string[] = this.alarmTemplateForm.get('alarmSeverities').value;
const index = displaySeverities.indexOf(existingSeverity);
if (index === -1) {
displaySeverities.push(existingSeverity);
this.alarmTemplateForm.get('alarmSeverities').setValue(displaySeverities);
this.alarmTemplateForm.get('alarmSeverities').markAsDirty();
this.severitiesChipList.errorState = false;
return true;
}
}
return false;
}
clearSeverityInput(value: string = '') {
this.severityInput.nativeElement.value = value;
this.severityInputChange.next(null);
setTimeout(() => {
this.severityInput.nativeElement.blur();
this.severityInput.nativeElement.focus();
}, 0);
}
severitySelected(event: MatAutocompleteSelectedEvent): void {
this.addSeverity(event.option.value);
this.clearSeverityInput('');
}
displaySeverityFn(severity?: string): string | undefined {
return severity ? this.translate.instant(this.alarmSeverityTranslationMap.get(this.alarmSeverityEnum[severity])) : undefined;
}
private fetchSeverities(searchText?: string): Observable<Array<string>> {
this.severitySearchText = searchText;
if (this.severitySearchText && this.severitySearchText.length) {
const search = this.severitySearchText.toUpperCase();
return of(this.alarmSeverities.filter(severity => severity.toUpperCase().includes(search)));
} else {
return of(this.alarmSeverities);
}
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
public alarmTypeList(): string[] {
return this.alarmTemplateForm.get('alarmTypes').value;
}
public removeAlarmType(type: string): void {
const types: string[] = this.alarmTemplateForm.get('alarmTypes').value;
const index = types.indexOf(type);
if (index >= 0) {
types.splice(index, 1);
this.alarmTemplateForm.get('alarmTypes').setValue(types);
this.alarmTemplateForm.get('alarmTypes').markAsDirty();
}
}
public addAlarmType(event: MatChipInputEvent): void {
const input = event.chipInput.inputElement;
let value = event.value || '';
if (value.trim()) {
value = value.trim();
let types: string[] = this.alarmTemplateForm.get('alarmTypes').value;
if (!types || types.indexOf(value) === -1) {
if (!types) {
types = [];
}
types.push(value);
this.alarmTemplateForm.get('alarmTypes').setValue(types);
this.alarmTemplateForm.get('alarmTypes').markAsDirty();
}
}
if (input) {
input.value = '';
}
}
changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex;
}
@ -356,16 +197,7 @@ export class RuleNotificationDialogComponent extends
}
nextStepLabel(): string {
if (this.selectedIndex === 1 && this.selectedIndex < this.maxStepperIndex && this.alarmTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex === 2 && this.selectedIndex < this.maxStepperIndex && this.deviceInactivityTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex === 3 && this.selectedIndex < this.maxStepperIndex && this.entityActionTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex !== 0 && this.selectedIndex >= this.maxStepperIndex) {
if (this.selectedIndex !== 0) {
return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save';
}
return 'action.next';
@ -378,30 +210,15 @@ export class RuleNotificationDialogComponent extends
private add(): void {
if (this.allValid()) {
let formValue = this.ruleNotificationForm.value;
const triggerType = this.ruleNotificationForm.get('triggerType').value;
if (triggerType === TriggerType.ALARM) {
Object.assign(formValue.triggerConfig, this.alarmTemplateForm.value);
const parsedEscalationTable = {};
this.alarmTemplateForm.get('escalationTable').value.forEach(
escalation => parsedEscalationTable[escalation.delayInSec] = escalation.targets
);
formValue.recipientsConfig.escalationTable = parsedEscalationTable;
formValue.additionalConfig.description = this.alarmTemplateForm.get('description').value;
delete formValue.triggerConfig.escalationTable;
} else if (triggerType === TriggerType.DEVICE_INACTIVITY) {
Object.assign(formValue.triggerConfig, this.deviceInactivityTemplateForm.value);
formValue.recipientsConfig.targets = this.deviceInactivityTemplateForm.get('targets').value;
formValue.additionalConfig.description = this.deviceInactivityTemplateForm.get('description').value;
const triggerType: TriggerType = this.ruleNotificationForm.get('triggerType').value;
const currentForm = this.triggerTypeFormsMap.get(triggerType);
Object.assign(formValue, currentForm.value);
if (triggerType === TriggerType.DEVICE_INACTIVITY) {
delete formValue.triggerConfig.filterByDevice;
} else {
Object.assign(formValue.triggerConfig, this.entityActionTemplateForm.value);
formValue.recipientsConfig.targets = this.entityActionTemplateForm.get('targets').value;
formValue.additionalConfig.description = this.entityActionTemplateForm.get('description').value;
}
if (triggerType === TriggerType.DEVICE_INACTIVITY || triggerType === TriggerType.ENTITY_ACTION) {
delete formValue.triggerConfig.trigger;
}
if (this.ruleNotification) {
formValue.recipientsConfig.triggerType = triggerType;
formValue.triggerConfig.triggerType = triggerType;
if (this.ruleNotification && !this.data.isCopy) {
formValue = {...this.ruleNotification, ...formValue};
}
this.notificationService.saveNotificationRule(deepTrim(formValue)).subscribe(

2
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts

@ -215,5 +215,7 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
private reset() {
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: false});
this.updateView(null);
this.dirty = true;
}
}

2
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts

@ -243,7 +243,7 @@ export class TemplateNotificationDialogComponent
delete template.configuration.deliveryMethodsTemplates[method];
}
});
if (this.templateNotification) {
if (this.templateNotification && !this.data.isCopy) {
template = {...this.templateNotification, ...template};
}
this.notificationService.saveNotificationTemplate(deepTrim(template)).subscribe(

4
ui-ngx/src/app/shared/models/notification.models.ts

@ -118,12 +118,12 @@ export interface NotificationRuleTriggerConfig {
export interface NotificationRuleRecipientConfig {
targets?: Array<string>;
escalationTable?: {[key: string]: Array<string>};
escalationTable?: {[key: number]: Array<string>};
}
export interface NonConfirmedNotificationEscalation {
delayInSec: number;
notificationTargetId: NotificationTargetId;
targets: Array<string>;
}
export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>{

100
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2687,29 +2687,34 @@
"action-button": "Action button",
"add-notification-target": "Add notification recipient",
"add-notification-template": "Add notification template",
"add-rule": "Add rule",
"add-target": "Add recipient",
"add-template": "Add template",
"at-least-one-should-be-selected": "At least one should be selected",
"alarm-trigger-settings": "Alarm trigger settings",
"all": "All",
"at-least-one-should-be-selected": "At least one should be selected",
"basic-settings": "Basic settings",
"button-text": "Button text",
"button-text-required": "Button text is required",
"clear-rule": "Clear rule",
"compose": "Compose",
"conversation": "Conversation",
"conversation-required": "Conversation is required",
"add-rule": "Add rule",
"edit-rule": "Edit rule",
"copy-rule": "Copy rule",
"copy-notification-template": "Copy notification template",
"copy-rule": "Copy rule",
"copy-template": "Copy template",
"create-target": "Create recipient",
"created": "Created",
"created-time": "Created time",
"delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.",
"delete-request-title": "Are you sure you want to delete the notification request?",
"delete-rule-text": "Be careful, after the confirmation the notification rule will become unrecoverable.",
"delete-rule-title": "Are you sure you want to delete the notification rule '{{ruleName}}'?",
"delete-target-text": "Be careful, after the confirmation the notification recipient will become unrecoverable.",
"delete-target-title": "Are you sure you want to delete the notification recipient '{{targetName}}'?",
"delete-template-text": "Be careful, after the confirmation the notification template will become unrecoverable.",
"delete-template-title": "Are you sure you want to delete the notification template '{{templateName}}'?",
"deleted": "Deleted",
"delivery-method": "Delivery method",
"delivery-method-type": {
"email": "Email",
@ -2717,14 +2722,26 @@
"slack": "Slack",
"sms": "SMS"
},
"delete-rule-text": "Be careful, after the confirmation the notification rule will become unrecoverable.",
"delete-rule-title": "Are you sure you want to delete the notification rule '{{ruleName}}'?",
"delivery-methods": "Delivery methods",
"description": "Description",
"device": "Device",
"device-inactivity-trigger-settings": "Device inactive trigger settings",
"device-list-rule-hint": "If the field is empty, the trigger will be applied to all devices",
"device-profile": "Device profile",
"device-profiles": "Device profiles",
"device-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all device profiles",
"devices": "Devices",
"edit-notification-target": "Edit notification recipient",
"edit-notification-template": "Edit notification template",
"edit-rule": "Edit rule",
"edit-template": "Edit template",
"email-settings": "Email settings",
"entity-action-trigger-settings": "Entity action trigger settings",
"entity-type": "Entity type",
"filter": "Filter",
"filter-by": "Filter by",
"first-recipient": "First recipient",
"hierarchy-of-receiving": "Hierarchy of receiving notifications",
"inbox": "Inbox",
"link": "Link",
"link-required": "Link is required",
@ -2738,12 +2755,19 @@
"no-notification-request": "No notification request",
"no-notification-templates": "Not found notification templates",
"no-notifications-yet": "No notifications yet",
"no-rule": "No rule configured",
"no-rules-notification": "No rules notification",
"no-severity-found": "No severity found",
"no-severity-matching": "'{{severity}}' not found.",
"no-targets-notification": "No recipients notification",
"not-found-slack-recipient": "Not found slack recipient",
"notification": "Notification",
"notification-center": "Notification center",
"notification-target": "Notification recipient",
"notify": "notify",
"notify-again": "Notify again",
"platform-users": "Platform users",
"recipient": "Recipient",
"recipient-type": "Recipient type",
"recipients-count": "{ count, plural, 1 {1 Recipient} other {# Recipients} }",
"request-status": {
@ -2752,15 +2776,15 @@
"sent": "Sent"
},
"review": "Review",
"no-rules-notification": "No rules notification",
"rule": "Rule",
"rule-name": "Rule name",
"rule-name-required": "Name is required",
"rules": "Rules",
"platform-users": "Platform users",
"scheduler-later": "Schedule for later",
"search-notification": "Search notification",
"search-rules": "Search rules",
"search-targets": "Search recipients",
"search-templates": "Search templates",
"search-rules": "Search rules",
"send-notification": "Send notification",
"send-test-notification": "Send test notification",
"sent": "Sent",
@ -2783,64 +2807,42 @@
"target-type": {
"all-users": "All users",
"customer-users": "Customer users",
"originator-entity-owner-users": "Users of originator entity owner",
"user-filters": "User filter",
"user-list": "User list",
"originator-entity-owner-users": "Users of originator entity owner"
"user-list": "User list"
},
"targets": "Recipients",
"targets-required": "Recipients is required",
"recipient": "Recipient",
"template": "Template",
"template-hint": {
"alarm": "Available params: ${alarmType}, ${alarmSeverity}, ${alarmStatus}, ${alarmOriginatorEntityType}, ${alarmOriginatorId}, ${alarmId}",
"device-inactivity": "Available params: ${deviceName}, ${deviceType}, ${deviceId}",
"entity-action": "Available params: ${actionType}, ${entityType}, ${entityName}, ${entityId}, ${originatorUserName}, ${originatorUserId}",
"general": "Available params: ${recipientEmail}, ${recipientFirstName}, ${recipientLastName}"
},
"template-name": "Template name",
"template-required": "Template is required",
"template-type": {
"alarm": "Alarm",
"general": "General",
"device-inactivity": "Device inactivity",
"entity-action": "Entity action"
},
"template-hint": {
"general": "Available params: ${recipientEmail}, ${recipientFirstName}, ${recipientLastName}",
"alarm": "Available params: ${alarmType}, ${alarmSeverity}, ${alarmStatus}, ${alarmOriginatorEntityType}, ${alarmOriginatorId}, ${alarmId}",
"device-inactivity": "Available params: ${deviceName}, ${deviceType}, ${deviceId}",
"entity-action": "Available params: ${actionType}, ${entityType}, ${entityName}, ${entityId}, ${originatorUserName}, ${originatorUserId}"
"entity-action": "Entity action",
"general": "General"
},
"templates": "Templates",
"text": "Text",
"time": "Time",
"type": "Type",
"unread": "Unread",
"view-all": "View all",
"web-settings": "Web settings",
"rule-name": "Rule name",
"rule-name-required": "Name is required",
"template-name": "Template name",
"hierarchy-of-receiving": "Hierarchy of receiving notifications",
"trigger": {
"trigger": "Trigger",
"trigger-required": "Trigger is required",
"alarm": "Alarm",
"device-inactivity": "Device inactivity",
"entity-action": "Entity action"
"entity-action": "Entity action",
"trigger": "Trigger",
"trigger-required": "Trigger is required"
},
"alarm-trigger-settings": "Alarm trigger settings",
"device-inactivity-trigger-settings": "Device inactive trigger settings",
"entity-action-trigger-settings": "Entity action trigger settings",
"filter": "Filter",
"clear-rule": "Clear rule",
"no-severity-matching": "'{{severity}}' not found.",
"no-severity-found": "No severity found",
"first-recipient": "First recipient",
"notify": "notify",
"no-rule": "No rule configured",
"device": "Device",
"devices": "Devices",
"device-profile": "Device profile",
"device-profiles": "Device profiles",
"filter-by": "Filter by",
"entity-type": "Entity type",
"created": "Created",
"type": "Type",
"unread": "Unread",
"updated": "Updated",
"deleted": "Deleted"
"view-all": "View all",
"web-settings": "Web settings"
},
"ota-update": {
"add": "Add package",

Loading…
Cancel
Save