feat(storidge): add node details / cordon / uncordon / remove

storidge-standalone
baron_l 2019-02-25 17:52:38 +01:00
parent 7890c80999
commit 6a5e0ea2a8
11 changed files with 422 additions and 20 deletions

View File

@ -79,6 +79,17 @@ angular.module('extension.storidge', [])
}
};
var node = {
name: 'storidge.cluster.node',
url: '/:name',
views: {
'content@': {
templateUrl: 'app/extensions/storidge/views/nodes/inspect/node.html',
controller: 'StoridgeNodeController'
}
}
};
var monitor = {
name: 'storidge.monitor',
url: '/events',
@ -97,5 +108,6 @@ angular.module('extension.storidge', [])
$stateRegistryProvider.register(profile);
$stateRegistryProvider.register(profileCreation);
$stateRegistryProvider.register(cluster);
$stateRegistryProvider.register(node);
$stateRegistryProvider.register(monitor);
}]);

View File

@ -6,6 +6,20 @@
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.cordonNodeAction($ctrl.state.selectedItems)">
<i class="fas fa-wrench space-right" aria-hidden="true"></i>Put in maintenance
</button>
<button type="button" class="btn btn-sm btn-primary"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.uncordonNodeAction($ctrl.state.selectedItems)">
<i class="fa fa-power-off space-right" aria-hidden="true"></i>Put out of maintenance
</button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus>
@ -15,6 +29,10 @@
<thead>
<tr>
<th>
<span class="md-checkbox">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
<a ng-click="$ctrl.changeOrderBy('Name')">
Name
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
@ -46,7 +64,13 @@
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<td>{{ item.Name }}</td>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="storidge.cluster.node({name: item.Name})"> {{ item.Name }}</a>
</td>
<td>{{ item.IP }}</td>
<td>{{ item.Role }}</td>
<td>

View File

@ -7,6 +7,9 @@ angular.module('extension.storidge').component('storidgeNodesDatatable', {
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<'
reverseOrder: '<',
removeAction: '<',
cordonNodeAction: '<',
uncordonNodeAction: '<'
}
});

View File

@ -4,3 +4,27 @@ function StoridgeNodeModel(name, data) {
this.Role = data.role;
this.Status = data.status;
}
function StoridgeNodeDetailedModel(name, properties) {
this.Name = name;
this.Domain = properties['domain:'];
this.DomainID = properties['domainID:'];
this.FreeBandwidth = properties['freeBandwidth:'];
this.FreeCapacity = properties['freeCapacity:'];
this.FreeIOPS = properties['freeIOPS:'];
this.Hdds = properties['hdds:'];
this.MetadataVersion = properties['metadataVersion:'];
this.Nodes = properties['nodes:'];
this.ProvisionedBandwidth = properties['provisionedBandwidth:'];
this.ProvisionedCapacity = properties['provisionedCapacity:'];
this.ProvisionedIOPS = properties['provisionedIOPS:'];
this.Ssds = properties['ssds:'];
this.Status = properties['status:'];
this.TotalBandwidth = properties['totalBandwidth:'];
this.TotalCapacity = properties['totalCapacity:'];
this.TotalIOPS = properties['totalIOPS:'];
this.UsedBandwidth = properties['usedBandwidth:'];
this.UsedCapacity = properties['usedCapacity:'];
this.UsedIOPS = properties['usedIOPS:'];
this.Vdisks = properties['vdisks:'];
}

View File

@ -10,7 +10,14 @@ angular.module('extension.storidge')
queryEvents: { method: 'GET', params: { resource: 'clusters', action: 'events' }, timeout: 4500, ignoreLoadingBar: true, isArray: true },
getVersion: { method: 'GET', params: { resource: 'clusters', action: 'version' } },
getInfo: { method: 'GET', params: { resource: 'clusters', action: 'info' }, timeout: 4500, ignoreLoadingBar: true },
queryNodes: { method: 'GET', params: { resource: 'nodes' } },
getNode: { method: 'GET', params: { resource: 'nodes', id: '@id' } },
addNode: { method: 'POST', params: { resource: 'nodes' } },
removeNode: { method: 'DELETE', params: { resource: 'nodes', id: '@id' } },
cordonNode: { method: 'POST', params : { resource: 'nodes', action:'cordon', id: '@id' } },
uncordonNode: { method: 'POST', params : { resource: 'nodes', action: 'uncordon', id:'@id' } },
queryProfiles: { method: 'GET', params: { resource: 'profiles' } },
getProfile: { method: 'GET', params: { resource: 'profiles' } },
createProfile: { method: 'POST', params: { resource: 'profiles' } },
@ -19,7 +26,6 @@ angular.module('extension.storidge')
queryDrives: { method: 'GET', params: { resource: 'drives' } },
getDrive: { method: 'GET', params: { resource: 'drives', id: '@id' } },
addDrive: { method: 'POST', params: { resource: 'drives' } },
removeDrive: { method: 'DELETE', params: { resource: 'drives', id: '@id' } },
getNode: { method: 'GET', params: { resource: 'nodes', id: '@id' } }
removeDrive: { method: 'DELETE', params: { resource: 'drives', id: '@id' } }
});
}]);

View File

@ -20,11 +20,42 @@ angular.module('extension.storidge')
deferred.resolve(nodes);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve Storidge profiles', err: err });
deferred.reject({ msg: 'Unable to retrieve Storidge nodes', err: err });
});
return deferred.promise;
};
service.node = function (id) {
var deferred = $q.defer();
Storidge.getNode({id:id}).$promise
.then(function success(data) {
var node = new StoridgeNodeDetailedModel(data.name, data.properties);
deferred.resolve(node);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve Storidge node', err: err });
});
return deferred.promise;
};
service.add = function () {
return Storidge.addNode().$promise;
};
service.cordon = function (id) {
return Storidge.cordonNode({id: id}).$promise;
};
service.uncordon = function (id) {
return Storidge.uncordonNode({id: id}).$promise;
};
service.remove = function (id) {
return Storidge.removeNode({id:id}).$promise;
};
return service;
}]);

View File

@ -31,6 +31,23 @@
</tbody>
</table>
<form class="form-horizontal">
<div class="col-sm-12 form-section-title" >
Information
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
To add a node to this cluster, run the following command on your new node
<div style="margin-top: 10px;">
<code>
{{ addInfo }}
</code>
<span class="btn btn-primary btn-sm space-left" ng-click="copyAddNodeCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</span>
<span>
<i id="copyNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i>
</span>
</div>
</span>
</div>
<div class="col-sm-12 form-section-title">
Actions
</div>
@ -57,7 +74,9 @@
<storidge-nodes-datatable
title-text="Storage nodes" title-icon="fa-object-group"
dataset="clusterNodes" table-key="storidge_nodes"
order-by="Name"
order-by="Name" remove-action="removeAction"
cordon-node-action="cordonNodeAction"
uncordon-node-action="uncordonNodeAction"
></storidge-nodes-datatable>
</div>
</div>

View File

@ -1,12 +1,126 @@
angular.module('extension.storidge')
.controller('StoridgeClusterController', ['$q', '$scope', '$state', 'Notifications', 'StoridgeClusterService', 'StoridgeNodeService', 'ModalService',
function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNodeService, ModalService) {
.controller('StoridgeClusterController', ['$q', '$scope', '$state', 'clipboard', 'Notifications', 'StoridgeClusterService', 'StoridgeNodeService', 'ModalService',
function ($q, $scope, $state, clipboard, Notifications, StoridgeClusterService, StoridgeNodeService, ModalService) {
$scope.state = {
shutdownInProgress: false,
rebootInProgress: false
};
$scope.copyAddNodeCommand = function() {
clipboard.copyText($scope.addInfo);
$('#copyNotification').show();
$('#copyNotification').fadeOut(2000);
};
$scope.removeAction = function(selectedItems) {
ModalService.confirm({
title: 'Are you sure?',
message: 'Do you want really want to remove the nodes from the cluster?',
buttons: {
confirm: {
label: 'Remove',
className: 'btn-danger'
}
},
callback: function onConfirm(confirmed) {
if(!confirmed) { return; }
remove(selectedItems);
}
});
};
function remove(selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (node) {
StoridgeNodeService.remove(node.Name)
.then(function success() {
Notifications.success('Node successfully removed', node.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove node');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
}
$scope.cordonNodeAction = function(selectedItems) {
ModalService.confirm({
title: 'Are you sure?',
message: 'Do you want really want to put the nodes in maintenance mode?',
buttons: {
confirm: {
label: 'Cordon',
className: 'btn-danger'
}
},
callback: function onConfirm(confirmed) {
if(!confirmed) { return; }
cordonNode(selectedItems);
}
});
};
function cordonNode(selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (node) {
StoridgeNodeService.cordon(node.Name)
.then(function success() {
Notifications.success('Node successfully put in maintenance', node.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to put node in maintenance mode');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$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: 'Uncordon',
className: 'btn-danger'
}
},
callback: function onConfirm(confirmed) {
if(!confirmed) { return; }
uncordonNode(selectedItems);
}
});
};
function uncordonNode(selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (node) {
StoridgeNodeService.uncordon(node.Name)
.then(function success() {
Notifications.success('Node successfully bringed back', node.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to put node out of maintenance mode');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
}
$scope.rebootCluster = function() {
ModalService.confirm({
title: 'Are you sure?',
@ -24,6 +138,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,26 +175,18 @@ 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(),
version: StoridgeClusterService.version(),
nodes: StoridgeNodeService.nodes()
nodes: StoridgeNodeService.nodes(),
addInfo: StoridgeNodeService.add()
})
.then(function success(data) {
$scope.clusterInfo = data.info;
$scope.clusterVersion = data.version;
$scope.clusterNodes = data.nodes;
$scope.addInfo = data.addInfo.content;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve cluster information');

View File

@ -5,7 +5,7 @@ function ($q, $scope, $state, Notifications, ModalService, StoridgeDriveService)
$scope.removeAction = function(selectedItems) {
ModalService.confirm({
title: 'Are you sure?',
message: 'Do you want really want to remove this drive from the storage pool?',
message: 'Do you want really want to remove the drives from the storage pool?',
buttons: {
confirm: {
label: 'Remove',

View File

@ -0,0 +1,147 @@
<rd-header>
<rd-header-title title-text="Node details"></rd-header-title>
<rd-header-content>
<a ui-sref="storidge.cluster">Storidge</a> &gt; <a
ui-sref="storidge.cluster.node({id: node.Name})">{{ node.Name }}</a>
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-object-group" title-text="Node details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Name</td>
<td>{{ node.Name }}</td>
</tr>
<tr>
<td>Domain</td>
<td>{{ node.Domain }}</td>
</tr>
<tr>
<td>Domain ID</td>
<td>{{ node.DomainID }}</td>
</tr>
<tr>
<td>Status</td>
<td>{{ node.Status }}</td>
</tr>
<tr>
<td>Metadata version</td>
<td>{{ node.MetadataVersion }}</td>
</tr>
<tr>
<td>Nodes</td>
<td>{{ node.Nodes }}</td>
</tr>
<tr>
<td>HDDs</td>
<td>{{ node.Hdds }}</td>
</tr>
<tr>
<td>SSDs</td>
<td>{{ node.Ssds }}</td>
</tr>
<tr>
<td>VDisks</td>
<td>{{ node.Vdisks }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-object-group" title-text="Bandwidth details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Free</td>
<td>{{ node.FreeBandwidth }}</td>
</tr>
<tr>
<td>Used</td>
<td>{{ node.UsedBandwidth }}</td>
</tr>
<tr>
<td>Provisioned</td>
<td>{{ node.ProvisionedBandwidth }}</td>
</tr>
<tr>
<td>Total</td>
<td>{{ node.TotalBandwidth }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-object-group" title-text="Capacity details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Free</td>
<td>{{ node.FreeCapacity }}</td>
</tr>
<tr>
<td>Used</td>
<td>{{ node.UsedCapacity }}</td>
</tr>
<tr>
<td>Provisioned</td>
<td>{{ node.ProvisionedCapacity }}</td>
</tr>
<tr>
<td>Total</td>
<td>{{ node.TotalCapacity }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-object-group" title-text="IOPS details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Free</td>
<td>{{ node.FreeIOPS }}</td>
</tr>
<tr>
<td>Used</td>
<td>{{ node.UsedIOPS }}</td>
</tr>
<tr>
<td>Provisioned</td>
<td>{{ node.ProvisionedIOPS }}</td>
</tr>
<tr>
<td>Total</td>
<td>{{ node.TotalIOPS }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -0,0 +1,20 @@
angular.module('extension.storidge')
.controller('StoridgeNodeController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeNodeService',
function ($scope, $state, $transition$, Notifications, StoridgeNodeService) {
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();
}]);