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',
bindings: {
volumeId: '<',
nodeName: '<'
nodeName: '<',
isUploadEnabled: '<'
}
});

View File

@ -8,4 +8,7 @@
rename="$ctrl.rename(name, newName)"
download="$ctrl.download(name)"
delete="$ctrl.delete(name)"
is-upload-allowed="$ctrl.isUploadEnabled"
on-file-selected-for-upload="$ctrl.onFileSelectedForUpload"
></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) {
if (path.lastIndexOf('/') === 0) {
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')
.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';
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/agents', {
endpointId: EndpointProvider.endpointID
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/agents', {
endpointId: EndpointProvider.endpointID,
version: StateManager.getAgentApiVersion
},
{
query: { method: 'GET', isArray: true }

View File

@ -1,8 +1,10 @@
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';
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/browse/:action', {
endpointId: EndpointProvider.endpointID
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/browse/:action', {
endpointId: EndpointProvider.endpointID,
version: StateManager.getAgentApiVersion
},
{
ls: {

View File

@ -1,11 +1,12 @@
angular.module('portainer.agent').factory('Host', [
'$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider',
function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
'$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'StateManager',
function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, StateManager) {
'use strict';
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' } }

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

View File

@ -1,6 +1,6 @@
angular.module('portainer.agent').factory('HostBrowserService', [
'Browse', 'Upload', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q',
function HostBrowserServiceFactory(Browse, Upload, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q) {
'Browse', 'Upload', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q', 'StateManager',
function HostBrowserServiceFactory(Browse, Upload, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q, StateManager) {
var service = {};
service.ls = ls;
@ -31,9 +31,17 @@ angular.module('portainer.agent').factory('HostBrowserService', [
function upload(path, file, onProgress) {
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({
url: url,
url: url,
data: { file: file, Path: path }
}).then(deferred.resolve, deferred.reject, onProgress);
return deferred.promise;

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,27 +1,60 @@
angular.module('portainer.agent').factory('VolumeBrowserService', [
'$q', 'Browse',
function VolumeBrowserServiceFactory($q, Browse) {
'StateManager', 'Browse', 'BrowseVersion1', '$q', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'Upload',
function VolumeBrowserServiceFactory(StateManager, Browse, BrowseVersion1, $q, API_ENDPOINT_ENDPOINTS, EndpointProvider, Upload) {
'use strict';
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) {
return Browse.ls({ volumeID: volumeId, path: path }).$promise;
return getBrowseService().ls({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise;
};
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) {
return Browse.delete({ volumeID: volumeId, path: path }).$promise;
return getBrowseService().delete({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise;
};
service.rename = function(volumeId, path, newPath) {
var payload = {
CurrentFilePath: path,
CurrentFilePath: path,
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;

View File

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

View File

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

View File

@ -26,7 +26,7 @@
<td>Total memory</td>
<td>{{ $ctrl.host.totalMemory | humansize }}</td>
</tr>
<tr ng-if="$ctrl.isAgent">
<tr ng-if="$ctrl.isBrowseEnabled">
<td colspan="2">
<button
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',
bindings: {
host: '<',
isAgent: '<',
isBrowseEnabled: '<',
browseUrl: '@'
}
});

View File

@ -10,10 +10,14 @@ angular.module('portainer.docker').controller('HostViewController', [
this.engineDetails = {};
this.hostDetails = {};
this.devices = null;
this.disks = null;
function initView() {
var applicationState = StateManager.getState();
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
var agentApiVersion = applicationState.endpoint.agentApiVersion;
ctrl.state.agentApiVersion = agentApiVersion;
$q.all({
version: SystemService.version(),
@ -23,7 +27,7 @@ angular.module('portainer.docker').controller('HostViewController', [
ctrl.engineDetails = buildEngineDetails(data);
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) {
ctrl.devices = agentHostInfo.PCIDevices;
ctrl.disks = agentHostInfo.PhysicalDisks;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
angular.module('portainer.app')
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'APPLICATION_CACHE_VALIDITY',
function StateManagerFactory($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, AgentPingService) {
'use strict';
var manager = {};
@ -157,6 +157,14 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
state.endpoint.name = name;
state.endpoint.apiVersion = endpointAPIVersion;
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);
deferred.resolve();
})
@ -170,5 +178,9 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
return deferred.promise;
};
manager.getAgentApiVersion = function getAgentApiVersion() {
return state.endpoint.agentApiVersion;
};
return manager;
}]);