diff --git a/api/http/handler/extensions.go b/api/http/handler/extensions.go index f34aab9a7..ecae4678e 100644 --- a/api/http/handler/extensions.go +++ b/api/http/handler/extensions.go @@ -32,7 +32,9 @@ func NewExtensionHandler(bouncer *security.RequestBouncer) *ExtensionHandler { Logger: log.New(os.Stderr, "", log.LstdFlags), } h.Handle("/{endpointId}/extensions", - bouncer.AdministratorAccess(http.HandlerFunc(h.handlePostExtensions))).Methods(http.MethodPost) + bouncer.AuthenticatedAccess(http.HandlerFunc(h.handlePostExtensions))).Methods(http.MethodPost) + h.Handle("/{endpointId}/extensions/{extensionType}", + bouncer.AuthenticatedAccess(http.HandlerFunc(h.handleDeleteExtensions))).Methods(http.MethodDelete) return h } @@ -75,20 +77,24 @@ func (handler *ExtensionHandler) handlePostExtensions(w http.ResponseWriter, r * extensionType := portainer.EndpointExtensionType(req.Type) - for _, extension := range endpoint.Extensions { - if extension.Type == extensionType { - httperror.WriteErrorResponse(w, portainer.ErrEndpointExtensionAlreadyAssociated, http.StatusConflict, handler.Logger) - return + var extension *portainer.EndpointExtension + + for _, ext := range endpoint.Extensions { + if ext.Type == extensionType { + extension = &ext } } - extension := portainer.EndpointExtension{ - Type: extensionType, - URL: req.URL, + if extension != nil { + extension.URL = req.URL + } else { + extension = &portainer.EndpointExtension{ + Type: extensionType, + URL: req.URL, + } + endpoint.Extensions = append(endpoint.Extensions, *extension) } - endpoint.Extensions = append(endpoint.Extensions, extension) - err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) @@ -97,3 +103,41 @@ func (handler *ExtensionHandler) handlePostExtensions(w http.ResponseWriter, r * encodeJSON(w, extension, handler.Logger) } + +func (handler *ExtensionHandler) handleDeleteExtensions(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["endpointId"]) + if err != nil { + httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger) + return + } + endpointID := portainer.EndpointID(id) + + endpoint, err := handler.EndpointService.Endpoint(endpointID) + if err == portainer.ErrEndpointNotFound { + httperror.WriteErrorResponse(w, err, http.StatusNotFound, handler.Logger) + return + } else if err != nil { + httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) + return + } + + extType, err := strconv.Atoi(vars["extensionType"]) + if err != nil { + httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger) + return + } + extensionType := portainer.EndpointExtensionType(extType) + + for idx, ext := range endpoint.Extensions { + if ext.Type == extensionType { + endpoint.Extensions = append(endpoint.Extensions[:idx], endpoint.Extensions[idx+1:]...) + } + } + + err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint) + if err != nil { + httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) + return + } +} diff --git a/api/portainer.go b/api/portainer.go index 291f75059..f001f83df 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -400,7 +400,7 @@ type ( const ( // APIVersion is the version number of the Portainer API. - APIVersion = "1.16.3" + APIVersion = "1.16.4" // DBVersion is the version number of the Portainer database. DBVersion = 8 // DefaultTemplatesURL represents the default URL for the templates definitions. diff --git a/api/swagger.yaml b/api/swagger.yaml index bec349046..4d2546919 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -56,7 +56,7 @@ info: **NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8). - version: "1.16.3" + version: "1.16.4" title: "Portainer API" contact: email: "info@portainer.io" @@ -2143,7 +2143,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.16.3" + example: "1.16.4" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" diff --git a/app/config.js b/app/config.js index 328647f38..85a9937db 100644 --- a/app/config.js +++ b/app/config.js @@ -26,6 +26,8 @@ angular.module('portainer') toastr.options.timeOut = 3000; + Terminal.applyAddon(fit); + $uibTooltipProvider.setTriggers({ 'mouseenter': 'mouseleave', 'click': 'click', diff --git a/app/docker/components/log-viewer/logViewer.html b/app/docker/components/log-viewer/logViewer.html index e61923b38..884249c13 100644 --- a/app/docker/components/log-viewer/logViewer.html +++ b/app/docker/components/log-viewer/logViewer.html @@ -7,21 +7,11 @@
-
-
-
-
- -
@@ -40,6 +30,7 @@
+ @@ -53,8 +44,8 @@
-
-      

{{ line }}

+
+      

{{ line }}

No log line matching the '{{ $ctrl.state.search }}' filter

No logs available

diff --git a/app/docker/components/log-viewer/logViewerController.js b/app/docker/components/log-viewer/logViewerController.js index 10b544c58..98ec13721 100644 --- a/app/docker/components/log-viewer/logViewerController.js +++ b/app/docker/components/log-viewer/logViewerController.js @@ -15,13 +15,17 @@ function (clipboard) { this.copy = function() { clipboard.copyText(this.state.filteredLogs); $('#refreshRateChange').show(); - $('#refreshRateChange').fadeOut(1500); + $('#refreshRateChange').fadeOut(2000); }; this.copySelection = function() { clipboard.copyText(this.state.selectedLines); $('#refreshRateChange').show(); - $('#refreshRateChange').fadeOut(1500); + $('#refreshRateChange').fadeOut(2000); + }; + + this.clearSelection = function() { + this.state.selectedLines = []; }; this.selectLine = function(line) { diff --git a/app/docker/views/containers/console/containerConsoleController.js b/app/docker/views/containers/console/containerConsoleController.js index 03d42f11b..bf8adee6c 100644 --- a/app/docker/views/containers/console/containerConsoleController.js +++ b/app/docker/views/containers/console/containerConsoleController.js @@ -87,7 +87,8 @@ function ($scope, $transition$, Container, Image, EndpointProvider, Notification term.on('data', function (data) { socket.send(data); }); - term.open(document.getElementById('terminal-container'), true); + term.open(document.getElementById('terminal-container')); + term.focus(); term.resize(width, height); term.setOption('cursorBlink', true); term.fit(); diff --git a/app/docker/views/services/logs/serviceLogsController.js b/app/docker/views/services/logs/serviceLogsController.js index b2760b24e..47fd117d0 100644 --- a/app/docker/views/services/logs/serviceLogsController.js +++ b/app/docker/views/services/logs/serviceLogsController.js @@ -44,7 +44,6 @@ function ($scope, $transition$, $interval, ServiceService, Notifications) { ServiceService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) .then(function success(data) { $scope.logs = data; - console.log(JSON.stringify(data, null, 4)); setUpdateRepeater(); }) .catch(function error(err) { diff --git a/app/docker/views/services/logs/servicelogs.html b/app/docker/views/services/logs/servicelogs.html index cc509660c..07ed352c3 100644 --- a/app/docker/views/services/logs/servicelogs.html +++ b/app/docker/views/services/logs/servicelogs.html @@ -1,7 +1,7 @@ - Services > {{ service.Name }} > Logs + Services > {{ service.Name }} > Logs diff --git a/app/portainer/rest/extension.js b/app/portainer/rest/extension.js index bd00f3158..1f9dd75bb 100644 --- a/app/portainer/rest/extension.js +++ b/app/portainer/rest/extension.js @@ -1,10 +1,11 @@ angular.module('portainer.app') .factory('Extensions', ['$resource', 'EndpointProvider', 'API_ENDPOINT_ENDPOINTS', function Extensions($resource, EndpointProvider, API_ENDPOINT_ENDPOINTS) { 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/extensions', { + return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/extensions/:type', { endpointId: EndpointProvider.endpointID }, { - register: { method: 'POST', params: { endpointId: '@endpointId' } } + register: { method: 'POST' }, + deregister: { method: 'DELETE', params: { type: '@type' } } }); }]); diff --git a/app/portainer/services/api/endpointService.js b/app/portainer/services/api/endpointService.js index 3d7f43d65..4905eda77 100644 --- a/app/portainer/services/api/endpointService.js +++ b/app/portainer/services/api/endpointService.js @@ -1,5 +1,6 @@ angular.module('portainer.app') -.factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, Endpoints, FileUploadService) { +.factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', +function EndpointServiceFactory($q, Endpoints, FileUploadService) { 'use strict'; var service = {}; diff --git a/app/portainer/services/api/extensionService.js b/app/portainer/services/api/extensionService.js index 42ae3d7dc..3cd08a828 100644 --- a/app/portainer/services/api/extensionService.js +++ b/app/portainer/services/api/extensionService.js @@ -3,9 +3,8 @@ angular.module('portainer.app') 'use strict'; var service = {}; - service.registerStoridgeExtension = function(endpointId, url) { + service.registerStoridgeExtension = function(url) { var payload = { - endpointId: endpointId, Type: 1, URL: url }; @@ -13,5 +12,9 @@ angular.module('portainer.app') return Extensions.register(payload).$promise; }; + service.deregisterStoridgeExtension = function() { + return Extensions.deregister({ type: 1 }).$promise; + }; + return service; }]); diff --git a/app/portainer/services/extensionManager.js b/app/portainer/services/extensionManager.js index 82a3ae1c8..adfa4468f 100644 --- a/app/portainer/services/extensionManager.js +++ b/app/portainer/services/extensionManager.js @@ -4,7 +4,7 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ 'use strict'; var service = {}; - service.initEndpointExtensions = function(endpointId) { + service.initEndpointExtensions = function() { var deferred = $q.defer(); SystemService.version() @@ -12,13 +12,11 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ var endpointAPIVersion = parseFloat(data.ApiVersion); return $q.all([ - endpointAPIVersion >= 1.25 ? initStoridgeExtension(endpointId): null + endpointAPIVersion >= 1.25 ? initStoridgeExtension(): null ]); }) .then(function success(data) { - var extensions = data.filter(function filterNull(x) { - return x; - }); + var extensions = data; deferred.resolve(extensions); }) .catch(function error(err) { @@ -28,14 +26,16 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ return deferred.promise; }; - function initStoridgeExtension(endpointId) { + function initStoridgeExtension() { var deferred = $q.defer(); PluginService.volumePlugins() .then(function success(data) { var volumePlugins = data; if (_.includes(volumePlugins, 'cio:latest')) { - return registerStoridgeUsingSwarmManagerIP(endpointId); + return registerStoridgeUsingSwarmManagerIP(); + } else { + return deregisterStoridgeExtension(); } }) .then(function success(data) { @@ -48,14 +48,14 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ return deferred.promise; } - function registerStoridgeUsingSwarmManagerIP(endpointId) { + function registerStoridgeUsingSwarmManagerIP() { var deferred = $q.defer(); SystemService.info() .then(function success(data) { var managerIP = data.Swarm.NodeAddr; var storidgeAPIURL = 'tcp://' + managerIP + ':8282'; - return ExtensionService.registerStoridgeExtension(endpointId, storidgeAPIURL); + return ExtensionService.registerStoridgeExtension(storidgeAPIURL); }) .then(function success(data) { deferred.resolve(data); @@ -67,5 +67,9 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ return deferred.promise; } + function deregisterStoridgeExtension() { + return ExtensionService.deregisterStoridgeExtension(); + } + return service; }]); diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js index 757175299..300372b75 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$', '$window', '$timeout', '$sanitize', 'Authentication', 'Users', 'UserService', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'SettingsService', -function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentication, Users, UserService, EndpointService, StateManager, EndpointProvider, Notifications, SettingsService) { +.controller('AuthenticationController', ['$scope', '$state', '$transition$', '$window', '$timeout', '$sanitize', 'Authentication', 'Users', 'UserService', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'SettingsService', 'ExtensionManager', +function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentication, Users, UserService, EndpointService, StateManager, EndpointProvider, Notifications, SettingsService, ExtensionManager) { $scope.logo = StateManager.getState().application.logo; @@ -18,7 +18,12 @@ function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentica if (!endpointID) { EndpointProvider.setEndpointID(endpoint.Id); } - StateManager.updateEndpointState(true, endpoint.Extensions) + + ExtensionManager.initEndpointExtensions(endpoint.Id) + .then(function success(data) { + var extensions = data; + return StateManager.updateEndpointState(true, extensions); + }) .then(function success(data) { $state.go('docker.dashboard'); }) diff --git a/app/portainer/views/endpoints/endpointsController.js b/app/portainer/views/endpoints/endpointsController.js index 5c8b76e43..7bf887d20 100644 --- a/app/portainer/views/endpoints/endpointsController.js +++ b/app/portainer/views/endpoints/endpointsController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications', 'ExtensionManager', 'EndpointProvider', -function ($scope, $state, $filter, EndpointService, Notifications, ExtensionManager, EndpointProvider) { +.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications', 'SystemService', 'EndpointProvider', +function ($scope, $state, $filter, EndpointService, Notifications, SystemService, EndpointProvider) { $scope.state = { uploadInProgress: false, actionInProgress: false @@ -37,8 +37,8 @@ function ($scope, $state, $filter, EndpointService, Notifications, ExtensionMana endpointId = data.Id; var currentEndpointId = EndpointProvider.endpointID(); EndpointProvider.setEndpointID(endpointId); - ExtensionManager.initEndpointExtensions(endpointId) - .then(function success(data) { + SystemService.info() + .then(function success() { Notifications.success('Endpoint created', name); $state.reload(); }) diff --git a/app/portainer/views/sidebar/sidebarController.js b/app/portainer/views/sidebar/sidebarController.js index 3fdf00264..03606be71 100644 --- a/app/portainer/views/sidebar/sidebarController.js +++ b/app/portainer/views/sidebar/sidebarController.js @@ -1,6 +1,6 @@ angular.module('portainer.app') -.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService', -function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService) { +.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService', 'ExtensionManager', +function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService, ExtensionManager) { $scope.switchEndpoint = function(endpoint) { var activeEndpointID = EndpointProvider.endpointID(); @@ -8,7 +8,11 @@ function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointP EndpointProvider.setEndpointID(endpoint.Id); EndpointProvider.setEndpointPublicURL(endpoint.PublicURL); - StateManager.updateEndpointState(true, endpoint.Extensions) + ExtensionManager.initEndpointExtensions(endpoint.Id) + .then(function success(data) { + var extensions = data; + return StateManager.updateEndpointState(true, extensions); + }) .then(function success() { $state.go('docker.dashboard'); }) diff --git a/distribution/portainer.spec b/distribution/portainer.spec index fd900f0ac..f2cd9c12a 100644 --- a/distribution/portainer.spec +++ b/distribution/portainer.spec @@ -1,5 +1,5 @@ Name: portainer -Version: 1.16.3 +Version: 1.16.4 Release: 0 License: Zlib Summary: A lightweight docker management UI diff --git a/package.json b/package.json index 24be875da..babf2346a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "1.16.3", + "version": "1.16.4", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git" @@ -50,13 +50,13 @@ "jquery": "^3.3.1", "js-yaml": "~3.10.0", "lodash": "4.12.0", - "moment": "~2.14.1", + "moment": "^2.21.0", "ng-file-upload": "~12.2.13", "rdash-ui": "1.0.*", "splitargs": "github:deviantony/splitargs#~0.2.0", "toastr": "github:CodeSeven/toastr#~2.1.3", "ui-select": "~0.19.6", - "xterm": "~2.8.1" + "xterm": "^3.1.0" }, "devDependencies": { "autoprefixer": "^7.1.1", diff --git a/yarn.lock b/yarn.lock index 8a306fe51..0ecbadb85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2882,10 +2882,14 @@ mkdirp@0.x.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: dependencies: minimist "0.0.8" -moment@^2.10.6, moment@~2.14.1: +moment@^2.10.6: version "2.14.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.14.1.tgz#b35b27c47e57ed2ddc70053d6b07becdb291741c" +moment@^2.21.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" + morgan@~1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.6.1.tgz#5fd818398c6819cba28a7cd6664f292fe1c0bbf2" @@ -4483,9 +4487,9 @@ xtend@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" -xterm@~2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-2.8.1.tgz#3f6b939bcb8d015a1f247d66257102cb16a0b2e1" +xterm@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.1.0.tgz#7f7e1c8cf4b80bd881a4e8891213b851423e90c9" yargs@~3.10.0: version "3.10.0"