Added Supervision/Monitoring support
[src/xds/xds-agent.git] / webapp / src / app / pages / supervision / supervision-config.component.ts
diff --git a/webapp/src/app/pages/supervision/supervision-config.component.ts b/webapp/src/app/pages/supervision/supervision-config.component.ts
new file mode 100644 (file)
index 0000000..e96b936
--- /dev/null
@@ -0,0 +1,305 @@
+/**
+* @license
+* Copyright (C) 2017-2018 "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 } 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 <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 + ')';
+    }
+  }
+}