]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: customizable log-in page text/banner 46343/head
authorSarthak0702 <sarthak.dev.0702@gmail.com>
Thu, 14 Apr 2022 10:17:21 +0000 (15:47 +0530)
committerSarthak0702 <sarthak.dev.0702@gmail.com>
Thu, 19 May 2022 18:34:58 +0000 (00:04 +0530)
Fixes:https://tracker.ceph.com/issues/55231
Signed-off-by: Sarthak0702 <sarthak.dev.0702@gmail.com>
(cherry picked from commit 9f8bcd764e6d488d488e6ba1c05c2972329827b7)

12 files changed:
src/pybind/mgr/dashboard/controllers/home.py
src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.scss
src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/module.py
src/pybind/mgr/dashboard/services/custom_banner.py [new file with mode: 0644]

index eec811495e707fc8abbc0698e09c3011798ef9d8..8f69af4dccee0103ea754104d39776334305f48c 100644 (file)
@@ -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()
index 81f1d56cfe3d84c2e635e6f2da3ba64f11fd9f4a..1222fcc2ad5c3f9e0f5d6c2cb3b0bc52002f963c 100644 (file)
@@ -9,14 +9,14 @@
   </header>
   <section>
     <div class="container">
-      <div class="row full-height vertical-align">
-        <div class="col-sm-12 col-md-6 d-sm-block">
+      <div class="row full-height">
+        <div class="col-sm-12 col-md-6 d-sm-block login-form">
           <router-outlet></router-outlet>
         </div>
-        <div class="col-sm-12 col-md-6 d-sm-block">
+        <div class="col-sm-12 col-md-6 d-sm-block branding-info">
           <img src="assets/Ceph_Ceph_Logo_with_text_white.svg"
                alt="Ceph"
-               class="img-fluid">
+               class="img-fluid pb-3">
           <ul class="list-inline">
             <li class="list-inline-item p-3"
                 *ngFor="let docItem of docItems">
@@ -26,6 +26,7 @@
                       i18n-docText></cd-doc>
             </li>
           </ul>
+          <cd-custom-login-banner></cd-custom-login-banner>
         </div>
       </div>
     </div>
index f55ae8d0c5139c82fe0aae6dd1104af25a65f65d..d5c9f73ec52f828d5952ba12caf40fa34417721b 100644 (file)
@@ -31,7 +31,7 @@
   }
 
   .list-inline {
-    margin-bottom: 20%;
+    margin-bottom: 0;
     margin-left: 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 (file)
index 0000000..d1db441
--- /dev/null
@@ -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 (file)
index 0000000..7c499eb
--- /dev/null
@@ -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<string>(this.baseUiURL);
+  }
+}
index a6d0624d31885d162000fc39dbc6b09078f6ab9f..a281bf8598e54d1a03f01be140cddf73a95b2442 100644 (file)
@@ -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 (file)
index 0000000..7bb087c
--- /dev/null
@@ -0,0 +1,2 @@
+<p class="login-text"
+   *ngIf="bannerText$ | async as bannerText">{{ bannerText }}</p>
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 (file)
index 0000000..4721f65
--- /dev/null
@@ -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 (file)
index 0000000..6005cbd
--- /dev/null
@@ -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<CustomLoginBannerComponent>;
+
+  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 (file)
index 0000000..ad0d546
--- /dev/null
@@ -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<string>;
+  constructor(private customLoginBannerService: CustomLoginBannerService) {}
+
+  ngOnInit(): void {
+    this.bannerText$ = this.customLoginBannerService.getBannerText();
+  }
+}
index 1b0cebd36a24446431cd69d81ea0b17476a54fce..e27ff7b589d91bc0e5f62af45cb3b172c4ccef80 100644 (file)
@@ -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 (file)
index 0000000..e28addd
--- /dev/null
@@ -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')