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")
@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()
</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">
i18n-docText></cd-doc>
</li>
</ul>
+ <cd-custom-login-banner></cd-custom-login-banner>
</div>
</div>
</div>
}
.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;
+ }
+ }
}
--- /dev/null
+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');
+ });
+});
--- /dev/null
+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);
+ }
+}
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';
DownloadButtonComponent,
FormButtonPanelComponent,
MotdComponent,
- WizardComponent
+ WizardComponent,
+ CustomLoginBannerComponent
],
providers: [],
exports: [
DownloadButtonComponent,
FormButtonPanelComponent,
MotdComponent,
- WizardComponent
+ WizardComponent,
+ CustomLoginBannerComponent
]
})
export class ComponentsModule {}
--- /dev/null
+<p class="login-text"
+ *ngIf="bannerText$ | async as bannerText">{{ bannerText }}</p>
--- /dev/null
+.login-text {
+ font-weight: bold;
+ margin: 0;
+ padding: 12px 20% 12px 12px;
+}
--- /dev/null
+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();
+ });
+});
--- /dev/null
+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();
+ }
+}
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
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)
--- /dev/null
+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')