Added functionality to enroll a node, and delete node(s)
These changes add functionality to enroll a node into the Ironic database, and delete node(s) from the database. Change-Id: Id0837b75e946ff702e81a47b9f8c3656beb5a783
This commit is contained in:
parent
fd86177443
commit
3a8ff7b3c8
@ -117,3 +117,52 @@ def node_set_maintenance(request, node_id, state, maint_reason=None):
|
|||||||
node_id,
|
node_id,
|
||||||
state,
|
state,
|
||||||
maint_reason=maint_reason)
|
maint_reason=maint_reason)
|
||||||
|
|
||||||
|
|
||||||
|
def node_create(request, params):
|
||||||
|
"""Create a node
|
||||||
|
|
||||||
|
:param request: HTTP request.
|
||||||
|
:param params: Dictionary of node parameters
|
||||||
|
"""
|
||||||
|
node_manager = ironicclient(request).node
|
||||||
|
node = node_manager.create(**params)
|
||||||
|
field_list = ['chassis_uuid',
|
||||||
|
'driver',
|
||||||
|
'driver_info',
|
||||||
|
'properties',
|
||||||
|
'extra',
|
||||||
|
'uuid',
|
||||||
|
'name']
|
||||||
|
return dict([(f, getattr(node, f, '')) for f in field_list])
|
||||||
|
|
||||||
|
|
||||||
|
def node_delete(request, node_id):
|
||||||
|
"""Delete a node from inventory.
|
||||||
|
|
||||||
|
:param request: HTTP request.
|
||||||
|
:param node_id: The UUID of the node.
|
||||||
|
:return: node.
|
||||||
|
|
||||||
|
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.delete
|
||||||
|
"""
|
||||||
|
return ironicclient(request).node.delete(node_id)
|
||||||
|
|
||||||
|
|
||||||
|
def driver_list(request):
|
||||||
|
"""Retrieve a list of drivers.
|
||||||
|
|
||||||
|
:param request: HTTP request.
|
||||||
|
:return: A list of drivers.
|
||||||
|
"""
|
||||||
|
return ironicclient(request).driver.list()
|
||||||
|
|
||||||
|
|
||||||
|
def driver_properties(request, driver_name):
|
||||||
|
"""Retrieve the properties of a specified driver
|
||||||
|
|
||||||
|
:param request: HTTP request
|
||||||
|
:param driver_name: Name of the driver
|
||||||
|
:return: Property list
|
||||||
|
"""
|
||||||
|
return ironicclient(request).driver.properties(driver_name)
|
||||||
|
@ -40,6 +40,24 @@ class Nodes(generic.View):
|
|||||||
'items': [i.to_dict() for i in items],
|
'items': [i.to_dict() for i in items],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@rest_utils.ajax(data_required=True)
|
||||||
|
def post(self, request):
|
||||||
|
"""Create an Ironic node
|
||||||
|
|
||||||
|
:param request: HTTP request
|
||||||
|
"""
|
||||||
|
params = request.DATA.get('node')
|
||||||
|
return ironic.node_create(request, params)
|
||||||
|
|
||||||
|
@rest_utils.ajax(data_required=True)
|
||||||
|
def delete(self, request):
|
||||||
|
"""Delete an Ironic node from inventory
|
||||||
|
|
||||||
|
:param request: HTTP request
|
||||||
|
"""
|
||||||
|
params = request.DATA.get('node')
|
||||||
|
return ironic.node_delete(request, params)
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
@urls.register
|
||||||
class Node(generic.View):
|
class Node(generic.View):
|
||||||
@ -122,3 +140,37 @@ class Maintenance(generic.View):
|
|||||||
:return: Return code
|
:return: Return code
|
||||||
"""
|
"""
|
||||||
return ironic.node_set_maintenance(request, node_id, 'off')
|
return ironic.node_set_maintenance(request, node_id, 'off')
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class Drivers(generic.View):
|
||||||
|
|
||||||
|
url_regex = r'ironic/drivers/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request):
|
||||||
|
"""Get the list of drivers
|
||||||
|
|
||||||
|
:param request: HTTP request
|
||||||
|
:return: drivers
|
||||||
|
"""
|
||||||
|
items = ironic.driver_list(request)
|
||||||
|
return {
|
||||||
|
'items': [i.to_dict() for i in items]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class DriverProperties(generic.View):
|
||||||
|
|
||||||
|
url_regex = r'ironic/drivers/(?P<driver_name>[0-9a-zA-Z_-]+)/properties$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request, driver_name):
|
||||||
|
"""Get the properties associated with a specified driver
|
||||||
|
|
||||||
|
:param request: HTTP request
|
||||||
|
:param driver_name: Driver name
|
||||||
|
:return: Dictionary of properties
|
||||||
|
"""
|
||||||
|
return ironic.driver_properties(request, driver_name)
|
||||||
|
@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Cray Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller used to enroll a node in the Ironic database
|
||||||
|
*/
|
||||||
|
angular
|
||||||
|
.module('horizon.dashboard.admin.ironic')
|
||||||
|
.controller('EnrollNodeController', EnrollNodeController);
|
||||||
|
|
||||||
|
EnrollNodeController.$inject = [
|
||||||
|
'$rootScope',
|
||||||
|
'$modalInstance',
|
||||||
|
'horizon.app.core.openstack-service-api.ironic',
|
||||||
|
'horizon.dashboard.admin.ironic.enroll-node.service',
|
||||||
|
'$log'
|
||||||
|
];
|
||||||
|
|
||||||
|
function EnrollNodeController($rootScope,
|
||||||
|
$modalInstance,
|
||||||
|
ironic,
|
||||||
|
enrollNodeService,
|
||||||
|
$log) {
|
||||||
|
var ctrl = this;
|
||||||
|
|
||||||
|
ctrl.drivers = null;
|
||||||
|
ctrl.loadingDriverProperties = false;
|
||||||
|
// Object containing the set of properties associated with the currently
|
||||||
|
// selected driver
|
||||||
|
ctrl.driverProperties = null;
|
||||||
|
|
||||||
|
// Paramater object that defines the node to be enrolled
|
||||||
|
ctrl.node = {
|
||||||
|
name: null,
|
||||||
|
driver: null,
|
||||||
|
driver_info: {},
|
||||||
|
properties: {},
|
||||||
|
extra: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
loadDrivers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of currently active Ironic drivers
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function loadDrivers() {
|
||||||
|
ironic.getDrivers().then(function(response) {
|
||||||
|
ctrl.drivers = response.data.items;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the properties associated with a specified driver
|
||||||
|
*
|
||||||
|
* @param {string} driverName - Name of driver
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
ctrl.loadDriverProperties = function(driverName) {
|
||||||
|
ctrl.node.driver = driverName;
|
||||||
|
ctrl.node.driver_info = {};
|
||||||
|
|
||||||
|
ctrl.loadingDriverProperties = true;
|
||||||
|
ctrl.driverProperties = null;
|
||||||
|
|
||||||
|
ironic.getDriverProperties(driverName).then(function(response) {
|
||||||
|
ctrl.driverProperties = {};
|
||||||
|
angular.forEach(response.data, function(desc, property) {
|
||||||
|
ctrl.driverProperties[property] =
|
||||||
|
new enrollNodeService.DriverProperty(property,
|
||||||
|
desc,
|
||||||
|
ctrl.driverProperties);
|
||||||
|
});
|
||||||
|
ctrl.loadingDriverProperties = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the node enrollment process
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
ctrl.cancel = function() {
|
||||||
|
$modalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enroll the defined node
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
ctrl.enroll = function() {
|
||||||
|
$log.debug(">> EnrollNodeController.enroll()");
|
||||||
|
angular.forEach(ctrl.driverProperties, function(property, name) {
|
||||||
|
$log.debug(name +
|
||||||
|
", required = " + property.isRequired() +
|
||||||
|
", active = " + property.isActive() +
|
||||||
|
", input value = " + property.inputValue);
|
||||||
|
if (property.isActive() && property.inputValue) {
|
||||||
|
$log.debug("Setting driver property " + name + " to " +
|
||||||
|
property.inputValue);
|
||||||
|
ctrl.node.driver_info[name] = property.inputValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ironic.createNode(ctrl.node).then(
|
||||||
|
function() {
|
||||||
|
$modalInstance.close();
|
||||||
|
$rootScope.$emit('ironic-ui:new-node');
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
// No additional error processing for now
|
||||||
|
});
|
||||||
|
$log.debug("<< EnrollNodeController.enroll()");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a node property
|
||||||
|
*
|
||||||
|
* @param {string} propertyName - Name of the property
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
ctrl.deleteProperty = function(propertyName) {
|
||||||
|
delete ctrl.node.properties[propertyName];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the specified node property already exists
|
||||||
|
*
|
||||||
|
* @param {string} propertyName - Name of the property
|
||||||
|
* @return {boolean} True if the property already exists,
|
||||||
|
* otherwise false
|
||||||
|
*/
|
||||||
|
ctrl.checkPropertyUnique = function(propertyName) {
|
||||||
|
return !(propertyName in ctrl.node.properties);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a node metadata property
|
||||||
|
*
|
||||||
|
* @param {string} propertyName - Name of the property
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
ctrl.deleteExtra = function(propertyName) {
|
||||||
|
delete ctrl.node.extra[propertyName];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the specified node metadata property already exists
|
||||||
|
*
|
||||||
|
* @param {string} propertyName - Name of the metadata property
|
||||||
|
* @return {boolean} True if the property already exists,
|
||||||
|
* otherwise false
|
||||||
|
*/
|
||||||
|
ctrl.checkExtraUnique = function(propertyName) {
|
||||||
|
return !(propertyName in ctrl.node.extra);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
@ -0,0 +1,207 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title" translate>Enroll Node</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h4 class="modal-title" translate>General</h4>
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name"
|
||||||
|
class="col-sm-3 control-label"
|
||||||
|
translate>Node Name</label>
|
||||||
|
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="ctrl.node.name"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
placeholder="{$ 'A unique node name. Optional.' | translate $}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="driver"
|
||||||
|
class="col-sm-3 control-label"
|
||||||
|
translate>Node Driver</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="driver"
|
||||||
|
class="form-control"
|
||||||
|
ng-options="driver as driver.name for driver in ctrl.drivers"
|
||||||
|
ng-model="ctrl.selectedDriver"
|
||||||
|
ng-change="ctrl.loadDriverProperties(ctrl.selectedDriver.name)">
|
||||||
|
<option value="" disabled selected translate>Select a Driver</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div ng-if="!ctrl.loadingDriverProperties && ctrl.driverProperties">
|
||||||
|
<hr/>
|
||||||
|
<h4 class="modal-title" translate>Driver Info</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="form-horizontal"
|
||||||
|
name="DriverInfoForm"
|
||||||
|
id="DriverInfoForm">
|
||||||
|
<p class="text-center"
|
||||||
|
ng-if="ctrl.loadingDriverProperties">
|
||||||
|
<small><em><i class="fa fa-spin fa-refresh"></i></em></small>
|
||||||
|
</p>
|
||||||
|
<div class="form-group"
|
||||||
|
ng-repeat="(name, property) in ctrl.driverProperties"
|
||||||
|
ng-show="property.isActive()">
|
||||||
|
<label for="{$ name $}"
|
||||||
|
class="col-sm-3 control-label"
|
||||||
|
style="white-space: nowrap"
|
||||||
|
translate>
|
||||||
|
{$ name $}
|
||||||
|
<span class="help-icon"
|
||||||
|
data-container="body"
|
||||||
|
title=""
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-original-title="{$ property.getDescription() | translate $}">
|
||||||
|
<span class="fa fa-question-circle"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div ng-if="!property.getSelectOptions()" class="col-sm-9">
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="{$ name $}"
|
||||||
|
name="{$ name $}"
|
||||||
|
ng-model="property.inputValue"
|
||||||
|
placeholder="{$ property.getDescription() | translate $}"
|
||||||
|
ng-required="property.isRequired()"/>
|
||||||
|
</div>
|
||||||
|
<div ng-if="property.getSelectOptions()" class="col-sm-9">
|
||||||
|
<select id="{$ name $}"
|
||||||
|
class="form-control"
|
||||||
|
ng-options="opt for opt in property.getSelectOptions()"
|
||||||
|
ng-model="property.inputValue"
|
||||||
|
ng-required="property.isRequired()">
|
||||||
|
<option value=""
|
||||||
|
disabled
|
||||||
|
selected
|
||||||
|
translate>{$ property.getDescription() $}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<h4 class="modal-title" translate>Properties</h4>
|
||||||
|
|
||||||
|
<form class="form-horizontal"
|
||||||
|
id="AddPropertyForm"
|
||||||
|
name="AddPropertyForm"
|
||||||
|
style="margin-bottom:10px;">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-addon"
|
||||||
|
style="width:25%;text-align:right">
|
||||||
|
Property</span>
|
||||||
|
<input class="form-control"
|
||||||
|
type="text"
|
||||||
|
ng-model="propertyName"
|
||||||
|
validate-unique="ctrl.checkPropertyUnique"
|
||||||
|
placeholder="{$ 'Property Name' | translate $}"/>
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
type="button"
|
||||||
|
ng-disabled="!propertyName || AddPropertyForm.$invalid"
|
||||||
|
ng-click="ctrl.node.properties[propertyName] = null;
|
||||||
|
propertyName = null">
|
||||||
|
<span class="fa fa-plus"> </span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="form-horizontal"
|
||||||
|
id="PropertiesForm"
|
||||||
|
name="PropertiesForm">
|
||||||
|
<div class="input-group input-group-sm"
|
||||||
|
ng-repeat="(propertyName, propertyValue) in ctrl.node.properties">
|
||||||
|
<span class="input-group-addon"
|
||||||
|
style="width:25%;text-align:right">
|
||||||
|
{$ propertyName $}
|
||||||
|
</span>
|
||||||
|
<input class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="{$ propertyName $}"
|
||||||
|
ng-model="ctrl.node.properties[propertyName]"
|
||||||
|
ng-required="true"/>
|
||||||
|
<div class="input-group-btn">
|
||||||
|
<a class="btn btn-default"
|
||||||
|
ng-click="ctrl.deleteProperty(propertyName)">
|
||||||
|
<span class="fa fa-minus"> </span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<h4 class="modal-title" translate>Extra</h4>
|
||||||
|
|
||||||
|
<form class="form-horizontal"
|
||||||
|
id="AddExtraForm"
|
||||||
|
name="AddExtraForm"
|
||||||
|
style="margin-bottom:10px;">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-addon"
|
||||||
|
style="width:25%;text-align:right">
|
||||||
|
Extra</span>
|
||||||
|
<input class="form-control"
|
||||||
|
type="text"
|
||||||
|
ng-model="extraName"
|
||||||
|
validate-unique="ctrl.checkExtraUnique"
|
||||||
|
placeholder="{$ 'Property Name' | translate $}"/>
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
type="button"
|
||||||
|
ng-disabled="!extraName || AddExtraForm.$invalid"
|
||||||
|
ng-click="ctrl.node.extra[extraName] = null;
|
||||||
|
extraName = null">
|
||||||
|
<span class="fa fa-plus"> </span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="form-horizontal"
|
||||||
|
id="ExtraForm"
|
||||||
|
name="ExtraForm">
|
||||||
|
<div class="input-group input-group-sm"
|
||||||
|
ng-repeat="(propertyName, propertyValue) in ctrl.node.extra">
|
||||||
|
<span class="input-group-addon"
|
||||||
|
style="width:25%;text-align:right">
|
||||||
|
{$ propertyName $}
|
||||||
|
</span>
|
||||||
|
<input class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="{$ propertyName $}"
|
||||||
|
ng-model="ctrl.node.extra[propertyName]"
|
||||||
|
ng-required="true"/>
|
||||||
|
<div class="input-group-btn">
|
||||||
|
<a class="btn btn-default"
|
||||||
|
ng-click="ctrl.deleteExtra(propertyName)">
|
||||||
|
<span class="fa fa-minus"> </span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer ng-scope">
|
||||||
|
<button class="btn btn-default"
|
||||||
|
ng-click="ctrl.cancel()">
|
||||||
|
<span class="fa fa-close"></span>
|
||||||
|
<span class="ng-scope" translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
ng-disabled="!ctrl.driverProperties ||
|
||||||
|
DriverInfoForm.$invalid ||
|
||||||
|
PropertiesForm.$invalid ||
|
||||||
|
ExtraForm.$invalid"
|
||||||
|
ng-click="ctrl.enroll()"
|
||||||
|
class="btn btn-primary"
|
||||||
|
translate>
|
||||||
|
Enroll Node
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -0,0 +1,332 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Cray Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var REQUIRED = " " + gettext("Required") + ".";
|
||||||
|
var selectOptionsRegexp =
|
||||||
|
new RegExp(
|
||||||
|
gettext('(?:[Oo]ne of )(?!this)((?:(?:"[^"]+"|[^,\. ]+)(?:, |\.))+)'));
|
||||||
|
var defaultValueRegexp = new RegExp(gettext('default is ([^". ]+|"[^"]+")'));
|
||||||
|
var oneOfRegexp =
|
||||||
|
new RegExp(gettext('One of this, (.*) must be specified\.'));
|
||||||
|
var notInsideMatch = -1;
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('horizon.dashboard.admin.ironic')
|
||||||
|
.factory('horizon.dashboard.admin.ironic.enroll-node.service',
|
||||||
|
enrollNodeService);
|
||||||
|
|
||||||
|
enrollNodeService.$inject = [
|
||||||
|
'$modal',
|
||||||
|
'horizon.dashboard.admin.basePath',
|
||||||
|
'$log'
|
||||||
|
];
|
||||||
|
|
||||||
|
function enrollNodeService($modal, basePath, $log) {
|
||||||
|
var service = {
|
||||||
|
modal: modal,
|
||||||
|
DriverProperty: DriverProperty
|
||||||
|
};
|
||||||
|
|
||||||
|
function modal() {
|
||||||
|
var options = {
|
||||||
|
controller: 'EnrollNodeController as ctrl',
|
||||||
|
templateUrl: basePath + '/ironic/enroll-node/enroll-node.html'
|
||||||
|
};
|
||||||
|
return $modal.open(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new driver property
|
||||||
|
*
|
||||||
|
* @class DriverProperty
|
||||||
|
* @param {string} name - Name of property
|
||||||
|
* @param {string} desc - Description of property
|
||||||
|
* @param {object} propertySet - Set of properties to which this one belongs
|
||||||
|
*
|
||||||
|
* @property {string} defaultValue - Default value of the property
|
||||||
|
* @property {string[]} selectOptions - If the property is limited to a
|
||||||
|
* set of enumerted values then selectOptions will be an array of those
|
||||||
|
* values, otherwise null
|
||||||
|
* @property {boolean} required - Boolean value indicating whether a value
|
||||||
|
* must be supplied for this property if it is active
|
||||||
|
* @property {PostfixExpr} isActiveExpr - Null if this property is always
|
||||||
|
* active; otherwise, a boolean expression that when evaluated will
|
||||||
|
* return whether this variable is active
|
||||||
|
* @propery {string} inputValue User assigned value for this property
|
||||||
|
*/
|
||||||
|
function DriverProperty(name, desc, propertySet) {
|
||||||
|
this.name = name;
|
||||||
|
this.desc = desc;
|
||||||
|
this.propertySet = propertySet;
|
||||||
|
// Determine whether this property should be presented as a selection
|
||||||
|
this.selectOptions = this._analyzeSelectOptions();
|
||||||
|
this.required = null; // Initialize to unknown
|
||||||
|
// Expression to be evaluated to determine whether property is active.
|
||||||
|
// By default the property is considered active.
|
||||||
|
this.isActiveExpr = null;
|
||||||
|
var result = this._analyzeRequiredOnlyDependencies();
|
||||||
|
if (result) {
|
||||||
|
this.required = result[0];
|
||||||
|
this.isActiveExpr = result[1];
|
||||||
|
}
|
||||||
|
if (!this.isActiveExpr) {
|
||||||
|
result = this._analyzeOneOfDependencies();
|
||||||
|
if (result) {
|
||||||
|
this.required = result[0];
|
||||||
|
this.isActiveExpr = result[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.required === null) {
|
||||||
|
this.required = desc.endsWith(REQUIRED);
|
||||||
|
}
|
||||||
|
this.defaultValue = this._getDefaultValue();
|
||||||
|
this.inputValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverProperty.prototype.isActive = function() {
|
||||||
|
if (!this.isActiveExpr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var active = this.isActiveExpr.evaluate(this.propertySet);
|
||||||
|
return active === null ? true : active;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Must a value be provided for this property
|
||||||
|
*
|
||||||
|
* @return {boolean} True if a value must be provided for this property
|
||||||
|
*/
|
||||||
|
DriverProperty.prototype.isRequired = function() {
|
||||||
|
return this.required && this.isActive();
|
||||||
|
};
|
||||||
|
|
||||||
|
DriverProperty.prototype._analyzeSelectOptions = function() {
|
||||||
|
var match = this.desc.match(selectOptionsRegexp);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches = match[1].substring(0, match[1].length - 1).split(", ");
|
||||||
|
var options = [];
|
||||||
|
angular.forEach(matches, function(match) {
|
||||||
|
options.push(trimQuotes(match));
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of select options for this property
|
||||||
|
*
|
||||||
|
* @return {string[]} null if this property is not selectable; else,
|
||||||
|
* an array of selectable options
|
||||||
|
*/
|
||||||
|
DriverProperty.prototype.getSelectOptions = function() {
|
||||||
|
return this.selectOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove leading/trailing double-quotes from a string
|
||||||
|
*
|
||||||
|
* @param {string} str - String to be trimmed
|
||||||
|
* @return {string} trim'd string
|
||||||
|
*/
|
||||||
|
function trimQuotes(str) {
|
||||||
|
return str.charAt(0) === '"'
|
||||||
|
? str.substring(1, str.length - 1) : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default value of this property
|
||||||
|
*
|
||||||
|
* @return {string} Default value of this property
|
||||||
|
*/
|
||||||
|
DriverProperty.prototype._getDefaultValue = function() {
|
||||||
|
var match = this.desc.match(defaultValueRegexp);
|
||||||
|
return match ? trimQuotes(match[1]) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual value of this property
|
||||||
|
*
|
||||||
|
* @return {string} Get the actual value of this property. If
|
||||||
|
* an input value has not been specified, but a default value exists
|
||||||
|
* that will be returned.
|
||||||
|
*/
|
||||||
|
DriverProperty.prototype.getActualValue = function() {
|
||||||
|
return this.inputValue ? this.inputValue
|
||||||
|
: this.defaultValue ? this.defaultValue : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the description of this property
|
||||||
|
*
|
||||||
|
* @return {string} Description of this property
|
||||||
|
*/
|
||||||
|
DriverProperty.prototype.getDescription = function() {
|
||||||
|
return this.desc;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the property description to build an expression that will
|
||||||
|
* evaluate to a boolean result indicating whether the property is
|
||||||
|
* active
|
||||||
|
*
|
||||||
|
* @return {array} null if this property is not dependent on any others;
|
||||||
|
* otherwise,
|
||||||
|
* [0] boolean indicating whether if active a value must be
|
||||||
|
* supplied for this property.
|
||||||
|
* [1] an expression that when evaluated will return a boolean
|
||||||
|
* result indicating whether this property is active
|
||||||
|
*/
|
||||||
|
DriverProperty.prototype._analyzeRequiredOnlyDependencies = function() {
|
||||||
|
var re = /(Required|Used) only if ([^ ]+) is set to /g;
|
||||||
|
var match = re.exec(this.desc);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build logical expression to describe under what conditions this
|
||||||
|
// property is active
|
||||||
|
var expr = new PostfixExpr();
|
||||||
|
var numAdds = 0;
|
||||||
|
|
||||||
|
var i = notInsideMatch;
|
||||||
|
var j = re.lastIndex;
|
||||||
|
while (j < this.desc.length) {
|
||||||
|
if (i === notInsideMatch && this.desc.charAt(j) === ".") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.desc.charAt(j) === '"') {
|
||||||
|
if (i === notInsideMatch) {
|
||||||
|
i = j + 1;
|
||||||
|
} else {
|
||||||
|
expr.addProperty(match[2]);
|
||||||
|
expr.addValue(this.desc.substring(i, j));
|
||||||
|
expr.addOperator("==");
|
||||||
|
numAdds++;
|
||||||
|
if (numAdds > 1) {
|
||||||
|
expr.addOperator("or");
|
||||||
|
}
|
||||||
|
i = notInsideMatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
$log.debug("_analyzeRequiredOnlyDependencies | " +
|
||||||
|
this.desc + " | " +
|
||||||
|
match[2] + ", " +
|
||||||
|
JSON.stringify(expr));
|
||||||
|
return [match[1] === "Required", expr];
|
||||||
|
};
|
||||||
|
|
||||||
|
DriverProperty.prototype._analyzeOneOfDependencies = function() {
|
||||||
|
var match = this.desc.match(oneOfRegexp);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build logical expression to describe under what conditions this
|
||||||
|
// property is active
|
||||||
|
var expr = new PostfixExpr();
|
||||||
|
|
||||||
|
var parts = match[1].split(", or ");
|
||||||
|
expr.addProperty(parts[1]);
|
||||||
|
expr.addValue(null);
|
||||||
|
expr.addOperator("==");
|
||||||
|
|
||||||
|
parts = parts[0].split(", ");
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
expr.addProperty(parts[i]);
|
||||||
|
expr.addValue(null);
|
||||||
|
expr.addOperator("==");
|
||||||
|
expr.addOperator("and");
|
||||||
|
}
|
||||||
|
$log.debug("_analyzeOneOfDependencies | " +
|
||||||
|
this.desc + " | " +
|
||||||
|
JSON.stringify(match) + ", " +
|
||||||
|
JSON.stringify(expr));
|
||||||
|
return [true, expr];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostFixExpr is a class primarily developed to support the
|
||||||
|
* evaluation of boolean expressions that determine whether a
|
||||||
|
* particular property is active.
|
||||||
|
*
|
||||||
|
* The expression is stored as a postfix sequence of operands and
|
||||||
|
* operators. Operands are currently limited to the literal values
|
||||||
|
* and the values of properties in a specified set. Currently
|
||||||
|
* supported operands are ==, or, and.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function PostfixExpr() {
|
||||||
|
this.elem = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
PostfixExpr.prototype.addProperty = function(propertyName) {
|
||||||
|
this.elem.push({name: propertyName});
|
||||||
|
};
|
||||||
|
|
||||||
|
PostfixExpr.prototype.addValue = function(value) {
|
||||||
|
this.elem.push({value: value});
|
||||||
|
};
|
||||||
|
|
||||||
|
PostfixExpr.prototype.addOperator = function(opId) {
|
||||||
|
this.elem.push({op: opId});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the experssion using property values from a specified
|
||||||
|
* set
|
||||||
|
*
|
||||||
|
* @param {object} propertySet - Dictionary of DriverProperty instances
|
||||||
|
*
|
||||||
|
* @return {value} Value of the expression. Null if the expression
|
||||||
|
* could not be successfully evaluated.
|
||||||
|
*/
|
||||||
|
PostfixExpr.prototype.evaluate = function(propertySet) {
|
||||||
|
var resultStack = [];
|
||||||
|
for (var i = 0, len = this.elem.length; i < len; i++) {
|
||||||
|
var elem = this.elem[i];
|
||||||
|
if (angular.isDefined(elem.name)) {
|
||||||
|
resultStack.push(propertySet[elem.name].getActualValue());
|
||||||
|
} else if (angular.isDefined(elem.value)) {
|
||||||
|
resultStack.push(elem.value);
|
||||||
|
} else if (angular.isDefined(elem.op)) {
|
||||||
|
if (elem.op === "==") {
|
||||||
|
resultStack.push(resultStack.pop() === resultStack.pop());
|
||||||
|
} else if (elem.op === "or") {
|
||||||
|
resultStack.push(resultStack.pop() || resultStack.pop());
|
||||||
|
} else if (elem.op === "and") {
|
||||||
|
resultStack.push(resultStack.pop() && resultStack.pop());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultStack.length === 1 ? resultStack.pop() : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
})();
|
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Cray Inc
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe(
|
||||||
|
'horizon.dashboard.admin.ironic.enroll-node.service',
|
||||||
|
function() {
|
||||||
|
var service;
|
||||||
|
|
||||||
|
beforeEach(module('horizon.dashboard.admin'));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('$modal', jasmine.createSpy());
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(inject(function($injector) {
|
||||||
|
service =
|
||||||
|
$injector.get('horizon.dashboard.admin.ironic.enroll-node.service');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('defines the service', function() {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DriverProperty', function() {
|
||||||
|
it('Base construction', function() {
|
||||||
|
var propertyName = 'propertyName';
|
||||||
|
var description = '';
|
||||||
|
var propertySet = [];
|
||||||
|
var property = new service.DriverProperty(propertyName,
|
||||||
|
description,
|
||||||
|
propertySet);
|
||||||
|
expect(property.name).toBe(propertyName);
|
||||||
|
expect(property.desc).toBe(description);
|
||||||
|
expect(property.propertySet).toBe(propertySet);
|
||||||
|
expect(property.getSelectOptions()).toBe(null);
|
||||||
|
expect(property.required).toBe(false);
|
||||||
|
expect(property.defaultValue).toBe(null);
|
||||||
|
expect(property.inputValue).toBe(null);
|
||||||
|
expect(property.getActualValue()).toBe(null);
|
||||||
|
expect(property.isActive()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Required - ends with', function() {
|
||||||
|
var property = new service.DriverProperty('propertyName',
|
||||||
|
' Required.',
|
||||||
|
[]);
|
||||||
|
expect(property.required).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Not required - missing space', function() {
|
||||||
|
var property = new service.DriverProperty('propertyName',
|
||||||
|
'Required.',
|
||||||
|
[]);
|
||||||
|
expect(property.required).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Not required - missing period', function() {
|
||||||
|
var property = new service.DriverProperty('propertyName',
|
||||||
|
' Required',
|
||||||
|
[]);
|
||||||
|
expect(property.required).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Select options', function() {
|
||||||
|
var property = new service.DriverProperty(
|
||||||
|
'propertyName',
|
||||||
|
'One of "foo", bar.',
|
||||||
|
[]);
|
||||||
|
expect(property.getSelectOptions()).toEqual(['foo', 'bar']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Select options - No single quotes', function() {
|
||||||
|
var property = new service.DriverProperty(
|
||||||
|
'propertyName',
|
||||||
|
"One of 'foo', bar.",
|
||||||
|
[]);
|
||||||
|
expect(property.getSelectOptions()).toEqual(["'foo'", 'bar']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('default - is string', function() {
|
||||||
|
var property = new service.DriverProperty(
|
||||||
|
'propertyName',
|
||||||
|
'default is "5.1".',
|
||||||
|
[]);
|
||||||
|
expect(property._getDefaultValue()).toEqual('5.1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('default - period processing', function() {
|
||||||
|
var property = new service.DriverProperty(
|
||||||
|
'propertyName',
|
||||||
|
'default is 5.1.',
|
||||||
|
[]);
|
||||||
|
expect(property._getDefaultValue()).toEqual('5');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
@ -28,157 +28,252 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc service
|
* Service that provides access to the Ironic client API
|
||||||
* @name horizon.app.core.openstack-service-api.ironic
|
*
|
||||||
* @description Provides access to Ironic API
|
* @param {object} apiService - HTTP service
|
||||||
|
* @param {object} toastService - User message service
|
||||||
|
* @return {object} Ironic API service
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function ironicAPI(apiService, toastService) {
|
function ironicAPI(apiService, toastService) {
|
||||||
var service = {
|
var service = {
|
||||||
getNodes: getNodes,
|
createNode: createNode,
|
||||||
|
deleteNode: deleteNode,
|
||||||
|
getDrivers: getDrivers,
|
||||||
|
getDriverProperties: getDriverProperties,
|
||||||
getNode: getNode,
|
getNode: getNode,
|
||||||
|
getNodes: getNodes,
|
||||||
getPortsWithNode: getPortsWithNode,
|
getPortsWithNode: getPortsWithNode,
|
||||||
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
powerOffNode: powerOffNode,
|
||||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode,
|
|
||||||
powerOnNode: powerOnNode,
|
powerOnNode: powerOnNode,
|
||||||
powerOffNode: powerOffNode
|
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
||||||
|
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode
|
||||||
};
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
///////////
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name horizon.app.core.openstack-service-api.ironic.getNodes
|
* Retrieve a list of nodes
|
||||||
* @description Retrieve a list of nodes
|
|
||||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-nodes
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-nodes
|
||||||
*
|
*
|
||||||
* @return Node collection in JSON
|
* @return {promise} Node collection in JSON
|
||||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#NodeCollection
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#NodeCollection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getNodes() {
|
function getNodes() {
|
||||||
return apiService.get('/api/ironic/nodes/')
|
return apiService.get('/api/ironic/nodes/')
|
||||||
.error(function() {
|
.error(function() {
|
||||||
toastService.add('error', gettext('Unable to retrieve Ironic nodes.'));
|
toastService.add('error',
|
||||||
|
gettext('Unable to retrieve Ironic nodes.'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name horizon.app.core.openstack-service-api.ironic.getNode
|
* Retrieve information about the given node.
|
||||||
* @description Retrieve information about the given node.
|
|
||||||
*
|
*
|
||||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-nodes-(node_ident)
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-
|
||||||
|
* nodes-(node_ident)
|
||||||
*
|
*
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
* @param {string} uuid – UUID or logical name of a node.
|
||||||
|
* @return {promise} Node
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getNode(uuid) {
|
function getNode(uuid) {
|
||||||
return apiService.get('/api/ironic/nodes/' + uuid).error(function() {
|
return apiService.get('/api/ironic/nodes/' + uuid)
|
||||||
toastService.add('error', gettext('Unable to retrieve the Ironic node.'));
|
.error(function(reason) {
|
||||||
|
var msg = gettext('Unable to retrieve the Ironic node: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name horizon.app.core.openstack-service-api.ironic.getPortsWithNode
|
* Retrieve a list of ports associated with a node.
|
||||||
* @description Retrieve a list of ports associated with a node.
|
|
||||||
*
|
*
|
||||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-ports
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-ports
|
||||||
*
|
*
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
* @param {string} uuid – UUID or logical name of a node.
|
||||||
|
* @return {promise} List of ports
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getPortsWithNode(uuid) {
|
function getPortsWithNode(uuid) {
|
||||||
var config = {
|
var config = {
|
||||||
params : {
|
params : {
|
||||||
node_id: uuid
|
node_id: uuid
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return apiService.get('/api/ironic/ports/', config).error(function() {
|
return apiService.get('/api/ironic/ports/', config)
|
||||||
toastService.add('error', gettext('Unable to retrieve the Ironic node ports.'));
|
.error(function(reason) {
|
||||||
|
var msg = gettext(
|
||||||
|
'Unable to retrieve the Ironic node ports: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name horizon.app.core.openstack-service-api.ironic.putNodeInMaintenanceMode
|
* Put the node in maintenance mode.
|
||||||
* @description Put the node in maintenance mode.
|
|
||||||
*
|
*
|
||||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||||
* put--v1-nodes-(node_ident)-maintenance}
|
* put--v1-nodes-(node_ident)-maintenance
|
||||||
*
|
*
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
* @param {string} uuid – UUID or logical name of a node.
|
||||||
|
* @param {string} reason – Reason for why node is being put into
|
||||||
|
* maintenance mode
|
||||||
|
* @return {promise} Promise
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function putNodeInMaintenanceMode(uuid, reason) {
|
function putNodeInMaintenanceMode(uuid, reason) {
|
||||||
var data = {
|
var data = {
|
||||||
maint_reason: reason ? reason : gettext("No maintenance reason given.")
|
maint_reason: reason ? reason : gettext("No maintenance reason given.")
|
||||||
};
|
};
|
||||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance', data).error(function() {
|
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance',
|
||||||
toastService.add('error',
|
data)
|
||||||
gettext('Unable to put the Ironic node in maintenance mode.'));
|
.error(function(reason) {
|
||||||
|
var msg = gettext(
|
||||||
|
'Unable to put the Ironic node in maintenance mode: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name horizon.app.core.openstack-service-api.ironic.removeNodeFromMaintenanceMode
|
* Remove the node from maintenance mode.
|
||||||
* @description Remove the node from maintenance mode.
|
|
||||||
*
|
*
|
||||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||||
* delete--v1-nodes-(node_ident)-maintenance}
|
* delete--v1-nodes-(node_ident)-maintenance
|
||||||
*
|
*
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
* @param {string} uuid – UUID or logical name of a node.
|
||||||
|
* @return {promise} Promise
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function removeNodeFromMaintenanceMode(uuid) {
|
function removeNodeFromMaintenanceMode(uuid) {
|
||||||
return apiService.delete('/api/ironic/nodes/' + uuid + '/maintenance').error(function() {
|
return apiService.delete('/api/ironic/nodes/' + uuid + '/maintenance')
|
||||||
toastService.add('error',
|
.error(function(reason) {
|
||||||
gettext('Unable to remove the Ironic node from maintenance mode.'));
|
var msg = gettext('Unable to remove the Ironic node ' +
|
||||||
|
'from maintenance mode: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name horizon.app.core.openstack-service-api.ironic.powerOnNode
|
* Set the power state of the node.
|
||||||
* @description Set the power state of the node.
|
|
||||||
*
|
*
|
||||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||||
* put--v1-nodes-(node_ident)-states-power}
|
* put--v1-nodes-(node_ident)-states-power
|
||||||
*
|
*
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
* @param {string} uuid – UUID or logical name of a node.
|
||||||
|
* @return {promise} Promise
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function powerOnNode(uuid) {
|
function powerOnNode(uuid) {
|
||||||
var data = {
|
var data = {
|
||||||
state: 'on'
|
state: 'on'
|
||||||
};
|
};
|
||||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power', data)
|
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power',
|
||||||
.success(function () {
|
data)
|
||||||
toastService.add('success', gettext('Refresh page to see updated power status'));
|
.success(function() {
|
||||||
|
toastService.add('success',
|
||||||
|
gettext('Refresh page to see updated power status'));
|
||||||
})
|
})
|
||||||
.error(function () {
|
.error(function(reason) {
|
||||||
toastService.add('error', gettext('Unable to power on the node'));
|
var msg = gettext('Unable to power on the node: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name horizon.app.core.openstack-service-api.ironic.powerOffNode
|
* Set the power state of the node.
|
||||||
* @description Set the power state of the node.
|
|
||||||
*
|
*
|
||||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||||
* put--v1-nodes-(node_ident)-states-power}
|
* put--v1-nodes-(node_ident)-states-power
|
||||||
*
|
*
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
* @param {string} uuid – UUID or logical name of a node.
|
||||||
|
* @return {promise} Promise
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function powerOffNode(uuid) {
|
function powerOffNode(uuid) {
|
||||||
var data = {
|
var data = {
|
||||||
state: 'off'
|
state: 'off'
|
||||||
};
|
};
|
||||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power', data)
|
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power',
|
||||||
.success(function () {
|
data)
|
||||||
toastService.add('success', gettext('Refresh page to see updated power status'));
|
.success(function() {
|
||||||
|
toastService.add('success',
|
||||||
|
gettext('Refresh page to see updated power status'));
|
||||||
})
|
})
|
||||||
.error(function () {
|
.error(function(reason) {
|
||||||
toastService.add('error', gettext('Unable to power off the node'));
|
var msg = gettext('Unable to power off the node: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Ironic node
|
||||||
|
*
|
||||||
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#post--v1-nodes
|
||||||
|
*
|
||||||
|
* @param {object} params – Object containing parameters that define
|
||||||
|
* the node to be created
|
||||||
|
* @return {promise} Promise
|
||||||
|
*/
|
||||||
|
function createNode(params) {
|
||||||
|
var data = {
|
||||||
|
node: params
|
||||||
|
};
|
||||||
|
return apiService.post('/api/ironic/nodes/', data)
|
||||||
|
.success(function() {
|
||||||
|
})
|
||||||
|
.error(function(reason) {
|
||||||
|
var msg = gettext('Unable to create node: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the specified node from inventory
|
||||||
|
*
|
||||||
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||||
|
* delete--v1-nodes
|
||||||
|
*
|
||||||
|
* @param {string} nodeIdent – UUID or logical name of a node.
|
||||||
|
* @return {promise} Promise
|
||||||
|
*/
|
||||||
|
function deleteNode(nodeIdent) {
|
||||||
|
var data = {
|
||||||
|
node: nodeIdent
|
||||||
|
};
|
||||||
|
return apiService.delete('/api/ironic/nodes/', data)
|
||||||
|
.success(function() {
|
||||||
|
})
|
||||||
|
.error(function(reason) {
|
||||||
|
var msg = gettext('Unable to delete node %s: %s');
|
||||||
|
toastService.add(
|
||||||
|
'error',
|
||||||
|
interpolate(msg, [nodeIdent, reason], false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the list of Ironic drivers
|
||||||
|
*
|
||||||
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-drivers
|
||||||
|
*
|
||||||
|
* @return {promise} Driver collection in JSON
|
||||||
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#DriverList
|
||||||
|
*/
|
||||||
|
function getDrivers() {
|
||||||
|
return apiService.get('/api/ironic/drivers/').error(function(reason) {
|
||||||
|
var msg = gettext('Unable to retrieve Ironic drivers: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve properities of a specified driver
|
||||||
|
*
|
||||||
|
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||||
|
* get--v1-drivers-properties
|
||||||
|
*
|
||||||
|
* @param {string} driverName - Driver name
|
||||||
|
* @returns {promise} Property list
|
||||||
|
*/
|
||||||
|
function getDriverProperties(driverName) {
|
||||||
|
return apiService.get(
|
||||||
|
'/api/ironic/drivers/' + driverName + '/properties').error(
|
||||||
|
function(reason) {
|
||||||
|
var msg = gettext(
|
||||||
|
'Unable to retrieve driver properties: %s');
|
||||||
|
toastService.add('error', interpolate(msg, [reason], false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,3 +28,4 @@
|
|||||||
Put Node(s) Into Maintenance Mode
|
Put Node(s) Into Maintenance Mode
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -20,6 +20,20 @@
|
|||||||
var POWER_STATE_ON = 'power on';
|
var POWER_STATE_ON = 'power on';
|
||||||
var POWER_STATE_OFF = 'power off';
|
var POWER_STATE_OFF = 'power off';
|
||||||
|
|
||||||
|
var DELETE_NODE_TITLE = gettext("Delete Node");
|
||||||
|
var DELETE_NODE_MSG =
|
||||||
|
gettext('Are you sure you want to delete node "%s"? ' +
|
||||||
|
'This action cannot be undone.');
|
||||||
|
var DELETE_NODE_SUCCESS = gettext('Successfully deleted node "%s"');
|
||||||
|
var DELETE_NODE_ERROR = gettext('Unable to delete node "%s"');
|
||||||
|
|
||||||
|
var DELETE_NODES_TITLE = gettext("Delete Nodes");
|
||||||
|
var DELETE_NODES_MSG =
|
||||||
|
gettext('Are you sure you want to delete nodes "%s"? ' +
|
||||||
|
'This action cannot be undone.');
|
||||||
|
var DELETE_NODES_SUCCESS = gettext('Successfully deleted nodes "%s"');
|
||||||
|
var DELETE_NODES_ERROR = gettext('Error deleting nodes "%s"');
|
||||||
|
|
||||||
angular
|
angular
|
||||||
.module('horizon.dashboard.admin.ironic')
|
.module('horizon.dashboard.admin.ironic')
|
||||||
.factory('horizon.dashboard.admin.ironic.actions', actions);
|
.factory('horizon.dashboard.admin.ironic.actions', actions);
|
||||||
@ -27,11 +41,15 @@
|
|||||||
actions.$inject = [
|
actions.$inject = [
|
||||||
'horizon.app.core.openstack-service-api.ironic',
|
'horizon.app.core.openstack-service-api.ironic',
|
||||||
'horizon.framework.widgets.toast.service',
|
'horizon.framework.widgets.toast.service',
|
||||||
'$q'
|
'horizon.framework.widgets.modal.deleteModalService',
|
||||||
|
'$q',
|
||||||
|
'$rootScope'
|
||||||
];
|
];
|
||||||
|
|
||||||
function actions(ironic, toastService, $q) {
|
function actions(ironic, toastService, deleteModalService, $q, $rootScope) {
|
||||||
var service = {
|
var service = {
|
||||||
|
deleteNode: deleteNode,
|
||||||
|
deleteNodes: deleteNodes,
|
||||||
powerOn: powerOn,
|
powerOn: powerOn,
|
||||||
powerOff: powerOff,
|
powerOff: powerOff,
|
||||||
powerOnAll: powerOnNodes,
|
powerOnAll: powerOnNodes,
|
||||||
@ -44,33 +62,62 @@
|
|||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
|
function deleteNode(node) {
|
||||||
|
var labels = {
|
||||||
|
title: DELETE_NODE_TITLE,
|
||||||
|
message: DELETE_NODE_MSG,
|
||||||
|
submit: DELETE_NODE_TITLE,
|
||||||
|
success: DELETE_NODE_SUCCESS,
|
||||||
|
error: DELETE_NODE_ERROR
|
||||||
|
};
|
||||||
|
var context = {
|
||||||
|
labels: labels,
|
||||||
|
deleteEntity: ironic.deleteNode,
|
||||||
|
successEvent: "ironic-ui:delete-node-success"
|
||||||
|
};
|
||||||
|
return deleteModalService.open($rootScope, [node], context);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteNodes(nodes) {
|
||||||
|
var labels = {
|
||||||
|
title: DELETE_NODES_TITLE,
|
||||||
|
message: DELETE_NODES_MSG,
|
||||||
|
submit: DELETE_NODES_TITLE,
|
||||||
|
success: DELETE_NODES_SUCCESS,
|
||||||
|
error: DELETE_NODES_ERROR
|
||||||
|
};
|
||||||
|
var context = {
|
||||||
|
labels: labels,
|
||||||
|
deleteEntity: ironic.deleteNode,
|
||||||
|
successEvent: "ironic-ui:delete-node-success"
|
||||||
|
};
|
||||||
|
return deleteModalService.open($rootScope, nodes, context);
|
||||||
|
}
|
||||||
|
|
||||||
// power state
|
// power state
|
||||||
|
|
||||||
function powerOn(node) {
|
function powerOn(node) {
|
||||||
if (node.power_state !== POWER_STATE_OFF) {
|
if (node.power_state !== POWER_STATE_OFF) {
|
||||||
return $q.reject(gettext("Node is not powered off."));
|
var msg = gettext("Node %s is not powered off.");
|
||||||
|
return $q.reject(interpolate(msg, [node], false));
|
||||||
}
|
}
|
||||||
return ironic.powerOnNode(node.uuid).then(
|
return ironic.powerOnNode(node.uuid).then(
|
||||||
function() {
|
function() {
|
||||||
// Set power state to be indeterminate
|
// Set power state to be indeterminate
|
||||||
node.power_state = null;
|
node.power_state = null;
|
||||||
},
|
}
|
||||||
function(reason) {
|
);
|
||||||
toastService.add('error', reason);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function powerOff(node) {
|
function powerOff(node) {
|
||||||
if (node.power_state !== POWER_STATE_ON) {
|
if (node.power_state !== POWER_STATE_ON) {
|
||||||
return $q.reject(gettext("Node is not powered on."));
|
var msg = gettext("Node %s is not powered on.");
|
||||||
|
return $q.reject(interpolate(msg, [node], false));
|
||||||
}
|
}
|
||||||
return ironic.powerOffNode(node.uuid).then(
|
return ironic.powerOffNode(node.uuid).then(
|
||||||
function() {
|
function() {
|
||||||
// Set power state to be indeterminate
|
// Set power state to be indeterminate
|
||||||
node.power_state = null;
|
node.power_state = null;
|
||||||
},
|
|
||||||
function(reason) {
|
|
||||||
toastService.add('error', reason);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -87,30 +134,26 @@
|
|||||||
|
|
||||||
function putInMaintenanceMode(node, maintReason) {
|
function putInMaintenanceMode(node, maintReason) {
|
||||||
if (node.maintenance !== false) {
|
if (node.maintenance !== false) {
|
||||||
return $q.reject(gettext("Node is already in maintenance mode."));
|
var msg = gettext("Node %s is already in maintenance mode.");
|
||||||
|
return $q.reject(interpolate(msg, [node], false));
|
||||||
}
|
}
|
||||||
return ironic.putNodeInMaintenanceMode(node.uuid, maintReason).then(
|
return ironic.putNodeInMaintenanceMode(node.uuid, maintReason).then(
|
||||||
function () {
|
function () {
|
||||||
node.maintenance = true;
|
node.maintenance = true;
|
||||||
node.maintenance_reason = maintReason;
|
node.maintenance_reason = maintReason;
|
||||||
},
|
|
||||||
function(reason) {
|
|
||||||
toastService.add('error', reason);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFromMaintenanceMode(node) {
|
function removeFromMaintenanceMode(node) {
|
||||||
if (node.maintenance !== true) {
|
if (node.maintenance !== true) {
|
||||||
return $q.reject(gettext("Node is not in maintenance mode."));
|
var msg = gettext("Node %s is not in maintenance mode.");
|
||||||
|
return $q.reject(interpolate(msg, [node], false));
|
||||||
}
|
}
|
||||||
return ironic.removeNodeFromMaintenanceMode(node.uuid).then(
|
return ironic.removeNodeFromMaintenanceMode(node.uuid).then(
|
||||||
function () {
|
function () {
|
||||||
node.maintenance = false;
|
node.maintenance = false;
|
||||||
node.maintenance_reason = "";
|
node.maintenance_reason = "";
|
||||||
},
|
|
||||||
function (reason) {
|
|
||||||
toastService.add('error', reason);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<dt ng-repeat-start="port in ctrl.ports">
|
<dt ng-repeat-start="port in ctrl.ports">
|
||||||
{$ 'MAC ' + (1 + $index) $}</dt>
|
{$ 'MAC ' + (1 + $index) $}</dt>
|
||||||
<dd ng-if="vif_port_id = ctrl.getVifPortId(port)">
|
<dd ng-if="vif_port_id = ctrl.getVifPortId(port)">
|
||||||
<a href="/admin/networks/ports/{$ vif_port_id $}/detail">
|
<a href="/dashboard/admin/networks/ports/{$ vif_port_id $}/detail">
|
||||||
{$ port.address $}
|
{$ port.address $}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
@ -109,13 +109,13 @@
|
|||||||
<dd>{$ ctrl.node.instance_info.display_name $}</dd>
|
<dd>{$ ctrl.node.instance_info.display_name $}</dd>
|
||||||
<dt translate>Ramdisk</dt>
|
<dt translate>Ramdisk</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<a href="/admin/images/{$ ctrl.node.instance_info.ramdisk $}/detail">
|
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.ramdisk $}/detail">
|
||||||
{$ ctrl.node.instance_info.ramdisk $}
|
{$ ctrl.node.instance_info.ramdisk $}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
<dt translate>Kernel</dt>
|
<dt translate>Kernel</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<a href="/admin/images/{$ ctrl.node.instance_info.kernel $}/detail">
|
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.kernel $}/detail">
|
||||||
{$ ctrl.node.instance_info.kernel $}
|
{$ ctrl.node.instance_info.kernel $}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
@ -22,16 +22,20 @@
|
|||||||
.controller('IronicNodeListController', IronicNodeListController);
|
.controller('IronicNodeListController', IronicNodeListController);
|
||||||
|
|
||||||
IronicNodeListController.$inject = [
|
IronicNodeListController.$inject = [
|
||||||
|
'$rootScope',
|
||||||
'horizon.app.core.openstack-service-api.ironic',
|
'horizon.app.core.openstack-service-api.ironic',
|
||||||
'horizon.dashboard.admin.ironic.actions',
|
'horizon.dashboard.admin.ironic.actions',
|
||||||
'horizon.dashboard.admin.basePath',
|
'horizon.dashboard.admin.basePath',
|
||||||
'horizon.dashboard.admin.ironic.maintenance.service'
|
'horizon.dashboard.admin.ironic.maintenance.service',
|
||||||
|
'horizon.dashboard.admin.ironic.enroll-node.service'
|
||||||
];
|
];
|
||||||
|
|
||||||
function IronicNodeListController(ironic,
|
function IronicNodeListController($rootScope,
|
||||||
|
ironic,
|
||||||
actions,
|
actions,
|
||||||
basePath,
|
basePath,
|
||||||
maintenanceService) {
|
maintenanceService,
|
||||||
|
enrollNodeService) {
|
||||||
var ctrl = this;
|
var ctrl = this;
|
||||||
|
|
||||||
ctrl.nodes = [];
|
ctrl.nodes = [];
|
||||||
@ -43,6 +47,7 @@
|
|||||||
ctrl.putNodesInMaintenanceMode = putNodesInMaintenanceMode;
|
ctrl.putNodesInMaintenanceMode = putNodesInMaintenanceMode;
|
||||||
ctrl.removeNodeFromMaintenanceMode = removeNodeFromMaintenanceMode;
|
ctrl.removeNodeFromMaintenanceMode = removeNodeFromMaintenanceMode;
|
||||||
ctrl.removeNodesFromMaintenanceMode = removeNodesFromMaintenanceMode;
|
ctrl.removeNodesFromMaintenanceMode = removeNodesFromMaintenanceMode;
|
||||||
|
ctrl.enrollNode = enrollNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filtering - client-side MagicSearch
|
* Filtering - client-side MagicSearch
|
||||||
@ -81,6 +86,15 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Listen for the creation of new nodes, and update the node list
|
||||||
|
$rootScope.$on('ironic-ui:new-node', function() {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$on('ironic-ui:delete-node-success', function() {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
// RETRIVE NODES AND PORTS
|
// RETRIVE NODES AND PORTS
|
||||||
@ -124,6 +138,10 @@
|
|||||||
function removeNodesFromMaintenanceMode(nodes) {
|
function removeNodesFromMaintenanceMode(nodes) {
|
||||||
maintenanceService.removeNodesFromMaintenanceMode(nodes);
|
maintenanceService.removeNodesFromMaintenanceMode(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enrollNode() {
|
||||||
|
enrollNodeService.modal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -15,7 +15,14 @@
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="100" class="action-col">
|
<th colspan="8">
|
||||||
|
<button class="btn btn-default btn-sm pull-right"
|
||||||
|
ng-click="table.enrollNode()">
|
||||||
|
<span class="fa fa-plus"></span>
|
||||||
|
<span translate>Enroll Node</span>
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
<th class="action-col">
|
||||||
<action-list dropdown class="pull-right">
|
<action-list dropdown class="pull-right">
|
||||||
<action button-type="split-button"
|
<action button-type="split-button"
|
||||||
action-classes="'btn btn-default btn-sm'"
|
action-classes="'btn btn-default btn-sm'"
|
||||||
@ -43,6 +50,13 @@
|
|||||||
disabled="tCtrl.selected.length === 0">
|
disabled="tCtrl.selected.length === 0">
|
||||||
{$ 'Maintenance off' | translate $}
|
{$ 'Maintenance off' | translate $}
|
||||||
</action>
|
</action>
|
||||||
|
<action button-type="menu-item"
|
||||||
|
callback="table.actions.deleteNodes"
|
||||||
|
item="tCtrl.selected"
|
||||||
|
disabled="tCtrl.selected.length === 0">
|
||||||
|
<span class="fa fa-trash"></span>
|
||||||
|
{$ 'Delete nodes' | translate $}
|
||||||
|
</action>
|
||||||
</menu>
|
</menu>
|
||||||
</action-list>
|
</action-list>
|
||||||
</th>
|
</th>
|
||||||
@ -140,9 +154,15 @@
|
|||||||
item="node">
|
item="node">
|
||||||
{$ 'Maintenance off' | translate $}
|
{$ 'Maintenance off' | translate $}
|
||||||
</action>
|
</action>
|
||||||
|
<action button-type="menu-item"
|
||||||
|
callback="table.actions.deleteNode"
|
||||||
|
disabled="!(node['provision_state']==='available' || node['provision_state']==='nostate' || node['provision_state']==='manageable' || node['provision_state']==='enroll')"
|
||||||
|
item="node">
|
||||||
|
<span class="fa fa-trash"></span>
|
||||||
|
{$ 'Delete node' | translate $}
|
||||||
|
</action>
|
||||||
</menu>
|
</menu>
|
||||||
</action-list>
|
</action-list>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
Loading…
Reference in New Issue
Block a user