*ngIf="available === false"
title="iSCSI Targets not available"
i18n-title>
- <ng-container i18n>Please consult the <a href="{{docsUrl}}"
- target="_blank">documentation</a>
- on how to configure and enable the iSCSI Targets management functionality.</ng-container>
+ <ng-container i18n>Please consult the <cd-doc section="iscsi"></cd-doc> on
+ how to configure and enable the iSCSI Targets management functionality.</ng-container>
<ng-container *ngIf="status">
<br>
import { FinishedTask } from '../../../shared/models/finished-task';
import { Permission } from '../../../shared/models/permissions';
import { Task } from '../../../shared/models/task';
-import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
import { NotAvailablePipe } from '../../../shared/pipes/not-available.pipe';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
-import { SummaryService } from '../../../shared/services/summary.service';
import { TaskListService } from '../../../shared/services/task-list.service';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { IscsiTargetDiscoveryModalComponent } from '../iscsi-target-discovery-modal/iscsi-target-discovery-modal.component';
available: boolean = undefined;
columns: CdTableColumn[];
- docsUrl: string;
modalRef: BsModalRef;
permission: Permission;
selection = new CdTableSelection();
private i18n: I18n,
private iscsiService: IscsiService,
private taskListService: TaskListService,
- private cephReleaseNamePipe: CephReleaseNamePipe,
private notAvailablePipe: NotAvailablePipe,
- private summaryservice: SummaryService,
private modalService: BsModalService,
private taskWrapper: TaskWrapperService,
public actionLabels: ActionLabelsI18n
this.settings = settings;
});
} else {
- this.summaryservice.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/#enabling-iscsi-management`;
- this.status = result.message;
- });
+ this.status = result.message;
}
});
}
icons = Icons;
hasOrchestrator = false;
- docsUrl: string;
devices: Array<InventoryDevice> = [];
featureList: OsdFeature[] = [];
hasOrchestrator = false;
- docsUrl: string;
constructor(
public actionLabels: ActionLabelsI18n,
<cd-active-alert-list *ngIf="isAlertmanagerConfigured"></cd-active-alert-list>
<cd-alert-panel type="info"
i18n
- *ngIf="!isAlertmanagerConfigured">To see all active Prometheus alerts, please
- provide the URL to the API of Prometheus' Alertmanager as described in the
- <a href="{{docsUrl}}"
- target="_blank">documentation</a>.</cd-alert-panel>
+ *ngIf="!isAlertmanagerConfigured">To see all active Prometheus alerts,
+ please provide the URL to the API of Prometheus' Alertmanager as described in
+ the <cd-doc section="prometheus"></cd-doc>.</cd-alert-panel>
</tab>
<tab id="all-alerts"
heading="All Alerts"
<cd-rules-list *ngIf="isPrometheusConfigured"
[data]="prometheusAlertService.rules"></cd-rules-list>
<cd-alert-panel type="info"
- *ngIf="!isPrometheusConfigured">To see all configured Prometheus alerts, please provide the URL to
- the API of Prometheus as described in the
- <a href="{{docsUrl}}"
- target="_blank">documentation</a>.</cd-alert-panel>
+ *ngIf="!isPrometheusConfigured">To see all configured Prometheus alerts,
+ please provide the URL to the API of Prometheus as described in
+ the <cd-doc section="prometheus"></cd-doc>.</cd-alert-panel>
</tab>
<tab id="silences"
heading="Silences"
<cd-silences-list *ngIf="isAlertmanagerConfigured"></cd-silences-list>
<cd-alert-panel *ngIf="!isAlertmanagerConfigured"
type="info"
- i18n>To enable Silences, please provide the URL to the API of the Prometheus' Alertmanager as
- described in the
- <a href="{{docsUrl}}"
- target="_blank">documentation</a>.</cd-alert-panel>
+ i18n>To enable Silences, please provide the URL to the API
+ of the Prometheus' Alertmanager as described in
+ the <cd-doc section="prometheus"></cd-doc>.</cd-alert-panel>
</tab>
</tabset>
import { TabDirective, TabsetComponent } from 'ngx-bootstrap/tabs';
import { PrometheusService } from '../../../../shared/api/prometheus.service';
-import { CephReleaseNamePipe } from '../../../../shared/pipes/ceph-release-name.pipe';
import { PrometheusAlertService } from '../../../../shared/services/prometheus-alert.service';
-import { SummaryService } from '../../../../shared/services/summary.service';
@Component({
selector: 'cd-monitoring-list',
public prometheusAlertService: PrometheusAlertService,
private prometheusService: PrometheusService,
private route: ActivatedRoute,
- private router: Router,
- private summaryService: SummaryService,
- private cephReleaseNamePipe: CephReleaseNamePipe
+ private router: Router
) {}
@ViewChild('tabs', { static: true })
tabs: TabsetComponent;
isPrometheusConfigured = false;
isAlertmanagerConfigured = false;
- docsUrl = '';
-
ngOnInit() {
this.prometheusService.ifAlertmanagerConfigured(() => {
this.isAlertmanagerConfigured = true;
this.isPrometheusConfigured = true;
});
- this.summaryService.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl = `https://docs.ceph.com/docs/${releaseName}/mgr/dashboard/#enabling-prometheus-alerting`;
- });
-
// Activate tab according to given fragment
if (this.route.snapshot.fragment) {
const tab = this.tabs.tabs.find(
checkingOrchestrator = true;
hasOrchestrator = false;
- docsUrl: string;
columns: Array<CdTableColumn> = [];
services: Array<CephServiceSpec> = [];
<cd-alert-panel type="info">
{{ message }}<br>
- <ng-container i18n>Please consult the <a href="{{docsUrl}}"
- target="_blank">documentation</a>
- on how to configure and enable the NFS Ganesha management functionality.</ng-container>
+ <ng-container i18n>Please consult the <cd-doc section="nfs-ganesha"></cd-doc> on how
+ to configure and enable the NFS Ganesha management functionality.</ng-container>
</cd-alert-panel>
import { I18n } from '@ngx-translate/i18n-polyfill';
-import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
-import { SummaryService } from '../../../shared/services/summary.service';
-
@Component({
selector: 'cd-nfs-501',
templateUrl: './nfs-501.component.html',
styleUrls: ['./nfs-501.component.scss']
})
export class Nfs501Component implements OnInit, OnDestroy {
- docsUrl: string;
message = this.i18n('The NFS Ganesha service is not configured.');
routeParamsSubscribe: any;
- constructor(
- private route: ActivatedRoute,
- private summaryService: SummaryService,
- private cephReleaseNamePipe: CephReleaseNamePipe,
- private i18n: I18n
- ) {}
+ constructor(private route: ActivatedRoute, private i18n: I18n) {}
ngOnInit() {
- this.summaryService.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl =
- `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/` +
- `#configuring-nfs-ganesha-in-the-dashboard`;
- });
-
this.routeParamsSubscribe = this.route.params.subscribe((params: { message: string }) => {
this.message = params.message;
});
*ngIf="nfsForm.getValue('access_type') === 'RW' && nfsForm.getValue('name') === 'RGW'"
i18n>The Object Gateway NFS backend has a number of
limitations which will seriously affect applications writing to
- the share. Please consult the
- <a href="{{docsUrl}}"
- target="_blank"> documentation</a> for details before enabling write access.</span>
+ the share. Please consult the <cd-doc section="rgw-nfs"></cd-doc>
+ for details before enabling write access.</span>
<span class="invalid-feedback"
*ngIf="nfsForm.showError('access_type', formDir, 'required')"
i18n>This field is required.</span>
import { CdValidators } from '../../../shared/forms/cd-validators';
import { FinishedTask } from '../../../shared/models/finished-task';
import { Permission } from '../../../shared/models/permissions';
-import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
-import { SummaryService } from '../../../shared/services/summary.service';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { NfsFormClientComponent } from '../nfs-form-client/nfs-form-client.component';
action: string;
resource: string;
- docsUrl: string;
daemonsSelections: SelectOption[] = [];
daemonsMessages = new SelectMessages(
private router: Router,
private rgwUserService: RgwUserService,
private formBuilder: CdFormBuilder,
- private summaryservice: SummaryService,
- private cephReleaseNamePipe: CephReleaseNamePipe,
private taskWrapper: TaskWrapperService,
private cdRef: ChangeDetectorRef,
private i18n: I18n,
this.action = this.actionLabels.CREATE;
this.getData(promises);
}
-
- this.summaryservice.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/radosgw/nfs/`;
- });
}
getData(promises: Observable<any>[]) {
*ngIf="form.showError('pgNum', formDir, '34')"
i18n>Your cluster can't handle this many PGs. Please recalculate the PG amount needed.</span>
<span class="form-text text-muted">
- <a i18n
- target="_blank"
- href="http://ceph.com/pgcalc">Calculation help</a>
+ <cd-doc section="pgs"
+ docText="Calculation help"
+ i18n-docText></cd-doc>
</span>
<span class="form-text text-muted"
*ngIf="externalPgChange"
<cd-alert-panel type="info">
{{ message }}<br>
- <ng-container i18n>Please consult the <a href="{{docsUrl}}"
- target="_blank">documentation</a>
- on how to configure and enable the Object Gateway management functionality.</ng-container>
+ <ng-container i18n>Please consult the <cd-doc section="rgw"></cd-doc> on how
+ to configure and enable the Object Gateway management functionality.</ng-container>
</cd-alert-panel>
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
-import { SummaryService } from '../../../shared/services/summary.service';
-
@Component({
selector: 'cd-rgw-501',
templateUrl: './rgw-501.component.html',
styleUrls: ['./rgw-501.component.scss']
})
export class Rgw501Component implements OnInit, OnDestroy {
- docsUrl: string;
message = 'The Object Gateway service is not configured.';
routeParamsSubscribe: any;
- constructor(
- private route: ActivatedRoute,
- private summaryService: SummaryService,
- private cephReleaseNamePipe: CephReleaseNamePipe
- ) {}
+ constructor(private route: ActivatedRoute) {}
ngOnInit() {
- this.summaryService.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl =
- `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/` +
- `#enabling-the-object-gateway-management-frontend`;
- });
-
this.routeParamsSubscribe = this.route.params.subscribe((params: { message: string }) => {
this.message = params.message;
});
class="dropdown-menu dropdown-menu-right"
role="menu">
<li>
- <a i18n
- class="dropdown-item"
+ <a class="dropdown-item text-capitalize"
[ngClass]="{'disabled': !docsUrl}"
href="{{ docsUrl }}"
- target="_blank">Documentation</a>
+ target="_blank"
+ i18n>documentation</a>
</li>
<li>
<a i18n
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { Icons } from '../../../shared/enum/icons.enum';
-import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
-import { SummaryService } from '../../../shared/services/summary.service';
+import { DocService } from '../../../shared/services/doc.service';
import { AboutComponent } from '../about/about.component';
@Component({
icons = Icons;
constructor(
- private summaryService: SummaryService,
- private cephReleaseNamePipe: CephReleaseNamePipe,
private modalService: BsModalService,
- private authStorageService: AuthStorageService
+ private authStorageService: AuthStorageService,
+ private docService: DocService
) {}
ngOnInit() {
- this.summaryService.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/`;
+ this.docService.subscribeOnce('dashboard', (url: string) => {
+ this.docsUrl = url;
});
}
import { ConfigOptionComponent } from './config-option/config-option.component';
import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component';
import { CriticalConfirmationModalComponent } from './critical-confirmation-modal/critical-confirmation-modal.component';
+import { DocComponent } from './doc/doc.component';
import { FormModalComponent } from './form-modal/form-modal.component';
import { GrafanaComponent } from './grafana/grafana.component';
import { HelperComponent } from './helper/helper.component';
PwdExpirationNotificationComponent,
TelemetryNotificationComponent,
OrchestratorDocPanelComponent,
- OrchestratorDocModalComponent
+ OrchestratorDocModalComponent,
+ DocComponent
],
providers: [],
exports: [
AlertPanelComponent,
PwdExpirationNotificationComponent,
TelemetryNotificationComponent,
- OrchestratorDocPanelComponent
+ OrchestratorDocPanelComponent,
+ DocComponent
],
entryComponents: [
ModalComponent,
--- /dev/null
+<a href="{{ docUrl }}"
+ target="_blank">{{ docText }}</a>
--- /dev/null
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
+import { DocComponent } from './doc.component';
+
+describe('DocComponent', () => {
+ let component: DocComponent;
+ let fixture: ComponentFixture<DocComponent>;
+
+ configureTestBed({
+ declarations: [DocComponent],
+ imports: [HttpClientTestingModule],
+ providers: [CephReleaseNamePipe, i18nProviders]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DocComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, Input, OnInit } from '@angular/core';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+
+import { DocService } from '../../../shared/services/doc.service';
+
+@Component({
+ selector: 'cd-doc',
+ templateUrl: './doc.component.html',
+ styleUrls: ['./doc.component.scss']
+})
+export class DocComponent implements OnInit {
+ @Input() section: string;
+ @Input() docText = this.i18n(`documentation`);
+
+ docUrl: string;
+
+ constructor(private docService: DocService, private i18n: I18n) {}
+
+ ngOnInit() {
+ this.docService.subscribeOnce(this.section, (url: string) => {
+ this.docUrl = url;
+ });
+ }
+}
<cd-alert-panel type="info"
*ngIf="!grafanaExist"
- i18n>Please consult the
- <a href="{{ docsUrl }}"
- target="_blank">documentation</a> on how to
- configure and enable the monitoring functionality.</cd-alert-panel>
+ i18n>Please consult the <cd-doc section="grafana"></cd-doc> on
+ how to configure and enable the monitoring functionality.</cd-alert-panel>
<cd-alert-panel type="info"
*ngIf="!dashboardExist"
i18n>Grafana Dashboard doesn't exist. Please refer to
- <a href="{{ docsUrl }}"
- target="_blank">documentation</a> on how to
- add dashboards to Grafana.</cd-alert-panel>
+ <cd-doc section="grafana"></cd-doc> on how to add dashboards to Grafana.</cd-alert-panel>
<ng-container *ngIf="grafanaExist && dashboardExist">
<div class="row">
import { SettingsService } from '../../api/settings.service';
import { CephReleaseNamePipe } from '../../pipes/ceph-release-name.pipe';
import { AlertPanelComponent } from '../alert-panel/alert-panel.component';
+import { DocComponent } from '../doc/doc.component';
import { LoadingPanelComponent } from '../loading-panel/loading-panel.component';
import { GrafanaComponent } from './grafana.component';
let fixture: ComponentFixture<GrafanaComponent>;
configureTestBed({
- declarations: [GrafanaComponent, AlertPanelComponent, LoadingPanelComponent],
+ declarations: [GrafanaComponent, AlertPanelComponent, LoadingPanelComponent, DocComponent],
imports: [AlertModule.forRoot(), HttpClientTestingModule, RouterTestingModule, FormsModule],
providers: [CephReleaseNamePipe, SettingsService, SummaryService, i18nProviders]
});
import { I18n } from '@ngx-translate/i18n-polyfill';
import { Icons } from '../../../shared/enum/icons.enum';
-import { CephReleaseNamePipe } from '../../../shared/pipes/ceph-release-name.pipe';
-import { SummaryService } from '../../../shared/services/summary.service';
import { SettingsService } from '../../api/settings.service';
@Component({
@Input()
uid: string;
- docsUrl: string;
-
constructor(
- private summaryService: SummaryService,
private sanitizer: DomSanitizer,
private settingsService: SettingsService,
- private cephReleaseNamePipe: CephReleaseNamePipe,
private i18n: I18n
) {
this.grafanaTimes = [
three: 'grafana_three'
};
- this.summaryService.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl =
- `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/` +
- `#enabling-the-embedding-of-grafana-dashboards`;
- });
-
this.settingsService.ifSettingConfigured('api/grafana/url', (url) => {
this.grafanaExist = true;
this.loading = false;
<cd-alert-panel type="info"
- i18n>Orchestrator is not available. Please consult the
- <a href="{{ docsUrl }}"
- target="_blank">documentation</a> on how to
- configure and enable the functionality.</cd-alert-panel>
+ i18n>Orchestrator is not available.
+ Please consult the <cd-doc section="orch"></cd-doc> on how to configure and
+ enable the functionality.</cd-alert-panel>
-import { Component, OnInit } from '@angular/core';
-
-import { CephReleaseNamePipe } from '../../pipes/ceph-release-name.pipe';
-import { SummaryService } from '../../services/summary.service';
+import { Component } from '@angular/core';
@Component({
selector: 'cd-orchestrator-doc-panel',
templateUrl: './orchestrator-doc-panel.component.html',
styleUrls: ['./orchestrator-doc-panel.component.scss']
})
-export class OrchestratorDocPanelComponent implements OnInit {
- docsUrl: string;
-
- constructor(
- private cephReleaseNamePipe: CephReleaseNamePipe,
- private summaryService: SummaryService
- ) {}
-
- ngOnInit() {
- this.summaryService.subscribeOnce((summary) => {
- const releaseName = this.cephReleaseNamePipe.transform(summary.version);
- this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/orchestrator/`;
- });
- }
-}
+export class OrchestratorDocPanelComponent {}
--- /dev/null
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+
+import { Subscriber } from 'rxjs';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+import { SharedModule } from '../shared.module';
+import { DocService } from './doc.service';
+
+describe('DocService', () => {
+ let service: DocService;
+
+ configureTestBed({ imports: [HttpClientTestingModule, SharedModule] });
+
+ beforeEach(() => {
+ service = TestBed.get(DocService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should return full URL', () => {
+ expect(service.urlGenerator('foo', 'iscsi')).toBe(
+ 'http://docs.ceph.com/docs/foo/mgr/dashboard/#enabling-iscsi-management'
+ );
+ });
+
+ describe('Name of the group', () => {
+ let result: string;
+ let i: number;
+
+ const nextSummary = (newData: any) => service['releaseDataSource'].next(newData);
+
+ const callback = (response: string) => {
+ i++;
+ result = response;
+ };
+
+ beforeEach(() => {
+ i = 0;
+ result = undefined;
+ nextSummary(undefined);
+ });
+
+ it('should call subscribeOnce without releaseName', () => {
+ const subscriber = service.subscribeOnce('prometheus', callback);
+
+ expect(subscriber).toEqual(jasmine.any(Subscriber));
+ expect(i).toBe(0);
+ expect(result).toEqual(undefined);
+ });
+
+ it('should call subscribeOnce with releaseName', () => {
+ const subscriber = service.subscribeOnce('prometheus', callback);
+
+ expect(subscriber).toEqual(jasmine.any(Subscriber));
+ expect(i).toBe(0);
+ expect(result).toEqual(undefined);
+
+ nextSummary('foo');
+ expect(result).toEqual(
+ 'http://docs.ceph.com/docs/foo/mgr/dashboard/#enabling-prometheus-alerting'
+ );
+ expect(i).toBe(1);
+ expect(subscriber.closed).toBe(true);
+ });
+ });
+});
--- /dev/null
+import { Injectable } from '@angular/core';
+
+import { BehaviorSubject, Subscription } from 'rxjs';
+import { filter, first, map } from 'rxjs/operators';
+
+import { CephReleaseNamePipe } from '../pipes/ceph-release-name.pipe';
+import { SummaryService } from './summary.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class DocService {
+ private releaseDataSource = new BehaviorSubject<string>(null);
+ releaseData$ = this.releaseDataSource.asObservable();
+
+ constructor(
+ private summaryservice: SummaryService,
+ private cephReleaseNamePipe: CephReleaseNamePipe
+ ) {
+ this.summaryservice.subscribeOnce((summary) => {
+ const releaseName = this.cephReleaseNamePipe.transform(summary.version);
+ this.releaseDataSource.next(releaseName);
+ });
+ }
+
+ urlGenerator(release: string, section: string): string {
+ const domain = `http://docs.ceph.com/docs/${release}/`;
+
+ const sections = {
+ iscsi: `${domain}mgr/dashboard/#enabling-iscsi-management`,
+ prometheus: `${domain}mgr/dashboard/#enabling-prometheus-alerting`,
+ 'nfs-ganesha': `${domain}mgr/dashboard/#configuring-nfs-ganesha-in-the-dashboard`,
+ 'rgw-nfs': `${domain}radosgw/nfs`,
+ rgw: `${domain}mgr/dashboard/#enabling-the-object-gateway-management-frontend`,
+ dashboard: `${domain}mgr/dashboard`,
+ grafana: `${domain}mgr/dashboard/#enabling-the-embedding-of-grafana-dashboards`,
+ orch: `${domain}mgr/orchestrator`,
+ pgs: `http://ceph.com/pgcalc`
+ };
+
+ return sections[section];
+ }
+
+ subscribeOnce(
+ section: string,
+ next: (release: string) => void,
+ error?: (error: any) => void
+ ): Subscription {
+ return this.releaseData$
+ .pipe(
+ filter((value) => !!value),
+ map((release) => this.urlGenerator(release, section)),
+ first()
+ )
+ .subscribe(next, error);
+ }
+}