3 * Copyright (C) 2017-2019 "IoT.bzh"
4 * Author Sebastien Douheret <sebastien@iot.bzh>
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
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';
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';
29 interface WsCheckbox {
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
41 export class MonitoringConfigComponent implements OnInit, AfterViewInit {
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[] = [];
53 private _aglTopoSub: Subscription;
55 constructor(@Inject(DOCUMENT) private document: Document,
56 private router: Router,
57 private monitoringSvr: MonitoringService,
58 private alert: AlertService,
68 this.aglTopoInit.next(true);
72 if (this._aglTopoSub !== undefined) {
73 this._aglTopoSub.unsubscribe();
76 this._aglTopoSub = this.monitoringSvr.getTopo().subscribe(topo => {
77 this.graphAGLBindings(topo);
78 this.createCheckboxes(topo);
86 this.daemonCheckboxes.forEach(dm => dm.value && dmArr.push(dm.topo.pid));
88 this.monitoringSvr.startTrace({ pids: dmArr }).subscribe(res => {
89 // console.log('Trace Started: res', res);
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;
97 this.starting = false;
98 this.alert.error(err);
102 this.starting = false;
103 this.alert.error(err);
108 this.stopping = true;
109 this.monitoringSvr.stopTrace({}).subscribe(res => {
110 // console.log('Trace Stopped: res', res);
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;
118 this.stopping = false;
119 this.alert.error(err);
123 this.stopping = false;
124 this.alert.error(err);
129 this.router.navigate([`/pages/monitoring/graph`]);
132 isStartBtnDisable(): boolean {
133 return this.starting;
136 isStopBtnDisable(): boolean {
137 return this.stopping;
140 isDaemonDisabled(name: string): boolean {
142 // FIXME - better to use map
144 // if (this.daemonCheckboxes.has(name)) {
145 // sts = this.daemonCheckboxes[name].value;
147 this.daemonCheckboxes.forEach(e => {
148 if (e.topo.name === name) {
155 private createCheckboxes(topo: AglTopology[]) {
157 // let newDaemonChB: Map<string, WsCheckbox> = new Map<string, WsCheckbox>();
158 const newDaemonChB: WsCheckbox[] = [];
160 this.daemonCheckboxes.forEach(e => {
161 if (e.topo.name === name) {
165 topo.forEach(elem => {
167 // newDaemonChB.set(elem.name, {
169 topo: Object.assign({}, elem),
171 tooltip: 'Daemon binding ' + elem.name + ' (pid ' + elem.pid + ')',
175 this.daemonCheckboxes = newDaemonChB;
179 // Compute the distinct nodes from the links.
180 // Based on http://bl.ocks.org/mbostock/1153292
181 private graphAGLBindings(topo: AglTopology[]) {
183 const ws_link: { [id: string]: string[] } = {};
185 topo.forEach(elem => {
186 if (elem.name === 'null') {
187 elem.name = 'Daemon-' + String(ii++);
189 if (elem.ws_clients && elem.ws_clients instanceof Array) {
190 elem.ws_clients.forEach((ws: string) => {
192 ws_link[ws].push(elem.name);
194 ws_link[ws] = [elem.name];
198 if (elem.ws_servers && elem.ws_servers instanceof Array) {
199 elem.ws_servers.forEach((ws: string) => {
201 ws_link[ws].push(elem.name);
203 ws_link[ws] = [elem.name];
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) {
219 this.links.push({ source: elem.name, target: appName, type: 'ws-client' });
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) {
229 this.links.push({ source: elem.name, target: appName, type: 'ws-server' });
235 const name = '???-' + String(ii++);
237 source: elem.isServer ? name : elem.name,
238 target: elem.isServer ? elem.name : name,
239 type: 'not-connected',
244 this.links.forEach(function (link) {
245 link.source = nodes[link.source] || (nodes[link.source] = {
248 link.target = nodes[link.target] || (nodes[link.target] = {
253 const width = this.document.getElementById('graph').clientWidth,
254 height = this.document.getElementById('graph').clientHeight;
256 // Delete previous graph
262 const force = d3.layout.force()
263 .nodes(d3.values(nodes))
265 .size([width, height])
270 // const force = d3.forceSimulation()
272 this.graph = d3.select('#graph');
273 this.svg = this.graph.append('svg')
274 .attr('width', width)
275 .attr('height', height);
277 // Define the div for the tooltip
279 const divTooltip = d3.select('#graph').append('div')
280 .attr('class', 'tooltip')
281 .style('opacity', 0);
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) {
291 .attr('viewBox', '0 -5 10 10')
294 .attr('markerWidth', 12)
295 .attr('markerHeight', 12)
296 .attr('orient', 'auto')
298 .attr('d', 'M0,-5L10,0L0,5');
300 const path = this.svg.append('g').selectAll('path')
302 .enter().append('path')
303 .attr('class', function (d) {
304 return 'link ' + d.type;
306 .attr('marker-end', function (d) {
307 return 'url(#' + d.type + ')';
310 const circle = this.svg.append('g').selectAll('circle')
312 .enter().append('circle')
316 const text = this.svg.append('g').selectAll('text')
318 .enter().append('text')
326 circle.on('mouseover', d => {
327 divTooltip.transition()
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');
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');
344 // Use elliptical arc path segments to doubly-encode directionally.
346 path.attr('d', linkArc);
347 circle.attr('transform', transform);
348 text.attr('transform', transform);
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;
358 function transform(d) {
359 return 'translate(' + d.x + ',' + d.y + ')';