Merge "Add support for additional port attributes"

This commit is contained in:
Jenkins
2017-02-06 14:05:29 +00:00
committed by Gerrit Code Review
8 changed files with 240 additions and 25 deletions

View File

@@ -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

View File

@@ -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
*

View File

@@ -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 $}

View File

@@ -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);
});
};

View File

@@ -43,7 +43,7 @@
},
templateUrl: basePath + '/base-port/base-port.html'
};
return $uibModal.open(options);
return $uibModal.open(options).result;
}
}
})();

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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>