mirror of
https://github.com/ceph/ceph
synced 2025-01-20 01:51:34 +00:00
mgr/dashboard: customizable log-in page text/banner
Fixes:https://tracker.ceph.com/issues/55231 Signed-off-by: Sarthak0702 <sarthak.dev.0702@gmail.com>
This commit is contained in:
parent
a31813d251
commit
9f8bcd764e
@ -14,6 +14,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")
|
||||
@ -139,3 +140,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()
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 {}
|
||||
|
@ -0,0 +1,2 @@
|
||||
<p class="login-text"
|
||||
*ngIf="bannerText$ | async as bannerText">{{ bannerText }}</p>
|
@ -0,0 +1,5 @@
|
||||
.login-text {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 20% 12px 12px;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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();
|
||||
}
|
||||
}
|
@ -13,14 +13,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
|
||||
|
||||
@ -417,6 +420,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)
|
||||
|
27
src/pybind/mgr/dashboard/services/custom_banner.py
Normal file
27
src/pybind/mgr/dashboard/services/custom_banner.py
Normal file
@ -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')
|
Loading…
Reference in New Issue
Block a user