Use go module as dependency tool instead of glide
[src/xds/xds-agent.git] / webapp / src / app / pages / monitoring / monitoring-config.component.ts
1 /**
2 * @license
3 * Copyright (C) 2017-2019 "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, Inject } from '@angular/core';
20 import { DOCUMENT } from '@angular/common';
21 import * as d3 from 'd3';
22 import { Router } from '@angular/router';
23
24 import { MonitoringService, AglTopology } from '../../@core-xds/services/monitoring.service';
25 import { AlertService } from '../../@core-xds/services/alert.service';
26 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
27 import { Subscription } from 'rxjs/Subscription';
28
29 interface WsCheckbox {
30   topo: AglTopology;
31   value: boolean;
32   tooltip: string;
33 }
34
35 @Component({
36   selector: 'xds-monitoring',
37   styleUrls: ['./monitoring-config.component.scss'],
38   templateUrl: './monitoring-config.component.html',
39   encapsulation: ViewEncapsulation.None,  // workaround about https://github.com/angular/angular/issues/7845
40 })
41 export class MonitoringConfigComponent implements OnInit, AfterViewInit {
42
43   aglTopoInit = new BehaviorSubject(false);
44   // FIXME: use Map instead of array and use '| keyvalue' for ngfor loop (but angular > 6.1 requested)
45   // daemonCheckboxes: Map<string, WsCheckbox> = new Map<string, WsCheckbox>();
46   daemonCheckboxes: WsCheckbox[] = [];
47   starting = false;
48   stopping = false;
49
50   private graph: any;
51   private svg: any;
52   private links = [];
53   private _aglTopoSub: Subscription;
54
55   constructor(@Inject(DOCUMENT) private document: Document,
56     private router: Router,
57     private monitoringSvr: MonitoringService,
58     private alert: AlertService,
59   ) {
60
61   }
62
63   ngOnInit() {
64   }
65
66   ngAfterViewInit() {
67     this.getAGLTopo();
68     this.aglTopoInit.next(true);
69   }
70
71   getAGLTopo() {
72     if (this._aglTopoSub !== undefined) {
73       this._aglTopoSub.unsubscribe();
74     }
75
76     this._aglTopoSub = this.monitoringSvr.getTopo().subscribe(topo => {
77       this.graphAGLBindings(topo);
78       this.createCheckboxes(topo);
79     });
80   }
81
82   onStartTrace() {
83     this.starting = true;
84
85     const dmArr = [];
86     this.daemonCheckboxes.forEach(dm => dm.value && dmArr.push(dm.topo.pid));
87
88     this.monitoringSvr.startTrace({ pids: dmArr }).subscribe(res => {
89       // console.log('Trace Started: res', res);
90
91       this.monitoringSvr.startLowCollector(null).subscribe((/*res*/) => {
92         // console.log('Low Collector Started: res', res);
93         this.alert.info('Monitoring successfully started');
94         this.starting = false;
95
96       }, err => {
97         this.starting = false;
98         this.alert.error(err);
99       });
100
101     }, err => {
102       this.starting = false;
103       this.alert.error(err);
104     });
105   }
106
107   onStopTrace() {
108     this.stopping = true;
109     this.monitoringSvr.stopTrace({}).subscribe(res => {
110       // console.log('Trace Stopped: res', res);
111
112       this.monitoringSvr.stopLowCollector().subscribe((/*res*/) => {
113         // console.log('Low Collector Stopped: res', res);
114         this.alert.info('Monitoring successfully started');
115         this.stopping = false;
116
117       }, err => {
118         this.stopping = false;
119         this.alert.error(err);
120       });
121
122     }, err => {
123       this.stopping = false;
124       this.alert.error(err);
125     });
126   }
127
128   showGraph() {
129     this.router.navigate([`/pages/monitoring/graph`]);
130   }
131
132   isStartBtnDisable(): boolean {
133     return this.starting;
134   }
135
136   isStopBtnDisable(): boolean {
137     return this.stopping;
138   }
139
140   isDaemonDisabled(name: string): boolean {
141     let sts = false;
142     // FIXME - better to use map
143     // with Map
144     // if (this.daemonCheckboxes.has(name)) {
145     //   sts = this.daemonCheckboxes[name].value;
146     // }
147     this.daemonCheckboxes.forEach(e => {
148       if (e.topo.name === name) {
149         sts = true;
150       }
151     });
152     return sts;
153   }
154
155   private createCheckboxes(topo: AglTopology[]) {
156
157     // let newDaemonChB: Map<string, WsCheckbox> = new Map<string, WsCheckbox>();
158     const newDaemonChB: WsCheckbox[] = [];
159     let prevVal = false;
160     this.daemonCheckboxes.forEach(e => {
161       if (e.topo.name === name) {
162         prevVal = e.value;
163       }
164     });
165     topo.forEach(elem => {
166       // with Map
167       // newDaemonChB.set(elem.name, {
168       newDaemonChB.push({
169         topo: Object.assign({}, elem),
170         value: prevVal,
171         tooltip: 'Daemon binding ' + elem.name + ' (pid ' + elem.pid + ')',
172       });
173     });
174
175     this.daemonCheckboxes = newDaemonChB;
176   }
177
178
179   // Compute the distinct nodes from the links.
180   // Based on http://bl.ocks.org/mbostock/1153292
181   private graphAGLBindings(topo: AglTopology[]) {
182
183     const ws_link: { [id: string]: string[] } = {};
184     let ii = 1;
185     topo.forEach(elem => {
186       if (elem.name === 'null') {
187         elem.name = 'Daemon-' + String(ii++);
188       }
189       if (elem.ws_clients && elem.ws_clients instanceof Array) {
190         elem.ws_clients.forEach((ws: string) => {
191           if (ws_link[ws]) {
192             ws_link[ws].push(elem.name);
193           } else {
194             ws_link[ws] = [elem.name];
195           }
196         });
197       }
198       if (elem.ws_servers && elem.ws_servers instanceof Array) {
199         elem.ws_servers.forEach((ws: string) => {
200           if (ws_link[ws]) {
201             ws_link[ws].push(elem.name);
202           } else {
203             ws_link[ws] = [elem.name];
204           }
205         });
206       }
207     });
208
209     const nodes = {};
210     this.links = [];
211     ii = 1;
212     topo.forEach(elem => {
213       let almostOne = false;
214       if (elem.ws_clients && elem.ws_clients.length) {
215         elem.ws_clients.forEach(wsCli => {
216           ws_link[wsCli].forEach(appName => {
217             if (appName !== elem.name) {
218               almostOne = true;
219               this.links.push({ source: elem.name, target: appName, type: 'ws-client' });
220             }
221           });
222         });
223       }
224       if (elem.ws_servers && elem.ws_servers.length) {
225         elem.ws_servers.forEach(wsSvr => {
226           ws_link[wsSvr].forEach(appName => {
227             if (appName !== elem.name) {
228               almostOne = true;
229               this.links.push({ source: elem.name, target: appName, type: 'ws-server' });
230             }
231           });
232         });
233       }
234       if (!almostOne) {
235         const name = '???-' + String(ii++);
236         this.links.push({
237           source: elem.isServer ? name : elem.name,
238           target: elem.isServer ? elem.name : name,
239           type: 'not-connected',
240         });
241       }
242     });
243
244     this.links.forEach(function (link) {
245       link.source = nodes[link.source] || (nodes[link.source] = {
246         name: link.source,
247       });
248       link.target = nodes[link.target] || (nodes[link.target] = {
249         name: link.target,
250       });
251     });
252
253     const width = this.document.getElementById('graph').clientWidth,
254       height = this.document.getElementById('graph').clientHeight;
255
256     // Delete previous graph
257     if (this.svg) {
258       this.svg.remove();
259     }
260
261     // Create new graph
262     const force = d3.layout.force()
263       .nodes(d3.values(nodes))
264       .links(this.links)
265       .size([width, height])
266       .linkDistance(120)
267       .charge(-600)
268       .on('tick', tick)
269       .start();
270     // const force = d3.forceSimulation()
271
272     this.graph = d3.select('#graph');
273     this.svg = this.graph.append('svg')
274       .attr('width', width)
275       .attr('height', height);
276
277     // Define the div for the tooltip
278     /*
279     const divTooltip = d3.select('#graph').append('div')
280       .attr('class', 'tooltip')
281       .style('opacity', 0);
282     */
283
284     // Per-type markers, as they don't inherit styles.
285     this.svg.append('defs').selectAll('marker')
286       .data(['ws-server', 'ws-client', 'not-connected'])
287       .enter().append('marker')
288       .attr('id', function (d) {
289         return d;
290       })
291       .attr('viewBox', '0 -5 10 10')
292       .attr('refX', 15)
293       .attr('refY', -1.5)
294       .attr('markerWidth', 12)
295       .attr('markerHeight', 12)
296       .attr('orient', 'auto')
297       .append('path')
298       .attr('d', 'M0,-5L10,0L0,5');
299
300     const path = this.svg.append('g').selectAll('path')
301       .data(force.links())
302       .enter().append('path')
303       .attr('class', function (d) {
304         return 'link ' + d.type;
305       })
306       .attr('marker-end', function (d) {
307         return 'url(#' + d.type + ')';
308       });
309
310     const circle = this.svg.append('g').selectAll('circle')
311       .data(force.nodes())
312       .enter().append('circle')
313       .attr('r', 12)
314       .call(force.drag);
315
316     const text = this.svg.append('g').selectAll('text')
317       .data(force.nodes())
318       .enter().append('text')
319       .attr('x', 20)
320       .attr('y', '.31em')
321       .text(function (d) {
322         return d.name;
323       });
324
325     /* TODO - SEB
326         circle.on('mouseover', d => {
327           divTooltip.transition()
328             .duration(200)
329             .style('opacity', .9);
330             divTooltip.html('This is a Tooltip <br/>' + d.close)
331             .style('left', (d3.event.pageX) + 'px')
332             .style('top', (d3.event.pageY - 28) + 'px');
333         });
334
335         //  Tooltip Object
336         const tooltip = d3.select('body')
337           .append('div').attr('id', 'tooltip')
338           .style('position', 'absolute')
339           .style('z-index', '10')
340           .style('visibility', 'hidden')
341           .text('a simple tooltip');
342     */
343
344     // Use elliptical arc path segments to doubly-encode directionally.
345     function tick() {
346       path.attr('d', linkArc);
347       circle.attr('transform', transform);
348       text.attr('transform', transform);
349     }
350
351     function linkArc(d) {
352       const dx = d.target.x - d.source.x,
353         dy = d.target.y - d.source.y,
354         dr = Math.sqrt(dx * dx + dy * dy);
355       return 'M' + d.source.x + ',' + d.source.y + 'A' + dr + ',' + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y;
356     }
357
358     function transform(d) {
359       return 'translate(' + d.x + ',' + d.y + ')';
360     }
361   }
362 }