diff --git a/ui-ngx/src/app/core/http/notification.service.ts b/ui-ngx/src/app/core/http/notification.service.ts index f55f08bae1..93e6f0ab22 100644 --- a/ui-ngx/src/app/core/http/notification.service.ts +++ b/ui-ngx/src/app/core/http/notification.service.ts @@ -129,4 +129,9 @@ export class NotificationService { public deleteNotificationTemplate(id: string, config?: RequestConfig): Observable { return this.http.delete(`/api/notification/template/${id}`, defaultHttpOptionsFromConfig(config)); } + + public getNotificationTemplates(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/notification/templates${pageLink.toQuery()}`, + defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index bf2da1ebbd..b8cde27e26 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -103,6 +103,13 @@ export class MenuService { path: '/widgets-bundles', icon: 'now_widgets' }, + { + id: guid(), + name: 'notification.notification-center', + type: 'link', + path: '/notification-center', + icon: 'notifications' + }, { id: guid(), name: 'admin.system-settings', @@ -203,6 +210,16 @@ export class MenuService { } ] }, + { + name: 'notification.management', + places: [ + { + name: 'notification.notification-center', + path: '/notification-center', + icon: 'notifications' + }, + ] + }, { name: 'admin.system-settings', places: [ @@ -396,6 +413,13 @@ export class MenuService { path: '/usage', icon: 'insert_chart' }, + { + id: guid(), + name: 'notification.notification-center', + type: 'link', + path: '/notification-center', + icon: 'notifications' + }, { id: guid(), name: 'admin.system-settings', @@ -554,6 +578,16 @@ export class MenuService { } ] }, + { + name: 'notification.management', + places: [ + { + name: 'notification.notification-center', + path: '/notification-center', + icon: 'notifications' + }, + ] + }, { name: 'audit-log.audit', places: [ @@ -648,7 +682,14 @@ export class MenuService { type: 'link', path: '/dashboards', icon: 'dashboard' - } + }, + { + id: guid(), + name: 'notification.notification-center', + type: 'link', + path: '/notification-center', + icon: 'notifications' + }, ); return sections; } @@ -711,6 +752,16 @@ export class MenuService { path: '/dashboards' } ] + }, + { + name: 'notification.management', + places: [ + { + name: 'notification.notification-center', + path: '/notification-center', + icon: 'notifications' + }, + ] } ); return homeSections; diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index ed26721661..d3df44d463 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -35,7 +35,7 @@
- +
{{ entitiesTableConfig.tableTitle }} diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index dfd9db2678..e6a8fe6a28 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -148,6 +148,7 @@ export class EntityTableConfig, P extends PageLink = P useTimePageLink = false; defaultTimewindowInterval = historyInterval(DAY); entityType: EntityType = null; + showTitle = true; tableTitle = ''; selectionEnabled = true; searchEnabled = true; diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index fe24a840bb..b161492561 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -40,6 +40,7 @@ import { OtaUpdateModule } from '@home/pages/ota-update/ota-update.module'; import { VcModule } from '@home/pages/vc/vc.module'; import { AssetProfileModule } from '@home/pages/asset-profile/asset-profile.module'; import { ProfilesModule } from '@home/pages/profiles/profiles.module'; +import { NotificationCenterModule } from '@home/pages/notification-center/notification-center.module'; @NgModule({ exports: [ @@ -64,6 +65,7 @@ import { ProfilesModule } from '@home/pages/profiles/profiles.module'; ApiUsageModule, OtaUpdateModule, UserModule, + NotificationCenterModule, VcModule ], providers: [ diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center-routing.module.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center-routing.module.ts new file mode 100644 index 0000000000..dd29229b6b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center-routing.module.ts @@ -0,0 +1,41 @@ +/// +/// 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 { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { NotificationCenterComponent } from '@home/pages/notification-center/notification-center.component'; + +const routes: Routes = [ + { + path: 'notification-center', + component: NotificationCenterComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'notification.notification-center', + breadcrumb: { + label: 'notification.notification-center', + icon: 'notifications' + } + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class NotificationCenterRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html new file mode 100644 index 0000000000..771c16320f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html @@ -0,0 +1,76 @@ + +
+
+
+

notification.notification-center

+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + Content 1 - Loaded + + + + +
+ +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss new file mode 100644 index 0000000000..755a0d3b68 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss @@ -0,0 +1,177 @@ +/** + * 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 '../../../../../scss/constants'; + +:host { + width: 100%; + height: 100%; + display: block; + .tb-notification-center { + padding: 8px; + width: 100%; + height: 100%; + display: block; + + .tb-notification-center-content { + width: 100%; + height: 100%; + background: #fff; + border-radius: 4px; + box-shadow: 0 4px 10px rgba(23, 33, 90, 0.08); + display: flex; + flex-direction: column; + + .title-container { + padding: 16px 16px 0; + + .title { + margin: 8px; + letter-spacing: 0.1px; + font-size: 24px; + line-height: 24px; + font-weight: 400; + } + + .action-buttons { + color: rgba(0,0,0,.54); + } + } + + .tabs-container { + position: relative; + flex-direction: column; + flex: 1 1 100%; + } + + //.mat-toolbar-tools{ + // min-height: auto; + //} + // + //.title-container{ + // overflow: hidden; + //} + // + //.tb-entity-table-title { + // padding-right: 20px; + // white-space: nowrap; + // overflow: hidden; + // text-overflow: ellipsis; + //} + // + //.table-container { + // overflow: auto; + //} + // + //.tb-entity-table-info{ + // white-space: nowrap; + // overflow: hidden; + // text-overflow: ellipsis; + //} + // + //.button-widget-action{ + // margin-left: auto; + // overflow: hidden; + // text-overflow: ellipsis; + //} + } + } + + //@media #{$mat-xs} { + // .mat-toolbar { + // height: auto; + // min-height: 100px; + // + // .tb-entity-table-title{ + // padding-bottom: 5px; + // width: 100%; + // } + // } + //} + + @media #{$mat-xs} { + .tb-notification-center { + .tb-notification-center-content { + .title-container { + padding: 8px; + + .title { + margin: 4px 0; + } + } + } + } + } +} + +:host ::ng-deep { + .tabs-container { + .mat-tab-group { + > .mat-tab-body-wrapper { + position: absolute; + top: 49px; + left: 0; + right: 0; + bottom: 0; + } + > .mat-tab-header { + .mat-tab-label { + min-width: 40px; + + &.mat-tab-disabled { + opacity: 1; + width: 100%; + + .mat-tab-label-content { + width: 100%; + } + } + } + } + } + + .tb-entity-table { + padding: 0; + } + } +} + +//:host ::ng-deep { +// .mat-sort-header-sorted .mat-sort-header-arrow { +// opacity: 1 !important; +// } +// tb-branch-autocomplete { +// mat-form-field { +// font-size: 16px; +// width: 250px; +// +// .mat-form-field-wrapper { +// padding-bottom: 0; +// } +// +// .mat-form-field-underline { +// bottom: 0; +// } +// +// @media #{$mat-xs} { +// width: 100%; +// +// .mat-form-field-infix { +// width: auto !important; +// } +// } +// } +// } +//} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts new file mode 100644 index 0000000000..11f0a0e0e9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts @@ -0,0 +1,60 @@ +/// +/// 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, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MatTabGroup } from '@angular/material/tabs'; +import { + NotificationTableComponent +} from '@home/pages/notification-center/notification-table/notification-table.component'; +import { EntityType } from '@shared/models/entity-type.models'; + +@Component({ + selector: 'tb-notification-center', + templateUrl: './notification-center.component.html', + styleUrls: ['notification-center.component.scss'] +}) +export class NotificationCenterComponent extends PageComponent { + + entityType = EntityType; + + @ViewChild('matTabGroup', {static: true}) matTabs: MatTabGroup; + @ViewChildren(NotificationTableComponent) tableComponent: QueryList; + + + constructor( + protected store: Store) { + super(store); + } + + updateData() { + this.currentTableComponent?.updateData(); + } + + openSetting() { + + } + + private get currentTableComponent(): NotificationTableComponent { + return this.tableComponent.get(this.matTabs.selectedIndex); + } + + createTarget($event: Event) { + this.currentTableComponent?.onEntityAction({event: $event, action: 'add', entity: null}); + } +} 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 new file mode 100644 index 0000000000..26b3b8fc8c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts @@ -0,0 +1,41 @@ +/// +/// 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { NotificationCenterRoutingModule } from './notification-center-routing.module'; +import { NotificationCenterComponent } from './notification-center.component'; +import { HomeComponentsModule } from '@home/components/home-components.module'; +import { + TargetNotificationDialogComponent +} from '@home/pages/notification-center/targets-table/target-notification-dialog.componet'; +import { NotificationTableComponent } from '@home/pages/notification-center/notification-table/notification-table.component'; + +@NgModule({ + declarations: [ + NotificationCenterComponent, + TargetNotificationDialogComponent, + NotificationTableComponent + ], + imports: [ + CommonModule, + SharedModule, + NotificationCenterRoutingModule, + HomeComponentsModule + ] +}) +export class NotificationCenterModule { } 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 new file mode 100644 index 0000000000..6dbf4750e4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts @@ -0,0 +1,54 @@ +/// +/// 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 { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityTypeResource } from '@shared/models/entity-type.models'; +import { DatePipe } from '@angular/common'; +import { Direction } from '@shared/models/page/sort-order'; +import { Notification } from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; + +export class InboxTableConfig extends EntityTableConfig { + + constructor(private notificationService: NotificationService, + private datePipe: DatePipe, + updateOnInit = true) { + super(); + this.loadDataOnInit = true; + this.entitiesDeleteEnabled = false; + this.entityTranslations = { + noEntities: 'notification.no-inbox-notification' + }; + this.entityResources = {} as EntityTypeResource; + + this.entitiesFetchFunction = pageLink => this.notificationService.getNotifications(pageLink); + + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.columns.push( + new DateEntityTableColumn('createdTime', 'notification.created-time', this.datePipe, '150px'), + new EntityTableColumn('type', 'notification.type', '10%'), + new EntityTableColumn('text', 'notification.text', '40%'), + new EntityTableColumn('info.description', 'notification.description', '50%') + ); + + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.html new file mode 100644 index 0000000000..885289faea --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.html @@ -0,0 +1,18 @@ + + 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 new file mode 100644 index 0000000000..f099c3834e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts @@ -0,0 +1,94 @@ +/// +/// 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, Input, OnInit, ViewChild } from '@angular/core'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { NotificationService } from '@core/http/notification.service'; +import { TargetsTableConfig } from '@home/pages/notification-center/notification-table/targets-table-config'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityType } from '@shared/models/entity-type.models'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { InboxTableConfig } from '@home/pages/notification-center/notification-table/inbox-table-config'; +import { DatePipe } from '@angular/common'; +import { TemplateTableConfig } from '@home/pages/notification-center/notification-table/template-table-config'; +import { EntityAction } from '@home/models/entity/entity-component.models'; + +@Component({ + selector: 'tb-notification-table', + templateUrl: './notification-table.component.html' +}) +export class NotificationTableComponent implements OnInit { + + @Input() + notificationType = EntityType.NOTIFICATION; + + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; + + entityTableConfig: EntityTableConfig; + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog) { + } + + ngOnInit() { + this.entityTableConfig = this.getTableConfig(); + this.entityTableConfig.pageMode = false; + this.entityTableConfig.showTitle = false; + this.entityTableConfig.detailsPanelEnabled = false; + this.entityTableConfig.selectionEnabled = false; + this.entityTableConfig.searchEnabled = false; + this.entityTableConfig.addEnabled = false; + } + + updateData() { + this.entitiesTable.updateData(); + } + + onEntityAction(action: EntityAction) { + if (this.entityTableConfig.onEntityAction) { + this.entityTableConfig.onEntityAction(action); + } + } + + private getTableConfig(): EntityTableConfig { + switch (this.notificationType) { + case EntityType.NOTIFICATION_TARGET: + return new TargetsTableConfig( + this.notificationService, + this.translate, + this.dialog + ); + case EntityType.NOTIFICATION: + return new InboxTableConfig( + this.notificationService, + this.datePipe + ); + case EntityType.NOTIFICATION_TEMPLATE: + return new TemplateTableConfig( + this.notificationService, + this.datePipe + ); + case EntityType.NOTIFICATION_REQUEST: + return new TemplateTableConfig( + this.notificationService, + this.datePipe + ); + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts new file mode 100644 index 0000000000..9cef9e157f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts @@ -0,0 +1,102 @@ +/// +/// 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 { + CellActionDescriptor, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityTypeResource } from '@shared/models/entity-type.models'; +import { Direction } from '@shared/models/page/sort-order'; +import { NotificationTarget, NotificationTargetConfigTypeTranslateMap } from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { + TargetNotificationDialogComponent, + TargetsNotificationDialogData +} from '@home/pages/notification-center/targets-table/target-notification-dialog.componet'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityAction } from '@home/models/entity/entity-component.models'; + +export class TargetsTableConfig extends EntityTableConfig { + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private dialog: MatDialog) { + super(); + this.loadDataOnInit = false; + this.entityTranslations = { + noEntities: 'notification.no-targets-notification' + }; + this.entityResources = {} as EntityTypeResource; + + this.entitiesFetchFunction = pageLink => this.notificationService.getNotificationTargets(pageLink); + + this.deleteEntityTitle = target => this.translate.instant('notification.delete-target-title', {targetName: target.name}); + this.deleteEntityContent = () => this.translate.instant('notification.delete-target-text'); + this.deleteEntity = id => this.notificationService.deleteNotificationTarget(id.id); + + this.cellActionDescriptors = this.configureCellActions(); + this.onEntityAction = action => this.onTargetAction(action); + + this.defaultSortOrder = {property: 'name', direction: Direction.ASC}; + + this.columns.push( + new EntityTableColumn('name', 'notification.notification-target', '30%'), + new EntityTableColumn('configuration.type', 'notification.type', '40%', + (target) => this.translate.instant(NotificationTargetConfigTypeTranslateMap.get(target.configuration.type)), + () => ({}), false) + ); + } + + private configureCellActions(): Array> { + return [{ + name: this.translate.instant('device.make-public'), + icon: 'edit', + isEnabled: () => true, + onAction: ($event, entity) => this.editTarget($event, entity) + }]; + } + + private editTarget($event: Event, target: NotificationTarget, isAdd = false) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(TargetNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + target + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.updateData(); + } + }); + } + + private onTargetAction(action: EntityAction): boolean { + switch (action.action) { + case 'add': + this.editTarget(action.event, action.entity, true); + return true; + } + return false; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/template-table-config.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/template-table-config.ts new file mode 100644 index 0000000000..1bda4e5c19 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/template-table-config.ts @@ -0,0 +1,53 @@ +/// +/// 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 { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityTypeResource } from '@shared/models/entity-type.models'; +import { DatePipe } from '@angular/common'; +import { Direction } from '@shared/models/page/sort-order'; +import { NotificationTemplate } from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; + +export class TemplateTableConfig extends EntityTableConfig { + + constructor(private notificationService: NotificationService, + private datePipe: DatePipe) { + super(); + this.loadDataOnInit = false; + this.entitiesDeleteEnabled = false; + this.entityTranslations = { + noEntities: 'notification.no-inbox-notification' + }; + this.entityResources = {} as EntityTypeResource; + + this.entitiesFetchFunction = pageLink => this.notificationService.getNotificationTemplates(pageLink); + + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.columns.push( + new DateEntityTableColumn('createdTime', 'notification.created-time', this.datePipe, '150px'), + new EntityTableColumn('type', 'notification.type', '10%'), + new EntityTableColumn('text', 'notification.text', '40%'), + new EntityTableColumn('info.description', 'notification.description', '50%') + ); + + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html new file mode 100644 index 0000000000..8f37ce7e0d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html @@ -0,0 +1,78 @@ + +
+ +

{{ (isAdd ? 'notification.add-notification-target' : 'notification.edit-notification-target') | translate }}

+ + +
+ + +
+
+ + notification.target-name + + + {{ 'notification.target-name-required' | translate }} + + +
+ + notification.target-type.type + + + {{ notificationTargetConfigTypeTranslateMap.get(type) | translate }} + + + + + +
+ + {{ 'notification.get-customer-id-from-originator' | translate }} + + + +
+
+
+ +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts b/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts new file mode 100644 index 0000000000..d37596bf0a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts @@ -0,0 +1,129 @@ +/// +/// 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 { + NotificationTarget, + NotificationTargetConfigType, + NotificationTargetConfigTypeTranslateMap +} from '@shared/models/notification.models'; +import { Component, Inject, OnDestroy } 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 { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { NotificationService } from '@core/http/notification.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { deepTrim, isDefined } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +export interface TargetsNotificationDialogData { + target?: NotificationTarget; + isAdd?: boolean; +} + +@Component({ + selector: 'tb-target-notification-dialog', + templateUrl: './target-notification-dialog.component.html', + styleUrls: [] +}) +export class TargetNotificationDialogComponent extends + DialogComponent implements OnDestroy { + + targetNotificationForm: FormGroup; + notificationTargetConfigType = NotificationTargetConfigType; + notificationTargetConfigTypes: NotificationTargetConfigType[] = Object.values(NotificationTargetConfigType); + notificationTargetConfigTypeTranslateMap = NotificationTargetConfigTypeTranslateMap; + entityType = EntityType; + isAdd = true; + + private readonly destroy$ = new Subject(); + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: TargetsNotificationDialogData, + private fb: FormBuilder, + private notificationService: NotificationService) { + super(store, router, dialogRef); + + if (isDefined(data.isAdd)) { + this.isAdd = data.isAdd; + } + + this.targetNotificationForm = this.fb.group({ + name: [null, Validators.required], + configuration: this.fb.group({ + type: [NotificationTargetConfigType.ALL_USERS], + usersIds: [{value: null, disabled: true}, Validators.required], + customerId: [{value: null, disabled: true}, Validators.required], + getCustomerIdFromOriginatorEntity: [{value: false, disabled: true}], + }) + }); + + this.targetNotificationForm.get('configuration.type').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((type: NotificationTargetConfigType) => { + this.targetNotificationForm.get('configuration').disable({emitEvent: false}); + switch (type) { + case NotificationTargetConfigType.USER_LIST: + this.targetNotificationForm.get('configuration.usersIds').enable({emitEvent: false}); + break; + case NotificationTargetConfigType.CUSTOMER_USERS: + this.targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').updateValueAndValidity({onlySelf: true}); + break; + } + this.targetNotificationForm.get('configuration.type').enable({emitEvent: false}); + }); + this.targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: boolean) => { + if (value) { + this.targetNotificationForm.get('configuration.customerId').disable({emitEvent: false}); + } else { + this.targetNotificationForm.get('configuration.customerId').enable({emitEvent: false}); + } + }); + + if (isDefined(data.target)) { + this.targetNotificationForm.patchValue(data.target, {emitEvent: false}); + this.targetNotificationForm.get('configuration.type').updateValueAndValidity({onlySelf: true}); + } + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); + } + + cancel(): void { + this.dialogRef.close(null); + } + + save() { + let formValue = deepTrim(this.targetNotificationForm.value); + if (isDefined(this.data.target)) { + formValue = Object.assign({}, this.data.target, formValue); + } + this.notificationService.saveNotificationTarget(formValue).subscribe( + (target) => this.dialogRef.close(target) + ); + } +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index d8b1d78437..165e011d7f 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -39,6 +39,7 @@ export enum EntityType { OTA_PACKAGE = 'OTA_PACKAGE', RPC = 'RPC', QUEUE = 'QUEUE', + NOTIFICATION = 'NOTIFICATION', NOTIFICATION_REQUEST = 'NOTIFICATION_REQUEST', NOTIFICATION_RULE = 'NOTIFICATION_RULE', NOTIFICATION_TARGET = 'NOTIFICATION_TARGET', diff --git a/ui-ngx/src/app/shared/models/id/notification-id.ts b/ui-ngx/src/app/shared/models/id/notification-id.ts index 211cb7a6bc..8abd383b69 100644 --- a/ui-ngx/src/app/shared/models/id/notification-id.ts +++ b/ui-ngx/src/app/shared/models/id/notification-id.ts @@ -14,7 +14,11 @@ /// limitations under the License. /// -export class NotificationId { +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class NotificationId implements EntityId { + entityType = EntityType.NOTIFICATION; id: string; constructor(id: string) { this.id = id; diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index c355249122..fd62e060b7 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -106,14 +106,10 @@ export interface NotificationTarget extends Omit, } export interface NotificationTargetConfig extends - Partial{ + Partial{ type: NotificationTargetConfigType; } -interface SingleUserNotificationTargetConfig { - userId: string; -} - interface UserListNotificationTargetConfig { usersIds: Array; } @@ -179,8 +175,13 @@ export enum SlackChanelType { } export enum NotificationTargetConfigType { - SINGLE_USER = 'SINGLE_USER', USER_LIST = 'USER_LIST', CUSTOMER_USERS = 'CUSTOMER_USERS', ALL_USERS = 'ALL_USERS' } + +export const NotificationTargetConfigTypeTranslateMap = new Map([ + [NotificationTargetConfigType.ALL_USERS, 'notification.target-type.all-users'], + [NotificationTargetConfigType.USER_LIST, 'notification.target-type.user-list'], + [NotificationTargetConfigType.CUSTOMER_USERS, 'notification.target-type.customer-users'], +]); 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 dd28154103..543e990858 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2665,6 +2665,37 @@ "copy-code": "Click to copy", "copied": "Copied!" }, + "notification": { + "notification-center": "Notification center", + "management": "Notification management", + "settings": "Notification settings", + "inbox": "Inbox", + "sent": "Sent", + "templates": "Templates", + "targets": "Targets", + "rules": "Rules", + "created-time": "Created time", + "type": "Type", + "no-inbox-notification": "No notification found", + "no-targets-notification": "No targets notification", + "text": "Text", + "description": "Description", + "notification-target": "Notification target", + "create-target": "Create target", + "add-notification-target": "Add notification target", + "edit-notification-target": "Edit notification target", + "target-name": "Name", + "target-name-required": "Name is required", + "target-type": { + "type": "Type", + "all-users": "All users", + "user-list": "User list", + "customer-users": "Customer users" + }, + "get-customer-id-from-originator": "Get Customer id from originator", + "delete-target-title": "Are you sure you want to delete the notification target '{{targetName}}'?", + "delete-target-text": "Be careful, after the confirmation the notification target will become unrecoverable." + }, "ota-update": { "add": "Add package", "assign-firmware": "Assigned firmware",