- + + fxFlex class="mat-toolbar-tools">
@@ -73,7 +73,7 @@ + *ngIf="!hideLoadingBar && (isLoading$ | async)">
diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 5f59c2a9a9..6e045b9786 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -15,21 +15,22 @@ /// import { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; -import { fromEvent, Observable } from 'rxjs'; -import { select, Store } from '@ngrx/store'; -import { debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; +import { fromEvent } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; -import { User } from '@shared/models/user.model'; import { PageComponent } from '@shared/components/page.component'; import { AppState } from '@core/core.state'; -import { getCurrentAuthState, selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { MediaBreakpoints } from '@shared/models/constants'; import screenfull from 'screenfull'; import { MatSidenav } from '@angular/material/sidenav'; import { AuthState } from '@core/auth/auth.models'; import { WINDOW } from '@core/services/window.service'; import { instanceOfSearchableComponent, ISearchableComponent } from '@home/models/searchable-component.models'; +import { ActiveComponentService } from '@core/services/active-component.service'; +import { RouterTabsComponent } from '@home/components/router-tabs.component'; @Component({ selector: 'tb-home', @@ -57,28 +58,21 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni fullscreenEnabled = screenfull.isEnabled; - authUser$: Observable; - userDetails$: Observable; - userDetailsString: Observable; - searchEnabled = false; showSearch = false; searchText = ''; + hideLoadingBar = false; + constructor(protected store: Store, @Inject(WINDOW) private window: Window, + private activeComponentService: ActiveComponentService, public breakpointObserver: BreakpointObserver) { super(store); } ngOnInit() { - this.authUser$ = this.store.pipe(select(selectAuthUser)); - this.userDetails$ = this.store.pipe(select(selectUserDetails)); - this.userDetailsString = this.userDetails$.pipe(map((user: User) => { - return JSON.stringify(user); - })); - const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); this.sidenavMode = isGtSm ? 'side' : 'over'; this.sidenavOpened = isGtSm; @@ -130,6 +124,7 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni } activeComponentChanged(activeComponent: any) { + this.activeComponentService.setCurrentActiveComponent(activeComponent); if (!this.activeComponent) { setTimeout(() => { this.updateActiveComponent(activeComponent); @@ -143,6 +138,7 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni this.showSearch = false; this.searchText = ''; this.activeComponent = activeComponent; + this.hideLoadingBar = activeComponent && activeComponent instanceof RouterTabsComponent; if (this.activeComponent && instanceOfSearchableComponent(this.activeComponent)) { this.searchEnabled = true; this.searchableComponent = this.activeComponent; diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.html b/ui-ngx/src/app/modules/home/menu/menu-link.component.html index d07dc16b7e..ab5b1faa27 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-link.component.html +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.html @@ -15,8 +15,8 @@ limitations under the License. --> - - {{section.icon}} - + + {{section.icon}} + {{section.name | translate}} diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html index 3cd9922707..de4f16b163 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html @@ -15,13 +15,12 @@ limitations under the License. --> - - {{section.icon}} - + + {{section.icon}} + {{section.name | translate}} + [ngClass]="{'tb-toggled' : section.opened}">
  • diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss index 85c5dfa42e..93d0276639 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss @@ -33,7 +33,7 @@ z-index: 1; overflow: hidden; - transition: .75s cubic-bezier(.35, 0, .25, 1); + transition: .45s cubic-bezier(.35, 0, .25, 1); transition-property: height; diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts index eedc09612b..70d711469a 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts @@ -17,6 +17,9 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { MenuSection } from '@core/services/menu.models'; import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ActionPreferencesUpdateOpenedMenuSection } from '@core/auth/auth.actions'; @Component({ selector: 'tb-menu-toggle', @@ -28,24 +31,27 @@ export class MenuToggleComponent implements OnInit { @Input() section: MenuSection; - constructor(private router: Router) { + constructor(private router: Router, + private store: Store) { } ngOnInit() { } - sectionActive(): boolean { - return this.router.isActive(this.section.path, false); - } - sectionHeight(): string { - if (this.router.isActive(this.section.path, false)) { - return this.section.height; + if (this.section.opened) { + return this.section.pages.length * 40 + 'px'; } else { return '0px'; } } + toggleSection(event: MouseEvent) { + event.stopPropagation(); + this.section.opened = !this.section.opened; + this.store.dispatch(new ActionPreferencesUpdateOpenedMenuSection({path: this.section.path, opened: this.section.opened})); + } + trackBySectionPages(index: number, section: MenuSection){ return section.id; } diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.scss b/ui-ngx/src/app/modules/home/menu/side-menu.component.scss index 54cbf11c53..533c1e2fea 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.scss +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.scss @@ -63,6 +63,7 @@ white-space: nowrap; display: inline-flex; & > span { + letter-spacing: .03125em; overflow: hidden; text-overflow: ellipsis; } diff --git a/ui-ngx/src/app/modules/home/models/services.map.ts b/ui-ngx/src/app/modules/home/models/services.map.ts index 40655bfc33..d623a0abc3 100644 --- a/ui-ngx/src/app/modules/home/models/services.map.ts +++ b/ui-ngx/src/app/modules/home/models/services.map.ts @@ -42,6 +42,7 @@ import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentica import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { NotificationService } from '@core/http/notification.service'; import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { UserSettingsService } from '@core/http/user-settings.service'; export const ServicesMap = new Map>( [ @@ -71,6 +72,7 @@ export const ServicesMap = new Map>( ['resourceService', ResourceService], ['twoFactorAuthenticationService', TwoFactorAuthenticationService], ['telemetryWsService', TelemetryWebsocketService], + ['userSettingsService', UserSettingsService], ['notificationService', NotificationService] ] ); diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 09949a29b3..8ddb3d6218 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -90,6 +90,7 @@ import { EntityId } from '@shared/models/id/entity-id'; import { AlarmQuery, AlarmSearchStatus, AlarmStatus} from '@app/shared/models/alarm.models'; import { MillisecondsToTimeStringPipe, TelemetrySubscriber } from '@app/shared/public-api'; import { UserId } from '@shared/models/id/user-id'; +import { UserSettingsService } from '@core/http/user-settings.service'; export interface IWidgetAction { name: string; @@ -180,6 +181,7 @@ export class WidgetContext { dialogs: DialogService; customDialog: CustomDialogService; resourceService: ResourceService; + userSettingsService: UserSettingsService; telemetryWsService: TelemetryWebsocketService; telemetrySubscribers?: TelemetrySubscriber[]; date: DatePipe; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 56fa9ca307..7b3fb8664e 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -36,7 +36,9 @@ import { QueuesTableConfigResolver } from '@home/pages/admin/queue/queues-table- import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component'; import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component'; import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-auth-settings.component'; -import { NotificationSettingsComponent } from '@home/pages/admin/notification-settings.component'; +import { widgetsBundlesRoutes } from '@home/pages/widget/widget-library-routing.module'; +import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { auditLogsRoutes } from '@home/pages/audit-log/audit-log-routing.module'; @Injectable() export class OAuth2LoginProcessingUrlResolver implements Resolve { @@ -50,12 +52,72 @@ export class OAuth2LoginProcessingUrlResolver implements Resolve { } const routes: Routes = [ + { + path: 'resources', + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + breadcrumb: { + label: 'admin.resources', + icon: 'folder' + } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + redirectTo: '/resources/widgets-bundles' + } + }, + ...widgetsBundlesRoutes, + { + path: 'resources-library', + data: { + breadcrumb: { + label: 'resource.resources-library', + icon: 'mdi:rhombus-split' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'resource.resources-library', + }, + resolve: { + entitiesTableConfig: ResourcesLibraryTableConfigResolver + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'mdi:rhombus-split' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'resource.resources-library' + }, + resolve: { + entitiesTableConfig: ResourcesLibraryTableConfigResolver + } + } + ] + } + ] + }, { path: 'settings', + component: RouterTabsComponent, data: { auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], breadcrumb: { - label: 'admin.system-settings', + label: 'admin.settings', icon: 'settings' } }, @@ -98,111 +160,18 @@ const routes: Routes = [ } }, { - path: 'sms-provider', + path: 'notifications', component: SmsProviderComponent, canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN], - title: 'admin.sms-provider-settings', - breadcrumb: { - label: 'admin.sms-provider', - icon: 'sms' - } - } - }, - { - path: 'security-settings', - component: SecuritySettingsComponent, - canDeactivate: [ConfirmOnExitGuard], - data: { - auth: [Authority.SYS_ADMIN], - title: 'admin.security-settings', - breadcrumb: { - label: 'admin.security-settings', - icon: 'security' - } - } - }, - { - path: 'oauth2', - component: OAuth2SettingsComponent, - canDeactivate: [ConfirmOnExitGuard], - data: { - auth: [Authority.SYS_ADMIN], - title: 'admin.oauth2.oauth2', + title: 'admin.notifications-settings', breadcrumb: { - label: 'admin.oauth2.oauth2', - icon: 'security' + label: 'admin.notifications', + icon: 'mdi:message-badge' } - }, - resolve: { - loginProcessingUrl: OAuth2LoginProcessingUrlResolver } }, - { - path: 'home', - component: HomeSettingsComponent, - canDeactivate: [ConfirmOnExitGuard], - data: { - auth: [Authority.TENANT_ADMIN], - title: 'admin.home-settings', - breadcrumb: { - label: 'admin.home-settings', - icon: 'settings_applications' - } - } - }, - { - path: 'notification', - component: NotificationSettingsComponent, - canDeactivate: [ConfirmOnExitGuard], - data: { - auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], - title: 'admin.notification.notification-settings', - breadcrumb: { - label: 'admin.notification.notification-settings', - icon: 'notifications' - } - } - }, - { - path: 'resources-library', - data: { - breadcrumb: { - label: 'resource.resources-library', - icon: 'folder' - } - }, - children: [ - { - path: '', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], - title: 'resource.resources-library', - }, - resolve: { - entitiesTableConfig: ResourcesLibraryTableConfigResolver - } - }, - { - path: ':entityId', - component: EntityDetailsPageComponent, - canDeactivate: [ConfirmOnExitGuard], - data: { - breadcrumb: { - labelFunction: entityDetailsPageBreadcrumbLabelFunction, - icon: 'folder' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], - title: 'resource.resources-library' - }, - resolve: { - entitiesTableConfig: ResourcesLibraryTableConfigResolver - } - } - ] - }, { path: 'queues', data: { @@ -242,16 +211,15 @@ const routes: Routes = [ ] }, { - path: '2fa', - component: TwoFactorAuthSettingsComponent, + path: 'home', + component: HomeSettingsComponent, canDeactivate: [ConfirmOnExitGuard], data: { - auth: [Authority.SYS_ADMIN], - title: 'admin.2fa.2fa', + auth: [Authority.TENANT_ADMIN], + title: 'admin.home-settings', breadcrumb: { - label: 'admin.2fa.2fa', - icon: 'mdi:two-factor-authentication', - isMdiIcon: true + label: 'admin.home', + icon: 'settings_applications' } } }, @@ -263,7 +231,7 @@ const routes: Routes = [ auth: [Authority.TENANT_ADMIN], title: 'admin.repository-settings', breadcrumb: { - label: 'admin.repository-settings', + label: 'admin.repository', icon: 'manage_history' } } @@ -276,12 +244,104 @@ const routes: Routes = [ auth: [Authority.TENANT_ADMIN], title: 'admin.auto-commit-settings', breadcrumb: { - label: 'admin.auto-commit-settings', + label: 'admin.auto-commit', icon: 'settings_backup_restore' } } + }, + { + path: 'security-settings', + redirectTo: '/security-settings/general' + }, + { + path: 'oauth2', + redirectTo: '/security-settings/oauth2' + }, + { + path: 'resources-library', + pathMatch: 'full', + redirectTo: '/resources/resources-library' + }, + { + path: 'resources-library/:entityId', + redirectTo: '/resources/resources-library/:entityId' + }, + { + path: '2fa', + redirectTo: '/security-settings/2fa' + }, + { + path: 'sms-provider', + redirectTo: '/settings/notifications' } ] + }, + { + path: 'security-settings', + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + breadcrumb: { + label: 'security.security', + icon: 'security' + } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + redirectTo: { + SYS_ADMIN: '/security-settings/general', + TENANT_ADMIN: '/security-settings/auditLogs' + } + } + }, + { + path: 'general', + component: SecuritySettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.general', + breadcrumb: { + label: 'admin.general', + icon: 'settings_applications' + } + } + }, + { + path: '2fa', + component: TwoFactorAuthSettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.2fa.2fa', + breadcrumb: { + label: 'admin.2fa.2fa', + icon: 'mdi:two-factor-authentication', + isMdiIcon: true + } + } + }, + { + path: 'oauth2', + component: OAuth2SettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.oauth2.oauth2', + breadcrumb: { + label: 'admin.oauth2.oauth2', + icon: 'mdi:shield-account' + } + }, + resolve: { + loginProcessingUrl: OAuth2LoginProcessingUrlResolver + } + }, + ...auditLogsRoutes + ] } ]; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index aa2b8957f6..02a4bf5867 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -32,7 +32,6 @@ import { QueueComponent } from '@home/pages/admin/queue/queue.component'; import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component'; import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component'; import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-auth-settings.component'; -import { NotificationSettingsComponent } from '@home/pages/admin/notification-settings.component'; @NgModule({ declarations: @@ -48,8 +47,7 @@ import { NotificationSettingsComponent } from '@home/pages/admin/notification-se QueueComponent, RepositoryAdminSettingsComponent, AutoCommitAdminSettingsComponent, - TwoFactorAuthSettingsComponent, - NotificationSettingsComponent + TwoFactorAuthSettingsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss index 66df772d2d..4b2d51d1e9 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss @@ -14,5 +14,10 @@ * limitations under the License. */ :host { +} +:host ::ng-deep { + .mat-checkbox-layout { + white-space: normal; + } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/notification-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/notification-settings.component.html deleted file mode 100644 index f5fa148993..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/notification-settings.component.html +++ /dev/null @@ -1,49 +0,0 @@ - -
    - - - -
    - admin.notification.notification-settings -
    -
    -
    - - -
    - -
    -
    -
    - admin.notification.slack - - admin.notification.slack-api-token - - -
    -
    - -
    -
    -
    -
    -
    -
    diff --git a/ui-ngx/src/app/modules/home/pages/admin/notification-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/notification-settings.component.ts deleted file mode 100644 index 07f626f7f1..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/notification-settings.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -/// -/// 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 { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { PageComponent } from '@shared/components/page.component'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; -import { NotificationService } from '@core/http/notification.service'; -import { NotificationSettings } from '@shared/models/notification.models'; -import { deepTrim, isEmptyStr } from '@core/utils'; - -@Component({ - selector: 'tb-notification-settings', - templateUrl: './notification-settings.component.html', - styleUrls: ['./settings-card.scss'] -}) -export class NotificationSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { - - notificationSettingsForm: FormGroup; - private notificationSettings: NotificationSettings; - - constructor(protected store: Store, - private notificationService: NotificationService, - public fb: FormBuilder) { - super(store); - } - - ngOnInit() { - this.buildGeneralServerSettingsForm(); - this.notificationService.getNotificationSettings().subscribe( - (settings) => { - this.notificationSettings = settings; - this.notificationSettingsForm.reset(this.notificationSettings); - } - ); - } - - buildGeneralServerSettingsForm() { - this.notificationSettingsForm = this.fb.group({ - deliveryMethodsConfigs: this.fb.group({ - SLACK: this.fb.group({ - botToken: [''] - }) - }) - }); - } - - save(): void { - this.notificationSettings = deepTrim({ - ...this.notificationSettings, - ...this.notificationSettingsForm.value - }); - for (const method in this.notificationSettings.deliveryMethodsConfigs) { - const keys = Object.keys(this.notificationSettings.deliveryMethodsConfigs[method]); - if (keys.some(item => isEmptyStr(this.notificationSettings.deliveryMethodsConfigs[method][item]))) { - delete this.notificationSettings.deliveryMethodsConfigs[method]; - } else { - this.notificationSettings.deliveryMethodsConfigs[method].method = method; - } - } - this.notificationService.saveNotificationSettings(this.notificationSettings).subscribe(setting => { - this.notificationSettings = setting; - this.notificationSettingsForm.reset(this.notificationSettings); - }); - } - - confirmForm(): FormGroup { - return this.notificationSettingsForm; - } - -} diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index da85bde598..a488bd3807 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -123,7 +123,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve - admin.sms-provider-settings + admin.notifications-settings
    @@ -47,4 +47,34 @@ + + + + + + + + + + +
    + +
    +
    +
    + admin.slack + + admin.slack-api-token + + +
    +
    + +
    +
    +
    +
    +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts index 6d15487b6c..d8cbe0548d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts @@ -19,12 +19,15 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; import { Router } from '@angular/router'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AdminSettings, SmsProviderConfiguration } from '@shared/models/settings.models'; import { AdminService } from '@core/http/admin.service'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { MatDialog } from '@angular/material/dialog'; import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/admin/send-test-sms-dialog.component'; +import { NotificationSettings } from '@shared/models/notification.models'; +import { deepTrim, isEmptyStr } from '@core/utils'; +import { NotificationService } from '@core/http/notification.service'; @Component({ selector: 'tb-sms-provider', @@ -33,35 +36,46 @@ import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/a }) export class SmsProviderComponent extends PageComponent implements OnInit, HasConfirmForm { - smsProvider: UntypedFormGroup; + smsProvider: FormGroup; adminSettings: AdminSettings; + notificationSettingsForm: FormGroup; + private notificationSettings: NotificationSettings; + constructor(protected store: Store, private router: Router, private adminService: AdminService, + private notificationService: NotificationService, private dialog: MatDialog, - public fb: UntypedFormBuilder) { + public fb: FormBuilder) { super(store); } ngOnInit() { this.buildSmsProviderForm(); - this.adminService.getAdminSettings('sms', {ignoreErrors: true}).subscribe( - (adminSettings) => { + this.buildGeneralServerSettingsForm(); + this.notificationService.getNotificationSettings().subscribe( + (settings) => { + this.notificationSettings = settings; + this.notificationSettingsForm.reset(this.notificationSettings); + } + ); + this.adminService.getAdminSettings('sms', {ignoreErrors: true}).subscribe({ + next: adminSettings => { this.adminSettings = adminSettings; this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); }, - () => { + error: () => { this.adminSettings = { key: 'sms', jsonValue: null }; this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); } - ); + }); } - buildSmsProviderForm() { + private buildSmsProviderForm() { this.smsProvider = this.fb.group({ configuration: [null, [Validators.required]] }); @@ -88,8 +102,38 @@ export class SmsProviderComponent extends PageComponent implements OnInit, HasCo ); } - confirmForm(): UntypedFormGroup { - return this.smsProvider; + confirmForm(): FormGroup { + return this.smsProvider.dirty ? this.smsProvider : this.notificationSettingsForm; + } + + private buildGeneralServerSettingsForm() { + this.notificationSettingsForm = this.fb.group({ + deliveryMethodsConfigs: this.fb.group({ + SLACK: this.fb.group({ + botToken: [''] + }) + }) + }); + } + + saveNotification(): void { + this.notificationSettings = deepTrim({ + ...this.notificationSettings, + ...this.notificationSettingsForm.value + }); + // eslint-disable-next-line guard-for-in + for (const method in this.notificationSettings.deliveryMethodsConfigs) { + const keys = Object.keys(this.notificationSettings.deliveryMethodsConfigs[method]); + if (keys.some(item => isEmptyStr(this.notificationSettings.deliveryMethodsConfigs[method][item]))) { + delete this.notificationSettings.deliveryMethodsConfigs[method]; + } else { + this.notificationSettings.deliveryMethodsConfigs[method].method = method; + } + } + this.notificationService.saveNotificationSettings(this.notificationSettings).subscribe(setting => { + this.notificationSettings = setting; + this.notificationSettingsForm.reset(this.notificationSettings); + }); } } diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts b/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts new file mode 100644 index 0000000000..30a6cbb8fd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts @@ -0,0 +1,58 @@ +/// +/// 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 { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { Observable } from 'rxjs'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; +import { AlarmsMode } from '@shared/models/alarm.models'; + +@Injectable() +export class OAuth2LoginProcessingUrlResolver implements Resolve { + + constructor(private oauth2Service: OAuth2Service) { + } + + resolve(): Observable { + return this.oauth2Service.getLoginProcessingUrl(); + } +} + +const routes: Routes = [ + { + path: 'alarms', + component: AlarmTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'alarm.alarms', + breadcrumb: { + label: 'alarm.alarms', + icon: 'notifications' + }, + isPage: true, + alarmsMode: AlarmsMode.ALL + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [] +}) +export class AlarmRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts b/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts new file mode 100644 index 0000000000..cd6f257833 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts @@ -0,0 +1,34 @@ +/// +/// 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 { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; +import { AlarmRoutingModule } from '@home/pages/alarm/alarm-routing.module'; + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + HomeDialogsModule, + AlarmRoutingModule + ] +}) +export class AlarmModule { } diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts index b93125758a..1821acbf6c 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts @@ -25,7 +25,7 @@ import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; -const routes: Routes = [ +export const assetRoutes: Routes = [ { path: 'assets', data: { @@ -68,6 +68,18 @@ const routes: Routes = [ } ]; +const routes: Routes = [ + { + path: 'assets', + pathMatch: 'full', + redirectTo: '/entities/assets' + }, + { + path: 'assets/:entityId', + redirectTo: '/entities/assets/:entityId' + } +]; + @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts b/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts index 17e5b8c84f..557311181a 100644 --- a/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts @@ -19,7 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component'; -const routes: Routes = [ +export const auditLogsRoutes: Routes = [ { path: 'auditLogs', component: AuditLogTableComponent, @@ -35,8 +35,16 @@ const routes: Routes = [ } ]; +const routes: Routes = [ + { + path: 'auditLogs', + redirectTo: '/security-settings/auditLogs' + } +]; + @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + exports: [RouterModule], + providers: [] }) export class AuditLogRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts b/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts index ac29871576..220f9fd623 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts @@ -24,8 +24,10 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { assetRoutes } from '@home/pages/asset/asset-routing.module'; +import { entityViewRoutes } from '@home/pages/entity-view/entity-view-routing.module'; -const routes: Routes = [ +export const deviceRoutes: Routes = [ { path: 'devices', data: { @@ -68,6 +70,18 @@ const routes: Routes = [ } ]; +const routes: Routes = [ + { + path: 'devices', + pathMatch: 'full', + redirectTo: '/entities/devices' + }, + { + path: 'devices/:entityId', + redirectTo: '/entities/devices/:entityId' + } +]; + @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts index 44e0c9c00f..5dcdee3169 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts @@ -44,49 +44,28 @@ import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages const routes: Routes = [ { - path: 'edgeInstances', + path: 'edgeManagement', data: { breadcrumb: { - label: 'edge.edge-instances', - icon: 'router' + label: 'edge.management', + icon: 'settings_input_antenna' } }, children: [ { path: '', - component: EntitiesTableComponent, + children: [], data: { auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.edge-instances', - edgesType: 'tenant' - }, - resolve: { - entitiesTableConfig: EdgesTableConfigResolver + redirectTo: '/edgeManagement/instances' } }, { - path: ':entityId', - component: EntityDetailsPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: 'instances', data: { breadcrumb: { - labelFunction: entityDetailsPageBreadcrumbLabelFunction, + label: 'edge.instances', icon: 'router' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.edge-instances', - edgesType: 'tenant' - }, - resolve: { - entitiesTableConfig: EdgesTableConfigResolver - } - }, - { - path: ':edgeId/assets', - data: { - breadcrumb: { - label: 'edge.assets', - icon: 'domain' } }, children: [ @@ -95,11 +74,11 @@ const routes: Routes = [ component: EntitiesTableComponent, data: { auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.assets', - assetsType: 'edge' + title: 'edge.edge-instances', + edgesType: 'tenant' }, resolve: { - entitiesTableConfig: AssetsTableConfigResolver + entitiesTableConfig: EdgesTableConfigResolver } }, { @@ -109,198 +88,219 @@ const routes: Routes = [ data: { breadcrumb: { labelFunction: entityDetailsPageBreadcrumbLabelFunction, - icon: 'domain' + icon: 'router' } as BreadCrumbConfig, auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.assets', - assetsType: 'edge' + title: 'edge.edge-instances', + edgesType: 'tenant' }, resolve: { - entitiesTableConfig: AssetsTableConfigResolver - } - } - ] - }, - { - path: ':edgeId/devices', - data: { - breadcrumb: { - label: 'edge.devices', - icon: 'devices_other' - } - }, - children: [ - { - path: '', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.devices', - devicesType: 'edge' - }, - resolve: { - entitiesTableConfig: DevicesTableConfigResolver + entitiesTableConfig: EdgesTableConfigResolver } }, { - path: ':entityId', - component: EntityDetailsPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: ':edgeId/assets', data: { breadcrumb: { - labelFunction: entityDetailsPageBreadcrumbLabelFunction, - icon: 'devices_other' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.devices', - devicesType: 'edge' - }, - resolve: { - entitiesTableConfig: DevicesTableConfigResolver - } - } - ] - }, - { - path: ':edgeId/entityViews', - data: { - breadcrumb: { - label: 'edge.entity-views', - icon: 'view_quilt' - }, - }, - children: [ - { - path: '', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.entity-views', - entityViewsType: 'edge' + label: 'edge.assets', + icon: 'domain' + } }, - resolve: { - entitiesTableConfig: EntityViewsTableConfigResolver - } + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.assets', + assetsType: 'edge' + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'domain' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.assets', + assetsType: 'edge' + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } + } + ] }, { - path: ':entityId', - component: EntityDetailsPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: ':edgeId/devices', data: { breadcrumb: { - labelFunction: entityDetailsPageBreadcrumbLabelFunction, + label: 'edge.devices', icon: 'devices_other' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.entity-views', - entityViewsType: 'edge' + } }, - resolve: { - entitiesTableConfig: EntityViewsTableConfigResolver - } - } - ] - }, - { - path: ':edgeId/dashboards', - data: { - breadcrumb: { - label: 'edge.dashboards', - icon: 'dashboard' - } - }, - children: [ + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.devices', + devicesType: 'edge' + }, + resolve: { + entitiesTableConfig: DevicesTableConfigResolver + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'devices_other' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.devices', + devicesType: 'edge' + }, + resolve: { + entitiesTableConfig: DevicesTableConfigResolver + } + } + ] + }, { - path: '', - component: EntitiesTableComponent, + path: ':edgeId/entityViews', data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - dashboardsType: 'edge' - }, - resolve: { - entitiesTableConfig: DashboardsTableConfigResolver + breadcrumb: { + label: 'edge.entity-views', + icon: 'view_quilt' + }, }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.entity-views', + entityViewsType: 'edge' + }, + resolve: { + entitiesTableConfig: EntityViewsTableConfigResolver + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'devices_other' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.entity-views', + entityViewsType: 'edge' + }, + resolve: { + entitiesTableConfig: EntityViewsTableConfigResolver + } + } + ] }, { - path: ':dashboardId', - component: DashboardPageComponent, + path: ':edgeId/dashboards', data: { breadcrumb: { - labelFunction: dashboardBreadcumbLabelFunction, + label: 'edge.dashboards', icon: 'dashboard' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'edge.dashboard', - widgetEditMode: false + } }, - resolve: { - dashboard: DashboardResolver - } - } - ] - }, - { - path: ':edgeId/ruleChains', - data: { - breadcrumb: { - label: 'edge.edge-rulechains', - icon: 'settings_ethernet' - } - }, - children: [ - { - path: '', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN], - title: 'edge.rulechains', - ruleChainsType: 'edge' - }, - resolve: { - entitiesTableConfig: RuleChainsTableConfigResolver - } + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + dashboardsType: 'edge' + }, + resolve: { + entitiesTableConfig: DashboardsTableConfigResolver + }, + }, + { + path: ':dashboardId', + component: DashboardPageComponent, + data: { + breadcrumb: { + labelFunction: dashboardBreadcumbLabelFunction, + icon: 'dashboard' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'edge.dashboard', + widgetEditMode: false + }, + resolve: { + dashboard: DashboardResolver + } + } + ] }, { - path: ':ruleChainId', - component: RuleChainPageComponent, - canDeactivate: [ConfirmOnExitGuard], + path: ':edgeId/ruleChains', data: { breadcrumb: { - labelFunction: ruleChainBreadcumbLabelFunction, + label: 'edge.edge-rulechains', icon: 'settings_ethernet' - } as BreadCrumbConfig, - auth: [Authority.TENANT_ADMIN], - title: 'rulechain.edge-rulechain', - import: false, - ruleChainType: RuleChainType.EDGE + } }, - resolve: { - ruleChain: RuleChainResolver, - ruleChainMetaData: RuleChainMetaDataResolver, - ruleNodeComponents: RuleNodeComponentsResolver, - tooltipster: TooltipsterResolver - } + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'edge.rulechains', + ruleChainsType: 'edge' + }, + resolve: { + entitiesTableConfig: RuleChainsTableConfigResolver + } + }, + { + path: ':ruleChainId', + component: RuleChainPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: ruleChainBreadcumbLabelFunction, + icon: 'settings_ethernet' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN], + title: 'rulechain.edge-rulechain', + import: false, + ruleChainType: RuleChainType.EDGE + }, + resolve: { + ruleChain: RuleChainResolver, + ruleChainMetaData: RuleChainMetaDataResolver, + ruleNodeComponents: RuleNodeComponentsResolver, + tooltipster: TooltipsterResolver + } + } + ] } ] - } - ] - }, - { - path: 'edgeManagement', - data: { - breadcrumb: { - label: 'edge.management', - icon: 'settings_input_antenna' - } - }, - children: [ - { - path: '', - children: [], - data: { - auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - redirectTo: '/edgeManagement/ruleChains' - } }, { path: 'ruleChains', @@ -367,7 +367,63 @@ const routes: Routes = [ ] } ] - }]; + }, + { + path: 'edgeInstances', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances' + }, + { + path: 'edgeInstances/:entityId', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:entityId' + }, + { + path: 'edgeInstances/:edgeId/assets', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/assets' + }, + { + path: 'edgeInstances/:edgeId/assets/:entityId', + redirectTo: '/edgeManagement/instances/:edgeId/assets/:entityId' + }, + { + path: 'edgeInstances/:edgeId/devices', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/devices' + }, + { + path: 'edgeInstances/:edgeId/devices/:entityId', + redirectTo: '/edgeManagement/instances/:edgeId/devices/:entityId' + }, + { + path: 'edgeInstances/:edgeId/entityViews', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/entityViews' + }, + { + path: 'edgeInstances/:edgeId/entityViews/:entityId', + redirectTo: '/edgeManagement/instances/:edgeId/entityViews/:entityId' + }, + { + path: 'edgeInstances/:edgeId/dashboards', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/dashboards' + }, + { + path: 'edgeInstances/:edgeId/dashboards/:dashboardId', + redirectTo: '/edgeManagement/instances/:edgeId/dashboards/:dashboardId' + }, + { + path: 'edgeInstances/:edgeId/ruleChains', + pathMatch: 'full', + redirectTo: '/edgeManagement/instances/:edgeId/ruleChains' + }, + { + path: 'edgeInstances/:edgeId/ruleChains/:ruleChainId', + redirectTo: '/edgeManagement/instances/:edgeId/ruleChains/:ruleChainId' + } +]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts index fa44be34c3..e73bae7c7a 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts @@ -429,7 +429,7 @@ export class EdgesTableConfigResolver implements Resolve) { diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center-routing.module.ts b/ui-ngx/src/app/modules/home/pages/entities/entities-routing.module.ts similarity index 58% rename from ui-ngx/src/app/modules/home/pages/notification-center/notification-center-routing.module.ts rename to ui-ngx/src/app/modules/home/pages/entities/entities-routing.module.ts index 1a99fa6608..c071a294b1 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/entities/entities-routing.module.ts @@ -14,23 +14,35 @@ /// 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'; +import { NgModule } from '@angular/core'; +import { deviceRoutes } from '@home/pages/device/device-routing.module'; +import { assetRoutes } from '@home/pages/asset/asset-routing.module'; +import { entityViewRoutes } from '@home/pages/entity-view/entity-view-routing.module'; const routes: Routes = [ { - path: 'notification-center', - component: NotificationCenterComponent, + path: 'entities', data: { - auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - title: 'notification.notification-center', + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], breadcrumb: { - label: 'notification.notification-center', - icon: 'notifications' + skip: true } - } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + redirectTo: '/entities/devices' + } + }, + ...deviceRoutes, + ...assetRoutes, + ...entityViewRoutes + ] } ]; @@ -38,4 +50,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class NotificationCenterRoutingModule { } +export class EntitiesRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/entities/entities.module.ts b/ui-ngx/src/app/modules/home/pages/entities/entities.module.ts new file mode 100644 index 0000000000..8aa4bd21a0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entities/entities.module.ts @@ -0,0 +1,30 @@ +/// +/// 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 { EntitiesRoutingModule } from '@home/pages/entities/entities-routing.module'; + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + SharedModule, + EntitiesRoutingModule + ] +}) +export class EntitiesModule { } diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts index a083e8fb4b..98a1bc3379 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts @@ -25,7 +25,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; import { BreadCrumbConfig } from '@shared/components/breadcrumb'; -const routes: Routes = [ +export const entityViewRoutes: Routes = [ { path: 'entityViews', data: { @@ -68,6 +68,18 @@ const routes: Routes = [ } ]; +const routes: Routes = [ + { + path: 'entityViews', + pathMatch: 'full', + redirectTo: '/entities/entityViews' + }, + { + path: 'entityViews/:entityId', + redirectTo: '/entities/entityViews/:entityId' + } +]; + @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts b/ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts new file mode 100644 index 0000000000..4448f8a559 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts @@ -0,0 +1,54 @@ +/// +/// 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 { RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { NgModule } from '@angular/core'; +import { ruleChainsRoutes } from '@home/pages/rulechain/rulechain-routing.module'; +import { otaUpdatesRoutes } from '@home/pages/ota-update/ota-update-routing.module'; +import { vcRoutes } from '@home/pages/vc/vc-routing.module'; + +const routes: Routes = [ + { + path: 'features', + data: { + auth: [Authority.TENANT_ADMIN], + breadcrumb: { + label: 'feature.advanced-features', + icon: 'construction' + } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.TENANT_ADMIN], + redirectTo: '/features/ruleChains' + } + }, + ...ruleChainsRoutes, + ...otaUpdatesRoutes, + ...vcRoutes + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class FeaturesRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/features/features.module.ts b/ui-ngx/src/app/modules/home/pages/features/features.module.ts new file mode 100644 index 0000000000..aa3a3d9174 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/features/features.module.ts @@ -0,0 +1,30 @@ +/// +/// 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 { FeaturesRoutingModule } from '@home/pages/features/features-routing.module'; + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + SharedModule, + FeaturesRoutingModule + ] +}) +export class FeaturesModule { } diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts index 2d43c56a49..cd6190bb00 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts @@ -19,6 +19,6 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai export const entityDetailsPageBreadcrumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { - return component.entity?.name || component.headerSubtitle; + return component.entity?.name; }); diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index 311442899d..b1f4715c33 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -38,7 +38,10 @@ 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'; +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'; @NgModule({ exports: [ @@ -51,8 +54,12 @@ import { NotificationCenterModule } from '@home/pages/notification-center/notifi DeviceProfileModule, AssetProfileModule, ProfilesModule, + EntitiesModule, + FeaturesModule, + NotificationModule, DeviceModule, AssetModule, + AlarmModule, EdgeModule, EntityViewModule, CustomerModule, @@ -63,7 +70,6 @@ import { NotificationCenterModule } from '@home/pages/notification-center/notifi ApiUsageModule, OtaUpdateModule, UserModule, - NotificationCenterModule, VcModule ] }) diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html deleted file mode 100644 index b67eec3ff9..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html +++ /dev/null @@ -1,69 +0,0 @@ - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-
-
-
-
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss deleted file mode 100644 index b8971abfa4..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 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 { - 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; - - .tabs-container { - position: relative; - flex-direction: column; - flex: 1 1 100%; - } - } - } -} - -:host ::ng-deep { - .tabs-container { - > .mat-mdc-tab-group { - > .mat-mdc-tab-body-wrapper { - position: absolute; - top: 49px; - left: 0; - right: 0; - bottom: 0; - } - - > .mat-mdc-tab-header { - .mat-mdc-tab { - &.mat-mdc-tab-disabled { - opacity: 1; - flex: 1; - - .mdc-tab__content, .mdc-tab__text-label { - flex: 1; - } - } - } - } - } - - .tb-entity-table { - padding: 0; - - .mat-toolbar-single-row, .mat-toolbar-tools { - height: 56px; - } - - .mat-toolbar-tools > div { - width: 100%; - } - - .mat-toolbar-single-row { - padding: 0 24px 0 12px; - } - } - } - - @media #{$mat-xs} { - .tabs-container { - > .mat-mdc-tab-group { - > .mat-mdc-tab-header { - .mat-mdc-tab { - &.mat-tab-disabled { - width: auto; - } - } - } - } - } - } -} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts deleted file mode 100644 index 545ba01a98..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -/// -/// 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, 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 { MatTab, 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'; -import { Authority } from '@shared/models/authority.enum'; -import { AuthState } from '@core/auth/auth.models'; -import { getCurrentAuthState } from '@core/auth/auth.selectors'; -import { AuthUser } from '@shared/models/user.model'; - -@Component({ - selector: 'tb-notification-center', - templateUrl: './notification-center.component.html', - styleUrls: ['notification-center.component.scss'] -}) -export class NotificationCenterComponent extends PageComponent { - - private authState: AuthState = getCurrentAuthState(this.store); - private authUser: AuthUser = this.authState.authUser; - - entityType = EntityType; - - @ViewChild('matTabGroup') matTabs: MatTabGroup; - @ViewChild('requestTab') requestTab: MatTab; - @ViewChild('notificationRequest') notificationRequestTable: NotificationTableComponent; - @ViewChildren(NotificationTableComponent) tableComponent: QueryList; - - - constructor( - protected store: Store) { - super(store); - } - - updateData() { - this.currentTableComponent?.updateData(); - } - - openSetting() { - - } - - private get currentTableComponent(): NotificationTableComponent { - return this.tableComponent.get(this.matTabs.selectedIndex); - } - - sendNotification($event: Event) { - this.notificationRequestTable.entityTableConfig.onEntityAction({event: $event, action: this.requestTab.isActive ? 'add' : 'add-without-update', entity: null}); - } - - public isTenantAdmin(): boolean { - return this.authUser.authority === Authority.TENANT_ADMIN; - } - - public isCustomerUser(): boolean { - return this.authUser.authority === Authority.CUSTOMER_USER; - } -} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts deleted file mode 100644 index 383387b647..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-center.module.ts +++ /dev/null @@ -1,83 +0,0 @@ -/// -/// 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 { 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'; -import { - TemplateNotificationDialogComponent -} from '@home/pages/notification-center/template-table/template-notification-dialog.component'; -import { - TargetTableHeaderComponent -} from '@home/pages/notification-center/targets-table/target-table-header.component'; -import { - TemplateTableHeaderComponent -} from '@home/pages/notification-center/template-table/template-table-header.component'; -import { - RequestNotificationDialogComponent -} from '@home/pages/notification-center/request-table/request-notification-dialog.componet'; -import { - TemplateAutocompleteComponent -} from '@home/pages/notification-center/template-table/template-autocomplete.component'; -import { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox-table/inbox-table-header.component'; -import { RuleTableHeaderComponent } from '@home/pages/notification-center/rule-table/rule-table-header.component'; -import { - RuleNotificationDialogComponent -} from '@home/pages/notification-center/rule-table/rule-notification-dialog.component'; -import { EscalationsComponent } from '@home/pages/notification-center/rule-table/escalations.component'; -import { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component'; -import { - InboxNotificationDialogComponent -} from '@home/pages/notification-center/inbox-table/inbox-notification-dialog.component'; -import { - NotificationRequestErrorDialogComponent -} from '@home/pages/notification-center/request-table/notification-request-error-dialog.component'; - -@NgModule({ - declarations: [ - NotificationCenterComponent, - TargetNotificationDialogComponent, - TemplateNotificationDialogComponent, - RequestNotificationDialogComponent, - TargetTableHeaderComponent, - TemplateTableHeaderComponent, - NotificationTableComponent, - TemplateAutocompleteComponent, - InboxTableHeaderComponent, - RuleTableHeaderComponent, - RuleNotificationDialogComponent, - EscalationsComponent, - EscalationFormComponent, - InboxNotificationDialogComponent, - NotificationRequestErrorDialogComponent - ], - imports: [ - CommonModule, - SharedModule, - NotificationCenterRoutingModule, - HomeComponentsModule - ] -}) -export class NotificationCenterModule { } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts deleted file mode 100644 index a84c9a32c0..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts +++ /dev/null @@ -1,98 +0,0 @@ -/// -/// 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, 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 { RequestTableConfig } from '@home/pages/notification-center/notification-table/request-table-config'; -import { RuleTableConfig } from '@home/pages/notification-center/notification-table/rule-table-config'; - -@Component({ - selector: 'tb-notification-table', - templateUrl: './notification-table.component.html' -}) -export class NotificationTableComponent implements OnInit { - - @Input() - notificationType = EntityType.NOTIFICATION; - - @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; - - entityTableConfig: EntityTableConfig; - - constructor(private notificationService: NotificationService, - private translate: TranslateService, - private datePipe: DatePipe, - private dialog: MatDialog) { - } - - ngOnInit() { - this.entityTableConfig = this.getTableConfig(); - this.entityTableConfig.pageMode = false; - this.entityTableConfig.detailsPanelEnabled = false; - this.entityTableConfig.selectionEnabled = false; - this.entityTableConfig.addEnabled = false; - } - - updateData() { - this.entitiesTable.updateData(); - } - - private getTableConfig(): EntityTableConfig { - switch (this.notificationType) { - case EntityType.NOTIFICATION_TARGET: - return new TargetsTableConfig( - this.notificationService, - this.translate, - this.dialog - ); - case EntityType.NOTIFICATION: - return new InboxTableConfig( - this.notificationService, - this.translate, - this.dialog, - this.datePipe - ); - case EntityType.NOTIFICATION_TEMPLATE: - return new TemplateTableConfig( - this.notificationService, - this.translate, - this.dialog - ); - case EntityType.NOTIFICATION_REQUEST: - return new RequestTableConfig( - this.notificationService, - this.translate, - this.dialog, - this.datePipe - ); - case EntityType.NOTIFICATION_RULE: - return new RuleTableConfig( - this.notificationService, - this.translate, - this.dialog - ); - } - } -} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts b/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts deleted file mode 100644 index d9a6271b80..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts +++ /dev/null @@ -1,113 +0,0 @@ -/// -/// 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 { - 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, NotificationTargetTypeTranslationMap } 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'; -import { - TargetTableHeaderComponent -} from '@home/pages/notification-center/targets-table/target-table-header.component'; - -export class TargetsTableConfig extends EntityTableConfig { - - constructor(private notificationService: NotificationService, - private translate: TranslateService, - private dialog: MatDialog) { - super(); - this.loadDataOnInit = false; - this.rowPointer = true; - - this.entityTranslations = { - noEntities: 'notification.no-targets-notification', - search: 'notification.search-targets' - }; - this.entityResources = {} as EntityTypeResource; - - this.entitiesFetchFunction = pageLink => this.notificationService.getNotificationTargets(pageLink); - - this.deleteEntityTitle = target => this.translate.instant('notification.delete-target-title', {targetName: target.name}); - this.deleteEntityContent = () => this.translate.instant('notification.delete-target-text'); - this.deleteEntity = id => this.notificationService.deleteNotificationTarget(id.id); - - this.cellActionDescriptors = this.configureCellActions(); - - this.headerComponent = TargetTableHeaderComponent; - this.onEntityAction = action => this.onTargetAction(action); - - this.defaultSortOrder = {property: 'name', direction: Direction.ASC}; - - this.handleRowClick = ($event, target) => { - this.editTarget($event, target); - return true; - } - - this.columns.push( - new EntityTableColumn('name', 'notification.notification-target', '20%'), - new EntityTableColumn('configuration.type', 'notification.type', '20%', - (target) => this.translate.instant(NotificationTargetTypeTranslationMap.get(target.configuration.type)), - () => ({}), false), - new EntityTableColumn('configuration.description', 'notification.description', '60%', - (target) => target.configuration.description || '', - () => ({}), false) - ); - } - - private configureCellActions(): Array> { - return []; - } - - private editTarget($event: Event, target: NotificationTarget, isAdd = false) { - if ($event) { - $event.stopPropagation(); - } - this.dialog.open(TargetNotificationDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - isAdd, - target - } - }).afterClosed() - .subscribe((res) => { - if (res) { - this.updateData(); - } - }); - } - - private onTargetAction(action: EntityAction): boolean { - switch (action.action) { - case 'add': - this.editTarget(action.event, action.entity, true); - return true; - } - return false; - } -} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.scss b/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.scss deleted file mode 100644 index 4f6d9f0af3..0000000000 --- a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.scss +++ /dev/null @@ -1,59 +0,0 @@ -/** - * 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"; -@import "../../../../../../theme"; - -:host { - form.tb-dialog-container { - min-width: 600px; - max-height: 100vh; - } - - .mat-dialog-content { - height: 85%; - } - - .tb-title { - display: block; - padding-bottom: 6px; - } -} - -:host ::ng-deep { - .mat-mdc-radio-group { - display: flex; - flex-direction: row; - margin-bottom: 22px; - gap: 12px; - - .mat-mdc-radio-button { - flex: 1 1 100%; - padding: 4px; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 6px; - - &.mat-mdc-radio-checked { - border-color: map_get($tb-accent, 500); - } - } - } - - @media #{$mat-xs} { - .mat-mdc-radio-group { - flex-direction: column; - } - } -} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.html rename to ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.html diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.scss diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.ts similarity index 98% rename from ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.ts rename to ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.ts index cadfed0991..275caa6647 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-notification-dialog.component.ts @@ -45,6 +45,6 @@ export class InboxNotificationDialogComponent extends DialogComponent { +@Injectable() +export class InboxTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); constructor(private notificationService: NotificationService, private translate: TranslateService, private dialog: MatDialog, private datePipe: DatePipe) { - super(); - this.entitiesDeleteEnabled = false; - this.rowPointer = true; - this.entityTranslations = { - noEntities: 'notification.no-inbox-notification', - search: 'notification.search-notification' - }; - this.entityResources = {} as EntityTypeResource; - this.entitiesFetchFunction = pageLink => this.notificationService.getNotifications(pageLink, this.componentsData.unreadOnly); + this.config.entityType = EntityType.NOTIFICATION; + this.config.detailsPanelEnabled = false; + this.config.addEnabled = false; + this.config.rowPointer = true; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.NOTIFICATION); + this.config.entityResources = {} as EntityTypeResource; + + this.config.deleteEntityTitle = () => this.translate.instant('notification.delete-notification-title'); + this.config.deleteEntityContent = () => this.translate.instant('notification.delete-notification-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('notification.delete-notifications-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('notification.delete-notifications-text'); + + this.config.deleteEntity = id => this.notificationService.deleteNotification(id.id); + this.config.entitiesFetchFunction = pageLink => + this.notificationService.getNotifications(pageLink, this.config.componentsData.unreadOnly); - this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; - this.handleRowClick = ($event, notification) => { - this.showNotification($event, notification) + this.config.handleRowClick = ($event, notification) => { + this.showNotification($event, notification); return true; - } + }; - this.componentsData = { + this.config.componentsData = { unreadOnly: true }; - this.cellActionDescriptors = this.configureCellActions(); + this.config.cellActionDescriptors = this.configureCellActions(); - this.headerComponent = InboxTableHeaderComponent; + this.config.headerComponent = InboxTableHeaderComponent; - this.headerActionDescriptors = [{ + this.config.headerActionDescriptors = [{ name: this.translate.instant('notification.mark-all-as-read'), icon: 'done_all', isEnabled: () => true, onAction: $event => this.markAllRead($event) }]; - this.columns.push( - new DateEntityTableColumn('createdTime', 'notification.created-time', this.datePipe, '170px'), + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), new EntityTableColumn('type', 'notification.type', '10%', (notification) => this.translate.instant(NotificationTemplateTypeTranslateMap.get(notification.type).name)), new EntityTableColumn('subject', 'notification.subject', '30%'), @@ -86,6 +98,10 @@ export class InboxTableConfig extends EntityTableConfig { } + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + private configureCellActions(): Array> { return [{ name: this.translate.instant('notification.mark-as-read'), @@ -100,10 +116,10 @@ export class InboxTableConfig extends EntityTableConfig { $event.stopPropagation(); } this.notificationService.markAllNotificationsAsRead().subscribe(() => { - if (this.componentsData.unreadOnly) { - this.getTable().resetSortAndFilter(true); + if (this.config.componentsData.unreadOnly) { + this.config.getTable().resetSortAndFilter(true); } else { - this.updateData(); + this.config.updateData(); } }); } @@ -113,18 +129,18 @@ export class InboxTableConfig extends EntityTableConfig { $event.stopPropagation(); } this.notificationService.markNotificationAsRead(entity.id.id).subscribe(() => { - if (this.componentsData.unreadOnly) { - this.getTable().dataSource.pageData$.pipe(take(1)).subscribe( + if (this.config.componentsData.unreadOnly) { + this.config.getTable().dataSource.pageData$.pipe(take(1)).subscribe( (value) => { - if (value.data.length === 1 && this.getTable().pageLink.page) { - this.getTable().pageLink.page--; + if (value.data.length === 1 && this.config.getTable().pageLink.page) { + this.config.getTable().pageLink.page--; } - this.updateData(); + this.config.updateData(); } ); } else { entity.status = NotificationStatus.READ; - this.getTable().detectChanges(); + this.config.getTable().detectChanges(); } }); } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-table-header.component.html b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-table-header.component.html rename to ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.html diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-table-header.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.scss diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/pages/notification-center/inbox-table/inbox-table-header.component.ts rename to ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-header.component.ts diff --git a/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts b/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts new file mode 100644 index 0000000000..791a1d97c4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts @@ -0,0 +1,138 @@ +/// +/// 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 { RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { NgModule } from '@angular/core'; +import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { InboxTableConfigResolver } from '@home/pages/notification/inbox/inbox-table-config.resolver'; +import { SentTableConfigResolver } from '@home/pages/notification/sent/sent-table-config.resolver'; +import { RecipientTableConfigResolver } from '@home/pages/notification/recipient/recipient-table-config.resolver'; +import { TemplateTableConfigResolver } from '@home/pages/notification/template/template-table-config.resolver'; +import { RuleTableConfigResolver } from '@home/pages/notification/rule/rule-table-config.resolver'; + +const routes: Routes = [ + { + path: 'notification', + component: RouterTabsComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], + breadcrumb: { + label: 'notification.notification-center', + icon: 'mdi:message-badge' + } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], + redirectTo: '/notification/inbox' + } + }, + { + path: 'inbox', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], + title: 'notification.inbox', + breadcrumb: { + label: 'notification.inbox', + icon: 'inbox' + } + }, + resolve: { + entitiesTableConfig: InboxTableConfigResolver + } + }, + { + path: 'sent', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.sent', + breadcrumb: { + label: 'notification.sent', + icon: 'outbox' + } + }, + resolve: { + entitiesTableConfig: SentTableConfigResolver + } + }, + { + path: 'templates', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.templates', + breadcrumb: { + label: 'notification.templates', + icon: 'mdi:message-draw' + } + }, + resolve: { + entitiesTableConfig: TemplateTableConfigResolver + } + }, + { + path: 'recipients', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.recipients', + breadcrumb: { + label: 'notification.recipients', + icon: 'contacts' + }, + }, + resolve: { + entitiesTableConfig: RecipientTableConfigResolver + } + }, + { + path: 'rules', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'notification.rules', + breadcrumb: { + label: 'notification.rules', + icon: 'mdi:message-cog' + } + }, + resolve: { + entitiesTableConfig: RuleTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + providers: [ + InboxTableConfigResolver, + SentTableConfigResolver, + RecipientTableConfigResolver, + TemplateTableConfigResolver, + RuleTableConfigResolver + ], + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class NotificationRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/notification/notification.module.ts b/ui-ngx/src/app/modules/home/pages/notification/notification.module.ts new file mode 100644 index 0000000000..48751e08ba --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/notification.module.ts @@ -0,0 +1,65 @@ +/// +/// 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 { NotificationRoutingModule } from '@home/pages/notification/notification-routing.module'; +import { InboxTableHeaderComponent } from '@home/pages/notification/inbox/inbox-table-header.component'; +import { InboxNotificationDialogComponent } from '@home/pages/notification/inbox/inbox-notification-dialog.component'; +import { HomeComponentsModule } from '@home/components/home-components.module'; +import { SentErrorDialogComponent } from '@home/pages/notification/sent/sent-error-dialog.component'; +import { SentNotificationDialogComponent } from '@home/pages/notification/sent/sent-notification-dialog.componet'; +import { + RecipientNotificationDialogComponent +} from '@home/pages/notification/recipient/recipient-notification-dialog.componet'; +import { RecipientTableHeaderComponent } from '@home/pages/notification/recipient/recipient-table-header.component'; +import { TemplateAutocompleteComponent } from '@home/pages/notification/template/template-autocomplete.component'; +import { + TemplateNotificationDialogComponent +} from '@home/pages/notification/template/template-notification-dialog.component'; +import { TemplateTableHeaderComponent } from '@home/pages/notification/template/template-table-header.component'; +import { EscalationFormComponent } from '@home/pages/notification/rule/escalation-form.component'; +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 { SentTableHeaderComponent } from '@home/pages/notification/sent/sent-table-header.component'; + +@NgModule({ + declarations: [ + InboxTableHeaderComponent, + InboxNotificationDialogComponent, + SentErrorDialogComponent, + SentNotificationDialogComponent, + RecipientNotificationDialogComponent, + RecipientTableHeaderComponent, + TemplateAutocompleteComponent, + TemplateNotificationDialogComponent, + TemplateTableHeaderComponent, + EscalationFormComponent, + EscalationsComponent, + RuleNotificationDialogComponent, + RuleTableHeaderComponent, + SentTableHeaderComponent + ], + imports: [ + CommonModule, + SharedModule, + NotificationRoutingModule, + HomeComponentsModule + ] +}) +export class NotificationModule { } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html similarity index 63% rename from ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html rename to ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html index 4c3583eeaf..9eecacf268 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html @@ -17,7 +17,7 @@ -->
-

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

+

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

- notification.target-name + notification.name - {{ 'notification.target-name-required' | translate }} + {{ 'notification.name-required' | translate }}
@@ -47,15 +47,48 @@
- - notification.target-type.user-filters + + notification.recipient-type.user-filters - {{ notificationTargetConfigTypeTranslateMap.get(type) | translate }} + {{ notificationTargetConfigTypeInfoMap.get(type).name | translate }} + {{ notificationTargetConfigTypeInfoMap.get(targetNotificationForm.get('configuration.usersFilter.type').value).hint | translate }} + +
+
+ + {{ 'tenant.tenant' | translate }} + {{ 'tenant-profile.tenant-profile' | translate }} + +
+ + + + + + + + +
+
- + {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.componet.scss b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.componet.scss new file mode 100644 index 0000000000..8406cae3fe --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.componet.scss @@ -0,0 +1,109 @@ +/** + * 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"; +@import "../../../../../../theme"; + +:host { + form.tb-dialog-container { + min-width: 600px; + max-height: 100vh; + } + + .mat-dialog-content { + height: 85%; + } + + .tb-title { + display: block; + padding-bottom: 6px; + } +} + +:host ::ng-deep { + .mat-mdc-radio-group { + display: flex; + flex-direction: row; + margin-bottom: 22px; + gap: 12px; + + .mat-mdc-radio-button { + flex: 1 1 100%; + padding: 4px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; + } + } + + @media #{$mat-xs} { + .mat-mdc-radio-group { + flex-direction: column; + } + } + + .mat-button-toggle-group.tb-notification-tenant-group { + &.mat-button-toggle-group-appearance-standard { + border: none; + border-radius: 18px; + margin-bottom: 14px; + + .mat-button-toggle + .mat-button-toggle { + border-left: none; + } + } + + .mat-button-toggle { + background: rgba(0, 0, 0, 0.06); + height: 36px; + align-items: center; + display: flex; + + .mat-button-toggle-ripple { + top: 2px; + left: 2px; + right: 2px; + bottom: 2px; + border-radius: 18px; + } + } + + .mat-button-toggle-button { + color: #959595; + } + + .mat-button-toggle-focus-overlay { + border-radius: 18px; + margin: 2px; + } + + .mat-button-toggle-checked .mat-button-toggle-button { + background-color: $tb-primary-color; + color: #fff; + border-radius: 18px; + margin-left: 2px; + margin-right: 2px; + } + + .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { + line-height: 34px; + font-size: 16px; + font-weight: 500; + } + + .mat-button-toggle-checked.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { + opacity: .01; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.componet.ts similarity index 61% rename from ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts rename to ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.componet.ts index c54fcfa2a6..20e75ea195 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.componet.ts @@ -17,7 +17,7 @@ import { NotificationTarget, NotificationTargetConfigType, - NotificationTargetConfigTypeTranslateMap, + NotificationTargetConfigTypeInfoMap, NotificationTargetType, NotificationTargetTypeTranslationMap, SlackChanelType, @@ -32,7 +32,7 @@ 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 { deepTrim, isDefinedAndNotNull } from '@core/utils'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { Authority } from '@shared/models/authority.enum'; @@ -40,18 +40,18 @@ import { AuthState } from '@core/auth/auth.models'; import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { AuthUser } from '@shared/models/user.model'; -export interface TargetsNotificationDialogData { +export interface RecipientNotificationDialogData { target?: NotificationTarget; isAdd?: boolean; } @Component({ selector: 'tb-target-notification-dialog', - templateUrl: './target-notification-dialog.component.html', - styleUrls: ['target-notification-dialog.componet.scss'] + templateUrl: './recipient-notification-dialog.component.html', + styleUrls: ['recipient-notification-dialog.componet.scss'] }) -export class TargetNotificationDialogComponent extends - DialogComponent implements OnDestroy { +export class RecipientNotificationDialogComponent extends + DialogComponent implements OnDestroy { private authState: AuthState = getCurrentAuthState(this.store); private authUser: AuthUser = this.authState.authUser; @@ -61,8 +61,8 @@ export class TargetNotificationDialogComponent extends notificationTargetTypes: NotificationTargetType[] = Object.values(NotificationTargetType); notificationTargetTypeTranslationMap = NotificationTargetTypeTranslationMap; notificationTargetConfigType = NotificationTargetConfigType; - notificationTargetConfigTypes: NotificationTargetConfigType[] = Object.values(NotificationTargetConfigType); - notificationTargetConfigTypeTranslateMap = NotificationTargetConfigTypeTranslateMap; + notificationTargetConfigTypes: NotificationTargetConfigType[] = this.allowNotificationTargetConfigTypes(); + notificationTargetConfigTypeInfoMap = NotificationTargetConfigTypeInfoMap; slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[]; slackChanelTypesTranslateMap = SlackChanelTypesTranslateMap; @@ -73,13 +73,13 @@ export class TargetNotificationDialogComponent extends constructor(protected store: Store, protected router: Router, - protected dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: TargetsNotificationDialogData, + protected dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: RecipientNotificationDialogData, private fb: FormBuilder, private notificationService: NotificationService) { super(store, router, dialogRef); - if (isDefined(data.isAdd)) { + if (isDefinedAndNotNull(data.isAdd)) { this.isAdd = data.isAdd; } @@ -88,9 +88,12 @@ export class TargetNotificationDialogComponent extends configuration: this.fb.group({ type: [NotificationTargetType.PLATFORM_USERS], usersFilter: this.fb.group({ - type: [{value: NotificationTargetConfigType.ALL_USERS, disabled: !this.isTenantAdmin()}], + type: [NotificationTargetConfigType.ALL_USERS], + filterByTenants: [{value: true, disabled: true}], + tenantsIds: [{value: null, disabled: true}], + tenantProfilesIds: [{value: null, disabled: true}], + customerId: [{value: null, disabled: true}, Validators.required], usersIds: [{value: null, disabled: true}, Validators.required], - customerId: [{value: null, disabled: true}, Validators.required] }), conversationType: [{value: SlackChanelType.PUBLIC_CHANNEL, disabled: true}], conversation: [{value: '', disabled: true}, Validators.required], @@ -104,10 +107,8 @@ export class TargetNotificationDialogComponent extends this.targetNotificationForm.get('configuration').disable({emitEvent: false}); switch (type) { case NotificationTargetType.PLATFORM_USERS: - if (this.isTenantAdmin()) { - this.targetNotificationForm.get('configuration.usersFilter').enable({emitEvent: false}); - this.targetNotificationForm.get('configuration.usersFilter.type').updateValueAndValidity({onlySelf: true}); - } + this.targetNotificationForm.get('configuration.usersFilter').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.usersFilter.type').updateValueAndValidity({onlySelf: true}); break; case NotificationTargetType.SLACK: this.targetNotificationForm.get('configuration.conversationType').enable({emitEvent: false}); @@ -123,6 +124,11 @@ export class TargetNotificationDialogComponent extends ).subscribe((type: NotificationTargetConfigType) => { this.targetNotificationForm.get('configuration.usersFilter').disable({emitEvent: false}); switch (type) { + case NotificationTargetConfigType.TENANT_ADMINISTRATORS: + if (this.isSysAdmin()) { + this.targetNotificationForm.get('configuration.usersFilter.filterByTenants').enable({onlySelf: true}); + } + break; case NotificationTargetConfigType.USER_LIST: this.targetNotificationForm.get('configuration.usersFilter.usersIds').enable({emitEvent: false}); break; @@ -133,9 +139,25 @@ export class TargetNotificationDialogComponent extends this.targetNotificationForm.get('configuration.usersFilter.type').enable({emitEvent: false}); }); - if (isDefined(data.target)) { + this.targetNotificationForm.get('configuration.usersFilter.filterByTenants').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: boolean) => { + if (value) { + this.targetNotificationForm.get('configuration.usersFilter.tenantsIds').enable({emitEvent: false}); + this.targetNotificationForm.get('configuration.usersFilter.tenantProfilesIds').disable({emitEvent: false}); + } else { + this.targetNotificationForm.get('configuration.usersFilter.tenantsIds').disable({emitEvent: false}); + this.targetNotificationForm.get('configuration.usersFilter.tenantProfilesIds').enable({emitEvent: false}); + } + }); + + if (isDefinedAndNotNull(data.target)) { this.targetNotificationForm.patchValue(data.target, {emitEvent: false}); this.targetNotificationForm.get('configuration.type').updateValueAndValidity({onlySelf: true}); + if (this.isSysAdmin() && data.target.configuration.usersFilter.type === NotificationTargetConfigType.TENANT_ADMINISTRATORS) { + this.targetNotificationForm.get('configuration.usersFilter.filterByTenants') + .patchValue(!Array.isArray(this.data.target.configuration.usersFilter.tenantProfilesIds), {onlySelf: true}); + } } } @@ -150,21 +172,33 @@ export class TargetNotificationDialogComponent extends } save() { - let formValue: NotificationTarget = deepTrim(this.targetNotificationForm.value); - if (isDefined(this.data.target)) { + let formValue = deepTrim(this.targetNotificationForm.value); + if (isDefinedAndNotNull(this.data.target)) { formValue = Object.assign({}, this.data.target, formValue); } - if (formValue.configuration.type === NotificationTargetType.PLATFORM_USERS && !formValue.configuration.usersFilter) { - formValue.configuration.usersFilter = { - type: NotificationTargetConfigType.ALL_USERS - } + if (this.isSysAdmin() && formValue.configuration.type === NotificationTargetType.PLATFORM_USERS && + formValue.configuration.usersFilter.type === NotificationTargetConfigType.TENANT_ADMINISTRATORS) { + delete formValue.configuration.usersFilter.filterByTenants; } this.notificationService.saveNotificationTarget(formValue).subscribe( (target) => this.dialogRef.close(target) ); } - public isTenantAdmin(): boolean { - return this.authUser.authority === Authority.TENANT_ADMIN; + isSysAdmin(): boolean { + return this.authUser.authority === Authority.SYS_ADMIN; + } + + private allowNotificationTargetConfigTypes(): NotificationTargetConfigType[] { + if (this.isSysAdmin()) { + return [ + NotificationTargetConfigType.ALL_USERS, + NotificationTargetConfigType.TENANT_ADMINISTRATORS, + NotificationTargetConfigType.AFFECTED_TENANT_ADMINISTRATORS, + NotificationTargetConfigType.SYSTEM_ADMINISTRATORS + ]; + } + return Object.values(NotificationTargetConfigType).filter(type => + type !== NotificationTargetConfigType.AFFECTED_TENANT_ADMINISTRATORS && type !== NotificationTargetConfigType.SYSTEM_ADMINISTRATORS); } } diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-config.resolver.ts new file mode 100644 index 0000000000..306b5e8ea8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-config.resolver.ts @@ -0,0 +1,126 @@ +/// +/// 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 { + CellActionDescriptor, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { Direction } from '@shared/models/page/sort-order'; +import { NotificationTarget, NotificationTargetTypeTranslationMap } from '@shared/models/notification.models'; +import { NotificationService } from '@core/http/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { + RecipientNotificationDialogComponent, + RecipientNotificationDialogData +} from '@home/pages/notification/recipient/recipient-notification-dialog.componet'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { RecipientTableHeaderComponent } from '@home/pages/notification/recipient/recipient-table-header.component'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { DatePipe } from '@angular/common'; + +@Injectable() +export class RecipientTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private notificationService: NotificationService, + private translate: TranslateService, + private dialog: MatDialog, + private datePipe: DatePipe) { + + this.config.entityType = EntityType.NOTIFICATION_TARGET; + this.config.detailsPanelEnabled = false; + this.config.addEnabled = false; + this.config.rowPointer = true; + + this.config.entityTranslations = entityTypeTranslations.get(EntityType.NOTIFICATION_TARGET); + this.config.entityResources = {} as EntityTypeResource; + + this.config.entitiesFetchFunction = pageLink => this.notificationService.getNotificationTargets(pageLink); + + this.config.deleteEntityTitle = target => this.translate.instant('notification.delete-recipient-title', {recipientName: target.name}); + this.config.deleteEntityContent = () => this.translate.instant('notification.delete-recipient-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('notification.delete-recipients-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('notification.delete-recipients-text'); + + this.config.deleteEntity = id => this.notificationService.deleteNotificationTarget(id.id); + + this.config.cellActionDescriptors = this.configureCellActions(); + + this.config.headerComponent = RecipientTableHeaderComponent; + this.config.onEntityAction = action => this.onTargetAction(action); + + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.config.handleRowClick = ($event, target) => { + this.editTarget($event, target); + return true; + }; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('name', 'notification.recipient-group', '20%'), + new EntityTableColumn('configuration.type', 'notification.type', '20%', + (target) => this.translate.instant(NotificationTargetTypeTranslationMap.get(target.configuration.type)), + () => ({}), false), + new EntityTableColumn('configuration.description', 'notification.description', '60%', + (target) => target.configuration.description || '', + () => ({}), false) + ); + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private configureCellActions(): Array> { + return []; + } + + private editTarget($event: Event, target: NotificationTarget, isAdd = false) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(RecipientNotificationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + target + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.updateData(); + } + }); + } + + private onTargetAction(action: EntityAction): boolean { + switch (action.action) { + case 'add': + this.editTarget(action.event, action.entity, true); + return true; + } + return false; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.html b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.html similarity index 91% rename from ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.html rename to ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.html index a3eb7ddb4d..e2effa3207 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.html @@ -18,7 +18,7 @@
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.scss similarity index 97% rename from ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.scss rename to ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.scss index 37c58a4b53..33537b4b23 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.scss +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.scss @@ -14,5 +14,5 @@ * limitations under the License. */ :host{ - width: 100%; + width: 10000px; } diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.ts similarity index 81% rename from ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.ts rename to ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.ts index 19aaae7ad6..f7285d3836 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-table-header.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-table-header.component.ts @@ -21,11 +21,11 @@ import { AppState } from '@core/core.state'; import { NotificationTarget } from '@shared/models/notification.models'; @Component({ - selector: 'tb-target-table-header', - templateUrl: './target-table-header.component.html', - styleUrls: ['target-table-header.component.scss'] + selector: 'tb-recipient-table-header', + templateUrl: './recipient-table-header.component.html', + styleUrls: ['recipient-table-header.component.scss'] }) -export class TargetTableHeaderComponent extends EntityTableHeaderComponent { +export class RecipientTableHeaderComponent extends EntityTableHeaderComponent { constructor(protected store: Store) { super(store); diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalation-form.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html similarity index 92% rename from ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalation-form.component.html rename to ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html index f37da71867..b9a5ef2119 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/escalation-form.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html @@ -35,8 +35,9 @@ fxFlex="60" formControlName="targets" subscriptSizing="dynamic" - [entityType]="entityType.NOTIFICATION_TARGET" - [placeholderText]="escalationFormGroup.get('targets').value ? '' : ('notification.add-target' | translate)"> + entityType="{{ entityType.NOTIFICATION_TARGET }}" + subType="{{ notificationType.ALARM }}" + [placeholderText]="escalationFormGroup.get('targets').value ? '' : ('notification.add-recipient' | translate)">