diff options
author | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2017-11-24 01:14:30 +0100 |
---|---|---|
committer | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2017-11-24 01:37:24 +0100 |
commit | 4d843d2bde236ec23810d0904dfb8aebbc53a37b (patch) | |
tree | 84c01452f01620cedb7bf6bcb608a0eade82161b | |
parent | 38c0c21a969e621c725245ce91c78e77076c5ce7 (diff) |
New dashboard improvements.
- add build buttons
- add build settings support and backup into project clientData
- improved async alert
- fixed project dropdown
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
49 files changed, 918 insertions, 356 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 4c9ba28..5d43dd0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,51 +29,17 @@ ], // Words to add to dictionary for a workspace. "cSpell.words": [ - "apiv", - "gonic", - "devel", - "csrffound", - "Syncthing", - "STID", - "ISTCONFIG", - "socketio", - "ldflags", - "SThg", - "stconfig", - "Intf", - "dismissible", - "rpath", - "WSID", - "sess", - "IXDS", - "golib", - "xdsapi", - "xdsconfig", - "xdsserver", - "xdsagent", - "nbsp", - "Inot", - "inotify", - "cmdi", - "sdkid", - "Flds", - "prjs", - "iosk", - "CIFS", - "IPROJECT", - "unregister", - "conv", - "PATHMAP", - "nospace", - "graphx", - "Truthy", - "darkviolet", - "dwnl", - "topnav", - "leftbar" + "apiv", "gonic", "devel", "csrffound", "Syncthing", "STID", "ISTCONFIG", + "socketio", "ldflags", "SThg", "stconfig", "Intf", "dismissible", "rpath", + "WSID", "sess", "IXDS", "golib", "xdsapi", "xdsconfig", "xdsserver", + "xdsagent", "nbsp", "Inot", "inotify", "cmdi", "sdkid", "Flds", "prjs", + "iosk", "CIFS", "IPROJECT", "unregister", "conv", "PATHMAP", "nospace", + "graphx", "Truthy", "darkviolet", "dwnl", "topnav", "leftbar", "urfave", + "unmarshall", "sebd", "priv", "evts", "gdbserver", "tabset", "pageview", + "subpath", "prebuild", "reflectme", "franciscocpg" ], // codelyzer "tslint.rulesDirectory": "./webapp/node_modules/codelyzer", "typescript.tsdk": "webapp/node_modules/typescript/lib", "tslint.configFile": "webapp/tslint.json" -}
\ No newline at end of file +} @@ -25,3 +25,5 @@ import: version: ^0.1.0 subpackages: - golib/common +- package: github.com/franciscocpg/reflectme + version: ^0.1.9 diff --git a/lib/agent/apiv1-exec.go b/lib/agent/apiv1-exec.go index c199267..3cb4d23 100644 --- a/lib/agent/apiv1-exec.go +++ b/lib/agent/apiv1-exec.go @@ -5,15 +5,13 @@ import ( "io/ioutil" "net/http" + "github.com/franciscocpg/reflectme" "github.com/gin-gonic/gin" "github.com/iotbzh/xds-agent/lib/apiv1" common "github.com/iotbzh/xds-common/golib" uuid "github.com/satori/go.uuid" ) -var execCmdID = 1 -var fwdFuncID []uuid.UUID - // ExecCmd executes remotely a command func (s *APIService) execCmd(c *gin.Context) { s._execRequest("/exec", c) @@ -81,6 +79,7 @@ func (s *APIService) _execRequest(cmd string, c *gin.Context) { apiv1.ExecInferiorOutEvent, } + var fwdFuncID []uuid.UUID for _, evName := range evtList { evN := evName fwdFunc := func(pData interface{}, evData interface{}) error { @@ -92,6 +91,9 @@ func (s *APIService) _execRequest(cmd string, c *gin.Context) { return nil } + // Add sessionID to event Data + reflectme.SetField(evData, "sessionID", sid) + // Forward event to Client/Dashboard (*so).Emit(evN, evData) return nil @@ -110,15 +112,17 @@ func (s *APIService) _execRequest(cmd string, c *gin.Context) { evN := apiv1.ExecExitEvent sid := pData.(string) + // Add sessionID to event Data + reflectme.SetField(evData, "sessionID", sid) + // IO socket can be nil when disconnected so := s.sessions.IOSocketGet(sid) - if so == nil { + if so != nil { + (*so).Emit(evN, evData) + } else { s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid) - return nil } - (*so).Emit(evN, evData) - // cleanup listener for i, evName := range evtList { svr.EventOff(evName, fwdFuncID[i]) diff --git a/lib/agent/apiv1-projects.go b/lib/agent/apiv1-projects.go index c835967..5784896 100644 --- a/lib/agent/apiv1-projects.go +++ b/lib/agent/apiv1-projects.go @@ -39,7 +39,7 @@ func (s *APIService) addProject(c *gin.Context) { s.Log.Debugln("Add project config: ", cfgArg) - newFld, err := s.projects.Add(cfgArg) + newFld, err := s.projects.Add(cfgArg, s.sessions.GetID(c)) if err != nil { common.APIError(c, err.Error()) return @@ -77,10 +77,34 @@ func (s *APIService) delProject(c *gin.Context) { s.Log.Debugln("Delete project id ", id) - delEntry, err := s.projects.Delete(id) + delEntry, err := s.projects.Delete(id, s.sessions.GetID(c)) if err != nil { common.APIError(c, err.Error()) return } c.JSON(http.StatusOK, delEntry) } + +// updateProject Update some field of a specific project +func (s *APIService) updateProject(c *gin.Context) { + id, err := s.projects.ResolveID(c.Param("id")) + if err != nil { + common.APIError(c, err.Error()) + return + } + + var cfgArg apiv1.ProjectConfig + if c.BindJSON(&cfgArg) != nil { + common.APIError(c, "Invalid arguments") + return + } + + s.Log.Debugln("Update project id ", id) + + upPrj, err := s.projects.Update(id, cfgArg, s.sessions.GetID(c)) + if err != nil { + common.APIError(c, err.Error()) + return + } + c.JSON(http.StatusOK, upPrj) +} diff --git a/lib/agent/apiv1.go b/lib/agent/apiv1.go index 3e742f5..36e5a54 100644 --- a/lib/agent/apiv1.go +++ b/lib/agent/apiv1.go @@ -8,7 +8,7 @@ import ( "github.com/iotbzh/xds-agent/lib/xdsconfig" ) -const apiBaseUrl = "/api/v1" +const apiBaseURL = "/api/v1" // APIService . type APIService struct { @@ -21,7 +21,7 @@ type APIService struct { func NewAPIV1(ctx *Context) *APIService { s := &APIService{ Context: ctx, - apiRouter: ctx.webServer.router.Group(apiBaseUrl), + apiRouter: ctx.webServer.router.Group(apiBaseURL), serverIndex: 0, } @@ -34,6 +34,7 @@ func NewAPIV1(ctx *Context) *APIService { s.apiRouter.GET("/projects", s.getProjects) s.apiRouter.GET("/projects/:id", s.getProject) + s.apiRouter.PUT("/projects/:id", s.updateProject) s.apiRouter.POST("/projects", s.addProject) s.apiRouter.POST("/projects/sync/:id", s.syncProject) s.apiRouter.DELETE("/projects/:id", s.delProject) @@ -80,7 +81,7 @@ func (s *APIService) AddXdsServer(cfg xdsconfig.XDSServerConf) (*XdsServer, erro // Create a new server object if cfg.APIBaseURL == "" { - cfg.APIBaseURL = apiBaseUrl + cfg.APIBaseURL = apiBaseURL } if cfg.APIPartialURL == "" { cfg.APIPartialURL = "/server/" + strconv.Itoa(s.serverIndex) diff --git a/lib/agent/events.go b/lib/agent/events.go index 9ff72ac..ccf8ddc 100644 --- a/lib/agent/events.go +++ b/lib/agent/events.go @@ -71,7 +71,7 @@ func (e *Events) UnRegister(evName, sessionID string) error { } // Emit Used to manually emit an event -func (e *Events) Emit(evName string, data interface{}) error { +func (e *Events) Emit(evName string, data interface{},fromSid string) error { var firstErr error if _, ok := e.eventsMap[evName]; !ok { @@ -93,9 +93,10 @@ func (e *Events) Emit(evName string, data interface{}) error { continue } msg := apiv1.EventMsg{ - Time: time.Now().String(), - Type: evName, - Data: data, + Time: time.Now().String(), + FromSessionID: fromSid, + Type: evName, + Data: data, } e.Log.Debugf("Emit Event %s: %v", evName, sid) if err := (*so).Emit(evName, msg); err != nil { diff --git a/lib/agent/project-interface.go b/lib/agent/project-interface.go index c9e9ec5..0d6bb1a 100644 --- a/lib/agent/project-interface.go +++ b/lib/agent/project-interface.go @@ -4,11 +4,12 @@ import "github.com/iotbzh/xds-agent/lib/apiv1" // IPROJECT Project interface type IPROJECT interface { - Add(cfg apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) // Add a new project - Delete() error // Delete a project - GetProject() *apiv1.ProjectConfig // Get project public configuration - UpdateProject(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) // Update project configuration - GetServer() *XdsServer // Get XdsServer that holds this project - Sync() error // Force project files synchronization - IsInSync() (bool, error) // Check if project files are in-sync + Add(cfg apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) // Add a new project + Setup(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) // Local setup of the project + Delete() error // Delete a project + GetProject() *apiv1.ProjectConfig // Get project public configuration + Update(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) // Update project configuration + GetServer() *XdsServer // Get XdsServer that holds this project + Sync() error // Force project files synchronization + IsInSync() (bool, error) // Check if project files are in-sync } diff --git a/lib/agent/project-pathmap.go b/lib/agent/project-pathmap.go index 7a96e6e..3c87770 100644 --- a/lib/agent/project-pathmap.go +++ b/lib/agent/project-pathmap.go @@ -69,7 +69,7 @@ func (p *PathMap) Add(cfg apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { // Send request to create folder on XDS server side err = p.server.FolderAdd(fld, p.folder) if err != nil { - return nil, fmt.Errorf("Folders mapping verification failure.\n%v", err) + return nil, err } // 2nd part of sanity checker @@ -98,16 +98,30 @@ func (p *PathMap) GetProject() *apiv1.ProjectConfig { return &prj } -// UpdateProject Set project config -func (p *PathMap) UpdateProject(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { +// Setup Setup local project config +func (p *PathMap) Setup(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { p.folder = p.server.ProjectToFolder(prj) np := p.GetProject() - if err := p.events.Emit(apiv1.EVTProjectChange, np); err != nil { + if err := p.events.Emit(apiv1.EVTProjectChange, np, ""); err != nil { return np, err } return np, nil } +// Update Update some field of a project +func (p *PathMap) Update(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { + if p.folder.ID != prj.ID { + return nil, fmt.Errorf("Invalid id") + } + + err := p.server.FolderUpdate(p.server.ProjectToFolder(prj), p.folder) + if err != nil { + return nil, err + } + + return p.GetProject(), nil +} + // GetServer Get the XdsServer that holds this project func (p *PathMap) GetServer() *XdsServer { return p.server diff --git a/lib/agent/project-st.go b/lib/agent/project-st.go index e2cd3cb..c4e8fce 100644 --- a/lib/agent/project-st.go +++ b/lib/agent/project-st.go @@ -1,6 +1,8 @@ package agent import ( + "fmt" + "github.com/iotbzh/xds-agent/lib/apiv1" st "github.com/iotbzh/xds-agent/lib/syncthing" ) @@ -56,8 +58,8 @@ func (p *STProject) Add(cfg apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { p.Log.Errorf("Project ID in XDSServer and local ST differ: %s != %s", svrPrj.ID, locPrj.ID) } - // Use Update function to setup remains fields - return p.UpdateProject(*svrPrj) + // Use Setup function to setup remains fields + return p.Setup(*svrPrj) } // Delete a project @@ -77,16 +79,16 @@ func (p *STProject) GetProject() *apiv1.ProjectConfig { return &prj } -// UpdateProject Update project config -func (p *STProject) UpdateProject(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { +// Setup Setup local project config +func (p *STProject) Setup(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { // Update folder p.folder = p.server.ProjectToFolder(prj) svrPrj := p.GetProject() // Register events to update folder status // Register to XDS Server events - p.server.EventOn("event:FolderStateChanged", "", p._cbServerFolderChanged) - if err := p.server.EventRegister("FolderStateChanged", svrPrj.ID); err != nil { + p.server.EventOn("event:folder-state-change", "", p._cbServerFolderChanged) + if err := p.server.EventRegister("folder-state-change", svrPrj.ID); err != nil { p.Log.Warningf("XDS Server EventRegister failed: %v", err) return svrPrj, err } @@ -103,6 +105,21 @@ func (p *STProject) UpdateProject(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig return svrPrj, nil } +// Update Update some field of a project +func (p *STProject) Update(prj apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { + + if p.folder.ID != prj.ID { + return nil, fmt.Errorf("Invalid id") + } + + err := p.server.FolderUpdate(p.server.ProjectToFolder(prj), p.folder) + if err != nil { + return nil, err + } + + return p.GetProject(), nil +} + // GetServer Get the XdsServer that holds this project func (p *STProject) GetServer() *XdsServer { return p.server @@ -142,7 +159,7 @@ func (p *STProject) _cbServerFolderChanged(pData interface{}, data interface{}) p.folder.DataCloudSync.STSvrIsInSync = evt.Folder.IsInSync p.folder.DataCloudSync.STSvrStatus = evt.Folder.Status - if err := p.events.Emit(apiv1.EVTProjectChange, p.server.FolderToProject(*p.folder)); err != nil { + if err := p.events.Emit(apiv1.EVTProjectChange, p.server.FolderToProject(*p.folder), ""); err != nil { p.Log.Warningf("Cannot notify project change (from server): %v", err) } } @@ -181,7 +198,7 @@ func (p *STProject) _cbLocalSTEvents(ev st.Event, data *st.EventsCBData) { p.folder.DataCloudSync.STLocIsInSync = inSync p.folder.DataCloudSync.STLocStatus = sts - if err := p.events.Emit(apiv1.EVTProjectChange, p.server.FolderToProject(*p.folder)); err != nil { + if err := p.events.Emit(apiv1.EVTProjectChange, p.server.FolderToProject(*p.folder), ""); err != nil { p.Log.Warningf("Cannot notify project change (local): %v", err) } } diff --git a/lib/agent/projects.go b/lib/agent/projects.go index f089882..966c231 100644 --- a/lib/agent/projects.go +++ b/lib/agent/projects.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/franciscocpg/reflectme" "github.com/iotbzh/xds-agent/lib/apiv1" "github.com/iotbzh/xds-agent/lib/syncthing" "github.com/syncthing/syncthing/lib/sync" @@ -119,14 +120,14 @@ func (p *Projects) GetProjectArrUnsafe() []apiv1.ProjectConfig { } // Add adds a new folder -func (p *Projects) Add(newF apiv1.ProjectConfig) (*apiv1.ProjectConfig, error) { +func (p *Projects) Add(newF apiv1.ProjectConfig, fromSid string) (*apiv1.ProjectConfig, error) { prj, err := p.createUpdate(newF, true, false) if err != nil { return prj, err } // Notify client with event - if err := p.events.Emit(apiv1.EVTProjectAdd, *prj); err != nil { + if err := p.events.Emit(apiv1.EVTProjectAdd, *prj, fromSid); err != nil { p.Log.Warningf("Cannot notify project deletion: %v", err) } @@ -190,7 +191,7 @@ func (p *Projects) createUpdate(newF apiv1.ProjectConfig, create bool, initial b } } else { // Just update project config - if newPrj, err = fld.UpdateProject(newF); err != nil { + if newPrj, err = fld.Setup(newF); err != nil { newF.Status = apiv1.StatusErrorConfig log.Printf("ERROR Updating project: %v\n", err) return newPrj, err @@ -217,7 +218,7 @@ func (p *Projects) createUpdate(newF apiv1.ProjectConfig, create bool, initial b } // Delete deletes a specific folder -func (p *Projects) Delete(id string) (apiv1.ProjectConfig, error) { +func (p *Projects) Delete(id, fromSid string) (apiv1.ProjectConfig, error) { var err error pjMutex.Lock() @@ -238,7 +239,7 @@ func (p *Projects) Delete(id string) (apiv1.ProjectConfig, error) { delete(p.projects, id) // Notify client with event - if err := p.events.Emit(apiv1.EVTProjectDelete, *prj); err != nil { + if err := p.events.Emit(apiv1.EVTProjectDelete, *prj, fromSid); err != nil { p.Log.Warningf("Cannot notify project deletion: %v", err) } @@ -262,3 +263,50 @@ func (p *Projects) IsProjectInSync(id string) (bool, error) { } return (*fc).IsInSync() } + +// Update Update some field of a project +func (p *Projects) Update(id string, prj apiv1.ProjectConfig, fromSid string) (*apiv1.ProjectConfig, error) { + + pjMutex.Lock() + defer pjMutex.Unlock() + + fc, exist := p.projects[id] + if !exist { + return nil, fmt.Errorf("Unknown id") + } + + // Copy current in a new object to change nothing in case of an error rises + newFld := apiv1.ProjectConfig{} + reflectme.Copy((*fc).GetProject(), &newFld) + + // Only update some fields + dirty := false + for _, fieldName := range apiv1.ProjectConfigUpdatableFields { + valNew, err := reflectme.GetField(prj, fieldName) + if err == nil { + valCur, err := reflectme.GetField(newFld, fieldName) + if err == nil && valNew != valCur { + err = reflectme.SetField(&newFld, fieldName, valNew) + if err != nil { + return nil, err + } + dirty = true + } + } + } + + if !dirty { + return &newFld, nil + } + + upPrj, err := (*fc).Update(newFld) + if err != nil { + return nil, err + } + + // Notify client with event + if err := p.events.Emit(apiv1.EVTProjectChange, *upPrj, fromSid); err != nil { + p.Log.Warningf("Cannot notify project change: %v", err) + } + return upPrj, err +} diff --git a/lib/agent/sessions.go b/lib/agent/sessions.go index 7347480..3d8b0f4 100644 --- a/lib/agent/sessions.go +++ b/lib/agent/sessions.go @@ -125,6 +125,14 @@ func (s *Sessions) Get(c *gin.Context) *ClientSession { return nil } +// GetID returns the session or an empty string +func (s *Sessions) GetID(c *gin.Context) string { + if sess := s.Get(c); sess != nil { + return sess.ID + } + return "" +} + // IOSocketGet Get socketio definition from sid func (s *Sessions) IOSocketGet(sid string) *socketio.Socket { s.mutex.Lock() diff --git a/lib/agent/xdsserver.go b/lib/agent/xdsserver.go index 73a5bd9..7b03579 100644 --- a/lib/agent/xdsserver.go +++ b/lib/agent/xdsserver.go @@ -64,9 +64,12 @@ type XdsBuilderConfig struct { type XdsFolderType string const ( - XdsTypePathMap = "PathMap" + // XdsTypePathMap Path Mapping folder type + XdsTypePathMap = "PathMap" + // XdsTypeCloudSync Cloud synchronization (AKA syncthing) folder type XdsTypeCloudSync = "CloudSync" - XdsTypeCifsSmb = "CIFS" + // XdsTypeCifsSmb CIFS (AKA samba) folder type + XdsTypeCifsSmb = "CIFS" ) // XdsFolderConfig XdsServer folder config @@ -78,6 +81,8 @@ type XdsFolderConfig struct { Status string `json:"status"` IsInSync bool `json:"isInSync"` DefaultSdk string `json:"defaultSdk"` + ClientData string `json:"clientData"` // free form field that can used by client + // Specific data depending on which Type is used DataPathMap XdsPathMapConfig `json:"dataPathMap,omitempty"` DataCloudSync XdsCloudSyncConfig `json:"dataCloudSync,omitempty"` @@ -112,7 +117,7 @@ type XdsEventFolderChange struct { Folder XdsFolderConfig `json:"folder"` } -// Event emitter callback +// EventCB Event emitter callback type EventCB func(privData interface{}, evtData interface{}) error // caller Used to chain event listeners @@ -241,6 +246,11 @@ func (xs *XdsServer) FolderSync(id string) error { return xs.client.HTTPPost("/folders/sync/"+id, "") } +// FolderUpdate Send PUT request to update a folder +func (xs *XdsServer) FolderUpdate(fld *XdsFolderConfig, resFld *XdsFolderConfig) error { + return xs.client.Put("/folders/"+fld.ID, fld, resFld) +} + // SetAPIRouterGroup . func (xs *XdsServer) SetAPIRouterGroup(r *gin.RouterGroup) { xs.apiRouter = r @@ -334,7 +344,7 @@ func (xs *XdsServer) EventOn(evName string, privData interface{}, f EventCB) (uu // FIXME: use generic type: data interface{} instead of data XdsEventFolderChange var err error - if evName == "event:FolderStateChanged" { + if evName == "event:folder-state-change" { err = xs.ioSock.On(evn, func(data XdsEventFolderChange) error { xs.sockEventsLock.Lock() sEvts := make([]*caller, len(xs.sockEvents[evn])) @@ -400,6 +410,7 @@ func (xs *XdsServer) ProjectToFolder(pPrj apiv1.ProjectConfig) *XdsFolderConfig if pPrj.Type == XdsTypeCloudSync { stID, _ = xs.SThg.IDGet() } + // TODO: limit ClientData size and gzip it (see https://golang.org/pkg/compress/gzip/) fPrj := XdsFolderConfig{ ID: pPrj.ID, Label: pPrj.Label, @@ -408,6 +419,7 @@ func (xs *XdsServer) ProjectToFolder(pPrj apiv1.ProjectConfig) *XdsFolderConfig Status: pPrj.Status, IsInSync: pPrj.IsInSync, DefaultSdk: pPrj.DefaultSdk, + ClientData: pPrj.ClientData, DataPathMap: XdsPathMapConfig{ ServerPath: pPrj.ServerPath, }, @@ -457,6 +469,7 @@ func (xs *XdsServer) FolderToProject(fPrj XdsFolderConfig) apiv1.ProjectConfig { Status: sts, IsInSync: inSync, DefaultSdk: fPrj.DefaultSdk, + ClientData: fPrj.ClientData, } return pPrj } @@ -628,7 +641,7 @@ func (xs *XdsServer) _NotifyState() { ConnRetry: xs.ConnRetry, Connected: xs.Connected, } - if err := xs.events.Emit(apiv1.EVTServerConfig, evSts); err != nil { + if err := xs.events.Emit(apiv1.EVTServerConfig, evSts, ""); err != nil { xs.Log.Warningf("Cannot notify XdsServer state change: %v", err) } } diff --git a/lib/apiv1/events.go b/lib/apiv1/events.go index cdd0889..b2fda62 100644 --- a/lib/apiv1/events.go +++ b/lib/apiv1/events.go @@ -40,9 +40,10 @@ var EVTAllList = []string{ // EventMsg Event message send over Websocket, data format depend to Type (see DecodeXXX function) type EventMsg struct { - Time string `json:"time"` - Type string `json:"type"` - Data interface{} `json:"data"` + Time string `json:"time"` // Timestamp + FromSessionID string `json:"sessionID"` // Session ID of client that emits this event + Type string `json:"type"` // Data type + Data interface{} `json:"data"` // Data } // DecodeServerCfg Helper to decode Data field type ServerCfg diff --git a/lib/apiv1/projects.go b/lib/apiv1/projects.go index d76fa09..b1e64c8 100644 --- a/lib/apiv1/projects.go +++ b/lib/apiv1/projects.go @@ -29,4 +29,10 @@ type ProjectConfig struct { Status string `json:"status"` IsInSync bool `json:"isInSync"` DefaultSdk string `json:"defaultSdk"` + ClientData string `json:"clientData"` // free form field that can used by client +} + +// ProjectConfigUpdatableFields List fields that can be updated using Update function +var ProjectConfigUpdatableFields = []string{ + "Label", "DefaultSdk", "ClientData", } diff --git a/webapp/.stylelintrc.json b/webapp/.stylelintrc.json index 9732970..8132883 100644 --- a/webapp/.stylelintrc.json +++ b/webapp/.stylelintrc.json @@ -63,7 +63,7 @@ "selector-pseudo-element-colon-notation": "double", "selector-pseudo-element-no-unknown": true, "selector-type-case": "lower", - "selector-max-id": 0, + "selector-max-id": 1, "no-missing-end-of-source-newline": true, diff --git a/webapp/src/app/@core-xds/services/alert.service.spec.ts b/webapp/src/app/@core-xds/services/alert.service.spec.ts index b3d364c..2de2ac3 100644 --- a/webapp/src/app/@core-xds/services/alert.service.spec.ts +++ b/webapp/src/app/@core-xds/services/alert.service.spec.ts @@ -3,13 +3,13 @@ import { TestBed, inject } from '@angular/core/testing'; import { AlertService } from './alert.service'; describe('AlertService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [AlertService] - }); + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AlertService], }); + }); - it('should be created', inject([AlertService], (service: AlertService) => { - expect(service).toBeTruthy(); - })); + it('should be created', inject([AlertService], (service: AlertService) => { + expect(service).toBeTruthy(); + })); }); diff --git a/webapp/src/app/@core-xds/services/alert.service.ts b/webapp/src/app/@core-xds/services/alert.service.ts index c15e176..23a5e5d 100644 --- a/webapp/src/app/@core-xds/services/alert.service.ts +++ b/webapp/src/app/@core-xds/services/alert.service.ts @@ -31,7 +31,7 @@ export class AlertService { public error(msg: string, dismissTime?: number) { this.add({ - type: 'error', msg: msg, dismissible: true, dismissTimeout: dismissTime + type: 'error', msg: msg, dismissible: true, dismissTimeout: dismissTime, }); } diff --git a/webapp/src/app/@core-xds/services/build-settings.service.ts b/webapp/src/app/@core-xds/services/build-settings.service.ts new file mode 100644 index 0000000..cb52ce3 --- /dev/null +++ b/webapp/src/app/@core-xds/services/build-settings.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@angular/core'; +import { CookieService } from 'ngx-cookie'; +import { Observable } from 'rxjs/Observable'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export interface IBuildSettings { + subpath: string; + cmdClean: string; + cmdPrebuild: string; + cmdBuild: string; + cmdPopulate: string; + cmdArgs: string[]; + envVars: string[]; +} + +@Injectable() +export class BuildSettingsService { + public settings$: Observable<IBuildSettings>; + + private settingsSubject: BehaviorSubject<IBuildSettings>; + private settingsStore: IBuildSettings; + + constructor( + private cookie: CookieService, + ) { + this._load(); + } + + // Load build settings from cookie + private _load() { + // Try to retrieve previous config from cookie + const cookConf = this.cookie.getObject('xds-build-settings'); + if (cookConf != null) { + this.settingsStore = <IBuildSettings>cookConf; + } else { + // Set default config + this.settingsStore = { + subpath: '', + cmdClean: 'rm -rf build && echo Done', + cmdPrebuild: 'mkdir -p build && cd build && cmake ..', + cmdBuild: 'cd build && make', + cmdPopulate: 'cd build && make remote-target-populate', + cmdArgs: [], + envVars: [], + }; + } + } + + // Save config into cookie + private _save() { + // Notify subscribers + this.settingsSubject.next(Object.assign({}, this.settingsStore)); + + const cfg = Object.assign({}, this.settingsStore); + this.cookie.putObject('xds-build-settings', cfg); + } + + // Get whole config values + get(): IBuildSettings { + return this.settingsStore; + } + + // Get whole config values + set(bs: IBuildSettings) { + this.settingsStore = bs; + this._save(); + } + + get subpath(): string { + return this.settingsStore.subpath; + } + + set subpath(p: string) { + this.settingsStore.subpath = p; + this._save(); + } + +} diff --git a/webapp/src/app/@core-xds/services/config.service.spec.ts b/webapp/src/app/@core-xds/services/config.service.spec.ts index a20d4ba..f39b9d9 100644 --- a/webapp/src/app/@core-xds/services/config.service.spec.ts +++ b/webapp/src/app/@core-xds/services/config.service.spec.ts @@ -5,7 +5,7 @@ import { ConfigService } from './config.service'; describe('ConfigService', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [ConfigService] + providers: [ConfigService], }); }); diff --git a/webapp/src/app/@core-xds/services/project.service.spec.ts b/webapp/src/app/@core-xds/services/project.service.spec.ts index b8edfc7..0924a73 100644 --- a/webapp/src/app/@core-xds/services/project.service.spec.ts +++ b/webapp/src/app/@core-xds/services/project.service.spec.ts @@ -5,7 +5,7 @@ import { ProjectService } from './project.service'; describe('ProjectService', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [ProjectService] + providers: [ProjectService], }); }); diff --git a/webapp/src/app/@core-xds/services/project.service.ts b/webapp/src/app/@core-xds/services/project.service.ts index 8aeed80..94469fe 100644 --- a/webapp/src/app/@core-xds/services/project.service.ts +++ b/webapp/src/app/@core-xds/services/project.service.ts @@ -1,4 +1,4 @@ -import { Injectable, SecurityContext } from '@angular/core'; +import { Injectable, SecurityContext, isDevMode } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @@ -15,12 +15,12 @@ export type ProjectTypeEnum = '' | 'PathMap' | 'CloudSync'; export const ProjectType = { UNSET: '', NATIVE_PATHMAP: 'PathMap', - SYNCTHING: 'CloudSync' + SYNCTHING: 'CloudSync', }; export const ProjectTypes = [ { value: ProjectType.NATIVE_PATHMAP, display: 'Path mapping' }, - { value: ProjectType.SYNCTHING, display: 'Cloud Sync' } + { value: ProjectType.SYNCTHING, display: 'Cloud Sync' }, ]; export const ProjectStatus = { @@ -28,9 +28,18 @@ export const ProjectStatus = { Disable: 'Disable', Enable: 'Enable', Pause: 'Pause', - Syncing: 'Syncing' + Syncing: 'Syncing', }; +export interface IUISettings { + subpath: string; + cmdClean: string; + cmdPrebuild: string; + cmdBuild: string; + cmdPopulate: string; + cmdArgs: string[]; + envVars: string[]; +} export interface IProject { id?: string; serverId: string; @@ -45,95 +54,84 @@ export interface IProject { isExpanded?: boolean; visible?: boolean; defaultSdkID?: string; + uiSettings?: IUISettings; } +const defaultUISettings: IUISettings = { + subpath: '', + cmdClean: 'rm -rf build && echo Done', + cmdPrebuild: 'mkdir -p build && cd build && cmake ..', + cmdBuild: 'cd build && make', + cmdPopulate: 'cd build && make remote-target-populate', + cmdArgs: [], + envVars: [], +}; + @Injectable() export class ProjectService { - public Projects$: Observable<IProject[]>; + projects$: Observable<IProject[]>; + curProject$: Observable<IProject>; private _prjsList: IProject[] = []; - private current: IProject; private prjsSubject = <BehaviorSubject<IProject[]>>new BehaviorSubject(this._prjsList); + private _current: IProject; + private curPrjSubject = <BehaviorSubject<IProject>>new BehaviorSubject(this._current); constructor(private xdsSvr: XDSAgentService) { - this.current = null; - this.Projects$ = this.prjsSubject.asObservable(); + this._current = null; + this.projects$ = this.prjsSubject.asObservable(); + this.curProject$ = this.curPrjSubject.asObservable(); + // Load initial projects list this.xdsSvr.getProjects().subscribe((projects) => { this._prjsList = []; projects.forEach(p => { this._addProject(p, true); }); - this.prjsSubject.next(Object.assign([], this._prjsList)); - }); - // Update Project data - this.xdsSvr.ProjectState$.subscribe(prj => { - const i = this._getProjectIdx(prj.id); - if (i >= 0) { - // XXX for now, only isInSync and status may change - this._prjsList[i].isInSync = prj.isInSync; - this._prjsList[i].status = prj.status; - this._prjsList[i].isUsable = this._isUsableProject(prj); - this.prjsSubject.next(Object.assign([], this._prjsList)); + // TODO: get previous val from xds-config service / cookie + if (this._prjsList.length > 0) { + this._current = this._prjsList[0]; + this.curPrjSubject.next(this._current); } - }); - // Add listener on create and delete project events - this.xdsSvr.addEventListener('event:project-add', (ev) => { - if (ev && ev.data && ev.data.id) { - this._addProject(ev.data); - } else { - console.log('Warning: received events with unknown data: ev=', ev); - } - }); - this.xdsSvr.addEventListener('event:project-delete', (ev) => { - if (ev && ev.data && ev.data.id) { - const idx = this._prjsList.findIndex(item => item.id === ev.data.id); - if (idx === -1) { - console.log('Warning: received events on unknown project id: ev=', ev); - return; - } - this._prjsList.splice(idx, 1); - this.prjsSubject.next(Object.assign([], this._prjsList)); - } else { - console.log('Warning: received events with unknown data: ev=', ev); - } + this.prjsSubject.next(this._prjsList); }); + // Add listener on projects creation, deletion and change events + this.xdsSvr.onProjectAdd().subscribe(prj => this._addProject(prj)); + this.xdsSvr.onProjectDelete().subscribe(prj => this._delProject(prj)); + this.xdsSvr.onProjectChange().subscribe(prj => this._updateProject(prj)); } - public setCurrent(s: IProject) { - this.current = s; + setCurrent(p: IProject): IProject | undefined { + if (!p) { + this._current = null; + return undefined; + } + return this.setCurrentById(p.id); } - public getCurrent(): IProject { - return this.current; + setCurrentById(id: string): IProject | undefined { + const p = this._prjsList.find(item => item.id === id); + if (p) { + this._current = p; + this.curPrjSubject.next(this._current); + } + return this._current; } - public getCurrentId(): string { - if (this.current && this.current.id) { - return this.current.id; - } - return ''; + getCurrent(): IProject { + return this._current; } - Add(prj: IProject): Observable<IProject> { - const xdsPrj: IXDSProjectConfig = { - id: '', - serverId: prj.serverId, - label: prj.label || '', - clientPath: prj.pathClient.trim(), - serverPath: prj.pathServer, - type: prj.type, - defaultSdkID: prj.defaultSdkID, - }; + add(prj: IProject): Observable<IProject> { // Send config to XDS server - return this.xdsSvr.addProject(xdsPrj) + return this.xdsSvr.addProject(this._convToIXdsProject(prj)) .map(xp => this._convToIProject(xp)); } - Delete(prj: IProject): Observable<IProject> { + delete(prj: IProject): Observable<IProject> { const idx = this._getProjectIdx(prj.id); const delPrj = prj; if (idx === -1) { @@ -143,7 +141,7 @@ export class ProjectService { .map(res => delPrj); } - Sync(prj: IProject): Observable<string> { + sync(prj: IProject): Observable<string> { const idx = this._getProjectIdx(prj.id); if (idx === -1) { throw new Error('Invalid project id (id=' + prj.id + ')'); @@ -151,6 +149,17 @@ export class ProjectService { return this.xdsSvr.syncProject(prj.id); } + setSettings(prj: IProject): Observable<IProject> { + return this.xdsSvr.updateProject(this._convToIXdsProject(prj)) + .map(xp => this._convToIProject(xp)); + } + + getDefaultSettings(): IUISettings { + return defaultUISettings; + } + + /*** Private functions ***/ + private _isUsableProject(p) { return p && p.isInSync && (p.status === ProjectStatus.Enable) && @@ -161,7 +170,27 @@ export class ProjectService { return this._prjsList.findIndex((item) => item.id === id); } + + private _convToIXdsProject(prj: IProject): IXDSProjectConfig { + const xPrj: IXDSProjectConfig = { + id: prj.id || '', + serverId: prj.serverId, + label: prj.label || '', + clientPath: prj.pathClient.trim(), + serverPath: prj.pathServer, + type: prj.type, + defaultSdkID: prj.defaultSdkID, + clientData: JSON.stringify(prj.uiSettings || defaultUISettings), + }; + return xPrj; + } + private _convToIProject(rPrj: IXDSProjectConfig): IProject { + let settings = defaultUISettings; + if (rPrj.clientData && rPrj.clientData !== '') { + settings = JSON.parse(rPrj.clientData); + } + // Convert XDSFolderConfig to IProject const pp: IProject = { id: rPrj.id, @@ -175,14 +204,15 @@ export class ProjectService { isUsable: this._isUsableProject(rPrj), defaultSdkID: rPrj.defaultSdkID, serverPrjDef: Object.assign({}, rPrj), // do a copy + uiSettings: settings, }; return pp; } - private _addProject(rPrj: IXDSProjectConfig, noNext?: boolean): IProject { + private _addProject(prj: IXDSProjectConfig, noNext?: boolean): IProject { // Convert XDSFolderConfig to IProject - const pp = this._convToIProject(rPrj); + const pp = this._convToIProject(prj); // add new project this._prjsList.push(pp); @@ -199,9 +229,38 @@ export class ProjectService { }); if (!noNext) { - this.prjsSubject.next(Object.assign([], this._prjsList)); + this.prjsSubject.next(this._prjsList); } return pp; } + + private _delProject(prj: IXDSProjectConfig) { + const idx = this._prjsList.findIndex(item => item.id === prj.id); + if (idx === -1) { + if (isDevMode) { + /* tslint:disable:no-console */ + console.log('Warning: Try to delete project unknown id: prj=', prj); + } + return; + } + const delId = this._prjsList[idx].id; + this._prjsList.splice(idx, 1); + if (this._prjsList[idx].id === this._current.id) { + this.setCurrent(this._prjsList[0]); + } + this.prjsSubject.next(this._prjsList); + } + + private _updateProject(prj: IXDSProjectConfig) { + const i = this._getProjectIdx(prj.id); + if (i >= 0) { + // XXX for now, only isInSync and status may change + this._prjsList[i].isInSync = prj.isInSync; + this._prjsList[i].status = prj.status; + this._prjsList[i].isUsable = this._isUsableProject(prj); + this.prjsSubject.next(this._prjsList); + } + } + } diff --git a/webapp/src/app/@core-xds/services/xds-config.service.ts b/webapp/src/app/@core-xds/services/xds-config.service.ts index 7559673..2f751a7 100644 --- a/webapp/src/app/@core-xds/services/xds-config.service.ts +++ b/webapp/src/app/@core-xds/services/xds-config.service.ts @@ -47,7 +47,7 @@ export class XDSConfigService { } getCurServer(): IXDServerCfg { - return Object.assign({}, this._curServer); + return this._curServer; } setCurServer(svr: IXDServerCfg): Observable<IXDServerCfg> { @@ -58,7 +58,7 @@ export class XDSConfigService { .map(cfg => this._updateCurServer()) .catch(err => { this._curServer.connected = false; - this.curServer$.next(Object.assign({}, this._curServer)); + this.curServer$.next(this._curServer); return Observable.throw(err); }); } else { @@ -66,7 +66,7 @@ export class XDSConfigService { return this.xdsAgentSvr.setServerRetry(curSvr.id, svr.connRetry) .map(cfg => this._updateCurServer()) .catch(err => { - this.curServer$.next(Object.assign({}, this._curServer)); + this.curServer$.next(this._curServer); return Observable.throw(err); }); } @@ -76,7 +76,7 @@ export class XDSConfigService { private _updateCurServer() { this._curServer = this._getCurServer(); - this.curServer$.next(Object.assign({}, this._curServer)); + this.curServer$.next(this._curServer); } private _getCurServer(url?: string): IXDServerCfg { diff --git a/webapp/src/app/@core-xds/services/xdsagent.service.ts b/webapp/src/app/@core-xds/services/xdsagent.service.ts index 56e493f..06ca557 100644 --- a/webapp/src/app/@core-xds/services/xdsagent.service.ts +++ b/webapp/src/app/@core-xds/services/xdsagent.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Inject } from '@angular/core'; +import { Injectable, Inject, isDevMode } from '@angular/core'; import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import { DOCUMENT } from '@angular/common'; import { Observable } from 'rxjs/Observable'; @@ -44,6 +44,7 @@ export interface IXDSProjectConfig { status?: string; isInSync?: boolean; defaultSdkID: string; + clientData?: string; } export interface IXDSVer { @@ -107,12 +108,16 @@ export class XDSAgentService { public XdsConfig$: Observable<IXDSConfig>; public Status$: Observable<IAgentStatus>; - public ProjectState$ = <Subject<IXDSProjectConfig>>new Subject(); public CmdOutput$ = <Subject<ICmdOutput>>new Subject(); public CmdExit$ = <Subject<ICmdExit>>new Subject(); + protected projectAdd$ = new Subject<IXDSProjectConfig>(); + protected projectDel$ = new Subject<IXDSProjectConfig>(); + protected projectChange$ = new Subject<IXDSProjectConfig>(); + private baseUrl: string; private wsUrl: string; + private httpSessionID: string; private _config = <IXDSConfig>{ servers: [] }; private _status = { connected: false, servers: [] }; @@ -130,14 +135,25 @@ export class XDSAgentService { const originUrl = this.document.location.origin; this.baseUrl = originUrl + '/api/v1'; - const re = originUrl.match(/http[s]?:\/\/([^\/]*)[\/]?/); - if (re === null || re.length < 2) { - console.error('ERROR: cannot determine Websocket url'); - } else { - this.wsUrl = 'ws://' + re[1]; - this._handleIoSocket(); - this._RegisterEvents(); - } + // Retrieve Session ID / token + this.http.get(this.baseUrl + '/version', { observe: 'response' }) + .subscribe( + resp => { + this.httpSessionID = resp.headers.get('xds-agent-sid'); + + const re = originUrl.match(/http[s]?:\/\/([^\/]*)[\/]?/); + if (re === null || re.length < 2) { + console.error('ERROR: cannot determine Websocket url'); + } else { + this.wsUrl = 'ws://' + re[1]; + this._handleIoSocket(); + this._RegisterEvents(); + } + }, + err => { + /* tslint:disable:no-console */ + console.error('ERROR while retrieving session id:', err); + }); } private _NotifyXdsAgentState(sts: boolean) { @@ -182,6 +198,8 @@ export class XDSAgentService { console.error('WS error:', err); }); + // XDS Events decoding + this.socket.on('make:output', data => { this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data)); }); @@ -198,8 +216,6 @@ export class XDSAgentService { this.CmdExit$.next(Object.assign({}, <ICmdExit>data)); }); - // Events - // (project-add and project-delete events are managed by project.service) this.socket.on('event:server-config', ev => { if (ev && ev.data) { const cfg: IXDServerCfg = ev.data; @@ -212,19 +228,52 @@ export class XDSAgentService { } }); + this.socket.on('event:project-add', (ev) => { + if (ev && ev.data && ev.data.id) { + this.projectAdd$.next(Object.assign({}, ev.data)); + if (ev.sessionID !== this.httpSessionID && ev.data.label) { + this.alert.info('Project "' + ev.data.label + '" has been added by another tool.'); + } + } else if (isDevMode) { + /* tslint:disable:no-console */ + console.log('Warning: received event:project-add with unknown data: ev=', ev); + } + }); + + this.socket.on('event:project-delete', (ev) => { + if (ev && ev.data && ev.data.id) { + this.projectDel$.next(Object.assign({}, ev.data)); + if (ev.sessionID !== this.httpSessionID && ev.data.label) { + this.alert.info('Project "' + ev.data.label + '" has been deleted by another tool.'); + } + } else if (isDevMode) { + console.log('Warning: received event:project-delete with unknown data: ev=', ev); + } + }); + this.socket.on('event:project-state-change', ev => { if (ev && ev.data) { - this.ProjectState$.next(Object.assign({}, ev.data)); + this.projectChange$.next(Object.assign({}, ev.data)); + } else if (isDevMode) { + console.log('Warning: received event:project-state-change with unknown data: ev=', ev); } }); } /** - ** Events + ** Events registration ***/ - addEventListener(ev: string, fn: Function): SocketIOClient.Emitter { - return this.socket.addEventListener(ev, fn); + onProjectAdd(): Observable<IXDSProjectConfig> { + return this.projectAdd$.asObservable(); + } + + onProjectDelete(): Observable<IXDSProjectConfig> { + return this.projectDel$.asObservable(); + } + + onProjectChange(): Observable<IXDSProjectConfig> { + return this.projectChange$.asObservable(); } /** @@ -307,6 +356,10 @@ export class XDSAgentService { return this._delete('/projects/' + id); } + updateProject(cfg: IXDSProjectConfig): Observable<IXDSProjectConfig> { + return this._put('/projects/' + cfg.id, cfg); + } + syncProject(id: string): Observable<string> { return this._post('/projects/sync/' + id, {}); } @@ -337,8 +390,8 @@ export class XDSAgentService { res => { }, error => { this.alert.error('ERROR while registering to all events: ' + error); - } - ); + }, + ); } private _getServer(ID: string): IXDServerCfg { @@ -371,6 +424,12 @@ export class XDSAgentService { return this._decodeError(error); }); } + private _put(url: string, body: any): Observable<any> { + return this.http.put(this.baseUrl + url, JSON.stringify(body), this._attachAuthHeaders()) + .catch((error) => { + return this._decodeError(error); + }); + } private _delete(url: string): Observable<any> { return this.http.delete(this.baseUrl + url, this._attachAuthHeaders()) .catch(this._decodeError); @@ -391,7 +450,10 @@ export class XDSAgentService { } else { e = err.message ? err.message : err.toString(); } - console.log('xdsagent.service - ERROR: ', e); + /* tslint:disable:no-console */ + if (isDevMode) { + console.log('xdsagent.service - ERROR: ', e); + } return Observable.throw(e); } } diff --git a/webapp/src/app/@theme/components/header/header.component.html b/webapp/src/app/@theme/components/header/header.component.html index 5d5eff6..4fa66b8 100644 --- a/webapp/src/app/@theme/components/header/header.component.html +++ b/webapp/src/app/@theme/components/header/header.component.html @@ -1,32 +1,37 @@ -<div class="header-container" - [class.left]="position === 'normal'" - [class.right]="position === 'inverse'"> +<div class="header-container" [class.left]="position === 'normal'" [class.right]="position === 'inverse'"> <div class="logo-containter"> - <a (click)="toggleSidebar()" href="#" class="navigation"><i class="nb-menu"></i></a> -<!-- MODS_XDS + <a (click)="toggleSidebar()" href="#" class="navigation"> + <i class="nb-menu"></i> + </a> + <!-- MODS_XDS <div class="logo" (click)="goToHome()">ngx-<span>admin</span></div> --> - <div class="logo" (click)="goToHome()">XDS <span>dashboard</span></div> + <div class="logo" (click)="goToHome()">XDS + <span>dashboard</span> + </div> </div> -<!-- MODS_XDS + <!-- MODS_XDS <ngx-theme-switcher></ngx-theme-switcher> --> </div> -<nb-actions - size="medium" - class="header-container" - [class.right]="position === 'normal'" - [class.left]="position === 'inverse'"> +<nb-actions size="medium" class="header-container" [class.right]="position === 'normal'" [class.left]="position === 'inverse'"> + <!-- MODS_XDS <nb-action icon="nb-grid-b" class="toggle-layout" (click)="toggleSettings()"></nb-action> +--> <nb-action> <nb-user [menu]="userMenu" [name]="user?.name" [picture]="user?.picture"></nb-user> </nb-action> - <nb-action class="control-item" disabled icon="nb-notifications"></nb-action> -<!-- MODS_XDS + <nb-action class="control-item" disabled icon="nb-notifications"> + </nb-action> + <nb-action icon="fa fa-question-circle-o"> + </nb-action> + + <!-- MODS_XDS <nb-action class="control-item" icon="nb-email"></nb-action> ---> <nb-action class="control-item"> <nb-search type="rotate-layout" (click)="startSearch()"></nb-search> </nb-action> +--> + </nb-actions> diff --git a/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html b/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html new file mode 100644 index 0000000..7dd2ec7 --- /dev/null +++ b/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.html @@ -0,0 +1,60 @@ +<div class="modal-header"> + <span>Build Settings</span> + <button class="close" aria-label="Close" (click)="closeModal()"> + <span aria-hidden="true">×</span> + </button> +</div> + +<div class="modal-body row"> + <div class="col-12"> + <form [formGroup]="settingsProjectForm" (ngSubmit)="onSubmit()"> + + <div class="form-group row"> + <label for="clean-cmd" class="col-sm-3 col-form-label">Clean command</label> + <div class="col-sm-9"> + <input type="text" id="inputCleanCmd" class="form-control" formControlName="cmdClean"> + </div> + </div> + + <div class="form-group row"> + <label for="prebuild-cmd" class="col-sm-3 col-form-label">Pre-Build command</label> + <div class="col-sm-9"> + <input type="text" id="inputPrebuildCmd" class="form-control" formControlName="cmdPrebuild"> + </div> + </div> + + <div class="form-group row"> + <label for="build-cmd" class="col-sm-3 col-form-label">Build command</label> + <div class="col-sm-9"> + <input type="text" id="inputBuildCmd" class="form-control" formControlName="cmdBuild"> + </div> + </div> + + <div class="form-group row"> + <label for="populate-cmd" class="col-sm-3 col-form-label">Populate command</label> + <div class="col-sm-9"> + <input type="text" id="inputPopulateCmd" class="form-control" formControlName="cmdPopulate"> + </div> + </div> + + <div class="form-group row"> + <label for="envvars-cmd" class="col-sm-3 col-form-label">Env variables</label> + <div class="col-sm-9"> + <input type="text" id="inputEnvVars" class="form-control" formControlName="envVars"> + </div> + </div> + + <div class="offset-sm-9 col-sm-9"> + <button class="btn btn-sm btn-hero-secondary" (click)="closeAction=false; resetDefault()">Reset settings</button> + </div> + </form> + </div> +</div> +<div class="modal-footer form-group"> + <div class="col-12"> + <div class="offset-sm-4 col-sm-6"> + <button class="btn btn-md btn-secondary" (click)="closeAction=false; closeModal()"> Cancel </button> + <button class="btn btn-md btn-primary" [disabled]="!settingsProjectForm.valid" (click)="closeAction=true; onSubmit()">Update</button> + </div> + </div> +</div> diff --git a/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.ts b/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.ts new file mode 100644 index 0000000..01c6d1e --- /dev/null +++ b/webapp/src/app/pages/build/build-settings-modal/build-settings-modal.component.ts @@ -0,0 +1,77 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormControl, FormGroup, Validators, ValidationErrors, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms'; + +import { AlertService } from '../../../@core-xds/services/alert.service'; +import { ProjectService, IProject } from '../../../@core-xds/services/project.service'; + + +@Component({ + selector: 'xds-build-settings-modal', + templateUrl: 'build-settings-modal.component.html', +}) + +export class BuildSettingsModalComponent implements OnInit { + // @Input('server-id') serverID: string; + private serverID: string; + + closeAction = false; + userEditedLabel = false; + + settingsProjectForm: FormGroup; + subpathCtrl = new FormControl('', Validators.nullValidator); + + private curPrj: IProject; + + constructor( + private alert: AlertService, + private projectSvr: ProjectService, + private fb: FormBuilder, + private activeModal: NgbActiveModal, + ) { + this.settingsProjectForm = fb.group({ + subpath: this.subpathCtrl, + cmdClean: ['', Validators.required], + cmdPrebuild: ['', Validators.nullValidator], + cmdBuild: ['', Validators.required], + cmdPopulate: ['', Validators.nullValidator], + cmdArgs: ['', Validators.nullValidator], + envVars: ['', Validators.nullValidator], + }); + } + + ngOnInit() { + this.curPrj = this.projectSvr.getCurrent(); + this.settingsProjectForm.patchValue(this.curPrj.uiSettings); + } + + closeModal() { + this.activeModal.close(); + } + + resetDefault() { + this.settingsProjectForm.patchValue(this.projectSvr.getDefaultSettings()); + } + + onSubmit() { + if (!this.closeAction) { + return; + } + + this.curPrj.uiSettings = this.settingsProjectForm.value; + this.projectSvr.setSettings(this.curPrj) + .subscribe(prj => { + this.alert.info('Settings of project "' + prj.label + '" successfully updated.'); + this.closeModal(); + + // Reset Value for the next creation + this.settingsProjectForm.reset(); + }, + err => { + this.alert.error(err, 60); + this.closeModal(); + }); + } + +} diff --git a/webapp/src/app/pages/build/build.component.html b/webapp/src/app/pages/build/build.component.html index a1ef62d..1ce9484 100644 --- a/webapp/src/app/pages/build/build.component.html +++ b/webapp/src/app/pages/build/build.component.html @@ -11,43 +11,48 @@ </nb-actions> </nb-card-body> </div> - <div class="col-md-12 col-lg-12 col-xxxl-6"> + <div class="col-md-12 col-lg-12"> <nb-card size="xlarge"> <nb-tabset fullWidth> - <nb-tab tabTitle="Build"> + <nb-tab tabTitle="Build" style="overflow: hidden;"> - <div class="row" style="margin-top:1em;"> - <!-- FIXME SEB - <button class="btn pull-right " (click)="reset() "> - <span class="fa fa-eraser fa-size-x2"></span> - </button> - --> - <div class="col-md-12 text-center "> - <textarea rows="20" class="textarea-scroll" #scrollOutput>{{ cmdOutput }}</textarea> - </div> + <div class="offset-md-10 col-md-2 right"> + <i class="control-icon fa fa-eraser" (click)="resetOutput()"> + </i> + </div> + <div class="col-md-12 text-center "> + <textarea rows="20" class="textarea-scroll" #scrollOutput>{{ cmdOutput }}</textarea> </div> <nb-card-body> - <nb-actions size="medium" fullWidth> - <nb-action (click)="clean()"> - <i class="fa fa-eraser"></i> - <span>Clean</span> - </nb-action> - <nb-action (click)="preBuild()"> - <i class="nb-list"></i> - <span>Pre-Build</span> - </nb-action> - <nb-action (click)="build()"> - <i class="fa fa-wrench"></i> - <span>Build</span> - </nb-action> - <nb-action (click)="populate()"> - <i class="fa fa-send"></i> - <span>Populate</span> - </nb-action> - </nb-actions> - </nb-card-body> + <nb-actions size="medium" fullWidth> + <nb-action (click)="settingsShow()"> + <i class="fa fa-cog"></i> + <span>Settings</span> + </nb-action> + + <nb-action> + </nb-action> + + <nb-action (click)="clean()"> + <i class="fa fa-eraser"></i> + <span>Clean</span> + </nb-action> + <nb-action (click)="preBuild()"> + <i class="nb-list"></i> + <span>Pre-Build</span> + </nb-action> + <nb-action (click)="build()"> + <i class="fa fa-wrench"></i> + <span>Build</span> + </nb-action> + <nb-action (click)="populate()"> + <i class="fa fa-send"></i> + <span>Populate</span> + </nb-action> + </nb-actions> + </nb-card-body> </nb-tab> diff --git a/webapp/src/app/pages/build/build.component.scss b/webapp/src/app/pages/build/build.component.scss index b256f66..5308f3f 100644 --- a/webapp/src/app/pages/build/build.component.scss +++ b/webapp/src/app/pages/build/build.component.scss @@ -23,6 +23,12 @@ } } +.right { + display: flex; + flex-direction: row-reverse; + padding-right: 30px; +} + nb-action { i { font-size: 2rem; diff --git a/webapp/src/app/pages/build/build.component.spec.ts b/webapp/src/app/pages/build/build.component.spec.ts index 016192c..a676a66 100644 --- a/webapp/src/app/pages/build/build.component.spec.ts +++ b/webapp/src/app/pages/build/build.component.spec.ts @@ -8,7 +8,7 @@ describe('BuildComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ BuildComponent ] + declarations: [ BuildComponent ], }) .compileComponents(); })); diff --git a/webapp/src/app/pages/build/build.component.ts b/webapp/src/app/pages/build/build.component.ts index 5adb9bc..99b7e54 100644 --- a/webapp/src/app/pages/build/build.component.ts +++ b/webapp/src/app/pages/build/build.component.ts @@ -1,11 +1,13 @@ import { Component, ViewEncapsulation, AfterViewChecked, ElementRef, ViewChild, OnInit, Input } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; -import { CookieService } from 'ngx-cookie'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import 'rxjs/add/operator/scan'; import 'rxjs/add/operator/startWith'; +import { BuildSettingsModalComponent } from './build-settings-modal/build-settings-modal.component'; + import { XDSAgentService, ICmdOutput } from '../../@core-xds/services/xdsagent.service'; import { ProjectService, IProject } from '../../@core-xds/services/project.service'; import { AlertService, IAlert } from '../../@core-xds/services/alert.service'; @@ -26,9 +28,6 @@ export class BuildComponent implements OnInit, AfterViewChecked { // @Input() curProject: IProject; @Input() curProject = <IProject>null; - public buildForm: FormGroup; - public subpathCtrl = new FormControl('', Validators.required); - public debugEnable = false; public buildIsCollapsed = false; public cmdOutput: string; public cmdInfo: string; @@ -38,37 +37,16 @@ export class BuildComponent implements OnInit, AfterViewChecked { constructor( private prjSvr: ProjectService, private xdsSvr: XDSAgentService, - private fb: FormBuilder, private alertSvr: AlertService, private sdkSvr: SdkService, - private cookie: CookieService, + private modalService: NgbModal, ) { 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; @@ -88,70 +66,77 @@ export class BuildComponent implements OnInit, AfterViewChecked { }); this._scrollToBottom(); - - // only use for debug - this.debugEnable = (this.cookie.get('debug_build') === '1'); } ngAfterViewChecked() { this._scrollToBottom(); } - reset() { + resetOutput() { this.cmdOutput = ''; } + settingsShow() { + const activeModal = this.modalService.open(BuildSettingsModalComponent, { size: 'lg', container: 'nb-layout' }); + activeModal.componentInstance.modalHeader = 'Large Modal'; + } + clean() { + const curPrj = this.prjSvr.getCurrent(); this._exec( - this.buildForm.value.cmdClean, - this.buildForm.value.subpath, + curPrj.uiSettings.cmdClean, + curPrj.uiSettings.subpath, [], - this.buildForm.value.envVars); + curPrj.uiSettings.envVars.join(' ')); } preBuild() { + const curPrj = this.prjSvr.getCurrent(); this._exec( - this.buildForm.value.cmdPrebuild, - this.buildForm.value.subpath, + curPrj.uiSettings.cmdPrebuild, + curPrj.uiSettings.subpath, [], - this.buildForm.value.envVars); + curPrj.uiSettings.envVars.join(' ')); } build() { + const curPrj = this.prjSvr.getCurrent(); this._exec( - this.buildForm.value.cmdBuild, - this.buildForm.value.subpath, + curPrj.uiSettings.cmdBuild, + curPrj.uiSettings.subpath, [], - this.buildForm.value.envVars + curPrj.uiSettings.envVars.join(' '), ); } populate() { + const curPrj = this.prjSvr.getCurrent(); this._exec( - this.buildForm.value.cmdPopulate, - this.buildForm.value.subpath, + curPrj.uiSettings.cmdPopulate, + curPrj.uiSettings.subpath, [], // args - this.buildForm.value.envVars + curPrj.uiSettings.envVars.join(' '), ); } execCmd() { + const curPrj = this.prjSvr.getCurrent(); this._exec( - this.buildForm.value.cmdArgs, - this.buildForm.value.subpath, + curPrj.uiSettings.cmdArgs.join(' '), + curPrj.uiSettings.subpath, [], - this.buildForm.value.envVars + curPrj.uiSettings.envVars.join(' '), ); } private _exec(cmd: string, dir: string, args: string[], env: string) { + this.curProject = this.prjSvr.getCurrent(); + const prjID = this.curProject.id; + if (!this.curProject) { - this.alertSvr.warning('No active project', true); + return this.alertSvr.warning('No active project', true); } - // const prjID = this.curProject.id; - const prjID = this.prjSvr.getCurrent().id; - this.cmdOutput += this._outputHeader(); const sdkid = this.sdkSvr.getCurrentId(); diff --git a/webapp/src/app/pages/build/build.module.ts b/webapp/src/app/pages/build/build.module.ts index ac1dfab..34f05f2 100644 --- a/webapp/src/app/pages/build/build.module.ts +++ b/webapp/src/app/pages/build/build.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { ThemeModule } from '../../@theme/theme.module'; import { BuildComponent } from './build.component'; +import { BuildSettingsModalComponent } from './build-settings-modal/build-settings-modal.component'; import { ProjectSelectDropdownComponent } from './settings/project-select-dropdown.component'; import { SdkSelectDropdownComponent } from './settings/sdk-select-dropdown.component'; @@ -11,10 +12,12 @@ import { SdkSelectDropdownComponent } from './settings/sdk-select-dropdown.compo ], declarations: [ BuildComponent, + BuildSettingsModalComponent, ProjectSelectDropdownComponent, SdkSelectDropdownComponent, ], entryComponents: [ + BuildSettingsModalComponent, ], }) export class BuildModule { } diff --git a/webapp/src/app/pages/build/settings-modal/build-settings-modal.component.ts b/webapp/src/app/pages/build/settings-modal/build-settings-modal.component.ts new file mode 100644 index 0000000..fd1b904 --- /dev/null +++ b/webapp/src/app/pages/build/settings-modal/build-settings-modal.component.ts @@ -0,0 +1,143 @@ +import { Component, Input, ViewChild, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormControl, FormGroup, Validators, ValidationErrors, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms'; + +// Import RxJs required methods +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/debounceTime'; + +import { AlertService, IAlert } from '../../../@core-xds/services/alert.service'; +import { ProjectService, IProject, ProjectType, ProjectTypes } from '../../../@core-xds/services/project.service'; +import { XDSConfigService } from '../../../@core-xds/services/xds-config.service'; + + +@Component({ + selector: 'xds-build-settings-modal', + templateUrl: 'build-settings-modal.component.html', + styleUrls: ['build-settings-modal.component.scss'], +}) +export class BuildSettingsModalComponent implements OnInit { + // @Input('server-id') serverID: string; + private serverID: string; + + cancelAction = false; + userEditedLabel = false; + projectTypes = ProjectTypes; + + addProjectForm: FormGroup; + typeCtrl: FormControl; + pathCliCtrl: FormControl; + pathSvrCtrl: FormControl; + + constructor( + private alert: AlertService, + private projectSvr: ProjectService, + private XdsConfigSvr: XDSConfigService, + private fb: FormBuilder, + private activeModal: NgbActiveModal, + ) { + // Define types (first one is special/placeholder) + this.projectTypes.unshift({ value: ProjectType.UNSET, display: '--Select a type--' }); + + this.typeCtrl = new FormControl(this.projectTypes[0].value, this.validatorProjType); + this.pathCliCtrl = new FormControl('', this.validatorProjPath); + this.pathSvrCtrl = new FormControl({ value: '', disabled: true }, this.validatorProjPath); + + this.addProjectForm = fb.group({ + type: this.typeCtrl, + pathCli: this.pathCliCtrl, + pathSvr: this.pathSvrCtrl, + label: ['', Validators.nullValidator], + }); + } + + + ngOnInit() { + // Update server ID + this.serverID = this.XdsConfigSvr.getCurServer().id; + this.XdsConfigSvr.onCurServer().subscribe(svr => this.serverID = svr.id); + + // Auto create label name + this.pathCliCtrl.valueChanges + .debounceTime(100) + .filter(n => n) + .map(n => { + const 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 => { + const dis = (valType === String(ProjectType.SYNCTHING)); + this.pathSvrCtrl.reset({ value: '', disabled: dis }); + }); + } + + closeModal() { + this.activeModal.close(); + } + + onKeyLabel(event: any) { + this.userEditedLabel = (this.addProjectForm.value.label !== ''); + } + + onChangeLocalProject(e) { + } + + onSubmit() { + if (this.cancelAction) { + return; + } + + const formVal = this.addProjectForm.value; + + const type = formVal['type'].value; + this.projectSvr.add({ + serverId: this.serverID, + label: formVal['label'], + pathClient: formVal['pathCli'], + pathServer: formVal['pathSvr'], + type: formVal['type'], + // FIXME: allow to set defaultSdkID from New Project config panel + }) + .subscribe(prj => { + this.alert.info('Project ' + prj.label + ' successfully created.'); + this.closeModal(); + + // Reset Value for the next creation + this.addProjectForm.reset(); + const selectedType = this.projectTypes[0].value; + this.addProjectForm.patchValue({ type: selectedType }); + + }, + err => { + this.alert.error(err, 60); + this.closeModal(); + }); + } + + private validatorProjType(g: FormGroup): ValidationErrors | null { + return (g.value !== ProjectType.UNSET) ? null : { validatorProjType: { valid: false } }; + } + + private validatorProjPath(g: FormGroup): ValidationErrors | null { + return (g.disabled || g.value !== '') ? null : { validatorProjPath: { valid: false } }; + } + +} diff --git a/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts b/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts index da3580a..a83ec0a 100644 --- a/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts +++ b/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit, Input } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; import { IProject, ProjectService } from '../../../@core-xds/services/project.service'; @@ -7,33 +8,27 @@ import { IProject, ProjectService } from '../../../@core-xds/services/project.se template: ` <div class="form-group"> <label>Project</label> - <select class="form-control"> - <option *ngFor="let prj of projects" (click)="select(prj)">{{prj.label}}</option> + <select class="form-control" [(ngModel)]="curPrj" (click)="select()"> + <option *ngFor="let prj of projects$ | async" [ngValue]="prj">{{ prj.label }}</option> </select> </div> `, }) export class ProjectSelectDropdownComponent implements OnInit { - projects: IProject[]; + projects$: Observable<IProject[]>; curPrj: IProject; - constructor(private prjSvr: ProjectService) { } + constructor(private projectSvr: ProjectService) { } ngOnInit() { - this.curPrj = this.prjSvr.getCurrent(); - this.prjSvr.Projects$.subscribe((s) => { - if (s) { - this.projects = s; - if (this.curPrj === null || s.indexOf(this.curPrj) === -1) { - this.prjSvr.setCurrent(this.curPrj = s.length ? s[0] : null); - } - } - }); + this.curPrj = this.projectSvr.getCurrent(); + this.projects$ = this.projectSvr.projects$; + this.projectSvr.curProject$.subscribe(p => this.curPrj = p); } - select(s) { - this.prjSvr.setCurrent(this.curPrj = s); + select() { + this.projectSvr.setCurrentById(this.curPrj.id); } } diff --git a/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts b/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts index 3901331..556316c 100644 --- a/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts +++ b/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts @@ -2,6 +2,8 @@ import { Component } from '@angular/core'; @Component({ selector: 'xds-dwnl-agent', + template: ``, + /* FIXME SEB: to be reworked template: ` <template #popTemplate> <h3>Install xds-agent:</h3> @@ -25,7 +27,8 @@ import { Component } from '@angular/core'; .fa-size-x2 { font-size: 20px; } - `] + `], + */ }) export class DwnlAgentComponent { diff --git a/webapp/src/app/pages/config/config.module.ts b/webapp/src/app/pages/config/config.module.ts index 2fdaf94..d11bcf3 100644 --- a/webapp/src/app/pages/config/config.module.ts +++ b/webapp/src/app/pages/config/config.module.ts @@ -10,6 +10,6 @@ import { ConfigRoutingModule, routedConfig } from './config-routing.module'; ], declarations: [ ...routedConfig, - ] + ], }) export class ConfigModule { } diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html index e2c6748..9d26c71 100644 --- a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html +++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html @@ -39,13 +39,14 @@ <input type="text" id="inputLabel" class="form-control" formControlName="label" (keyup)="onKeyLabel($event)"> </div> </div> - - <div class="offset-sm-3 col-sm-9"> - <button class="btn btn-md btn-secondary" (click)="cancelAction=true; closeModal()"> Cancel </button> - <button class="btn btn-md btn-primary" type="submit" [disabled]="!addProjectForm.valid">Add Folder</button> - </div> </form> </div> </div> <div class="modal-footer form-group"> + <div class="col-12"> + <div class="offset-sm-4 col-sm-6"> + <button class="btn btn-md btn-secondary" (click)="cancelAction=true; closeModal()"> Cancel </button> + <button class="btn btn-md btn-primary" (click)="onSubmit()" [disabled]="!addProjectForm.valid">Add Folder</button> + </div> + </div> </div> diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts index 640ac5c..15b1b24 100644 --- a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts +++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts @@ -16,7 +16,7 @@ import { XDSConfigService } from '../../../@core-xds/services/xds-config.service @Component({ selector: 'xds-project-add-modal', templateUrl: 'project-add-modal.component.html', - styleUrls: ['project-add-modal.component.scss'] + styleUrls: ['project-add-modal.component.scss'], }) export class ProjectAddModalComponent implements OnInit { // @Input('server-id') serverID: string; @@ -24,7 +24,7 @@ export class ProjectAddModalComponent implements OnInit { cancelAction = false; userEditedLabel = false; - projectTypes = ProjectTypes; + projectTypes = Object.assign([], ProjectTypes); addProjectForm: FormGroup; typeCtrl: FormControl; @@ -36,7 +36,7 @@ export class ProjectAddModalComponent implements OnInit { private projectSvr: ProjectService, private XdsConfigSvr: XDSConfigService, private fb: FormBuilder, - private activeModal: NgbActiveModal + private activeModal: NgbActiveModal, ) { // Define types (first one is special/placeholder) this.projectTypes.unshift({ value: ProjectType.UNSET, display: '--Select a type--' }); @@ -108,7 +108,7 @@ export class ProjectAddModalComponent implements OnInit { const formVal = this.addProjectForm.value; const type = formVal['type'].value; - this.projectSvr.Add({ + this.projectSvr.add({ serverId: this.serverID, label: formVal['label'], pathClient: formVal['pathCli'], diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.scss b/webapp/src/app/pages/projects/project-card/project-card.component.scss index a433f58..6ac8d11 100644 --- a/webapp/src/app/pages/projects/project-card/project-card.component.scss +++ b/webapp/src/app/pages/projects/project-card/project-card.component.scss @@ -13,11 +13,6 @@ nb-card-footer { text-align: right; } -.fa-big { - font-size: 20px; - font-weight: bold; -} - .fa-size-x2 { font-size: 20px; } @@ -31,14 +26,6 @@ th label { margin-bottom: 0; } -tr.info>th { - vertical-align: middle; -} - -tr.info>td { - vertical-align: middle; -} - .btn-outline-danger.btn-xds { color: #ff4c6a; &:focus { diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.ts b/webapp/src/app/pages/projects/project-card/project-card.component.ts index 160c4c8..840d656 100644 --- a/webapp/src/app/pages/projects/project-card/project-card.component.ts +++ b/webapp/src/app/pages/projects/project-card/project-card.component.ts @@ -17,28 +17,28 @@ export class ProjectCardComponent { constructor( private alert: AlertService, - private projectSvr: ProjectService + private projectSvr: ProjectService, ) { } delete(prj: IProject) { - this.projectSvr.Delete(prj).subscribe( + this.projectSvr.delete(prj).subscribe( res => { }, - err => this.alert.error('ERROR delete: ' + err) + err => this.alert.error('ERROR delete: ' + err), ); } sync(prj: IProject) { - this.projectSvr.Sync(prj).subscribe( + this.projectSvr.sync(prj).subscribe( res => { }, - err => this.alert.error('ERROR: ' + err) + err => this.alert.error('ERROR: ' + err), ); } } // Make Project type human readable @Pipe({ - name: 'readableType' + name: 'readableType', }) export class ProjectReadableTypePipe implements PipeTransform { diff --git a/webapp/src/app/pages/projects/projects.component.html b/webapp/src/app/pages/projects/projects.component.html index 662dfcc..ecce824 100644 --- a/webapp/src/app/pages/projects/projects.component.html +++ b/webapp/src/app/pages/projects/projects.component.html @@ -20,7 +20,7 @@ </div> </nb-card-body> </div> - <div class="col-md-12 col-lg-12 col-xxxl-6" *ngFor="let prj of (projects$ | async)"> + <div class="col-md-6 col-lg-6" *ngFor="let prj of (projects$ | async)"> <xds-project-card [project]="prj"></xds-project-card> </div> </div> diff --git a/webapp/src/app/pages/projects/projects.component.scss b/webapp/src/app/pages/projects/projects.component.scss index 3631fbb..92a8807 100644 --- a/webapp/src/app/pages/projects/projects.component.scss +++ b/webapp/src/app/pages/projects/projects.component.scss @@ -25,7 +25,7 @@ order: 1; flex-direction: row-reverse; } - nb-actions>nb-action { + nb-actions > nb-action { padding: 0; } nb-action { diff --git a/webapp/src/app/pages/projects/projects.component.ts b/webapp/src/app/pages/projects/projects.component.ts index 179bbd0..761ff50 100644 --- a/webapp/src/app/pages/projects/projects.component.ts +++ b/webapp/src/app/pages/projects/projects.component.ts @@ -23,7 +23,7 @@ export class ProjectsComponent implements OnInit { } ngOnInit() { - this.projects$ = this.projectSvr.Projects$; + this.projects$ = this.projectSvr.projects$; } add() { diff --git a/webapp/src/app/pages/projects/projects.module.ts b/webapp/src/app/pages/projects/projects.module.ts index 48f37ce..960a2da 100644 --- a/webapp/src/app/pages/projects/projects.module.ts +++ b/webapp/src/app/pages/projects/projects.module.ts @@ -17,7 +17,7 @@ import { ProjectAddModalComponent } from './project-add-modal/project-add-modal. ProjectReadableTypePipe, ], entryComponents: [ - ProjectAddModalComponent + ProjectAddModalComponent, ], }) export class ProjectsModule { } diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss index e404610..a83cca4 100644 --- a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss +++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss @@ -13,11 +13,6 @@ nb-card-footer { text-align: right; } -.fa-big { - font-size: 20px; - font-weight: bold; -} - .fa-size-x2 { font-size: 20px; } @@ -31,14 +26,6 @@ th label { margin-bottom: 0; } -tr.info>th { - vertical-align: middle; -} - -tr.info>td { - vertical-align: middle; -} - .btn-outline-danger.btn-xds { color: #ff4c6a; &:focus { diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts index 8228570..7135d36 100644 --- a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts +++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts @@ -17,7 +17,7 @@ export class SdkCardComponent { constructor( private alert: AlertService, - private sdkSvr: SdkService + private sdkSvr: SdkService, ) { } @@ -28,7 +28,7 @@ export class SdkCardComponent { delete(sdk: ISdk) { this.sdkSvr.delete(sdk).subscribe( res => { }, - err => this.alert.error('ERROR delete: ' + err) + err => this.alert.error('ERROR delete: ' + err), ); } } diff --git a/webapp/src/app/pages/sdks/sdks.component.html b/webapp/src/app/pages/sdks/sdks.component.html index adfd924..94c2501 100644 --- a/webapp/src/app/pages/sdks/sdks.component.html +++ b/webapp/src/app/pages/sdks/sdks.component.html @@ -20,7 +20,7 @@ </div> </nb-card-body> </div> - <div class="col-md-12 col-lg-12 col-xxxl-6" *ngFor="let sdk of (sdks$ | async)"> + <div class="col-md-6 col-lg-6" *ngFor="let sdk of (sdks$ | async)"> <xds-sdk-card [sdk]="sdk"></xds-sdk-card> </div> </div> diff --git a/webapp/src/app/pages/sdks/sdks.component.scss b/webapp/src/app/pages/sdks/sdks.component.scss index 3631fbb..92a8807 100644 --- a/webapp/src/app/pages/sdks/sdks.component.scss +++ b/webapp/src/app/pages/sdks/sdks.component.scss @@ -25,7 +25,7 @@ order: 1; flex-direction: row-reverse; } - nb-actions>nb-action { + nb-actions > nb-action { padding: 0; } nb-action { diff --git a/webapp/src/app/pages/sdks/sdks.component.ts b/webapp/src/app/pages/sdks/sdks.component.ts index 3208121..ef933af 100644 --- a/webapp/src/app/pages/sdks/sdks.component.ts +++ b/webapp/src/app/pages/sdks/sdks.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -//import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component'; +// import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component'; import { SdkService, ISdk } from '../../@core-xds/services/sdk.service'; diff --git a/webapp/src/app/pages/sdks/sdks.module.ts b/webapp/src/app/pages/sdks/sdks.module.ts index 48629f6..c811eb4 100644 --- a/webapp/src/app/pages/sdks/sdks.module.ts +++ b/webapp/src/app/pages/sdks/sdks.module.ts @@ -3,7 +3,7 @@ import { ThemeModule } from '../../@theme/theme.module'; import { SdksComponent } from './sdks.component'; import { SdkCardComponent } from './sdk-card/sdk-card.component'; -//import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component'; +// import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component'; @NgModule({ @@ -13,10 +13,10 @@ import { SdkCardComponent } from './sdk-card/sdk-card.component'; declarations: [ SdksComponent, SdkCardComponent, - //SdkAddModalComponent, + // SdkAddModalComponent, ], entryComponents: [ - //SdkAddModalComponent + // SdkAddModalComponent, ], }) export class SdksModule { } |