/** * @license * Copyright (C) 2017-2018 "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 } from '@angular/core'; import { Injectable, Inject } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import * as d3 from 'd3'; import { SupervisionService, AglTopology } from '../../@core-xds/services/supervision.service'; import { AlertService } from '../../@core-xds/services/alert.service'; interface WsCheckbox { name: string; pid: number; value: boolean; disabled: boolean; tooltip: string; } @Component({ selector: 'xds-supervision', styleUrls: ['./supervision-config.component.scss'], templateUrl: './supervision-config.component.html', encapsulation: ViewEncapsulation.None, // workaround about https://github.com/angular/angular/issues/7845 }) export class SupervisionConfigComponent implements OnInit, AfterViewInit { daemonCheckboxes: WsCheckbox[] = []; starting = false; stopping = false; private graph: any; private svg: any; private links = []; constructor(@Inject(DOCUMENT) private document: Document, private supervisorSvr: SupervisionService, private alert: AlertService, ) { } ngOnInit() { } ngAfterViewInit() { this.getAGLTopo(); } getAGLTopo() { this.supervisorSvr.getTopo().subscribe(topo => { this.graphAGLBindings(topo); this.updateCheckboxes(topo); }); } onStartTrace() { this.starting = true; const dmArr = []; this.daemonCheckboxes.forEach(dm => dm.value && dmArr.push(dm.pid)); this.supervisorSvr.startTrace({ pids: dmArr }).subscribe(res => { this.starting = false; this.alert.info('Monitoring successfully started'); }, err => { this.starting = false; this.alert.error(err); }); } onStopTrace() { this.stopping = true; this.supervisorSvr.stopTrace({}).subscribe(res => { this.stopping = false; this.alert.info('Monitoring successfully stopped'); }, err => { this.stopping = false; this.alert.error(err); }); } isStartBtnDisable(): boolean { return this.starting; } isStopBtnDisable(): boolean { return this.stopping; } private updateCheckboxes(topo: AglTopology[]) { this.daemonCheckboxes = []; topo.forEach(elem => { this.daemonCheckboxes.push({ name: elem.name, pid: elem.pid, value: false, disabled: false, tooltip: 'Daemon ' + elem.name + ' (pid ' + elem.pid + ')', }); }); } // 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 + ')'; } } }