]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: add ceph configuration documentation page
authorTiago Melo <tmelo@suse.com>
Fri, 16 Feb 2018 17:17:20 +0000 (17:17 +0000)
committerRicardo Dias <rdias@suse.com>
Mon, 5 Mar 2018 13:07:15 +0000 (13:07 +0000)
Signed-off-by: Tiago Melo <tmelo@suse.com>
15 files changed:
src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/filter.pipe.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/filter.pipe.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/pipes.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/configuration.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/configuration.service.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/services.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/shared.module.ts

index 822159985eb3fa16a25674e7c1babd6f2b8f710d..9e9ae715abc0681c6fbb7d4ff27377f9985a0baf 100644 (file)
@@ -5,6 +5,7 @@ import { IscsiComponent } from './ceph/block/iscsi/iscsi.component';
 import { PoolDetailComponent } from './ceph/block/pool-detail/pool-detail.component';
 import { CephfsComponent } from './ceph/cephfs/cephfs/cephfs.component';
 import { ClientsComponent } from './ceph/cephfs/clients/clients.component';
+import { ConfigurationComponent } from './ceph/cluster/configuration/configuration.component';
 import { HostsComponent } from './ceph/cluster/hosts/hosts.component';
 import { MonitorComponent } from './ceph/cluster/monitor/monitor.component';
 import { DashboardComponent } from './ceph/dashboard/dashboard/dashboard.component';
@@ -37,6 +38,7 @@ const routes: Routes = [
   { path: 'monitor', component: MonitorComponent, canActivate: [AuthGuardService] },
   { path: 'cephfs/:id/clients', component: ClientsComponent, canActivate: [AuthGuardService] },
   { path: 'cephfs/:id', component: CephfsComponent, canActivate: [AuthGuardService] },
+  { path: 'configuration', component: ConfigurationComponent, canActivate: [AuthGuardService] },
   { path: '404', component: NotFoundComponent },
   { path: '**', redirectTo: '/404'}
 ];
index e9e278a59b08ada56eee61f3aabf91f6a8988558..7509789a3b91c31294e68ed78200bf28b0ee5589 100644 (file)
@@ -1,6 +1,7 @@
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 
+import { SharedModule } from '../shared/shared.module';
 import { BlockModule } from './block/block.module';
 import { CephfsModule } from './cephfs/cephfs.module';
 import { ClusterModule } from './cluster/cluster.module';
@@ -14,7 +15,8 @@ import { RgwModule } from './rgw/rgw.module';
     DashboardModule,
     RgwModule,
     BlockModule,
-    CephfsModule
+    CephfsModule,
+    SharedModule
   ],
   declarations: []
 })
index 9992bfac456a166987d3afdfbb8d76a65517c4a5..dad1d6c59b655ad944c6633db115e142d419ea74 100644 (file)
@@ -1,9 +1,11 @@
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
 import { ComponentsModule } from '../../shared/components/components.module';
 import { SharedModule } from '../../shared/shared.module';
+import { ConfigurationComponent } from './configuration/configuration.component';
 import { HostsComponent } from './hosts/hosts.component';
 import { MonitorService } from './monitor.service';
 import { MonitorComponent } from './monitor/monitor.component';
@@ -13,11 +15,13 @@ import { MonitorComponent } from './monitor/monitor.component';
     CommonModule,
     ComponentsModule,
     SharedModule,
-    RouterModule
+    RouterModule,
+    FormsModule
   ],
   declarations: [
     HostsComponent,
     MonitorComponent,
+    ConfigurationComponent
   ],
   providers: [
     MonitorService
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.html
new file mode 100644 (file)
index 0000000..efe071a
--- /dev/null
@@ -0,0 +1,67 @@
+<nav aria-label="breadcrumb">
+  <ol class="breadcrumb">
+    <li class="breadcrumb-item">Cluster</li>
+    <li class="breadcrumb-item active"
+        aria-current="page">Configuration Documentation</li>
+  </ol>
+</nav>
+
+<div class="dataTables_wrapper">
+  <div class="dataTables_header clearfix form-inline">
+    <!-- filters -->
+    <div class="form-group pull-right filter"
+         *ngFor="let filter of filters">
+      <label>{{ filter.label }}: </label>
+      <select class="form-control input-sm"
+              [(ngModel)]="filter.value"
+              (ngModelChange)="updateFilter()">
+        <option *ngFor="let opt of filter.options">{{ opt }}</option>
+      </select>
+    </div>
+    <!-- end filters -->
+  </div>
+
+  <table class="oadatatable table table-striped table-condensed table-bordered table-hover">
+    <thead class="datatable-header">
+      <tr>
+        <th >Name</th>
+        <th style="width:400px;">Description</th>
+        <th>Type</th>
+        <th>Level</th>
+        <th style="width: 200px">Default</th>
+        <th>Tags</th>
+        <th>Services</th>
+        <th>See_also</th>
+        <th>Max</th>
+        <th>Min</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr *ngFor="let row of data | filter:filters">
+        <td >{{ row.name }}</td>
+        <td>
+          <p>
+            {{ row.desc }}</p>
+          <p *ngIf="row.long_desc"
+             class=text-muted>{{ row.long_desc }}</p>
+        </td>
+        <td>{{ row.type }}</td>
+        <td>{{ row.level }}</td>
+        <td class="wrap">
+          {{ row.default }} {{ row.daemon_default }}
+        </td>
+        <td>
+          <p *ngFor="let item of row.tags">{{ item }}</p>
+        </td>
+        <td>
+          <p *ngFor="let item of row.services">{{ item }}</p>
+        </td>
+        <td class="wrap">
+          <p *ngFor="let item of row.see_also">{{ item }}</p>
+        </td>
+        <td>{{ row.max }}</td>
+        <td>{{ row.min }}</td>
+      </tr>
+    </tbody>
+  </table>
+</div>
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.scss
new file mode 100644 (file)
index 0000000..e968d6d
--- /dev/null
@@ -0,0 +1,5 @@
+@import '../../../shared/datatable/table/table.component.scss';
+
+td.wrap {
+  word-break: break-all;
+}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.spec.ts
new file mode 100644 (file)
index 0000000..0d98766
--- /dev/null
@@ -0,0 +1,41 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule } from '@angular/forms';
+
+import { Observable } from 'rxjs/Observable';
+
+import { ConfigurationService } from '../../../shared/services/configuration.service';
+import { SharedModule } from '../../../shared/shared.module';
+import { ConfigurationComponent } from './configuration.component';
+
+describe('ConfigurationComponent', () => {
+  let component: ConfigurationComponent;
+  let fixture: ComponentFixture<ConfigurationComponent>;
+
+  const fakeService = {
+    getConfigData: () => {
+      return Observable.create(observer => {
+        return () => console.log('disposed');
+      });
+    }
+  };
+
+  beforeEach(
+    async(() => {
+      TestBed.configureTestingModule({
+        declarations: [ConfigurationComponent],
+        providers: [{ provide: ConfigurationService, useValue: fakeService }],
+        imports: [SharedModule, FormsModule]
+      }).compileComponents();
+    })
+  );
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ConfigurationComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/configuration/configuration.component.ts
new file mode 100644 (file)
index 0000000..d6112e7
--- /dev/null
@@ -0,0 +1,79 @@
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
+
+import { ConfigurationService } from '../../../shared/services/configuration.service';
+
+@Component({
+  selector: 'cd-configuration',
+  templateUrl: './configuration.component.html',
+  styleUrls: ['./configuration.component.scss']
+})
+export class ConfigurationComponent implements OnInit {
+  @ViewChild('arrayTmpl') arrayTmpl: TemplateRef<any>;
+
+  data = [];
+  columns: any;
+
+  filters = [
+    {
+      label: 'Level',
+      prop: 'level',
+      value: 'basic',
+      options: ['basic', 'advanced', 'developer'],
+      applyFilter: (row, value) => {
+        enum Level {
+          basic = 0,
+          advanced = 1,
+          developer = 2
+        }
+
+        const levelVal = Level[value];
+
+        return Level[row.level] <= levelVal;
+      }
+    },
+    {
+      label: 'Service',
+      prop: 'services',
+      value: 'any',
+      options: ['mon', 'mgr', 'osd', 'mds', 'common', 'mds_client', 'rgw', 'any'],
+      applyFilter: (row, value) => {
+        if (value === 'any') {
+          return true;
+        }
+
+        return row.services.includes(value);
+      }
+    }
+  ];
+
+  constructor(private configurationService: ConfigurationService) {}
+
+  ngOnInit() {
+    this.columns = [
+      { flexGrow: 2, canAutoResize: true, prop: 'name' },
+      { flexGrow: 2, prop: 'desc', name: 'Description' },
+      { flexGrow: 2, prop: 'long_desc', name: 'Long description' },
+      { flexGrow: 1, prop: 'type' },
+      { flexGrow: 1, prop: 'level' },
+      { flexGrow: 1, prop: 'default' },
+      { flexGrow: 2, prop: 'daemon_default', name: 'Daemon default' },
+      { flexGrow: 1, prop: 'tags', name: 'Tags', cellTemplate: this.arrayTmpl },
+      { flexGrow: 1, prop: 'services', name: 'Services', cellTemplate: this.arrayTmpl },
+      { flexGrow: 1, prop: 'see_also', name: 'See_also', cellTemplate: this.arrayTmpl },
+      { flexGrow: 1, prop: 'max', name: 'Max' },
+      { flexGrow: 1, prop: 'min', name: 'Min' }
+    ];
+
+    this.fetchData();
+  }
+
+  fetchData() {
+    this.configurationService.getConfigData().subscribe((data: any) => {
+      this.data = data;
+    });
+  }
+
+  updateFilter() {
+    this.data = [...this.data];
+  }
+}
index ae50c267c87034dfd0c55fa50848ef55e08739b7..6b33769a2f4a1922e2e3a45763224fcc18f54f77 100644 (file)
                routerLink="/monitor/"> Monitors
             </a>
           </li>
+
+          <li routerLinkActive="active"
+              class="tc_submenuitem tc_submenuitem_configuration">
+            <a i18n
+               class="dropdown-item"
+               routerLink="/configuration">Configuration Doc.
+            </a>
+          </li>
         </ul>
       </li>
 
@@ -85,7 +93,8 @@
                routerLink="/block/iscsi">iSCSI</a>
           </li>
           <li class="dropdown-submenu">
-            <a class="dropdown-toggle" data-toggle="dropdown">Pools</a>
+            <a class="dropdown-toggle"
+               data-toggle="dropdown">Pools</a>
             <ul *dropdownMenu
                 class="dropdown-menu">
               <li routerLinkActive="active"
               </li>
               <li class="tc_submenuitem tc_submenuitem_cephfs_nofs"
                   *ngIf="rbdPools.length === 0">
-                <a class="dropdown-item disabled" i18n>There are no pools</a>
+                <a class="dropdown-item disabled"
+                   i18n>There are no pools</a>
               </li>
             </ul>
           </li>
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/filter.pipe.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/filter.pipe.spec.ts
new file mode 100644 (file)
index 0000000..1427de3
--- /dev/null
@@ -0,0 +1,8 @@
+import { FilterPipe } from './filter.pipe';
+
+describe('FilterPipe', () => {
+  it('create an instance', () => {
+    const pipe = new FilterPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/filter.pipe.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/filter.pipe.ts
new file mode 100644 (file)
index 0000000..21115a7
--- /dev/null
@@ -0,0 +1,25 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'filter'
+})
+export class FilterPipe implements PipeTransform {
+  transform(value: any, args?: any): any {
+    return value.filter(row => {
+      let result = true;
+
+      args.forEach(filter => {
+        if (!filter.value) {
+          return;
+        }
+
+        result = result && filter.applyFilter(row, filter.value);
+        if (!result) {
+          return result;
+        }
+      });
+
+      return result;
+    });
+  }
+}
index 0d59e4e92dd3707fc3cfa59a4b5041844a56bf56..51dc736c8a694d8e416ca4323c7560a921f00af6 100644 (file)
@@ -4,6 +4,7 @@ import { NgModule } from '@angular/core';
 import { CephShortVersionPipe } from './ceph-short-version.pipe';
 import { DimlessBinaryPipe } from './dimless-binary.pipe';
 import { DimlessPipe } from './dimless.pipe';
+import { FilterPipe } from './filter.pipe';
 import { HealthColorPipe } from './health-color.pipe';
 import { ListPipe } from './list.pipe';
 import { RelativeDatePipe } from './relative-date.pipe';
@@ -16,7 +17,8 @@ import { RelativeDatePipe } from './relative-date.pipe';
     DimlessPipe,
     CephShortVersionPipe,
     RelativeDatePipe,
-    ListPipe
+    ListPipe,
+    FilterPipe
   ],
   exports: [
     DimlessBinaryPipe,
@@ -24,7 +26,8 @@ import { RelativeDatePipe } from './relative-date.pipe';
     DimlessPipe,
     CephShortVersionPipe,
     RelativeDatePipe,
-    ListPipe
+    ListPipe,
+    FilterPipe
   ],
   providers: [
     CephShortVersionPipe,
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/configuration.service.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/configuration.service.spec.ts
new file mode 100644 (file)
index 0000000..dcb5a9e
--- /dev/null
@@ -0,0 +1,21 @@
+import { HttpClientModule } from '@angular/common/http';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { inject, TestBed } from '@angular/core/testing';
+
+import { ConfigurationService } from './configuration.service';
+
+describe('ConfigurationService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [ConfigurationService],
+      imports: [HttpClientTestingModule, HttpClientModule]
+    });
+  });
+
+  it(
+    'should be created',
+    inject([ConfigurationService], (service: ConfigurationService) => {
+      expect(service).toBeTruthy();
+    })
+  );
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/configuration.service.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/configuration.service.ts
new file mode 100644 (file)
index 0000000..80b04aa
--- /dev/null
@@ -0,0 +1,11 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class ConfigurationService {
+  constructor(private http: HttpClient) {}
+
+  getConfigData() {
+    return this.http.get('/api/cluster_conf/');
+  }
+}
index 71adc6212ad7ac0789bdc933400346c52facfa2f..2901b4b90e61b74c6d7164200f7ba07a2a5a9870 100644 (file)
@@ -1,6 +1,7 @@
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 
+import { ConfigurationService } from './configuration.service';
 import { FormatterService } from './formatter.service';
 import { TcmuIscsiService } from './tcmu-iscsi.service';
 import { TopLevelService } from './top-level.service';
@@ -10,6 +11,6 @@ import { TopLevelService } from './top-level.service';
     CommonModule
   ],
   declarations: [],
-  providers: [FormatterService, TopLevelService, TcmuIscsiService]
+  providers: [FormatterService, TopLevelService, TcmuIscsiService, ConfigurationService]
 })
 export class ServicesModule { }
index 678c1b40524264f487d239804daf1f50cdcfd347..4e453253427fdee397226e641d551c79e2f43d0b 100644 (file)
@@ -26,7 +26,6 @@ import { ServicesModule } from './services/services.module';
     ComponentsModule,
     ServicesModule,
     PasswordButtonDirective,
-    ComponentsModule,
     DataTableModule
   ],
   declarations: [