]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: Add iSCSI page
authorRicardo Marques <rimarques@suse.com>
Fri, 16 Feb 2018 11:48:20 +0000 (11:48 +0000)
committerRicardo Dias <rdias@suse.com>
Mon, 5 Mar 2018 13:07:11 +0000 (13:07 +0000)
Signed-off-by: Ricardo Marques <rimarques@suse.com>
16 files changed:
src/pybind/mgr/dashboard_v2/frontend/package.json
src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/block.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.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/list.pipe.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/list.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/pipes/relative-date.pipe.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/relative-date.pipe.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/formatter.service.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/services.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/tcmu-iscsi.service.ts [new file with mode: 0644]

index 4f478f375d5c14943f503d09d37e9bc9d59ee073..e173870909fbecdc794d8d4cfa9547574995e025 100644 (file)
     "@angular/platform-browser": "^5.0.0",
     "@angular/platform-browser-dynamic": "^5.0.0",
     "@angular/router": "^5.0.0",
+    "@swimlane/ngx-datatable": "^11.1.7",
     "@types/lodash": "^4.14.95",
     "awesome-bootstrap-checkbox": "0.3.7",
-    "@swimlane/ngx-datatable": "^11.1.7",
     "bootstrap": "^3.3.7",
     "chart.js": "^2.7.1",
     "core-js": "^2.4.1",
     "font-awesome": "4.7.0",
     "lodash": "^4.17.4",
+    "moment": "2.20.1",
     "ng2-charts": "^1.6.0",
     "ng2-toastr": "4.1.2",
     "ngx-bootstrap": "^2.0.1",
index 143ef057f2baed2dda7267b9699551db1c32d28f..822159985eb3fa16a25674e7c1babd6f2b8f710d 100644 (file)
@@ -1,6 +1,7 @@
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
 
+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';
@@ -26,6 +27,7 @@ const routes: Routes = [
     component: RgwDaemonListComponent,
     canActivate: [AuthGuardService]
   },
+  { path: 'block/iscsi', component: IscsiComponent, canActivate: [AuthGuardService] },
   { path: 'block/pool/:name', component: PoolDetailComponent, canActivate: [AuthGuardService] },
   {
     path: 'perf_counters/:type/:id',
index 601b22f142e181fe7d339d59890c829e75da124f..b1c71c52a7cdf024430f194f78c0865a56126d79 100644 (file)
@@ -6,7 +6,9 @@ import { TabsModule } from 'ngx-bootstrap';
 
 import { ComponentsModule } from '../../shared/components/components.module';
 import { PipesModule } from '../../shared/pipes/pipes.module';
+import { ServicesModule } from '../../shared/services/services.module';
 import { SharedModule } from '../../shared/shared.module';
+import { IscsiComponent } from './iscsi/iscsi.component';
 import { PoolDetailComponent } from './pool-detail/pool-detail.component';
 
 @NgModule({
@@ -16,8 +18,12 @@ import { PoolDetailComponent } from './pool-detail/pool-detail.component';
     TabsModule.forRoot(),
     SharedModule,
     ComponentsModule,
-    PipesModule
+    PipesModule,
+    ServicesModule
   ],
-  declarations: [PoolDetailComponent]
+  declarations: [
+    PoolDetailComponent,
+    IscsiComponent
+  ]
 })
 export class BlockModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.html
new file mode 100644 (file)
index 0000000..e4b285b
--- /dev/null
@@ -0,0 +1,16 @@
+<nav aria-label="breadcrumb">
+  <ol class="breadcrumb">
+    <li class="breadcrumb-item">Block</li>
+    <li class="breadcrumb-item active" aria-current="page">iSCSI</li>
+  </ol>
+</nav>
+
+<legend>Daemons</legend>
+<cd-table [data]="daemons"
+          [columns]="daemonsColumns">
+</cd-table>
+
+<legend>Images</legend>
+<cd-table [data]="images"
+          [columns]="imagesColumns">
+</cd-table>
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.spec.ts
new file mode 100644 (file)
index 0000000..c0f4c36
--- /dev/null
@@ -0,0 +1,26 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AppModule } from '../../../app.module';
+import { IscsiComponent } from './iscsi.component';
+
+describe('IscsiComponent', () => {
+  let component: IscsiComponent;
+  let fixture: ComponentFixture<IscsiComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [AppModule]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(IscsiComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.ts
new file mode 100644 (file)
index 0000000..f3a1c48
--- /dev/null
@@ -0,0 +1,115 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+
+import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe';
+import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
+import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
+import { ListPipe } from '../../../shared/pipes/list.pipe';
+import { RelativeDatePipe } from '../../../shared/pipes/relative-date.pipe';
+import { TcmuIscsiService } from '../../../shared/services/tcmu-iscsi.service';
+
+@Component({
+  selector: 'cd-iscsi',
+  templateUrl: './iscsi.component.html',
+  styleUrls: ['./iscsi.component.scss']
+})
+export class IscsiComponent implements OnInit, OnDestroy {
+
+  daemons = [];
+  daemonsColumns: any;
+  images = [];
+  imagesColumns: any;
+  interval: any;
+
+  constructor(private tcmuIscsiService: TcmuIscsiService,
+              cephShortVersionPipe: CephShortVersionPipe,
+              dimlessBinaryPipe: DimlessBinaryPipe,
+              dimlessPipe: DimlessPipe,
+              relativeDatePipe: RelativeDatePipe,
+              listPipe: ListPipe) {
+    this.daemonsColumns = [
+      {
+        name: 'Hostname',
+        prop: 'server_hostname'
+      },
+      {
+        name: '# Active/Optimized',
+        prop: 'optimized_paths',
+      },
+      {
+        name: '# Active/Non-Optimized',
+        prop: 'non_optimized_paths'
+      },
+      {
+        name: 'Version',
+        prop: 'version',
+        pipe: cephShortVersionPipe
+      }
+    ];
+    this.imagesColumns = [
+      {
+        name: 'Pool',
+        prop: 'pool_name'
+      },
+      {
+        name: 'Image',
+        prop: 'name'
+      },
+      {
+        name: 'Active/Optimized',
+        prop: 'optimized_paths',
+        pipe: listPipe
+      },
+      {
+        name: 'Active/Non-Optimized',
+        prop: 'non_optimized_paths',
+        pipe: listPipe
+      },
+      {
+        name: 'Read Bytes',
+        prop: 'stats.rd_bytes',
+        pipe: dimlessBinaryPipe
+      },
+      {
+        name: 'Write Bytes',
+        prop: 'stats.wr_bytes',
+        pipe: dimlessBinaryPipe
+      },
+      {
+        name: 'Read Ops',
+        prop: 'stats.rd',
+        pipe: dimlessPipe
+      },
+      {
+        name: 'Write Ops',
+        prop: 'stats.wr',
+        pipe: dimlessPipe
+      },
+      {
+        name: 'A/O Since',
+        prop: 'optimized_since',
+        pipe: relativeDatePipe
+      },
+    ];
+
+  }
+
+  ngOnInit() {
+    this.refresh();
+
+    this.interval = setInterval(() => {
+      this.refresh();
+    }, 5000);
+  }
+
+  ngOnDestroy() {
+    clearInterval(this.interval);
+  }
+
+  refresh() {
+    this.tcmuIscsiService.tcmuiscsi().then((resp) => {
+      this.daemons = resp.daemons;
+      this.images = resp.images;
+    });
+  }
+
+}
index 757b99729d72be06aa3e389df824a6153aea6b86..9df024e0dfa63a8f40b65fb23c2b2d28ca9755dc 100644 (file)
         </a>
 
         <ul class="dropdown-menu">
+          <li routerLinkActive="active">
+            <a i18n
+               class="dropdown-item"
+               routerLink="/block/iscsi">iSCSI</a>
+          </li>
           <li class="dropdown-submenu">
             <a class="dropdown-toggle" data-toggle="dropdown">Pools</a>
             <ul *dropdownMenu
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/list.pipe.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/list.pipe.spec.ts
new file mode 100644 (file)
index 0000000..768f12a
--- /dev/null
@@ -0,0 +1,8 @@
+import { ListPipe } from './list.pipe';
+
+describe('ListPipe', () => {
+  it('create an instance', () => {
+    const pipe = new ListPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/list.pipe.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/list.pipe.ts
new file mode 100644 (file)
index 0000000..1e37919
--- /dev/null
@@ -0,0 +1,10 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'list'
+})
+export class ListPipe implements PipeTransform {
+  transform(value: any, args?: any): any {
+    return value.join(', ');
+  }
+}
index 0cb5bccf1ba9dc82448b74f440e7f53f230e2d00..0d59e4e92dd3707fc3cfa59a4b5041844a56bf56 100644 (file)
@@ -5,6 +5,8 @@ import { CephShortVersionPipe } from './ceph-short-version.pipe';
 import { DimlessBinaryPipe } from './dimless-binary.pipe';
 import { DimlessPipe } from './dimless.pipe';
 import { HealthColorPipe } from './health-color.pipe';
+import { ListPipe } from './list.pipe';
+import { RelativeDatePipe } from './relative-date.pipe';
 
 @NgModule({
   imports: [CommonModule],
@@ -12,18 +14,24 @@ import { HealthColorPipe } from './health-color.pipe';
     DimlessBinaryPipe,
     HealthColorPipe,
     DimlessPipe,
-    CephShortVersionPipe
+    CephShortVersionPipe,
+    RelativeDatePipe,
+    ListPipe
   ],
   exports: [
     DimlessBinaryPipe,
     HealthColorPipe,
     DimlessPipe,
-    CephShortVersionPipe
+    CephShortVersionPipe,
+    RelativeDatePipe,
+    ListPipe
   ],
   providers: [
     CephShortVersionPipe,
     DimlessBinaryPipe,
-    DimlessPipe
+    DimlessPipe,
+    RelativeDatePipe,
+    ListPipe
   ]
 })
 export class PipesModule {}
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/relative-date.pipe.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/relative-date.pipe.spec.ts
new file mode 100644 (file)
index 0000000..1295b0d
--- /dev/null
@@ -0,0 +1,8 @@
+import { RelativeDatePipe } from './relative-date.pipe';
+
+describe('RelativeDatePipe', () => {
+  it('create an instance', () => {
+    const pipe = new RelativeDatePipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/relative-date.pipe.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/pipes/relative-date.pipe.ts
new file mode 100644 (file)
index 0000000..6bfa395
--- /dev/null
@@ -0,0 +1,17 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+import * as moment from 'moment';
+
+@Pipe({
+  name: 'relativeDate'
+})
+export class RelativeDatePipe implements PipeTransform {
+  constructor() {}
+
+  transform(value: any, args?: any): any {
+    if (!value) {
+      return 'unknown';
+    }
+    return moment(value * 1000).fromNow();
+  }
+}
index e22d4082d2db1a6bbf24b66dbfd358657bc01d5b..3986408798fd207eea02febdecf1c7d567cb6fd4 100644 (file)
@@ -46,6 +46,6 @@ export class FormatterService {
       truncatedFloat = this.truncate(n, width);
     }
 
-    return truncatedFloat + units[unit];
+    return truncatedFloat === '' ? '-' : (truncatedFloat + units[unit]);
   }
 }
index 0b54f9743875f5461c9c89f364a2c2adc57a74fa..71adc6212ad7ac0789bdc933400346c52facfa2f 100644 (file)
@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 
 import { FormatterService } from './formatter.service';
+import { TcmuIscsiService } from './tcmu-iscsi.service';
 import { TopLevelService } from './top-level.service';
 
 @NgModule({
@@ -9,6 +10,6 @@ import { TopLevelService } from './top-level.service';
     CommonModule
   ],
   declarations: [],
-  providers: [FormatterService, TopLevelService]
+  providers: [FormatterService, TopLevelService, TcmuIscsiService]
 })
 export class ServicesModule { }
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/tcmu-iscsi.service.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/shared/services/tcmu-iscsi.service.ts
new file mode 100644 (file)
index 0000000..2976404
--- /dev/null
@@ -0,0 +1,15 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class TcmuIscsiService {
+
+  constructor(private http: HttpClient) {
+  }
+
+  tcmuiscsi() {
+    return this.http.get('/api/tcmuiscsi').toPromise().then((resp: any) => {
+      return resp;
+    });
+  }
+}