diff options
author | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-06-18 00:42:22 +0200 |
---|---|---|
committer | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-06-18 22:29:55 +0200 |
commit | ee66af78c42c4d7ff33f104415bc09d60dbdc27b (patch) | |
tree | 834e494c29d339940a5a7036671650e439e3864d /webapp | |
parent | 72c9174cecdfbe4cde9baa71c0c02d0bee753224 (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')
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 => { |