Rework development page: Pre-build, Build, Populate.
authorSebastien Douheret <sebastien.douheret@iot.bzh>
Mon, 22 May 2017 21:58:31 +0000 (23:58 +0200)
committerSebastien Douheret <sebastien.douheret@iot.bzh>
Wed, 24 May 2017 22:17:51 +0000 (00:17 +0200)
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
36 files changed:
README.md
lib/apiv1/apiv1.go
lib/apiv1/exec.go
lib/apiv1/make.go
lib/common/execPipeWs.go
lib/crosssdk/sdk.go
lib/crosssdk/sdks.go
lib/webserver/server.go
main.go
webapp/src/app/alert/alert.component.ts
webapp/src/app/app.component.html
webapp/src/app/app.module.ts
webapp/src/app/app.routing.ts
webapp/src/app/build/build.component.html [deleted file]
webapp/src/app/config/config.component.ts
webapp/src/app/devel/build/build.component.css [moved from webapp/src/app/build/build.component.css with 75% similarity]
webapp/src/app/devel/build/build.component.html [new file with mode: 0644]
webapp/src/app/devel/build/build.component.ts [moved from webapp/src/app/build/build.component.ts with 53% similarity]
webapp/src/app/devel/deploy/deploy.component.css [new file with mode: 0644]
webapp/src/app/devel/deploy/deploy.component.html [new file with mode: 0644]
webapp/src/app/devel/deploy/deploy.component.ts [new file with mode: 0644]
webapp/src/app/devel/devel.component.css [new file with mode: 0644]
webapp/src/app/devel/devel.component.html [new file with mode: 0644]
webapp/src/app/devel/devel.component.ts [new file with mode: 0644]
webapp/src/app/projects/projectCard.component.ts
webapp/src/app/projects/projectsListAccordion.component.ts
webapp/src/app/sdks/sdkCard.component.ts
webapp/src/app/sdks/sdkSelectDropdown.component.ts
webapp/src/app/sdks/sdksListAccordion.component.ts
webapp/src/app/services/alert.service.ts [moved from webapp/src/app/common/alert.service.ts with 100% similarity]
webapp/src/app/services/config.service.ts [moved from webapp/src/app/common/config.service.ts with 89% similarity]
webapp/src/app/services/sdk.service.ts [moved from webapp/src/app/common/sdk.service.ts with 94% similarity]
webapp/src/app/services/syncthing.service.ts [moved from webapp/src/app/common/syncthing.service.ts with 100% similarity]
webapp/src/app/services/utils.service.ts [moved from webapp/src/app/common/utils.service.ts with 100% similarity]
webapp/src/app/services/xdsagent.service.ts [moved from webapp/src/app/common/xdsagent.service.ts with 88% similarity]
webapp/src/app/services/xdsserver.service.ts [moved from webapp/src/app/common/xdsserver.service.ts with 88% similarity]

index b910e06..1697009 100644 (file)
--- a/README.md
+++ b/README.md
@@ -128,3 +128,5 @@ Visual Studio Code launcher settings can be found into `.vscode/launch.json`.
 - replace makefile by build.go to make Windows build support easier
 - add more tests
 - add more documentation
+- add authentication / login (oauth) + HTTPS
+- enable syncthing user/password + HTTPS
index 2df8ea7..7fa69e9 100644 (file)
@@ -50,10 +50,8 @@ func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolder *
        s.apiRouter.POST("/make", s.buildMake)
        s.apiRouter.POST("/make/:id", s.buildMake)
 
-       /* TODO: to be tested and then enabled
        s.apiRouter.POST("/exec", s.execCmd)
        s.apiRouter.POST("/exec/:id", s.execCmd)
-       */
 
        return s
 }
index 18fdc7e..895807d 100644 (file)
@@ -12,10 +12,12 @@ import (
 
 // ExecArgs JSON parameters of /exec command
 type ExecArgs struct {
-       ID         string   `json:"id"`
-       RPath      string   `json:"rpath"` // relative path into project
+       ID         string   `json:"id" binding:"required"`
+       SdkID      string   `json:"sdkid"` // sdk ID to use for setting env
        Cmd        string   `json:"cmd" binding:"required"`
        Args       []string `json:"args"`
+       Env        []string `json:"env"`
+       RPath      string   `json:"rpath"`   // relative path into project
        CmdTimeout int      `json:"timeout"` // command completion timeout in Second
 }
 
@@ -51,7 +53,7 @@ func (s *APIService) execCmd(c *gin.Context) {
                return
        }
 
-       // TODO: add permission
+       // TODO: add permission ?
 
        // Retrieve session info
        sess := s.sessions.Get(c)
@@ -89,14 +91,23 @@ func (s *APIService) execCmd(c *gin.Context) {
 
        // Define callback for output
        var oCB common.EmitOutputCB
-       oCB = func(sid string, id int, stdout, stderr string) {
+       oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) {
                // IO socket can be nil when disconnected
                so := s.sessions.IOSocketGet(sid)
                if so == nil {
                        s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%d", ExecOutEvent, sid, id)
                        return
                }
-               s.log.Debugf("%s emitted - WS sid %s - id:%d", ExecOutEvent, sid, id)
+
+               // Retrieve project ID and RootPath
+               prjID := (*data)["ID"].(string)
+               prjRootPath := (*data)["RootPath"].(string)
+
+               // Cleanup any references to internal rootpath in stdout & stderr
+               stdout = strings.Replace(stdout, prjRootPath, "", -1)
+               stderr = strings.Replace(stderr, prjRootPath, "", -1)
+
+               s.log.Debugf("%s emitted - WS sid %s - id:%d - prjID:%s", ExecOutEvent, sid, id, prjID)
 
                // FIXME replace by .BroadcastTo a room
                err := (*so).Emit(ExecOutEvent, ExecOutMsg{
@@ -135,14 +146,26 @@ func (s *APIService) execCmd(c *gin.Context) {
 
        cmdID := execCommandID
        execCommandID++
+       cmd := []string{}
 
-       cmd := "cd " + prj.GetFullPath(args.RPath) + " && " + args.Cmd
+       // Setup env var regarding Sdk ID (used for example to setup cross toolchain)
+       if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
+               cmd = append(cmd, envCmd...)
+               cmd = append(cmd, "&&")
+       }
+
+       cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", args.Cmd)
        if len(args.Args) > 0 {
-               cmd += " " + strings.Join(args.Args, " ")
+               cmd = append(cmd, args.Args...)
        }
 
-       s.log.Debugf("Execute [Cmd ID %d]: %v %v", cmdID, cmd)
-       err := common.ExecPipeWs(cmd, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB)
+       s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd)
+
+       data := make(map[string]interface{})
+       data["ID"] = prj.ID
+       data["RootPath"] = prj.RootPath
+
+       err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, &data)
        if err != nil {
                common.APIError(c, err.Error())
                return
index fb6435e..098e41c 100644 (file)
@@ -13,11 +13,12 @@ import (
 
 // MakeArgs is the parameters (json format) of /make command
 type MakeArgs struct {
-       ID         string `json:"id"`
-       RPath      string `json:"rpath"`   // relative path into project
-       Args       string `json:"args"`    // args to pass to make command
-       SdkID      string `json:"sdkid"`   // sdk ID to use for setting env
-       CmdTimeout int    `json:"timeout"` // command completion timeout in Second
+       ID         string   `json:"id"`
+       SdkID      string   `json:"sdkid"` // sdk ID to use for setting env
+       Args       []string `json:"args"`  // args to pass to make command
+       Env        []string `json:"env"`
+       RPath      string   `json:"rpath"`   // relative path into project
+       CmdTimeout int      `json:"timeout"` // command completion timeout in Second
 }
 
 // MakeOutMsg Message send on each output (stdout+stderr) of make command
@@ -85,14 +86,9 @@ func (s *APIService) buildMake(c *gin.Context) {
                execTmo = 24 * 60 * 60 // 1 day
        }
 
-       cmd := "cd " + prj.GetFullPath(args.RPath) + " && make"
-       if args.Args != "" {
-               cmd += " " + args.Args
-       }
-
        // Define callback for output
        var oCB common.EmitOutputCB
-       oCB = func(sid string, id int, stdout, stderr string) {
+       oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) {
                // IO socket can be nil when disconnected
                so := s.sessions.IOSocketGet(sid)
                if so == nil {
@@ -138,14 +134,21 @@ func (s *APIService) buildMake(c *gin.Context) {
 
        cmdID := makeCommandID
        makeCommandID++
+       cmd := []string{}
 
        // Retrieve env command regarding Sdk ID
-       if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); envCmd != "" {
-               cmd = envCmd + " && " + cmd
+       if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
+               cmd = append(cmd, envCmd...)
+               cmd = append(cmd, "&&")
+       }
+
+       cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", "make")
+       if len(args.Args) > 0 {
+               cmd = append(cmd, args.Args...)
        }
 
        s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd)
-       err := common.ExecPipeWs(cmd, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB)
+       err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, nil)
        if err != nil {
                common.APIError(c, err.Error())
                return
index 3b63cdc..4994d9d 100644 (file)
@@ -5,6 +5,7 @@ import (
        "fmt"
        "io"
        "os"
+       "strings"
        "time"
 
        "syscall"
@@ -14,7 +15,7 @@ import (
 )
 
 // EmitOutputCB is the function callback used to emit data
-type EmitOutputCB func(sid string, cmdID int, stdout, stderr string)
+type EmitOutputCB func(sid string, cmdID int, stdout, stderr string, data *map[string]interface{})
 
 // EmitExitCB is the function callback used to emit exit proc code
 type EmitExitCB func(sid string, cmdID int, code int, err error)
@@ -23,8 +24,8 @@ type EmitExitCB func(sid string, cmdID int, code int, err error)
 // https://github.com/gorilla/websocket/blob/master/examples/command/main.go
 
 // ExecPipeWs executes a command and redirect stdout/stderr into a WebSocket
-func ExecPipeWs(cmd string, so *socketio.Socket, sid string, cmdID int,
-       cmdExecTimeout int, log *logrus.Logger, eoCB EmitOutputCB, eeCB EmitExitCB) error {
+func ExecPipeWs(cmd []string, env []string, so *socketio.Socket, sid string, cmdID int,
+       cmdExecTimeout int, log *logrus.Logger, eoCB EmitOutputCB, eeCB EmitExitCB, data *map[string]interface{}) error {
 
        outr, outw, err := os.Pipe()
        if err != nil {
@@ -39,9 +40,10 @@ func ExecPipeWs(cmd string, so *socketio.Socket, sid string, cmdID int,
                return fmt.Errorf("Pipe stdin error: " + err.Error())
        }
 
-       bashArgs := []string{"/bin/bash", "-c", cmd}
+       bashArgs := []string{"/bin/bash", "-c", strings.Join(cmd, " ")}
        proc, err := os.StartProcess("/bin/bash", bashArgs, &os.ProcAttr{
                Files: []*os.File{inr, outw, outw},
+               Env:   append(os.Environ(), env...),
        })
        if err != nil {
                outr.Close()
@@ -58,7 +60,7 @@ func ExecPipeWs(cmd string, so *socketio.Socket, sid string, cmdID int,
                defer inw.Close()
 
                stdoutDone := make(chan struct{})
-               go cmdPumpStdout(so, outr, stdoutDone, sid, cmdID, log, eoCB)
+               go cmdPumpStdout(so, outr, stdoutDone, sid, cmdID, log, eoCB, data)
 
                // Blocking function that poll input or wait for end of process
                cmdPumpStdin(so, inw, proc, sid, cmdID, cmdExecTimeout, log, eeCB)
@@ -133,13 +135,13 @@ func cmdPumpStdin(so *socketio.Socket, w io.Writer, proc *os.Process,
 }
 
 func cmdPumpStdout(so *socketio.Socket, r io.Reader, done chan struct{},
-       sid string, cmdID int, log *logrus.Logger, emitFuncCB EmitOutputCB) {
+       sid string, cmdID int, log *logrus.Logger, emitFuncCB EmitOutputCB, data *map[string]interface{}) {
        defer func() {
        }()
 
        sc := bufio.NewScanner(r)
        for sc.Scan() {
-               emitFuncCB(sid, cmdID, string(sc.Bytes()), "")
+               emitFuncCB(sid, cmdID, string(sc.Bytes()), "", data)
        }
        if sc.Err() != nil {
                log.Errorln("scan:", sc.Err())
index 9aeec90..5a5770d 100644 (file)
@@ -48,6 +48,6 @@ func NewCrossSDK(path string) (*SDK, error) {
 }
 
 // GetEnvCmd returns the command used to initialized the environment
-func (s *SDK) GetEnvCmd() string {
-       return ". " + s.EnvFile
+func (s *SDK) GetEnvCmd() []string {
+       return []string{"source", s.EnvFile}
 }
index abfef82..d08afc5 100644 (file)
@@ -71,15 +71,15 @@ func (s *SDKs) Get(id int) SDK {
 }
 
 // GetEnvCmd returns the command used to initialized the environment for an SDK
-func (s *SDKs) GetEnvCmd(id string, defaultID string) string {
+func (s *SDKs) GetEnvCmd(id string, defaultID string) []string {
        if id == "" && defaultID == "" {
                // no env cmd
-               return ""
+               return []string{}
        }
 
        s.mutex.Lock()
        defer s.mutex.Unlock()
-       defaultEnv := ""
+       defaultEnv := []string{}
        for _, sdk := range s.Sdks {
                if sdk.ID == id {
                        return sdk.GetEnvCmd()
index 774195c..4268b40 100644 (file)
@@ -154,12 +154,10 @@ func (s *Server) middlewareXDSDetails() gin.HandlerFunc {
 // CORS middleware
 func (s *Server) middlewareCORS() gin.HandlerFunc {
        return func(c *gin.Context) {
-
                if c.Request.Method == "OPTIONS" {
                        c.Header("Access-Control-Allow-Origin", "*")
                        c.Header("Access-Control-Allow-Headers", "Content-Type")
-                       c.Header("Access-Control-Allow-Methods", "POST, DELETE, GET, PUT")
-                       c.Header("Content-Type", "application/json")
+                       c.Header("Access-Control-Allow-Methods", "GET, POST, DELETE")
                        c.Header("Access-Control-Max-Age", cookieMaxAge)
                        c.AbortWithStatus(204)
                        return
diff --git a/main.go b/main.go
index 69f6676..49f36a5 100644 (file)
--- a/main.go
+++ b/main.go
@@ -183,7 +183,7 @@ func xdsApp(cliCtx *cli.Context) error {
                                relativePath = stFld.RawPath
                        }
 
-                       newFld := xdsconfig.NewFolderConfig(stFld.ID, stFld.Label, ctx.Config.ShareRootDir, strings.Trim(relativePath, "/"), defaultSdk)
+                       newFld := xdsconfig.NewFolderConfig(stFld.ID, stFld.Label, ctx.Config.ShareRootDir, strings.TrimRight(relativePath, "/"), defaultSdk)
                        ctx.Config.Folders = ctx.Config.Folders.Update(xdsconfig.FoldersConfig{newFld})
                }
 
index 449506f..672d7bf 100644 (file)
@@ -1,7 +1,7 @@
 import { Component } from '@angular/core';
 import { Observable } from 'rxjs';
 
-import {AlertService, IAlert} from '../common/alert.service';
+import {AlertService, IAlert} from '../services/alert.service';
 
 @Component({
     selector: 'app-alert',
index ab792be..3dc77ef 100644 (file)
@@ -6,7 +6,7 @@
 
         <div class="navbar-collapse collapse menu2">
             <ul class="nav navbar-nav navbar-right">
-                <li><a routerLink="/build"><i class="fa fa-2x fa-play-circle" title="Open build page"></i></a></li>
+                <li><a routerLink="/devel"><i class="fa fa-2x fa-play-circle" title="Open build page"></i></a></li>
                 <li><a routerLink="/config"><i class="fa fa-2x fa-cog" title="Open configuration page"></i></a></li>
                 <li><a routerLink="/home"><i class="fa fa-2x fa-home" title="Back to home page"></i></a></li>
             </ul>
index 1abcf0c..d02cdf2 100644 (file)
@@ -24,14 +24,16 @@ import { SdksListAccordionComponent } from "./sdks/sdksListAccordion.component";
 import { SdkSelectDropdownComponent } from "./sdks/sdkSelectDropdown.component";
 
 import { HomeComponent } from "./home/home.component";
-import { BuildComponent } from "./build/build.component";
-import { XDSServerService } from "./common/xdsserver.service";
-import { XDSAgentService } from "./common/xdsagent.service";
-import { SyncthingService } from "./common/syncthing.service";
-import { ConfigService } from "./common/config.service";
-import { AlertService } from './common/alert.service';
-import { UtilsService } from './common/utils.service';
-import { SdkService } from "./common/sdk.service";
+import { DevelComponent } from "./devel/devel.component";
+import { BuildComponent } from "./devel/build/build.component";
+import { DeployComponent } from "./devel/deploy/deploy.component";
+import { XDSServerService } from "./services/xdsserver.service";
+import { XDSAgentService } from "./services/xdsagent.service";
+import { SyncthingService } from "./services/syncthing.service";
+import { ConfigService } from "./services/config.service";
+import { AlertService } from './services/alert.service';
+import { UtilsService } from './services/utils.service';
+import { SdkService } from "./services/sdk.service";
 
 
 
@@ -54,6 +56,8 @@ import { SdkService } from "./common/sdk.service";
         AlertComponent,
         HomeComponent,
         BuildComponent,
+        DevelComponent,
+        DeployComponent,
         ConfigComponent,
         ProjectCardComponent,
         ProjectReadableTypePipe,
index 747727c..f0d808f 100644 (file)
@@ -2,7 +2,7 @@ import {Routes, RouterModule} from "@angular/router";
 import {ModuleWithProviders} from "@angular/core";
 import {ConfigComponent} from "./config/config.component";
 import {HomeComponent} from "./home/home.component";
-import {BuildComponent} from "./build/build.component";
+import {DevelComponent} from "./devel/devel.component";
 
 
 const appRoutes: Routes = [
@@ -10,7 +10,7 @@ const appRoutes: Routes = [
 
     {path: 'config', component: ConfigComponent, data: {title: 'Config'}},
     {path: 'home', component: HomeComponent, data: {title: 'Home'}},
-    {path: 'build', component: BuildComponent, data: {title: 'Build'}}
+    {path: 'devel', component: DevelComponent, data: {title: 'Build & Deploy'}}
 ];
 
 export const AppRoutingProviders: any[] = [];
diff --git a/webapp/src/app/build/build.component.html b/webapp/src/app/build/build.component.html
deleted file mode 100644 (file)
index 3d866f3..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-<form [formGroup]="buildForm">
-    <div class="col-xs-12">
-        <table class="table table-borderless">
-            <tbody>
-                <tr>
-                    <th style="border: none;">Project</th>
-                    <td>
-                        <div class="btn-group" dropdown *ngIf="curProject">
-                            <button dropdownToggle type="button" class="btn btn-primary dropdown-toggle" style="width: 20em;">
-                            {{curProject.label}} <span class="caret" style="float: right; margin-top: 8px;"></span>
-                        </button>
-                            <ul *dropdownMenu class="dropdown-menu" role="menu">
-                                <li role="menuitem"><a class="dropdown-item" *ngFor="let prj of (config$ | async)?.projects" (click)="curProject=prj">
-                                {{prj.label}}</a>
-                                </li>
-                            </ul>
-                        </div>
-                    </td>
-                </tr>
-                <tr>
-                    <th>Cross SDK</th>
-                    <td>
-                        <!-- FIXME why not working ?
-                        <sdk-select-dropdown [sdks]="(sdks$ | async)"></sdk-select-dropdown>
-                        -->
-                        <sdk-select-dropdown></sdk-select-dropdown>
-                    </td>
-                </tr>
-                <tr>
-                    <th>Sub-directory</th>
-                    <td> <input type="text" style="width:99%;" formControlName="subpath"> </td>
-                </tr>
-                <tr>
-                    <th>Make arguments</th>
-                    <td> <input type="text" style="width:99%;" formControlName="makeArgs"> </td>
-                </tr>
-            </tbody>
-        </table>
-    </div>
-    <div class="row">
-        <div class="col-xs-12 text-center">
-            <div class="btn-group blocks">
-                <button class="btn btn-primary btn-large" (click)="make() " [disabled]="!confValid ">Build</button>
-                <button class="btn btn-primary btn-large" (click)="make('clean') " [disabled]="!confValid ">Clean</button>
-            </div>
-        </div>
-    </div>
-</form>
-
-<div style="margin-left: 2em; margin-right: 2em; ">
-    <div class="row ">
-        <div class="col-xs-10">
-            <div class="row ">
-                <div class="col-xs-4">
-                    <label>Command Output</label>
-                </div>
-                <div class="col-xs-8" style="font-size:x-small; margin-top:5px;">
-                    {{ cmdInfo }}
-                </div>
-            </div>
-        </div>
-        <div class="col-xs-2">
-            <button class="btn btn-link pull-right " (click)="reset() "><span class="fa fa-eraser fa-size-x2"></span></button>
-        </div>
-    </div>
-    <div class="row ">
-        <div class="col-xs-12 text-center ">
-            <textarea rows="20" class="textarea-scroll" #scrollOutput>{{ cmdOutput }}</textarea>
-        </div>
-    </div>
-</div>
index 1e1e9c2..c6b2573 100644 (file)
@@ -7,12 +7,12 @@ import 'rxjs/add/operator/map';
 import 'rxjs/add/operator/filter';
 import 'rxjs/add/operator/debounceTime';
 
-import { ConfigService, IConfig, IProject, ProjectType } from "../common/config.service";
-import { XDSServerService, IServerStatus, IXDSAgentInfo } from "../common/xdsserver.service";
-import { XDSAgentService, IAgentStatus } from "../common/xdsagent.service";
-import { SyncthingService, ISyncThingStatus } from "../common/syncthing.service";
-import { AlertService } from "../common/alert.service";
-import { ISdk, SdkService } from "../common/sdk.service";
+import { ConfigService, IConfig, IProject, ProjectType } from "../services/config.service";
+import { XDSServerService, IServerStatus, IXDSAgentInfo } from "../services/xdsserver.service";
+import { XDSAgentService, IAgentStatus } from "../services/xdsagent.service";
+import { SyncthingService, ISyncThingStatus } from "../services/syncthing.service";
+import { AlertService } from "../services/alert.service";
+import { ISdk, SdkService } from "../services/sdk.service";
 
 @Component({
     templateUrl: './app/config/config.component.html',
similarity index 75%
rename from webapp/src/app/build/build.component.css
rename to webapp/src/app/devel/build/build.component.css
index 11784db..6784a9f 100644 (file)
     border-radius: 4px !important;
 }
 
+.table-center {
+    width: 80%;
+    margin-left: auto;
+    margin-right: auto;
+}
+
 .table-borderless>tbody>tr>td,
 .table-borderless>tbody>tr>th,
 .table-borderless>tfoot>tr>td,
 .textarea-scroll {
     width: 100%;
     overflow-y: scroll;
+}
+
+h2 {
+    font-family: sans-serif;
+    font-variant: small-caps;
+    font-size: x-large;
 }
\ No newline at end of file
diff --git a/webapp/src/app/devel/build/build.component.html b/webapp/src/app/devel/build/build.component.html
new file mode 100644 (file)
index 0000000..f4be204
--- /dev/null
@@ -0,0 +1,74 @@
+<div class="panel panel-default">
+    <div class="panel-heading">
+        <h2 class="panel-title">Build</h2>
+    </div>
+    <div class="panel-body">
+        <form [formGroup]="buildForm">
+            <div class="col-xs-12">
+                <table class="table table-borderless table-center">
+                    <tbody>
+                        <tr>
+                            <th>Cross SDK</th>
+                            <td>
+                                <!-- FIXME why not working ?
+                        <sdk-select-dropdown [sdks]="(sdks$ | async)"></sdk-select-dropdown>
+                        -->
+                                <sdk-select-dropdown></sdk-select-dropdown>
+                            </td>
+                        </tr>
+                        <tr>
+                            <th>Project root path</th>
+                            <td> <input type="text" disabled style="width:99%;" [value]="curProject && curProject.path"></td>
+                        </tr>
+                        <tr>
+                            <th>Sub-path</th>
+                            <td> <input type="text" style="width:99%;" formControlName="subpath"> </td>
+                        </tr>
+                        <tr>
+                            <th>Command arguments</th>
+                            <td> <input type="text" style="width:99%;" formControlName="cmdArgs"> </td>
+                        </tr>
+                        <tr>
+                            <th>Env variables</th>
+                            <td> <input type="text" style="width:99%;" formControlName="envVars"> </td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <div class="row">
+                <div class="col-xs-12 text-center">
+                    <div class="btn-group blocks">
+                        <button class="btn btn-primary btn-large" (click)="preBuild()" [disabled]="!curProject">Pre-Build</button>
+                        <button class="btn btn-primary btn-large" (click)="build()" [disabled]="!curProject">Build</button>
+                        <button class="btn btn-primary btn-large" (click)="populate()" [disabled]="!curProject ">Populate</button>
+                        <button *ngIf="debugEnable" class="btn btn-primary btn-large" (click)="execCmd()" [disabled]="!curProject ">Execute command</button>
+                        <button *ngIf="debugEnable" class="btn btn-primary btn-large" (click)="make()" [disabled]="!curProject ">Make</button>
+                    </div>
+                </div>
+            </div>
+        </form>
+
+        <div style="margin-left: 2em; margin-right: 2em; ">
+            <div class="row ">
+                <div class="col-xs-10">
+                    <div class="row ">
+                        <div class="col-xs-4">
+                            <label>Command Output</label>
+                        </div>
+                        <div class="col-xs-8" style="font-size:x-small; margin-top:5px;">
+                            {{ cmdInfo }}
+                        </div>
+                    </div>
+                </div>
+                <div class="col-xs-2">
+                    <button class="btn btn-link pull-right " (click)="reset() "><span class="fa fa-eraser fa-size-x2"></span></button>
+                </div>
+            </div>
+            <div class="row ">
+                <div class="col-xs-12 text-center ">
+                    <textarea rows="20" class="textarea-scroll" #scrollOutput>{{ cmdOutput }}</textarea>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
similarity index 53%
rename from webapp/src/app/build/build.component.ts
rename to webapp/src/app/devel/build/build.component.ts
index 17e545f..b7003b1 100644 (file)
@@ -1,17 +1,18 @@
-import { Component, AfterViewChecked, ElementRef, ViewChild, OnInit } from '@angular/core';
+import { Component, AfterViewChecked, ElementRef, ViewChild, OnInit, Input } from '@angular/core';
 import { Observable } from 'rxjs';
 import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
+import { CookieService } from 'ngx-cookie';
 
 import 'rxjs/add/operator/scan';
 import 'rxjs/add/operator/startWith';
 
-import { XDSServerService, ICmdOutput } from "../common/xdsserver.service";
-import { ConfigService, IConfig, IProject } from "../common/config.service";
-import { AlertService, IAlert } from "../common/alert.service";
-import { SdkService } from "../common/sdk.service";
+import { XDSServerService, ICmdOutput } from "../../services/xdsserver.service";
+import { ConfigService, IConfig, IProject } from "../../services/config.service";
+import { AlertService, IAlert } from "../../services/alert.service";
+import { SdkService } from "../../services/sdk.service";
 
 @Component({
-    selector: 'build',
+    selector: 'panel-build',
     moduleId: module.id,
     templateUrl: './build.component.html',
     styleUrls: ['./build.component.css']
@@ -20,46 +21,34 @@ import { SdkService } from "../common/sdk.service";
 export class BuildComponent implements OnInit, AfterViewChecked {
     @ViewChild('scrollOutput') private scrollContainer: ElementRef;
 
-    config$: Observable<IConfig>;
+    @Input() curProject: IProject;
 
     buildForm: FormGroup;
     subpathCtrl = new FormControl("", Validators.required);
+    debugEnable: boolean = false;
 
     public cmdOutput: string;
-    public confValid: boolean;
-    public curProject: IProject;
     public cmdInfo: string;
 
     private startTime: Map<string, number> = new Map<string, number>();
 
-    // I initialize the app component.
     constructor(private configSvr: ConfigService,
         private xdsSvr: XDSServerService,
         private fb: FormBuilder,
         private alertSvr: AlertService,
-        private sdkSvr: SdkService
+        private sdkSvr: SdkService,
+        private cookie: CookieService,
     ) {
         this.cmdOutput = "";
-        this.confValid = false;
         this.cmdInfo = "";      // TODO: to be remove (only for debug)
         this.buildForm = fb.group({
             subpath: this.subpathCtrl,
-            makeArgs: ["", Validators.nullValidator],
+            cmdArgs: ["", Validators.nullValidator],
+            envVars: ["", Validators.nullValidator],
         });
     }
 
     ngOnInit() {
-        this.config$ = this.configSvr.conf;
-        this.config$.subscribe((cfg) => {
-            if ("projects" in cfg) {
-                this.curProject = cfg.projects[0];
-                this.confValid = (cfg.projects.length && this.curProject.id != null);
-            } else {
-                this.curProject = null;
-                this.confValid = false;
-            }
-        });
-
         // Command output data tunneling
         this.xdsSvr.CmdOutput$.subscribe(data => {
             this.cmdOutput += data.stdout + "\n";
@@ -78,6 +67,9 @@ export class BuildComponent implements OnInit, AfterViewChecked {
         });
 
         this._scrollToBottom();
+
+        // only use for debug
+        this.debugEnable = (this.cookie.get("debug_build") !== "");
     }
 
     ngAfterViewChecked() {
@@ -88,6 +80,69 @@ export class BuildComponent implements OnInit, AfterViewChecked {
         this.cmdOutput = '';
     }
 
+    preBuild() {
+        this._exec(
+            "mkdir -p build && cd build && cmake ..",
+            this.buildForm.value.subpath,
+            [],
+            this.buildForm.value.envVars);
+    }
+
+    build() {
+        this._exec(
+            "cd build && make",
+            this.buildForm.value.subpath,
+            this.buildForm.value.cmdArgs,
+            this.buildForm.value.envVars
+        );
+    }
+
+    populate() {
+        this._exec(
+            "SEB_TODO_script_populate",
+            this.buildForm.value.subpath,
+            [], // args
+            this.buildForm.value.envVars
+        );
+    }
+
+    execCmd() {
+        this._exec(
+            this.buildForm.value.cmdArgs,
+            this.buildForm.value.subpath,
+            [],
+            this.buildForm.value.envVars
+        );
+    }
+
+    private _exec(cmd: string, dir: string, args: string[], env: string) {
+        if (!this.curProject) {
+            this.alertSvr.warning('No active project', true);
+        }
+
+        let prjID = this.curProject.id;
+
+        this.cmdOutput += this._outputHeader();
+
+        let sdkid = this.sdkSvr.getCurrentId();
+
+        // Detect key=value in env string to build array of string
+        let envArr = [];
+        env.split(';').forEach(v => envArr.push(v.trim()));
+
+        let t0 = performance.now();
+        this.cmdInfo = 'Start build of ' + prjID + ' at ' + t0;
+
+        this.xdsSvr.exec(prjID, dir, cmd, sdkid, args, envArr)
+            .subscribe(res => {
+                this.startTime.set(String(res.cmdID), t0);
+            },
+            err => {
+                this.cmdInfo = 'Last command duration: ' + this._computeTime(t0);
+                this.alertSvr.error('ERROR: ' + err);
+            });
+    }
+
     make(args: string) {
         if (!this.curProject) {
             this.alertSvr.warning('No active project', true);
@@ -99,12 +154,16 @@ export class BuildComponent implements OnInit, AfterViewChecked {
 
         let sdkid = this.sdkSvr.getCurrentId();
 
-        let cmdArgs = args ? args : this.buildForm.value.makeArgs;
+        let argsArr = args ? args.split(' ') : this.buildForm.value.cmdArgs.split(' ');
+
+        // Detect key=value in env string to build array of string
+        let envArr = [];
+        this.buildForm.value.envVars.split(';').forEach(v => envArr.push(v.trim()));
 
         let t0 = performance.now();
         this.cmdInfo = 'Start build of ' + prjID + ' at ' + t0;
 
-        this.xdsSvr.make(prjID, this.buildForm.value.subpath, cmdArgs, sdkid)
+        this.xdsSvr.make(prjID, this.buildForm.value.subpath, sdkid, argsArr, envArr)
             .subscribe(res => {
                 this.startTime.set(String(res.cmdID), t0);
             },
diff --git a/webapp/src/app/devel/deploy/deploy.component.css b/webapp/src/app/devel/deploy/deploy.component.css
new file mode 100644 (file)
index 0000000..c1b39d8
--- /dev/null
@@ -0,0 +1,45 @@
+.vcenter {
+    display: inline-block;
+    vertical-align: middle;
+}
+
+.blocks .btn-primary {
+    margin-left: 5px;
+    margin-right: 5px;
+    margin-top: 5px;
+    border-radius: 4px !important;
+}
+
+.table-center {
+    width: 99%;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.table-borderless>tbody>tr>td,
+.table-borderless>tbody>tr>th,
+.table-borderless>tfoot>tr>td,
+.table-borderless>tfoot>tr>th,
+.table-borderless>thead>tr>td,
+.table-borderless>thead>tr>th {
+    border: none;
+}
+
+.btn-large {
+    width: 10em;
+}
+
+.fa-size-x2 {
+    font-size: 18px;
+}
+
+.textarea-scroll {
+    width: 100%;
+    overflow-y: scroll;
+}
+
+h2 {
+    font-family: sans-serif;
+    font-variant: small-caps;
+    font-size: x-large;
+}
\ No newline at end of file
diff --git a/webapp/src/app/devel/deploy/deploy.component.html b/webapp/src/app/devel/deploy/deploy.component.html
new file mode 100644 (file)
index 0000000..7a15fa6
--- /dev/null
@@ -0,0 +1,31 @@
+<div class="panel panel-default">
+    <div class="panel-heading">
+        <h2 class="panel-title">Deployment</h2>
+    </div>
+    <div class="panel-body">
+        <form [formGroup]="deployForm">
+            <div class="col-xs-12">
+                <table class="table table-borderless table-center">
+                    <tbody>
+                        <tr>
+                            <th>Board IP</th>
+                            <td> <input type="text" style="width:99%;" formControlName="boardIP" placeholder="1.2.3.4"> </td>
+                        </tr>
+                        <tr>
+                            <th>File to deploy</th>
+                            <td> <input type="text" style="width:99%;" formControlName="wgtFile"> </td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <div class="row">
+                <div class="col-xs-12 text-center">
+                    <div class="btn-group blocks">
+                        <button class="btn btn-primary btn-large" (click)="deploy()" [disabled]="!curProject ">Deploy</button>
+                    </div>
+                </div>
+            </div>
+        </form>
+
+    </div>
+</div>
\ No newline at end of file
diff --git a/webapp/src/app/devel/deploy/deploy.component.ts b/webapp/src/app/devel/deploy/deploy.component.ts
new file mode 100644 (file)
index 0000000..4dba256
--- /dev/null
@@ -0,0 +1,63 @@
+import { Component, OnInit, Input } from "@angular/core";
+import { Observable } from 'rxjs';
+import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
+
+import 'rxjs/add/operator/scan';
+import 'rxjs/add/operator/startWith';
+
+import { XDSAgentService, IXDSDeploy } from "../../services/xdsagent.service";
+import { ConfigService, IConfig, IProject } from "../../services/config.service";
+import { AlertService, IAlert } from "../../services/alert.service";
+import { SdkService } from "../../services/sdk.service";
+
+@Component({
+    selector: 'panel-deploy',
+    moduleId: module.id,
+    templateUrl: './deploy.component.html',
+    styleUrls: ['./deploy.component.css']
+})
+
+export class DeployComponent implements OnInit {
+
+    @Input() curProject: IProject;
+
+    deploying: boolean;
+    deployForm: FormGroup;
+
+    constructor(private configSvr: ConfigService,
+        private xdsAgent: XDSAgentService,
+        private fb: FormBuilder,
+        private alert: AlertService,
+    ) {
+        this.deployForm = fb.group({
+            boardIP: ["", Validators.nullValidator],
+            wgtFile: ["", Validators.nullValidator],
+        });
+    }
+
+    ngOnInit() {
+        this.deploying = false;
+        if (this.curProject && this.curProject.path) {
+            this.deployForm.patchValue({ wgtFile: this.curProject.path });
+        }
+    }
+
+    deploy() {
+        this.deploying = true;
+
+        this.xdsAgent.deploy(
+            {
+                boardIP: this.deployForm.value.boardIP,
+                file: this.deployForm.value.wgtFile
+            }
+        ).subscribe(res => {
+            this.deploying = false;
+        }, err => {
+            this.deploying = false;
+            let msg = '<span>ERROR while deploying "' + this.deployForm.value.wgtFile + '"<br>';
+            msg += err;
+            msg += '</span>';
+            this.alert.error(msg);
+        });
+    }
+}
\ No newline at end of file
diff --git a/webapp/src/app/devel/devel.component.css b/webapp/src/app/devel/devel.component.css
new file mode 100644 (file)
index 0000000..40d6fec
--- /dev/null
@@ -0,0 +1,14 @@
+.table-center {
+    width: 60%;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.table-borderless>tbody>tr>td,
+.table-borderless>tbody>tr>th,
+.table-borderless>tfoot>tr>td,
+.table-borderless>tfoot>tr>th,
+.table-borderless>thead>tr>td,
+.table-borderless>thead>tr>th {
+    border: none;
+}
diff --git a/webapp/src/app/devel/devel.component.html b/webapp/src/app/devel/devel.component.html
new file mode 100644 (file)
index 0000000..5950f51
--- /dev/null
@@ -0,0 +1,36 @@
+<div class="row">
+    <div class="col-md-8">
+        <table class="table table-borderless table-center">
+            <tbody>
+                <tr>
+                    <th style="border: none;">Project</th>
+                    <td>
+                        <div class="btn-group" dropdown *ngIf="curPrj">
+                            <button dropdownToggle type="button" class="btn btn-primary dropdown-toggle" style="width: 20em;">
+                    {{curPrj.label}} <span class="caret" style="float: right; margin-top: 8px;"></span>
+                                    </button>
+                            <ul *dropdownMenu class="dropdown-menu" role="menu">
+                                <li role="menuitem"><a class="dropdown-item" *ngFor="let prj of (config$ | async)?.projects" (click)="curPrj=prj">{{prj.label}}</a>
+                                </li>
+                            </ul>
+                        </div>
+                        <span *ngIf="!curPrj" style="color:red; font-style: italic;">
+                            No project detected, please create first a project using the configuration page.
+                        </span>
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-md-8">
+        <panel-build [curProject]=curPrj></panel-build>
+    </div>
+    <!-- TODO: disable for now
+    <div class="col-md-4">
+        <panel-deploy [curProject]=curPrj></panel-deploy>
+    </div>
+    -->
+</div>
\ No newline at end of file
diff --git a/webapp/src/app/devel/devel.component.ts b/webapp/src/app/devel/devel.component.ts
new file mode 100644 (file)
index 0000000..ff12127
--- /dev/null
@@ -0,0 +1,32 @@
+import { Component } from '@angular/core';
+
+import { Observable } from 'rxjs';
+
+import { ConfigService, IConfig, IProject } from "../services/config.service";
+
+@Component({
+    selector: 'devel',
+    moduleId: module.id,
+    templateUrl: './devel.component.html',
+    styleUrls: ['./devel.component.css'],
+})
+
+export class DevelComponent {
+
+    curPrj: IProject;
+    config$: Observable<IConfig>;
+
+    constructor(private configSvr: ConfigService) {
+    }
+
+    ngOnInit() {
+        this.config$ = this.configSvr.conf;
+        this.config$.subscribe((cfg) => {
+            if ("projects" in cfg) {
+                this.curPrj = cfg.projects[0];
+            } else {
+                this.curPrj = null;
+            }
+        });
+    }
+}
\ No newline at end of file
index 010b476..7a7fa21 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, Input, Pipe, PipeTransform } from '@angular/core';
-import { ConfigService, IProject, ProjectType } from "../common/config.service";
+import { ConfigService, IProject, ProjectType } from "../services/config.service";
 
 @Component({
     selector: 'project-card',
index bea3f0f..1b43cea 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, Input } from "@angular/core";
 
-import { IProject } from "../common/config.service";
+import { IProject } from "../services/config.service";
 
 @Component({
     selector: 'projects-list-accordion',
index f5d2a54..579d224 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, Input } from '@angular/core';
-import { ISdk } from "../common/sdk.service";
+import { ISdk } from "../services/sdk.service";
 
 @Component({
     selector: 'sdk-card',
index f213db0..a2fe37a 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, Input } from "@angular/core";
 
-import { ISdk, SdkService } from "../common/sdk.service";
+import { ISdk, SdkService } from "../services/sdk.service";
 
 @Component({
     selector: 'sdk-select-dropdown',
index 9094f27..9d5f7e9 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, Input } from "@angular/core";
 
-import { ISdk } from "../common/sdk.service";
+import { ISdk } from "../services/sdk.service";
 
 @Component({
     selector: 'sdks-list-accordion',
similarity index 89%
rename from webapp/src/app/common/config.service.ts
rename to webapp/src/app/services/config.service.ts
index 091ee06..390340a 100644 (file)
@@ -13,11 +13,11 @@ import 'rxjs/add/observable/throw';
 import 'rxjs/add/operator/mergeMap';
 
 
-import { XDSServerService, IXDSConfigProject } from "../common/xdsserver.service";
-import { XDSAgentService } from "../common/xdsagent.service";
-import { SyncthingService, ISyncThingProject, ISyncThingStatus } from "../common/syncthing.service";
-import { AlertService, IAlert } from "../common/alert.service";
-import { UtilsService } from "../common/utils.service";
+import { XDSServerService, IXDSConfigProject } from "../services/xdsserver.service";
+import { XDSAgentService } from "../services/xdsagent.service";
+import { SyncthingService, ISyncThingProject, ISyncThingStatus } from "../services/syncthing.service";
+import { AlertService, IAlert } from "../services/alert.service";
+import { UtilsService } from "../services/utils.service";
 
 export enum ProjectType {
     NATIVE = 1,
@@ -150,9 +150,27 @@ export class ConfigService {
         this.AgentConnectObs = this.xdsAgentSvr.connect(cfg.retry, cfg.URL)
             .subscribe((sts) => {
                 //console.log("Agent sts", sts);
-            }, error => this.alert.error(error)
-            );
+                // FIXME: load projects from local XDS Agent and
+                //  not directly from local syncthing
+                this._loadProjectFromLocalST();
+
+            }, error => {
+                if (error.indexOf("XDS local Agent not responding") !== -1) {
+                    let msg = "<span><strong>" + error + "<br></strong>";
+                    msg += "You may need to download and execute XDS-Agent.<br>";
+                    if (this.confStore.xdsAgentZipUrl !== "") {
+                        msg += "<a class=\"fa fa-download\" href=\"" + this.confStore.xdsAgentZipUrl + "\" target=\"_blank\"></a>";
+                        msg += " Download XDS-Agent tarball.";
+                    }
+                    msg += "</span>";
+                    this.alert.error(msg);
+                } else {
+                    this.alert.error(error);
+                }
+            });
+    }
 
+    private _loadProjectFromLocalST() {
         // Remove previous subscriber if existing
         if (this.stConnectObs) {
             try {
@@ -198,11 +216,7 @@ export class ConfigService {
         }, error => {
             if (error.indexOf("Syncthing local daemon not responding") !== -1) {
                 let msg = "<span><strong>" + error + "<br></strong>";
-                msg += "You may need to download and execute XDS-Agent.<br>";
-                if (this.confStore.xdsAgentZipUrl !== "") {
-                    msg += "<a class=\"fa fa-download\" href=\"" + this.confStore.xdsAgentZipUrl + "\" target=\"_blank\"></a>";
-                    msg += " Download XDS-Agent tarball.";
-                }
+                msg += "Please check that local XDS-Agent is running.<br>";
                 msg += "</span>";
                 this.alert.error(msg);
             } else {
similarity index 94%
rename from webapp/src/app/common/sdk.service.ts
rename to webapp/src/app/services/sdk.service.ts
index 19c49d9..fa4cd55 100644 (file)
@@ -2,7 +2,7 @@ import { Injectable, SecurityContext } from '@angular/core';
 import { Observable } from 'rxjs/Observable';
 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 
-import { XDSServerService } from "../common/xdsserver.service";
+import { XDSServerService } from "../services/xdsserver.service";
 
 export interface ISdk {
     id: string;
similarity index 88%
rename from webapp/src/app/common/xdsagent.service.ts
rename to webapp/src/app/services/xdsagent.service.ts
index 86f0336..c6c52c8 100644 (file)
@@ -21,6 +21,11 @@ export interface IXDSVersion {
 
 }
 
+export interface IXDSDeploy {
+    boardIP: string;
+    file: string;
+}
+
 export interface IAgentStatus {
     baseURL: string;
     connected: boolean;
@@ -66,9 +71,7 @@ export class XDSAgentService {
         if (url) {
             this._initURLs(url);
         }
-        //FIXME [XDS-Agent]: not implemented yet, set always as connected
-        //this._status.connected = false;
-        this._status.connected = true;
+        this._status.connected = false;
         this._status.connectionRetry = 0;
         this.connectionMaxRetry = retry || 3600;   // 1 hour
 
@@ -85,14 +88,11 @@ export class XDSAgentService {
     }
 
     public getVersion(): Observable<IXDSVersion> {
-        /*FIXME [XDS-Agent]: Not implemented for now
         return this._get('/version');
-        */
-        return Observable.of({
-            version: "NOT_IMPLEMENTED",
-            apiVersion: "NOT_IMPLEMENTED",
-            gitTag: "NOT_IMPLEMENTED"
-        });
+    }
+
+    public deploy(dpy: IXDSDeploy) {
+        return this._post('/deploy', dpy);
     }
 
     private _initURLs(url: string) {
@@ -139,7 +139,6 @@ export class XDSAgentService {
         options = options || {};
         let headers = options.headers || new Headers();
         // headers.append('Authorization', 'Basic ' + btoa('username:password'));
-        headers.append('Access-Control-Allow-Origin', '*');
         headers.append('Accept', 'application/json');
         headers.append('Content-Type', 'application/json');
         if (this.apikey !== "") {
@@ -156,7 +155,7 @@ export class XDSAgentService {
             return Observable.of(true);
         }
 
-        return this.http.get(this._status.baseURL, this._attachAuthHeaders())
+        return this.http.get(this.baseRestUrl + "/version", this._attachAuthHeaders())
             .map((r) => this._status.connected = true)
             .retryWhen((attempts) => {
                 this._status.connectionRetry = 0;
@@ -181,9 +180,7 @@ export class XDSAgentService {
         return this._checkAlive()
             .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders()))
             .map((res: Response) => res.json())
-            .catch((error) => {
-                return this._decodeError(error);
-            });
+            .catch(this._decodeError);
     }
     private _delete(url: string): Observable<any> {
         return this._checkAlive()
@@ -197,7 +194,13 @@ export class XDSAgentService {
         if (this._status) {
             this._status.connected = false;
         }
-        if (typeof err === "object") {
+        if (err instanceof Response) {
+            const body = err.json() || 'Server error';
+            e = body.error || JSON.stringify(body);
+            if (!e || e === "") {
+                e = `${err.status} - ${err.statusText || 'Unknown error'}`;
+            }
+        } else if (typeof err === "object") {
             if (err.statusText) {
                 e = err.statusText;
             } else if (err.error) {
@@ -205,10 +208,6 @@ export class XDSAgentService {
             } else {
                 e = JSON.stringify(err);
             }
-        } else if (err instanceof Response) {
-            const body = err.json() || 'Server error';
-            const error = body.error || JSON.stringify(body);
-            e = `${err.status} - ${err.statusText || ''} ${error}`;
         } else {
             e = err.message ? err.message : err.toString();
         }
similarity index 88%
rename from webapp/src/app/common/xdsserver.service.ts
rename to webapp/src/app/services/xdsserver.service.ts
index 49c2d37..22e4ac9 100644 (file)
@@ -147,6 +147,14 @@ export class XDSServerService {
             this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
         });
 
+        this.socket.on('exec:output', data => {
+            this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
+        });
+
+        this.socket.on('exec:exit', data => {
+            this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
+        });
+
     }
 
     getSdks(): Observable<ISdk[]> {
@@ -177,16 +185,27 @@ export class XDSServerService {
         return this._delete('/folder/' + id);
     }
 
-    exec(cmd: string, args?: string[], options?: any): Observable<any> {
+    exec(prjID: string, dir: string, cmd: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> {
         return this._post('/exec',
             {
+                id: prjID,
+                rpath: dir,
                 cmd: cmd,
-                args: args || []
+                sdkid: sdkid || "",
+                args: args || [],
+                env: env || [],
             });
     }
 
-    make(prjID: string, dir: string, args: string, sdkid?: string): Observable<any> {
-        return this._post('/make', { id: prjID, rpath: dir, args: args, sdkid: sdkid });
+    make(prjID: string, dir: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> {
+        return this._post('/make',
+            {
+                id: prjID,
+                rpath: dir,
+                sdkid: sdkid,
+                args: args || [],
+                env: env || [],
+            });
     }