Merge "Add support for additional port attributes"
This commit is contained in:
@@ -23,7 +23,7 @@ from horizon.utils.memoized import memoized # noqa
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
|
||||
DEFAULT_IRONIC_API_VERSION = '1.13'
|
||||
DEFAULT_IRONIC_API_VERSION = '1.19'
|
||||
DEFAULT_INSECURE = False
|
||||
DEFAULT_CACERT = None
|
||||
|
||||
|
@@ -25,16 +25,144 @@
|
||||
|
||||
BasePortController.$inject = [
|
||||
'$uibModalInstance',
|
||||
'horizon.dashboard.admin.ironic.validMacAddressPattern',
|
||||
'horizon.dashboard.admin.ironic.validDatapathIdPattern',
|
||||
'ctrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* Utility class for managing form fields
|
||||
*
|
||||
* @param {object} args - Valid properties are:
|
||||
* value - Initial value of the field
|
||||
* required - Does the field require a value
|
||||
* desc - Field description
|
||||
* pattern - Regular expression pattern used to match
|
||||
* valid input values
|
||||
* disabled - Is the field disabled
|
||||
* info - Additional information about the current state of
|
||||
* the field. It will be displayed in a tooltip associated
|
||||
* with the field.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function Field(args) {
|
||||
this.value = angular.isDefined(args.value) ? args.value : undefined;
|
||||
this.required = angular.isDefined(args.required) ? args.required : false;
|
||||
this.desc = angular.isDefined(args.desc) ? args.desc : undefined;
|
||||
this.pattern = angular.isDefined(args.pattern)
|
||||
? new RegExp(args.pattern) : undefined;
|
||||
this.disabled = angular.isDefined(args.disabled) ? args.disabled : false;
|
||||
this.info = angular.isDefined(args.info) ? args.info : undefined;
|
||||
|
||||
/**
|
||||
* Test whether the field has a non-empty value. Note that an
|
||||
* empty value can be either '' or undefined in the case of a
|
||||
* required field
|
||||
*
|
||||
* @return {boolean} Return true if the field has a value
|
||||
*/
|
||||
this.hasValue = function() {
|
||||
return angular.isDefined(this.value) && this.value !== '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the field has help-text
|
||||
*
|
||||
* @return {boolean} Return true if the field has help text
|
||||
*/
|
||||
this.hasHelpText = function() {
|
||||
return this.desc || this.info;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the help-text associated with this field
|
||||
*
|
||||
* @return {string} Return true if the field has help text
|
||||
*/
|
||||
this.getHelpText = function() {
|
||||
var text = angular.isDefined(this.desc) ? this.desc : '';
|
||||
if (angular.isDefined(this.info)) {
|
||||
if (text !== '') {
|
||||
text += '<br><br>';
|
||||
}
|
||||
text += this.info;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
}
|
||||
|
||||
function BasePortController($uibModalInstance,
|
||||
validMacAddressPattern,
|
||||
validDatapathIdPattern,
|
||||
ctrl) {
|
||||
ctrl.port = {
|
||||
address: null,
|
||||
extra: {}
|
||||
};
|
||||
|
||||
ctrl.pxeEnabled = new Field({value: 'True'});
|
||||
|
||||
// Object used to manage local-link-connection form fields
|
||||
ctrl.localLinkConnection = {
|
||||
port_id: new Field({}),
|
||||
switch_id: new Field({
|
||||
desc: gettext("MAC address or OpenFlow datapath ID"),
|
||||
pattern: validMacAddressPattern + '|' + validDatapathIdPattern}),
|
||||
switch_info: new Field({}),
|
||||
|
||||
/**
|
||||
* Update the required property of each field based on current values
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
$update: function() {
|
||||
var required = this.port_id.hasValue() || this.switch_id.hasValue();
|
||||
|
||||
this.port_id.required = required;
|
||||
this.switch_id.required = required;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate an attribute object that conforms to the format
|
||||
* required for port creation using the Ironic client
|
||||
*
|
||||
* @return {object} local_link_connection attribute object.
|
||||
* A value of null is returned if the local-link-connection
|
||||
* information is incomplete.
|
||||
*/
|
||||
$toPortAttr: function() {
|
||||
var attr = {};
|
||||
if (this.port_id.hasValue() &&
|
||||
this.switch_id.hasValue()) {
|
||||
attr.port_id = this.port_id.value;
|
||||
attr.switch_id = this.switch_id.value;
|
||||
|
||||
if (this.switch_info.hasValue()) {
|
||||
attr.switch_info = this.switch_info.value;
|
||||
}
|
||||
}
|
||||
return attr;
|
||||
},
|
||||
|
||||
/**
|
||||
* dis/enable the local-link-connection form fields
|
||||
*
|
||||
* @param {boolean} disabled - True if the local-link-connection form
|
||||
* fields should be disabled
|
||||
* @param {string} reason - Optional reason for the state change
|
||||
* @return {void}
|
||||
*/
|
||||
$setDisabled: function(disabled, reason) {
|
||||
angular.forEach(this, function(item) {
|
||||
if (item instanceof Field) {
|
||||
item.disabled = disabled;
|
||||
item.info = reason;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the modal
|
||||
*
|
||||
|
@@ -16,6 +16,7 @@
|
||||
<label for="macAddress"
|
||||
class="control-label"
|
||||
translate>MAC address</label>
|
||||
<span class="hz-icon-required fa fa-asterisk"></span>
|
||||
<div>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
@@ -28,6 +29,66 @@
|
||||
placeholder="{$ 'MAC address for this port. Required.' | translate $}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label for="pxeEnabled"
|
||||
class="control-label"
|
||||
translate>
|
||||
PXE enabled
|
||||
<span ng-if="ctrl.pxeEnabled.hasHelpText()"
|
||||
class="help-icon"
|
||||
data-container="body"
|
||||
data-html="true"
|
||||
title=""
|
||||
data-toggle="tooltip"
|
||||
data-original-title="{$ ctrl.pxeEnabled.getHelpText() $}">
|
||||
<span class="fa fa-question-circle"></span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn-group" id="pxeEnabled">
|
||||
<label class="btn btn-default"
|
||||
ng-model="ctrl.pxeEnabled.value"
|
||||
ng-repeat="opt in ['True', 'False']"
|
||||
ng-disabled="ctrl.pxeEnabled.disabled"
|
||||
uib-btn-radio="opt">{$ opt $}</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="LocalLinkConnectionForm"
|
||||
name="LocalLinkConnectionForm"
|
||||
class="well well-sm">
|
||||
<h4 translate>Local link connection</h4>
|
||||
<div class="form-group"
|
||||
ng-repeat="(propertyName, propertyObj) in ctrl.localLinkConnection"
|
||||
ng-class="{'has-error': LocalLinkConnectionForm.{$ propertyName $}.$invalid &&
|
||||
LocalLinkConnectionForm.{$ propertyName $}.$dirty}">
|
||||
<label class="control-label"
|
||||
for="{$ propertyName $}">{$ propertyName $}
|
||||
<span ng-if="propertyObj.required"
|
||||
class="hz-icon-required fa fa-asterisk"></span>
|
||||
<span ng-if="propertyObj.hasHelpText()"
|
||||
class="help-icon"
|
||||
data-container="body"
|
||||
title=""
|
||||
data-toggle="tooltip"
|
||||
data-html="true"
|
||||
data-original-title="{$ propertyObj.getHelpText() $}">
|
||||
<span class="fa fa-question-circle"></span>
|
||||
</span>
|
||||
</label>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
id="{$ propertyName $}"
|
||||
name="{$ propertyName $}"
|
||||
ng-model="propertyObj.value"
|
||||
ng-required="propertyObj.required"
|
||||
placeholder="{$ propertyObj.desc $}"
|
||||
ng-pattern="propertyObj.pattern"
|
||||
ng-disabled="propertyObj.disabled"
|
||||
ng-change="ctrl.localLinkConnection.$update()"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="AddExtraForm" name="AddExtraForm" style="margin-bottom:10px;">
|
||||
@@ -81,7 +142,9 @@
|
||||
<span class="ng-scope" translate>Cancel</span>
|
||||
</button>
|
||||
<button type="submit"
|
||||
ng-disabled="CreatePortForm.$invalid || ExtraForm.$invalid"
|
||||
ng-disabled="CreatePortForm.$invalid ||
|
||||
LocalLinkConnectionForm.$invalid ||
|
||||
ExtraForm.$invalid"
|
||||
ng-click="ctrl.submit()"
|
||||
class="btn btn-primary">
|
||||
{$ ctrl.submitButtonTitle $}
|
||||
|
@@ -45,7 +45,7 @@
|
||||
$uibModalInstance: $uibModalInstance});
|
||||
|
||||
ctrl.modalTitle = gettext("Create Port");
|
||||
ctrl.submitButtonTitle = ctrl.modalTtile;
|
||||
ctrl.submitButtonTitle = ctrl.modalTitle;
|
||||
|
||||
/**
|
||||
* Create the defined port
|
||||
@@ -53,13 +53,22 @@
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.createPort = function() {
|
||||
ctrl.port.node_uuid = node.id;
|
||||
ironic.createPort(ctrl.port).then(
|
||||
function() {
|
||||
$uibModalInstance.close();
|
||||
var port = angular.copy(ctrl.port);
|
||||
port.node_uuid = node.id;
|
||||
|
||||
var attr = ctrl.localLinkConnection.$toPortAttr();
|
||||
if (attr) {
|
||||
port.local_link_connection = attr;
|
||||
}
|
||||
|
||||
if (ctrl.pxeEnabled.value !== 'True') {
|
||||
port.pxe_enabled = ctrl.pxeEnabled.value;
|
||||
}
|
||||
|
||||
ironic.createPort(port).then(
|
||||
function(createdPort) {
|
||||
$rootScope.$emit(ironicEvents.CREATE_PORT_SUCCESS);
|
||||
},
|
||||
function() {
|
||||
$uibModalInstance.close(createdPort);
|
||||
});
|
||||
};
|
||||
|
||||
|
@@ -43,7 +43,7 @@
|
||||
},
|
||||
templateUrl: basePath + '/base-port/base-port.html'
|
||||
};
|
||||
return $uibModal.open(options);
|
||||
return $uibModal.open(options).result;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@@ -38,6 +38,12 @@
|
||||
$provide.constant('horizon.dashboard.admin.ironic.validUuidPattern',
|
||||
'^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$'); // eslint-disable-line max-len
|
||||
|
||||
$provide.constant('horizon.dashboard.admin.ironic.validMacAddressPattern',
|
||||
'^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}$'); // eslint-disable-line max-len
|
||||
|
||||
$provide.constant('horizon.dashboard.admin.ironic.validDatapathIdPattern',
|
||||
'^[0-9A-Fa-f]{16}$'); // eslint-disable-line max-len
|
||||
|
||||
var path = $windowProvider.$get().STATIC_URL + 'dashboard/admin/ironic/';
|
||||
$provide.constant('horizon.dashboard.admin.ironic.basePath', path);
|
||||
|
||||
|
@@ -83,6 +83,10 @@
|
||||
ctrl.deletePort = deletePort;
|
||||
ctrl.refresh = refresh;
|
||||
|
||||
$scope.emptyObject = function(obj) {
|
||||
return angular.isUndefined(obj) || Object.keys(obj).length === 0;
|
||||
};
|
||||
|
||||
var editNodeHandler =
|
||||
$rootScope.$on(ironicEvents.EDIT_NODE_SUCCESS,
|
||||
function() {
|
||||
|
@@ -52,7 +52,7 @@
|
||||
MAC Address
|
||||
</th>
|
||||
<th translate class="rsp-p2" style="width:100%;">
|
||||
Extra
|
||||
Properties
|
||||
</th>
|
||||
<th translate class="actions_column">
|
||||
Actions
|
||||
@@ -69,20 +69,25 @@
|
||||
{$ port.address $}
|
||||
</td>
|
||||
<td>
|
||||
<dl class="dl-horizontal">
|
||||
<dt style="width:auto;" ng-repeat-start="(id, value) in port.extra">
|
||||
{$ id $}
|
||||
</dt>
|
||||
<dd ng-if="id === 'vif_port_id'">
|
||||
<a href="/dashboard/admin/networks/ports/{$ value $}/detail">
|
||||
{$ value $}
|
||||
</a>
|
||||
</dd>
|
||||
<dd ng-if="id !== 'vif_port_id'">
|
||||
{$ value $}
|
||||
</dd>
|
||||
<p ng-repeat-end></p>
|
||||
</dl>
|
||||
<ul style="list-style:none;padding-left:0;">
|
||||
<li><strong>pxe_enabled</strong>: {$ port.pxe_enabled $}</li>
|
||||
<li ng-repeat="propertyObject in
|
||||
['local_link_connection',
|
||||
'extra']"
|
||||
ng-if="!emptyObject(port[propertyObject])">
|
||||
<strong>{$ propertyObject $}</strong>:
|
||||
<ul style="list-style:none;padding-left:10px;">
|
||||
<li ng-repeat="(id, value) in port[propertyObject]">
|
||||
<strong>{$ id $}</strong>:
|
||||
<span ng-switch="id">
|
||||
<a ng-switch-when="vif_port_id"
|
||||
href="/dashboard/admin/networks/ports/{$ value $}/detail">{$ value $}</a>
|
||||
<span ng-switch-default>{$ value $}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td class="actions_column">
|
||||
<action-list>
|
||||
|
Reference in New Issue
Block a user