feat(app): toggle features based on agent API version (#2378)

* feat(agent): get agent's version from ping

* feat(agent): add version to api url

* feat(agent): query agent with api version

* feat(agent): rename agent api version name on state

* feat(agent): disable feature based on agent's api version

* style(agent): rename ping rest service + remove whitespaces

* style(state): remove whitespace

* style(agent): add whitespace

* fix(agent): remove check for error status 403

* refactor(agent): rename ping file name

* refactor(agent): move old services to v1 folder

* refactor(agent): turn ping service to usual pattern

* refactor(agent): change version to a global variable

* refactor(agent): move ping to version2

* refactor(agent): restore ping to use root ping

* fix(volumes): add volumeID to browse api path

* feat(volume): add upload button to volume browser
pull/2401/head
Chaim Lev-Ari 2018-10-26 06:16:29 +03:00 committed by Anthony Lapenna
parent cca378b2e8
commit 9813099aa4
24 changed files with 224 additions and 41 deletions

View File

@ -3,6 +3,7 @@ angular.module('portainer.agent').component('volumeBrowser', {
controller: 'VolumeBrowserController', controller: 'VolumeBrowserController',
bindings: { bindings: {
volumeId: '<', volumeId: '<',
nodeName: '<' nodeName: '<',
isUploadEnabled: '<'
} }
}); });

View File

@ -8,4 +8,7 @@
rename="$ctrl.rename(name, newName)" rename="$ctrl.rename(name, newName)"
download="$ctrl.download(name)" download="$ctrl.download(name)"
delete="$ctrl.delete(name)" delete="$ctrl.delete(name)"
is-upload-allowed="$ctrl.isUploadEnabled"
on-file-selected-for-upload="$ctrl.onFileSelectedForUpload"
></files-datatable> ></files-datatable>

View File

@ -84,6 +84,16 @@ function (HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, ModalService
}); });
} }
this.onFileSelectedForUpload = function onFileSelectedForUpload(file) {
VolumeBrowserService.upload(ctrl.state.path, file, ctrl.volumeId)
.then(function onFileUpload() {
onFileUploaded();
})
.catch(function onFileUpload(err) {
Notifications.error('Failure', err, 'Unable to upload file');
});
};
function parentPath(path) { function parentPath(path) {
if (path.lastIndexOf('/') === 0) { if (path.lastIndexOf('/') === 0) {
return '/'; return '/';
@ -112,4 +122,14 @@ function (HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, ModalService
}); });
}; };
function onFileUploaded() {
refreshList();
}
function refreshList() {
browse(ctrl.state.path);
}
}]); }]);

View File

@ -1,8 +1,10 @@
angular.module('portainer.agent') angular.module('portainer.agent')
.factory('Agent', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { .factory('Agent', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'StateManager',
function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, StateManager) {
'use strict'; 'use strict';
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/agents', { return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/agents', {
endpointId: EndpointProvider.endpointID endpointId: EndpointProvider.endpointID,
version: StateManager.getAgentApiVersion
}, },
{ {
query: { method: 'GET', isArray: true } query: { method: 'GET', isArray: true }

View File

@ -1,8 +1,10 @@
angular.module('portainer.agent') angular.module('portainer.agent')
.factory('Browse', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function BrowseFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { .factory('Browse', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'StateManager',
function BrowseFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, StateManager) {
'use strict'; 'use strict';
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/browse/:action', { return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/browse/:action', {
endpointId: EndpointProvider.endpointID endpointId: EndpointProvider.endpointID,
version: StateManager.getAgentApiVersion
}, },
{ {
ls: { ls: {

View File

@ -1,11 +1,12 @@
angular.module('portainer.agent').factory('Host', [ angular.module('portainer.agent').factory('Host', [
'$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'StateManager',
function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, StateManager) {
'use strict'; 'use strict';
return $resource( return $resource(
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/host/:action', API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/host/:action',
{ {
endpointId: EndpointProvider.endpointID endpointId: EndpointProvider.endpointID,
version: StateManager.getAgentApiVersion
}, },
{ {
info: { method: 'GET', params: { action: 'info' } } info: { method: 'GET', params: { action: 'info' } }

33
app/agent/rest/ping.js Normal file
View File

@ -0,0 +1,33 @@
angular.module('portainer.agent').factory('AgentPing', [
'$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q',
function AgentPingFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q) {
'use strict';
return $resource(
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/ping',
{
endpointId: EndpointProvider.endpointID
},
{
ping: {
method: 'GET',
interceptor: {
response: function versionInterceptor(response) {
var instance = response.resource;
var version =
response.headers('Portainer-Agent-Api-Version') || 1;
instance.version = Number(version);
return instance;
},
responseError: function versionResponseError(error) {
// 404 - agent is up - set version to 1
if (error.status === 404) {
return { version: 1 };
}
return $q.reject(error);
}
}
}
}
);
}
]);

View File

@ -0,0 +1,10 @@
angular.module('portainer.agent')
.factory('AgentVersion1', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
'use strict';
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/agents', {
endpointId: EndpointProvider.endpointID
},
{
query: { method: 'GET', isArray: true }
});
}]);

View File

@ -0,0 +1,22 @@
angular.module('portainer.agent')
.factory('BrowseVersion1', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function BrowseFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
'use strict';
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/browse/:volumeID/:action', {
endpointId: EndpointProvider.endpointID
},
{
ls: {
method: 'GET', isArray: true, params: { action: 'ls' }
},
get: {
method: 'GET', params: { action: 'get' },
transformResponse: browseGetResponse
},
delete: {
method: 'DELETE', params: { action: 'delete' }
},
rename: {
method: 'PUT', params: { action: 'rename' }
}
});
}]);

View File

@ -1,12 +1,17 @@
angular.module('portainer.agent').factory('AgentService', [ angular.module('portainer.agent').factory('AgentService', [
'$q', 'Agent','HttpRequestHelper', 'Host', '$q', 'Agent', 'AgentVersion1', 'HttpRequestHelper', 'Host', 'StateManager',
function AgentServiceFactory($q, Agent, HttpRequestHelper, Host) { function AgentServiceFactory($q, Agent, AgentVersion1, HttpRequestHelper, Host, StateManager) {
'use strict'; 'use strict';
var service = {}; var service = {};
service.agents = agents; service.agents = agents;
service.hostInfo = hostInfo; service.hostInfo = hostInfo;
function getAgentApiVersion() {
var state = StateManager.getState();
return state.endpoint.agentApiVersion;
}
function hostInfo(nodeName) { function hostInfo(nodeName) {
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
return Host.info().$promise; return Host.info().$promise;
@ -15,7 +20,10 @@ angular.module('portainer.agent').factory('AgentService', [
function agents() { function agents() {
var deferred = $q.defer(); var deferred = $q.defer();
Agent.query({}) var agentVersion = getAgentApiVersion();
var service = agentVersion > 1 ? Agent : AgentVersion1;
service.query({ version: agentVersion })
.$promise.then(function success(data) { .$promise.then(function success(data) {
var agents = data.map(function(item) { var agents = data.map(function(item) {
return new AgentViewModel(item); return new AgentViewModel(item);

View File

@ -1,6 +1,6 @@
angular.module('portainer.agent').factory('HostBrowserService', [ angular.module('portainer.agent').factory('HostBrowserService', [
'Browse', 'Upload', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q', 'Browse', 'Upload', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q', 'StateManager',
function HostBrowserServiceFactory(Browse, Upload, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q) { function HostBrowserServiceFactory(Browse, Upload, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q, StateManager) {
var service = {}; var service = {};
service.ls = ls; service.ls = ls;
@ -31,7 +31,15 @@ angular.module('portainer.agent').factory('HostBrowserService', [
function upload(path, file, onProgress) { function upload(path, file, onProgress) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/docker/browse/put'; var agentVersion = StateManager.getAgentApiVersion();
var url =
API_ENDPOINT_ENDPOINTS +
'/' +
EndpointProvider.endpointID() +
'/docker' +
(agentVersion > 1 ? '/v' + agentVersion : '') +
'/browse/put';
Upload.upload({ Upload.upload({
url: url, url: url,
data: { file: file, Path: path } data: { file: file, Path: path }

View File

@ -0,0 +1,14 @@
angular.module('portainer.agent').service('AgentPingService', [
'AgentPing',
function AgentPingService(AgentPing) {
var service = {};
service.ping = ping;
function ping() {
return AgentPing.ping().$promise;
}
return service;
}
]);

View File

@ -1,19 +1,29 @@
angular.module('portainer.agent').factory('VolumeBrowserService', [ angular.module('portainer.agent').factory('VolumeBrowserService', [
'$q', 'Browse', 'StateManager', 'Browse', 'BrowseVersion1', '$q', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'Upload',
function VolumeBrowserServiceFactory($q, Browse) { function VolumeBrowserServiceFactory(StateManager, Browse, BrowseVersion1, $q, API_ENDPOINT_ENDPOINTS, EndpointProvider, Upload) {
'use strict'; 'use strict';
var service = {}; var service = {};
function getAgentApiVersion() {
var state = StateManager.getState();
return state.endpoint.agentApiVersion;
}
function getBrowseService() {
var agentVersion = getAgentApiVersion();
return agentVersion > 1 ? Browse : BrowseVersion1;
}
service.ls = function(volumeId, path) { service.ls = function(volumeId, path) {
return Browse.ls({ volumeID: volumeId, path: path }).$promise; return getBrowseService().ls({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise;
}; };
service.get = function(volumeId, path) { service.get = function(volumeId, path) {
return Browse.get({ volumeID: volumeId, path: path }).$promise; return getBrowseService().get({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise;
}; };
service.delete = function(volumeId, path) { service.delete = function(volumeId, path) {
return Browse.delete({ volumeID: volumeId, path: path }).$promise; return getBrowseService().delete({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise;
}; };
service.rename = function(volumeId, path, newPath) { service.rename = function(volumeId, path, newPath) {
@ -21,7 +31,30 @@ angular.module('portainer.agent').factory('VolumeBrowserService', [
CurrentFilePath: path, CurrentFilePath: path,
NewFilePath: newPath NewFilePath: newPath
}; };
return Browse.rename({ volumeID: volumeId }, payload).$promise; return getBrowseService().rename({ volumeID: volumeId, version: getAgentApiVersion() }, payload).$promise;
};
service.upload = function upload(path, file, volumeId, onProgress) {
var deferred = $q.defer();
var agentVersion = StateManager.getAgentApiVersion();
if (agentVersion <2) {
deferred.reject('upload is not supported on this agent version');
return;
}
var url =
API_ENDPOINT_ENDPOINTS +
'/' +
EndpointProvider.endpointID() +
'/docker' +
'/v' + agentVersion +
'/browse/put?volumeID=' +
volumeId;
Upload.upload({
url: url,
data: { file: file, Path: path }
}).then(deferred.resolve, deferred.reject, onProgress);
return deferred.promise;
}; };
return service; return service;

View File

@ -10,12 +10,12 @@
<host-details-panel <host-details-panel
host="$ctrl.hostDetails" host="$ctrl.hostDetails"
is-agent="$ctrl.isAgent" is-browse-enabled="$ctrl.isAgent && $ctrl.agentApiVersion > 1"
browse-url="{{$ctrl.browseUrl}}"></host-details-panel> browse-url="{{$ctrl.browseUrl}}"></host-details-panel>
<engine-details-panel engine="$ctrl.engineDetails"></engine-details-panel> <engine-details-panel engine="$ctrl.engineDetails"></engine-details-panel>
<devices-panel ng-if="$ctrl.isAgent" devices="$ctrl.devices"></devices-panel> <devices-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1" devices="$ctrl.devices"></devices-panel>
<disks-panel ng-if="$ctrl.isAgent" disks="$ctrl.disks"></disks-panel> <disks-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1" disks="$ctrl.disks"></disks-panel>
<ng-transclude></ng-transclude> <ng-transclude></ng-transclude>

View File

@ -6,6 +6,7 @@ angular.module('portainer.docker').component('hostOverview', {
devices: '<', devices: '<',
disks: '<', disks: '<',
isAgent: '<', isAgent: '<',
agentApiVersion: '<',
refreshUrl: '@', refreshUrl: '@',
browseUrl: '@' browseUrl: '@'
}, },

View File

@ -26,7 +26,7 @@
<td>Total memory</td> <td>Total memory</td>
<td>{{ $ctrl.host.totalMemory | humansize }}</td> <td>{{ $ctrl.host.totalMemory | humansize }}</td>
</tr> </tr>
<tr ng-if="$ctrl.isAgent"> <tr ng-if="$ctrl.isBrowseEnabled">
<td colspan="2"> <td colspan="2">
<button <button
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"

View File

@ -3,7 +3,7 @@ angular.module('portainer.docker').component('hostDetailsPanel', {
'app/docker/components/host-view-panels/host-details-panel/host-details-panel.html', 'app/docker/components/host-view-panels/host-details-panel/host-details-panel.html',
bindings: { bindings: {
host: '<', host: '<',
isAgent: '<', isBrowseEnabled: '<',
browseUrl: '@' browseUrl: '@'
} }
}); });

View File

@ -10,10 +10,14 @@ angular.module('portainer.docker').controller('HostViewController', [
this.engineDetails = {}; this.engineDetails = {};
this.hostDetails = {}; this.hostDetails = {};
this.devices = null;
this.disks = null;
function initView() { function initView() {
var applicationState = StateManager.getState(); var applicationState = StateManager.getState();
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy; ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
var agentApiVersion = applicationState.endpoint.agentApiVersion;
ctrl.state.agentApiVersion = agentApiVersion;
$q.all({ $q.all({
version: SystemService.version(), version: SystemService.version(),
@ -23,7 +27,7 @@ angular.module('portainer.docker').controller('HostViewController', [
ctrl.engineDetails = buildEngineDetails(data); ctrl.engineDetails = buildEngineDetails(data);
ctrl.hostDetails = buildHostDetails(data.info); ctrl.hostDetails = buildHostDetails(data.info);
if (ctrl.state.isAgent) { if (ctrl.state.isAgent && agentApiVersion > 1) {
return AgentService.hostInfo(data.info.Hostname).then(function onHostInfoLoad(agentHostInfo) { return AgentService.hostInfo(data.info.Hostname).then(function onHostInfoLoad(agentHostInfo) {
ctrl.devices = agentHostInfo.PCIDevices; ctrl.devices = agentHostInfo.PCIDevices;
ctrl.disks = agentHostInfo.PhysicalDisks; ctrl.disks = agentHostInfo.PhysicalDisks;

View File

@ -2,6 +2,7 @@
engine-details="$ctrl.engineDetails" engine-details="$ctrl.engineDetails"
host-details="$ctrl.hostDetails" host-details="$ctrl.hostDetails"
is-agent="$ctrl.state.isAgent" is-agent="$ctrl.state.isAgent"
agent-api-version="$ctrl.state.agentApiVersion"
disks="$ctrl.disks" disks="$ctrl.disks"
devices="$ctrl.devices" devices="$ctrl.devices"

View File

@ -20,9 +20,13 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [
ctrl.engineDetails = buildEngineDetails(node); ctrl.engineDetails = buildEngineDetails(node);
ctrl.nodeDetails = buildNodeDetails(node); ctrl.nodeDetails = buildNodeDetails(node);
if (ctrl.state.isAgent) { if (ctrl.state.isAgent) {
AgentService.hostInfo(node.Hostname).then(function onHostInfoLoad( var agentApiVersion = applicationState.endpoint.agentApiVersion;
agentHostInfo ctrl.state.agentApiVersion = agentApiVersion;
) { if (agentApiVersion < 2) {
return;
}
AgentService.hostInfo(node.Hostname)
.then(function onHostInfoLoad(agentHostInfo) {
ctrl.devices = agentHostInfo.PCIDevices; ctrl.devices = agentHostInfo.PCIDevices;
ctrl.disks = agentHostInfo.PhysicalDisks; ctrl.disks = agentHostInfo.PhysicalDisks;
}); });

View File

@ -1,4 +1,5 @@
<host-overview <host-overview
agent-api-version="$ctrl.state.agentApiVersion"
is-agent="$ctrl.state.isAgent" is-agent="$ctrl.state.isAgent"
host-details="$ctrl.hostDetails" host-details="$ctrl.hostDetails"
engine-details="$ctrl.engineDetails" engine-details="$ctrl.engineDetails"

View File

@ -1,10 +1,11 @@
angular.module('portainer.docker') angular.module('portainer.docker')
.controller('BrowseVolumeController', ['$scope', '$transition$', .controller('BrowseVolumeController', ['$scope', '$transition$', 'StateManager',
function ($scope, $transition$) { function ($scope, $transition$, StateManager) {
function initView() { function initView() {
$scope.volumeId = $transition$.params().id; $scope.volumeId = $transition$.params().id;
$scope.nodeName = $transition$.params().nodeName; $scope.nodeName = $transition$.params().nodeName;
$scope.agentApiVersion = StateManager.getAgentApiVersion();
} }
initView(); initView();

View File

@ -10,6 +10,8 @@
<volume-browser <volume-browser
volume-id="volumeId" volume-id="volumeId"
node-name="nodeName" node-name="nodeName"
is-upload-enabled="agentApiVersion > 1"
></volume-browser> ></volume-browser>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
angular.module('portainer.app') angular.module('portainer.app')
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'APPLICATION_CACHE_VALIDITY', .factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'APPLICATION_CACHE_VALIDITY', 'AgentPingService',
function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY) { function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY, AgentPingService) {
'use strict'; 'use strict';
var manager = {}; var manager = {};
@ -157,6 +157,14 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
state.endpoint.name = name; state.endpoint.name = name;
state.endpoint.apiVersion = endpointAPIVersion; state.endpoint.apiVersion = endpointAPIVersion;
state.endpoint.extensions = assignExtensions(extensions); state.endpoint.extensions = assignExtensions(extensions);
if (endpointMode.agentProxy) {
return AgentPingService.ping().then(function onPingSuccess(data) {
state.endpoint.agentApiVersion = data.version;
});
}
}).then(function () {
LocalStorage.storeEndpointState(state.endpoint); LocalStorage.storeEndpointState(state.endpoint);
deferred.resolve(); deferred.resolve();
}) })
@ -170,5 +178,9 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
return deferred.promise; return deferred.promise;
}; };
manager.getAgentApiVersion = function getAgentApiVersion() {
return state.endpoint.agentApiVersion;
};
return manager; return manager;
}]); }]);