blob: dbe32a316ad8ccdd002d395fb552ec08c0130b93 [file] [log] [blame]
import {
Component,
ElementRef,
OnInit,
AfterViewInit,
OnDestroy
} from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Observable, Subscription } from 'rxjs';
import * as _ from 'lodash';
import { VisNode, VisNodes, VisEdges, VisNetworkService, VisNetworkData, VisNetworkOptions } from 'ngx-vis';
import { ResourceService } from '../resource/shared/resource.service';
import { InstanceService } from '../instance/shared/instance.service';
import { HelperService } from '../shared/helper.service';
class DashboardNetworkData implements VisNetworkData {
public nodes: VisNodes;
public edges: VisEdges;
}
@Component({
selector: 'hi-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
providers: [ResourceService, InstanceService]
})
export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy {
visNetwork = 'cluster-dashboard';
visNetworkData: DashboardNetworkData;
visNetworkOptions: VisNetworkOptions;
clusterName: string;
isLoading = false;
resourceToId = {};
instanceToId = {};
selectedResource: any;
selectedInstance: any;
updateSubscription: Subscription;
updateInterval = 3000;
constructor(
private el:ElementRef,
private route: ActivatedRoute,
protected visService: VisNetworkService,
protected resourceService: ResourceService,
protected instanceService: InstanceService,
protected helper: HelperService
) { }
networkInitialized() {
this.visService.on(this.visNetwork, 'click');
this.visService.on(this.visNetwork, 'zoom');
this.visService.click
.subscribe((eventData: any[]) => {
if (eventData[0] === this.visNetwork) {
// clear the edges first
this.visNetworkData.edges.clear();
this.selectedResource = null;
this.selectedInstance = null;
//
if (eventData[1].nodes.length) {
const id = eventData[1].nodes[0];
this.onNodeSelected(id);
}
}
});
this.visService.zoom
.subscribe((eventData: any) => {
if (eventData[0] === this.visNetwork) {
const scale = eventData[1].scale;
if (scale == 10) {
// too big
} else if (scale < 0.3) {
// small enough
}
}
});
}
ngOnInit() {
const nodes = new VisNodes();
const edges = new VisEdges();
this.visNetworkData = { nodes, edges };
this.visNetworkOptions = {
interaction: {
navigationButtons: true,
keyboard: true
},
layout: {
// layout will be the same every time the nodes are settled
randomSeed: 7
},
physics: {
// default is barnesHut
solver: 'forceAtlas2Based',
forceAtlas2Based: {
// default: -50
gravitationalConstant: -30,
// default: 0
// avoidOverlap: 0.3
}
},
groups: {
resource: {
color: '#7FCAC3',
shape: 'ellipse',
widthConstraint: { maximum: 140 }
},
instance: {
color: '#90CAF9',
shape: 'box',
widthConstraint: { maximum: 140 }
},
instance_bad: {
color: '#CA7F86',
shape: 'box',
widthConstraint: { maximum: 140 }
},
partition: {
color: '#98D4B1',
shape: 'ellipse',
widthConstraint: { maximum: 140 }
}
}
};
}
initDashboard() {
// resize container according to the parent
let width = this.el.nativeElement.offsetWidth;
let height = this.el.nativeElement.offsetHeight - 36;
let dashboardDom = this.el.nativeElement.getElementsByClassName(this.visNetwork)[0];
dashboardDom.style.width = `${ width }px`;
dashboardDom.style.height = `${ height }px`;
// load data
this.route.parent.params
.map(p => p.name)
.subscribe(name => {
this.clusterName = name;
this.fetchResources();
// this.updateResources();
});
}
ngAfterViewInit() {
setTimeout(_ => this.initDashboard());
}
ngOnDestroy(): void {
if (this.updateSubscription) {
this.updateSubscription.unsubscribe();
}
this.visService.off(this.visNetwork, 'zoom');
this.visService.off(this.visNetwork, 'click');
}
protected fetchResources() {
this.isLoading = true;
this.resourceService
.getAll(this.clusterName)
.subscribe(
result => {
_.forEach(result, (resource) => {
const newId = this.visNetworkData.nodes.getLength() + 1;
this.resourceToId[resource.name] = newId;
this.visNetworkData.nodes.add({
id: newId,
label: resource.name,
group: 'resource',
title: JSON.stringify(resource)
});
});
this.visService.fit(this.visNetwork);
},
error => this.helper.showError(error),
() => this.isLoading = false
);
this.instanceService
.getAll(this.clusterName)
.subscribe(
result => {
_.forEach(result, (instance) => {
const newId = this.visNetworkData.nodes.getLength() + 1;
this.instanceToId[instance.name] = newId;
this.visNetworkData.nodes.add({
id: newId,
label: instance.name,
group: instance.healthy ? 'instance' : 'instance_bad',
title: JSON.stringify(instance),
});
});
this.visService.fit(this.visNetwork);
},
error => this.helper.showError(error),
() => this.isLoading = false
);
}
updateResources() {
/* disable auto-update for now
this.updateSubscription = Observable
.interval(this.updateInterval)
.flatMap(i => this.instanceService.getAll(this.clusterName))*/
this.instanceService.getAll(this.clusterName)
.subscribe(
result => {
_.forEach(result, instance => {
const id = this.instanceToId[instance.name];
this.visNetworkData.nodes.update([{
id: id,
group: instance.healthy ? 'instance' : 'instance_bad'
}]);
});
}
);
}
protected onNodeSelected(id) {
const instanceName = _.findKey(this.instanceToId, value => value === id);
if (instanceName) {
this.selectedInstance = instanceName;
// fetch relationships
this.resourceService
.getAllOnInstance(this.clusterName, instanceName)
.subscribe(
resources => {
_.forEach(resources, (resource) => {
this.visNetworkData.edges.add({
from: id,
to: this.resourceToId[resource.name]
});
});
},
error => this.helper.showError(error)
);
} else {
const resourceName = _.findKey(this.resourceToId, value => value === id);
if (resourceName) {
this.selectedResource = resourceName;
this.resourceService
.get(this.clusterName, resourceName)
.subscribe(
resource => {
_(resource.partitions)
.flatMap('replicas')
.unionBy('instanceName')
.map('instanceName')
.forEach((instanceName) => {
this.visNetworkData.edges.add({
from: this.instanceToId[instanceName],
to: this.resourceToId[resourceName]
});
});
},
error => this.helper.showError(error)
);
}
}
}
}