Added Supervision/Monitoring support
[src/xds/xds-agent.git] / webapp / src / app / pages / supervision / supervision-config.component.ts
1 /**
2 * @license
3 * Copyright (C) 2017-2018 "IoT.bzh"
4 * Author Sebastien Douheret <sebastien@iot.bzh>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 import { Component, OnInit, AfterViewInit, ViewEncapsulation } from '@angular/core';
20 import { Injectable, Inject } from '@angular/core';
21 import { DOCUMENT } from '@angular/common';
22 import * as d3 from 'd3';
23
24 import { SupervisionService, AglTopology } from '../../@core-xds/services/supervision.service';
25 import { AlertService } from '../../@core-xds/services/alert.service';
26
27 interface WsCheckbox {
28   name: string;
29   pid: number;
30   value: boolean;
31   disabled: boolean;
32   tooltip: string;
33 }
34
35 @Component({
36   selector: 'xds-supervision',
37   styleUrls: ['./supervision-config.component.scss'],
38   templateUrl: './supervision-config.component.html',
39   encapsulation: ViewEncapsulation.None,  // workaround about https://github.com/angular/angular/issues/7845
40 })
41 export class SupervisionConfigComponent implements OnInit, AfterViewInit {
42
43   daemonCheckboxes: WsCheckbox[] = [];
44   starting = false;
45   stopping = false;
46
47   private graph: any;
48   private svg: any;
49   private links = [];
50
51   constructor(@Inject(DOCUMENT) private document: Document,
52     private supervisorSvr: SupervisionService,
53     private alert: AlertService,
54   ) {
55
56   }
57
58   ngOnInit() {
59
60   }
61
62   ngAfterViewInit() {
63     this.getAGLTopo();
64   }
65
66   getAGLTopo() {
67     this.supervisorSvr.getTopo().subscribe(topo => {
68       this.graphAGLBindings(topo);
69       this.updateCheckboxes(topo);
70     });
71   }
72
73   onStartTrace() {
74     this.starting = true;
75
76     const dmArr = [];
77     this.daemonCheckboxes.forEach(dm => dm.value && dmArr.push(dm.pid));
78
79     this.supervisorSvr.startTrace({ pids: dmArr }).subscribe(res => {
80       this.starting = false;
81       this.alert.info('Monitoring successfully started');
82     }, err => {
83       this.starting = false;
84       this.alert.error(err);
85     });
86   }
87
88   onStopTrace() {
89     this.stopping = true;
90     this.supervisorSvr.stopTrace({}).subscribe(res => {
91       this.stopping = false;
92       this.alert.info('Monitoring successfully stopped');
93     }, err => {
94       this.stopping = false;
95       this.alert.error(err);
96     });
97   }
98
99   isStartBtnDisable(): boolean {
100     return this.starting;
101   }
102
103   isStopBtnDisable(): boolean {
104     return this.stopping;
105   }
106
107   private updateCheckboxes(topo: AglTopology[]) {
108     this.daemonCheckboxes = [];
109     topo.forEach(elem => {
110       this.daemonCheckboxes.push({
111         name: elem.name,
112         pid: elem.pid,
113         value: false,
114         disabled: false,
115         tooltip: 'Daemon ' + elem.name + ' (pid ' + elem.pid + ')',
116       });
117     });
118
119   }
120
121
122   // Compute the distinct nodes from the links.
123   // Based on http://bl.ocks.org/mbostock/1153292
124   private graphAGLBindings(topo: AglTopology[]) {
125
126     const ws_link: { [id: string]: string[] } = {};
127     let ii = 1;
128     topo.forEach(elem => {
129       if (elem.name === 'null') {
130         elem.name = 'Daemon-' + String(ii++);
131       }
132       if (elem.ws_clients && elem.ws_clients instanceof Array) {
133         elem.ws_clients.forEach((ws: string) => {
134           if (ws_link[ws]) {
135             ws_link[ws].push(elem.name);
136           } else {
137             ws_link[ws] = [elem.name];
138           }
139         });
140       }
141       if (elem.ws_servers && elem.ws_servers instanceof Array) {
142         elem.ws_servers.forEach((ws: string) => {
143           if (ws_link[ws]) {
144             ws_link[ws].push(elem.name);
145           } else {
146             ws_link[ws] = [elem.name];
147           }
148         });
149       }
150     });
151
152     const nodes = {};
153     this.links = [];
154     ii = 1;
155     topo.forEach(elem => {
156       let almostOne = false;
157       if (elem.ws_clients && elem.ws_clients.length) {
158         elem.ws_clients.forEach(wsCli => {
159           ws_link[wsCli].forEach(appName => {
160             if (appName !== elem.name) {
161               almostOne = true;
162               this.links.push({ source: elem.name, target: appName, type: 'ws-client' });
163             }
164           });
165         });
166       }
167       if (elem.ws_servers && elem.ws_servers.length) {
168         elem.ws_servers.forEach(wsSvr => {
169           ws_link[wsSvr].forEach(appName => {
170             if (appName !== elem.name) {
171               almostOne = true;
172               this.links.push({ source: elem.name, target: appName, type: 'ws-server' });
173             }
174           });
175         });
176       }
177       if (!almostOne) {
178         const name = '???-' + String(ii++);
179         this.links.push({
180           source: elem.isServer ? name : elem.name,
181           target: elem.isServer ? elem.name : name,
182           type: 'not-connected',
183         });
184       }
185     });
186
187     this.links.forEach(function (link) {
188       link.source = nodes[link.source] || (nodes[link.source] = {
189         name: link.source,
190       });
191       link.target = nodes[link.target] || (nodes[link.target] = {
192         name: link.target,
193       });
194     });
195
196     const width = this.document.getElementById('graph').clientWidth,
197       height = this.document.getElementById('graph').clientHeight;
198
199     // Delete previous graph
200     if (this.svg) {
201       this.svg.remove();
202     }
203
204     // Create new graph
205     const force = d3.layout.force()
206       .nodes(d3.values(nodes))
207       .links(this.links)
208       .size([width, height])
209       .linkDistance(120)
210       .charge(-600)
211       .on('tick', tick)
212       .start();
213     // const force = d3.forceSimulation()
214
215     this.graph = d3.select('#graph');
216     this.svg = this.graph.append('svg')
217       .attr('width', width)
218       .attr('height', height);
219
220     // Define the div for the tooltip
221     /*
222     const divTooltip = d3.select('#graph').append('div')
223       .attr('class', 'tooltip')
224       .style('opacity', 0);
225     */
226
227     // Per-type markers, as they don't inherit styles.
228     this.svg.append('defs').selectAll('marker')
229       .data(['ws-server', 'ws-client', 'not-connected'])
230       .enter().append('marker')
231       .attr('id', function (d) {
232         return d;
233       })
234       .attr('viewBox', '0 -5 10 10')
235       .attr('refX', 15)
236       .attr('refY', -1.5)
237       .attr('markerWidth', 12)
238       .attr('markerHeight', 12)
239       .attr('orient', 'auto')
240       .append('path')
241       .attr('d', 'M0,-5L10,0L0,5');
242
243     const path = this.svg.append('g').selectAll('path')
244       .data(force.links())
245       .enter().append('path')
246       .attr('class', function (d) {
247         return 'link ' + d.type;
248       })
249       .attr('marker-end', function (d) {
250         return 'url(#' + d.type + ')';
251       });
252
253     const circle = this.svg.append('g').selectAll('circle')
254       .data(force.nodes())
255       .enter().append('circle')
256       .attr('r', 12)
257       .call(force.drag);
258
259     const text = this.svg.append('g').selectAll('text')
260       .data(force.nodes())
261       .enter().append('text')
262       .attr('x', 20)
263       .attr('y', '.31em')
264       .text(function (d) {
265         return d.name;
266       });
267
268     /* TODO - SEB
269         circle.on('mouseover', d => {
270           divTooltip.transition()
271             .duration(200)
272             .style('opacity', .9);
273             divTooltip.html('This is a Tooltip <br/>' + d.close)
274             .style('left', (d3.event.pageX) + 'px')
275             .style('top', (d3.event.pageY - 28) + 'px');
276         });
277
278         //  Tooltip Object
279         const tooltip = d3.select('body')
280           .append('div').attr('id', 'tooltip')
281           .style('position', 'absolute')
282           .style('z-index', '10')
283           .style('visibility', 'hidden')
284           .text('a simple tooltip');
285     */
286
287     // Use elliptical arc path segments to doubly-encode directionally.
288     function tick() {
289       path.attr('d', linkArc);
290       circle.attr('transform', transform);
291       text.attr('transform', transform);
292     }
293
294     function linkArc(d) {
295       const dx = d.target.x - d.source.x,
296         dy = d.target.y - d.source.y,
297         dr = Math.sqrt(dx * dx + dy * dy);
298       return 'M' + d.source.x + ',' + d.source.y + 'A' + dr + ',' + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y;
299     }
300
301     function transform(d) {
302       return 'translate(' + d.x + ',' + d.y + ')';
303     }
304   }
305 }