]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: Auto reload cd-table
authorStephan Müller <smueller@suse.com>
Wed, 21 Feb 2018 13:09:00 +0000 (14:09 +0100)
committerRicardo Dias <rdias@suse.com>
Mon, 5 Mar 2018 13:07:17 +0000 (13:07 +0000)
Now every used cd-table with a valid "fetch" function will auto reload
itself by default every 10 seconds. You can also identify the unique
property of the row now, by default the unique identifier is "id".

Signed-off-by: Stephan Müller <smueller@suse.com>
15 files changed:
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/iscsi/iscsi.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/mirroring/mirroring.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/mirroring/mirroring.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cephfs/cephfs/cephfs.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cephfs/cephfs/cephfs.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cephfs/clients/clients.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cephfs/clients/clients.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/monitor/monitor.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/cluster/monitor/monitor.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/performance-counter/performance-counter/performance-counter.component.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/datatable/table/table.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/shared/datatable/table/table.component.ts

index edd89716d6ec7e82f10d8f34c01cd8110327ebf5..68f9326690cd76411c0204d5400857f8aace16f0 100644 (file)
@@ -10,6 +10,7 @@
 
 <legend i18n>Daemons</legend>
 <cd-table [data]="daemons"
+          (fetchData)="refresh()"
           [columns]="daemonsColumns">
 </cd-table>
 
index f3a1c48a3af973bca7aa78ca24de0496a562092b..9d700f44ed20575c76db2c69df2a2736e1d5661f 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 
 import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe';
 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
@@ -12,13 +12,12 @@ import { TcmuIscsiService } from '../../../shared/services/tcmu-iscsi.service';
   templateUrl: './iscsi.component.html',
   styleUrls: ['./iscsi.component.scss']
 })
-export class IscsiComponent implements OnInit, OnDestroy {
+export class IscsiComponent {
 
   daemons = [];
   daemonsColumns: any;
   images = [];
   imagesColumns: any;
-  interval: any;
 
   constructor(private tcmuIscsiService: TcmuIscsiService,
               cephShortVersionPipe: CephShortVersionPipe,
@@ -93,18 +92,6 @@ export class IscsiComponent implements OnInit, OnDestroy {
 
   }
 
-  ngOnInit() {
-    this.refresh();
-
-    this.interval = setInterval(() => {
-      this.refresh();
-    }, 5000);
-  }
-
-  ngOnDestroy() {
-    clearInterval(this.interval);
-  }
-
   refresh() {
     this.tcmuIscsiService.tcmuiscsi().then((resp) => {
       this.daemons = resp.daemons;
index 405889e4bda496eaa697484c7e0d180e1983c929..a76047d431f666924e541e34876cf1b611fda903 100644 (file)
@@ -16,6 +16,7 @@
       <cd-table [data]="daemons.data"
                 columnMode="flex"
                 [columns]="daemons.columns"
+                [autoReload]="30000"
                 (fetchData)="refresh()">
       </cd-table>
     </fieldset>
@@ -27,8 +28,9 @@
 
       <cd-table [data]="pools.data"
                 columnMode="flex"
-                [columns]="pools.columns"
-                (fetchData)="refresh()">
+                [autoReload]="0"
+                (fetchData)="refresh()"
+                [columns]="pools.columns">
       </cd-table>
     </fieldset>
   </div>
         <tab heading="Issues" i18n-heading>
           <cd-table [data]="image_error.data"
                     columnMode="flex"
-                    [columns]="image_error.columns"
-                    (fetchData)="refresh()">
+                    [autoReload]="0"
+                    (fetchData)="refresh()"
+                    [columns]="image_error.columns">
           </cd-table>
         </tab>
         <tab heading="Syncing" i18n-heading>
           <cd-table [data]="image_syncing.data"
                     columnMode="flex"
-                    [columns]="image_syncing.columns"
-                    (fetchData)="refresh()">
+                    [autoReload]="0"
+                    (fetchData)="refresh()"
+                    [columns]="image_syncing.columns">
           </cd-table>
         </tab>
         <tab heading="Ready" i18n-heading>
           <cd-table [data]="image_ready.data"
                     columnMode="flex"
-                    [columns]="image_ready.columns"
-                    (fetchData)="refresh()">
+                    [autoReload]="0"
+                    (fetchData)="refresh()"
+                    [columns]="image_ready.columns">
           </cd-table>
         </tab>
       </tabset>
index b89a23f825151ddc99dc0000d8dad58f3326c9a9..63e960ef52e425f5358f2a917cdb43427a384d92 100644 (file)
@@ -1,5 +1,5 @@
 import { HttpClient } from '@angular/common/http';
-import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 
 import * as _ from 'lodash';
 
@@ -12,14 +12,13 @@ import { RbdMirroringService } from '../../../shared/services/rbd-mirroring.serv
   templateUrl: './mirroring.component.html',
   styleUrls: ['./mirroring.component.scss']
 })
-export class MirroringComponent implements OnInit, OnDestroy {
+export class MirroringComponent implements OnInit {
   @ViewChild('healthTmpl') healthTmpl: TemplateRef<any>;
   @ViewChild('stateTmpl') stateTmpl: TemplateRef<any>;
   @ViewChild('syncTmpl') syncTmpl: TemplateRef<any>;
   @ViewChild('progressTmpl') progressTmpl: TemplateRef<any>;
 
   contentData: any;
-  interval: any;
 
   status: ViewCacheStatus;
   daemons = {
@@ -122,14 +121,6 @@ export class MirroringComponent implements OnInit, OnDestroy {
         flexGrow: 1
       }
     ];
-
-    setTimeout(() => {
-      this.interval = this.refresh();
-    }, 30000);
-  }
-
-  ngOnDestroy() {
-    clearInterval(this.interval);
   }
 
   refresh() {
index 7e8d82cfcc495e2887abbebebdffb978e2fc78ef..98ac59c66640ac86397eb41bd072f1785cb8e27d 100644 (file)
@@ -5,7 +5,6 @@ import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
-import { FormatterService } from '../../../shared/services/formatter.service';
 import { PoolService } from '../../../shared/services/pool.service';
 
 @Component({
@@ -18,10 +17,8 @@ export class PoolDetailComponent implements OnInit, OnDestroy {
   images: any;
   columns: CdTableColumn[];
   retries: number;
-  maxRetries = 5;
   routeParamsSubscribe: any;
   viewCacheStatus: ViewCacheStatus;
-  interval: any;
 
   constructor(
     private route: ActivatedRoute,
@@ -75,15 +72,10 @@ export class PoolDetailComponent implements OnInit, OnDestroy {
       this.images = [];
       this.retries = 0;
     });
-
-    this.interval = setInterval(() => {
-      this.loadImages();
-    }, 5000);
   }
 
   ngOnDestroy() {
     this.routeParamsSubscribe.unsubscribe();
-    clearInterval(this.interval);
   }
 
   loadImages() {
index 4e9a683ccce27eb05eb048cc1fd9bf6720c8fd13..2333f770b66ad3e51a93c55865b729c311eb5704 100644 (file)
@@ -25,6 +25,7 @@
 
       <cd-table [data]="ranks.data"
                 [columns]="ranks.columns"
+                (fetchData)="refresh()"
                 toolHeader="false">
       </cd-table>
     </fieldset>
index 77f3174401b224be950bde6df5763fda6865f40c..1cd93f3a9cacc5658e0c99e362ddce0b1d12f64d 100644 (file)
@@ -3,7 +3,6 @@ import { ActivatedRoute } from '@angular/router';
 
 import * as _ from 'lodash';
 
-import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
 import { CephfsService } from '../cephfs.service';
@@ -95,19 +94,10 @@ export class CephfsComponent implements OnInit, OnDestroy {
       this.pools.data = [];
       this.standbys = [];
       this.mdsCounters = {};
-
-      this.refresh();
-      this.draw_chart();
     });
-
-    this.interval = setInterval(() => {
-      this.refresh();
-      this.draw_chart();
-    }, 5000);
   }
 
   ngOnDestroy() {
-    clearInterval(this.interval);
     this.routeParamsSubscribe.unsubscribe();
   }
 
@@ -123,6 +113,7 @@ export class CephfsComponent implements OnInit, OnDestroy {
       ];
       this.name = data.cephfs.name;
       this.clientCount = data.cephfs.client_count;
+      this.draw_chart();
     });
   }
 
@@ -130,9 +121,6 @@ export class CephfsComponent implements OnInit, OnDestroy {
     this.cephfsService.getMdsCounters(this.id).subscribe(data => {
       const topChart = true;
 
-      const oldKeys = Object.keys(this.mdsCounters);
-      const newKeys = Object.keys(data);
-
       _.each(this.mdsCounters, (value, key) => {
         if (data[key] === undefined) {
           delete this.mdsCounters[key];
@@ -144,7 +132,7 @@ export class CephfsComponent implements OnInit, OnDestroy {
         const rhsData = this.delta_timeseries(mdsData[this.rhsCounter]);
 
         if (this.mdsCounters[mdsName] === undefined) {
-          const elem = {
+          this.mdsCounters[mdsName] = {
             datasets: [
               {
                 label: this.lhsCounter,
@@ -197,8 +185,6 @@ export class CephfsComponent implements OnInit, OnDestroy {
             },
             chartType: 'line'
           };
-
-          this.mdsCounters[mdsName] = elem;
         } else {
           this.mdsCounters[mdsName].datasets[0].data = lhsData;
           this.mdsCounters[mdsName].datasets[1].data = rhsData;
index 12ae365918016ed3b7a2cc8c6d0a2b68dcfb095b..7832a38744f0f8a085f368341f8fc2c1a7367e8b 100644 (file)
@@ -16,6 +16,7 @@
 
   <cd-table [data]="clients.data"
             [columns]="clients.columns"
+            (fetchData)="refresh()"
             [header]="false">
   </cd-table>
 </fieldset>
index d7f581fefb5cd5f37c7272bfdc8119f19ae993e9..fc2cbdec61b241e872e32467fa34ee9b8071fae8 100644 (file)
@@ -17,8 +17,6 @@ export class ClientsComponent implements OnInit, OnDestroy {
   clients: any;
   viewCacheStatus: ViewCacheStatus;
 
-  interval: any;
-
   constructor(private route: ActivatedRoute, private cephfsService: CephfsService) {}
 
   ngOnInit() {
@@ -42,17 +40,10 @@ export class ClientsComponent implements OnInit, OnDestroy {
       this.cephfsService.getCephfs(this.id).subscribe((data: any) => {
         this.name = data.cephfs.name;
       });
-
-      this.refresh();
     });
-
-    this.interval = setInterval(() => {
-      this.refresh();
-    }, 5000);
   }
 
   ngOnDestroy() {
-    clearInterval(this.interval);
     this.routeParamsSubscribe.unsubscribe();
   }
 
index f92f6de881b03a28ba937b581f3b262206fdc0ad..28a193fd8ce8d0228b09f80a42bee375f0e840d5 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe';
@@ -9,11 +9,10 @@ import { HostService } from '../../../shared/services/host.service';
   templateUrl: './hosts.component.html',
   styleUrls: ['./hosts.component.scss']
 })
-export class HostsComponent implements OnInit, OnDestroy {
+export class HostsComponent implements OnInit {
 
   columns: Array<CdTableColumn> = [];
   hosts: Array<object> = [];
-  interval: any;
   isLoadingHosts = false;
 
   @ViewChild('servicesTpl') public servicesTpl: TemplateRef<any>;
@@ -41,13 +40,6 @@ export class HostsComponent implements OnInit, OnDestroy {
         pipe: this.cephShortVersionPipe
       }
     ];
-    this.interval = setInterval(() => {
-      this.getHosts();
-    }, 5000);
-  }
-
-  ngOnDestroy() {
-    clearInterval(this.interval);
   }
 
   getHosts() {
index 2038929f51f18ed1bb75953fae1de5e2b2772881..d59de84c1451215ebf371d7631d392f0fa1f85ac 100644 (file)
@@ -64,6 +64,7 @@
       <legend i18n
               class="in-quorum">Not In Quorum</legend>
       <cd-table [data]="notInQuorum.data"
+                (fetchData)="refresh()"
                 [columns]="notInQuorum.columns">
       </cd-table>
     </fieldset>
index fd2e23edc5eaa90172c869bb9eae06daedfd9ff2..0a23129667ef1e40e73940a0109e823b86316f98 100644 (file)
@@ -1,6 +1,4 @@
-import { Component, OnDestroy, OnInit } from '@angular/core';
-
-import * as _ from 'lodash';
+import { Component } from '@angular/core';
 
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { MonitorService } from '../monitor.service';
@@ -10,7 +8,7 @@ import { MonitorService } from '../monitor.service';
   templateUrl: './monitor.component.html',
   styleUrls: ['./monitor.component.scss']
 })
-export class MonitorComponent implements OnInit, OnDestroy {
+export class MonitorComponent {
 
   mon_status: any;
   inQuorum: any;
@@ -22,9 +20,7 @@ export class MonitorComponent implements OnInit, OnDestroy {
     width: '50%'
   };
 
-  constructor(private monitorService: MonitorService) {}
-
-  ngOnInit() {
+  constructor(private monitorService: MonitorService) {
     this.inQuorum = {
       columns: [
         { prop: 'name', name: 'Name', cellTransformation: CellTemplate.routerLink },
@@ -47,16 +43,6 @@ export class MonitorComponent implements OnInit, OnDestroy {
       ],
       data: []
     };
-
-    this.refresh();
-
-    this.interval = setInterval(() => {
-      this.refresh();
-    }, 5000);
-  }
-
-  ngOnDestroy() {
-    clearInterval(this.interval);
   }
 
   refresh() {
index 60549afa800a68a8b03cd89960b57b6010eebd54..25fa82e4e9d14798314d704cef153fea8a9eb6d6 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
 
 @Component({
@@ -6,14 +6,12 @@ import { ActivatedRoute } from '@angular/router';
   templateUrl: './performance-counter.component.html',
   styleUrls: ['./performance-counter.component.scss']
 })
-export class PerformanceCounterComponent implements OnInit, OnDestroy {
+export class PerformanceCounterComponent implements OnDestroy {
   serviceId: string;
   serviceType: string;
   routeParamsSubscribe: any;
 
-  constructor(private route: ActivatedRoute) { }
-
-  ngOnInit() {
+  constructor(private route: ActivatedRoute) {
     this.routeParamsSubscribe = this.route.params.subscribe(
       (params: { type: string; id: string }) => {
         this.serviceId = params.id;
index a8fda111faf8686468eabf4c7a7f8a889a7cded9..87feb73b7007ee1306253fdbafccf9bc59e2bf60 100644 (file)
@@ -41,7 +41,7 @@
 
     <!-- refresh button -->
     <div class="widget-toolbar tc_refreshBtn">
-      <a (click)="reloadData()">
+      <a (click)="refreshBtn()">
         <i class="fa fa-lg fa-refresh"></i>
       </a>
     </div>
@@ -62,6 +62,7 @@
                  [footerHeight]="footer ? 'auto' : 0"
                  [limit]="limit > 0 ? limit : undefined"
                  [loadingIndicator]="loadingIndicator"
+                 [rowIdentity]="rowIdentity()"
                  [rowHeight]="'auto'">
     <!-- Row Detail Template -->
     <ngx-datatable-row-detail (toggle)="updateDetailView()">
index c2e9684aafc74b16864d58fc04b50f026f3cc3fa..0df9d419f41cd1bfc00930a642f26eefef13b5bc 100644 (file)
@@ -5,6 +5,7 @@ import {
   EventEmitter,
   Input,
   OnChanges,
+  OnDestroy,
   OnInit,
   Output,
   TemplateRef,
@@ -13,7 +14,10 @@ import {
 } from '@angular/core';
 
 import { DatatableComponent, SortDirection, SortPropDir } from '@swimlane/ngx-datatable';
+
 import * as _ from 'lodash';
+import 'rxjs/add/observable/timer';
+import { Observable } from 'rxjs/Observable';
 
 import { CdTableColumn } from '../../models/cd-table-column';
 import { TableDetailsDirective } from '../table-details.directive';
@@ -23,7 +27,7 @@ import { TableDetailsDirective } from '../table-details.directive';
   templateUrl: './table.component.html',
   styleUrls: ['./table.component.scss']
 })
-export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
+export class TableComponent implements AfterContentChecked, OnInit, OnChanges, OnDestroy {
   @ViewChild(DatatableComponent) table: DatatableComponent;
   @ViewChild(TableDetailsDirective) detailTemplate: TableDetailsDirective;
   @ViewChild('tableCellBoldTpl') tableCellBoldTpl: TemplateRef<any>;
@@ -32,7 +36,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
   @ViewChild('perSecondTpl') perSecondTpl: TemplateRef<any>;
 
   // This is the array with the items to be shown.
-  @Input() data: any[] = [];
+  @Input() data: any[];
   // Each item -> { prop: 'attribute name', name: 'display name' }
   @Input() columns: CdTableColumn[];
   // Each item -> { prop: 'attribute name', dir: 'asc'||'desc'}
@@ -54,7 +58,25 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
   // the details page, return false.
   @Input() beforeShowDetails: Function;
 
-  // Should be the function that will update the input data.
+  /**
+   * Auto reload time in ms - per default every 5s
+   * You can set it to 0, undefined or false to disable the auto reload feature in order to
+   * trigger 'fetchData' if the reload button is clicked.
+   */
+  @Input() autoReload: any = 5000;
+
+  // Which row property is unique for a row
+  @Input() identifier = 'id';
+
+  /**
+   * Should be a function to update the input data if undefined nothing will be triggered
+   *
+   * Sometimes it's useful to only define fetchData once.
+   * Example:
+   * Usage of multiple tables with data which is updated by the same function
+   * What happens:
+   * The function is triggered through one table and all tables will update
+   */
   @Output() fetchData = new EventEmitter();
 
   cellTemplates: {
@@ -64,13 +86,15 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
   search = '';
   rows = [];
   selected = [];
-  loadingIndicator = false;
+  loadingIndicator = true;
   paginationClasses = {
     pagerLeftArrow: 'i fa fa-angle-double-left',
     pagerRightArrow: 'i fa fa-angle-double-right',
     pagerPrevious: 'i fa fa-angle-left',
     pagerNext: 'i fa fa-angle-right'
   };
+  private subscriber;
+  private updating = false;
 
   // Internal variable to check if it is necessary to recalculate the
   // table columns after the browser window has been resized.
@@ -86,18 +110,31 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
       }
       return column;
     });
-    this.reloadData();
     if (this.detailsComponent) {
       this.selectionType = 'multi';
     }
     if (!this.sorts) {
+      const sortProp = this.columns.some((c) => c.prop === this.identifier) ?
+        this.identifier :
+        this.columns[0].prop;
       this.sorts = [
         {
-          prop: this.columns[0].prop,
+          prop: sortProp,
           dir: SortDirection.asc
         }
       ];
     }
+    if (this.autoReload) { // Also if nothing is bound to fetchData nothing will be triggered
+      this.subscriber = Observable.timer(0, this.autoReload).subscribe(x => {
+        return this.reloadData();
+      });
+    }
+  }
+
+  ngOnDestroy() {
+    if (this.subscriber) {
+      this.subscriber.unsubscribe();
+    }
   }
 
   ngAfterContentChecked() {
@@ -131,11 +168,25 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
   }
 
   reloadData() {
-    if (this.loadingIndicator) {
-      return;
+    if (!this.updating) {
+      this.fetchData.emit();
+      this.updating = true;
     }
+  }
+
+  refreshBtn () {
     this.loadingIndicator = true;
-    this.fetchData.emit();
+    this.reloadData();
+  }
+
+  rowIdentity() {
+    return (row) => {
+      const id = row[this.identifier];
+      if (_.isUndefined(id)) {
+        throw new Error(`Wrong identifier "${this.identifier}" -> "${id}"`);
+      }
+      return id;
+    };
   }
 
   useData() {
@@ -143,7 +194,11 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
       return; // Wait for data
     }
     this.rows = [...this.data];
+    if (this.search.length > 0) {
+      this.updateFilter(true);
+    }
     this.loadingIndicator = false;
+    this.updating = false;
   }
 
   toggleExpandRow() {