From f2cd33e831780dd81c2610193cd116613afb5a7a Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 20 Dec 2018 17:37:35 +0200 Subject: [PATCH 01/28] feat(container-creation): call stopAndRename after pullImage (#2564) * refactor(container): remove bind of function --- .../views/containers/create/createContainerController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 2498c4245..44f3af9dd 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -659,7 +659,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } $scope.state.actionInProgress = true; return pullImageIfNeeded() - .then(stopAndRenameContainer(oldContainer)) + .then(stopAndRenameContainer) .then(createNewContainer) .then(applyResourceControl) .then(connectToExtraNetworks) @@ -695,7 +695,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } } - function stopAndRenameContainer(oldContainer) { + function stopAndRenameContainer() { if (!oldContainer) { return $q.when(); } From 463b379876014041c5f2b2c1382f19d827a53d75 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 27 Dec 2018 09:03:13 +0100 Subject: [PATCH 02/28] docs(README): remove broken badges and links --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index cee536856..3a8035de2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ [![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/stable/?badge=stable) [![Build Status](https://semaphoreci.com/api/v1/portainer/portainer-ci/branches/develop/badge.svg)](https://semaphoreci.com/portainer/portainer-ci) [![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) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6) @@ -20,8 +19,6 @@ ## Demo - - You can try out the public demo instance: http://demo.portainer.io/ (login with the username **admin** and the password **tryportainer**). Please note that the public demo cluster is **reset every 15min**. From dedc02cc8dc5c67aba7f3862c5a78263e315c56d Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 14 Jan 2019 16:50:53 +1300 Subject: [PATCH 03/28] docs(api): fix invalid example value for AutoCreateUsers property (#2618) --- api/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/swagger.yaml b/api/swagger.yaml index d8a3ad48a..bd9965230 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -3146,7 +3146,7 @@ definitions: $ref: "#/definitions/LDAPGroupSearchSettings" AutoCreateUsers: type: "boolean" - example: "true" + example: true description: "Automatically provision users and assign them to matching LDAP group names" Settings: From bed49c37e401ab9e23aa1208d071457b1c00a546 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Mon, 14 Jan 2019 17:27:55 +1300 Subject: [PATCH 04/28] fix(teams): remove name sanitization when creating a team (#2619) --- app/portainer/views/teams/teamsController.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/portainer/views/teams/teamsController.js b/app/portainer/views/teams/teamsController.js index 6c01427ac..9b63b234e 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', '$sanitize', 'TeamService', 'UserService', 'ModalService', 'Notifications', 'Authentication', -function ($q, $scope, $state, $sanitize, TeamService, UserService, ModalService, Notifications, Authentication) { +.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'ModalService', 'Notifications', 'Authentication', +function ($q, $scope, $state, TeamService, UserService, ModalService, Notifications, Authentication) { $scope.state = { actionInProgress: false }; @@ -22,7 +22,7 @@ function ($q, $scope, $state, $sanitize, TeamService, UserService, ModalService, }; $scope.addTeam = function() { - var teamName = $sanitize($scope.formValues.Name); + var teamName = $scope.formValues.Name; var leaderIds = []; angular.forEach($scope.formValues.Leaders, function(user) { leaderIds.push(user.Id); From 3a3577754ef6ab9e821fa57823f6714ace11bd83 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 15 Jan 2019 08:52:26 +1300 Subject: [PATCH 05/28] fix(home): only display group name if available (#2621) --- .../components/endpoint-list/endpoint-item/endpointItem.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html index a2f96a392..a44f84163 100644 --- a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html +++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html @@ -23,11 +23,11 @@ - + Group: {{ $ctrl.model.GroupName }} From 34667bd3b3ae94b88b247c30f72c1870846278f7 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 15 Jan 2019 12:10:29 +1300 Subject: [PATCH 06/28] fix(network-creation): force overlay network creation on manager node (#2622) * fix(network-creation): force overlay network creation on manager node * fix(app): fix function override * fix(app): use portainerAgentManagerOperation in interceptor --- app/app.js | 2 +- app/config.js | 3 +++ .../views/networks/create/createNetworkController.js | 8 +++++++- app/portainer/services/httpRequestHelper.js | 12 +++++++++++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/app.js b/app/app.js index 021f4d58a..8d6c669b1 100644 --- a/app/app.js +++ b/app/app.js @@ -30,7 +30,7 @@ function ($rootScope, $state, Authentication, authManager, StateManager, Endpoin }; $transitions.onBefore({ to: 'docker.**' }, function() { - HttpRequestHelper.resetAgentTargetQueue(); + HttpRequestHelper.resetAgentHeaders(); }); }]); diff --git a/app/config.js b/app/config.js index cd21e1f08..268b39288 100644 --- a/app/config.js +++ b/app/config.js @@ -27,6 +27,9 @@ angular.module('portainer') request: function(config) { if (config.url.indexOf('/docker/') > -1) { config.headers['X-PortainerAgent-Target'] = HttpRequestHelper.portainerAgentTargetHeader(); + if (HttpRequestHelper.portainerAgentManagerOperation()) { + config.headers['X-PortainerAgent-ManagerOperation'] = '1'; + } } return config; } diff --git a/app/docker/views/networks/create/createNetworkController.js b/app/docker/views/networks/create/createNetworkController.js index a2893f353..eb92079f3 100644 --- a/app/docker/views/networks/create/createNetworkController.js +++ b/app/docker/views/networks/create/createNetworkController.js @@ -126,6 +126,7 @@ angular.module('portainer.docker') function createNetwork(context) { HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName); + HttpRequestHelper.setPortainerAgentManagerOperation(context.managerOperation); $scope.state.actionInProgress = true; NetworkService.create(context.networkConfiguration) @@ -162,12 +163,17 @@ angular.module('portainer.docker') var creationContext = { nodeName: $scope.formValues.NodeName, + managerOperation: false, networkConfiguration: networkConfiguration, userDetails: userDetails, accessControlData: accessControlData, reload: true }; + if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && $scope.config.Driver === 'overlay') { + creationContext.managerOperation = true; + } + if ($scope.config.Driver === 'macvlan') { if ($scope.formValues.Macvlan.Scope === 'local') { modifyNetworkConfigurationForMacvlanConfigOnly(networkConfiguration); @@ -205,4 +211,4 @@ angular.module('portainer.docker') initView(); } - ]); \ No newline at end of file + ]); diff --git a/app/portainer/services/httpRequestHelper.js b/app/portainer/services/httpRequestHelper.js index 1d5b5dbe8..d63e2fe2f 100644 --- a/app/portainer/services/httpRequestHelper.js +++ b/app/portainer/services/httpRequestHelper.js @@ -5,6 +5,7 @@ angular.module('portainer.app') var service = {}; var headers = {}; headers.agentTargetQueue = []; + headers.agentManagerOperation = false; service.registryAuthenticationHeader = function() { return headers.registryAuthentication; @@ -36,9 +37,18 @@ angular.module('portainer.app') } }; - service.resetAgentTargetQueue = function() { + service.setPortainerAgentManagerOperation = function(set) { + headers.agentManagerOperation = set; + }; + + service.portainerAgentManagerOperation = function() { + return headers.agentManagerOperation; + }; + + service.resetAgentHeaders = function() { headers.agentTargetQueue = []; delete headers.agentTargetLastValue; + headers.agentManagerOperation = false; }; return service; From c7983d899361a661270c4ec49bbc485a56f7d32c Mon Sep 17 00:00:00 2001 From: baron_l Date: Tue, 15 Jan 2019 19:58:35 +0100 Subject: [PATCH 07/28] fix(app): remove endpoint status update on 502/503 http return * refactor(app): removing unused dep and function --- app/portainer/interceptors/endpointStatusInterceptor.js | 6 +----- app/portainer/services/endpointProvider.js | 4 ---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/app/portainer/interceptors/endpointStatusInterceptor.js b/app/portainer/interceptors/endpointStatusInterceptor.js index 9411f8d62..5acc075f1 100644 --- a/app/portainer/interceptors/endpointStatusInterceptor.js +++ b/app/portainer/interceptors/endpointStatusInterceptor.js @@ -1,5 +1,5 @@ angular.module('portainer.app') - .factory('EndpointStatusInterceptor', ['$q', '$injector', 'EndpointProvider', function ($q, $injector, EndpointProvider) { + .factory('EndpointStatusInterceptor', ['$q', 'EndpointProvider', function ($q, EndpointProvider) { 'use strict'; var interceptor = {}; @@ -18,21 +18,17 @@ angular.module('portainer.app') } function responseInterceptor(response) { - var EndpointService = $injector.get('EndpointService'); var url = response.config.url; if (response.status === 200 && canBeOffline(url) && EndpointProvider.offlineMode()) { EndpointProvider.setOfflineMode(false); - EndpointService.updateEndpoint(EndpointProvider.endpointID(), {Status: EndpointProvider.endpointStatusFromOfflineMode(false)}); } return response || $q.when(response); } function responseErrorInterceptor(rejection) { - var EndpointService = $injector.get('EndpointService'); var url = rejection.config.url; if ((rejection.status === 502 || rejection.status === 503 || rejection.status === -1) && canBeOffline(url) && !EndpointProvider.offlineMode()) { EndpointProvider.setOfflineMode(true); - EndpointService.updateEndpoint(EndpointProvider.endpointID(), {Status: EndpointProvider.endpointStatusFromOfflineMode(true)}); } return $q.reject(rejection); } diff --git a/app/portainer/services/endpointProvider.js b/app/portainer/services/endpointProvider.js index ebd89497e..72e38d9f6 100644 --- a/app/portainer/services/endpointProvider.js +++ b/app/portainer/services/endpointProvider.js @@ -64,10 +64,6 @@ angular.module('portainer.app') return endpoint.OfflineMode; }; - service.endpointStatusFromOfflineMode = function(isOffline) { - return isOffline ? 2 : 1; - }; - service.setOfflineMode = function(isOffline) { endpoint.OfflineMode = isOffline; LocalStorage.storeOfflineMode(isOffline); From d6aafceba81c6b5a656895d1eff8bd136bd57b14 Mon Sep 17 00:00:00 2001 From: Mark Stansberry Date: Tue, 15 Jan 2019 14:04:47 -0500 Subject: [PATCH 08/28] docs(api): update swagger definitions --- api/swagger.yaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/api/swagger.yaml b/api/swagger.yaml index bd9965230..fc3ddf9e3 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -525,7 +525,7 @@ paths: **Access policy**: administrator operationId: "EndpointJob" consumes: - - "application/json" + - "multipart/form-data" produces: - "application/json" security: @@ -1434,7 +1434,7 @@ paths: **Access policy**: restricted operationId: "StackCreate" consumes: - - "application/json" + - "multipart/form-data" produces: - "application/json" security: @@ -2733,7 +2733,7 @@ paths: - "application/json" security: - jwt: [] - parameters: + parameters: [] responses: 200: description: "Success" @@ -3606,6 +3606,7 @@ definitions: - "Authentication" - "Name" - "Password" + - "Type" - "URL" - "Username" properties: @@ -3613,6 +3614,10 @@ definitions: type: "string" example: "my-registry" description: "Name that will be used to identify this registry" + Type: + type: "integer" + example: 1 + description: "Registry Type. Valid values are: 1 (Quay.io), 2 (Azure container registry) or 3 (custom registry)" URL: type: "string" example: "registry.mydomain.tld:2375" @@ -4037,7 +4042,7 @@ definitions: description: "A list of categories associated to the template" items: type: "string" - exampe: "database" + example: "database" registry: type: "string" example: "quay.io" @@ -4133,7 +4138,7 @@ definitions: description: "A list of categories associated to the template" items: type: "string" - exampe: "database" + example: "database" registry: type: "string" example: "quay.io" @@ -4233,7 +4238,7 @@ definitions: description: "A list of categories associated to the template" items: type: "string" - exampe: "database" + example: "database" registry: type: "string" example: "quay.io" From 42365a52b103feab8f5f0361d07264bb240fa898 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Tue, 15 Jan 2019 21:05:55 +0200 Subject: [PATCH 09/28] feat(container-details): change network identifier to name (#2623) --- .../containerNetworksDatatable.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html b/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html index a79fdc698..bb8a5d4c6 100644 --- a/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html +++ b/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html @@ -38,12 +38,12 @@ - {{ key }} + {{ key }} {{ value.IPAddress || '-' }} {{ value.Gateway || '-' }} {{ value.MacAddress || '-' }} - From fe63b4a156ae46a2ae552f5e662bbe7249da6de2 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 16 Jan 2019 02:34:28 +0200 Subject: [PATCH 10/28] fix(container-creation): populate logger config from existing container (#2602) * refactor(container): change map function to lodash * style(container): add semicolon --- .../containers/create/createContainerController.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 44f3af9dd..552d9e65c 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -509,6 +509,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai $scope.fromContainer = fromContainer; $scope.config = ContainerHelper.configFromContainer(fromContainer.Model); loadFromContainerCmd(d); + loadFromContainerLogging(d); loadFromContainerPortBindings(d); loadFromContainerVolumes(d); loadFromContainerNetworkConfig(d); @@ -525,6 +526,17 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai }); } + function loadFromContainerLogging(config) { + var logConfig = config.HostConfig.LogConfig; + $scope.formValues.LogDriverName = logConfig.Type; + $scope.formValues.LogDriverOpts = _.map(logConfig.Config, function (value, name) { + return { + name: name, + value: value + }; + }); + } + function initView() { var nodeName = $transition$.params().nodeName; $scope.formValues.NodeName = nodeName; From 50a3b0820987a58b42a2d0ae26e6711d94b272b1 Mon Sep 17 00:00:00 2001 From: DevHugo Date: Wed, 16 Jan 2019 23:28:40 +0100 Subject: [PATCH 11/28] feat(app): add driver name in the volume selector for container/service creation (#2534) * Feat(containers): add driver name in the volume selector * Feat(services): add driver name in the volume selector --- app/docker/views/containers/create/createcontainer.html | 2 +- app/docker/views/services/create/createservice.html | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index 3f61e44fb..b25f2d318 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -320,7 +320,7 @@ volume diff --git a/app/docker/views/services/create/createservice.html b/app/docker/views/services/create/createservice.html index 2003ff97f..ceca97a55 100644 --- a/app/docker/views/services/create/createservice.html +++ b/app/docker/views/services/create/createservice.html @@ -315,8 +315,9 @@
volume - +
From 50e77d2bf171bc8bcdf95440bf7b3b4c3004afa7 Mon Sep 17 00:00:00 2001 From: baron_l Date: Wed, 16 Jan 2019 23:39:15 +0100 Subject: [PATCH 12/28] fix(network-details): displaying all subnets and gateways on network details (#2629) --- app/docker/views/networks/edit/network.html | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/docker/views/networks/edit/network.html b/app/docker/views/networks/edit/network.html index f22562163..0bad80b2e 100644 --- a/app/docker/views/networks/edit/network.html +++ b/app/docker/views/networks/edit/network.html @@ -39,13 +39,9 @@ Internal {{ network.Internal }} - - Subnet - {{ network.IPAM.Config[0].Subnet }} - - - Gateway - {{ network.IPAM.Config[0].Gateway }} + + Subnet - {{ config.Subnet }} + Gateway - {{ config.Gateway }} From a33eca4bbb906164b1222c30823cfb8161f60146 Mon Sep 17 00:00:00 2001 From: hiyao Date: Fri, 18 Jan 2019 03:01:47 +0800 Subject: [PATCH 13/28] fix(registry-manager): fix an issue when removing all tags of a repository (#2545) * fix repository reload got error in remove tags When I remove all tags, removeTags() will reload and do initView() again, but data.tags response null, that trigger data.tags.length got error. * Revert "fix repository reload got error in remove tags" This reverts commit 5d9b1778ef91aefd7969909d60b68ca55cbcc705. * fix(registry-management): change response repository tags type to array by force * feat(registry-management): redirect to repositories page when no tag in the repository after delete tags --- .../edit/registryRepositoryController.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/extensions/registry-management/views/repositories/edit/registryRepositoryController.js b/app/extensions/registry-management/views/repositories/edit/registryRepositoryController.js index de4521cb1..04c7b0e46 100644 --- a/app/extensions/registry-management/views/repositories/edit/registryRepositoryController.js +++ b/app/extensions/registry-management/views/repositories/edit/registryRepositoryController.js @@ -81,9 +81,17 @@ angular.module('portainer.app') }); return $q.all(promises); }) - .then(function success() { + .then(function success(data) { Notifications.success('Success', 'Tags successfully deleted'); - $state.reload(); + if (data.length === 0) { + $state.go('portainer.registries.registry.repositories', { + id: $scope.registryId + }, { + reload: true + }); + } else { + $state.reload(); + } }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to delete tags'); @@ -127,9 +135,9 @@ angular.module('portainer.app') }) .then(function success(data) { $scope.registry = data.registry; - $scope.repository.Tags = data.tags; + $scope.repository.Tags = [].concat(data.tags || []); $scope.tags = []; - for (var i = 0; i < data.tags.length; i++) { + for (var i = 0; i < $scope.repository.Tags.length; i++) { var tag = data.tags[i]; RegistryV2Service.tag(registryId, repository, tag) .then(function success(data) { From 808eb7d3411d5619dbd77f38bc18707f654cf21d Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 18 Jan 2019 08:51:12 +1300 Subject: [PATCH 14/28] dep(bootstrap): update bootstrap version to 3.4.0 (#2632) --- package.json | 2 +- yarn.lock | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 2fc6e9772..104382c36 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "angularjs-scroll-glue": "^2.2.0", "angularjs-slider": "^6.4.0", "bootbox": "^4.4.0", - "bootstrap": "~3.3.6", + "bootstrap": "^3.4.0", "chart.js": "~2.6.0", "codemirror": "~5.30.0", "filesize": "~3.3.0", diff --git a/yarn.lock b/yarn.lock index 5e094094b..4877533d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -139,7 +139,6 @@ angular-mocks@~1.5.0: angular-moment-picker@^0.10.2: version "0.10.2" resolved "https://registry.yarnpkg.com/angular-moment-picker/-/angular-moment-picker-0.10.2.tgz#54c8b3c228b33dffa3b7b3d0773a585323815c33" - integrity sha512-WvmrQM0zEcFqi50yDELaF34Ilrx4PtL7mWLcpTZCJGQDvMlIsxJrB30LxOkoJv8yrrLxD2s6nnR3t1/SqioWWw== dependencies: angular "^1.3" moment "^2.16.0" @@ -167,7 +166,6 @@ angular@1.x, angular@~1.5.0: angular@^1.3: version "1.7.5" resolved "https://registry.yarnpkg.com/angular/-/angular-1.7.5.tgz#d1c1c01c6f5dc835638f3f9aa51012857bdac49e" - integrity sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw== angularjs-scroll-glue@^2.2.0: version "2.2.0" @@ -407,9 +405,9 @@ bootbox@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/bootbox/-/bootbox-4.4.0.tgz#ff7f898fb87d4527e547feb64158f88450d1a0c9" -bootstrap@~3.3.6: - version "3.3.7" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71" +bootstrap@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.0.tgz#f8d77540dd3062283d2ae7687e21c1e691961640" brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.8" @@ -2921,7 +2919,6 @@ moment@^2.10.6: moment@^2.16.0: version "2.22.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= moment@^2.21.0: version "2.21.0" @@ -4527,7 +4524,6 @@ xtend@~3.0.0: xterm@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.8.0.tgz#55d1de518bdc9c9793823f5e4e97d6898972938d" - integrity sha512-rS3HLryuMWbLsv98+jVVSUXCxmoyXPwqwJNC0ad0VSMdXgl65LefPztQVwfurkaF7kM7ZSgM8eJjnJ9kkdoR1w== yargs@~3.10.0: version "3.10.0" From 62eb47b3cb0b8e95ab687fe7eb5109bcf7cf28e5 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 17 Jan 2019 21:59:43 +0200 Subject: [PATCH 15/28] fix(container-creation): revert container state if creation failed (#2565) * fix(container): rename old container only if exist * fix(container): remove new container only if created * style(container): fix typo Co-Authored-By: chiptus --- .../create/createContainerController.js | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 552d9e65c..cf96c9b3c 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -633,9 +633,9 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai function create() { var oldContainer = null; - HttpRequestHelper.setPortainerAgentTargetHeader($scope.formValues.NodeName); return findCurrentContainer() + .then(setOldContainer) .then(confirmCreateContainer) .then(startCreationProcess) .catch(notifyOnError) @@ -645,6 +645,11 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai $scope.state.actionInProgress = false; } + function setOldContainer(container) { + oldContainer = container; + return container; + } + function findCurrentContainer() { return Container.query({ all: 1, filters: { name: ['^/' + $scope.config.name + '$'] } }) .$promise @@ -652,8 +657,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai if (!containers.length) { return; } - oldContainer = containers[0]; - return oldContainer; + return containers[0]; }) .catch(notifyOnError); @@ -676,7 +680,36 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai .then(applyResourceControl) .then(connectToExtraNetworks) .then(removeOldContainer) - .then(onSuccess); + .then(onSuccess) + .catch(onCreationProcessFail); + } + + function onCreationProcessFail(error) { + var deferred = $q.defer(); + removeNewContainer() + .then(restoreOldContainerName) + .then(function() { + deferred.reject(error); + }) + .catch(function(restoreError) { + deferred.reject(restoreError); + }); + return deferred.promise; + } + + function removeNewContainer() { + return findCurrentContainer().then(function onContainerLoaded(container) { + if (container && (!oldContainer || container.Id !== oldContainer.Id)) { + return ContainerService.remove(container, true); + } + }); + } + + function restoreOldContainerName() { + if (!oldContainer) { + return; + } + return ContainerService.renameContainer(oldContainer.Id, oldContainer.Names[0].substring(1)); } function confirmCreateContainer(container) { From 54163e3b9278f9708ff98d6e89208011c550fa0e Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 18 Jan 2019 10:00:18 +1300 Subject: [PATCH 16/28] fix(extensions): fix an issue with extensions with expired licenses (#2628) * fix(extensions): fix an issue with extensions with expired licenses * fix(api): fix invalid log call * fix(api): allow to re-enable an extension --- api/bolt/datastore.go | 1 + api/bolt/migrator/migrate_dbversion16.go | 19 +++++++++++++++++++ api/bolt/migrator/migrator.go | 11 +++++++++++ api/cmd/portainer/main.go | 5 ++++- api/exec/extension.go | 1 + .../handler/extensions/extension_create.go | 2 +- api/http/handler/extensions/extension_list.go | 1 + api/portainer.go | 3 ++- .../extension-item/extensionItem.html | 8 ++++---- .../extension-item/extensionItemController.js | 7 ------- .../product-item/productItem.html | 10 +--------- .../views/extensions/inspect/extension.html | 4 +++- 12 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 api/bolt/migrator/migrate_dbversion16.go diff --git a/api/bolt/datastore.go b/api/bolt/datastore.go index 0e94ad157..85273c23b 100644 --- a/api/bolt/datastore.go +++ b/api/bolt/datastore.go @@ -139,6 +139,7 @@ func (store *Store) MigrateData() error { DatabaseVersion: version, EndpointGroupService: store.EndpointGroupService, EndpointService: store.EndpointService, + ExtensionService: store.ExtensionService, ResourceControlService: store.ResourceControlService, SettingsService: store.SettingsService, StackService: store.StackService, diff --git a/api/bolt/migrator/migrate_dbversion16.go b/api/bolt/migrator/migrate_dbversion16.go new file mode 100644 index 000000000..4464a87ce --- /dev/null +++ b/api/bolt/migrator/migrate_dbversion16.go @@ -0,0 +1,19 @@ +package migrator + +func (m *Migrator) updateExtensionsToDBVersion17() error { + legacyExtensions, err := m.extensionService.Extensions() + if err != nil { + return err + } + + for _, extension := range legacyExtensions { + extension.License.Valid = true + + err = m.extensionService.Persist(&extension) + if err != nil { + return err + } + } + + return nil +} diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go index 6ec80dcc9..78e452d8a 100644 --- a/api/bolt/migrator/migrator.go +++ b/api/bolt/migrator/migrator.go @@ -5,6 +5,7 @@ import ( "github.com/portainer/portainer" "github.com/portainer/portainer/bolt/endpoint" "github.com/portainer/portainer/bolt/endpointgroup" + "github.com/portainer/portainer/bolt/extension" "github.com/portainer/portainer/bolt/resourcecontrol" "github.com/portainer/portainer/bolt/settings" "github.com/portainer/portainer/bolt/stack" @@ -20,6 +21,7 @@ type ( db *bolt.DB endpointGroupService *endpointgroup.Service endpointService *endpoint.Service + extensionService *extension.Service resourceControlService *resourcecontrol.Service settingsService *settings.Service stackService *stack.Service @@ -35,6 +37,7 @@ type ( DatabaseVersion int EndpointGroupService *endpointgroup.Service EndpointService *endpoint.Service + ExtensionService *extension.Service ResourceControlService *resourcecontrol.Service SettingsService *settings.Service StackService *stack.Service @@ -52,6 +55,7 @@ func NewMigrator(parameters *Parameters) *Migrator { currentDBVersion: parameters.DatabaseVersion, endpointGroupService: parameters.EndpointGroupService, endpointService: parameters.EndpointService, + extensionService: parameters.ExtensionService, resourceControlService: parameters.ResourceControlService, settingsService: parameters.SettingsService, templateService: parameters.TemplateService, @@ -210,5 +214,12 @@ func (m *Migrator) Migrate() error { } } + if m.currentDBVersion < 17 { + err := m.updateExtensionsToDBVersion17() + if err != nil { + return err + } + } + return m.versionService.StoreDBVersion(portainer.DBVersion) } diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index e0a9dd2f9..09e223593 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -486,7 +486,10 @@ func initExtensionManager(fileService portainer.FileService, extensionService po for _, extension := range extensions { err := extensionManager.EnableExtension(&extension, extension.License.LicenseKey) if err != nil { - return nil, err + log.Printf("Unable to enable extension: %s [extension: %s]", err.Error(), extension.Name) + extension.Enabled = false + extension.License.Valid = false + extensionService.Persist(&extension) } } diff --git a/api/exec/extension.go b/api/exec/extension.go index 20cf2eca8..cb58ecad6 100644 --- a/api/exec/extension.go +++ b/api/exec/extension.go @@ -113,6 +113,7 @@ func (manager *ExtensionManager) EnableExtension(extension *portainer.Extension, LicenseKey: licenseKey, Company: licenseDetails[0], Expiration: licenseDetails[1], + Valid: true, } extension.Version = licenseDetails[2] diff --git a/api/http/handler/extensions/extension_create.go b/api/http/handler/extensions/extension_create.go index b0ce72406..22d146f6e 100644 --- a/api/http/handler/extensions/extension_create.go +++ b/api/http/handler/extensions/extension_create.go @@ -42,7 +42,7 @@ func (handler *Handler) extensionCreate(w http.ResponseWriter, r *http.Request) } for _, existingExtension := range extensions { - if existingExtension.ID == extensionID { + if existingExtension.ID == extensionID && existingExtension.Enabled { return &httperror.HandlerError{http.StatusConflict, "Unable to enable extension", portainer.ErrExtensionAlreadyEnabled} } } diff --git a/api/http/handler/extensions/extension_list.go b/api/http/handler/extensions/extension_list.go index 392822528..68d26a7e7 100644 --- a/api/http/handler/extensions/extension_list.go +++ b/api/http/handler/extensions/extension_list.go @@ -42,6 +42,7 @@ func associateExtensionData(definition *portainer.Extension, extensions []portai definition.Enabled = extension.Enabled definition.License.Company = extension.License.Company definition.License.Expiration = extension.License.Expiration + definition.License.Valid = extension.License.Valid definitionVersion := semver.New(definition.Version) extensionVersion := semver.New(extension.Version) diff --git a/api/portainer.go b/api/portainer.go index c8062ed7a..a87d91e6a 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -503,6 +503,7 @@ type ( LicenseKey string `json:"LicenseKey,omitempty"` Company string `json:"Company,omitempty"` Expiration string `json:"Expiration,omitempty"` + Valid bool `json:"Valid,omitempty"` } // CLIService represents a service for managing CLI @@ -780,7 +781,7 @@ const ( // APIVersion is the version number of the Portainer API APIVersion = "1.20.0" // DBVersion is the version number of the Portainer database - DBVersion = 16 + DBVersion = 17 // AssetsServerURL represents the URL of the Portainer asset server AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com" // MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved diff --git a/app/portainer/components/extension-list/extension-item/extensionItem.html b/app/portainer/components/extension-list/extension-item/extensionItem.html index 95acdb9cb..18a41ccc4 100644 --- a/app/portainer/components/extension-list/extension-item/extensionItem.html +++ b/app/portainer/components/extension-list/extension-item/extensionItem.html @@ -21,10 +21,10 @@
coming soon - deal - expired - enabled - update available + deal + expired + enabled + update available diff --git a/app/portainer/components/extension-list/extension-item/extensionItemController.js b/app/portainer/components/extension-list/extension-item/extensionItemController.js index 810e93990..841aadc5a 100644 --- a/app/portainer/components/extension-list/extension-item/extensionItemController.js +++ b/app/portainer/components/extension-list/extension-item/extensionItemController.js @@ -3,7 +3,6 @@ angular.module('portainer.app') function($state) { var ctrl = this; - ctrl.$onInit = $onInit; ctrl.goToExtensionView = goToExtensionView; function goToExtensionView() { @@ -11,10 +10,4 @@ angular.module('portainer.app') $state.go('portainer.extensions.extension', { id: ctrl.model.Id }); } } - - function $onInit() { - if (ctrl.currentDate === ctrl.model.License.Expiration) { - ctrl.model.Expired = true; - } - } }]); diff --git a/app/portainer/components/product-list/product-item/productItem.html b/app/portainer/components/product-list/product-item/productItem.html index fa72a7092..b4ac46d6b 100644 --- a/app/portainer/components/product-list/product-item/productItem.html +++ b/app/portainer/components/product-list/product-item/productItem.html @@ -3,7 +3,7 @@
@@ -15,11 +15,6 @@ {{ $ctrl.model.Name }} - - expired - enabled - update available -
@@ -29,9 +24,6 @@ {{ $ctrl.model.ShortDescription }}
- - Licensed to {{ $ctrl.model.License.Company }} - Expires on {{ $ctrl.model.License.Expiration }} - diff --git a/app/portainer/views/extensions/inspect/extension.html b/app/portainer/views/extensions/inspect/extension.html index 0a83740d9..df59e2966 100644 --- a/app/portainer/views/extensions/inspect/extension.html +++ b/app/portainer/views/extensions/inspect/extension.html @@ -36,7 +36,9 @@
- {{ extension.Enabled ? 'Enabled' : extension.Price }} + Enabled + Expired + {{ extension.Price }}
From c1f2d90997fc92e03ddb49c7424cb384e2a39661 Mon Sep 17 00:00:00 2001 From: baron_l Date: Tue, 22 Jan 2019 21:28:44 +0100 Subject: [PATCH 17/28] fix(container-creation): fix missing capabilities on duplicate (#2635) --- .../containers/create/createContainerController.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index cf96c9b3c..6dd6acd9f 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -493,6 +493,19 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai $scope.formValues.capabilities.push(new ContainerCapability(cap, false)); }); } + + function hasCapability(item) { + return item.capability === cap.capability; + } + + var capabilities = new ContainerCapabilities(); + for (var i = 0; i < capabilities.length; i++) { + var cap = capabilities[i]; + if (!_.find($scope.formValues.capabilities, hasCapability)) { + $scope.formValues.capabilities.push(cap); + } + } + $scope.formValues.capabilities.sort(function(a, b) { return a.capability < b.capability ? -1 : 1; }); From 86c60807cd42a2b49687a23656981a5f6bdb8087 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 23 Jan 2019 12:18:18 +1300 Subject: [PATCH 18/28] feat(endpoint-creation): fix invalid link (#2644) --- app/portainer/views/endpoints/create/createendpoint.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/portainer/views/endpoints/create/createendpoint.html b/app/portainer/views/endpoints/create/createendpoint.html index 4b7a762bd..3563a99b9 100644 --- a/app/portainer/views/endpoints/create/createendpoint.html +++ b/app/portainer/views/endpoints/create/createendpoint.html @@ -67,7 +67,7 @@ Ensure that you have deployed the Portainer agent in your cluster first. You can use execute the following command on any manager node to deploy it.
- curl -L https://portainer.io/download/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent + curl -L https://downloads.portainer.io/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent Copy From 8160fe47171e8397a3047935b5b26e42d1e98e49 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 23 Jan 2019 01:21:48 +0200 Subject: [PATCH 19/28] feat(app): redirect to home if no endpoint is set (#2601) * refactor(stacks): set newstack state as a child state of stacks * fix(docker): add check on docker states for endpoint * refactor(app): remove redirect notification --- app/docker/__module.js | 12 +++++++++++- app/portainer/__module.js | 12 +++++++++++- .../datatables/stacks-datatable/stacksDatatable.html | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/docker/__module.js b/app/docker/__module.js index 9bd99a7cd..d36f0df8c 100644 --- a/app/docker/__module.js +++ b/app/docker/__module.js @@ -5,7 +5,17 @@ angular.module('portainer.docker', ['portainer.app']) var docker = { name: 'docker', parent: 'root', - abstract: true + abstract: true, + resolve: { + endpointID: ['EndpointProvider', '$state', + function (EndpointProvider, $state) { + var id = EndpointProvider.endpointID(); + if (!id) { + return $state.go('portainer.home'); + } + } + ] + } }; var configs = { diff --git a/app/portainer/__module.js b/app/portainer/__module.js index b79553a52..3b29b82ee 100644 --- a/app/portainer/__module.js +++ b/app/portainer/__module.js @@ -327,6 +327,16 @@ angular.module('portainer.app', []) templateUrl: 'app/portainer/views/stacks/stacks.html', controller: 'StacksController' } + }, + resolve: { + endpointID: ['EndpointProvider', '$state', + function (EndpointProvider, $state) { + var id = EndpointProvider.endpointID(); + if (!id) { + return $state.go('portainer.home'); + } + } + ] } }; @@ -342,7 +352,7 @@ angular.module('portainer.app', []) }; var stackCreation = { - name: 'portainer.newstack', + name: 'portainer.stacks.newstack', url: '/newstack', views: { 'content@': { diff --git a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html index dc18ed8e1..b9d499949 100644 --- a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html +++ b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html @@ -11,7 +11,7 @@ ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"> Remove -
From f772cd31cb893f95b63d739b4522ffd29c605002 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 23 Jan 2019 01:22:56 +0200 Subject: [PATCH 20/28] feat(auth): preserve url when redirected to login (#2591) * feat(auth): preserve url when redirected to login * feat(auth): add redirect also to unauthenticated flow * style(app): remove style changes from files * fix(app): remove reference to otpLogin * style(auth): remove semicolon --- app/app.js | 2 +- app/portainer/__module.js | 8 ++++---- app/portainer/views/auth/authController.js | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/app.js b/app/app.js index 8d6c669b1..8a05b219b 100644 --- a/app/app.js +++ b/app/app.js @@ -45,7 +45,7 @@ function initAuthentication(authManager, Authentication, $rootScope, $state) { // to have more controls on which URL should trigger the unauthenticated state. $rootScope.$on('unauthenticated', function (event, data) { if (!_.includes(data.config.url, '/v2/')) { - $state.go('portainer.auth', {error: 'Your session has expired'}); + $state.go('portainer.auth', {error: 'Your session has expired', redirect: $state.current.name}); } }); } diff --git a/app/portainer/__module.js b/app/portainer/__module.js index 3b29b82ee..129c6b4b2 100644 --- a/app/portainer/__module.js +++ b/app/portainer/__module.js @@ -48,7 +48,7 @@ angular.module('portainer.app', []) var authentication = { name: 'portainer.auth', - url: '/auth', + url: '/auth?redirect', params: { logout: false, error: '' @@ -87,7 +87,7 @@ angular.module('portainer.app', []) } }; - var endpointCreation = { + var endpointCreation = { name: 'portainer.endpoints.new', url: '/new', views: { @@ -242,7 +242,7 @@ angular.module('portainer.app', []) } }; - var registryCreation = { + var registryCreation = { name: 'portainer.registries.new', url: '/new', views: { @@ -286,7 +286,7 @@ angular.module('portainer.app', []) } }; - var scheduleCreation = { + var scheduleCreation = { name: 'portainer.schedules.new', url: '/new', views: { diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js index 055d359be..2e2b729bf 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', ['$q', '$scope', '$state', '$transition$', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'StateManager', 'Notifications', 'SettingsService', -function ($q, $scope, $state, $transition$, $sanitize, Authentication, UserService, EndpointService, StateManager, Notifications, SettingsService) { +.controller('AuthenticationController', ['$q', '$scope', '$state', '$transition$', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'StateManager', 'Notifications', 'SettingsService', '$stateParams', +function ($q, $scope, $state, $transition$, $sanitize, Authentication, UserService, EndpointService, StateManager, Notifications, SettingsService, $stateParams) { $scope.logo = StateManager.getState().application.logo; @@ -44,7 +44,7 @@ function ($q, $scope, $state, $transition$, $sanitize, Authentication, UserServi if (endpoints.length === 0) { $state.go('portainer.init.endpoint'); } else { - $state.go('portainer.home'); + $state.go($stateParams.redirect ||'portainer.home'); } }) .catch(function error(err) { @@ -73,7 +73,7 @@ function ($q, $scope, $state, $transition$, $sanitize, Authentication, UserServi if (endpoints.length === 0 && userDetails.role === 1) { $state.go('portainer.init.endpoint'); } else { - $state.go('portainer.home'); + $state.go($stateParams.redirect || 'portainer.home'); } }) .catch(function error(err) { From 1a4dff536dbb6a3b0dcf6140bfda081c5ebb6792 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 23 Jan 2019 12:25:42 +1300 Subject: [PATCH 21/28] fix(container-creation): fix an issue with command parsing (#2642) * fix(container-creation): fix an issue with command parsing * refactor(container-creation): remove indentation update --- app/docker/helpers/containerHelper.js | 5 ++--- app/docker/views/containers/create/createcontainer.html | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/docker/helpers/containerHelper.js b/app/docker/helpers/containerHelper.js index f683bf3ff..345f2d1e9 100644 --- a/app/docker/helpers/containerHelper.js +++ b/app/docker/helpers/containerHelper.js @@ -1,10 +1,9 @@ -angular.module('portainer.docker') -.factory('ContainerHelper', [function ContainerHelperFactory() { +angular.module('portainer.docker').factory('ContainerHelper', [function ContainerHelperFactory() { 'use strict'; var helper = {}; helper.commandStringToArray = function(command) { - return splitargs(command, undefined, true); + return splitargs(command); }; helper.commandArrayToString = function(array) { diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index b25f2d318..da8521a1c 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -162,7 +162,7 @@
- +
From 90a0998502772a8ee0ccec4d6d84c004bfc2ffd4 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 23 Jan 2019 16:05:07 +1300 Subject: [PATCH 22/28] feat(templates): add sonatype nexus 3 template --- templates.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/templates.json b/templates.json index f99369d9f..c2dfe94a3 100644 --- a/templates.json +++ b/templates.json @@ -874,5 +874,18 @@ "label": "Datadog API key" } ] + }, + { + "type": 1, + "title": "Sonatype Nexus3", + "description": "Sonatype Nexus3 registry manager", + "categories": ["docker"], + "platform": "linux", + "logo": "https://portainer-io-assets.sfo2.digitaloceanspaces.com/logos/sonatype.png", + "image": "sonatype/nexus3:latest", + "ports": [ + "8081/tcp" + ], + "volumes": [{ "container": "/nexus-data"}] } ] From 801336336f79447b709c25fe23ab39d5d1201c43 Mon Sep 17 00:00:00 2001 From: baron_l Date: Thu, 24 Jan 2019 01:38:36 +0100 Subject: [PATCH 23/28] fix(registry-manager): add repositories pagination support (#2641) * fix(registry-management): add support for repositories list with multiple requests * refactor(registry-management): change regex usage to a reusable interceptor function * refactor(registry-management): change interceptor to transformResponse function --- .../registry-management/rest/catalog.js | 6 ++-- .../rest/transform/linkGetResponse.js | 13 +++++++++ .../services/registryAPIService.js | 29 +++++++++++++++---- 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 app/extensions/registry-management/rest/transform/linkGetResponse.js diff --git a/app/extensions/registry-management/rest/catalog.js b/app/extensions/registry-management/rest/catalog.js index 1d6f24be5..728a18dcd 100644 --- a/app/extensions/registry-management/rest/catalog.js +++ b/app/extensions/registry-management/rest/catalog.js @@ -1,11 +1,13 @@ angular.module('portainer.extensions.registrymanagement') -.factory('RegistryCatalog', ['$resource', 'API_ENDPOINT_REGISTRIES', function RegistryCatalogFactory($resource, API_ENDPOINT_REGISTRIES) { +.factory('RegistryCatalog', ['$resource', 'API_ENDPOINT_REGISTRIES', +function RegistryCatalogFactory($resource, API_ENDPOINT_REGISTRIES) { 'use strict'; return $resource(API_ENDPOINT_REGISTRIES + '/:id/v2/:action', {}, { get: { method: 'GET', - params: { id: '@id', action: '_catalog' } + params: { id: '@id', action: '_catalog' }, + transformResponse: linkGetResponse }, ping: { method: 'GET', diff --git a/app/extensions/registry-management/rest/transform/linkGetResponse.js b/app/extensions/registry-management/rest/transform/linkGetResponse.js new file mode 100644 index 000000000..5307701ca --- /dev/null +++ b/app/extensions/registry-management/rest/transform/linkGetResponse.js @@ -0,0 +1,13 @@ +function linkGetResponse(data, headers) { + var response = angular.fromJson(data); + var link = headers('link'); + if (link) { + var queryString = link.substring(link.indexOf('?') + 1).split('>;')[0]; + var queries = queryString.split('&'); + for (var i = 0; i < queries.length; i++) { + var kv = queries[i].split('='); + response[kv[0]] = kv[1]; + } + } + return response; +} \ No newline at end of file diff --git a/app/extensions/registry-management/services/registryAPIService.js b/app/extensions/registry-management/services/registryAPIService.js index b766ca568..18089bff9 100644 --- a/app/extensions/registry-management/services/registryAPIService.js +++ b/app/extensions/registry-management/services/registryAPIService.js @@ -11,16 +11,33 @@ function RegistryV2ServiceFactory($q, RegistryCatalog, RegistryTags, RegistryMan return RegistryCatalog.ping({ id: id }).$promise; }; + function getCatalog(id) { + var deferred = $q.defer(); + var repositories = []; + + _getCatalogPage({id: id}, deferred, repositories); + + return deferred.promise; + } + + function _getCatalogPage(params, deferred, repositories) { + RegistryCatalog.get(params).$promise.then(function(data) { + repositories = _.concat(repositories, data.repositories); + if (data.last && data.n) { + _getCatalogPage({id: params.id, n: data.n, last: data.last}, deferred, repositories); + } else { + deferred.resolve(repositories); + } + }); + } + service.repositories = function (id) { var deferred = $q.defer(); - RegistryCatalog.get({ - id: id - }).$promise - .then(function success(data) { + getCatalog(id).then(function success(data) { var promises = []; - for (var i = 0; i < data.repositories.length; i++) { - var repository = data.repositories[i]; + for (var i = 0; i < data.length; i++) { + var repository = data[i]; promises.push(RegistryTags.get({ id: id, repository: repository From fca4f619b50ac21a40366d5eb57f2c14299c1b71 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 30 Jan 2019 14:53:14 +1300 Subject: [PATCH 24/28] fix(api): re-use previous password when ldap settings update use empty password (#2659) --- api/http/handler/settings/settings_update.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go index 5b4d33ae3..5c68fca94 100644 --- a/api/http/handler/settings/settings_update.go +++ b/api/http/handler/settings/settings_update.go @@ -66,7 +66,12 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) * } if payload.LDAPSettings != nil { + ldapPassword := settings.LDAPSettings.Password + if payload.LDAPSettings.Password != "" { + ldapPassword = payload.LDAPSettings.Password + } settings.LDAPSettings = *payload.LDAPSettings + settings.LDAPSettings.Password = ldapPassword } if payload.AllowBindMountsForRegularUsers != nil { From 576f3691521b00892b1c2dfa4948ebfbd693f34e Mon Sep 17 00:00:00 2001 From: Steven Kang Date: Thu, 31 Jan 2019 11:37:16 +1300 Subject: [PATCH 25/28] feat(build-system): introduce Azure DevOps support (#2666) --- appveyor-ci.yml | 44 ---------------------- appveyor-release.yml | 61 ------------------------------ build/build_binary.ps1 | 17 +++++++++ build/build_binary.sh | 9 +++++ build/build_binary_devops.ps1 | 24 ++++++++++++ build/build_binary_devops.sh | 15 ++++++++ build/download_docker_binary.ps1 | 13 +++++++ gruntfile.js | 64 ++++++++++++++++++++++++-------- 8 files changed, 127 insertions(+), 120 deletions(-) delete mode 100644 appveyor-ci.yml delete mode 100644 appveyor-release.yml create mode 100755 build/build_binary.ps1 create mode 100755 build/build_binary.sh create mode 100755 build/build_binary_devops.ps1 create mode 100755 build/build_binary_devops.sh create mode 100644 build/download_docker_binary.ps1 diff --git a/appveyor-ci.yml b/appveyor-ci.yml deleted file mode 100644 index ab1188611..000000000 --- a/appveyor-ci.yml +++ /dev/null @@ -1,44 +0,0 @@ -version: 1.0.{build} -image: - - Visual Studio 2017 - - Ubuntu -environment: - matrix: - - ARCH: amd64 - - ARCH: arm - - ARCH: arm64 - - ARCH: ppc64le - - ARCH: s390x - DOCKER_USER: - secure: JapmC7j5F0mY3j/MVzU+Cw== - DOCKER_PASS: - secure: QGlCLNWzPD0HL8ipkohVic45/yU3bVOdjn0IiV6NnSQ= -matrix: - exclude: - - image: Visual Studio 2017 - ARCH: arm - - image: Visual Studio 2017 - ARCH: arm64 - - image: Visual Studio 2017 - ARCH: ppc64le - - image: Visual Studio 2017 - ARCH: s390x -branches: - except: - - master -stack: - - node 9, go 1.10 -install: - - yarn install - - npm install -g rebase-docker-image -init: - - sh: export IMAGE=linux - - cmd: SET IMAGE=windows - - ps: >- - if (!(Test-Path ~/.docker)) { mkdir ~/.docker }; - Set-Content -Value '{ "experimental": "enabled" }' -Path ~/.docker/config.json -Encoding Ascii -build_script: - - sh: yarn grunt appveyorbuild:$IMAGE:$ARCH - - cmd: yarn grunt appveyorbuild:%IMAGE%:%ARCH% - - sh: sudo bash build/ci-linux.sh $IMAGE $ARCH $DOCKER_USER $DOCKER_PASS $APPVEYOR_REPO_BRANCH $APPVEYOR_PULL_REQUEST_NUMBER - - cmd: powershell -Command "& .\\build\\ci-windows.ps1" diff --git a/appveyor-release.yml b/appveyor-release.yml deleted file mode 100644 index 6293e6c90..000000000 --- a/appveyor-release.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: 1.0.{build} -image: - - Visual Studio 2017 - - Ubuntu -environment: - matrix: - - ARCH: amd64 - - ARCH: arm - - ARCH: arm64 - - ARCH: ppc64le - - ARCH: s390x - DOCKER_USER: - secure: JapmC7j5F0mY3j/MVzU+Cw== - DOCKER_PASS: - secure: QGlCLNWzPD0HL8ipkohVic45/yU3bVOdjn0IiV6NnSQ= - PORTAINER_VERSION: "1.19.2" -matrix: - exclude: - - image: Visual Studio 2017 - ARCH: arm - - image: Visual Studio 2017 - ARCH: arm64 - - image: Visual Studio 2017 - ARCH: ppc64le - - image: Visual Studio 2017 - ARCH: s390x -branches: - only: - - master -stack: node 9, go 1.10 -artifacts: - - path: 'portainer-$(PORTAINER_VERSION)-$(IMAGE)-$(ARCH).tar.gz' - type: file - - path: 'portainer-$(PORTAINER_VERSION)-$(IMAGE)-$(ARCH)-checksum.txt' - type: file -install: - - yarn install - - npm install -g rebase-docker-image -init: - - sh: export IMAGE=linux - - cmd: SET IMAGE=windows - - ps: >- - if (!(Test-Path ~/.docker)) { mkdir ~/.docker } - Set-Content -Value '{ "experimental": "enabled" }' -Path ~/.docker/config.json -Encoding Ascii -build_script: - - sh: yarn grunt appveyorbuild:$IMAGE:$ARCH - - cmd: yarn grunt appveyorbuild:%IMAGE%:%ARCH% - - sh: sudo bash build/release-linux.sh $IMAGE $ARCH $PORTAINER_VERSION $DOCKER_USER $DOCKER_PASS - - cmd: powershell -Command "& .\\build\\release-windows.ps1" -test: off -deploy: - release: Release $(PORTAINER_VERSION) - description: '' - provider: GitHub - auth_token: - secure: BRYVGj94QlFBCMoO8yhSu+AGqKNV1+03LJEFrNUTRzo5erXfUHUIi/rgztnxfSGW - artifact: /portainer-$(PORTAINER_VERSION)-$(IMAGE)-$(ARCH).*/ - draft: true - prerelease: false - on: - branch: master diff --git a/build/build_binary.ps1 b/build/build_binary.ps1 new file mode 100755 index 000000000..f444f776e --- /dev/null +++ b/build/build_binary.ps1 @@ -0,0 +1,17 @@ +param ( + [string]$platform, + [string]$arch +) + +$ErrorActionPreference = "Stop"; + +$binary = "portainer.exe" +$project_path = (Get-ITEM -Path env:APPVEYOR_BUILD_FOLDER).Value + +New-Item -Name dist -Path "$project_path" -ItemType Directory | Out-Null +Set-Location -Path "$project_path\api\cmd\portainer" + +C:\go\bin\go.exe get -t -d -v ./... +C:\go\bin\go.exe build -v + +Move-Item -Path "$($binary)" -Destination "..\..\..\dist" diff --git a/build/build_binary.sh b/build/build_binary.sh new file mode 100755 index 000000000..b9fe6509c --- /dev/null +++ b/build/build_binary.sh @@ -0,0 +1,9 @@ +binary="portainer" +mkdir -p dist + +cd 'api/cmd/portainer' + +go get -t -d -v ./... +GOOS=$1 GOARCH=$2 CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' + +mv "${binary}" "../../../dist/portainer" \ No newline at end of file diff --git a/build/build_binary_devops.ps1 b/build/build_binary_devops.ps1 new file mode 100755 index 000000000..6e048b0f3 --- /dev/null +++ b/build/build_binary_devops.ps1 @@ -0,0 +1,24 @@ +param ( + [string]$platform, + [string]$arch +) + +$ErrorActionPreference = "Stop"; + +$binary = "portainer.exe" +$project_path = (Get-ITEM -Path env:BUILD_SOURCESDIRECTORY).Value + +Set-Item env:GOPATH "$project_path\api" + +New-Item -Name dist -Path "$project_path" -ItemType Directory | Out-Null +New-Item -Name portainer -Path "$project_path\api\src\github.com\" -ItemType Directory | Out-Null + +Copy-Item -Path "$project_path\api" -Destination "$project_path\api\src\github.com\portainer" -Recurse -Force -ErrorAction:SilentlyContinue +Rename-Item -Path "$project_path\api\src\github.com\portainer\api" -NewName "portainer" -ErrorAction:SilentlyContinue + +Set-Location -Path "$project_path\api\cmd\portainer" + +go.exe get -t -d -v ./... +go.exe build -v + +Move-Item -Path "$project_path\api\cmd\portainer\$($binary)" -Destination "$project_path\dist" diff --git a/build/build_binary_devops.sh b/build/build_binary_devops.sh new file mode 100755 index 000000000..ccabcc6d7 --- /dev/null +++ b/build/build_binary_devops.sh @@ -0,0 +1,15 @@ +export GOPATH="$BUILD_SOURCESDIRECTORY/api" + +binary="portainer" + +mkdir -p dist +mkdir -p api/src/github.com/portainer/ + +cp -R api/ api/src/github.com/portainer/portainer/ + +cd 'api/cmd/portainer' + +go get -t -d -v ./... +GOOS=$1 GOARCH=$2 CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' + +mv "$BUILD_SOURCESDIRECTORY/api/cmd/portainer/$binary" "$BUILD_SOURCESDIRECTORY/dist/portainer" diff --git a/build/download_docker_binary.ps1 b/build/download_docker_binary.ps1 new file mode 100644 index 000000000..b5d6b6c13 --- /dev/null +++ b/build/download_docker_binary.ps1 @@ -0,0 +1,13 @@ +param ( + [string]$docker_version +) + +$ErrorActionPreference = "Stop"; + +New-Item -Path "docker-binary" -ItemType Directory | Out-Null + +$download_folder = "docker-binary" + +Invoke-WebRequest -O "$($download_folder)/docker-binaries.zip" "https://download.docker.com/win/static/stable/x86_64/docker-$($docker_version).zip" +Expand-Archive -Path "$($download_folder)/docker-binaries.zip" -DestinationPath "$($download_folder)" +Move-Item -Path "$($download_folder)/docker/docker.exe" -Destination "dist" diff --git a/gruntfile.js b/gruntfile.js index 8379d98f3..99760a25d 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -51,6 +51,10 @@ module.exports = function(grunt) { grunt.task.run(['config:prod', 'clean:all', 'shell:buildBinary:' + p + ':' + a, 'shell:downloadDockerBinary:' + p + ':' + a, 'before-copy', 'copy:assets', 'after-copy']); }); + grunt.task.registerTask('devopsbuild', 'devopsbuild::', function(p, a) { + grunt.task.run(['config:prod', 'clean:all', 'shell:buildBinaryOnDevOps:' + p + ':' + a, 'shell:downloadDockerBinary:' + p + ':' + a, 'before-copy', 'copy:assets', 'after-copy']); + }); + grunt.registerTask('lint', ['eslint']); grunt.registerTask('run-dev', ['build', 'shell:run:' + arch, 'watch:build']); grunt.registerTask('clear', ['clean:app']); @@ -261,13 +265,32 @@ gruntfile_cfg.replace = { function shell_buildBinary(p, a) { var binfile = 'dist/portainer-' + p + '-' + a; - return [ - 'if [ -f ' + ((p === 'windows') ? binfile + '.exe' : binfile) + ' ]; then', - 'echo "Portainer binary exists";', - 'else', - 'build/build_in_container.sh ' + p + ' ' + a + ';', - 'fi' - ].join(' '); + if (p === 'linux') { + return [ + 'if [ -f ' + (binfile) + ' ]; then', + 'echo "Portainer binary exists";', + 'else', + 'build/build_binary.sh ' + p + ' ' + a + ';', + 'fi' + ].join(' '); + } else { + return [ + 'powershell -Command "& {if (Get-Item -Path ' + binfile + '.exe -ErrorAction:SilentlyContinue) {', + 'Write-Host "Portainer binary exists"', + '} else {', + '& ".\\build\\build_binary.ps1" -platform ' + p + ' -arch ' + a + '', + '}}"' + ].join(' '); + } +} + +function shell_buildBinaryOnDevOps(p, a) { + var binfile = 'portainer-' + p + '-' + a; + if (p === 'linux') { + return 'build/build_binary_devops.sh ' + p + ' ' + a + ';' + } else { + return 'powershell -Command ".\\build\\build_binary_devops.ps1 -platform ' + p + ' -arch ' + a + '"' + } } function shell_run(arch) { @@ -283,17 +306,28 @@ function shell_downloadDockerBinary(p, a) { var ip = ((ps[p] === undefined) ? p : ps[p]); var ia = ((as[a] === undefined) ? a : as[a]); var binaryVersion = ((p === 'windows' ? '<%= shippedDockerVersionWindows %>' : '<%= shippedDockerVersion %>')); - return [ - 'if [ -f ' + ((p === 'windows') ? 'dist/docker.exe' : 'dist/docker') + ' ]; then', - 'echo "Docker binary exists";', - 'else', - 'build/download_docker_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';', - 'fi' - ].join(' '); + if (p === 'linux') { + return [ + 'if [ -f dist/docker ]; then', + 'echo "Docker binary exists";', + 'else', + 'build/download_docker_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';', + 'fi' + ].join(' '); + } else { + return [ + 'powershell -Command "& {if (Get-Item -Path dist/docker.exe -ErrorAction:SilentlyContinue) {', + 'Write-Host "Docker binary exists"', + '} else {', + '& ".\\build\\download_docker_binary.ps1" -docker_version ' + binaryVersion + '', + '}}"' + ].join(' '); + } } gruntfile_cfg.shell = { buildBinary: { command: shell_buildBinary }, + buildBinaryOnDevOps: { command: shell_buildBinaryOnDevOps }, run: { command: shell_run }, downloadDockerBinary: { command: shell_downloadDockerBinary } -}; +}; \ No newline at end of file From cea2c60b555e8fe9e399b00c6a65c4b585d90798 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 31 Jan 2019 11:38:27 +1300 Subject: [PATCH 26/28] refactor(build-system): fix lint issues --- gruntfile.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 99760a25d..defcd07bf 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -285,11 +285,10 @@ function shell_buildBinary(p, a) { } function shell_buildBinaryOnDevOps(p, a) { - var binfile = 'portainer-' + p + '-' + a; if (p === 'linux') { - return 'build/build_binary_devops.sh ' + p + ' ' + a + ';' + return 'build/build_binary_devops.sh ' + p + ' ' + a + ';'; } else { - return 'powershell -Command ".\\build\\build_binary_devops.ps1 -platform ' + p + ' -arch ' + a + '"' + return 'powershell -Command ".\\build\\build_binary_devops.ps1 -platform ' + p + ' -arch ' + a + '"'; } } @@ -330,4 +329,4 @@ gruntfile_cfg.shell = { buildBinaryOnDevOps: { command: shell_buildBinaryOnDevOps }, run: { command: shell_run }, downloadDockerBinary: { command: shell_downloadDockerBinary } -}; \ No newline at end of file +}; From c5b5f80beaab51ec247e411fca758bffae8db2aa Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 31 Jan 2019 12:02:12 +1300 Subject: [PATCH 27/28] docs(README): update build badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a8035de2..65117ef76 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) -[![Build Status](https://semaphoreci.com/api/v1/portainer/portainer-ci/branches/develop/badge.svg)](https://semaphoreci.com/portainer/portainer-ci) +[![Build Status](https://portainer.visualstudio.com/Portainer%20CI/_apis/build/status/Portainer%20CI?branchName=develop)](https://portainer.visualstudio.com/Portainer%20CI/_build/latest?definitionId=3&branchName=develop) [![Code Climate](https://codeclimate.com/github/portainer/portainer/badges/gpa.svg)](https://codeclimate.com/github/portainer/portainer) [![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6) From 6b1c476b63044fea1eb5c52c9ca9dea034275549 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 31 Jan 2019 13:15:18 +1300 Subject: [PATCH 28/28] chore(version): bump version number --- api/bolt/migrator/migrator.go | 1 + api/portainer.go | 2 +- api/swagger.yaml | 4 ++-- api/swagger_config.json | 2 +- distribution/portainer.spec | 2 +- package.json | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go index 78e452d8a..ccee735ff 100644 --- a/api/bolt/migrator/migrator.go +++ b/api/bolt/migrator/migrator.go @@ -214,6 +214,7 @@ func (m *Migrator) Migrate() error { } } + // Portainer 1.20.1 if m.currentDBVersion < 17 { err := m.updateExtensionsToDBVersion17() if err != nil { diff --git a/api/portainer.go b/api/portainer.go index a87d91e6a..9ff82bb37 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -779,7 +779,7 @@ type ( const ( // APIVersion is the version number of the Portainer API - APIVersion = "1.20.0" + APIVersion = "1.20.1" // DBVersion is the version number of the Portainer database DBVersion = 17 // AssetsServerURL represents the URL of the Portainer asset server diff --git a/api/swagger.yaml b/api/swagger.yaml index fc3ddf9e3..7f5339d2e 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.20.0" + version: "1.20.1" title: "Portainer API" contact: email: "info@portainer.io" @@ -3018,7 +3018,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.20.0" + example: "1.20.1" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" diff --git a/api/swagger_config.json b/api/swagger_config.json index beee114ea..cdd9e1115 100644 --- a/api/swagger_config.json +++ b/api/swagger_config.json @@ -1,5 +1,5 @@ { "packageName": "portainer", - "packageVersion": "1.20.0", + "packageVersion": "1.20.1", "projectName": "portainer" } diff --git a/distribution/portainer.spec b/distribution/portainer.spec index b974ae153..b4800443e 100644 --- a/distribution/portainer.spec +++ b/distribution/portainer.spec @@ -1,5 +1,5 @@ Name: portainer -Version: 1.20.0 +Version: 1.20.1 Release: 0 License: Zlib Summary: A lightweight docker management UI diff --git a/package.json b/package.json index 104382c36..d6277314c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "1.20.0", + "version": "1.20.1", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git"