@@ -41,23 +41,29 @@
-
+
- Go to parent
+ Go
+ to parent
|
-
+
-
+
- {{ item.Name }}
+ {{ item.Name }}
- {{ item.Name }}
+ {{
+ item.Name }}
|
{{ item.Size | humansize }} |
@@ -65,13 +71,14 @@
{{ item.ModTime | getisodatefromtimestamp }}
-
+
Download
Rename
-
+
Delete
|
@@ -87,4 +94,4 @@
-
+
\ No newline at end of file
diff --git a/app/agent/components/files-datatable/files-datatable.js b/app/agent/components/files-datatable/files-datatable.js
new file mode 100644
index 000000000..4e90e1d00
--- /dev/null
+++ b/app/agent/components/files-datatable/files-datatable.js
@@ -0,0 +1,22 @@
+angular.module('portainer.agent').component('filesDatatable', {
+ templateUrl: 'app/agent/components/files-datatable/files-datatable.html',
+ controller: 'GenericDatatableController',
+ bindings: {
+ titleText: '@',
+ titleIcon: '@',
+ dataset: '<',
+ tableKey: '@',
+ orderBy: '@',
+ reverseOrder: '<',
+
+ isRoot: '<',
+ goToParent: '&',
+ browse: '&',
+ rename: '&',
+ download: '&',
+ delete: '&',
+
+ isUploadAllowed: '<',
+ onFileSelectedForUpload: '<'
+ }
+});
diff --git a/app/agent/components/host-browser/host-browser-controller.js b/app/agent/components/host-browser/host-browser-controller.js
new file mode 100644
index 000000000..a50cb6b6e
--- /dev/null
+++ b/app/agent/components/host-browser/host-browser-controller.js
@@ -0,0 +1,147 @@
+angular.module('portainer.agent').controller('HostBrowserController', [
+ 'HostBrowserService', 'Notifications', 'FileSaver', 'ModalService',
+ function HostBrowserController(HostBrowserService, Notifications, FileSaver, ModalService) {
+ var ctrl = this;
+ var ROOT_PATH = '/host';
+ ctrl.state = {
+ path: ROOT_PATH
+ };
+
+ ctrl.goToParent = goToParent;
+ ctrl.browse = browse;
+ ctrl.renameFile = renameFile;
+ ctrl.downloadFile = downloadFile;
+ ctrl.deleteFile = confirmDeleteFile;
+ ctrl.isRoot = isRoot;
+ ctrl.onFileSelectedForUpload = onFileSelectedForUpload;
+ ctrl.$onInit = $onInit;
+ ctrl.getRelativePath = getRelativePath;
+
+ function getRelativePath(path) {
+ path = path || ctrl.state.path;
+ var rootPathRegex = new RegExp('^' + ROOT_PATH + '\/?');
+ var relativePath = path.replace(rootPathRegex, '/');
+ return relativePath;
+ }
+
+ function goToParent() {
+ getFilesForPath(parentPath(this.state.path));
+ }
+
+ function isRoot() {
+ return ctrl.state.path === ROOT_PATH;
+ }
+
+ function browse(folder) {
+ getFilesForPath(buildPath(ctrl.state.path, folder));
+ }
+
+ function getFilesForPath(path) {
+ HostBrowserService.ls(path)
+ .then(function onFilesLoaded(files) {
+ ctrl.state.path = path;
+ ctrl.files = files;
+ })
+ .catch(function onLoadingFailed(err) {
+ Notifications.error('Failure', err, 'Unable to browse');
+ });
+ }
+
+ function renameFile(name, newName) {
+ var filePath = buildPath(ctrl.state.path, name);
+ var newFilePath = buildPath(ctrl.state.path, newName);
+
+ HostBrowserService.rename(filePath, newFilePath)
+ .then(function onRenameSuccess() {
+ Notifications.success('File successfully renamed', getRelativePath(newFilePath));
+ return HostBrowserService.ls(ctrl.state.path);
+ })
+ .then(function onFilesLoaded(files) {
+ ctrl.files = files;
+ })
+ .catch(function notifyOnError(err) {
+ Notifications.error('Failure', err, 'Unable to rename file');
+ });
+ }
+
+ function downloadFile(file) {
+ var filePath = buildPath(ctrl.state.path, file);
+ HostBrowserService.get(filePath)
+ .then(function onFileReceived(data) {
+ var downloadData = new Blob([data.file], {
+ type: 'text/plain;charset=utf-8'
+ });
+ FileSaver.saveAs(downloadData, file);
+ })
+ .catch(function notifyOnError(err) {
+ Notifications.error('Failure', err, 'Unable to download file');
+ });
+ }
+
+ function confirmDeleteFile(name) {
+ var filePath = buildPath(ctrl.state.path, name);
+
+ ModalService.confirmDeletion(
+ 'Are you sure that you want to delete ' + getRelativePath(filePath) + ' ?',
+ function onConfirm(confirmed) {
+ if (!confirmed) {
+ return;
+ }
+ return deleteFile(filePath);
+ }
+ );
+ }
+
+ function deleteFile(path) {
+ HostBrowserService.delete(path)
+ .then(function onDeleteSuccess() {
+ Notifications.success('File successfully deleted', getRelativePath(path));
+ return HostBrowserService.ls(ctrl.state.path);
+ })
+ .then(function onFilesLoaded(data) {
+ ctrl.files = data;
+ })
+ .catch(function notifyOnError(err) {
+ Notifications.error('Failure', err, 'Unable to delete file');
+ });
+ }
+
+ function $onInit() {
+ getFilesForPath(ROOT_PATH);
+ }
+
+ function parentPath(path) {
+ if (path === ROOT_PATH) {
+ return ROOT_PATH;
+ }
+
+ var split = _.split(path, '/');
+ return _.join(_.slice(split, 0, split.length - 1), '/');
+ }
+
+ function buildPath(parent, file) {
+ if (parent.lastIndexOf('/') === parent.length - 1) {
+ return parent + file;
+ }
+ return parent + '/' + file;
+ }
+
+ function onFileSelectedForUpload(file) {
+ HostBrowserService.upload(ctrl.state.path, file)
+ .then(function onFileUpload() {
+ onFileUploaded();
+ })
+ .catch(function onFileUpload(err) {
+ Notifications.error('Failure', err, 'Unable to upload file');
+ });
+ }
+
+ function onFileUploaded() {
+ refreshList();
+ }
+
+ function refreshList() {
+ getFilesForPath(ctrl.state.path);
+ }
+ }
+]);
diff --git a/app/agent/components/host-browser/host-browser.html b/app/agent/components/host-browser/host-browser.html
new file mode 100644
index 000000000..cf7b59306
--- /dev/null
+++ b/app/agent/components/host-browser/host-browser.html
@@ -0,0 +1,16 @@
+
+
+
diff --git a/app/agent/components/host-browser/host-browser.js b/app/agent/components/host-browser/host-browser.js
new file mode 100644
index 000000000..a136730ee
--- /dev/null
+++ b/app/agent/components/host-browser/host-browser.js
@@ -0,0 +1,5 @@
+angular.module('portainer.agent').component('hostBrowser', {
+ controller: 'HostBrowserController',
+ templateUrl: 'app/agent/components/host-browser/host-browser.html',
+ bindings: {}
+});
diff --git a/app/agent/components/volume-browser/volume-browser-datatable/volume-browser-datatable.js b/app/agent/components/volume-browser/volume-browser-datatable/volume-browser-datatable.js
deleted file mode 100644
index e3139974d..000000000
--- a/app/agent/components/volume-browser/volume-browser-datatable/volume-browser-datatable.js
+++ /dev/null
@@ -1,15 +0,0 @@
-angular.module('portainer.agent').component('volumeBrowserDatatable', {
- templateUrl: 'app/agent/components/volume-browser/volume-browser-datatable/volumeBrowserDatatable.html',
- controller: 'GenericDatatableController',
- bindings: {
- titleText: '@',
- titleIcon: '@',
- dataset: '<',
- tableKey: '@',
- orderBy: '@',
- reverseOrder: '<'
- },
- require: {
- volumeBrowser: '^^volumeBrowser'
- }
-});
diff --git a/app/agent/components/volume-browser/volumeBrowser.html b/app/agent/components/volume-browser/volumeBrowser.html
index 643d8c88b..3759f22d2 100644
--- a/app/agent/components/volume-browser/volumeBrowser.html
+++ b/app/agent/components/volume-browser/volumeBrowser.html
@@ -1,5 +1,11 @@
-
+ is-root="$ctrl.state.path === '/'"
+ go-to-parent="$ctrl.up()"
+ browse="$ctrl.browse(name)"
+ rename="$ctrl.rename(name, newName)"
+ download="$ctrl.download(name)"
+ delete="$ctrl.delete(name)"
+>
diff --git a/app/agent/services/hostBrowserService.js b/app/agent/services/hostBrowserService.js
new file mode 100644
index 000000000..935d34be4
--- /dev/null
+++ b/app/agent/services/hostBrowserService.js
@@ -0,0 +1,44 @@
+angular.module('portainer.agent').factory('HostBrowserService', [
+ 'Browse', 'Upload', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q',
+ function HostBrowserServiceFactory(Browse, Upload, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q) {
+ var service = {};
+
+ service.ls = ls;
+ service.get = get;
+ service.delete = deletePath;
+ service.rename = rename;
+ service.upload = upload;
+
+ function ls(path) {
+ return Browse.ls({ path: path }).$promise;
+ }
+
+ function get(path) {
+ return Browse.get({ path: path }).$promise;
+ }
+
+ function deletePath(path) {
+ return Browse.delete({ path: path }).$promise;
+ }
+
+ function rename(path, newPath) {
+ var payload = {
+ CurrentFilePath: path,
+ NewFilePath: newPath
+ };
+ return Browse.rename({}, payload).$promise;
+ }
+
+ function upload(path, file, onProgress) {
+ var deferred = $q.defer();
+ var url = API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/docker/browse/put';
+ Upload.upload({
+ url: url,
+ data: { file: file, Path: path }
+ }).then(deferred.resolve, deferred.reject, onProgress);
+ return deferred.promise;
+ }
+
+ return service;
+ }
+]);
diff --git a/app/agent/services/volumeBrowserService.js b/app/agent/services/volumeBrowserService.js
index 2a5c92f56..1da020c38 100644
--- a/app/agent/services/volumeBrowserService.js
+++ b/app/agent/services/volumeBrowserService.js
@@ -1,27 +1,29 @@
-angular.module('portainer.agent')
-.factory('VolumeBrowserService', ['$q', 'Browse', function VolumeBrowserServiceFactory($q, Browse) {
- 'use strict';
- var service = {};
+angular.module('portainer.agent').factory('VolumeBrowserService', [
+ '$q', 'Browse',
+ function VolumeBrowserServiceFactory($q, Browse) {
+ 'use strict';
+ var service = {};
- service.ls = function(volumeId, path) {
- return Browse.ls({ volumeID: volumeId, path: path }).$promise;
- };
-
- service.get = function(volumeId, path) {
- return Browse.get({ volumeID: volumeId, path: path }).$promise;
- };
-
- service.delete = function(volumeId, path) {
- return Browse.delete({ volumeID: volumeId, path: path }).$promise;
- };
-
- service.rename = function(volumeId, path, newPath) {
- var payload = {
- CurrentFilePath: path,
- NewFilePath: newPath
+ service.ls = function(volumeId, path) {
+ return Browse.ls({ volumeID: volumeId, path: path }).$promise;
};
- return Browse.rename({ volumeID: volumeId }, payload).$promise;
- };
- return service;
-}]);
+ service.get = function(volumeId, path) {
+ return Browse.get({ volumeID: volumeId, path: path }).$promise;
+ };
+
+ service.delete = function(volumeId, path) {
+ return Browse.delete({ volumeID: volumeId, path: path }).$promise;
+ };
+
+ service.rename = function(volumeId, path, newPath) {
+ var payload = {
+ CurrentFilePath: path,
+ NewFilePath: newPath
+ };
+ return Browse.rename({ volumeID: volumeId }, payload).$promise;
+ };
+
+ return service;
+ }
+]);
diff --git a/app/docker/__module.js b/app/docker/__module.js
index 23ce18433..4b4bdfc74 100644
--- a/app/docker/__module.js
+++ b/app/docker/__module.js
@@ -139,6 +139,16 @@ angular.module('portainer.docker', ['portainer.app'])
}
};
+ var hostBrowser = {
+ name: 'docker.host.browser',
+ url: '/browser',
+ views: {
+ 'content@': {
+ component: 'hostBrowserView'
+ }
+ }
+ };
+
var events = {
name: 'docker.events',
url: '/events',
@@ -243,6 +253,16 @@ angular.module('portainer.docker', ['portainer.app'])
}
};
+ var nodeBrowser = {
+ name: 'docker.nodes.node.browse',
+ url: '/browse',
+ views: {
+ 'content@': {
+ component: 'nodeBrowserView'
+ }
+ }
+ };
+
var secrets = {
name: 'docker.secrets',
url: '/secrets',
@@ -414,6 +434,8 @@ angular.module('portainer.docker', ['portainer.app'])
}
};
+
+
$stateRegistryProvider.register(configs);
$stateRegistryProvider.register(config);
$stateRegistryProvider.register(configCreation);
@@ -427,6 +449,7 @@ angular.module('portainer.docker', ['portainer.app'])
$stateRegistryProvider.register(docker);
$stateRegistryProvider.register(dashboard);
$stateRegistryProvider.register(host);
+ $stateRegistryProvider.register(hostBrowser);
$stateRegistryProvider.register(events);
$stateRegistryProvider.register(images);
$stateRegistryProvider.register(image);
@@ -437,6 +460,7 @@ angular.module('portainer.docker', ['portainer.app'])
$stateRegistryProvider.register(networkCreation);
$stateRegistryProvider.register(nodes);
$stateRegistryProvider.register(node);
+ $stateRegistryProvider.register(nodeBrowser);
$stateRegistryProvider.register(secrets);
$stateRegistryProvider.register(secret);
$stateRegistryProvider.register(secretCreation);
diff --git a/app/docker/components/dockerSidebarContent/docker-sidebar-content.js b/app/docker/components/dockerSidebarContent/docker-sidebar-content.js
index 325a3497d..5031a44a7 100644
--- a/app/docker/components/dockerSidebarContent/docker-sidebar-content.js
+++ b/app/docker/components/dockerSidebarContent/docker-sidebar-content.js
@@ -1,9 +1,9 @@
angular.module('portainer.docker').component('dockerSidebarContent', {
templateUrl: 'app/docker/components/dockerSidebarContent/dockerSidebarContent.html',
bindings: {
- 'endpointApiVersion': '<',
- 'swarmManagement': '<',
- 'standaloneManagement': '<',
- 'adminAccess': '<'
+ endpointApiVersion: '<',
+ swarmManagement: '<',
+ standaloneManagement: '<',
+ adminAccess: '<'
}
});
diff --git a/app/docker/components/host-overview/host-overview.html b/app/docker/components/host-overview/host-overview.html
index baa43628e..81ac13c62 100644
--- a/app/docker/components/host-overview/host-overview.html
+++ b/app/docker/components/host-overview/host-overview.html
@@ -1,13 +1,17 @@
-
+
Docker
-
+
diff --git a/app/docker/components/host-overview/host-overview.js b/app/docker/components/host-overview/host-overview.js
index add9515b4..78883e4e8 100644
--- a/app/docker/components/host-overview/host-overview.js
+++ b/app/docker/components/host-overview/host-overview.js
@@ -6,7 +6,8 @@ angular.module('portainer.docker').component('hostOverview', {
devices: '<',
disks: '<',
isAgent: '<',
- refreshUrl: '@'
+ refreshUrl: '@',
+ browseUrl: '@'
},
transclude: true
});
diff --git a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html
index 2ea05f88f..9bb9b7f85 100644
--- a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html
+++ b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html
@@ -26,6 +26,17 @@
Total memory |
{{ $ctrl.host.totalMemory | humansize }} |
+
+
+
+ |
+
+
diff --git a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js
index 6865b5872..328832c42 100644
--- a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js
+++ b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js
@@ -3,6 +3,7 @@ angular.module('portainer.docker').component('hostDetailsPanel', {
'app/docker/components/host-view-panels/host-details-panel/host-details-panel.html',
bindings: {
host: '<',
- isAgent: '<'
+ isAgent: '<',
+ browseUrl: '@'
}
});
diff --git a/app/docker/views/host/host-browser-view/host-browser-view-controller.js b/app/docker/views/host/host-browser-view/host-browser-view-controller.js
new file mode 100644
index 000000000..9638e966e
--- /dev/null
+++ b/app/docker/views/host/host-browser-view/host-browser-view-controller.js
@@ -0,0 +1,21 @@
+angular
+ .module('portainer.docker')
+ .controller('HostBrowserViewController', [
+ 'SystemService', 'HttpRequestHelper',
+ function HostBrowserViewController(SystemService, HttpRequestHelper) {
+ var ctrl = this;
+
+ ctrl.$onInit = $onInit;
+
+ function $onInit() {
+ loadInfo();
+ }
+
+ function loadInfo() {
+ SystemService.info().then(function onInfoLoaded(host) {
+ HttpRequestHelper.setPortainerAgentTargetHeader(host.Name);
+ ctrl.host = host;
+ });
+ }
+ }
+ ]);
diff --git a/app/docker/views/host/host-browser-view/host-browser-view.html b/app/docker/views/host/host-browser-view/host-browser-view.html
new file mode 100644
index 000000000..2d87e4b2e
--- /dev/null
+++ b/app/docker/views/host/host-browser-view/host-browser-view.html
@@ -0,0 +1,14 @@
+
+
+
+ Host > {{ $ctrl.host.Name }} > browse
+
+
+
+
diff --git a/app/docker/views/host/host-browser-view/host-browser-view.js b/app/docker/views/host/host-browser-view/host-browser-view.js
new file mode 100644
index 000000000..7ce93994a
--- /dev/null
+++ b/app/docker/views/host/host-browser-view/host-browser-view.js
@@ -0,0 +1,4 @@
+angular.module('portainer.docker').component('hostBrowserView', {
+ templateUrl: 'app/docker/views/host/host-browser-view/host-browser-view.html',
+ controller: 'HostBrowserViewController'
+});
diff --git a/app/docker/views/host/host-view.html b/app/docker/views/host/host-view.html
index 419e96995..ada86fef8 100644
--- a/app/docker/views/host/host-view.html
+++ b/app/docker/views/host/host-view.html
@@ -1,8 +1,10 @@
\ No newline at end of file
diff --git a/app/docker/views/nodes/node-browser/node-browser-controller.js b/app/docker/views/nodes/node-browser/node-browser-controller.js
new file mode 100644
index 000000000..d846ac8f9
--- /dev/null
+++ b/app/docker/views/nodes/node-browser/node-browser-controller.js
@@ -0,0 +1,20 @@
+angular.module('portainer.docker').controller('NodeBrowserController', [
+ 'NodeService', 'HttpRequestHelper', '$stateParams',
+ function NodeBrowserController(NodeService, HttpRequestHelper, $stateParams) {
+ var ctrl = this;
+
+ ctrl.$onInit = $onInit;
+
+ function $onInit() {
+ ctrl.nodeId = $stateParams.id;
+ loadNode();
+ }
+
+ function loadNode() {
+ NodeService.node(ctrl.nodeId).then(function onNodeLoaded(node) {
+ HttpRequestHelper.setPortainerAgentTargetHeader(node.Hostname);
+ ctrl.node = node;
+ });
+ }
+ }
+]);
diff --git a/app/docker/views/nodes/node-browser/node-browser.html b/app/docker/views/nodes/node-browser/node-browser.html
new file mode 100644
index 000000000..2edeae199
--- /dev/null
+++ b/app/docker/views/nodes/node-browser/node-browser.html
@@ -0,0 +1,14 @@
+
+
+
+ Swarm > {{ $ctrl.node.Hostname }} > browse
+
+
+
+
diff --git a/app/docker/views/nodes/node-browser/node-browser.js b/app/docker/views/nodes/node-browser/node-browser.js
new file mode 100644
index 000000000..3e7269384
--- /dev/null
+++ b/app/docker/views/nodes/node-browser/node-browser.js
@@ -0,0 +1,4 @@
+angular.module('portainer.docker').component('nodeBrowserView', {
+ templateUrl: 'app/docker/views/nodes/node-browser/node-browser.html',
+ controller: 'NodeBrowserController'
+});
diff --git a/app/docker/views/nodes/node-details/node-details-view.html b/app/docker/views/nodes/node-details/node-details-view.html
index 13df99558..17d0dc470 100644
--- a/app/docker/views/nodes/node-details/node-details-view.html
+++ b/app/docker/views/nodes/node-details/node-details-view.html
@@ -2,9 +2,11 @@
is-agent="$ctrl.state.isAgent"
host-details="$ctrl.hostDetails"
engine-details="$ctrl.engineDetails"
- refresh-url="docker.nodes.node"
disks="$ctrl.disks"
devices="$ctrl.devices"
+
+ refresh-url="docker.nodes.node"
+ browse-url="docker.nodes.node.browse"
>