Add Cross SDKs support (part 2)
authorSebastien Douheret <sebastien.douheret@iot.bzh>
Thu, 18 May 2017 09:01:13 +0000 (11:01 +0200)
committerSebastien Douheret <sebastien.douheret@iot.bzh>
Thu, 18 May 2017 09:01:13 +0000 (11:01 +0200)
23 files changed:
.vscode/settings.json
lib/apiv1/apiv1.go
lib/apiv1/make.go
lib/apiv1/sdks.go [new file with mode: 0644]
lib/crosssdk/sdk.go
lib/crosssdk/sdks.go
lib/model/folder.go
lib/syncthing/st.go
lib/syncthing/stfolder.go
lib/webserver/server.go
lib/xdsconfig/folderconfig.go
main.go
webapp/src/app/app.module.ts
webapp/src/app/build/build.component.html
webapp/src/app/build/build.component.ts
webapp/src/app/common/config.service.ts
webapp/src/app/common/sdk.service.ts [new file with mode: 0644]
webapp/src/app/common/xdsserver.service.ts
webapp/src/app/config/config.component.html
webapp/src/app/config/config.component.ts
webapp/src/app/sdks/sdkCard.component.ts [new file with mode: 0644]
webapp/src/app/sdks/sdkSelectDropdown.component.ts [new file with mode: 0644]
webapp/src/app/sdks/sdksListAccordion.component.ts [new file with mode: 0644]

index 9023297..8274151 100644 (file)
@@ -17,7 +17,7 @@
     "cSpell.words": [
         "apiv", "gonic", "devel", "csrffound", "Syncthing", "STID",
         "ISTCONFIG", "socketio", "ldflags", "SThg", "Intf", "dismissible",
-        "rpath", "WSID", "sess", "IXDS", "xdsconfig", "xdsserver", "mfolder", 
-       "inotify", "Inot", "pname", "pkill"
+        "rpath", "WSID", "sess", "IXDS", "xdsconfig", "xdsserver", "mfolder",
+       "inotify", "Inot", "pname", "pkill", "sdkid", "CLOUDSYNC"
     ]
 }
\ No newline at end of file
index c94849d..7359266 100644 (file)
@@ -4,6 +4,7 @@ import (
        "github.com/Sirupsen/logrus"
        "github.com/gin-gonic/gin"
 
+       "github.com/iotbzh/xds-server/lib/crosssdk"
        "github.com/iotbzh/xds-server/lib/model"
        "github.com/iotbzh/xds-server/lib/session"
        "github.com/iotbzh/xds-server/lib/xdsconfig"
@@ -16,17 +17,19 @@ type APIService struct {
        sessions  *session.Sessions
        cfg       *xdsconfig.Config
        mfolder   *model.Folder
+       sdks      *crosssdk.SDKs
        log       *logrus.Logger
 }
 
 // New creates a new instance of API service
-func New(sess *session.Sessions, cfg *xdsconfig.Config, mfolder *model.Folder, r *gin.Engine) *APIService {
+func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolder *model.Folder, sdks *crosssdk.SDKs) *APIService {
        s := &APIService{
                router:    r,
                sessions:  sess,
                apiRouter: r.Group("/api/v1"),
                cfg:       cfg,
                mfolder:   mfolder,
+               sdks:      sdks,
                log:       cfg.Log,
        }
 
@@ -40,6 +43,9 @@ func New(sess *session.Sessions, cfg *xdsconfig.Config, mfolder *model.Folder, r
        s.apiRouter.POST("/folder", s.addFolder)
        s.apiRouter.DELETE("/folder/:id", s.delFolder)
 
+       s.apiRouter.GET("/sdks", s.getSdks)
+       s.apiRouter.GET("/sdk/:id", s.getSdk)
+
        s.apiRouter.POST("/make", s.buildMake)
        s.apiRouter.POST("/make/:id", s.buildMake)
 
index 1e6d937..fb6435e 100644 (file)
@@ -14,8 +14,9 @@ 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"`
+       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
 }
 
@@ -138,9 +139,10 @@ func (s *APIService) buildMake(c *gin.Context) {
        cmdID := makeCommandID
        makeCommandID++
 
-       /* SEB TODO . /opt/poky-agl/3.90.0+snapshot/environment-setup-aarch64-agl-linux
-       env := os.Environ()
-       */
+       // Retrieve env command regarding Sdk ID
+       if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); envCmd != "" {
+               cmd = envCmd + " && " + cmd
+       }
 
        s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd)
        err := common.ExecPipeWs(cmd, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB)
diff --git a/lib/apiv1/sdks.go b/lib/apiv1/sdks.go
new file mode 100644 (file)
index 0000000..5ae2b03
--- /dev/null
@@ -0,0 +1,31 @@
+package apiv1
+
+import (
+       "net/http"
+       "strconv"
+
+       "github.com/gin-gonic/gin"
+       "github.com/iotbzh/xds-server/lib/common"
+)
+
+// getSdks returns all SDKs configuration
+func (s *APIService) getSdks(c *gin.Context) {
+       c.JSON(http.StatusOK, s.sdks.GetAll())
+}
+
+// getSdk returns a specific Sdk configuration
+func (s *APIService) getSdk(c *gin.Context) {
+       id, err := strconv.Atoi(c.Param("id"))
+       if err != nil {
+               common.APIError(c, "Invalid id")
+               return
+       }
+
+       sdk := s.sdks.Get(id)
+       if sdk.Profile == "" {
+               common.APIError(c, "Invalid id")
+               return
+       }
+
+       c.JSON(http.StatusOK, sdk)
+}
index 2f22f22..9aeec90 100644 (file)
@@ -2,17 +2,20 @@ package crosssdk
 
 import (
        "fmt"
-       "path"
        "path/filepath"
 )
 
 // SDK Define a cross tool chain used to build application
 type SDK struct {
-       Profile string
-       Version string
-       Arch    string
-       Path    string
-       EnvFile string
+       ID      string `json:"id" binding:"required"`
+       Name    string `json:"name"`
+       Profile string `json:"profile"`
+       Version string `json:"version"`
+       Arch    string `json:"arch"`
+       Path    string `json:"path"`
+
+       // Not exported fields
+       EnvFile string `json:"-"`
 }
 
 // NewCrossSDK creates a new instance of Syncthing
@@ -28,6 +31,9 @@ func NewCrossSDK(path string) (*SDK, error) {
        d = filepath.Dir(d)
        s.Profile = filepath.Base(d)
 
+       s.ID = s.Profile + "_" + s.Arch + "_" + s.Version
+       s.Name = s.Arch + "   (" + s.Version + ")"
+
        envFile := filepath.Join(path, "environment-setup*")
        ef, err := filepath.Glob(envFile)
        if err != nil {
@@ -41,7 +47,7 @@ func NewCrossSDK(path string) (*SDK, error) {
        return &s, nil
 }
 
-// GetEnvCmd returns the command to initialized the environment to use a cross SDK
+// GetEnvCmd returns the command used to initialized the environment
 func (s *SDK) GetEnvCmd() string {
-       return ". " + path.Join(s.Path, s.EnvFile)
+       return ". " + s.EnvFile
 }
index 435aae6..87bb85d 100644 (file)
@@ -3,6 +3,7 @@ package crosssdk
 import (
        "path"
        "path/filepath"
+       "sync"
 
        "github.com/Sirupsen/logrus"
        "github.com/iotbzh/xds-server/lib/common"
@@ -10,7 +11,11 @@ import (
 )
 
 // SDKs List of installed SDK
-type SDKs []*SDK
+type SDKs struct {
+       Sdks []SDK
+
+       mutex sync.Mutex
+}
 
 // Init creates a new instance of Syncthing
 func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) {
@@ -27,13 +32,61 @@ func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) {
                        log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error())
                        return &s, err
                }
+               s.mutex.Lock()
+               defer s.mutex.Unlock()
+
                for _, d := range dirs {
                        sdk, err := NewCrossSDK(d)
                        if err != nil {
                                log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error())
                        }
-                       s = append(s, sdk)
+                       s.Sdks = append(s.Sdks, *sdk)
                }
        }
+
+       log.Debugf("SDKs: %d cross sdks found", len(s.Sdks))
+
        return &s, nil
 }
+
+// GetAll returns all existing SDKs
+func (s *SDKs) GetAll() []SDK {
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+       res := s.Sdks
+       return res
+}
+
+// Get returns an SDK from id
+func (s *SDKs) Get(id int) SDK {
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       if id < 0 || id > len(s.Sdks) {
+               return SDK{}
+       }
+       res := s.Sdks[id]
+       return res
+}
+
+// GetEnvCmd returns the command used to initialized the environment for an SDK
+func (s *SDKs) GetEnvCmd(id string, defaultID string) string {
+       if id == "" && defaultID == "" {
+               // no env cmd
+               return ""
+       }
+
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+       defaultEnv := ""
+       for _, sdk := range s.Sdks {
+               if sdk.ID == id {
+                       return sdk.GetEnvCmd()
+               }
+               if sdk.ID == defaultID {
+                       defaultEnv = sdk.GetEnvCmd()
+               }
+       }
+       // Return default env that may be empty
+       return defaultEnv
+}
index 6687b68..be1bc33 100644 (file)
@@ -71,13 +71,8 @@ func (c *Folder) UpdateFolder(newFolder xdsconfig.FolderConfig) (xdsconfig.Folde
 
        c.Conf.Folders = c.Conf.Folders.Update(xdsconfig.FoldersConfig{newFolder})
 
-       err := c.SThg.FolderChange(st.FolderChangeArg{
-               ID:           newFolder.ID,
-               Label:        newFolder.Label,
-               RelativePath: newFolder.RelativePath,
-               SyncThingID:  newFolder.SyncThingID,
-               ShareRootDir: c.Conf.ShareRootDir,
-       })
+       err := c.SThg.FolderChange(newFolder)
+
        newFolder.BuilderSThgID = c.Conf.Builder.SyncThingID // FIXME - should be removed after local ST config rework
        newFolder.Status = xdsconfig.FolderStatusEnable
 
index 841901d..957dd65 100644 (file)
@@ -38,6 +38,7 @@ type SyncThing struct {
        logsDir     string
        exitSTChan  chan ExitChan
        exitSTIChan chan ExitChan
+       conf        *xdsconfig.Config
        client      *common.HTTPClient
        log         *logrus.Logger
 }
@@ -85,6 +86,7 @@ func NewSyncThing(conf *xdsconfig.Config, log *logrus.Logger) *SyncThing {
                binDir:  binDir,
                logsDir: conf.FileConf.LogsDir,
                log:     log,
+               conf:    conf,
        }
 
        return &s
index d79e579..45ac60d 100644 (file)
@@ -4,21 +4,13 @@ import (
        "path/filepath"
        "strings"
 
+       "github.com/iotbzh/xds-server/lib/xdsconfig"
        "github.com/syncthing/syncthing/lib/config"
        "github.com/syncthing/syncthing/lib/protocol"
 )
 
-// FIXME remove and use an interface on xdsconfig.FolderConfig
-type FolderChangeArg struct {
-       ID           string
-       Label        string
-       RelativePath string
-       SyncThingID  string
-       ShareRootDir string
-}
-
 // FolderChange is called when configuration has changed
-func (s *SyncThing) FolderChange(f FolderChangeArg) error {
+func (s *SyncThing) FolderChange(f xdsconfig.FolderConfig) error {
 
        // Get current config
        stCfg, err := s.ConfigGet()
@@ -63,7 +55,7 @@ func (s *SyncThing) FolderChange(f FolderChangeArg) error {
        folder := config.FolderConfiguration{
                ID:      id,
                Label:   label,
-               RawPath: filepath.Join(f.ShareRootDir, f.RelativePath),
+               RawPath: filepath.Join(s.conf.ShareRootDir, f.RelativePath),
        }
 
        folder.Devices = append(folder.Devices, config.FolderDeviceConfiguration{
index 40ce948..0905c77 100644 (file)
@@ -83,7 +83,7 @@ func (s *Server) Serve() error {
        s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge)
 
        // Create REST API
-       s.api = apiv1.New(s.sessions, s.cfg, s.mfolder, s.router)
+       s.api = apiv1.New(s.router, s.sessions, s.cfg, s.mfolder, s.sdks)
 
        // Websocket routes
        s.sIOServer, err = socketio.NewServer(nil)
index e32f46a..bb2b56f 100644 (file)
@@ -29,13 +29,14 @@ type FolderConfig struct {
        SyncThingID   string     `json:"syncThingID"`
        BuilderSThgID string     `json:"builderSThgID"`
        Status        string     `json:"status"`
+       DefaultSdk    string     `json:"defaultSdk"`
 
        // Not exported fields
        RootPath string `json:"-"`
 }
 
 // NewFolderConfig creates a new folder object
-func NewFolderConfig(id, label, rootDir, path string) FolderConfig {
+func NewFolderConfig(id, label, rootDir, path string, defaultSdk string) FolderConfig {
        return FolderConfig{
                ID:           id,
                Label:        label,
@@ -44,6 +45,7 @@ func NewFolderConfig(id, label, rootDir, path string) FolderConfig {
                SyncThingID:  "",
                Status:       FolderStatusDisable,
                RootPath:     rootDir,
+               DefaultSdk:   defaultSdk,
        }
 }
 
diff --git a/main.go b/main.go
index 36586cf..6594bdb 100644 (file)
--- a/main.go
+++ b/main.go
@@ -168,6 +168,10 @@ func xdsApp(cliCtx *cli.Context) error {
                }
 
                // Retrieve initial Syncthing config
+
+               // FIXME: cannot retrieve default SDK, need to save on disk or somewhere
+               // else all config to be able to restore it.
+               defaultSdk := ""
                stCfg, err := ctx.SThg.ConfigGet()
                if err != nil {
                        return cli.NewExitError(err, 2)
@@ -177,7 +181,8 @@ func xdsApp(cliCtx *cli.Context) error {
                        if relativePath == "" {
                                relativePath = stFld.RawPath
                        }
-                       newFld := xdsconfig.NewFolderConfig(stFld.ID, stFld.Label, ctx.Config.ShareRootDir, strings.Trim(relativePath, "/"))
+
+                       newFld := xdsconfig.NewFolderConfig(stFld.ID, stFld.Label, ctx.Config.ShareRootDir, strings.Trim(relativePath, "/"), defaultSdk)
                        ctx.Config.Folders = ctx.Config.Folders.Update(xdsconfig.FoldersConfig{newFld})
                }
 
index 5c33e43..d4a6918 100644 (file)
@@ -19,12 +19,17 @@ import { ConfigComponent } from "./config/config.component";
 import { ProjectCardComponent } from "./projects/projectCard.component";
 import { ProjectReadableTypePipe } from "./projects/projectCard.component";
 import { ProjectsListAccordionComponent } from "./projects/projectsListAccordion.component";
+import { SdkCardComponent } from "./sdks/sdkCard.component";
+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 { SyncthingService } from "./common/syncthing.service";
 import { ConfigService } from "./common/config.service";
 import { AlertService } from './common/alert.service';
+import { SdkService } from "./common/sdk.service";
 
 
 
@@ -51,6 +56,9 @@ import { AlertService } from './common/alert.service';
         ProjectCardComponent,
         ProjectReadableTypePipe,
         ProjectsListAccordionComponent,
+        SdkCardComponent,
+        SdksListAccordionComponent,
+        SdkSelectDropdownComponent,
     ],
     providers: [
         AppRoutingProviders,
@@ -61,7 +69,8 @@ import { AlertService } from './common/alert.service';
         XDSServerService,
         ConfigService,
         SyncthingService,
-        AlertService
+        AlertService,
+        SdkService,
     ],
     bootstrap: [AppComponent]
 })
index d2a8da6..2ab7821 100644 (file)
         </div>
     </div>
     &nbsp;
+    <div class="row">
+        <div class="col-xs-8">
+            <label>Cross SDK </label>
+            <!-- FIXME why not working ?
+                    <sdk-select-dropdown [sdks]="(sdks$ | async)"></sdk-select-dropdown>
+                    -->
+            <sdk-select-dropdown></sdk-select-dropdown>
+        </div>
+    </div>
+    &nbsp;
     <div class="row ">
         <div class="col-xs-8 pull-left ">
             <label>Sub-directory</label>
index a1a965b..a99a1fe 100644 (file)
@@ -8,6 +8,7 @@ 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";
 
 @Component({
     selector: 'build',
@@ -32,8 +33,11 @@ export class BuildComponent implements OnInit, AfterViewChecked {
     private startTime: Map<string, number> = new Map<string, number>();
 
     // I initialize the app component.
-    constructor(private configSvr: ConfigService, private sdkSvr: XDSServerService,
-        private fb: FormBuilder, private alertSvr: AlertService
+    constructor(private configSvr: ConfigService,
+        private xdsSvr: XDSServerService,
+        private fb: FormBuilder,
+        private alertSvr: AlertService,
+        private sdkSvr: SdkService
     ) {
         this.cmdOutput = "";
         this.confValid = false;
@@ -54,12 +58,12 @@ export class BuildComponent implements OnInit, AfterViewChecked {
         });
 
         // Command output data tunneling
-        this.sdkSvr.CmdOutput$.subscribe(data => {
+        this.xdsSvr.CmdOutput$.subscribe(data => {
             this.cmdOutput += data.stdout + "\n";
         });
 
         // Command exit
-        this.sdkSvr.CmdExit$.subscribe(exit => {
+        this.xdsSvr.CmdExit$.subscribe(exit => {
             if (this.startTime.has(exit.cmdID)) {
                 this.cmdInfo = 'Last command duration: ' + this._computeTime(this.startTime.get(exit.cmdID));
                 this.startTime.delete(exit.cmdID);
@@ -93,7 +97,9 @@ export class BuildComponent implements OnInit, AfterViewChecked {
         let t0 = performance.now();
         this.cmdInfo = 'Start build of ' + prjID + ' at ' + t0;
 
-        this.sdkSvr.make(prjID, this.buildForm.value.subpath, args)
+        let sdkid = this.sdkSvr.getCurrentId();
+
+        this.xdsSvr.make(prjID, this.buildForm.value.subpath, args, sdkid)
             .subscribe(res => {
                 this.startTime.set(String(res.cmdID), t0);
             },
index 67ee14c..201ee8b 100644 (file)
@@ -35,6 +35,7 @@ export interface IProject {
     localPrjDef?: any;
     isExpanded?: boolean;
     visible?: boolean;
+    defaultSdkID?: string;
 }
 
 export interface ILocalSTConfig {
@@ -213,6 +214,7 @@ export class ConfigService {
             label: prj.label,
             path: prj.path,
             hostSyncThingID: this.confStore.localSThg.ID,
+            defaultSdkID: prj.defaultSdkID,
         };
 
         // Send config to XDS server
diff --git a/webapp/src/app/common/sdk.service.ts b/webapp/src/app/common/sdk.service.ts
new file mode 100644 (file)
index 0000000..3f2f32a
--- /dev/null
@@ -0,0 +1,43 @@
+import { Injectable, SecurityContext } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
+import { XDSServerService } from "../common/xdsserver.service";
+
+export interface ISdk {
+    id: string;
+    profile: string;
+    version: string;
+    arch: number;
+    path: string;
+}
+
+@Injectable()
+export class SdkService {
+    public Sdks$: Observable<ISdk[]>;
+
+    private _sdksList = [];
+    private current: ISdk;
+    private sdksSubject = <BehaviorSubject<ISdk[]>>new BehaviorSubject(this._sdksList);
+
+    constructor(private xdsSvr: XDSServerService) {
+        this.current = null;
+        this.Sdks$ = this.sdksSubject.asObservable();
+
+        this.xdsSvr.getSdks().subscribe((s) => {
+            this._sdksList = s;
+            this.sdksSubject.next(s);
+        });
+    }
+
+    public setCurrent(s: ISdk) {
+        this.current = s;
+    }
+
+    public getCurrentId(): string {
+        if (this.current && this.current.id) {
+            return this.current.id;
+        }
+        return "";
+    }
+}
\ No newline at end of file
index fd2e32a..6cd9ba3 100644 (file)
@@ -7,6 +7,8 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 import * as io from 'socket.io-client';
 
 import { AlertService } from './alert.service';
+import { ISdk } from './sdk.service';
+
 
 // Import RxJs required methods
 import 'rxjs/add/operator/map';
@@ -20,6 +22,7 @@ export interface IXDSConfigProject {
     path: string;
     hostSyncThingID: string;
     label?: string;
+    defaultSdkID?: string;
 }
 
 interface IXDSBuilderConfig {
@@ -36,6 +39,7 @@ interface IXDSFolderConfig {
     syncThingID: string;
     builderSThgID?: string;
     status?: string;
+    defaultSdkID: string;
 }
 
 interface IXDSConfig {
@@ -136,6 +140,10 @@ export class XDSServerService {
 
     }
 
+    getSdks(): Observable<ISdk[]> {
+        return this._get('/sdks');
+    }
+
     getProjects(): Observable<IXDSFolderConfig[]> {
         return this._get('/folders');
     }
@@ -146,7 +154,8 @@ export class XDSServerService {
             label: cfg.label || "",
             path: cfg.path,
             type: FOLDER_TYPE_CLOUDSYNC,
-            syncThingID: cfg.hostSyncThingID
+            syncThingID: cfg.hostSyncThingID,
+            defaultSdkID: cfg.defaultSdkID || "",
         };
         return this._post('/folder', folder);
     }
@@ -163,8 +172,8 @@ export class XDSServerService {
             });
     }
 
-    make(prjID: string, dir: string, args: string): Observable<any> {
-        return this._post('/make', { id: prjID, rpath: dir, args: args });
+    make(prjID: string, dir: string, args: string, sdkid?: string): Observable<any> {
+        return this._post('/make', { id: prjID, rpath: dir, args: args, sdkid: sdkid });
     }
 
 
index 45b0e14..2a3d322 100644 (file)
     </div>
 </div>
 
+<div class="panel panel-default">
+    <div class="panel-heading">
+        <h2 class="panel-title">Cross SDKs Configuration</h2>
+    </div>
+    <div class="panel-body">
+        <div class="row col-xs-12">
+            <sdks-list-accordion [sdks]="(sdks$ | async)"></sdks-list-accordion>
+        </div>
+    </div>
+</div>
+
 <div class="panel panel-default">
     <div class="panel-heading">
         <h2 class="panel-title">Projects Configuration</h2>
index 681c296..745e9f6 100644 (file)
@@ -11,6 +11,7 @@ import { ConfigService, IConfig, IProject, ProjectType } from "../common/config.
 import { XDSServerService, IServerStatus } from "../common/xdsserver.service";
 import { SyncthingService, ISyncThingStatus } from "../common/syncthing.service";
 import { AlertService } from "../common/alert.service";
+import { ISdk, SdkService } from "../common/sdk.service";
 
 @Component({
     templateUrl: './app/config/config.component.html',
@@ -23,6 +24,7 @@ import { AlertService } from "../common/alert.service";
 export class ConfigComponent implements OnInit {
 
     config$: Observable<IConfig>;
+    sdks$: Observable<ISdk[]>;
     severStatus$: Observable<IServerStatus>;
     localSTStatus$: Observable<ISyncThingStatus>;
 
@@ -44,8 +46,9 @@ export class ConfigComponent implements OnInit {
 
     constructor(
         private configSvr: ConfigService,
-        private sdkSvr: XDSServerService,
+        private xdsSvr: XDSServerService,
         private stSvr: SyncthingService,
+        private sdkSvr: SdkService,
         private alert: AlertService,
         private fb: FormBuilder
     ) {
@@ -59,7 +62,8 @@ export class ConfigComponent implements OnInit {
 
     ngOnInit() {
         this.config$ = this.configSvr.conf;
-        this.severStatus$ = this.sdkSvr.Status$;
+        this.sdks$ = this.sdkSvr.Sdks$;
+        this.severStatus$ = this.xdsSvr.Status$;
         this.localSTStatus$ = this.stSvr.Status$;
 
         // Bind syncToolUrl to baseURL
@@ -117,6 +121,7 @@ export class ConfigComponent implements OnInit {
             label: formVal['label'],
             path: formVal['path'],
             type: ProjectType.SYNCTHING,
+            // FIXME: allow to set defaultSdkID from New Project config panel
         });
     }
 
diff --git a/webapp/src/app/sdks/sdkCard.component.ts b/webapp/src/app/sdks/sdkCard.component.ts
new file mode 100644 (file)
index 0000000..f5d2a54
--- /dev/null
@@ -0,0 +1,51 @@
+import { Component, Input } from '@angular/core';
+import { ISdk } from "../common/sdk.service";
+
+@Component({
+    selector: 'sdk-card',
+    template: `
+        <div class="row">
+            <div class="col-xs-12">
+                <div class="text-right" role="group">
+                    <button disabled class="btn btn-link" (click)="delete(sdk)"><span class="fa fa-trash fa-size-x2"></span></button>
+                </div>
+            </div>
+        </div>
+
+        <table class="table table-striped">
+            <tbody>
+            <tr>
+                <th><span class="fa fa-fw fa-id-badge"></span>&nbsp;<span>Profile</span></th>
+                <td>{{ sdk.profile }}</td>
+            </tr>
+            <tr>
+                <th><span class="fa fa-fw fa-tasks"></span>&nbsp;<span>Architecture</span></th>
+                <td>{{ sdk.arch }}</td>
+            </tr>
+            <tr>
+                <th><span class="fa fa-fw fa-code-fork"></span>&nbsp;<span>Version</span></th>
+                <td>{{ sdk.version }}</td>
+            </tr>
+            <tr>
+                <th><span class="fa fa-fw fa-folder-open-o"></span>&nbsp;<span>Sdk path</span></th>
+                <td>{{ sdk.path}}</td>
+            </tr>
+
+            </tbody>
+        </table >
+    `,
+    styleUrls: ['./app/config/config.component.css']
+})
+
+export class SdkCardComponent {
+
+    @Input() sdk: ISdk;
+
+    constructor() { }
+
+
+    delete(sdk: ISdk) {
+        // Not supported for now
+    }
+
+}
diff --git a/webapp/src/app/sdks/sdkSelectDropdown.component.ts b/webapp/src/app/sdks/sdkSelectDropdown.component.ts
new file mode 100644 (file)
index 0000000..5122cd2
--- /dev/null
@@ -0,0 +1,44 @@
+import { Component, Input } from "@angular/core";
+
+import { ISdk, SdkService } from "../common/sdk.service";
+
+@Component({
+    selector: 'sdk-select-dropdown',
+    template: `
+        <div class="btn-group" dropdown *ngIf="curSdk" >
+            <button dropdownToggle type="button" class="btn btn-primary dropdown-toggle" style="width: 20em;">
+                {{curSdk.name}} <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 sdk of sdks" (click)="select(sdk)">
+                    {{sdk.name}}</a>
+                </li>
+            </ul>
+        </div>
+    `
+})
+export class SdkSelectDropdownComponent {
+
+    // FIXME investigate to understand why not working with sdks as input
+    // <sdk-select-dropdown [sdks]="(sdks$ | async)"></sdk-select-dropdown>
+    //@Input() sdks: ISdk[];
+    sdks: ISdk[];
+
+    curSdk: ISdk;
+
+    constructor(private sdkSvr: SdkService) { }
+
+    ngOnInit() {
+        this.sdkSvr.Sdks$.subscribe((s) => {
+            this.sdks = s;
+            this.curSdk = this.sdks.length ? this.sdks[0] : null;
+            this.sdkSvr.setCurrent(this.curSdk);
+        });
+    }
+
+    select(s) {
+         this.sdkSvr.setCurrent(this.curSdk = s);
+    }
+}
+
+
diff --git a/webapp/src/app/sdks/sdksListAccordion.component.ts b/webapp/src/app/sdks/sdksListAccordion.component.ts
new file mode 100644 (file)
index 0000000..9094f27
--- /dev/null
@@ -0,0 +1,26 @@
+import { Component, Input } from "@angular/core";
+
+import { ISdk } from "../common/sdk.service";
+
+@Component({
+    selector: 'sdks-list-accordion',
+    template: `
+        <accordion>
+            <accordion-group #group *ngFor="let sdk of sdks">
+                <div accordion-heading>
+                    {{ sdk.name }}
+                    <i class="pull-right float-xs-right fa"
+                    [ngClass]="{'fa-chevron-down': group.isOpen, 'fa-chevron-right': !group.isOpen}"></i>
+                </div>
+                <sdk-card [sdk]="sdk"></sdk-card>
+            </accordion-group>
+        </accordion>
+    `
+})
+export class SdksListAccordionComponent {
+
+    @Input() sdks: ISdk[];
+
+}
+
+