Added Supervision/Monitoring support
[src/xds/xds-agent.git] / webapp / src / app / @core-xds / services / xdsagent.service.ts
index 06ca557..002c84b 100644 (file)
@@ -1,3 +1,21 @@
+/**
+* @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 { Injectable, Inject, isDevMode } from '@angular/core';
 import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
 import { DOCUMENT } from '@angular/common';
@@ -7,8 +25,9 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 import * as io from 'socket.io-client';
 
 import { AlertService } from './alert.service';
-import { ISdk } from './sdk.service';
+import { ISdk, ISdkManagementMsg } from './sdk.service';
 import { ProjectType, ProjectTypeEnum } from './project.service';
+import { TargetType, TargetTypeEnum } from './target.service';
 
 // Import RxJs required methods
 import 'rxjs/add/operator/map';
@@ -16,7 +35,7 @@ import 'rxjs/add/operator/catch';
 import 'rxjs/add/observable/throw';
 import 'rxjs/add/operator/mergeMap';
 import 'rxjs/add/observable/of';
-import 'rxjs/add/operator/retryWhen';
+import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
 
 
 export interface IXDSConfigProject {
@@ -47,6 +66,25 @@ export interface IXDSProjectConfig {
   clientData?: string;
 }
 
+/** Targets **/
+export interface IXDSTargetConfig {
+  id?: string;
+  name: string;
+  type: TargetTypeEnum;
+  ip: string;
+  status?: string;
+  terms?: IXDSTargetTerminal[];
+}
+
+export interface IXDSTargetTerminal {
+  id?: string;
+  type: string;
+  name: string;
+  status?: string;
+  cols?: number;
+  rows?: number;
+}
+
 export interface IXDSVer {
   id: string;
   version: string;
@@ -106,15 +144,33 @@ export interface IAgentStatus {
 @Injectable()
 export class XDSAgentService {
 
+  public Socket: SocketIOClient.Socket;
   public XdsConfig$: Observable<IXDSConfig>;
   public Status$: Observable<IAgentStatus>;
   public CmdOutput$ = <Subject<ICmdOutput>>new Subject();
   public CmdExit$ = <Subject<ICmdExit>>new Subject();
 
+  protected sockConnect$ = new Subject<SocketIOClient.Socket>();
+  protected sockDisconnect$ = new Subject<SocketIOClient.Socket>();
+
   protected projectAdd$ = new Subject<IXDSProjectConfig>();
   protected projectDel$ = new Subject<IXDSProjectConfig>();
   protected projectChange$ = new Subject<IXDSProjectConfig>();
 
+  protected sdkAdd$ = new Subject<ISdk>();
+  protected sdkRemove$ = new Subject<ISdk>();
+  protected sdkChange$ = new Subject<ISdk>();
+  protected sdkManagement$ = new Subject<ISdkManagementMsg>();
+
+  protected targetAdd$ = new Subject<IXDSTargetConfig>();
+  protected targetDel$ = new Subject<IXDSTargetConfig>();
+  protected targetChange$ = new Subject<IXDSTargetConfig>();
+
+  protected targetTerminalAdd$ = new Subject<IXDSTargetTerminal>();
+  protected targetTerminalDel$ = new Subject<IXDSTargetTerminal>();
+  protected targetTerminalChange$ = new Subject<IXDSTargetTerminal>();
+
+  private _socket: SocketIOClient.Socket;
   private baseUrl: string;
   private wsUrl: string;
   private httpSessionID: string;
@@ -124,9 +180,9 @@ export class XDSAgentService {
   private configSubject = <BehaviorSubject<IXDSConfig>>new BehaviorSubject(this._config);
   private statusSubject = <BehaviorSubject<IAgentStatus>>new BehaviorSubject(this._status);
 
-  private socket: SocketIOClient.Socket;
 
-  constructor( @Inject(DOCUMENT) private document: Document,
+
+  constructor(@Inject(DOCUMENT) private document: Document,
     private http: HttpClient, private alert: AlertService) {
 
     this.XdsConfig$ = this.configSubject.asObservable();
@@ -138,22 +194,22 @@ export class XDSAgentService {
     // Retrieve Session ID / token
     this.http.get(this.baseUrl + '/version', { observe: 'response' })
       .subscribe(
-      resp => {
-        this.httpSessionID = resp.headers.get('xds-agent-sid');
-
-        const re = originUrl.match(/http[s]?:\/\/([^\/]*)[\/]?/);
-        if (re === null || re.length < 2) {
-          console.error('ERROR: cannot determine Websocket url');
-        } else {
-          this.wsUrl = 'ws://' + re[1];
-          this._handleIoSocket();
-          this._RegisterEvents();
-        }
-      },
-      err => {
-        /* tslint:disable:no-console */
-        console.error('ERROR while retrieving session id:', err);
-      });
+        resp => {
+          this.httpSessionID = resp.headers.get('xds-agent-sid');
+
+          const re = originUrl.match(/http[s]?:\/\/([^\/]*)[\/]?/);
+          if (re === null || re.length < 2) {
+            console.error('ERROR: cannot determine Websocket url');
+          } else {
+            this.wsUrl = 'ws://' + re[1];
+            this._handleIoSocket();
+            this._RegisterEvents();
+          }
+        },
+        err => {
+          /* tslint:disable:no-console */
+          console.error('ERROR while retrieving session id:', err);
+        });
   }
 
   private _NotifyXdsAgentState(sts: boolean) {
@@ -178,45 +234,39 @@ export class XDSAgentService {
   }
 
   private _handleIoSocket() {
-    this.socket = io(this.wsUrl, { transports: ['websocket'] });
+    this.Socket = this._socket = io(this.wsUrl, { transports: ['websocket'] });
 
-    this.socket.on('connect_error', (res) => {
+    this._socket.on('connect_error', (res) => {
       this._NotifyXdsAgentState(false);
       console.error('XDS Agent WebSocket Connection error !');
     });
 
-    this.socket.on('connect', (res) => {
+    this._socket.on('connect', (res) => {
       this._NotifyXdsAgentState(true);
+      this.sockConnect$.next(this._socket);
     });
 
-    this.socket.on('disconnection', (res) => {
+    this._socket.on('disconnection', (res) => {
       this._NotifyXdsAgentState(false);
       this.alert.error('WS disconnection: ' + res);
+      this.sockDisconnect$.next(this._socket);
     });
 
-    this.socket.on('error', (err) => {
+    this._socket.on('error', (err) => {
       console.error('WS error:', err);
     });
 
     // XDS Events decoding
 
-    this.socket.on('make:output', data => {
-      this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
-    });
-
-    this.socket.on('make:exit', data => {
-      this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
-    });
-
-    this.socket.on('exec:output', data => {
+    this._socket.on('exec:output', data => {
       this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
     });
 
-    this.socket.on('exec:exit', data => {
+    this._socket.on('exec:exit', data => {
       this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
     });
 
-    this.socket.on('event:server-config', ev => {
+    this._socket.on('event:server-config', ev => {
       if (ev && ev.data) {
         const cfg: IXDServerCfg = ev.data;
         const idx = this._config.servers.findIndex(el => el.id === cfg.id);
@@ -228,10 +278,12 @@ export class XDSAgentService {
       }
     });
 
-    this.socket.on('event:project-add', (ev) => {
+    /*** Project events ****/
+
+    this._socket.on('event:project-add', (ev) => {
       if (ev && ev.data && ev.data.id) {
         this.projectAdd$.next(Object.assign({}, ev.data));
-        if (ev.sessionID !== this.httpSessionID && ev.data.label) {
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && ev.data.label) {
           this.alert.info('Project "' + ev.data.label + '" has been added by another tool.');
         }
       } else if (isDevMode) {
@@ -240,10 +292,10 @@ export class XDSAgentService {
       }
     });
 
-    this.socket.on('event:project-delete', (ev) => {
+    this._socket.on('event:project-delete', (ev) => {
       if (ev && ev.data && ev.data.id) {
         this.projectDel$.next(Object.assign({}, ev.data));
-        if (ev.sessionID !== this.httpSessionID && ev.data.label) {
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && ev.data.label) {
           this.alert.info('Project "' + ev.data.label + '" has been deleted by another tool.');
         }
       } else if (isDevMode) {
@@ -251,7 +303,7 @@ export class XDSAgentService {
       }
     });
 
-    this.socket.on('event:project-state-change', ev => {
+    this._socket.on('event:project-state-change', ev => {
       if (ev && ev.data) {
         this.projectChange$.next(Object.assign({}, ev.data));
       } else if (isDevMode) {
@@ -259,11 +311,138 @@ export class XDSAgentService {
       }
     });
 
+    /*** SDK Events ***/
+
+    this._socket.on('event:sdk-add', (ev) => {
+      if (ev && ev.data && ev.data.id) {
+        const evt = <ISdk>ev.data;
+        this.sdkAdd$.next(Object.assign({}, evt));
+
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && evt.name) {
+          this.alert.info('SDK "' + evt.name + '" has been added by another tool.');
+        }
+      } else if (isDevMode) {
+        console.log('Warning: received event:sdk-add with unknown data: ev=', ev);
+      }
+    });
+
+    this._socket.on('event:sdk-remove', (ev) => {
+      if (ev && ev.data && ev.data.id) {
+        const evt = <ISdk>ev.data;
+        this.sdkRemove$.next(Object.assign({}, evt));
+
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && evt.name) {
+          this.alert.info('SDK "' + evt.name + '" has been removed by another tool.');
+        }
+      } else if (isDevMode) {
+        console.log('Warning: received event:sdk-remove with unknown data: ev=', ev);
+      }
+    });
+
+    this._socket.on('event:sdk-state-change', (ev) => {
+      if (ev && ev.data && ev.data.id) {
+        const evt = <ISdk>ev.data;
+        this.sdkChange$.next(Object.assign({}, evt));
+
+      } else if (isDevMode) {
+        console.log('Warning: received event:sdk-state-change with unknown data: ev=', ev);
+      }
+    });
+
+    this._socket.on('event:sdk-management', (ev) => {
+      if (ev && ev.data && ev.data.sdk) {
+        const evt = <ISdkManagementMsg>ev.data;
+        this.sdkManagement$.next(Object.assign({}, evt));
+
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && evt.sdk.name) {
+          this.alert.info('SDK "' + evt.sdk.name + '" has been installed by another tool.');
+        }
+      } else if (isDevMode) {
+        /* tslint:disable:no-console */
+        console.log('Warning: received event:sdk-install with unknown data: ev=', ev);
+      }
+    });
+
+    /*** Target events ****/
+
+    this._socket.on('event:target-add', (ev) => {
+      if (ev && ev.data && ev.data.id) {
+        this.targetAdd$.next(Object.assign({}, ev.data));
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && ev.data.label) {
+          this.alert.info('Target "' + ev.data.label + '" has been added by another tool.');
+        }
+      } else if (isDevMode) {
+        /* tslint:disable:no-console */
+        console.log('Warning: received event:target-add with unknown data: ev=', ev);
+      }
+    });
+
+    this._socket.on('event:target-remove', (ev) => {
+      if (ev && ev.data && ev.data.id) {
+        this.targetDel$.next(Object.assign({}, ev.data));
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && ev.data.label) {
+          this.alert.info('Target "' + ev.data.label + '" has been deleted by another tool.');
+        }
+      } else if (isDevMode) {
+        console.log('Warning: received event:target-remove with unknown data: ev=', ev);
+      }
+    });
+
+    this._socket.on('event:target-state-change', ev => {
+      if (ev && ev.data) {
+        this.targetChange$.next(Object.assign({}, ev.data));
+      } else if (isDevMode) {
+        console.log('Warning: received event:target-state-change with unknown data: ev=', ev);
+      }
+    });
+
+    /*** Target Terminal events ****/
+
+    this._socket.on('event:target-terminal-add', (ev) => {
+      if (ev && ev.data && ev.data.id) {
+        this.targetTerminalAdd$.next(Object.assign({}, ev.data));
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && ev.data.label) {
+          this.alert.info('Target terminal "' + ev.data.label + '" has been added by another tool.');
+        }
+      } else if (isDevMode) {
+        /* tslint:disable:no-console */
+        console.log('Warning: received event:target-terminal-add with unknown data: ev=', ev);
+      }
+    });
+
+    this._socket.on('event:target-terminal-delete', (ev) => {
+      if (ev && ev.data && ev.data.id) {
+        this.targetTerminalDel$.next(Object.assign({}, ev.data));
+        if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && ev.data.label) {
+          this.alert.info('Target terminal "' + ev.data.label + '" has been deleted by another tool.');
+        }
+      } else if (isDevMode) {
+        console.log('Warning: received event:target-terminal-delete with unknown data: ev=', ev);
+      }
+    });
+
+    this._socket.on('event:target-terminal-state-change', ev => {
+      if (ev && ev.data) {
+        this.targetTerminalChange$.next(Object.assign({}, ev.data));
+      } else if (isDevMode) {
+        console.log('Warning: received event:target-terminal-state-change with unknown data: ev=', ev);
+      }
+    });
+
   }
 
   /**
   ** Events registration
   ***/
+
+  onSocketConnect(): Observable<any> {
+    return this.sockConnect$.asObservable();
+  }
+
+  onSocketDisconnect(): Observable<any> {
+    return this.sockDisconnect$.asObservable();
+  }
+
   onProjectAdd(): Observable<IXDSProjectConfig> {
     return this.projectAdd$.asObservable();
   }
@@ -276,6 +455,46 @@ export class XDSAgentService {
     return this.projectChange$.asObservable();
   }
 
+  onSdkAdd(): Observable<ISdk> {
+    return this.sdkAdd$.asObservable();
+  }
+
+  onSdkRemove(): Observable<ISdk> {
+    return this.sdkRemove$.asObservable();
+  }
+
+  onSdkChange(): Observable<ISdk> {
+    return this.sdkChange$.asObservable();
+  }
+
+  onSdkManagement(): Observable<ISdkManagementMsg> {
+    return this.sdkManagement$.asObservable();
+  }
+
+  onTargetAdd(): Observable<IXDSTargetConfig> {
+    return this.targetAdd$.asObservable();
+  }
+
+  onTargetDelete(): Observable<IXDSTargetConfig> {
+    return this.targetDel$.asObservable();
+  }
+
+  onTargetChange(): Observable<IXDSTargetConfig> {
+    return this.targetChange$.asObservable();
+  }
+
+  onTargetTerminalAdd(): Observable<IXDSTargetTerminal> {
+    return this.targetTerminalAdd$.asObservable();
+  }
+
+  onTargetTerminalDelete(): Observable<IXDSTargetTerminal> {
+    return this.targetTerminalDel$.asObservable();
+  }
+
+  onTargetTerminalChange(): Observable<IXDSTargetTerminal> {
+    return this.targetTerminalChange$.asObservable();
+  }
+
   /**
   ** Misc / Version
   ***/
@@ -337,10 +556,22 @@ export class XDSAgentService {
     if (!svr || !svr.connected) {
       return Observable.of([]);
     }
-
     return this._get(svr.partialUrl + '/sdks');
   }
 
+  installSdk(serverID: string, id: string, filename?: string, force?: boolean): Observable<ISdk> {
+    return this._post(this._getServerUrl(serverID) + '/sdks', { id: id, filename: filename, force: force });
+  }
+
+  abortInstall(serverID: string, id: string): Observable<ISdk> {
+    return this._post(this._getServerUrl(serverID) + '/sdks/abortinstall', { id: id });
+  }
+
+  removeSdk(serverID: string, id: string): Observable<ISdk> {
+    return this._delete(this._getServerUrl(serverID) + '/sdks/' + id);
+  }
+
+
   /***
   ** Projects
   ***/
@@ -379,6 +610,76 @@ export class XDSAgentService {
       });
   }
 
+
+  /***
+  ** Targets
+  ***/
+  getTargets(serverID: string): Observable<IXDSTargetConfig[]> {
+    return this._get(this._getServerUrl(serverID) + '/targets');
+  }
+
+  addTarget(serverID: string, cfg: IXDSTargetConfig): Observable<IXDSTargetConfig> {
+    return this._post(this._getServerUrl(serverID) + '/targets', cfg);
+  }
+
+  deleteTarget(serverID: string, id: string): Observable<IXDSTargetConfig> {
+    return this._delete(this._getServerUrl(serverID) + '/targets/' + id);
+  }
+
+  updateTarget(serverID: string, cfg: IXDSTargetConfig): Observable<IXDSTargetConfig> {
+    return this._put(this._getServerUrl(serverID) + '/targets/' + cfg.id, cfg);
+  }
+
+  /***
+  ** Terminals
+  ***/
+  getTerminalsTarget(serverID, targetID: string): Observable<IXDSTargetTerminal[]> {
+    return this._get(this._getServerUrl(serverID) + '/targets/' + targetID + '/terminals');
+  }
+
+  getTerminalTarget(serverID, targetID, termID: string): Observable<IXDSTargetTerminal> {
+    return this._get(this._getServerUrl(serverID) + '/targets/' + targetID + '/terminals/' + termID);
+  }
+
+  createTerminalTarget(serverID, targetID: string, cfg: IXDSTargetTerminal): Observable<IXDSTargetTerminal> {
+    return this._post(this._getServerUrl(serverID) + '/targets/' + targetID + '/terminals', cfg);
+  }
+
+  updateTerminalTarget(serverID, targetID: string, cfg: IXDSTargetTerminal): Observable<IXDSTargetTerminal> {
+    if (cfg && (cfg.id !== '' || cfg.id !== undefined)) {
+      return this._put(this._getServerUrl(serverID) + '/targets/' + targetID + '/terminals/' + cfg.id, cfg);
+    }
+    return Observable.throw('Undefined terminal id');
+  }
+
+  openTerminalTarget(serverID, targetID, termID: string): Observable<IXDSTargetTerminal> {
+    return this._post(this._getServerUrl(serverID) + '/targets/' + targetID + '/terminals/' + termID + '/open', {});
+  }
+
+  closeTerminalTarget(serverID, targetID, termID: string): Observable<IXDSTargetTerminal> {
+    return this._post(this._getServerUrl(serverID) + '/targets/' + targetID + '/terminals/' + termID + '/close', {});
+  }
+
+  resizeTerminalTarget(serverID, targetID, termID: string, cols, rows: number): Observable<IXDSTargetTerminal> {
+    return this._post(this._getServerUrl(serverID) + '/targets/' + targetID + '/terminals/' + termID + '/resize',
+      { 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
   ***/
@@ -387,10 +688,10 @@ export class XDSAgentService {
     // Register to all existing events
     this._post('/events/register', { 'name': 'event:all' })
       .subscribe(
-      res => { },
-      error => {
-        this.alert.error('ERROR while registering to all events: ' + error);
-      },
+        res => { },
+        error => {
+          this.alert.error('ERROR while registering to all events: ' + error);
+        },
     );
   }
 
@@ -402,6 +703,17 @@ export class XDSAgentService {
     return svr[0];
   }
 
+  private _getServerUrl(serverID: string): string | ErrorObservable {
+    const svr = this._getServer(serverID);
+    if (!svr || !svr.connected) {
+      if (isDevMode) {
+        console.log('ERROR: XDS Server unknown: serverID=' + serverID);
+      }
+      return Observable.throw('Cannot identify XDS Server');
+    }
+    return svr.partialUrl;
+  }
+
   private _attachAuthHeaders(options?: any) {
     options = options || {};
     const headers = options.headers || new HttpHeaders();