src/app/dashboard/dashboard.component.ts
OnInit
AfterViewInit
OnDestroy
providers |
ResourceService
InstanceService
|
selector | hi-dashboard |
styleUrls | ./dashboard.component.scss |
templateUrl | ./dashboard.component.html |
Properties |
|
Methods |
|
Public
constructor(el: ElementRef, route: ActivatedRoute, visNetworkService: VisNetworkService, resourceService: ResourceService, instanceService: InstanceService, helper: HelperService)
|
|||||||||||||||||||||
Defined in src/app/dashboard/dashboard.component.ts:39
|
|||||||||||||||||||||
Parameters :
|
Protected fetchResources |
fetchResources()
|
Defined in src/app/dashboard/dashboard.component.ts:162
|
Returns :
void
|
initDashboard |
initDashboard()
|
Defined in src/app/dashboard/dashboard.component.ts:129
|
Returns :
void
|
networkInitialized |
networkInitialized()
|
Defined in src/app/dashboard/dashboard.component.ts:50
|
Returns :
void
|
ngAfterViewInit |
ngAfterViewInit()
|
Defined in src/app/dashboard/dashboard.component.ts:149
|
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Defined in src/app/dashboard/dashboard.component.ts:153
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Defined in src/app/dashboard/dashboard.component.ts:81
|
Returns :
void
|
Protected onNodeSelected | ||||
onNodeSelected(id)
|
||||
Defined in src/app/dashboard/dashboard.component.ts:224
|
||||
Parameters :
Returns :
void
|
updateResources |
updateResources()
|
Defined in src/app/dashboard/dashboard.component.ts:209
|
Returns :
void
|
Public clusterName |
Type : string
|
Defined in src/app/dashboard/dashboard.component.ts:32
|
Public edges |
Type : Edge[]
|
Defined in src/app/dashboard/dashboard.component.ts:30
|
Public instanceToId |
Type : object
|
Default value : {}
|
Defined in src/app/dashboard/dashboard.component.ts:35
|
Public isLoading |
Default value : false
|
Defined in src/app/dashboard/dashboard.component.ts:33
|
Public nodes |
Type : Node[]
|
Defined in src/app/dashboard/dashboard.component.ts:29
|
Public resourceToId |
Type : object
|
Default value : {}
|
Defined in src/app/dashboard/dashboard.component.ts:34
|
Public selectedInstance |
Type : any
|
Defined in src/app/dashboard/dashboard.component.ts:37
|
Public selectedResource |
Type : any
|
Defined in src/app/dashboard/dashboard.component.ts:36
|
Public updateInterval |
Type : number
|
Default value : 3000
|
Defined in src/app/dashboard/dashboard.component.ts:39
|
Public updateSubscription |
Type : Subscription
|
Defined in src/app/dashboard/dashboard.component.ts:38
|
Public visNetwork |
Type : string
|
Default value : 'cluster-dashboard'
|
Defined in src/app/dashboard/dashboard.component.ts:26
|
Public visNetworkData |
Type : Data
|
Defined in src/app/dashboard/dashboard.component.ts:27
|
Public visNetworkOptions |
Type : Options
|
Defined in src/app/dashboard/dashboard.component.ts:28
|
import {
Component,
ElementRef,
OnInit,
AfterViewInit,
OnDestroy,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import _ from 'lodash';
import { forEach as lodashForEach } from 'lodash';
import { Data, Edge, Node, Options, VisNetworkService } from 'ngx-vis';
import { ResourceService } from '../resource/shared/resource.service';
import { InstanceService } from '../instance/shared/instance.service';
import { HelperService } from '../shared/helper.service';
@Component({
selector: 'hi-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
providers: [ResourceService, InstanceService],
})
export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy {
public visNetwork = 'cluster-dashboard';
public visNetworkData: Data;
public visNetworkOptions: Options;
public nodes: Node[];
public edges: Edge[];
public clusterName: string;
public isLoading = false;
public resourceToId = {};
public instanceToId = {};
public selectedResource: any;
public selectedInstance: any;
public updateSubscription: Subscription;
public updateInterval = 3000;
public constructor(
private el: ElementRef,
private route: ActivatedRoute,
protected visNetworkService: VisNetworkService,
protected resourceService: ResourceService,
protected instanceService: InstanceService,
protected helper: HelperService
) {}
networkInitialized() {
this.visNetworkService.on(this.visNetwork, 'click');
this.visNetworkService.on(this.visNetwork, 'zoom');
this.visNetworkService.click.subscribe((eventData: any[]) => {
if (eventData[0] === this.visNetwork) {
// clear the edges
this.visNetworkData.edges = [];
this.selectedResource = null;
this.selectedInstance = null;
//
if (eventData[1].nodes.length) {
const id = eventData[1].nodes[0];
this.onNodeSelected(id);
}
}
});
this.visNetworkService.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() {
this.nodes = [];
this.edges = [];
this.visNetworkData = { nodes: this.nodes, edges: this.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
const width = this.el.nativeElement.offsetWidth;
const height = this.el.nativeElement.offsetHeight - 36;
const dashboardDom = this.el.nativeElement.getElementsByClassName(
this.visNetwork
)[0];
dashboardDom.style.width = `${width}px`;
dashboardDom.style.height = `${height}px`;
// load data
if (this.route && this.route.parent) {
this.route.parent.params.pipe(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.visNetworkService.off(this.visNetwork, 'zoom');
this.visNetworkService.off(this.visNetwork, 'click');
}
protected fetchResources() {
this.isLoading = true;
this.resourceService.getAll(this.clusterName).subscribe(
(result) => {
lodashForEach(result, (resource) => {
const lastId = this.nodes.length;
const newId = this.nodes.length + 1;
this.resourceToId[resource.name] = newId;
(this.visNetworkData.nodes as Node[])[
this.visNetworkData.nodes.length
] = {
id: newId,
label: resource.name,
group: 'resource',
title: JSON.stringify(resource),
};
});
this.visNetworkService.fit(this.visNetwork);
},
(error) => this.helper.showError(error),
() => (this.isLoading = false)
);
this.instanceService.getAll(this.clusterName).subscribe(
(result) => {
lodashForEach(result, (instance) => {
const newId = this.visNetworkData.nodes.length + 1;
this.instanceToId[instance.name] = newId;
(this.visNetworkData.nodes as Node[])[
this.visNetworkData.nodes.length
] = {
id: newId,
label: instance.name,
group: instance.healthy ? 'instance' : 'instance_bad',
title: JSON.stringify(instance),
};
});
this.visNetworkService.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, index, _collection) => {
(this.visNetworkData.nodes as Node[])[index] = {
id: this.instanceToId[instance.name],
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) => {
lodashForEach(resources, (resource) => {
(this.visNetworkData.edges as Edge[])[
this.visNetworkData.nodes.length
] = {
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 as Edge[])[
this.visNetworkData.nodes.length
] = {
from: this.instanceToId[instanceName],
to: this.resourceToId[resourceName],
};
});
},
(error) => this.helper.showError(error)
);
}
}
}
}
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<section fxLayout="column" fxFlex>
<section
class="info"
fxLayout="row"
fxLayoutAlign="start center"
fxLayoutGap="10px"
>
<div class="oval">Resource</div>
<div class="rectangle">Instance</div>
<div class="hint">(Scroll to zoom; Drag to move; Click for details)</div>
<span fxFlex="1 1 auto"></span>
<button mat-button (click)="updateResources()">
<mat-icon>refresh</mat-icon>
Refresh Status
</button>
<button
mat-button
*ngIf="selectedResource || selectedInstance"
color="accent"
[routerLink]="[
'../',
selectedResource ? 'resources' : 'instances',
selectedResource || selectedInstance
]"
>
{{ selectedResource || selectedInstance }}
<mat-icon>arrow_forward</mat-icon>
</button>
</section>
<section
class="cluster-dashboard"
[visNetwork]="visNetwork"
[visNetworkData]="visNetworkData"
[visNetworkOptions]="visNetworkOptions"
(initialized)="networkInitialized()"
></section>
</section>
./dashboard.component.scss
:host {
width: 100%;
height: 100%;
}
.cluster-dashboard {
width: 800px;
height: 600px;
}
.info {
font-size: 12px;
height: 36px;
background-color: #fff;
border-bottom: 1px solid #ccc;
text-align: center;
vertical-align: middle;
line-height: 24px;
}
.oval {
width: 80px;
height: 24px;
background-color: #7fcac3;
border: 1px solid #65a19c;
-moz-border-radius: 80px / 24px;
-webkit-border-radius: 80px / 24px;
border-radius: 80px / 24px;
}
.rectangle {
width: 80px;
height: 24px;
background-color: #90caf9;
border: 1px solid #73a1c7;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
.hint {
color: gray;
}