diff --git a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts index af928af27b..517b4761d2 100644 --- a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts +++ b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts @@ -56,7 +56,7 @@ export class ShowNotificationPopoverComponent extends PageComponent implements O } ngOnInit() { - this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription(this.notificationWsService, this.zone); + this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription(this.notificationWsService, this.zone, 6); this.notifications$ = this.notificationSubscriber.notifications$.pipe( share({ connector: () => new ReplaySubject(1) diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.html new file mode 100644 index 0000000000..e14c00695f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.html @@ -0,0 +1,21 @@ + + diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.scss new file mode 100644 index 0000000000..fc1e7d6d89 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.scss @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2023 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. + */ +:host{ + display: block; + max-width: 400px; + width: 350px; + padding: 2px; +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.ts new file mode 100644 index 0000000000..cadfed0991 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2023 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, Inject } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Notification } from '@shared/models/notification.models'; + +export interface InboxNotificationDialogData { + notification: Notification; +} + +@Component({ + selector: 'tb-inbox-notification-dialog', + templateUrl: './inbox-notification-dialog.component.html', + styleUrls: ['inbox-notification-dialog.component.scss'] +}) +export class InboxNotificationDialogComponent extends DialogComponent { + + notification: Notification; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: InboxNotificationDialogData) { + super(store, router, dialogRef); + + this.notification = data.notification; + } + + markAsRead(id: string) { + this.dialogRef.close(id) + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts index 584c244e63..383387b647 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts @@ -49,8 +49,11 @@ import { import { EscalationsComponent } from '@home/pages/notification-center/rule-table/escalations.component'; import { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component'; import { - AlarmSeveritiesListComponent -} from '@home/pages/notification-center/rule-table/alarm-severities-list.component'; + InboxNotificationDialogComponent +} from '@home/pages/notification-center/inbox-table/inbox-notification-dialog.component'; +import { + NotificationRequestErrorDialogComponent +} from '@home/pages/notification-center/request-table/notification-request-error-dialog.component'; @NgModule({ declarations: [ @@ -67,7 +70,8 @@ import { RuleNotificationDialogComponent, EscalationsComponent, EscalationFormComponent, - AlarmSeveritiesListComponent + InboxNotificationDialogComponent, + NotificationRequestErrorDialogComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts index f3041d0a54..e3be99e933 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts @@ -32,14 +32,20 @@ import { NotificationService } from '@core/http/notification.service'; import { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox-table/inbox-table-header.component'; import { TranslateService } from '@ngx-translate/core'; import { take } from 'rxjs/operators'; +import { MatDialog } from '@angular/material/dialog'; +import { + InboxNotificationDialogComponent, InboxNotificationDialogData +} from '@home/pages/notification-center/inbox-table/inbox-notification-dialog.component'; export class InboxTableConfig extends EntityTableConfig { constructor(private notificationService: NotificationService, private translate: TranslateService, + private dialog: MatDialog, private datePipe: DatePipe) { super(); this.entitiesDeleteEnabled = false; + this.rowPointer = true; this.entityTranslations = { noEntities: 'notification.no-inbox-notification', search: 'notification.search-notification' @@ -50,6 +56,11 @@ export class InboxTableConfig extends EntityTableConfig { this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + this.handleRowClick = ($event, notification) => { + this.showNotification($event, notification) + return true; + } + this.componentsData = { unreadOnly: true }; @@ -117,4 +128,22 @@ export class InboxTableConfig extends EntityTableConfig { } }); } + + private showNotification($event: Event, notification: Notification) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(InboxNotificationDialogComponent, { + disableClose: false, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + notification + } + }).afterClosed().subscribe(res => { + if (res) { + this.markAsRead(null, notification); + } + }); + } } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts index 4b6ddb647f..a84c9a32c0 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts @@ -71,6 +71,7 @@ export class NotificationTableComponent implements OnInit { return new InboxTableConfig( this.notificationService, this.translate, + this.dialog, this.datePipe ); case EntityType.NOTIFICATION_TEMPLATE: diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts index 526afb0cef..8c55d0e47f 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts @@ -24,6 +24,7 @@ import { NotificationDeliveryMethodTranslateMap, NotificationRequest, NotificationRequestInfo, + NotificationRequestStats, NotificationRequestStatus, NotificationRequestStatusTranslateMap, NotificationTemplate @@ -40,6 +41,10 @@ import { RequestNotificationDialogData } from '@home/pages/notification-center/request-table/request-notification-dialog.componet'; import { PageLink } from '@shared/models/page/page-link'; +import { + NotificationRequestErrorDialogComponent, + NotificationRequestErrorDialogData +} from '@home/pages/notification-center/request-table/notification-request-error-dialog.component'; export class RequestTableConfig extends EntityTableConfig { @@ -66,12 +71,21 @@ export class RequestTableConfig extends EntityTableConfig this.onRequestAction(action); + this.handleRowClick = (event, entity) => { + const path = (event as any).path || (event.composedPath && event.composedPath()); + if ((event.target as HTMLElement).getElementsByClassName('stats').length || (event.target as HTMLElement).className === 'stats') { + this.openStatsErrorDialog(event, entity); + } + return true; + }; + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.columns.push( new DateEntityTableColumn('createdTime', 'notification.created-time', this.datePipe, '170px'), new EntityTableColumn('status', 'notification.status', '15%', - request => this.requestStatus(request.status), request => this.requestStatusStyle(request.status)), + request => `${this.requestStatus(request.status)}${this.requestStats(request.stats)}`, + request => this.requestStatusStyle(request.status)), new EntityTableColumn('deliveryMethods', 'notification.delivery-method', '15%', (request) => request.deliveryMethods .map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodTranslateMap.get(deliveryMethod))).join(', '), @@ -136,6 +150,18 @@ export class RequestTableConfig extends EntityTableConfig`; } + private requestStats(stats: NotificationRequestStats): string { + let countError = 0; + Object.keys(stats.errors).forEach(method => countError += Object.keys(stats.errors[method]).length); + if (countError === 0) { + return '' + } + return `
+ ${countError} ${this.translate.instant('notification.fails')} > +
`; + } + private requestStatusStyle(status: NotificationRequestStatus): object { const styleObj = { fontSize: '14px', @@ -152,4 +178,18 @@ export class RequestTableConfig extends EntityTableConfig(NotificationRequestErrorDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + notificationRequest + } + }).afterClosed().subscribe(() => {}); + } } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.html new file mode 100644 index 0000000000..f8df6cc13b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.html @@ -0,0 +1,40 @@ + + +

notification.failed-send

+ + +
+
+
+
{{ notificationDeliveryMethodErrorTranslateMap.get(errorStat.key) | translate }}
+ + + + + +
+ {{ error.key }} + {{ error.value }}
+ +
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.scss new file mode 100644 index 0000000000..95301b4daa --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.scss @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2023 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. + */ +:host{ + display: block; + width: 600px; + max-width: 100%; + + h6 { + font-weight: 500; + font-size: 16px; + line-height: 20px; + margin-top: 0; + } + + .tb-table-list { + table-layout: fixed; + width: 100%; + border: hidden; + border-collapse: collapse; + + td { + border: 8px solid transparent; + border-collapse: collapse; + font-size: 14px; + } + + .tb-row-error { + word-wrap: break-word; + } + } + + .mat-divider { + margin: 24px 0; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.ts new file mode 100644 index 0000000000..d30639e310 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/notification-request-error-dialog.component.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2023 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, Inject } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { NotificationDeliveryMethod, NotificationRequest } from '@shared/models/notification.models'; + +export interface NotificationRequestErrorDialogData { + notificationRequest: NotificationRequest; +} + +@Component({ + selector: 'tb-notification-request-error-dialog', + templateUrl: './notification-request-error-dialog.component.html', + styleUrls: ['notification-request-error-dialog.component.scss'] +}) +export class NotificationRequestErrorDialogComponent extends DialogComponent { + + errorStats: { [key in NotificationDeliveryMethod]: {[key in string]: string}}; + + notificationDeliveryMethodErrorTranslateMap = new Map([ + [NotificationDeliveryMethod.PUSH, 'notification.delivery-method-type.push-failed-sent'], + [NotificationDeliveryMethod.SMS, 'notification.delivery-method-type.sms-failed-sent'], + [NotificationDeliveryMethod.EMAIL, 'notification.delivery-method-type.email-failed-sent'], + [NotificationDeliveryMethod.SLACK, 'notification.delivery-method-type.slack-failed-sent'], + ]); + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: NotificationRequestErrorDialogData) { + super(store, router, dialogRef); + + this.errorStats = data.notificationRequest.stats.errors; + } + + cancel(): void { + this.dialogRef.close(null); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html index 1f29a6918f..b5f5b8057a 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html @@ -167,8 +167,6 @@ {{ 'notification.button-text-required' | translate }} - -
@@ -199,6 +197,10 @@
+ + {{ 'notification.set-entity-from-notification' | translate }} + diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html deleted file mode 100644 index 2724caed9d..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.html +++ /dev/null @@ -1,63 +0,0 @@ - -
- - alarm.alarm-severity-list - - - {{ alarmSeverityTranslationMap.get(severity) | translate }} - close - - - - - - - - -
-
- notification.no-severity-found -
- - - {{ 'notification.no-severity-matching' | translate :{severity: truncate.transform(searchText, true, 6, '...')} }} - - -
-
-
-
-
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts deleted file mode 100644 index 3f99054475..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/alarm-severities-list.component.ts +++ /dev/null @@ -1,220 +0,0 @@ -/// -/// Copyright © 2016-2023 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'; - -@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; - @ViewChild('severityInput', {read: MatAutocompleteTrigger, static: true}) autocompleteTrigger: MatAutocompleteTrigger; - - alarmSeveritiesForm: FormGroup; - - alarmSeverityTranslationMap = alarmSeverityTranslations; - private alarmSeveritiesValues = Object.keys(AlarmSeverity) as Array; - alarmSeverity = AlarmSeverity; - - alarmSeverities: Array = [] - - readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; - - private modelValue: Array | 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>; - - 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 | null): void { - this.searchText = ''; - if (value != null && value.length > 0) { - this.modelValue = [...value]; - this.alarmSeverities = [...value]; - this.alarmSeveritiesForm.get('alarmSeverities').setValue(value); - } else { - this.alarmSeverities = []; - 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: AlarmSeverity) { - if (alarmSeverity) { - if (!this.modelValue || this.modelValue.indexOf(alarmSeverity) === -1) { - if (!this.modelValue) { - this.modelValue = []; - } - this.modelValue.push(alarmSeverity); - this.alarmSeverities.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); - this.alarmSeverities.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 { - this.searchText = searchText; - const allowAlarmSeverity = this.alarmSeveritiesValues.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); - } - } - -} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html index d2b33c5db9..8abc108e50 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html @@ -97,18 +97,23 @@ formControlName="alarmTypes"> - - + + alarm.alarm-severity-list + + + {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + + +
notification.clear-rule alarm.alarm-status-list - - {{ 'alarm.any-status' | translate }} + {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} @@ -205,7 +210,37 @@ [stepControl]="alarmCommentTemplateForm"> {{ 'notification.alarm-comment-trigger-settings' | translate }}
+
+
+ notification.filter + + + + alarm.alarm-severity-list + + + {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + + + + + alarm.alarm-status-list + + + {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} + + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts index 5d69432360..efa2d88b05 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts @@ -36,7 +36,12 @@ 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 { AlarmStatus, alarmStatusTranslations } from '@shared/models/alarm.models'; +import { + AlarmSearchStatus, + alarmSearchStatusTranslations, + AlarmSeverity, + alarmSeverityTranslations +} from '@shared/models/alarm.models'; import { TranslateService } from '@ngx-translate/core'; import { TargetNotificationDialogComponent, @@ -72,8 +77,16 @@ export class RuleNotificationDialogComponent extends triggerTypes: TriggerType[] = Object.values(TriggerType); triggerTypeTranslationMap = TriggerTypeTranslationMap; - alarmSearchStatuses: AlarmStatus[] = Object.values(AlarmStatus); - alarmSearchStatusTranslationMap = alarmStatusTranslations; + alarmSearchStatuses = [ + AlarmSearchStatus.ACTIVE, + AlarmSearchStatus.CLEARED, + AlarmSearchStatus.ACK, + AlarmSearchStatus.UNACK + ]; + alarmSearchStatusTranslationMap = alarmSearchStatusTranslations; + + alarmSeverityTranslationMap = alarmSeverityTranslations; + alarmSeverities = Object.keys(AlarmSeverity) as Array; entityType = EntityType; entityTypes: EntityType[] = Object.values(EntityType); @@ -136,9 +149,9 @@ export class RuleNotificationDialogComponent extends this.alarmTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ alarmTypes: [null], - alarmSeverities: [null], + alarmSeverities: [[]], clearRule: this.fb.group({ - alarmStatus: [null] + alarmStatus: [[]] }) }) }); @@ -172,7 +185,13 @@ export class RuleNotificationDialogComponent extends }) }); - this.alarmCommentTemplateForm = this.fb.group({ }); + this.alarmCommentTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + alarmTypes: [null], + alarmSeverities: [[]], + alarmStatus: [[]] + }) + }); this.triggerTypeFormsMap = new Map([ [TriggerType.ALARM, this.alarmTemplateForm], diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts index d3c27ffb92..9621b88bd7 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts +++ b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts @@ -102,11 +102,11 @@ export abstract class TemplateConfiguration extends DialogComponent< actionButtonConfig: this.fb.group({ enabled: [false], text: [{value: '', disabled: true}, Validators.required], - color: ['#305680'], linkType: [ActionButtonLinkType.LINK], link: [{value: '', disabled: true}, Validators.required], dashboardId: [{value: null, disabled: true}, Validators.required], - dashboardState: [{value: null, disabled: true}] + dashboardState: [{value: null, disabled: true}], + setEntityIdInState: [{value: true, disabled: true}], }), }) }); @@ -142,10 +142,12 @@ export abstract class TemplateConfiguration extends DialogComponent< this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').disable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').disable({emitEvent: false}); + this.pushTemplateForm.get('additionalConfig.actionButtonConfig.setEntityIdInState').disable({emitEvent: false}); } else { this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardId').enable({emitEvent: false}); this.pushTemplateForm.get('additionalConfig.actionButtonConfig.dashboardState').enable({emitEvent: false}); + this.pushTemplateForm.get('additionalConfig.actionButtonConfig.setEntityIdInState').enable({emitEvent: false}); } } }); diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html index 9fe5c1412a..573d5404b3 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html @@ -131,8 +131,6 @@ {{ 'notification.button-text-required' | translate }} - -
@@ -163,6 +161,10 @@
+ + {{ 'notification.set-entity-from-notification' | translate }} +
diff --git a/ui-ngx/src/app/shared/components/notification/notification.component.html b/ui-ngx/src/app/shared/components/notification/notification.component.html index 5baa62fdc5..5e9871e475 100644 --- a/ui-ngx/src/app/shared/components/notification/notification.component.html +++ b/ui-ngx/src/app/shared/components/notification/notification.component.html @@ -30,8 +30,7 @@
- @@ -39,7 +38,7 @@
{{ notification.createdTime ?? currentDate | dateAgo }} -
diff --git a/ui-ngx/src/app/shared/components/notification/notification.component.ts b/ui-ngx/src/app/shared/components/notification/notification.component.ts index 0fedd3a978..2173c30023 100644 --- a/ui-ngx/src/app/shared/components/notification/notification.component.ts +++ b/ui-ngx/src/app/shared/components/notification/notification.component.ts @@ -19,6 +19,7 @@ import { ActionButtonLinkType, AlarmSeverityNotificationColors, Notification, + NotificationStatus, NotificationType, NotificationTypeIcons } from '@shared/models/notification.models'; @@ -53,6 +54,7 @@ export class NotificationComponent implements OnInit { showIcon = false; showButton = false; buttonLabel = ''; + hideMarkAsReadButton = false; tinycolor = tinycolor_; @@ -71,6 +73,7 @@ export class NotificationComponent implements OnInit { ngOnInit() { this.showIcon = this.notification.additionalConfig?.icon?.enabled; this.showButton = this.notification.additionalConfig?.actionButtonConfig?.enabled; + this.hideMarkAsReadButton = this.notification.status === NotificationStatus.READ; if (this.showButton) { this.buttonLabel = this.utils.customTranslation(this.notification.additionalConfig.actionButtonConfig.text, this.notification.additionalConfig.actionButtonConfig.text); @@ -96,7 +99,13 @@ export class NotificationComponent implements OnInit { let state = null; if (this.notification.additionalConfig.actionButtonConfig.dashboardState) { const stateObject: StateObject = {}; - stateObject.params = {}; + if (this.notification.additionalConfig.actionButtonConfig.setEntityIdInState) { + stateObject.params = { + entityId: this.notification.info.originatorId ?? null + }; + } else { + stateObject.params = {}; + } stateObject.id = this.notification.additionalConfig.actionButtonConfig.dashboardState; state = objToBase64URI([ stateObject ]); } diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 1c9cc0e472..3c4ffbb994 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -23,7 +23,7 @@ import { NotificationTargetId } from '@shared/models/id/notification-target-id'; import { NotificationTemplateId } from '@shared/models/id/notification-template-id'; import { EntityId } from '@shared/models/id/entity-id'; import { NotificationRuleId } from '@shared/models/id/notification-rule-id'; -import { AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models'; +import { AlarmSearchStatus, AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models'; import { EntityType } from '@shared/models/entity-type.models'; export interface Notification { @@ -45,6 +45,7 @@ export interface NotificationInfo { alarmSeverity?: AlarmSeverity; alarmStatus?: AlarmStatus; alarmType?: string; + originatorId?: EntityId; } export interface NotificationRequest extends Omit, 'label'> { @@ -73,7 +74,7 @@ export interface NotificationRequestPreview { export interface NotificationRequestStats { sent: Map; - errors: Map>; + errors: { [key in NotificationDeliveryMethod]: {[key in string]: string}}; processedRecipients: Map>; } @@ -111,7 +112,10 @@ export interface NotificationRule extends Omit, 'la export interface NotificationRuleTriggerConfig { alarmTypes?: Array; alarmSeverities?: Array; - clearRule?: AlarmStatus; + alarmStatus?: Array; + clearRule?: { + alarmStatus: Array; + }; devices?: Array; deviceProfiles?: Array; entityType?: EntityType; @@ -205,11 +209,11 @@ interface PushDeliveryMethodAdditionalConfig { actionButtonConfig: { enabled: boolean; text: string; - color: string; linkType: ActionButtonLinkType, link?: string; dashboardId?: string; dashboardState?: string; + setEntityIdInState?: boolean }; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 1ec049c875..12f33f3a34 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2749,12 +2749,16 @@ "delivery-method": "Delivery method", "delivery-method-type": { "email": "Email", + "email-failed-sent": "Email messages failed sent", "email-preview": "Email notification preview", "push": "Web", + "push-failed-sent": "Web messages failed sent", "push-preview": "Web notification preview", "slack": "Slack", + "slack-failed-sent": "Slack messages failed sent", "slack-preview": "Slack notification preview", "sms": "SMS", + "sms-failed-sent": "SMS messages failed sent", "sms-preview": "SMS notification preview" }, "delivery-methods": "Delivery methods", @@ -2773,6 +2777,8 @@ "email-settings": "Email settings", "entity-action-trigger-settings": "Entity action trigger settings", "entity-type": "Entity type", + "failed-send": "Failed send", + "fails": "Fails", "filter": "Filter", "first-recipient": "First recipient", "hierarchy-of-receiving": "Hierarchy of receiving notifications", @@ -2827,6 +2833,7 @@ "send-notification": "Send notification", "sent": "Sent", "settings": "Notification settings", + "set-entity-from-notification": "Set entity from notification to dashboard state", "slack": "Slack", "slack-chanel-type": "Slack chanel type", "slack-chanel-types": {