From 851607394c78edfec07f75a5f95d59cb8e4aae87 Mon Sep 17 00:00:00 2001 From: baron_l Date: Fri, 24 May 2019 23:53:10 +0200 Subject: [PATCH] feat(integrations): storidge evolution (#2711) * feat(storidge): update storidge routes * feat(storidge): add new fields on profile create/edit * feat(storidge): add drives list and details view * feat(storidge): add node details / cordon / uncordon / remove * feat(storidge): add volume and snapshot details * feat(storidge): add snapshot creation on volume details * feat(storidge): add rescan drives button * refactor(storidge): move add / remove / put in / put ouf maintenance buttons for cluster nodes * style(storidge): change cluster / node icon color based on status * feat(storidge): profiles can enable snapshots without interval + interval in minutes * refactor(storidge): split cluster and node status badge filter * fix(storidge): error on volume IOPS update * fix(storidge): snapshot can now be created without comments * feat(storidge): remove snapshots panels when volume snapshots are disabled * fix(app): paginatedItemLimit now retrieved for datables extending GenericDatatableController * fix(storidge): addDrive is called with the good parameters * fix(storidge): update model and views for Storidge v2695 * refactor(storidge): webpack migration * fix(storidge): display modifications + fix js errors * feat(storidge): snapshots, profile and nodes evolution * fix(storidge): values for InterfaceDriver on profile create/edit * feat(storidge): v5 update without style (profile / statuses / volume) * fix(storidge): description tables on the same view have now the same fixed offset * fix(app): override rdash-ui select style * Revert "fix(app): override rdash-ui select style" This reverts commit e7248332611345dc803be83560282517c44c0646. * feat(storidge): wip on update 6 * feat(storidge): update 6 * feat(storidge): update 6 * feat(storidge): update 6 * feat(storidge): update 7 - node details + cluster views * fix(storidge): update 7 - profiles creation + volume details * fix(storidge): update 7 - profile create/edit interface type * feat(storidge): update 8 - add drive * feat(storidge): update 8 - UI refactors + cluster availability * fix(storidge): update 8 - revert cluster availability * feat(storidge): update 8 - node availability on swarm overview * feat(storidge): cluster condition badge * fix(storidge): update 9 - move add storage button + api profile filesystem kv to obj * feat(storidge): update 9 - disable add drive button when action is in progress * fix(storidge): update 9 - add drive button will now change only for the concerned drive * fix(storidge): update 10 - disable remove drive button when removal in progress * fix(api): update Storidge proxy creation process * refactor(api): update version number * feat(extensions): fix an issue with Storidge API URL * feat(storidge): force the use of a manager node --- .../handler/endpointproxy/proxy_storidge.go | 2 +- .../endpoints/endpoint_extension_add.go | 6 +- api/http/proxy/manager.go | 2 +- .../nodes-datatable/nodesDatatable.html | 8 + app/docker/filters/filters.js | 12 ++ app/docker/services/nodeService.js | 21 ++ app/docker/views/volumes/edit/volume.html | 25 +++ .../views/volumes/edit/volumeController.js | 67 +++++- app/extensions/storidge/__module.js | 48 +++++ .../storidgeClusterEventsDatatable.js | 2 +- .../storidgeDrivesDatatable.html | 130 ++++++++++++ .../storidgeDrivesDatatable.js | 17 ++ .../storidgeDrivesDatatableController.js | 16 ++ .../storidgeNodesDatatable.html | 25 ++- .../nodes-datatable/storidgeNodesDatatable.js | 4 +- .../storidgeNodesDatatableController.js | 23 ++ .../storidgeProfilesDatatable.js | 2 +- .../storidgeSnapshotCreation.html | 23 ++ .../storidgeSnapshotCreation.js | 7 + .../storidgeSnapshotCreationController.js | 26 +++ .../storidgeSnapshotsDatatable.html | 95 +++++++++ .../storidgeSnapshotsDatatable.js | 13 ++ .../storidgeSnapshotsDatatableController.js | 7 + .../volumeStoridgeInfo.html | 198 ++++++++++++++++++ .../volumeStoridgeInfo.js | 7 + .../volumeStoridgeInfoController.js | 103 +++++++++ app/extensions/storidge/filters/filters.js | 51 +++++ app/extensions/storidge/models/drive.js | 9 + app/extensions/storidge/models/info.js | 2 +- app/extensions/storidge/models/node.js | 25 +++ app/extensions/storidge/models/profile.js | 76 +++++++ app/extensions/storidge/models/snapshot.js | 9 + app/extensions/storidge/models/volume.js | 40 ++++ app/extensions/storidge/rest/storidge.js | 37 +++- .../storidge/services/driveService.js | 79 +++++++ .../storidge/services/nodeService.js | 35 +++- .../storidge/services/snapshotService.js | 80 +++++++ .../storidge/services/volumeService.js | 38 ++++ .../storidge/views/cluster/cluster.html | 90 +------- .../views/cluster/clusterController.js | 20 +- .../storidge/views/drives/drives.html | 24 +++ .../storidge/views/drives/drivesController.js | 48 +++++ .../storidge/views/drives/inspect/drive.html | 56 +++++ .../views/drives/inspect/driveController.js | 49 +++++ .../storidge/views/nodes/inspect/node.html | 170 +++++++++++++++ .../views/nodes/inspect/nodeController.js | 107 ++++++++++ .../create/createProfileController.js | 44 +++- .../views/profiles/create/createprofile.html | 160 +++++++++++++- .../storidge/views/profiles/edit/profile.html | 144 +++++++++++++ .../views/profiles/edit/profileController.js | 53 ++++- .../views/profiles/profilesController.js | 8 +- .../views/snapshots/inspect/snapshot.html | 53 +++++ .../snapshots/inspect/snapshotController.js | 44 ++++ .../datatables/genericDatatableController.js | 7 +- .../components/slider/sliderController.js | 2 +- app/portainer/filters/filters.js | 2 +- .../services/legacyExtensionManager.js | 8 +- app/portainer/services/localStorage.js | 4 +- app/portainer/services/notifications.js | 2 + app/portainer/views/sidebar/sidebar.html | 9 +- assets/css/app.css | 10 + 61 files changed, 2338 insertions(+), 146 deletions(-) create mode 100644 app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatable.html create mode 100644 app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatable.js create mode 100644 app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatableController.js create mode 100644 app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatableController.js create mode 100644 app/extensions/storidge/components/snapshot-creation/storidgeSnapshotCreation.html create mode 100644 app/extensions/storidge/components/snapshot-creation/storidgeSnapshotCreation.js create mode 100644 app/extensions/storidge/components/snapshot-creation/storidgeSnapshotCreationController.js create mode 100644 app/extensions/storidge/components/snapshots-datatable/storidgeSnapshotsDatatable.html create mode 100644 app/extensions/storidge/components/snapshots-datatable/storidgeSnapshotsDatatable.js create mode 100644 app/extensions/storidge/components/snapshots-datatable/storidgeSnapshotsDatatableController.js create mode 100644 app/extensions/storidge/components/volume-storidge-info/volumeStoridgeInfo.html create mode 100644 app/extensions/storidge/components/volume-storidge-info/volumeStoridgeInfo.js create mode 100644 app/extensions/storidge/components/volume-storidge-info/volumeStoridgeInfoController.js create mode 100644 app/extensions/storidge/filters/filters.js create mode 100644 app/extensions/storidge/models/drive.js create mode 100644 app/extensions/storidge/models/snapshot.js create mode 100644 app/extensions/storidge/models/volume.js create mode 100644 app/extensions/storidge/services/driveService.js create mode 100644 app/extensions/storidge/services/snapshotService.js create mode 100644 app/extensions/storidge/services/volumeService.js create mode 100644 app/extensions/storidge/views/drives/drives.html create mode 100644 app/extensions/storidge/views/drives/drivesController.js create mode 100644 app/extensions/storidge/views/drives/inspect/drive.html create mode 100644 app/extensions/storidge/views/drives/inspect/driveController.js create mode 100644 app/extensions/storidge/views/nodes/inspect/node.html create mode 100644 app/extensions/storidge/views/nodes/inspect/nodeController.js create mode 100644 app/extensions/storidge/views/snapshots/inspect/snapshot.html create mode 100644 app/extensions/storidge/views/snapshots/inspect/snapshotController.js diff --git a/api/http/handler/endpointproxy/proxy_storidge.go b/api/http/handler/endpointproxy/proxy_storidge.go index b375cee09..465ebfb52 100644 --- a/api/http/handler/endpointproxy/proxy_storidge.go +++ b/api/http/handler/endpointproxy/proxy_storidge.go @@ -41,7 +41,7 @@ func (handler *Handler) proxyRequestsToStoridgeAPI(w http.ResponseWriter, r *htt return &httperror.HandlerError{http.StatusServiceUnavailable, "Storidge extension not supported on this endpoint", portainer.ErrEndpointExtensionNotSupported} } - proxyExtensionKey := string(endpoint.ID) + "_" + string(portainer.StoridgeEndpointExtension) + proxyExtensionKey := strconv.Itoa(endpointID) + "_" + strconv.Itoa(int(portainer.StoridgeEndpointExtension)) + "_" + storidgeExtension.URL var proxy http.Handler proxy = handler.ProxyManager.GetLegacyExtensionProxy(proxyExtensionKey) diff --git a/api/http/handler/endpoints/endpoint_extension_add.go b/api/http/handler/endpoints/endpoint_extension_add.go index a7d152058..f91a714c3 100644 --- a/api/http/handler/endpoints/endpoint_extension_add.go +++ b/api/http/handler/endpoints/endpoint_extension_add.go @@ -50,9 +50,9 @@ func (handler *Handler) endpointExtensionAdd(w http.ResponseWriter, r *http.Requ extensionType := portainer.EndpointExtensionType(payload.Type) var extension *portainer.EndpointExtension - for _, ext := range endpoint.Extensions { - if ext.Type == extensionType { - extension = &ext + for idx := range endpoint.Extensions { + if endpoint.Extensions[idx].Type == extensionType { + extension = &endpoint.Extensions[idx] } } diff --git a/api/http/proxy/manager.go b/api/http/proxy/manager.go index f7d875fcf..8d1efb404 100644 --- a/api/http/proxy/manager.go +++ b/api/http/proxy/manager.go @@ -132,7 +132,7 @@ func (manager *Manager) CreateLegacyExtensionProxy(key, extensionAPIURL string) } proxy := manager.proxyFactory.newHTTPProxy(extensionURL) - manager.extensionProxies.Set(key, proxy) + manager.legacyExtensionProxies.Set(key, proxy) return proxy, nil } diff --git a/app/docker/components/datatables/nodes-datatable/nodesDatatable.html b/app/docker/components/datatables/nodes-datatable/nodesDatatable.html index dac5c644b..5ca2ba136 100644 --- a/app/docker/components/datatables/nodes-datatable/nodesDatatable.html +++ b/app/docker/components/datatables/nodes-datatable/nodesDatatable.html @@ -63,6 +63,13 @@ + + + Availability + + + + @@ -77,6 +84,7 @@ {{ item.EngineVersion }} {{ item.Addr }} {{ item.Status }} + {{ item.Availability }} Loading... diff --git a/app/docker/filters/filters.js b/app/docker/filters/filters.js index 315f62014..3ba22ef50 100644 --- a/app/docker/filters/filters.js +++ b/app/docker/filters/filters.js @@ -97,6 +97,18 @@ angular.module('portainer.docker') return 'success'; }; }) +.filter('dockerNodeAvailabilityBadge', function () { + 'use strict'; + return function (text) { + if (text === 'pause') { + return 'warning'; + } + else if (text === 'drain') { + return 'danger'; + } + return 'success'; + }; +}) .filter('trimcontainername', function () { 'use strict'; return function (name) { diff --git a/app/docker/services/nodeService.js b/app/docker/services/nodeService.js index 7dae3f46f..943f4201d 100644 --- a/app/docker/services/nodeService.js +++ b/app/docker/services/nodeService.js @@ -9,6 +9,7 @@ angular.module('portainer.docker').factory('NodeService', [ service.nodes = nodes; service.node = node; service.updateNode = updateNode; + service.getActiveManager = getActiveManager; function node(id) { var deferred = $q.defer(); @@ -45,6 +46,26 @@ angular.module('portainer.docker').factory('NodeService', [ return Node.update({ id: node.Id, version: node.Version }, node).$promise; } + function getActiveManager() { + var deferred = $q.defer(); + + service.nodes() + .then(function success(data) { + for (var i = 0; i < data.length; ++i) { + var node = data[i]; + if (node.Role === 'manager' && node.Availability === 'active' && node.Status === 'ready' && node.Addr !== '0.0.0.0') { + deferred.resolve(node); + break; + } + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve nodes', err: err }); + }); + + return deferred.promise; + } + return service; } ]); diff --git a/app/docker/views/volumes/edit/volume.html b/app/docker/views/volumes/edit/volume.html index ce9744c16..475a7ea85 100644 --- a/app/docker/views/volumes/edit/volume.html +++ b/app/docker/views/volumes/edit/volume.html @@ -49,6 +49,31 @@ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + + +
+
+ {{ $ctrl.titleText }} +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Id + + + + + + Node + + + + + + Device + + + + + + Size + + + + + + Use + + + + + + Type + + + + + + Status + + + + + Actions +
+ {{ item.Id }} + {{ item.Node }}{{ item.Device }}{{ item.Size }}{{ item.Use }}{{ item.Type }} + {{ item.Status|capitalize }} + + +
Loading...
No drives available.
+
+ +
+
+ diff --git a/app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatable.js b/app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatable.js new file mode 100644 index 000000000..9e236ce7c --- /dev/null +++ b/app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatable.js @@ -0,0 +1,17 @@ +angular.module('extension.storidge').component('storidgeDrivesDatatable', { + templateUrl: './storidgeDrivesDatatable.html', + controller: 'StoridgeDrivesDatatableController', + bindings: { + titleText: '@', + titleIcon: '@', + dataset: '<', + tableKey: '@', + orderBy: '@', + reverseOrder: '<', + removeAction: '<', + addAction: '<', + rescanAction: '<', + actionInProgress: '<', + additionInProgress: '<' + } +}); diff --git a/app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatableController.js b/app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatableController.js new file mode 100644 index 000000000..65f44972b --- /dev/null +++ b/app/extensions/storidge/components/drives-datatable/storidgeDrivesDatatableController.js @@ -0,0 +1,16 @@ +angular.module('portainer.docker') + .controller('StoridgeDrivesDatatableController', ['$scope', '$controller', + function ($scope, $controller) { + angular.extend(this, $controller('GenericDatatableController', {$scope: $scope})); + + this.selectAll = function() { + for (var i = 0; i < this.state.filteredDataSet.length; i++) { + var item = this.state.filteredDataSet[i]; + if (item.Status !== 'normal' && item.Checked !== this.state.selectAll) { + item.Checked = this.state.selectAll; + this.selectItem(item); + } + } + }; + } +]); \ No newline at end of file diff --git a/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html b/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html index 2a0e35f97..4a8857844 100644 --- a/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html +++ b/app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html @@ -6,6 +6,25 @@ {{ $ctrl.titleText }} +
+
+ +
+ + To add a node to this cluster, run the following command on your new node + + {{ $ctrl.addInfo }} + + Copy + + + + +
+
+
- - diff --git a/app/extensions/storidge/views/cluster/clusterController.js b/app/extensions/storidge/views/cluster/clusterController.js index 6cf3d9f84..0bf2a1568 100644 --- a/app/extensions/storidge/views/cluster/clusterController.js +++ b/app/extensions/storidge/views/cluster/clusterController.js @@ -24,6 +24,16 @@ function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNod }); }; + function rebootCluster() { + $scope.state.rebootInProgress = true; + StoridgeClusterService.reboot() + .finally(function final() { + $scope.state.rebootInProgress = false; + Notifications.success('Cluster successfully rebooted'); + $state.reload(); + }); + } + $scope.shutdownCluster = function() { ModalService.confirm({ title: 'Are you sure?', @@ -51,16 +61,6 @@ function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNod }); } - function rebootCluster() { - $scope.state.rebootInProgress = true; - StoridgeClusterService.reboot() - .finally(function final() { - $scope.state.rebootInProgress = false; - Notifications.success('Cluster successfully rebooted'); - $state.reload(); - }); - } - function initView() { $q.all({ info: StoridgeClusterService.info(), diff --git a/app/extensions/storidge/views/drives/drives.html b/app/extensions/storidge/views/drives/drives.html new file mode 100644 index 000000000..58cf2182c --- /dev/null +++ b/app/extensions/storidge/views/drives/drives.html @@ -0,0 +1,24 @@ + + + + + + + + Storidge > Drives + + + +
+
+ +
+
diff --git a/app/extensions/storidge/views/drives/drivesController.js b/app/extensions/storidge/views/drives/drivesController.js new file mode 100644 index 000000000..837433240 --- /dev/null +++ b/app/extensions/storidge/views/drives/drivesController.js @@ -0,0 +1,48 @@ +angular.module('extension.storidge') +.controller('StoridgeDrivesController', ['$scope', '$state', 'Notifications', 'StoridgeDriveService', +function ($scope, $state, Notifications, StoridgeDriveService) { + + $scope.state = { + additionInProgress: [], + actionInProgress: false + }; + + $scope.addAction = function (drive, idx) { + $scope.state.additionInProgress[idx] = true; + $scope.state.actionInProgress = true; + StoridgeDriveService.add(drive.Device, drive.Node) + .then(function success() { + Notifications.success('Drive ' + drive.Device + ' successfully added on node ' + drive.Node); + $state.reload(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to add drive'); + }) + .finally(function final() { + $scope.state.additionInProgress[idx] = false; + $scope.state.actionInProgress = false; + }); + }; + + $scope.rescanAction = function () { + StoridgeDriveService.rescan() + .then(function sucess() { + $state.reload(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to scan drives'); + }); + }; + + function initView() { + StoridgeDriveService.drives() + .then(function success(data) { + $scope.drives = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve drives'); + }); + } + + initView(); +}]); diff --git a/app/extensions/storidge/views/drives/inspect/drive.html b/app/extensions/storidge/views/drives/inspect/drive.html new file mode 100644 index 000000000..bef7ba2a2 --- /dev/null +++ b/app/extensions/storidge/views/drives/inspect/drive.html @@ -0,0 +1,56 @@ + + + + Storidge > Drives > {{ drive.Id }} + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID + {{ drive.Id }} + +
Node{{ drive.Node }}
Device{{ drive.Device }}
Size{{ drive.Size }}
Use{{ drive.Use }}
Type{{ drive.Type }}
Status + {{ drive.Status|capitalize }} +
+
+
+
+
\ No newline at end of file diff --git a/app/extensions/storidge/views/drives/inspect/driveController.js b/app/extensions/storidge/views/drives/inspect/driveController.js new file mode 100644 index 000000000..7d4c0ccef --- /dev/null +++ b/app/extensions/storidge/views/drives/inspect/driveController.js @@ -0,0 +1,49 @@ +angular.module('extension.storidge') +.controller('StoridgeDriveController', ['$scope', '$state', '$transition$', 'Notifications', 'ModalService', 'StoridgeDriveService', +function ($scope, $state, $transition$, Notifications, ModalService, StoridgeDriveService) { + + $scope.actionInProgress = false; + + $scope.removeDrive = function () { + ModalService.confirm({ + title: 'Are you sure?', + message: 'Do you want really want to remove this drive from the storage pool?', + buttons: { + confirm: { + label: 'Remove', + className: 'btn-danger' + } + }, + callback: function onConfirm(confirmed) { + if(!confirmed) { return; } + $scope.actionInProgress = true; + StoridgeDriveService.remove($scope.drive.Id) + .then(function () { + Notifications.success('Success', 'Drive removed from storage pool'); + $state.go('storidge.drives', {}, { reload:true }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove drive from storage pool'); + }) + .finally(function final() { + $scope.actionInProgress = false; + }); + } + }); + }; + + function initView() { + $scope.id = $transition$.params().id; + + StoridgeDriveService.drive($scope.id) + .then(function success(data) { + $scope.drive = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve drive details'); + }); + } + + initView(); + +}]); diff --git a/app/extensions/storidge/views/nodes/inspect/node.html b/app/extensions/storidge/views/nodes/inspect/node.html new file mode 100644 index 000000000..a0a7c718e --- /dev/null +++ b/app/extensions/storidge/views/nodes/inspect/node.html @@ -0,0 +1,170 @@ + + + + Storidge > {{ node.Name }} + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name{{ node.Name }}
Domain{{ node.Domain }}
Domain ID{{ node.DomainID }}
Node status + + {{ node.Status }} +
Operating condition + + {{ node.Condition }} +
Metadata version{{ node.MetadataVersion }}
Nodes{{ node.Nodes }}
HDDs{{ node.Hdds }}
SSDs{{ node.Ssds }}
VDisks{{ node.Vdisks }}
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
Free{{ node.FreeBandwidth }}
Used{{ node.UsedBandwidth }}
Provisioned{{ node.ProvisionedBandwidth }}
Total{{ node.TotalBandwidth }}
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
Free{{ node.FreeCapacity | bytes }}
Used{{ node.UsedCapacity | bytes }}
Provisioned{{ node.ProvisionedCapacity | bytes }}
Total{{ node.TotalCapacity | bytes }}
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
Free{{ node.FreeIOPS }}
Used{{ node.UsedIOPS }}
Provisioned{{ node.ProvisionedIOPS }}
Total{{ node.TotalIOPS }}
+
+
+
+
\ No newline at end of file diff --git a/app/extensions/storidge/views/nodes/inspect/nodeController.js b/app/extensions/storidge/views/nodes/inspect/nodeController.js new file mode 100644 index 000000000..3d9993cc4 --- /dev/null +++ b/app/extensions/storidge/views/nodes/inspect/nodeController.js @@ -0,0 +1,107 @@ +angular.module('extension.storidge') +.controller('StoridgeNodeController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeNodeService', 'ModalService', +function ($scope, $state, $transition$, Notifications, StoridgeNodeService, ModalService) { + + $scope.removeNodeAction = function(selectedItems) { + ModalService.confirm({ + title: 'Are you sure?', + message: 'Do you want really want to remove the node from the cluster?', + buttons: { + confirm: { + label: 'Remove', + className: 'btn-danger' + } + }, + callback: function onConfirm(confirmed) { + if(!confirmed) { return; } + remove(selectedItems); + } + }); + }; + + function remove() { + StoridgeNodeService.remove($scope.node.Name) + .then(function success() { + Notifications.success('Node successfully removed', $scope.node.Name); + $state.go('storidge.cluster'); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove node'); + }); + } + + $scope.cordonNodeAction = function(selectedItems) { + ModalService.confirm({ + title: 'Are you sure?', + message: 'Do you want really want to put the node in maintenance mode?', + buttons: { + confirm: { + label: 'Enter maintenance', + className: 'btn-danger' + } + }, + callback: function onConfirm(confirmed) { + if(!confirmed) { return; } + cordonNode(selectedItems); + } + }); + }; + + function cordonNode() { + StoridgeNodeService.cordon($scope.node.Name) + .then(function success() { + Notifications.success('Node successfully put in maintenance'); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to put node in maintenance mode'); + }) + .finally(function final() { + $state.reload(); + }); + } + + $scope.uncordonNodeAction = function(selectedItems) { + ModalService.confirm({ + title: 'Are you sure?', + message: 'Do you want really want to bring the nodes out of maintenance mode?', + buttons: { + confirm: { + label: 'Exit maintenance', + className: 'btn-danger' + } + }, + callback: function onConfirm(confirmed) { + if(!confirmed) { return; } + uncordonNode(selectedItems); + } + }); + }; + + function uncordonNode() { + StoridgeNodeService.uncordon($scope.node.Name) + .then(function success() { + Notifications.success('Node successfully bringed back'); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to put node out of maintenance mode'); + }) + .finally(function final() { + $state.reload(); + }); + } + + function initView() { + $scope.name = $transition$.params().name; + + StoridgeNodeService.node($scope.name) + .then(function success(data) { + $scope.node = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve node details'); + }); + } + + initView(); + +}]); diff --git a/app/extensions/storidge/views/profiles/create/createProfileController.js b/app/extensions/storidge/views/profiles/create/createProfileController.js index fbb894d96..89c0658af 100644 --- a/app/extensions/storidge/views/profiles/create/createProfileController.js +++ b/app/extensions/storidge/views/profiles/create/createProfileController.js @@ -1,9 +1,14 @@ +import _ from 'lodash-es'; import { StoridgeProfileDefaultModel } from '../../../models/profile'; angular.module('extension.storidge') .controller('StoridgeCreateProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService', function ($scope, $state, $transition$, Notifications, StoridgeProfileService) { + $scope.formValues = { + Labels: [] + }; + $scope.state = { NoLimit: true, LimitIOPS: false, @@ -17,6 +22,24 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService) { { value: 3, label: '3-copy' } ]; + $scope.addLabel = function() { + $scope.formValues.Labels.push({ name: '', value: ''}); + }; + + $scope.removeLabel = function(index) { + $scope.formValues.Labels.splice(index, 1); + }; + + function prepareLabels(profile) { + var labels = {}; + $scope.formValues.Labels.forEach(function (label) { + if (label.name && label.value) { + labels[label.name] = label.value; + } + }); + profile.Labels = labels; + } + $scope.create = function () { var profile = $scope.model; @@ -30,6 +53,23 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService) { delete profile.MaxBandwidth; } + if (profile.SnapshotEnabled) { + if (!profile.SnapshotMax || profile.SnapshotMax <= 0) { + profile.SnapshotMax = 1; + } + if (!$scope.state.RecurringSnapshotEnabled) { + delete profile.SnapshotInterval; + } + if ($scope.state.RecurringSnapshotEnabled && (!profile.SnapshotInterval || profile.SnapshotInterval <= 0)) { + profile.SnapshotInterval = 1440; + } + } else { + delete profile.SnapshotMax; + delete profile.SnapshotInterval; + } + + prepareLabels(profile); + $scope.state.actionInProgress = true; StoridgeProfileService.create(profile) .then(function success() { @@ -47,7 +87,7 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService) { $scope.updatedName = function() { if (!$scope.state.ManualInputDirectory) { var profile = $scope.model; - profile.Directory = '/cio/' + profile.Name; + profile.Directory = '/cio/' + (profile.Name ? _.toLower(profile.Name) : ''); } }; @@ -60,7 +100,7 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService) { function initView() { var profile = new StoridgeProfileDefaultModel(); profile.Name = $transition$.params().profileName; - profile.Directory = '/cio/' + profile.Name; + profile.Directory = profile.Directory + _.toLower(profile.Name); $scope.model = profile; } diff --git a/app/extensions/storidge/views/profiles/create/createprofile.html b/app/extensions/storidge/views/profiles/create/createprofile.html index 7680d515e..c95764928 100644 --- a/app/extensions/storidge/views/profiles/create/createprofile.html +++ b/app/extensions/storidge/views/profiles/create/createprofile.html @@ -91,20 +91,164 @@ + +
+ +
+ +
+
+ + +
+ + +
+ + +
+ +
+ +
+
+ +
+
+

+ Snapshot max (count) +

+
+
+ + +
+ + +
+ + +
+ +
+ +
+
+ +
+
+

+ Snapshot interval (minutes) +

+
+
+ + +
+ + +
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + + add label + +
+ +
+
+
+ name + +
+
+ value + +
+ +
+
+ +
+
IOPS
-
+
-
+
@@ -142,14 +286,14 @@ Bandwidth
-
+
-
+
diff --git a/app/extensions/storidge/views/profiles/edit/profile.html b/app/extensions/storidge/views/profiles/edit/profile.html index 12994ad5f..5b5903913 100644 --- a/app/extensions/storidge/views/profiles/edit/profile.html +++ b/app/extensions/storidge/views/profiles/edit/profile.html @@ -84,6 +84,150 @@
+ +
+ +
+ +
+
+ + +
+ + +
+ + +
+ +
+ +
+
+ +
+
+

+ Snapshot max (count) +

+
+
+ + +
+ + +
+ + +
+ +
+ +
+
+ +
+
+

+ Snapshot interval (minutes) +

+
+
+ + +
+ + +
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + + add label + +
+ +
+
+
+ name + +
+
+ value + +
+ +
+
+ +
+
diff --git a/app/extensions/storidge/views/profiles/edit/profileController.js b/app/extensions/storidge/views/profiles/edit/profileController.js index ff6aecfc0..ecafae46f 100644 --- a/app/extensions/storidge/views/profiles/edit/profileController.js +++ b/app/extensions/storidge/views/profiles/edit/profileController.js @@ -2,21 +2,49 @@ angular.module('extension.storidge') .controller('StoridgeProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService', 'ModalService', function ($scope, $state, $transition$, Notifications, StoridgeProfileService, ModalService) { + $scope.formValues = { + Labels: [] + }; + $scope.state = { NoLimit: false, LimitIOPS: false, LimitBandwidth: false, updateInProgress: false, - deleteInProgress: false + deleteInProgress: false, + RecurringSnapshotEnabled: false }; + $scope.addLabel = function() { + $scope.formValues.Labels.push({ name: '', value: ''}); + }; + + $scope.removeLabel = function(index) { + $scope.formValues.Labels.splice(index, 1); + }; + + function prepareLabels(profile) { + var labels = {}; + $scope.formValues.Labels.forEach(function (label) { + if (label.name && label.value) { + labels[label.name] = label.value; + } + }); + profile.Labels = labels; + } + + function initLabels(labels) { + $scope.formValues.Labels = Object.keys(labels).map(function(key) { + return { name:key, value:labels[key] }; + }); + } + $scope.RedundancyOptions = [ { value: 2, label: '2-copy' }, { value: 3, label: '3-copy' } ]; $scope.update = function() { - var profile = $scope.profile; if (!$scope.state.LimitIOPS) { @@ -29,6 +57,23 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService, M delete profile.MaxBandwidth; } + if (profile.SnapshotEnabled) { + if (!profile.SnapshotMax || profile.SnapshotMax <= 0) { + profile.SnapshotMax = 1; + } + if (!$scope.state.RecurringSnapshotEnabled) { + delete profile.SnapshotInterval; + } + if ($scope.state.RecurringSnapshotEnabled && (!profile.SnapshotInterval || profile.SnapshotInterval <= 0)) { + profile.SnapshotInterval = 1440; + } + } else { + delete profile.SnapshotMax; + delete profile.SnapshotInterval; + } + + prepareLabels(profile); + $scope.state.updateInProgress = true; StoridgeProfileService.update(profile) .then(function success() { @@ -81,6 +126,10 @@ function ($scope, $state, $transition$, Notifications, StoridgeProfileService, M } else { $scope.state.NoLimit = true; } + if (profile.SnapshotEnabled && profile.SnapshotInterval !== 0) { + $scope.state.RecurringSnapshotEnabled = true; + } + initLabels(profile.Labels); $scope.profile = profile; }) .catch(function error(err) { diff --git a/app/extensions/storidge/views/profiles/profilesController.js b/app/extensions/storidge/views/profiles/profilesController.js index 2214739c7..f60d5c6d5 100644 --- a/app/extensions/storidge/views/profiles/profilesController.js +++ b/app/extensions/storidge/views/profiles/profilesController.js @@ -1,3 +1,4 @@ +import _ from 'lodash-es'; import { StoridgeProfileDefaultModel } from '../../models/profile'; angular.module('extension.storidge') @@ -35,8 +36,13 @@ function ($q, $scope, $state, Notifications, StoridgeProfileService) { $scope.create = function() { var model = new StoridgeProfileDefaultModel(); + model.Labels = {}; model.Name = $scope.formValues.Name; - model.Directory = model.Directory + model.Name; + model.Directory = model.Directory + _.toLower(model.Name); + delete model.MinBandwidth; + delete model.MaxBandwidth; + delete model.MinIOPS; + delete model.MaxIOPS; $scope.state.actionInProgress = true; StoridgeProfileService.create(model) diff --git a/app/extensions/storidge/views/snapshots/inspect/snapshot.html b/app/extensions/storidge/views/snapshots/inspect/snapshot.html new file mode 100644 index 000000000..fb9d1afa4 --- /dev/null +++ b/app/extensions/storidge/views/snapshots/inspect/snapshot.html @@ -0,0 +1,53 @@ + + + + Volumes > {{ volumeId }} > Snapshots > {{ snapshot.Id }} + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID + {{ snapshot.Id }} + +
Date{{ snapshot.Date }}
Description{{ snapshot.Description }}
SourceID{{ snapshot.SourceID }}
Type{{ snapshot.Type }}
Directory{{ snapshot.Directory }}
Source{{ snapshot.Source }}
+
+
+
+
\ No newline at end of file diff --git a/app/extensions/storidge/views/snapshots/inspect/snapshotController.js b/app/extensions/storidge/views/snapshots/inspect/snapshotController.js new file mode 100644 index 000000000..1534087cd --- /dev/null +++ b/app/extensions/storidge/views/snapshots/inspect/snapshotController.js @@ -0,0 +1,44 @@ +angular.module('extension.storidge') +.controller('StoridgeSnapshotController', ['$scope', '$state', '$transition$', 'Notifications', 'ModalService', 'StoridgeSnapshotService', +function ($scope, $state, $transition$, Notifications, ModalService, StoridgeSnapshotService) { + + $scope.removeSnapshot = function () { + ModalService.confirm({ + title: 'Are you sure?', + message: 'Do you want really want to remove this snapshot?', + buttons: { + confirm: { + label: 'Remove', + className: 'btn-danger' + } + }, + callback: function onConfirm(confirmed) { + if(!confirmed) { return; } + StoridgeSnapshotService.remove($scope.snapshot.Id) + .then(function () { + Notifications.success('Success', 'Snapshot removed'); + $state.go('portainer.volumes.volume', {id: $scope.volumeId}); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove snapshot'); + }); + } + }); + }; + + function initView() { + $scope.volumeId = $transition$.params().id; + $scope.snapshotId = $transition$.params().snapshotId; + + StoridgeSnapshotService.snapshot($scope.snapshotId) + .then(function success(data) { + $scope.snapshot = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve snapshot details'); + }); + } + + initView(); + +}]); diff --git a/app/portainer/components/datatables/genericDatatableController.js b/app/portainer/components/datatables/genericDatatableController.js index 15fe1a8d0..3fb1db1c8 100644 --- a/app/portainer/components/datatables/genericDatatableController.js +++ b/app/portainer/components/datatables/genericDatatableController.js @@ -1,13 +1,13 @@ import './datatable.css'; angular.module('portainer.app') -.controller('GenericDatatableController', ['PaginationService', 'DatatableService', -function (PaginationService, DatatableService) { +.controller('GenericDatatableController', ['PaginationService', 'DatatableService', 'PAGINATION_MAX_ITEMS', +function (PaginationService, DatatableService, PAGINATION_MAX_ITEMS) { this.state = { selectAll: false, orderBy: this.orderBy, - paginatedItemLimit: PaginationService.getPaginationLimit(this.tableKey), + paginatedItemLimit: PAGINATION_MAX_ITEMS, displayTextFilter: false, selectedItemCount: 0, selectedItems: [] @@ -67,5 +67,6 @@ function (PaginationService, DatatableService) { function setDefaults(ctrl) { ctrl.showTextFilter = ctrl.showTextFilter ? ctrl.showTextFilter : false; ctrl.state.reverseOrder = ctrl.reverseOrder ? ctrl.reverseOrder : false; + ctrl.state.paginatedItemLimit = PaginationService.getPaginationLimit(ctrl.tableKey); } }]); diff --git a/app/portainer/components/slider/sliderController.js b/app/portainer/components/slider/sliderController.js index 21175579d..8755097cb 100644 --- a/app/portainer/components/slider/sliderController.js +++ b/app/portainer/components/slider/sliderController.js @@ -10,7 +10,7 @@ angular.module('portainer.app') showSelectionBar: true, enforceStep: false, translate: function(value, sliderId, label) { - if (label === 'floor' || value === 0) { + if ((label === 'floor' && ctrl.floor === 0) || value === 0) { return 'unlimited'; } return value; diff --git a/app/portainer/filters/filters.js b/app/portainer/filters/filters.js index f6b6d881f..30ef37384 100644 --- a/app/portainer/filters/filters.js +++ b/app/portainer/filters/filters.js @@ -38,7 +38,7 @@ angular.module('portainer.app') .filter('capitalize', function () { 'use strict'; return function (text) { - return _.capitalize(text); + return text ? _.capitalize(text) : ''; }; }) .filter('stripprotocol', function() { diff --git a/app/portainer/services/legacyExtensionManager.js b/app/portainer/services/legacyExtensionManager.js index 580457b14..8b0aa783a 100644 --- a/app/portainer/services/legacyExtensionManager.js +++ b/app/portainer/services/legacyExtensionManager.js @@ -2,8 +2,8 @@ import _ from 'lodash-es'; // TODO: legacy extension management angular.module('portainer.app') -.factory('LegacyExtensionManager', ['$q', 'PluginService', 'SystemService', 'LegacyExtensionService', -function ExtensionManagerFactory($q, PluginService, SystemService, LegacyExtensionService) { +.factory('LegacyExtensionManager', ['$q', 'PluginService', 'SystemService', 'NodeService', 'LegacyExtensionService', +function ExtensionManagerFactory($q, PluginService, SystemService, NodeService, LegacyExtensionService) { 'use strict'; var service = {}; @@ -59,9 +59,9 @@ function ExtensionManagerFactory($q, PluginService, SystemService, LegacyExtensi function registerStoridgeUsingSwarmManagerIP() { var deferred = $q.defer(); - SystemService.info() + NodeService.getActiveManager() .then(function success(data) { - var managerIP = data.Swarm.NodeAddr; + var managerIP = data.Addr; var storidgeAPIURL = 'tcp://' + managerIP + ':8282'; return LegacyExtensionService.registerStoridgeExtension(storidgeAPIURL); }) diff --git a/app/portainer/services/localStorage.js b/app/portainer/services/localStorage.js index f23f0ba50..31754881d 100644 --- a/app/portainer/services/localStorage.js +++ b/app/portainer/services/localStorage.js @@ -60,10 +60,10 @@ angular.module('portainer.app') localStorageService.remove('JWT'); }, storePaginationLimit: function(key, count) { - localStorageService.cookie.set('pagination_' + key, count); + localStorageService.set('datatable_pagination_' + key, count); }, getPaginationLimit: function(key) { - return localStorageService.cookie.get('pagination_' + key); + return localStorageService.get('datatable_pagination_' + key); }, getDataTableOrder: function(key) { return localStorageService.get('datatable_order_' + key); diff --git a/app/portainer/services/notifications.js b/app/portainer/services/notifications.js index cff57364e..010c27079 100644 --- a/app/portainer/services/notifications.js +++ b/app/portainer/services/notifications.js @@ -21,6 +21,8 @@ angular.module('portainer.app') msg = e.data.details; } else if (e.data && e.data.message) { msg = e.data.message; + } else if (e.data && e.data.content) { + msg = e.data.content; } else if (e.message) { msg = e.message; } else if (e.err && e.err.data && e.err.data.message) { diff --git a/app/portainer/views/sidebar/sidebar.html b/app/portainer/views/sidebar/sidebar.html index 6193f73ef..0f833f739 100644 --- a/app/portainer/views/sidebar/sidebar.html +++ b/app/portainer/views/sidebar/sidebar.html @@ -25,16 +25,19 @@ offline-mode="endpointState.OfflineMode" >