diff --git a/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.controller.js b/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.controller.js
index 9e8d1076..582655e0 100644
--- a/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.controller.js
+++ b/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.controller.js
@@ -27,95 +27,44 @@
'$uibModalInstance',
'horizon.dashboard.admin.ironic.validMacAddressPattern',
'horizon.dashboard.admin.ironic.validDatapathIdPattern',
+ 'horizon.dashboard.admin.ironic.form-field.service',
'ctrl'
];
- /**
- * @description 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 += '
';
- }
- text += this.info;
- }
- return text;
- };
- }
-
/**
* @description Utility class used to manage local-link-connection
* form fields.
*
+ * @param {string} formFieldService - Provider service for creating
+ * form fields.
* @param {string} validMacAddressPattern - Regular expression
* pattern used to test for valid mac addresses.
* @param {string} validDatapathIdPattern - Regular expression
* pattern used to test for valid datapath ids.
* @return {void}
*/
- function LocalLinkConnectionMgr(validMacAddressPattern,
+ function LocalLinkConnectionMgr(formFieldService,
+ validMacAddressPattern,
validDatapathIdPattern) {
- this.port_id = new Field({});
+ var mgr = this;
- this.switch_id = new Field({
- desc: gettext("MAC address or OpenFlow datapath ID"),
- pattern: validMacAddressPattern + '|' + validDatapathIdPattern});
+ mgr.port_id = new formFieldService.FormField(
+ {id: 'port_id', title: 'port_id'});
- this.switch_info = new Field({});
+ mgr.switch_id = new formFieldService.FormField(
+ {id: 'switch_id',
+ title: 'switch_id',
+ desc: gettext("MAC address or OpenFlow datapath ID"),
+ pattern: new RegExp(validMacAddressPattern + '|' +
+ validDatapathIdPattern)});
- this.fields = {
- port_id: this.port_id,
- switch_id: this.switch_id,
- switch_info: this.switch_info
+ mgr.switch_info = new formFieldService.FormField(
+ {id: 'switch_info', title: 'switch_info'});
+
+ mgr.fields = {
+ port_id: mgr.port_id,
+ switch_id: mgr.switch_id,
+ switch_info: mgr.switch_info
};
/**
@@ -123,13 +72,17 @@
*
* @return {void}
*/
- this.update = function() {
- var required = this.port_id.hasValue() || this.switch_id.hasValue();
-
- this.port_id.required = required;
- this.switch_id.required = required;
+ mgr.update = function() {
+ var required = mgr.port_id.hasValue() || mgr.switch_id.hasValue();
+ mgr.port_id.required = required;
+ mgr.switch_id.required = required;
};
+ // Add form field value change handlers
+ angular.forEach(mgr.fields, function(field) {
+ field.change = mgr.update;
+ });
+
/**
* Generate an attribute object that conforms to the format
* required for port creation using the Ironic client
@@ -138,33 +91,45 @@
* A value of null is returned if the local-link-connection
* information is incomplete.
*/
- this.toPortAttr = function() {
+ mgr.toPortAttr = function() {
var attr = null;
- if (this.port_id.hasValue() &&
- this.switch_id.hasValue()) {
+ if (mgr.port_id.hasValue() &&
+ mgr.switch_id.hasValue()) {
attr = {};
- attr.port_id = this.port_id.value;
- attr.switch_id = this.switch_id.value;
+ attr.port_id = mgr.port_id.value;
+ attr.switch_id = mgr.switch_id.value;
- if (this.switch_info.hasValue()) {
- attr.switch_info = this.switch_info.value;
+ if (mgr.switch_info.hasValue()) {
+ attr.switch_info = mgr.switch_info.value;
}
}
return attr;
};
/**
- * dis/enable the local-link-connection form fields
+ * @description Set values of 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
+ * @param {object} values - Dictionary of values indexed by
+ * property-name
* @return {void}
*/
- this.setDisabled = function(disabled, reason) {
- angular.forEach(this.fields, function(field) {
- field.disabled = disabled;
- field.info = reason;
+ mgr.setValues = function(values) {
+ angular.forEach(mgr.fields, function(field, propertyName) {
+ if (angular.isDefined(values[propertyName])) {
+ field.value = values[propertyName];
+ }
+ });
+ };
+
+ /**
+ * @description Disable the local-link-connection form fields.
+ *
+ * @param {string} reason - Optional reason for disabling fields.
+ * @return {void}
+ */
+ mgr.disable = function(reason) {
+ angular.forEach(mgr.fields, function(item) {
+ item.disable(reason);
});
};
}
@@ -172,17 +137,35 @@
function BasePortController($uibModalInstance,
validMacAddressPattern,
validDatapathIdPattern,
+ formFieldService,
ctrl) {
ctrl.port = {
- address: null,
extra: {}
};
- ctrl.pxeEnabled = new Field({value: 'True'});
+ ctrl.address = new formFieldService.FormField({
+ id: "macAddress",
+ title: gettext("MAC address"),
+ desc: gettext("MAC address for this port. Required."),
+ pattern: new RegExp(validMacAddressPattern),
+ value: null,
+ required: true,
+ autoFocus: true
+ });
+
+ ctrl.pxeEnabled = new formFieldService.FormField({
+ type: "radio",
+ id: "pxeEnabled",
+ title: gettext("PXE enabled"),
+ desc: gettext(
+ "Indicates whether this port should be used when PXE booting this node"),
+ options: ['True', 'False'],
+ value: 'True'});
// Object used to manage local-link-connection form fields
ctrl.localLinkConnection =
- new LocalLinkConnectionMgr(validMacAddressPattern,
+ new LocalLinkConnectionMgr(formFieldService,
+ validMacAddressPattern,
validDatapathIdPattern);
/**
diff --git a/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.html b/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.html
index a6754e62..594d942a 100644
--- a/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.html
+++ b/ironic_ui/static/dashboard/admin/ironic/base-port/base-port.html
@@ -10,50 +10,8 @@
diff --git a/ironic_ui/static/dashboard/admin/ironic/create-port/create-port.controller.js b/ironic_ui/static/dashboard/admin/ironic/create-port/create-port.controller.js
index 28c9e4d2..0706cff3 100644
--- a/ironic_ui/static/dashboard/admin/ironic/create-port/create-port.controller.js
+++ b/ironic_ui/static/dashboard/admin/ironic/create-port/create-port.controller.js
@@ -56,6 +56,8 @@
var port = angular.copy(ctrl.port);
port.node_uuid = node.id;
+ port.address = ctrl.address.value;
+
var attr = ctrl.localLinkConnection.toPortAttr();
if (attr) {
port.local_link_connection = attr;
diff --git a/ironic_ui/static/dashboard/admin/ironic/edit-port/edit-port.controller.js b/ironic_ui/static/dashboard/admin/ironic/edit-port/edit-port.controller.js
index 4b1caa73..17076793 100644
--- a/ironic_ui/static/dashboard/admin/ironic/edit-port/edit-port.controller.js
+++ b/ironic_ui/static/dashboard/admin/ironic/edit-port/edit-port.controller.js
@@ -62,26 +62,22 @@
node.provision_state === "manageable"));
// Initialize form fields
- ctrl.port.address = port.address;
+ ctrl.address.value = port.address;
+ if ((node.provision_state === "active" || node.instance_uuid) &&
+ !node.maintenance) {
+ ctrl.address.disable();
+ }
ctrl.pxeEnabled.value = port.pxe_enabled ? 'True' : 'False';
if (cannotEditConnectivityAttr) {
- ctrl.pxeEnabled.disabled = true;
- ctrl.pxeEnabled.info = UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG;
+ ctrl.pxeEnabled.disable(UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG);
}
- angular.forEach(
- ['port_id', 'switch_id', 'switch_info'],
- function(prop) {
- if (angular.isDefined(port.local_link_connection[prop])) {
- ctrl.localLinkConnection[prop].value =
- port.local_link_connection[prop];
- }
- });
+ ctrl.localLinkConnection.setValues(
+ port.local_link_connection);
if (cannotEditConnectivityAttr) {
- ctrl.localLinkConnection.setDisabled(
- true,
+ ctrl.localLinkConnection.disable(
UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG);
}
@@ -97,7 +93,7 @@
$log.info("Updating port " + JSON.stringify(port));
- patcher.buildPatch(port.address, ctrl.port.address, "/address");
+ patcher.buildPatch(port.address, ctrl.address.value, "/address");
patcher.buildPatch(port.pxe_enabled ? 'True' : 'False',
ctrl.pxeEnabled.value,
"/pxe_enabled");
diff --git a/ironic_ui/static/dashboard/admin/ironic/form-field.directive.js b/ironic_ui/static/dashboard/admin/ironic/form-field.directive.js
new file mode 100644
index 00000000..d2c70c88
--- /dev/null
+++ b/ironic_ui/static/dashboard/admin/ironic/form-field.directive.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 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';
+
+ angular
+ .module('horizon.dashboard.admin.ironic')
+ .directive('formField', FormField);
+
+ FormField.$inject = [
+ '$timeout',
+ '$compile',
+ 'horizon.dashboard.admin.ironic.basePath'
+ ];
+
+ function FormField($timeout, $compile, basePath) {
+ return {
+ restrict: 'E',
+ scope: {
+ field: '=',
+ form: '='
+ },
+ templateUrl: basePath + '/form-field.html',
+ link: function(scope, element) {
+ // Process the auto-focus attribute
+ if (scope.field.autoFocus) {
+ // Need to defer processing until the DOM is fully instantiated
+ $timeout(function() {
+ var inputs = element.find('input');
+ if (inputs[0]) {
+ inputs.attr('auto-focus', '');
+ $compile(element.contents())(scope);
+ }
+ });
+ }
+ }
+ };
+ }
+})();
diff --git a/ironic_ui/static/dashboard/admin/ironic/form-field.html b/ironic_ui/static/dashboard/admin/ironic/form-field.html
new file mode 100644
index 00000000..6a8c8eec
--- /dev/null
+++ b/ironic_ui/static/dashboard/admin/ironic/form-field.html
@@ -0,0 +1,47 @@
+
diff --git a/ironic_ui/static/dashboard/admin/ironic/form-field.service.js b/ironic_ui/static/dashboard/admin/ironic/form-field.service.js
new file mode 100644
index 00000000..4d406eeb
--- /dev/null
+++ b/ironic_ui/static/dashboard/admin/ironic/form-field.service.js
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 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';
+
+ angular
+ .module('horizon.dashboard.admin.ironic')
+ .factory('horizon.dashboard.admin.ironic.form-field.service',
+ formFieldService);
+
+ function formFieldService() {
+ var service = {
+ FormField: FormField
+ };
+
+ /**
+ * @description Utility class for managing form fields.
+ * Used is association with the form-field directive.
+ *
+ * @param {object} args - Base properties are:
+ * type [string] - Field type. One of: 'input', 'radio', 'select'
+ * id [string] - id/name of the DOM value element
+ * title [string] - Label used to identify the field to the user
+ * options - type == radio [array]:
+ * List of options for a radio field
+ * type == select [string]:
+ * String expression that is passed to ng-options
+ * value - Initial value of the field
+ * required [boolean] - Does the field require a value
+ * desc [string] - Field description
+ * pattern [RegExp] - Regular expression pattern used to match
+ * valid input values
+ * disabled [boolean] - Is the field disabled
+ * info [string] - Additional information about the current state of
+ * the field. It will be displayed in a tooltip
+ * associated with the field.
+ * autoFocus [boolean] - True if the focus should be set to this field. Only
+ * applies to fields of type input.
+ * change [string] - Expression to be evaluated when the value of this
+ * field changes. Only applies to fields of type input.
+ *
+ * @return {void}
+ */
+ function FormField(args) {
+ var field = this;
+ field.type = 'input';
+ field.id = undefined;
+ field.title = undefined;
+ field.options = undefined;
+ field.value = undefined;
+ field.required = false;
+ field.desc = undefined;
+ field.pattern = undefined;
+ field.disabled = false;
+ field.info = undefined;
+ field.autoFocus = false;
+ field.change = undefined;
+
+ angular.forEach(args, function(value, arg) {
+ field[arg] = value;
+ });
+
+ /**
+ * @description 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 !== '';
+ };
+
+ /**
+ * @description Test whether the field has help-text.
+ *
+ * @return {boolean} Return true if the field has help text.
+ */
+ this.hasHelpText = function() {
+ return angular.isDefined(this.desc) || angular.isDefined(this.info);
+ };
+
+ /**
+ * @description 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 += '
';
+ }
+ text += this.info;
+ }
+ return text;
+ };
+
+ /**
+ * @description Disable this field.
+ *
+ * @param {string} reason - Optional reason for disabling this field.
+ * @return {void}
+ */
+ this.disable = function(reason) {
+ this.disabled = true;
+ if (angular.isDefined(reason)) {
+ this.info = reason;
+ }
+ };
+ }
+
+ return service;
+ }
+})();
diff --git a/ironic_ui/static/dashboard/admin/ironic/form-field.service.spec.js b/ironic_ui/static/dashboard/admin/ironic/form-field.service.spec.js
new file mode 100644
index 00000000..1f31f891
--- /dev/null
+++ b/ironic_ui/static/dashboard/admin/ironic/form-field.service.spec.js
@@ -0,0 +1,120 @@
+/**
+ * Copyright 2017 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";
+
+ /**
+ * @description Unit tests for the form-field service
+ */
+
+ describe(
+ 'horizon.dashboard.admin.ironic.form-field.service',
+
+ function() {
+ var formFieldService;
+
+ beforeEach(module('horizon.dashboard.admin.ironic'));
+
+ beforeEach(inject(function($injector) {
+ formFieldService =
+ $injector.get('horizon.dashboard.admin.ironic.form-field.service');
+ }));
+
+ it('defines the form-field service', function() {
+ expect(formFieldService).toBeDefined();
+ });
+
+ it('FormField - default construction', function() {
+ var field = new formFieldService.FormField({});
+
+ expect(field.type).toEqual('input');
+ expect(field.id).toBeUndefined();
+ expect(field.title).toBeUndefined();
+ expect(field.options).toBeUndefined();
+ expect(field.value).toBeUndefined();
+ expect(field.required).toBe(false);
+ expect(field.desc).toBeUndefined();
+ expect(field.pattern).toBeUndefined();
+ expect(field.disabled).toBe(false);
+ expect(field.info).toBeUndefined();
+ expect(field.autoFocus).toBe(false);
+ expect(field.change).toBeUndefined();
+ expect(formFieldService).toBeDefined();
+ });
+
+ it('FormField - local parameters', function() {
+ var title = "title";
+ var field = new formFieldService.FormField({
+ title: title
+ });
+
+ expect(field.title).toBe(title);
+ });
+
+ it('hasValue', function() {
+ var field = new formFieldService.FormField({});
+ expect(field.hasValue()).toBe(false);
+
+ field.value = '';
+ expect(field.hasValue()).toBe(false);
+
+ field.value = null;
+ expect(field.hasValue()).toBe(true);
+
+ field.value = 'True';
+ expect(field.hasValue()).toBe(true);
+ });
+
+ it('hasHelpText', function() {
+ var field = new formFieldService.FormField({});
+ expect(field.hasHelpText()).toBe(false);
+ expect(field.getHelpText()).toBe('');
+ });
+
+ it('hasHelpText/getHelpText - desc', function() {
+ var field = new formFieldService.FormField({
+ desc: 'desc'
+ });
+ expect(field.hasHelpText()).toBe(true);
+ expect(field.getHelpText()).toBe('desc');
+ });
+
+ it('hasHelpText/getHelpText - info', function() {
+ var field = new formFieldService.FormField({
+ info: 'info'
+ });
+ expect(field.hasHelpText()).toBe(true);
+ expect(field.getHelpText()).toBe('info');
+ });
+
+ it('getHelpText - desc/info', function() {
+ var field = new formFieldService.FormField({
+ desc: 'desc',
+ info: 'info'
+ });
+ expect(field.hasHelpText()).toBe(true);
+ expect(field.getHelpText()).toBe('desc
info');
+ });
+
+ it('disable', function() {
+ var field = new formFieldService.FormField({});
+ expect(field.disabled).toBe(false);
+ field.disable();
+ expect(field.disabled).toBe(true);
+ });
+ });
+})();