summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2018-06-18 00:42:22 +0200
committerSebastien Douheret <sebastien.douheret@iot.bzh>2018-06-18 22:29:55 +0200
commitee66af78c42c4d7ff33f104415bc09d60dbdc27b (patch)
tree834e494c29d339940a5a7036671650e439e3864d /webapp
parent72c9174cecdfbe4cde9baa71c0c02d0bee753224 (diff)
Added Supervision/Monitoring support
Added new API supervisor/* to control and get status of AGL XDS Supervisor. Also add new panel in dashboard to control and visualized data collected by the supervision (visualiazation is based on Granafa). Change-Id: I093470f6e384e96a0856b233390e85a98911162e Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'webapp')
-rw-r--r--webapp/package.json2
-rw-r--r--webapp/src/app/@core-xds/services/@core-xds-services.module.ts2
-rw-r--r--webapp/src/app/@core-xds/services/supervision.service.ts61
-rw-r--r--webapp/src/app/@core-xds/services/xdsagent.service.ts15
-rw-r--r--webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts2
-rw-r--r--webapp/src/app/pages/pages-menu.ts31
-rw-r--r--webapp/src/app/pages/pages-routing.module.ts8
-rw-r--r--webapp/src/app/pages/pages.module.ts2
-rw-r--r--webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts2
-rw-r--r--webapp/src/app/pages/supervision/supervision-config.component.html45
-rw-r--r--webapp/src/app/pages/supervision/supervision-config.component.scss139
-rw-r--r--webapp/src/app/pages/supervision/supervision-config.component.ts305
-rw-r--r--webapp/src/app/pages/supervision/supervision.component.html69
-rw-r--r--webapp/src/app/pages/supervision/supervision.component.scss83
-rw-r--r--webapp/src/app/pages/supervision/supervision.component.ts152
-rw-r--r--webapp/src/app/pages/supervision/supervision.module.ts37
-rw-r--r--webapp/src/app/pages/targets/target-add-modal/target-add-modal.component.ts1
17 files changed, 942 insertions, 14 deletions
diff --git a/webapp/package.json b/webapp/package.json
index d79aa91..2df4888 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -52,7 +52,7 @@
"bootstrap": "4.0.0-beta.2",
"classlist.js": "1.1.20150312",
"core-js": "2.4.1",
- "d3": "4.8.0",
+ "d3": "3.5.17",
"font-awesome": "4.7.0",
"font-awesome-animation": "0.0.10",
"intl": "1.2.5",
diff --git a/webapp/src/app/@core-xds/services/@core-xds-services.module.ts b/webapp/src/app/@core-xds/services/@core-xds-services.module.ts
index a3a67c5..6a4eb3c 100644
--- a/webapp/src/app/@core-xds/services/@core-xds-services.module.ts
+++ b/webapp/src/app/@core-xds/services/@core-xds-services.module.ts
@@ -23,6 +23,7 @@ import { AlertService } from './alert.service';
import { ConfigService } from './config.service';
import { ProjectService } from './project.service';
import { SdkService } from './sdk.service';
+import { SupervisionService } from './supervision.service';
import { TargetService } from './target.service';
import { UserService } from './users.service';
import { XDSConfigService } from './xds-config.service';
@@ -33,6 +34,7 @@ const SERVICES = [
ConfigService,
ProjectService,
SdkService,
+ SupervisionService,
TargetService,
UserService,
XDSConfigService,
diff --git a/webapp/src/app/@core-xds/services/supervision.service.ts b/webapp/src/app/@core-xds/services/supervision.service.ts
new file mode 100644
index 0000000..4a9f578
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/supervision.service.ts
@@ -0,0 +1,61 @@
+/**
+* @license
+* Copyright (C) 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 { Injectable, SecurityContext, isDevMode } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+import { XDSAgentService } from '../services/xdsagent.service';
+
+export interface AglTopology {
+ name: string;
+ pid: number;
+ isClient: boolean;
+ isServer: boolean;
+ ws_clients: string[];
+ ws_servers: string[];
+ apis: any;
+}
+
+@Injectable()
+export class SupervisionService {
+
+ private curServerID;
+
+ constructor(private xdsSvr: XDSAgentService) {
+ /*
+ this.xdsSvr.XdsConfig$.subscribe(cfg => {
+ if (!cfg || cfg.servers.length < 1) {
+ return;
+ }
+ });
+ */
+ }
+
+ getTopo(): Observable<AglTopology[]> {
+ return this.xdsSvr.getTopoSupervisor();
+ }
+
+ startTrace(cfg: any): Observable<any> {
+ return this.xdsSvr.startTraceSupervisor(cfg);
+ }
+
+ stopTrace(cfg: any): Observable<any> {
+ return this.xdsSvr.stopTraceSupervisor(cfg);
+ }
+
+}
diff --git a/webapp/src/app/@core-xds/services/xdsagent.service.ts b/webapp/src/app/@core-xds/services/xdsagent.service.ts
index adbee98..002c84b 100644
--- a/webapp/src/app/@core-xds/services/xdsagent.service.ts
+++ b/webapp/src/app/@core-xds/services/xdsagent.service.ts
@@ -665,6 +665,21 @@ export class XDSAgentService {
{ cols: cols, rows: rows });
}
+ /***
+ ** Supervision
+ ***/
+ getTopoSupervisor(): Observable<any> {
+ return this._get('/supervisor/topo');
+ }
+
+ startTraceSupervisor(cfg: any): Observable<any> {
+ return this._post('/supervisor/trace/start', cfg);
+ }
+
+ stopTraceSupervisor(cfg: any): Observable<any> {
+ return this._post('/supervisor/trace/stop', cfg);
+ }
+
/**
** Private functions
***/
diff --git a/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts b/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts
index 67a7d87..d3fec86 100644
--- a/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts
+++ b/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts
@@ -17,7 +17,6 @@
*/
import { Component, OnInit, Input } from '@angular/core';
-import { DomSanitizer } from '@angular/platform-browser';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
export enum EType {
@@ -69,7 +68,6 @@ export class ConfirmModalComponent implements OnInit {
constructor(
private modalRef: NgbActiveModal,
- private sanitizer: DomSanitizer,
) { }
ngOnInit() {
diff --git a/webapp/src/app/pages/pages-menu.ts b/webapp/src/app/pages/pages-menu.ts
index 86884bc..230966d 100644
--- a/webapp/src/app/pages/pages-menu.ts
+++ b/webapp/src/app/pages/pages-menu.ts
@@ -48,11 +48,6 @@ export const MENU_ITEMS: NbMenuItem[] = [
group: true,
},
{
- title: 'Projects',
- icon: 'nb-keypad',
- link: '/pages/projects',
- },
- {
title: 'SDKs',
icon: 'fa fa-file-archive-o',
link: '/pages/sdks',
@@ -66,6 +61,16 @@ export const MENU_ITEMS: NbMenuItem[] = [
*/
},
{
+ title: 'Projects',
+ icon: 'nb-keypad',
+ link: '/pages/projects',
+ },
+ {
+ title: 'Build',
+ icon: 'fa fa-cogs',
+ link: '/pages/build',
+ },
+ {
title: 'Targets',
icon: 'fa fa-microchip',
link: '/pages/targets',
@@ -81,9 +86,19 @@ export const MENU_ITEMS: NbMenuItem[] = [
],
},
{
- title: 'Build',
- icon: 'fa fa-cogs',
- link: '/pages/build',
+ title: 'Supervision / Monitoring',
+ icon: 'fa fa-bar-chart',
+ link: '/pages/supervision',
+ children: [
+ {
+ title: 'Config',
+ link: '/pages/supervision/config',
+ },
+ {
+ title: 'Graph',
+ link: '/pages/supervision/graph',
+ },
+ ],
},
{
title: 'MISC',
diff --git a/webapp/src/app/pages/pages-routing.module.ts b/webapp/src/app/pages/pages-routing.module.ts
index 655dea2..ae2ef4a 100644
--- a/webapp/src/app/pages/pages-routing.module.ts
+++ b/webapp/src/app/pages/pages-routing.module.ts
@@ -27,6 +27,8 @@ import { SdkManagementComponent } from './sdks/sdk-management/sdk-management.com
import { TargetsComponent } from './targets/targets.component';
import { TerminalsComponent } from './targets/terminals/terminals.component';
import { BuildComponent } from './build/build.component';
+import { SupervisionComponent } from './supervision/supervision.component';
+import { SupervisionConfigComponent } from './supervision/supervision-config.component';
const routes: Routes = [{
path: '',
@@ -53,6 +55,12 @@ const routes: Routes = [{
path: 'targets/term',
component: TerminalsComponent,
}, {
+ path: 'supervision/config',
+ component: SupervisionConfigComponent,
+ }, {
+ path: 'supervision/graph',
+ component: SupervisionComponent,
+ }, {
path: 'config',
loadChildren: './config/config.module#ConfigModule',
},
diff --git a/webapp/src/app/pages/pages.module.ts b/webapp/src/app/pages/pages.module.ts
index 55fe61a..5ffa8d6 100644
--- a/webapp/src/app/pages/pages.module.ts
+++ b/webapp/src/app/pages/pages.module.ts
@@ -26,6 +26,7 @@ import { DashboardModule } from './dashboard/dashboard.module';
import { BuildModule } from './build/build.module';
import { ProjectsModule } from './projects/projects.module';
import { SdksModule } from './sdks/sdks.module';
+import { SupervisionModule } from './supervision/supervision.module';
import { TargetsModule } from './targets/targets.module';
import { PagesRoutingModule } from './pages-routing.module';
import { NotificationsComponent } from './notifications/notifications.component';
@@ -48,6 +49,7 @@ const PAGES_COMPONENTS = [
SdksModule,
ToasterModule,
TargetsModule,
+ SupervisionModule,
],
declarations: [
...PAGES_COMPONENTS,
diff --git a/webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts b/webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts
index 1957c8b..c21cb66 100644
--- a/webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts
+++ b/webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts
@@ -17,7 +17,6 @@
*/
import { Component, OnInit, Input, ViewChild, AfterViewChecked, ElementRef } from '@angular/core';
-import { DomSanitizer } from '@angular/platform-browser';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { AlertService } from '../../../@core-xds/services/alert.service';
@@ -73,7 +72,6 @@ export class SdkInstallComponent implements OnInit {
constructor(
private modalRef: NgbActiveModal,
- private sanitizer: DomSanitizer,
private alert: AlertService,
private sdkSvr: SdkService,
) { }
diff --git a/webapp/src/app/pages/supervision/supervision-config.component.html b/webapp/src/app/pages/supervision/supervision-config.component.html
new file mode 100644
index 0000000..1fbcd70
--- /dev/null
+++ b/webapp/src/app/pages/supervision/supervision-config.component.html
@@ -0,0 +1,45 @@
+<div class="row">
+ <h3 style="margin-top: auto; margin-bottom: auto">Configuration</h3>
+
+ <div class="row">
+ <div class="col-md-12">
+ <nb-card-body>
+ <div class="col-md-9">
+ <nb-actions size="small">
+ <nb-action>
+ <button id="refresh-topo" (click)="getAGLTopo()">
+ <i class="fa fa-refresh"></i>
+ </button>
+ </nb-action>
+ </nb-actions>
+ </div>
+ </nb-card-body>
+ </div>
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-10">
+ <svg id="graph" width="100%" height="500">
+ </svg>
+ </div>
+ <div class="col-md-2">
+ <div>
+ <label>Daemons to monitor</label>
+ </div>
+ <nb-checkbox *ngFor="let wsCkx of daemonCheckboxes" [disabled]="wsCkx.disabled" [(ngModel)]="wsCkx.value">{{wsCkx.name}}
+ </nb-checkbox>
+ <div style="margin-top: 20px;">
+ <div>
+ <label>Monitoring actions:</label>
+ </div>
+ <button id="start-trace" class="btn btn-primary" (click)="onStartTrace()" [disabled]="
+ isStartBtnDisable()">{{ starting ?"Starting... ":"Start" }}
+ <span *ngIf="starting" class="fa fa-gear faa-spin animated fa-size-x2"></span>
+ </button>
+ <button id="stop-trace" class="btn btn-primary" (click)="onStopTrace()" [disabled]="
+ isStopBtnDisable()">{{ stopping ?"Stopping... ":"Stop" }}
+ <span *ngIf="stopping" class="fa fa-gear faa-spin animated fa-size-x2"></span>
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/webapp/src/app/pages/supervision/supervision-config.component.scss b/webapp/src/app/pages/supervision/supervision-config.component.scss
new file mode 100644
index 0000000..ea263cc
--- /dev/null
+++ b/webapp/src/app/pages/supervision/supervision-config.component.scss
@@ -0,0 +1,139 @@
+/* FIXME: not working due to workaround (ViewEncapsulation.None) for svg
+@import '../../@theme/styles/themes';
+@import '~@nebular/theme/components/card/card.component.theme';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+@include nb-install-component() {
+ nb-card-body {
+ display: flex;
+ align-items: center;
+ }
+ .action-groups-header {
+ flex-basis: 20%;
+ color: nb-theme(card-header-fg-heading);
+ font-family: nb-theme(card-header-font-family);
+ font-size: nb-theme(card-header-font-size);
+ font-weight: nb-theme(card-header-font-weight);
+ }
+ .nb-actions {
+ flex-basis: 80%;
+ }
+ .right {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ order: 1;
+ flex-direction: row-reverse;
+ }
+ nb-actions > nb-action {
+ padding: 0;
+ }
+ nb-action {
+ i {
+ color: nb-theme(color-fg);
+ font-size: 1.5rem;
+ margin-right: 1rem;
+ }
+ span {
+ font-family: nb-theme(font-secondary);
+ font-weight: nb-theme(font-weight-bold);
+ color: nb-theme(color-fg-heading);
+ text-transform: uppercase;
+ }
+ button {
+ margin: 0 auto;
+ padding: 0;
+ cursor: pointer;
+ border: none;
+ background: none;
+ display: flex;
+ align-items: center;
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+ }
+ }
+ @include media-breakpoint-down(md) {
+ nb-actions nb-action {
+ padding: 0 0.75rem;
+ }
+ }
+ @include media-breakpoint-down(sm) {
+ nb-card-body {
+ padding: 1rem;
+ }
+ nb-action {
+ font-size: 0.75rem;
+ i {
+ font-size: 2rem;
+ margin-right: 0.5rem;
+ }
+ }
+ }
+ @include media-breakpoint-down(is) {
+ nb-action i {
+ font-size: 1.75rem;
+ margin: 0;
+ }
+ span {
+ display: none;
+ }
+ }
+}
+*/
+
+button#refresh-topo {
+ color: #a4abb3;
+ font-size: 1.5rem;
+ margin-right: 1rem;
+ margin: 0 auto;
+ padding: 0;
+ cursor: pointer;
+ border: none;
+ background: none;
+ display: flex;
+ align-items: center;
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+}
+
+button#start-trace {
+ margin-top: 10px;
+ margin-left: 10px;
+}
+
+button#stop-trace {
+ margin-top: 10px;
+ margin-left: 10px;
+}
+
+svg#graph {
+ .link {
+ fill: none;
+ stroke: #666;
+ stroke-width: 1.5px;
+ }
+ #ws-client {
+ fill: green;
+ }
+ .link.ws-client {
+ stroke: green;
+ }
+ .link.not-connected {
+ stroke-dasharray: 0, 2 1;
+ }
+ circle {
+ fill: #ccc;
+ stroke: #333;
+ stroke-width: 1.5px;
+ }
+ text {
+ font: 1rem sans-serif;
+ pointer-events: none;
+ text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
+ }
+}
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
index 0000000..e96b936
--- /dev/null
+++ b/webapp/src/app/pages/supervision/supervision-config.component.ts
@@ -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 + ')';
+ }
+ }
+}
diff --git a/webapp/src/app/pages/supervision/supervision.component.html b/webapp/src/app/pages/supervision/supervision.component.html
new file mode 100644
index 0000000..0db8ec8
--- /dev/null
+++ b/webapp/src/app/pages/supervision/supervision.component.html
@@ -0,0 +1,69 @@
+<!-- FIXME - cleanup
+<div class="row" *ngIf="displayMode==='panels'">
+ <div class="col-12">
+ <nb-card-body>
+ <div class="col-9">
+ <nb-actions size="small" *ngIf="displayMode==='panels'">
+ <nb-action>
+ <button (click)="timeChange(-1)">
+ <i class="nb-skip-backward"></i>
+ </button>
+ </nb-action>
+ <nb-action>
+ <button (click)="zoomOut()">
+ <i class="nb-search"></i>
+ </button>
+ </nb-action>
+ <nb-action>
+ <button (click)="timeChange(1)">
+ <i class="nb-skip-forward"></i>
+ </button>
+ </nb-action>
+ <nb-action>
+ <button disabled>
+ <pre>
+ start={{tm_from}} end={{tm_to}}
+ </pre>
+ </button>
+ </nb-action>
+ </nb-actions>
+ </div>
+ <div class="col-3 right">
+ <nb-actions size="small">
+ <nb-action>
+ <button (click)="displayModeChange()">
+ <i class="fa fa-eye"></i>
+ </button>
+ </nb-action>
+ </nb-actions>
+ </div>
+ </nb-card-body>
+ </div>
+</div>
+
+-- Display mode: using panels --
+<div *ngIf="displayMode==='panels'">
+ <div class="row">
+ <div class="col-md-6 col-lg-6">
+ <iframe [src]="getPanel('req_evts_per_sec')" width="100%" height="320" frameborder="0"></iframe>
+ </div>
+
+ <div class="col-md-6 col-lg-6">
+ <iframe [src]="getPanel('evt_data_bytes')" width="100%" height="320" frameborder="0"></iframe>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-12">
+ <iframe [src]="getPanel('table')" width="100%" height="500px" frameborder="0"></iframe>
+ </div>
+ </div>
+</div>
+-->
+
+<!-- Display mode: using dashboard -->
+<div class="row" *ngIf="displayMode==='dashboard'">
+ <div class="col-md-12">
+ <iframe [src]="getDashboard('xds_supervisor')" width="100%" height="800px" frameborder="0"></iframe>
+ </div>
+</div>
diff --git a/webapp/src/app/pages/supervision/supervision.component.scss b/webapp/src/app/pages/supervision/supervision.component.scss
new file mode 100644
index 0000000..a125e8d
--- /dev/null
+++ b/webapp/src/app/pages/supervision/supervision.component.scss
@@ -0,0 +1,83 @@
+@import '../../@theme/styles/themes';
+@import '~@nebular/theme/components/card/card.component.theme';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+@include nb-install-component() {
+ nb-card-body {
+ display: flex;
+ align-items: center;
+ }
+ .action-groups-header {
+ flex-basis: 20%;
+ color: nb-theme(card-header-fg-heading);
+ font-family: nb-theme(card-header-font-family);
+ font-size: nb-theme(card-header-font-size);
+ font-weight: nb-theme(card-header-font-weight);
+ }
+ .nb-actions {
+ flex-basis: 80%;
+ }
+ .right {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ order: 1;
+ flex-direction: row-reverse;
+ }
+ nb-actions > nb-action {
+ padding: 0;
+ }
+ nb-action {
+ i {
+ color: nb-theme(color-fg);
+ font-size: 1.5rem;
+ margin-right: 1rem;
+ }
+ span {
+ font-family: nb-theme(font-secondary);
+ font-weight: nb-theme(font-weight-bold);
+ color: nb-theme(color-fg-heading);
+ text-transform: uppercase;
+ }
+ button {
+ margin: 0 auto;
+ padding: 0;
+ cursor: pointer;
+ border: none;
+ background: none;
+ display: flex;
+ align-items: center;
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+ }
+ }
+ @include media-breakpoint-down(md) {
+ nb-actions nb-action {
+ padding: 0 0.75rem;
+ }
+ }
+ @include media-breakpoint-down(sm) {
+ nb-card-body {
+ padding: 1rem;
+ }
+ nb-action {
+ font-size: 0.75rem;
+ i {
+ font-size: 2rem;
+ margin-right: 0.5rem;
+ }
+ }
+ }
+ @include media-breakpoint-down(is) {
+ nb-action i {
+ font-size: 1.75rem;
+ margin: 0;
+ }
+ span {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/pages/supervision/supervision.component.ts b/webapp/src/app/pages/supervision/supervision.component.ts
new file mode 100644
index 0000000..219f28f
--- /dev/null
+++ b/webapp/src/app/pages/supervision/supervision.component.ts
@@ -0,0 +1,152 @@
+/**
+* @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, Input } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Subject } from 'rxjs/Subject';
+import { NbThemeService } from '@nebular/theme';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+
+import { SupervisionService } from '../../@core-xds/services/supervision.service';
+import { AlertService } from '../../@core-xds/services/alert.service';
+
+export interface GrafanaDashboard {
+ name: string;
+ shortname: string;
+ url?: string;
+ safeUrl?: SafeResourceUrl;
+}
+
+export interface GrafanaPanel {
+ name: string;
+ index: string;
+ url?: string;
+ safeUrl?: SafeResourceUrl;
+}
+
+@Component({
+ selector: 'xds-supervision',
+ styleUrls: ['./supervision.component.scss'],
+ templateUrl: './supervision.component.html',
+})
+
+export class SupervisionComponent implements OnInit {
+
+ @Input() theme = 'light';
+ @Input() tm_from = 1528988550450;
+ @Input() tm_to = 1528988842496;
+ @Input() scroll_factor = 10000;
+ @Input() zoom_factor = 100000;
+
+ displayMode = 'dashboard';
+
+ private dashboards: Map<string, GrafanaDashboard> = new Map<string, GrafanaDashboard>([
+ ['xds_supervisor', { name: 'AGL XDS Supervisor', shortname: 'agl-xds-supervisor' }],
+ ]);
+
+ private panels: Map<string, GrafanaPanel> = new Map<string, GrafanaPanel>([
+ ['table', { name: 'Supervisor traces table', index: '2' }],
+ ['evt_data_bytes', { name: 'Requests & Events per second', index: '5' }],
+ ['req_evts_per_sec', { name: 'Events Data bytes', index: '12' }],
+ ]);
+
+ constructor(
+ private supervisionSvr: SupervisionService,
+ private alert: AlertService,
+ private themeService: NbThemeService,
+ private sanitizer: DomSanitizer,
+ ) {
+ }
+
+ ngOnInit() {
+ this._initDashboard();
+ this._initPanels();
+
+ this.themeService.onThemeChange().subscribe(tm => {
+ this.theme = (tm.name === 'cosmic') ? 'dark' : 'light';
+ this.themeUpdate();
+ });
+ }
+
+ getDashboard(name: string): SafeResourceUrl {
+ return this.dashboards.get(name).safeUrl;
+ }
+
+ getPanel(name: string): SafeResourceUrl {
+ return this.panels.get(name).safeUrl;
+ }
+
+ displayModeChange() {
+ if (this.displayMode === 'dashboard') {
+ this.displayMode = 'panels';
+ } else {
+ this.displayMode = 'dashboard';
+ }
+ }
+
+ themeUpdate() {
+ this._initDashboard();
+ this._initPanels();
+ }
+
+ timeChange(val: number) {
+ this.tm_from += val * this.scroll_factor;
+ this.tm_to += val * this.scroll_factor;
+ this._initPanels();
+ }
+
+ zoomOut() {
+ this.tm_from -= this.zoom_factor;
+ this.tm_to += this.zoom_factor;
+ this._initPanels();
+ }
+
+
+ private _initDashboard() {
+ this.dashboards.forEach(dd => {
+ dd.url = this._buildDashboardUrl(dd.shortname, this.tm_from, this.tm_to, this.theme);
+ dd.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(dd.url);
+ });
+ }
+ private _initPanels() {
+ this.panels.forEach(gg => {
+ gg.url = this._buildPanelUrl(gg.index, this.tm_from, this.tm_to, this.theme);
+ gg.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(gg.url);
+ });
+ }
+
+ private _buildDashboardUrl(sname: string, from: number, to: number, theme: string) {
+ let url = 'http://localhost:3000/d/Lbpwc6Iiz/' + sname;
+ url += '?orgId=1';
+ url += '&from=' + from;
+ url += '&to=' + to;
+ url += '&theme=' + theme;
+ return url;
+ }
+
+ private _buildPanelUrl(idx: string, from: number, to: number, theme: string) {
+ let url = 'http://localhost:3000/d-solo/Lbpwc6Iiz/agl-xds-supervisor';
+ url += '?panelId=' + idx;
+ url += '&orgId=1';
+ url += '&from=' + from;
+ url += '&to=' + to;
+ url += '&theme=' + theme;
+ url += '&sidemenu=close';
+ return url;
+ }
+}
diff --git a/webapp/src/app/pages/supervision/supervision.module.ts b/webapp/src/app/pages/supervision/supervision.module.ts
new file mode 100644
index 0000000..4c1cb0b
--- /dev/null
+++ b/webapp/src/app/pages/supervision/supervision.module.ts
@@ -0,0 +1,37 @@
+/**
+* @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 { NgModule } from '@angular/core';
+import { ThemeModule } from '../../@theme/theme.module';
+
+import { SupervisionComponent } from './supervision.component';
+import { SupervisionConfigComponent } from './supervision-config.component';
+
+
+@NgModule({
+ imports: [
+ ThemeModule,
+ ],
+ declarations: [
+ SupervisionComponent,
+ SupervisionConfigComponent,
+ ],
+ entryComponents: [
+ ],
+})
+export class SupervisionModule { }
diff --git a/webapp/src/app/pages/targets/target-add-modal/target-add-modal.component.ts b/webapp/src/app/pages/targets/target-add-modal/target-add-modal.component.ts
index fdcb048..6260b87 100644
--- a/webapp/src/app/pages/targets/target-add-modal/target-add-modal.component.ts
+++ b/webapp/src/app/pages/targets/target-add-modal/target-add-modal.component.ts
@@ -88,7 +88,6 @@ export class TargetAddModalComponent implements OnInit {
if (this._isIPstart(n)) {
return 'Target_' + n;
}
-// SEB PB
return n;
})
.subscribe(value => {