mgr/dashboard_v2: Auto reload cd-table

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>
This commit is contained in:
Stephan Müller 2018-02-21 14:09:00 +01:00 committed by Ricardo Dias
parent 1f5051ab8a
commit 772609ffd4
No known key found for this signature in database
GPG Key ID: 74390C579BD37B68
15 changed files with 97 additions and 109 deletions

View File

@ -10,6 +10,7 @@
<legend i18n>Daemons</legend>
<cd-table [data]="daemons"
(fetchData)="refresh()"
[columns]="daemonsColumns">
</cd-table>

View 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;

View 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>
@ -42,22 +44,25 @@
<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>

View 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() {

View 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() {

View File

@ -25,6 +25,7 @@
<cd-table [data]="ranks.data"
[columns]="ranks.columns"
(fetchData)="refresh()"
toolHeader="false">
</cd-table>
</fieldset>

View 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;

View File

@ -16,6 +16,7 @@
<cd-table [data]="clients.data"
[columns]="clients.columns"
(fetchData)="refresh()"
[header]="false">
</cd-table>
</fieldset>

View 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();
}

View 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() {

View 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>

View 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() {

View 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;

View 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()">

View 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() {