From ad2d19f2d5a6255e5774b66c2a89785940c7061e Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Wed, 22 Jan 2020 08:46:30 -0100 Subject: [PATCH] mgr/dashboard: Improve Notification sidebar Remove the external package and add custom scrollbar. Fix random e2e tests. Fixes: https://tracker.ceph.com/issues/43804 Fixes: https://tracker.ceph.com/issues/43593 Signed-off-by: Tiago Melo --- .../dashboard/frontend/e2e/page-helper.po.ts | 4 ++ .../frontend/e2e/ui/notification.e2e-spec.ts | 13 ++--- .../frontend/e2e/ui/notification.po.ts | 2 +- .../mgr/dashboard/frontend/package-lock.json | 48 +++++++-------- .../mgr/dashboard/frontend/package.json | 2 +- .../core/auth/login/login.component.spec.ts | 17 +----- .../app/core/auth/login/login.component.ts | 7 +-- .../frontend/src/app/core/core.module.ts | 10 +--- .../workbench-layout.component.html | 35 +++-------- .../workbench-layout.component.scss | 4 ++ .../workbench-layout.component.spec.ts | 44 +------------- .../workbench-layout.component.ts | 33 +---------- .../navigation/navigation.component.html | 9 +-- .../navigation/navigation.component.scss | 27 ++++++--- .../navigation/navigation.component.ts | 5 +- .../shared/components/components.module.ts | 4 ++ .../notifications-sidebar.component.html | 16 +++-- .../notifications-sidebar.component.scss | 58 +++++++++---------- .../notifications-sidebar.component.spec.ts | 44 +++++++++++++- .../notifications-sidebar.component.ts | 42 ++++++++++++-- .../frontend/src/styles/defaults.scss | 3 + 21 files changed, 203 insertions(+), 224 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts index 263974f56e7..0a33340a431 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts @@ -249,6 +249,10 @@ export abstract class PageHelper { return $$('legend'); } + getToast() { + return $('.ngx-toastr'); + } + async waitPresence(elem: ElementFinder, message?: string) { return browser.wait(EC.presenceOf(elem), TIMEOUT, message); } diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts index 7822a54b9fc..52d172a87c6 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts @@ -11,16 +11,11 @@ describe('Notification page', () => { }); afterEach(async () => { - NotificationSidebarPageHelper.checkConsole(); - - if (await notification.getCloseBtn().isPresent()) { - await notification.waitClickableAndClick(notification.getCloseBtn()); - await notification.waitStaleness(notification.getSidebar()); - } + await NotificationSidebarPageHelper.checkConsole(); }); it('should open notification sidebar', async () => { - await notification.waitStaleness(notification.getSidebar()); + await notification.waitInvisibility(notification.getSidebar()); await notification.open(); await notification.waitVisibility(notification.getSidebar()); }); @@ -31,6 +26,7 @@ describe('Notification page', () => { await pools.navigateTo('create'); await pools.create(poolName, 16); await pools.edit_pool_pg(poolName, 8, false); + await notification.waitStaleness(notification.getToast()); await notification.open(); await notification.waitVisibility(notification.getTasks().first()); @@ -45,8 +41,9 @@ describe('Notification page', () => { }); it('should clear notifications', async () => { - await notification.open(); + await notification.waitStaleness(notification.getToast()); await expect((await notification.getNotifications()).length).toBeGreaterThan(0); + await notification.waitVisibility(notification.getClearNotficationsBtn()); await notification.waitClickableAndClick(notification.getClearNotficationsBtn()); await notification.waitStaleness(notification.getNotifications().first()); await expect((await notification.getNotifications()).length).toBe(0); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts index 35e493d8df6..68eeb61e919 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts @@ -8,7 +8,7 @@ export class NotificationSidebarPageHelper extends PageHelper { } getSidebar() { - return element(by.css('aside.ng-sidebar--opened cd-notifications-sidebar')); + return element(by.css('cd-notifications-sidebar')); } getTasks() { diff --git a/src/pybind/mgr/dashboard/frontend/package-lock.json b/src/pybind/mgr/dashboard/frontend/package-lock.json index 2e9506f0d58..1c911576027 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -89,6 +89,12 @@ "worker-plugin": "3.2.0" }, "dependencies": { + "caniuse-lite": { + "version": "1.0.30001019", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001019.tgz", + "integrity": "sha512-6ljkLtF1KM5fQ+5ZN0wuyVvvebJxgJPTmScOMaFuQN2QuOzvRJnWSKfzQskQU5IOU4Gap3zasYPIinzwUjoj/g==", + "dev": true + }, "core-js": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", @@ -1108,8 +1114,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "os-locale": { "version": "3.1.0", @@ -7481,7 +7486,6 @@ } }, "mem": { - "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -7492,8 +7496,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "os-locale": { "version": "3.1.0", @@ -11399,7 +11402,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, "requires": { "p-defer": "^1.0.0" } @@ -11448,12 +11450,6 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "mem": { - "requires": { - "mimic-fn": "^1.0.0" - }, - "version": "4.3.0" - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -11647,11 +11643,6 @@ "mime-db": "1.43.0" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, "mini-css-extract-plugin": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", @@ -11934,10 +11925,10 @@ "tslib": "^1.9.0" } }, - "ng-sidebar": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ng-sidebar/-/ng-sidebar-9.1.1.tgz", - "integrity": "sha512-G8BAaV/TsfkMHyy4FbaDEjrKdu0b55aEjZM1Nrz1xG62J/jyfhgK+S0ma3nszUWK6hKMqwXXVBghoX8pl9SoVg==" + "ng-click-outside": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ng-click-outside/-/ng-click-outside-5.3.0.tgz", + "integrity": "sha512-+WYtu2hSQy0F6VlHOqKhPtdVJimTiXXNtZPBGfLORJNX71ieYGsentke8KG+8EudR36FUB6Ya9g2GwGXM0UqdA==" }, "ng2-charts": { "version": "2.3.0", @@ -12567,6 +12558,11 @@ "dependencies": { "mem": { "version": "4.3.0" + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" } } }, @@ -12599,8 +12595,7 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" }, "p-each-series": { "version": "1.0.0", @@ -12619,8 +12614,7 @@ "p-is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" }, "p-limit": { "version": "1.3.0", @@ -18352,7 +18346,6 @@ } }, "mem": { - "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -18363,8 +18356,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "os-locale": { "version": "3.1.0", diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index 1d77fef928a..3c5ab4ff263 100644 --- a/src/pybind/mgr/dashboard/frontend/package.json +++ b/src/pybind/mgr/dashboard/frontend/package.json @@ -99,7 +99,7 @@ "moment": "2.24.0", "ng-block-ui": "2.1.7", "ng-bootstrap-form-validation": "5.0.0", - "ng-sidebar": "9.1.1", + "ng-click-outside": "5.3.0", "ng2-charts": "2.3.0", "ng2-tree": "2.0.0-rc.11", "ngx-bootstrap": "5.1.2", diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.spec.ts index 03a48b86324..daa96f34e8b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.spec.ts @@ -2,10 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { ToastrModule } from 'ngx-toastr'; - -import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; -import { NotificationService } from '../../../shared/services/notification.service'; +import { configureTestBed } from '../../../../testing/unit-test-helper'; import { AuthModule } from '../auth.module'; import { LoginComponent } from './login.component'; @@ -14,8 +11,7 @@ describe('LoginComponent', () => { let fixture: ComponentFixture; configureTestBed({ - imports: [RouterTestingModule, HttpClientTestingModule, AuthModule, ToastrModule.forRoot()], - providers: [i18nProviders] + imports: [RouterTestingModule, HttpClientTestingModule, AuthModule] }); beforeEach(() => { @@ -33,13 +29,4 @@ describe('LoginComponent', () => { component.ngOnInit(); expect(component['bsModalService'].getModalsCount()).toBe(0); }); - - it('should call toggleSidebar if not logged in', () => { - const notificationService: NotificationService = TestBed.get(NotificationService); - spyOn(notificationService, 'toggleSidebar').and.callThrough(); - - component.ngOnInit(); - - expect(notificationService.toggleSidebar).toHaveBeenCalledWith(true); - }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts index db095550f55..9286e31d30f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts @@ -6,7 +6,6 @@ import { BsModalService } from 'ngx-bootstrap/modal'; import { AuthService } from '../../../shared/api/auth.service'; import { Credentials } from '../../../shared/models/credentials'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; -import { NotificationService } from '../../../shared/services/notification.service'; @Component({ selector: 'cd-login', @@ -21,8 +20,7 @@ export class LoginComponent implements OnInit { private authService: AuthService, private authStorageService: AuthStorageService, private bsModalService: BsModalService, - private router: Router, - private notificationService: NotificationService + private router: Router ) {} ngOnInit() { @@ -37,9 +35,6 @@ export class LoginComponent implements OnInit { this.bsModalService.hide(i); } - // Make sure notification sidebar is closed. - this.notificationService.toggleSidebar(true); - let token: string = null; if (window.location.hash.indexOf('access_token=') !== -1) { token = window.location.hash.split('access_token=')[1]; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/core.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/core.module.ts index e6194267320..b452185fd12 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/core.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/core.module.ts @@ -3,7 +3,6 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { BlockUIModule } from 'ng-block-ui'; -import { SidebarModule } from 'ng-sidebar'; import { SharedModule } from '../shared/shared.module'; import { ForbiddenComponent } from './forbidden/forbidden.component'; @@ -13,14 +12,7 @@ import { NavigationModule } from './navigation/navigation.module'; import { NotFoundComponent } from './not-found/not-found.component'; @NgModule({ - imports: [ - BlockUIModule.forRoot(), - CommonModule, - NavigationModule, - RouterModule, - SharedModule, - SidebarModule.forRoot() - ], + imports: [BlockUIModule.forRoot(), CommonModule, NavigationModule, RouterModule, SharedModule], exports: [NavigationModule], declarations: [ NotFoundComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html index 1b32a4dfea5..ae81cdee71d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html @@ -1,26 +1,9 @@ - - - - - - - - -
- - -
- - -
-
-
-
-
+ + +
+ + +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss index 238524b866d..78884d1a4b9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss @@ -6,6 +6,10 @@ padding: 0; } +.container-fluid { + overflow: auto; +} + ::ng-deep #toast-container { margin-top: 2vw; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts index 4bf4a049418..3d1d440824f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts @@ -3,14 +3,12 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { SidebarModule } from 'ng-sidebar'; import { ToastrModule } from 'ngx-toastr'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; import { RbdService } from '../../../shared/api/rbd.service'; import { PipesModule } from '../../../shared/pipes/pipes.module'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; -import { NotificationService } from '../../../shared/services/notification.service'; import { WorkbenchLayoutComponent } from './workbench-layout.component'; describe('WorkbenchLayoutComponent', () => { @@ -18,13 +16,7 @@ describe('WorkbenchLayoutComponent', () => { let fixture: ComponentFixture; configureTestBed({ - imports: [ - RouterTestingModule, - ToastrModule.forRoot(), - PipesModule, - HttpClientTestingModule, - SidebarModule.forRoot() - ], + imports: [RouterTestingModule, ToastrModule.forRoot(), PipesModule, HttpClientTestingModule], declarations: [WorkbenchLayoutComponent], schemas: [NO_ERRORS_SCHEMA], providers: [AuthStorageService, i18nProviders, RbdService] @@ -39,38 +31,4 @@ describe('WorkbenchLayoutComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); - - describe('Sidebar', () => { - let notificationService: NotificationService; - - beforeEach(() => { - notificationService = TestBed.get(NotificationService); - }); - - it('should always close if sidebarSubject value is true', () => { - // Closed before next value - expect(component.sidebarOpened).toBeFalsy(); - notificationService.sidebarSubject.next(true); - expect(component.sidebarOpened).toBeFalsy(); - - // Opened before next value - component.sidebarOpened = true; - expect(component.sidebarOpened).toBeTruthy(); - notificationService.sidebarSubject.next(true); - expect(component.sidebarOpened).toBeFalsy(); - }); - - it('should toggle sidebar visibility if sidebarSubject value is false', () => { - // Closed before next value - expect(component.sidebarOpened).toBeFalsy(); - notificationService.sidebarSubject.next(false); - expect(component.sidebarOpened).toBeTruthy(); - - // Opened before next value - component.sidebarOpened = true; - expect(component.sidebarOpened).toBeTruthy(); - notificationService.sidebarSubject.next(false); - expect(component.sidebarOpened).toBeFalsy(); - }); - }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts index c913918fd34..64d3f4c38ed 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts @@ -1,9 +1,7 @@ -import { Component, OnDestroy, ViewChild } from '@angular/core'; +import { Component } from '@angular/core'; import { Router } from '@angular/router'; -import { Sidebar } from 'ng-sidebar'; import { TooltipConfig } from 'ngx-bootstrap/tooltip'; -import { Subscription } from 'rxjs'; import { NotificationService } from '../../../shared/services/notification.service'; @@ -21,33 +19,8 @@ import { NotificationService } from '../../../shared/services/notification.servi } ] }) -export class WorkbenchLayoutComponent implements OnDestroy { - @ViewChild(Sidebar, { static: true }) - sidebar: Sidebar; - - sidebarOpened = false; - // There is a bug in ng-sidebar that will show the sidebar closing animation - // when the page is first loaded. This prevents that. - sidebarAnimate = false; - - private readonly sidebarSubscription: Subscription; - - constructor(private router: Router, public notificationService: NotificationService) { - this.sidebarSubscription = this.notificationService.sidebarSubject.subscribe((forcedClose) => { - if (forcedClose) { - this.sidebar.close(); - } else { - this.sidebarAnimate = true; - this.sidebarOpened = !this.sidebarOpened; - } - }); - } - - ngOnDestroy() { - if (this.sidebarSubscription) { - this.sidebarSubscription.unsubscribe(); - } - } +export class WorkbenchLayoutComponent { + constructor(private router: Router, public notificationService: NotificationService) {} isDashboardPage() { return this.router.url === '/dashboard'; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html index 7ef562d4f85..1b1b9051161 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html @@ -1,7 +1,8 @@ + +
-
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.scss index 7357b3ee49f..b1d8e0e25d8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.scss @@ -1,7 +1,5 @@ @import 'defaults'; -$pwd-exp-height: 37.6px; - /* --------------------------------------------------- NAVBAR STYLE --------------------------------------------------- */ @@ -141,7 +139,6 @@ $pwd-exp-height: 37.6px; --------------------------------------------------- */ $sidebar-width: 200px; -$navbar-height: 43px; .cd-navbar-primary .active > a, .cd-navbar-primary > .active > a:focus, @@ -171,10 +168,6 @@ $navbar-height: 43px; margin-left: -$sidebar-width; } - &.isPwdDisplayed { - top: $navbar-height + $pwd-exp-height; - } - ul { &.component { padding: 20px 0; @@ -251,13 +244,29 @@ $navbar-height: 43px; top: $navbar-height; bottom: 0; right: 0; - overflow: auto; &.active { width: 100vw; } +} - &.isPwdDisplayed { +/* --------------------------------------------------- + isPwdDisplayed +--------------------------------------------------- */ +:host.isPwdDisplayed { + .cd-navbar-top .cd-navbar-brand { + top: $pwd-exp-height; + } + + #sidebar { top: $navbar-height + $pwd-exp-height; } + + #content { + top: $navbar-height + $pwd-exp-height; + } + + cd-notifications-sidebar { + top: $navbar-height + $pwd-exp-height + 10px; + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts index d13c4b1d3f6..b7cd7dc08c3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, HostBinding, OnInit } from '@angular/core'; import { PrometheusService } from '../../../shared/api/prometheus.service'; import { Icons } from '../../../shared/enum/icons.enum'; @@ -16,6 +16,8 @@ import { SummaryService } from '../../../shared/services/summary.service'; styleUrls: ['./navigation.component.scss'] }) export class NavigationComponent implements OnInit { + @HostBinding('class.isPwdDisplayed') isPwdDisplayed = false; + permissions: Permissions; summaryData: any; icons = Icons; @@ -27,7 +29,6 @@ export class NavigationComponent implements OnInit { isCollapsed = true; showMenuSidebar = true; displayedSubMenu = ''; - isPwdDisplayed = false; simplebar = { autoHide: false diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index 8d1d1ae644e..6c729a85858 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -4,6 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation'; +import { ClickOutsideModule } from 'ng-click-outside'; import { ChartsModule } from 'ng2-charts'; import { AlertModule } from 'ngx-bootstrap/alert'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; @@ -11,6 +12,7 @@ import { ModalModule } from 'ngx-bootstrap/modal'; import { PopoverModule } from 'ngx-bootstrap/popover'; import { ProgressbarModule } from 'ngx-bootstrap/progressbar'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; +import { SimplebarAngularModule } from 'simplebar-angular'; import { DirectivesModule } from '../directives/directives.module'; import { PipesModule } from '../pipes/pipes.module'; @@ -51,6 +53,8 @@ import { ViewCacheComponent } from './view-cache/view-cache.component'; DirectivesModule, BsDropdownModule, NgBootstrapFormValidationModule, + ClickOutsideModule, + SimplebarAngularModule, RouterModule ], declarations: [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.html index 8397b8b2590..16771427f3d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.html @@ -105,7 +105,9 @@ -
+
Tasks and Notifications @@ -117,9 +119,11 @@
-
- - - -
+ +
+ + + +
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.scss index a22a6e4d6cb..8f5d577e8b4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.scss @@ -1,44 +1,40 @@ @import 'defaults'; -::ng-deep .ng-sidebar__content { - overflow: hidden !important; -} - -::ng-deep .isPwdDisplayed .ng-sidebar { - top: 92px !important; -} - -// sidebar -::ng-deep .ng-sidebar { - &.ng-sidebar--opened { - margin-right: 20px; - } +:host { + position: fixed; + top: $navbar-height + 10px; + bottom: 10px; + right: -350px; width: 350px; max-width: 90vw; - z-index: 9 !important; - top: 54px !important; - bottom: 10px !important; + z-index: 9; - .card { - height: 100%; + transition: all 0.6s; +} - .card-body { - overflow: auto; - } - } +:host.active { + right: 20px; +} - .separator { - padding: 5px 12px; - color: $color-popover-seperator-text; - background-color: $color-popover-seperator-bg; - font-size: 12px; - } +.card { + height: 100%; +} + +ngx-simplebar { + height: calc(100% - 42.2px); +} + +.separator { + padding: 5px 12px; + color: $color-popover-seperator-text; + background-color: $color-popover-seperator-bg; + font-size: 12px; +} - .btn-link .fa-trash-o { - color: $color-black; - } +.btn-link .fa-trash-o { + color: $color-black; } table { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts index 229c136ad91..a4b8aff2651 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts @@ -2,9 +2,11 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { ClickOutsideModule } from 'ng-click-outside'; import { PopoverModule } from 'ngx-bootstrap/popover'; import { ProgressbarModule } from 'ngx-bootstrap/progressbar'; import { ToastrModule } from 'ngx-toastr'; +import { SimplebarAngularModule } from 'simplebar-angular'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; @@ -34,7 +36,9 @@ describe('NotificationsSidebarComponent', () => { ProgressbarModule.forRoot(), RouterTestingModule, ToastrModule.forRoot(), - NoopAnimationsModule + NoopAnimationsModule, + SimplebarAngularModule, + ClickOutsideModule ], declarations: [NotificationsSidebarComponent], providers: [ @@ -101,6 +105,7 @@ describe('NotificationsSidebarComponent', () => { expectPrometheusServicesToBeCalledTimes(0); }); + it('should first refresh prometheus notifications and alerts during init', () => { fixture.detectChanges(); @@ -156,4 +161,41 @@ describe('NotificationsSidebarComponent', () => { expect(component.notifications[0].title).toBe('Sample title'); })); }); + + describe('Sidebar', () => { + let notificationService: NotificationService; + + beforeEach(() => { + notificationService = TestBed.get(NotificationService); + fixture.detectChanges(); + }); + + it('should always close if sidebarSubject value is true', fakeAsync(() => { + // Closed before next value + expect(component.isSidebarOpened).toBeFalsy(); + notificationService.sidebarSubject.next(true); + tick(); + expect(component.isSidebarOpened).toBeFalsy(); + + // Opened before next value + component.isSidebarOpened = true; + expect(component.isSidebarOpened).toBeTruthy(); + notificationService.sidebarSubject.next(true); + tick(); + expect(component.isSidebarOpened).toBeFalsy(); + })); + + it('should toggle sidebar visibility if sidebarSubject value is false', () => { + // Closed before next value + expect(component.isSidebarOpened).toBeFalsy(); + notificationService.sidebarSubject.next(false); + expect(component.isSidebarOpened).toBeTruthy(); + + // Opened before next value + component.isSidebarOpened = true; + expect(component.isSidebarOpened).toBeTruthy(); + notificationService.sidebarSubject.next(false); + expect(component.isSidebarOpened).toBeFalsy(); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts index 7bb1624b321..c395e4650a5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + HostBinding, NgZone, OnDestroy, OnInit @@ -11,6 +12,7 @@ import { Mutex } from 'async-mutex'; import * as _ from 'lodash'; import * as moment from 'moment'; import { LocalStorage } from 'ngx-store'; +import { Subscription } from 'rxjs'; import { ExecutingTask } from '../../../shared/models/executing-task'; import { SummaryService } from '../../../shared/services/summary.service'; @@ -30,17 +32,27 @@ import { PrometheusNotificationService } from '../../services/prometheus-notific changeDetection: ChangeDetectionStrategy.OnPush }) export class NotificationsSidebarComponent implements OnInit, OnDestroy { + @HostBinding('class.active') isSidebarOpened = false; + notifications: CdNotification[]; private interval: number; + private timeout: number; executingTasks: ExecutingTask[] = []; + private sidebarSubscription: Subscription; + private notificationDataSubscription: Subscription; + icons = Icons; // Tasks @LocalStorage() last_task = ''; mutex = new Mutex(); + simplebar = { + autoHide: false + }; + constructor( public notificationService: NotificationService, private summaryService: SummaryService, @@ -56,6 +68,13 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy { ngOnDestroy() { window.clearInterval(this.interval); + window.clearTimeout(this.timeout); + if (this.sidebarSubscription) { + this.sidebarSubscription.unsubscribe(); + } + if (this.notificationDataSubscription) { + this.notificationDataSubscription.unsubscribe(); + } } ngOnInit() { @@ -71,9 +90,24 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy { }); } - this.notificationService.data$.subscribe((notifications: CdNotification[]) => { - this.notifications = _.orderBy(notifications, ['timestamp'], ['desc']); - this.cdRef.detectChanges(); + this.notificationDataSubscription = this.notificationService.data$.subscribe( + (notifications: CdNotification[]) => { + this.notifications = _.orderBy(notifications, ['timestamp'], ['desc']); + this.cdRef.detectChanges(); + } + ); + + this.sidebarSubscription = this.notificationService.sidebarSubject.subscribe((forceClose) => { + if (forceClose) { + this.isSidebarOpened = false; + } else { + this.isSidebarOpened = !this.isSidebarOpened; + } + + window.clearTimeout(this.timeout); + this.timeout = window.setTimeout(() => { + this.cdRef.detectChanges(); + }, 0); }); this.summaryService.subscribe((data: any) => { @@ -127,7 +161,7 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy { } closeSidebar() { - this.notificationService.toggleSidebar(true); + this.isSidebarOpened = false; } trackByFn(index: number) { diff --git a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss index 7b323683474..04540abc5c6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss @@ -109,6 +109,9 @@ $color-nav-links-hover: $color-primary !default; $color-nav-active-link-bg: $color-primary !default; $color-nav-border-top-collapse: $color-white-gray !default; +$navbar-height: 43px; +$pwd-exp-height: 37.6px; + /*Helper*/ $color-helper-bg: $color-primary !default; -- 2.39.5