]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Extract documentation link to a component 36587/head
authorTiago Melo <tmelo@suse.com>
Thu, 30 Jul 2020 20:31:13 +0000 (20:31 +0000)
committerTiago Melo <tmelo@suse.com>
Mon, 24 Aug 2020 17:05:52 +0000 (17:05 +0000)
Fixes: https://tracker.ceph.com/issues/36565
Fixes: https://tracker.ceph.com/issues/43375
Signed-off-by: Tiago Melo <tmelo@suse.com>
(cherry picked from commit e12702a6c71d2050fe3f3991173a60332c5d583a)

 Conflicts:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-list-helper.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-501/nfs-501.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts
        src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.html
        src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts
        src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.ts
        src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.spec.ts

28 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-501/nfs-501.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-501/nfs-501.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts [new file with mode: 0644]

index 276629cd413b2490d87a73282dc58265d8e7dd82..0103c11249a1bf2aa5daa7c622a35d42adfd3c51 100644 (file)
@@ -4,9 +4,8 @@
                 *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>
index 07976f2c2195e60501a7ff4e14dd2bffccfc1967..fb49691f8fd8098b9b66d3c043c43ed022e66863 100644 (file)
@@ -18,10 +18,8 @@ import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 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';
@@ -38,7 +36,6 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit,
 
   available: boolean = undefined;
   columns: CdTableColumn[];
-  docsUrl: string;
   modalRef: BsModalRef;
   permission: Permission;
   selection = new CdTableSelection();
@@ -63,9 +60,7 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit,
     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
@@ -146,11 +141,7 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit,
           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;
       }
     });
   }
index a529924d93e53b3805fbeebe0b005dba9cecccb9..da0f1a541a14071a98cecdc015fa137207d0e27e 100644 (file)
@@ -16,7 +16,6 @@ export class InventoryComponent implements OnChanges, OnInit {
   icons = Icons;
 
   hasOrchestrator = false;
-  docsUrl: string;
 
   devices: Array<InventoryDevice> = [];
 
index a4eddc43a263e12c74dc133457839644ab2d14bf..b38e540ddf2978c6286fad1f16ec66e91588778d 100644 (file)
@@ -61,7 +61,6 @@ export class OsdFormComponent implements OnInit {
   featureList: OsdFeature[] = [];
 
   hasOrchestrator = false;
-  docsUrl: string;
 
   constructor(
     public actionLabels: ActionLabelsI18n,
index f54c1fe515f554ead4338402f8f7e35dc26cab61..9bb49de583c97f3edee5e4955ddef8fc260e1acd 100644 (file)
@@ -6,10 +6,9 @@
     <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"
@@ -30,9 +28,8 @@
     <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>
index e08e235fe751fcf6f6ebebad99add9f672aeaf97..9b549aca8426f0e4dbc1179285f7b07fcf01a914 100644 (file)
@@ -4,9 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
 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',
@@ -18,9 +16,7 @@ export class MonitoringListComponent implements OnInit {
     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;
@@ -28,8 +24,6 @@ export class MonitoringListComponent implements OnInit {
   isPrometheusConfigured = false;
   isAlertmanagerConfigured = false;
 
-  docsUrl = '';
-
   ngOnInit() {
     this.prometheusService.ifAlertmanagerConfigured(() => {
       this.isAlertmanagerConfigured = true;
@@ -38,11 +32,6 @@ export class MonitoringListComponent implements OnInit {
       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(
index ddb14cca189dcc725183f567197348b91fc60822..8890ff5531ed1a0d95448e88f6cd3fee19724e20 100644 (file)
@@ -31,7 +31,6 @@ export class ServicesComponent extends ListWithDetails implements OnChanges, OnI
 
   checkingOrchestrator = true;
   hasOrchestrator = false;
-  docsUrl: string;
 
   columns: Array<CdTableColumn> = [];
   services: Array<CephServiceSpec> = [];
index dae183db0d6ccbe383cd298618bbdb5a07e8fad6..bcbb88125429d01f42627d1d628118f7ba848112 100644 (file)
@@ -1,6 +1,5 @@
 <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>
index 985b3b3f531de656c49d10b758f53b8e8eca8e02..1b34aaf7df5d039ab7b0a0cd10b7f8dcb111d28d 100644 (file)
@@ -3,34 +3,18 @@ import { ActivatedRoute } from '@angular/router';
 
 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;
     });
index 75a07e47f3be27abc336b20bb77f48590e4c8837..70d577b3456ef3440ea39014bcc96bc08046f150 100644 (file)
                   *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>
index 50616fb2d9408c0314e66918b48ea6cff0fea5ec..8467d46e481b109a408061fe4efd50c32c14abbb 100644 (file)
@@ -18,9 +18,7 @@ import { CdFormGroup } from '../../../shared/forms/cd-form-group';
 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';
 
@@ -59,7 +57,6 @@ export class NfsFormComponent implements OnInit {
 
   action: string;
   resource: string;
-  docsUrl: string;
 
   daemonsSelections: SelectOption[] = [];
   daemonsMessages = new SelectMessages(
@@ -85,8 +82,6 @@ export class NfsFormComponent implements OnInit {
     private router: Router,
     private rgwUserService: RgwUserService,
     private formBuilder: CdFormBuilder,
-    private summaryservice: SummaryService,
-    private cephReleaseNamePipe: CephReleaseNamePipe,
     private taskWrapper: TaskWrapperService,
     private cdRef: ChangeDetectorRef,
     private i18n: I18n,
@@ -122,11 +117,6 @@ export class NfsFormComponent implements OnInit {
       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>[]) {
index 7f0b57bfbf9e5e930e7a1046651f21c2fd399890..398a535bf8c6d4cf842cb82ce4820714a6af4a12 100644 (file)
                     *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"
index bfec49ceadbe8711651e3848faf8a5fccf29a82c..334d1703b0ef22ea4f9d40cb3d2343b1de1d8164 100644 (file)
@@ -1,6 +1,5 @@
 <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>
index 02fbc18cdc6077b55e1323d8d67bcbfd42335b08..77bea30a6be1e6f7f35f7f96664bbbfd0b9003c9 100644 (file)
@@ -1,33 +1,18 @@
 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;
     });
index 122bcfd5943d273e85fdad3f76d3f6e2f900dee8..27dda06bc659fee2ee0bceeff9a27655a0af23dc 100644 (file)
       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
index e6821ddf960ff137a01487745975e21023ecb7ac..5d212d6cac43b1ca1f20c5615876db635a9db0bd 100644 (file)
@@ -3,9 +3,8 @@ import { Component, OnInit, ViewChild } from '@angular/core';
 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({
@@ -21,16 +20,14 @@ export class DashboardHelpComponent implements OnInit {
   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;
     });
   }
 
index 3d08c56c3598dd3e25a6ea4d61650c69e21044d8..811e411340a6b9c1d240b30e9d27aab868bb1ebb 100644 (file)
@@ -21,6 +21,7 @@ import { BackButtonComponent } from './back-button/back-button.component';
 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';
@@ -83,7 +84,8 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
     PwdExpirationNotificationComponent,
     TelemetryNotificationComponent,
     OrchestratorDocPanelComponent,
-    OrchestratorDocModalComponent
+    OrchestratorDocModalComponent,
+    DocComponent
   ],
   providers: [],
   exports: [
@@ -105,7 +107,8 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
     AlertPanelComponent,
     PwdExpirationNotificationComponent,
     TelemetryNotificationComponent,
-    OrchestratorDocPanelComponent
+    OrchestratorDocPanelComponent,
+    DocComponent
   ],
   entryComponents: [
     ModalComponent,
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.html
new file mode 100644 (file)
index 0000000..b90fedc
--- /dev/null
@@ -0,0 +1,2 @@
+<a href="{{ docUrl }}"
+   target="_blank">{{ docText }}</a>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.spec.ts
new file mode 100644 (file)
index 0000000..a0fc059
--- /dev/null
@@ -0,0 +1,27 @@
+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();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.ts
new file mode 100644 (file)
index 0000000..355601a
--- /dev/null
@@ -0,0 +1,25 @@
+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;
+    });
+  }
+}
index d6ac94af42aa46bfde559bf338185de903c374e5..a5a29bfbee8509f47be002b3cc48f5a0cf731f20 100644 (file)
@@ -4,17 +4,13 @@
 
 <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">
index 409f3b8a1a31242ce1e46c332411d39551c24ee3..f6bc6a42f8ac716a0a4d02cd0a92745630bd2de8 100644 (file)
@@ -10,6 +10,7 @@ import { SummaryService } from '../../../shared/services/summary.service';
 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';
 
@@ -18,7 +19,7 @@ describe('GrafanaComponent', () => {
   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]
   });
index afb4136189005b68b0e982cd2aea5952b541ec20..27d300ed9310215d95d6c881ba45e1c36fa11611 100644 (file)
@@ -4,8 +4,6 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
 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({
@@ -38,13 +36,9 @@ export class GrafanaComponent implements OnInit, OnChanges {
   @Input()
   uid: string;
 
-  docsUrl: string;
-
   constructor(
-    private summaryService: SummaryService,
     private sanitizer: DomSanitizer,
     private settingsService: SettingsService,
-    private cephReleaseNamePipe: CephReleaseNamePipe,
     private i18n: I18n
   ) {
     this.grafanaTimes = [
@@ -179,13 +173,6 @@ export class GrafanaComponent implements OnInit, OnChanges {
       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;
index 5159177ab50623b541b7e2946eba68d26cb59d5d..4c7175910b068360a9733c5edcd903b2d93c7c2d 100644 (file)
@@ -1,5 +1,4 @@
 <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>
index d4728bce702367edc7f71457e7ccf93094cf33e4..71c94ec7facdc75b569edec5e1547145bfe32a80 100644 (file)
@@ -1,25 +1,8 @@
-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 {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.spec.ts
new file mode 100644 (file)
index 0000000..131d343
--- /dev/null
@@ -0,0 +1,69 @@
+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);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts
new file mode 100644 (file)
index 0000000..4d3ab0f
--- /dev/null
@@ -0,0 +1,57 @@
+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);
+  }
+}