From: Sebastien Douheret Date: Fri, 17 Nov 2017 09:31:07 +0000 (+0100) Subject: Merge remote-tracking branch 'origin/master' into wip X-Git-Tag: v1.0.0-rc1~12 X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=commitdiff_plain;h=985b32b78da6e5ea008e45f6cf2df140df350ea9;hp=8b800f6c9d862009684ce52d0007da08f46e748d;p=src%2Fxds%2Fxds-server.git Merge remote-tracking branch 'origin/master' into wip Signed-off-by: Sebastien Douheret Conflicts: Makefile webapp/package.json --- diff --git a/.vscode/settings.json b/.vscode/settings.json index 826f7fd..d2c6144 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -55,6 +55,7 @@ "mfolders", "IFOLDER", "flds", - "dflt" + "dflt", + "stconfig" ] } diff --git a/Makefile b/Makefile index 222e708..8eacc1f 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,19 @@ -# Makefile used to build XDS daemon Web Server - -# Application Version -VERSION := 0.3.4 +# Makefile used to build XDS Server # Syncthing version to install -SYNCTHING_VERSION = 0.14.28 -SYNCTHING_INOTIFY_VERSION = 0.8.6 - - -# Retrieve git tag/commit to set sub-version string -ifeq ($(origin SUB_VERSION), undefined) - SUB_VERSION := $(shell git describe --exact-match --tags 2>/dev/null | sed 's/^v//') - ifneq ($(SUB_VERSION), ) - VERSION := $(firstword $(subst -, ,$(SUB_VERSION))) - SUB_VERSION := $(word 2,$(subst -, ,$(SUB_VERSION))) - endif - ifeq ($(SUB_VERSION), ) - SUB_VERSION := $(shell git rev-parse --short HEAD) - ifeq ($(SUB_VERSION), ) - SUB_VERSION := unknown-dev - endif - endif +SYNCTHING_VERSION = 0.14.38 +SYNCTHING_INOTIFY_VERSION = 0.8.7 + + +# Retrieve git tag/commit to set version & sub-version strings +GIT_DESC := $(shell git describe --always --tags) +VERSION := $(firstword $(subst -, ,$(GIT_DESC))) +SUB_VERSION := $(subst $(VERSION)-,,$(GIT_DESC)) +ifeq ($(VERSION), ) + VERSION := unknown-dev +endif +ifeq ($(SUB_VERSION), ) + SUB_VERSION := $(shell date +'%Y-%m-%d_%H%M%S') endif # for backward compatibility @@ -103,7 +96,7 @@ fmt: tools/glide run: build/xds tools/syncthing/copytobin $(LOCAL_BINDIR)/xds-server$(EXT) --log info -c config.json.in -debug: build/xds webapp/debug tools/syncthing/copytobin +debug: build/xds tools/syncthing/copytobin $(LOCAL_BINDIR)/xds-server$(EXT) --log debug -c config.json.in .PHONY: clean @@ -112,17 +105,15 @@ clean: .PHONY: distclean distclean: clean - rm -rf $(LOCAL_BINDIR) $(ROOT_SRCDIR)/tools glide.lock vendor webapp/node_modules webapp/dist webapp/assets/xds-agent-tarballs/*.zip + rm -rf $(LOCAL_BINDIR) $(ROOT_SRCDIR)/tools glide.lock vendor $(ROOT_SRCDIR)/webapp/dist $(ROOT_SRCDIR)/webapp/node_modules webapp: webapp/install - (cd webapp && gulp build) - -webapp/debug: - (cd webapp && gulp watch &) + mkdir -p $(ROOT_SRCDIR)/webapp/dist $(ROOT_SRCDIR)/webapp/dist/fonts + (cd $(ROOT_SRCDIR)/webapp && cp -a ./assets ./src/index.html ./node_modules/font-awesome/css/font-awesome.min.css ./dist/) + (cd $(ROOT_SRCDIR)/webapp && cp -a ./node_modules/font-awesome/fonts/* ./dist/fonts/) webapp/install: (cd webapp && npm install) - @if [ -d ${DESTDIR}/usr/local/etc ]; then rm -rf ${DESTDIR}/usr; fi .PHONY: scripts scripts: diff --git a/config.json.in b/config.json.in index ac2f891..3dcc04a 100644 --- a/config.json.in +++ b/config.json.in @@ -1,6 +1,6 @@ { "webAppDir": "www", - "httpPort": "8000", + "httpPort": "8010", "shareRootDir": "${HOME}/.xds-server/projects", "logsDir": "/tmp/xds-server/logs", "sdkRootDir": "/xdt/sdk", diff --git a/glide.yaml b/glide.yaml index e017281..94ef2db 100644 --- a/glide.yaml +++ b/glide.yaml @@ -9,7 +9,7 @@ import: - package: github.com/gin-contrib/static version: master - package: github.com/syncthing/syncthing - version: =0.14.28 + version: =0.14.38 subpackages: - lib/sync - lib/config @@ -25,7 +25,7 @@ import: - package: github.com/satori/go.uuid version: ^1.1.0 - package: github.com/iotbzh/xds-common - version: 4b8e35b6786b + version: ^0.1.0 subpackages: - golib/common - golib/eows diff --git a/lib/apiv1/agent.go b/lib/apiv1/agent.go deleted file mode 100644 index 925f12b..0000000 --- a/lib/apiv1/agent.go +++ /dev/null @@ -1,70 +0,0 @@ -package apiv1 - -import ( - "net/http" - "path" - "strings" - - "path/filepath" - - "github.com/gin-gonic/gin" - common "github.com/iotbzh/xds-common/golib" -) - -// XDSAgentTarball . -type XDSAgentTarball struct { - OS string `json:"os"` - Arch string `json:"arch"` - Version string `json:"version"` - RawVersion string `json:"raw-version"` - FileURL string `json:"fileUrl"` -} - -// XDSAgentInfo . -type XDSAgentInfo struct { - Tarballs []XDSAgentTarball `json:"tarballs"` -} - -// getXdsAgentInfo : return various information about Xds Agent -func (s *APIService) getXdsAgentInfo(c *gin.Context) { - - res := XDSAgentInfo{} - tarballURL := "assets/xds-agent-tarballs" - tarballDir := filepath.Join(s.cfg.FileConf.WebAppDir, "assets", "xds-agent-tarballs") - if common.Exists(tarballDir) { - files, err := filepath.Glob(path.Join(tarballDir, "xds-agent_*.zip")) - if err != nil { - s.log.Debugf("Error while retrieving xds-agent tarballs: dir=%s, error=%v", tarballDir, err) - } - for _, ff := range files { - file := filepath.Base(ff) - // Assume that tarball name format is: xds-agent_OS-ARCH-RAWVERSION.zip - fs := strings.TrimSuffix(strings.TrimPrefix(file, "xds-agent_"), ".zip") - f := strings.Split(fs, "-") - - if len(f) >= 3 { - vers := strings.Split(f[2], "_") - ver := f[2] - if len(vers) > 1 { - ver = vers[0] - } - - newT := XDSAgentTarball{ - OS: f[0], - Arch: f[1], - Version: ver, - RawVersion: f[2], - FileURL: filepath.Join(tarballURL, file), - } - - s.log.Infof("Added XDS-Agent tarball: %s", file) - res.Tarballs = append(res.Tarballs, newT) - - } else { - s.log.Debugf("Error while retrieving xds-agent, decoding failure: file:%v", ff) - } - } - } - - c.JSON(http.StatusOK, res) -} diff --git a/lib/apiv1/apiv1.go b/lib/apiv1/apiv1.go index 262f513..d10a08e 100644 --- a/lib/apiv1/apiv1.go +++ b/lib/apiv1/apiv1.go @@ -34,19 +34,18 @@ func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolders } s.apiRouter.GET("/version", s.getVersion) - s.apiRouter.GET("/xdsagent/info", s.getXdsAgentInfo) s.apiRouter.GET("/config", s.getConfig) s.apiRouter.POST("/config", s.setConfig) s.apiRouter.GET("/folders", s.getFolders) - s.apiRouter.GET("/folder/:id", s.getFolder) - s.apiRouter.POST("/folder", s.addFolder) - s.apiRouter.POST("/folder/sync/:id", s.syncFolder) - s.apiRouter.DELETE("/folder/:id", s.delFolder) + s.apiRouter.GET("/folders/:id", s.getFolder) + s.apiRouter.POST("/folders", s.addFolder) + s.apiRouter.POST("/folders/sync/:id", s.syncFolder) + s.apiRouter.DELETE("/folders/:id", s.delFolder) s.apiRouter.GET("/sdks", s.getSdks) - s.apiRouter.GET("/sdk/:id", s.getSdk) + s.apiRouter.GET("/sdks/:id", s.getSdk) s.apiRouter.POST("/make", s.buildMake) s.apiRouter.POST("/make/:id", s.buildMake) diff --git a/lib/apiv1/events.go b/lib/apiv1/events.go index da8298c..9444262 100644 --- a/lib/apiv1/events.go +++ b/lib/apiv1/events.go @@ -112,6 +112,9 @@ func (s *APIService) eventsRegister(c *gin.Context) { Folder: *cfg, } + s.log.Debugf("WS Emit %s - Status=%10s, IsInSync=%6v, ID=%s", + EventEventType+evType, cfg.Status, cfg.IsInSync, cfg.ID) + if err := (*so).Emit(EventEventType+evType, msg); err != nil { s.log.Errorf("WS Emit Folder StateChanged event : %v", err) } @@ -119,11 +122,15 @@ func (s *APIService) eventsRegister(c *gin.Context) { data := make(folder.EventCBData) data["sid"] = sess.ID - err := s.mfolders.RegisterEventChange(args.ProjectID, &cbFunc, &data) + prjID, err := s.mfolders.ResolveID(args.ProjectID) if err != nil { common.APIError(c, err.Error()) return } + if err = s.mfolders.RegisterEventChange(prjID, &cbFunc, &data); err != nil { + common.APIError(c, err.Error()) + return + } c.JSON(http.StatusOK, gin.H{"status": "OK"}) } diff --git a/lib/apiv1/exec.go b/lib/apiv1/exec.go index de40c70..30444c1 100644 --- a/lib/apiv1/exec.go +++ b/lib/apiv1/exec.go @@ -19,7 +19,8 @@ type ( // ExecArgs JSON parameters of /exec command ExecArgs struct { ID string `json:"id" binding:"required"` - SdkID string `json:"sdkid"` // sdk ID to use for setting env + SdkID string `json:"sdkID"` // sdk ID to use for setting env + CmdID string `json:"cmdID"` // command unique ID Cmd string `json:"cmd" binding:"required"` Args []string `json:"args"` Env []string `json:"env"` @@ -104,15 +105,19 @@ func (s *APIService) execCmd(c *gin.Context) { } // Allow to pass id in url (/exec/:id) or as JSON argument - id := c.Param("id") - if id == "" { - id = args.ID + idArg := c.Param("id") + if idArg == "" { + idArg = args.ID } - if id == "" { + if idArg == "" { common.APIError(c, "Invalid id") return } - + id, err := s.mfolders.ResolveID(idArg) + if err != nil { + common.APIError(c, err.Error()) + return + } f := s.mfolders.Get(id) if f == nil { common.APIError(c, "Unknown id") @@ -168,11 +173,13 @@ func (s *APIService) execCmd(c *gin.Context) { } // Unique ID for each commands - cmdID := strconv.Itoa(execCommandID) - execCommandID++ + if args.CmdID == "" { + args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(execCommandID) + execCommandID++ + } // Create new execution over WS context - execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, cmdID) + execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, args.CmdID) execWS.Log = s.log // Append client project dir to environment diff --git a/lib/apiv1/folders.go b/lib/apiv1/folders.go index a231b86..398e21c 100644 --- a/lib/apiv1/folders.go +++ b/lib/apiv1/folders.go @@ -16,7 +16,12 @@ func (s *APIService) getFolders(c *gin.Context) { // getFolder returns a specific folder configuration func (s *APIService) getFolder(c *gin.Context) { - f := s.mfolders.Get(c.Param("id")) + id, err := s.mfolders.ResolveID(c.Param("id")) + if err != nil { + common.APIError(c, err.Error()) + return + } + f := s.mfolders.Get(id) if f == nil { common.APIError(c, "Invalid id") return @@ -67,11 +72,14 @@ func (s *APIService) addFolder(c *gin.Context) { // syncFolder force synchronization of folder files func (s *APIService) syncFolder(c *gin.Context) { - id := c.Param("id") - + id, err := s.mfolders.ResolveID(c.Param("id")) + if err != nil { + common.APIError(c, err.Error()) + return + } s.log.Debugln("Sync folder id: ", id) - err := s.mfolders.ForceSync(id) + err = s.mfolders.ForceSync(id) if err != nil { common.APIError(c, err.Error()) return @@ -82,7 +90,11 @@ func (s *APIService) syncFolder(c *gin.Context) { // delFolder deletes folder from server config func (s *APIService) delFolder(c *gin.Context) { - id := c.Param("id") + id, err := s.mfolders.ResolveID(c.Param("id")) + if err != nil { + common.APIError(c, err.Error()) + return + } s.log.Debugln("Delete folder id ", id) diff --git a/lib/apiv1/make.go b/lib/apiv1/make.go index cf76476..6e0c7d6 100644 --- a/lib/apiv1/make.go +++ b/lib/apiv1/make.go @@ -15,7 +15,8 @@ import ( // MakeArgs is the parameters (json format) of /make command type MakeArgs struct { ID string `json:"id"` - SdkID string `json:"sdkid"` // sdk ID to use for setting env + SdkID string `json:"sdkID"` // sdk ID to use for setting env + CmdID string `json:"cmdID"` // command unique ID Args []string `json:"args"` // args to pass to make command Env []string `json:"env"` RPath string `json:"rpath"` // relative path into project @@ -67,15 +68,19 @@ func (s *APIService) buildMake(c *gin.Context) { } // Allow to pass id in url (/make/:id) or as JSON argument - id := c.Param("id") - if id == "" { - id = args.ID + idArg := c.Param("id") + if idArg == "" { + idArg = args.ID } - if id == "" { + if idArg == "" { common.APIError(c, "Invalid id") return } - + id, err := s.mfolders.ResolveID(idArg) + if err != nil { + common.APIError(c, err.Error()) + return + } pf := s.mfolders.Get(id) if pf == nil { common.APIError(c, "Unknown id") @@ -171,8 +176,11 @@ func (s *APIService) buildMake(c *gin.Context) { } } - cmdID := strconv.Itoa(makeCommandID) - makeCommandID++ + // Unique ID for each commands + if args.CmdID == "" { + args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(makeCommandID) + makeCommandID++ + } cmd := []string{} // Retrieve env command regarding Sdk ID @@ -186,14 +194,14 @@ func (s *APIService) buildMake(c *gin.Context) { cmd = append(cmd, args.Args...) } - s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd) + s.log.Debugf("Execute [Cmd ID %d]: %v", args.CmdID, cmd) data := make(map[string]interface{}) data["ID"] = prj.ID data["RootPath"] = prj.RootPath data["ExitImmediate"] = args.ExitImmediate - err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, &data) + err = common.ExecPipeWs(cmd, args.Env, sop, sess.ID, args.CmdID, execTmo, s.log, oCB, eCB, &data) if err != nil { common.APIError(c, err.Error()) return @@ -202,6 +210,6 @@ func (s *APIService) buildMake(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "OK", - "cmdID": cmdID, + "cmdID": args.CmdID, }) } diff --git a/lib/apiv1/sdks.go b/lib/apiv1/sdks.go index 52af506..f67a0ef 100644 --- a/lib/apiv1/sdks.go +++ b/lib/apiv1/sdks.go @@ -2,7 +2,6 @@ package apiv1 import ( "net/http" - "strconv" "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" @@ -15,12 +14,11 @@ func (s *APIService) getSdks(c *gin.Context) { // getSdk returns a specific Sdk configuration func (s *APIService) getSdk(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) + id, err := s.sdks.ResolveID(c.Param("id")) if err != nil { - common.APIError(c, "Invalid id") + common.APIError(c, err.Error()) return } - sdk := s.sdks.Get(id) if sdk.Profile == "" { common.APIError(c, "Invalid id") diff --git a/lib/apiv1/version.go b/lib/apiv1/version.go index e022441..8f928ec 100644 --- a/lib/apiv1/version.go +++ b/lib/apiv1/version.go @@ -7,6 +7,7 @@ import ( ) type version struct { + ID string `json:"id"` Version string `json:"version"` APIVersion string `json:"apiVersion"` VersionGitTag string `json:"gitTag"` @@ -15,6 +16,7 @@ type version struct { // getInfo : return various information about server func (s *APIService) getVersion(c *gin.Context) { response := version{ + ID: s.cfg.ServerUID, Version: s.cfg.Version, APIVersion: s.cfg.APIVersion, VersionGitTag: s.cfg.VersionGitTag, diff --git a/lib/crosssdk/sdk.go b/lib/crosssdk/sdk.go index 5a5770d..5be8954 100644 --- a/lib/crosssdk/sdk.go +++ b/lib/crosssdk/sdk.go @@ -3,6 +3,8 @@ package crosssdk import ( "fmt" "path/filepath" + + uuid "github.com/satori/go.uuid" ) // SDK Define a cross tool chain used to build application @@ -31,8 +33,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 + ")" + // Use V3 to ensure that we get same uuid on restart + s.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), s.Profile+"_"+s.Arch+"_"+s.Version).String() + s.Name = s.Arch + " (" + s.Version + ")" envFile := filepath.Join(path, "environment-setup*") ef, err := filepath.Glob(envFile) diff --git a/lib/crosssdk/sdks.go b/lib/crosssdk/sdks.go index 0da0d1b..e3d6607 100644 --- a/lib/crosssdk/sdks.go +++ b/lib/crosssdk/sdks.go @@ -1,8 +1,10 @@ package crosssdk import ( + "fmt" "path" "path/filepath" + "strings" "sync" "github.com/Sirupsen/logrus" @@ -12,14 +14,16 @@ import ( // SDKs List of installed SDK type SDKs struct { - Sdks []SDK + Sdks map[string]*SDK mutex sync.Mutex } // Init creates a new instance of Syncthing func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { - s := SDKs{} + s := SDKs{ + Sdks: make(map[string]*SDK), + } // Retrieve installed sdks sdkRD := cfg.FileConf.SdkRootDir @@ -44,7 +48,7 @@ func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error()) continue } - s.Sdks = append(s.Sdks, *sdk) + s.Sdks[sdk.ID] = sdk } } @@ -53,23 +57,50 @@ func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { 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 +// ResolveID Complete an SDK ID (helper for user that can use partial ID value) +func (s *SDKs) ResolveID(id string) (string, error) { + if id == "" { + return "", nil + } + + match := []string{} + for iid := range s.Sdks { + fmt.Printf("SEB prefix iid=%v id=%v\n", iid, id) + if strings.HasPrefix(iid, id) { + match = append(match, iid) + fmt.Printf(" SEB match (%d): %v\n", len(match), match) + } + } + fmt.Printf("SEB match (%d): %v\n", len(match), match) + + if len(match) == 1 { + return match[0], nil + } else if len(match) == 0 { + return id, fmt.Errorf("Unknown id") + } + return id, fmt.Errorf("Multiple IDs found with provided prefix: " + id) } // Get returns an SDK from id -func (s *SDKs) Get(id int) SDK { +func (s *SDKs) Get(id string) *SDK { s.mutex.Lock() defer s.mutex.Unlock() - if id < 0 || id > len(s.Sdks) { - return SDK{} + sc, exist := s.Sdks[id] + if !exist { + return nil + } + return sc +} + +// GetAll returns all existing SDKs +func (s *SDKs) GetAll() []SDK { + s.mutex.Lock() + defer s.mutex.Unlock() + res := []SDK{} + for _, v := range s.Sdks { + res = append(res, *v) } - res := s.Sdks[id] return res } @@ -82,15 +113,15 @@ func (s *SDKs) GetEnvCmd(id string, defaultID string) []string { s.mutex.Lock() defer s.mutex.Unlock() - defaultEnv := []string{} - for _, sdk := range s.Sdks { - if sdk.ID == id { - return sdk.GetEnvCmd() - } - if sdk.ID == defaultID { - defaultEnv = sdk.GetEnvCmd() - } + + if sdk, exist := s.Sdks[id]; exist { + return sdk.GetEnvCmd() } + + if sdk, exist := s.Sdks[defaultID]; defaultID != "" && exist { + return sdk.GetEnvCmd() + } + // Return default env that may be empty - return defaultEnv + return []string{} } diff --git a/lib/folder/folder-interface.go b/lib/folder/folder-interface.go index 4beccb8..9eb6829 100644 --- a/lib/folder/folder-interface.go +++ b/lib/folder/folder-interface.go @@ -1,12 +1,12 @@ package folder // FolderType definition -type FolderType int +type FolderType string const ( - TypePathMap = 1 - TypeCloudSync = 2 - TypeCifsSmb = 3 + TypePathMap = "PathMap" + TypeCloudSync = "CloudSync" + TypeCifsSmb = "CIFS" ) // Folder Status definition @@ -61,10 +61,13 @@ type FolderConfig struct { // PathMapConfig Path mapping specific data type PathMapConfig struct { ServerPath string `json:"serverPath"` + + // Don't keep temporary file name (IOW we don't want to save it and reuse it) + CheckFile string `json:"checkFile" xml:"-"` + CheckContent string `json:"checkContent" xml:"-"` } // CloudSyncConfig CloudSync (AKA Syncthing) specific data type CloudSyncConfig struct { - SyncThingID string `json:"syncThingID"` - BuilderSThgID string `json:"builderSThgID"` + SyncThingID string `json:"syncThingID"` } diff --git a/lib/folder/folder-pathmap.go b/lib/folder/folder-pathmap.go index 1020026..e200164 100644 --- a/lib/folder/folder-pathmap.go +++ b/lib/folder/folder-pathmap.go @@ -24,13 +24,20 @@ type PathMap struct { func NewFolderPathMap(gc *xdsconfig.Config) *PathMap { f := PathMap{ globalConfig: gc, + config: FolderConfig{ + Status: StatusDisable, + }, } return &f } // NewUID Get a UUID func (f *PathMap) NewUID(suffix string) string { - return uuid.NewV1().String() + "_" + suffix + uuid := uuid.NewV1().String() + if len(suffix) > 0 { + uuid += "_" + suffix + } + return uuid } // Add a new folder @@ -55,22 +62,43 @@ func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) { if !common.Exists(dir) { return nil, fmt.Errorf("ServerPath directory is not accessible: %s", dir) } - file, err := ioutil.TempFile(dir, "xds_pathmap_check") - if err != nil { - return nil, fmt.Errorf("ServerPath sanity check error: %s", err.Error()) - } - defer os.Remove(file.Name()) - - msg := "sanity check PathMap Add folder" - n, err := file.Write([]byte(msg)) - if err != nil || n != len(msg) { - return nil, fmt.Errorf("ServerPath sanity check error: %s", err.Error()) - } f.config = cfg f.config.RootPath = dir f.config.DataPathMap.ServerPath = dir f.config.IsInSync = true + + // Verify file created by XDS agent when needed + if cfg.DataPathMap.CheckFile != "" { + errMsg := "ServerPath sanity check error (%d): %v" + ckFile := f.ConvPathCli2Svr(cfg.DataPathMap.CheckFile) + if !common.Exists(ckFile) { + return nil, fmt.Errorf(errMsg, 1, "file not present") + } + if cfg.DataPathMap.CheckContent != "" { + fd, err := os.OpenFile(ckFile, os.O_APPEND|os.O_RDWR, 0600) + if err != nil { + return nil, fmt.Errorf(errMsg, 2, err) + } + defer fd.Close() + + // Check specific message written by agent + content, err := ioutil.ReadAll(fd) + if err != nil { + return nil, fmt.Errorf(errMsg, 3, err) + } + if string(content) != cfg.DataPathMap.CheckContent { + return nil, fmt.Errorf(errMsg, 4, "file content differ") + } + + // Write a specific message that will be check back on agent side + msg := "Pathmap checked message written by xds-server ID: " + f.globalConfig.ServerUID + "\n" + if n, err := fd.WriteString(msg); n != len(msg) || err != nil { + return nil, fmt.Errorf(errMsg, 5, err) + } + } + } + f.config.Status = StatusEnable return &f.config, nil diff --git a/lib/folder/folder-st-disable.go b/lib/folder/folder-st-disable.go index f90b776..7b53ca8 100644 --- a/lib/folder/folder-st-disable.go +++ b/lib/folder/folder-st-disable.go @@ -25,7 +25,11 @@ func NewFolderSTDisable(gc *xdsconfig.Config) *STFolderDisable { // NewUID Get a UUID func (f *STFolderDisable) NewUID(suffix string) string { - return uuid.NewV1().String() + "_" + suffix + uuid := uuid.NewV1().String() + if len(suffix) > 0 { + uuid += "_" + suffix + } + return uuid } // Add a new folder diff --git a/lib/model/folders.go b/lib/model/folders.go index 576c4a2..b8e6cf5 100644 --- a/lib/model/folders.go +++ b/lib/model/folders.go @@ -146,6 +146,27 @@ func (f *Folders) SaveConfig() error { return foldersConfigWrite(f.fileOnDisk, f.getConfigArrUnsafe()) } +// ResolveID Complete a Folder ID (helper for user that can use partial ID value) +func (f *Folders) ResolveID(id string) (string, error) { + if id == "" { + return "", nil + } + + match := []string{} + for iid := range f.folders { + if strings.HasPrefix(iid, id) { + match = append(match, iid) + } + } + + if len(match) == 1 { + return match[0], nil + } else if len(match) == 0 { + return id, fmt.Errorf("Unknown id") + } + return id, fmt.Errorf("Multiple IDs found with provided prefix: " + id) +} + // Get returns the folder config or nil if not existing func (f *Folders) Get(id string) *folder.IFOLDER { if id == "" { @@ -168,8 +189,7 @@ func (f *Folders) GetConfigArr() []folder.FolderConfig { // getConfigArrUnsafe Same as GetConfigArr without mutex protection func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig { - var conf []folder.FolderConfig - + conf := []folder.FolderConfig{} for _, v := range f.folders { conf = append(conf, (*v).GetConfig()) } @@ -214,24 +234,23 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo return nil, fmt.Errorf("Unsupported folder type") } + // Allocate a new UUID + if create { + newF.ID = fld.NewUID("") + } + if !create && newF.ID == "" { + return nil, fmt.Errorf("Cannot update folder with null ID") + } + // Set default value if needed if newF.Status == "" { newF.Status = folder.StatusDisable } if newF.Label == "" { - newF.Label = filepath.Base(newF.ClientPath) + "_" + newF.ID[0:8] - } - - // Allocate a new UUID - if create { - i := len(newF.Label) - if i > 20 { - i = 20 + newF.Label = filepath.Base(newF.ClientPath) + if len(newF.ID) > 8 { + newF.Label += "_" + newF.ID[0:8] } - newF.ID = fld.NewUID(newF.Label[:i]) - } - if !create && newF.ID == "" { - return nil, fmt.Errorf("Cannot update folder with null ID") } // Normalize path (needed for Windows path including bashlashes) diff --git a/lib/session/session.go b/lib/session/session.go index d4e1ad3..60b7b8a 100644 --- a/lib/session/session.go +++ b/lib/session/session.go @@ -36,27 +36,29 @@ type ClientSession struct { // Sessions holds client sessions type Sessions struct { - router *gin.Engine - cookieMaxAge int64 - sessMap map[string]ClientSession - mutex sync.Mutex - log *logrus.Logger - stop chan struct{} // signals intentional stop + router *gin.Engine + cookieMaxAge int64 + sessMap map[string]ClientSession + mutex sync.Mutex + log *logrus.Logger + LogLevelSilly bool + stop chan struct{} // signals intentional stop } // NewClientSessions . -func NewClientSessions(router *gin.Engine, log *logrus.Logger, cookieMaxAge string) *Sessions { +func NewClientSessions(router *gin.Engine, log *logrus.Logger, cookieMaxAge string, sillyLog bool) *Sessions { ckMaxAge, err := strconv.ParseInt(cookieMaxAge, 10, 0) if err != nil { ckMaxAge = 0 } s := Sessions{ - router: router, - cookieMaxAge: ckMaxAge, - sessMap: make(map[string]ClientSession), - mutex: sync.NewMutex(), - log: log, - stop: make(chan struct{}), + router: router, + cookieMaxAge: ckMaxAge, + sessMap: make(map[string]ClientSession), + mutex: sync.NewMutex(), + log: log, + LogLevelSilly: sillyLog, + stop: make(chan struct{}), } s.router.Use(s.Middleware()) @@ -197,15 +199,13 @@ func (s *Sessions) refresh(sid string) { } func (s *Sessions) monitorSessMap() { - const dbgFullTrace = false // for debugging - for { select { case <-s.stop: s.log.Debugln("Stop monitorSessMap") return case <-time.After(sessionMonitorTime * time.Second): - if dbgFullTrace { + if s.LogLevelSilly { s.log.Debugf("Sessions Map size: %d", len(s.sessMap)) s.log.Debugf("Sessions Map : %v", s.sessMap) } diff --git a/lib/syncthing/folder-st.go b/lib/syncthing/folder-st.go index 7e1fe55..f25a505 100644 --- a/lib/syncthing/folder-st.go +++ b/lib/syncthing/folder-st.go @@ -39,7 +39,11 @@ func (f *STFolder) NewUID(suffix string) string { if i > 15 { i = 15 } - return uuid.NewV1().String()[:14] + f.st.MyID[:i] + "_" + suffix + uuid := uuid.NewV1().String()[:14] + f.st.MyID[:i] + if len(suffix) > 0 { + uuid += "_" + suffix + } + return uuid } // Add a new folder @@ -57,10 +61,8 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) { f.fConfig = cfg - f.fConfig.DataCloudSync.BuilderSThgID = f.st.MyID // FIXME - should be removed after local ST config rework - // Update Syncthing folder - // (expect if status is ErrorConfig) + // (except if status is ErrorConfig) // TODO: add cache to avoid multiple requests on startup if f.fConfig.Status != folder.StatusErrorConfig { id, err := f.st.FolderChange(f.fConfig) diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go index 99a17a1..d1ebbe6 100644 --- a/lib/syncthing/st.go +++ b/lib/syncthing/st.go @@ -34,16 +34,16 @@ type SyncThing struct { STICmd *exec.Cmd MyID string Connected bool + Events *Events // Private fields binDir string logsDir string exitSTChan chan ExitChan exitSTIChan chan ExitChan - conf *xdsconfig.Config client *common.HTTPClient log *logrus.Logger - Events *Events + conf *xdsconfig.Config } // ExitChan Channel used for process exit @@ -134,7 +134,8 @@ func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan // Kill existing process (useful for debug ;-) ) if os.Getenv("DEBUG_MODE") != "" { - exec.Command("bash", "-c", "pkill -9 "+exeName).Output() + fmt.Printf("\n!!! DEBUG_MODE set: KILL existing %s process(es) !!!\n", exeName) + exec.Command("bash", "-c", "ps -ax |grep "+exeName+" |grep "+s.BaseURL+" |cut -d' ' -f 1|xargs -I{} kill -9 {}").Output() } // When not set (or set to '.') set bin to path of xds-agent executable @@ -227,7 +228,6 @@ func (s *SyncThing) Start() (*exec.Cmd, error) { env := []string{ "STNODEFAULTFOLDER=1", "STNOUPGRADE=1", - "STNORESTART=1", // FIXME SEB remove ? } s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan) @@ -317,7 +317,12 @@ func (s *SyncThing) Connect() error { common.HTTPClientConfig{ URLPrefix: "/rest", HeaderClientKeyName: "X-Syncthing-ID", + LogOut: s.conf.LogVerboseOut, + LogPrefix: "SYNCTHING: ", + LogLevel: common.HTTPLogLevelWarning, }) + s.client.SetLogLevel(s.log.Level.String()) + if err != nil { msg := ": " + err.Error() if strings.Contains(err.Error(), "connection refused") { @@ -329,11 +334,6 @@ func (s *SyncThing) Connect() error { return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)") } - // Redirect HTTP log into a file - s.client.SetLogLevel(s.conf.Log.Level.String()) - s.client.LoggerPrefix = "SYNCTHING: " - s.client.LoggerOut = s.conf.LogVerboseOut - s.MyID, err = s.IDGet() if err != nil { return fmt.Errorf("ERROR: cannot retrieve ID") diff --git a/lib/syncthing/stfolder.go b/lib/syncthing/stfolder.go index 70ac70a..503ba4b 100644 --- a/lib/syncthing/stfolder.go +++ b/lib/syncthing/stfolder.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/iotbzh/xds-server/lib/folder" - "github.com/syncthing/syncthing/lib/config" + stconfig "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/protocol" ) @@ -32,9 +32,9 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error { } for _, stFld := range stCfg.Folders { - cliPath := strings.TrimPrefix(stFld.RawPath, s.conf.FileConf.ShareRootDir) + cliPath := strings.TrimPrefix(stFld.Path, s.conf.FileConf.ShareRootDir) if cliPath == "" { - cliPath = stFld.RawPath + cliPath = stFld.Path } *f = append(*f, folder.FolderConfig{ ID: stFld.ID, @@ -69,7 +69,7 @@ func (s *SyncThing) FolderChange(f folder.FolderConfig) (string, error) { return "", err } - newDevice := config.DeviceConfiguration{ + newDevice := stconfig.DeviceConfiguration{ DeviceID: devID, Name: stClientID, Addresses: []string{"dynamic"}, @@ -95,22 +95,22 @@ func (s *SyncThing) FolderChange(f folder.FolderConfig) (string, error) { id = stClientID[0:15] + "_" + label } - folder := config.FolderConfiguration{ - ID: id, - Label: label, - RawPath: filepath.Join(s.conf.FileConf.ShareRootDir, f.ClientPath), + folder := stconfig.FolderConfiguration{ + ID: id, + Label: label, + Path: filepath.Join(s.conf.FileConf.ShareRootDir, f.ClientPath), } if s.conf.FileConf.SThgConf.RescanIntervalS > 0 { folder.RescanIntervalS = s.conf.FileConf.SThgConf.RescanIntervalS } - folder.Devices = append(folder.Devices, config.FolderDeviceConfiguration{ + folder.Devices = append(folder.Devices, stconfig.FolderDeviceConfiguration{ DeviceID: newDevice.DeviceID, }) found = false - var fld config.FolderConfiguration + var fld stconfig.FolderConfiguration for _, fld = range stCfg.Folders { if folder.ID == fld.ID { fld = folder @@ -155,8 +155,8 @@ func (s *SyncThing) FolderDelete(id string) error { } // FolderConfigGet Returns the configuration of a specific folder -func (s *SyncThing) FolderConfigGet(folderID string) (config.FolderConfiguration, error) { - fc := config.FolderConfiguration{} +func (s *SyncThing) FolderConfigGet(folderID string) (stconfig.FolderConfiguration, error) { + fc := stconfig.FolderConfiguration{} if folderID == "" { return fc, fmt.Errorf("folderID not set") } diff --git a/lib/webserver/server.go b/lib/webserver/server.go index a2fdf6f..85a2c40 100644 --- a/lib/webserver/server.go +++ b/lib/webserver/server.go @@ -30,6 +30,7 @@ type Server struct { mfolders *model.Folders sdks *crosssdk.SDKs log *logrus.Logger + sillyLog bool stop chan struct{} // signals intentional stop } @@ -37,7 +38,7 @@ const indexFilename = "index.html" const cookieMaxAge = "3600" // New creates an instance of Server -func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, logr *logrus.Logger) *Server { +func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, logr *logrus.Logger, sillyLog bool) *Server { // Setup logging for gin router if logr.Level == logrus.DebugLevel { @@ -66,6 +67,7 @@ func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, lo mfolders: mfolders, sdks: sdks, log: logr, + sillyLog: sillyLog, stop: make(chan struct{}), } @@ -83,7 +85,7 @@ func (s *Server) Serve() error { s.router.Use(s.middlewareCORS()) // Sessions manager - s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge) + s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge, s.sillyLog) // Create REST API s.api = apiv1.New(s.router, s.sessions, s.cfg, s.mfolders, s.sdks) diff --git a/lib/xdsconfig/builderconfig.go b/lib/xdsconfig/builderconfig.go index c64fe9c..6fc1814 100644 --- a/lib/xdsconfig/builderconfig.go +++ b/lib/xdsconfig/builderconfig.go @@ -28,10 +28,7 @@ func NewBuilderConfig(stID string) (BuilderConfig, error) { return b, nil } -// Copy makes a real copy of BuilderConfig -func (c *BuilderConfig) Copy(n BuilderConfig) { - // TODO -} +/*** Private ***/ func getLocalIP() (string, error) { addrs, err := net.InterfaceAddrs() diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go index 84e0778..0fc1346 100644 --- a/lib/xdsconfig/config.go +++ b/lib/xdsconfig/config.go @@ -13,10 +13,12 @@ import ( // Config parameters (json format) of /config command type Config struct { - Version string `json:"version"` - APIVersion string `json:"apiVersion"` - VersionGitTag string `json:"gitTag"` - Builder BuilderConfig `json:"builder"` + ServerUID string `json:"id"` + Version string `json:"version"` + APIVersion string `json:"apiVersion"` + VersionGitTag string `json:"gitTag"` + SupportedSharing map[string]bool `json:"supportedSharing"` + Builder BuilderConfig `json:"builder"` // Private (un-exported fields in REST GET /config route) Options Options `json:"-"` @@ -55,12 +57,19 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { dfltSTHomeDir = resDir } + uuid, err := ServerIDGet() + if err != nil { + return nil, err + } + // Define default configuration c := Config{ - Version: cliCtx.App.Metadata["version"].(string), - APIVersion: DefaultAPIVersion, - VersionGitTag: cliCtx.App.Metadata["git-tag"].(string), - Builder: BuilderConfig{}, + ServerUID: uuid, + Version: cliCtx.App.Metadata["version"].(string), + APIVersion: DefaultAPIVersion, + VersionGitTag: cliCtx.App.Metadata["git-tag"].(string), + Builder: BuilderConfig{}, + SupportedSharing: map[string]bool{ /*FIXME USE folder.TypePathMap*/ "PathMap": true}, Options: Options{ ConfigFile: cliCtx.GlobalString("config"), @@ -79,6 +88,8 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { Log: log, } + c.Log.Infoln("Server UUID: ", uuid) + // config file settings overwrite default config err = readGlobalConfig(&c, c.Options.ConfigFile) if err != nil { @@ -121,8 +132,9 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { return nil, fmt.Errorf("Cannot create logs dir: %v", err) } } - c.Log.Infoln("Logs file: ", c.Options.LogFile) - c.Log.Infoln("Logs directory: ", c.FileConf.LogsDir) + + c.Log.Infoln("Logs file: ", c.Options.LogFile) + c.Log.Infoln("Logs directory: ", c.FileConf.LogsDir) return &c, nil } diff --git a/lib/xdsconfig/data.go b/lib/xdsconfig/data.go new file mode 100644 index 0000000..65e0fc6 --- /dev/null +++ b/lib/xdsconfig/data.go @@ -0,0 +1,87 @@ +package xdsconfig + +import ( + "encoding/xml" + "fmt" + "os" + + common "github.com/iotbzh/xds-common/golib" + uuid "github.com/satori/go.uuid" + "github.com/syncthing/syncthing/lib/sync" +) + +// xmlServerData contains persistent data stored/loaded by server +type xmlServerData struct { + XMLName xml.Name `xml:"XDS-Server"` + Version string `xml:"version,attr"` + Data ServerData `xml:"server-data"` +} + +type ServerData struct { + ID string `xml:"id"` +} + +var sdMutex = sync.NewMutex() + +// ServerIDGet +func ServerIDGet() (string, error) { + var f string + var err error + + d := ServerData{} + if f, err = ServerDataFilenameGet(); err != nil { + return "", err + } + if err = serverDataRead(f, &d); err != nil || d.ID == "" { + // Create a new uuid when not found + d.ID = uuid.NewV1().String() + if err := serverDataWrite(f, d); err != nil { + return "", err + } + } + return d.ID, nil +} + +// serverDataRead reads data saved on disk +func serverDataRead(file string, data *ServerData) error { + if !common.Exists(file) { + return fmt.Errorf("No folder config file found (%s)", file) + } + + sdMutex.Lock() + defer sdMutex.Unlock() + + fd, err := os.Open(file) + defer fd.Close() + if err != nil { + return err + } + + xsd := xmlServerData{} + err = xml.NewDecoder(fd).Decode(&xsd) + if err == nil { + *data = xsd.Data + } + return err +} + +// serverDataWrite writes persistant data to disk +func serverDataWrite(file string, data ServerData) error { + sdMutex.Lock() + defer sdMutex.Unlock() + + fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + defer fd.Close() + if err != nil { + return err + } + + xsd := &xmlServerData{ + Version: "1", + Data: data, + } + + enc := xml.NewEncoder(fd) + enc.Indent("", " ") + return enc.Encode(xsd) +} diff --git a/lib/xdsconfig/fileconfig.go b/lib/xdsconfig/fileconfig.go index 2651caf..dafb034 100644 --- a/lib/xdsconfig/fileconfig.go +++ b/lib/xdsconfig/fileconfig.go @@ -16,6 +16,8 @@ const ( ConfigDir = ".xds-server" // GlobalConfigFilename Global config filename GlobalConfigFilename = "config.json" + // ServerDataFilename Server data filename + ServerDataFilename = "server-data.xml" // FoldersConfigFilename Folders config filename FoldersConfigFilename = "server-config_folders.xml" ) @@ -82,7 +84,7 @@ func readGlobalConfig(c *Config, confFile string) error { // No config file found return nil } - c.Log.Infof("Use config file: %s", *cFile) + c.Log.Infof("Use config file: %s", *cFile) // TODO move on viper package to support comments in JSON and also // bind with flags (command line options) @@ -146,11 +148,20 @@ func readGlobalConfig(c *Config, confFile string) error { return nil } -// FoldersConfigFilenameGet -func FoldersConfigFilenameGet() (string, error) { +func configFilenameGet(cfgFile string) (string, error) { usr, err := user.Current() if err != nil { return "", err } - return path.Join(usr.HomeDir, ConfigDir, FoldersConfigFilename), nil + return path.Join(usr.HomeDir, ConfigDir, cfgFile), nil +} + +// FoldersConfigFilenameGet +func FoldersConfigFilenameGet() (string, error) { + return configFilenameGet(FoldersConfigFilename) +} + +// ServerDataFilenameGet +func ServerDataFilenameGet() (string, error) { + return configFilenameGet(ServerDataFilename) } diff --git a/main.go b/main.go index c73d881..6089e74 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" "github.com/iotbzh/xds-server/lib/crosssdk" + "github.com/iotbzh/xds-server/lib/folder" "github.com/iotbzh/xds-server/lib/model" "github.com/iotbzh/xds-server/lib/syncthing" "github.com/iotbzh/xds-server/lib/webserver" @@ -40,17 +41,18 @@ var AppSubVersion = "unknown-dev" // Context holds the XDS server context type Context struct { - ProgName string - Cli *cli.Context - Config *xdsconfig.Config - Log *logrus.Logger - SThg *st.SyncThing - SThgCmd *exec.Cmd - SThgInotCmd *exec.Cmd - MFolders *model.Folders - SDKs *crosssdk.SDKs - WWWServer *webserver.Server - Exit chan os.Signal + ProgName string + Cli *cli.Context + Config *xdsconfig.Config + Log *logrus.Logger + LogLevelSilly bool + SThg *st.SyncThing + SThgCmd *exec.Cmd + SThgInotCmd *exec.Cmd + MFolders *model.Folders + SDKs *crosssdk.SDKs + WWWServer *webserver.Server + Exit chan os.Signal } // NewContext Create a new instance of XDS server @@ -70,12 +72,15 @@ func NewContext(cliCtx *cli.Context) *Context { } log.Formatter = &logrus.TextFormatter{} + sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY") + // Define default configuration ctx := Context{ - ProgName: cliCtx.App.Name, - Cli: cliCtx, - Log: log, - Exit: make(chan os.Signal, 1), + ProgName: cliCtx.App.Name, + Cli: cliCtx, + Log: log, + LogLevelSilly: (sillyLog && sillyVal == "1"), + Exit: make(chan os.Signal, 1), } // register handler on SIGTERM / exit @@ -147,7 +152,7 @@ func xdsApp(cliCtx *cli.Context) error { } ctx.Config.LogVerboseOut = fdLH - logPrint(ctx, "Logging file for HTTP requests: %s\n", logFileHTTPReq) + logPrint(ctx, "Logging file for HTTP requests: %s\n", logFileHTTPReq) } // Create syncthing instance when section "syncthing" is present in config.json @@ -193,6 +198,7 @@ func xdsApp(cliCtx *cli.Context) error { if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(ctx.SThg.MyID); err != nil { return cli.NewExitError(err, -4) } + ctx.Config.SupportedSharing[folder.TypeCloudSync] = true } // Init model folder @@ -210,7 +216,7 @@ func xdsApp(cliCtx *cli.Context) error { } // Create Web Server - ctx.WWWServer = webserver.New(ctx.Config, ctx.MFolders, ctx.SDKs, ctx.Log) + ctx.WWWServer = webserver.New(ctx.Config, ctx.MFolders, ctx.SDKs, ctx.Log, ctx.LogLevelSilly) // Run Web Server until exit requested (blocking call) if err = ctx.WWWServer.Serve(); err != nil { diff --git a/test/test_stdoutstderr.sh b/test/test_stdoutstderr.sh new file mode 100755 index 0000000..652c4f8 --- /dev/null +++ b/test/test_stdoutstderr.sh @@ -0,0 +1,7 @@ +#!/bin/bash +echo "1:STDOUT" +>&2 echo "2:STDERR" +echo "3:STDOUT" +>&2 echo "4:STDERR" +>&2 echo "5:STDERR" +echo "6:STDOUT" diff --git a/webapp/README.md b/webapp/README.md index acee846..015a70d 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -1,45 +1,5 @@ -XDS Dashboard -============= +# XDS Server Web page -This is the web application dashboard for Cross Development System. +This a basic web page that just gives basic instructions. -## 1. Prerequisites - -*nodejs* must be installed on your system and the below global node packages must be installed: - -> sudo npm install -g gulp-cli - -## 2. Installing dependencies - -Install dependencies by running the following command: - -> npm install - -`node_modules` and `typings` directories will be created during the install. - -## 3. Building the project - -Build the project by running the following command: - -> npm run clean & npm run build - -`dist` directory will be created during the build - -## 4. Starting the application - -Start the application by running the following command: - -> npm start - -The application will be displayed in the browser. - - -## TODO - -- Upgrade to angular 2.4.9 or 2.4.10 AND rxjs 5.2.0 -- Complete README + package.json -- Add prod mode and use update gulpfile tslint: "./tslint/prod.json" -- Generate a bundle minified file, using systemjs-builder or find a better way - http://stackoverflow.com/questions/35280582/angular2-too-many-file-requests-on-load -- Add SASS support - http://foundation.zurb.com/sites/docs/sass.html \ No newline at end of file +XDS Dashboard is now part of [xds-agent](https://github.com/iotbzh/xds-agent). diff --git a/webapp/assets/images/background_iot_bzh_light.png b/webapp/assets/images/background_iot_bzh_light.png new file mode 100644 index 0000000..a119d63 Binary files /dev/null and b/webapp/assets/images/background_iot_bzh_light.png differ diff --git a/webapp/assets/images/iot-graphx.jpg b/webapp/assets/images/iot-graphx.jpg deleted file mode 100644 index 74c640a..0000000 Binary files a/webapp/assets/images/iot-graphx.jpg and /dev/null differ diff --git a/webapp/assets/xds-agent-tarballs/.gitkeep b/webapp/assets/xds-agent-tarballs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/webapp/bs-config.json b/webapp/bs-config.json deleted file mode 100644 index 0041c6d..0000000 --- a/webapp/bs-config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "port": 8000, - "files": [ - "dist/**/*.{html,htm,css,js}" - ], - "server": { - "baseDir": "dist" - } -} \ No newline at end of file diff --git a/webapp/gulp.conf.js b/webapp/gulp.conf.js deleted file mode 100644 index 0529c02..0000000 --- a/webapp/gulp.conf.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; - -module.exports = { - prodMode: process.env.PRODUCTION || false, - outDir: "dist", - paths: { - tsSources: ["src/**/*.ts"], - srcDir: "src", - assets: ["assets/**"], - node_modules_libs: [ - 'core-js/client/shim.min.js', - 'reflect-metadata/Reflect.js', - 'rxjs-system-bundle/*.min.js', - 'socket.io-client/dist/socket.io*.js', - 'systemjs/dist/system-polyfills.js', - 'systemjs/dist/system.src.js', - 'zone.js/dist/**', - '@angular/**/bundles/**', - 'ngx-cookie/bundles/**', - 'ngx-bootstrap/bundles/**', - 'bootstrap/dist/**', - 'moment/*.min.js', - 'font-awesome-animation/dist/font-awesome-animation.min.css', - 'font-awesome/css/font-awesome.min.css', - 'font-awesome/fonts/**' - ] - }, - deploy: { - target_ip: 'ip', - username: "user", - //port: 6666, - dir: '/tmp/xds-server' - } -} \ No newline at end of file diff --git a/webapp/gulpfile.js b/webapp/gulpfile.js deleted file mode 100644 index 0226380..0000000 --- a/webapp/gulpfile.js +++ /dev/null @@ -1,123 +0,0 @@ -"use strict"; -//FIXME in VSC/eslint or add to typings declare function require(v: string): any; - -// FIXME: Rework based on -// https://github.com/iotbzh/app-framework-templates/blob/master/templates/hybrid-html5/gulpfile.js -// AND -// https://github.com/antonybudianto/angular-starter -// and/or -// https://github.com/smmorneau/tour-of-heroes/blob/master/gulpfile.js - -const gulp = require("gulp"), - gulpif = require('gulp-if'), - del = require("del"), - sourcemaps = require('gulp-sourcemaps'), - tsc = require("gulp-typescript"), - tsProject = tsc.createProject("tsconfig.json"), - tslint = require('gulp-tslint'), - gulpSequence = require('gulp-sequence'), - rsync = require('gulp-rsync'), - conf = require('./gulp.conf'); - - -var tslintJsonFile = "./tslint.json" -if (conf.prodMode) { - tslintJsonFile = "./tslint.prod.json" -} - - -/** - * Remove output directory. - */ -gulp.task('clean', (cb) => { - return del([conf.outDir], cb); -}); - -/** - * Lint all custom TypeScript files. - */ -gulp.task('tslint', function() { - return gulp.src(conf.paths.tsSources) - .pipe(tslint({ - formatter: 'verbose', - configuration: tslintJsonFile - })) - .pipe(tslint.report()); -}); - -/** - * Compile TypeScript sources and create sourcemaps in build directory. - */ -gulp.task("compile", ["tslint"], function() { - var tsResult = gulp.src(conf.paths.tsSources) - .pipe(sourcemaps.init()) - .pipe(tsProject()); - return tsResult.js - .pipe(sourcemaps.write(".", { sourceRoot: '/src' })) - .pipe(gulp.dest(conf.outDir)); -}); - -/** - * Copy all resources that are not TypeScript files into build directory. - */ -gulp.task("resources", function() { - return gulp.src(["src/**/*", "!**/*.ts"]) - .pipe(gulp.dest(conf.outDir)); -}); - -/** - * Copy all assets into build directory. - */ -gulp.task("assets", function() { - return gulp.src(conf.paths.assets) - .pipe(gulp.dest(conf.outDir + "/assets")); -}); - -/** - * Copy all required libraries into build directory. - */ -gulp.task("libs", function() { - return gulp.src(conf.paths.node_modules_libs, - { cwd: "node_modules/**" }) /* Glob required here. */ - .pipe(gulp.dest(conf.outDir + "/lib")); -}); - -/** - * Watch for changes in TypeScript, HTML and CSS files. - */ -gulp.task('watch', function () { - gulp.watch([conf.paths.tsSources], ['compile']).on('change', function (e) { - console.log('TypeScript file ' + e.path + ' has been changed. Compiling.'); - }); - gulp.watch(["src/**/*.html", "src/**/*.css"], ['resources']).on('change', function (e) { - console.log('Resource file ' + e.path + ' has been changed. Updating.'); - }); -}); - -/** - * Build the project. - */ -gulp.task("build", ['compile', 'resources', 'libs', 'assets'], function() { - console.log("Building the project ..."); -}); - -/** - * Deploy the project on another machine/container - */ -gulp.task('rsync', function () { - return gulp.src(conf.outDir) - .pipe(rsync({ - root: conf.outDir, - username: conf.deploy.username, - hostname: conf.deploy.target_ip, - port: conf.deploy.port || null, - archive: true, - recursive: true, - compress: true, - progress: false, - incremental: true, - destination: conf.deploy.dir - })); -}); - -gulp.task('deploy', gulpSequence('build', 'rsync')); \ No newline at end of file diff --git a/webapp/package.json b/webapp/package.json index b08ce0b..5acda9f 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -1,63 +1,17 @@ { - "name": "xds-server-dashboard", - "version": "1.0.0", - "description": "X (cross) Development System dashboard", - "scripts": { - "clean": "gulp clean", - "compile": "gulp compile", - "build": "gulp build", - "start": "concurrently --kill-others \"gulp watch\" \"lite-server\"" - }, - "repository": { - "type": "git", - "url": "https://github.com/iotbzh/xds-server" - }, - "author": "Sebastien Douheret [IoT.bzh]", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/iotbzh/xds-server/issues" - }, - "dependencies": { - "@angular/common": "2.4.4", - "@angular/compiler": "2.4.4", - "@angular/core": "2.4.4", - "@angular/forms": "2.4.4", - "@angular/http": "2.4.4", - "@angular/platform-browser": "2.4.4", - "@angular/platform-browser-dynamic": "2.4.4", - "@angular/router": "3.4.4", - "@angular/upgrade": "2.4.4", - "@types/core-js": "0.9.35", - "@types/node": "7.0.5", - "@types/socket.io-client": "^1.4.29", - "bootstrap": "^3.3.7", - "core-js": "^2.4.1", - "font-awesome": "^4.7.0", - "font-awesome-animation": "0.0.10", - "ngx-bootstrap": "1.6.6", - "ngx-cookie": "1.0.0", - "reflect-metadata": "^0.1.8", - "rxjs": "5.0.3", - "rxjs-system-bundle": "5.0.3", - "socket.io-client": "^1.7.3", - "socketio": "^1.0.0", - "systemjs": "0.20.0", - "zone.js": "^0.7.6" - }, - "devDependencies": { - "concurrently": "^3.1.0", - "del": "^2.2.0", - "gulp": "^3.9.1", - "gulp-if": "2.0.2", - "gulp-rsync": "0.0.7", - "gulp-sequence": "^0.4.6", - "gulp-sourcemaps": "^1.9.1", - "gulp-tslint": "^7.0.1", - "gulp-typescript": "^3.1.3", - "lite-server": "^2.2.2", - "ts-node": "^1.7.2", - "tslint": "^4.0.2", - "typescript": "^2.2.1", - "typings": "^2.0.0" + "name": "xds-server-minimal-dashboard", + "version": "1.0.0", + "description": "X (cross) Development System minimal dashboard", + "repository": { + "type": "git", + "url": "https://github.com/iotbzh/xds-server" + }, + "author": "Sebastien Douheret [IoT.bzh]", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/iotbzh/xds-server/issues" + }, + "dependencies": { + "font-awesome": "^4.7.0" + } } -} diff --git a/webapp/src/app/alert/alert.component.ts b/webapp/src/app/alert/alert.component.ts deleted file mode 100644 index 672d7bf..0000000 --- a/webapp/src/app/alert/alert.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component } from '@angular/core'; -import { Observable } from 'rxjs'; - -import {AlertService, IAlert} from '../services/alert.service'; - -@Component({ - selector: 'app-alert', - template: ` -
- -
-
-
- ` -}) - -export class AlertComponent { - - alerts$: Observable; - - constructor(private alertSvr: AlertService) { - this.alerts$ = this.alertSvr.alerts; - } - - onClose(al) { - this.alertSvr.del(al); - } - -} diff --git a/webapp/src/app/app.component.css b/webapp/src/app/app.component.css deleted file mode 100644 index a47ad13..0000000 --- a/webapp/src/app/app.component.css +++ /dev/null @@ -1,31 +0,0 @@ -.navbar { - background-color: whitesmoke; -} - -.navbar-brand { - font-size: x-large; - font-variant: small-caps; - color: #5a28a1; -} - -a.navbar-brand { - margin-top: 5px; -} - - -.navbar-nav ul li a { - color: #fff; -} - -.menu-text { - color: #fff; -} - -#logo-iot { - padding: 0 2px; - height: 60px; -} - -li>a { - color:#5a28a1; -} diff --git a/webapp/src/app/app.component.html b/webapp/src/app/app.component.html deleted file mode 100644 index a889b12..0000000 --- a/webapp/src/app/app.component.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - -
- -
diff --git a/webapp/src/app/app.component.ts b/webapp/src/app/app.component.ts deleted file mode 100644 index 40cfb24..0000000 --- a/webapp/src/app/app.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Component, OnInit, OnDestroy } from "@angular/core"; -import { Router } from '@angular/router'; -//TODO import {TranslateService} from "ng2-translate"; - -@Component({ - selector: 'app', - templateUrl: './app/app.component.html', - styleUrls: ['./app/app.component.css'] -}) - -export class AppComponent implements OnInit, OnDestroy { - - isCollapsed: boolean = true; - - private defaultLanguage: string = 'en'; - - // I initialize the app component. - //TODO constructor(private translate: TranslateService) { - constructor(public router: Router) { - } - - ngOnInit() { - - /* TODO - this.translate.addLangs(["en", "fr"]); - this.translate.setDefaultLang(this.defaultLanguage); - - let browserLang = this.translate.getBrowserLang(); - this.translate.use(browserLang.match(/en|fr/) ? browserLang : this.defaultLanguage); - */ - } - - ngOnDestroy(): void { - } - - -} diff --git a/webapp/src/app/app.module.ts b/webapp/src/app/app.module.ts deleted file mode 100644 index 10ff7a4..0000000 --- a/webapp/src/app/app.module.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { HttpModule } from "@angular/http"; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { CookieModule } from 'ngx-cookie'; - -// Import bootstrap -import { AlertModule } from 'ngx-bootstrap/alert'; -import { ModalModule } from 'ngx-bootstrap/modal'; -import { AccordionModule } from 'ngx-bootstrap/accordion'; -import { CarouselModule } from 'ngx-bootstrap/carousel'; -import { PopoverModule } from 'ngx-bootstrap/popover'; -import { CollapseModule } from 'ngx-bootstrap/collapse'; -import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; - -// Import the application components and services. -import { Routing, AppRoutingProviders } from './app.routing'; -import { AppComponent } from "./app.component"; -import { AlertComponent } from './alert/alert.component'; -import { ConfigComponent } from "./config/config.component"; -import { DlXdsAgentComponent, CapitalizePipe } from "./config/downloadXdsAgent.component"; -import { ProjectCardComponent } from "./projects/projectCard.component"; -import { ProjectReadableTypePipe } from "./projects/projectCard.component"; -import { ProjectsListAccordionComponent } from "./projects/projectsListAccordion.component"; -import { ProjectAddModalComponent} from "./projects/projectAddModal.component"; -import { SdkCardComponent } from "./sdks/sdkCard.component"; -import { SdksListAccordionComponent } from "./sdks/sdksListAccordion.component"; -import { SdkSelectDropdownComponent } from "./sdks/sdkSelectDropdown.component"; -import { SdkAddModalComponent} from "./sdks/sdkAddModal.component"; - -import { HomeComponent } from "./home/home.component"; -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"; - - - -@NgModule({ - imports: [ - BrowserModule, - HttpModule, - FormsModule, - ReactiveFormsModule, - Routing, - CookieModule.forRoot(), - AlertModule.forRoot(), - ModalModule.forRoot(), - AccordionModule.forRoot(), - CarouselModule.forRoot(), - PopoverModule.forRoot(), - CollapseModule.forRoot(), - BsDropdownModule.forRoot(), - ], - declarations: [ - AppComponent, - AlertComponent, - HomeComponent, - BuildComponent, - DevelComponent, - DeployComponent, - ConfigComponent, - DlXdsAgentComponent, - CapitalizePipe, - ProjectCardComponent, - ProjectReadableTypePipe, - ProjectsListAccordionComponent, - ProjectAddModalComponent, - SdkCardComponent, - SdksListAccordionComponent, - SdkSelectDropdownComponent, - SdkAddModalComponent, - ], - providers: [ - AppRoutingProviders, - { - provide: Window, - useValue: window - }, - XDSServerService, - XDSAgentService, - ConfigService, - SyncthingService, - AlertService, - UtilsService, - SdkService, - ], - bootstrap: [AppComponent] -}) -export class AppModule { -} diff --git a/webapp/src/app/app.routing.ts b/webapp/src/app/app.routing.ts deleted file mode 100644 index f0d808f..0000000 --- a/webapp/src/app/app.routing.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Routes, RouterModule} from "@angular/router"; -import {ModuleWithProviders} from "@angular/core"; -import {ConfigComponent} from "./config/config.component"; -import {HomeComponent} from "./home/home.component"; -import {DevelComponent} from "./devel/devel.component"; - - -const appRoutes: Routes = [ - {path: '', redirectTo: 'home', pathMatch: 'full'}, - - {path: 'config', component: ConfigComponent, data: {title: 'Config'}}, - {path: 'home', component: HomeComponent, data: {title: 'Home'}}, - {path: 'devel', component: DevelComponent, data: {title: 'Build & Deploy'}} -]; - -export const AppRoutingProviders: any[] = []; -export const Routing: ModuleWithProviders = RouterModule.forRoot(appRoutes, { - useHash: true -}); diff --git a/webapp/src/app/config/config.component.css b/webapp/src/app/config/config.component.css deleted file mode 100644 index 6412f9a..0000000 --- a/webapp/src/app/config/config.component.css +++ /dev/null @@ -1,35 +0,0 @@ -.fa-big { - font-size: 20px; - font-weight: bold; -} - -.fa-size-x2 { - font-size: 20px; -} - -h2 { - font-family: sans-serif; - font-variant: small-caps; - font-size: x-large; -} - -th span { - font-weight: 100; -} - -th label { - font-weight: 100; - margin-bottom: 0; -} - -tr.info>th { - vertical-align: middle; -} - -tr.info>td { - vertical-align: middle; -} - -.panel-heading { - background: aliceblue; -} diff --git a/webapp/src/app/config/config.component.html b/webapp/src/app/config/config.component.html deleted file mode 100644 index c36ba02..0000000 --- a/webapp/src/app/config/config.component.html +++ /dev/null @@ -1,106 +0,0 @@ -
-
-

- Global Configuration -
- - - -
-

-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
- -
- -
- -
-
-
-
-
- -
-
-

- Cross SDKs -
- - - -
-

-
-
-
- -
-
-
- -
-
-

- Projects -
- - - -
-

-
-
-
- -
-
-
- - - - - - - - -
- {{config$ | async | json}} -
diff --git a/webapp/src/app/config/config.component.ts b/webapp/src/app/config/config.component.ts deleted file mode 100644 index b107e81..0000000 --- a/webapp/src/app/config/config.component.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Component, ViewChild, OnInit } from "@angular/core"; -import { Observable } from 'rxjs/Observable'; -import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; -import { CollapseModule } from 'ngx-bootstrap/collapse'; - -import { ConfigService, IConfig, IxdsAgentPackage } 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"; -import { ProjectAddModalComponent } from "../projects/projectAddModal.component"; -import { SdkAddModalComponent } from "../sdks/sdkAddModal.component"; - -@Component({ - templateUrl: './app/config/config.component.html', - styleUrls: ['./app/config/config.component.css'] -}) - -// Inspired from https://embed.plnkr.co/jgDTXknPzAaqcg9XA9zq/ -// and from http://plnkr.co/edit/vCdjZM?p=preview - -export class ConfigComponent implements OnInit { - @ViewChild('childProjectModal') childProjectModal: ProjectAddModalComponent; - @ViewChild('childSdkModal') childSdkModal: SdkAddModalComponent; - - config$: Observable; - sdks$: Observable; - serverStatus$: Observable; - agentStatus$: Observable; - localSTStatus$: Observable; - - curProj: number; - userEditedLabel: boolean = false; - xdsAgentPackages: IxdsAgentPackage[] = []; - - gConfigIsCollapsed: boolean = true; - sdksIsCollapsed: boolean = true; - projectsIsCollapsed: boolean = false; - - // TODO replace by reactive FormControl + add validation - syncToolUrl: string; - xdsAgentUrl: string; - xdsAgentRetry: string; - projectsRootDir: string; // FIXME: should be remove when projectAddModal will always return full path - showApplyBtn = { // Used to show/hide Apply buttons - "retry": false, - "rootDir": false, - }; - - constructor( - private configSvr: ConfigService, - private xdsServerSvr: XDSServerService, - private xdsAgentSvr: XDSAgentService, - private stSvr: SyncthingService, - private sdkSvr: SdkService, - private alert: AlertService, - ) { - } - - ngOnInit() { - this.config$ = this.configSvr.conf; - this.sdks$ = this.sdkSvr.Sdks$; - this.serverStatus$ = this.xdsServerSvr.Status$; - this.agentStatus$ = this.xdsAgentSvr.Status$; - this.localSTStatus$ = this.stSvr.Status$; - - // Bind xdsAgentUrl to baseURL - this.config$.subscribe(cfg => { - this.syncToolUrl = cfg.localSThg.URL; - this.xdsAgentUrl = cfg.xdsAgent.URL; - this.xdsAgentRetry = String(cfg.xdsAgent.retry); - this.projectsRootDir = cfg.projectsRootDir; - this.xdsAgentPackages = cfg.xdsAgentPackages; - }); - - } - - submitGlobConf(field: string) { - switch (field) { - case "retry": - let re = new RegExp('^[0-9]+$'); - let rr = parseInt(this.xdsAgentRetry, 10); - if (re.test(this.xdsAgentRetry) && rr >= 0) { - this.configSvr.xdsAgentRetry = rr; - } else { - this.alert.warning("Not a valid number", true); - } - break; - case "rootDir": - this.configSvr.projectsRootDir = this.projectsRootDir; - break; - default: - return; - } - this.showApplyBtn[field] = false; - } - - xdsAgentRestartConn() { - let aUrl = this.xdsAgentUrl; - this.configSvr.syncToolURL = this.syncToolUrl; - this.configSvr.xdsAgentUrl = aUrl; - this.configSvr.loadProjects(); - } - -} diff --git a/webapp/src/app/config/downloadXdsAgent.component.ts b/webapp/src/app/config/downloadXdsAgent.component.ts deleted file mode 100644 index b35a17f..0000000 --- a/webapp/src/app/config/downloadXdsAgent.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Component, Input, Pipe, PipeTransform } from '@angular/core'; - -import { IxdsAgentPackage } from "../services/config.service"; - -@Component({ - selector: 'dl-xds-agent', - /* XXX - cleanup - template: ` - - - `, -*/ - template: ` - - - `, - styles: [` - .fa-size-x2 { - font-size: 20px; - } - `] -}) - -export class DlXdsAgentComponent { - - @Input() packageUrls: IxdsAgentPackage[]; - - public url_OS_Linux = "https://en.opensuse.org/LinuxAutomotive#Installation_AGL_XDS"; - public url_OS_Other = "https://github.com/iotbzh/xds-agent#how-to-install-on-other-platform"; -} - -@Pipe({ - name: 'capitalize' -}) -export class CapitalizePipe implements PipeTransform { - transform(value: string): string { - if (value) { - return value.charAt(0).toUpperCase() + value.slice(1); - } - return value; - } -} diff --git a/webapp/src/app/devel/build/build.component.css b/webapp/src/app/devel/build/build.component.css deleted file mode 100644 index 695a89b..0000000 --- a/webapp/src/app/devel/build/build.component.css +++ /dev/null @@ -1,54 +0,0 @@ -.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: 80%; - 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; -} - -.table-in-accordion>tbody>tr>th { - width: 30% -} - -.btn-large { - width: 10em; -} - -.fa-big { - font-size: 18px; - font-weight: bold; -} - -.textarea-scroll { - width: 100%; - overflow-y: scroll; -} - -h2 { - font-family: sans-serif; - font-variant: small-caps; - font-size: x-large; -} - -.panel-heading { - background: aliceblue; -} diff --git a/webapp/src/app/devel/build/build.component.html b/webapp/src/app/devel/build/build.component.html deleted file mode 100644 index 2bcd2c7..0000000 --- a/webapp/src/app/devel/build/build.component.html +++ /dev/null @@ -1,115 +0,0 @@ -
-
-

- Build -
- -
-

-
-
-
-
- - - - - - - - - - - - - - - - - - -
Cross SDK - - -
Project root path
Sub-path
- - -
- Advanced Settings - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Clean Command
Pre-Build Command
Build Command
Populate Command
Env variables
Args variables
-
-
-
-
-
-
-
- - - - - - -
-
-
-
- -
-
-
-
-
- -
-
- {{ cmdInfo }} -
-
-
-
- -
-
-
-
- -
-
-
-
-
diff --git a/webapp/src/app/devel/build/build.component.ts b/webapp/src/app/devel/build/build.component.ts deleted file mode 100644 index 48a5824..0000000 --- a/webapp/src/app/devel/build/build.component.ts +++ /dev/null @@ -1,223 +0,0 @@ -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 "../../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: 'panel-build', - moduleId: module.id, - templateUrl: './build.component.html', - styleUrls: ['./build.component.css'] -}) - -export class BuildComponent implements OnInit, AfterViewChecked { - @ViewChild('scrollOutput') private scrollContainer: ElementRef; - - @Input() curProject: IProject; - - public buildForm: FormGroup; - public subpathCtrl = new FormControl("", Validators.required); - public debugEnable: boolean = false; - public buildIsCollapsed: boolean = false; - public cmdOutput: string; - public cmdInfo: string; - - private startTime: Map = new Map(); - - constructor(private configSvr: ConfigService, - private xdsSvr: XDSServerService, - private fb: FormBuilder, - private alertSvr: AlertService, - private sdkSvr: SdkService, - private cookie: CookieService, - ) { - this.cmdOutput = ""; - this.cmdInfo = ""; // TODO: to be remove (only for debug) - this.buildForm = fb.group({ - subpath: this.subpathCtrl, - cmdClean: ["", Validators.nullValidator], - cmdPrebuild: ["", Validators.nullValidator], - cmdBuild: ["", Validators.nullValidator], - cmdPopulate: ["", Validators.nullValidator], - cmdArgs: ["", Validators.nullValidator], - envVars: ["", Validators.nullValidator], - }); - } - - ngOnInit() { - // Set default settings - // TODO save & restore values from cookies - this.buildForm.patchValue({ - subpath: "", - cmdClean: "rm -rf build", - cmdPrebuild: "mkdir -p build && cd build && cmake ..", - cmdBuild: "cd build && make", - cmdPopulate: "cd build && make remote-target-populate", - cmdArgs: "", - envVars: "", - }); - - // Command output data tunneling - this.xdsSvr.CmdOutput$.subscribe(data => { - this.cmdOutput += data.stdout; - this.cmdOutput += data.stderr; - }); - - // Command 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); - } - - if (exit && exit.code !== 0) { - this.cmdOutput += "--- Command exited with code " + exit.code + " ---\n\n"; - } - }); - - this._scrollToBottom(); - - // only use for debug - this.debugEnable = (this.cookie.get("debug_build") === "1"); - } - - ngAfterViewChecked() { - this._scrollToBottom(); - } - - reset() { - this.cmdOutput = ''; - } - - clean() { - this._exec( - this.buildForm.value.cmdClean, - this.buildForm.value.subpath, - [], - this.buildForm.value.envVars); - } - - preBuild() { - this._exec( - this.buildForm.value.cmdPrebuild, - this.buildForm.value.subpath, - [], - this.buildForm.value.envVars); - } - - build() { - this._exec( - this.buildForm.value.cmdBuild, - this.buildForm.value.subpath, - [], - this.buildForm.value.envVars - ); - } - - populate() { - this._exec( - this.buildForm.value.cmdPopulate, - 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); - } - - let prjID = this.curProject.id; - - this.cmdOutput += this._outputHeader(); - - let sdkid = this.sdkSvr.getCurrentId(); - - 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, sdkid, argsArr, envArr) - .subscribe(res => { - this.startTime.set(String(res.cmdID), t0); - }, - err => { - this.cmdInfo = 'Last command duration: ' + this._computeTime(t0); - this.alertSvr.error('ERROR: ' + err); - }); - } - - private _scrollToBottom(): void { - try { - this.scrollContainer.nativeElement.scrollTop = this.scrollContainer.nativeElement.scrollHeight; - } catch (err) { } - } - - private _computeTime(t0: number, t1?: number): string { - let enlap = Math.round((t1 || performance.now()) - t0); - if (enlap < 1000.0) { - return enlap.toFixed(2) + ' ms'; - } else { - return (enlap / 1000.0).toFixed(3) + ' seconds'; - } - } - - private _outputHeader(): string { - return "--- " + new Date().toString() + " ---\n"; - } - - private _outputFooter(): string { - return "\n"; - } -} diff --git a/webapp/src/app/devel/deploy/deploy.component.css b/webapp/src/app/devel/deploy/deploy.component.css deleted file mode 100644 index c1b39d8..0000000 --- a/webapp/src/app/devel/deploy/deploy.component.css +++ /dev/null @@ -1,45 +0,0 @@ -.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 deleted file mode 100644 index 7a15fa6..0000000 --- a/webapp/src/app/devel/deploy/deploy.component.html +++ /dev/null @@ -1,31 +0,0 @@ -
-
-

Deployment

-
-
-
-
- - - - - - - - - - - -
Board IP
File to deploy
-
-
-
-
- -
-
-
-
- -
-
\ 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 deleted file mode 100644 index e51b7f2..0000000 --- a/webapp/src/app/devel/deploy/deploy.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -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.pathClient) { - this.deployForm.patchValue({ wgtFile: this.curProject.pathClient }); - } - } - - 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 = 'ERROR while deploying "' + this.deployForm.value.wgtFile + '"
'; - msg += err; - msg += '
'; - this.alert.error(msg); - }); - } -} diff --git a/webapp/src/app/devel/devel.component.css b/webapp/src/app/devel/devel.component.css deleted file mode 100644 index 4b03dcb..0000000 --- a/webapp/src/app/devel/devel.component.css +++ /dev/null @@ -1,19 +0,0 @@ -.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; -} - -a.dropdown-item.disabled { - pointer-events:none; - opacity:0.4; -} diff --git a/webapp/src/app/devel/devel.component.html b/webapp/src/app/devel/devel.component.html deleted file mode 100644 index 8e71c58..0000000 --- a/webapp/src/app/devel/devel.component.html +++ /dev/null @@ -1,40 +0,0 @@ -
-
- - - - - - - -
Project -
- - -
- - No project detected, please create first a project using the configuration page. - -
-
-
- -
- -
- -
- -
diff --git a/webapp/src/app/devel/devel.component.ts b/webapp/src/app/devel/devel.component.ts deleted file mode 100644 index f40f25f..0000000 --- a/webapp/src/app/devel/devel.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -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; - - constructor(private configSvr: ConfigService) { - } - - ngOnInit() { - this.config$ = this.configSvr.conf; - this.config$.subscribe((cfg) => { - // Select project if no one is selected or no project exists - if (this.curPrj && "id" in this.curPrj) { - this.curPrj = cfg.projects.find(p => p.id === this.curPrj.id) || cfg.projects[0]; - } else if (this.curPrj == null && "projects" in cfg) { - this.curPrj = cfg.projects[0]; - } else { - this.curPrj = null; - } - }); - } -} diff --git a/webapp/src/app/home/home.component.ts b/webapp/src/app/home/home.component.ts deleted file mode 100644 index 0e3c995..0000000 --- a/webapp/src/app/home/home.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -export interface ISlide { - img?: string; - imgAlt?: string; - hText?: string; - hHtml?: string; - text?: string; - html?: string; - btn?: string; - btnHref?: string; -} - -@Component({ - selector: 'home', - moduleId: module.id, - template: ` - - -
- - - - - - -
- ` -}) - -export class HomeComponent { - - public carInterval: number = 4000; - - // FIXME SEB - Add more slides and info - public slides: ISlide[] = [ - { - img: 'assets/images/iot-graphx.jpg', - imgAlt: "iot graphx image", - hText: "Welcome to XDS Dashboard !", - text: "X(cross) Development System allows developers to easily cross-compile applications.", - }, - { - img: 'assets/images/iot-graphx.jpg', - imgAlt: "iot graphx image", - hText: "Create, Build, Deploy, Enjoy !", - }, - { - img: 'assets/images/iot-graphx.jpg', - imgAlt: "iot graphx image", - hHtml: '

To Start: click on icon and add new folder

', - } - ]; - - constructor() { } -} \ No newline at end of file diff --git a/webapp/src/app/main.ts b/webapp/src/app/main.ts deleted file mode 100644 index 1f68ccc..0000000 --- a/webapp/src/app/main.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; -import {AppModule} from './app.module'; - -const platform = platformBrowserDynamic(); - -platform.bootstrapModule(AppModule); \ No newline at end of file diff --git a/webapp/src/app/projects/projectAddModal.component.css b/webapp/src/app/projects/projectAddModal.component.css deleted file mode 100644 index 77f73a5..0000000 --- a/webapp/src/app/projects/projectAddModal.component.css +++ /dev/null @@ -1,24 +0,0 @@ -.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; -} - -tr>th { - vertical-align: middle; -} - -tr>td { - vertical-align: middle; -} - -th label { - margin-bottom: 0; -} - -td input { - width: 100%; -} diff --git a/webapp/src/app/projects/projectAddModal.component.html b/webapp/src/app/projects/projectAddModal.component.html deleted file mode 100644 index dc84985..0000000 --- a/webapp/src/app/projects/projectAddModal.component.html +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/webapp/src/app/projects/projectAddModal.component.ts b/webapp/src/app/projects/projectAddModal.component.ts deleted file mode 100644 index 7ef5b5e..0000000 --- a/webapp/src/app/projects/projectAddModal.component.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Component, Input, ViewChild, OnInit } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { ModalDirective } from 'ngx-bootstrap/modal'; -import { FormControl, FormGroup, Validators, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms'; - -// Import RxJs required methods -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/filter'; -import 'rxjs/add/operator/debounceTime'; - -import { AlertService, IAlert } from "../services/alert.service"; -import { - ConfigService, IConfig, IProject, ProjectType, ProjectTypes, - IxdsAgentPackage -} from "../services/config.service"; - - -@Component({ - selector: 'project-add-modal', - templateUrl: './app/projects/projectAddModal.component.html', - styleUrls: ['./app/projects/projectAddModal.component.css'] -}) -export class ProjectAddModalComponent { - @ViewChild('childProjectModal') public childProjectModal: ModalDirective; - @Input() title?: string; - - config$: Observable; - - cancelAction: boolean = false; - userEditedLabel: boolean = false; - projectTypes = ProjectTypes; - - addProjectForm: FormGroup; - typeCtrl: FormControl; - pathCliCtrl: FormControl; - pathSvrCtrl: FormControl; - - constructor( - private alert: AlertService, - private configSvr: ConfigService, - private fb: FormBuilder - ) { - // Define types (first one is special/placeholder) - this.projectTypes.unshift({ value: -1, display: "--Select a type--" }); - - this.typeCtrl = new FormControl(this.projectTypes[0].value, Validators.pattern("[0-9]+")); - this.pathCliCtrl = new FormControl("", Validators.required); - this.pathSvrCtrl = new FormControl({ value: "", disabled: true }, [Validators.required, Validators.minLength(1)]); - - this.addProjectForm = fb.group({ - type: this.typeCtrl, - pathCli: this.pathCliCtrl, - pathSvr: this.pathSvrCtrl, - label: ["", Validators.nullValidator], - }); - } - - ngOnInit() { - this.config$ = this.configSvr.conf; - - // Auto create label name - this.pathCliCtrl.valueChanges - .debounceTime(100) - .filter(n => n) - .map(n => { - let last = n.split('/'); - let nm = n; - if (last.length > 0) { - nm = last.pop(); - if (nm === "" && last.length > 0) { - nm = last.pop(); - } - } - return "Project_" + nm; - }) - .subscribe(value => { - if (value && !this.userEditedLabel) { - this.addProjectForm.patchValue({ label: value }); - } - }); - - // Handle disabling of Server path - this.typeCtrl.valueChanges - .debounceTime(500) - .subscribe(valType => { - let dis = (valType === String(ProjectType.SYNCTHING)); - this.pathSvrCtrl.reset({ value: "", disabled: dis }); - }); - } - - show() { - this.cancelAction = false; - this.childProjectModal.show(); - } - - hide() { - this.childProjectModal.hide(); - } - - onKeyLabel(event: any) { - this.userEditedLabel = (this.addProjectForm.value.label !== ""); - } - - /* FIXME: change input to file type - - - onChangeLocalProject(e) { - if e.target.files.length < 1 { - console.log('NO files'); - } - let dir = e.target.files[0].webkitRelativePath; - console.log("files: " + dir); - let u = URL.createObjectURL(e.target.files[0]); - } - */ - onChangeLocalProject(e) { - } - - onSubmit() { - if (this.cancelAction) { - return; - } - - let formVal = this.addProjectForm.value; - - let type = formVal['type'].value; - let numType = Number(formVal['type']); - this.configSvr.addProject({ - label: formVal['label'], - pathClient: formVal['pathCli'], - pathServer: formVal['pathSvr'], - type: numType, - // FIXME: allow to set defaultSdkID from New Project config panel - }) - .subscribe(prj => { - this.alert.info("Project " + prj.label + " successfully created."); - this.hide(); - - // Reset Value for the next creation - this.addProjectForm.reset(); - let selectedType = this.projectTypes[0].value; - this.addProjectForm.patchValue({ type: selectedType }); - - }, - err => { - this.alert.error("Configuration ERROR: " + err, 60); - this.hide(); - }); - } - -} diff --git a/webapp/src/app/projects/projectCard.component.ts b/webapp/src/app/projects/projectCard.component.ts deleted file mode 100644 index a7ca9a3..0000000 --- a/webapp/src/app/projects/projectCard.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Component, Input, Pipe, PipeTransform } from '@angular/core'; -import { ConfigService, IProject, ProjectType } from "../services/config.service"; -import { AlertService } from "../services/alert.service"; - -@Component({ - selector: 'project-card', - template: ` -
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - -
 Project ID{{ project.id }}
 Sharing type{{ project.type | readableType }}
 Local path{{ project.pathClient }}
 Server path{{ project.pathServer }}
 Status{{ project.status }} - {{ project.isInSync ? "Up to Date" : "Out of Sync"}} - -
- `, - styleUrls: ['./app/config/config.component.css'] -}) - -export class ProjectCardComponent { - - @Input() project: IProject; - - constructor( - private alert: AlertService, - private configSvr: ConfigService - ) { - } - - delete(prj: IProject) { - this.configSvr.deleteProject(prj) - .subscribe(res => { - }, err => { - this.alert.error("Delete local ERROR: " + err); - }); - } - - sync(prj: IProject) { - this.configSvr.syncProject(prj) - .subscribe(res => { - }, err => { - this.alert.error("ERROR: " + err); - }); - } - -} - -// Remove APPS. prefix if translate has failed -@Pipe({ - name: 'readableType' -}) - -export class ProjectReadableTypePipe implements PipeTransform { - transform(type: ProjectType): string { - switch (type) { - case ProjectType.NATIVE_PATHMAP: return "Native (path mapping)"; - case ProjectType.SYNCTHING: return "Cloud (Syncthing)"; - default: return String(type); - } - } -} diff --git a/webapp/src/app/projects/projectsListAccordion.component.ts b/webapp/src/app/projects/projectsListAccordion.component.ts deleted file mode 100644 index 6e697f4..0000000 --- a/webapp/src/app/projects/projectsListAccordion.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, Input } from "@angular/core"; - -import { IProject } from "../services/config.service"; - -@Component({ - selector: 'projects-list-accordion', - template: ` - - - -
- {{ prj.label }} -
- - - -
-
- -
-
- ` -}) -export class ProjectsListAccordionComponent { - - @Input() projects: IProject[]; - -} - - diff --git a/webapp/src/app/sdks/sdkAddModal.component.html b/webapp/src/app/sdks/sdkAddModal.component.html deleted file mode 100644 index 2c07fca..0000000 --- a/webapp/src/app/sdks/sdkAddModal.component.html +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/webapp/src/app/sdks/sdkAddModal.component.ts b/webapp/src/app/sdks/sdkAddModal.component.ts deleted file mode 100644 index b6c8eb2..0000000 --- a/webapp/src/app/sdks/sdkAddModal.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, Input, ViewChild } from '@angular/core'; -import { ModalDirective } from 'ngx-bootstrap/modal'; - -@Component({ - selector: 'sdk-add-modal', - templateUrl: './app/sdks/sdkAddModal.component.html', -}) -export class SdkAddModalComponent { - @ViewChild('sdkChildModal') public sdkChildModal: ModalDirective; - - @Input() title?: string; - - // TODO - constructor() { - } - - show() { - this.sdkChildModal.show(); - } - - hide() { - this.sdkChildModal.hide(); - } -} diff --git a/webapp/src/app/sdks/sdkCard.component.ts b/webapp/src/app/sdks/sdkCard.component.ts deleted file mode 100644 index 579d224..0000000 --- a/webapp/src/app/sdks/sdkCard.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { ISdk } from "../services/sdk.service"; - -@Component({ - selector: 'sdk-card', - template: ` -
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - -
 Profile{{ sdk.profile }}
 Architecture{{ sdk.arch }}
 Version{{ sdk.version }}
 Sdk path{{ sdk.path}}
- `, - 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 deleted file mode 100644 index a2fe37a..0000000 --- a/webapp/src/app/sdks/sdkSelectDropdown.component.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Component, Input } from "@angular/core"; - -import { ISdk, SdkService } from "../services/sdk.service"; - -@Component({ - selector: 'sdk-select-dropdown', - template: ` -
- - -
- ` -}) -export class SdkSelectDropdownComponent { - - // FIXME investigate to understand why not working with sdks as input - // - //@Input() sdks: ISdk[]; - sdks: ISdk[]; - - curSdk: ISdk; - - constructor(private sdkSvr: SdkService) { } - - ngOnInit() { - this.curSdk = this.sdkSvr.getCurrent(); - this.sdkSvr.Sdks$.subscribe((s) => { - if (s) { - this.sdks = s; - if (this.curSdk === null || s.indexOf(this.curSdk) === -1) { - this.sdkSvr.setCurrent(this.curSdk = s.length ? s[0] : null); - } - } - }); - } - - 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 deleted file mode 100644 index 9d5f7e9..0000000 --- a/webapp/src/app/sdks/sdksListAccordion.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, Input } from "@angular/core"; - -import { ISdk } from "../services/sdk.service"; - -@Component({ - selector: 'sdks-list-accordion', - template: ` - - -
- {{ sdk.name }} - -
- -
-
- ` -}) -export class SdksListAccordionComponent { - - @Input() sdks: ISdk[]; - -} - - diff --git a/webapp/src/app/services/alert.service.ts b/webapp/src/app/services/alert.service.ts deleted file mode 100644 index c3cae7a..0000000 --- a/webapp/src/app/services/alert.service.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Injectable, SecurityContext } from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; - - -export type AlertType = "danger" | "warning" | "info" | "success"; - -export interface IAlert { - type: AlertType; - msg: string; - show?: boolean; - dismissible?: boolean; - dismissTimeout?: number; // close alert after this time (in seconds) - id?: number; -} - -@Injectable() -export class AlertService { - public alerts: Observable; - - private _alerts: IAlert[]; - private alertsSubject = >new Subject(); - private uid = 0; - private defaultDissmissTmo = 5; // in seconds - - constructor(private sanitizer: DomSanitizer) { - this.alerts = this.alertsSubject.asObservable(); - this._alerts = []; - this.uid = 0; - } - - public error(msg: string, dismissTime?: number) { - this.add({ - type: "danger", msg: msg, dismissible: true, dismissTimeout: dismissTime - }); - } - - public warning(msg: string, dismissible?: boolean) { - this.add({ type: "warning", msg: msg, dismissible: true, dismissTimeout: (dismissible ? this.defaultDissmissTmo : 0) }); - } - - public info(msg: string) { - this.add({ type: "info", msg: msg, dismissible: true, dismissTimeout: this.defaultDissmissTmo }); - } - - public add(al: IAlert) { - this._alerts.push({ - show: true, - type: al.type, - msg: this.sanitizer.sanitize(SecurityContext.HTML, al.msg), - dismissible: al.dismissible || true, - dismissTimeout: (al.dismissTimeout * 1000) || 0, - id: this.uid, - }); - this.uid += 1; - this.alertsSubject.next(this._alerts); - } - - public del(al: IAlert) { - let idx = this._alerts.findIndex((a) => a.id === al.id); - if (idx > -1) { - this._alerts.splice(idx, 1); - } - } -} diff --git a/webapp/src/app/services/config.service.ts b/webapp/src/app/services/config.service.ts deleted file mode 100644 index 6cab73c..0000000 --- a/webapp/src/app/services/config.service.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { Injectable, OnInit } from '@angular/core'; -import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; -import { Location } from '@angular/common'; -import { CookieService } from 'ngx-cookie'; -import { Observable } from 'rxjs/Observable'; -import { Subscriber } from 'rxjs/Subscriber'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -// Import RxJs required methods -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/throw'; -import 'rxjs/add/operator/mergeMap'; - - -import { XDSServerService, IXDSFolderConfig } 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_PATHMAP = 1, - SYNCTHING = 2 -} - -export var ProjectTypes = [ - { value: ProjectType.NATIVE_PATHMAP, display: "Path mapping" }, - { value: ProjectType.SYNCTHING, display: "Cloud Sync" } -]; - -export var ProjectStatus = { - ErrorConfig: "ErrorConfig", - Disable: "Disable", - Enable: "Enable", - Pause: "Pause", - Syncing: "Syncing" -}; - -export interface IProject { - id?: string; - label: string; - pathClient: string; - pathServer?: string; - type: ProjectType; - status?: string; - isInSync?: boolean; - isUsable?: boolean; - serverPrjDef?: IXDSFolderConfig; - isExpanded?: boolean; - visible?: boolean; - defaultSdkID?: string; -} - -export interface IXDSAgentConfig { - URL: string; - retry: number; -} - -export interface ILocalSTConfig { - ID: string; - URL: string; - retry: number; - tilde: string; -} - -export interface IxdsAgentPackage { - os: string; - arch: string; - version: string; - url: string; -} - -export interface IConfig { - xdsServerURL: string; - xdsAgent: IXDSAgentConfig; - xdsAgentPackages: IxdsAgentPackage[]; - projectsRootDir: string; - projects: IProject[]; - localSThg: ILocalSTConfig; -} - -@Injectable() -export class ConfigService { - - public conf: Observable; - - private confSubject: BehaviorSubject; - private confStore: IConfig; - private AgentConnectObs = null; - private stConnectObs = null; - - constructor(private _window: Window, - private cookie: CookieService, - private xdsServerSvr: XDSServerService, - private xdsAgentSvr: XDSAgentService, - private stSvr: SyncthingService, - private alert: AlertService, - private utils: UtilsService, - ) { - this.load(); - this.confSubject = >new BehaviorSubject(this.confStore); - this.conf = this.confSubject.asObservable(); - - // force to load projects - this.loadProjects(); - } - - // Load config - load() { - // Try to retrieve previous config from cookie - let cookConf = this.cookie.getObject("xds-config"); - if (cookConf != null) { - this.confStore = cookConf; - } else { - // Set default config - this.confStore = { - xdsServerURL: this._window.location.origin + '/api/v1', - xdsAgent: { - URL: 'http://localhost:8010', - retry: 10, - }, - xdsAgentPackages: [], - projectsRootDir: "", - projects: [], - localSThg: { - ID: null, - URL: "http://localhost:8386", - retry: 10, // 10 seconds - tilde: "", - } - }; - } - - // Update XDS Agent tarball url - this.xdsServerSvr.getXdsAgentInfo().subscribe(nfo => { - this.confStore.xdsAgentPackages = []; - nfo.tarballs && nfo.tarballs.forEach(el => - this.confStore.xdsAgentPackages.push({ - os: el.os, - arch: el.arch, - version: el.version, - url: el.fileUrl - }) - ); - this.confSubject.next(Object.assign({}, this.confStore)); - }); - - // Update Project data - this.xdsServerSvr.FolderStateChange$.subscribe(prj => { - let i = this._getProjectIdx(prj.id); - if (i >= 0) { - // XXX for now, only isInSync and status may change - this.confStore.projects[i].isInSync = prj.isInSync; - this.confStore.projects[i].status = prj.status; - this.confStore.projects[i].isUsable = this._isUsableProject(prj); - this.confSubject.next(Object.assign({}, this.confStore)); - } - }); - } - - // Save config into cookie - save() { - // Notify subscribers - this.confSubject.next(Object.assign({}, this.confStore)); - - // Don't save projects in cookies (too big!) - let cfg = Object.assign({}, this.confStore); - delete (cfg.projects); - this.cookie.putObject("xds-config", cfg); - } - - loadProjects() { - // Setup connection with local XDS agent - if (this.AgentConnectObs) { - try { - this.AgentConnectObs.unsubscribe(); - } catch (err) { } - this.AgentConnectObs = null; - } - - let cfg = this.confStore.xdsAgent; - this.AgentConnectObs = this.xdsAgentSvr.connect(cfg.retry, cfg.URL) - .subscribe((sts) => { - //console.log("Agent sts", sts); - // 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 rootUrl = "http://docs.automotivelinux.org/docs/devguides/en/dev/reference/"; - let url_OS_Linux = rootUrl + "xds/part-1/1_install-client.html#install-packages-for-debian-distro-type"; - let url_OS_Other = rootUrl + "xds/part-1/1_install-client.html#install-for-other-platforms-windows--macos"; - let msg = `` + error + `
- You may need to install and execute XDS-Agent:
- On Linux machine -
- On Windows machine -
- On MacOS machine - `; - this.alert.error(msg); - } else { - this.alert.error(error); - } - }); - } - - private _loadProjectFromLocalST() { - // Remove previous subscriber if existing - if (this.stConnectObs) { - try { - this.stConnectObs.unsubscribe(); - } catch (err) { } - this.stConnectObs = null; - } - - // FIXME: move this code and all logic about syncthing inside XDS Agent - // Setup connection with local SyncThing - let retry = this.confStore.localSThg.retry; - let url = this.confStore.localSThg.URL; - this.stConnectObs = this.stSvr.connect(retry, url).subscribe((sts) => { - this.confStore.localSThg.ID = sts.ID; - this.confStore.localSThg.tilde = sts.tilde; - if (this.confStore.projectsRootDir === "") { - this.confStore.projectsRootDir = sts.tilde; - } - - // Rebuild projects definition from local and remote syncthing - this.confStore.projects = []; - - this.xdsServerSvr.getProjects().subscribe(remotePrj => { - this.stSvr.getProjects().subscribe(localPrj => { - remotePrj.forEach(rPrj => { - let lPrj = localPrj.filter(item => item.id === rPrj.id); - if (lPrj.length > 0 || rPrj.type === ProjectType.NATIVE_PATHMAP) { - this._addProject(rPrj, true); - } - }); - this.confSubject.next(Object.assign({}, this.confStore)); - }), error => this.alert.error('Could not load initial state of local projects.'); - }), error => this.alert.error('Could not load initial state of remote projects.'); - - }, error => { - if (error.indexOf("Syncthing local daemon not responding") !== -1) { - let msg = "" + error + "
"; - msg += "Please check that local XDS-Agent is running.
"; - msg += "
"; - this.alert.error(msg); - } else { - this.alert.error(error); - } - }); - } - - set syncToolURL(url: string) { - this.confStore.localSThg.URL = url; - this.save(); - } - - set xdsAgentRetry(r: number) { - this.confStore.localSThg.retry = r; - this.confStore.xdsAgent.retry = r; - this.save(); - } - - set xdsAgentUrl(url: string) { - this.confStore.xdsAgent.URL = url; - this.save(); - } - - - set projectsRootDir(p: string) { - if (p.charAt(0) === '~') { - p = this.confStore.localSThg.tilde + p.substring(1); - } - this.confStore.projectsRootDir = p; - this.save(); - } - - getLabelRootName(): string { - let id = this.confStore.localSThg.ID; - if (!id || id === "") { - return null; - } - return id.slice(0, 15); - } - - addProject(prj: IProject): Observable { - // Substitute tilde with to user home path - let pathCli = prj.pathClient.trim(); - if (pathCli.charAt(0) === '~') { - pathCli = this.confStore.localSThg.tilde + pathCli.substring(1); - - // Must be a full path (on Linux or Windows) - } else if (!((pathCli.charAt(0) === '/') || - (pathCli.charAt(1) === ':' && (pathCli.charAt(2) === '\\' || pathCli.charAt(2) === '/')))) { - pathCli = this.confStore.projectsRootDir + '/' + pathCli; - } - - let xdsPrj: IXDSFolderConfig = { - id: "", - label: prj.label || "", - path: pathCli, - type: prj.type, - defaultSdkID: prj.defaultSdkID, - dataPathMap: { - serverPath: prj.pathServer, - }, - dataCloudSync: { - syncThingID: this.confStore.localSThg.ID, - } - }; - // Send config to XDS server - let newPrj = prj; - return this.xdsServerSvr.addProject(xdsPrj) - .flatMap(resStRemotePrj => { - xdsPrj = resStRemotePrj; - if (xdsPrj.type === ProjectType.SYNCTHING) { - // FIXME REWORK local ST config - // move logic to server side tunneling-back by WS - let stData = xdsPrj.dataCloudSync; - - // Now setup local config - let stLocPrj: ISyncThingProject = { - id: xdsPrj.id, - label: xdsPrj.label, - path: xdsPrj.path, - serverSyncThingID: stData.builderSThgID - }; - - // Set local Syncthing config - return this.stSvr.addProject(stLocPrj); - - } else { - return Observable.of(null); - } - }) - .map(resStLocalPrj => { - this._addProject(xdsPrj); - return newPrj; - }); - } - - deleteProject(prj: IProject): Observable { - let idx = this._getProjectIdx(prj.id); - let delPrj = prj; - if (idx === -1) { - throw new Error("Invalid project id (id=" + prj.id + ")"); - } - return this.xdsServerSvr.deleteProject(prj.id) - .flatMap(res => { - if (prj.type === ProjectType.SYNCTHING) { - return this.stSvr.deleteProject(prj.id); - } - return Observable.of(null); - }) - .map(res => { - this.confStore.projects.splice(idx, 1); - return delPrj; - }); - } - - syncProject(prj: IProject): Observable { - let idx = this._getProjectIdx(prj.id); - if (idx === -1) { - throw new Error("Invalid project id (id=" + prj.id + ")"); - } - return this.xdsServerSvr.syncProject(prj.id); - } - - private _isUsableProject(p) { - return p && p.isInSync && - (p.status === ProjectStatus.Enable) && - (p.status !== ProjectStatus.Syncing); - } - - private _getProjectIdx(id: string): number { - return this.confStore.projects.findIndex((item) => item.id === id); - } - - private _addProject(rPrj: IXDSFolderConfig, noNext?: boolean) { - - // Convert XDSFolderConfig to IProject - let pp: IProject = { - id: rPrj.id, - label: rPrj.label, - pathClient: rPrj.path, - pathServer: rPrj.dataPathMap.serverPath, - type: rPrj.type, - status: rPrj.status, - isInSync: rPrj.isInSync, - isUsable: this._isUsableProject(rPrj), - defaultSdkID: rPrj.defaultSdkID, - serverPrjDef: Object.assign({}, rPrj), // do a copy - }; - - // add new project - this.confStore.projects.push(pp); - - // sort project array - this.confStore.projects.sort((a, b) => { - if (a.label < b.label) { - return -1; - } - if (a.label > b.label) { - return 1; - } - return 0; - }); - - // FIXME: maybe reduce subject to only .project - //this.confSubject.next(Object.assign({}, this.confStore).project); - if (!noNext) { - this.confSubject.next(Object.assign({}, this.confStore)); - } - } -} diff --git a/webapp/src/app/services/sdk.service.ts b/webapp/src/app/services/sdk.service.ts deleted file mode 100644 index fa4cd55..0000000 --- a/webapp/src/app/services/sdk.service.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Injectable, SecurityContext } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -import { XDSServerService } from "../services/xdsserver.service"; - -export interface ISdk { - id: string; - profile: string; - version: string; - arch: number; - path: string; -} - -@Injectable() -export class SdkService { - public Sdks$: Observable; - - private _sdksList = []; - private current: ISdk; - private sdksSubject = >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 getCurrent(): ISdk { - return this.current; - } - - public getCurrentId(): string { - if (this.current && this.current.id) { - return this.current.id; - } - return ""; - } -} \ No newline at end of file diff --git a/webapp/src/app/services/syncthing.service.ts b/webapp/src/app/services/syncthing.service.ts deleted file mode 100644 index 2e6da1c..0000000 --- a/webapp/src/app/services/syncthing.service.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; -import { CookieService } from 'ngx-cookie'; -import { Location } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -// Import RxJs required methods -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/throw'; -import 'rxjs/add/observable/of'; -import 'rxjs/add/observable/timer'; -import 'rxjs/add/operator/retryWhen'; - -export interface ISyncThingProject { - id: string; - path: string; - serverSyncThingID: string; - label?: string; -} - -export interface ISyncThingStatus { - ID: string; - baseURL: string; - connected: boolean; - connectionRetry: number; - tilde: string; - rawStatus: any; -} - -// Private interfaces of Syncthing -const ISTCONFIG_VERSION = 20; - -interface ISTFolderDeviceConfiguration { - deviceID: string; - introducedBy: string; -} -interface ISTFolderConfiguration { - id: string; - label: string; - path: string; - type?: number; - devices?: ISTFolderDeviceConfiguration[]; - rescanIntervalS?: number; - ignorePerms?: boolean; - autoNormalize?: boolean; - minDiskFreePct?: number; - versioning?: { type: string; params: string[] }; - copiers?: number; - pullers?: number; - hashers?: number; - order?: number; - ignoreDelete?: boolean; - scanProgressIntervalS?: number; - pullerSleepS?: number; - pullerPauseS?: number; - maxConflicts?: number; - disableSparseFiles?: boolean; - disableTempIndexes?: boolean; - fsync?: boolean; - paused?: boolean; -} - -interface ISTDeviceConfiguration { - deviceID: string; - name?: string; - address?: string[]; - compression?: string; - certName?: string; - introducer?: boolean; - skipIntroductionRemovals?: boolean; - introducedBy?: string; - paused?: boolean; - allowedNetwork?: string[]; -} - -interface ISTGuiConfiguration { - enabled: boolean; - address: string; - user?: string; - password?: string; - useTLS: boolean; - apiKey?: string; - insecureAdminAccess?: boolean; - theme: string; - debugging: boolean; - insecureSkipHostcheck?: boolean; -} - -interface ISTOptionsConfiguration { - listenAddresses: string[]; - globalAnnounceServer: string[]; - // To be completed ... -} - -interface ISTConfiguration { - version: number; - folders: ISTFolderConfiguration[]; - devices: ISTDeviceConfiguration[]; - gui: ISTGuiConfiguration; - options: ISTOptionsConfiguration; - ignoredDevices: string[]; -} - -// Default settings -const DEFAULT_GUI_PORT = 8386; -const DEFAULT_GUI_API_KEY = "1234abcezam"; -const DEFAULT_RESCAN_INTERV = 0; // 0: use syncthing-inotify to detect changes - - -@Injectable() -export class SyncthingService { - - public Status$: Observable; - - private baseRestUrl: string; - private apikey: string; - private localSTID: string; - private stCurVersion: number; - private connectionMaxRetry: number; - private _status: ISyncThingStatus = { - ID: null, - baseURL: "", - connected: false, - connectionRetry: 0, - tilde: "", - rawStatus: null, - }; - private statusSubject = >new BehaviorSubject(this._status); - - constructor(private http: Http, private _window: Window, private cookie: CookieService) { - this._status.baseURL = 'http://localhost:' + DEFAULT_GUI_PORT; - this.baseRestUrl = this._status.baseURL + '/rest'; - this.apikey = DEFAULT_GUI_API_KEY; - this.stCurVersion = -1; - this.connectionMaxRetry = 10; // 10 seconds - - this.Status$ = this.statusSubject.asObservable(); - } - - connect(retry: number, url?: string): Observable { - if (url) { - this._status.baseURL = url; - this.baseRestUrl = this._status.baseURL + '/rest'; - } - this._status.connected = false; - this._status.ID = null; - this._status.connectionRetry = 0; - this.connectionMaxRetry = retry || 3600; // 1 hour - return this.getStatus(); - } - - getID(): Observable { - if (this._status.ID != null) { - return Observable.of(this._status.ID); - } - return this.getStatus().map(sts => sts.ID); - } - - getStatus(): Observable { - return this._get('/system/status') - .map((status) => { - this._status.ID = status["myID"]; - this._status.tilde = status["tilde"]; - console.debug('ST local ID', this._status.ID); - - this._status.rawStatus = status; - - return this._status; - }); - } - - getProjects(): Observable { - return this._getConfig() - .map((conf) => conf.folders); - } - - addProject(prj: ISyncThingProject): Observable { - return this.getID() - .flatMap(() => this._getConfig()) - .flatMap((stCfg) => { - let newDevID = prj.serverSyncThingID; - - // Add new Device if needed - let dev = stCfg.devices.filter(item => item.deviceID === newDevID); - if (dev.length <= 0) { - stCfg.devices.push( - { - deviceID: newDevID, - name: "Builder_" + newDevID.slice(0, 15), - address: ["dynamic"], - } - ); - } - - // Add or update Folder settings - let label = prj.label || ""; - let scanInterval = parseInt(this.cookie.get("st-rescanInterval"), 10) || DEFAULT_RESCAN_INTERV; - let folder: ISTFolderConfiguration = { - id: prj.id, - label: label, - path: prj.path, - devices: [{ deviceID: newDevID, introducedBy: "" }], - autoNormalize: true, - rescanIntervalS: scanInterval, - }; - - let idx = stCfg.folders.findIndex(item => item.id === prj.id); - if (idx === -1) { - stCfg.folders.push(folder); - } else { - let newFld = Object.assign({}, stCfg.folders[idx], folder); - stCfg.folders[idx] = newFld; - } - - // Set new config - return this._setConfig(stCfg); - }) - .flatMap(() => this._getConfig()) - .map((newConf) => { - let idx = newConf.folders.findIndex(item => item.id === prj.id); - return newConf.folders[idx]; - }); - } - - deleteProject(id: string): Observable { - let delPrj: ISTFolderConfiguration; - return this._getConfig() - .flatMap((conf: ISTConfiguration) => { - let idx = conf.folders.findIndex(item => item.id === id); - if (idx === -1) { - throw new Error("Cannot delete project: not found"); - } - delPrj = Object.assign({}, conf.folders[idx]); - conf.folders.splice(idx, 1); - return this._setConfig(conf); - }) - .map(() => delPrj); - } - - /* - * --- Private functions --- - */ - private _getConfig(): Observable { - return this._get('/system/config'); - } - - private _setConfig(cfg: ISTConfiguration): Observable { - return this._post('/system/config', cfg); - } - - private _attachAuthHeaders(options?: any) { - options = options || {}; - let headers = options.headers || new Headers(); - // headers.append('Authorization', 'Basic ' + btoa('username:password')); - headers.append('Accept', 'application/json'); - headers.append('Content-Type', 'application/json'); - if (this.apikey !== "") { - headers.append('X-API-Key', this.apikey); - - } - options.headers = headers; - return options; - } - - private _checkAlive(): Observable { - if (this._status.connected) { - return Observable.of(true); - } - - return this.http.get(this.baseRestUrl + '/system/version', this._attachAuthHeaders()) - .map((r) => this._status.connected = true) - .retryWhen((attempts) => { - this._status.connectionRetry = 0; - return attempts.flatMap(error => { - this._status.connected = false; - if (++this._status.connectionRetry >= this.connectionMaxRetry) { - return Observable.throw("Syncthing local daemon not responding (url=" + this._status.baseURL + ")"); - } else { - return Observable.timer(1000); - } - }); - }); - } - - private _getAPIVersion(): Observable { - if (this.stCurVersion !== -1) { - return Observable.of(this.stCurVersion); - } - - return this.http.get(this.baseRestUrl + '/system/config', this._attachAuthHeaders()) - .map((res: Response) => { - let conf: ISTConfiguration = res.json(); - this.stCurVersion = (conf && conf.version) || -1; - return this.stCurVersion; - }) - .catch(this._handleError); - } - - private _checkAPIVersion(): Observable { - return this._getAPIVersion().map(ver => { - if (ver !== ISTCONFIG_VERSION) { - throw new Error("Unsupported Syncthing version api (" + ver + - " != " + ISTCONFIG_VERSION + ") !"); - } - return ver; - }); - } - - private _get(url: string): Observable { - return this._checkAlive() - .flatMap(() => this._checkAPIVersion()) - .flatMap(() => this.http.get(this.baseRestUrl + url, this._attachAuthHeaders())) - .map((res: Response) => res.json()) - .catch(this._handleError); - } - - private _post(url: string, body: any): Observable { - return this._checkAlive() - .flatMap(() => this._checkAPIVersion()) - .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders())) - .map((res: Response) => { - if (res && res.status && res.status === 200) { - return res; - } - throw new Error(res.toString()); - - }) - .catch(this._handleError); - } - - private _handleError(error: Response | any) { - // In a real world app, you might use a remote logging infrastructure - let errMsg: string; - if (this._status) { - this._status.connected = false; - } - if (error instanceof Response) { - const body = error.json() || 'Server error'; - const err = body.error || JSON.stringify(body); - errMsg = `${error.status} - ${error.statusText || ''} ${err}`; - } else { - errMsg = error.message ? error.message : error.toString(); - } - return Observable.throw(errMsg); - } -} diff --git a/webapp/src/app/services/utils.service.ts b/webapp/src/app/services/utils.service.ts deleted file mode 100644 index 84b9ab6..0000000 --- a/webapp/src/app/services/utils.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable() -export class UtilsService { - constructor() { } - - getOSName(lowerCase?: boolean): string { - var checkField = function (ff) { - if (ff.indexOf("Linux") !== -1) { - return "Linux"; - } else if (ff.indexOf("Win") !== -1) { - return "Windows"; - } else if (ff.indexOf("Mac") !== -1) { - return "MacOS"; - } else if (ff.indexOf("X11") !== -1) { - return "UNIX"; - } - return ""; - }; - - let OSName = checkField(navigator.platform); - if (OSName === "") { - OSName = checkField(navigator.appVersion); - } - if (OSName === "") { - OSName = "Unknown OS"; - } - if (lowerCase) { - return OSName.toLowerCase(); - } - return OSName; - } -} \ No newline at end of file diff --git a/webapp/src/app/services/xdsagent.service.ts b/webapp/src/app/services/xdsagent.service.ts deleted file mode 100644 index c6c52c8..0000000 --- a/webapp/src/app/services/xdsagent.service.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; -import { Location } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; -import * as io from 'socket.io-client'; - -import { AlertService } from './alert.service'; - - -// Import RxJs required methods -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/throw'; - -export interface IXDSVersion { - version: string; - apiVersion: string; - gitTag: string; - -} - -export interface IXDSDeploy { - boardIP: string; - file: string; -} - -export interface IAgentStatus { - baseURL: string; - connected: boolean; - WS_connected: boolean; - connectionRetry: number; - version: string; -} - -// Default settings -const DEFAULT_PORT = 8010; -const DEFAULT_API_KEY = "1234abcezam"; -const API_VERSION = "v1"; - -@Injectable() -export class XDSAgentService { - public Status$: Observable; - - private baseRestUrl: string; - private wsUrl: string; - private connectionMaxRetry: number; - private apikey: string; - private _status: IAgentStatus = { - baseURL: "", - connected: false, - WS_connected: false, - connectionRetry: 0, - version: "", - }; - private statusSubject = >new BehaviorSubject(this._status); - - - private socket: SocketIOClient.Socket; - - constructor(private http: Http, private _window: Window, private alert: AlertService) { - - this.Status$ = this.statusSubject.asObservable(); - - this.apikey = DEFAULT_API_KEY; // FIXME Add dynamic allocated key - this._initURLs('http://localhost:' + DEFAULT_PORT); - } - - connect(retry: number, url?: string): Observable { - if (url) { - this._initURLs(url); - } - this._status.connected = false; - this._status.connectionRetry = 0; - this.connectionMaxRetry = retry || 3600; // 1 hour - - // Init IO Socket connection - this._handleIoSocket(); - - // Get Version in order to check connection via a REST request - return this.getVersion() - .map((v) => { - this._status.version = v.version; - this.statusSubject.next(Object.assign({}, this._status)); - return this._status; - }); - } - - public getVersion(): Observable { - return this._get('/version'); - } - - public deploy(dpy: IXDSDeploy) { - return this._post('/deploy', dpy); - } - - private _initURLs(url: string) { - this._status.baseURL = url; - this.baseRestUrl = this._status.baseURL + '/api/' + API_VERSION; - let re = this._status.baseURL.match(/http[s]?:\/\/([^\/]*)[\/]?/); - if (re === null || re.length < 2) { - this.wsUrl = ''; - console.error('ERROR: cannot determine Websocket url'); - return; - } - this.wsUrl = 'ws://' + re[1]; - } - - private _WSState(sts: boolean) { - this._status.WS_connected = sts; - this.statusSubject.next(Object.assign({}, this._status)); - } - - private _handleIoSocket() { - this.socket = io(this.wsUrl, { transports: ['websocket'] }); - - this.socket.on('connect_error', (res) => { - this._WSState(false); - console.error('WS Connect_error ', res); - }); - - this.socket.on('connect', (res) => { - this._WSState(true); - }); - - this.socket.on('disconnection', (res) => { - this._WSState(false); - this.alert.error('WS disconnection: ' + res); - }); - - this.socket.on('error', (err) => { - console.error('WS error:', err); - }); - - } - - private _attachAuthHeaders(options?: any) { - options = options || {}; - let headers = options.headers || new Headers(); - // headers.append('Authorization', 'Basic ' + btoa('username:password')); - headers.append('Accept', 'application/json'); - headers.append('Content-Type', 'application/json'); - if (this.apikey !== "") { - headers.append('X-API-Key', this.apikey); - - } - - options.headers = headers; - return options; - } - - private _checkAlive(): Observable { - if (this._status.connected) { - return Observable.of(true); - } - - return this.http.get(this.baseRestUrl + "/version", this._attachAuthHeaders()) - .map((r) => this._status.connected = true) - .retryWhen((attempts) => { - this._status.connectionRetry = 0; - return attempts.flatMap(error => { - this._status.connected = false; - if (++this._status.connectionRetry >= this.connectionMaxRetry) { - return Observable.throw("XDS local Agent not responding (url=" + this._status.baseURL + ")"); - } else { - return Observable.timer(1000); - } - }); - }); - } - - private _get(url: string): Observable { - return this._checkAlive() - .flatMap(() => this.http.get(this.baseRestUrl + url, this._attachAuthHeaders())) - .map((res: Response) => res.json()) - .catch(this._decodeError); - } - private _post(url: string, body: any): Observable { - return this._checkAlive() - .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders())) - .map((res: Response) => res.json()) - .catch(this._decodeError); - } - private _delete(url: string): Observable { - return this._checkAlive() - .flatMap(() => this.http.delete(this.baseRestUrl + url, this._attachAuthHeaders())) - .map((res: Response) => res.json()) - .catch(this._decodeError); - } - - private _decodeError(err: Response | any) { - let e: string; - if (this._status) { - this._status.connected = false; - } - 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) { - e = String(err.error); - } else { - e = JSON.stringify(err); - } - } else { - e = err.message ? err.message : err.toString(); - } - return Observable.throw(e); - } -} diff --git a/webapp/src/app/services/xdsserver.service.ts b/webapp/src/app/services/xdsserver.service.ts deleted file mode 100644 index b69a196..0000000 --- a/webapp/src/app/services/xdsserver.service.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; -import { Location } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; -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'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/throw'; -import 'rxjs/add/operator/mergeMap'; - - -export interface IXDSConfigProject { - id: string; - path: string; - clientSyncThingID: string; - type: number; - label?: string; - defaultSdkID?: string; -} - -interface IXDSBuilderConfig { - ip: string; - port: string; - syncThingID: string; -} - -export interface IXDSFolderConfig { - id: string; - label: string; - path: string; - type: number; - status?: string; - isInSync?: boolean; - defaultSdkID: string; - - // FIXME better with union but tech pb with go code - //data?: IXDSPathMapConfig|IXDSCloudSyncConfig; - dataPathMap?: IXDSPathMapConfig; - dataCloudSync?: IXDSCloudSyncConfig; -} - -export interface IXDSPathMapConfig { - // TODO - serverPath: string; -} - -export interface IXDSCloudSyncConfig { - syncThingID: string; - builderSThgID?: string; -} - -interface IXDSConfig { - version: number; - builder: IXDSBuilderConfig; - folders: IXDSFolderConfig[]; -} - -export interface IXDSAgentTarball { - os: string; - arch: string; - version: string; - rawVersion: string; - fileUrl: string; -} - -export interface IXDSAgentInfo { - tarballs: IXDSAgentTarball[]; -} - -export interface ISdkMessage { - wsID: string; - msgType: string; - data: any; -} - -export interface ICmdOutput { - cmdID: string; - timestamp: string; - stdout: string; - stderr: string; -} - -export interface ICmdExit { - cmdID: string; - timestamp: string; - code: number; - error: string; -} - -export interface IServerStatus { - WS_connected: boolean; - -} - -const FOLDER_TYPE_CLOUDSYNC = 2; - -@Injectable() -export class XDSServerService { - - public CmdOutput$ = >new Subject(); - public CmdExit$ = >new Subject(); - public FolderStateChange$ = >new Subject(); - public Status$: Observable; - - - private baseUrl: string; - private wsUrl: string; - private _status = { WS_connected: false }; - private statusSubject = >new BehaviorSubject(this._status); - - - private socket: SocketIOClient.Socket; - - constructor(private http: Http, private _window: Window, private alert: AlertService) { - - this.Status$ = this.statusSubject.asObservable(); - - this.baseUrl = this._window.location.origin + '/api/v1'; - let re = this._window.location.origin.match(/http[s]?:\/\/([^\/]*)[\/]?/); - if (re === null || re.length < 2) { - console.error('ERROR: cannot determine Websocket url'); - } else { - this.wsUrl = 'ws://' + re[1]; - this._handleIoSocket(); - this._RegisterEvents(); - } - } - - private _WSState(sts: boolean) { - this._status.WS_connected = sts; - this.statusSubject.next(Object.assign({}, this._status)); - } - - private _handleIoSocket() { - this.socket = io(this.wsUrl, { transports: ['websocket'] }); - - this.socket.on('connect_error', (res) => { - this._WSState(false); - console.error('WS Connect_error ', res); - }); - - this.socket.on('connect', (res) => { - this._WSState(true); - }); - - this.socket.on('disconnection', (res) => { - this._WSState(false); - this.alert.error('WS disconnection: ' + res); - }); - - this.socket.on('error', (err) => { - console.error('WS error:', err); - }); - - this.socket.on('make:output', data => { - this.CmdOutput$.next(Object.assign({}, data)); - }); - - this.socket.on('make:exit', data => { - this.CmdExit$.next(Object.assign({}, data)); - }); - - this.socket.on('exec:output', data => { - this.CmdOutput$.next(Object.assign({}, data)); - }); - - this.socket.on('exec:exit', data => { - this.CmdExit$.next(Object.assign({}, data)); - }); - - this.socket.on('event:FolderStateChanged', ev => { - if (ev && ev.folder) { - this.FolderStateChange$.next(Object.assign({}, ev.folder)); - } - }); - } - - private _RegisterEvents() { - let ev = "FolderStateChanged"; - this._post('/events/register', { "name": ev }) - .subscribe( - res => { }, - error => { - this.alert.error("ERROR while registering events " + ev + ": ", error); - } - ); - } - - getSdks(): Observable { - return this._get('/sdks'); - } - - getXdsAgentInfo(): Observable { - return this._get('/xdsagent/info'); - } - - getProjects(): Observable { - return this._get('/folders'); - } - - addProject(cfg: IXDSFolderConfig): Observable { - return this._post('/folder', cfg); - } - - deleteProject(id: string): Observable { - return this._delete('/folder/' + id); - } - - syncProject(id: string): Observable { - return this._post('/folder/sync/' + id, {}); - } - - exec(prjID: string, dir: string, cmd: string, sdkid?: string, args?: string[], env?: string[]): Observable { - return this._post('/exec', - { - id: prjID, - rpath: dir, - cmd: cmd, - sdkid: sdkid || "", - args: args || [], - env: env || [], - }); - } - - make(prjID: string, dir: string, sdkid?: string, args?: string[], env?: string[]): Observable { - return this._post('/make', - { - id: prjID, - rpath: dir, - sdkid: sdkid, - args: args || [], - env: env || [], - }); - } - - - private _attachAuthHeaders(options?: any) { - options = options || {}; - let headers = options.headers || new Headers(); - // headers.append('Authorization', 'Basic ' + btoa('username:password')); - headers.append('Accept', 'application/json'); - headers.append('Content-Type', 'application/json'); - // headers.append('Access-Control-Allow-Origin', '*'); - - options.headers = headers; - return options; - } - - private _get(url: string): Observable { - return this.http.get(this.baseUrl + url, this._attachAuthHeaders()) - .map((res: Response) => res.json()) - .catch(this._decodeError); - } - private _post(url: string, body: any): Observable { - return this.http.post(this.baseUrl + url, JSON.stringify(body), this._attachAuthHeaders()) - .map((res: Response) => res.json()) - .catch((error) => { - return this._decodeError(error); - }); - } - private _delete(url: string): Observable { - return this.http.delete(this.baseUrl + url, this._attachAuthHeaders()) - .map((res: Response) => res.json()) - .catch(this._decodeError); - } - - private _decodeError(err: any) { - let e: string; - 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) { - e = String(err.error); - } else { - e = JSON.stringify(err); - } - } else { - e = err.message ? err.message : err.toString(); - } - return Observable.throw(e); - } -} diff --git a/webapp/src/index.html b/webapp/src/index.html index 290b4be..45ac90d 100644 --- a/webapp/src/index.html +++ b/webapp/src/index.html @@ -2,49 +2,97 @@ - XDS Dashboard + XDS Server - - - - + - - + - + + + + +

X(cross) Development System

+ +

Please start XDS agent on your machine
and +
connect to XDS Dashboard +

+ + + + (URL may depend on your configuration) + +


- - -
- -
Loading... - -
-
+

For more information, please refer to XDS User's Guide:

+ diff --git a/webapp/src/systemjs.config.js b/webapp/src/systemjs.config.js deleted file mode 100644 index 15c52ba..0000000 --- a/webapp/src/systemjs.config.js +++ /dev/null @@ -1,69 +0,0 @@ -(function (global) { - System.config({ - paths: { - // paths serve as alias - 'npm:': 'lib/' - }, - bundles: { - "npm:rxjs-system-bundle/Rx.system.min.js": [ - "rxjs", - "rxjs/*", - "rxjs/operator/*", - "rxjs/observable/*", - "rxjs/scheduler/*", - "rxjs/symbol/*", - "rxjs/add/operator/*", - "rxjs/add/observable/*", - "rxjs/util/*" - ] - }, - // map tells the System loader where to look for things - map: { - // our app is within the app folder - app: 'app', - // angular bundles - '@angular/core': 'npm:@angular/core/bundles/core.umd.js', - '@angular/common': 'npm:@angular/common/bundles/common.umd.js', - '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', - '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', - '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', - '@angular/http': 'npm:@angular/http/bundles/http.umd.js', - '@angular/router': 'npm:@angular/router/bundles/router.umd.js', - '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', - 'ngx-cookie': 'npm:ngx-cookie/bundles/ngx-cookie.umd.js', - // ng2-bootstrap - 'moment': 'npm:moment', - 'ngx-bootstrap/alert': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - 'ngx-bootstrap/modal': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - 'ngx-bootstrap/accordion': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - 'ngx-bootstrap/carousel': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - 'ngx-bootstrap/popover': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - 'ngx-bootstrap/dropdown': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - 'ngx-bootstrap/collapse': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - // other libraries - 'socket.io-client': 'npm:socket.io-client/dist/socket.io.min.js' - }, - // packages tells the System loader how to load when no filename and/or no extension - packages: { - 'app': { - main: './main.js', - defaultExtension: 'js' - }, - 'rxjs': { - defaultExtension: false - }, - 'socket.io-client': { - defaultExtension: 'js' - }, - 'ngx-bootstrap': { - format: 'cjs', - main: 'bundles/ng2-bootstrap.umd.js', - defaultExtension: 'js' - }, - 'moment': { - main: 'moment.js', - defaultExtension: 'js' - } - } - }); -})(this); diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json deleted file mode 100644 index 9bad681..0000000 --- a/webapp/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist/app", - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false, - "noImplicitAny": false, - "noStrictGenericChecks": true // better to switch to RxJS 5.4.2 ; workaround https://stackoverflow.com/questions/44810195/how-do-i-get-around-this-subject-incorrectly-extends-observable-error-in-types - }, - "exclude": [ - "gulpfile.ts", - "node_modules" - ] -} diff --git a/webapp/tslint.json b/webapp/tslint.json deleted file mode 100644 index 15969a4..0000000 --- a/webapp/tslint.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "rules": { - "class-name": true, - "curly": true, - "eofline": false, - "forin": true, - "indent": [ - true, - 4 - ], - "label-position": true, - "max-line-length": [ - true, - 140 - ], - "no-arg": true, - "no-bitwise": true, - "no-console": [ - true, - "info", - "time", - "timeEnd", - "trace" - ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-variable": true, - "no-empty": false, - "no-eval": true, - "no-string-literal": false, - "no-trailing-whitespace": true, - "no-use-before-declare": true, - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" - ], - "radix": true, - "semicolon": true, - "triple-equals": [ - true, - "allow-null-check" - ], - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator" - ] - } -} diff --git a/webapp/tslint.prod.json b/webapp/tslint.prod.json deleted file mode 100644 index aa64c7f..0000000 --- a/webapp/tslint.prod.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "rules": { - "class-name": true, - "curly": true, - "eofline": false, - "forin": true, - "indent": [ - true, - 4 - ], - "label-position": true, - "max-line-length": [ - true, - 140 - ], - "no-arg": true, - "no-bitwise": true, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-variable": true, - "no-empty": false, - "no-eval": true, - "no-string-literal": false, - "no-trailing-whitespace": true, - "no-use-before-declare": true, - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" - ], - "radix": true, - "semicolon": true, - "triple-equals": [ - true, - "allow-null-check" - ], - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator" - ] - } -} diff --git a/webapp/typings.json b/webapp/typings.json deleted file mode 100644 index 23c6a41..0000000 --- a/webapp/typings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "dependencies": {}, - "devDependencies": {}, - "globalDependencies": { - "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654", - "socket.io-client": "registry:dt/socket.io-client#1.4.4+20160317120654" - }, - "globalDevDependencies": { - "jasmine": "registry:dt/jasmine#2.2.0+20160505161446" - } -}