From 562f57c44c9e2d3dac448c1adce31463a37595a8 Mon Sep 17 00:00:00 2001 From: Sarthak0702 Date: Thu, 14 Apr 2022 15:47:21 +0530 Subject: [PATCH] mgr/dashboard: customizable log-in page text/banner Fixes:https://tracker.ceph.com/issues/55231 Signed-off-by: Sarthak0702 (cherry picked from commit 9f8bcd764e6d488d488e6ba1c05c2972329827b7) --- src/pybind/mgr/dashboard/controllers/home.py | 8 +++++ .../login-layout/login-layout.component.html | 9 ++--- .../login-layout/login-layout.component.scss | 18 +++++++++- .../api/custom-login-banner.service.spec.ts | 35 +++++++++++++++++++ .../shared/api/custom-login-banner.service.ts | 15 ++++++++ .../shared/components/components.module.ts | 7 ++-- .../custom-login-banner.component.html | 2 ++ .../custom-login-banner.component.scss | 5 +++ .../custom-login-banner.component.spec.ts | 25 +++++++++++++ .../custom-login-banner.component.ts | 20 +++++++++++ src/pybind/mgr/dashboard/module.py | 31 ++++++++++++++-- .../mgr/dashboard/services/custom_banner.py | 27 ++++++++++++++ 12 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.ts create mode 100644 src/pybind/mgr/dashboard/services/custom_banner.py diff --git a/src/pybind/mgr/dashboard/controllers/home.py b/src/pybind/mgr/dashboard/controllers/home.py index eec811495e707..8f69af4dccee0 100644 --- a/src/pybind/mgr/dashboard/controllers/home.py +++ b/src/pybind/mgr/dashboard/controllers/home.py @@ -15,6 +15,7 @@ import cherrypy from cherrypy.lib.static import serve_file from .. import mgr +from ..services.custom_banner import get_login_banner_mgr from . import BaseController, Endpoint, Proxy, Router, UIRouter logger = logging.getLogger("controllers.home") @@ -140,3 +141,10 @@ class LangsController(BaseController, LanguageMixin): @Endpoint('GET') def __call__(self): return list(self.LANGUAGES) + + +@UIRouter("/login", secure=False) +class LoginController(BaseController): + @Endpoint('GET', 'custom_banner') + def __call__(self): + return get_login_banner_mgr() diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.html index 81f1d56cfe3d8..1222fcc2ad5c3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.html @@ -9,14 +9,14 @@
-
-
+
+ -
+
Ceph + class="img-fluid pb-3">
  • @@ -26,6 +26,7 @@ i18n-docText>
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.scss index f55ae8d0c5139..d5c9f73ec52f8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.scss @@ -31,7 +31,7 @@ } .list-inline { - margin-bottom: 20%; + margin-bottom: 0; margin-left: 20%; } @@ -42,4 +42,20 @@ color: vv.$fg-hover-color-over-dark-bg; } } + + @media screen and (min-width: vv.$screen-sm-min) { + .login-form, + .branding-info { + padding-top: 30vh; + } + } + @media screen and (max-width: vv.$screen-sm-max) { + .login-form { + padding-top: 10vh; + } + + .branding-info { + padding-top: 0; + } + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.spec.ts new file mode 100644 index 0000000000000..d1db441c710ca --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.spec.ts @@ -0,0 +1,35 @@ +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +import { configureTestBed } from '~/testing/unit-test-helper'; +import { CustomLoginBannerService } from './custom-login-banner.service'; + +describe('CustomLoginBannerService', () => { + let service: CustomLoginBannerService; + let httpTesting: HttpTestingController; + const baseUiURL = 'ui-api/login/custom_banner'; + + configureTestBed({ + providers: [CustomLoginBannerService], + imports: [HttpClientTestingModule] + }); + + beforeEach(() => { + service = TestBed.inject(CustomLoginBannerService); + httpTesting = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpTesting.verify(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call getBannerText', () => { + service.getBannerText().subscribe(); + const req = httpTesting.expectOne(baseUiURL); + expect(req.request.method).toBe('GET'); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.ts new file mode 100644 index 0000000000000..7c499eb13707b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.ts @@ -0,0 +1,15 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class CustomLoginBannerService { + baseUiURL = 'ui-api/login/custom_banner'; + + constructor(private http: HttpClient) {} + + getBannerText() { + return this.http.get(this.baseUiURL); + } +} 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 a6d0624d31885..a281bf8598e54 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 @@ -25,6 +25,7 @@ import { ConfigOptionComponent } from './config-option/config-option.component'; import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component'; import { Copy2ClipboardButtonComponent } from './copy2clipboard-button/copy2clipboard-button.component'; import { CriticalConfirmationModalComponent } from './critical-confirmation-modal/critical-confirmation-modal.component'; +import { CustomLoginBannerComponent } from './custom-login-banner/custom-login-banner.component'; import { DateTimePickerComponent } from './date-time-picker/date-time-picker.component'; import { DocComponent } from './doc/doc.component'; import { DownloadButtonComponent } from './download-button/download-button.component'; @@ -95,7 +96,8 @@ import { WizardComponent } from './wizard/wizard.component'; DownloadButtonComponent, FormButtonPanelComponent, MotdComponent, - WizardComponent + WizardComponent, + CustomLoginBannerComponent ], providers: [], exports: [ @@ -123,7 +125,8 @@ import { WizardComponent } from './wizard/wizard.component'; DownloadButtonComponent, FormButtonPanelComponent, MotdComponent, - WizardComponent + WizardComponent, + CustomLoginBannerComponent ] }) export class ComponentsModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.html new file mode 100644 index 0000000000000..7bb087c3f079e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.html @@ -0,0 +1,2 @@ + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.scss new file mode 100644 index 0000000000000..4721f6531403e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.scss @@ -0,0 +1,5 @@ +.login-text { + font-weight: bold; + margin: 0; + padding: 12px 20% 12px 12px; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.spec.ts new file mode 100644 index 0000000000000..6005cbd0bae6e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.spec.ts @@ -0,0 +1,25 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { configureTestBed } from '~/testing/unit-test-helper'; +import { CustomLoginBannerComponent } from './custom-login-banner.component'; + +describe('CustomLoginBannerComponent', () => { + let component: CustomLoginBannerComponent; + let fixture: ComponentFixture; + + configureTestBed({ + declarations: [CustomLoginBannerComponent], + imports: [HttpClientTestingModule] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomLoginBannerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.ts new file mode 100644 index 0000000000000..ad0d546885469 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; + +import _ from 'lodash'; +import { Observable } from 'rxjs'; + +import { CustomLoginBannerService } from '~/app/shared/api/custom-login-banner.service'; + +@Component({ + selector: 'cd-custom-login-banner', + templateUrl: './custom-login-banner.component.html', + styleUrls: ['./custom-login-banner.component.scss'] +}) +export class CustomLoginBannerComponent implements OnInit { + bannerText$: Observable; + constructor(private customLoginBannerService: CustomLoginBannerService) {} + + ngOnInit(): void { + this.bannerText$ = this.customLoginBannerService.getBannerText(); + } +} diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index 1b0cebd36a244..e27ff7b589d91 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -15,14 +15,17 @@ import threading import time from typing import TYPE_CHECKING, Optional +from .services.custom_banner import get_login_banner_mgr, \ + set_login_banner_mgr, unset_login_banner_mgr + if TYPE_CHECKING: if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal -from mgr_module import CLIWriteCommand, HandleCommandResult, MgrModule, \ - MgrStandbyModule, NotifyType, Option, _get_localized_key +from mgr_module import CLIReadCommand, CLIWriteCommand, HandleCommandResult, \ + MgrModule, MgrStandbyModule, NotifyType, Option, _get_localized_key from mgr_util import ServerConfigException, build_url, \ create_self_signed_cert, get_default_addr, verify_tls_files @@ -411,6 +414,30 @@ class Module(MgrModule, CherryPyConfig): return 0, 'RGW credentials configured', '' + @CLIWriteCommand("dashboard set-login-banner") + def set_login_banner(self, mgr_id: Optional[str] = None, inbuf: Optional[str] = None): + item_label = 'login banner file' + if inbuf is None: + return HandleCommandResult( + -errno.EINVAL, + stderr=f'Please specify the {item_label} with "-i" option' + ) + set_login_banner_mgr(inbuf, mgr_id) + return HandleCommandResult(stdout=f'{item_label} added') + + @CLIReadCommand("dashboard get-login-banner") + def get_login_banner(self): + banner_text = get_login_banner_mgr() + if banner_text is None: + return HandleCommandResult(stdout='No login banner set') + else: + return HandleCommandResult(stdout=banner_text) + + @CLIWriteCommand("dashboard unset-login-banner") + def unset_login_banner(self): + unset_login_banner_mgr() + return HandleCommandResult(stdout='Login banner removed') + def handle_command(self, inbuf, cmd): # pylint: disable=too-many-return-statements res = handle_option_command(cmd, inbuf) diff --git a/src/pybind/mgr/dashboard/services/custom_banner.py b/src/pybind/mgr/dashboard/services/custom_banner.py new file mode 100644 index 0000000000000..e28addde7b8f5 --- /dev/null +++ b/src/pybind/mgr/dashboard/services/custom_banner.py @@ -0,0 +1,27 @@ +import logging +from typing import Optional + +from mgr_module import _get_localized_key + +from .. import mgr + +logger = logging.getLogger(__name__) + + +def set_login_banner_mgr(inbuf: str, mgr_id: Optional[str] = None): + item_key = 'custom_login_banner' + if mgr_id is not None: + mgr.set_store(_get_localized_key(mgr_id, item_key), inbuf) + else: + mgr.set_store(item_key, inbuf) + + +def get_login_banner_mgr(): + banner_text = mgr.get_store('custom_login_banner') + logger.info('Reading custom login banner: %s', banner_text) + return banner_text + + +def unset_login_banner_mgr(): + mgr.set_store('custom_login_banner', None) + logger.info('Removing custom login banner') -- 2.39.5