Browse Source

Merge pull request #8819 from ArtemDzhereleiko/feature/account-notification-settings

User notification settings
pull/8878/head
Vladyslav 3 years ago
committed by GitHub
parent
commit
165543c598
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ui-ngx/src/app/core/auth/auth.service.ts
  2. 9
      ui-ngx/src/app/core/http/notification.service.ts
  3. 100
      ui-ngx/src/app/core/services/menu.service.ts
  4. 12
      ui-ngx/src/app/modules/home/menu/side-menu.component.ts
  5. 64
      ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts
  6. 31
      ui-ngx/src/app/modules/home/pages/account/account.module.ts
  7. 4
      ui-ngx/src/app/modules/home/pages/home-pages.module.ts
  8. 12
      ui-ngx/src/app/modules/home/pages/notification/notification.module.ts
  9. 42
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.html
  10. 24
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.scss
  11. 120
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.ts
  12. 70
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings-routing.modules.ts
  13. 80
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.html
  14. 37
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.scss
  15. 167
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.ts
  16. 9
      ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts
  17. 9
      ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts
  18. 6
      ui-ngx/src/app/shared/components/user-menu.component.html
  19. 6
      ui-ngx/src/app/shared/components/user-menu.component.ts
  20. 12
      ui-ngx/src/app/shared/models/notification.models.ts
  21. 17
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
ui-ngx/src/app/core/auth/auth.service.ts

@ -244,7 +244,7 @@ export class AuthService {
if (authState && authState.authUser) {
if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) {
if ((this.userHasDefaultDashboard(authState) && authState.forceFullscreen) || authState.authUser.isPublic) {
if (path === 'profile' || path === 'security') {
if (path === 'account' || path === 'account.profile' || path === 'account.security' || path === 'account.notificationSettings') {
if (this.userHasProfile(authState.authUser)) {
return false;
} else {

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) {

12
ui-ngx/src/app/modules/home/menu/side-menu.component.ts

@ -17,6 +17,8 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { MenuService } from '@core/services/menu.service';
import { MenuSection } from '@core/services/menu.models';
import { Observable, of } from 'rxjs';
import { mergeMap, share } from 'rxjs/operators';
@Component({
selector: 'tb-side-menu',
@ -26,15 +28,23 @@ import { MenuSection } from '@core/services/menu.models';
})
export class SideMenuComponent implements OnInit {
menuSections$ = this.menuService.menuSections();
menuSections$: Observable<Array<MenuSection>>;
constructor(private menuService: MenuService) {
this.menuSections$ = this.menuService.menuSections().pipe(
mergeMap((sections) => this.filterSections(sections)),
share()
);
}
trackByMenuSection(index: number, section: MenuSection){
return section.id;
}
private filterSections(sections: Array<MenuSection>): Observable<Array<MenuSection>> {
return of(sections.filter(section => !section.disabled));
}
ngOnInit() {
}

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

@ -0,0 +1,64 @@
///
/// 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 { SecurityRoutes, UserTwoFAProvidersResolver } from '@home/pages/security/security-routing.module';
import {
NotificationUserSettingsResolver,
NotificationUserSettingsRoutes
} from '@home/pages/notification/settings/notification-settings-routing.modules';
import { ProfileRoutes, 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',
}
},
...ProfileRoutes,
...SecurityRoutes,
...NotificationUserSettingsRoutes
]
}
];
@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)
}
}
}

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

@ -0,0 +1,120 @@
///
/// 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 propagateChange = null;
private valueChange$: Subscription = null;
constructor(private utils: UtilsService,
private fb: UntypedFormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
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);
}
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 {
if (isDefinedAndNotNull(value)) {
this.notificationSettingsFormGroup.patchValue(value, {emitEvent: false});
}
}
private updateModel() {
this.propagateChange(this.notificationSettingsFormGroup.value);
}
}

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

@ -0,0 +1,70 @@
///
/// 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();
}
}
export const NotificationUserSettingsRoutes: 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
}
}
];
const routes: Routes = [
{
path: 'notificationSettings',
redirectTo: '/account/notificationSettings'
}
];
@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;
}
}

9
ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts

@ -40,7 +40,7 @@ export class UserProfileResolver implements Resolve<User> {
}
}
const routes: Routes = [
export const ProfileRoutes: Routes = [
{
path: 'profile',
component: ProfileComponent,
@ -59,6 +59,13 @@ const routes: Routes = [
}
];
const routes: Routes = [
{
path: 'profile',
redirectTo: 'account/profile'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],

9
ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts

@ -53,7 +53,7 @@ export class UserTwoFAProvidersResolver implements Resolve<Array<TwoFactorAuthPr
}
}
const routes: Routes = [
export const SecurityRoutes: Routes = [
{
path: 'security',
component: SecurityComponent,
@ -73,6 +73,13 @@ const routes: Routes = [
}
];
const routes: Routes = [
{
path: 'security',
redirectTo: '/account/security'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],

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

@ -30,11 +30,7 @@
<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)="logout()">
<mat-icon class="material-icons">exit_to_app</mat-icon>

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

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

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