/** * @license * Copyright (C) 2017-2019 "IoT.bzh" * Author Sebastien Douheret * * 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 = new Map(); 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 = new Map(); 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
' + 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 + ')'; } } }