Added Supervision/Monitoring support 97/14497/2
authorSebastien Douheret <sebastien.douheret@iot.bzh>
Sun, 17 Jun 2018 22:42:22 +0000 (00:42 +0200)
committerSebastien Douheret <sebastien.douheret@iot.bzh>
Mon, 18 Jun 2018 20:29:55 +0000 (22:29 +0200)
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>
25 files changed:
.vscode/settings.json
lib/agent/agent.go
lib/agent/apiv1-supervisor.go [new file with mode: 0644]
lib/agent/apiv1.go
lib/agent/xdsserver.go
lib/agent/xdssupervior.go [new file with mode: 0644]
lib/xdsconfig/config.go
lib/xdsconfig/configfile.go
webapp/package.json
webapp/src/app/@core-xds/services/@core-xds-services.module.ts
webapp/src/app/@core-xds/services/supervision.service.ts [new file with mode: 0644]
webapp/src/app/@core-xds/services/xdsagent.service.ts
webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts
webapp/src/app/pages/pages-menu.ts
webapp/src/app/pages/pages-routing.module.ts
webapp/src/app/pages/pages.module.ts
webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts
webapp/src/app/pages/supervision/supervision-config.component.html [new file with mode: 0644]
webapp/src/app/pages/supervision/supervision-config.component.scss [new file with mode: 0644]
webapp/src/app/pages/supervision/supervision-config.component.ts [new file with mode: 0644]
webapp/src/app/pages/supervision/supervision.component.html [new file with mode: 0644]
webapp/src/app/pages/supervision/supervision.component.scss [new file with mode: 0644]
webapp/src/app/pages/supervision/supervision.component.ts [new file with mode: 0644]
webapp/src/app/pages/supervision/supervision.module.ts [new file with mode: 0644]
webapp/src/app/pages/targets/target-add-modal/target-add-modal.component.ts

index 47bcd75..c1d3b4d 100644 (file)
@@ -1,5 +1,8 @@
 // Place your settings in this file to overwrite default and user settings.
 {
+  // Specify GOPATH here to override the one that is set as environment variable. The inferred GOPATH from workspace root overrides this, if go.inferGopath is set to true.
+  "go.gopath": "${workspaceRoot}/../../../../../..",
+
   // Controls the rendering size of tabs in characters.
   // If set to auto, the value will be guessed based on the opened file.
   "editor.tabSize": 2,
   ],
   // Words to add to dictionary for a workspace.
   "cSpell.words": [
-    "apiv",
-    "gonic",
-    "devel",
-    "csrffound",
-    "Syncthing",
-    "STID",
+    "CIFS",
+    "Checkboxes",
+    "EVTSDK",
+    "EXEPATH",
+    "Flds",
+    "Grafana",
+    "IPROJECT",
     "ISTCONFIG",
-    "socketio",
-    "ldflags",
-    "SThg",
-    "stconfig",
-    "Intf",
-    "dismissible",
-    "rpath",
-    "WSID",
-    "sess",
     "IXDS",
-    "golib",
-    "xdsapi",
-    "xdsconfig",
-    "xdsserver",
-    "xdsagent",
-    "nbsp",
     "Inot",
-    "inotify",
-    "cmdi",
-    "sdkid",
-    "Flds",
-    "prjs",
-    "iosk",
-    "CIFS",
-    "IPROJECT",
-    "unregister",
-    "conv",
+    "Intf",
     "PATHMAP",
-    "nospace",
-    "graphx",
+    "STID",
+    "SThg",
+    "Sillyf",
+    "Syncthing",
     "Truthy",
+    "WSID",
+    "XDSSUPERV",
+    "abortinstall",
+    "apiv",
+    "cmdi",
+    "conv",
+    "csrffound",
     "darkviolet",
+    "devel",
+    "dismissible",
     "dwnl",
-    "topnav",
-    "leftbar",
-    "urfave",
-    "unmarshall",
-    "sebd",
-    "priv",
     "evts",
+    "franciscocpg",
     "gdbserver",
-    "tabset",
+    "gerrit",
+    "golib",
+    "gonic",
+    "graphx",
+    "inotify",
+    "iosk",
+    "ldflags",
+    "leftbar",
+    "nbsp",
+    "nospace",
     "pageview",
-    "subpath",
     "prebuild",
+    "priv",
+    "prjs",
     "reflectme",
-    "franciscocpg",
-    "xsapiv",
-    "xaapiv",
-    "Sillyf",
+    "rpath",
+    "sdkid",
+    "sebd",
+    "sess",
+    "socketio",
+    "stconfig",
+    "subpath",
     "tabindex",
-    "EVTSDK",
-    "gerrit",
-    "tgts"
+    "tabset",
+    "tgts",
+    "topnav",
+    "topo",
+    "unmarshall",
+    "unregister",
+    "urfave",
+    "xaapiv",
+    "xdsagent",
+    "xdsapi",
+    "xdsconfig",
+    "xdspvr",
+    "xdsserver",
+    "xsapiv"
   ],
   // codelyzer
   "tslint.rulesDirectory": "./webapp/node_modules/codelyzer",
index 3aa89a8..58f336c 100644 (file)
@@ -47,11 +47,12 @@ type Context struct {
        SThgCmd       *exec.Cmd
        SThgInotCmd   *exec.Cmd
 
-       webServer  *WebServer
-       xdsServers map[string]*XdsServer
-       sessions   *Sessions
-       events     *Events
-       projects   *Projects
+       webServer     *WebServer
+       xdsServers    map[string]*XdsServer
+       XdsSupervisor *XdsSupervisor
+       sessions      *Sessions
+       events        *Events
+       projects      *Projects
 
        Exit chan os.Signal
 }
diff --git a/lib/agent/apiv1-supervisor.go b/lib/agent/apiv1-supervisor.go
new file mode 100644 (file)
index 0000000..a34a913
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+package agent
+
+import (
+       "net/http"
+
+       common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
+       "github.com/gin-gonic/gin"
+)
+
+// getSupervisorTopo : return current AGL daemons topology using supervisor
+func (s *APIService) getSupervisorTopo(c *gin.Context) {
+
+       xdspvr, err := s._initXdsSupervisor()
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       var res XdsSuperVReply
+       if err = xdspvr.GetTopo(&res); err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       if res.Request.Status != "success" {
+               common.APIError(c, res.Request.Info)
+               return
+       }
+
+       c.JSON(http.StatusOK, res.Response)
+}
+
+// startSupervisor : resquest to supervisor to start tracing
+func (s *APIService) startSupervisor(c *gin.Context) {
+
+       xdspvr, err := s._initXdsSupervisor()
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       var cfg XdsSuperVTraceConfig
+       if c.BindJSON(&cfg) != nil {
+               common.APIError(c, "Invalid config argument")
+               return
+       }
+       s.Log.Debugf("Start Supervisor cfgArg %v\n", cfg)
+
+       var res XdsSuperVReply
+       if err = xdspvr.StartTrace(cfg, &res); err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       if res.Request.Status != "success" {
+               common.APIError(c, res.Request.Info)
+               return
+       }
+
+       c.JSON(http.StatusOK, res.Response)
+}
+
+// stopSupervisor : resquest to supervisor to stop tracing
+func (s *APIService) stopSupervisor(c *gin.Context) {
+
+       xdspvr, err := s._initXdsSupervisor()
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       var res XdsSuperVReply
+       if err = xdspvr.StopTrace(&res); err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       if res.Request.Status != "success" {
+               common.APIError(c, res.Request.Info)
+               return
+       }
+
+       c.JSON(http.StatusOK, res.Response)
+}
+
+// _initXdsSupervisor .
+func (s *APIService) _initXdsSupervisor() (*XdsSupervisor, error) {
+
+       if s.XdsSupervisor == nil {
+               xs := NewXdsSupervisor(s.Context)
+               if err := xs.Connect(); err != nil {
+                       return nil, err
+               }
+               s.XdsSupervisor = xs
+       }
+       return s.XdsSupervisor, nil
+}
index 97165b3..504558e 100644 (file)
@@ -67,6 +67,9 @@ func NewAPIV1(ctx *Context) *APIService {
        s.apiRouter.POST("/events/register", s.eventsRegister)
        s.apiRouter.POST("/events/unregister", s.eventsUnRegister)
 
+       s.apiRouter.GET("/supervisor/topo", s.getSupervisorTopo)
+       s.apiRouter.POST("/supervisor/trace/start", s.startSupervisor)
+       s.apiRouter.POST("/supervisor/trace/stop", s.stopSupervisor)
        return s
 }
 
index f74e3ba..24e51d7 100644 (file)
@@ -157,7 +157,7 @@ func (xs *XdsServer) SetLoggerOutput(out io.Writer) {
        xs.logOut = out
 }
 
-// GetConfig
+// GetConfig return the current server config
 func (xs *XdsServer) GetConfig() xaapiv1.ServerCfg {
        return xaapiv1.ServerCfg{
                ID:         xs.ID,
diff --git a/lib/agent/xdssupervior.go b/lib/agent/xdssupervior.go
new file mode 100644 (file)
index 0000000..bbe2500
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+package agent
+
+import (
+       "fmt"
+       "io"
+       "strings"
+       "time"
+
+       common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
+       uuid "github.com/satori/go.uuid"
+)
+
+// XdsSupervisor .
+type XdsSupervisor struct {
+       *Context
+       ID        string
+       BaseURL   string
+       ConnRetry int
+       Connected bool
+       Disabled  bool
+
+       // Private fields
+       client      *common.HTTPClient
+       logOut      io.Writer
+       cbOnConnect OnConnectedXdsSupervCB
+}
+
+// XdsSuperVRequest Resquest field of a reply
+type XdsSuperVRequest struct {
+       Status string `json:"status"`
+       Info   string `json:"info"`
+}
+
+// XdsSuperVReply Reply structure of XDS Supervision Daemon
+type XdsSuperVReply struct {
+       JType    string           `json:"jtype"`
+       Request  XdsSuperVRequest `json:"request"`
+       Response interface{}      `json:"response"`
+}
+
+// XdsSuperVTraceConfig
+type XdsSuperVTraceConfig struct {
+       Pid    int    `json:"pid"`
+       Pids   []int  `json:"pids"`
+       WsName string `json:"ws"`
+}
+
+// OnConnectedXdsSupervCB connect callback
+type OnConnectedXdsSupervCB func(svr *XdsSupervisor) error
+
+// NewXdsSupervisor creates an instance of XdsSupervisor
+func NewXdsSupervisor(ctx *Context) *XdsSupervisor {
+       return &XdsSupervisor{
+               Context:   ctx,
+               ID:        "XdsSupervisor-" + uuid.NewV1().String(),
+               BaseURL:   ctx.Config.FileConf.ProfileConf.XDSBinder.URL,
+               ConnRetry: ctx.Config.FileConf.ProfileConf.XDSBinder.ConnRetry,
+               Connected: false,
+               Disabled:  false,
+
+               logOut: ctx.Log.Out,
+       }
+}
+
+// Connect Establish HTTP connection with XDS Supervisor Dameon
+func (xs *XdsSupervisor) Connect() error {
+       var err error
+       var retry int
+
+       xs.Disabled = false
+       xs.Connected = false
+
+       err = nil
+       for retry = xs.ConnRetry; retry > 0; retry-- {
+               if err = xs._CreateConnectHTTP(); err == nil {
+                       break
+               }
+               if retry == xs.ConnRetry {
+                       // Notify only on the first conn error
+                       // doing that avoid 2 notifs (conn false; conn true) on startup
+                       xs._NotifyState()
+               }
+               xs.Log.Infof("Establishing connection to XDS Supervisor daemon (retry %d/%d)", retry, xs.ConnRetry)
+               time.Sleep(time.Second)
+       }
+       if retry == 0 {
+               // FIXME: re-use _Reconnect to wait longer in background
+               return fmt.Errorf("Connection to XDS Supervisor daemon failure")
+       }
+       if err != nil {
+               return err
+       }
+
+       // Check HTTP connection and establish WS connection
+       err = xs._Connect(false)
+
+       return err
+}
+
+// ConnectOn Register a callback on events reception
+func (xs *XdsSupervisor) ConnectOn(f OnConnectedXdsSupervCB) error {
+       xs.cbOnConnect = f
+       return nil
+}
+
+// GetVersion Send Get request to retrieve XDS Supervision version
+func (xs *XdsSupervisor) GetVersion(res interface{}) error {
+       // FIXME add suffix URLSuffix in common HTTP client lib instead of _BuildURL
+       return xs.client.Get(xs._BuildURL("/version"), &res)
+}
+
+// GetTopo Send Get request to retrieve Services/Daemons topology
+func (xs *XdsSupervisor) GetTopo(res interface{}) error {
+       return xs.client.Get(xs._BuildURL("/list"), &res)
+}
+
+// StartTrace Send Supervisor config and start tracing
+func (xs *XdsSupervisor) StartTrace(cfg XdsSuperVTraceConfig, res interface{}) error {
+       return xs.client.Post(xs._BuildURL("/trace/start"), cfg, &res)
+}
+
+// StopTrace Send Supervisor stop tracing
+func (xs *XdsSupervisor) StopTrace(res interface{}) error {
+       var cfg interface{}
+       return xs.client.Post(xs._BuildURL("/trace/stop"), cfg, res)
+}
+
+/***
+** Private functions
+***/
+
+// _BuildURL .
+func (xs *XdsSupervisor) _BuildURL(url string) string {
+       return url + "?token=HELLO&uuid=magic"
+}
+
+// Create HTTP client
+func (xs *XdsSupervisor) _CreateConnectHTTP() error {
+       var err error
+       // FIXME SEB - Client key not in header but in cookie
+       // temporary workaround: used _BuildURL to append uuid=magic in URL
+       // map[Set-Cookie:[x-afb-uuid-5678=2b185cc3-276b-4097-91fa-d607eaf937e6; Path=/api; Max-Age=32000000; ...
+       //port := strings.Split(xs.BaseURL, ":")[2]
+       //"x-afb-uuid-" + port
+
+       xs.client, err = common.HTTPNewClient(xs.BaseURL,
+               common.HTTPClientConfig{
+                       //HeaderClientKeyName: "Xds-Sid",
+                       HeaderAPIKeyName: "token",
+                       Apikey:           "HELLO",
+                       URLPrefix:        "/api/xds",
+                       CsrfDisable:      true,
+                       LogOut:           xs.logOut,
+                       LogPrefix:        "XDSSUPERV: ",
+                       LogLevel:         common.HTTPLogLevelWarning,
+               })
+
+       xs.client.SetLogLevel(xs.Log.Level.String())
+
+       if err != nil {
+               msg := ": " + err.Error()
+               if strings.Contains(err.Error(), "connection refused") {
+                       msg = fmt.Sprintf("(url: %s)", xs.BaseURL)
+               }
+               return fmt.Errorf("ERROR: cannot connect to XDS Supervisor %s", msg)
+       }
+       if xs.client == nil {
+               return fmt.Errorf("ERROR: cannot connect to XDS Supervisor (null client)")
+       }
+
+       return nil
+}
+
+// _Connect Established HTTP and WS connection
+func (xs *XdsSupervisor) _Connect(reConn bool) error {
+
+       var res interface{}
+       if err := xs.client.Get(xs._BuildURL("/ping"), &res); err != nil {
+               xs.Connected = false
+               if !reConn {
+                       xs._NotifyState()
+               }
+               return err
+       }
+
+       xs.Connected = true
+
+       // Call OnConnect callback
+       if xs.cbOnConnect != nil {
+               xs.cbOnConnect(xs)
+       }
+
+       xs._NotifyState()
+       return nil
+}
+
+// _NotifyState Send event to notify changes
+func (xs *XdsSupervisor) _NotifyState() {
+
+       /* TODO
+       evSts := xaapiv1.ServerCfg{
+               ID:         xs.ID,
+               URL:        xs.BaseURL,
+               APIURL:     xs.APIURL,
+               PartialURL: xs.PartialURL,
+               ConnRetry:  xs.ConnRetry,
+               Connected:  xs.Connected,
+       }
+       if err := xs.events.Emit(xaapiv1.EVTServerConfig, evSts, ""); err != nil {
+               xs.Log.Warningf("Cannot notify XdsServer state change: %v", err)
+       }
+       */
+}
index b7bf57b..352ba84 100644 (file)
@@ -91,6 +91,12 @@ func Init(ctx *cli.Context, log *logrus.Logger) (*Config, error) {
                        SThgConf: &SyncThingConf{
                                Home: defaultSTHomeDir,
                        },
+                       ProfileConf: ProfileConfT{
+                               XDSBinder: XDSBinderConf{
+                                       URL:       "http://localhost:5678",
+                                       ConnRetry: 10,
+                               },
+                       },
                },
                Log: log,
        }
index 009517f..1390456 100644 (file)
@@ -43,6 +43,15 @@ type XDSServerConf struct {
        APIPartialURL string `json:"-"`
 }
 
+type XDSBinderConf struct {
+       URL       string `json:"url"`
+       ConnRetry int    `json:"connRetry"`
+}
+
+type ProfileConfT struct {
+       XDSBinder XDSBinderConf `json:"xdsBinder"`
+}
+
 type FileConfig struct {
        HTTPPort    string          `json:"httpPort"`
        WebAppDir   string          `json:"webAppDir"`
@@ -50,6 +59,7 @@ type FileConfig struct {
        XDSAPIKey   string          `json:"xds-apikey"`
        ServersConf []XDSServerConf `json:"xdsServers"`
        SThgConf    *SyncThingConf  `json:"syncthing"`
+       ProfileConf ProfileConfT    `json:"profileConf"`
 }
 
 // readGlobalConfig reads configuration from a config file.
index d79aa91..2df4888 100644 (file)
@@ -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",
index a3a67c5..6a4eb3c 100644 (file)
@@ -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 (file)
index 0000000..4a9f578
--- /dev/null
@@ -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);
+  }
+
+}
index adbee98..002c84b 100644 (file)
@@ -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
   ***/
index 67a7d87..d3fec86 100644 (file)
@@ -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() {
index 86884bc..230966d 100644 (file)
@@ -47,11 +47,6 @@ export const MENU_ITEMS: NbMenuItem[] = [
     title: 'DEVELOPMENT',
     group: true,
   },
-  {
-    title: 'Projects',
-    icon: 'nb-keypad',
-    link: '/pages/projects',
-  },
   {
     title: 'SDKs',
     icon: 'fa fa-file-archive-o',
@@ -65,6 +60,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',
@@ -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',
index 655dea2..ae2ef4a 100644 (file)
@@ -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: '',
@@ -52,6 +54,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',
index 55fe61a..5ffa8d6 100644 (file)
@@ -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,
index 1957c8b..c21cb66 100644 (file)
@@ -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 (file)
index 0000000..1fbcd70
--- /dev/null
@@ -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 (file)
index 0000000..ea263cc
--- /dev/null
@@ -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 (file)
index 0000000..e96b936
--- /dev/null
@@ -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 (file)
index 0000000..0db8ec8
--- /dev/null
@@ -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 (file)
index 0000000..a125e8d
--- /dev/null
@@ -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 (file)
index 0000000..219f28f
--- /dev/null
@@ -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 (file)
index 0000000..4c1cb0b
--- /dev/null
@@ -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 { }
index fdcb048..6260b87 100644 (file)
@@ -88,7 +88,6 @@ export class TargetAddModalComponent implements OnInit {
         if (this._isIPstart(n)) {
           return 'Target_' + n;
         }
-//        SEB PB
         return n;
       })
       .subscribe(value => {