aboutsummaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--.vscode/settings.json106
-rw-r--r--lib/agent/agent.go11
-rw-r--r--lib/agent/apiv1-supervisor.go114
-rw-r--r--lib/agent/apiv1.go3
-rw-r--r--lib/agent/xdsserver.go2
-rw-r--r--lib/agent/xdssupervior.go230
-rw-r--r--lib/xdsconfig/config.go6
-rw-r--r--lib/xdsconfig/configfile.go10
-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
25 files changed, 1370 insertions, 68 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 47bcd75..c1d3b4d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -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,
@@ -31,67 +34,74 @@
],
// 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",
diff --git a/lib/agent/agent.go b/lib/agent/agent.go
index 3aa89a8..58f336c 100644
--- a/lib/agent/agent.go
+++ b/lib/agent/agent.go
@@ -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
index 0000000..a34a913
--- /dev/null
+++ b/lib/agent/apiv1-supervisor.go
@@ -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
+}
diff --git a/lib/agent/apiv1.go b/lib/agent/apiv1.go
index 97165b3..504558e 100644
--- a/lib/agent/apiv1.go
+++ b/lib/agent/apiv1.go
@@ -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
}
diff --git a/lib/agent/xdsserver.go b/lib/agent/xdsserver.go
index f74e3ba..24e51d7 100644
--- a/lib/agent/xdsserver.go
+++ b/lib/agent/xdsserver.go
@@ -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
index 0000000..bbe2500
--- /dev/null
+++ b/lib/agent/xdssupervior.go
@@ -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)
+ }
+ */
+}
diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go
index b7bf57b..352ba84 100644
--- a/lib/xdsconfig/config.go
+++ b/lib/xdsconfig/config.go
@@ -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,
}
diff --git a/lib/xdsconfig/configfile.go b/lib/xdsconfig/configfile.go
index 009517f..1390456 100644
--- a/lib/xdsconfig/configfile.go
+++ b/lib/xdsconfig/configfile.go
@@ -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.
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 => {