aboutsummaryrefslogtreecommitdiffstats
path: root/webapp/src/app/pages/monitoring/monitoring-config.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/app/pages/monitoring/monitoring-config.component.ts')
-rw-r--r--webapp/src/app/pages/monitoring/monitoring-config.component.ts362
1 files changed, 362 insertions, 0 deletions
diff --git a/webapp/src/app/pages/monitoring/monitoring-config.component.ts b/webapp/src/app/pages/monitoring/monitoring-config.component.ts
new file mode 100644
index 0000000..2a5a84b
--- /dev/null
+++ b/webapp/src/app/pages/monitoring/monitoring-config.component.ts
@@ -0,0 +1,362 @@
+/**
+* @license
+* Copyright (C) 2017-2019 "IoT.bzh"
+* Author Sebastien Douheret <sebastien@iot.bzh>
+*
+* Licensed 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.
+*/
+
+import { Component, OnInit, AfterViewInit, ViewEncapsulation, Inject } from '@angular/core';
+import { DOCUMENT } from '@angular/common';
+import * as d3 from 'd3';
+import { Router } from '@angular/router';
+
+import { MonitoringService, AglTopology } from '../../@core-xds/services/monitoring.service';
+import { AlertService } from '../../@core-xds/services/alert.service';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { Subscription } from 'rxjs/Subscription';
+
+interface WsCheckbox {
+ topo: AglTopology;
+ value: boolean;
+ tooltip: string;
+}
+
+@Component({
+ selector: 'xds-monitoring',
+ styleUrls: ['./monitoring-config.component.scss'],
+ templateUrl: './monitoring-config.component.html',
+ encapsulation: ViewEncapsulation.None, // workaround about https://github.com/angular/angular/issues/7845
+})
+export class MonitoringConfigComponent implements OnInit, AfterViewInit {
+
+ aglTopoInit = new BehaviorSubject(false);
+ // FIXME: use Map instead of array and use '| keyvalue' for ngfor loop (but angular > 6.1 requested)
+ // daemonCheckboxes: Map<string, WsCheckbox> = new Map<string, WsCheckbox>();
+ daemonCheckboxes: WsCheckbox[] = [];
+ starting = false;
+ stopping = false;
+
+ private graph: any;
+ private svg: any;
+ private links = [];
+ private _aglTopoSub: Subscription;
+
+ constructor(@Inject(DOCUMENT) private document: Document,
+ private router: Router,
+ private monitoringSvr: MonitoringService,
+ private alert: AlertService,
+ ) {
+
+ }
+
+ ngOnInit() {
+ }
+
+ ngAfterViewInit() {
+ this.getAGLTopo();
+ this.aglTopoInit.next(true);
+ }
+
+ getAGLTopo() {
+ if (this._aglTopoSub !== undefined) {
+ this._aglTopoSub.unsubscribe();
+ }
+
+ this._aglTopoSub = this.monitoringSvr.getTopo().subscribe(topo => {
+ this.graphAGLBindings(topo);
+ this.createCheckboxes(topo);
+ });
+ }
+
+ onStartTrace() {
+ this.starting = true;
+
+ const dmArr = [];
+ this.daemonCheckboxes.forEach(dm => dm.value && dmArr.push(dm.topo.pid));
+
+ this.monitoringSvr.startTrace({ pids: dmArr }).subscribe(res => {
+ // console.log('Trace Started: res', res);
+
+ this.monitoringSvr.startLowCollector(null).subscribe((/*res*/) => {
+ // console.log('Low Collector Started: res', res);
+ this.alert.info('Monitoring successfully started');
+ this.starting = false;
+
+ }, err => {
+ this.starting = false;
+ this.alert.error(err);
+ });
+
+ }, err => {
+ this.starting = false;
+ this.alert.error(err);
+ });
+ }
+
+ onStopTrace() {
+ this.stopping = true;
+ this.monitoringSvr.stopTrace({}).subscribe(res => {
+ // console.log('Trace Stopped: res', res);
+
+ this.monitoringSvr.stopLowCollector().subscribe((/*res*/) => {
+ // console.log('Low Collector Stopped: res', res);
+ this.alert.info('Monitoring successfully started');
+ this.stopping = false;
+
+ }, err => {
+ this.stopping = false;
+ this.alert.error(err);
+ });
+
+ }, err => {
+ this.stopping = false;
+ this.alert.error(err);
+ });
+ }
+
+ showGraph() {
+ this.router.navigate([`/pages/monitoring/graph`]);
+ }
+
+ isStartBtnDisable(): boolean {
+ return this.starting;
+ }
+
+ isStopBtnDisable(): boolean {
+ return this.stopping;
+ }
+
+ isDaemonDisabled(name: string): boolean {
+ let sts = false;
+ // FIXME - better to use map
+ // with Map
+ // if (this.daemonCheckboxes.has(name)) {
+ // sts = this.daemonCheckboxes[name].value;
+ // }
+ this.daemonCheckboxes.forEach(e => {
+ if (e.topo.name === name) {
+ sts = true;
+ }
+ });
+ return sts;
+ }
+
+ private createCheckboxes(topo: AglTopology[]) {
+
+ // let newDaemonChB: Map<string, WsCheckbox> = new Map<string, WsCheckbox>();
+ const newDaemonChB: WsCheckbox[] = [];
+ let prevVal = false;
+ this.daemonCheckboxes.forEach(e => {
+ if (e.topo.name === name) {
+ prevVal = e.value;
+ }
+ });
+ topo.forEach(elem => {
+ // with Map
+ // newDaemonChB.set(elem.name, {
+ newDaemonChB.push({
+ topo: Object.assign({}, elem),
+ value: prevVal,
+ tooltip: 'Daemon binding ' + elem.name + ' (pid ' + elem.pid + ')',
+ });
+ });
+
+ this.daemonCheckboxes = newDaemonChB;
+ }
+
+
+ // Compute the distinct nodes from the links.
+ // Based on http://bl.ocks.org/mbostock/1153292
+ private graphAGLBindings(topo: AglTopology[]) {
+
+ const ws_link: { [id: string]: string[] } = {};
+ let ii = 1;
+ topo.forEach(elem => {
+ if (elem.name === 'null') {
+ elem.name = 'Daemon-' + String(ii++);
+ }
+ if (elem.ws_clients && elem.ws_clients instanceof Array) {
+ elem.ws_clients.forEach((ws: string) => {
+ if (ws_link[ws]) {
+ ws_link[ws].push(elem.name);
+ } else {
+ ws_link[ws] = [elem.name];
+ }
+ });
+ }
+ if (elem.ws_servers && elem.ws_servers instanceof Array) {
+ elem.ws_servers.forEach((ws: string) => {
+ if (ws_link[ws]) {
+ ws_link[ws].push(elem.name);
+ } else {
+ ws_link[ws] = [elem.name];
+ }
+ });
+ }
+ });
+
+ const nodes = {};
+ this.links = [];
+ ii = 1;
+ topo.forEach(elem => {
+ let almostOne = false;
+ if (elem.ws_clients && elem.ws_clients.length) {
+ elem.ws_clients.forEach(wsCli => {
+ ws_link[wsCli].forEach(appName => {
+ if (appName !== elem.name) {
+ almostOne = true;
+ this.links.push({ source: elem.name, target: appName, type: 'ws-client' });
+ }
+ });
+ });
+ }
+ if (elem.ws_servers && elem.ws_servers.length) {
+ elem.ws_servers.forEach(wsSvr => {
+ ws_link[wsSvr].forEach(appName => {
+ if (appName !== elem.name) {
+ almostOne = true;
+ this.links.push({ source: elem.name, target: appName, type: 'ws-server' });
+ }
+ });
+ });
+ }
+ if (!almostOne) {
+ const name = '???-' + String(ii++);
+ this.links.push({
+ source: elem.isServer ? name : elem.name,
+ target: elem.isServer ? elem.name : name,
+ type: 'not-connected',
+ });
+ }
+ });
+
+ this.links.forEach(function (link) {
+ link.source = nodes[link.source] || (nodes[link.source] = {
+ name: link.source,
+ });
+ link.target = nodes[link.target] || (nodes[link.target] = {
+ name: link.target,
+ });
+ });
+
+ const width = this.document.getElementById('graph').clientWidth,
+ height = this.document.getElementById('graph').clientHeight;
+
+ // Delete previous graph
+ if (this.svg) {
+ this.svg.remove();
+ }
+
+ // Create new graph
+ const force = d3.layout.force()
+ .nodes(d3.values(nodes))
+ .links(this.links)
+ .size([width, height])
+ .linkDistance(120)
+ .charge(-600)
+ .on('tick', tick)
+ .start();
+ // const force = d3.forceSimulation()
+
+ this.graph = d3.select('#graph');
+ this.svg = this.graph.append('svg')
+ .attr('width', width)
+ .attr('height', height);
+
+ // Define the div for the tooltip
+ /*
+ const divTooltip = d3.select('#graph').append('div')
+ .attr('class', 'tooltip')
+ .style('opacity', 0);
+ */
+
+ // Per-type markers, as they don't inherit styles.
+ this.svg.append('defs').selectAll('marker')
+ .data(['ws-server', 'ws-client', 'not-connected'])
+ .enter().append('marker')
+ .attr('id', function (d) {
+ return d;
+ })
+ .attr('viewBox', '0 -5 10 10')
+ .attr('refX', 15)
+ .attr('refY', -1.5)
+ .attr('markerWidth', 12)
+ .attr('markerHeight', 12)
+ .attr('orient', 'auto')
+ .append('path')
+ .attr('d', 'M0,-5L10,0L0,5');
+
+ const path = this.svg.append('g').selectAll('path')
+ .data(force.links())
+ .enter().append('path')
+ .attr('class', function (d) {
+ return 'link ' + d.type;
+ })
+ .attr('marker-end', function (d) {
+ return 'url(#' + d.type + ')';
+ });
+
+ const circle = this.svg.append('g').selectAll('circle')
+ .data(force.nodes())
+ .enter().append('circle')
+ .attr('r', 12)
+ .call(force.drag);
+
+ const text = this.svg.append('g').selectAll('text')
+ .data(force.nodes())
+ .enter().append('text')
+ .attr('x', 20)
+ .attr('y', '.31em')
+ .text(function (d) {
+ return d.name;
+ });
+
+ /* TODO - SEB
+ circle.on('mouseover', d => {
+ divTooltip.transition()
+ .duration(200)
+ .style('opacity', .9);
+ divTooltip.html('This is a Tooltip <br/>' + d.close)
+ .style('left', (d3.event.pageX) + 'px')
+ .style('top', (d3.event.pageY - 28) + 'px');
+ });
+
+ // Tooltip Object
+ const tooltip = d3.select('body')
+ .append('div').attr('id', 'tooltip')
+ .style('position', 'absolute')
+ .style('z-index', '10')
+ .style('visibility', 'hidden')
+ .text('a simple tooltip');
+ */
+
+ // Use elliptical arc path segments to doubly-encode directionally.
+ function tick() {
+ path.attr('d', linkArc);
+ circle.attr('transform', transform);
+ text.attr('transform', transform);
+ }
+
+ function linkArc(d) {
+ const dx = d.target.x - d.source.x,
+ dy = d.target.y - d.source.y,
+ dr = Math.sqrt(dx * dx + dy * dy);
+ return 'M' + d.source.x + ',' + d.source.y + 'A' + dr + ',' + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y;
+ }
+
+ function transform(d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
+ }
+ }
+}