summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2017-12-21 15:07:04 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2017-12-23 00:43:11 +0100
commit45f6472d1e8ecad428da314a6d762143f033865d (patch)
tree3d4f3f413ab752fcb0d5c661fd3fec63ba5f5c24
parenta85f3ef5017e7e1406476194cd5f3e848a3718f9 (diff)
Added new SDKs management supportv1.0.0-rc2
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
-rw-r--r--.vscode/settings.json72
-rw-r--r--glide.yaml2
-rw-r--r--lib/agent/apiv1-sdks.go74
-rw-r--r--lib/agent/apiv1.go11
-rw-r--r--lib/agent/events.go6
-rw-r--r--lib/agent/project-st.go5
-rw-r--r--lib/agent/projects.go5
-rw-r--r--lib/agent/xdsserver.go104
-rw-r--r--lib/syncthing/st.go2
-rw-r--r--lib/xaapiv1/events.go21
-rw-r--r--lib/xaapiv1/sdks.go50
-rw-r--r--webapp/src/app/@core-xds/services/sdk.service.ts187
-rw-r--r--webapp/src/app/@core-xds/services/xdsagent.service.ts72
-rw-r--r--webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html2
-rw-r--r--webapp/src/app/pages/build/build.component.ts9
-rw-r--r--webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts5
-rw-r--r--webapp/src/app/pages/pages-menu.ts8
-rw-r--r--webapp/src/app/pages/pages-routing.module.ts4
-rw-r--r--webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts9
-rw-r--r--webapp/src/app/pages/projects/projects.component.ts7
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html23
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts32
-rw-r--r--webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts131
-rw-r--r--webapp/src/app/pages/sdks/sdk-management/sdk-management.component.html26
-rw-r--r--webapp/src/app/pages/sdks/sdk-management/sdk-management.component.scss83
-rw-r--r--webapp/src/app/pages/sdks/sdk-management/sdk-management.component.ts168
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.html17
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.scss2
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.ts8
-rw-r--r--webapp/src/app/pages/sdks/sdks.module.ts11
30 files changed, 1011 insertions, 145 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 524a7d8..64ef9d6 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,10 +5,8 @@
"editor.tabSize": 2,
// Insert spaces when pressing Tab. This setting is overriden based on the file contents when `editor.detectIndentation` is on.
"editor.insertSpaces": true,
-
// .jsbeautifyrc not at root directory but under webapp/
"beautify.config": "webapp/.jsbeautifyrc",
-
// Configure glob patterns for excluding files and folders.
"files.exclude": {
".tmp": true,
@@ -33,18 +31,68 @@
],
// Words to add to dictionary for a workspace.
"cSpell.words": [
- "apiv", "gonic", "devel", "csrffound", "Syncthing", "STID", "ISTCONFIG",
- "socketio", "ldflags", "SThg", "stconfig", "Intf", "dismissible", "rpath",
- "WSID", "sess", "IXDS", "golib", "xdsapi", "xdsconfig", "xdsserver",
- "xdsagent", "nbsp", "Inot", "inotify", "cmdi", "sdkid", "Flds", "prjs",
- "iosk", "CIFS", "IPROJECT", "unregister", "conv", "PATHMAP", "nospace",
- "graphx", "Truthy", "darkviolet", "dwnl", "topnav", "leftbar", "urfave",
- "unmarshall", "sebd", "priv", "evts", "gdbserver", "tabset", "pageview",
- "subpath", "prebuild", "reflectme", "franciscocpg", "xsapiv", "xaapiv",
- "Sillyf"
+ "apiv",
+ "gonic",
+ "devel",
+ "csrffound",
+ "Syncthing",
+ "STID",
+ "ISTCONFIG",
+ "socketio",
+ "ldflags",
+ "SThg",
+ "stconfig",
+ "Intf",
+ "dismissible",
+ "rpath",
+ "WSID",
+ "sess",
+ "IXDS",
+ "golib",
+ "xdsapi",
+ "xdsconfig",
+ "xdsserver",
+ "xdsagent",
+ "nbsp",
+ "Inot",
+ "inotify",
+ "cmdi",
+ "sdkid",
+ "Flds",
+ "prjs",
+ "iosk",
+ "CIFS",
+ "IPROJECT",
+ "unregister",
+ "conv",
+ "PATHMAP",
+ "nospace",
+ "graphx",
+ "Truthy",
+ "darkviolet",
+ "dwnl",
+ "topnav",
+ "leftbar",
+ "urfave",
+ "unmarshall",
+ "sebd",
+ "priv",
+ "evts",
+ "gdbserver",
+ "tabset",
+ "pageview",
+ "subpath",
+ "prebuild",
+ "reflectme",
+ "franciscocpg",
+ "xsapiv",
+ "xaapiv",
+ "Sillyf",
+ "tabindex",
+ "EVTSDK"
],
// codelyzer
"tslint.rulesDirectory": "./webapp/node_modules/codelyzer",
"typescript.tsdk": "webapp/node_modules/typescript/lib",
"tslint.configFile": "webapp/tslint.json"
-}
+} \ No newline at end of file
diff --git a/glide.yaml b/glide.yaml
index 65ea68f..a08c3f5 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -26,7 +26,7 @@ import:
subpackages:
- golib/common
- package: github.com/iotbzh/xds-server
- version: v1.0.0-rc1
+ version: v1.0.0-rc2
subpackages:
- lib/xsapiv1
- package: github.com/franciscocpg/reflectme
diff --git a/lib/agent/apiv1-sdks.go b/lib/agent/apiv1-sdks.go
index 7a445ce..7d6342b 100644
--- a/lib/agent/apiv1-sdks.go
+++ b/lib/agent/apiv1-sdks.go
@@ -17,10 +17,84 @@
package agent
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/iotbzh/xds-agent/lib/xaapiv1"
+ "github.com/iotbzh/xds-server/lib/xsapiv1"
+)
+
// sdksPassthroughInit Declare passthrough routes for sdks
func (s *APIService) sdksPassthroughInit(svr *XdsServer) error {
svr.PassthroughGet("/sdks")
svr.PassthroughGet("/sdks/:id")
+ svr.PassthroughPost("/sdks")
+ svr.PassthroughPost("/sdks/abortinstall")
+ svr.PassthroughDelete("/sdks/:id")
+
+ return nil
+}
+
+// sdksEventsForwardInit Register events forwarder for sdks
+func (s *APIService) sdksEventsForwardInit(svr *XdsServer) error {
+
+ if !svr.Connected {
+ return fmt.Errorf("Cannot register events: XDS Server %v not connected", svr.ID)
+ }
+
+ // Forward SDK events from XDS-server to client
+ if _, err := svr.EventOn(xsapiv1.EVTSDKInstall, "", s._sdkEventInstallCB); err != nil {
+ s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTSDKInstall, err)
+ return err
+ }
+
+ if _, err := svr.EventOn(xsapiv1.EVTSDKRemove, "", s._sdkEventRemoveCB); err != nil {
+ s.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTSDKRemove, err)
+ return err
+ }
+
+ return nil
+}
+
+func (s *APIService) _sdkEventInstallCB(privD interface{}, data interface{}) error {
+ // assume that xsapiv1.SDKManagementMsg == xaapiv1.SDKManagementMsg
+ evt := xaapiv1.SDKManagementMsg{}
+ evtName := xaapiv1.EVTSDKInstall
+ d, err := json.Marshal(data)
+ if err != nil {
+ s.Log.Errorf("Cannot marshal XDS Server %s: err=%v", evtName, err)
+ return err
+ }
+ if err = json.Unmarshal(d, &evt); err != nil {
+ s.Log.Errorf("Cannot unmarshal XDS Server %s: err=%v", evtName, err)
+ return err
+ }
+
+ if err := s.events.Emit(evtName, evt, ""); err != nil {
+ s.Log.Warningf("Cannot notify %s (from server): %v", evtName, err)
+ return err
+ }
+ return nil
+}
+
+func (s *APIService) _sdkEventRemoveCB(privD interface{}, data interface{}) error {
+ // assume that xsapiv1.SDKManagementMsg == xaapiv1.SDKManagementMsg
+ evt := xaapiv1.SDKManagementMsg{}
+ evtName := xaapiv1.EVTSDKRemove
+ d, err := json.Marshal(data)
+ if err != nil {
+ s.Log.Errorf("Cannot marshal XDS Server %s: err=%v", evtName, err)
+ return err
+ }
+ if err = json.Unmarshal(d, &evt); err != nil {
+ s.Log.Errorf("Cannot unmarshal XDS Server %s: err=%v", evtName, err)
+ return err
+ }
+ if err := s.events.Emit(evtName, evt, ""); err != nil {
+ s.Log.Warningf("Cannot notify %s (from server): %v", evtName, err)
+ return err
+ }
return nil
}
diff --git a/lib/agent/apiv1.go b/lib/agent/apiv1.go
index 8ec26d2..d0e5a1c 100644
--- a/lib/agent/apiv1.go
+++ b/lib/agent/apiv1.go
@@ -23,6 +23,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/iotbzh/xds-agent/lib/xdsconfig"
+ "github.com/iotbzh/xds-server/lib/xsapiv1"
)
const apiBaseURL = "/api/v1"
@@ -129,11 +130,21 @@ func (s *APIService) AddXdsServer(cfg xdsconfig.XDSServerConf) (*XdsServer, erro
// Add to map
s.xdsServers[svr.ID] = svr
+ // Register event forwarder
+ s.sdksEventsForwardInit(svr)
+
// Load projects
if err == nil && svr.Connected {
err = s.projects.Init(svr)
}
+ // Registered to all events
+ if err == nil && svr.Connected {
+ if err = svr.EventRegister(xsapiv1.EVTAll, ""); err != nil {
+ s.Log.Errorf("XDS Server %v - register all events error: %v", svr.ID, err)
+ }
+ }
+
return svr, err
}
diff --git a/lib/agent/events.go b/lib/agent/events.go
index df7015a..678f116 100644
--- a/lib/agent/events.go
+++ b/lib/agent/events.go
@@ -88,14 +88,14 @@ func (e *Events) UnRegister(evName, sessionID string) error {
}
// Emit Used to manually emit an event
-func (e *Events) Emit(evName string, data interface{},fromSid string) error {
+func (e *Events) Emit(evName string, data interface{}, fromSid string) error {
var firstErr error
if _, ok := e.eventsMap[evName]; !ok {
return fmt.Errorf("Unsupported event type")
}
- e.LogSillyf("Emit Event %s: %v", evName, data)
+ e.LogSillyf("Emit Event %s: %v", evName, data)
firstErr = nil
evm := e.eventsMap[evName]
@@ -113,7 +113,7 @@ func (e *Events) Emit(evName string, data interface{},fromSid string) error {
Type: evName,
Data: data,
}
- e.Log.Debugf("Emit Event %s: %v", evName, sid)
+
if err := (*so).Emit(evName, msg); err != nil {
e.Log.Errorf("WS Emit %v error : %v", evName, err)
if firstErr == nil {
diff --git a/lib/agent/project-st.go b/lib/agent/project-st.go
index 8ac5f36..c92f5d4 100644
--- a/lib/agent/project-st.go
+++ b/lib/agent/project-st.go
@@ -106,9 +106,8 @@ func (p *STProject) Setup(prj xaapiv1.ProjectConfig) (*xaapiv1.ProjectConfig, er
// Register events to update folder status
// Register to XDS Server events
- p.server.EventOn(xsapiv1.EVTFolderStateChange, "", p._cbServerFolderChanged)
- if err := p.server.EventRegister(xsapiv1.EVTFolderStateChange, svrPrj.ID); err != nil {
- p.Log.Warningf("XDS Server EventRegister failed: %v", err)
+ if _, err := p.server.EventOn(xsapiv1.EVTFolderStateChange, "", p._cbServerFolderChanged); err != nil {
+ p.Log.Errorf("XDS Server EventOn '%s' failed: %v", xsapiv1.EVTFolderStateChange, err)
return svrPrj, err
}
diff --git a/lib/agent/projects.go b/lib/agent/projects.go
index d6268fa..7393364 100644
--- a/lib/agent/projects.go
+++ b/lib/agent/projects.go
@@ -75,6 +75,11 @@ func (p *Projects) Init(server *XdsServer) error {
for _, prj := range xFlds {
newP := svr.FolderToProject(prj)
if _, err := p.createUpdate(newP, false, true); err != nil {
+ // Don't consider that as an error (allow support config without CloudSync support)
+ if p.Context.SThg == nil && strings.Contains(err.Error(), "Server doesn't support project type CloudSync") {
+ continue
+ }
+
errMsg += "Error while creating project id " + prj.ID + ": " + err.Error() + "\n "
continue
}
diff --git a/lib/agent/xdsserver.go b/lib/agent/xdsserver.go
index 32656cf..3ec6123 100644
--- a/lib/agent/xdsserver.go
+++ b/lib/agent/xdsserver.go
@@ -21,7 +21,6 @@ import (
"encoding/json"
"fmt"
"io"
- "io/ioutil"
"net/http"
"strings"
"sync"
@@ -239,8 +238,11 @@ func (xs *XdsServer) PassthroughPost(url string) {
}
xs.apiRouter.POST(url, func(c *gin.Context) {
- bodyReq := []byte{}
- n, err := c.Request.Body.Read(bodyReq)
+ var err error
+ var data interface{}
+
+ // Get raw body
+ body, err := c.GetRawData()
if err != nil {
common.APIError(c, err.Error())
return
@@ -253,34 +255,80 @@ func (xs *XdsServer) PassthroughPost(url string) {
}
// Send Post request
- body, err := json.Marshal(bodyReq[:n])
+ response, err := xs.client.HTTPPostWithRes(nURL, string(body))
if err != nil {
- common.APIError(c, err.Error())
+ goto httpError
+ }
+ if response.StatusCode != 200 {
+ err = fmt.Errorf(response.Status)
return
}
-
- response, err := xs.client.HTTPPostWithRes(nURL, string(body))
+ err = json.Unmarshal(xs.client.ResponseToBArray(response), &data)
if err != nil {
- common.APIError(c, err.Error())
- return
+ goto httpError
}
- bodyRes, err := ioutil.ReadAll(response.Body)
+ c.JSON(http.StatusOK, data)
+ return
+
+ /* Handle error case */
+ httpError:
+ if strings.Contains(err.Error(), "connection refused") {
+ xs._Disconnected()
+ }
+ common.APIError(c, err.Error())
+ })
+}
+
+// PassthroughDelete Used to declare a route that sends directly a Delete request to XDS Server
+func (xs *XdsServer) PassthroughDelete(url string) {
+ if xs.apiRouter == nil {
+ xs.Log.Errorf("apiRouter not set !")
+ return
+ }
+
+ xs.apiRouter.DELETE(url, func(c *gin.Context) {
+ var err error
+ var data interface{}
+
+ // Take care of param (eg. id in /projects/:id)
+ nURL := url
+ if strings.Contains(url, ":") {
+ nURL = strings.TrimPrefix(c.Request.URL.Path, xs.APIURL)
+ }
+
+ // Send Post request
+ response, err := xs.client.HTTPDeleteWithRes(nURL)
if err != nil {
- common.APIError(c, "Cannot read response body")
+ goto httpError
+ }
+ if response.StatusCode != 200 {
+ err = fmt.Errorf(response.Status)
return
}
- c.JSON(http.StatusOK, string(bodyRes))
+ err = json.Unmarshal(xs.client.ResponseToBArray(response), &data)
+ if err != nil {
+ goto httpError
+ }
+
+ c.JSON(http.StatusOK, data)
+ return
+
+ /* Handle error case */
+ httpError:
+ if strings.Contains(err.Error(), "connection refused") {
+ xs._Disconnected()
+ }
+ common.APIError(c, err.Error())
})
}
// EventRegister Post a request to register to an XdsServer event
-func (xs *XdsServer) EventRegister(evName string, id string) error {
- return xs.client.Post(
- "/events/register",
+func (xs *XdsServer) EventRegister(evName string, filter string) error {
+ return xs.client.Post("/events/register",
xsapiv1.EventRegisterArgs{
- Name: evName,
- ProjectID: id,
+ Name: evName,
+ Filter: filter,
},
nil)
}
@@ -308,15 +356,15 @@ func (xs *XdsServer) EventOn(evName string, privData interface{}, f EventCB) (uu
evn := evName
err := xs.ioSock.On(evn, func(data interface{}) error {
- xs.sockEventsLock.Lock()
- sEvts := make([]*caller, len(xs.sockEvents[evn]))
- copy(sEvts, xs.sockEvents[evn])
- xs.sockEventsLock.Unlock()
- for _, c := range sEvts {
- c.Func(c.PrivateData, data)
- }
- return nil
- })
+ xs.sockEventsLock.Lock()
+ sEvts := make([]*caller, len(xs.sockEvents[evn]))
+ copy(sEvts, xs.sockEvents[evn])
+ xs.sockEventsLock.Unlock()
+ for _, c := range sEvts {
+ c.Func(c.PrivateData, data)
+ }
+ return nil
+ })
if err != nil {
return uuid.Nil, err
}
@@ -493,6 +541,10 @@ func (xs *XdsServer) _Reconnect() error {
// Reload projects list for this server
err = xs.projects.Init(xs)
}
+ if err == nil {
+ // Register again to all events
+ err = xs.EventRegister(xsapiv1.EVTAll, "")
+ }
return err
}
diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go
index c4b72c5..1ea3947 100644
--- a/lib/syncthing/st.go
+++ b/lib/syncthing/st.go
@@ -188,7 +188,7 @@ func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan
cmdOut, err := cmd.StdoutPipe()
if err != nil {
- return nil, fmt.Errorf("Pipe stdout error for : %s", err)
+ return nil, fmt.Errorf("Pipe stdout error for : %v", err)
}
go io.Copy(outfile, cmdOut)
diff --git a/lib/xaapiv1/events.go b/lib/xaapiv1/events.go
index 0ac08e8..16c2dd7 100644
--- a/lib/xaapiv1/events.go
+++ b/lib/xaapiv1/events.go
@@ -45,6 +45,8 @@ const (
EVTProjectAdd = EventTypePrefix + "project-add" // type EventMsg with Data type xaapiv1.ProjectConfig
EVTProjectDelete = EventTypePrefix + "project-delete" // type EventMsg with Data type xaapiv1.ProjectConfig
EVTProjectChange = EventTypePrefix + "project-state-change" // type EventMsg with Data type xaapiv1.ProjectConfig
+ EVTSDKInstall = EventTypePrefix + "sdk-install" // type EventMsg with Data type xaapiv1.SDKManagementMsg
+ EVTSDKRemove = EventTypePrefix + "sdk-remove" // type EventMsg with Data type xaapiv1.SDKManagementMsg
)
// EVTAllList List of all supported events
@@ -53,6 +55,8 @@ var EVTAllList = []string{
EVTProjectAdd,
EVTProjectDelete,
EVTProjectChange,
+ EVTSDKInstall,
+ EVTSDKRemove,
}
// EventMsg Event message send over Websocket, data format depend to Type (see DecodeXXX function)
@@ -92,3 +96,20 @@ func (e *EventMsg) DecodeProjectConfig() (ProjectConfig, error) {
}
return p, err
}
+
+// DecodeSDKMsg Helper to decode Data field type SDKManagementMsg
+func (e *EventMsg) DecodeSDKMsg() (SDKManagementMsg, error) {
+ var err error
+ s := SDKManagementMsg{}
+ switch e.Type {
+ case EVTSDKInstall, EVTSDKRemove:
+ d := []byte{}
+ d, err = json.Marshal(e.Data)
+ if err == nil {
+ err = json.Unmarshal(d, &s)
+ }
+ default:
+ err = fmt.Errorf("Invalid type")
+ }
+ return s, err
+}
diff --git a/lib/xaapiv1/sdks.go b/lib/xaapiv1/sdks.go
index 2dceecf..589f748 100644
--- a/lib/xaapiv1/sdks.go
+++ b/lib/xaapiv1/sdks.go
@@ -17,12 +17,50 @@
package xaapiv1
+// SDK status definition
+const (
+ SdkStatusDisable = "Disable"
+ SdkStatusNotInstalled = "Not Installed"
+ SdkStatusInstalling = "Installing"
+ SdkStatusUninstalling = "Un-installing"
+ SdkStatusInstalled = "Installed"
+)
+
// SDK Define a cross tool chain used to build application
type SDK struct {
- 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"`
+ ID string `json:"id" binding:"required"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Profile string `json:"profile"`
+ Version string `json:"version"`
+ Arch string `json:"arch"`
+ Path string `json:"path"`
+ URL string `json:"url"`
+ Status string `json:"status"`
+ Date string `json:"date"`
+ Size string `json:"size"`
+ Md5sum string `json:"md5sum"`
+ SetupFile string `json:"setupFile"`
+ LastError string `json:"lastError"`
+}
+
+// SDKInstallArgs JSON parameters of POST /sdks or /sdks/abortinstall commands
+type SDKInstallArgs struct {
+ ID string `json:"id" binding:"required"` // install by ID (must be part of GET /sdks result)
+ Filename string `json:"filename"` // install by using a file
+ Force bool `json:"force"` // force SDK install when already existing
+ Timeout int `json:"timeout"` // 1800 == default 30 minutes
+}
+
+// SDKManagementMsg Message send during SDK installation or when installation is complete
+type SDKManagementMsg struct {
+ CmdID string `json:"cmdID"`
+ Timestamp string `json:"timestamp"`
+ Sdk SDK `json:"sdk"`
+ Stdout string `json:"stdout"`
+ Stderr string `json:"stderr"`
+ Progress int `json:"progress"` // 0 = not started to 100% = complete
+ Exited bool `json:"exited"`
+ Code int `json:"code"`
+ Error string `json:"error"`
}
diff --git a/webapp/src/app/@core-xds/services/sdk.service.ts b/webapp/src/app/@core-xds/services/sdk.service.ts
index 2d54d4e..8fa6ad2 100644
--- a/webapp/src/app/@core-xds/services/sdk.service.ts
+++ b/webapp/src/app/@core-xds/services/sdk.service.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { Injectable, SecurityContext } from '@angular/core';
+import { Injectable, SecurityContext, isDevMode } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@@ -25,60 +25,159 @@ import { XDSAgentService } from '../services/xdsagent.service';
import 'rxjs/add/observable/throw';
export interface ISdk {
- id: string;
- profile: string;
- version: string;
- arch: number;
- path: string;
+ id: string;
+ name: string;
+ description: string;
+ profile: string;
+ version: string;
+ arch: string;
+ path: string;
+ url: string;
+ status: string;
+ date: string;
+ size: string;
+ md5sum: string;
+ setupFile: string;
+ lastError: string;
+}
+
+export interface ISdkManagementMsg {
+ cmdID: string;
+ timestamp: string;
+ sdk: ISdk;
+ stdout: string;
+ stderr: string;
+ progress: number;
+ exited: boolean;
+ code: number;
+ error: 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: XDSAgentService) {
- this.current = null;
- this.Sdks$ = this.sdksSubject.asObservable();
-
- this.xdsSvr.XdsConfig$.subscribe(cfg => {
- if (!cfg || cfg.servers.length < 1) {
- return;
- }
- // FIXME support multiple server
- // cfg.servers.forEach(svr => {
- this.xdsSvr.getSdks(cfg.servers[0].id).subscribe((s) => {
- this._sdksList = s;
- this.sdksSubject.next(s);
- });
- });
- }
+ public Sdks$: Observable<ISdk[]>;
+ public curSdk$: Observable<ISdk>;
- public setCurrent(s: ISdk) {
- this.current = s;
- }
+ private _sdksList = [];
+ private sdksSubject = <BehaviorSubject<ISdk[]>>new BehaviorSubject(this._sdksList);
+ private current: ISdk;
+ private curSdkSubject = <BehaviorSubject<ISdk>>new BehaviorSubject(this.current);
+ private curServerID;
- public getCurrent(): ISdk {
- return this.current;
- }
+ constructor(private xdsSvr: XDSAgentService) {
+ this.current = null;
+ this.Sdks$ = this.sdksSubject.asObservable();
+ this.curSdk$ = this.curSdkSubject.asObservable();
- public getCurrentId(): string {
- if (this.current && this.current.id) {
- return this.current.id;
+ this.xdsSvr.XdsConfig$.subscribe(cfg => {
+ if (!cfg || cfg.servers.length < 1) {
+ return;
+ }
+ // FIXME support multiple server
+ // cfg.servers.forEach(svr => {
+ this.curServerID = cfg.servers[0].id;
+ this.xdsSvr.getSdks(this.curServerID).subscribe((sdks) => {
+ this._sdksList = [];
+ sdks.forEach(s => {
+ this._addSdk(s, true);
+ });
+
+ // TODO: get previous val from xds-config service / cookie
+ if (this._sdksList.length > 0) {
+ this.current = this._sdksList[0];
+ this.curSdkSubject.next(this.current);
}
- return '';
+
+ this.sdksSubject.next(this._sdksList);
+ });
+ });
+
+ // Add listener on sdk creation, deletion and change events
+ this.xdsSvr.onSdkInstall().subscribe(evMgt => {
+ this._addSdk(evMgt.sdk);
+ });
+ this.xdsSvr.onSdkRemove().subscribe(evMgt => {
+ if (evMgt.sdk.status !== 'Not Installed') {
+ /* tslint:disable:no-console */
+ console.log('Error: received event:sdk-remove with invalid status: evMgt=', evMgt);
+ return;
+ }
+ this._delSdk(evMgt.sdk);
+ });
+
+ }
+
+ public setCurrent(s: ISdk) {
+ this.current = s;
+ }
+
+ public getCurrent(): ISdk {
+ return this.current;
+ }
+
+ public getCurrentId(): string {
+ if (this.current && this.current.id) {
+ return this.current.id;
}
+ return '';
+ }
+
+ public install(sdk: ISdk): Observable<ISdk> {
+ return this.xdsSvr.installSdk(this.curServerID, sdk.id);
+ }
+
+ public onInstall(): Observable<ISdkManagementMsg> {
+ return this.xdsSvr.onSdkInstall();
+ }
+
+ public abortInstall(sdk: ISdk): Observable<ISdk> {
+ return this.xdsSvr.abortInstall(this.curServerID, sdk.id);
+ }
+
+ public remove(sdk: ISdk): Observable<ISdk> {
+ return this.xdsSvr.removeSdk(this.curServerID, sdk.id);
+ }
+
+ /** Private **/
- public add(sdk: ISdk): Observable<ISdk> {
- // TODO SEB
- return Observable.throw('Not implement yet');
+ private _addSdk(sdk: ISdk, noNext?: boolean): ISdk {
+
+ // add new sdk
+ this._sdksList.push(sdk);
+
+ // sort sdk array
+ this._sdksList.sort((a, b) => {
+ if (a.name < b.name) {
+ return -1;
+ }
+ if (a.name > b.name) {
+ return 1;
+ }
+ return 0;
+ });
+
+ if (!noNext) {
+ this.sdksSubject.next(this._sdksList);
}
- public delete(sdk: ISdk): Observable<ISdk> {
- // TODO SEB
- return Observable.throw('Not implement yet');
+ return sdk;
+ }
+
+ private _delSdk(sdk: ISdk) {
+ const idx = this._sdksList.findIndex(item => item.id === sdk.id);
+ if (idx === -1) {
+ if (isDevMode) {
+ /* tslint:disable:no-console */
+ console.log('Warning: Try to delete sdk unknown id: sdk=', sdk);
+ }
+ return;
+ }
+ const delId = this._sdksList[idx].id;
+ this._sdksList.splice(idx, 1);
+ if (delId === this.current.id) {
+ this.setCurrent(this._sdksList[0]);
}
+ this.sdksSubject.next(this._sdksList);
+ }
+
}
diff --git a/webapp/src/app/@core-xds/services/xdsagent.service.ts b/webapp/src/app/@core-xds/services/xdsagent.service.ts
index 94d3dec..9f8385a 100644
--- a/webapp/src/app/@core-xds/services/xdsagent.service.ts
+++ b/webapp/src/app/@core-xds/services/xdsagent.service.ts
@@ -25,7 +25,7 @@ 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 RxJs required methods
@@ -34,7 +34,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 {
@@ -133,6 +133,9 @@ export class XDSAgentService {
protected projectDel$ = new Subject<IXDSProjectConfig>();
protected projectChange$ = new Subject<IXDSProjectConfig>();
+ protected sdkInstall$ = new Subject<ISdkManagementMsg>();
+ protected sdkRemove$ = new Subject<ISdkManagementMsg>();
+
private baseUrl: string;
private wsUrl: string;
private httpSessionID: string;
@@ -249,7 +252,7 @@ export class XDSAgentService {
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) {
@@ -261,7 +264,7 @@ export class XDSAgentService {
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) {
@@ -273,10 +276,36 @@ export class XDSAgentService {
if (ev && ev.data) {
this.projectChange$.next(Object.assign({}, ev.data));
} else if (isDevMode) {
- console.log('Warning: received event:project-state-change with unknown data: ev=', ev);
+ console.log('Warning: received event:project-state-change with unkn220own data: ev=', ev);
+ }
+ });
+
+ this.socket.on('event:sdk-install', (ev) => {
+ if (ev && ev.data && ev.data.sdk) {
+ const evt = <ISdkManagementMsg>ev.data;
+ this.sdkInstall$.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);
}
});
+ this.socket.on('event:sdk-remove', (ev) => {
+ if (ev && ev.data && ev.data.sdk) {
+ const evt = <ISdkManagementMsg>ev.data;
+ this.sdkRemove$.next(Object.assign({}, evt));
+
+ if (ev.sessionID !== '' && ev.sessionID !== this.httpSessionID && evt.sdk.name) {
+ this.alert.info('SDK "' + evt.sdk.name + '" has been removed by another tool.');
+ }
+ } else if (isDevMode) {
+ console.log('Warning: received event:sdk-remove with unknown data: ev=', ev);
+ }
+ });
}
/**
@@ -294,6 +323,14 @@ export class XDSAgentService {
return this.projectChange$.asObservable();
}
+ onSdkInstall(): Observable<ISdkManagementMsg> {
+ return this.sdkInstall$.asObservable();
+ }
+
+ onSdkRemove(): Observable<ISdkManagementMsg> {
+ return this.sdkRemove$.asObservable();
+ }
+
/**
** Misc / Version
***/
@@ -355,10 +392,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
***/
@@ -420,6 +469,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();
diff --git a/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html b/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html
index 7dd2ec7..4890b91 100644
--- a/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html
+++ b/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html
@@ -44,7 +44,7 @@
</div>
</div>
- <div class="offset-sm-9 col-sm-9">
+ <div>
<button class="btn btn-sm btn-hero-secondary" (click)="closeAction=false; resetDefault()">Reset settings</button>
</div>
</form>
diff --git a/webapp/src/app/pages/build/build.component.ts b/webapp/src/app/pages/build/build.component.ts
index f937fc3..9a47c35 100644
--- a/webapp/src/app/pages/build/build.component.ts
+++ b/webapp/src/app/pages/build/build.component.ts
@@ -105,9 +105,10 @@ export class BuildComponent implements OnInit, AfterViewChecked {
if (!this.isSetupValid()) {
return this.alertSvr.warning('Please select first a valid project.', true);
}
-
- const activeModal = this.modalService.open(BuildSettingsModalComponent, { size: 'lg', container: 'nb-layout' });
- activeModal.componentInstance.modalHeader = 'Large Modal';
+ const modal = this.modalService.open(
+ BuildSettingsModalComponent,
+ { size: 'lg', container: 'nb-layout' },
+ );
}
execCmd(cmdName: string) {
@@ -134,7 +135,7 @@ export class BuildComponent implements OnInit, AfterViewChecked {
cmd = this.curPrj.uiSettings.cmdPopulate;
break;
case 'exec':
- if (this.curPrj.uiSettings.cmdArgs instanceof Array)  {
+ if (this.curPrj.uiSettings.cmdArgs instanceof Array) {
cmd = this.curPrj.uiSettings.cmdArgs.join(' ');
} else {
cmd = this.curPrj.uiSettings.cmdArgs;
diff --git a/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts b/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts
index 3282d6b..b5b5db0 100644
--- a/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts
+++ b/webapp/src/app/pages/confirm/confirm-modal/confirm-modal.component.ts
@@ -24,6 +24,7 @@ export enum EType {
YesNo = 1,
OKCancel,
OK,
+ Cancel,
}
@Component({
@@ -77,6 +78,10 @@ export class ConfirmModalComponent implements OnInit {
this.textBtn = [ 'OK', '', '' ];
break;
+ case EType.Cancel:
+ this.textBtn = [ '', 'Cancel', '' ];
+ break;
+
case EType.OKCancel:
this.textBtn = [ 'OK', 'Cancel', '' ];
break;
diff --git a/webapp/src/app/pages/pages-menu.ts b/webapp/src/app/pages/pages-menu.ts
index eb5462c..c212179 100644
--- a/webapp/src/app/pages/pages-menu.ts
+++ b/webapp/src/app/pages/pages-menu.ts
@@ -56,6 +56,14 @@ export const MENU_ITEMS: NbMenuItem[] = [
title: 'SDKs',
icon: 'fa fa-file-archive-o',
link: '/pages/sdks',
+ /*
+ children: [
+ {
+ title: 'SDKs Management',
+ link: '/pages/sdks/management',
+ },
+ ],
+ */
},
{
title: 'Boards',
diff --git a/webapp/src/app/pages/pages-routing.module.ts b/webapp/src/app/pages/pages-routing.module.ts
index beaae51..df28160 100644
--- a/webapp/src/app/pages/pages-routing.module.ts
+++ b/webapp/src/app/pages/pages-routing.module.ts
@@ -23,6 +23,7 @@ import { PagesComponent } from './pages.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { ProjectsComponent } from './projects/projects.component';
import { SdksComponent } from './sdks/sdks.component';
+import { SdkManagementComponent } from './sdks/sdk-management/sdk-management.component';
import { BuildComponent } from './build/build.component';
const routes: Routes = [{
@@ -38,6 +39,9 @@ const routes: Routes = [{
path: 'sdks',
component: SdksComponent,
}, {
+ path: 'sdks/management',
+ component: SdkManagementComponent,
+ }, {
path: 'build',
component: BuildComponent,
}, {
diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts
index adfc1d8..9c2f95f 100644
--- a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts
+++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { Component, Input, ViewChild, OnInit } from '@angular/core';
+import { Component, ViewEncapsulation, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { FormControl, FormGroup, Validators, ValidationErrors, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms';
@@ -34,6 +34,13 @@ import { XDSConfigService } from '../../../@core-xds/services/xds-config.service
@Component({
selector: 'xds-project-add-modal',
templateUrl: 'project-add-modal.component.html',
+ encapsulation: ViewEncapsulation.None,
+ styles: [`
+ .modal-xxl .modal-lg {
+ width: 90%;
+ max-width:1200px;
+ }
+ `],
})
export class ProjectAddModalComponent implements OnInit {
// @Input('server-id') serverID: string;
diff --git a/webapp/src/app/pages/projects/projects.component.ts b/webapp/src/app/pages/projects/projects.component.ts
index 13f7a79..3d271e1 100644
--- a/webapp/src/app/pages/projects/projects.component.ts
+++ b/webapp/src/app/pages/projects/projects.component.ts
@@ -45,7 +45,10 @@ export class ProjectsComponent implements OnInit {
}
add() {
- const activeModal = this.modalService.open(ProjectAddModalComponent, { size: 'lg', container: 'nb-layout' });
- activeModal.componentInstance.modalHeader = 'Large Modal';
+ const activeModal = this.modalService.open(ProjectAddModalComponent, {
+ size: 'lg',
+ windowClass: 'modal-xxl',
+ container: 'nb-layout',
+ });
}
}
diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html
index 0c2787c..2edc0d3 100644
--- a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html
+++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html
@@ -3,10 +3,10 @@
<div class="row">
<div class="col-12 col-md-8">
- {{ labelGet(sdk) }}
+ {{ sdk.name }}
</div>
<div class="col-6 col-md-4 text-right" role="group">
- <button class="btn btn-outline-danger btn-tn btn-xds" (click)="delete(sdk)">
+ <button class="btn btn-outline-danger btn-tn btn-xds" (click)="remove(sdk)">
<span class="fa fa-trash fa-size-x2"></span>
</button>
</div>
@@ -24,19 +24,24 @@
<td>{{ sdk.id }}</td>
</tr>
<tr>
+ <th><span class="fa fa-fw fa-file-text-o"></span>&nbsp;<span>Description</span></th>
+ <td>{{ sdk.description }}</td>
+ </tr>
+ <tr>
<th><span class="fa fa-fw fa-user"></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>
+ <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>
+ <th><span class="fa fa-fw fa-folder-open-o"></span>&nbsp;<span>Sdk path</span></th>
+ <td>{{ sdk.path}}</td>
</tr>
</tbody>
</table>
diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts
index d41e2fb..997f01d 100644
--- a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts
+++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts
@@ -20,6 +20,8 @@ import { Component, Input, Pipe, PipeTransform } from '@angular/core';
import { SdkService, ISdk } from '../../../@core-xds/services/sdk.service';
import { AlertService } from '../../../@core-xds/services/alert.service';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { ConfirmModalComponent, EType } from '../../confirm/confirm-modal/confirm-modal.component';
@Component({
selector: 'xds-sdk-card',
@@ -36,18 +38,32 @@ export class SdkCardComponent {
constructor(
private alert: AlertService,
private sdkSvr: SdkService,
+ private modalService: NgbModal,
) {
}
- labelGet(sdk: ISdk) {
- return sdk.profile + '-' + sdk.arch + '-' + sdk.version;
- }
+ remove(sdk: ISdk) {
+ const modal = this.modalService.open(ConfirmModalComponent, {
+ size: 'lg',
+ backdrop: 'static',
+ container: 'nb-layout',
+ });
+ modal.componentInstance.title = 'Confirm SDK deletion';
+ modal.componentInstance.type = EType.YesNo;
+ modal.componentInstance.question = `
+ Do you <b>permanently remove '` + sdk.name + `'</b> SDK ?
+ <br><br>
+ <i><small>(SDK ID: ` + sdk.id + ` )</small></i>`;
- delete(sdk: ISdk) {
- this.sdkSvr.delete(sdk).subscribe(
- res => { },
- err => this.alert.error('ERROR delete: ' + err),
- );
+ modal.result
+ .then(res => {
+ if (res === 'yes') {
+ this.sdkSvr.remove(sdk).subscribe(
+ r => { },
+ err => this.alert.error(err),
+ );
+ }
+ });
}
}
diff --git a/webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts b/webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts
new file mode 100644
index 0000000..c9c518f
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdk-management/sdk-install.component.ts
@@ -0,0 +1,131 @@
+/**
+* @license
+* Copyright (C) 2017 "IoT.bzh"
+* Author Sebastien Douheret <sebastien@iot.bzh>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { Component, OnInit, Input, ViewChild, AfterViewChecked, ElementRef } from '@angular/core';
+import { DomSanitizer } from '@angular/platform-browser';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+
+import { AlertService } from '../../../@core-xds/services/alert.service';
+import { SdkService, ISdk } from '../../../@core-xds/services/sdk.service';
+
+@Component({
+ selector: 'xds-sdk-install-modal',
+ template: `
+ <div tabindex="-1">
+ <div class="modal-header">
+ SDK installation
+ </div>
+
+ <div class="modal-body row">
+ <div class="col-12 text-center">
+ Installation of <b> {{ sdk?.name }} '</b> <span [innerHTML]="instStatus"></span>
+ </div>
+ <br>
+ <br>
+ <div class="col-12 text-center">
+ <textarea rows="20" class="textarea-scroll" #scrollOutput [innerHtml]="installOutput"></textarea>
+ </div>
+ <div class="col-12 text-center">
+ <button type="button" class="btn" tabindex="1"
+ [ngClass]="(btnName=='Cancel')?'btn-default':'btn-primary'"
+ (click)="onBtnClick()">{{ btnName }}</button>
+ </div>
+ </div>
+
+ <!-- <div *ngIf="footer!=''" class="modal-footer">
+ <div class="col-12 text-center">
+ </div>
+ </div> -->
+ </div>
+ `,
+ styles: [`
+ .btn {
+ margin-top: 2em;
+ min-width: 10em;
+ }
+ .textarea-scroll {
+ font-family: monospace;
+ width: 100%;
+ overflow-y: scroll;
+ `],
+})
+
+export class SdkInstallComponent implements OnInit {
+ @Input() sdk;
+ @ViewChild('scrollOutput') private scrollContainer: ElementRef;
+
+ constructor(
+ private modalRef: NgbActiveModal,
+ private sanitizer: DomSanitizer,
+ private alert: AlertService,
+ private sdkSvr: SdkService,
+ ) { }
+
+ onInstallSub: any;
+ installOutput = '';
+ btnName = 'Cancel';
+ instStatus = '';
+
+ ngOnInit() {
+ this.instStatus = 'in progress...';
+
+ this.onInstallSub = this.sdkSvr.onInstall().subscribe(ev => {
+ if (ev.exited) {
+ this.btnName = 'OK';
+ this.instStatus = '<font color="green"> Done. </font>';
+
+ if (ev.code === 0) {
+ this.alert.info('SDK ' + ev.sdk.name + ' successfully installed.');
+
+ } else {
+ if (ev.sdk.lastError !== '') {
+ this.alert.error(ev.sdk.lastError);
+ } else {
+ this.alert.error('SDK ' + ev.sdk.name + ' installation failed. ' + ev.error);
+ }
+ }
+
+ } else {
+ if (ev.stdout !== '') {
+ this.installOutput += ev.stdout;
+ }
+ if (ev.stderr !== '') {
+ this.installOutput += ev.stderr;
+ }
+ this._scrollToBottom();
+ }
+ });
+ }
+
+ onBtnClick(): void {
+ this.onInstallSub.unsubscribe();
+ if (this.btnName === 'Cancel') {
+ this.btnName = 'OK';
+ this.instStatus = '<b><font color="red"> ABORTED </font></b>';
+ this.sdkSvr.abortInstall(this.sdk).subscribe(r => { }, err => this.alert.error(err));
+ } else {
+ this.modalRef.close();
+ }
+ }
+
+ private _scrollToBottom(): void {
+ try {
+ this.scrollContainer.nativeElement.scrollTop = this.scrollContainer.nativeElement.scrollHeight;
+ } catch (err) { }
+ }
+}
diff --git a/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.html b/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.html
new file mode 100644
index 0000000..0587194
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.html
@@ -0,0 +1,26 @@
+<div class="row">
+ <div class="col-12">
+ <nb-card-body>
+ <div class="col-9">
+ <nb-actions size="medium">
+ <nb-action>
+ <a href="#/pages/sdks" class="btn" role="Button">
+ <i id="hack-i" class="fa fa-list-alt"></i>
+ <span id="hack-span">Switch to basic SDKs view</span>
+ </a>
+ </nb-action>
+ </nb-actions>
+ </div>
+ </nb-card-body>
+ </div>
+
+ <nb-card>
+ <nb-card-header>
+ <span>SDKs Management</span>
+ </nb-card-header>
+
+ <nb-card-body>
+ <ng2-smart-table [settings]="settings" [source]="sdks" (custom)="onCustom($event)"></ng2-smart-table>
+ </nb-card-body>
+ </nb-card>
+</div>
diff --git a/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.scss b/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.scss
new file mode 100644
index 0000000..0ba2741
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.scss
@@ -0,0 +1,83 @@
+@import '../../../@theme/styles/themes';
+@import '~@nebular/theme/components/card/card.component.theme';
+// FIXME SEB: remove this ugly hack and use nb-theme
+#hack-i {
+ color: #a4abb3; // nb-theme(color-fg);
+ font-size: 1.5rem;
+ margin-right: 1rem;
+}
+
+#hack-span {
+ font-family: nb-theme(font-secondary);
+ font-weight: nb-theme(font-weight-bold);
+ color: #2a2a2a; // nb-theme(color-fg-heading);
+ text-transform: uppercase;
+}
+
+@include nb-install-component() {
+ nb-card-body {
+ display: flex;
+ align-items: center;
+ }
+ .action-groups-header {
+ flex-basis: 20%;
+ color: nb-theme(card-header-fg-heading);
+ font-family: nb-theme(card-header-font-family);
+ font-size: nb-theme(card-header-font-size);
+ font-weight: nb-theme(card-header-font-weight);
+ }
+ .nb-actions {
+ flex-basis: 80%;
+ }
+ .right {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ order: 1;
+ flex-direction: row-reverse;
+ }
+ nb-actions > nb-action {
+ padding: 0;
+ }
+ nb-action {
+ i {
+ color: nb-theme(color-fg);
+ font-size: 1.5rem;
+ margin-right: 1rem;
+ }
+ span {
+ font-family: nb-theme(font-secondary);
+ font-weight: nb-theme(font-weight-bold);
+ color: nb-theme(color-fg-heading);
+ text-transform: uppercase;
+ }
+ button {
+ margin: 0 auto;
+ padding: 0;
+ cursor: pointer;
+ border: none;
+ background: none;
+ display: flex;
+ align-items: center;
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+ }
+ }
+}
+
+td.ng2-smart-actions {
+ height: auto !important;
+}
+
+nav.ng2-smart-pagination-nav {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.ng2-smart-pagination .page-link.page-link-prev,
+.page-link.page-link-next {
+ font-size: 1em !important;
+}
diff --git a/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.ts b/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.ts
new file mode 100644
index 0000000..c885238
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdk-management/sdk-management.component.ts
@@ -0,0 +1,168 @@
+/**
+* @license
+* Copyright (C) 2017 "IoT.bzh"
+* Author Sebastien Douheret <sebastien@iot.bzh>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { Component, ViewEncapsulation, OnInit, isDevMode } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { LocalDataSource } from 'ng2-smart-table';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { ConfirmModalComponent, EType } from '../../confirm/confirm-modal/confirm-modal.component';
+import { SdkInstallComponent } from './sdk-install.component';
+
+import { AlertService } from '../../../@core-xds/services/alert.service';
+import { SdkService, ISdk } from '../../../@core-xds/services/sdk.service';
+import { ISdkMessage } from '../../../@core-xds/services/xdsagent.service';
+
+interface ISdkMgt extends ISdk {
+ link: string;
+ selected: boolean;
+}
+
+/*
+ * FIXME / TODO:
+ * - support install of multi SDKs (see settings.selectMode: 'multi')
+ * - add Uninstall button (use delete)
+ * - add (mouseover) to display description, date, size, ...
+ */
+
+@Component({
+ selector: 'xds-sdk-management',
+ templateUrl: 'sdk-management.component.html',
+ styleUrls: ['sdk-management.component.scss'],
+ encapsulation: ViewEncapsulation.None,
+})
+
+export class SdkManagementComponent implements OnInit {
+
+ sdks$: Observable<ISdk[]>;
+ sdks: ISdkMgt[];
+ source: LocalDataSource = new LocalDataSource();
+
+ settings = {
+ mode: 'external',
+ actions: {
+ add: false,
+ edit: false,
+ delete: false, // TODO, add delete == uninstall
+ custom: [
+ { name: 'install', title: '<i class="nb-plus"></i>' },
+ ],
+ },
+ delete: {
+ deleteButtonContent: '<i class="nb-trash"></i>',
+ confirmDelete: true,
+ },
+ columns: {
+ name: { title: 'Name', editable: false },
+ profile: { title: 'Profile', editable: false, filter: {} },
+ arch: { title: 'Architecture', editable: false, filter: {} },
+ version: { title: 'Version', editable: false },
+ // TODO: add status when delete supported:
+ // status: { title: 'Status', editable: false },
+ link: { title: 'Link', editable: false, type: 'html', filter: false, width: '2%' },
+ },
+ };
+
+ constructor(
+ private alert: AlertService,
+ private sdkSvr: SdkService,
+ private modalService: NgbModal,
+ ) { }
+
+ ngOnInit() {
+ this.sdkSvr.Sdks$.subscribe(sdks => {
+ const profMap = {};
+ const archMap = {};
+ this.sdks = [];
+ sdks.forEach(s => {
+ // only display not installed SDK
+ if (s.status !== 'Not Installed') {
+ return;
+ }
+ profMap[s.profile] = s.profile;
+ archMap[s.arch] = s.arch;
+
+ const sm = <ISdkMgt>s;
+ sm.selected = false;
+ if (s.url !== '') {
+ sm.link = '<a href="' + s.url.substr(0, s.url.lastIndexOf('/')) + '" target="_blank" class="fa fa-external-link"></a>';
+ }
+ this.sdks.push(sm);
+
+ });
+
+ // Add text box filter for Profile and Arch columns
+ const profList = []; Object.keys(profMap).forEach(a => profList.push({ value: a, title: a }));
+ this.settings.columns.profile.filter = {
+ type: 'list',
+ config: { selectText: 'Select...', list: profList },
+ };
+
+ const archList = []; Object.keys(archMap).forEach(a => archList.push({ value: a, title: a }));
+ this.settings.columns.arch.filter = {
+ type: 'list',
+ config: { selectText: 'Select...', list: archList },
+ };
+
+ // update sources
+ this.source.load(this.sdks);
+
+ });
+ }
+
+ onCustom(event): void {
+ if (event.action === 'install') {
+ const sdk = <ISdkMgt>event.data;
+ const modal = this.modalService.open(ConfirmModalComponent, {
+ size: 'lg',
+ backdrop: 'static',
+ container: 'nb-layout',
+ });
+ modal.componentInstance.title = 'Confirm SDK installation';
+ modal.componentInstance.type = EType.YesNo;
+ modal.componentInstance.question = `
+ Please confirm installation of <b>` + sdk.name + `'</b> SDK ?<br>
+ <br>
+ <i><small>(size: ` + sdk.size + `, date: ` + sdk.date + `)</small></i>`;
+
+ modal.result.then(res => {
+ if (res === 'yes') {
+ // Request installation
+ this.sdkSvr.install(sdk).subscribe(r => { }, err => this.alert.error(err));
+
+ const modalInstall = this.modalService.open(SdkInstallComponent, {
+ size: 'lg',
+ backdrop: 'static',
+ container: 'nb-layout',
+ });
+ modalInstall.componentInstance.sdk = sdk;
+ }
+ });
+
+
+ } else if (event.action === 'uninstall') {
+ // TODO
+
+ } else {
+ /* tslint:disable:no-console */
+ if (isDevMode) {
+ console.error('onCustom: unknown event action: ', event);
+ }
+ }
+ }
+
+}
diff --git a/webapp/src/app/pages/sdks/sdks.component.html b/webapp/src/app/pages/sdks/sdks.component.html
index 94c2501..9f9204f 100644
--- a/webapp/src/app/pages/sdks/sdks.component.html
+++ b/webapp/src/app/pages/sdks/sdks.component.html
@@ -4,10 +4,10 @@
<div class="col-9">
<nb-actions size="medium">
<nb-action>
- <button (click)="add()">
- <i class="nb-plus"></i>
- <span>Add new SDK</span>
- </button>
+ <a href="#/pages/sdks/management" class="btn" role="Button">
+ <i class="fa fa-wrench"></i>
+ <span>SDKs Management</span>
+ </a>
</nb-action>
</nb-actions>
</div>
@@ -20,7 +20,10 @@
</div>
</nb-card-body>
</div>
- <div class="col-md-6 col-lg-6" *ngFor="let sdk of (sdks$ | async)">
- <xds-sdk-card [sdk]="sdk"></xds-sdk-card>
- </div>
+
+ <ng-container *ngFor="let sdk of (sdks$ | async)">
+ <div class="col-md-6 col-lg-6" *ngIf="isVisible(sdk)">
+ <xds-sdk-card [sdk]="sdk"></xds-sdk-card>
+ </div>
+ </ng-container>
</div>
diff --git a/webapp/src/app/pages/sdks/sdks.component.scss b/webapp/src/app/pages/sdks/sdks.component.scss
index 92a8807..a125e8d 100644
--- a/webapp/src/app/pages/sdks/sdks.component.scss
+++ b/webapp/src/app/pages/sdks/sdks.component.scss
@@ -31,7 +31,7 @@
nb-action {
i {
color: nb-theme(color-fg);
- font-size: 2.5rem;
+ font-size: 1.5rem;
margin-right: 1rem;
}
span {
diff --git a/webapp/src/app/pages/sdks/sdks.component.ts b/webapp/src/app/pages/sdks/sdks.component.ts
index f9e31a4..12d2f71 100644
--- a/webapp/src/app/pages/sdks/sdks.component.ts
+++ b/webapp/src/app/pages/sdks/sdks.component.ts
@@ -20,7 +20,6 @@ import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
-// import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component';
import { SdkService, ISdk } from '../../@core-xds/services/sdk.service';
@@ -44,10 +43,7 @@ export class SdksComponent implements OnInit {
this.sdks$ = this.sdkSvr.Sdks$;
}
- add() {
- /* SEB TODO
- const activeModal = this.modalService.open(SdkAddModalComponent, { size: 'lg', container: 'nb-layout' });
- activeModal.componentInstance.modalHeader = 'Large Modal';
- */
+ isVisible(sdk: ISdk) {
+ return sdk.status === 'Installed' || sdk.status === 'Installing';
}
}
diff --git a/webapp/src/app/pages/sdks/sdks.module.ts b/webapp/src/app/pages/sdks/sdks.module.ts
index d717d86..17ede0e 100644
--- a/webapp/src/app/pages/sdks/sdks.module.ts
+++ b/webapp/src/app/pages/sdks/sdks.module.ts
@@ -21,20 +21,23 @@ import { ThemeModule } from '../../@theme/theme.module';
import { SdksComponent } from './sdks.component';
import { SdkCardComponent } from './sdk-card/sdk-card.component';
-// import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component';
-
+import { SdkManagementComponent } from './sdk-management/sdk-management.component';
+import { SdkInstallComponent } from './sdk-management/sdk-install.component';
+import { Ng2SmartTableModule } from 'ng2-smart-table';
@NgModule({
imports: [
ThemeModule,
+ Ng2SmartTableModule,
],
declarations: [
SdksComponent,
SdkCardComponent,
- // SdkAddModalComponent,
+ SdkManagementComponent,
+ SdkInstallComponent,
],
entryComponents: [
- // SdkAddModalComponent,
+ SdkInstallComponent,
],
})
export class SdksModule { }