From 252af86ceaaefee53809067b7505fd8fd82c5cdf Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 28 Jul 2018 20:35:01 +0200 Subject: [PATCH 01/57] fix(build-system): fix an invalid condition in shell_downloadDockerBinary task --- gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 217ddc041..a68ca4722 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -281,9 +281,9 @@ function shell_downloadDockerBinary(p, a) { var as = { 'amd64': 'x86_64', 'arm': 'armhf', 'arm64': 'aarch64' }; var ip = ((ps[p] === undefined) ? p : ps[p]); var ia = ((as[a] === undefined) ? a : as[a]); - var binaryVersion = (( p === 'win' ? '<%= shippedDockerVersionWindows %>' : '<%= shippedDockerVersion %>' )); + var binaryVersion = (( p === 'windows' ? '<%= shippedDockerVersionWindows %>' : '<%= shippedDockerVersion %>' )); return [ - 'if [ -f '+(( p === 'win' ) ? 'dist/docker.exe' : 'dist/docker')+' ]; then', + 'if [ -f '+(( p === 'windows' ) ? 'dist/docker.exe' : 'dist/docker')+' ]; then', 'echo "Docker binary exists";', 'else', 'build/download_docker_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';', From b8ed6d3d4af0cc1023ff9b15af1024982230c049 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 28 Jul 2018 20:42:17 +0200 Subject: [PATCH 02/57] chore(version): bump version number --- api/portainer.go | 2 +- api/swagger.yaml | 4 ++-- distribution/portainer.spec | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/portainer.go b/api/portainer.go index ac5cda1a1..2e0e94d05 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -611,7 +611,7 @@ type ( const ( // APIVersion is the version number of the Portainer API. - APIVersion = "1.19.1" + APIVersion = "1.19.2-dev" // DBVersion is the version number of the Portainer database. DBVersion = 13 // PortainerAgentHeader represents the name of the header available in any agent response diff --git a/api/swagger.yaml b/api/swagger.yaml index 0e4e9426c..1e975c5bf 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -54,7 +54,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.19.1" + version: "1.19.2-dev" title: "Portainer API" contact: email: "info@portainer.io" @@ -2816,7 +2816,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.19.1" + example: "1.19.2-dev" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" diff --git a/distribution/portainer.spec b/distribution/portainer.spec index 65f1b8baf..c62ed0b72 100644 --- a/distribution/portainer.spec +++ b/distribution/portainer.spec @@ -1,5 +1,5 @@ Name: portainer -Version: 1.19.1 +Version: 1.19.2-dev Release: 0 License: Zlib Summary: A lightweight docker management UI diff --git a/package.json b/package.json index 0cb5e3b7b..33a723fda 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "1.19.1", + "version": "1.19.2-dev", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git" From 5f7954713858d1d142bc5ce0b8afaf11eb9327dc Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 31 Jul 2018 11:50:04 +0200 Subject: [PATCH 03/57] fix(api): filter sensitive information from API response (#2103) --- api/http/handler/endpoints/endpoint_list.go | 5 +++-- api/http/handler/registries/registry_list.go | 7 ++++--- api/http/handler/users/user_list.go | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api/http/handler/endpoints/endpoint_list.go b/api/http/handler/endpoints/endpoint_list.go index 13268b48d..3348c630e 100644 --- a/api/http/handler/endpoints/endpoint_list.go +++ b/api/http/handler/endpoints/endpoint_list.go @@ -27,8 +27,9 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext) - for _, endpoint := range filteredEndpoints { - hideFields(&endpoint) + for idx := range filteredEndpoints { + hideFields(&filteredEndpoints[idx]) } + return response.JSON(w, filteredEndpoints) } diff --git a/api/http/handler/registries/registry_list.go b/api/http/handler/registries/registry_list.go index 158f8e3fe..9986ce820 100644 --- a/api/http/handler/registries/registry_list.go +++ b/api/http/handler/registries/registry_list.go @@ -22,8 +22,9 @@ func (handler *Handler) registryList(w http.ResponseWriter, r *http.Request) *ht filteredRegistries := security.FilterRegistries(registries, securityContext) - for _, registry := range filteredRegistries { - hideFields(®istry) + for idx := range filteredRegistries { + hideFields(&filteredRegistries[idx]) } - return response.JSON(w, registries) + + return response.JSON(w, filteredRegistries) } diff --git a/api/http/handler/users/user_list.go b/api/http/handler/users/user_list.go index 760c4ec54..945e85f10 100644 --- a/api/http/handler/users/user_list.go +++ b/api/http/handler/users/user_list.go @@ -22,8 +22,9 @@ func (handler *Handler) userList(w http.ResponseWriter, r *http.Request) *httper filteredUsers := security.FilterUsers(users, securityContext) - for _, user := range filteredUsers { - hideFields(&user) + for idx := range filteredUsers { + hideFields(&filteredUsers[idx]) } + return response.JSON(w, filteredUsers) } From 2216bd6e80de0395fa2aa6ec83768817964f4b1d Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 31 Jul 2018 11:58:08 +0200 Subject: [PATCH 04/57] style(home): only display CPU/MEM for standalone endpoints --- api/http/handler/endpoints/endpoint_create.go | 2 +- .../endpoint-list/endpoint-item/endpointItem.html | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go index a369194ab..3ce3bd6d4 100644 --- a/api/http/handler/endpoints/endpoint_create.go +++ b/api/http/handler/endpoints/endpoint_create.go @@ -174,7 +174,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po endpoint := &portainer.Endpoint{ ID: portainer.EndpointID(endpointID), Name: payload.Name, - URL: payload.URL, + URL: "https://management.azure.com", Type: portainer.AzureEnvironment, GroupID: portainer.EndpointGroupID(payload.GroupID), PublicURL: payload.PublicURL, diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html index dd7dc8be2..24723beac 100644 --- a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html +++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html @@ -58,8 +58,12 @@
- {{ $ctrl.model.Snapshots[0].TotalCPU }} {{ $ctrl.model.Snapshots[0].TotalMemory | humansize }} - - + + + {{ $ctrl.model.Snapshots[0].TotalCPU }} {{ $ctrl.model.Snapshots[0].TotalMemory | humansize }} + + - + No tags From aa36adc5fd2e041dedaada4d5c07bd7363a42c3a Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 2 Aug 2018 09:39:43 +0200 Subject: [PATCH 05/57] chore(project): update CONTRIBUTING.md --- CONTRIBUTING.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1af8bc0be..3ddc75e8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Some basic conventions for contributing to this project. -### General +## General Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork. @@ -13,7 +13,7 @@ When creating a new branch, prefix it with the *type* of the change (see section For example, if you work on a bugfix for the issue #361, you could name the branch `fix361-template-selection`. -### Issues open to contribution +## Issues open to contribution Want to contribute but don't know where to start? @@ -24,14 +24,14 @@ Some of the open issues are labeled with prefix `exp/`, this is used to mark the either AngularJS or Golang * **advanced**: a task that require a deep understanding of the project codebase -You can have a use Github filters to list these issues: +You can use Github filters to list these issues: * beginner labeled issues: https://github.com/portainer/portainer/labels/exp%2Fbeginner * intermediate labeled issues: https://github.com/portainer/portainer/labels/exp%2Fintermediate * advanced labeled issues: https://github.com/portainer/portainer/labels/exp%2Fadvanced -### Commit Message Format +## Commit Message Format Each commit message should include a **type**, a **scope** and a **subject**: @@ -47,7 +47,7 @@ Lines should not exceed 100 characters. This allows the message to be easier to #269 style(dashboard): update dashboard with new layout ``` -#### Type +### Type Must be one of the following: @@ -61,16 +61,30 @@ Must be one of the following: * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation -#### Scope +### Scope The scope could be anything specifying place of the commit change. For example `networks`, `containers`, `images` etc... You can use the **area** label tag associated on the issue here (for `area/containers` use `containers` as a scope...) -#### Subject +### Subject The subject contains succinct description of the change: * use the imperative, present tense: "change" not "changed" nor "changes" * don't capitalize first letter * no dot (.) at the end + +## Contribution process + +Our contribution process is described below. Some of the steps can be visualized inside Github via specific `contrib/` labels, such as `contrib/func-review-in-progress` or `contrib/tech-review-approved`. + +### Bug report + +![portainer_bugreport_workflow](https://user-images.githubusercontent.com/5485061/43569306-5571b3a0-9637-11e8-8559-786cfc82a14f.png) + +### Feature request + +The feature request process is similar to the bug report process but has an extra functional validation before the technical validation. + +![portainer_featurerequest_workflow](https://user-images.githubusercontent.com/5485061/43569315-5d30a308-9637-11e8-8292-3c62b5612925.png) From 892276b105cbe49698f12a8d1fa0903fce06eff4 Mon Sep 17 00:00:00 2001 From: Olli Janatuinen Date: Thu, 2 Aug 2018 17:52:36 +0300 Subject: [PATCH 06/57] feat(build-system): add Dockerfile for Windows server 2016 (#2117) --- build/windows2016/nanoserver/Dockerfile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 build/windows2016/nanoserver/Dockerfile diff --git a/build/windows2016/nanoserver/Dockerfile b/build/windows2016/nanoserver/Dockerfile new file mode 100644 index 000000000..ae8a25a39 --- /dev/null +++ b/build/windows2016/nanoserver/Dockerfile @@ -0,0 +1,11 @@ +FROM microsoft/nanoserver + +COPY dist / + +VOLUME C:\\data + +WORKDIR / + +EXPOSE 9000 + +ENTRYPOINT ["/portainer.exe"] From 0ae10c6f82be9daaecb6d483121da2ce08c10cc7 Mon Sep 17 00:00:00 2001 From: Kendrick Date: Thu, 2 Aug 2018 12:00:58 -0700 Subject: [PATCH 07/57] feat(container-details): add the image name to the container details in addition to the sha (#1369) (#2121) --- app/docker/views/containers/edit/container.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/docker/views/containers/edit/container.html b/app/docker/views/containers/edit/container.html index 95c790140..967d5321e 100644 --- a/app/docker/views/containers/edit/container.html +++ b/app/docker/views/containers/edit/container.html @@ -187,7 +187,7 @@ Image - {{ container.Image }} + {{ container.Config.Image}}@{{container.Image}} Port configuration From 8dfa129129cbb4dcc2842c103c269b07e96ecb58 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Mon, 6 Aug 2018 14:09:23 +0100 Subject: [PATCH 08/57] fix(dashboard): update stopped/running container filters --- app/docker/filters/filters.js | 23 +++++++++++++---------- app/docker/views/dashboard/dashboard.html | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/docker/filters/filters.js b/app/docker/filters/filters.js index b6f9a71dd..a55419333 100644 --- a/app/docker/filters/filters.js +++ b/app/docker/filters/filters.js @@ -218,17 +218,20 @@ angular.module('portainer.docker') return runningTasks; }; }) -.filter('containerswithstatus', function () { +.filter('runningcontainers', function () { 'use strict'; - return function (containers, status) { - var containersWithStatus = 0; - for (var i = 0; i < containers.length; i++) { - var container = containers[i]; - if (container.Status === status) { - containersWithStatus++; - } - } - return containersWithStatus; + return function runningContainersFilter(containers) { + return containers.filter(function (container) { + return container.State === 'running'; + }).length; + }; +}) +.filter('stoppedcontainers', function () { + 'use strict'; + return function stoppedContainersFilter(containers) { + return containers.filter(function (container) { + return container.State === 'exited'; + }).length; }; }) .filter('imagestotalsize', function () { diff --git a/app/docker/views/dashboard/dashboard.html b/app/docker/views/dashboard/dashboard.html index 86950c43e..2e31750fb 100644 --- a/app/docker/views/dashboard/dashboard.html +++ b/app/docker/views/dashboard/dashboard.html @@ -115,8 +115,8 @@
-
{{ containers | containerswithstatus:'running' }} running
-
{{ containers | containerswithstatus:'stopped' }} stopped
+
{{ containers | runningcontainers }} running
+
{{ containers | stoppedcontainers }} stopped
{{ containers.length }}
Containers
From 09cb8e7350bf5d079c25fd399f386cc4d2b00c30 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Mon, 6 Aug 2018 14:32:27 +0100 Subject: [PATCH 09/57] chore(gitignore): add .vscode to .gitignore (#2130) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 244386e1e..43b170ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist portainer-checksum.txt api/cmd/portainer/portainer* .tmp +.vscode \ No newline at end of file From ee9c8d7d1a2619e1c855531735f6514eedcc968d Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 7 Aug 2018 17:43:36 +0200 Subject: [PATCH 10/57] feat(templates): re-introduce external template management (#2119) * feat(templates): re-introduce external template management * refactor(api): review error handling --- api/cmd/portainer/main.go | 27 +++++----------- api/http/handler/settings/settings_public.go | 6 ++++ api/http/handler/settings/settings_update.go | 8 +++++ api/http/handler/templates/handler.go | 30 +++++++++++++++--- api/http/handler/templates/template_list.go | 27 ++++++++++++++-- api/http/server.go | 1 + api/portainer.go | 2 +- app/portainer/models/settings.js | 2 ++ app/portainer/views/settings/settings.html | 31 +++++++++++++++++++ .../views/settings/settingsController.js | 8 +++++ app/portainer/views/templates/templates.html | 6 ++-- .../views/templates/templatesController.js | 4 ++- 12 files changed, 120 insertions(+), 32 deletions(-) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 470c5d136..e706907d7 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -193,6 +193,10 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL } func initTemplates(templateService portainer.TemplateService, fileService portainer.FileService, templateURL, templateFile string) error { + if templateURL != "" { + log.Printf("Portainer started with the --templates flag. Using external templates, template management will be disabled.") + return nil + } existingTemplates, err := templateService.Templates() if err != nil { @@ -204,32 +208,14 @@ func initTemplates(templateService portainer.TemplateService, fileService portai return nil } - var templatesJSON []byte - if templateURL == "" { - return loadTemplatesFromFile(fileService, templateService, templateFile) - } - - templatesJSON, err = client.Get(templateURL) - if err != nil { - log.Println("Unable to retrieve templates via HTTP") - return err - } - - return unmarshalAndPersistTemplates(templateService, templatesJSON) -} - -func loadTemplatesFromFile(fileService portainer.FileService, templateService portainer.TemplateService, templateFile string) error { templatesJSON, err := fileService.GetFileContent(templateFile) if err != nil { - log.Println("Unable to retrieve template via filesystem") + log.Println("Unable to retrieve template definitions via filesystem") return err } - return unmarshalAndPersistTemplates(templateService, templatesJSON) -} -func unmarshalAndPersistTemplates(templateService portainer.TemplateService, templateData []byte) error { var templates []portainer.Template - err := json.Unmarshal(templateData, &templates) + err = json.Unmarshal(templatesJSON, &templates) if err != nil { log.Println("Unable to parse templates file. Please review your template definition file.") return err @@ -241,6 +227,7 @@ func unmarshalAndPersistTemplates(templateService portainer.TemplateService, tem return err } } + return nil } diff --git a/api/http/handler/settings/settings_public.go b/api/http/handler/settings/settings_public.go index c2ee2a616..07d9c7d75 100644 --- a/api/http/handler/settings/settings_public.go +++ b/api/http/handler/settings/settings_public.go @@ -13,6 +13,7 @@ type publicSettingsResponse struct { AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"` AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"` AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"` + ExternalTemplates bool `json:"ExternalTemplates"` } // GET request on /api/settings/public @@ -27,6 +28,11 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) * AuthenticationMethod: settings.AuthenticationMethod, AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers, AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers, + ExternalTemplates: false, + } + + if settings.TemplatesURL != "" { + publicSettings.ExternalTemplates = true } return response.JSON(w, publicSettings) diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go index 827818fa7..6c171917b 100644 --- a/api/http/handler/settings/settings_update.go +++ b/api/http/handler/settings/settings_update.go @@ -19,6 +19,7 @@ type settingsUpdatePayload struct { AllowBindMountsForRegularUsers *bool AllowPrivilegedModeForRegularUsers *bool SnapshotInterval *string + TemplatesURL *string } func (payload *settingsUpdatePayload) Validate(r *http.Request) error { @@ -28,6 +29,9 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error { if payload.LogoURL != nil && *payload.LogoURL != "" && !govalidator.IsURL(*payload.LogoURL) { return portainer.Error("Invalid logo URL. Must correspond to a valid URL format") } + if payload.TemplatesURL != nil && *payload.TemplatesURL != "" && !govalidator.IsURL(*payload.TemplatesURL) { + return portainer.Error("Invalid external templates URL. Must correspond to a valid URL format") + } return nil } @@ -52,6 +56,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) * settings.LogoURL = *payload.LogoURL } + if payload.TemplatesURL != nil { + settings.TemplatesURL = *payload.TemplatesURL + } + if payload.BlackListedLabels != nil { settings.BlackListedLabels = payload.BlackListedLabels } diff --git a/api/http/handler/templates/handler.go b/api/http/handler/templates/handler.go index db65830c2..c193b1def 100644 --- a/api/http/handler/templates/handler.go +++ b/api/http/handler/templates/handler.go @@ -9,10 +9,15 @@ import ( "github.com/portainer/portainer/http/security" ) +const ( + errTemplateManagementDisabled = portainer.Error("Template management is disabled") +) + // Handler represents an HTTP API handler for managing templates. type Handler struct { *mux.Router TemplateService portainer.TemplateService + SettingsService portainer.SettingsService } // NewHandler returns a new instance of Handler. @@ -20,15 +25,32 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { h := &Handler{ Router: mux.NewRouter(), } + h.Handle("/templates", bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet) h.Handle("/templates", - bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateCreate))).Methods(http.MethodPost) + bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateCreate)))).Methods(http.MethodPost) h.Handle("/templates/{id}", - bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateInspect))).Methods(http.MethodGet) + bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateInspect)))).Methods(http.MethodGet) h.Handle("/templates/{id}", - bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateUpdate))).Methods(http.MethodPut) + bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateUpdate)))).Methods(http.MethodPut) h.Handle("/templates/{id}", - bouncer.AdministratorAccess(httperror.LoggerHandler(h.templateDelete))).Methods(http.MethodDelete) + bouncer.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete) return h } + +func (handler *Handler) templateManagementCheck(next http.Handler) http.Handler { + return httperror.LoggerHandler(func(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError { + settings, err := handler.SettingsService.Settings() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err} + } + + if settings.TemplatesURL != "" { + return &httperror.HandlerError{http.StatusServiceUnavailable, "Portainer is configured to use external templates, template management is disabled", errTemplateManagementDisabled} + } + + next.ServeHTTP(rw, r) + return nil + }) +} diff --git a/api/http/handler/templates/template_list.go b/api/http/handler/templates/template_list.go index 7c31e1c10..cd312685d 100644 --- a/api/http/handler/templates/template_list.go +++ b/api/http/handler/templates/template_list.go @@ -1,8 +1,11 @@ package templates import ( + "encoding/json" "net/http" + "github.com/portainer/portainer" + "github.com/portainer/portainer/http/client" httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" @@ -10,9 +13,28 @@ import ( // GET request on /api/templates func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - templates, err := handler.TemplateService.Templates() + settings, err := handler.SettingsService.Settings() if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err} + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err} + } + + var templates []portainer.Template + if settings.TemplatesURL == "" { + templates, err = handler.TemplateService.Templates() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve templates from the database", err} + } + } else { + var templateData []byte + templateData, err = client.Get(settings.TemplatesURL) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve external templates", err} + } + + err = json.Unmarshal(templateData, &templates) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to parse external templates", err} + } } securityContext, err := security.RetrieveRestrictedRequestContext(r) @@ -21,6 +43,5 @@ func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *ht } filteredTemplates := security.FilterTemplates(templates, securityContext) - return response.JSON(w, filteredTemplates) } diff --git a/api/http/server.go b/api/http/server.go index 4babc109b..6b3415280 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -151,6 +151,7 @@ func (server *Server) Start() error { var templatesHandler = templates.NewHandler(requestBouncer) templatesHandler.TemplateService = server.TemplateService + templatesHandler.SettingsService = server.SettingsService var uploadHandler = upload.NewHandler(requestBouncer) uploadHandler.FileService = server.FileService diff --git a/api/portainer.go b/api/portainer.go index 2e0e94d05..1f2d6b26a 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -88,11 +88,11 @@ type ( AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"` AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"` SnapshotInterval string `json:"SnapshotInterval"` + TemplatesURL string `json:"TemplatesURL"` // Deprecated fields DisplayDonationHeader bool DisplayExternalContributors bool - TemplatesURL string } // User represents a user account. diff --git a/app/portainer/models/settings.js b/app/portainer/models/settings.js index 16ec568c2..03af7a686 100644 --- a/app/portainer/models/settings.js +++ b/app/portainer/models/settings.js @@ -6,6 +6,8 @@ function SettingsViewModel(data) { this.AllowBindMountsForRegularUsers = data.AllowBindMountsForRegularUsers; this.AllowPrivilegedModeForRegularUsers = data.AllowPrivilegedModeForRegularUsers; this.SnapshotInterval = data.SnapshotInterval; + this.TemplatesURL = data.TemplatesURL; + this.ExternalTemplates = data.ExternalTemplates; } function LDAPSettingsViewModel(data) { diff --git a/app/portainer/views/settings/settings.html b/app/portainer/views/settings/settings.html index 741f8d6cb..e1939ee9e 100644 --- a/app/portainer/views/settings/settings.html +++ b/app/portainer/views/settings/settings.html @@ -44,6 +44,37 @@ + +
+ App Templates +
+
+
+ + +
+
+
+
+ + You can specify the URL to your own template definitions file here. See Portainer documentation for more details. + +
+
+ +
+ +
+
+
+
Security diff --git a/app/portainer/views/settings/settingsController.js b/app/portainer/views/settings/settingsController.js index c2b7b2127..7e8819e43 100644 --- a/app/portainer/views/settings/settingsController.js +++ b/app/portainer/views/settings/settingsController.js @@ -8,6 +8,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_ $scope.formValues = { customLogo: false, + externalTemplates: false, restrictBindMounts: false, restrictPrivilegedMode: false, labelName: '', @@ -39,6 +40,10 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_ settings.LogoURL = ''; } + if (!$scope.formValues.externalTemplates) { + settings.TemplatesURL = ''; + } + settings.AllowBindMountsForRegularUsers = !$scope.formValues.restrictBindMounts; settings.AllowPrivilegedModeForRegularUsers = !$scope.formValues.restrictPrivilegedMode; @@ -70,6 +75,9 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_ if (settings.LogoURL !== '') { $scope.formValues.customLogo = true; } + if (settings.TemplatesURL !== '') { + $scope.formValues.externalTemplates = true; + } $scope.formValues.restrictBindMounts = !settings.AllowBindMountsForRegularUsers; $scope.formValues.restrictPrivilegedMode = !settings.AllowPrivilegedModeForRegularUsers; }) diff --git a/app/portainer/views/templates/templates.html b/app/portainer/views/templates/templates.html index a0715ef2b..14bed010d 100644 --- a/app/portainer/views/templates/templates.html +++ b/app/portainer/views/templates/templates.html @@ -353,9 +353,9 @@ templates="templates" select-action="selectTemplate" delete-action="deleteTemplate" - show-add-action="isAdmin" - show-update-action="isAdmin" - show-delete-action="isAdmin" + show-add-action="state.templateManagement && isAdmin" + show-update-action="state.templateManagement && isAdmin" + show-delete-action="state.templateManagement && isAdmin" show-swarm-stacks="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER' && applicationState.endpoint.apiVersion >= 1.25" >
diff --git a/app/portainer/views/templates/templatesController.js b/app/portainer/views/templates/templatesController.js index 30c4119ec..286d4fe43 100644 --- a/app/portainer/views/templates/templatesController.js +++ b/app/portainer/views/templates/templatesController.js @@ -5,7 +5,8 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima selectedTemplate: null, showAdvancedOptions: false, formValidationError: '', - actionInProgress: false + actionInProgress: false, + templateManagement: true }; $scope.formValues = { @@ -253,6 +254,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima $scope.availableNetworks = networks; var settings = data.settings; $scope.allowBindMounts = settings.AllowBindMountsForRegularUsers; + $scope.state.templateManagement = !settings.ExternalTemplates; }) .catch(function error(err) { $scope.templates = []; From 5222413532bad44475eec9b1bc515d03253d6749 Mon Sep 17 00:00:00 2001 From: baron_l Date: Thu, 9 Aug 2018 10:33:16 +0200 Subject: [PATCH 11/57] feat(volume-creation) : NFS volume creation (#2083) (#2108) * feat(volume-creation): NFS support for volume creation - layout * feat(volume-creation): NFS support for volume creation * fix(volume-creation): NFS style, display and check on submit * refactor(volume-creation): remove useless controller + refactor var naming * refactor(volume-creation): NFS wording, help and style --- .../volumesNFSForm/volumes-nfs-form.js | 6 ++ .../volumesNFSForm/volumesNFSFormModel.js | 8 ++ .../volumesNFSForm/volumesnfsForm.html | 90 +++++++++++++++++++ .../volumes/create/createVolumeController.js | 20 ++++- .../views/volumes/create/createvolume.html | 15 ++-- 5 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 app/docker/components/volumesNFSForm/volumes-nfs-form.js create mode 100644 app/docker/components/volumesNFSForm/volumesNFSFormModel.js create mode 100644 app/docker/components/volumesNFSForm/volumesnfsForm.html diff --git a/app/docker/components/volumesNFSForm/volumes-nfs-form.js b/app/docker/components/volumesNFSForm/volumes-nfs-form.js new file mode 100644 index 000000000..423c3bed0 --- /dev/null +++ b/app/docker/components/volumesNFSForm/volumes-nfs-form.js @@ -0,0 +1,6 @@ +angular.module('portainer.docker').component('volumesNfsForm', { + templateUrl: 'app/docker/components/volumesNFSForm/volumesnfsForm.html', + bindings: { + data: '=' + } +}); diff --git a/app/docker/components/volumesNFSForm/volumesNFSFormModel.js b/app/docker/components/volumesNFSForm/volumesNFSFormModel.js new file mode 100644 index 000000000..a1204dccb --- /dev/null +++ b/app/docker/components/volumesNFSForm/volumesNFSFormModel.js @@ -0,0 +1,8 @@ +function VolumesNFSFormData() { + this.useNFS = false; + this.serverAddress = ''; + this.mountPoint = ''; + this.version = 'NFS4'; + this.options = 'rw,noatime,rsize=8192,wsize=8192,tcp,timeo=14'; + this.versions = ['NFS4', 'NFS']; +} \ No newline at end of file diff --git a/app/docker/components/volumesNFSForm/volumesnfsForm.html b/app/docker/components/volumesNFSForm/volumesnfsForm.html new file mode 100644 index 000000000..bd0d1014c --- /dev/null +++ b/app/docker/components/volumesNFSForm/volumesnfsForm.html @@ -0,0 +1,90 @@ +
+
+ + +
+ +
+ +
+ NFS Settings +
+ +
+ +
+ +
+
+
+
+
+

+ This field is required.

+
+
+
+ + +
+ +
+ +
+
+
+
+
+

+ This field is required.

+
+
+
+ + +
+ +
+ +
+
+
+
+
+

+ This field is required.

+
+
+
+ + +
+ +
+ +
+
+
+
+
+

+ This field is required.

+
+
+
+ +
+
+ +
\ No newline at end of file diff --git a/app/docker/views/volumes/create/createVolumeController.js b/app/docker/views/volumes/create/createVolumeController.js index 5180f3976..a9ccec09b 100644 --- a/app/docker/views/volumes/create/createVolumeController.js +++ b/app/docker/views/volumes/create/createVolumeController.js @@ -6,7 +6,8 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi Driver: 'local', DriverOptions: [], AccessControlData: new AccessControlFormData(), - NodeName: null + NodeName: null, + NFSData: new VolumesNFSFormData() }; $scope.state = { @@ -36,8 +37,19 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi return true; } - $scope.create = function () { + function prepareNFSConfiguration(driverOptions) { + var data = $scope.formValues.NFSData; + driverOptions.push({ name: 'type', value: data.version.toLowerCase() }); + + var options = 'addr=' + data.serverAddress + ',' + data.options; + driverOptions.push({ name: 'o', value: options }); + + var mountPoint = data.mountPoint[0] === ':' ? data.mountPoint : ':' + data.mountPoint; + driverOptions.push({ name: 'device', value: mountPoint }); + } + + $scope.create = function () { var name = $scope.formValues.Name; var driver = $scope.formValues.Driver; var driverOptions = $scope.formValues.DriverOptions; @@ -47,6 +59,10 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi driverOptions.push({ name: 'profile', value: storidgeProfile.Name }); } + if ($scope.formValues.NFSData.useNFS) { + prepareNFSConfiguration(driverOptions); + } + var volumeConfiguration = VolumeService.createVolumeConfiguration(name, driver, driverOptions); var accessControlData = $scope.formValues.AccessControlData; var userDetails = Authentication.getUserDetails(); diff --git a/app/docker/views/volumes/create/createvolume.html b/app/docker/views/volumes/create/createvolume.html index 0547eef8a..a591661d8 100644 --- a/app/docker/views/volumes/create/createvolume.html +++ b/app/docker/views/volumes/create/createvolume.html @@ -9,11 +9,11 @@
-
+
- -
+ +
@@ -23,8 +23,8 @@
- -
+ +
@@ -62,6 +62,9 @@
+ + +
@@ -89,7 +92,7 @@
- From 9c0b568773895cd6b05f9752588839f3d03a0c69 Mon Sep 17 00:00:00 2001 From: baron_l Date: Thu, 9 Aug 2018 10:40:06 +0200 Subject: [PATCH 12/57] feat(container-creation): container add/drop capabilities on creation (#468) (#2078) * feat(container-creation): container add/drop capabilities on creation * feat(container-creation): capabilities are now loaded on edit/duplicate/update --- .../container-capabilities.js | 6 ++ .../containerCapabilities.html | 22 +++++ app/docker/models/containerCapabilities.js | 90 +++++++++++++++++++ .../create/createContainerController.js | 35 +++++++- .../containers/create/createcontainer.html | 6 ++ 5 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 app/docker/components/container-capabilities/container-capabilities.js create mode 100644 app/docker/components/container-capabilities/containerCapabilities.html create mode 100644 app/docker/models/containerCapabilities.js diff --git a/app/docker/components/container-capabilities/container-capabilities.js b/app/docker/components/container-capabilities/container-capabilities.js new file mode 100644 index 000000000..6d572a464 --- /dev/null +++ b/app/docker/components/container-capabilities/container-capabilities.js @@ -0,0 +1,6 @@ +angular.module('portainer.docker').component('containerCapabilities', { + templateUrl: 'app/docker/components/container-capabilities/containerCapabilities.html', + bindings: { + capabilities: '=' + } +}); diff --git a/app/docker/components/container-capabilities/containerCapabilities.html b/app/docker/components/container-capabilities/containerCapabilities.html new file mode 100644 index 000000000..5d1f8401e --- /dev/null +++ b/app/docker/components/container-capabilities/containerCapabilities.html @@ -0,0 +1,22 @@ + +
+ Container capabilities +
+
+
+
+ +
+
+ +
+
+
+
+
+ \ No newline at end of file diff --git a/app/docker/models/containerCapabilities.js b/app/docker/models/containerCapabilities.js new file mode 100644 index 000000000..0ff22239e --- /dev/null +++ b/app/docker/models/containerCapabilities.js @@ -0,0 +1,90 @@ +var capDesc = { + 'SETPCAP': 'Modify process capabilities.', + 'MKNOD': 'Create special files using mknod(2).', + 'AUDIT_WRITE': 'Write records to kernel auditing log.', + 'CHOWN': 'Make arbitrary changes to file UIDs and GIDs (see chown(2)).', + 'NET_RAW': 'Use RAW and PACKET sockets.', + 'DAC_OVERRIDE': 'Bypass file read, write, and execute permission checks.', + 'FOWNER': 'Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file.', + 'FSETID': 'Don’t clear set-user-ID and set-group-ID permission bits when a file is modified.', + 'KILL': 'Bypass permission checks for sending signals.', + 'SETGID': 'Make arbitrary manipulations of process GIDs and supplementary GID list.', + 'SETUID': 'Make arbitrary manipulations of process UIDs.', + 'NET_BIND_SERVICE': 'Bind a socket to internet domain privileged ports (port numbers less than 1024).', + 'SYS_CHROOT': 'Use chroot(2), change root directory.', + 'SETFCAP': 'Set file capabilities.', + 'SYS_MODULE': 'Load and unload kernel modules.', + 'SYS_RAWIO': 'Perform I/O port operations (iopl(2) and ioperm(2)).', + 'SYS_PACCT': 'Use acct(2), switch process accounting on or off.', + 'SYS_ADMIN': 'Perform a range of system administration operations.', + 'SYS_NICE': 'Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes.', + 'SYS_RESOURCE': 'Override resource Limits.', + 'SYS_TIME': 'Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock.', + 'SYS_TTY_CONFIG': 'Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals.', + 'AUDIT_CONTROL': 'Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules.', + 'MAC_ADMIN': 'Allow MAC configuration or state changes. Implemented for the Smack LSM.', + 'MAC_OVERRIDE': 'Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM).', + 'NET_ADMIN': 'Perform various network-related operations.', + 'SYSLOG': 'Perform privileged syslog(2) operations.', + 'DAC_READ_SEARCH': 'Bypass file read permission checks and directory read and execute permission checks.', + 'LINUX_IMMUTABLE': 'Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags.', + 'NET_BROADCAST': 'Make socket broadcasts, and listen to multicasts.', + 'IPC_LOCK': 'Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)).', + 'IPC_OWNER': 'Bypass permission checks for operations on System V IPC objects.', + 'SYS_PTRACE': 'Trace arbitrary processes using ptrace(2).', + 'SYS_BOOT': 'Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution.', + 'LEASE': 'Establish leases on arbitrary files (see fcntl(2)).', + 'WAKE_ALARM': 'Trigger something that will wake up the system.', + 'BLOCK_SUSPEND': 'Employ features that can block system suspend.' +}; + +function ContainerCapabilities() { + // all capabilities can be found at https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities + return [ + new ContainerCapability('SETPCAP', true), + new ContainerCapability('MKNOD', true), + new ContainerCapability('AUDIT_WRITE', true), + new ContainerCapability('CHOWN', true), + new ContainerCapability('NET_RAW', true), + new ContainerCapability('DAC_OVERRIDE', true), + new ContainerCapability('FOWNER', true), + new ContainerCapability('FSETID', true), + new ContainerCapability('KILL', true), + new ContainerCapability('SETGID', true), + new ContainerCapability('SETUID', true), + new ContainerCapability('NET_BIND_SERVICE', true), + new ContainerCapability('SYS_CHROOT', true), + new ContainerCapability('SETFCAP', true), + new ContainerCapability('SYS_MODULE', false), + new ContainerCapability('SYS_RAWIO', false), + new ContainerCapability('SYS_PACCT', false), + new ContainerCapability('SYS_ADMIN', false), + new ContainerCapability('SYS_NICE', false), + new ContainerCapability('SYS_RESOURCE', false), + new ContainerCapability('SYS_TIME', false), + new ContainerCapability('SYS_TTY_CONFIG', false), + new ContainerCapability('AUDIT_CONTROL', false), + new ContainerCapability('MAC_ADMIN', false), + new ContainerCapability('MAC_OVERRIDE', false), + new ContainerCapability('NET_ADMIN', false), + new ContainerCapability('SYSLOG', false), + new ContainerCapability('DAC_READ_SEARCH', false), + new ContainerCapability('LINUX_IMMUTABLE', false), + new ContainerCapability('NET_BROADCAST', false), + new ContainerCapability('IPC_LOCK', false), + new ContainerCapability('IPC_OWNER', false), + new ContainerCapability('SYS_PTRACE', false), + new ContainerCapability('SYS_BOOT', false), + new ContainerCapability('LEASE', false), + new ContainerCapability('WAKE_ALARM', false), + new ContainerCapability('BLOCK_SUSPEND', false) + ].sort(function (a, b) { + return a.capability < b.capability ? -1 : 1; + }); +} + +function ContainerCapability(cap, allowed) { + this.capability = cap; + this.allowed = allowed; + this.description = capDesc[cap]; +} \ No newline at end of file diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index d45b989be..530c22dd7 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -16,7 +16,8 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai CpuLimit: 0, MemoryLimit: 0, MemoryReservation: 0, - NodeName: null + NodeName: null, + capabilities: [] }; $scope.extraNetworks = {}; @@ -48,7 +49,9 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai NetworkMode: 'bridge', Privileged: false, ExtraHosts: [], - Devices:[] + Devices: [], + CapAdd: [], + CapDrop: [] }, NetworkingConfig: { EndpointsConfig: {} @@ -251,6 +254,15 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } } + function prepareCapabilities(config) { + var allowed = $scope.formValues.capabilities.filter(function(item) {return item.allowed === true;}); + var notAllowed = $scope.formValues.capabilities.filter(function(item) {return item.allowed === false;}); + + var getCapName = function(item) {return item.capability;}; + config.HostConfig.CapAdd = allowed.map(getCapName); + config.HostConfig.CapDrop = notAllowed.map(getCapName); + } + function prepareConfiguration() { var config = angular.copy($scope.config); config.Cmd = ContainerHelper.commandStringToArray(config.Cmd); @@ -263,6 +275,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai prepareLabels(config); prepareDevices(config); prepareResources(config); + prepareCapabilities(config); return config; } @@ -478,6 +491,22 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } } + function loadFromContainerCapabilities(d) { + if (d.HostConfig.CapAdd) { + d.HostConfig.CapAdd.forEach(function(cap) { + $scope.formValues.capabilities.push(new ContainerCapability(cap, true)); + }); + } + if (d.HostConfig.CapDrop) { + d.HostConfig.CapDrop.forEach(function(cap) { + $scope.formValues.capabilities.push(new ContainerCapability(cap, false)); + }); + } + $scope.formValues.capabilities.sort(function(a, b) { + return a.capability < b.capability ? -1 : 1; + }); + } + function loadFromContainerSpec() { // Get container Container.get({ id: $transition$.params().from }).$promise @@ -498,6 +527,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai loadFromContainerDevices(d); loadFromContainerImageConfig(d); loadFromContainerResources(d); + loadFromContainerCapabilities(d); }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to retrieve container'); @@ -543,6 +573,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } else { $scope.fromContainer = {}; $scope.formValues.Registry = {}; + $scope.formValues.capabilities = new ContainerCapabilities(); } }, function(e) { Notifications.error('Failure', e, 'Unable to retrieve running containers'); diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index 4c6f957c1..e466f3249 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -152,6 +152,7 @@
  • Labels
  • Restart policy
  • Runtime & Resources
  • +
  • Capabilities
  • @@ -585,6 +586,11 @@
    + +
    + +
    +
    From 80c2adfc5331f674096d9fb379fa9cf610e78e21 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 9 Aug 2018 17:53:25 +0200 Subject: [PATCH 13/57] chore(ci): remove codefresh workflows (#2144) --- .codefresh/codefresh_branch.yml | 46 ---------------------------- .codefresh/codefresh_pullrequest.yml | 46 ---------------------------- 2 files changed, 92 deletions(-) delete mode 100644 .codefresh/codefresh_branch.yml delete mode 100644 .codefresh/codefresh_pullrequest.yml diff --git a/.codefresh/codefresh_branch.yml b/.codefresh/codefresh_branch.yml deleted file mode 100644 index 0d6e52555..000000000 --- a/.codefresh/codefresh_branch.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: '1.0' -steps: - - build_backend: - image: portainer/golang-builder:ci - working_directory: ${{main_clone}} - commands: - - mkdir -p /go/src/github.com/${{CF_REPO_OWNER}} - - ln -s /codefresh/volume/${{CF_REPO_NAME}}/api /go/src/github.com/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}} - - /build.sh api/cmd/portainer - - build_frontend: - image: portainer/angular-builder:latest - working_directory: ${{build_backend}} - commands: - - yarn - - yarn grunt build-webapp - - mv api/cmd/portainer/portainer dist/ - - get_docker_version: - image: alpine:3.7 - working_directory: ${{build_frontend}} - commands: - - cf_export DOCKER_VERSION=`cat gruntfile.js | grep -m 1 'shippedDockerVersion' | cut -d\' -f2` - - download_docker_binary: - image: busybox - working_directory: ${{build_frontend}} - commands: - - echo ${{DOCKER_VERSION}} - - wget -O /tmp/docker-binaries.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${{DOCKER_VERSION}}.tgz - - tar -xf /tmp/docker-binaries.tgz -C /tmp - - mv /tmp/docker/docker dist/ - - build_image: - type: build - working_directory: ${{download_docker_binary}} - dockerfile: ./build/linux/Dockerfile - image_name: portainer/portainer - tag: ${{CF_BRANCH}} - - push_image: - type: push - candidate: '${{build_image}}' - tag: '${{CF_BRANCH}}' - registry: dockerhub diff --git a/.codefresh/codefresh_pullrequest.yml b/.codefresh/codefresh_pullrequest.yml deleted file mode 100644 index d48b9870f..000000000 --- a/.codefresh/codefresh_pullrequest.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: '1.0' -steps: - - build_backend: - image: portainer/golang-builder:ci - working_directory: ${{main_clone}} - commands: - - mkdir -p /go/src/github.com/${{CF_REPO_OWNER}} - - ln -s /codefresh/volume/${{CF_REPO_NAME}}/api /go/src/github.com/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}} - - /build.sh api/cmd/portainer - - build_frontend: - image: portainer/angular-builder:latest - working_directory: ${{build_backend}} - commands: - - yarn - - yarn grunt build-webapp - - mv api/cmd/portainer/portainer dist/ - - get_docker_version: - image: alpine:3.7 - working_directory: ${{build_frontend}} - commands: - - cf_export DOCKER_VERSION=`cat gruntfile.js | grep -m 1 'shippedDockerVersion' | cut -d\' -f2` - - download_docker_binary: - image: busybox - working_directory: ${{build_frontend}} - commands: - - echo ${{DOCKER_VERSION}} - - wget -O /tmp/docker-binaries.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${{DOCKER_VERSION}}.tgz - - tar -xf /tmp/docker-binaries.tgz -C /tmp - - mv /tmp/docker/docker dist/ - - build_image: - type: build - working_directory: ${{download_docker_binary}} - dockerfile: ./build/linux/Dockerfile - image_name: portainer/portainer - tag: ${{CF_BRANCH}} - - push_image: - type: push - candidate: '${{build_image}}' - tag: 'pr${{CF_PULL_REQUEST_NUMBER}}' - registry: dockerhub From cbe4cc92dbba81491ea12c38811fbb013a124ffd Mon Sep 17 00:00:00 2001 From: salcedo Date: Mon, 13 Aug 2018 03:11:54 -0400 Subject: [PATCH 14/57] feat(templates): update file browser image (#2152) --- templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates.json b/templates.json index bedcd2dd6..b83f0c2e0 100644 --- a/templates.json +++ b/templates.json @@ -634,7 +634,7 @@ "categories": ["filesystem", "storage"], "platform": "linux", "logo": "https://portainer.io/images/logos/filebrowser.png", - "image": "hacdias/filemanager:latest", + "image": "filebrowser/filebrowser:latest", "ports": [ "80/tcp" ], From df1592a3d2c6f6cfb2879e741454fcd61bcf3e50 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 13 Aug 2018 14:06:54 +0200 Subject: [PATCH 15/57] feat(templates): add datadog agent templates --- templates.json | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/templates.json b/templates.json index b83f0c2e0..97d7f2b61 100644 --- a/templates.json +++ b/templates.json @@ -500,6 +500,38 @@ } ] }, + { + "type": 1, + "title": "Datadog agent", + "description": "Collect events and metrics", + "categories": ["Monitoring"], + "platform": "linux", + "logo": "https://portainer.io/images/logos/datadog_agent.png", + "image": "datadog/agent:latest", + "env": [ + { + "name": "DD_API_KEY", + "label": "Datadog API key" + } + ], + "volumes": [ + { + "container": "/var/run/docker.sock", + "bind": "/var/run/docker.sock", + "readonly": true + }, + { + "container": "/host/sys/fs/cgroup", + "bind": "/sys/fs/cgroup", + "readonly": true + }, + { + "container": "/host/proc", + "bind": "/proc", + "readonly": true + } + ] + }, { "type": 1, "title": "Mautic", @@ -824,5 +856,23 @@ "label": "SPM monitoring token" } ] + }, + { + "title": "Datadog agent", + "type": 2, + "categories": ["Monitoring"], + "description": "Collect events and metrics", + "logo": "https://portainer.io/images/logos/datadog_agent.png", + "platform": "linux", + "repository": { + "url": "https://github.com/portainer/templates", + "stackfile": "stacks/datadog-agent/docker-stack.yml" + }, + "env": [ + { + "name": "API_KEY", + "label": "Datadog API key" + } + ] } ] From d4e4d34ea46f91a412b89a7106c9791ed49ebaa5 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Mon, 13 Aug 2018 16:28:59 +0100 Subject: [PATCH 16/57] chore(build-system): add dev, clean, build scripts (#2146) --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 33a723fda..db1d80fb5 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,11 @@ "url": "https://raw.githubusercontent.com/portainer/portainer/develop/LICENSE" } ], + "scripts": { + "dev": "yarn grunt run-dev", + "clean-all": "yarn grunt clean:all", + "build": "yarn grunt build" + }, "engines": { "node": ">= 0.8.4" }, From 1233cb7f084a1da4175d489a61634dcbb801df79 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 13 Aug 2018 19:10:09 +0200 Subject: [PATCH 17/57] chore(project): update lodash version to 4.17.10 (#2156) --- package.json | 2 +- yarn.lock | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index db1d80fb5..74f4215dc 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "isteven-angular-multiselect": "~4.0.0", "jquery": "^3.3.1", "js-yaml": "~3.10.0", - "lodash": "4.12.0", + "lodash": "^4.17.10", "moment": "^2.21.0", "ng-file-upload": "~12.2.13", "rdash-ui": "1.0.*", diff --git a/yarn.lock b/yarn.lock index 0cfd0d2c4..275854f70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2698,14 +2698,18 @@ lodash@3.7.x: version "3.7.0" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45" -lodash@4.12.0, lodash@^4.0.0, lodash@^4.11.0, lodash@^4.3.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.12.0.tgz#2bd6dc46a040f59e686c972ed21d93dc59053258" - lodash@^3.10.0, lodash@^3.2.0, lodash@^3.6.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" +lodash@^4.0.0, lodash@^4.11.0, lodash@^4.3.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.12.0.tgz#2bd6dc46a040f59e686c972ed21d93dc59053258" + +lodash@^4.17.10: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + lodash@~0.9.2: version "0.9.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-0.9.2.tgz#8f3499c5245d346d682e5b0d3b40767e09f1a92c" From f3dc67a852d390dcc21a03f9e557b2b360102140 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Mon, 13 Aug 2018 20:13:43 +0100 Subject: [PATCH 18/57] fix(container-details): change order of container recreation --- .../containers/edit/containerController.js | 129 ++++++++++++++---- 1 file changed, 99 insertions(+), 30 deletions(-) diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index ad0f261a8..d77819136 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -192,40 +192,109 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co var container = $scope.container; var config = ContainerHelper.configFromContainer(container.Model); $scope.state.recreateContainerInProgress = true; - ContainerService.remove(container, true) - .then(function success() { - return RegistryService.retrieveRegistryFromRepository(container.Config.Image); - }) - .then(function success(data) { - return $q.when(!pullImage || ImageService.pullImage(container.Config.Image, data, true)); - }) - .then(function success() { - return ContainerService.createAndStartContainer(config); - }) - .then(function success(data) { - if (!container.ResourceControl) { - return true; - } else { - var containerIdentifier = data.Id; - var resourceControl = container.ResourceControl; - var users = resourceControl.UserAccesses.map(function(u) { - return u.UserId; - }); - var teams = resourceControl.TeamAccesses.map(function(t) { - return t.TeamId; - }); - return ResourceControlService.createResourceControl(resourceControl.AdministratorsOnly, - users, teams, containerIdentifier, 'container', []); + var isRunning = container.State.Running; + + return stopContainerIfNeeded() + .then(renameContainer) + .then(pullImageIfNeeded) + .then(setMainNetworkAndCreateContainer) + .then(connectContainerToOtherNetworks) + .then(startContainerIfNeeded) + .then(createResourceControlIfNeeded) + .then(deleteOldContainer) + .then(notifyAndChangeView) + .catch(notifyOnError); + + function stopContainerIfNeeded() { + if (!isRunning) { + return $q.when(); } - }) - .then(function success(data) { + return ContainerService.stopContainer(container.Id); + } + + function renameContainer() { + return ContainerService.renameContainer(container.Id, container.Name + '-old'); + } + + function pullImageIfNeeded() { + if (!pullImage) { + return $q.when(); + } + return getRegistry().then(function pullImage(containerRegistery) { + return ImageService.pullImage(container.Config.Image, containerRegistery, true); + }); + } + + function getRegistry() { + return RegistryService.retrieveRegistryFromRepository(container.Config.Image); + } + + function setMainNetworkAndCreateContainer() { + var networks = config.NetworkingConfig.EndpointsConfig; + var networksNames = Object.keys(networks); + if (networksNames.length > 1) { + config.NetworkingConfig.EndpointsConfig = {}; + config.NetworkingConfig.EndpointsConfig[networksNames[0]] = networks[0]; + } + return $q.all([ContainerService.createContainer(config), networks]); + } + + function connectContainerToOtherNetworks(createContainerData) { + var newContainer = createContainerData[0]; + var networks = createContainerData[1]; + var networksNames = Object.keys(networks); + var connectionPromises = networksNames.map(function connectToNetwork(name) { + NetworkService.connectContainer(name, newContainer.Id); + }); + return $q.all(connectionPromises) + .then(function onConnectToNetworkSuccess() { + return newContainer; + }); + } + + function deleteOldContainer(newContainer) { + return ContainerService.remove(container, true).then( + function onRemoveSuccess() { + return newContainer; + } + ); + } + + function startContainerIfNeeded(newContainer) { + if (!isRunning) { + return $q.when(newContainer); + } + return ContainerService.startContainer(newContainer.Id).then( + function onStartSuccess() { + return newContainer; + } + ); + } + + function createResourceControlIfNeeded(newContainer) { + if (!container.ResourceControl) { + return $q.when(); + } + var containerIdentifier = newContainer.Id; + var resourceControl = container.ResourceControl; + var users = resourceControl.UserAccesses.map(function(u) { + return u.UserId; + }); + var teams = resourceControl.TeamAccesses.map(function(t) { + return t.TeamId; + }); + return ResourceControlService.createResourceControl(resourceControl.AdministratorsOnly, users, teams, containerIdentifier, 'container', []); + } + + function notifyAndChangeView() { Notifications.success('Container successfully re-created'); - $state.go('docker.containers', {}, {reload: true}); - }) - .catch(function error(err) { + $state.go('docker.containers', {}, { reload: true }); + } + + function notifyOnError(err) { Notifications.error('Failure', err, 'Unable to re-create container'); $scope.state.recreateContainerInProgress = false; - }); + } } $scope.recreate = function() { From 594daf0de8ea9dd795eacf6fbf661712ba8b79d3 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Mon, 13 Aug 2018 20:20:56 +0100 Subject: [PATCH 19/57] fix(home): Show correct number of cpus and total memory for swarm (#2147) * fix(home): show cpu/mem for swarm * fix(home): add nodes data to snapshot * fix(dashboard): get cpus/mem from snapshot * refactor(home): remove temp variable --- api/docker/snapshot.go | 21 +++++++++++++++++++ app/docker/views/dashboard/dashboard.html | 5 ++++- .../endpoint-item/endpointItem.html | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/api/docker/snapshot.go b/api/docker/snapshot.go index 5f37ad985..d8c481fb7 100644 --- a/api/docker/snapshot.go +++ b/api/docker/snapshot.go @@ -30,6 +30,11 @@ func snapshot(cli *client.Client) (*portainer.Snapshot, error) { if err != nil { return nil, err } + + err = snapshotNodes(snapshot, cli) + if err != nil { + return nil, err + } } err = snapshotContainers(snapshot, cli) @@ -64,6 +69,22 @@ func snapshotInfo(snapshot *portainer.Snapshot, cli *client.Client) error { return nil } +func snapshotNodes(snapshot *portainer.Snapshot, cli *client.Client) error { + nodes, err := cli.NodeList(context.Background(), types.NodeListOptions{}) + if err != nil { + return err + } + var nanoCpus int64 + var totalMem int64 + for _, node := range nodes { + nanoCpus += node.Description.Resources.NanoCPUs + totalMem += node.Description.Resources.MemoryBytes + } + snapshot.TotalCPU = int(nanoCpus / 1e9) + snapshot.TotalMemory = totalMem + return nil +} + func snapshotSwarmServices(snapshot *portainer.Snapshot, cli *client.Client) error { stacks := make(map[string]struct{}) diff --git a/app/docker/views/dashboard/dashboard.html b/app/docker/views/dashboard/dashboard.html index 2e31750fb..3821dfead 100644 --- a/app/docker/views/dashboard/dashboard.html +++ b/app/docker/views/dashboard/dashboard.html @@ -45,7 +45,10 @@ Endpoint {{ endpoint.Name }} - {{ info.NCPU }} {{ info.MemTotal | humansize }} + + {{ endpoint.Snapshots[0].TotalCPU }} + {{ endpoint.Snapshots[0].TotalMemory | humansize }} + - {{ info.Swarm && info.Swarm.NodeID !== '' ? 'Swarm' : 'Standalone' }} {{ info.ServerVersion }} + Agent diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html index 24723beac..322ab9a90 100644 --- a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html +++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html @@ -58,7 +58,7 @@
    - + {{ $ctrl.model.Snapshots[0].TotalCPU }} {{ $ctrl.model.Snapshots[0].TotalMemory | humansize }} From 55f719128b1e65bb614ab9adc811e23c0dcb5eb5 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 15 Aug 2018 21:02:01 +0200 Subject: [PATCH 20/57] docs(README): update build badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ee305f3f..7b2e9e096 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Docker Pulls](https://img.shields.io/docker/pulls/portainer/portainer.svg)](https://hub.docker.com/r/portainer/portainer/) [![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size") [![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/stable/?badge=stable) -[![Codefresh build status]( https://g.codefresh.io/api/badges/build?repoOwner=portainer&repoName=portainer&branch=develop&pipelineName=portainer-ci&accountName=deviantony&type=cf-1)]( https://g.codefresh.io/repositories/portainer/portainer/builds?filter=trigger:build;branch:develop;service:5922a08a3a1aab000116fcc6~portainer-ci) +[![Build Status](https://semaphoreci.com/api/v1/portainer/portainer/branches/develop/badge.svg)](https://semaphoreci.com/portainer/portainer) [![Code Climate](https://codeclimate.com/github/portainer/portainer/badges/gpa.svg)](https://codeclimate.com/github/portainer/portainer) [![Slack](https://portainer.io/slack/badge.svg)](https://portainer.io/slack/) [![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) From de9f99d030434da11528f91c414f5997a0abcf79 Mon Sep 17 00:00:00 2001 From: Ru Fan Date: Thu, 16 Aug 2018 17:28:06 +0800 Subject: [PATCH 21/57] feat(container-creation): add runtime option in (#2162) (#2163) --- .../containers/create/createContainerController.js | 9 +++++++++ .../views/containers/create/createcontainer.html | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 530c22dd7..0e017c6eb 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -48,6 +48,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai Binds: [], NetworkMode: 'bridge', Privileged: false, + Runtime: '', ExtraHosts: [], Devices: [], CapAdd: [], @@ -581,6 +582,14 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai SystemService.info() .then(function success(data) { + var runtimes = data.Runtimes; + $scope.availableRuntimes = runtimes; + if ('runc' in runtimes) { + $scope.config.HostConfig.Runtime = 'runc'; + } + else if (Object.keys(runtimes).length !== 0) { + $scope.config.HostConfig.Runtime = Object.keys(runtimes)[0]; + } $scope.state.sliderMaxCpu = 32; if (data.NCPU) { $scope.state.sliderMaxCpu = data.NCPU; diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index e466f3249..4ed3f6e4d 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -500,6 +500,17 @@
    + +
    + +
    + +
    +
    +
    From 8769fadd5c9a52f5a73813493ed33f8e10e79300 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 16 Aug 2018 10:31:00 +0100 Subject: [PATCH 22/57] feat(container-details): add the ability to update restart policy --- .../container-restart-policy-controller.js | 36 +++++++++++++++ .../container-restart-policy.html | 45 +++++++++++++++++++ .../container-restart-policy.js | 10 +++++ app/docker/rest/container.js | 3 ++ app/docker/services/containerService.js | 8 ++++ .../views/containers/edit/container.html | 18 +++----- .../containers/edit/containerController.js | 23 ++++++++++ 7 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 app/docker/components/container-restart-policy/container-restart-policy-controller.js create mode 100644 app/docker/components/container-restart-policy/container-restart-policy.html create mode 100644 app/docker/components/container-restart-policy/container-restart-policy.js diff --git a/app/docker/components/container-restart-policy/container-restart-policy-controller.js b/app/docker/components/container-restart-policy/container-restart-policy-controller.js new file mode 100644 index 000000000..c9ece16d4 --- /dev/null +++ b/app/docker/components/container-restart-policy/container-restart-policy-controller.js @@ -0,0 +1,36 @@ +angular + .module('portainer.docker') + .controller('ContainerRestartPolicyController', [ + function ContainerRestartPolicyController() { + var ctrl = this; + + this.state = { + editMode :false, + editModel :{} + }; + + + ctrl.toggleEdit = toggleEdit; + ctrl.save = save; + + function toggleEdit() { + ctrl.state.editMode = true; + ctrl.state.editModel = { + name: ctrl.name, + maximumRetryCount: ctrl.maximumRetryCount + }; + } + + function save() { + if (ctrl.state.editModel.name === ctrl.name && + ctrl.state.editModel.maximumRetryCount === ctrl.maximumRetryCount) { + ctrl.state.editMode = false; + return; + } + ctrl.updateRestartPolicy(ctrl.state.editModel) + .then(function onUpdateSucceed() { + ctrl.state.editMode = false; + }); + } + } + ]); diff --git a/app/docker/components/container-restart-policy/container-restart-policy.html b/app/docker/components/container-restart-policy/container-restart-policy.html new file mode 100644 index 000000000..c5352b6e4 --- /dev/null +++ b/app/docker/components/container-restart-policy/container-restart-policy.html @@ -0,0 +1,45 @@ +
    + + + + + + + + + + +
    + + + + Name + {{$ctrl.name }}
    Maximum Retry Count + {{ $ctrl.maximumRetryCount }} +
    + + + + + + + + + + + +
    + Name + + + + +
    Maximum Retry Count + +
    +
    \ No newline at end of file diff --git a/app/docker/components/container-restart-policy/container-restart-policy.js b/app/docker/components/container-restart-policy/container-restart-policy.js new file mode 100644 index 000000000..0a8d7edf2 --- /dev/null +++ b/app/docker/components/container-restart-policy/container-restart-policy.js @@ -0,0 +1,10 @@ +angular.module('portainer.docker') +.component('containerRestartPolicy', { + templateUrl: 'app/docker/components/container-restart-policy/container-restart-policy.html', + controller: 'ContainerRestartPolicyController', + bindings: { + 'name': '<', + 'maximumRetryCount': '<', + 'updateRestartPolicy': '&' + } +}); diff --git a/app/docker/rest/container.js b/app/docker/rest/container.js index d100d5a1c..d46d9e474 100644 --- a/app/docker/rest/container.js +++ b/app/docker/rest/container.js @@ -65,6 +65,9 @@ function ContainerFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { }, inspect: { method: 'GET', params: { id: '@id', action: 'json' } + }, + update: { + method: 'POST', params: { id: '@id', action: 'update'} } }); }]); diff --git a/app/docker/services/containerService.js b/app/docker/services/containerService.js index a0783d2d9..ce81cd8c5 100644 --- a/app/docker/services/containerService.js +++ b/app/docker/services/containerService.js @@ -63,6 +63,14 @@ function ContainerServiceFactory($q, Container, ResourceControlService, LogHelpe return Container.rename({id: id, name: newContainerName }, {}).$promise; }; + service.updateRestartPolicy = updateRestartPolicy; + + function updateRestartPolicy(id, restartPolicy, maximumRetryCounts) { + return Container.update({ id: id }, + { RestartPolicy: { Name: restartPolicy, MaximumRetryCount: maximumRetryCounts } } + ).$promise; + } + service.createContainer = function(configuration) { var deferred = $q.defer(); Container.create(configuration).$promise diff --git a/app/docker/views/containers/edit/container.html b/app/docker/views/containers/edit/container.html index 967d5321e..92815f9c0 100644 --- a/app/docker/views/containers/edit/container.html +++ b/app/docker/views/containers/edit/container.html @@ -223,21 +223,13 @@ - + Restart policies - - - - - - - - - -
    Name{{ container.HostConfig.RestartPolicy.Name }}
    MaximumRetryCount - {{ container.HostConfig.RestartPolicy.MaximumRetryCount }} -
    + diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index d77819136..22f56e7b7 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -15,6 +15,8 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co leaveNetworkInProgress: false }; + $scope.updateRestartPolicy = updateRestartPolicy; + var update = function () { var nodeName = $transition$.params().nodeName; HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); @@ -308,6 +310,27 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co }); }; + function updateRestartPolicy(restartPolicy, maximumRetryCount) { + maximumRetryCount = restartPolicy === 'on-failure' ? maximumRetryCount : undefined; + + return ContainerService + .updateRestartPolicy($scope.container.Id, restartPolicy, maximumRetryCount) + .then(onUpdateSuccess) + .catch(notifyOnError); + + function onUpdateSuccess() { + $scope.container.HostConfig.RestartPolicy = { + Name: restartPolicy, + MaximumRetryCount: maximumRetryCount + }; + } + + function notifyOnError(err) { + Notifications.error('Failure', err, 'Unable to update restart policy'); + return $q.reject(err); + } + } + var provider = $scope.applicationState.endpoint.mode.provider; var apiVersion = $scope.applicationState.endpoint.apiVersion; NetworkService.networks( From bda5eac0c16054d7101a4c0bb464b2e8e072704e Mon Sep 17 00:00:00 2001 From: baron_l Date: Thu, 16 Aug 2018 12:29:15 +0200 Subject: [PATCH 23/57] feat(network-creation): enhance UX with macvlan driver for swarm mode (#2082) (#2122) * feat(network-creation): macvlan driver for swarm * refactor(network-creation): layout rework to make it simpler with MACVLAN and keep it consistent with other drivers * fix(network-creation): MACVLAN - parent network card is now properly saved, names are not prefixed anymore and the --attachable option is now supported * refactor(network-creation): PR macvlan review - rework of macvlan view + code optimisation * fix(network-creation): disable attachable and internal options on macvlan config creation --- .../macvlanNodesDatatable.html | 106 ++++++ .../macvlanNodesDatatable.js | 15 + .../network-macvlan-form.js | 8 + .../networkMacvlanForm.html | 108 ++++++ .../networkMacvlanFormController.js | 51 +++ .../networkMacvlanFormModel.js | 8 + app/docker/models/network.js | 5 +- .../create/createNetworkController.js | 321 +++++++++++------- .../views/networks/create/createnetwork.html | 85 +++-- assets/css/app.css | 6 + 10 files changed, 559 insertions(+), 154 deletions(-) create mode 100644 app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html create mode 100644 app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.js create mode 100644 app/docker/components/network-macvlan-form/network-macvlan-form.js create mode 100644 app/docker/components/network-macvlan-form/networkMacvlanForm.html create mode 100644 app/docker/components/network-macvlan-form/networkMacvlanFormController.js create mode 100644 app/docker/components/network-macvlan-form/networkMacvlanFormModel.js diff --git a/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html new file mode 100644 index 000000000..5b8b3660e --- /dev/null +++ b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html @@ -0,0 +1,106 @@ +
    + + +
    +
    + {{ $ctrl.titleText }} +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + Name + + + + + + Role + + + + + + Engine + + + + + + IP Address + + + + + + Status + + + +
    + + + + + {{ item.Hostname }} + {{ item.Hostname }} + {{ item.Role }}{{ item.EngineVersion }}{{ item.Addr }} + {{ item.Status }} +
    Loading...
    No node available.
    +
    + +
    +
    +
    \ No newline at end of file diff --git a/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.js b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.js new file mode 100644 index 000000000..d18e47ab7 --- /dev/null +++ b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.js @@ -0,0 +1,15 @@ +angular.module('portainer.docker').component('macvlanNodesDatatable', { + templateUrl: 'app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html', + controller: 'GenericDatatableController', + bindings: { + titleText: '@', + titleIcon: '@', + dataset: '<', + tableKey: '@', + orderBy: '@', + reverseOrder: '<', + showIpAddressColumn: '<', + accessToNodeDetails: '<', + state: '=' + } +}); diff --git a/app/docker/components/network-macvlan-form/network-macvlan-form.js b/app/docker/components/network-macvlan-form/network-macvlan-form.js new file mode 100644 index 000000000..f9981f1cd --- /dev/null +++ b/app/docker/components/network-macvlan-form/network-macvlan-form.js @@ -0,0 +1,8 @@ +angular.module('portainer.docker').component('networkMacvlanForm', { + templateUrl: 'app/docker/components/network-macvlan-form/networkMacvlanForm.html', + controller: 'NetworkMacvlanFormController', + bindings: { + data: '=', + applicationState: '<' + } +}); \ No newline at end of file diff --git a/app/docker/components/network-macvlan-form/networkMacvlanForm.html b/app/docker/components/network-macvlan-form/networkMacvlanForm.html new file mode 100644 index 000000000..b104bac9d --- /dev/null +++ b/app/docker/components/network-macvlan-form/networkMacvlanForm.html @@ -0,0 +1,108 @@ +
    +
    + Macvlan configuration +
    + +
    + + + To create a MACVLAN network you need to create a configuration, then create the network from this configuration. + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + + + +
    + +
    + +
    + +
    +
    +
    +
    +
    +

    + Parent network card must be specified.

    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    +
    +
    +

    + At least one node must be selected.

    +
    +
    +
    +
    + +
    + + +
    + + +
    + +
    + +
    +
    + +
    +
    +
    +

    + Select a configuration network.

    +
    +
    +
    + +
    + +
    +
    \ No newline at end of file diff --git a/app/docker/components/network-macvlan-form/networkMacvlanFormController.js b/app/docker/components/network-macvlan-form/networkMacvlanFormController.js new file mode 100644 index 000000000..5cf4231fb --- /dev/null +++ b/app/docker/components/network-macvlan-form/networkMacvlanFormController.js @@ -0,0 +1,51 @@ +angular.module('portainer.docker') + .controller('NetworkMacvlanFormController', ['$q', 'NodeService', 'NetworkService', 'Notifications', 'StateManager', 'Authentication', + function ($q, NodeService, NetworkService, Notifications, StateManager, Authentication) { + var ctrl = this; + + ctrl.requiredNodeSelection = function () { + if (ctrl.data.Scope !== 'local' || ctrl.data.DatatableState === undefined) { + return false; + } + return ctrl.data.DatatableState.selectedItemCount === 0; + }; + + ctrl.requiredConfigSelection = function () { + if (ctrl.data.Scope !== 'swarm') { + return false; + } + return !ctrl.data.SelectedNetworkConfig; + }; + + function initComponent() { + if (StateManager.getState().application.authentication) { + var userDetails = Authentication.getUserDetails(); + var isAdmin = userDetails.role === 1 ? true : false; + ctrl.isAdmin = isAdmin; + } + var provider = ctrl.applicationState.endpoint.mode.provider; + var apiVersion = ctrl.applicationState.endpoint.apiVersion; + $q.all({ + nodes: provider !== 'DOCKER_SWARM_MODE' || NodeService.nodes(), + networks: NetworkService.networks( + provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE', + false, + provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25 + ) + }) + .then(function success(data) { + if (data.nodes !== true) { + ctrl.nodes = data.nodes; + } + ctrl.availableNetworks = data.networks.filter(function (item) { + return item.ConfigOnly === true; + }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve informations for macvlan'); + }); + } + + initComponent(); + } + ]); \ No newline at end of file diff --git a/app/docker/components/network-macvlan-form/networkMacvlanFormModel.js b/app/docker/components/network-macvlan-form/networkMacvlanFormModel.js new file mode 100644 index 000000000..10ed60510 --- /dev/null +++ b/app/docker/components/network-macvlan-form/networkMacvlanFormModel.js @@ -0,0 +1,8 @@ +function MacvlanFormData() { + this.Scope = 'local'; + this.SelectedNetworkConfig = ''; + this.DatatableState = { + selectedItems: [] + }; + this.ParentNetworkCard = ''; +} \ No newline at end of file diff --git a/app/docker/models/network.js b/app/docker/models/network.js index 402d4cd96..744f10061 100644 --- a/app/docker/models/network.js +++ b/app/docker/models/network.js @@ -23,4 +23,7 @@ function NetworkViewModel(data) { this.NodeName = data.Portainer.Agent.NodeName; } } -} + + this.ConfigFrom = data.ConfigFrom; + this.ConfigOnly = data.ConfigOnly; +} \ No newline at end of file diff --git a/app/docker/views/networks/create/createNetworkController.js b/app/docker/views/networks/create/createNetworkController.js index 0f6d3f31c..9f5dd6493 100644 --- a/app/docker/views/networks/create/createNetworkController.js +++ b/app/docker/views/networks/create/createNetworkController.js @@ -1,138 +1,207 @@ angular.module('portainer.docker') -.controller('CreateNetworkController', ['$q', '$scope', '$state', 'PluginService', 'Notifications', 'NetworkService', 'LabelHelper', 'Authentication', 'ResourceControlService', 'FormValidator', 'HttpRequestHelper', -function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator, HttpRequestHelper) { + .controller('CreateNetworkController', ['$q', '$scope', '$state', 'PluginService', 'Notifications', 'NetworkService', 'LabelHelper', 'Authentication', 'ResourceControlService', 'FormValidator', 'HttpRequestHelper', + function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator, HttpRequestHelper) { - $scope.formValues = { - DriverOptions: [], - Subnet: '', - Gateway: '', - Labels: [], - AccessControlData: new AccessControlFormData(), - NodeName: null - }; + $scope.formValues = { + DriverOptions: [], + Subnet: '', + Gateway: '', + IPRange: '', + AuxAddress: '', + Labels: [], + AccessControlData: new AccessControlFormData(), + NodeName: null, + Macvlan: new MacvlanFormData() + }; - $scope.state = { - formValidationError: '', - actionInProgress: false - }; + $scope.state = { + formValidationError: '', + actionInProgress: false + }; - $scope.availableNetworkDrivers = []; + $scope.availableNetworkDrivers = []; - $scope.config = { - Driver: 'bridge', - CheckDuplicate: true, - Internal: false, - // Force IPAM Driver to 'default', should not be required. - // See: https://github.com/docker/docker/issues/25735 - IPAM: { - Driver: 'default', - Config: [] - }, - Labels: {} - }; + $scope.config = { + Driver: 'bridge', + CheckDuplicate: true, + Internal: false, + Attachable: false, + // Force IPAM Driver to 'default', should not be required. + // See: https://github.com/docker/docker/issues/25735 + IPAM: { + Driver: 'default', + Config: [] + }, + Labels: {} + }; - $scope.addDriverOption = function() { - $scope.formValues.DriverOptions.push({ name: '', value: '' }); - }; + $scope.addDriverOption = function () { + $scope.formValues.DriverOptions.push({ + name: '', + value: '' + }); + }; - $scope.removeDriverOption = function(index) { - $scope.formValues.DriverOptions.splice(index, 1); - }; + $scope.removeDriverOption = function (index) { + $scope.formValues.DriverOptions.splice(index, 1); + }; - $scope.addLabel = function() { - $scope.formValues.Labels.push({ key: '', value: ''}); - }; + $scope.addLabel = function () { + $scope.formValues.Labels.push({ + key: '', + value: '' + }); + }; - $scope.removeLabel = function(index) { - $scope.formValues.Labels.splice(index, 1); - }; + $scope.removeLabel = function (index) { + $scope.formValues.Labels.splice(index, 1); + }; - function prepareIPAMConfiguration(config) { - if ($scope.formValues.Subnet) { - var ipamConfig = {}; - ipamConfig.Subnet = $scope.formValues.Subnet; - if ($scope.formValues.Gateway) { - ipamConfig.Gateway = $scope.formValues.Gateway ; + function prepareIPAMConfiguration(config) { + if ($scope.formValues.Subnet) { + var ipamConfig = {}; + ipamConfig.Subnet = $scope.formValues.Subnet; + if ($scope.formValues.Gateway) { + ipamConfig.Gateway = $scope.formValues.Gateway; + } + if ($scope.formValues.IPRange) { + ipamConfig.IPRange = $scope.formValues.IPRange; + } + if ($scope.formValues.AuxAddress) { + ipamConfig.AuxAddress = $scope.formValues.AuxAddress; + } + config.IPAM.Config.push(ipamConfig); + } } - config.IPAM.Config.push(ipamConfig); + + function prepareDriverOptions(config) { + var options = {}; + $scope.formValues.DriverOptions.forEach(function (option) { + options[option.name] = option.value; + }); + config.Options = options; + } + + function prepareLabelsConfig(config) { + config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); + } + + function prepareConfiguration() { + var config = angular.copy($scope.config); + prepareIPAMConfiguration(config); + prepareDriverOptions(config); + prepareLabelsConfig(config); + return config; + } + + function modifyNetworkConfigurationForMacvlanConfigOnly(config) { + config.Internal = null; + config.Attachable = null; + config.ConfigOnly = true; + config.Options.parent = $scope.formValues.Macvlan.ParentNetworkCard; + } + + function modifyNetworkConfigurationForMacvlanConfigFrom(config, selectedNetworkConfig) { + config.ConfigFrom = { + Network: selectedNetworkConfig.Name + }; + config.Scope = 'swarm'; + } + + function validateForm(accessControlData, isAdmin) { + $scope.state.formValidationError = ''; + var error = ''; + error = FormValidator.validateAccessControl(accessControlData, isAdmin); + + if (error) { + $scope.state.formValidationError = error; + return false; + } + return true; + } + + function createNetwork(context) { + HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName); + + $scope.state.actionInProgress = true; + NetworkService.create(context.networkConfiguration) + .then(function success(data) { + var networkIdentifier = data.Id; + var userId = context.userDetails.ID; + return ResourceControlService.applyResourceControl('network', networkIdentifier, userId, context.accessControlData, []); + }) + .then(function success() { + Notifications.success('Network successfully created'); + if (context.reload) { + $state.go('docker.networks', {}, { + reload: true + }); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'An error occured during network creation'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + } + + $scope.create = function () { + var networkConfiguration = prepareConfiguration(); + var accessControlData = $scope.formValues.AccessControlData; + var userDetails = Authentication.getUserDetails(); + var isAdmin = userDetails.role === 1; + + if (!validateForm(accessControlData, isAdmin)) { + return; + } + + var creationContext = { + nodeName: $scope.formValues.NodeName, + networkConfiguration: networkConfiguration, + userDetails: userDetails, + accessControlData: accessControlData, + reload: true + }; + + if ($scope.config.Driver === 'macvlan') { + if ($scope.formValues.Macvlan.Scope === 'local') { + modifyNetworkConfigurationForMacvlanConfigOnly(networkConfiguration); + } else if ($scope.formValues.Macvlan.Scope === 'swarm') { + var selectedNetworkConfig = $scope.formValues.Macvlan.SelectedNetworkConfig; + modifyNetworkConfigurationForMacvlanConfigFrom(networkConfiguration, selectedNetworkConfig); + creationContext.nodeName = selectedNetworkConfig.NodeName; + } + } + + if ($scope.config.Driver === 'macvlan' && $scope.formValues.Macvlan.Scope === 'local' && + $scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { + var selectedNodes = $scope.formValues.Macvlan.DatatableState.selectedItems; + selectedNodes.forEach(function (node, idx) { + creationContext.nodeName = node.Hostname; + creationContext.reload = idx === selectedNodes.length - 1 ? true : false; + createNetwork(creationContext); + }); + } else { + createNetwork(creationContext); + } + }; + + function initView() { + var apiVersion = $scope.applicationState.endpoint.apiVersion; + + PluginService.networkPlugins(apiVersion < 1.25) + .then(function success(data) { + if ($scope.applicationState.endpoint.mode.provider !== 'DOCKER_SWARM_MODE') { + data.splice(data.indexOf('macvlan'), 1); + } + $scope.availableNetworkDrivers = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve network drivers'); + }); + } + + initView(); } - } - - function prepareDriverOptions(config) { - var options = {}; - $scope.formValues.DriverOptions.forEach(function (option) { - options[option.name] = option.value; - }); - config.Options = options; - } - - function prepareLabelsConfig(config) { - config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); - } - - function prepareConfiguration() { - var config = angular.copy($scope.config); - prepareIPAMConfiguration(config); - prepareDriverOptions(config); - prepareLabelsConfig(config); - return config; - } - - function validateForm(accessControlData, isAdmin) { - $scope.state.formValidationError = ''; - var error = ''; - error = FormValidator.validateAccessControl(accessControlData, isAdmin); - - if (error) { - $scope.state.formValidationError = error; - return false; - } - return true; - } - - $scope.create = function () { - var networkConfiguration = prepareConfiguration(); - var accessControlData = $scope.formValues.AccessControlData; - var userDetails = Authentication.getUserDetails(); - var isAdmin = userDetails.role === 1; - - if (!validateForm(accessControlData, isAdmin)) { - return; - } - - var nodeName = $scope.formValues.NodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - - $scope.state.actionInProgress = true; - NetworkService.create(networkConfiguration) - .then(function success(data) { - var networkIdentifier = data.Id; - var userId = userDetails.ID; - return ResourceControlService.applyResourceControl('network', networkIdentifier, userId, accessControlData, []); - }) - .then(function success() { - Notifications.success('Network successfully created'); - $state.go('docker.networks', {}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'An error occured during network creation'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }; - - function initView() { - var apiVersion = $scope.applicationState.endpoint.apiVersion; - - PluginService.networkPlugins(apiVersion < 1.25) - .then(function success(data){ - $scope.availableNetworkDrivers = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve network drivers'); - }); - } - - initView(); -}]); + ]); \ No newline at end of file diff --git a/app/docker/views/networks/create/createnetwork.html b/app/docker/views/networks/create/createnetwork.html index 827f725d3..03711c36c 100644 --- a/app/docker/views/networks/create/createnetwork.html +++ b/app/docker/views/networks/create/createnetwork.html @@ -9,37 +9,22 @@
    -
    +
    - -
    + +
    -
    - Network configuration -
    - -
    - -
    - -
    - -
    - -
    -
    -
    Driver configuration
    -
    +
    @@ -77,6 +62,38 @@
    + + + +
    +
    + Network configuration +
    + +
    + +
    + +
    + +
    + +
    +
    + + +
    + +
    + +
    + +
    + +
    +
    + +
    Advanced configuration
    @@ -108,24 +125,37 @@
    -
    +
    -
    -
    + +
    +
    + + +
    +
    + +
    Deployment
    - +
    @@ -138,7 +168,8 @@
    - @@ -151,4 +182,4 @@
    -
    +
    \ No newline at end of file diff --git a/assets/css/app.css b/assets/css/app.css index e13a1e66a..2be345ca2 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -626,6 +626,12 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active { box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); position: relative; } +.boxselector_wrapper label.boxselector_disabled { + background: #CACACA; + border-color: #787878; + color: #787878; + cursor: not-allowed; +} .boxselector_wrapper input[type="radio"]:checked + label { background: #337ab7; From 7e08227ddb14cb2a8d3883717f5e02f0e9fa33eb Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Fri, 17 Aug 2018 07:37:31 +0100 Subject: [PATCH 24/57] feat(build-system): add build-offline script (#2169) --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 74f4215dc..b908a30b3 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "scripts": { "dev": "yarn grunt run-dev", "clean-all": "yarn grunt clean:all", - "build": "yarn grunt build" + "build": "yarn grunt build", + "build-offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' && mv -b portainer ../../../dist/portainer-linux-amd64" + }, "engines": { "node": ">= 0.8.4" From 102e63e1e5741cb7a651f0e92147ea48c0811865 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sat, 18 Aug 2018 09:27:24 +0100 Subject: [PATCH 25/57] refactor(container-creation): change order of container re-creation/duplication steps * refactor(container-creation): change order of container creation steps * refactor(container-creation): remove nested methods * fix(container-creation): skip actions if old container missing * fix(container-creation): reject if user is not authorized * fix(container-creation): remove rejection on invalid form * refactor(container-creation): start container after duplicate * fix(container-creation): add form validation error message * fix(container-creation): pass correct id to create resource control * fix(container-creation): set action in progress after confirmation --- .../create/createContainerController.js | 259 ++++++++++++------ .../containers/create/createcontainer.html | 1 + 2 files changed, 170 insertions(+), 90 deletions(-) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 0e017c6eb..6580485f9 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -2,6 +2,8 @@ angular.module('portainer.docker') .controller('CreateContainerController', ['$q', '$scope', '$state', '$timeout', '$transition$', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', 'ModalService', 'RegistryService', 'SystemService', 'SettingsService', 'HttpRequestHelper', function ($q, $scope, $state, $timeout, $transition$, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SystemService, SettingsService, HttpRequestHelper) { + $scope.create = create; + $scope.formValues = { alwaysPull: true, Console: 'none', @@ -280,47 +282,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai return config; } - function confirmCreateContainer() { - var deferred = $q.defer(); - Container.query({ all: 1, filters: {name: ['^/' + $scope.config.name + '$'] }}).$promise - .then(function success(data) { - var existingContainer = data[0]; - if (existingContainer) { - ModalService.confirm({ - title: 'Are you sure ?', - message: 'A container with the same name already exists. Portainer can automatically remove it and re-create one. Do you want to replace it?', - buttons: { - confirm: { - label: 'Replace', - className: 'btn-danger' - } - }, - callback: function onConfirm(confirmed) { - if(!confirmed) { deferred.resolve(false); } - else { - // Remove old container - ContainerService.remove(existingContainer, true) - .then(function success(data) { - Notifications.success('Container Removed', existingContainer.Id); - deferred.resolve(true); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to remove container', err: err }); - }); - } - } - }); - } else { - deferred.resolve(true); - } - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve containers'); - return undefined; - }); - return deferred.promise; - } - + function loadFromContainerCmd(d) { if ($scope.config.Cmd) { $scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd); @@ -628,62 +590,179 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai return true; } - $scope.create = function () { - confirmCreateContainer() - .then(function success(confirm) { - if (!confirm) { - return false; + + function create() { + var oldContainer = null; + + + HttpRequestHelper.setPortainerAgentTargetHeader($scope.formValues.NodeName); + return findCurrentContainer() + .then(confirmCreateContainer) + .then(startCreationProcess) + .catch(notifyOnError) + .finally(final); + + function final() { + $scope.state.actionInProgress = false; + } + + function findCurrentContainer() { + return Container.query({ all: 1, filters: { name: ['^/' + $scope.config.name + '$'] } }) + .$promise + .then(function onQuerySuccess(containers) { + if (!containers.length) { + return; + } + oldContainer = containers[0]; + return oldContainer; + }) + .catch(notifyOnError); + + function notifyOnError(err) { + Notifications.error('Failure', err, 'Unable to retrieve containers'); + } + } + + function startCreationProcess(confirmed) { + if (!confirmed) { + return $q.when(); + } + if (!validateAccessControl()) { + return $q.when(); + } + $scope.state.actionInProgress = true; + return stopAndRenameContainer(oldContainer) + .then(pullImageIfNeeded) + .then(createNewContainer) + .then(applyResourceControl) + .then(connectToExtraNetworks) + .then(removeOldContainer) + .then(onSuccess); + } + + function confirmCreateContainer(container) { + if (!container) { + return $q.when(true); } + return showConfirmationModal(); + + function showConfirmationModal() { + var deferred = $q.defer(); + + ModalService.confirm({ + title: 'Are you sure ?', + message: 'A container with the same name already exists. Portainer can automatically remove it and re-create one. Do you want to replace it?', + buttons: { + confirm: { + label: 'Replace', + className: 'btn-danger' + } + }, + callback: function onConfirm(confirmed) { + deferred.resolve(confirmed); + } + }); + + return deferred.promise; + } + } + + function stopAndRenameContainer(oldContainer) { + if (!oldContainer) { + return $q.when(); + } + return stopContainerIfNeeded(oldContainer) + .then(renameContainer); + } + + function stopContainerIfNeeded(oldContainer) { + if (oldContainer.State !== 'running') { + return $q.when(); + } + return ContainerService.stopContainer(oldContainer.Id); + } + + function renameContainer() { + return ContainerService.renameContainer(oldContainer.Id, oldContainer.Names[0].substring(1) + '-old'); + } + + function pullImageIfNeeded() { + return $q.when($scope.formValues.alwaysPull && + ImageService.pullImage($scope.config.Image, $scope.formValues.Registry, true)); + } + + function createNewContainer() { + var config = prepareConfiguration(); + return ContainerService.createAndStartContainer(config); + } + + function applyResourceControl(newContainer) { + var containerIdentifier = newContainer.Id; + var userId = Authentication.getUserDetails().ID; + + return $q.when(ResourceControlService.applyResourceControl( + 'container', + containerIdentifier, + userId, + $scope.formValues.AccessControlData, [] + )).then(function onApplyResourceControlSuccess() { + return containerIdentifier; + }); + } + + function connectToExtraNetworks(newContainerId) { + if (!$scope.extraNetworks) { + return $q.when(); + } + + var connectionPromises = Object.keys($scope.extraNetworks).map(function (networkName) { + return NetworkService.connectContainer(networkName, newContainerId); + }); + + return $q.all(connectionPromises); + } + + function removeOldContainer() { + var deferred = $q.defer(); + + if (!oldContainer) { + deferred.resolve(); + return; + } + + ContainerService.remove(oldContainer, true) + .then(notifyOnRemoval) + .catch(notifyOnRemoveError); + + return deferred.promise; + + function notifyOnRemoval() { + Notifications.success('Container Removed', oldContainer.Id); + deferred.resolve(); + } + + function notifyOnRemoveError(err) { + deferred.reject({ msg: 'Unable to remove container', err: err }); + } + } + + function notifyOnError(err) { + Notifications.error('Failure', err, 'Unable to create container'); + } + + function validateAccessControl() { var accessControlData = $scope.formValues.AccessControlData; var userDetails = Authentication.getUserDetails(); var isAdmin = userDetails.role === 1; - if (!validateForm(accessControlData, isAdmin)) { - return; - } + return validateForm(accessControlData, isAdmin); + } - $scope.state.actionInProgress = true; - var config = prepareConfiguration(); - var nodeName = $scope.formValues.NodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - createContainer(config, accessControlData); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to create container'); - }); - }; - - function createContainer(config, accessControlData) { - var containerIdentifier; - $q.when(!$scope.formValues.alwaysPull || ImageService.pullImage($scope.config.Image, $scope.formValues.Registry, true)) - .finally(function final() { - ContainerService.createAndStartContainer(config) - .then(function success(data) { - containerIdentifier = data.Id; - var userId = Authentication.getUserDetails().ID; - return ResourceControlService.applyResourceControl('container', containerIdentifier, userId, accessControlData, []); - }) - .then(function success() { - if($scope.extraNetworks) { - return $q.all( - Object.keys($scope.extraNetworks).map(function(networkName) { - return NetworkService.connectContainer(networkName, containerIdentifier); - }) - ); - } - }) - .then(function success() { - Notifications.success('Container successfully created'); - $state.go('docker.containers', {}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to create container'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }); + function onSuccess() { + Notifications.success('Container successfully created'); + $state.go('docker.containers', {}, { reload: true }); + } } initView(); diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index 4ed3f6e4d..e946afa17 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -130,6 +130,7 @@ Deploy the container Deployment in progress... + {{ state.formValidationError }}
    From 31c2a6d9e7208f77da264f178ac7dc6fcadc1ea5 Mon Sep 17 00:00:00 2001 From: Hasnat Date: Sat, 18 Aug 2018 09:31:01 +0100 Subject: [PATCH 26/57] feat(container-console): Adds custom commands based on container labels (#2159) * feat(console): Adds custom commands based on container labels * feat(console): Update custom commands label prefix --- app/constants.js | 3 ++- .../console/containerConsoleController.js | 13 +++++++++++-- .../views/containers/console/containerconsole.html | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/constants.js b/app/constants.js index c4f911034..9b92ddf87 100644 --- a/app/constants.js +++ b/app/constants.js @@ -15,4 +15,5 @@ angular.module('portainer') .constant('API_ENDPOINT_TEMPLATES', 'api/templates') .constant('DEFAULT_TEMPLATES_URL', 'https://raw.githubusercontent.com/portainer/templates/master/templates.json') .constant('PAGINATION_MAX_ITEMS', 10) -.constant('APPLICATION_CACHE_VALIDITY', 3600); +.constant('APPLICATION_CACHE_VALIDITY', 3600) +.constant('CONSOLE_COMMANDS_LABEL_PREFIX', 'io.portainer.commands.'); diff --git a/app/docker/views/containers/console/containerConsoleController.js b/app/docker/views/containers/console/containerConsoleController.js index f877c5836..9f29e11dd 100644 --- a/app/docker/views/containers/console/containerConsoleController.js +++ b/app/docker/views/containers/console/containerConsoleController.js @@ -1,6 +1,6 @@ angular.module('portainer.docker') -.controller('ContainerConsoleController', ['$scope', '$transition$', 'ContainerService', 'ImageService', 'EndpointProvider', 'Notifications', 'ContainerHelper', 'ExecService', 'HttpRequestHelper', 'LocalStorage', -function ($scope, $transition$, ContainerService, ImageService, EndpointProvider, Notifications, ContainerHelper, ExecService, HttpRequestHelper, LocalStorage) { +.controller('ContainerConsoleController', ['$scope', '$transition$', 'ContainerService', 'ImageService', 'EndpointProvider', 'Notifications', 'ContainerHelper', 'ExecService', 'HttpRequestHelper', 'LocalStorage', 'CONSOLE_COMMANDS_LABEL_PREFIX', +function ($scope, $transition$, ContainerService, ImageService, EndpointProvider, Notifications, ContainerHelper, ExecService, HttpRequestHelper, LocalStorage, CONSOLE_COMMANDS_LABEL_PREFIX) { var socket, term; $scope.state = { @@ -9,6 +9,7 @@ function ($scope, $transition$, ContainerService, ImageService, EndpointProvider }; $scope.formValues = {}; + $scope.containerCommands = []; // Ensure the socket is closed before leaving the view $scope.$on('$stateChangeStart', function (event, next, current) { @@ -106,8 +107,16 @@ function ($scope, $transition$, ContainerService, ImageService, EndpointProvider }) .then(function success(data) { var image = data; + var containerLabels = $scope.container.Config.Labels; $scope.imageOS = image.Os; $scope.formValues.command = image.Os === 'windows' ? 'powershell' : 'bash'; + $scope.containerCommands = Object.keys(containerLabels) + .filter(function(label) { + return label.indexOf(CONSOLE_COMMANDS_LABEL_PREFIX) === 0; + }) + .map(function(label) { + return {title: label.replace(CONSOLE_COMMANDS_LABEL_PREFIX, ''), command: containerLabels[label]}; + }); $scope.state.loaded = true; }) .catch(function error(err) { diff --git a/app/docker/views/containers/console/containerconsole.html b/app/docker/views/containers/console/containerconsole.html index f8b4e7e54..e938c49c0 100644 --- a/app/docker/views/containers/console/containerconsole.html +++ b/app/docker/views/containers/console/containerconsole.html @@ -27,6 +27,7 @@ +
    From e1e263d8c88f0eefa68f4e013a0ce5d6b24f56c3 Mon Sep 17 00:00:00 2001 From: Ricardo Cardona Ramirez Date: Sun, 19 Aug 2018 00:57:28 -0500 Subject: [PATCH 27/57] feat(UAC): change default ownership to admininstrators (#2137) * #960 feat(UAC): change ownership to admins for externally created ressources * feat(UAC): change ownership to admins for externally created resources Deprecated AdministratorsOnly js and go backend * #960 feat(UAC): remove AdministratorsOnly property and minor GUI fixes Update swagger definition changing AdministratorsOnly to Public * #960 feat(UAC): fix create resource with access control data * #960 feat(UAC): authorization of non-admin users for restricted operations On stacks, containers networks, services , tasks and volumes. * #960 feat(UAC): database migration to version 14 The administrator resources are deleted and Public resources are now managed by admins * #960 feat(UAC): small fixes from PR #2137 * #960 feat(UAC): improve the readability of the source code * feat(UAC) fix displayed ownership for Swarm related resources (#960) --- api/bolt/migrator/migrate_dbversion13.go | 19 +++++++ api/bolt/migrator/migrator.go | 8 +++ .../resourcecontrol_create.go | 28 +++++------ .../resourcecontrol_update.go | 12 ++--- api/http/handler/stacks/stack_delete.go | 4 +- api/http/handler/stacks/stack_file.go | 4 ++ api/http/handler/stacks/stack_inspect.go | 4 ++ api/http/handler/stacks/stack_migrate.go | 4 +- api/http/handler/stacks/stack_update.go | 4 +- api/http/proxy/access_control.go | 39 ++++++++------- api/http/proxy/containers.go | 31 ++++++------ api/http/proxy/networks.go | 19 +++---- api/http/proxy/services.go | 19 +++---- api/http/proxy/tasks.go | 9 ++-- api/http/proxy/volumes.go | 19 +++---- api/http/security/authorization.go | 50 +++++-------------- api/portainer.go | 19 ++++--- api/swagger.yaml | 15 +++--- .../configs-datatable/configsDatatable.html | 2 +- .../containersDatatable.html | 2 +- .../networks-datatable/networksDatatable.html | 2 +- .../secrets-datatable/secretsDatatable.html | 2 +- .../services-datatable/servicesDatatable.html | 2 +- .../volumes-datatable/volumesDatatable.html | 2 +- .../containers/edit/containerController.js | 2 +- .../porAccessControlPanel.html | 6 +-- .../porAccessControlPanelController.js | 18 ++++--- .../stacks-datatable/stacksDatatable.html | 2 +- app/portainer/models/resourceControl.js | 8 +-- .../services/api/resourceControlService.js | 30 +++++------ 30 files changed, 206 insertions(+), 179 deletions(-) create mode 100644 api/bolt/migrator/migrate_dbversion13.go diff --git a/api/bolt/migrator/migrate_dbversion13.go b/api/bolt/migrator/migrate_dbversion13.go new file mode 100644 index 000000000..5434d00e2 --- /dev/null +++ b/api/bolt/migrator/migrate_dbversion13.go @@ -0,0 +1,19 @@ +package migrator + +func (m *Migrator) updateResourceControlsToDBVersion14() error { + resourceControls, err := m.resourceControlService.ResourceControls() + if err != nil { + return err + } + + for _, resourceControl := range resourceControls { + if resourceControl.AdministratorsOnly == true { + err = m.resourceControlService.DeleteResourceControl(resourceControl.ID) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go index 9c61fc2c7..b70172256 100644 --- a/api/bolt/migrator/migrator.go +++ b/api/bolt/migrator/migrator.go @@ -178,5 +178,13 @@ func (m *Migrator) Migrate() error { } } + // 1.19.2-dev + if m.currentDBVersion < 14 { + err := m.updateResourceControlsToDBVersion14() + if err != nil { + return err + } + } + return m.versionService.StoreDBVersion(portainer.DBVersion) } diff --git a/api/http/handler/resourcecontrols/resourcecontrol_create.go b/api/http/handler/resourcecontrols/resourcecontrol_create.go index baaee3360..10bde9a4f 100644 --- a/api/http/handler/resourcecontrols/resourcecontrol_create.go +++ b/api/http/handler/resourcecontrols/resourcecontrol_create.go @@ -12,12 +12,12 @@ import ( ) type resourceControlCreatePayload struct { - ResourceID string - Type string - AdministratorsOnly bool - Users []int - Teams []int - SubResourceIDs []string + ResourceID string + Type string + Public bool + Users []int + Teams []int + SubResourceIDs []string } func (payload *resourceControlCreatePayload) Validate(r *http.Request) error { @@ -29,8 +29,8 @@ func (payload *resourceControlCreatePayload) Validate(r *http.Request) error { return portainer.Error("Invalid type") } - if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly { - return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly") + if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public { + return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public") } return nil } @@ -90,12 +90,12 @@ func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Req } resourceControl := portainer.ResourceControl{ - ResourceID: payload.ResourceID, - SubResourceIDs: payload.SubResourceIDs, - Type: resourceControlType, - AdministratorsOnly: payload.AdministratorsOnly, - UserAccesses: userAccesses, - TeamAccesses: teamAccesses, + ResourceID: payload.ResourceID, + SubResourceIDs: payload.SubResourceIDs, + Type: resourceControlType, + Public: payload.Public, + UserAccesses: userAccesses, + TeamAccesses: teamAccesses, } securityContext, err := security.RetrieveRestrictedRequestContext(r) diff --git a/api/http/handler/resourcecontrols/resourcecontrol_update.go b/api/http/handler/resourcecontrols/resourcecontrol_update.go index 3f3b25799..69299fcee 100644 --- a/api/http/handler/resourcecontrols/resourcecontrol_update.go +++ b/api/http/handler/resourcecontrols/resourcecontrol_update.go @@ -11,14 +11,14 @@ import ( ) type resourceControlUpdatePayload struct { - AdministratorsOnly bool - Users []int - Teams []int + Public bool + Users []int + Teams []int } func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error { - if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly { - return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly") + if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public { + return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public") } return nil } @@ -52,7 +52,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the resource control", portainer.ErrResourceAccessDenied} } - resourceControl.AdministratorsOnly = payload.AdministratorsOnly + resourceControl.Public = payload.Public var userAccesses = make([]portainer.UserResourceAccess, 0) for _, v := range payload.Users { diff --git a/api/http/handler/stacks/stack_delete.go b/api/http/handler/stacks/stack_delete.go index 3844a4d7e..f481f808a 100644 --- a/api/http/handler/stacks/stack_delete.go +++ b/api/http/handler/stacks/stack_delete.go @@ -48,8 +48,8 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err} } - if resourceControl != nil { - if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { + if !securityContext.IsAdmin { + if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} } } diff --git a/api/http/handler/stacks/stack_file.go b/api/http/handler/stacks/stack_file.go index f8d25af8d..b86888a37 100644 --- a/api/http/handler/stacks/stack_file.go +++ b/api/http/handler/stacks/stack_file.go @@ -41,6 +41,10 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe } extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}} + if !securityContext.IsAdmin && resourceControl == nil { + return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} + } + if resourceControl != nil { if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { extendedStack.ResourceControl = *resourceControl diff --git a/api/http/handler/stacks/stack_inspect.go b/api/http/handler/stacks/stack_inspect.go index 28d5030ac..380482149 100644 --- a/api/http/handler/stacks/stack_inspect.go +++ b/api/http/handler/stacks/stack_inspect.go @@ -36,6 +36,10 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht } extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}} + if !securityContext.IsAdmin && resourceControl == nil { + return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} + } + if resourceControl != nil { if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { extendedStack.ResourceControl = *resourceControl diff --git a/api/http/handler/stacks/stack_migrate.go b/api/http/handler/stacks/stack_migrate.go index beb579a34..b6fed5a96 100644 --- a/api/http/handler/stacks/stack_migrate.go +++ b/api/http/handler/stacks/stack_migrate.go @@ -53,8 +53,8 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err} } - if resourceControl != nil { - if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { + if !securityContext.IsAdmin { + if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} } } diff --git a/api/http/handler/stacks/stack_update.go b/api/http/handler/stacks/stack_update.go index de1c560ae..456612249 100644 --- a/api/http/handler/stacks/stack_update.go +++ b/api/http/handler/stacks/stack_update.go @@ -62,8 +62,8 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err} } - if resourceControl != nil { - if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { + if !securityContext.IsAdmin { + if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} } } diff --git a/api/http/proxy/access_control.go b/api/http/proxy/access_control.go index 331b9d0da..e63dc6346 100644 --- a/api/http/proxy/access_control.go +++ b/api/http/proxy/access_control.go @@ -1,6 +1,8 @@ package proxy -import "github.com/portainer/portainer" +import ( + "github.com/portainer/portainer" +) type ( // ExtendedStack represents a stack combined with its associated access control @@ -15,7 +17,7 @@ type ( // It will retrieve an identifier from the labels object. If an identifier exists, it will check for // an existing resource control associated to it. // Returns a decorated object and authorized access (true) when a resource control is found and the user can access the resource. -// Returns the original object and authorized access (true) when no resource control is found. +// Returns the original object and denied access (false) when no resource control is found. // Returns the original object and denied access (false) when a resource control is found and the user cannot access the resource. func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string]interface{}, labelIdentifier string, context *restrictedOperationContext) (map[string]interface{}, bool) { @@ -24,32 +26,31 @@ func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string resourceIdentifier := labelsObject[labelIdentifier].(string) return applyResourceAccessControl(resourceObject, resourceIdentifier, context) } - return resourceObject, true + return resourceObject, false } // applyResourceAccessControl returns an optionally decorated object as the first return value and the // access level for the user (granted or denied) as the second return value. // Returns a decorated object and authorized access (true) when a resource control is found to the specified resource // identifier and the user can access the resource. -// Returns the original object and authorized access (true) when no resource control is found for the specified +// Returns the original object and authorized access (false) when no resource control is found for the specified // resource identifier. // Returns the original object and denied access (false) when a resource control is associated to the resource // and the user cannot access the resource. func applyResourceAccessControl(resourceObject map[string]interface{}, resourceIdentifier string, context *restrictedOperationContext) (map[string]interface{}, bool) { - authorizedAccess := true - resourceControl := getResourceControlByResourceID(resourceIdentifier, context.resourceControls) - if resourceControl != nil { - if context.isAdmin || canUserAccessResource(context.userID, context.userTeamIDs, resourceControl) { - resourceObject = decorateObject(resourceObject, resourceControl) - } else { - authorizedAccess = false - } + if resourceControl == nil { + return resourceObject, context.isAdmin } - return resourceObject, authorizedAccess + if context.isAdmin || resourceControl.Public || canUserAccessResource(context.userID, context.userTeamIDs, resourceControl) { + resourceObject = decorateObject(resourceObject, resourceControl) + return resourceObject, true + } + + return resourceObject, false } // decorateResourceWithAccessControlFromLabel will retrieve an identifier from the labels object. If an identifier exists, @@ -94,7 +95,7 @@ func canUserAccessResource(userID portainer.UserID, userTeamIDs []portainer.Team } } - return false + return resourceControl.Public } func decorateObject(object map[string]interface{}, resourceControl *portainer.ResourceControl) map[string]interface{} { @@ -123,6 +124,10 @@ func getResourceControlByResourceID(resourceID string, resourceControls []portai // CanAccessStack checks if a user can access a stack func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceControl, userID portainer.UserID, memberships []portainer.TeamMembership) bool { + if resourceControl == nil { + return false + } + userTeamIDs := make([]portainer.TeamID, 0) for _, membership := range memberships { userTeamIDs = append(userTeamIDs, membership.TeamID) @@ -132,7 +137,7 @@ func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceC return true } - return false + return resourceControl.Public } // FilterStacks filters stacks based on user role and resource controls. @@ -149,9 +154,9 @@ func FilterStacks(stacks []portainer.Stack, resourceControls []portainer.Resourc for _, stack := range stacks { extendedStack := ExtendedStack{stack, portainer.ResourceControl{}} resourceControl := getResourceControlByResourceID(stack.Name, resourceControls) - if resourceControl == nil { + if resourceControl == nil && isAdmin { filteredStacks = append(filteredStacks, extendedStack) - } else if resourceControl != nil && (isAdmin || canUserAccessResource(userID, userTeamIDs, resourceControl)) { + } else if resourceControl != nil && (isAdmin || resourceControl.Public || canUserAccessResource(userID, userTeamIDs, resourceControl)) { extendedStack.ResourceControl = *resourceControl filteredStacks = append(filteredStacks, extendedStack) } diff --git a/api/http/proxy/containers.go b/api/http/proxy/containers.go index 9da66e21f..4e6c835ff 100644 --- a/api/http/proxy/containers.go +++ b/api/http/proxy/containers.go @@ -62,27 +62,27 @@ func containerInspectOperation(response *http.Response, executor *operationExecu containerID := responseObject[containerIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, containerID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } containerLabels := extractContainerLabelsFromContainerInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForServiceIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForSwarmStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForComposeStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractContainerLabelsFromContainerInspectObject retrieve the Labels of the container if present. @@ -148,19 +148,20 @@ func filterContainerList(containerData []interface{}, context *restrictedOperati containerID := containerObject[containerIdentifier].(string) containerObject, access := applyResourceAccessControl(containerObject, containerID, context) - if access { + if !access { containerLabels := extractContainerLabelsFromContainerListObject(containerObject) containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForComposeStackIdentifier, context) - if access { + if !access { containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForServiceIdentifier, context) - if access { + if !access { containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForSwarmStackIdentifier, context) - if access { - filteredContainerData = append(filteredContainerData, containerObject) - } } } } + + if access { + filteredContainerData = append(filteredContainerData, containerObject) + } } return filteredContainerData, nil diff --git a/api/http/proxy/networks.go b/api/http/proxy/networks.go index 7e40d385a..a131ab2ab 100644 --- a/api/http/proxy/networks.go +++ b/api/http/proxy/networks.go @@ -53,17 +53,17 @@ func networkInspectOperation(response *http.Response, executor *operationExecuto networkID := responseObject[networkIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, networkID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } networkLabels := extractNetworkLabelsFromNetworkInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(networkLabels, responseObject, networkLabelForStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractNetworkLabelsFromNetworkInspectObject retrieve the Labels of the network if present. @@ -121,12 +121,13 @@ func filterNetworkList(networkData []interface{}, context *restrictedOperationCo networkID := networkObject[networkIdentifier].(string) networkObject, access := applyResourceAccessControl(networkObject, networkID, context) - if access { + if !access { networkLabels := extractNetworkLabelsFromNetworkListObject(networkObject) networkObject, access = applyResourceAccessControlFromLabel(networkLabels, networkObject, networkLabelForStackIdentifier, context) - if access { - filteredNetworkData = append(filteredNetworkData, networkObject) - } + } + + if access { + filteredNetworkData = append(filteredNetworkData, networkObject) } } diff --git a/api/http/proxy/services.go b/api/http/proxy/services.go index 9934cf381..b11208b58 100644 --- a/api/http/proxy/services.go +++ b/api/http/proxy/services.go @@ -53,17 +53,17 @@ func serviceInspectOperation(response *http.Response, executor *operationExecuto serviceID := responseObject[serviceIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, serviceID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } serviceLabels := extractServiceLabelsFromServiceInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(serviceLabels, responseObject, serviceLabelForStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractServiceLabelsFromServiceInspectObject retrieve the Labels of the service if present. @@ -129,12 +129,13 @@ func filterServiceList(serviceData []interface{}, context *restrictedOperationCo serviceID := serviceObject[serviceIdentifier].(string) serviceObject, access := applyResourceAccessControl(serviceObject, serviceID, context) - if access { + if !access { serviceLabels := extractServiceLabelsFromServiceListObject(serviceObject) serviceObject, access = applyResourceAccessControlFromLabel(serviceLabels, serviceObject, serviceLabelForStackIdentifier, context) - if access { - filteredServiceData = append(filteredServiceData, serviceObject) - } + } + + if access { + filteredServiceData = append(filteredServiceData, serviceObject) } } diff --git a/api/http/proxy/tasks.go b/api/http/proxy/tasks.go index daea6b926..d75ce54ec 100644 --- a/api/http/proxy/tasks.go +++ b/api/http/proxy/tasks.go @@ -65,12 +65,13 @@ func filterTaskList(taskData []interface{}, context *restrictedOperationContext) serviceID := taskObject[taskServiceIdentifier].(string) taskObject, access := applyResourceAccessControl(taskObject, serviceID, context) - if access { + if !access { taskLabels := extractTaskLabelsFromTaskListObject(taskObject) taskObject, access = applyResourceAccessControlFromLabel(taskLabels, taskObject, taskLabelForStackIdentifier, context) - if access { - filteredTaskData = append(filteredTaskData, taskObject) - } + } + + if access { + filteredTaskData = append(filteredTaskData, taskObject) } } diff --git a/api/http/proxy/volumes.go b/api/http/proxy/volumes.go index c0582a6bd..82f1b5cf6 100644 --- a/api/http/proxy/volumes.go +++ b/api/http/proxy/volumes.go @@ -62,17 +62,17 @@ func volumeInspectOperation(response *http.Response, executor *operationExecutor volumeID := responseObject[volumeIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, volumeID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } volumeLabels := extractVolumeLabelsFromVolumeInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(volumeLabels, responseObject, volumeLabelForStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractVolumeLabelsFromVolumeInspectObject retrieve the Labels of the volume if present. @@ -130,12 +130,13 @@ func filterVolumeList(volumeData []interface{}, context *restrictedOperationCont volumeID := volumeObject[volumeIdentifier].(string) volumeObject, access := applyResourceAccessControl(volumeObject, volumeID, context) - if access { + if !access { volumeLabels := extractVolumeLabelsFromVolumeListObject(volumeObject) volumeObject, access = applyResourceAccessControlFromLabel(volumeLabels, volumeObject, volumeLabelForStackIdentifier, context) - if access { - filteredVolumeData = append(filteredVolumeData, volumeObject) - } + } + + if access { + filteredVolumeData = append(filteredVolumeData, volumeObject) } } diff --git a/api/http/security/authorization.go b/api/http/security/authorization.go index 7fa7a6f31..6ffa3a0e4 100644 --- a/api/http/security/authorization.go +++ b/api/http/security/authorization.go @@ -1,21 +1,19 @@ package security -import "github.com/portainer/portainer" +import ( + "github.com/portainer/portainer" +) // AuthorizedResourceControlDeletion ensure that the user can delete a resource control object. // A non-administrator user cannot delete a resource control where: -// * the AdministratorsOnly flag is set +// * the Public flag is false // * he is not one of the users in the user accesses // * he is not a member of any team within the team accesses func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool { - if context.IsAdmin { + if context.IsAdmin || resourceControl.Public { return true } - if resourceControl.AdministratorsOnly { - return false - } - userAccessesCount := len(resourceControl.UserAccesses) teamAccessesCount := len(resourceControl.TeamAccesses) @@ -42,39 +40,25 @@ func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceContro // AuthorizedResourceControlAccess checks whether the user can alter an existing resource control. func AuthorizedResourceControlAccess(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool { - if context.IsAdmin { + if context.IsAdmin || resourceControl.Public { return true } - if resourceControl.AdministratorsOnly { - return false - } - - authorizedTeamAccess := false for _, access := range resourceControl.TeamAccesses { for _, membership := range context.UserMemberships { if membership.TeamID == access.TeamID { - authorizedTeamAccess = true - break + return true } } } - if !authorizedTeamAccess { - return false - } - authorizedUserAccess := false for _, access := range resourceControl.UserAccesses { if context.UserID == access.UserID { - authorizedUserAccess = true - break + return true } } - if !authorizedUserAccess { - return false - } - return true + return false } // AuthorizedResourceControlUpdate ensure that the user can update a resource control object. @@ -92,20 +76,16 @@ func AuthorizedResourceControlUpdate(resourceControl *portainer.ResourceControl, // AuthorizedResourceControlCreation ensure that the user can create a resource control object. // A non-administrator user cannot create a resource control where: -// * the AdministratorsOnly flag is set +// * the Public flag is set false // * he wants to create a resource control without any user/team accesses // * he wants to add more than one user in the user accesses // * he wants tp add a user in the user accesses that is not corresponding to its id // * he wants to add a team he is not a member of func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool { - if context.IsAdmin { + if context.IsAdmin || resourceControl.Public { return true } - if resourceControl.AdministratorsOnly { - return false - } - userAccessesCount := len(resourceControl.UserAccesses) teamAccessesCount := len(resourceControl.TeamAccesses) @@ -126,19 +106,15 @@ func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceContro if teamAccessesCount > 0 { for _, access := range resourceControl.TeamAccesses { - isMember := false for _, membership := range context.UserMemberships { if membership.TeamID == access.TeamID { - isMember = true + return true } } - if !isMember { - return false - } } } - return true + return false } // AuthorizedTeamManagement ensure that access to the management of the specified team is granted. diff --git a/api/portainer.go b/api/portainer.go index 1f2d6b26a..4c65d1149 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -274,18 +274,21 @@ type ( // ResourceControl represent a reference to a Docker resource with specific access controls ResourceControl struct { - ID ResourceControlID `json:"Id"` - ResourceID string `json:"ResourceId"` - SubResourceIDs []string `json:"SubResourceIds"` - Type ResourceControlType `json:"Type"` - AdministratorsOnly bool `json:"AdministratorsOnly"` - UserAccesses []UserResourceAccess `json:"UserAccesses"` - TeamAccesses []TeamResourceAccess `json:"TeamAccesses"` + ID ResourceControlID `json:"Id"` + ResourceID string `json:"ResourceId"` + SubResourceIDs []string `json:"SubResourceIds"` + Type ResourceControlType `json:"Type"` + UserAccesses []UserResourceAccess `json:"UserAccesses"` + TeamAccesses []TeamResourceAccess `json:"TeamAccesses"` + Public bool `json:"Public"` // Deprecated fields // Deprecated in DBVersion == 2 OwnerID UserID `json:"OwnerId,omitempty"` AccessLevel ResourceAccessLevel `json:"AccessLevel,omitempty"` + + // Deprecated in DBVersion == 14 + AdministratorsOnly bool `json:"AdministratorsOnly,omitempty"` } // ResourceControlType represents the type of resource associated to the resource control (volume, container, service...). @@ -613,7 +616,7 @@ const ( // APIVersion is the version number of the Portainer API. APIVersion = "1.19.2-dev" // DBVersion is the version number of the Portainer database. - DBVersion = 13 + DBVersion = 14 // PortainerAgentHeader represents the name of the header available in any agent response PortainerAgentHeader = "Portainer-Agent" // PortainerAgentTargetHeader represent the name of the header containing the target node name. diff --git a/api/swagger.yaml b/api/swagger.yaml index 1e975c5bf..f589efd7f 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -3268,11 +3268,10 @@ definitions: example: "container" description: "Type of Docker resource. Valid values are: container, volume\ \ service, secret, config or stack" - AdministratorsOnly: + Public: type: "boolean" example: true - description: "Restrict access to the associated resource to administrators\ - \ only" + description: "Permit access to the associated resource to any user" Users: type: "array" description: "List of user identifiers with access to the associated resource" @@ -3491,11 +3490,10 @@ definitions: example: "container" description: "Type of Docker resource. Valid values are: container, volume\ \ service, secret, config or stack" - AdministratorsOnly: + Public: type: "boolean" example: true - description: "Restrict access to the associated resource to administrators\ - \ only" + description: "Permit access to the associated resource to any user" Users: type: "array" description: "List of user identifiers with access to the associated resource" @@ -3520,11 +3518,10 @@ definitions: ResourceControlUpdateRequest: type: "object" properties: - AdministratorsOnly: + Public: type: "boolean" example: false - description: "Restrict access to the associated resource to administrators\ - \ only" + description: "Permit access to the associated resource to any user" Users: type: "array" description: "List of user identifiers with access to the associated resource" diff --git a/app/docker/components/datatables/configs-datatable/configsDatatable.html b/app/docker/components/datatables/configs-datatable/configsDatatable.html index 7c9e5078f..b1e462101 100644 --- a/app/docker/components/datatables/configs-datatable/configsDatatable.html +++ b/app/docker/components/datatables/configs-datatable/configsDatatable.html @@ -63,7 +63,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/containers-datatable/containersDatatable.html b/app/docker/components/datatables/containers-datatable/containersDatatable.html index 2119d8f35..19cd2a0c0 100644 --- a/app/docker/components/datatables/containers-datatable/containersDatatable.html +++ b/app/docker/components/datatables/containers-datatable/containersDatatable.html @@ -244,7 +244,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/networks-datatable/networksDatatable.html b/app/docker/components/datatables/networks-datatable/networksDatatable.html index 98fb15791..7e88a2549 100644 --- a/app/docker/components/datatables/networks-datatable/networksDatatable.html +++ b/app/docker/components/datatables/networks-datatable/networksDatatable.html @@ -111,7 +111,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/secrets-datatable/secretsDatatable.html b/app/docker/components/datatables/secrets-datatable/secretsDatatable.html index 4e6a6c148..ed5249557 100644 --- a/app/docker/components/datatables/secrets-datatable/secretsDatatable.html +++ b/app/docker/components/datatables/secrets-datatable/secretsDatatable.html @@ -63,7 +63,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/services-datatable/servicesDatatable.html b/app/docker/components/datatables/services-datatable/servicesDatatable.html index 8d25b3bf1..8ed52ff2b 100644 --- a/app/docker/components/datatables/services-datatable/servicesDatatable.html +++ b/app/docker/components/datatables/services-datatable/servicesDatatable.html @@ -118,7 +118,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/volumes-datatable/volumesDatatable.html b/app/docker/components/datatables/volumes-datatable/volumesDatatable.html index c93f506f4..60632f296 100644 --- a/app/docker/components/datatables/volumes-datatable/volumesDatatable.html +++ b/app/docker/components/datatables/volumes-datatable/volumesDatatable.html @@ -115,7 +115,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index 22f56e7b7..e2c017632 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -285,7 +285,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co var teams = resourceControl.TeamAccesses.map(function(t) { return t.TeamId; }); - return ResourceControlService.createResourceControl(resourceControl.AdministratorsOnly, users, teams, containerIdentifier, 'container', []); + return ResourceControlService.createResourceControl(resourceControl.Public, users, teams, containerIdentifier, 'container', []); } function notifyAndChangeView() { diff --git a/app/portainer/components/accessControlPanel/porAccessControlPanel.html b/app/portainer/components/accessControlPanel/porAccessControlPanel.html index 1f5752f0d..0927dd23e 100644 --- a/app/portainer/components/accessControlPanel/porAccessControlPanel.html +++ b/app/portainer/components/accessControlPanel/porAccessControlPanel.html @@ -11,12 +11,12 @@ - public - + administrators + {{ $ctrl.resourceControl.Ownership }} - + diff --git a/app/portainer/components/accessControlPanel/porAccessControlPanelController.js b/app/portainer/components/accessControlPanel/porAccessControlPanelController.js index 940c33a74..142603304 100644 --- a/app/portainer/components/accessControlPanel/porAccessControlPanelController.js +++ b/app/portainer/components/accessControlPanel/porAccessControlPanelController.js @@ -12,7 +12,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica }; ctrl.formValues = { - Ownership: 'public', + Ownership: 'administrators', Ownership_Users: [], Ownership_Teams: [] }; @@ -51,7 +51,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica return true; } - function processOwnershipFormValues() { + function processOwnershipFormValues() { var userIds = []; angular.forEach(ctrl.formValues.Ownership_Users, function(user) { userIds.push(user.Id); @@ -60,13 +60,14 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica angular.forEach(ctrl.formValues.Ownership_Teams, function(team) { teamIds.push(team.Id); }); - var administratorsOnly = ctrl.formValues.Ownership === 'administrators' ? true : false; + + var publicOnly = ctrl.formValues.Ownership === 'public' ? true : false; return { ownership: ctrl.formValues.Ownership, - authorizedUserIds: administratorsOnly ? [] : userIds, - authorizedTeamIds: administratorsOnly ? [] : teamIds, - administratorsOnly: administratorsOnly + authorizedUserIds: publicOnly ? [] : userIds, + authorizedTeamIds: publicOnly ? [] : teamIds, + publicOnly: publicOnly }; } @@ -96,12 +97,13 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica if (resourceControl) { ctrl.formValues.Ownership = resourceControl.Ownership === 'private' ? 'restricted' : resourceControl.Ownership; } else { - ctrl.formValues.Ownership = 'public'; + ctrl.formValues.Ownership = 'administrators'; } } else { - ctrl.formValues.Ownership = 'public'; + ctrl.formValues.Ownership = 'administrators'; } + ResourceControlService.retrieveOwnershipDetails(resourceControl) .then(function success(data) { ctrl.authorizedUsers = data.authorizedUsers; diff --git a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html index 694eb8014..6c8dbbb79 100644 --- a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html +++ b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html @@ -70,7 +70,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/portainer/models/resourceControl.js b/app/portainer/models/resourceControl.js index f7e08c3f1..28c49602b 100644 --- a/app/portainer/models/resourceControl.js +++ b/app/portainer/models/resourceControl.js @@ -4,16 +4,18 @@ function ResourceControlViewModel(data) { this.ResourceId = data.ResourceId; this.UserAccesses = data.UserAccesses; this.TeamAccesses = data.TeamAccesses; - this.AdministratorsOnly = data.AdministratorsOnly; + this.Public = data.Public; this.Ownership = determineOwnership(this); } function determineOwnership(resourceControl) { - if (resourceControl.AdministratorsOnly) { - return 'administrators'; + if (resourceControl.Public) { + return 'public'; } else if (resourceControl.UserAccesses.length === 1 && resourceControl.TeamAccesses.length === 0) { return 'private'; } else if (resourceControl.UserAccesses.length > 1 || resourceControl.TeamAccesses.length > 0) { return 'restricted'; + } else { + return 'administrators'; } } diff --git a/app/portainer/services/api/resourceControlService.js b/app/portainer/services/api/resourceControlService.js index 1ee0b148c..ff4e60107 100644 --- a/app/portainer/services/api/resourceControlService.js +++ b/app/portainer/services/api/resourceControlService.js @@ -3,10 +3,10 @@ angular.module('portainer.app') 'use strict'; var service = {}; - service.createResourceControl = function(administratorsOnly, userIDs, teamIDs, resourceID, type, subResourceIDs) { + service.createResourceControl = function(publicOnly, userIDs, teamIDs, resourceID, type, subResourceIDs) { var payload = { Type: type, - AdministratorsOnly: administratorsOnly, + Public: publicOnly, ResourceID: resourceID, Users: userIDs, Teams: teamIDs, @@ -19,9 +19,9 @@ angular.module('portainer.app') return ResourceControl.remove({id: rcID}).$promise; }; - service.updateResourceControl = function(admin, userIDs, teamIDs, resourceControlId) { + service.updateResourceControl = function(publicOnly, userIDs, teamIDs, resourceControlId) { var payload = { - AdministratorsOnly: admin, + Public: publicOnly, Users: userIDs, Teams: teamIDs }; @@ -30,15 +30,15 @@ angular.module('portainer.app') service.applyResourceControl = function(resourceControlType, resourceIdentifier, userId, accessControlData, subResources) { if (!accessControlData.AccessControlEnabled) { - return; + accessControlData.Ownership = 'public'; } var authorizedUserIds = []; var authorizedTeamIds = []; - var administratorsOnly = false; + var publicOnly = false; switch (accessControlData.Ownership) { - case 'administrators': - administratorsOnly = true; + case 'public': + publicOnly = true; break; case 'private': authorizedUserIds.push(userId); @@ -51,21 +51,23 @@ angular.module('portainer.app') authorizedTeamIds.push(team.Id); }); break; - } - return service.createResourceControl(administratorsOnly, authorizedUserIds, + default: + return; + } + return service.createResourceControl(publicOnly, authorizedUserIds, authorizedTeamIds, resourceIdentifier, resourceControlType, subResources); }; - service.applyResourceControlChange = function(resourceControlType, resourceId, resourceControl, ownershipParameters) { + service.applyResourceControlChange = function(resourceControlType, resourceId, resourceControl, ownershipParameters) { if (resourceControl) { - if (ownershipParameters.ownership === 'public') { + if (ownershipParameters.ownership === 'administrators') { return service.deleteResourceControl(resourceControl.Id); } else { - return service.updateResourceControl(ownershipParameters.administratorsOnly, ownershipParameters.authorizedUserIds, + return service.updateResourceControl(ownershipParameters.publicOnly, ownershipParameters.authorizedUserIds, ownershipParameters.authorizedTeamIds, resourceControl.Id); } } else { - return service.createResourceControl(ownershipParameters.administratorsOnly, ownershipParameters.authorizedUserIds, + return service.createResourceControl(ownershipParameters.publicOnly, ownershipParameters.authorizedUserIds, ownershipParameters.authorizedTeamIds, resourceId, resourceControlType); } }; From 1b51daf9c4e6efd4a7275ed1efbcd7d33d8b97ea Mon Sep 17 00:00:00 2001 From: baron_l Date: Sun, 19 Aug 2018 08:05:16 +0200 Subject: [PATCH 28/57] fix(services): fix invalid replica count (#1990) (#2127) * fix(services): replicas numbers display is now correct with constraints and down nodes * refactor(helpers): constraint helper has less complexity * feat(services): constraints on node/engine labels are now supported * refactor(helpers): ConstraintsHelper - remove regex patterns and improve code lisibility * refactor(helpers): rework matchesConstraint() for better code lisibility and lodash find() instead for IE compatibility --- .../services-datatable/servicesDatatable.html | 2 +- app/docker/filters/filters.js | 10 +- app/docker/helpers/constraintsHelper.js | 106 ++++++++++++++++++ app/docker/models/task.js | 1 + 4 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 app/docker/helpers/constraintsHelper.js diff --git a/app/docker/components/datatables/services-datatable/servicesDatatable.html b/app/docker/components/datatables/services-datatable/servicesDatatable.html index 8ed52ff2b..ba3d870ad 100644 --- a/app/docker/components/datatables/services-datatable/servicesDatatable.html +++ b/app/docker/components/datatables/services-datatable/servicesDatatable.html @@ -96,7 +96,7 @@ {{ item.Image | hideshasum }} {{ item.Mode }} - {{ item.Tasks | runningtaskscount }} / {{ item.Mode === 'replicated' ? item.Replicas : ($ctrl.nodes | availablenodecount) }} + {{ item.Tasks | runningtaskscount }} / {{ item.Mode === 'replicated' ? item.Replicas : ($ctrl.nodes | availablenodecount:item) }} Scale diff --git a/app/docker/filters/filters.js b/app/docker/filters/filters.js index a55419333..a07f1be0d 100644 --- a/app/docker/filters/filters.js +++ b/app/docker/filters/filters.js @@ -192,26 +192,26 @@ angular.module('portainer.docker') return ''; }; }) -.filter('availablenodecount', function () { +.filter('availablenodecount', ['ConstraintsHelper', function (ConstraintsHelper) { 'use strict'; - return function (nodes) { + return function (nodes, service) { var availableNodes = 0; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; - if (node.Availability === 'active' && node.Status === 'ready') { + if (node.Availability === 'active' && node.Status === 'ready' && ConstraintsHelper.matchesServiceConstraints(service, node)) { availableNodes++; } } return availableNodes; }; -}) +}]) .filter('runningtaskscount', function () { 'use strict'; return function (tasks) { var runningTasks = 0; for (var i = 0; i < tasks.length; i++) { var task = tasks[i]; - if (task.Status.State === 'running') { + if (task.Status.State === 'running' && task.DesiredState === 'running') { runningTasks++; } } diff --git a/app/docker/helpers/constraintsHelper.js b/app/docker/helpers/constraintsHelper.js new file mode 100644 index 000000000..b2700deba --- /dev/null +++ b/app/docker/helpers/constraintsHelper.js @@ -0,0 +1,106 @@ +function ConstraintModel(op, key, value) { + this.op = op; + this.value = value; + this.key = key; +} + +var patterns = { + id: { + nodeId: 'node.id', + nodeHostname: 'node.hostname', + nodeRole: 'node.role', + nodeLabels: 'node.labels.', + engineLabels: 'engine.labels.' + }, + op: { + eq: '==', + neq: '!=' + } +}; + +function matchesConstraint(value, constraint) { + if (!constraint || + (constraint.op === patterns.op.eq && value === constraint.value) || + (constraint.op === patterns.op.neq && value !== constraint.value)) { + return true; + } + return false; +} + +function matchesLabel(labels, constraint) { + if (!constraint) { + return true; + } + var found = _.find(labels, function (label) { + return label.key === constraint.key && label.value === constraint.value; + }); + return found !== undefined; +} + +function extractValue(constraint, op) { + return constraint.split(op).pop().trim(); +} + +function extractCustomLabelKey(constraint, op, baseLabelKey) { + return constraint.split(op).shift().trim().replace(baseLabelKey, ''); +} + +angular.module('portainer.docker') + .factory('ConstraintsHelper', [function ConstraintsHelperFactory() { + 'use strict'; + return { + transformConstraints: function (constraints) { + var transform = {}; + for (var i = 0; i < constraints.length; i++) { + var constraint = constraints[i]; + + var op; + if (constraint.includes(patterns.op.eq)) { + op = patterns.op.eq; + } else if (constraint.includes(patterns.op.neq)) { + op = patterns.op.neq; + } + + var value = extractValue(constraint, op); + var key = ''; + switch (true) { + case constraint.includes(patterns.id.nodeId): + transform.nodeId = new ConstraintModel(op, key, value); + break; + case constraint.includes(patterns.id.nodeHostname): + transform.nodeHostname = new ConstraintModel(op, key, value); + break; + case constraint.includes(patterns.id.nodeRole): + transform.nodeRole = new ConstraintModel(op, key, value); + break; + case constraint.includes(patterns.id.nodeLabels): + key = extractCustomLabelKey(constraint, op, patterns.id.nodeLabels); + transform.nodeLabels = new ConstraintModel(op, key, value); + break; + case constraint.includes(patterns.id.engineLabels): + key = extractCustomLabelKey(constraint, op, patterns.id.engineLabels); + transform.engineLabels = new ConstraintModel(op, key, value); + break; + default: + break; + } + } + return transform; + }, + matchesServiceConstraints: function (service, node) { + if (service.Constraints === undefined || service.Constraints.length === 0) { + return true; + } + var constraints = this.transformConstraints(angular.copy(service.Constraints)); + if (matchesConstraint(node.Id, constraints.nodeId) && + matchesConstraint(node.Hostname, constraints.nodeHostname) && + matchesConstraint(node.Role, constraints.nodeRole) && + matchesLabel(node.Labels, constraints.nodeLabels) && + matchesLabel(node.EngineLabels, constraints.engineLabels) + ) { + return true; + } + return false; + } + }; + }]); \ No newline at end of file diff --git a/app/docker/models/task.js b/app/docker/models/task.js index e28390483..5714c1509 100644 --- a/app/docker/models/task.js +++ b/app/docker/models/task.js @@ -5,6 +5,7 @@ function TaskViewModel(data) { this.Slot = data.Slot; this.Spec = data.Spec; this.Status = data.Status; + this.DesiredState = data.DesiredState; this.ServiceId = data.ServiceID; this.NodeId = data.NodeID; if (data.Status && data.Status.ContainerStatus && data.Status.ContainerStatus.ContainerID) { From 64beaaa279b46322e40ae08d46b4d2c9cefe6031 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 20 Aug 2018 20:55:12 +0200 Subject: [PATCH 29/57] feat(container-details): update re-creation flow (#2193) --- .../containers/create/createContainerController.js | 10 +++++----- .../views/containers/edit/containerController.js | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 6580485f9..c03862d44 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -282,7 +282,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai return config; } - + function loadFromContainerCmd(d) { if ($scope.config.Cmd) { $scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd); @@ -374,7 +374,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } else { $scope.formValues.MacAddress = ''; } - + // ExtraHosts if ($scope.config.HostConfig.ExtraHosts) { var extraHosts = $scope.config.HostConfig.ExtraHosts; @@ -594,7 +594,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai function create() { var oldContainer = null; - + HttpRequestHelper.setPortainerAgentTargetHeader($scope.formValues.NodeName); return findCurrentContainer() .then(confirmCreateContainer) @@ -631,8 +631,8 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai return $q.when(); } $scope.state.actionInProgress = true; - return stopAndRenameContainer(oldContainer) - .then(pullImageIfNeeded) + return pullImageIfNeeded() + .then(stopAndRenameContainer(oldContainer)) .then(createNewContainer) .then(applyResourceControl) .then(connectToExtraNetworks) diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index e2c017632..0efe59f1e 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -196,9 +196,9 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co $scope.state.recreateContainerInProgress = true; var isRunning = container.State.Running; - return stopContainerIfNeeded() + return pullImageIfNeeded() + .then(stopContainerIfNeeded) .then(renameContainer) - .then(pullImageIfNeeded) .then(setMainNetworkAndCreateContainer) .then(connectContainerToOtherNetworks) .then(startContainerIfNeeded) @@ -312,7 +312,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co function updateRestartPolicy(restartPolicy, maximumRetryCount) { maximumRetryCount = restartPolicy === 'on-failure' ? maximumRetryCount : undefined; - + return ContainerService .updateRestartPolicy($scope.container.Id, restartPolicy, maximumRetryCount) .then(onUpdateSuccess) From e60d8091547c5869ef192f3f690e8f5a02ca27d8 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 20 Aug 2018 21:06:30 +0200 Subject: [PATCH 30/57] fix(container-creation): fix an issue with container-edition and UAC --- app/docker/views/containers/create/createContainerController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index c03862d44..54d2ae6bb 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -475,7 +475,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai Container.get({ id: $transition$.params().from }).$promise .then(function success(d) { var fromContainer = new ContainerDetailsViewModel(d); - if (!fromContainer.ResourceControl) { + if (fromContainer.ResourceControl && fromContainer.ResourceControl.Public) { $scope.formValues.AccessControlData.AccessControlEnabled = false; } $scope.fromContainer = fromContainer; From 74ca908759abf14319b5d2efb3d1bfa38e83cabf Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Tue, 21 Aug 2018 13:11:39 +0300 Subject: [PATCH 31/57] fix(stack-details): pass agentProxy as an argument (#2196) --- app/portainer/views/stacks/edit/stackController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/portainer/views/stacks/edit/stackController.js b/app/portainer/views/stacks/edit/stackController.js index 57356dbe7..6b9d54ee0 100644 --- a/app/portainer/views/stacks/edit/stackController.js +++ b/app/portainer/views/stacks/edit/stackController.js @@ -233,9 +233,9 @@ function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceSe function loadExternalSwarmStack(name) { var agentProxy = $scope.applicationState.endpoint.mode.agentProxy; - retrieveSwarmStackResources(name) + retrieveSwarmStackResources(name, agentProxy) .then(function success(data) { - assignSwarmStackResources(data); + assignSwarmStackResources(data, agentProxy); }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to retrieve stack details'); From 6ab6cfafb7b8d686e1e3b0d00fc709b8003948a2 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 21 Aug 2018 20:40:42 +0200 Subject: [PATCH 32/57] feat(motd): add the ability to display motd and dimiss information panels (#2191) * feat(api): add motd handler * feat(app): add the motd api layer * feat(motd): display motd and add the ability to dismiss information messages * style(home): relocate important message before info01 * feat(api): silently fail when an error occurs during motd retrieval --- api/crypto/ecdsa.go | 5 +- api/crypto/md5.go | 10 ++ api/http/client/client.go | 8 + api/http/handler/handler.go | 4 + api/http/handler/motd/handler.go | 24 +++ api/http/handler/motd/motd.go | 27 ++++ api/http/server.go | 4 + api/portainer.go | 146 +++++++++--------- app/constants.js | 1 + app/docker/views/dashboard/dashboard.html | 40 ++--- .../views/dashboard/dashboardController.js | 8 +- .../information-panel/information-panel.js | 3 +- .../information-panel/informationPanel.html | 7 +- app/portainer/models/motd.js | 4 + app/portainer/rest/motd.js | 7 + app/portainer/services/api/motdService.js | 22 +++ app/portainer/services/localStorage.js | 6 + app/portainer/services/stateManager.js | 20 ++- app/portainer/views/home/home.html | 14 +- app/portainer/views/home/homeController.js | 17 +- 20 files changed, 269 insertions(+), 108 deletions(-) create mode 100644 api/crypto/md5.go create mode 100644 api/http/handler/motd/handler.go create mode 100644 api/http/handler/motd/motd.go create mode 100644 app/portainer/models/motd.js create mode 100644 app/portainer/rest/motd.js create mode 100644 app/portainer/services/api/motdService.js diff --git a/api/crypto/ecdsa.go b/api/crypto/ecdsa.go index 1b453dd3e..003547531 100644 --- a/api/crypto/ecdsa.go +++ b/api/crypto/ecdsa.go @@ -3,7 +3,6 @@ package crypto import ( "crypto/ecdsa" "crypto/elliptic" - "crypto/md5" "crypto/rand" "crypto/x509" "encoding/base64" @@ -97,9 +96,7 @@ func (service *ECDSAService) GenerateKeyPair() ([]byte, []byte, error) { // that hash. // It then encodes the generated signature in base64. func (service *ECDSAService) Sign(message string) (string, error) { - digest := md5.New() - digest.Write([]byte(message)) - hash := digest.Sum(nil) + hash := HashFromBytes([]byte(message)) r := big.NewInt(0) s := big.NewInt(0) diff --git a/api/crypto/md5.go b/api/crypto/md5.go new file mode 100644 index 000000000..42ca24602 --- /dev/null +++ b/api/crypto/md5.go @@ -0,0 +1,10 @@ +package crypto + +import "crypto/md5" + +// HashFromBytes returns the hash of the specified data +func HashFromBytes(data []byte) []byte { + digest := md5.New() + digest.Write(data) + return digest.Sum(nil) +} diff --git a/api/http/client/client.go b/api/http/client/client.go index 29ebb7d88..541ec8257 100644 --- a/api/http/client/client.go +++ b/api/http/client/client.go @@ -13,6 +13,10 @@ import ( "github.com/portainer/portainer" ) +const ( + errInvalidResponseStatus = portainer.Error("Invalid response status (expecting 200)") +) + // HTTPClient represents a client to send HTTP requests. type HTTPClient struct { *http.Client @@ -75,6 +79,10 @@ func Get(url string) ([]byte, error) { } defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, errInvalidResponseStatus + } + body, err := ioutil.ReadAll(response.Body) if err != nil { return nil, err diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index dd15e1212..342230396 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -10,6 +10,7 @@ import ( "github.com/portainer/portainer/http/handler/endpointproxy" "github.com/portainer/portainer/http/handler/endpoints" "github.com/portainer/portainer/http/handler/file" + "github.com/portainer/portainer/http/handler/motd" "github.com/portainer/portainer/http/handler/registries" "github.com/portainer/portainer/http/handler/resourcecontrols" "github.com/portainer/portainer/http/handler/settings" @@ -33,6 +34,7 @@ type Handler struct { EndpointHandler *endpoints.Handler EndpointProxyHandler *endpointproxy.Handler FileHandler *file.Handler + MOTDHandler *motd.Handler RegistryHandler *registries.Handler ResourceControlHandler *resourcecontrols.Handler SettingsHandler *settings.Handler @@ -67,6 +69,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { default: http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r) } + case strings.HasPrefix(r.URL.Path, "/api/motd"): + http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/api/registries"): http.StripPrefix("/api", h.RegistryHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/api/resource_controls"): diff --git a/api/http/handler/motd/handler.go b/api/http/handler/motd/handler.go new file mode 100644 index 000000000..429731aa4 --- /dev/null +++ b/api/http/handler/motd/handler.go @@ -0,0 +1,24 @@ +package motd + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/portainer/portainer/http/security" +) + +// Handler is the HTTP handler used to handle MOTD operations. +type Handler struct { + *mux.Router +} + +// NewHandler returns a new Handler +func NewHandler(bouncer *security.RequestBouncer) *Handler { + h := &Handler{ + Router: mux.NewRouter(), + } + h.Handle("/motd", + bouncer.AuthenticatedAccess(http.HandlerFunc(h.motd))).Methods(http.MethodGet) + + return h +} diff --git a/api/http/handler/motd/motd.go b/api/http/handler/motd/motd.go new file mode 100644 index 000000000..b2599be2d --- /dev/null +++ b/api/http/handler/motd/motd.go @@ -0,0 +1,27 @@ +package motd + +import ( + "net/http" + + "github.com/portainer/portainer" + "github.com/portainer/portainer/crypto" + "github.com/portainer/portainer/http/client" + "github.com/portainer/portainer/http/response" +) + +type motdResponse struct { + Message string `json:"Message"` + Hash []byte `json:"Hash"` +} + +func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) { + + motd, err := client.Get(portainer.MessageOfTheDayURL) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + hash := crypto.HashFromBytes(motd) + response.JSON(w, &motdResponse{Message: string(motd), Hash: hash}) +} diff --git a/api/http/server.go b/api/http/server.go index 6b3415280..116a6c1ec 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -11,6 +11,7 @@ import ( "github.com/portainer/portainer/http/handler/endpointproxy" "github.com/portainer/portainer/http/handler/endpoints" "github.com/portainer/portainer/http/handler/file" + "github.com/portainer/portainer/http/handler/motd" "github.com/portainer/portainer/http/handler/registries" "github.com/portainer/portainer/http/handler/resourcecontrols" "github.com/portainer/portainer/http/handler/settings" @@ -115,6 +116,8 @@ func (server *Server) Start() error { var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public")) + var motdHandler = motd.NewHandler(requestBouncer) + var registryHandler = registries.NewHandler(requestBouncer) registryHandler.RegistryService = server.RegistryService @@ -175,6 +178,7 @@ func (server *Server) Start() error { EndpointHandler: endpointHandler, EndpointProxyHandler: endpointProxyHandler, FileHandler: fileHandler, + MOTDHandler: motdHandler, RegistryHandler: registryHandler, ResourceControlHandler: resourceControlHandler, SettingsHandler: settingsHandler, diff --git a/api/portainer.go b/api/portainer.go index 4c65d1149..49f87c652 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -7,7 +7,7 @@ type ( Value string `json:"value"` } - // CLIFlags represents the available flags on the CLI. + // CLIFlags represents the available flags on the CLI CLIFlags struct { Addr *string AdminPassword *string @@ -35,7 +35,7 @@ type ( SnapshotInterval *string } - // Status represents the application status. + // Status represents the application status Status struct { Authentication bool `json:"Authentication"` EndpointManagement bool `json:"EndpointManagement"` @@ -44,7 +44,7 @@ type ( Version string `json:"Version"` } - // LDAPSettings represents the settings used to connect to a LDAP server. + // LDAPSettings represents the settings used to connect to a LDAP server LDAPSettings struct { ReaderDN string `json:"ReaderDN"` Password string `json:"Password"` @@ -56,7 +56,7 @@ type ( AutoCreateUsers bool `json:"AutoCreateUsers"` } - // TLSConfiguration represents a TLS configuration. + // TLSConfiguration represents a TLS configuration TLSConfiguration struct { TLS bool `json:"TLS"` TLSSkipVerify bool `json:"TLSSkipVerify"` @@ -65,21 +65,21 @@ type ( TLSKeyPath string `json:"TLSKey,omitempty"` } - // LDAPSearchSettings represents settings used to search for users in a LDAP server. + // LDAPSearchSettings represents settings used to search for users in a LDAP server LDAPSearchSettings struct { BaseDN string `json:"BaseDN"` Filter string `json:"Filter"` UserNameAttribute string `json:"UserNameAttribute"` } - // LDAPGroupSearchSettings represents settings used to search for groups in a LDAP server. + // LDAPGroupSearchSettings represents settings used to search for groups in a LDAP server LDAPGroupSearchSettings struct { GroupBaseDN string `json:"GroupBaseDN"` GroupFilter string `json:"GroupFilter"` GroupAttribute string `json:"GroupAttribute"` } - // Settings represents the application settings. + // Settings represents the application settings Settings struct { LogoURL string `json:"LogoURL"` BlackListedLabels []Pair `json:"BlackListedLabels"` @@ -95,7 +95,7 @@ type ( DisplayExternalContributors bool } - // User represents a user account. + // User represents a user account User struct { ID UserID `json:"Id"` Username string `json:"Username"` @@ -110,10 +110,10 @@ type ( // or a regular user UserRole int - // AuthenticationMethod represents the authentication method used to authenticate a user. + // AuthenticationMethod represents the authentication method used to authenticate a user AuthenticationMethod int - // Team represents a list of user accounts. + // Team represents a list of user accounts Team struct { ID TeamID `json:"Id"` Name string `json:"Name"` @@ -136,20 +136,20 @@ type ( // MembershipRole represents the role of a user within a team MembershipRole int - // TokenData represents the data embedded in a JWT token. + // TokenData represents the data embedded in a JWT token TokenData struct { ID UserID Username string Role UserRole } - // StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier). + // StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier) StackID int - // StackType represents the type of the stack (compose v2, stack deploy v3). + // StackType represents the type of the stack (compose v2, stack deploy v3) StackType int - // Stack represents a Docker stack created via docker stack deploy. + // Stack represents a Docker stack created via docker stack deploy Stack struct { ID StackID `json:"Id"` Name string `json:"Name"` @@ -161,11 +161,11 @@ type ( ProjectPath string } - // RegistryID represents a registry identifier. + // RegistryID represents a registry identifier RegistryID int // Registry represents a Docker registry with all the info required - // to connect to it. + // to connect to it Registry struct { ID RegistryID `json:"Id"` Name string `json:"Name"` @@ -178,24 +178,24 @@ type ( } // DockerHub represents all the required information to connect and use the - // Docker Hub. + // Docker Hub DockerHub struct { Authentication bool `json:"Authentication"` Username string `json:"Username"` Password string `json:"Password,omitempty"` } - // EndpointID represents an endpoint identifier. + // EndpointID represents an endpoint identifier EndpointID int - // EndpointType represents the type of an endpoint. + // EndpointType represents the type of an endpoint EndpointType int // EndpointStatus represents the status of an endpoint EndpointStatus int // Endpoint represents a Docker endpoint with all the info required - // to connect to it. + // to connect to it Endpoint struct { ID EndpointID `json:"Id"` Name string `json:"Name"` @@ -243,10 +243,10 @@ type ( StackCount int `json:"StackCount"` } - // EndpointGroupID represents an endpoint group identifier. + // EndpointGroupID represents an endpoint group identifier EndpointGroupID int - // EndpointGroup represents a group of endpoints. + // EndpointGroup represents a group of endpoints EndpointGroup struct { ID EndpointGroupID `json:"Id"` Name string `json:"Name"` @@ -259,17 +259,17 @@ type ( Labels []Pair `json:"Labels"` } - // EndpointExtension represents a extension associated to an endpoint. + // EndpointExtension represents a extension associated to an endpoint EndpointExtension struct { Type EndpointExtensionType `json:"Type"` URL string `json:"URL"` } // EndpointExtensionType represents the type of an endpoint extension. Only - // one extension of each type can be associated to an endpoint. + // one extension of each type can be associated to an endpoint EndpointExtensionType int - // ResourceControlID represents a resource control identifier. + // ResourceControlID represents a resource control identifier ResourceControlID int // ResourceControl represent a reference to a Docker resource with specific access controls @@ -291,37 +291,37 @@ type ( AdministratorsOnly bool `json:"AdministratorsOnly,omitempty"` } - // ResourceControlType represents the type of resource associated to the resource control (volume, container, service...). + // ResourceControlType represents the type of resource associated to the resource control (volume, container, service...) ResourceControlType int - // UserResourceAccess represents the level of control on a resource for a specific user. + // UserResourceAccess represents the level of control on a resource for a specific user UserResourceAccess struct { UserID UserID `json:"UserId"` AccessLevel ResourceAccessLevel `json:"AccessLevel"` } - // TeamResourceAccess represents the level of control on a resource for a specific team. + // TeamResourceAccess represents the level of control on a resource for a specific team TeamResourceAccess struct { TeamID TeamID `json:"TeamId"` AccessLevel ResourceAccessLevel `json:"AccessLevel"` } - // TagID represents a tag identifier. + // TagID represents a tag identifier TagID int - // Tag represents a tag that can be associated to a resource. + // Tag represents a tag that can be associated to a resource Tag struct { ID TagID Name string `json:"Name"` } - // TemplateID represents a template identifier. + // TemplateID represents a template identifier TemplateID int - // TemplateType represents the type of a template. + // TemplateType represents the type of a template TemplateType int - // Template represents an application template. + // Template represents an application template Template struct { // Mandatory container/stack fields ID TemplateID `json:"Id"` @@ -357,7 +357,7 @@ type ( Hostname string `json:"hostname,omitempty"` } - // TemplateEnv represents a template environment variable configuration. + // TemplateEnv represents a template environment variable configuration TemplateEnv struct { Name string `json:"name"` Label string `json:"label,omitempty"` @@ -367,41 +367,41 @@ type ( Select []TemplateEnvSelect `json:"select,omitempty"` } - // TemplateVolume represents a template volume configuration. + // TemplateVolume represents a template volume configuration TemplateVolume struct { Container string `json:"container"` Bind string `json:"bind,omitempty"` ReadOnly bool `json:"readonly,omitempty"` } - // TemplateRepository represents the git repository configuration for a template. + // TemplateRepository represents the git repository configuration for a template TemplateRepository struct { URL string `json:"url"` StackFile string `json:"stackfile"` } // TemplateEnvSelect represents text/value pair that will be displayed as a choice for the - // template user. + // template user TemplateEnvSelect struct { Text string `json:"text"` Value string `json:"value"` Default bool `json:"default"` } - // ResourceAccessLevel represents the level of control associated to a resource. + // ResourceAccessLevel represents the level of control associated to a resource ResourceAccessLevel int // TLSFileType represents a type of TLS file required to connect to a Docker endpoint. - // It can be either a TLS CA file, a TLS certificate file or a TLS key file. + // It can be either a TLS CA file, a TLS certificate file or a TLS key file TLSFileType int - // CLIService represents a service for managing CLI. + // CLIService represents a service for managing CLI CLIService interface { ParseFlags(version string) (*CLIFlags, error) ValidateFlags(flags *CLIFlags) error } - // DataStore defines the interface to manage the data. + // DataStore defines the interface to manage the data DataStore interface { Open() error Init() error @@ -409,12 +409,12 @@ type ( MigrateData() error } - // Server defines the interface to serve the API. + // Server defines the interface to serve the API Server interface { Start() error } - // UserService represents a service for managing user data. + // UserService represents a service for managing user data UserService interface { User(ID UserID) (*User, error) UserByUsername(username string) (*User, error) @@ -425,7 +425,7 @@ type ( DeleteUser(ID UserID) error } - // TeamService represents a service for managing user data. + // TeamService represents a service for managing user data TeamService interface { Team(ID TeamID) (*Team, error) TeamByName(name string) (*Team, error) @@ -435,7 +435,7 @@ type ( DeleteTeam(ID TeamID) error } - // TeamMembershipService represents a service for managing team membership data. + // TeamMembershipService represents a service for managing team membership data TeamMembershipService interface { TeamMembership(ID TeamMembershipID) (*TeamMembership, error) TeamMemberships() ([]TeamMembership, error) @@ -448,7 +448,7 @@ type ( DeleteTeamMembershipByTeamID(teamID TeamID) error } - // EndpointService represents a service for managing endpoint data. + // EndpointService represents a service for managing endpoint data EndpointService interface { Endpoint(ID EndpointID) (*Endpoint, error) Endpoints() ([]Endpoint, error) @@ -459,7 +459,7 @@ type ( GetNextIdentifier() int } - // EndpointGroupService represents a service for managing endpoint group data. + // EndpointGroupService represents a service for managing endpoint group data EndpointGroupService interface { EndpointGroup(ID EndpointGroupID) (*EndpointGroup, error) EndpointGroups() ([]EndpointGroup, error) @@ -468,7 +468,7 @@ type ( DeleteEndpointGroup(ID EndpointGroupID) error } - // RegistryService represents a service for managing registry data. + // RegistryService represents a service for managing registry data RegistryService interface { Registry(ID RegistryID) (*Registry, error) Registries() ([]Registry, error) @@ -477,7 +477,7 @@ type ( DeleteRegistry(ID RegistryID) error } - // StackService represents a service for managing stack data. + // StackService represents a service for managing stack data StackService interface { Stack(ID StackID) (*Stack, error) StackByName(name string) (*Stack, error) @@ -488,25 +488,25 @@ type ( GetNextIdentifier() int } - // DockerHubService represents a service for managing the DockerHub object. + // DockerHubService represents a service for managing the DockerHub object DockerHubService interface { DockerHub() (*DockerHub, error) UpdateDockerHub(registry *DockerHub) error } - // SettingsService represents a service for managing application settings. + // SettingsService represents a service for managing application settings SettingsService interface { Settings() (*Settings, error) UpdateSettings(settings *Settings) error } - // VersionService represents a service for managing version data. + // VersionService represents a service for managing version data VersionService interface { DBVersion() (int, error) StoreDBVersion(version int) error } - // ResourceControlService represents a service for managing resource control data. + // ResourceControlService represents a service for managing resource control data ResourceControlService interface { ResourceControl(ID ResourceControlID) (*ResourceControl, error) ResourceControlByResourceID(resourceID string) (*ResourceControl, error) @@ -516,14 +516,14 @@ type ( DeleteResourceControl(ID ResourceControlID) error } - // TagService represents a service for managing tag data. + // TagService represents a service for managing tag data TagService interface { Tags() ([]Tag, error) CreateTag(tag *Tag) error DeleteTag(ID TagID) error } - // TemplateService represents a service for managing template data. + // TemplateService represents a service for managing template data TemplateService interface { Templates() ([]Template, error) Template(ID TemplateID) (*Template, error) @@ -532,13 +532,13 @@ type ( DeleteTemplate(ID TemplateID) error } - // CryptoService represents a service for encrypting/hashing data. + // CryptoService represents a service for encrypting/hashing data CryptoService interface { Hash(data string) (string, error) CompareHashAndData(hash string, data string) error } - // DigitalSignatureService represents a service to manage digital signatures. + // DigitalSignatureService represents a service to manage digital signatures DigitalSignatureService interface { ParseKeyPair(private, public []byte) error GenerateKeyPair() ([]byte, []byte, error) @@ -547,13 +547,13 @@ type ( Sign(message string) (string, error) } - // JWTService represents a service for managing JWT tokens. + // JWTService represents a service for managing JWT tokens JWTService interface { GenerateToken(data *TokenData) (string, error) ParseAndVerifyToken(token string) (*TokenData, error) } - // FileService represents a service for managing files. + // FileService represents a service for managing files FileService interface { GetFileContent(filePath string) ([]byte, error) Rename(oldPath, newPath string) error @@ -571,13 +571,13 @@ type ( FileExists(path string) (bool, error) } - // GitService represents a service for managing Git. + // GitService represents a service for managing Git GitService interface { ClonePublicRepository(repositoryURL, referenceName string, destination string) error ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password string) error } - // JobScheduler represents a service to run jobs on a periodic basis. + // JobScheduler represents a service to run jobs on a periodic basis JobScheduler interface { ScheduleEndpointSyncJob(endpointFilePath, interval string) error ScheduleSnapshotJob(interval string) error @@ -585,19 +585,19 @@ type ( Start() } - // Snapshotter represents a service used to create endpoint snapshots. + // Snapshotter represents a service used to create endpoint snapshots Snapshotter interface { CreateSnapshot(endpoint *Endpoint) (*Snapshot, error) } - // LDAPService represents a service used to authenticate users against a LDAP/AD. + // LDAPService represents a service used to authenticate users against a LDAP/AD LDAPService interface { AuthenticateUser(username, password string, settings *LDAPSettings) error TestConnectivity(settings *LDAPSettings) error GetUserGroups(username string, settings *LDAPSettings) ([]string, error) } - // SwarmStackManager represents a service to manage Swarm stacks. + // SwarmStackManager represents a service to manage Swarm stacks SwarmStackManager interface { Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) Logout(endpoint *Endpoint) error @@ -605,7 +605,7 @@ type ( Remove(stack *Stack, endpoint *Endpoint) error } - // ComposeStackManager represents a service to manage Compose stacks. + // ComposeStackManager represents a service to manage Compose stacks ComposeStackManager interface { Up(stack *Stack, endpoint *Endpoint) error Down(stack *Stack, endpoint *Endpoint) error @@ -613,13 +613,15 @@ type ( ) const ( - // APIVersion is the version number of the Portainer API. + // APIVersion is the version number of the Portainer API APIVersion = "1.19.2-dev" - // DBVersion is the version number of the Portainer database. + // DBVersion is the version number of the Portainer database DBVersion = 14 + // MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved + MessageOfTheDayURL = "https://raw.githubusercontent.com/portainer/motd/master/message.html" // PortainerAgentHeader represents the name of the header available in any agent response PortainerAgentHeader = "Portainer-Agent" - // PortainerAgentTargetHeader represent the name of the header containing the target node name. + // PortainerAgentTargetHeader represent the name of the header containing the target node name PortainerAgentTargetHeader = "X-PortainerAgent-Target" // PortainerAgentSignatureHeader represent the name of the header containing the digital signature PortainerAgentSignatureHeader = "X-PortainerAgent-Signature" @@ -628,16 +630,16 @@ const ( // PortainerAgentSignatureMessage represents the message used to create a digital signature // to be used when communicating with an agent PortainerAgentSignatureMessage = "Portainer-App" - // SupportedDockerAPIVersion is the minimum Docker API version supported by Portainer. + // SupportedDockerAPIVersion is the minimum Docker API version supported by Portainer SupportedDockerAPIVersion = "1.24" ) const ( - // TLSFileCA represents a TLS CA certificate file. + // TLSFileCA represents a TLS CA certificate file TLSFileCA TLSFileType = iota - // TLSFileCert represents a TLS certificate file. + // TLSFileCert represents a TLS certificate file TLSFileCert - // TLSFileKey represents a TLS key file. + // TLSFileKey represents a TLS key file TLSFileKey ) diff --git a/app/constants.js b/app/constants.js index 9b92ddf87..de9631ddd 100644 --- a/app/constants.js +++ b/app/constants.js @@ -3,6 +3,7 @@ angular.module('portainer') .constant('API_ENDPOINT_DOCKERHUB', 'api/dockerhub') .constant('API_ENDPOINT_ENDPOINTS', 'api/endpoints') .constant('API_ENDPOINT_ENDPOINT_GROUPS', 'api/endpoint_groups') +.constant('API_ENDPOINT_MOTD', 'api/motd') .constant('API_ENDPOINT_REGISTRIES', 'api/registries') .constant('API_ENDPOINT_RESOURCE_CONTROLS', 'api/resource_controls') .constant('API_ENDPOINT_SETTINGS', 'api/settings') diff --git a/app/docker/views/dashboard/dashboard.html b/app/docker/views/dashboard/dashboard.html index 3821dfead..5b1a2bac3 100644 --- a/app/docker/views/dashboard/dashboard.html +++ b/app/docker/views/dashboard/dashboard.html @@ -9,30 +9,22 @@
    - + + +

    + + Portainer is connected to a node that is part of a Swarm cluster. Some resources located on other nodes in the cluster might not be available for management, have a look + at our agent setup for more details. +

    +

    + + Portainer is connected to a worker node. Swarm management features will not be available. +

    +
    +
    diff --git a/app/docker/views/dashboard/dashboardController.js b/app/docker/views/dashboard/dashboardController.js index d9bf1b2c4..114c745d0 100644 --- a/app/docker/views/dashboard/dashboardController.js +++ b/app/docker/views/dashboard/dashboardController.js @@ -1,6 +1,10 @@ angular.module('portainer.docker') -.controller('DashboardController', ['$scope', '$q', 'ContainerService', 'ImageService', 'NetworkService', 'VolumeService', 'SystemService', 'ServiceService', 'StackService', 'EndpointService', 'Notifications', 'EndpointProvider', -function ($scope, $q, ContainerService, ImageService, NetworkService, VolumeService, SystemService, ServiceService, StackService, EndpointService, Notifications, EndpointProvider) { +.controller('DashboardController', ['$scope', '$q', 'ContainerService', 'ImageService', 'NetworkService', 'VolumeService', 'SystemService', 'ServiceService', 'StackService', 'EndpointService', 'Notifications', 'EndpointProvider', 'StateManager', +function ($scope, $q, ContainerService, ImageService, NetworkService, VolumeService, SystemService, ServiceService, StackService, EndpointService, Notifications, EndpointProvider, StateManager) { + + $scope.dismissInformationPanel = function(id) { + StateManager.dismissInformationPanel(id); + }; function initView() { var endpointMode = $scope.applicationState.endpoint.mode; diff --git a/app/portainer/components/information-panel/information-panel.js b/app/portainer/components/information-panel/information-panel.js index ea014f619..083b6d013 100644 --- a/app/portainer/components/information-panel/information-panel.js +++ b/app/portainer/components/information-panel/information-panel.js @@ -1,7 +1,8 @@ angular.module('portainer.app').component('informationPanel', { templateUrl: 'app/portainer/components/information-panel/informationPanel.html', bindings: { - titleText: '@' + titleText: '@', + dismissAction: '&' }, transclude: true }); diff --git a/app/portainer/components/information-panel/informationPanel.html b/app/portainer/components/information-panel/informationPanel.html index 7407ec823..21f434354 100644 --- a/app/portainer/components/information-panel/informationPanel.html +++ b/app/portainer/components/information-panel/informationPanel.html @@ -3,7 +3,12 @@
    - {{ $ctrl.titleText }} + + {{ $ctrl.titleText }} + + + dismiss +
    diff --git a/app/portainer/models/motd.js b/app/portainer/models/motd.js new file mode 100644 index 000000000..165317b31 --- /dev/null +++ b/app/portainer/models/motd.js @@ -0,0 +1,4 @@ +function MotdViewModel(data) { + this.Message = data.Message; + this.Hash = data.Hash; +} diff --git a/app/portainer/rest/motd.js b/app/portainer/rest/motd.js new file mode 100644 index 000000000..49e54f534 --- /dev/null +++ b/app/portainer/rest/motd.js @@ -0,0 +1,7 @@ +angular.module('portainer.app') +.factory('Motd', ['$resource', 'API_ENDPOINT_MOTD', function MotdFactory($resource, API_ENDPOINT_MOTD) { + 'use strict'; + return $resource(API_ENDPOINT_MOTD, {}, { + get: { method: 'GET' } + }); +}]); diff --git a/app/portainer/services/api/motdService.js b/app/portainer/services/api/motdService.js new file mode 100644 index 000000000..3ea5dcf73 --- /dev/null +++ b/app/portainer/services/api/motdService.js @@ -0,0 +1,22 @@ +angular.module('portainer.app') +.factory('MotdService', ['$q', 'Motd', function MotdServiceFactory($q, Motd) { + 'use strict'; + var service = {}; + + service.motd = function() { + var deferred = $q.defer(); + + Motd.get().$promise + .then(function success(data) { + var motd = new MotdViewModel(data); + deferred.resolve(motd); + }) + .catch(function error(err) { + deferred.reject({msg: 'Unable to retrieve information message', err: err}); + }); + + return deferred.promise; + }; + + return service; +}]); diff --git a/app/portainer/services/localStorage.js b/app/portainer/services/localStorage.js index cb1dadceb..b104b4076 100644 --- a/app/portainer/services/localStorage.js +++ b/app/portainer/services/localStorage.js @@ -26,6 +26,12 @@ angular.module('portainer.app') getApplicationState: function() { return localStorageService.get('APPLICATION_STATE'); }, + storeUIState: function(state) { + localStorageService.cookie.set('UI_STATE', state); + }, + getUIState: function() { + return localStorageService.cookie.get('UI_STATE'); + }, storeJWT: function(jwt) { localStorageService.set('JWT', jwt); }, diff --git a/app/portainer/services/stateManager.js b/app/portainer/services/stateManager.js index 557c0853c..2b80109fe 100644 --- a/app/portainer/services/stateManager.js +++ b/app/portainer/services/stateManager.js @@ -9,7 +9,20 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin loading: true, application: {}, endpoint: {}, - UI: {} + UI: { + dismissedInfoPanels: {}, + dismissedInfoHash: '' + } + }; + + manager.dismissInformationPanel = function(id) { + state.UI.dismissedInfoPanels[id] = true; + LocalStorage.storeUIState(state.UI); + }; + + manager.dismissImportantInformation = function(hash) { + state.UI.dismissedInfoHash = hash; + LocalStorage.storeUIState(state.UI); }; manager.getState = function() { @@ -68,6 +81,11 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin manager.initialize = function () { var deferred = $q.defer(); + var UIState = LocalStorage.getUIState(); + if (UIState) { + state.UI = UIState; + } + var endpointState = LocalStorage.getEndpointState(); if (endpointState) { state.endpoint = endpointState; diff --git a/app/portainer/views/home/home.html b/app/portainer/views/home/home.html index 1ffae5f9e..e9de46386 100644 --- a/app/portainer/views/home/home.html +++ b/app/portainer/views/home/home.html @@ -7,7 +7,19 @@ Endpoints - + + +

    +
    +
    + +

    Welcome to Portainer ! Click on any endpoint in the list below to access management features. diff --git a/app/portainer/views/home/homeController.js b/app/portainer/views/home/homeController.js index 88bee800b..318e9e2c9 100644 --- a/app/portainer/views/home/homeController.js +++ b/app/portainer/views/home/homeController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('HomeController', ['$q', '$scope', '$state', 'Authentication', 'EndpointService', 'EndpointHelper', 'GroupService', 'Notifications', 'EndpointProvider', 'StateManager', 'ExtensionManager', 'ModalService', -function ($q, $scope, $state, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, ExtensionManager, ModalService) { +.controller('HomeController', ['$q', '$scope', '$state', 'Authentication', 'EndpointService', 'EndpointHelper', 'GroupService', 'Notifications', 'EndpointProvider', 'StateManager', 'ExtensionManager', 'ModalService', 'MotdService', +function ($q, $scope, $state, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, ExtensionManager, ModalService, MotdService) { $scope.goToDashboard = function(endpoint) { EndpointProvider.setEndpointID(endpoint.Id); @@ -12,6 +12,14 @@ function ($q, $scope, $state, Authentication, EndpointService, EndpointHelper, G } }; + $scope.dismissImportantInformation = function(hash) { + StateManager.dismissImportantInformation(hash); + }; + + $scope.dismissInformationPanel = function(id) { + StateManager.dismissInformationPanel(id); + }; + function triggerSnapshot() { EndpointService.snapshot() .then(function success(data) { @@ -57,6 +65,11 @@ function ($q, $scope, $state, Authentication, EndpointService, EndpointHelper, G function initView() { $scope.isAdmin = Authentication.getUserDetails().role === 1; + MotdService.motd() + .then(function success(data) { + $scope.motd = data; + }); + $q.all({ endpoints: EndpointService.endpoints(), groups: GroupService.groups() From e8ab89ae793863eeeb4c30db6eb608ce41516c02 Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 22 Aug 2018 08:41:02 +0200 Subject: [PATCH 33/57] feat(config-details): add the ability to clone a config (#2189) --- app/docker/__module.js | 2 +- .../configs-datatable/configsDatatable.html | 2 +- .../configs/create/createConfigController.js | 31 +++++++++++++++++-- .../views/configs/create/createconfig.html | 3 +- app/docker/views/configs/edit/config.html | 1 + 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/docker/__module.js b/app/docker/__module.js index 32253b43a..ffd451fbd 100644 --- a/app/docker/__module.js +++ b/app/docker/__module.js @@ -32,7 +32,7 @@ angular.module('portainer.docker', ['portainer.app']) var configCreation = { name: 'docker.configs.new', - url: '/new', + url: '/new?id', views: { 'content@': { templateUrl: 'app/docker/views/configs/create/createconfig.html', diff --git a/app/docker/components/datatables/configs-datatable/configsDatatable.html b/app/docker/components/datatables/configs-datatable/configsDatatable.html index b1e462101..9b2f4364f 100644 --- a/app/docker/components/datatables/configs-datatable/configsDatatable.html +++ b/app/docker/components/datatables/configs-datatable/configsDatatable.html @@ -11,7 +11,7 @@ ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> Remove -

    diff --git a/app/docker/views/configs/create/createConfigController.js b/app/docker/views/configs/create/createConfigController.js index 07868dd3c..c2f70685c 100644 --- a/app/docker/views/configs/create/createConfigController.js +++ b/app/docker/views/configs/create/createConfigController.js @@ -1,7 +1,6 @@ angular.module('portainer.docker') -.controller('CreateConfigController', ['$scope', '$state', 'Notifications', 'ConfigService', 'Authentication', 'FormValidator', 'ResourceControlService', -function ($scope, $state, Notifications, ConfigService, Authentication, FormValidator, ResourceControlService) { - +.controller('CreateConfigController', ['$scope', '$state', '$transition$', 'Notifications', 'ConfigService', 'Authentication', 'FormValidator', 'ResourceControlService', +function ($scope, $state, $transition$, Notifications, ConfigService, Authentication, FormValidator, ResourceControlService) { $scope.formValues = { Name: '', Labels: [], @@ -90,4 +89,30 @@ function ($scope, $state, Notifications, ConfigService, Authentication, FormVali $scope.editorUpdate = function(cm) { $scope.formValues.ConfigContent = cm.getValue(); }; + + function initView() { + if (!$transition$.params().id) { + $scope.formValues.displayCodeEditor = true; + return; + } + + ConfigService.config($transition$.params().id) + .then(function success(data) { + $scope.formValues.Name = data.Name + '_copy'; + $scope.formValues.Data = data.Data; + var labels = _.keys(data.Labels); + for (var i = 0; i < labels.length; i++) { + var labelName = labels[i]; + var labelValue = data.Labels[labelName]; + $scope.formValues.Labels.push({ name: labelName, value: labelValue}); + } + $scope.formValues.displayCodeEditor = true; + }) + .catch(function error(err) { + $scope.formValues.displayCodeEditor = true; + Notifications.error('Failure', err, 'Unable to clone config'); + }); + } + + initView(); }]); diff --git a/app/docker/views/configs/create/createconfig.html b/app/docker/views/configs/create/createconfig.html index 18cfc51ac..158cc4c2f 100644 --- a/app/docker/views/configs/create/createconfig.html +++ b/app/docker/views/configs/create/createconfig.html @@ -20,12 +20,13 @@
    -
    +
    diff --git a/app/docker/views/configs/edit/config.html b/app/docker/views/configs/edit/config.html index 376bd2014..5fb4dda01 100644 --- a/app/docker/views/configs/edit/config.html +++ b/app/docker/views/configs/edit/config.html @@ -25,6 +25,7 @@ {{ config.Id }} + From 68d77e5e0e28d781b665d3623a38d074e016db07 Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 22 Aug 2018 08:45:14 +0200 Subject: [PATCH 34/57] feat(networks): add details about the attachable/internal properties (#2200) --- .../networks-datatable/networksDatatable.html | 16 ++++++++++++++++ app/docker/models/network.js | 1 + app/docker/views/networks/edit/network.html | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/app/docker/components/datatables/networks-datatable/networksDatatable.html b/app/docker/components/datatables/networks-datatable/networksDatatable.html index 7e88a2549..afb808ce8 100644 --- a/app/docker/components/datatables/networks-datatable/networksDatatable.html +++ b/app/docker/components/datatables/networks-datatable/networksDatatable.html @@ -55,6 +55,20 @@ + + + Attachable + + + + + + + Internal + + + + IPAM Driver @@ -104,6 +118,8 @@ {{ item.StackName ? item.StackName : '-' }} {{ item.Scope }} {{ item.Driver }} + {{ item.Attachable }} + {{ item.Internal }} {{ item.IPAM.Driver }} {{ item.IPAM.Config[0].Subnet ? item.IPAM.Config[0].Subnet : '-' }} {{ item.IPAM.Config[0].Gateway ? item.IPAM.Config[0].Gateway : '-' }} diff --git a/app/docker/models/network.js b/app/docker/models/network.js index 744f10061..da60a10af 100644 --- a/app/docker/models/network.js +++ b/app/docker/models/network.js @@ -4,6 +4,7 @@ function NetworkViewModel(data) { this.Scope = data.Scope; this.Driver = data.Driver; this.Attachable = data.Attachable; + this.Internal = data.Internal; this.IPAM = data.IPAM; this.Containers = data.Containers; this.Options = data.Options; diff --git a/app/docker/views/networks/edit/network.html b/app/docker/views/networks/edit/network.html index 08db62c3f..f22562163 100644 --- a/app/docker/views/networks/edit/network.html +++ b/app/docker/views/networks/edit/network.html @@ -31,6 +31,14 @@ Scope {{ network.Scope }} + + Attachable + {{ network.Attachable }} + + + Internal + {{ network.Internal }} + Subnet {{ network.IPAM.Config[0].Subnet }} From 46da95ecfb300aab1d5e0ed783341bbd2385d96f Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 22 Aug 2018 13:18:02 +0200 Subject: [PATCH 35/57] feat(motd): ignore loading for motd --- app/portainer/rest/motd.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/portainer/rest/motd.js b/app/portainer/rest/motd.js index 49e54f534..4211ef0fd 100644 --- a/app/portainer/rest/motd.js +++ b/app/portainer/rest/motd.js @@ -2,6 +2,9 @@ angular.module('portainer.app') .factory('Motd', ['$resource', 'API_ENDPOINT_MOTD', function MotdFactory($resource, API_ENDPOINT_MOTD) { 'use strict'; return $resource(API_ENDPOINT_MOTD, {}, { - get: { method: 'GET' } + get: { + method: 'GET', + ignoreLoadingBar: true + } }); }]); From e58acd7dd651600cb6b2dd8a0927235cf5abbe9f Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 22 Aug 2018 18:33:06 +0300 Subject: [PATCH 36/57] * chore(eslint): update esllint and remove unused variables * chore(eslint-config): change no-unused-vars to warn * chore(eslint): remove unused variables * chore(eslint): allow unused globals * fixup! chore(eslint): allow unused globals * chore(eslint): remove commented unused vars * fixup! chore(eslint): remove commented unused vars --- .eslintrc.yml | 5 ++++- app/app.js | 2 +- app/azure/models/container_group.js | 2 +- app/azure/services/azureService.js | 2 +- .../create/createContainerInstanceController.js | 2 +- .../containersDatatableController.js | 2 +- .../imagesDatatableController.js | 2 +- .../serviceTasksDatatableController.js | 2 +- .../servicesDatatableActionsController.js | 4 ++-- .../volumesDatatableController.js | 2 +- .../components/log-viewer/logViewerController.js | 1 - app/docker/helpers/containerHelper.js | 6 ------ app/docker/models/service.js | 2 +- app/docker/services/volumeService.js | 2 +- .../views/configs/edit/configController.js | 2 +- .../console/containerConsoleController.js | 8 ++++---- .../create/createContainerController.js | 16 ++++++++-------- .../views/containers/edit/containerController.js | 10 +++++----- .../containers/logs/containerLogsController.js | 4 ---- app/docker/views/images/edit/imageController.js | 6 +++--- app/docker/views/images/imagesController.js | 2 +- .../views/networks/edit/networkController.js | 10 +++++----- app/docker/views/nodes/edit/nodeController.js | 2 +- .../views/secrets/edit/secretController.js | 2 +- .../services/create/createServiceController.js | 3 +-- .../views/services/edit/serviceController.js | 14 +++++++------- .../volumes/create/createVolumeController.js | 2 +- .../views/volumes/edit/volumeController.js | 2 +- app/extensions/storidge/services/chartService.js | 2 +- .../storidge/views/monitor/monitorController.js | 4 ++-- .../profiles/create/createProfileController.js | 2 +- .../views/profiles/edit/profileController.js | 4 ++-- .../views/profiles/profilesController.js | 2 +- .../porAccessControlPanelController.js | 4 ++-- .../porAccessManagementController.js | 8 ++++---- .../components/endpoint-list/endpoint-list.js | 2 -- app/portainer/components/header-content.js | 2 +- app/portainer/components/header-title.js | 4 ++-- .../tag-selector/tagSelectorController.js | 4 +--- app/portainer/helpers/templateHelper.js | 4 ++-- app/portainer/services/api/stackService.js | 6 +++--- app/portainer/services/api/teamService.js | 2 +- app/portainer/services/api/userService.js | 4 ++-- app/portainer/services/chartService.js | 4 ++-- app/portainer/services/fileUpload.js | 2 +- app/portainer/services/modalService.js | 2 +- app/portainer/services/stateManager.js | 4 ++-- .../endpoints/create/createEndpointController.js | 2 -- .../views/endpoints/edit/endpointController.js | 2 +- .../views/groups/edit/groupController.js | 2 +- app/portainer/views/home/homeController.js | 2 +- .../init/endpoint/initEndpointController.js | 13 +++---------- app/portainer/views/main/mainController.js | 2 +- .../create/createRegistryController.js | 2 +- .../views/registries/edit/registryController.js | 2 +- .../views/registries/registriesController.js | 6 +++--- .../settingsAuthenticationController.js | 8 ++++---- .../views/settings/settingsController.js | 6 +++--- .../views/stacks/create/createStackController.js | 2 +- .../views/stacks/edit/stackController.js | 4 ++-- app/portainer/views/tags/tagsController.js | 2 +- app/portainer/views/teams/edit/teamController.js | 8 ++++---- app/portainer/views/teams/teamsController.js | 2 +- .../views/templates/templatesController.js | 8 +++----- app/portainer/views/users/edit/userController.js | 6 +++--- app/portainer/views/users/usersController.js | 2 +- gruntfile.js | 1 - 67 files changed, 121 insertions(+), 146 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index 47be470b1..bc803c2e5 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -141,7 +141,10 @@ rules: no-undef-init: error no-undef: off no-undefined: off - no-unused-vars: off + no-unused-vars: + - warn + - + vars: local no-use-before-define: off # Node.js and CommonJS diff --git a/app/app.js b/app/app.js index 0ed1166fe..a32633fe2 100644 --- a/app/app.js +++ b/app/app.js @@ -48,7 +48,7 @@ function initAnalytics(Analytics, $rootScope) { Analytics.offline(false); Analytics.registerScriptTags(); Analytics.registerTrackers(); - $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) { + $rootScope.$on('$stateChangeSuccess', function (event, toState) { Analytics.trackPage(toState.url); Analytics.pageView(); }); diff --git a/app/azure/models/container_group.js b/app/azure/models/container_group.js index 0ba06a690..9a855129f 100644 --- a/app/azure/models/container_group.js +++ b/app/azure/models/container_group.js @@ -15,7 +15,7 @@ function ContainerGroupDefaultModel() { this.Memory = 1; } -function ContainerGroupViewModel(data, subscriptionId, resourceGroupName) { +function ContainerGroupViewModel(data) { this.Id = data.id; this.Name = data.name; this.Location = data.location; diff --git a/app/azure/services/azureService.js b/app/azure/services/azureService.js index 8d7def765..d0fa0468c 100644 --- a/app/azure/services/azureService.js +++ b/app/azure/services/azureService.js @@ -30,7 +30,7 @@ function AzureServiceFactory($q, Azure, SubscriptionService, ResourceGroupServic service.aggregate = function(resourcesBySubcription) { var aggregatedResources = []; - Object.keys(resourcesBySubcription).forEach(function(key, index) { + Object.keys(resourcesBySubcription).forEach(function(key) { aggregatedResources = aggregatedResources.concat(resourcesBySubcription[key]); }); return aggregatedResources; diff --git a/app/azure/views/containerinstances/create/createContainerInstanceController.js b/app/azure/views/containerinstances/create/createContainerInstanceController.js index b3a7ed173..a7ecab9e8 100644 --- a/app/azure/views/containerinstances/create/createContainerInstanceController.js +++ b/app/azure/views/containerinstances/create/createContainerInstanceController.js @@ -31,7 +31,7 @@ function ($q, $scope, $state, AzureService, Notifications) { $scope.state.actionInProgress = true; AzureService.createContainerGroup(model, subscriptionId, resourceGroupName) - .then(function success(data) { + .then(function success() { Notifications.success('Container successfully created', model.Name); $state.go('azure.containerinstances'); }) diff --git a/app/docker/components/datatables/containers-datatable/containersDatatableController.js b/app/docker/components/datatables/containers-datatable/containersDatatableController.js index f7125d7b8..7151e5d64 100644 --- a/app/docker/components/datatables/containers-datatable/containersDatatableController.js +++ b/app/docker/components/datatables/containers-datatable/containersDatatableController.js @@ -141,7 +141,7 @@ function (PaginationService, DatatableService, EndpointProvider) { PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit); }; - this.applyFilters = function(value, index, array) { + this.applyFilters = function(value) { var container = value; var filters = ctrl.filters; for (var i = 0; i < filters.state.values.length; i++) { diff --git a/app/docker/components/datatables/images-datatable/imagesDatatableController.js b/app/docker/components/datatables/images-datatable/imagesDatatableController.js index c7850a63b..f5254a47b 100644 --- a/app/docker/components/datatables/images-datatable/imagesDatatableController.js +++ b/app/docker/components/datatables/images-datatable/imagesDatatableController.js @@ -52,7 +52,7 @@ function (PaginationService, DatatableService) { PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit); }; - this.applyFilters = function(value, index, array) { + this.applyFilters = function(value) { var image = value; var filters = ctrl.filters; if ((image.ContainerCount === 0 && filters.usage.showUnusedImages) diff --git a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js index 33eecffc4..b0a8da0b2 100644 --- a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js +++ b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js @@ -15,7 +15,7 @@ function (DatatableService) { } }; - this.applyFilters = function(item, index, array) { + this.applyFilters = function(item) { var filters = ctrl.filters; for (var i = 0; i < filters.state.values.length; i++) { var filter = filters.state.values[i]; diff --git a/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js b/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js index e2a394e58..dff0d034c 100644 --- a/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js +++ b/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js @@ -6,7 +6,7 @@ function ($state, ServiceService, ServiceHelper, Notifications, ModalService, Im var config = ServiceHelper.serviceToConfig(service.Model); config.Mode.Replicated.Replicas = service.Replicas; ServiceService.update(service, config) - .then(function success(data) { + .then(function success() { Notifications.success('Service successfully scaled', 'New replica count: ' + service.Replicas); $state.reload(); }) @@ -53,7 +53,7 @@ function ($state, ServiceService, ServiceHelper, Notifications, ModalService, Im // value or an increment of the counter value to force an update. config.TaskTemplate.ForceUpdate++; ServiceService.update(service, config) - .then(function success(data) { + .then(function success() { Notifications.success('Service successfully updated', service.Name); }) .catch(function error(err) { diff --git a/app/docker/components/datatables/volumes-datatable/volumesDatatableController.js b/app/docker/components/datatables/volumes-datatable/volumesDatatableController.js index f146f107e..38b3ebbff 100644 --- a/app/docker/components/datatables/volumes-datatable/volumesDatatableController.js +++ b/app/docker/components/datatables/volumes-datatable/volumesDatatableController.js @@ -52,7 +52,7 @@ function (PaginationService, DatatableService) { PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit); }; - this.applyFilters = function(value, index, array) { + this.applyFilters = function(value) { var volume = value; var filters = ctrl.filters; if ((volume.dangling && filters.usage.showUnusedVolumes) diff --git a/app/docker/components/log-viewer/logViewerController.js b/app/docker/components/log-viewer/logViewerController.js index f85c20852..9de38f318 100644 --- a/app/docker/components/log-viewer/logViewerController.js +++ b/app/docker/components/log-viewer/logViewerController.js @@ -1,7 +1,6 @@ angular.module('portainer.docker') .controller('LogViewerController', ['clipboard', function (clipboard) { - var ctrl = this; this.state = { copySupported: clipboard.supported, diff --git a/app/docker/helpers/containerHelper.js b/app/docker/helpers/containerHelper.js index c30af02c6..1e5d45ff6 100644 --- a/app/docker/helpers/containerHelper.js +++ b/app/docker/helpers/containerHelper.js @@ -35,12 +35,6 @@ angular.module('portainer.docker') for (var v in container.Mounts) { if ({}.hasOwnProperty.call(container.Mounts, v)) { var mount = container.Mounts[v]; - var volume = { - 'type': mount.Type, - 'name': mount.Name || mount.Source, - 'containerPath': mount.Destination, - 'readOnly': mount.RW === false - }; var name = mount.Name || mount.Source; var containerPath = mount.Destination; if (name && containerPath) { diff --git a/app/docker/models/service.js b/app/docker/models/service.js index 07a769156..a49392f2c 100644 --- a/app/docker/models/service.js +++ b/app/docker/models/service.js @@ -1,4 +1,4 @@ -function ServiceViewModel(data, runningTasks, allTasks, nodes) { +function ServiceViewModel(data, runningTasks, allTasks) { this.Model = data; this.Id = data.ID; this.Tasks = []; diff --git a/app/docker/services/volumeService.js b/app/docker/services/volumeService.js index d52b60528..449ec661c 100644 --- a/app/docker/services/volumeService.js +++ b/app/docker/services/volumeService.js @@ -1,5 +1,5 @@ angular.module('portainer.docker') -.factory('VolumeService', ['$q', 'Volume', 'VolumeHelper', 'ResourceControlService', 'UserService', 'TeamService', function VolumeServiceFactory($q, Volume, VolumeHelper, ResourceControlService, UserService, TeamService) { +.factory('VolumeService', ['$q', 'Volume', 'VolumeHelper', 'ResourceControlService', function VolumeServiceFactory($q, Volume, VolumeHelper, ResourceControlService) { 'use strict'; var service = {}; diff --git a/app/docker/views/configs/edit/configController.js b/app/docker/views/configs/edit/configController.js index 3e0ad0178..2457c9609 100644 --- a/app/docker/views/configs/edit/configController.js +++ b/app/docker/views/configs/edit/configController.js @@ -4,7 +4,7 @@ function ($scope, $transition$, $state, ConfigService, Notifications) { $scope.removeConfig = function removeConfig(configId) { ConfigService.remove(configId) - .then(function success(data) { + .then(function success() { Notifications.success('Config successfully removed'); $state.go('docker.configs', {}); }) diff --git a/app/docker/views/containers/console/containerConsoleController.js b/app/docker/views/containers/console/containerConsoleController.js index 9f29e11dd..812e5b567 100644 --- a/app/docker/views/containers/console/containerConsoleController.js +++ b/app/docker/views/containers/console/containerConsoleController.js @@ -12,7 +12,7 @@ function ($scope, $transition$, ContainerService, ImageService, EndpointProvider $scope.containerCommands = []; // Ensure the socket is closed before leaving the view - $scope.$on('$stateChangeStart', function (event, next, current) { + $scope.$on('$stateChangeStart', function () { if (socket && socket !== null) { socket.close(); } @@ -69,7 +69,7 @@ function ($scope, $transition$, ContainerService, ImageService, EndpointProvider socket = new WebSocket(url); $scope.state.connected = true; - socket.onopen = function(evt) { + socket.onopen = function() { term = new Terminal(); term.on('data', function (data) { @@ -88,10 +88,10 @@ function ($scope, $transition$, ContainerService, ImageService, EndpointProvider socket.onmessage = function (e) { term.write(e.data); }; - socket.onerror = function (error) { + socket.onerror = function () { $scope.state.connected = false; }; - socket.onclose = function(evt) { + socket.onclose = function() { $scope.state.connected = false; }; }; diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 54d2ae6bb..83b153e44 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -282,8 +282,8 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai return config; } - - function loadFromContainerCmd(d) { + + function loadFromContainerCmd() { if ($scope.config.Cmd) { $scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd); } else { @@ -291,7 +291,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } } - function loadFromContainerPortBindings(d) { + function loadFromContainerPortBindings() { var bindings = []; for (var p in $scope.config.HostConfig.PortBindings) { if ({}.hasOwnProperty.call($scope.config.HostConfig.PortBindings, p)) { @@ -386,7 +386,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } } - function loadFromContainerEnvironmentVariables(d) { + function loadFromContainerEnvironmentVariables() { var envArr = []; for (var e in $scope.config.Env) { if ({}.hasOwnProperty.call($scope.config.Env, e)) { @@ -397,7 +397,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai $scope.config.Env = envArr; } - function loadFromContainerLabels(d) { + function loadFromContainerLabels() { for (var l in $scope.config.Labels) { if ({}.hasOwnProperty.call($scope.config.Labels, l)) { $scope.formValues.Labels.push({ name: l, value: $scope.config.Labels[l]}); @@ -405,7 +405,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } } - function loadFromContainerConsole(d) { + function loadFromContainerConsole() { if ($scope.config.OpenStdin && $scope.config.Tty) { $scope.formValues.Console = 'both'; } else if (!$scope.config.OpenStdin && $scope.config.Tty) { @@ -417,7 +417,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } } - function loadFromContainerDevices(d) { + function loadFromContainerDevices() { var path = []; for (var dev in $scope.config.HostConfig.Devices) { if ({}.hasOwnProperty.call($scope.config.HostConfig.Devices, dev)) { @@ -428,7 +428,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai $scope.config.HostConfig.Devices = path; } - function loadFromContainerImageConfig(d) { + function loadFromContainerImageConfig() { var imageInfo = ImageHelper.extractImageAndRegistryFromRepository($scope.config.Image); RegistryService.retrieveRegistryFromRepository($scope.config.Image) .then(function success(data) { diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index 0efe59f1e..e7b1a988b 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -56,7 +56,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co function executeContainerAction(id, action, successMessage, errorMessage) { action(id) - .then(function success(data) { + .then(function success() { Notifications.success(successMessage, id); update(); }) @@ -104,7 +104,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co $scope.renameContainer = function () { var container = $scope.container; ContainerService.renameContainer($transition$.params().id, container.newContainerName) - .then(function success(data) { + .then(function success() { container.Name = container.newContainerName; Notifications.success('Container successfully renamed', container.Name); }) @@ -120,7 +120,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co $scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) { $scope.state.leaveNetworkInProgress = true; NetworkService.disconnectContainer(networkId, container.Id, false) - .then(function success(data) { + .then(function success() { Notifications.success('Container left network', container.Id); $state.reload(); }) @@ -135,7 +135,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co $scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) { $scope.state.joinNetworkInProgress = true; NetworkService.connectContainer(networkId, container.Id) - .then(function success(data) { + .then(function success() { Notifications.success('Container joined network', container.Id); $state.reload(); }) @@ -151,7 +151,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co var image = $scope.config.Image; var registry = $scope.config.Registry; var imageConfig = ImageHelper.createImageConfigForCommit(image, registry.URL); - Commit.commitContainer({id: $transition$.params().id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) { + Commit.commitContainer({id: $transition$.params().id, tag: imageConfig.tag, repo: imageConfig.repo}, function () { update(); Notifications.success('Container commited', $transition$.params().id); }, function (e) { diff --git a/app/docker/views/containers/logs/containerLogsController.js b/app/docker/views/containers/logs/containerLogsController.js index 2141a1f3b..1d23adeb8 100644 --- a/app/docker/views/containers/logs/containerLogsController.js +++ b/app/docker/views/containers/logs/containerLogsController.js @@ -27,10 +27,6 @@ function ($scope, $transition$, $interval, ContainerService, Notifications, Http } } - function update(logs) { - $scope.logs = logs; - } - function setUpdateRepeater(skipHeaders) { var refreshRate = $scope.state.refreshRate; $scope.repeater = $interval(function() { diff --git a/app/docker/views/images/edit/imageController.js b/app/docker/views/images/edit/imageController.js index d89cd4dc3..29d91d8da 100644 --- a/app/docker/views/images/edit/imageController.js +++ b/app/docker/views/images/edit/imageController.js @@ -29,7 +29,7 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ var registry = $scope.formValues.Registry; ImageService.tagImage($transition$.params().id, image, registry.URL) - .then(function success(data) { + .then(function success() { Notifications.success('Image successfully tagged'); $state.go('docker.images.image', {id: $transition$.params().id}, {reload: true}); }) @@ -45,7 +45,7 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ var registry = data; return ImageService.pushImage(repository, registry); }) - .then(function success(data) { + .then(function success() { Notifications.success('Image successfully pushed', repository); }) .catch(function error(err) { @@ -63,7 +63,7 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ var registry = data; return ImageService.pullImage(repository, registry, false); }) - .then(function success(data) { + .then(function success() { Notifications.success('Image successfully pulled', repository); }) .catch(function error(err) { diff --git a/app/docker/views/images/imagesController.js b/app/docker/views/images/imagesController.js index cb364edad..f495f4185 100644 --- a/app/docker/views/images/imagesController.js +++ b/app/docker/views/images/imagesController.js @@ -21,7 +21,7 @@ function ($scope, $state, ImageService, Notifications, ModalService, HttpRequest $scope.state.actionInProgress = true; ImageService.pullImage(image, registry, false) - .then(function success(data) { + .then(function success() { Notifications.success('Image successfully pulled', image); $state.reload(); }) diff --git a/app/docker/views/networks/edit/networkController.js b/app/docker/views/networks/edit/networkController.js index 0ba905c2a..9ca04092e 100644 --- a/app/docker/views/networks/edit/networkController.js +++ b/app/docker/views/networks/edit/networkController.js @@ -1,10 +1,10 @@ angular.module('portainer.docker') -.controller('NetworkController', ['$scope', '$state', '$transition$', '$filter', 'NetworkService', 'Container', 'ContainerHelper', 'Notifications', 'HttpRequestHelper', -function ($scope, $state, $transition$, $filter, NetworkService, Container, ContainerHelper, Notifications, HttpRequestHelper) { +.controller('NetworkController', ['$scope', '$state', '$transition$', '$filter', 'NetworkService', 'Container', 'Notifications', 'HttpRequestHelper', +function ($scope, $state, $transition$, $filter, NetworkService, Container, Notifications, HttpRequestHelper) { - $scope.removeNetwork = function removeNetwork(networkId) { + $scope.removeNetwork = function removeNetwork() { NetworkService.remove($transition$.params().id, $transition$.params().id) - .then(function success(data) { + .then(function success() { Notifications.success('Network removed', $transition$.params().id); $state.go('docker.networks', {}); }) @@ -16,7 +16,7 @@ function ($scope, $state, $transition$, $filter, NetworkService, Container, Cont $scope.containerLeaveNetwork = function containerLeaveNetwork(network, container) { HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName); NetworkService.disconnectContainer($transition$.params().id, container.Id, false) - .then(function success(data) { + .then(function success() { Notifications.success('Container left network', $transition$.params().id); $state.go('docker.networks.network', { id: network.Id }, { reload: true }); }) diff --git a/app/docker/views/nodes/edit/nodeController.js b/app/docker/views/nodes/edit/nodeController.js index bdb59984e..30f210b2a 100644 --- a/app/docker/views/nodes/edit/nodeController.js +++ b/app/docker/views/nodes/edit/nodeController.js @@ -52,7 +52,7 @@ function ($scope, $state, $transition$, LabelHelper, Node, NodeHelper, Task, Not config.Role = node.Role; config.Labels = LabelHelper.fromKeyValueToLabelHash(node.Labels); - Node.update({ id: node.Id, version: node.Version }, config, function (data) { + Node.update({ id: node.Id, version: node.Version }, config, function () { Notifications.success('Node successfully updated', 'Node updated'); $state.go('docker.nodes.node', {id: node.Id}, {reload: true}); }, function (e) { diff --git a/app/docker/views/secrets/edit/secretController.js b/app/docker/views/secrets/edit/secretController.js index 3be0614f9..15aecfc1f 100644 --- a/app/docker/views/secrets/edit/secretController.js +++ b/app/docker/views/secrets/edit/secretController.js @@ -4,7 +4,7 @@ function ($scope, $transition$, $state, SecretService, Notifications) { $scope.removeSecret = function removeSecret(secretId) { SecretService.remove(secretId) - .then(function success(data) { + .then(function success() { Notifications.success('Secret successfully removed'); $state.go('docker.secrets', {}); }) diff --git a/app/docker/views/services/create/createServiceController.js b/app/docker/views/services/create/createServiceController.js index 8a2fd3e4e..e56dc1e4c 100644 --- a/app/docker/views/services/create/createServiceController.js +++ b/app/docker/views/services/create/createServiceController.js @@ -142,7 +142,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C $scope.formValues.ContainerLabels.splice(index, 1); }; - $scope.addLogDriverOpt = function(value) { + $scope.addLogDriverOpt = function() { $scope.formValues.LogDriverOpts.push({ name: '', value: ''}); }; @@ -492,7 +492,6 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C function initView() { var apiVersion = $scope.applicationState.endpoint.apiVersion; - var provider = $scope.applicationState.endpoint.mode.provider; $q.all({ volumes: VolumeService.volumes(), diff --git a/app/docker/views/services/edit/serviceController.js b/app/docker/views/services/edit/serviceController.js index f7dcb9c59..c13f06112 100644 --- a/app/docker/views/services/edit/serviceController.js +++ b/app/docker/views/services/edit/serviceController.js @@ -125,7 +125,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, updateServiceArray(service, 'ServiceMounts', service.ServiceMounts); } }; - $scope.updateMount = function updateMount(service, mount) { + $scope.updateMount = function updateMount(service) { updateServiceArray(service, 'ServiceMounts', service.ServiceMounts); }; $scope.addPlacementConstraint = function addPlacementConstraint(service) { @@ -138,7 +138,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); } }; - $scope.updatePlacementConstraint = function(service, constraint) { + $scope.updatePlacementConstraint = function(service) { updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); }; @@ -152,7 +152,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, updateServiceArray(service, 'ServicePreferences', service.ServicePreferences); } }; - $scope.updatePlacementPreference = function(service, constraint) { + $scope.updatePlacementPreference = function(service) { updateServiceArray(service, 'ServicePreferences', service.ServicePreferences); }; @@ -162,7 +162,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, } service.Ports.push({ PublishedPort: '', TargetPort: '', Protocol: 'tcp', PublishMode: 'ingress' }); }; - $scope.updatePublishedPort = function updatePublishedPort(service, portMapping) { + $scope.updatePublishedPort = function updatePublishedPort(service) { updateServiceArray(service, 'Ports', service.Ports); }; $scope.removePortPublishedBinding = function removePortPublishedBinding(service, index) { @@ -203,7 +203,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, updateServiceArray(service, 'Hosts', service.Hosts); } }; - $scope.updateHostsEntry = function(service, entry) { + $scope.updateHostsEntry = function(service) { updateServiceArray(service, 'Hosts', service.Hosts); }; @@ -340,7 +340,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, function removeService() { $scope.state.deletionInProgress = true; ServiceService.remove($scope.service) - .then(function success(data) { + .then(function success() { Notifications.success('Service successfully deleted'); $state.go('docker.services', {}); }) @@ -377,7 +377,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, config.TaskTemplate.ForceUpdate++; $scope.state.updateInProgress = true; ServiceService.update(service, config) - .then(function success(data) { + .then(function success() { Notifications.success('Service successfully updated', service.Name); $scope.cancelChanges({}); initView(); diff --git a/app/docker/views/volumes/create/createVolumeController.js b/app/docker/views/volumes/create/createVolumeController.js index a9ccec09b..ef5fc60ca 100644 --- a/app/docker/views/volumes/create/createVolumeController.js +++ b/app/docker/views/volumes/create/createVolumeController.js @@ -82,7 +82,7 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi var userId = userDetails.ID; return ResourceControlService.applyResourceControl('volume', volumeIdentifier, userId, accessControlData, []); }) - .then(function success(data) { + .then(function success() { Notifications.success('Volume successfully created'); $state.go('docker.volumes', {}, {reload: true}); }) diff --git a/app/docker/views/volumes/edit/volumeController.js b/app/docker/views/volumes/edit/volumeController.js index f314c1b3b..384490b7a 100644 --- a/app/docker/views/volumes/edit/volumeController.js +++ b/app/docker/views/volumes/edit/volumeController.js @@ -4,7 +4,7 @@ function ($scope, $state, $transition$, VolumeService, ContainerService, Notific $scope.removeVolume = function removeVolume() { VolumeService.remove($scope.volume) - .then(function success(data) { + .then(function success() { Notifications.success('Volume successfully removed', $transition$.params().id); $state.go('docker.volumes', {}); }) diff --git a/app/extensions/storidge/services/chartService.js b/app/extensions/storidge/services/chartService.js index de99a9c97..7dd468a38 100644 --- a/app/extensions/storidge/services/chartService.js +++ b/app/extensions/storidge/services/chartService.js @@ -178,7 +178,7 @@ angular.module('extension.storidge') return label + ': ' + processedValue + '/s'; } - function bytePerSecBasedAxisLabel(value, index, values) { + function bytePerSecBasedAxisLabel(value) { if (value > 5) { return filesize(value, {base: 10, round: 1}); } diff --git a/app/extensions/storidge/views/monitor/monitorController.js b/app/extensions/storidge/views/monitor/monitorController.js index 21c0f788d..d8dc5eda6 100644 --- a/app/extensions/storidge/views/monitor/monitorController.js +++ b/app/extensions/storidge/views/monitor/monitorController.js @@ -1,6 +1,6 @@ angular.module('extension.storidge') -.controller('StoridgeMonitorController', ['$q', '$scope', '$interval', '$document', 'Notifications', 'StoridgeClusterService', 'StoridgeChartService', 'ModalService', -function ($q, $scope, $interval, $document, Notifications, StoridgeClusterService, StoridgeChartService, ModalService) { +.controller('StoridgeMonitorController', ['$q', '$scope', '$interval', '$document', 'Notifications', 'StoridgeClusterService', 'StoridgeChartService', +function ($q, $scope, $interval, $document, Notifications, StoridgeClusterService, StoridgeChartService) { $scope.$on('$destroy', function() { stopRepeater(); diff --git a/app/extensions/storidge/views/profiles/create/createProfileController.js b/app/extensions/storidge/views/profiles/create/createProfileController.js index 6f8f05a7c..0246d1716 100644 --- a/app/extensions/storidge/views/profiles/create/createProfileController.js +++ b/app/extensions/storidge/views/profiles/create/createProfileController.js @@ -30,7 +30,7 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService) { $scope.state.actionInProgress = true; StoridgeProfileService.create(profile) - .then(function success(data) { + .then(function success() { Notifications.success('Profile successfully created'); $state.go('storidge.profiles'); }) diff --git a/app/extensions/storidge/views/profiles/edit/profileController.js b/app/extensions/storidge/views/profiles/edit/profileController.js index 87db55c95..ff6aecfc0 100644 --- a/app/extensions/storidge/views/profiles/edit/profileController.js +++ b/app/extensions/storidge/views/profiles/edit/profileController.js @@ -31,7 +31,7 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService, M $scope.state.updateInProgress = true; StoridgeProfileService.update(profile) - .then(function success(data) { + .then(function success() { Notifications.success('Profile successfully updated'); $state.go('storidge.profiles'); }) @@ -58,7 +58,7 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService, M $scope.state.deleteInProgress = true; StoridgeProfileService.delete(profile.Name) - .then(function success(data) { + .then(function success() { Notifications.success('Profile successfully deleted'); $state.go('storidge.profiles'); }) diff --git a/app/extensions/storidge/views/profiles/profilesController.js b/app/extensions/storidge/views/profiles/profilesController.js index 0d6ba5fc6..25276b1f5 100644 --- a/app/extensions/storidge/views/profiles/profilesController.js +++ b/app/extensions/storidge/views/profiles/profilesController.js @@ -38,7 +38,7 @@ function ($q, $scope, $state, Notifications, StoridgeProfileService) { $scope.state.actionInProgress = true; StoridgeProfileService.create(model) - .then(function success(data) { + .then(function success() { Notifications.success('Profile successfully created'); $state.reload(); }) diff --git a/app/portainer/components/accessControlPanel/porAccessControlPanelController.js b/app/portainer/components/accessControlPanel/porAccessControlPanelController.js index 142603304..eab4a8960 100644 --- a/app/portainer/components/accessControlPanel/porAccessControlPanelController.js +++ b/app/portainer/components/accessControlPanel/porAccessControlPanelController.js @@ -22,7 +22,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica ctrl.authorizedTeams = []; ctrl.availableTeams = []; - ctrl.confirmUpdateOwnership = function (force) { + ctrl.confirmUpdateOwnership = function () { if (!validateForm()) { return; } @@ -77,7 +77,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica ResourceControlService.applyResourceControlChange(ctrl.resourceType, resourceId, ctrl.resourceControl, ownershipParameters) - .then(function success(data) { + .then(function success() { Notifications.success('Access control successfully updated'); $state.reload(); }) diff --git a/app/portainer/components/accessManagement/porAccessManagementController.js b/app/portainer/components/accessManagement/porAccessManagementController.js index dcf870498..666ec3628 100644 --- a/app/portainer/components/accessManagement/porAccessManagementController.js +++ b/app/portainer/components/accessManagement/porAccessManagementController.js @@ -52,7 +52,7 @@ function (AccessService, Notifications) { } ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs }) - .then(function success(data) { + .then(function success() { removeFromAccesses(access, ctrl.accesses); ctrl.authorizedAccesses.push(access); Notifications.success('Accesses successfully updated'); @@ -74,7 +74,7 @@ function (AccessService, Notifications) { } ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs }) - .then(function success(data) { + .then(function success() { removeFromAccesses(access, ctrl.authorizedAccesses); ctrl.accesses.push(access); Notifications.success('Accesses successfully updated'); @@ -96,7 +96,7 @@ function (AccessService, Notifications) { ctrl.unauthorizeAllAccesses = function() { ctrl.updateAccess({ userAccesses: [], teamAccesses: [] }) - .then(function success(data) { + .then(function success() { moveAccesses(ctrl.authorizedAccesses, ctrl.accesses); Notifications.success('Accesses successfully updated'); }) @@ -111,7 +111,7 @@ function (AccessService, Notifications) { var authorizedTeamIDs = accessData.teamIDs; ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs }) - .then(function success(data) { + .then(function success() { moveAccesses(ctrl.accesses, ctrl.authorizedAccesses); Notifications.success('Accesses successfully updated'); }) diff --git a/app/portainer/components/endpoint-list/endpoint-list.js b/app/portainer/components/endpoint-list/endpoint-list.js index 319d6ef08..d11d7611d 100644 --- a/app/portainer/components/endpoint-list/endpoint-list.js +++ b/app/portainer/components/endpoint-list/endpoint-list.js @@ -1,8 +1,6 @@ angular.module('portainer.app').component('endpointList', { templateUrl: 'app/portainer/components/endpoint-list/endpointList.html', controller: function() { - var ctrl = this; - this.state = { textFilter: '' }; diff --git a/app/portainer/components/header-content.js b/app/portainer/components/header-content.js index 638e78d50..18273fffe 100644 --- a/app/portainer/components/header-content.js +++ b/app/portainer/components/header-content.js @@ -3,7 +3,7 @@ angular.module('portainer.app') var directive = { requires: '^rdHeader', transclude: true, - link: function (scope, iElement, iAttrs) { + link: function (scope) { scope.username = Authentication.getUserDetails().username; }, template: '', diff --git a/app/portainer/components/header-title.js b/app/portainer/components/header-title.js index c79cd1f2d..6cfac7182 100644 --- a/app/portainer/components/header-title.js +++ b/app/portainer/components/header-title.js @@ -1,11 +1,11 @@ angular.module('portainer.app') -.directive('rdHeaderTitle', ['Authentication', 'StateManager', function rdHeaderTitle(Authentication, StateManager) { +.directive('rdHeaderTitle', ['Authentication', function rdHeaderTitle(Authentication) { var directive = { requires: '^rdHeader', scope: { titleText: '@' }, - link: function (scope, iElement, iAttrs) { + link: function (scope) { scope.username = Authentication.getUserDetails().username; }, transclude: true, diff --git a/app/portainer/components/tag-selector/tagSelectorController.js b/app/portainer/components/tag-selector/tagSelectorController.js index b7c18c2ca..2bf96d105 100644 --- a/app/portainer/components/tag-selector/tagSelectorController.js +++ b/app/portainer/components/tag-selector/tagSelectorController.js @@ -1,8 +1,6 @@ angular.module('portainer.app') .controller('TagSelectorController', function () { - var ctrl = this; - this.$onChanges = function(changes) { if(angular.isDefined(changes.tags.currentValue)) { this.tags = _.difference(changes.tags.currentValue, this.model); @@ -14,7 +12,7 @@ angular.module('portainer.app') noResult: false }; - this.selectTag = function($item, $model, $label) { + this.selectTag = function($item) { this.state.selectedValue = ''; this.model.push($item); this.tags = _.remove(this.tags, function(item) { diff --git a/app/portainer/helpers/templateHelper.js b/app/portainer/helpers/templateHelper.js index 9f2dbde97..9c07dd661 100644 --- a/app/portainer/helpers/templateHelper.js +++ b/app/portainer/helpers/templateHelper.js @@ -1,5 +1,5 @@ angular.module('portainer.app') -.factory('TemplateHelper', ['$filter', function TemplateHelperFactory($filter) { +.factory('TemplateHelper', [function TemplateHelperFactory() { 'use strict'; var helper = {}; @@ -57,7 +57,7 @@ angular.module('portainer.app') return labels; }; - helper.EnvToStringArray = function(templateEnvironment, containerMapping) { + helper.EnvToStringArray = function(templateEnvironment) { var env = []; templateEnvironment.forEach(function(envvar) { if (envvar.value || envvar.set) { diff --git a/app/portainer/services/api/stackService.js b/app/portainer/services/api/stackService.js index e21da318c..dafbc4379 100644 --- a/app/portainer/services/api/stackService.js +++ b/app/portainer/services/api/stackService.js @@ -48,7 +48,7 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic return Stack.migrate({ id: stack.Id, endpointId: stack.EndpointId }, { EndpointID: targetEndpointId, SwarmID: swarm.Id }).$promise; }) - .then(function success(data) { + .then(function success() { deferred.resolve(); }) .catch(function error(err) { @@ -67,7 +67,7 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic EndpointProvider.setEndpointID(targetEndpointId); Stack.migrate({ id: stack.Id, endpointId: stack.EndpointId }, { EndpointID: targetEndpointId }).$promise - .then(function success(data) { + .then(function success() { deferred.resolve(); }) .catch(function error(err) { @@ -204,7 +204,7 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic var deferred = $q.defer(); Stack.remove({ id: stack.Id ? stack.Id : stack.Name, external: external, endpointId: endpointId }).$promise - .then(function success(data) { + .then(function success() { if (stack.ResourceControl && stack.ResourceControl.Id) { return ResourceControlService.deleteResourceControl(stack.ResourceControl.Id); } diff --git a/app/portainer/services/api/teamService.js b/app/portainer/services/api/teamService.js index d13ff8ffd..4a78931d3 100644 --- a/app/portainer/services/api/teamService.js +++ b/app/portainer/services/api/teamService.js @@ -58,7 +58,7 @@ angular.module('portainer.app') return Teams.remove({id: id}).$promise; }; - service.updateTeam = function(id, name, members, leaders) { + service.updateTeam = function(id, name) { var payload = { Name: name }; diff --git a/app/portainer/services/api/userService.js b/app/portainer/services/api/userService.js index 1c3d0c2ff..4ed381843 100644 --- a/app/portainer/services/api/userService.js +++ b/app/portainer/services/api/userService.js @@ -83,7 +83,7 @@ angular.module('portainer.app') return service.updateUser(id, newPassword, undefined); } }) - .then(function success(data) { + .then(function success() { deferred.resolve(); }) .catch(function error(err) { @@ -142,7 +142,7 @@ angular.module('portainer.app') var deferred = $q.defer(); Users.checkAdminUser({}).$promise - .then(function success(data) { + .then(function success() { deferred.resolve(true); }) .catch(function error(err) { diff --git a/app/portainer/services/chartService.js b/app/portainer/services/chartService.js index 7fb1e21a1..4b10c9a83 100644 --- a/app/portainer/services/chartService.js +++ b/app/portainer/services/chartService.js @@ -139,14 +139,14 @@ angular.module('portainer.app') return label + ': ' + processedValue; } - function byteBasedAxisLabel(value, index, values) { + function byteBasedAxisLabel(value) { if (value > 5) { return filesize(value, {base: 10, round: 1}); } return value.toFixed(1) + 'B'; } - function percentageBasedAxisLabel(value, index, values) { + function percentageBasedAxisLabel(value) { if (value > 1) { return Math.round(value) + '%'; } diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js index d74cb8f8b..fdbf9ff54 100644 --- a/app/portainer/services/fileUpload.js +++ b/app/portainer/services/fileUpload.js @@ -21,7 +21,7 @@ angular.module('portainer.app') dockerfile: path }, ignoreLoadingBar: true, - transformResponse: function(data, headers) { + transformResponse: function(data) { return jsonObjectsToArrayHandler(data); } }); diff --git a/app/portainer/services/modalService.js b/app/portainer/services/modalService.js index 1a16b8e55..f08a02962 100644 --- a/app/portainer/services/modalService.js +++ b/app/portainer/services/modalService.js @@ -59,7 +59,7 @@ angular.module('portainer.app') box.find('.bootbox-input-checkbox').prop('checked', optionToggled); }; - service.confirmAccessControlUpdate = function(callback, msg) { + service.confirmAccessControlUpdate = function(callback) { service.confirm({ title: 'Are you sure ?', message: 'Changing the ownership of this resource will potentially restrict its management to some users.', diff --git a/app/portainer/services/stateManager.js b/app/portainer/services/stateManager.js index 2b80109fe..787f67328 100644 --- a/app/portainer/services/stateManager.js +++ b/app/portainer/services/stateManager.js @@ -97,7 +97,7 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin var cacheValidity = now - applicationState.validity; if (cacheValidity > APPLICATION_CACHE_VALIDITY) { loadApplicationState() - .then(function success(data) { + .then(function success() { deferred.resolve(state); }) .catch(function error(err) { @@ -110,7 +110,7 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin } } else { loadApplicationState() - .then(function success(data) { + .then(function success() { deferred.resolve(state); }) .catch(function error(err) { diff --git a/app/portainer/views/endpoints/create/createEndpointController.js b/app/portainer/views/endpoints/create/createEndpointController.js index 6453902e9..13fc1ad16 100644 --- a/app/portainer/views/endpoints/create/createEndpointController.js +++ b/app/portainer/views/endpoints/create/createEndpointController.js @@ -60,8 +60,6 @@ function ($q, $scope, $state, $filter, EndpointService, GroupService, TagService }; function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags) { - var endpoint; - $scope.state.actionInProgress = true; EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags) .then(function success() { diff --git a/app/portainer/views/endpoints/edit/endpointController.js b/app/portainer/views/endpoints/edit/endpointController.js index 137bac012..d091bf635 100644 --- a/app/portainer/views/endpoints/edit/endpointController.js +++ b/app/portainer/views/endpoints/edit/endpointController.js @@ -45,7 +45,7 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi $scope.state.actionInProgress = true; EndpointService.updateEndpoint(endpoint.Id, payload) - .then(function success(data) { + .then(function success() { Notifications.success('Endpoint updated', $scope.endpoint.Name); EndpointProvider.setEndpointPublicURL(endpoint.PublicURL); $state.go('portainer.endpoints', {}, {reload: true}); diff --git a/app/portainer/views/groups/edit/groupController.js b/app/portainer/views/groups/edit/groupController.js index f242dfb09..1022b9b48 100644 --- a/app/portainer/views/groups/edit/groupController.js +++ b/app/portainer/views/groups/edit/groupController.js @@ -17,7 +17,7 @@ function ($q, $scope, $state, $transition$, GroupService, EndpointService, TagSe $scope.state.actionInProgress = true; GroupService.updateGroup(model, associatedEndpoints) - .then(function success(data) { + .then(function success() { Notifications.success('Group successfully updated'); $state.go('portainer.groups', {}, {reload: true}); }) diff --git a/app/portainer/views/home/homeController.js b/app/portainer/views/home/homeController.js index 318e9e2c9..684c1ff7e 100644 --- a/app/portainer/views/home/homeController.js +++ b/app/portainer/views/home/homeController.js @@ -22,7 +22,7 @@ function ($q, $scope, $state, Authentication, EndpointService, EndpointHelper, G function triggerSnapshot() { EndpointService.snapshot() - .then(function success(data) { + .then(function success() { Notifications.success('Success', 'Endpoints updated'); $state.reload(); }) diff --git a/app/portainer/views/init/endpoint/initEndpointController.js b/app/portainer/views/init/endpoint/initEndpointController.js index 9a7b64276..a621d014e 100644 --- a/app/portainer/views/init/endpoint/initEndpointController.js +++ b/app/portainer/views/init/endpoint/initEndpointController.js @@ -29,13 +29,9 @@ function ($scope, $state, EndpointService, StateManager, Notifications) { }; $scope.createLocalEndpoint = function() { - var name = 'local'; - var URL = ''; - var endpoint; - $scope.state.actionInProgress = true; EndpointService.createLocalEndpoint() - .then(function success(data) { + .then(function success() { $state.go('portainer.home'); }) .catch(function error(err) { @@ -78,11 +74,9 @@ function ($scope, $state, EndpointService, StateManager, Notifications) { }; function createAzureEndpoint(name, applicationId, tenantId, authenticationKey) { - var endpoint; - $scope.state.actionInProgress = true; EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, 1, []) - .then(function success(data) { + .then(function success() { $state.go('portainer.home'); }) .catch(function error(err) { @@ -94,10 +88,9 @@ function ($scope, $state, EndpointService, StateManager, Notifications) { } function createRemoteEndpoint(name, type, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { - var endpoint; $scope.state.actionInProgress = true; EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, [], TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) - .then(function success(data) { + .then(function success() { $state.go('portainer.home'); }) .catch(function error(err) { diff --git a/app/portainer/views/main/mainController.js b/app/portainer/views/main/mainController.js index 3af2a6d95..3eb3e9cde 100644 --- a/app/portainer/views/main/mainController.js +++ b/app/portainer/views/main/mainController.js @@ -12,7 +12,7 @@ function ($scope, $cookieStore, StateManager) { $scope.applicationState = StateManager.getState(); - $scope.$watch($scope.getWidth, function(newValue, oldValue) { + $scope.$watch($scope.getWidth, function(newValue) { if (newValue >= mobileView) { if (angular.isDefined($cookieStore.get('toggle'))) { $scope.toggle = ! $cookieStore.get('toggle') ? false : true; diff --git a/app/portainer/views/registries/create/createRegistryController.js b/app/portainer/views/registries/create/createRegistryController.js index 2dd0f021a..ef6212087 100644 --- a/app/portainer/views/registries/create/createRegistryController.js +++ b/app/portainer/views/registries/create/createRegistryController.js @@ -36,7 +36,7 @@ function ($scope, $state, RegistryService, Notifications) { $scope.state.actionInProgress = true; RegistryService.createRegistry(registryName, registryURL, authentication, username, password) - .then(function success(data) { + .then(function success() { Notifications.success('Registry successfully created'); $state.go('portainer.registries'); }) diff --git a/app/portainer/views/registries/edit/registryController.js b/app/portainer/views/registries/edit/registryController.js index 7cee10785..5dc3a2a5a 100644 --- a/app/portainer/views/registries/edit/registryController.js +++ b/app/portainer/views/registries/edit/registryController.js @@ -15,7 +15,7 @@ function ($scope, $state, $transition$, $filter, RegistryService, Notifications) registry.Password = $scope.formValues.Password; $scope.state.actionInProgress = true; RegistryService.updateRegistry(registry) - .then(function success(data) { + .then(function success() { Notifications.success('Registry successfully updated'); $state.go('portainer.registries'); }) diff --git a/app/portainer/views/registries/registriesController.js b/app/portainer/views/registries/registriesController.js index 9c92354e5..317091841 100644 --- a/app/portainer/views/registries/registriesController.js +++ b/app/portainer/views/registries/registriesController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'PaginationService', -function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, PaginationService) { +.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', +function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications) { $scope.state = { actionInProgress: false @@ -15,7 +15,7 @@ function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, N dockerhub.Password = $scope.formValues.dockerHubPassword; $scope.state.actionInProgress = true; DockerHubService.update(dockerhub) - .then(function success(data) { + .then(function success() { Notifications.success('DockerHub registry updated'); }) .catch(function error(err) { diff --git a/app/portainer/views/settings/authentication/settingsAuthenticationController.js b/app/portainer/views/settings/authentication/settingsAuthenticationController.js index 8c617678b..1eea8dda3 100644 --- a/app/portainer/views/settings/authentication/settingsAuthenticationController.js +++ b/app/portainer/views/settings/authentication/settingsAuthenticationController.js @@ -39,11 +39,11 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) { $scope.state.connectivityCheckInProgress = true; $q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null)) - .then(function success(data) { + .then(function success() { addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS); return SettingsService.checkLDAPConnectivity(settings); }) - .then(function success(data) { + .then(function success() { $scope.state.failedConnectivityCheck = false; $scope.state.successfulConnectivityCheck = true; Notifications.success('Connection to LDAP successful'); @@ -68,11 +68,11 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) { $scope.state.actionInProgress = true; $q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null)) - .then(function success(data) { + .then(function success() { addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS); return SettingsService.update(settings); }) - .then(function success(data) { + .then(function success() { Notifications.success('Authentication settings updated'); }) .catch(function error(err) { diff --git a/app/portainer/views/settings/settingsController.js b/app/portainer/views/settings/settingsController.js index 7e8819e43..e1b7156e2 100644 --- a/app/portainer/views/settings/settingsController.js +++ b/app/portainer/views/settings/settingsController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('SettingsController', ['$scope', '$state', 'Notifications', 'SettingsService', 'StateManager', 'DEFAULT_TEMPLATES_URL', -function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_TEMPLATES_URL) { +.controller('SettingsController', ['$scope', '$state', 'Notifications', 'SettingsService', 'StateManager', +function ($scope, $state, Notifications, SettingsService, StateManager) { $scope.state = { actionInProgress: false @@ -53,7 +53,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_ function updateSettings(settings) { SettingsService.update(settings) - .then(function success(data) { + .then(function success() { Notifications.success('Settings updated'); StateManager.updateLogo(settings.LogoURL); StateManager.updateSnapshotInterval(settings.SnapshotInterval); diff --git a/app/portainer/views/stacks/create/createStackController.js b/app/portainer/views/stacks/create/createStackController.js index 17c2f39f3..5f7d1a892 100644 --- a/app/portainer/views/stacks/create/createStackController.js +++ b/app/portainer/views/stacks/create/createStackController.js @@ -114,7 +114,7 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid } $scope.state.actionInProgress = true; action(name, method) - .then(function success(data) { + .then(function success() { return ResourceControlService.applyResourceControl('stack', name, userId, accessControlData, []); }) .then(function success() { diff --git a/app/portainer/views/stacks/edit/stackController.js b/app/portainer/views/stacks/edit/stackController.js index 6b9d54ee0..50a8651b2 100644 --- a/app/portainer/views/stacks/edit/stackController.js +++ b/app/portainer/views/stacks/edit/stackController.js @@ -65,7 +65,7 @@ function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceSe $scope.state.migrationInProgress = true; migrateRequest(stack, targetEndpointId) - .then(function success(data) { + .then(function success() { Notifications.success('Stack successfully migrated', stack.Name); $state.go('portainer.stacks', {}, {reload: true}); }) @@ -108,7 +108,7 @@ function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceSe $scope.state.actionInProgress = true; StackService.updateStack(stack, stackFile, env, prune) - .then(function success(data) { + .then(function success() { Notifications.success('Stack successfully deployed'); $state.reload(); }) diff --git a/app/portainer/views/tags/tagsController.js b/app/portainer/views/tags/tagsController.js index 37f5eb5d0..1b1d59699 100644 --- a/app/portainer/views/tags/tagsController.js +++ b/app/portainer/views/tags/tagsController.js @@ -45,7 +45,7 @@ function ($scope, $state, TagService, Notifications) { $scope.createTag = function() { var tagName = $scope.formValues.Name; TagService.createTag(tagName) - .then(function success(data) { + .then(function success() { Notifications.success('Tag successfully created', tagName); $state.reload(); }) diff --git a/app/portainer/views/teams/edit/teamController.js b/app/portainer/views/teams/edit/teamController.js index 15079b834..f07054607 100644 --- a/app/portainer/views/teams/edit/teamController.js +++ b/app/portainer/views/teams/edit/teamController.js @@ -45,7 +45,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember $scope.promoteToLeader = function(user) { TeamMembershipService.updateMembership(user.MembershipId, user.Id, $scope.team.Id, 1) - .then(function success(data) { + .then(function success() { $scope.leaderCount++; user.TeamRole = 'Leader'; Notifications.success('User is now team leader', user.Username); @@ -57,7 +57,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember $scope.demoteToMember = function(user) { TeamMembershipService.updateMembership(user.MembershipId, user.Id, $scope.team.Id, 2) - .then(function success(data) { + .then(function success() { user.TeamRole = 'Member'; $scope.leaderCount--; Notifications.success('User is now team member', user.Username); @@ -109,7 +109,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember teamMembershipQueries.push(TeamMembershipService.deleteMembership(user.MembershipId)); }); $q.all(teamMembershipQueries) - .then(function success(data) { + .then(function success() { $scope.users = $scope.users.concat($scope.teamMembers); $scope.teamMembers = []; Notifications.success('All users successfully removed'); @@ -133,7 +133,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember function deleteTeam() { TeamService.deleteTeam($scope.team.Id) - .then(function success(data) { + .then(function success() { Notifications.success('Team successfully deleted', $scope.team.Name); $state.go('portainer.teams'); }) diff --git a/app/portainer/views/teams/teamsController.js b/app/portainer/views/teams/teamsController.js index ef9196743..9b63b234e 100644 --- a/app/portainer/views/teams/teamsController.js +++ b/app/portainer/views/teams/teamsController.js @@ -30,7 +30,7 @@ function ($q, $scope, $state, TeamService, UserService, ModalService, Notificati $scope.state.actionInProgress = true; TeamService.createTeam(teamName, leaderIds) - .then(function success(data) { + .then(function success() { Notifications.success('Team successfully created', teamName); $state.reload(); }) diff --git a/app/portainer/views/templates/templatesController.js b/app/portainer/views/templates/templatesController.js index 286d4fe43..948e54b3c 100644 --- a/app/portainer/views/templates/templatesController.js +++ b/app/portainer/views/templates/templatesController.js @@ -65,7 +65,6 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima var generatedVolumeIds = []; VolumeService.createXAutoGeneratedLocalVolumes(generatedVolumeCount) .then(function success(data) { - var volumeResourceControlQueries = []; angular.forEach(data, function (volume) { var volumeId = volume.Id; generatedVolumeIds.push(volumeId); @@ -73,7 +72,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data); return ImageService.pullImage(template.Image, { URL: template.Registry }, true); }) - .then(function success(data) { + .then(function success() { return ContainerService.createAndStartContainer(templateConfiguration); }) .then(function success(data) { @@ -109,7 +108,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima var endpointId = EndpointProvider.endpointID(); StackService.createComposeStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId) - .then(function success(data) { + .then(function success() { return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []); }) .then(function success() { @@ -141,7 +140,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima var endpointId = EndpointProvider.endpointID(); StackService.createSwarmStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId) - .then(function success(data) { + .then(function success() { return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []); }) .then(function success() { @@ -167,7 +166,6 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima } var template = $scope.state.selectedTemplate; - var templatesKey = $scope.templatesKey; $scope.state.actionInProgress = true; if (template.Type === 2) { diff --git a/app/portainer/views/users/edit/userController.js b/app/portainer/views/users/edit/userController.js index 01c41109e..e935aa466 100644 --- a/app/portainer/views/users/edit/userController.js +++ b/app/portainer/views/users/edit/userController.js @@ -25,7 +25,7 @@ function ($q, $scope, $state, $transition$, UserService, ModalService, Notificat $scope.updatePermissions = function() { var role = $scope.formValues.Administrator ? 1 : 2; UserService.updateUser($scope.user.Id, undefined, role) - .then(function success(data) { + .then(function success() { var newRole = role === 1 ? 'administrator' : 'user'; Notifications.success('Permissions successfully updated', $scope.user.Username + ' is now ' + newRole); $state.reload(); @@ -37,7 +37,7 @@ function ($q, $scope, $state, $transition$, UserService, ModalService, Notificat $scope.updatePassword = function() { UserService.updateUser($scope.user.Id, $scope.formValues.newPassword, undefined) - .then(function success(data) { + .then(function success() { Notifications.success('Password successfully updated'); $state.reload(); }) @@ -48,7 +48,7 @@ function ($q, $scope, $state, $transition$, UserService, ModalService, Notificat function deleteUser() { UserService.deleteUser($scope.user.Id) - .then(function success(data) { + .then(function success() { Notifications.success('User successfully deleted', $scope.user.Username); $state.go('portainer.users'); }) diff --git a/app/portainer/views/users/usersController.js b/app/portainer/views/users/usersController.js index a01ac14c8..3fb6de991 100644 --- a/app/portainer/views/users/usersController.js +++ b/app/portainer/views/users/usersController.js @@ -38,7 +38,7 @@ function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershi teamIds.push(team.Id); }); UserService.createUser(username, password, role, teamIds) - .then(function success(data) { + .then(function success() { Notifications.success('User successfully created', username); $state.reload(); }) diff --git a/gruntfile.js b/gruntfile.js index a68ca4722..79e05c95d 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -106,7 +106,6 @@ module.exports = function (grunt) { var autoprefixer = require('autoprefixer'); var cssnano = require('cssnano'); -var fs = require('fs'); gruntfile_cfg.config = { dev: { options: { variables: { 'environment': 'development' }}}, From 8cd3964d759be8c6906d2dedad4ae4342c830fa0 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 23 Aug 2018 17:10:18 +0200 Subject: [PATCH 37/57] feat(security): update secured headers and sanitize team name (#2167) --- api/http/handler/file/handler.go | 4 ++++ api/http/security/bouncer.go | 3 ++- app/portainer/views/teams/teamsController.js | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api/http/handler/file/handler.go b/api/http/handler/file/handler.go index 15ec1417f..464062be1 100644 --- a/api/http/handler/file/handler.go +++ b/api/http/handler/file/handler.go @@ -33,5 +33,9 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else { w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") } + + w.Header().Add("X-Frame-Options", "DENY") + w.Header().Add("X-XSS-Protection", "1; mode=block") + w.Header().Add("X-Content-Type-Options", "nosniff") handler.Handler.ServeHTTP(w, r) } diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go index 798d37f7b..5aad463f1 100644 --- a/api/http/security/bouncer.go +++ b/api/http/security/bouncer.go @@ -114,8 +114,9 @@ func (bouncer *RequestBouncer) EndpointAccess(r *http.Request, endpoint *portain // mwSecureHeaders provides secure headers middleware for handlers. func mwSecureHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("X-Content-Type-Options", "nosniff") w.Header().Add("X-Frame-Options", "DENY") + w.Header().Add("X-XSS-Protection", "1; mode=block") + w.Header().Add("X-Content-Type-Options", "nosniff") next.ServeHTTP(w, r) }) } diff --git a/app/portainer/views/teams/teamsController.js b/app/portainer/views/teams/teamsController.js index 9b63b234e..6c01427ac 100644 --- a/app/portainer/views/teams/teamsController.js +++ b/app/portainer/views/teams/teamsController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'ModalService', 'Notifications', 'Authentication', -function ($q, $scope, $state, TeamService, UserService, ModalService, Notifications, Authentication) { +.controller('TeamsController', ['$q', '$scope', '$state', '$sanitize', 'TeamService', 'UserService', 'ModalService', 'Notifications', 'Authentication', +function ($q, $scope, $state, $sanitize, TeamService, UserService, ModalService, Notifications, Authentication) { $scope.state = { actionInProgress: false }; @@ -22,7 +22,7 @@ function ($q, $scope, $state, TeamService, UserService, ModalService, Notificati }; $scope.addTeam = function() { - var teamName = $scope.formValues.Name; + var teamName = $sanitize($scope.formValues.Name); var leaderIds = []; angular.forEach($scope.formValues.Leaders, function(user) { leaderIds.push(user.Id); From 4b05699e66d51d267743dcde8385964068fae016 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 24 Aug 2018 10:40:05 +0200 Subject: [PATCH 38/57] chore(codeclimate): update .codeclimate.yml (#2212) * chore(codeclimate): update .codeclimate.yml * chore(codeclimate): update .codeclimate.yml * chore(codeclimate): update .codeclimate.yml --- .codeclimate.yml | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 84d9c8eda..845dacc08 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,5 +1,42 @@ ---- -engines: +version: "2" +checks: + argument-count: + enabled: true + config: + threshold: 4 + complex-logic: + enabled: true + config: + threshold: 4 + file-lines: + enabled: true + config: + threshold: 300 + method-complexity: + enabled: false + method-count: + enabled: true + config: + threshold: 20 + method-lines: + enabled: true + config: + threshold: 50 + nested-control-flow: + enabled: true + config: + threshold: 4 + return-statements: + enabled: false + similar-code: + enabled: true + config: + threshold: #language-specific defaults. overrides affect all languages. + identical-code: + enabled: true + config: + threshold: #language-specific defaults. overrides affect all languages. +plugins: gofmt: enabled: true golint: @@ -20,10 +57,5 @@ engines: config: .eslintrc.yml fixme: enabled: true -ratings: - paths: - - "**.css" - - "**.js" - - "**.go" -exclude_paths: +exclude_patterns: - test/ From c941fac2cc7c41d09d72b58712e8226831a32bb7 Mon Sep 17 00:00:00 2001 From: William Easton Date: Fri, 24 Aug 2018 06:08:46 -0500 Subject: [PATCH 39/57] fix(api): set templatesURL in settings when using the --templates flag Re-add the CLI for external template management --- api/cmd/portainer/main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index e706907d7..b3918d1eb 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -178,6 +178,10 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL SnapshotInterval: *flags.SnapshotInterval, } + if *flags.Templates != "" { + settings.TemplatesURL = *flags.Templates + } + if *flags.Labels != nil { settings.BlackListedLabels = *flags.Labels } else { From 538a2b5ee27ddf48775a6aae91b8cc542167dbfc Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 24 Aug 2018 14:30:41 +0200 Subject: [PATCH 40/57] fix(service-details): disable auto-focus on task datatable (#2214) * fix(service-details): disable auto-focus on task datatable * refactor(api): gofmt main.go --- api/cmd/portainer/main.go | 2 +- .../components/datatables/tasks-datatable/tasksDatatable.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index b3918d1eb..c7997bf14 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -181,7 +181,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL if *flags.Templates != "" { settings.TemplatesURL = *flags.Templates } - + if *flags.Labels != nil { settings.BlackListedLabels = *flags.Labels } else { diff --git a/app/docker/components/datatables/tasks-datatable/tasksDatatable.html b/app/docker/components/datatables/tasks-datatable/tasksDatatable.html index bbb0e7d18..db3d50ebb 100644 --- a/app/docker/components/datatables/tasks-datatable/tasksDatatable.html +++ b/app/docker/components/datatables/tasks-datatable/tasksDatatable.html @@ -8,7 +8,7 @@
    From bfccf55729d497897c505c65433661507e9e7265 Mon Sep 17 00:00:00 2001 From: aksappy Date: Mon, 27 Aug 2018 15:43:58 -0400 Subject: [PATCH 41/57] fix(images): Fix upload modal to allow both tar and tar.gz images (#2218) --- app/docker/views/images/import/importimage.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/docker/views/images/import/importimage.html b/app/docker/views/images/import/importimage.html index 7615080f6..6a6b20f31 100644 --- a/app/docker/views/images/import/importimage.html +++ b/app/docker/views/images/import/importimage.html @@ -21,7 +21,7 @@
    - + {{ formValues.UploadFile.name }} From 812f3e3e8532f1c23df251d8eaa0c6c1a1089ec6 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 28 Aug 2018 10:13:01 +0200 Subject: [PATCH 42/57] feat(auth): remove sanitization calls and ask for password update if needed (#2222) * wip * feat(auth): remove sanitization calls and ask for password update if needed --- app/portainer/__module.js | 90 +++++++++++-------- .../views/account/accountController.js | 8 +- app/portainer/views/auth/authController.js | 54 ++++++----- .../views/init/admin/initAdminController.js | 8 +- .../views/update-password/updatePassword.html | 64 +++++++++++++ .../updatePasswordController.js | 40 +++++++++ app/portainer/views/users/usersController.js | 8 +- index.html | 4 +- 8 files changed, 203 insertions(+), 73 deletions(-) create mode 100644 app/portainer/views/update-password/updatePassword.html create mode 100644 app/portainer/views/update-password/updatePasswordController.js diff --git a/app/portainer/__module.js b/app/portainer/__module.js index a2ad9053d..6ddfb5f2a 100644 --- a/app/portainer/__module.js +++ b/app/portainer/__module.js @@ -65,40 +65,6 @@ angular.module('portainer.app', []) } }; - var init = { - name: 'portainer.init', - abstract: true, - url: '/init', - data: { - requiresLogin: false - }, - views: { - 'sidebar@': {} - } - }; - - var initEndpoint = { - name: 'portainer.init.endpoint', - url: '/endpoint', - views: { - 'content@': { - templateUrl: 'app/portainer/views/init/endpoint/initEndpoint.html', - controller: 'InitEndpointController' - } - } - }; - - var initAdmin = { - name: 'portainer.init.admin', - url: '/admin', - views: { - 'content@': { - templateUrl: 'app/portainer/views/init/admin/initAdmin.html', - controller: 'InitAdminController' - } - } - }; - var endpoints = { name: 'portainer.endpoints', url: '/endpoints', @@ -198,6 +164,40 @@ angular.module('portainer.app', []) } }; + var init = { + name: 'portainer.init', + abstract: true, + url: '/init', + data: { + requiresLogin: false + }, + views: { + 'sidebar@': {} + } + }; + + var initEndpoint = { + name: 'portainer.init.endpoint', + url: '/endpoint', + views: { + 'content@': { + templateUrl: 'app/portainer/views/init/endpoint/initEndpoint.html', + controller: 'InitEndpointController' + } + } + }; + + var initAdmin = { + name: 'portainer.init.admin', + url: '/admin', + views: { + 'content@': { + templateUrl: 'app/portainer/views/init/admin/initAdmin.html', + controller: 'InitAdminController' + } + } + }; + var registries = { name: 'portainer.registries', url: '/registries', @@ -318,6 +318,21 @@ angular.module('portainer.app', []) } }; + var updatePassword = { + name: 'portainer.updatePassword', + url: '/update-password', + views: { + 'content@': { + templateUrl: 'app/portainer/views/update-password/updatePassword.html', + controller: 'UpdatePasswordController' + }, + 'sidebar@': {} + }, + params: { + password: '' + } + }; + var users = { name: 'portainer.users', url: '/users', @@ -404,9 +419,6 @@ angular.module('portainer.app', []) $stateRegistryProvider.register(about); $stateRegistryProvider.register(account); $stateRegistryProvider.register(authentication); - $stateRegistryProvider.register(init); - $stateRegistryProvider.register(initEndpoint); - $stateRegistryProvider.register(initAdmin); $stateRegistryProvider.register(endpoints); $stateRegistryProvider.register(endpoint); $stateRegistryProvider.register(endpointAccess); @@ -416,6 +428,9 @@ angular.module('portainer.app', []) $stateRegistryProvider.register(groupAccess); $stateRegistryProvider.register(groupCreation); $stateRegistryProvider.register(home); + $stateRegistryProvider.register(init); + $stateRegistryProvider.register(initEndpoint); + $stateRegistryProvider.register(initAdmin); $stateRegistryProvider.register(registries); $stateRegistryProvider.register(registry); $stateRegistryProvider.register(registryAccess); @@ -427,6 +442,7 @@ angular.module('portainer.app', []) $stateRegistryProvider.register(stackCreation); $stateRegistryProvider.register(support); $stateRegistryProvider.register(tags); + $stateRegistryProvider.register(updatePassword); $stateRegistryProvider.register(users); $stateRegistryProvider.register(user); $stateRegistryProvider.register(teams); diff --git a/app/portainer/views/account/accountController.js b/app/portainer/views/account/accountController.js index b842b7317..304fd2586 100644 --- a/app/portainer/views/account/accountController.js +++ b/app/portainer/views/account/accountController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('AccountController', ['$scope', '$state', '$sanitize', 'Authentication', 'UserService', 'Notifications', 'SettingsService', -function ($scope, $state, $sanitize, Authentication, UserService, Notifications, SettingsService) { +.controller('AccountController', ['$scope', '$state', 'Authentication', 'UserService', 'Notifications', 'SettingsService', +function ($scope, $state, Authentication, UserService, Notifications, SettingsService) { $scope.formValues = { currentPassword: '', newPassword: '', @@ -9,10 +9,8 @@ function ($scope, $state, $sanitize, Authentication, UserService, Notifications, $scope.updatePassword = function() { $scope.invalidPassword = false; - var currentPassword = $sanitize($scope.formValues.currentPassword); - var newPassword = $sanitize($scope.formValues.newPassword); - UserService.updateUserPassword($scope.userID, currentPassword, newPassword) + UserService.updateUserPassword($scope.userID, $scope.formValues.currentPassword, $scope.formValues.newPassword) .then(function success() { Notifications.success('Success', 'Password successfully updated'); $state.reload(); diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js index 90d5e85a2..3b18d600c 100644 --- a/app/portainer/views/auth/authController.js +++ b/app/portainer/views/auth/authController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('AuthenticationController', ['$scope', '$state', '$transition$', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'StateManager', 'Notifications', 'SettingsService', -function ($scope, $state, $transition$, $sanitize, Authentication, UserService, EndpointService, StateManager, Notifications, SettingsService) { +.controller('AuthenticationController', ['$q', '$scope', '$state', '$transition$', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'StateManager', 'Notifications', 'SettingsService', +function ($q, $scope, $state, $transition$, $sanitize, Authentication, UserService, EndpointService, StateManager, Notifications, SettingsService) { $scope.logo = StateManager.getState().application.logo; @@ -13,6 +13,31 @@ function ($scope, $state, $transition$, $sanitize, Authentication, UserService, AuthenticationError: '' }; + $scope.authenticateUser = function() { + var username = $scope.formValues.Username; + var password = $scope.formValues.Password; + + Authentication.login(username, password) + .then(function success() { + checkForEndpoints(); + }) + .catch(function error() { + SettingsService.publicSettings() + .then(function success(settings) { + if (settings.AuthenticationMethod === 1) { + return Authentication.login($sanitize(username), $sanitize(password)); + } + return $q.reject(); + }) + .then(function success() { + $state.go('portainer.updatePassword', { password: $sanitize(password) }); + }) + .catch(function error() { + $scope.state.AuthenticationError = 'Invalid credentials'; + }); + }); + }; + function unauthenticatedFlow() { EndpointService.endpoints() .then(function success(endpoints) { @@ -39,35 +64,22 @@ function ($scope, $state, $transition$, $sanitize, Authentication, UserService, }); } - $scope.authenticateUser = function() { - var username = $scope.formValues.Username; - var password = $scope.formValues.Password; - - SettingsService.publicSettings() - .then(function success(data) { - var settings = data; - if (settings.AuthenticationMethod === 1) { - username = $sanitize(username); - password = $sanitize(password); - } - return Authentication.login(username, password); - }) - .then(function success() { - return EndpointService.endpoints(); - }) + function checkForEndpoints() { + EndpointService.endpoints() .then(function success(data) { var endpoints = data; var userDetails = Authentication.getUserDetails(); + if (endpoints.length === 0 && userDetails.role === 1) { $state.go('portainer.init.endpoint'); } else { $state.go('portainer.home'); } }) - .catch(function error() { - $scope.state.AuthenticationError = 'Invalid credentials'; + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve endpoints'); }); - }; + } function initView() { if ($transition$.params().logout || $transition$.params().error) { diff --git a/app/portainer/views/init/admin/initAdminController.js b/app/portainer/views/init/admin/initAdminController.js index e43f8a0ec..569bb1491 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', -function ($scope, $state, $sanitize, Notifications, Authentication, StateManager, UserService, EndpointService) { +.controller('InitAdminController', ['$scope', '$state', 'Notifications', 'Authentication', 'StateManager', 'UserService', 'EndpointService', +function ($scope, $state, Notifications, Authentication, StateManager, UserService, EndpointService) { $scope.logo = StateManager.getState().application.logo; @@ -15,8 +15,8 @@ function ($scope, $state, $sanitize, Notifications, Authentication, StateManager }; $scope.createAdminUser = function() { - var username = $sanitize($scope.formValues.Username); - var password = $sanitize($scope.formValues.Password); + var username = $scope.formValues.Username; + var password = $scope.formValues.Password; $scope.state.actionInProgress = true; UserService.initAdministrator(username, password) diff --git a/app/portainer/views/update-password/updatePassword.html b/app/portainer/views/update-password/updatePassword.html new file mode 100644 index 000000000..62c726a61 --- /dev/null +++ b/app/portainer/views/update-password/updatePassword.html @@ -0,0 +1,64 @@ +
    + +
    +
    + +
    +
    + + +
    +
    + + Your password must be updated. + +
    +
    + + +
    + +
    + +
    +
    + + +
    + +
    +
    + + +
    +
    +
    + + +
    +
    + + + The password must be at least 8 characters long + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + +
    diff --git a/app/portainer/views/update-password/updatePasswordController.js b/app/portainer/views/update-password/updatePasswordController.js new file mode 100644 index 000000000..6809d1a34 --- /dev/null +++ b/app/portainer/views/update-password/updatePasswordController.js @@ -0,0 +1,40 @@ +angular.module('portainer.app') +.controller('UpdatePasswordController', ['$scope', '$state', '$transition$', 'UserService', 'Authentication', 'Notifications', +function UpdatePasswordController($scope, $state, $transition$, UserService, Authentication, Notifications) { + + $scope.formValues = { + Password: '', + ConfirmPassword: '' + }; + + $scope.state = { + actionInProgress: false, + currentPassword: '' + }; + + $scope.updatePassword = function() { + var userId = Authentication.getUserDetails().ID; + + $scope.state.actionInProgress = true; + UserService.updateUserPassword(userId, $scope.state.currentPassword, $scope.formValues.Password) + .then(function success() { + $state.go('portainer.home'); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to update password'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + }; + + function initView() { + if (!Authentication.isAuthenticated()) { + $state.go('portainer.auth'); + } + + $scope.state.currentPassword = $transition$.params().password; + } + + initView(); +}]); diff --git a/app/portainer/views/users/usersController.js b/app/portainer/views/users/usersController.js index 3fb6de991..ea9bb425d 100644 --- a/app/portainer/views/users/usersController.js +++ b/app/portainer/views/users/usersController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('UsersController', ['$q', '$scope', '$state', '$sanitize', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Authentication', 'SettingsService', -function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Authentication, SettingsService) { +.controller('UsersController', ['$q', '$scope', '$state', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Authentication', 'SettingsService', +function ($q, $scope, $state, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Authentication, SettingsService) { $scope.state = { userCreationError: '', validUsername: false, @@ -30,8 +30,8 @@ function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershi $scope.addUser = function() { $scope.state.actionInProgress = true; $scope.state.userCreationError = ''; - var username = $sanitize($scope.formValues.Username); - var password = $sanitize($scope.formValues.Password); + var username = $scope.formValues.Username; + var password = $scope.formValues.Password; var role = $scope.formValues.Administrator ? 1 : 2; var teamIds = []; angular.forEach($scope.formValues.Teams, function(team) { diff --git a/index.html b/index.html index cb5540b9a..0ec14b658 100644 --- a/index.html +++ b/index.html @@ -36,8 +36,8 @@
    From a5d6ab0410bdc71a262de68f945260c2cda1d52e Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 28 Aug 2018 10:50:15 +0200 Subject: [PATCH 43/57] refactor(app): remove unused params in templates state declaration --- app/portainer/__module.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/portainer/__module.js b/app/portainer/__module.js index 6ddfb5f2a..bb94d395d 100644 --- a/app/portainer/__module.js +++ b/app/portainer/__module.js @@ -385,10 +385,6 @@ angular.module('portainer.app', []) templateUrl: 'app/portainer/views/templates/templates.html', controller: 'TemplatesController' } - }, - params: { - key: 'containers', - hide_descriptions: false } }; From 887c16c5807e6a99be2a6823da1dafd094d341d1 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 30 Aug 2018 12:21:53 +0200 Subject: [PATCH 44/57] feat(api): display details in error response (#2228) --- api/http/error/error.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/http/error/error.go b/api/http/error/error.go index b9153a8a6..244397fdf 100644 --- a/api/http/error/error.go +++ b/api/http/error/error.go @@ -16,7 +16,8 @@ type ( Err error } errorResponse struct { - Err string `json:"err,omitempty"` + Err string `json:"err,omitempty"` + Details string `json:"details,omitempty"` } ) @@ -31,7 +32,7 @@ func writeErrorResponse(rw http.ResponseWriter, err *HandlerError) { log.Printf("http error: %s (err=%s) (code=%d)\n", err.Message, err.Err, err.StatusCode) rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(err.StatusCode) - json.NewEncoder(rw).Encode(&errorResponse{Err: err.Message}) + json.NewEncoder(rw).Encode(&errorResponse{Err: err.Message, Details: err.Err.Error()}) } // WriteError is a convenience function that creates a new HandlerError before calling writeErrorResponse. From 84fc3119a0b369ed48b7233bbd27e6a88d888a20 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 30 Aug 2018 13:11:15 +0200 Subject: [PATCH 45/57] docs(swagger): update StackCreate operation parameter --- api/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/swagger.yaml b/api/swagger.yaml index f589efd7f..17785d43e 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -1336,7 +1336,7 @@ paths: in: "formData" type: "string" description: "Swarm cluster identifier. Required when method equals file and type equals 1." - - name: "StackFileContent" + - name: "file" in: "formData" type: "file" description: "Stack file. Required when method equals file." From e17c873e73ed0e66f4dc2cd6bcd58be49e5ac994 Mon Sep 17 00:00:00 2001 From: classmember Date: Sat, 1 Sep 2018 04:09:24 -0400 Subject: [PATCH 46/57] refactor(build-system): update build_in_container.sh (#2230) wrapped `$(pwd)/api:/src` in `"` quotes to prevent word splitting on the `-tv` option --- build/build_in_container.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build_in_container.sh b/build/build_in_container.sh index 91186ca50..a827bddfd 100755 --- a/build/build_in_container.sh +++ b/build/build_in_container.sh @@ -4,7 +4,7 @@ binary="portainer-$1-$2" mkdir -p dist -docker run --rm -tv $(pwd)/api:/src -e BUILD_GOOS="$1" -e BUILD_GOARCH="$2" portainer/golang-builder:cross-platform /src/cmd/portainer +docker run --rm -tv "$(pwd)/api:/src" -e BUILD_GOOS="$1" -e BUILD_GOARCH="$2" portainer/golang-builder:cross-platform /src/cmd/portainer mv "api/cmd/portainer/$binary" dist/ #sha256sum "dist/$binary" > portainer-checksum.txt From d5facde9d4f735d3268a1f1f955352056426a392 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sun, 2 Sep 2018 10:35:05 +0200 Subject: [PATCH 47/57] fix(api): fix invalid error message in endpoint creation handler (#2233) --- api/http/handler/endpoints/endpoint_create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go index 3ce3bd6d4..277f6bd0f 100644 --- a/api/http/handler/endpoints/endpoint_create.go +++ b/api/http/handler/endpoints/endpoint_create.go @@ -35,7 +35,7 @@ type endpointCreatePayload struct { func (payload *endpointCreatePayload) Validate(r *http.Request) error { name, err := request.RetrieveMultiPartFormValue(r, "Name", false) if err != nil { - return portainer.Error("Invalid stack name") + return portainer.Error("Invalid endpoint name") } payload.Name = name From 0efeeaf1858b3c0ff8fe03498479484ce024962c Mon Sep 17 00:00:00 2001 From: Kendrick Date: Mon, 3 Sep 2018 03:08:03 -0700 Subject: [PATCH 48/57] feat(webhooks): add support for service update webhooks (#2161) * Initial pass at adding webhook controller and routes * Moving some objects around * Cleaning up comments * Fixing syntax, switching to using the docker sdk over building an http client * Adding delete and list functionality * Updating the handler to use the correct permissions. Updating some comments * Fixing some comments * Code cleanup per pull request comments * Cleanup per PR feedback. Syntax error fix * Initial creation of webhook app code * Moving ClientFactory creation out of handler code and instead using the one created by the main process. Removing webhookInspect method and updating the list function to use json filters * Delete now works on the webhook ID vs service ID * WIP - Service creates a webhook. Display will show an existing webhook URL. * Adding the webhook field to the service view. There is now the ability to add or remove a webhook from a service * Moving all api calls to be webhooks vs webhook * Code cleanup. Moving all api calls to be webhooks vs webhook * More conversion of webhook to webhooks? * Moving UI elements around. Starting function for copying to clipboard * Finalizing function for copying to clipboard. Adding button that calls function and copies webhook to clipboard. * Fixing UI issues. Hiding field entirely when there is no webhook * Moving URL crafting to a helper method. The edit pane for service now creates/deletes webhooks immidiately. * style(service-details): update webhook line * feat(api): strip sha when updating an image via the update webhook * Fixing up some copy. Only displying the port if it is not http or https * Fixing tooltip copy. Setting the forceupdate to be true to require an update to occur * Fixing code climate errors * Adding WebhookType field and setting to ServiceWebhook for new webhooks. Renaming ServiceID to resourceID so future work can add new types of webhooks in other resource areas. * Adding the webhook type to the payload to support more types of webhooks in the future. Setting the type correctly when creating one for a service * feat(webhooks): changes related to webhook management * API code cleanup, removing unneeded functions, and updating validation logic * Incorrectly ignoring the error that the webhook did not exist * Re-adding missing error handling. Changing error response to be a 404 vs 500 when token can't find an object * fix(webhooks): close Docker client after service webhook execution --- api/bolt/datastore.go | 8 + api/bolt/webhook/webhook.go | 151 ++++++++++++++++++ api/cmd/portainer/main.go | 2 + api/errors.go | 6 + api/http/handler/handler.go | 4 + api/http/handler/webhooks/handler.go | 35 ++++ api/http/handler/webhooks/webhook_create.go | 66 ++++++++ api/http/handler/webhooks/webhook_delete.go | 25 +++ api/http/handler/webhooks/webhook_execute.go | 71 ++++++++ api/http/handler/webhooks/webhook_list.go | 47 ++++++ api/http/server.go | 10 ++ api/portainer.go | 31 ++++ app/constants.js | 1 + .../servicesDatatableActionsController.js | 11 +- .../create/createServiceController.js | 16 +- .../views/services/create/createservice.html | 16 ++ app/docker/views/services/edit/service.html | 20 ++- .../views/services/edit/serviceController.js | 47 +++++- app/portainer/helpers/webhookHelper.js | 13 ++ app/portainer/models/webhook.js | 7 + app/portainer/rest/webhooks.js | 10 ++ app/portainer/services/api/webhookService.js | 33 ++++ 22 files changed, 619 insertions(+), 11 deletions(-) create mode 100644 api/bolt/webhook/webhook.go create mode 100644 api/http/handler/webhooks/handler.go create mode 100644 api/http/handler/webhooks/webhook_create.go create mode 100644 api/http/handler/webhooks/webhook_delete.go create mode 100644 api/http/handler/webhooks/webhook_execute.go create mode 100644 api/http/handler/webhooks/webhook_list.go create mode 100644 app/portainer/helpers/webhookHelper.js create mode 100644 app/portainer/models/webhook.js create mode 100644 app/portainer/rest/webhooks.js create mode 100644 app/portainer/services/api/webhookService.js diff --git a/api/bolt/datastore.go b/api/bolt/datastore.go index a6a921acf..192bc0271 100644 --- a/api/bolt/datastore.go +++ b/api/bolt/datastore.go @@ -21,6 +21,7 @@ import ( "github.com/portainer/portainer/bolt/template" "github.com/portainer/portainer/bolt/user" "github.com/portainer/portainer/bolt/version" + "github.com/portainer/portainer/bolt/webhook" ) const ( @@ -47,6 +48,7 @@ type Store struct { TemplateService *template.Service UserService *user.Service VersionService *version.Service + WebhookService *webhook.Service } // NewStore initializes a new Store and the associated services @@ -232,5 +234,11 @@ func (store *Store) initServices() error { } store.VersionService = versionService + webhookService, err := webhook.NewService(store.db) + if err != nil { + return err + } + store.WebhookService = webhookService + return nil } diff --git a/api/bolt/webhook/webhook.go b/api/bolt/webhook/webhook.go new file mode 100644 index 000000000..94ebe61c5 --- /dev/null +++ b/api/bolt/webhook/webhook.go @@ -0,0 +1,151 @@ +package webhook + +import ( + "github.com/portainer/portainer" + "github.com/portainer/portainer/bolt/internal" + + "github.com/boltdb/bolt" +) + +const ( + // BucketName represents the name of the bucket where this service stores data. + BucketName = "webhooks" +) + +// Service represents a service for managing webhook data. +type Service struct { + db *bolt.DB +} + +// NewService creates a new instance of a service. +func NewService(db *bolt.DB) (*Service, error) { + err := internal.CreateBucket(db, BucketName) + if err != nil { + return nil, err + } + + return &Service{ + db: db, + }, nil +} + +//Webhooks returns an array of all webhooks +func (service *Service) Webhooks() ([]portainer.Webhook, error) { + var webhooks = make([]portainer.Webhook, 0) + + err := service.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(BucketName)) + + cursor := bucket.Cursor() + for k, v := cursor.First(); k != nil; k, v = cursor.Next() { + var webhook portainer.Webhook + err := internal.UnmarshalObject(v, &webhook) + if err != nil { + return err + } + webhooks = append(webhooks, webhook) + } + + return nil + }) + + return webhooks, err +} + +// Webhook returns a webhook by ID. +func (service *Service) Webhook(ID portainer.WebhookID) (*portainer.Webhook, error) { + var webhook portainer.Webhook + identifier := internal.Itob(int(ID)) + + err := internal.GetObject(service.db, BucketName, identifier, &webhook) + if err != nil { + return nil, err + } + + return &webhook, nil +} + +// WebhookByResourceID returns a webhook by the ResourceID it is associated with. +func (service *Service) WebhookByResourceID(ID string) (*portainer.Webhook, error) { + var webhook *portainer.Webhook + + err := service.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(BucketName)) + cursor := bucket.Cursor() + + for k, v := cursor.First(); k != nil; k, v = cursor.Next() { + var w portainer.Webhook + err := internal.UnmarshalObject(v, &w) + if err != nil { + return err + } + + if w.ResourceID == ID { + webhook = &w + break + } + } + + if webhook == nil { + return portainer.ErrObjectNotFound + } + + return nil + }) + + return webhook, err +} + +// WebhookByToken returns a webhook by the random token it is associated with. +func (service *Service) WebhookByToken(token string) (*portainer.Webhook, error) { + var webhook *portainer.Webhook + + err := service.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(BucketName)) + cursor := bucket.Cursor() + + for k, v := cursor.First(); k != nil; k, v = cursor.Next() { + var w portainer.Webhook + err := internal.UnmarshalObject(v, &w) + if err != nil { + return err + } + + if w.Token == token { + webhook = &w + break + } + } + + if webhook == nil { + return portainer.ErrObjectNotFound + } + + return nil + }) + + return webhook, err +} + +// DeleteWebhook deletes a webhook. +func (service *Service) DeleteWebhook(ID portainer.WebhookID) error { + identifier := internal.Itob(int(ID)) + return internal.DeleteObject(service.db, BucketName, identifier) +} + +// CreateWebhook assign an ID to a new webhook and saves it. +func (service *Service) CreateWebhook(webhook *portainer.Webhook) error { + return service.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(BucketName)) + + id, _ := bucket.NextSequence() + webhook.ID = portainer.WebhookID(id) + + data, err := internal.MarshalObject(webhook) + if err != nil { + return err + } + + return bucket.Put(internal.Itob(int(webhook.ID)), data) + }) +} diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index c7997bf14..cecf986b7 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -505,6 +505,7 @@ func main() { StackService: store.StackService, TagService: store.TagService, TemplateService: store.TemplateService, + WebhookService: store.WebhookService, SwarmStackManager: swarmStackManager, ComposeStackManager: composeStackManager, CryptoService: cryptoService, @@ -518,6 +519,7 @@ func main() { SSL: *flags.SSL, SSLCert: *flags.SSLCert, SSLKey: *flags.SSLKey, + DockerClientFactory: clientFactory, } log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr) diff --git a/api/errors.go b/api/errors.go index 37552f104..e348aaf48 100644 --- a/api/errors.go +++ b/api/errors.go @@ -93,3 +93,9 @@ type Error string // Error returns the error message. func (e Error) Error() string { return string(e) } + +// Webhook errors +const ( + ErrWebhookAlreadyExists = Error("A webhook for this resource already exists") + ErrUnsupportedWebhookType = Error("Webhooks for this resource are not currently supported") +) diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index 342230396..40ae9f57d 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -22,6 +22,7 @@ import ( "github.com/portainer/portainer/http/handler/templates" "github.com/portainer/portainer/http/handler/upload" "github.com/portainer/portainer/http/handler/users" + "github.com/portainer/portainer/http/handler/webhooks" "github.com/portainer/portainer/http/handler/websocket" ) @@ -47,6 +48,7 @@ type Handler struct { UploadHandler *upload.Handler UserHandler *users.Handler WebSocketHandler *websocket.Handler + WebhookHandler *webhooks.Handler } // ServeHTTP delegates a request to the appropriate subhandler. @@ -95,6 +97,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.StripPrefix("/api", h.TeamMembershipHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/api/websocket"): http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r) + case strings.HasPrefix(r.URL.Path, "/api/webhooks"): + http.StripPrefix("/api", h.WebhookHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/"): h.FileHandler.ServeHTTP(w, r) } diff --git a/api/http/handler/webhooks/handler.go b/api/http/handler/webhooks/handler.go new file mode 100644 index 000000000..fe60f3fdb --- /dev/null +++ b/api/http/handler/webhooks/handler.go @@ -0,0 +1,35 @@ +package webhooks + +import ( + "net/http" + + "github.com/gorilla/mux" + portainer "github.com/portainer/portainer" + "github.com/portainer/portainer/docker" + httperror "github.com/portainer/portainer/http/error" + "github.com/portainer/portainer/http/security" +) + +// Handler is the HTTP handler used to handle webhook operations. +type Handler struct { + *mux.Router + WebhookService portainer.WebhookService + EndpointService portainer.EndpointService + DockerClientFactory *docker.ClientFactory +} + +// NewHandler creates a handler to manage settings operations. +func NewHandler(bouncer *security.RequestBouncer) *Handler { + h := &Handler{ + Router: mux.NewRouter(), + } + h.Handle("/webhooks", + bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookCreate))).Methods(http.MethodPost) + h.Handle("/webhooks", + bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookList))).Methods(http.MethodGet) + h.Handle("/webhooks/{id}", + bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookDelete))).Methods(http.MethodDelete) + h.Handle("/webhooks/{token}", + bouncer.PublicAccess(httperror.LoggerHandler(h.webhookExecute))).Methods(http.MethodPost) + return h +} diff --git a/api/http/handler/webhooks/webhook_create.go b/api/http/handler/webhooks/webhook_create.go new file mode 100644 index 000000000..c74db0c6e --- /dev/null +++ b/api/http/handler/webhooks/webhook_create.go @@ -0,0 +1,66 @@ +package webhooks + +import ( + "net/http" + + "github.com/asaskevich/govalidator" + "github.com/portainer/portainer" + httperror "github.com/portainer/portainer/http/error" + "github.com/portainer/portainer/http/request" + "github.com/portainer/portainer/http/response" + "github.com/satori/go.uuid" +) + +type webhookCreatePayload struct { + ResourceID string + EndpointID int + WebhookType int +} + +func (payload *webhookCreatePayload) Validate(r *http.Request) error { + if govalidator.IsNull(payload.ResourceID) { + return portainer.Error("Invalid ResourceID") + } + if payload.EndpointID == 0 { + return portainer.Error("Invalid EndpointID") + } + if payload.WebhookType != 1 { + return portainer.Error("Invalid WebhookType") + } + return nil +} + +func (handler *Handler) webhookCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + var payload webhookCreatePayload + err := request.DecodeAndValidateJSONPayload(r, &payload) + if err != nil { + return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err} + } + + webhook, err := handler.WebhookService.WebhookByResourceID(payload.ResourceID) + if err != nil && err != portainer.ErrObjectNotFound { + return &httperror.HandlerError{http.StatusInternalServerError, "An error occurred retrieving webhooks from the database", err} + } + if webhook != nil { + return &httperror.HandlerError{http.StatusConflict, "A webhook for this resource already exists", portainer.ErrWebhookAlreadyExists} + } + + token, err := uuid.NewV4() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Error creating unique token", err} + } + + webhook = &portainer.Webhook{ + Token: token.String(), + ResourceID: payload.ResourceID, + EndpointID: portainer.EndpointID(payload.EndpointID), + WebhookType: portainer.WebhookType(payload.WebhookType), + } + + err = handler.WebhookService.CreateWebhook(webhook) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the webhook inside the database", err} + } + + return response.JSON(w, webhook) +} diff --git a/api/http/handler/webhooks/webhook_delete.go b/api/http/handler/webhooks/webhook_delete.go new file mode 100644 index 000000000..b81a445e1 --- /dev/null +++ b/api/http/handler/webhooks/webhook_delete.go @@ -0,0 +1,25 @@ +package webhooks + +import ( + "net/http" + + "github.com/portainer/portainer" + httperror "github.com/portainer/portainer/http/error" + "github.com/portainer/portainer/http/request" + "github.com/portainer/portainer/http/response" +) + +// DELETE request on /api/webhook/:serviceID +func (handler *Handler) webhookDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + id, err := request.RetrieveNumericRouteVariableValue(r, "id") + if err != nil { + return &httperror.HandlerError{http.StatusBadRequest, "Invalid webhook id", err} + } + + err = handler.WebhookService.DeleteWebhook(portainer.WebhookID(id)) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the webhook from the database", err} + } + + return response.Empty(w) +} diff --git a/api/http/handler/webhooks/webhook_execute.go b/api/http/handler/webhooks/webhook_execute.go new file mode 100644 index 000000000..395be9999 --- /dev/null +++ b/api/http/handler/webhooks/webhook_execute.go @@ -0,0 +1,71 @@ +package webhooks + +import ( + "context" + "net/http" + "strings" + + dockertypes "github.com/docker/docker/api/types" + "github.com/portainer/portainer" + httperror "github.com/portainer/portainer/http/error" + "github.com/portainer/portainer/http/request" + "github.com/portainer/portainer/http/response" +) + +// Acts on a passed in token UUID to restart the docker service +func (handler *Handler) webhookExecute(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + + webhookToken, err := request.RetrieveRouteVariableValue(r, "token") + + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Invalid service id parameter", err} + } + + webhook, err := handler.WebhookService.WebhookByToken(webhookToken) + + if err == portainer.ErrObjectNotFound { + return &httperror.HandlerError{http.StatusNotFound, "Unable to find a webhook with this token", err} + } else if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve webhook from the database", err} + } + + resourceID := webhook.ResourceID + endpointID := webhook.EndpointID + webhookType := webhook.WebhookType + + endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID)) + if err == portainer.ErrObjectNotFound { + return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err} + } else if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} + } + switch webhookType { + case portainer.ServiceWebhook: + return handler.executeServiceWebhook(w, endpoint, resourceID) + default: + return &httperror.HandlerError{http.StatusInternalServerError, "Unsupported webhook type", portainer.ErrUnsupportedWebhookType} + } + +} + +func (handler *Handler) executeServiceWebhook(w http.ResponseWriter, endpoint *portainer.Endpoint, resourceID string) *httperror.HandlerError { + dockerClient, err := handler.DockerClientFactory.CreateClient(endpoint) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Error creating docker client", err} + } + defer dockerClient.Close() + + service, _, err := dockerClient.ServiceInspectWithRaw(context.Background(), resourceID, dockertypes.ServiceInspectOptions{InsertDefaults: true}) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Error looking up service", err} + } + + service.Spec.TaskTemplate.ForceUpdate++ + + service.Spec.TaskTemplate.ContainerSpec.Image = strings.Split(service.Spec.TaskTemplate.ContainerSpec.Image, "@sha")[0] + _, err = dockerClient.ServiceUpdate(context.Background(), resourceID, service.Version, service.Spec, dockertypes.ServiceUpdateOptions{QueryRegistry: true}) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Error updating service", err} + } + return response.Empty(w) +} diff --git a/api/http/handler/webhooks/webhook_list.go b/api/http/handler/webhooks/webhook_list.go new file mode 100644 index 000000000..288bdcd65 --- /dev/null +++ b/api/http/handler/webhooks/webhook_list.go @@ -0,0 +1,47 @@ +package webhooks + +import ( + "net/http" + + "github.com/portainer/portainer" + httperror "github.com/portainer/portainer/http/error" + "github.com/portainer/portainer/http/request" + "github.com/portainer/portainer/http/response" +) + +type webhookListOperationFilters struct { + ResourceID string `json:"ResourceID"` + EndpointID int `json:"EndpointID"` +} + +// GET request on /api/webhooks?(filters=) +func (handler *Handler) webhookList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + var filters webhookListOperationFilters + err := request.RetrieveJSONQueryParameter(r, "filters", &filters, true) + if err != nil { + return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: filters", err} + } + + webhooks, err := handler.WebhookService.Webhooks() + webhooks = filterWebhooks(webhooks, &filters) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve webhooks from the database", err} + } + + return response.JSON(w, webhooks) +} + +func filterWebhooks(webhooks []portainer.Webhook, filters *webhookListOperationFilters) []portainer.Webhook { + if filters.EndpointID == 0 && filters.ResourceID == "" { + return webhooks + } + + filteredWebhooks := make([]portainer.Webhook, 0, len(webhooks)) + for _, webhook := range webhooks { + if webhook.EndpointID == portainer.EndpointID(filters.EndpointID) && webhook.ResourceID == string(filters.ResourceID) { + filteredWebhooks = append(filteredWebhooks, webhook) + } + } + + return filteredWebhooks +} diff --git a/api/http/server.go b/api/http/server.go index 116a6c1ec..b9f1f2bb7 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -4,6 +4,7 @@ import ( "time" "github.com/portainer/portainer" + "github.com/portainer/portainer/docker" "github.com/portainer/portainer/http/handler" "github.com/portainer/portainer/http/handler/auth" "github.com/portainer/portainer/http/handler/dockerhub" @@ -23,6 +24,7 @@ import ( "github.com/portainer/portainer/http/handler/templates" "github.com/portainer/portainer/http/handler/upload" "github.com/portainer/portainer/http/handler/users" + "github.com/portainer/portainer/http/handler/webhooks" "github.com/portainer/portainer/http/handler/websocket" "github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/security" @@ -60,10 +62,12 @@ type Server struct { TeamMembershipService portainer.TeamMembershipService TemplateService portainer.TemplateService UserService portainer.UserService + WebhookService portainer.WebhookService Handler *handler.Handler SSL bool SSLCert string SSLKey string + DockerClientFactory *docker.ClientFactory } // Start starts the HTTP server @@ -171,6 +175,11 @@ func (server *Server) Start() error { websocketHandler.EndpointService = server.EndpointService websocketHandler.SignatureService = server.SignatureService + var webhookHandler = webhooks.NewHandler(requestBouncer) + webhookHandler.WebhookService = server.WebhookService + webhookHandler.EndpointService = server.EndpointService + webhookHandler.DockerClientFactory = server.DockerClientFactory + server.Handler = &handler.Handler{ AuthHandler: authHandler, DockerHubHandler: dockerHubHandler, @@ -191,6 +200,7 @@ func (server *Server) Start() error { UploadHandler: uploadHandler, UserHandler: userHandler, WebSocketHandler: websocketHandler, + WebhookHandler: webhookHandler, } if server.SSL { diff --git a/api/portainer.go b/api/portainer.go index 49f87c652..839abea7a 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -220,6 +220,21 @@ type ( TLSKeyPath string `json:"TLSKey,omitempty"` } + // WebhookID represents an webhook identifier. + WebhookID int + + // WebhookType represents the type of resource a webhook is related to + WebhookType int + + // Webhook represents a url webhook that can be used to update a service + Webhook struct { + ID WebhookID `json:"Id"` + Token string `json:"Token"` + ResourceID string `json:"ResourceId"` + EndpointID EndpointID `json:"EndpointId"` + WebhookType WebhookType `json:"Type"` + } + // AzureCredentials represents the credentials used to connect to an Azure // environment. AzureCredentials struct { @@ -506,6 +521,16 @@ type ( StoreDBVersion(version int) error } + // WebhookService represents a service for managing webhook data. + WebhookService interface { + Webhooks() ([]Webhook, error) + Webhook(ID WebhookID) (*Webhook, error) + CreateWebhook(portainer *Webhook) error + WebhookByResourceID(resourceID string) (*Webhook, error) + WebhookByToken(token string) (*Webhook, error) + DeleteWebhook(serviceID WebhookID) error + } + // ResourceControlService represents a service for managing resource control data ResourceControlService interface { ResourceControl(ID ResourceControlID) (*ResourceControl, error) @@ -732,3 +757,9 @@ const ( // EndpointStatusDown is used to represent an unavailable endpoint EndpointStatusDown ) + +const ( + _ WebhookType = iota + // ServiceWebhook is a webhook for restarting a docker service + ServiceWebhook +) diff --git a/app/constants.js b/app/constants.js index de9631ddd..b20d44649 100644 --- a/app/constants.js +++ b/app/constants.js @@ -14,6 +14,7 @@ angular.module('portainer') .constant('API_ENDPOINT_TEAMS', 'api/teams') .constant('API_ENDPOINT_TEAM_MEMBERSHIPS', 'api/team_memberships') .constant('API_ENDPOINT_TEMPLATES', 'api/templates') +.constant('API_ENDPOINT_WEBHOOKS', 'api/webhooks') .constant('DEFAULT_TEMPLATES_URL', 'https://raw.githubusercontent.com/portainer/templates/master/templates.json') .constant('PAGINATION_MAX_ITEMS', 10) .constant('APPLICATION_CACHE_VALIDITY', 3600) diff --git a/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js b/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js index dff0d034c..2f5ce7ce4 100644 --- a/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js +++ b/app/docker/components/datatables/services-datatable/actions/servicesDatatableActionsController.js @@ -1,6 +1,6 @@ angular.module('portainer.docker') -.controller('ServicesDatatableActionsController', ['$state', 'ServiceService', 'ServiceHelper', 'Notifications', 'ModalService', 'ImageHelper', -function ($state, ServiceService, ServiceHelper, Notifications, ModalService, ImageHelper) { +.controller('ServicesDatatableActionsController', ['$q', '$state', 'ServiceService', 'ServiceHelper', 'Notifications', 'ModalService', 'ImageHelper','WebhookService','EndpointProvider', +function ($q, $state, ServiceService, ServiceHelper, Notifications, ModalService, ImageHelper, WebhookService, EndpointProvider) { this.scaleAction = function scaleService(service) { var config = ServiceHelper.serviceToConfig(service.Model); @@ -71,7 +71,14 @@ function ($state, ServiceService, ServiceHelper, Notifications, ModalService, Im function removeServices(services) { var actionCount = services.length; angular.forEach(services, function (service) { + ServiceService.remove(service) + .then(function success() { + return WebhookService.webhooks(service.Id, EndpointProvider.endpointID()); + }) + .then(function success(data) { + return $q.when(data.length !== 0 && WebhookService.deleteWebhook(data[0].Id)); + }) .then(function success() { Notifications.success('Service successfully removed', service.Name); }) diff --git a/app/docker/views/services/create/createServiceController.js b/app/docker/views/services/create/createServiceController.js index e56dc1e4c..9f69ad0b5 100644 --- a/app/docker/views/services/create/createServiceController.js +++ b/app/docker/views/services/create/createServiceController.js @@ -1,6 +1,6 @@ -angular.module('portainer.docker') -.controller('CreateServiceController', ['$q', '$scope', '$state', '$timeout', 'Service', 'ServiceHelper', 'ConfigService', 'ConfigHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'FormValidator', 'PluginService', 'RegistryService', 'HttpRequestHelper', 'NodeService', 'SettingsService', -function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, ConfigHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, FormValidator, PluginService, RegistryService, HttpRequestHelper, NodeService, SettingsService) { + angular.module('portainer.docker') +.controller('CreateServiceController', ['$q', '$scope', '$state', '$timeout', 'Service', 'ServiceHelper', 'ConfigService', 'ConfigHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'FormValidator', 'PluginService', 'RegistryService', 'HttpRequestHelper', 'NodeService', 'SettingsService', 'WebhookService','EndpointProvider', +function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, ConfigHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, FormValidator, PluginService, RegistryService, HttpRequestHelper, NodeService, SettingsService, WebhookService,EndpointProvider) { $scope.formValues = { Name: '', @@ -40,7 +40,8 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C RestartMaxAttempts: 0, RestartWindow: '0s', LogDriverName: '', - LogDriverOpts: [] + LogDriverOpts: [], + Webhook: false }; $scope.state = { @@ -422,9 +423,14 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C var registry = $scope.formValues.Registry; var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : ''; HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); + + var serviceIdentifier; Service.create(config).$promise .then(function success(data) { - var serviceIdentifier = data.ID; + serviceIdentifier = data.ID; + return $q.when($scope.formValues.Webhook && WebhookService.createServiceWebhook(serviceIdentifier, EndpointProvider.endpointID())); + }) + .then(function success() { var userId = Authentication.getUserDetails().ID; return ResourceControlService.applyResourceControl('service', serviceIdentifier, userId, accessControlData, []); }) diff --git a/app/docker/views/services/create/createservice.html b/app/docker/views/services/create/createservice.html index d9481adb1..40d830f1a 100644 --- a/app/docker/views/services/create/createservice.html +++ b/app/docker/views/services/create/createservice.html @@ -101,6 +101,22 @@
    + +
    + Webhooks +
    +
    +
    + + +
    +
    + diff --git a/app/docker/views/services/edit/service.html b/app/docker/views/services/edit/service.html index e0b2b49de..0b3e2e854 100644 --- a/app/docker/views/services/edit/service.html +++ b/app/docker/views/services/edit/service.html @@ -71,6 +71,24 @@ ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" id="image_name" ng-disabled="isUpdating"> +
    + + +
    + Service webhook + + + + {{ webhookURL | truncatelr }} + + + + +
    Service logs @@ -93,7 +111,7 @@

    -
    -
    - - Current password is not valid -
    -
    diff --git a/app/portainer/views/account/accountController.js b/app/portainer/views/account/accountController.js index 304fd2586..c3ef8402b 100644 --- a/app/portainer/views/account/accountController.js +++ b/app/portainer/views/account/accountController.js @@ -8,19 +8,13 @@ function ($scope, $state, Authentication, UserService, Notifications, SettingsSe }; $scope.updatePassword = function() { - $scope.invalidPassword = false; - UserService.updateUserPassword($scope.userID, $scope.formValues.currentPassword, $scope.formValues.newPassword) .then(function success() { Notifications.success('Success', 'Password successfully updated'); $state.reload(); }) .catch(function error(err) { - if (err.invalidPassword) { - $scope.invalidPassword = true; - } else { - Notifications.error('Failure', err, err.msg); - } + Notifications.error('Failure', err, err.msg); }); }; diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js index 3b18d600c..055d359be 100644 --- a/app/portainer/views/auth/authController.js +++ b/app/portainer/views/auth/authController.js @@ -30,7 +30,7 @@ function ($q, $scope, $state, $transition$, $sanitize, Authentication, UserServi return $q.reject(); }) .then(function success() { - $state.go('portainer.updatePassword', { password: $sanitize(password) }); + $state.go('portainer.updatePassword'); }) .catch(function error() { $scope.state.AuthenticationError = 'Invalid credentials'; diff --git a/app/portainer/views/update-password/updatePassword.html b/app/portainer/views/update-password/updatePassword.html index 62c726a61..0f9d10acd 100644 --- a/app/portainer/views/update-password/updatePassword.html +++ b/app/portainer/views/update-password/updatePassword.html @@ -15,11 +15,19 @@
    + +
    + +
    + +
    +
    +
    - +
    diff --git a/app/portainer/views/update-password/updatePasswordController.js b/app/portainer/views/update-password/updatePasswordController.js index 6809d1a34..991bf6f33 100644 --- a/app/portainer/views/update-password/updatePasswordController.js +++ b/app/portainer/views/update-password/updatePasswordController.js @@ -1,22 +1,22 @@ angular.module('portainer.app') -.controller('UpdatePasswordController', ['$scope', '$state', '$transition$', 'UserService', 'Authentication', 'Notifications', -function UpdatePasswordController($scope, $state, $transition$, UserService, Authentication, Notifications) { +.controller('UpdatePasswordController', ['$scope', '$state', '$transition$', '$sanitize', 'UserService', 'Authentication', 'Notifications', +function UpdatePasswordController($scope, $state, $transition$, $sanitize, UserService, Authentication, Notifications) { $scope.formValues = { + CurrentPassword: '', Password: '', ConfirmPassword: '' }; $scope.state = { - actionInProgress: false, - currentPassword: '' + actionInProgress: false }; $scope.updatePassword = function() { var userId = Authentication.getUserDetails().ID; $scope.state.actionInProgress = true; - UserService.updateUserPassword(userId, $scope.state.currentPassword, $scope.formValues.Password) + UserService.updateUserPassword(userId, $sanitize($scope.formValues.CurrentPassword), $scope.formValues.Password) .then(function success() { $state.go('portainer.home'); }) @@ -32,8 +32,6 @@ function UpdatePasswordController($scope, $state, $transition$, UserService, Aut if (!Authentication.isAuthenticated()) { $state.go('portainer.auth'); } - - $scope.state.currentPassword = $transition$.params().password; } initView(); From 42f5aec6a544941e5c84d8b357fa1ea7650c0e20 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 7 Sep 2018 11:24:18 +0200 Subject: [PATCH 52/57] feat(container-console): increase hijacked tcp connection reader size (#2259) --- api/http/handler/websocket/websocket_exec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/http/handler/websocket/websocket_exec.go b/api/http/handler/websocket/websocket_exec.go index 80c62b9d9..58d734072 100644 --- a/api/http/handler/websocket/websocket_exec.go +++ b/api/http/handler/websocket/websocket_exec.go @@ -252,7 +252,7 @@ func streamFromWebsocketConnToTCPConn(websocketConn *websocket.Conn, tcpConn net func streamFromTCPConnToWebsocketConn(websocketConn *websocket.Conn, br *bufio.Reader, errorChan chan error) { for { - out := make([]byte, 1024) + out := make([]byte, 2048) _, err := br.Read(out) if err != nil { errorChan <- err From b24891a6bc57bc76faf73d3566260de04dc219f4 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 10 Sep 2018 12:01:38 +0200 Subject: [PATCH 53/57] refactor(api): introduce libhttp usage (#2263) --- api/http/error/error.go | 42 ----- api/http/handler/auth/authenticate.go | 6 +- api/http/handler/auth/handler.go | 2 +- .../handler/dockerhub/dockerhub_inspect.go | 4 +- .../handler/dockerhub/dockerhub_update.go | 6 +- api/http/handler/dockerhub/handler.go | 2 +- .../endpointgroups/endpointgroup_create.go | 6 +- .../endpointgroups/endpointgroup_delete.go | 6 +- .../endpointgroups/endpointgroup_inspect.go | 6 +- .../endpointgroups/endpointgroup_list.go | 4 +- .../endpointgroups/endpointgroup_update.go | 6 +- .../endpointgroup_update_access.go | 6 +- api/http/handler/endpointgroups/handler.go | 2 +- api/http/handler/endpointproxy/handler.go | 2 +- api/http/handler/endpointproxy/proxy_azure.go | 4 +- .../handler/endpointproxy/proxy_docker.go | 4 +- .../handler/endpointproxy/proxy_storidge.go | 4 +- api/http/handler/endpoints/endpoint_create.go | 12 +- api/http/handler/endpoints/endpoint_delete.go | 6 +- .../endpoints/endpoint_extension_add.go | 6 +- .../endpoints/endpoint_extension_remove.go | 6 +- .../handler/endpoints/endpoint_inspect.go | 6 +- api/http/handler/endpoints/endpoint_list.go | 4 +- .../handler/endpoints/endpoint_snapshot.go | 4 +- api/http/handler/endpoints/endpoint_update.go | 6 +- .../endpoints/endpoint_update_access.go | 6 +- api/http/handler/endpoints/handler.go | 2 +- api/http/handler/motd/motd.go | 2 +- api/http/handler/registries/handler.go | 2 +- .../handler/registries/registry_create.go | 6 +- .../handler/registries/registry_delete.go | 6 +- .../handler/registries/registry_inspect.go | 6 +- api/http/handler/registries/registry_list.go | 4 +- .../handler/registries/registry_update.go | 6 +- .../registries/registry_update_access.go | 6 +- api/http/handler/resourcecontrols/handler.go | 2 +- .../resourcecontrol_create.go | 6 +- .../resourcecontrol_delete.go | 6 +- .../resourcecontrol_update.go | 6 +- api/http/handler/settings/handler.go | 2 +- api/http/handler/settings/settings_inspect.go | 4 +- .../handler/settings/settings_ldap_check.go | 6 +- api/http/handler/settings/settings_public.go | 4 +- api/http/handler/settings/settings_update.go | 6 +- .../handler/stacks/create_compose_stack.go | 8 +- api/http/handler/stacks/create_swarm_stack.go | 8 +- api/http/handler/stacks/handler.go | 2 +- api/http/handler/stacks/stack_create.go | 11 +- api/http/handler/stacks/stack_delete.go | 6 +- api/http/handler/stacks/stack_file.go | 6 +- api/http/handler/stacks/stack_inspect.go | 6 +- api/http/handler/stacks/stack_list.go | 6 +- api/http/handler/stacks/stack_migrate.go | 6 +- api/http/handler/stacks/stack_update.go | 6 +- api/http/handler/status/handler.go | 2 +- api/http/handler/status/status_inspect.go | 4 +- api/http/handler/tags/handler.go | 2 +- api/http/handler/tags/tag_create.go | 6 +- api/http/handler/tags/tag_delete.go | 6 +- api/http/handler/tags/tag_list.go | 4 +- api/http/handler/teammemberships/handler.go | 2 +- .../teammemberships/teammembership_create.go | 6 +- .../teammemberships/teammembership_delete.go | 6 +- .../teammemberships/teammembership_list.go | 4 +- .../teammemberships/teammembership_update.go | 6 +- api/http/handler/teams/handler.go | 2 +- api/http/handler/teams/team_create.go | 6 +- api/http/handler/teams/team_delete.go | 6 +- api/http/handler/teams/team_inspect.go | 6 +- api/http/handler/teams/team_list.go | 4 +- api/http/handler/teams/team_memberships.go | 6 +- api/http/handler/teams/team_update.go | 6 +- api/http/handler/templates/handler.go | 2 +- api/http/handler/templates/template_create.go | 6 +- api/http/handler/templates/template_delete.go | 6 +- .../handler/templates/template_inspect.go | 6 +- api/http/handler/templates/template_list.go | 4 +- api/http/handler/templates/template_update.go | 6 +- api/http/handler/upload/handler.go | 2 +- api/http/handler/upload/upload_tls.go | 8 +- api/http/handler/users/admin_check.go | 4 +- api/http/handler/users/admin_init.go | 6 +- api/http/handler/users/handler.go | 2 +- api/http/handler/users/user_create.go | 6 +- api/http/handler/users/user_delete.go | 6 +- api/http/handler/users/user_inspect.go | 6 +- api/http/handler/users/user_list.go | 4 +- api/http/handler/users/user_memberships.go | 6 +- api/http/handler/users/user_update.go | 6 +- .../handler/users/user_update_password.go | 6 +- api/http/handler/webhooks/handler.go | 2 +- api/http/handler/webhooks/webhook_create.go | 6 +- api/http/handler/webhooks/webhook_delete.go | 6 +- api/http/handler/webhooks/webhook_execute.go | 6 +- api/http/handler/webhooks/webhook_list.go | 6 +- api/http/handler/websocket/handler.go | 2 +- api/http/handler/websocket/websocket_exec.go | 4 +- api/http/proxy/local.go | 2 +- api/http/request/request.go | 163 ------------------ api/http/response/response.go | 25 --- api/http/security/bouncer.go | 2 +- api/http/security/rate_limiter.go | 2 +- 102 files changed, 244 insertions(+), 473 deletions(-) delete mode 100644 api/http/error/error.go delete mode 100644 api/http/request/request.go delete mode 100644 api/http/response/response.go diff --git a/api/http/error/error.go b/api/http/error/error.go deleted file mode 100644 index 244397fdf..000000000 --- a/api/http/error/error.go +++ /dev/null @@ -1,42 +0,0 @@ -package error - -import ( - "encoding/json" - "log" - "net/http" -) - -type ( - // LoggerHandler defines a HTTP handler that includes a HandlerError return pointer - LoggerHandler func(http.ResponseWriter, *http.Request) *HandlerError - // HandlerError represents an error raised inside a HTTP handler - HandlerError struct { - StatusCode int - Message string - Err error - } - errorResponse struct { - Err string `json:"err,omitempty"` - Details string `json:"details,omitempty"` - } -) - -func (handler LoggerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - err := handler(rw, r) - if err != nil { - writeErrorResponse(rw, err) - } -} - -func writeErrorResponse(rw http.ResponseWriter, err *HandlerError) { - log.Printf("http error: %s (err=%s) (code=%d)\n", err.Message, err.Err, err.StatusCode) - rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(err.StatusCode) - json.NewEncoder(rw).Encode(&errorResponse{Err: err.Message, Details: err.Err.Error()}) -} - -// WriteError is a convenience function that creates a new HandlerError before calling writeErrorResponse. -// For use outside of the standard http handlers. -func WriteError(rw http.ResponseWriter, code int, message string, err error) { - writeErrorResponse(rw, &HandlerError{code, message, err}) -} diff --git a/api/http/handler/auth/authenticate.go b/api/http/handler/auth/authenticate.go index cf928357b..d5562edd3 100644 --- a/api/http/handler/auth/authenticate.go +++ b/api/http/handler/auth/authenticate.go @@ -6,10 +6,10 @@ import ( "strings" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type authenticatePayload struct { diff --git a/api/http/handler/auth/handler.go b/api/http/handler/auth/handler.go index 87be85429..1f0769e08 100644 --- a/api/http/handler/auth/handler.go +++ b/api/http/handler/auth/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/dockerhub/dockerhub_inspect.go b/api/http/handler/dockerhub/dockerhub_inspect.go index 25be6617c..b149a2a35 100644 --- a/api/http/handler/dockerhub/dockerhub_inspect.go +++ b/api/http/handler/dockerhub/dockerhub_inspect.go @@ -3,8 +3,8 @@ package dockerhub import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" ) // GET request on /api/dockerhub diff --git a/api/http/handler/dockerhub/dockerhub_update.go b/api/http/handler/dockerhub/dockerhub_update.go index 7bd37bce5..69d82ee50 100644 --- a/api/http/handler/dockerhub/dockerhub_update.go +++ b/api/http/handler/dockerhub/dockerhub_update.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type dockerhubUpdatePayload struct { diff --git a/api/http/handler/dockerhub/handler.go b/api/http/handler/dockerhub/handler.go index cd2f5ae50..9ad93232d 100644 --- a/api/http/handler/dockerhub/handler.go +++ b/api/http/handler/dockerhub/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/endpointgroups/endpointgroup_create.go b/api/http/handler/endpointgroups/endpointgroup_create.go index e2d7bd0c0..cc7a23054 100644 --- a/api/http/handler/endpointgroups/endpointgroup_create.go +++ b/api/http/handler/endpointgroups/endpointgroup_create.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type endpointGroupCreatePayload struct { diff --git a/api/http/handler/endpointgroups/endpointgroup_delete.go b/api/http/handler/endpointgroups/endpointgroup_delete.go index 01123d850..b436b4f2f 100644 --- a/api/http/handler/endpointgroups/endpointgroup_delete.go +++ b/api/http/handler/endpointgroups/endpointgroup_delete.go @@ -3,10 +3,10 @@ package endpointgroups import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/endpoint_groups/:id diff --git a/api/http/handler/endpointgroups/endpointgroup_inspect.go b/api/http/handler/endpointgroups/endpointgroup_inspect.go index 168d8cb8b..46a6895e8 100644 --- a/api/http/handler/endpointgroups/endpointgroup_inspect.go +++ b/api/http/handler/endpointgroups/endpointgroup_inspect.go @@ -3,10 +3,10 @@ package endpointgroups import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // GET request on /api/endpoint_groups/:id diff --git a/api/http/handler/endpointgroups/endpointgroup_list.go b/api/http/handler/endpointgroups/endpointgroup_list.go index fa7a35ec4..7acb696a8 100644 --- a/api/http/handler/endpointgroups/endpointgroup_list.go +++ b/api/http/handler/endpointgroups/endpointgroup_list.go @@ -3,8 +3,8 @@ package endpointgroups import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/endpointgroups/endpointgroup_update.go b/api/http/handler/endpointgroups/endpointgroup_update.go index ee31e3066..ec24b801f 100644 --- a/api/http/handler/endpointgroups/endpointgroup_update.go +++ b/api/http/handler/endpointgroups/endpointgroup_update.go @@ -3,10 +3,10 @@ package endpointgroups import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type endpointGroupUpdatePayload struct { diff --git a/api/http/handler/endpointgroups/endpointgroup_update_access.go b/api/http/handler/endpointgroups/endpointgroup_update_access.go index 7a3b3038e..6e859a4df 100644 --- a/api/http/handler/endpointgroups/endpointgroup_update_access.go +++ b/api/http/handler/endpointgroups/endpointgroup_update_access.go @@ -3,10 +3,10 @@ package endpointgroups import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type endpointGroupUpdateAccessPayload struct { diff --git a/api/http/handler/endpointgroups/handler.go b/api/http/handler/endpointgroups/handler.go index f31a40a27..3b731e911 100644 --- a/api/http/handler/endpointgroups/handler.go +++ b/api/http/handler/endpointgroups/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/endpointproxy/handler.go b/api/http/handler/endpointproxy/handler.go index cd17e0733..eaef36b1b 100644 --- a/api/http/handler/endpointproxy/handler.go +++ b/api/http/handler/endpointproxy/handler.go @@ -2,8 +2,8 @@ package endpointproxy import ( "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/endpointproxy/proxy_azure.go b/api/http/handler/endpointproxy/proxy_azure.go index dc46bfb3f..dcbe96f4b 100644 --- a/api/http/handler/endpointproxy/proxy_azure.go +++ b/api/http/handler/endpointproxy/proxy_azure.go @@ -3,9 +3,9 @@ package endpointproxy import ( "strconv" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" "net/http" ) diff --git a/api/http/handler/endpointproxy/proxy_docker.go b/api/http/handler/endpointproxy/proxy_docker.go index 01a56e017..f03ca8e67 100644 --- a/api/http/handler/endpointproxy/proxy_docker.go +++ b/api/http/handler/endpointproxy/proxy_docker.go @@ -3,9 +3,9 @@ package endpointproxy import ( "strconv" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" "net/http" ) diff --git a/api/http/handler/endpointproxy/proxy_storidge.go b/api/http/handler/endpointproxy/proxy_storidge.go index a582b561d..86375f091 100644 --- a/api/http/handler/endpointproxy/proxy_storidge.go +++ b/api/http/handler/endpointproxy/proxy_storidge.go @@ -3,9 +3,9 @@ package endpointproxy import ( "strconv" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" "net/http" ) diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go index 277f6bd0f..a3eeeb114 100644 --- a/api/http/handler/endpoints/endpoint_create.go +++ b/api/http/handler/endpoints/endpoint_create.go @@ -6,12 +6,12 @@ import ( "runtime" "strconv" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/crypto" "github.com/portainer/portainer/http/client" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type endpointCreatePayload struct { @@ -71,7 +71,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error { payload.TLSSkipClientVerify = skipTLSClientVerification if !payload.TLSSkipVerify { - caCert, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile") + caCert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCACertFile") if err != nil { return portainer.Error("Invalid CA certificate file. Ensure that the file is uploaded correctly") } @@ -79,13 +79,13 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error { } if !payload.TLSSkipClientVerify { - cert, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile") + cert, _, err := request.RetrieveMultiPartFormFile(r, "TLSCertFile") if err != nil { return portainer.Error("Invalid certificate file. Ensure that the file is uploaded correctly") } payload.TLSCertFile = cert - key, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile") + key, _, err := request.RetrieveMultiPartFormFile(r, "TLSKeyFile") if err != nil { return portainer.Error("Invalid key file. Ensure that the file is uploaded correctly") } diff --git a/api/http/handler/endpoints/endpoint_delete.go b/api/http/handler/endpoints/endpoint_delete.go index 40f18f348..865c2055b 100644 --- a/api/http/handler/endpoints/endpoint_delete.go +++ b/api/http/handler/endpoints/endpoint_delete.go @@ -4,10 +4,10 @@ import ( "net/http" "strconv" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/endpoints/:id diff --git a/api/http/handler/endpoints/endpoint_extension_add.go b/api/http/handler/endpoints/endpoint_extension_add.go index 009083190..9a9eebbda 100644 --- a/api/http/handler/endpoints/endpoint_extension_add.go +++ b/api/http/handler/endpoints/endpoint_extension_add.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type endpointExtensionAddPayload struct { diff --git a/api/http/handler/endpoints/endpoint_extension_remove.go b/api/http/handler/endpoints/endpoint_extension_remove.go index 2e238a89e..8b265dc6c 100644 --- a/api/http/handler/endpoints/endpoint_extension_remove.go +++ b/api/http/handler/endpoints/endpoint_extension_remove.go @@ -3,10 +3,10 @@ package endpoints import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/endpoints/:id/extensions/:extensionType diff --git a/api/http/handler/endpoints/endpoint_inspect.go b/api/http/handler/endpoints/endpoint_inspect.go index e382b3f16..dd0d3485b 100644 --- a/api/http/handler/endpoints/endpoint_inspect.go +++ b/api/http/handler/endpoints/endpoint_inspect.go @@ -3,10 +3,10 @@ package endpoints import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // GET request on /api/endpoints/:id diff --git a/api/http/handler/endpoints/endpoint_list.go b/api/http/handler/endpoints/endpoint_list.go index 3348c630e..5c1436ac9 100644 --- a/api/http/handler/endpoints/endpoint_list.go +++ b/api/http/handler/endpoints/endpoint_list.go @@ -3,8 +3,8 @@ package endpoints import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/endpoints/endpoint_snapshot.go b/api/http/handler/endpoints/endpoint_snapshot.go index 51a18293e..720f4c072 100644 --- a/api/http/handler/endpoints/endpoint_snapshot.go +++ b/api/http/handler/endpoints/endpoint_snapshot.go @@ -4,9 +4,9 @@ import ( "log" "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" ) // POST request on /api/endpoints/snapshot diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go index 769267449..5244cdc98 100644 --- a/api/http/handler/endpoints/endpoint_update.go +++ b/api/http/handler/endpoints/endpoint_update.go @@ -4,11 +4,11 @@ import ( "net/http" "strconv" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/http/client" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type endpointUpdatePayload struct { diff --git a/api/http/handler/endpoints/endpoint_update_access.go b/api/http/handler/endpoints/endpoint_update_access.go index bedf559ff..47a7d414b 100644 --- a/api/http/handler/endpoints/endpoint_update_access.go +++ b/api/http/handler/endpoints/endpoint_update_access.go @@ -3,10 +3,10 @@ package endpoints import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type endpointUpdateAccessPayload struct { diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index fc1bc1e6c..779cd9390 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -1,8 +1,8 @@ package endpoints import ( + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" "github.com/portainer/portainer/http/security" diff --git a/api/http/handler/motd/motd.go b/api/http/handler/motd/motd.go index b2599be2d..fbe8b5acd 100644 --- a/api/http/handler/motd/motd.go +++ b/api/http/handler/motd/motd.go @@ -3,10 +3,10 @@ package motd import ( "net/http" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/crypto" "github.com/portainer/portainer/http/client" - "github.com/portainer/portainer/http/response" ) type motdResponse struct { diff --git a/api/http/handler/registries/handler.go b/api/http/handler/registries/handler.go index 7a21561db..33a161932 100644 --- a/api/http/handler/registries/handler.go +++ b/api/http/handler/registries/handler.go @@ -1,8 +1,8 @@ package registries import ( + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" "net/http" diff --git a/api/http/handler/registries/registry_create.go b/api/http/handler/registries/registry_create.go index 781f172bf..ad4acb58a 100644 --- a/api/http/handler/registries/registry_create.go +++ b/api/http/handler/registries/registry_create.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type registryCreatePayload struct { diff --git a/api/http/handler/registries/registry_delete.go b/api/http/handler/registries/registry_delete.go index 2e968539b..ac463af5f 100644 --- a/api/http/handler/registries/registry_delete.go +++ b/api/http/handler/registries/registry_delete.go @@ -3,10 +3,10 @@ package registries import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/registries/:id diff --git a/api/http/handler/registries/registry_inspect.go b/api/http/handler/registries/registry_inspect.go index a60f24288..96cdf84ac 100644 --- a/api/http/handler/registries/registry_inspect.go +++ b/api/http/handler/registries/registry_inspect.go @@ -3,10 +3,10 @@ package registries import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // GET request on /api/registries/:id diff --git a/api/http/handler/registries/registry_list.go b/api/http/handler/registries/registry_list.go index 9986ce820..7f75427cc 100644 --- a/api/http/handler/registries/registry_list.go +++ b/api/http/handler/registries/registry_list.go @@ -3,8 +3,8 @@ package registries import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/registries/registry_update.go b/api/http/handler/registries/registry_update.go index 1a3743fb5..cd3a7ae67 100644 --- a/api/http/handler/registries/registry_update.go +++ b/api/http/handler/registries/registry_update.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type registryUpdatePayload struct { diff --git a/api/http/handler/registries/registry_update_access.go b/api/http/handler/registries/registry_update_access.go index a43ccb2f9..77f6fe08e 100644 --- a/api/http/handler/registries/registry_update_access.go +++ b/api/http/handler/registries/registry_update_access.go @@ -3,10 +3,10 @@ package registries import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type registryUpdateAccessPayload struct { diff --git a/api/http/handler/resourcecontrols/handler.go b/api/http/handler/resourcecontrols/handler.go index 4ad474f6b..60757ef28 100644 --- a/api/http/handler/resourcecontrols/handler.go +++ b/api/http/handler/resourcecontrols/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/resourcecontrols/resourcecontrol_create.go b/api/http/handler/resourcecontrols/resourcecontrol_create.go index 10bde9a4f..97c3d5ef1 100644 --- a/api/http/handler/resourcecontrols/resourcecontrol_create.go +++ b/api/http/handler/resourcecontrols/resourcecontrol_create.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/resourcecontrols/resourcecontrol_delete.go b/api/http/handler/resourcecontrols/resourcecontrol_delete.go index 48fd8a972..04ba8d5d4 100644 --- a/api/http/handler/resourcecontrols/resourcecontrol_delete.go +++ b/api/http/handler/resourcecontrols/resourcecontrol_delete.go @@ -3,10 +3,10 @@ package resourcecontrols import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/resourcecontrols/resourcecontrol_update.go b/api/http/handler/resourcecontrols/resourcecontrol_update.go index 69299fcee..c46247c5d 100644 --- a/api/http/handler/resourcecontrols/resourcecontrol_update.go +++ b/api/http/handler/resourcecontrols/resourcecontrol_update.go @@ -3,10 +3,10 @@ package resourcecontrols import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/settings/handler.go b/api/http/handler/settings/handler.go index 58c020877..9440aedd5 100644 --- a/api/http/handler/settings/handler.go +++ b/api/http/handler/settings/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/settings/settings_inspect.go b/api/http/handler/settings/settings_inspect.go index 48da08612..c922e1f47 100644 --- a/api/http/handler/settings/settings_inspect.go +++ b/api/http/handler/settings/settings_inspect.go @@ -3,8 +3,8 @@ package settings import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" ) // GET request on /api/settings diff --git a/api/http/handler/settings/settings_ldap_check.go b/api/http/handler/settings/settings_ldap_check.go index 80d058e33..a8466e285 100644 --- a/api/http/handler/settings/settings_ldap_check.go +++ b/api/http/handler/settings/settings_ldap_check.go @@ -3,11 +3,11 @@ package settings import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/filesystem" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type settingsLDAPCheckPayload struct { diff --git a/api/http/handler/settings/settings_public.go b/api/http/handler/settings/settings_public.go index 07d9c7d75..549cf999e 100644 --- a/api/http/handler/settings/settings_public.go +++ b/api/http/handler/settings/settings_public.go @@ -3,9 +3,9 @@ package settings import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" ) type publicSettingsResponse struct { diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go index 6c171917b..18513931f 100644 --- a/api/http/handler/settings/settings_update.go +++ b/api/http/handler/settings/settings_update.go @@ -4,11 +4,11 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/filesystem" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type settingsUpdatePayload struct { diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index a343c96fa..a6a8ddcfd 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -6,11 +6,11 @@ import ( "strings" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/filesystem" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) @@ -194,7 +194,7 @@ func (payload *composeStackFromFileUploadPayload) Validate(r *http.Request) erro } payload.Name = name - composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file") + composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file") if err != nil { return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly") } diff --git a/api/http/handler/stacks/create_swarm_stack.go b/api/http/handler/stacks/create_swarm_stack.go index 87c94e6ef..017122026 100644 --- a/api/http/handler/stacks/create_swarm_stack.go +++ b/api/http/handler/stacks/create_swarm_stack.go @@ -6,11 +6,11 @@ import ( "strings" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/filesystem" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) @@ -211,7 +211,7 @@ func (payload *swarmStackFromFileUploadPayload) Validate(r *http.Request) error } payload.SwarmID = swarmID - composeFileContent, err := request.RetrieveMultiPartFormFile(r, "file") + composeFileContent, _, err := request.RetrieveMultiPartFormFile(r, "file") if err != nil { return portainer.Error("Invalid Compose file. Ensure that the Compose file is uploaded correctly") } diff --git a/api/http/handler/stacks/handler.go b/api/http/handler/stacks/handler.go index ba231ebfe..7f2fd4bc0 100644 --- a/api/http/handler/stacks/handler.go +++ b/api/http/handler/stacks/handler.go @@ -5,8 +5,8 @@ import ( "sync" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/stacks/stack_create.go b/api/http/handler/stacks/stack_create.go index c0b9d9ce0..0fe7e07ad 100644 --- a/api/http/handler/stacks/stack_create.go +++ b/api/http/handler/stacks/stack_create.go @@ -1,12 +1,13 @@ package stacks import ( + "errors" "log" "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" ) func (handler *Handler) cleanUp(stack *portainer.Stack, doCleanUp *bool) error { @@ -57,7 +58,7 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt return handler.createComposeStack(w, r, method, endpoint) } - return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: type. Value must be one of: 1 (Swarm stack) or 2 (Compose stack)", request.ErrInvalidQueryParameter} + return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: type. Value must be one of: 1 (Swarm stack) or 2 (Compose stack)", errors.New(request.ErrInvalidQueryParameter)} } func (handler *Handler) createComposeStack(w http.ResponseWriter, r *http.Request, method string, endpoint *portainer.Endpoint) *httperror.HandlerError { @@ -71,7 +72,7 @@ func (handler *Handler) createComposeStack(w http.ResponseWriter, r *http.Reques return handler.createComposeStackFromFileUpload(w, r, endpoint) } - return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", request.ErrInvalidQueryParameter} + return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)} } func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request, method string, endpoint *portainer.Endpoint) *httperror.HandlerError { @@ -84,5 +85,5 @@ func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request, return handler.createSwarmStackFromFileUpload(w, r, endpoint) } - return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", request.ErrInvalidQueryParameter} + return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)} } diff --git a/api/http/handler/stacks/stack_delete.go b/api/http/handler/stacks/stack_delete.go index f481f808a..8f8fc25e8 100644 --- a/api/http/handler/stacks/stack_delete.go +++ b/api/http/handler/stacks/stack_delete.go @@ -4,11 +4,11 @@ import ( "net/http" "strconv" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/stacks/stack_file.go b/api/http/handler/stacks/stack_file.go index b86888a37..b0b247cef 100644 --- a/api/http/handler/stacks/stack_file.go +++ b/api/http/handler/stacks/stack_file.go @@ -4,11 +4,11 @@ import ( "net/http" "path" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/stacks/stack_inspect.go b/api/http/handler/stacks/stack_inspect.go index 380482149..e5377f654 100644 --- a/api/http/handler/stacks/stack_inspect.go +++ b/api/http/handler/stacks/stack_inspect.go @@ -3,11 +3,11 @@ package stacks import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/stacks/stack_list.go b/api/http/handler/stacks/stack_list.go index fc4732e06..337b204a7 100644 --- a/api/http/handler/stacks/stack_list.go +++ b/api/http/handler/stacks/stack_list.go @@ -3,11 +3,11 @@ package stacks import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/stacks/stack_migrate.go b/api/http/handler/stacks/stack_migrate.go index b6fed5a96..8a0ec0c69 100644 --- a/api/http/handler/stacks/stack_migrate.go +++ b/api/http/handler/stacks/stack_migrate.go @@ -3,11 +3,11 @@ package stacks import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/stacks/stack_update.go b/api/http/handler/stacks/stack_update.go index 456612249..03999a557 100644 --- a/api/http/handler/stacks/stack_update.go +++ b/api/http/handler/stacks/stack_update.go @@ -5,11 +5,11 @@ import ( "strconv" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/proxy" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/status/handler.go b/api/http/handler/status/handler.go index 692c64130..4d26fcd3a 100644 --- a/api/http/handler/status/handler.go +++ b/api/http/handler/status/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/status/status_inspect.go b/api/http/handler/status/status_inspect.go index 93d379179..1892ddd7d 100644 --- a/api/http/handler/status/status_inspect.go +++ b/api/http/handler/status/status_inspect.go @@ -3,8 +3,8 @@ package status import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" ) // GET request on /api/status diff --git a/api/http/handler/tags/handler.go b/api/http/handler/tags/handler.go index a700f7c3e..b38bd28f3 100644 --- a/api/http/handler/tags/handler.go +++ b/api/http/handler/tags/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/tags/tag_create.go b/api/http/handler/tags/tag_create.go index f75c050b9..50b9261ba 100644 --- a/api/http/handler/tags/tag_create.go +++ b/api/http/handler/tags/tag_create.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type tagCreatePayload struct { diff --git a/api/http/handler/tags/tag_delete.go b/api/http/handler/tags/tag_delete.go index eed7f6410..e74b5ef12 100644 --- a/api/http/handler/tags/tag_delete.go +++ b/api/http/handler/tags/tag_delete.go @@ -3,10 +3,10 @@ package tags import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/tags/:id diff --git a/api/http/handler/tags/tag_list.go b/api/http/handler/tags/tag_list.go index b572add68..a19aa48e7 100644 --- a/api/http/handler/tags/tag_list.go +++ b/api/http/handler/tags/tag_list.go @@ -3,8 +3,8 @@ package tags import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" ) // GET request on /api/tags diff --git a/api/http/handler/teammemberships/handler.go b/api/http/handler/teammemberships/handler.go index c50773a85..10cd63a83 100644 --- a/api/http/handler/teammemberships/handler.go +++ b/api/http/handler/teammemberships/handler.go @@ -1,8 +1,8 @@ package teammemberships import ( + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" "net/http" diff --git a/api/http/handler/teammemberships/teammembership_create.go b/api/http/handler/teammemberships/teammembership_create.go index 49783d767..37216df6a 100644 --- a/api/http/handler/teammemberships/teammembership_create.go +++ b/api/http/handler/teammemberships/teammembership_create.go @@ -3,10 +3,10 @@ package teammemberships import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teammemberships/teammembership_delete.go b/api/http/handler/teammemberships/teammembership_delete.go index a1263745f..846fb7892 100644 --- a/api/http/handler/teammemberships/teammembership_delete.go +++ b/api/http/handler/teammemberships/teammembership_delete.go @@ -3,10 +3,10 @@ package teammemberships import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teammemberships/teammembership_list.go b/api/http/handler/teammemberships/teammembership_list.go index 0f9267a10..4136053d6 100644 --- a/api/http/handler/teammemberships/teammembership_list.go +++ b/api/http/handler/teammemberships/teammembership_list.go @@ -3,9 +3,9 @@ package teammemberships import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teammemberships/teammembership_update.go b/api/http/handler/teammemberships/teammembership_update.go index 6d08bc90a..0e400e975 100644 --- a/api/http/handler/teammemberships/teammembership_update.go +++ b/api/http/handler/teammemberships/teammembership_update.go @@ -3,10 +3,10 @@ package teammemberships import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teams/handler.go b/api/http/handler/teams/handler.go index 2b8cd7c3b..946a071d7 100644 --- a/api/http/handler/teams/handler.go +++ b/api/http/handler/teams/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teams/team_create.go b/api/http/handler/teams/team_create.go index d865e56c5..16b230ec4 100644 --- a/api/http/handler/teams/team_create.go +++ b/api/http/handler/teams/team_create.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type teamCreatePayload struct { diff --git a/api/http/handler/teams/team_delete.go b/api/http/handler/teams/team_delete.go index 623c29c4c..00146d698 100644 --- a/api/http/handler/teams/team_delete.go +++ b/api/http/handler/teams/team_delete.go @@ -3,10 +3,10 @@ package teams import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/teams/:id diff --git a/api/http/handler/teams/team_inspect.go b/api/http/handler/teams/team_inspect.go index 4030a391e..b8aa83da1 100644 --- a/api/http/handler/teams/team_inspect.go +++ b/api/http/handler/teams/team_inspect.go @@ -3,10 +3,10 @@ package teams import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teams/team_list.go b/api/http/handler/teams/team_list.go index 7c4268e13..4acc5eabb 100644 --- a/api/http/handler/teams/team_list.go +++ b/api/http/handler/teams/team_list.go @@ -3,8 +3,8 @@ package teams import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teams/team_memberships.go b/api/http/handler/teams/team_memberships.go index d09abe7d5..450458e35 100644 --- a/api/http/handler/teams/team_memberships.go +++ b/api/http/handler/teams/team_memberships.go @@ -3,10 +3,10 @@ package teams import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/teams/team_update.go b/api/http/handler/teams/team_update.go index 8c0961c31..dea180b6a 100644 --- a/api/http/handler/teams/team_update.go +++ b/api/http/handler/teams/team_update.go @@ -3,10 +3,10 @@ package teams import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type teamUpdatePayload struct { diff --git a/api/http/handler/templates/handler.go b/api/http/handler/templates/handler.go index c193b1def..660991d8d 100644 --- a/api/http/handler/templates/handler.go +++ b/api/http/handler/templates/handler.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/templates/template_create.go b/api/http/handler/templates/template_create.go index f27cd494a..ef9c0c58f 100644 --- a/api/http/handler/templates/template_create.go +++ b/api/http/handler/templates/template_create.go @@ -4,11 +4,11 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/filesystem" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type templateCreatePayload struct { diff --git a/api/http/handler/templates/template_delete.go b/api/http/handler/templates/template_delete.go index c23c2d237..f5793bb8c 100644 --- a/api/http/handler/templates/template_delete.go +++ b/api/http/handler/templates/template_delete.go @@ -3,10 +3,10 @@ package templates import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/templates/:id diff --git a/api/http/handler/templates/template_inspect.go b/api/http/handler/templates/template_inspect.go index 6b1b4c6a1..70b6ada02 100644 --- a/api/http/handler/templates/template_inspect.go +++ b/api/http/handler/templates/template_inspect.go @@ -3,10 +3,10 @@ package templates import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // GET request on /api/templates/:id diff --git a/api/http/handler/templates/template_list.go b/api/http/handler/templates/template_list.go index cd312685d..24ca93bbd 100644 --- a/api/http/handler/templates/template_list.go +++ b/api/http/handler/templates/template_list.go @@ -4,10 +4,10 @@ import ( "encoding/json" "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" "github.com/portainer/portainer/http/client" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/templates/template_update.go b/api/http/handler/templates/template_update.go index 2eff9701f..123ea0f6a 100644 --- a/api/http/handler/templates/template_update.go +++ b/api/http/handler/templates/template_update.go @@ -3,10 +3,10 @@ package templates import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type templateUpdatePayload struct { diff --git a/api/http/handler/upload/handler.go b/api/http/handler/upload/handler.go index 6ce36df77..b0068979b 100644 --- a/api/http/handler/upload/handler.go +++ b/api/http/handler/upload/handler.go @@ -1,8 +1,8 @@ package upload import ( + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" "net/http" diff --git a/api/http/handler/upload/upload_tls.go b/api/http/handler/upload/upload_tls.go index aebab6813..964a7d29b 100644 --- a/api/http/handler/upload/upload_tls.go +++ b/api/http/handler/upload/upload_tls.go @@ -3,10 +3,10 @@ package upload import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // POST request on /api/upload/tls/{certificate:(?:ca|cert|key)}?folder= @@ -21,7 +21,7 @@ func (handler *Handler) uploadTLS(w http.ResponseWriter, r *http.Request) *httpe return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: folder", err} } - file, err := request.RetrieveMultiPartFormFile(r, "file") + file, _, err := request.RetrieveMultiPartFormFile(r, "file") if err != nil { return &httperror.HandlerError{http.StatusBadRequest, "Invalid certificate file. Ensure that the certificate file is uploaded correctly", err} } diff --git a/api/http/handler/users/admin_check.go b/api/http/handler/users/admin_check.go index 4d7ba233a..5fcd1d33d 100644 --- a/api/http/handler/users/admin_check.go +++ b/api/http/handler/users/admin_check.go @@ -3,9 +3,9 @@ package users import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" ) // GET request on /api/users/admin/check diff --git a/api/http/handler/users/admin_init.go b/api/http/handler/users/admin_init.go index f44eb43ca..fb2172686 100644 --- a/api/http/handler/users/admin_init.go +++ b/api/http/handler/users/admin_init.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type adminInitPayload struct { diff --git a/api/http/handler/users/handler.go b/api/http/handler/users/handler.go index 4a0dd2df2..ae43ef521 100644 --- a/api/http/handler/users/handler.go +++ b/api/http/handler/users/handler.go @@ -1,8 +1,8 @@ package users import ( + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" "net/http" diff --git a/api/http/handler/users/user_create.go b/api/http/handler/users/user_create.go index 9fb4cddda..7948ec2b6 100644 --- a/api/http/handler/users/user_create.go +++ b/api/http/handler/users/user_create.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/users/user_delete.go b/api/http/handler/users/user_delete.go index 90a5b52cc..1c500bfc1 100644 --- a/api/http/handler/users/user_delete.go +++ b/api/http/handler/users/user_delete.go @@ -3,10 +3,10 @@ package users import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/users/user_inspect.go b/api/http/handler/users/user_inspect.go index 9583c833c..a2e185bdf 100644 --- a/api/http/handler/users/user_inspect.go +++ b/api/http/handler/users/user_inspect.go @@ -3,10 +3,10 @@ package users import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // GET request on /api/users/:id diff --git a/api/http/handler/users/user_list.go b/api/http/handler/users/user_list.go index 945e85f10..9b46ff5eb 100644 --- a/api/http/handler/users/user_list.go +++ b/api/http/handler/users/user_list.go @@ -3,8 +3,8 @@ package users import ( "net/http" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/response" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/users/user_memberships.go b/api/http/handler/users/user_memberships.go index dfbb355ab..2ba837aec 100644 --- a/api/http/handler/users/user_memberships.go +++ b/api/http/handler/users/user_memberships.go @@ -3,10 +3,10 @@ package users import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/users/user_update.go b/api/http/handler/users/user_update.go index a8b6c8b1a..4ae5e50ab 100644 --- a/api/http/handler/users/user_update.go +++ b/api/http/handler/users/user_update.go @@ -3,10 +3,10 @@ package users import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/users/user_update_password.go b/api/http/handler/users/user_update_password.go index f810f0888..e5a65c8e3 100644 --- a/api/http/handler/users/user_update_password.go +++ b/api/http/handler/users/user_update_password.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/webhooks/handler.go b/api/http/handler/webhooks/handler.go index fe60f3fdb..d9b583535 100644 --- a/api/http/handler/webhooks/handler.go +++ b/api/http/handler/webhooks/handler.go @@ -4,9 +4,9 @@ import ( "net/http" "github.com/gorilla/mux" + httperror "github.com/portainer/libhttp/error" portainer "github.com/portainer/portainer" "github.com/portainer/portainer/docker" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/webhooks/webhook_create.go b/api/http/handler/webhooks/webhook_create.go index c74db0c6e..288435889 100644 --- a/api/http/handler/webhooks/webhook_create.go +++ b/api/http/handler/webhooks/webhook_create.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/asaskevich/govalidator" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" "github.com/satori/go.uuid" ) diff --git a/api/http/handler/webhooks/webhook_delete.go b/api/http/handler/webhooks/webhook_delete.go index b81a445e1..6df0e4156 100644 --- a/api/http/handler/webhooks/webhook_delete.go +++ b/api/http/handler/webhooks/webhook_delete.go @@ -3,10 +3,10 @@ package webhooks import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // DELETE request on /api/webhook/:serviceID diff --git a/api/http/handler/webhooks/webhook_execute.go b/api/http/handler/webhooks/webhook_execute.go index 395be9999..f43045899 100644 --- a/api/http/handler/webhooks/webhook_execute.go +++ b/api/http/handler/webhooks/webhook_execute.go @@ -6,10 +6,10 @@ import ( "strings" dockertypes "github.com/docker/docker/api/types" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) // Acts on a passed in token UUID to restart the docker service diff --git a/api/http/handler/webhooks/webhook_list.go b/api/http/handler/webhooks/webhook_list.go index 288bdcd65..7ed0df213 100644 --- a/api/http/handler/webhooks/webhook_list.go +++ b/api/http/handler/webhooks/webhook_list.go @@ -3,10 +3,10 @@ package webhooks import ( "net/http" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" + "github.com/portainer/libhttp/response" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" - "github.com/portainer/portainer/http/response" ) type webhookListOperationFilters struct { diff --git a/api/http/handler/websocket/handler.go b/api/http/handler/websocket/handler.go index 20364f52b..3de1cbc60 100644 --- a/api/http/handler/websocket/handler.go +++ b/api/http/handler/websocket/handler.go @@ -3,8 +3,8 @@ package websocket import ( "github.com/gorilla/mux" "github.com/gorilla/websocket" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "github.com/portainer/portainer/http/security" ) diff --git a/api/http/handler/websocket/websocket_exec.go b/api/http/handler/websocket/websocket_exec.go index 58d734072..d797e020a 100644 --- a/api/http/handler/websocket/websocket_exec.go +++ b/api/http/handler/websocket/websocket_exec.go @@ -15,10 +15,10 @@ import ( "github.com/asaskevich/govalidator" "github.com/gorilla/websocket" "github.com/koding/websocketproxy" + httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" "github.com/portainer/portainer" "github.com/portainer/portainer/crypto" - httperror "github.com/portainer/portainer/http/error" - "github.com/portainer/portainer/http/request" ) type webSocketExecRequestParams struct { diff --git a/api/http/proxy/local.go b/api/http/proxy/local.go index 8a7f5842d..7686768ad 100644 --- a/api/http/proxy/local.go +++ b/api/http/proxy/local.go @@ -5,7 +5,7 @@ import ( "log" "net/http" - httperror "github.com/portainer/portainer/http/error" + httperror "github.com/portainer/libhttp/error" ) type localProxy struct { diff --git a/api/http/request/request.go b/api/http/request/request.go deleted file mode 100644 index ee56b0d05..000000000 --- a/api/http/request/request.go +++ /dev/null @@ -1,163 +0,0 @@ -package request - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "strconv" - - "github.com/gorilla/mux" - "github.com/portainer/portainer" -) - -const ( - // ErrInvalidQueryParameter defines an error raised when a mandatory query parameter has an invalid value. - ErrInvalidQueryParameter = portainer.Error("Invalid query parameter") - // errInvalidRequestURL defines an error raised when the data sent in the query or the URL is invalid - errInvalidRequestURL = portainer.Error("Invalid request URL") - // errMissingQueryParameter defines an error raised when a mandatory query parameter is missing. - errMissingQueryParameter = portainer.Error("Missing query parameter") - // errMissingFormDataValue defines an error raised when a mandatory form data value is missing. - errMissingFormDataValue = portainer.Error("Missing form data value") -) - -// PayloadValidation is an interface used to validate the payload of a request. -type PayloadValidation interface { - Validate(request *http.Request) error -} - -// DecodeAndValidateJSONPayload decodes the body of the request into an object -// implementing the PayloadValidation interface. -// It also triggers a validation of object content. -func DecodeAndValidateJSONPayload(request *http.Request, v PayloadValidation) error { - if err := json.NewDecoder(request.Body).Decode(v); err != nil { - return err - } - return v.Validate(request) -} - -// RetrieveMultiPartFormFile returns the content of an uploaded file (form data) as bytes. -func RetrieveMultiPartFormFile(request *http.Request, requestParameter string) ([]byte, error) { - file, _, err := request.FormFile(requestParameter) - if err != nil { - return nil, err - } - defer file.Close() - - fileContent, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - return fileContent, nil -} - -// RetrieveMultiPartFormJSONValue decodes the value of some form data as a JSON object into the target parameter. -// If optional is set to true, will not return an error when the form data value is not found. -func RetrieveMultiPartFormJSONValue(request *http.Request, name string, target interface{}, optional bool) error { - value, err := RetrieveMultiPartFormValue(request, name, optional) - if err != nil { - return err - } - if value == "" { - return nil - } - return json.Unmarshal([]byte(value), target) -} - -// RetrieveMultiPartFormValue returns the value of some form data as a string. -// If optional is set to true, will not return an error when the form data value is not found. -func RetrieveMultiPartFormValue(request *http.Request, name string, optional bool) (string, error) { - value := request.FormValue(name) - if value == "" && !optional { - return "", errMissingFormDataValue - } - return value, nil -} - -// RetrieveNumericMultiPartFormValue returns the value of some form data as an integer. -// If optional is set to true, will not return an error when the form data value is not found. -func RetrieveNumericMultiPartFormValue(request *http.Request, name string, optional bool) (int, error) { - value, err := RetrieveMultiPartFormValue(request, name, optional) - if err != nil { - return 0, err - } - return strconv.Atoi(value) -} - -// RetrieveBooleanMultiPartFormValue returns the value of some form data as a boolean. -// If optional is set to true, will not return an error when the form data value is not found. -func RetrieveBooleanMultiPartFormValue(request *http.Request, name string, optional bool) (bool, error) { - value, err := RetrieveMultiPartFormValue(request, name, optional) - if err != nil { - return false, err - } - return value == "true", nil -} - -// RetrieveRouteVariableValue returns the value of a route variable as a string. -func RetrieveRouteVariableValue(request *http.Request, name string) (string, error) { - routeVariables := mux.Vars(request) - if routeVariables == nil { - return "", errInvalidRequestURL - } - routeVar := routeVariables[name] - if routeVar == "" { - return "", errInvalidRequestURL - } - return routeVar, nil -} - -// RetrieveNumericRouteVariableValue returns the value of a route variable as an integer. -func RetrieveNumericRouteVariableValue(request *http.Request, name string) (int, error) { - routeVar, err := RetrieveRouteVariableValue(request, name) - if err != nil { - return 0, err - } - return strconv.Atoi(routeVar) -} - -// RetrieveQueryParameter returns the value of a query parameter as a string. -// If optional is set to true, will not return an error when the query parameter is not found. -func RetrieveQueryParameter(request *http.Request, name string, optional bool) (string, error) { - queryParameter := request.FormValue(name) - if queryParameter == "" && !optional { - return "", errMissingQueryParameter - } - return queryParameter, nil -} - -// RetrieveNumericQueryParameter returns the value of a query parameter as an integer. -// If optional is set to true, will not return an error when the query parameter is not found. -func RetrieveNumericQueryParameter(request *http.Request, name string, optional bool) (int, error) { - queryParameter, err := RetrieveQueryParameter(request, name, optional) - if err != nil { - return 0, err - } - if queryParameter == "" && optional { - return 0, nil - } - return strconv.Atoi(queryParameter) -} - -// RetrieveBooleanQueryParameter returns the value of a query parameter as a boolean. -// If optional is set to true, will not return an error when the query parameter is not found. -func RetrieveBooleanQueryParameter(request *http.Request, name string, optional bool) (bool, error) { - queryParameter, err := RetrieveQueryParameter(request, name, optional) - if err != nil { - return false, err - } - return queryParameter == "true", nil -} - -// RetrieveJSONQueryParameter decodes the value of a query paramater as a JSON object into the target parameter. -// If optional is set to true, will not return an error when the query parameter is not found. -func RetrieveJSONQueryParameter(request *http.Request, name string, target interface{}, optional bool) error { - queryParameter, err := RetrieveQueryParameter(request, name, optional) - if err != nil { - return err - } - if queryParameter == "" { - return nil - } - return json.Unmarshal([]byte(queryParameter), target) -} diff --git a/api/http/response/response.go b/api/http/response/response.go deleted file mode 100644 index 2451abd24..000000000 --- a/api/http/response/response.go +++ /dev/null @@ -1,25 +0,0 @@ -package response - -import ( - "encoding/json" - "net/http" - - httperror "github.com/portainer/portainer/http/error" -) - -// JSON encodes data to rw in JSON format. Returns a pointer to a -// HandlerError if encoding fails. -func JSON(rw http.ResponseWriter, data interface{}) *httperror.HandlerError { - rw.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(rw).Encode(data) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to write JSON response", err} - } - return nil -} - -// Empty merely sets the response code to NoContent (204). -func Empty(rw http.ResponseWriter) *httperror.HandlerError { - rw.WriteHeader(http.StatusNoContent) - return nil -} diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go index 5aad463f1..0b25bd389 100644 --- a/api/http/security/bouncer.go +++ b/api/http/security/bouncer.go @@ -1,8 +1,8 @@ package security import ( + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" "net/http" "strings" diff --git a/api/http/security/rate_limiter.go b/api/http/security/rate_limiter.go index 27ab2523a..307329723 100644 --- a/api/http/security/rate_limiter.go +++ b/api/http/security/rate_limiter.go @@ -6,8 +6,8 @@ import ( "time" "github.com/g07cha/defender" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer" - httperror "github.com/portainer/portainer/http/error" ) // RateLimiter represents an entity that manages request rate limiting From 77913543b1b00e57878acf1aa01b734f5fb5414c Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 15 Sep 2018 09:53:35 +0800 Subject: [PATCH 54/57] feat(container-details): update container-restart-policy component (#2273) --- .../container-restart-policy-controller.js | 52 ++++++++----------- .../container-restart-policy.html | 27 ++-------- .../views/containers/edit/container.html | 2 +- .../containers/edit/containerController.js | 1 + 4 files changed, 27 insertions(+), 55 deletions(-) diff --git a/app/docker/components/container-restart-policy/container-restart-policy-controller.js b/app/docker/components/container-restart-policy/container-restart-policy-controller.js index c9ece16d4..fd96a1596 100644 --- a/app/docker/components/container-restart-policy/container-restart-policy-controller.js +++ b/app/docker/components/container-restart-policy/container-restart-policy-controller.js @@ -1,36 +1,26 @@ angular - .module('portainer.docker') - .controller('ContainerRestartPolicyController', [ - function ContainerRestartPolicyController() { - var ctrl = this; +.module('portainer.docker') +.controller('ContainerRestartPolicyController', [function ContainerRestartPolicyController() { + var ctrl = this; - this.state = { - editMode :false, - editModel :{} - }; - + this.state = { + editModel : {} + }; - ctrl.toggleEdit = toggleEdit; - ctrl.save = save; + ctrl.save = save; - function toggleEdit() { - ctrl.state.editMode = true; - ctrl.state.editModel = { - name: ctrl.name, - maximumRetryCount: ctrl.maximumRetryCount - }; - } - - function save() { - if (ctrl.state.editModel.name === ctrl.name && - ctrl.state.editModel.maximumRetryCount === ctrl.maximumRetryCount) { - ctrl.state.editMode = false; - return; - } - ctrl.updateRestartPolicy(ctrl.state.editModel) - .then(function onUpdateSucceed() { - ctrl.state.editMode = false; - }); - } + function save() { + if (ctrl.state.editModel.name === ctrl.name && ctrl.state.editModel.maximumRetryCount === ctrl.maximumRetryCount) { + return; } - ]); + ctrl.updateRestartPolicy(ctrl.state.editModel); + } + + this.$onInit = function() { + ctrl.state.editModel = { + name: ctrl.name, + maximumRetryCount: ctrl.maximumRetryCount + }; + }; +} +]); diff --git a/app/docker/components/container-restart-policy/container-restart-policy.html b/app/docker/components/container-restart-policy/container-restart-policy.html index c5352b6e4..41b87c2fc 100644 --- a/app/docker/components/container-restart-policy/container-restart-policy.html +++ b/app/docker/components/container-restart-policy/container-restart-policy.html @@ -1,23 +1,5 @@
    - - - - - - - - - - -
    - - - - Name - {{$ctrl.name }}
    Maximum Retry Count - {{ $ctrl.maximumRetryCount }} -
    - +
    - -
    Name @@ -31,15 +13,14 @@ - +
    Maximum Retry Count +
    -
    \ No newline at end of file + diff --git a/app/docker/views/containers/edit/container.html b/app/docker/views/containers/edit/container.html index 92815f9c0..a251ae2d2 100644 --- a/app/docker/views/containers/edit/container.html +++ b/app/docker/views/containers/edit/container.html @@ -226,7 +226,7 @@
    Restart policies - diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index e7b1a988b..f687b116c 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -323,6 +323,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co Name: restartPolicy, MaximumRetryCount: maximumRetryCount }; + Notifications.success('Restart policy updated'); } function notifyOnError(err) { From bab02f2b91b54b9b5651f2031a7e037e069dc872 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 15 Sep 2018 10:19:51 +0800 Subject: [PATCH 55/57] fix(container-details): update container restart policy init --- .../container-restart-policy-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/docker/components/container-restart-policy/container-restart-policy-controller.js b/app/docker/components/container-restart-policy/container-restart-policy-controller.js index fd96a1596..64a8604fc 100644 --- a/app/docker/components/container-restart-policy/container-restart-policy-controller.js +++ b/app/docker/components/container-restart-policy/container-restart-policy-controller.js @@ -18,7 +18,7 @@ angular this.$onInit = function() { ctrl.state.editModel = { - name: ctrl.name, + name: ctrl.name ? ctrl.name : 'no', maximumRetryCount: ctrl.maximumRetryCount }; }; From e2258f98cc0f787a6d8cfd28c9129d7569f43beb Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 15 Sep 2018 10:33:33 +0800 Subject: [PATCH 56/57] fix(services): only display logs action when container has ID in agent proxy mode --- .../service-tasks-datatable/serviceTasksDatatable.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html index 433be02d8..cfba0524f 100644 --- a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html +++ b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html @@ -65,7 +65,7 @@
    - +
    From 57bd82ba8568166bf8eadfc06eeb8563ae96779c Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sat, 15 Sep 2018 16:40:26 +0800 Subject: [PATCH 57/57] chore(version): bump version number --- api/bolt/migrator/migrator.go | 2 +- api/portainer.go | 2 +- api/swagger.yaml | 4 ++-- distribution/portainer.spec | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go index b70172256..4d05820aa 100644 --- a/api/bolt/migrator/migrator.go +++ b/api/bolt/migrator/migrator.go @@ -178,7 +178,7 @@ func (m *Migrator) Migrate() error { } } - // 1.19.2-dev + // Portainer 1.19.2 if m.currentDBVersion < 14 { err := m.updateResourceControlsToDBVersion14() if err != nil { diff --git a/api/portainer.go b/api/portainer.go index 839abea7a..6367b14e0 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -639,7 +639,7 @@ type ( const ( // APIVersion is the version number of the Portainer API - APIVersion = "1.19.2-dev" + APIVersion = "1.19.2" // DBVersion is the version number of the Portainer database DBVersion = 14 // MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved diff --git a/api/swagger.yaml b/api/swagger.yaml index 17785d43e..890c5fc2a 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -54,7 +54,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.19.2-dev" + version: "1.19.2" title: "Portainer API" contact: email: "info@portainer.io" @@ -2816,7 +2816,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.19.2-dev" + example: "1.19.2" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" diff --git a/distribution/portainer.spec b/distribution/portainer.spec index c62ed0b72..af2337e91 100644 --- a/distribution/portainer.spec +++ b/distribution/portainer.spec @@ -1,5 +1,5 @@ Name: portainer -Version: 1.19.2-dev +Version: 1.19.2 Release: 0 License: Zlib Summary: A lightweight docker management UI diff --git a/package.json b/package.json index b908a30b3..ffe21637b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "1.19.2-dev", + "version": "1.19.2", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git"