]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: feature-toggles: add front-end
authorErnesto Puerta <epuertat@redhat.com>
Thu, 31 Jan 2019 16:32:32 +0000 (17:32 +0100)
committerErnesto Puerta <epuertat@redhat.com>
Wed, 6 Feb 2019 17:08:01 +0000 (18:08 +0100)
Add front-end behaviours to feature toggles:
- In navigation pane, drop-down menu items are displayed/hidden accordingly.
- In main dashboard page, info cards are displayed/hidded.
- Routes are also enabled/disabled. When disabled, they redirect to 404.

Fixes: http://tracker.ceph.com/issues/37530
Signed-off-by: Ernesto Puerta <epuertat@redhat.com>
12 files changed:
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/locale/messages.xlf
src/pybind/mgr/dashboard/plugins/feature_toggles.py

index 87664ea740edd50e5330588057dec1134c44b8a1..edfee3dbd10a57a1d9a8d3b7239d607cbbcdfab3 100644 (file)
@@ -35,6 +35,7 @@ import { ForbiddenComponent } from './core/forbidden/forbidden.component';
 import { NotFoundComponent } from './core/not-found/not-found.component';
 import { BreadcrumbsResolver, IBreadcrumb } from './shared/models/breadcrumbs';
 import { AuthGuardService } from './shared/services/auth-guard.service';
+import { FeatureTogglesGuardService } from './shared/services/feature-toggles-guard.service';
 import { ModuleStatusGuardService } from './shared/services/module-status-guard.service';
 
 export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
@@ -152,6 +153,7 @@ const routes: Routes = [
       },
       {
         path: 'rbd',
+        canActivate: [FeatureTogglesGuardService],
         data: { breadcrumbs: 'Images' },
         children: [
           { path: '', component: RbdImagesComponent },
@@ -173,11 +175,13 @@ const routes: Routes = [
       {
         path: 'mirroring',
         component: RbdMirroringComponent,
+        canActivate: [FeatureTogglesGuardService],
         data: { breadcrumbs: 'Mirroring' }
       },
       // iSCSI
       {
         path: 'iscsi',
+        canActivate: [FeatureTogglesGuardService],
         data: { breadcrumbs: 'iSCSI' },
         children: [
           {
@@ -206,7 +210,7 @@ const routes: Routes = [
   {
     path: 'cephfs',
     component: CephfsListComponent,
-    canActivate: [AuthGuardService],
+    canActivate: [FeatureTogglesGuardService, AuthGuardService],
     data: { breadcrumbs: 'Filesystems' }
   },
   // Object Gateway
@@ -218,7 +222,7 @@ const routes: Routes = [
   },
   {
     path: 'rgw',
-    canActivateChild: [ModuleStatusGuardService, AuthGuardService],
+    canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService, AuthGuardService],
     data: {
       moduleStatusGuardConfig: {
         apiPath: 'rgw',
index cde3a60f94013a75c5efb201c1c756c8540e67a1..e9df42074acfc1fa238250d91b94d0c5310d71c6 100644 (file)
@@ -1,3 +1,4 @@
+<ng-container *ngIf="enabled_feature$ | async as enabled_feature">
 <div *ngIf="healthData"
      class="container-fluid">
   <cd-info-group groupTitle="Status"
                   link="/rgw/daemon"
                   class="col-sm-6 col-md-4 col-lg-3"
                   contentClass="content-medium content-highlight"
-                  *ngIf="healthData.rgw != null">
+                  *ngIf="enabled_feature.rgw && healthData.rgw != null">
       {{ healthData.rgw }} total
     </cd-info-card>
     <cd-info-card cardTitle="Metadata Servers"
                   i18n-cardTitle
                   class="col-sm-6 col-md-4 col-lg-3"
-                  *ngIf="(healthData.fs_map | mdsSummary) as transformedResult"
+                  *ngIf="(enabled_feature.cephfs && healthData.fs_map | mdsSummary) as transformedResult"
                   [contentClass]="(transformedResult.length > 1 ? 'text-area-size-2' : '') + ' content-highlight'">
       <span *ngFor="let result of transformedResult"
             [ngClass]="result.class">
                   link="/block/iscsi"
                   class="col-sm-6 col-md-4 col-lg-3"
                   contentClass="content-medium content-highlight"
-                  *ngIf="healthData.iscsi_daemons != null">
+                  *ngIf="enabled_feature.iscsi && healthData.iscsi_daemons != null">
       {{ healthData.iscsi_daemons }} total
     </cd-info-card>
   </cd-info-group>
     </ng-container>
   </ng-template>
 </div>
+</ng-container>
index aee287159ec8ca04fa22b7b28c6d0baf4c9de390..8bb61cf2cd879ab64998079bd9b5c630c3e6c54f 100644 (file)
@@ -11,6 +11,7 @@ import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-h
 import { HealthService } from '../../../shared/api/health.service';
 import { Permissions } from '../../../shared/models/permissions';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
+import { FeatureTogglesService } from '../../../shared/services/feature-toggles.service';
 import { SharedModule } from '../../../shared/shared.module';
 import { PgCategoryService } from '../../shared/pg-category.service';
 import { HealthPieColor } from '../health-pie/health-pie-color.enum';
@@ -45,6 +46,17 @@ describe('HealthComponent', () => {
       return new Permissions({ log: ['read'] });
     }
   };
+  const fakeFeatureTogglesService = {
+    get: () => {
+      return of({
+        rbd: true,
+        mirroring: true,
+        iscsi: true,
+        cephfs: true,
+        rgw: true
+      });
+    }
+  };
 
   configureTestBed({
     imports: [SharedModule, HttpClientTestingModule, PopoverModule.forRoot()],
@@ -60,6 +72,7 @@ describe('HealthComponent', () => {
     providers: [
       i18nProviders,
       { provide: AuthStorageService, useValue: fakeAuthStorageService },
+      { provide: FeatureTogglesService, useValue: fakeFeatureTogglesService },
       PgCategoryService
     ]
   });
index 9d42bc90f5a5572977ac675ea406e0842efb4d9a..713a05ea6c9f8ca3e7cce89ae6e39ab52c9c557e 100644 (file)
@@ -6,6 +6,10 @@ import * as _ from 'lodash';
 import { HealthService } from '../../../shared/api/health.service';
 import { Permissions } from '../../../shared/models/permissions';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
+import {
+  FeatureTogglesMap$,
+  FeatureTogglesService
+} from '../../../shared/services/feature-toggles.service';
 import { PgCategoryService } from '../../shared/pg-category.service';
 import { HealthPieColor } from '../health-pie/health-pie-color.enum';
 
@@ -18,14 +22,17 @@ export class HealthComponent implements OnInit, OnDestroy {
   healthData: any;
   interval: number;
   permissions: Permissions;
+  enabled_feature$: FeatureTogglesMap$;
 
   constructor(
     private healthService: HealthService,
     private i18n: I18n,
     private authStorageService: AuthStorageService,
-    private pgCategoryService: PgCategoryService
+    private pgCategoryService: PgCategoryService,
+    private featureToggles: FeatureTogglesService
   ) {
     this.permissions = this.authStorageService.getPermissions();
+    this.enabled_feature$ = this.featureToggles.get();
   }
 
   ngOnInit() {
index 7083d73690ac8c5bb7baba59e7025c4e6bc65b1a..307966699c621dd3784155b399296da9a4746ae5 100644 (file)
@@ -1,3 +1,4 @@
+<ng-container *ngIf="enabled_feature$ | async as enabled_feature">
 <nav class="navbar navbar-default navbar-main">
   <!-- Brand and toggle get grouped for better mobile display -->
 
       <li dropdown
           routerLinkActive="active"
           class="dropdown tc_menuitem tc_menuitem_block"
-          *ngIf="permissions.rbdImage.read || permissions.rbdMirroring.read || permissions.iscsi.read">
+          *ngIf="
+          (enabled_feature.rbd || enabled_feature.mirroring || enabled_feature.iscsi) &&
+          (permissions.rbdImage.read || permissions.rbdMirroring.read || permissions.iscsi.read)">
         <a dropdownToggle
            class="dropdown-toggle"
            data-toggle="dropdown"
 
         <ul class="dropdown-menu">
           <li routerLinkActive="active"
-              *ngIf="permissions.rbdImage.read">
+              *ngIf="enabled_feature.rbd && permissions.rbdImage.read">
             <a i18n
                class="dropdown-item"
                routerLink="/block/rbd">Images</a>
 
           <li routerLinkActive="active"
               class="tc_submenuitem tc_submenuitem_block_mirroring"
-              *ngIf="permissions.rbdMirroring.read">
+              *ngIf="enabled_feature.mirroring && permissions.rbdMirroring.read">
             <a class="dropdown-item"
                routerLink="/block/mirroring">
               <ng-container i18n>Mirroring</ng-container>
           </li>
 
           <li routerLinkActive="active"
-              *ngIf="permissions.iscsi.read">
+              *ngIf="enabled_feature.iscsi && permissions.iscsi.read">
             <a i18n
                class="dropdown-item"
                routerLink="/block/iscsi">iSCSI</a>
       <!-- Filesystem -->
       <li routerLinkActive="active"
           class="tc_menuitem tc_menuitem_cephs"
-          *ngIf="permissions.cephfs.read">
+          *ngIf="enabled_feature.cephfs && permissions.cephfs.read">
         <a i18n
            routerLink="/cephfs">Filesystems</a>
       </li>
       <li dropdown
           routerLinkActive="active"
           class="dropdown tc_menuitem tc_menuitem_rgw"
-          *ngIf="permissions.rgw.read">
+          *ngIf="enabled_feature.rgw && permissions.rgw.read">
         <a dropdownToggle
            class="dropdown-toggle"
            data-toggle="dropdown">
   </div>
   <!-- /.navbar-collapse -->
 </nav>
+</ng-container>
index 4c304646a5737f7ee7da271e9a364635bda2a098..018df3f14bdd04f81bd81334135c8eefbcaa57f6 100644 (file)
@@ -3,6 +3,10 @@ import { Component, OnInit } from '@angular/core';
 import { PrometheusService } from '../../../shared/api/prometheus.service';
 import { Permissions } from '../../../shared/models/permissions';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
+import {
+  FeatureTogglesMap$,
+  FeatureTogglesService
+} from '../../../shared/services/feature-toggles.service';
 import { SummaryService } from '../../../shared/services/summary.service';
 
 @Component({
@@ -16,13 +20,16 @@ export class NavigationComponent implements OnInit {
 
   isCollapsed = true;
   prometheusConfigured = false;
+  enabled_feature$: FeatureTogglesMap$;
 
   constructor(
     private authStorageService: AuthStorageService,
     private prometheusService: PrometheusService,
-    private summaryService: SummaryService
+    private summaryService: SummaryService,
+    private featureToggles: FeatureTogglesService
   ) {
     this.permissions = this.authStorageService.getPermissions();
+    this.enabled_feature$ = this.featureToggles.get();
   }
 
   ngOnInit() {
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.spec.ts
new file mode 100644 (file)
index 0000000..980b59d
--- /dev/null
@@ -0,0 +1,72 @@
+import { Component, NgZone } from '@angular/core';
+import { fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { ActivatedRouteSnapshot, Router, Routes } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+import { of as observableOf } from 'rxjs';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+
+import { FeatureTogglesGuardService } from './feature-toggles-guard.service';
+import { FeatureTogglesService } from './feature-toggles.service';
+
+describe('FeatureTogglesGuardService', () => {
+  let service: FeatureTogglesGuardService;
+  let fakeFeatureTogglesService: FeatureTogglesService;
+  let router: Router;
+  let ngZone: NgZone;
+
+  @Component({ selector: 'cd-cephfs', template: '' })
+  class CephfsComponent {}
+
+  @Component({ selector: 'cd-404', template: '' })
+  class NotFoundComponent {}
+
+  const routes: Routes = [
+    { path: 'cephfs', component: CephfsComponent },
+    { path: '404', component: NotFoundComponent }
+  ];
+
+  configureTestBed({
+    imports: [RouterTestingModule.withRoutes(routes)],
+    providers: [
+      { provide: FeatureTogglesService, useValue: { get: null } },
+      FeatureTogglesGuardService
+    ],
+    declarations: [CephfsComponent, NotFoundComponent]
+  });
+
+  beforeEach(() => {
+    service = TestBed.get(FeatureTogglesGuardService);
+    fakeFeatureTogglesService = TestBed.get(FeatureTogglesService);
+    ngZone = TestBed.get(NgZone);
+    router = TestBed.get(Router);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  function testCanActivate(path, feature_toggles_map) {
+    let result: boolean;
+    spyOn(fakeFeatureTogglesService, 'get').and.returnValue(observableOf(feature_toggles_map));
+
+    ngZone.run(() => {
+      service
+        .canActivate(<ActivatedRouteSnapshot>{ routeConfig: { path: path } }, null)
+        .subscribe((val) => (result = val));
+    });
+    tick();
+
+    return result;
+  }
+
+  it('should allow the feature if enabled', fakeAsync(() => {
+    expect(testCanActivate('cephfs', { cephfs: true })).toBe(true);
+    expect(router.url).toBe('/');
+  }));
+
+  it('should redirect to 404 if disable', fakeAsync(() => {
+    expect(testCanActivate('cephfs', { cephfs: false })).toBe(false);
+    expect(router.url).toBe('/404');
+  }));
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.ts
new file mode 100644 (file)
index 0000000..9114ffd
--- /dev/null
@@ -0,0 +1,36 @@
+import { Injectable } from '@angular/core';
+import {
+  ActivatedRouteSnapshot,
+  CanActivate,
+  CanActivateChild,
+  Router,
+  RouterStateSnapshot
+} from '@angular/router';
+
+import { map } from 'rxjs/operators';
+
+import { FeatureTogglesMap, FeatureTogglesService } from './feature-toggles.service';
+import { ServicesModule } from './services.module';
+
+@Injectable({
+  providedIn: ServicesModule
+})
+export class FeatureTogglesGuardService implements CanActivate, CanActivateChild {
+  constructor(private router: Router, private featureToggles: FeatureTogglesService) {}
+
+  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+    return this.featureToggles.get().pipe(
+      map((enabledFeatures: FeatureTogglesMap) => {
+        if (enabledFeatures[route.routeConfig.path] === false) {
+          this.router.navigate(['404']);
+          return false;
+        }
+        return true;
+      })
+    );
+  }
+
+  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+    return this.canActivate(route.parent, state);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.spec.ts
new file mode 100644 (file)
index 0000000..486ca59
--- /dev/null
@@ -0,0 +1,55 @@
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+
+import { FeatureTogglesService } from './feature-toggles.service';
+
+describe('FeatureTogglesService', () => {
+  let httpTesting: HttpTestingController;
+  let service: FeatureTogglesService;
+
+  configureTestBed({
+    providers: [FeatureTogglesService],
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    service = TestBed.get(FeatureTogglesService);
+    httpTesting = TestBed.get(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpTesting.verify();
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  it('should fetch HTTP endpoint once and only once', fakeAsync(() => {
+    const mockFeatureTogglesMap = [
+      {
+        rbd: true,
+        mirroring: true,
+        iscsi: true,
+        cephfs: true,
+        rgw: true
+      }
+    ];
+
+    service
+      .get()
+      .subscribe((featureTogglesMap) => expect(featureTogglesMap).toEqual(mockFeatureTogglesMap));
+    tick();
+
+    // Second subscription shouldn't trigger a new HTTP request
+    service
+      .get()
+      .subscribe((featureTogglesMap) => expect(featureTogglesMap).toEqual(mockFeatureTogglesMap));
+
+    const req = httpTesting.expectOne(service.API_URL);
+    req.flush(mockFeatureTogglesMap);
+    discardPeriodicTasks();
+  }));
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts
new file mode 100644 (file)
index 0000000..fbd435f
--- /dev/null
@@ -0,0 +1,29 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+
+import { Observable, timer } from 'rxjs';
+import { flatMap, shareReplay } from 'rxjs/operators';
+import { ServicesModule } from './services.module';
+
+export type FeatureTogglesMap = Map<string, boolean>;
+export type FeatureTogglesMap$ = Observable<FeatureTogglesMap>;
+
+@Injectable({
+  providedIn: ServicesModule
+})
+export class FeatureTogglesService {
+  readonly API_URL: string = 'api/feature_toggles';
+  readonly REFRESH_INTERVAL: number = 20000;
+  private featureToggleMap$: FeatureTogglesMap$;
+
+  constructor(private http: HttpClient) {
+    this.featureToggleMap$ = timer(0, this.REFRESH_INTERVAL).pipe(
+      flatMap(() => this.http.get<FeatureTogglesMap>(this.API_URL)),
+      shareReplay(1)
+    );
+  }
+
+  get(): FeatureTogglesMap$ {
+    return this.featureToggleMap$;
+  }
+}
index 666cc701a5866c551b713cf163f662e575372de8..6ddc58e45d3c558a9d7e23d58a2ebd91ded5518f 100644 (file)
@@ -6,79 +6,79 @@
         <source>Toggle navigation</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit><trans-unit id="f65253954b66e929a8b4d5ecaf61f9129f8cec64" datatype="html">
         <source>Dashboard</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">34</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit><trans-unit id="f4d1dd59b039ad818d9da7e29a773e10e41d9821" datatype="html">
         <source>Cluster</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">46</context>
+          <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit><trans-unit id="099b441d49333b3c6d30b36dc0a4763e64c78920" datatype="html">
         <source>Hosts</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">56</context>
+          <context context-type="linenumber">57</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">81</context>
+          <context context-type="linenumber">82</context>
         </context-group>
       </trans-unit><trans-unit id="624f596cc3320f5e0a0d7c7346c364e5af9bdd8c" datatype="html">
         <source>Monitors</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">63</context>
+          <context context-type="linenumber">64</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">48</context>
+          <context context-type="linenumber">49</context>
         </context-group>
       </trans-unit><trans-unit id="1a9183778f2c6473d7ccb080f651caa01faaf70c" datatype="html">
         <source>OSDs</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">71</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit><trans-unit id="4a41f824a35ba01d5bd7be61aa06b3e8145209d0" datatype="html">
         <source>Configuration</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">77</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit><trans-unit id="8c95898abff46bfac3ed6eb2afef74597e60b15c" datatype="html">
         <source>CRUSH map</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">84</context>
+          <context context-type="linenumber">85</context>
         </context-group>
       </trans-unit><trans-unit id="eb3d5aefff38a814b76da74371cbf02c0789a1ef" datatype="html">
         <source>Logs</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit><trans-unit id="9fe218829514884cdd0ca2300573a4e0428c324f" datatype="html">
         <source>Alerts</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">97</context>
+          <context context-type="linenumber">98</context>
         </context-group>
       </trans-unit><trans-unit id="92899fa68e8ca108912163ff58edc8540e453787" datatype="html">
         <source>Pools</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">107</context>
+          <context context-type="linenumber">108</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/block/mirroring/overview/overview.component.html</context>
@@ -86,7 +86,7 @@
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cephfs/cephfs-detail/cephfs-detail.component.html</context>
         <source>Block</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">119</context>
+          <context context-type="linenumber">122</context>
         </context-group>
       </trans-unit><trans-unit id="b73f7f5060fb22a1e9ec462b1bb02493fa3ab866" datatype="html">
         <source>Images</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">128</context>
+          <context context-type="linenumber">131</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/block/iscsi-target-form/iscsi-target-form.component.html</context>
         <source>Mirroring</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">136</context>
+          <context context-type="linenumber">139</context>
         </context-group>
       </trans-unit><trans-unit id="811c241d56601b91ef26735b770e64428089b950" datatype="html">
         <source>iSCSI</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">148</context>
+          <context context-type="linenumber">151</context>
         </context-group>
       </trans-unit><trans-unit id="a4eff72d97b7ced051398d581f10968218057ddc" datatype="html">
         <source>Filesystems</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">162</context>
         </context-group>
       </trans-unit><trans-unit id="2190548d236ca5f7bc7ab2bca334b860c5ff2ad4" datatype="html">
         <source>Object Gateway</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">170</context>
+          <context context-type="linenumber">173</context>
         </context-group>
       </trans-unit><trans-unit id="9e24f9e2d42104ffc01599db4d566d1cc518f9e6" datatype="html">
         <source>Daemons</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">179</context>
+          <context context-type="linenumber">182</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/block/iscsi/iscsi.component.html</context>
         <source>Users</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">185</context>
+          <context context-type="linenumber">188</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/auth/user-tabs/user-tabs.component.html</context>
         <source>Buckets</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
-          <context context-type="linenumber">191</context>
+          <context context-type="linenumber">194</context>
         </context-group>
       </trans-unit><trans-unit id="797f8214e8148f4bf0d244baaa7341706b419549" datatype="html">
         <source>Retrieving data<x id="START_TAG_SPAN_1" ctype="x-span" equiv-text="&lt;span&gt;"/> for
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">4</context>
         </context-group>
       </trans-unit><trans-unit id="57ec6032f5618d4a9f16eb950ad23d2ce7c24b54" datatype="html">
         <source>Cluster ID</source>
         <source>Cluster Status</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit><trans-unit id="1d4bc612bbf19aa9553853266b9e92c9d75f4464" datatype="html">
         <source>Manager Daemons</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">69</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit><trans-unit id="946ac5dea9921dc09d7b0a63b89535371f283b19" datatype="html">
         <source>Object Gateways</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">90</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit><trans-unit id="ff03fa5bcf37c4da46ad736c1f7d03f959e8ba9a" datatype="html">
         <source>Metadata Servers</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">98</context>
+          <context context-type="linenumber">99</context>
         </context-group>
       </trans-unit><trans-unit id="d817609ba4993eba859409ab71e566168f4d5f5a" datatype="html">
         <source>iSCSI Gateways</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">109</context>
+          <context context-type="linenumber">110</context>
         </context-group>
       </trans-unit><trans-unit id="42c13e50391250ea9379bdf55d5d6c0228c0c8bc" datatype="html">
         <source>Client IOPS</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">125</context>
+          <context context-type="linenumber">126</context>
         </context-group>
       </trans-unit><trans-unit id="52213660b2454d139ada3079a42ec6caf3c3c01e" datatype="html">
         <source>Client Throughput</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">134</context>
+          <context context-type="linenumber">135</context>
         </context-group>
       </trans-unit><trans-unit id="32efd1c3f70e3c5244239de97a2cc95d98534a14" datatype="html">
         <source>Client Read/Write</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">143</context>
+          <context context-type="linenumber">144</context>
         </context-group>
       </trans-unit><trans-unit id="5277e7546d03a767761199b70deb8c77a921b390" datatype="html">
         <source>Client Recovery</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">162</context>
         </context-group>
       </trans-unit><trans-unit id="6d9a9f55046891733ef71170e7652063765eb542" datatype="html">
         <source>Scrub</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">170</context>
+          <context context-type="linenumber">171</context>
         </context-group>
       </trans-unit><trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html">
         <source>Performance</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">119</context>
+          <context context-type="linenumber">120</context>
         </context-group>
       </trans-unit><trans-unit id="88f383269db2d32cccee9e936fe549dccb9fdbf4" datatype="html">
         <source>Raw Capacity</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">200</context>
+          <context context-type="linenumber">201</context>
         </context-group>
       </trans-unit><trans-unit id="afdb601c16162f2c798b16a2920955f1cc6a20aa" datatype="html">
         <source>Objects</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">213</context>
+          <context context-type="linenumber">214</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/block/rbd-details/rbd-details.component.html</context>
         <source>PGs per OSD</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">222</context>
+          <context context-type="linenumber">223</context>
         </context-group>
       </trans-unit><trans-unit id="498a109c6e9e94f1966de01aa0326f7f0ac6fb52" datatype="html">
         <source>PG Status</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">231</context>
+          <context context-type="linenumber">232</context>
         </context-group>
       </trans-unit><trans-unit id="ce9dfdc6dccb28dc75a78c704e09dc18fb02dcfa" datatype="html">
         <source>Capacity</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">182</context>
         </context-group>
       </trans-unit><trans-unit id="44ecac93d67c6a671198091c2270354f80322327" datatype="html">
         <source><x id="START_ITALIC_TEXT" ctype="x-i" equiv-text="&lt;i&gt;"/><x id="CLOSE_ITALIC_TEXT" ctype="x-i" equiv-text="&lt;/i&gt;"/> See <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Logs<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> for more details.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/dashboard/health/health.component.html</context>
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">266</context>
         </context-group>
       </trans-unit><trans-unit id="f0b5d789d42c0e69348e5fe0037fcbf5b5fbbdcc" datatype="html">
         <source>Move an image to trash</source>
index 5174f3f6d8eb7ae002ae6b6f774f649fba5ef616..6c5a8ad109fb284ab3ca00e73653fd66362da067 100644 (file)
@@ -62,9 +62,9 @@ except ImportError:
 
 
 class Features(Enum):
-    RBD_IMAGES = 'rbd_images'
-    RBD_MIRRORING = 'rbd_mirroring'
-    RBD_ISCSI = 'rbd_iscsi'
+    RBD = 'rbd'
+    MIRRORING = 'mirroring'
+    ISCSI = 'iscsi'
     CEPHFS = 'cephfs'
     RGW = 'rgw'
 
@@ -78,9 +78,9 @@ class Actions(Enum):
 PREDISABLED_FEATURES = set()
 
 Feature2Endpoint = {
-    Features.RBD_IMAGES: ["/api/block/image"],
-    Features.RBD_MIRRORING: ["/api/block/mirroring"],
-    Features.RBD_ISCSI: ["/api/tcmuiscsi"],
+    Features.RBD: ["/api/block/image"],
+    Features.MIRRORING: ["/api/block/mirroring"],
+    Features.ISCSI: ["/api/tcmuiscsi"],
     Features.CEPHFS: ["/api/cephfs"],
     Features.RGW: ["/api/rgw"],
 }