Browse Source

UI: User notification settings

pull/8819/head
Artem Dzhereleiko 3 years ago
parent
commit
8dcd32e4b3
  1. 9
      ui-ngx/src/app/core/http/notification.service.ts
  2. 100
      ui-ngx/src/app/core/services/menu.service.ts
  3. 2
      ui-ngx/src/app/modules/home/menu/side-menu.component.html
  4. 113
      ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts
  5. 31
      ui-ngx/src/app/modules/home/pages/account/account.module.ts
  6. 4
      ui-ngx/src/app/modules/home/pages/home-pages.module.ts
  7. 12
      ui-ngx/src/app/modules/home/pages/notification/notification.module.ts
  8. 42
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.html
  9. 24
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.scss
  10. 139
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.ts
  11. 63
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings-routing.modules.ts
  12. 80
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.html
  13. 37
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.scss
  14. 167
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.ts
  15. 10
      ui-ngx/src/app/shared/components/user-menu.component.html
  16. 8
      ui-ngx/src/app/shared/components/user-menu.component.ts
  17. 12
      ui-ngx/src/app/shared/models/notification.models.ts
  18. 17
      ui-ngx/src/assets/locale/locale.constant-en_US.json

9
ui-ngx/src/app/core/http/notification.service.ts

@ -31,6 +31,7 @@ import {
NotificationTarget,
NotificationTemplate,
NotificationType,
NotificationUserSettings,
SlackChanelType,
SlackConversation
} from '@shared/models/notification.models';
@ -174,4 +175,12 @@ export class NotificationService {
}
return this.http.get<PageData<NotificationTemplate>>(url, defaultHttpOptionsFromConfig(config));
}
public getNotificationUserSettings(config?: RequestConfig): Observable<NotificationUserSettings> {
return this.http.get<NotificationUserSettings>(`/api/notification/settings/user`, defaultHttpOptionsFromConfig(config));
}
public saveNotificationUserSettings(settings: NotificationUserSettings, config?: RequestConfig): Observable<NotificationUserSettings> {
return this.http.post<NotificationUserSettings>('/api/notification/settings/user', settings, defaultHttpOptionsFromConfig(config));
}
}

100
ui-ngx/src/app/core/services/menu.service.ts

@ -138,6 +138,42 @@ export class MenuService {
}
]
},
{
id: 'account',
name: 'profile.profile',
type: 'link',
path: '/account',
disabled: true,
icon: 'mdi:message-badge',
isMdiIcon: true,
pages: [
{
id: 'personal_info',
name: 'account.personal-info',
fullName: 'account.personal-info',
type: 'link',
path: '/account/profile',
icon: 'mdi:badge-account-horizontal',
isMdiIcon: true
},
{
id: 'security',
name: 'security.security',
fullName: 'security.security',
type: 'link',
path: '/account/security',
icon: 'lock'
},
{
id: 'notificationSettings',
name: 'account.notification-settings',
fullName: 'account.notification-settings',
type: 'link',
path: '/account/notificationSettings',
icon: 'settings'
}
]
},
{
id: 'notifications_center',
name: 'notification.notification-center',
@ -518,6 +554,42 @@ export class MenuService {
}
]
},
{
id: 'account',
name: 'profile.profile',
type: 'link',
path: '/account',
disabled: true,
icon: 'mdi:message-badge',
isMdiIcon: true,
pages: [
{
id: 'personal_info',
name: 'account.personal-info',
fullName: 'account.personal-info',
type: 'link',
path: '/account/profile',
icon: 'mdi:badge-account-horizontal',
isMdiIcon: true
},
{
id: 'security',
name: 'security.security',
fullName: 'security.security',
type: 'link',
path: '/account/security',
icon: 'lock'
},
{
id: 'notificationSettings',
name: 'account.notification-settings',
fullName: 'account.notification-settings',
type: 'link',
path: '/account/notificationSettings',
icon: 'settings'
}
]
},
{
id: 'notifications_center',
name: 'notification.notification-center',
@ -853,6 +925,34 @@ export class MenuService {
icon: 'view_quilt'
}
]
},
{
id: 'account',
name: 'profile.profile',
type: 'link',
path: '/account',
disabled: true,
icon: 'mdi:message-badge',
isMdiIcon: true,
pages: [
{
id: 'personal_info',
name: 'account.personal-info',
fullName: 'account.personal-info',
type: 'link',
path: '/account/profile',
icon: 'mdi:badge-account-horizontal',
isMdiIcon: true
},
{
id: 'security',
name: 'security.security',
fullName: 'security.security',
type: 'link',
path: '/account/security',
icon: 'lock'
}
]
}
);
if (authState.edgesSupportEnabled) {

2
ui-ngx/src/app/modules/home/menu/side-menu.component.html

@ -16,7 +16,7 @@
-->
<ul fxFlex fxLayout="column" fxLayoutAlign="start stretch" class="tb-side-menu">
<li *ngFor="let section of menuSections$ | async; trackBy: trackByMenuSection" [ngSwitch]="section.type === 'link'">
<li *ngFor="let section of menuSections$ | async; trackBy: trackByMenuSection" [ngSwitch]="section.type === 'link'" [fxHide]="section.disabled">
<tb-menu-link *ngSwitchCase="true" [section]="section"></tb-menu-link>
<tb-menu-toggle *ngSwitchCase="false" [section]="section"></tb-menu-toggle>
</li>

113
ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts

@ -0,0 +1,113 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { RouterTabsComponent } from '@home/components/router-tabs.component';
import { Authority } from '@shared/models/authority.enum';
import { ProfileComponent } from '@home/pages/profile/profile.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { SecurityComponent } from '@home/pages/security/security.component';
import { UserTwoFAProvidersResolver } from '@home/pages/security/security-routing.module';
import { NotificationSettingsComponent } from '@home/pages/notification/settings/notification-settings.component';
import {
NotificationUserSettingsResolver
} from '@home/pages/notification/settings/notification-settings-routing.modules';
import { UserProfileResolver } from '@home/pages/profile/profile-routing.module';
const routes: Routes = [
{
path: 'account',
component: RouterTabsComponent,
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
breadcrumb: {
label: 'account.account',
icon: 'account_circle'
}
},
children: [
{
path: '',
children: [],
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
redirectTo: '/account/profile',
}
},
{
path: 'profile',
component: ProfileComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'account.personal-info',
breadcrumb: {
label: 'account.personal-info',
icon: 'mdi:badge-account-horizontal',
}
},
resolve: {
user: UserProfileResolver
}
},
{
path: 'security',
component: SecurityComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'security.security',
breadcrumb: {
label: 'security.security',
icon: 'lock'
}
},
resolve: {
user: UserProfileResolver,
providers: UserTwoFAProvidersResolver
}
},
{
path: 'notificationSettings',
component: NotificationSettingsComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
title: 'account.notification-settings',
breadcrumb: {
label: 'account.notification-settings',
icon: 'settings'
}
},
resolve: {
userSettings: NotificationUserSettingsResolver
}
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
UserProfileResolver,
UserTwoFAProvidersResolver,
NotificationUserSettingsResolver
]
})
export class AccountRoutingModule { }

31
ui-ngx/src/app/modules/home/pages/account/account.module.ts

@ -0,0 +1,31 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { AccountRoutingModule } from '@home/pages/account/account-routing.module';
@NgModule({
declarations: [
],
imports: [
CommonModule,
SharedModule,
AccountRoutingModule
]
})
export class AccountModule { }

4
ui-ngx/src/app/modules/home/pages/home-pages.module.ts

@ -42,6 +42,7 @@ import { AlarmModule } from '@home/pages/alarm/alarm.module';
import { EntitiesModule } from '@home/pages/entities/entities.module';
import { FeaturesModule } from '@home/pages/features/features.module';
import { NotificationModule } from '@home/pages/notification/notification.module';
import { AccountModule } from '@home/pages/account/account.module';
@NgModule({
exports: [
@ -70,7 +71,8 @@ import { NotificationModule } from '@home/pages/notification/notification.module
ApiUsageModule,
OtaUpdateModule,
UserModule,
VcModule
VcModule,
AccountModule
]
})
export class HomePagesModule { }

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

@ -35,6 +35,13 @@ import { EscalationFormComponent } from '@home/pages/notification/rule/escalatio
import { EscalationsComponent } from '@home/pages/notification/rule/escalations.component';
import { RuleNotificationDialogComponent } from '@home/pages/notification/rule/rule-notification-dialog.component';
import { RuleTableHeaderComponent } from '@home/pages/notification/rule/rule-table-header.component';
import { NotificationSettingsComponent } from '@home/pages/notification/settings/notification-settings.component';
import {
NotificationSettingsRoutingModules
} from '@home/pages/notification/settings/notification-settings-routing.modules';
import {
NotificationSettingFormComponent
} from '@home/pages/notification/settings/notification-setting-form.component';
@NgModule({
declarations: [
@ -49,12 +56,15 @@ import { RuleTableHeaderComponent } from '@home/pages/notification/rule/rule-tab
EscalationFormComponent,
EscalationsComponent,
RuleNotificationDialogComponent,
RuleTableHeaderComponent
RuleTableHeaderComponent,
NotificationSettingsComponent,
NotificationSettingFormComponent
],
imports: [
CommonModule,
SharedModule,
NotificationRoutingModule,
NotificationSettingsRoutingModules,
HomeComponentsModule
]
})

42
ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.html

@ -0,0 +1,42 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="notificationSettingsFormGroup" fxLayout="column">
<div fxLayout="row" style="height: 48px;">
<div fxFlex="50" fxLayoutAlign="start center">
<button mat-icon-button
(click)="toggleEnabled()">
<mat-icon style="color: rgba(0, 0, 0, 0.54);"
[svgIcon]="notificationSettingsFormGroup.get('enabled').value ? 'mdi:toggle-switch': 'mdi:toggle-switch-off-outline'">
</mat-icon>
</button>
<span class="notification-type"
[ngClass]="{'notification-type-disabled': !notificationSettingsFormGroup.get('enabled').value}">
{{notificationSettingsFormGroup.get('ruleName').value}}
</span>
</div>
<div fxFlex fxLayout="row" *ngFor="let deliveryMethods of notificationDeliveryMethodMap">
<div fxFlex fxLayoutAlign="center center">
<mat-checkbox color="primary"
[disabled]="!allowDeliveryMethods?.includes(deliveryMethods) || !notificationSettingsFormGroup.get('enabled').value"
[checked]="getChecked(deliveryMethods)"
(click)="toggleDeliviryMethod(deliveryMethods)"
></mat-checkbox>
</div>
</div>
</div>
</form>

24
ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.scss

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.notification-type {
font-size: 14px;
&-disabled {
color: rgba(0, 0, 0, 0.38)
}
}
}

139
ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.ts

@ -0,0 +1,139 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { UtilsService } from '@core/services/utils.service';
import { isDefinedAndNotNull } from '@core/utils';
import { Subscription } from 'rxjs';
import { NotificationDeliveryMethod, NotificationUserSetting } from '@shared/models/notification.models';
@Component({
selector: 'tb-notification-setting-form',
templateUrl: './notification-setting-form.component.html',
styleUrls: ['./notification-setting-form.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NotificationSettingFormComponent),
multi: true
}
]
})
export class NotificationSettingFormComponent implements ControlValueAccessor, OnInit, OnDestroy {
@Input()
disabled: boolean;
@Input()
allowDeliveryMethods = [];
notificationSettingsFormGroup: UntypedFormGroup;
notificationDeliveryMethod = NotificationDeliveryMethod;
notificationDeliveryMethodMap = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
private modelValue;
private propagateChange = null;
private propagateChangePending = false;
private valueChange$: Subscription = null;
constructor(private utils: UtilsService,
private fb: UntypedFormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
if (this.propagateChangePending) {
this.propagateChangePending = false;
setTimeout(() => {
this.propagateChange(this.modelValue);
}, 0);
}
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.notificationSettingsFormGroup = this.fb.group(
{
ruleId: [],
ruleName: [''],
enabled: [true],
enabledDeliveryMethods: []
});
this.valueChange$ = this.notificationSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
ngOnDestroy() {
if (this.valueChange$) {
this.valueChange$.unsubscribe();
this.valueChange$ = null;
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.notificationSettingsFormGroup.disable({emitEvent: false});
} else {
this.notificationSettingsFormGroup.enable({emitEvent: false});
}
}
toggleEnabled() {
this.notificationSettingsFormGroup.get('enabled').patchValue(!this.notificationSettingsFormGroup.get('enabled').value,
{emitEvent: true});
}
getChecked(deliveryMethod: NotificationDeliveryMethod): boolean {
return this.notificationSettingsFormGroup.get('enabledDeliveryMethods').value.includes(deliveryMethod);
}
toggleDeliviryMethod(deliveryMethod: NotificationDeliveryMethod) {
const enabledDeliveryMethods = this.notificationSettingsFormGroup.get('enabledDeliveryMethods').value;
if (enabledDeliveryMethods.includes(deliveryMethod)) {
enabledDeliveryMethods.splice(enabledDeliveryMethods.indexOf(deliveryMethod), 1);
} else {
enabledDeliveryMethods.push(deliveryMethod);
}
this.notificationSettingsFormGroup.get('enabledDeliveryMethods').patchValue(enabledDeliveryMethods);
}
writeValue(value: NotificationUserSetting): void {
this.propagateChangePending = false;
this.modelValue = value;
if (isDefinedAndNotNull(this.modelValue)) {
this.notificationSettingsFormGroup.patchValue(this.modelValue, {emitEvent: false});
if (!this.disabled && !this.notificationSettingsFormGroup.valid) {
this.updateModel();
}
}
}
private updateModel() {
const value = this.notificationSettingsFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
if (this.propagateChange) {
this.propagateChange(this.modelValue);
} else {
this.propagateChangePending = true;
}
}
}

63
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings-routing.modules.ts

@ -0,0 +1,63 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Resolve, RouterModule, Routes } from '@angular/router';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { Authority } from '@shared/models/authority.enum';
import { Injectable, NgModule } from '@angular/core';
import { NotificationSettingsComponent } from '@home/pages/notification/settings/notification-settings.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Observable } from 'rxjs';
import { NotificationService } from '@core/http/notification.service';
@Injectable()
export class NotificationUserSettingsResolver implements Resolve<any> {
constructor(private store: Store<AppState>,
private notificationService: NotificationService) {
}
resolve(): Observable<any> {
return this.notificationService.getNotificationUserSettings();
}
}
const routes: Routes = [
{
path: 'notificationSettings',
component: NotificationSettingsComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'account.notification-settings',
breadcrumb: {
label: 'account.notification-settings',
icon: 'settings'
}
},
resolve: {
userSettings: NotificationUserSettingsResolver
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [NotificationUserSettingsResolver]
})
export class NotificationSettingsRoutingModules { }

80
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.html

@ -0,0 +1,80 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div>
<mat-card appearance="outlined" class="settings-card">
<mat-card-header>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.xs="8px"
fxLayoutAlign="space-between start" fxLayoutAlign.xs="start start">
<div fxFlex fxLayout="column">
<span class="mat-headline-5" translate>notification.settings.notification-settings</span>
</div>
<div fxLayout="column">
<button mat-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="resetSettings()">{{ 'notification.settings.reset-all' | translate }}</button>
</div>
</div>
</mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px;">
<form [formGroup]="notificationSettings" (ngSubmit)="save()">
<section class="notification-section">
<div class="notification-section-block">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 44px;">
<div fxFlex="50">
<mat-checkbox color="warn"
(click)="$event.stopPropagation()"
[indeterminate]="getIndeterminate()"
(change)="changeInstanceTypeCheckBox($event.checked)"
[checked]="getChecked()">
<mat-label translate>notification.settings.type</mat-label>
</mat-checkbox>
</div>
<div fxFlex *ngFor="let deliveryMethods of notificationDeliveryMethods">
<div fxFlex fxLayoutAlign="center center">
<mat-checkbox color="warn"
[disabled]="!allowNotificationDeliveryMethods?.includes(deliveryMethods) || !getSomeChecked()"
[checked]="getChecked(deliveryMethods)"
(change)="changeInstanceTypeCheckBox($event.checked, deliveryMethods)"
[indeterminate]="getIndeterminate(deliveryMethods)"
(click)="$event.stopPropagation()">
<mat-label>{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}</mat-label>
</mat-checkbox>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div *ngFor="let settingsControl of notificationSettingsFormArray.controls; let i = index; let $last = last;">
<tb-notification-setting-form [formControl]="settingsControl" [allowDeliveryMethods]="allowNotificationDeliveryMethods"></tb-notification-setting-form>
<mat-divider *ngIf="!$last"></mat-divider>
</div>
</div>
</section>
<div fxLayout="row" fxLayoutAlign="end start">
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || notificationSettings.invalid || !notificationSettings.dirty">
{{ 'action.save' | translate }}
</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>

37
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.scss

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../../scss/constants";
:host {
.mat-mdc-card.settings-card {
margin: 8px;
@media #{$mat-gt-sm} {
width: 60%;
}
.mat-headline-5 {
margin: 0;
}
.notification-section {
margin-bottom: 16px;
border: 1px solid rgba(0, 0, 0, 0.12);
overflow-y: hidden;
overflow-x: scroll;
&-block {
min-width: 700px;
}
}
}
}

167
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.ts

@ -0,0 +1,167 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { TranslateService } from '@ngx-translate/core';
import { ActivatedRoute } from '@angular/router';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationUserSettings
} from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { DialogService } from '@core/services/dialog.service';
@Component({
selector: 'tb-notification-settings',
templateUrl: './notification-settings.component.html',
styleUrls: ['./notification-settings.component.scss']
})
export class NotificationSettingsComponent extends PageComponent implements OnInit, HasConfirmForm {
notificationSettings: UntypedFormGroup;
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
allowNotificationDeliveryMethods: Array<NotificationDeliveryMethod>;
constructor(protected store: Store<AppState>,
private route: ActivatedRoute,
private translate: TranslateService,
private dialogService: DialogService,
private notificationService: NotificationService,
private fb: UntypedFormBuilder,) {
super(store);
}
ngOnInit() {
this.notificationService.getAvailableDeliveryMethods({ignoreLoading: true}).subscribe(allowMethods => {
this.allowNotificationDeliveryMethods = allowMethods;
});
this.buildNotificationSettingsForm();
this.patchNotificationSettings(this.route.snapshot.data.userSettings);
}
private buildNotificationSettingsForm() {
this.notificationSettings = this.fb.group({
prefs: this.fb.array([])
});
}
private patchNotificationSettings(settings: NotificationUserSettings) {
const notificationSettingsControls: Array<AbstractControl> = [];
if (settings.prefs) {
settings.prefs.forEach((setting) => {
notificationSettingsControls.push(this.fb.control(setting, [Validators.required]));
});
}
this.notificationSettings.setControl('prefs', this.fb.array(notificationSettingsControls), {emitEvent: false});
}
resetSettings() {
this.dialogService.confirm(
this.translate.instant('notification.settings.reset-all-title'),
this.translate.instant('notification.settings.reset-all-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),
true
).subscribe(
result => {
if (result) {
const settings = this.route.snapshot.data.userSettings;
const notificationSettingsControls: Array<AbstractControl> = [];
this.notificationSettings.reset({});
if (settings.prefs) {
settings.prefs.forEach((setting) => {
setting.enabled = true;
setting.enabledDeliveryMethods = this.notificationDeliveryMethods;
notificationSettingsControls.push(this.fb.control(setting, [Validators.required]));
});
}
this.notificationSettings.setControl('prefs', this.fb.array(notificationSettingsControls), {emitEvent: false});
this.save();
}
}
);
}
getChecked = (method: NotificationDeliveryMethod = null): boolean => {
const type = this.notificationSettings.get('prefs').value;
if (isDefinedAndNotNull(method)) {
return isDefinedAndNotNull(type) && type.every(resource => resource.enabledDeliveryMethods.includes(method));
}
return isDefinedAndNotNull(type) && type.every(resource => resource.enabled);
};
getSomeChecked = () => {
const type = this.notificationSettings.get('prefs').value;
return isDefinedAndNotNull(type) && type.some(resource => resource.enabled);
};
getIndeterminate = (deliveryMethod: NotificationDeliveryMethod = null): boolean => {
const type = this.notificationSettings.get('prefs').value;
if (isDefinedAndNotNull(type)) {
const checkedResource = isDefinedAndNotNull(deliveryMethod) ?
type.filter(resource => resource.enabledDeliveryMethods.includes(deliveryMethod)) :
type.filter(resource => resource.enabled);
return checkedResource.length !== 0 && checkedResource.length !== type.length;
}
return false;
};
changeInstanceTypeCheckBox = (value: boolean, deliveryMethod: NotificationDeliveryMethod = null): void => {
const type = deepClone(this.notificationSettings.get('prefs').value);
if (isDefinedAndNotNull(deliveryMethod)) {
type.forEach(notificationType => {
if (value && !notificationType.enabledDeliveryMethods.includes(deliveryMethod)) {
notificationType.enabledDeliveryMethods.push(deliveryMethod);
} else if (!value && notificationType.enabledDeliveryMethods.includes(deliveryMethod)) {
notificationType.enabledDeliveryMethods.splice(notificationType.enabledDeliveryMethods.indexOf(deliveryMethod), 1);
}
});
} else {
type.forEach(notificationType => notificationType.enabled = value);
}
this.notificationSettings.get('prefs').patchValue(type);
this.notificationSettings.markAsDirty();
};
get notificationSettingsFormArray(): UntypedFormArray {
return this.notificationSettings.get('prefs') as UntypedFormArray;
}
save(): void {
this.notificationService.saveNotificationUserSettings(this.notificationSettings.getRawValue()).subscribe(
(userSettings) => {
this.notificationSettings.get('prefs').reset({});
this.patchNotificationSettings(userSettings);
}
);
}
confirmForm(): UntypedFormGroup {
return this.notificationSettings;
}
}

10
ui-ngx/src/app/shared/components/user-menu.component.html

@ -30,12 +30,12 @@
<div class="tb-user-menu-items" *ngIf="authority$ | async; let authority">
<button mat-menu-item (click)="openProfile()">
<mat-icon class="material-icons">account_circle</mat-icon>
<span translate>home.profile</span>
</button>
<button mat-menu-item (click)="openSecurity()">
<mat-icon class="material-icons">lock</mat-icon>
<span translate>security.security</span>
<span translate>account.account</span>
</button>
<!-- <button mat-menu-item (click)="openSecurity()">-->
<!-- <mat-icon class="material-icons">lock</mat-icon>-->
<!-- <span translate>security.security</span>-->
<!-- </button>-->
<button mat-menu-item (click)="logout()">
<mat-icon class="material-icons">exit_to_app</mat-icon>
<span translate>home.logout</span>

8
ui-ngx/src/app/shared/components/user-menu.component.ts

@ -103,12 +103,12 @@ export class UserMenuComponent implements OnInit, OnDestroy {
}
openProfile(): void {
this.router.navigate(['profile']);
this.router.navigate(['account']);
}
openSecurity(): void {
this.router.navigate(['security']);
}
// openSecurity(): void {
// this.router.navigate(['security']);
// }
logout(): void {
this.authService.logout();

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

@ -589,3 +589,15 @@ export const TriggerTypeTranslationMap = new Map<TriggerType, string>([
[TriggerType.NEW_PLATFORM_VERSION, 'notification.trigger.new-platform-version'],
[TriggerType.RATE_LIMITS, 'notification.trigger.rate-limits'],
]);
export interface NotificationUserSettings {
prefs: Array<NotificationUserSetting>;
}
export interface NotificationUserSetting {
ruleId: string;
ruleName: string;
enabled: boolean;
enabledDeliveryMethods: Array<NotificationDeliveryMethod>;
}

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

@ -3089,7 +3089,17 @@
"updated": "Updated",
"use-template": "Use template",
"view-all": "View all",
"warning": "Warning"
"warning": "Warning",
"settings": {
"notification-settings": "Notification settings",
"reset-all": "Reset all settings",
"reset-all-title": "Are you sure you want to reset form?",
"reset-all-text": "After the confirmation, the settings form will reset to the default value and save.",
"type": "Type",
"enable-all": "Enable all",
"disable-all": "Disable all",
"delivery-not-configured": "Delivery method is not configured"
}
},
"ota-update": {
"add": "Add package",
@ -3176,6 +3186,11 @@
"profiles": {
"profiles": "Profiles"
},
"account": {
"account": "Account",
"personal-info": "Personal info",
"notification-settings": "Notification settings"
},
"security": {
"security": "Security",
"general-settings": "General security settings",

Loading…
Cancel
Save