21 changed files with 1028 additions and 9 deletions
@ -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 { } |
||||
@ -0,0 +1,76 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-notification-center"> |
||||
|
<div class="tb-notification-center-content"> |
||||
|
<section fxLayout="row" fxLayoutAlign="space-between center" class="title-container"> |
||||
|
<h2 translate class="title">notification.notification-center</h2> |
||||
|
<section class="action-buttons" fxLayoutAlign="end center"> |
||||
|
<button mat-icon-button [disabled]="isLoading$ | async" (click)="openSetting()" |
||||
|
matTooltip="{{ 'notification.settings' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>settings</mat-icon> |
||||
|
</button> |
||||
|
<button mat-icon-button [disabled]="isLoading$ | async" (click)="updateData()" |
||||
|
matTooltip="{{ 'action.refresh' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>refresh</mat-icon> |
||||
|
</button> |
||||
|
</section> |
||||
|
</section> |
||||
|
<div class="tabs-container"> |
||||
|
<mat-tab-group #matTabGroup (selectedIndexChange)="updateData()"> |
||||
|
<mat-tab label="{{ 'notification.inbox' | translate }}" #inboxTab="matTab"> |
||||
|
<tb-notification-table |
||||
|
[notificationType]="entityType.NOTIFICATION"> |
||||
|
</tb-notification-table> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{ 'notification.sent' | translate }}"> |
||||
|
<tb-notification-table |
||||
|
[notificationType]="entityType.NOTIFICATION_REQUEST"> |
||||
|
</tb-notification-table> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{ 'notification.templates' | translate }}" #templateTab="matTab"> |
||||
|
<tb-notification-table |
||||
|
[notificationType]="entityType.NOTIFICATION_TEMPLATE"> |
||||
|
</tb-notification-table> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{ 'notification.targets' | translate }}" #targetsTab="matTab"> |
||||
|
<tb-notification-table |
||||
|
[notificationType]="entityType.NOTIFICATION_TARGET"> |
||||
|
</tb-notification-table> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{ 'notification.rules' | translate }}"> |
||||
|
<ng-template matTabContent> |
||||
|
Content 1 - Loaded |
||||
|
</ng-template> |
||||
|
</mat-tab> |
||||
|
<mat-tab disabled> |
||||
|
<ng-template mat-tab-label> |
||||
|
<div fxFlex="100" fxLayout="row" fxLayoutAlign="end center"> |
||||
|
<button mat-flat-button color="primary" *ngIf="targetsTab.isActive" (click)="createTarget($event)"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<mat-icon>add</mat-icon>{{ 'notification.create-target' | translate }} |
||||
|
</div> |
||||
|
</button> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</mat-tab> |
||||
|
</mat-tab-group> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -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; |
||||
|
// } |
||||
|
// } |
||||
|
// } |
||||
|
// } |
||||
|
//} |
||||
@ -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<NotificationTableComponent>; |
||||
|
|
||||
|
|
||||
|
constructor( |
||||
|
protected store: Store<AppState>) { |
||||
|
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}); |
||||
|
} |
||||
|
} |
||||
@ -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 { } |
||||
@ -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<Notification> { |
||||
|
|
||||
|
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<Notification>; |
||||
|
|
||||
|
this.entitiesFetchFunction = pageLink => this.notificationService.getNotifications(pageLink); |
||||
|
|
||||
|
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
||||
|
|
||||
|
this.columns.push( |
||||
|
new DateEntityTableColumn<Notification>('createdTime', 'notification.created-time', this.datePipe, '150px'), |
||||
|
new EntityTableColumn<Notification>('type', 'notification.type', '10%'), |
||||
|
new EntityTableColumn<Notification>('text', 'notification.text', '40%'), |
||||
|
new EntityTableColumn<Notification>('info.description', 'notification.description', '50%') |
||||
|
); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<tb-entities-table [entitiesTableConfig]="entityTableConfig"></tb-entities-table> |
||||
@ -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<any>; |
||||
|
|
||||
|
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<any>) { |
||||
|
if (this.entityTableConfig.onEntityAction) { |
||||
|
this.entityTableConfig.onEntityAction(action); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private getTableConfig(): EntityTableConfig<any> { |
||||
|
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 |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<NotificationTarget> { |
||||
|
|
||||
|
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<NotificationTarget>; |
||||
|
|
||||
|
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<NotificationTarget>('name', 'notification.notification-target', '30%'), |
||||
|
new EntityTableColumn<NotificationTarget>('configuration.type', 'notification.type', '40%', |
||||
|
(target) => this.translate.instant(NotificationTargetConfigTypeTranslateMap.get(target.configuration.type)), |
||||
|
() => ({}), false) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private configureCellActions(): Array<CellActionDescriptor<NotificationTarget>> { |
||||
|
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, TargetsNotificationDialogData, |
||||
|
NotificationTarget>(TargetNotificationDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
isAdd, |
||||
|
target |
||||
|
} |
||||
|
}).afterClosed() |
||||
|
.subscribe((res) => { |
||||
|
if (res) { |
||||
|
this.updateData(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private onTargetAction(action: EntityAction<NotificationTarget>): boolean { |
||||
|
switch (action.action) { |
||||
|
case 'add': |
||||
|
this.editTarget(action.event, action.entity, true); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -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<NotificationTemplate> { |
||||
|
|
||||
|
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<NotificationTemplate>; |
||||
|
|
||||
|
this.entitiesFetchFunction = pageLink => this.notificationService.getNotificationTemplates(pageLink); |
||||
|
|
||||
|
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
||||
|
|
||||
|
this.columns.push( |
||||
|
new DateEntityTableColumn<NotificationTemplate>('createdTime', 'notification.created-time', this.datePipe, '150px'), |
||||
|
new EntityTableColumn<NotificationTemplate>('type', 'notification.type', '10%'), |
||||
|
new EntityTableColumn<NotificationTemplate>('text', 'notification.text', '40%'), |
||||
|
new EntityTableColumn<NotificationTemplate>('info.description', 'notification.description', '50%') |
||||
|
); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="targetNotificationForm" style="min-width: 560px"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ (isAdd ? 'notification.add-notification-target' : 'notification.edit-notification-target') | translate }}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-content> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>notification.target-name</mat-label> |
||||
|
<input matInput formControlName="name" required> |
||||
|
<mat-error *ngIf="targetNotificationForm.get('name').hasError('required')"> |
||||
|
{{ 'notification.target-name-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<section formGroupName="configuration"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>notification.target-type.type</mat-label> |
||||
|
<mat-select formControlName="type"> |
||||
|
<mat-option *ngFor="let type of notificationTargetConfigTypes" [value]="type"> |
||||
|
{{ notificationTargetConfigTypeTranslateMap.get(type) | translate }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
<tb-entity-list |
||||
|
required |
||||
|
formControlName="usersIds" |
||||
|
[entityType]="entityType.USER" |
||||
|
*ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetConfigType.USER_LIST"> |
||||
|
</tb-entity-list> |
||||
|
<section |
||||
|
*ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetConfigType.CUSTOMER_USERS"> |
||||
|
<mat-slide-toggle formControlName="getCustomerIdFromOriginatorEntity"> |
||||
|
{{ 'notification.get-customer-id-from-originator' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<tb-entity-autocomplete |
||||
|
required |
||||
|
formControlName="customerId" |
||||
|
[entityType]="entityType.CUSTOMER" |
||||
|
*ngIf="!targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').value"> |
||||
|
</tb-entity-autocomplete> |
||||
|
</section> |
||||
|
</section> |
||||
|
</div> |
||||
|
<mat-divider></mat-divider> |
||||
|
<div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end"> |
||||
|
<button mat-button |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="cancel()">{{ 'action.cancel' | translate }}</button> |
||||
|
<button mat-raised-button |
||||
|
[disabled]="(isLoading$ | async) || targetNotificationForm.invalid || !targetNotificationForm.dirty" |
||||
|
color="primary" |
||||
|
(click)="save()">{{ (isAdd ? 'action.add' : 'action.save') | translate }}</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -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<TargetNotificationDialogComponent, NotificationTarget> implements OnDestroy { |
||||
|
|
||||
|
targetNotificationForm: FormGroup; |
||||
|
notificationTargetConfigType = NotificationTargetConfigType; |
||||
|
notificationTargetConfigTypes: NotificationTargetConfigType[] = Object.values(NotificationTargetConfigType); |
||||
|
notificationTargetConfigTypeTranslateMap = NotificationTargetConfigTypeTranslateMap; |
||||
|
entityType = EntityType; |
||||
|
isAdd = true; |
||||
|
|
||||
|
private readonly destroy$ = new Subject<void>(); |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
protected dialogRef: MatDialogRef<TargetNotificationDialogComponent, NotificationTarget>, |
||||
|
@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) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue