UI: Add notification center and targets notification block
This commit is contained in:
parent
1317a8aca9
commit
9051759ce8
@ -129,4 +129,9 @@ export class NotificationService {
|
||||
public deleteNotificationTemplate(id: string, config?: RequestConfig): Observable<void> {
|
||||
return this.http.delete<void>(`/api/notification/template/${id}`, defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
public getNotificationTemplates(pageLink: PageLink, config?: RequestConfig): Observable<PageData<NotificationTemplate>> {
|
||||
return this.http.get<PageData<NotificationTemplate>>(`/api/notification/templates${pageLink.toQuery()}`,
|
||||
defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<mat-drawer-content>
|
||||
<div class="mat-padding tb-entity-table tb-absolute-fill">
|
||||
<div fxLayout="column" class="mat-elevation-z1 tb-entity-table-content">
|
||||
<mat-toolbar class="mat-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()">
|
||||
<mat-toolbar class="mat-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty() && entitiesTableConfig.showTitle">
|
||||
<div class="mat-toolbar-tools">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container">
|
||||
<span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
|
||||
|
||||
@ -148,6 +148,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
|
||||
useTimePageLink = false;
|
||||
defaultTimewindowInterval = historyInterval(DAY);
|
||||
entityType: EntityType = null;
|
||||
showTitle = true;
|
||||
tableTitle = '';
|
||||
selectionEnabled = true;
|
||||
searchEnabled = true;
|
||||
|
||||
@ -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: [
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -106,14 +106,10 @@ export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>,
|
||||
}
|
||||
|
||||
export interface NotificationTargetConfig extends
|
||||
Partial<SingleUserNotificationTargetConfig & UserListNotificationTargetConfig & CustomerUsersNotificationTargetConfig>{
|
||||
Partial<UserListNotificationTargetConfig & CustomerUsersNotificationTargetConfig>{
|
||||
type: NotificationTargetConfigType;
|
||||
}
|
||||
|
||||
interface SingleUserNotificationTargetConfig {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
interface UserListNotificationTargetConfig {
|
||||
usersIds: Array<string>;
|
||||
}
|
||||
@ -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, string>([
|
||||
[NotificationTargetConfigType.ALL_USERS, 'notification.target-type.all-users'],
|
||||
[NotificationTargetConfigType.USER_LIST, 'notification.target-type.user-list'],
|
||||
[NotificationTargetConfigType.CUSTOMER_USERS, 'notification.target-type.customer-users'],
|
||||
]);
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user