diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3aef1e1f..1af8bc0be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,9 +30,6 @@ You can have a use Github filters to list these issues: * intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate * advanced labeled issues: https://github.com/portainer/portainer/labels/exp%2Fadvanced -### Linting - -Please check your code using `grunt lint` before submitting your pull requests. ### Commit Message Format diff --git a/api/filesystem/filesystem.go b/api/filesystem/filesystem.go index 236845410..1f59fd0c8 100644 --- a/api/filesystem/filesystem.go +++ b/api/filesystem/filesystem.go @@ -42,20 +42,17 @@ func NewService(dataStorePath, fileStorePath string) (*Service, error) { fileStorePath: path.Join(dataStorePath, fileStorePath), } - // Checking if a mount directory exists is broken with Go on Windows. - // This will need to be reviewed after the issue has been fixed in Go. - // See: https://github.com/portainer/portainer/issues/474 - // err := createDirectoryIfNotExist(dataStorePath, 0755) - // if err != nil { - // return nil, err - // } - - err := service.createDirectoryInStoreIfNotExist(TLSStorePath) + err := os.MkdirAll(dataStorePath, 0755) if err != nil { return nil, err } - err = service.createDirectoryInStoreIfNotExist(ComposeStorePath) + err = service.createDirectoryInStore(TLSStorePath) + if err != nil { + return nil, err + } + + err = service.createDirectoryInStore(ComposeStorePath) if err != nil { return nil, err } @@ -76,14 +73,14 @@ func (service *Service) GetStackProjectPath(stackIdentifier string) string { // StoreStackFileFromString creates a subfolder in the ComposeStorePath and stores a new file using the content from a string. // It returns the path to the folder where the file is stored. -func (service *Service) StoreStackFileFromString(stackIdentifier, stackFileContent string) (string, error) { +func (service *Service) StoreStackFileFromString(stackIdentifier, fileName, stackFileContent string) (string, error) { stackStorePath := path.Join(ComposeStorePath, stackIdentifier) - err := service.createDirectoryInStoreIfNotExist(stackStorePath) + err := service.createDirectoryInStore(stackStorePath) if err != nil { return "", err } - composeFilePath := path.Join(stackStorePath, ComposeFileDefaultName) + composeFilePath := path.Join(stackStorePath, fileName) data := []byte(stackFileContent) r := bytes.NewReader(data) @@ -97,14 +94,14 @@ func (service *Service) StoreStackFileFromString(stackIdentifier, stackFileConte // StoreStackFileFromReader creates a subfolder in the ComposeStorePath and stores a new file using the content from an io.Reader. // It returns the path to the folder where the file is stored. -func (service *Service) StoreStackFileFromReader(stackIdentifier string, r io.Reader) (string, error) { +func (service *Service) StoreStackFileFromReader(stackIdentifier, fileName string, r io.Reader) (string, error) { stackStorePath := path.Join(ComposeStorePath, stackIdentifier) - err := service.createDirectoryInStoreIfNotExist(stackStorePath) + err := service.createDirectoryInStore(stackStorePath) if err != nil { return "", err } - composeFilePath := path.Join(stackStorePath, ComposeFileDefaultName) + composeFilePath := path.Join(stackStorePath, fileName) err = service.createFileInStore(composeFilePath, r) if err != nil { @@ -117,7 +114,7 @@ func (service *Service) StoreStackFileFromReader(stackIdentifier string, r io.Re // StoreTLSFile creates a folder in the TLSStorePath and stores a new file with the content from r. func (service *Service) StoreTLSFile(folder string, fileType portainer.TLSFileType, r io.Reader) error { storePath := path.Join(TLSStorePath, folder) - err := service.createDirectoryInStoreIfNotExist(storePath) + err := service.createDirectoryInStore(storePath) if err != nil { return err } @@ -201,24 +198,10 @@ func (service *Service) GetFileContent(filePath string) (string, error) { return string(content), nil } -// createDirectoryInStoreIfNotExist creates a new directory in the file store if it doesn't exists on the file system. -func (service *Service) createDirectoryInStoreIfNotExist(name string) error { +// createDirectoryInStore creates a new directory in the file store +func (service *Service) createDirectoryInStore(name string) error { path := path.Join(service.fileStorePath, name) - return createDirectoryIfNotExist(path, 0700) -} - -// createDirectoryIfNotExist creates a directory if it doesn't exists on the file system. -func createDirectoryIfNotExist(path string, mode uint32) error { - _, err := os.Stat(path) - if os.IsNotExist(err) { - err = os.Mkdir(path, os.FileMode(mode)) - if err != nil { - return err - } - } else if err != nil { - return err - } - return nil + return os.MkdirAll(path, 0700) } // createFile creates a new file in the file store with the content from r. diff --git a/api/git/git.go b/api/git/git.go index 8758363b9..5e58363c7 100644 --- a/api/git/git.go +++ b/api/git/git.go @@ -1,6 +1,9 @@ package git import ( + "net/url" + "strings" + "gopkg.in/src-d/go-git.v4" ) @@ -14,12 +17,23 @@ func NewService(dataStorePath string) (*Service, error) { return service, nil } -// CloneRepository clones a git repository using the specified URL in the specified +// ClonePublicRepository clones a public git repository using the specified URL in the specified // destination folder. -func (service *Service) CloneRepository(url, destination string) error { - _, err := git.PlainClone(destination, false, &git.CloneOptions{ - URL: url, - }) +func (service *Service) ClonePublicRepository(repositoryURL, destination string) error { + return cloneRepository(repositoryURL, destination) +} +// ClonePrivateRepositoryWithBasicAuth clones a private git repository using the specified URL in the specified +// destination folder. It will use the specified username and password for basic HTTP authentication. +func (service *Service) ClonePrivateRepositoryWithBasicAuth(repositoryURL, destination, username, password string) error { + credentials := username + ":" + url.PathEscape(password) + repositoryURL = strings.Replace(repositoryURL, "://", "://"+credentials+"@", 1) + return cloneRepository(repositoryURL, destination) +} + +func cloneRepository(repositoryURL, destination string) error { + _, err := git.PlainClone(destination, false, &git.CloneOptions{ + URL: repositoryURL, + }) return err } diff --git a/api/http/handler/dockerhub.go b/api/http/handler/dockerhub.go index a5ff36b57..75acc0517 100644 --- a/api/http/handler/dockerhub.go +++ b/api/http/handler/dockerhub.go @@ -52,6 +52,8 @@ func (handler *DockerHubHandler) handleGetDockerHub(w http.ResponseWriter, r *ht return } + dockerhub.Password = "" + encodeJSON(w, dockerhub, handler.Logger) return } diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index ac0cc7b9f..05eea9ef5 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -52,7 +52,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/api/endpoints"): switch { - case strings.Contains(r.URL.Path, "/docker"): + case strings.Contains(r.URL.Path, "/docker/"): http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r) case strings.Contains(r.URL.Path, "/stacks"): http.StripPrefix("/api/endpoints", h.StackHandler).ServeHTTP(w, r) diff --git a/api/http/handler/registry.go b/api/http/handler/registry.go index 9afeb1178..37cb2c971 100644 --- a/api/http/handler/registry.go +++ b/api/http/handler/registry.go @@ -91,6 +91,10 @@ func (handler *RegistryHandler) handleGetRegistries(w http.ResponseWriter, r *ht return } + for i := range filteredRegistries { + filteredRegistries[i].Password = "" + } + encodeJSON(w, filteredRegistries, handler.Logger) } @@ -159,6 +163,8 @@ func (handler *RegistryHandler) handleGetRegistry(w http.ResponseWriter, r *http return } + registry.Password = "" + encodeJSON(w, registry, handler.Logger) } diff --git a/api/http/handler/stack.go b/api/http/handler/stack.go index 86b597e40..5b9e53187 100644 --- a/api/http/handler/stack.go +++ b/api/http/handler/stack.go @@ -70,12 +70,15 @@ func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler { type ( postStacksRequest struct { - Name string `valid:"required"` - SwarmID string `valid:"required"` - StackFileContent string `valid:""` - GitRepository string `valid:""` - PathInRepository string `valid:""` - Env []portainer.Pair `valid:""` + Name string `valid:"required"` + SwarmID string `valid:"required"` + StackFileContent string `valid:""` + RepositoryURL string `valid:""` + RepositoryAuthentication bool `valid:""` + RepositoryUsername string `valid:""` + RepositoryPassword string `valid:""` + ComposeFilePathInRepository string `valid:""` + Env []portainer.Pair `valid:""` } postStacksResponse struct { ID string `json:"Id"` @@ -179,7 +182,7 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter, Env: req.Env, } - projectPath, err := handler.FileService.StoreStackFileFromString(string(stack.ID), stackFileContent) + projectPath, err := handler.FileService.StoreStackFileFromString(string(stack.ID), stack.EntryPoint, stackFileContent) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return @@ -263,24 +266,20 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri } stackName := req.Name - if stackName == "" { - httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger) - return - } - swarmID := req.SwarmID - if swarmID == "" { + + if stackName == "" || swarmID == "" || req.RepositoryURL == "" { httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger) return } - if req.GitRepository == "" { + if req.RepositoryAuthentication && (req.RepositoryUsername == "" || req.RepositoryPassword == "") { httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger) return } - if req.PathInRepository == "" { - req.PathInRepository = filesystem.ComposeFileDefaultName + if req.ComposeFilePathInRepository == "" { + req.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName } stacks, err := handler.StackService.Stacks() @@ -300,7 +299,7 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri ID: portainer.StackID(stackName + "_" + swarmID), Name: stackName, SwarmID: swarmID, - EntryPoint: req.PathInRepository, + EntryPoint: req.ComposeFilePathInRepository, Env: req.Env, } @@ -314,7 +313,11 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri return } - err = handler.GitService.CloneRepository(req.GitRepository, projectPath) + if req.RepositoryAuthentication { + err = handler.GitService.ClonePrivateRepositoryWithBasicAuth(req.RepositoryURL, projectPath, req.RepositoryUsername, req.RepositoryPassword) + } else { + err = handler.GitService.ClonePublicRepository(req.RepositoryURL, projectPath) + } if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return @@ -431,7 +434,7 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r Env: env, } - projectPath, err := handler.FileService.StoreStackFileFromReader(string(stack.ID), stackFile) + projectPath, err := handler.FileService.StoreStackFileFromReader(string(stack.ID), stack.EntryPoint, stackFile) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return @@ -631,7 +634,7 @@ func (handler *StackHandler) handlePutStack(w http.ResponseWriter, r *http.Reque } stack.Env = req.Env - _, err = handler.FileService.StoreStackFileFromString(string(stack.ID), req.StackFileContent) + _, err = handler.FileService.StoreStackFileFromString(string(stack.ID), stack.EntryPoint, req.StackFileContent) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return diff --git a/api/http/proxy/factory.go b/api/http/proxy/factory.go index 1602f9d8d..91015367c 100644 --- a/api/http/proxy/factory.go +++ b/api/http/proxy/factory.go @@ -15,6 +15,8 @@ type proxyFactory struct { ResourceControlService portainer.ResourceControlService TeamMembershipService portainer.TeamMembershipService SettingsService portainer.SettingsService + RegistryService portainer.RegistryService + DockerHubService portainer.DockerHubService } func (factory *proxyFactory) newExtensionHTTPPRoxy(u *url.URL) http.Handler { @@ -45,6 +47,8 @@ func (factory *proxyFactory) newDockerSocketProxy(path string) http.Handler { ResourceControlService: factory.ResourceControlService, TeamMembershipService: factory.TeamMembershipService, SettingsService: factory.SettingsService, + RegistryService: factory.RegistryService, + DockerHubService: factory.DockerHubService, dockerTransport: newSocketTransport(path), } proxy.Transport = transport @@ -57,6 +61,8 @@ func (factory *proxyFactory) createDockerReverseProxy(u *url.URL) *httputil.Reve ResourceControlService: factory.ResourceControlService, TeamMembershipService: factory.TeamMembershipService, SettingsService: factory.SettingsService, + RegistryService: factory.RegistryService, + DockerHubService: factory.DockerHubService, dockerTransport: &http.Transport{}, } proxy.Transport = transport diff --git a/api/http/proxy/manager.go b/api/http/proxy/manager.go index a5b57a535..747c7870a 100644 --- a/api/http/proxy/manager.go +++ b/api/http/proxy/manager.go @@ -17,7 +17,7 @@ type Manager struct { } // NewManager initializes a new proxy Service -func NewManager(resourceControlService portainer.ResourceControlService, teamMembershipService portainer.TeamMembershipService, settingsService portainer.SettingsService) *Manager { +func NewManager(resourceControlService portainer.ResourceControlService, teamMembershipService portainer.TeamMembershipService, settingsService portainer.SettingsService, registryService portainer.RegistryService, dockerHubService portainer.DockerHubService) *Manager { return &Manager{ proxies: cmap.New(), extensionProxies: cmap.New(), @@ -25,6 +25,8 @@ func NewManager(resourceControlService portainer.ResourceControlService, teamMem ResourceControlService: resourceControlService, TeamMembershipService: teamMembershipService, SettingsService: settingsService, + RegistryService: registryService, + DockerHubService: dockerHubService, }, } } diff --git a/api/http/proxy/registry.go b/api/http/proxy/registry.go new file mode 100644 index 000000000..5edeb73b7 --- /dev/null +++ b/api/http/proxy/registry.go @@ -0,0 +1,37 @@ +package proxy + +import ( + "github.com/portainer/portainer" + "github.com/portainer/portainer/http/security" +) + +func createRegistryAuthenticationHeader(serverAddress string, accessContext *registryAccessContext) *registryAuthenticationHeader { + var authenticationHeader *registryAuthenticationHeader + + if serverAddress == "" { + authenticationHeader = ®istryAuthenticationHeader{ + Username: accessContext.dockerHub.Username, + Password: accessContext.dockerHub.Password, + Serveraddress: "docker.io", + } + } else { + var matchingRegistry *portainer.Registry + for _, registry := range accessContext.registries { + if registry.URL == serverAddress && + (accessContext.isAdmin || (!accessContext.isAdmin && security.AuthorizedRegistryAccess(®istry, accessContext.userID, accessContext.teamMemberships))) { + matchingRegistry = ®istry + break + } + } + + if matchingRegistry != nil { + authenticationHeader = ®istryAuthenticationHeader{ + Username: matchingRegistry.Username, + Password: matchingRegistry.Password, + Serveraddress: matchingRegistry.URL, + } + } + } + + return authenticationHeader +} diff --git a/api/http/proxy/transport.go b/api/http/proxy/transport.go index 3b0b1fa3c..0e61dc1a5 100644 --- a/api/http/proxy/transport.go +++ b/api/http/proxy/transport.go @@ -1,6 +1,8 @@ package proxy import ( + "encoding/base64" + "encoding/json" "net/http" "path" "strings" @@ -14,6 +16,8 @@ type ( dockerTransport *http.Transport ResourceControlService portainer.ResourceControlService TeamMembershipService portainer.TeamMembershipService + RegistryService portainer.RegistryService + DockerHubService portainer.DockerHubService SettingsService portainer.SettingsService } restrictedOperationContext struct { @@ -22,6 +26,18 @@ type ( userTeamIDs []portainer.TeamID resourceControls []portainer.ResourceControl } + registryAccessContext struct { + isAdmin bool + userID portainer.UserID + teamMemberships []portainer.TeamMembership + registries []portainer.Registry + dockerHub *portainer.DockerHub + } + registryAuthenticationHeader struct { + Username string `json:"username"` + Password string `json:"password"` + Serveraddress string `json:"serveraddress"` + } operationExecutor struct { operationContext *restrictedOperationContext labelBlackList []portainer.Pair @@ -62,6 +78,8 @@ func (p *proxyTransport) proxyDockerRequest(request *http.Request) (*http.Respon return p.proxyTaskRequest(request) case strings.HasPrefix(path, "/build"): return p.proxyBuildRequest(request) + case strings.HasPrefix(path, "/images"): + return p.proxyImageRequest(request) default: return p.executeDockerRequest(request) } @@ -119,7 +137,7 @@ func (p *proxyTransport) proxyContainerRequest(request *http.Request) (*http.Res func (p *proxyTransport) proxyServiceRequest(request *http.Request) (*http.Response, error) { switch requestPath := request.URL.Path; requestPath { case "/services/create": - return p.executeDockerRequest(request) + return p.replaceRegistryAuthenticationHeader(request) case "/services": return p.rewriteOperation(request, serviceListOperation) @@ -235,6 +253,54 @@ func (p *proxyTransport) proxyBuildRequest(request *http.Request) (*http.Respons return p.interceptAndRewriteRequest(request, buildOperation) } +func (p *proxyTransport) proxyImageRequest(request *http.Request) (*http.Response, error) { + switch requestPath := request.URL.Path; requestPath { + case "/images/create": + return p.replaceRegistryAuthenticationHeader(request) + default: + if match, _ := path.Match("/images/*/push", requestPath); match { + return p.replaceRegistryAuthenticationHeader(request) + } + return p.executeDockerRequest(request) + } +} + +func (p *proxyTransport) replaceRegistryAuthenticationHeader(request *http.Request) (*http.Response, error) { + accessContext, err := p.createRegistryAccessContext(request) + if err != nil { + return nil, err + } + + originalHeader := request.Header.Get("X-Registry-Auth") + + if originalHeader != "" { + + decodedHeaderData, err := base64.StdEncoding.DecodeString(originalHeader) + if err != nil { + return nil, err + } + + var originalHeaderData registryAuthenticationHeader + err = json.Unmarshal(decodedHeaderData, &originalHeaderData) + if err != nil { + return nil, err + } + + authenticationHeader := createRegistryAuthenticationHeader(originalHeaderData.Serveraddress, accessContext) + + headerData, err := json.Marshal(authenticationHeader) + if err != nil { + return nil, err + } + + header := base64.StdEncoding.EncodeToString(headerData) + + request.Header.Set("X-Registry-Auth", header) + } + + return p.executeDockerRequest(request) +} + // restrictedOperation ensures that the current user has the required authorizations // before executing the original request. func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID string) (*http.Response, error) { @@ -270,7 +336,7 @@ func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID s return p.executeDockerRequest(request) } -// rewriteOperation will create a new operation context with data that will be used +// rewriteOperationWithLabelFiltering will create a new operation context with data that will be used // to decorate the original request's response as well as retrieve all the black listed labels // to filter the resources. func (p *proxyTransport) rewriteOperationWithLabelFiltering(request *http.Request, operation restrictedOperationRequest) (*http.Response, error) { @@ -341,6 +407,43 @@ func (p *proxyTransport) administratorOperation(request *http.Request) (*http.Re return p.executeDockerRequest(request) } +func (p *proxyTransport) createRegistryAccessContext(request *http.Request) (*registryAccessContext, error) { + tokenData, err := security.RetrieveTokenData(request) + if err != nil { + return nil, err + } + + accessContext := ®istryAccessContext{ + isAdmin: true, + userID: tokenData.ID, + } + + hub, err := p.DockerHubService.DockerHub() + if err != nil { + return nil, err + } + accessContext.dockerHub = hub + + registries, err := p.RegistryService.Registries() + if err != nil { + return nil, err + } + accessContext.registries = registries + + if tokenData.Role != portainer.AdministratorRole { + accessContext.isAdmin = false + + teamMemberships, err := p.TeamMembershipService.TeamMembershipsByUserID(tokenData.ID) + if err != nil { + return nil, err + } + + accessContext.teamMemberships = teamMemberships + } + + return accessContext, nil +} + func (p *proxyTransport) createOperationContext(request *http.Request) (*restrictedOperationContext, error) { var err error tokenData, err := security.RetrieveTokenData(request) diff --git a/api/http/security/authorization.go b/api/http/security/authorization.go index 976c2947f..54932f673 100644 --- a/api/http/security/authorization.go +++ b/api/http/security/authorization.go @@ -140,3 +140,22 @@ func AuthorizedEndpointAccess(endpoint *portainer.Endpoint, userID portainer.Use } return false } + +// AuthorizedRegistryAccess ensure that the user can access the specified registry. +// It will check if the user is part of the authorized users or part of a team that is +// listed in the authorized teams. +func AuthorizedRegistryAccess(registry *portainer.Registry, userID portainer.UserID, memberships []portainer.TeamMembership) bool { + for _, authorizedUserID := range registry.AuthorizedUsers { + if authorizedUserID == userID { + return true + } + } + for _, membership := range memberships { + for _, authorizedTeamID := range registry.AuthorizedTeams { + if membership.TeamID == authorizedTeamID { + return true + } + } + } + return false +} diff --git a/api/http/security/filter.go b/api/http/security/filter.go index 9f28f19c0..ffe5e1c49 100644 --- a/api/http/security/filter.go +++ b/api/http/security/filter.go @@ -69,7 +69,7 @@ func FilterRegistries(registries []portainer.Registry, context *RestrictedReques filteredRegistries = make([]portainer.Registry, 0) for _, registry := range registries { - if isRegistryAccessAuthorized(®istry, context.UserID, context.UserMemberships) { + if AuthorizedRegistryAccess(®istry, context.UserID, context.UserMemberships) { filteredRegistries = append(filteredRegistries, registry) } } @@ -87,7 +87,7 @@ func FilterEndpoints(endpoints []portainer.Endpoint, context *RestrictedRequestC filteredEndpoints = make([]portainer.Endpoint, 0) for _, endpoint := range endpoints { - if isEndpointAccessAuthorized(&endpoint, context.UserID, context.UserMemberships) { + if AuthorizedEndpointAccess(&endpoint, context.UserID, context.UserMemberships) { filteredEndpoints = append(filteredEndpoints, endpoint) } } @@ -95,35 +95,3 @@ func FilterEndpoints(endpoints []portainer.Endpoint, context *RestrictedRequestC return filteredEndpoints, nil } - -func isRegistryAccessAuthorized(registry *portainer.Registry, userID portainer.UserID, memberships []portainer.TeamMembership) bool { - for _, authorizedUserID := range registry.AuthorizedUsers { - if authorizedUserID == userID { - return true - } - } - for _, membership := range memberships { - for _, authorizedTeamID := range registry.AuthorizedTeams { - if membership.TeamID == authorizedTeamID { - return true - } - } - } - return false -} - -func isEndpointAccessAuthorized(endpoint *portainer.Endpoint, userID portainer.UserID, memberships []portainer.TeamMembership) bool { - for _, authorizedUserID := range endpoint.AuthorizedUsers { - if authorizedUserID == userID { - return true - } - } - for _, membership := range memberships { - for _, authorizedTeamID := range endpoint.AuthorizedTeams { - if membership.TeamID == authorizedTeamID { - return true - } - } - } - return false -} diff --git a/api/http/server.go b/api/http/server.go index fc5f08972..536344f68 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -42,7 +42,7 @@ type Server struct { // Start starts the HTTP server func (server *Server) Start() error { requestBouncer := security.NewRequestBouncer(server.JWTService, server.UserService, server.TeamMembershipService, server.AuthDisabled) - proxyManager := proxy.NewManager(server.ResourceControlService, server.TeamMembershipService, server.SettingsService) + proxyManager := proxy.NewManager(server.ResourceControlService, server.TeamMembershipService, server.SettingsService, server.RegistryService, server.DockerHubService) var fileHandler = handler.NewFileHandler(filepath.Join(server.AssetsPath, "public")) var authHandler = handler.NewAuthHandler(requestBouncer, server.AuthDisabled) diff --git a/api/portainer.go b/api/portainer.go index f001f83df..b3a5bc63d 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -152,7 +152,7 @@ type ( URL string `json:"URL"` Authentication bool `json:"Authentication"` Username string `json:"Username"` - Password string `json:"Password"` + Password string `json:"Password,omitempty"` AuthorizedUsers []UserID `json:"AuthorizedUsers"` AuthorizedTeams []TeamID `json:"AuthorizedTeams"` } @@ -162,7 +162,7 @@ type ( DockerHub struct { Authentication bool `json:"Authentication"` Username string `json:"Username"` - Password string `json:"Password"` + Password string `json:"Password,omitempty"` } // EndpointID represents an endpoint identifier. @@ -369,13 +369,14 @@ type ( DeleteTLSFile(folder string, fileType TLSFileType) error DeleteTLSFiles(folder string) error GetStackProjectPath(stackIdentifier string) string - StoreStackFileFromString(stackIdentifier string, stackFileContent string) (string, error) - StoreStackFileFromReader(stackIdentifier string, r io.Reader) (string, error) + StoreStackFileFromString(stackIdentifier, fileName, stackFileContent string) (string, error) + StoreStackFileFromReader(stackIdentifier, fileName string, r io.Reader) (string, error) } // GitService represents a service for managing Git. GitService interface { - CloneRepository(url, destination string) error + ClonePublicRepository(repositoryURL, destination string) error + ClonePrivateRepositoryWithBasicAuth(repositoryURL, destination, username, password string) error } // EndpointWatcher represents a service to synchronize the endpoints via an external source. @@ -400,7 +401,7 @@ type ( const ( // APIVersion is the version number of the Portainer API. - APIVersion = "1.16.4" + APIVersion = "1.16.5" // DBVersion is the version number of the Portainer database. DBVersion = 8 // DefaultTemplatesURL represents the default URL for the templates definitions. diff --git a/api/swagger.yaml b/api/swagger.yaml index 4d2546919..1d36febde 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -56,7 +56,7 @@ info: **NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8). - version: "1.16.4" + version: "1.16.5" title: "Portainer API" contact: email: "info@portainer.io" @@ -2143,7 +2143,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.16.4" + example: "1.16.5" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" @@ -2904,14 +2904,26 @@ definitions: type: "string" example: "version: 3\n services:\n web:\n image:nginx" description: "Content of the Stack file. Required when using the 'string' deployment method." - GitRepository: + RepositoryURL: type: "string" example: "https://github.com/openfaas/faas" - description: "URL of a public Git repository hosting the Stack file. Required when using the 'repository' deployment method." - PathInRepository: + description: "URL of a Git repository hosting the Stack file. Required when using the 'repository' deployment method." + ComposeFilePathInRepository: type: "string" example: "docker-compose.yml" description: "Path to the Stack file inside the Git repository. Required when using the 'repository' deployment method." + RepositoryAuthentication: + type: "boolean" + example: true + description: "Use basic authentication to clone the Git repository." + RepositoryUsername: + type: "string" + example: "myGitUsername" + description: "Username used in basic authentication. Required when RepositoryAuthentication is true." + RepositoryPassword: + type: "string" + example: "myGitPassword" + description: "Password used in basic authentication. Required when RepositoryAuthentication is true." Env: type: "array" description: "A list of environment variables used during stack deployment" diff --git a/app/docker/components/datatables/configs-datatable/configsDatatable.html b/app/docker/components/datatables/configs-datatable/configsDatatable.html index 96a2dcc76..f41830f44 100644 --- a/app/docker/components/datatables/configs-datatable/configsDatatable.html +++ b/app/docker/components/datatables/configs-datatable/configsDatatable.html @@ -14,7 +14,7 @@
diff --git a/app/docker/components/datatables/containers-datatable/containersDatatable.html b/app/docker/components/datatables/containers-datatable/containersDatatable.html index f9e82b812..973c1c5e6 100644 --- a/app/docker/components/datatables/containers-datatable/containersDatatable.html +++ b/app/docker/components/datatables/containers-datatable/containersDatatable.html @@ -67,7 +67,7 @@
+ diff --git a/app/docker/views/volumes/volumes.html b/app/docker/views/volumes/volumes.html index 234797714..85b57a11c 100644 --- a/app/docker/views/volumes/volumes.html +++ b/app/docker/views/volumes/volumes.html @@ -1,7 +1,7 @@ - + Volumes diff --git a/app/extensions/storidge/components/cluster-events-datatable/storidgeClusterEventsDatatable.html b/app/extensions/storidge/components/cluster-events-datatable/storidgeClusterEventsDatatable.html index 61800cb82..6ce9e3f1f 100644 --- a/app/extensions/storidge/components/cluster-events-datatable/storidgeClusterEventsDatatable.html +++ b/app/extensions/storidge/components/cluster-events-datatable/storidgeClusterEventsDatatable.html @@ -22,29 +22,29 @@ Date - - + + Category - - + + Module - - + + Content - - + + diff --git a/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html b/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html index 7478e83d5..973b517d7 100644 --- a/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html +++ b/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html @@ -22,29 +22,29 @@ Name - - + + IP Address - - + + Role - - + + Status - - + + diff --git a/app/extensions/storidge/components/profiles-datatable/storidgeProfilesDatatable.html b/app/extensions/storidge/components/profiles-datatable/storidgeProfilesDatatable.html index e2ab91dd0..642f69a03 100644 --- a/app/extensions/storidge/components/profiles-datatable/storidgeProfilesDatatable.html +++ b/app/extensions/storidge/components/profiles-datatable/storidgeProfilesDatatable.html @@ -14,7 +14,7 @@
diff --git a/app/extensions/storidge/views/monitor/monitor.html b/app/extensions/storidge/views/monitor/monitor.html index b4ce1bfc4..2101294ce 100644 --- a/app/extensions/storidge/views/monitor/monitor.html +++ b/app/extensions/storidge/views/monitor/monitor.html @@ -1,7 +1,7 @@ - + @@ -12,7 +12,7 @@
- +
@@ -45,7 +45,7 @@
- +
@@ -78,7 +78,7 @@
- +
diff --git a/app/extensions/storidge/views/profiles/profiles.html b/app/extensions/storidge/views/profiles/profiles.html index 5e5b054a8..d6a5dcfce 100644 --- a/app/extensions/storidge/views/profiles/profiles.html +++ b/app/extensions/storidge/views/profiles/profiles.html @@ -1,7 +1,7 @@ - + @@ -49,7 +49,7 @@
- +
Items per page: diff --git a/app/portainer/components/buttonSpinner.js b/app/portainer/components/buttonSpinner.js index ca82bf322..357f17bf2 100644 --- a/app/portainer/components/buttonSpinner.js +++ b/app/portainer/components/buttonSpinner.js @@ -6,7 +6,7 @@ angular.module('portainer.app') spinning: '=buttonSpinner' }, transclude: true, - template: ' ' + template: ' ' }; return directive; diff --git a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.html b/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.html index cee17d2eb..95dc16296 100644 --- a/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.html +++ b/app/portainer/components/datatables/endpoints-datatable/endpointsDatatable.html @@ -14,7 +14,7 @@
@@ -114,7 +114,7 @@ {{ $ctrl.formData.TLSKey.name }} - +
diff --git a/app/portainer/components/header-content.js b/app/portainer/components/header-content.js index cb9de8f6e..638e78d50 100644 --- a/app/portainer/components/header-content.js +++ b/app/portainer/components/header-content.js @@ -6,7 +6,7 @@ angular.module('portainer.app') link: function (scope, iElement, iAttrs) { scope.username = Authentication.getUserDetails().username; }, - template: '', + template: '', restrict: 'E' }; return directive; diff --git a/app/portainer/components/header-title.js b/app/portainer/components/header-title.js index 3b4f88665..92a6e3d4c 100644 --- a/app/portainer/components/header-title.js +++ b/app/portainer/components/header-title.js @@ -10,7 +10,7 @@ angular.module('portainer.app') scope.displayDonationHeader = StateManager.getState().application.displayDonationHeader; }, transclude: true, - template: '
{{title}} {{username}} Help support portainer
', + template: '
{{title}} {{username}} Help support portainer
', restrict: 'E' }; return directive; diff --git a/app/portainer/services/api/registryService.js b/app/portainer/services/api/registryService.js index b6193f89b..ff5d00f18 100644 --- a/app/portainer/services/api/registryService.js +++ b/app/portainer/services/api/registryService.js @@ -37,8 +37,6 @@ angular.module('portainer.app') service.encodedCredentials = function(registry) { var credentials = { - username: registry.Username, - password: registry.Password, serveraddress: registry.URL }; return btoa(JSON.stringify(credentials)); diff --git a/app/portainer/services/extensionManager.js b/app/portainer/services/extensionManager.js index adfa4468f..b0cde467e 100644 --- a/app/portainer/services/extensionManager.js +++ b/app/portainer/services/extensionManager.js @@ -12,7 +12,7 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ var endpointAPIVersion = parseFloat(data.ApiVersion); return $q.all([ - endpointAPIVersion >= 1.25 ? initStoridgeExtension(): null + endpointAPIVersion >= 1.25 ? initStoridgeExtension(): {} ]); }) .then(function success(data) { diff --git a/app/portainer/views/about/about.html b/app/portainer/views/about/about.html index 918c8532a..b1af972a3 100644 --- a/app/portainer/views/about/about.html +++ b/app/portainer/views/about/about.html @@ -28,23 +28,23 @@

Fund our work

Contribute

Spread the word

  • Talk to your friends and colleagues about how awesome Portainer is!
  • -
  • Follow us on Twitter
  • +
  • Follow us on Twitter

@@ -56,7 +56,7 @@
- +

@@ -69,15 +69,15 @@

Community support

Services

diff --git a/app/portainer/views/auth/auth.html b/app/portainer/views/auth/auth.html index f58276395..6cde5c633 100644 --- a/app/portainer/views/auth/auth.html +++ b/app/portainer/views/auth/auth.html @@ -28,7 +28,7 @@
- + {{ state.AuthenticationError }} diff --git a/app/portainer/views/endpoints/endpoints.html b/app/portainer/views/endpoints/endpoints.html index b27569673..7b6eb9205 100644 --- a/app/portainer/views/endpoints/endpoints.html +++ b/app/portainer/views/endpoints/endpoints.html @@ -1,7 +1,7 @@ - + Endpoint management diff --git a/app/portainer/views/init/admin/initAdminController.js b/app/portainer/views/init/admin/initAdminController.js index f89bdddca..103862b0a 100644 --- a/app/portainer/views/init/admin/initAdminController.js +++ b/app/portainer/views/init/admin/initAdminController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('InitAdminController', ['$scope', '$state', '$sanitize', 'Notifications', 'Authentication', 'StateManager', 'UserService', 'EndpointService', 'EndpointProvider', -function ($scope, $state, $sanitize, Notifications, Authentication, StateManager, UserService, EndpointService, EndpointProvider) { +.controller('InitAdminController', ['$scope', '$state', '$sanitize', 'Notifications', 'Authentication', 'StateManager', 'UserService', 'EndpointService', 'EndpointProvider', 'ExtensionManager', +function ($scope, $state, $sanitize, Notifications, Authentication, StateManager, UserService, EndpointService, EndpointProvider, ExtensionManager) { $scope.logo = StateManager.getState().application.logo; @@ -31,8 +31,13 @@ function ($scope, $state, $sanitize, Notifications, Authentication, StateManager $state.go('portainer.init.endpoint'); } else { var endpoint = data[0]; - EndpointProvider.setEndpointID(endpoint.Id); - StateManager.updateEndpointState(false, endpoint.Extensions) + endpointID = endpoint.Id; + EndpointProvider.setEndpointID(endpointID); + ExtensionManager.initEndpointExtensions(endpointID) + .then(function success(data) { + var extensions = data; + return StateManager.updateEndpointState(false, extensions); + }) .then(function success() { $state.go('docker.dashboard'); }) diff --git a/app/portainer/views/init/endpoint/initEndpoint.html b/app/portainer/views/init/endpoint/initEndpoint.html index c526d2ec8..5849c9b27 100644 --- a/app/portainer/views/init/endpoint/initEndpoint.html +++ b/app/portainer/views/init/endpoint/initEndpoint.html @@ -148,7 +148,7 @@ {{ formValues.TLSCACert.name }} - +
@@ -162,7 +162,7 @@ {{ formValues.TLSCert.name }} - +
@@ -175,7 +175,7 @@ {{ formValues.TLSKey.name }} - +
diff --git a/app/portainer/views/registries/registries.html b/app/portainer/views/registries/registries.html index e432c300b..8c822d8ee 100644 --- a/app/portainer/views/registries/registries.html +++ b/app/portainer/views/registries/registries.html @@ -1,7 +1,7 @@ - + Registry management diff --git a/app/portainer/views/settings/authentication/settingsAuthentication.html b/app/portainer/views/settings/authentication/settingsAuthentication.html index 2d655dea3..ce5b6798d 100644 --- a/app/portainer/views/settings/authentication/settingsAuthentication.html +++ b/app/portainer/views/settings/authentication/settingsAuthentication.html @@ -162,7 +162,7 @@ {{ formValues.TLSCACert.name }} - +
diff --git a/app/portainer/views/settings/settings.html b/app/portainer/views/settings/settings.html index a12aa4f0c..ed1e5ba74 100644 --- a/app/portainer/views/settings/settings.html +++ b/app/portainer/views/settings/settings.html @@ -168,7 +168,7 @@ {{ label.name }} {{ label.value }} - + No filter available. diff --git a/app/portainer/views/teams/edit/team.html b/app/portainer/views/teams/edit/team.html index a2942b38e..f0b4ca3be 100644 --- a/app/portainer/views/teams/edit/team.html +++ b/app/portainer/views/teams/edit/team.html @@ -16,7 +16,7 @@ Name {{ team.Name }} - + diff --git a/app/portainer/views/teams/teams.html b/app/portainer/views/teams/teams.html index 6da96c691..66c4b6e54 100644 --- a/app/portainer/views/teams/teams.html +++ b/app/portainer/views/teams/teams.html @@ -1,7 +1,7 @@ - + Teams management diff --git a/app/portainer/views/users/edit/user.html b/app/portainer/views/users/edit/user.html index e8a417461..5c8ab61a6 100644 --- a/app/portainer/views/users/edit/user.html +++ b/app/portainer/views/users/edit/user.html @@ -16,7 +16,7 @@ {{ user.Username }} - + diff --git a/app/portainer/views/users/users.html b/app/portainer/views/users/users.html index b1801f1db..047721771 100644 --- a/app/portainer/views/users/users.html +++ b/app/portainer/views/users/users.html @@ -1,7 +1,7 @@ - + User management diff --git a/assets/css/app.css b/assets/css/app.css index 3df9b6f5b..4475b6d13 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -132,6 +132,10 @@ a[ng-click]{ font-weight: normal; } +.widget .widget-body table tbody td { + white-space: nowrap; +} + .template-widget { height: 100%; } @@ -586,7 +590,7 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active { .boxselector_wrapper input[type="radio"]:checked + label::after { color: #337ab7; - font-family: FontAwesome; + font-family: "Font Awesome 5 Free"; border: 2px solid #337ab7; content: "\f00c"; font-size: 16px; diff --git a/distribution/portainer.spec b/distribution/portainer.spec index f2cd9c12a..0f24ff14c 100644 --- a/distribution/portainer.spec +++ b/distribution/portainer.spec @@ -1,5 +1,5 @@ Name: portainer -Version: 1.16.4 +Version: 1.16.5 Release: 0 License: Zlib Summary: A lightweight docker management UI diff --git a/gruntfile.js b/gruntfile.js index c957b3eb7..145789cf4 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -12,13 +12,13 @@ module.exports = function (grunt) { grunt.registerTask('default', ['eslint', 'build']); grunt.registerTask('before-copy', [ - 'vendor:', + 'vendor', 'html2js', 'useminPrepare:release', 'concat', - 'postcss:build', 'clean:tmpl', 'replace', + 'postcss:build', 'uglify' ]); grunt.registerTask('after-copy', [ @@ -38,7 +38,7 @@ module.exports = function (grunt) { 'clean:app', 'shell:buildBinary:linux:' + arch, 'shell:downloadDockerBinary:linux:' + arch, - 'vendor:regular', + 'vendor', 'html2js', 'useminPrepare:dev', 'concat', @@ -55,20 +55,19 @@ module.exports = function (grunt) { grunt.registerTask('clear', ['clean:app']); // Load content of `vendor.yml` to src.jsVendor, src.cssVendor and src.angularVendor - grunt.registerTask('vendor', 'vendor:', function(min) { - // Argument `min` defaults to 'minified' - var minification = (min === '') ? 'minified' : min; + grunt.registerTask('vendor', function() { var vendorFile = grunt.file.readYAML('vendor.yml'); for (var filelist in vendorFile) { if (vendorFile.hasOwnProperty(filelist)) { - var list = vendorFile[filelist][minification]; + var list = vendorFile[filelist]; // Check if any of the files is missing for (var itemIndex in list) { if (list.hasOwnProperty(itemIndex)) { - var item = list[itemIndex]; + var item = 'node_modules/'+list[itemIndex]; if (!grunt.file.exists(item)) { grunt.fail.warn('Dependency file ' + item + ' not found.'); } + list[itemIndex] = item; } } // If none is missing, save the list @@ -159,7 +158,7 @@ gruntfile_cfg.copy = { assets: { files: [ {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'node_modules/bootstrap/fonts/'}, - {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'node_modules/font-awesome/fonts/'}, + {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,eot,svg}', expand: true, cwd: 'node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/'}, {dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'node_modules/rdash-ui/dist/fonts/'}, {dest: '<%= distdir %>/images/', src: '**', expand: true, cwd: 'assets/images/'}, {dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'} @@ -218,7 +217,7 @@ gruntfile_cfg.postcss = { cssnano() // minify the result ] }, - src: '<%= distdir %>/css/<%= pkg.name %>.css', + src: '.tmp/concat/css/app.css', dest: '<%= distdir %>/css/app.css' } }; @@ -235,7 +234,8 @@ gruntfile_cfg.replace = { options: { patterns: [ { match: 'ENVIRONMENT', replacement: '<%= grunt.config.get("environment") %>' }, - { match: 'CONFIG_GA_ID', replacement: '<%= pkg.config.GA_ID %>' } + { match: 'CONFIG_GA_ID', replacement: '<%= pkg.config.GA_ID %>' }, + { match: /..\/webfonts\//g, replacement: '../fonts/'} ] }, files: [ @@ -244,6 +244,12 @@ gruntfile_cfg.replace = { flatten: true, src: ['.tmp/concat/js/app.js'], dest: '.tmp/concat/js' + }, + { + expand: true, + flatten: true, + src: ['.tmp/concat/css/app.css'], + dest: '.tmp/concat/css' } ] } diff --git a/package.json b/package.json index babf2346a..6a7dff5de 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "1.16.4", + "version": "1.16.5", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git" @@ -23,6 +23,7 @@ "node": ">= 0.8.4" }, "dependencies": { + "@fortawesome/fontawesome-free-webfonts": "^1.0.4", "@uirouter/angularjs": "~1.0.6", "angular": "~1.5.0", "angular-clipboard": "^1.6.2", @@ -45,7 +46,6 @@ "chart.js": "~2.6.0", "codemirror": "~5.30.0", "filesize": "~3.3.0", - "font-awesome": "~4.7.0", "isteven-angular-multiselect": "~4.0.0", "jquery": "^3.3.1", "js-yaml": "~3.10.0", diff --git a/vendor.yml b/vendor.yml index a38cb34a0..5234104ce 100644 --- a/vendor.yml +++ b/vendor.yml @@ -1,106 +1,54 @@ --- js: - regular: - - node_modules/jquery/dist/jquery.js - - node_modules/bootstrap/dist/js/bootstrap.js - - node_modules/bootbox/bootbox.js - - node_modules/filesize/lib/filesize.js - - node_modules/lodash/lodash.js - - node_modules/moment/moment.js - - node_modules/chart.js/dist/Chart.js - - node_modules/splitargs/src/splitargs.js - - node_modules/toastr/toastr.js - - node_modules/xterm/dist/xterm.js - - node_modules/xterm/dist/addons/fit/fit.js - - node_modules/js-yaml/dist/js-yaml.js - - node_modules/codemirror/lib/codemirror.js - - node_modules/codemirror/mode/yaml/yaml.js - - node_modules/codemirror/addon/lint/lint.js - - node_modules/codemirror/addon/lint/yaml-lint.js - - node_modules/codemirror/addon/display/placeholder.js - minified: - - node_modules/jquery/dist/jquery.min.js - - node_modules/bootstrap/dist/js/bootstrap.min.js - - node_modules/bootbox/bootbox.js - - node_modules/filesize/lib/filesize.js - - node_modules/lodash/lodash.min.js - - node_modules/moment/min/moment.min.js - - node_modules/chart.js/dist/Chart.min.js - - node_modules/splitargs/src/splitargs.js - - node_modules/toastr/build/toastr.min.js - - node_modules/xterm/dist/xterm.js - - node_modules/xterm/dist/addons/fit/fit.js - - node_modules/js-yaml/dist/js-yaml.min.js - - node_modules/codemirror/lib/codemirror.js - - node_modules/codemirror/mode/yaml/yaml.js - - node_modules/codemirror/addon/lint/lint.js - - node_modules/codemirror/addon/lint/yaml-lint.js - - node_modules/codemirror/addon/display/placeholder.js + - 'jquery/dist/jquery.js' + - 'bootstrap/dist/js/bootstrap.js' + - 'bootbox/bootbox.js' + - 'filesize/lib/filesize.js' + - 'lodash/lodash.js' + - 'moment/moment.js' + - 'chart.js/dist/Chart.js' + - 'splitargs/src/splitargs.js' + - 'toastr/toastr.js' + - 'xterm/dist/xterm.js' + - 'xterm/dist/addons/fit/fit.js' + - 'js-yaml/dist/js-yaml.js' + - 'codemirror/lib/codemirror.js' + - 'codemirror/mode/yaml/yaml.js' + - 'codemirror/addon/lint/lint.js' + - 'codemirror/addon/lint/yaml-lint.js' + - 'codemirror/addon/display/placeholder.js' css: - regular: - - node_modules/bootstrap/dist/css/bootstrap.css - - node_modules/rdash-ui/dist/css/rdash.css - - node_modules/isteven-angular-multiselect/isteven-multi-select.css - - node_modules/ui-select/dist/select.css - - node_modules/font-awesome/css/font-awesome.css - - node_modules/toastr/build/toastr.css - - node_modules/xterm/dist/xterm.css - - node_modules/angularjs-slider/dist/rzslider.css - - node_modules/codemirror/lib/codemirror.css - - node_modules/codemirror/addon/lint/lint.css - - node_modules/angular-json-tree/dist/angular-json-tree.css - - node_modules/angular-loading-bar/build/loading-bar.css - minified: - - node_modules/bootstrap/dist/css/bootstrap.min.css - - node_modules/rdash-ui/dist/css/rdash.min.css - - node_modules/isteven-angular-multiselect/isteven-multi-select.css - - node_modules/ui-select/dist/select.min.css - - node_modules/font-awesome/css/font-awesome.min.css - - node_modules/toastr/build/toastr.min.css - - node_modules/xterm/dist/xterm.css - - node_modules/angularjs-slider/dist/rzslider.min.css - - node_modules/codemirror/lib/codemirror.css - - node_modules/codemirror/addon/lint/lint.css - - node_modules/angular-json-tree/dist/angular-json-tree.css - - node_modules/angular-loading-bar/build/loading-bar.min.css + - 'bootstrap/dist/css/bootstrap.css' + - 'rdash-ui/dist/css/rdash.css' + - 'isteven-angular-multiselect/isteven-multi-select.css' + - 'ui-select/dist/select.css' + - '@fortawesome/fontawesome-free-webfonts/css/fa-brands.css' + - '@fortawesome/fontawesome-free-webfonts/css/fa-solid.css' + - '@fortawesome/fontawesome-free-webfonts/css/fontawesome.css' + - 'toastr/build/toastr.css' + - 'xterm/dist/xterm.css' + - 'angularjs-slider/dist/rzslider.css' + - 'codemirror/lib/codemirror.css' + - 'codemirror/addon/lint/lint.css' + - 'angular-json-tree/dist/angular-json-tree.css' + - 'angular-loading-bar/build/loading-bar.css' angular: - regular: - - node_modules/angular/angular.js - - node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js - - node_modules/angular-cookies/angular-cookies.js - - node_modules/angular-google-analytics/dist/angular-google-analytics.js - - node_modules/angular-jwt/dist/angular-jwt.js - - node_modules/angular-local-storage/dist/angular-local-storage.js - - node_modules/angular-messages/angular-messages.js - - node_modules/angular-resource/angular-resource.js - - node_modules/angular-sanitize/angular-sanitize.js - - node_modules/ui-select/dist/select.js - - node_modules/@uirouter/angularjs/release/angular-ui-router.js - - node_modules/angular-utils-pagination/dirPagination.js - - node_modules/ng-file-upload/dist/ng-file-upload.js - - node_modules/angularjs-slider/dist/rzslider.js - - node_modules/isteven-angular-multiselect/isteven-multi-select.js - - node_modules/angular-json-tree/dist/angular-json-tree.js - - node_modules/angular-loading-bar/build/loading-bar.js - - node_modules/angularjs-scroll-glue/src/scrollglue.js - - node_modules/angular-clipboard/angular-clipboard.js - minified: - - node_modules/angular/angular.min.js - - node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js - - node_modules/angular-cookies/angular-cookies.min.js - - node_modules/angular-google-analytics/dist/angular-google-analytics.min.js - - node_modules/angular-jwt/dist/angular-jwt.min.js - - node_modules/angular-local-storage/dist/angular-local-storage.min.js - - node_modules/angular-messages/angular-messages.min.js - - node_modules/angular-resource/angular-resource.min.js - - node_modules/angular-sanitize/angular-sanitize.min.js - - node_modules/ui-select/dist/select.min.js - - node_modules/@uirouter/angularjs/release/angular-ui-router.min.js - - node_modules/angular-utils-pagination/dirPagination.js - - node_modules/ng-file-upload/dist/ng-file-upload.min.js - - node_modules/angularjs-slider/dist/rzslider.min.js - - node_modules/isteven-angular-multiselect/isteven-multi-select.js - - node_modules/angular-json-tree/dist/angular-json-tree.min.js - - node_modules/angular-loading-bar/build/loading-bar.min.js - - node_modules/angularjs-scroll-glue/src/scrollglue.js - - node_modules/angular-clipboard/angular-clipboard.js + - 'angular/angular.js' + - 'angular-ui-bootstrap/dist/ui-bootstrap-tpls.js' + - 'angular-cookies/angular-cookies.js' + - 'angular-google-analytics/dist/angular-google-analytics.js' + - 'angular-jwt/dist/angular-jwt.js' + - 'angular-local-storage/dist/angular-local-storage.js' + - 'angular-messages/angular-messages.js' + - 'angular-resource/angular-resource.js' + - 'angular-sanitize/angular-sanitize.js' + - 'ui-select/dist/select.js' + - '@uirouter/angularjs/release/angular-ui-router.js' + - 'angular-utils-pagination/dirPagination.js' + - 'ng-file-upload/dist/ng-file-upload.js' + - 'angularjs-slider/dist/rzslider.js' + - 'isteven-angular-multiselect/isteven-multi-select.js' + - 'angular-json-tree/dist/angular-json-tree.js' + - 'angular-loading-bar/build/loading-bar.js' + - 'angularjs-scroll-glue/src/scrollglue.js' + - 'angular-clipboard/angular-clipboard.js' diff --git a/yarn.lock b/yarn.lock index 0ecbadb85..e64420e5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,10 @@ # yarn lockfile v1 +"@fortawesome/fontawesome-free-webfonts@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.4.tgz#bac5d89755bf3bc2d2b4deee47d92febf641bb1f" + "@uirouter/angularjs@~1.0.6": version "1.0.11" resolved "https://registry.yarnpkg.com/@uirouter/angularjs/-/angularjs-1.0.11.tgz#ced1ec8bea68a5db7dcfd77a43b7b8b9a2953540" @@ -1482,10 +1486,6 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" -font-awesome@~4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" - for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"