diff --git a/ironic_ui/api/ironic.py b/ironic_ui/api/ironic.py index 83250c0e..8a1a0ba4 100755 --- a/ironic_ui/api/ironic.py +++ b/ironic_ui/api/ironic.py @@ -357,3 +357,15 @@ def portgroup_delete(request, portgroup_id): http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.portgroup.html#ironicclient.v1.portgroup.PortgroupManager.delete """ return ironicclient(request).portgroup.delete(portgroup_id) + + +def portgroup_get_ports(request, portgroup_id): + """Get the ports associated with a specified portgroup. + + :param request: HTTP request. + :param portgroup_id: The UUID or name of the portgroup. + :return: List of ports. + + http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.portgroup.html#ironicclient.v1.portgroup.PortgroupManager.list_ports + """ + return ironicclient(request).portgroup.list_ports(portgroup_id) diff --git a/ironic_ui/api/ironic_rest_api.py b/ironic_ui/api/ironic_rest_api.py index 7e20cc6b..c31669af 100755 --- a/ironic_ui/api/ironic_rest_api.py +++ b/ironic_ui/api/ironic_rest_api.py @@ -379,3 +379,23 @@ class Portgroups(generic.View): """ return ironic.portgroup_delete(request, request.DATA.get('portgroup_id')) + + +@urls.register +class PortgroupPorts(generic.View): + + url_regex = r'ironic/portgroups/(?P{})/ports$'. \ + format(LOGICAL_NAME_PATTERN) + + @rest_utils.ajax() + def get(self, request, portgroup_id): + """Get the ports for a specified portgroup. + + :param request: HTTP request. + :param node_id: UUID or name of portgroup. + :return: List of port objects. + """ + ports = ironic.portgroup_get_ports(request, portgroup_id) + return { + 'ports': [i.to_dict() for i in ports] + } 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 582655e0..d62aca1b 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 @@ -28,7 +28,9 @@ 'horizon.dashboard.admin.ironic.validMacAddressPattern', 'horizon.dashboard.admin.ironic.validDatapathIdPattern', 'horizon.dashboard.admin.ironic.form-field.service', - 'ctrl' + 'horizon.app.core.openstack-service-api.ironic', + 'ctrl', + 'node' ]; /** @@ -138,9 +140,12 @@ validMacAddressPattern, validDatapathIdPattern, formFieldService, - ctrl) { + ironic, + ctrl, + node) { ctrl.port = { - extra: {} + extra: {}, + node_uuid: node.uuid }; ctrl.address = new formFieldService.FormField({ @@ -162,12 +167,34 @@ options: ['True', 'False'], value: 'True'}); + ctrl.portgroup_uuid = new formFieldService.FormField({ + type: "select", + id: "portgroup-uuid", + title: gettext("Portgroup"), + desc: gettext("Portgroup that this port belongs to"), + portgroups: [], + options: "portgroup.uuid as portgroup.name ? portgroup.name : portgroup.uuid for portgroup in field.portgroups", // eslint-disable-line max-len + value: null}); + // Object used to manage local-link-connection form fields ctrl.localLinkConnection = new LocalLinkConnectionMgr(formFieldService, validMacAddressPattern, validDatapathIdPattern); + ironic.getPortgroups(node.uuid).then(function(portgroups) { + var field = ctrl.portgroup_uuid; + + if (portgroups.length > 0) { + field.portgroups.push({uuid: null, name: gettext("Select a portgroup")}); + } + field.portgroups = field.portgroups.concat(portgroups); + + if (portgroups.length === 0) { + field.disable(); + } + }); + /** * Cancel the modal * 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 594d942a..7958e680 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 @@ -12,6 +12,7 @@
+
- + data-original-title="{$ field.desc $}"> + + +
@@ -24,8 +33,8 @@ ng-required="field.required" ng-disabled="field.disabled" ng-pattern="field.pattern" - ng-change="field.change()" - placeholder="{$ field.getHelpText() $}"/> + ng-change="{$ field.change $}" + placeholder="{$ field.desc $}"/>
diff --git a/ironic_ui/static/dashboard/admin/ironic/form-field.service.js b/ironic_ui/static/dashboard/admin/ironic/form-field.service.js index 4d406eeb..bc07ba62 100644 --- a/ironic_ui/static/dashboard/admin/ironic/form-field.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/form-field.service.js @@ -84,31 +84,6 @@ 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. * 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 index 1f31f891..ced898f1 100644 --- a/ironic_ui/static/dashboard/admin/ironic/form-field.service.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/form-field.service.spec.js @@ -53,7 +53,8 @@ expect(field.info).toBeUndefined(); expect(field.autoFocus).toBe(false); expect(field.change).toBeUndefined(); - expect(formFieldService).toBeDefined(); + expect(field.hasValue).toBeDefined(); + expect(field.disable).toBeDefined(); }); it('FormField - local parameters', function() { @@ -79,37 +80,6 @@ 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); diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js b/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js index 0444b72c..942f5e32 100644 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.backend-mock.service.js @@ -655,7 +655,27 @@ } return [status, ""]; }); - } + + // Get portgroup ports + $httpBackend.whenGET(/\/api\/ironic\/portgroups\/([^\/]+)\/ports$/, + undefined, + ['portgroupId']) + .respond(function(method, url, data, headers, params) { + var ports = []; + var status = responseCode.RESOURCE_NOT_FOUND; + if (angular.isDefined(portgroups[params.portgroupId])) { + var portgroup = portgroups[params.portgroupId]; + var node = nodes[portgroup.node_uuid]; + angular.forEach(node.ports, function(port) { + if (port.portgroup_uuid === portgroup.uuid) { + ports.push(port); + } + }); + status = responseCode.SUCCESS; + } + return [status, {ports: ports}]; + }); + } // init() /** * @description Get the list of supported drivers @@ -694,6 +714,5 @@ $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); } - } - -}()); + } // ironicBackendMockService() +})(); diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js index dab58e64..b9bacde6 100755 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js @@ -63,7 +63,8 @@ validateNode: validateNode, createPortgroup: createPortgroup, getPortgroups: getPortgroups, - deletePortgroup: deletePortgroup + deletePortgroup: deletePortgroup, + getPortgroupPorts: getPortgroupPorts }; return service; @@ -644,5 +645,29 @@ return $q.reject(msg); }); } + + /** + * @description Get the ports associated with a specified portgroup. + * + * http://developer.openstack.org/api-ref/baremetal/#list-ports-by-portgroup + * + * @param {string} portgroupId – UUID or name of the portgroup. + * @return {promise} Promise containing a list of ports. + */ + function getPortgroupPorts(portgroupId) { + return apiService.get( + '/api/ironic/portgroups/' + portgroupId + '/ports') + .then(function(response) { + return response.data.ports; // List of ports + }) + .catch(function(response) { + var msg = interpolate( + gettext('Unable to retrieve portgroup ports: %s'), + [response.data], + false); + toastService.add('error', msg); + return $q.reject(msg); + }); + } } }()); diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js b/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js index 16d7f8ec..8cec8a8a 100644 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js @@ -28,6 +28,7 @@ 'getDriverProperties', 'getNode', 'getNodes', + 'getPortgroupPorts', 'getPortgroups', 'getPortsWithNode', 'getBootDevice', @@ -576,6 +577,24 @@ ironicBackendMockService.flush(); }); + + it('getPortgroupPorts', function() { + createNode({driver: defaultDriver}) + .then(function(node) { + return ironicAPI.createPortgroup({node_uuid: node.uuid}); + }) + .then(function(portgroup) { + expect(portgroup).toBeDefined(); + expect(portgroup) + .toEqual(ironicBackendMockService.getPortgroup(portgroup.uuid)); + ironicAPI.getPortgroupPorts(portgroup.uuid).then(function(ports) { + expect(ports).toEqual([]); + }); + }) + .catch(failTest); + + ironicBackendMockService.flush(); + }); }); }); })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js index 337591fb..28ebf3b1 100755 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js @@ -182,6 +182,12 @@ function retrievePortgroups() { ironic.getPortgroups(ctrl.node.uuid).then(function(portgroups) { ctrl.portgroupsSrc = portgroups; + angular.forEach(portgroups, function(portgroup) { + portgroup.ports = []; + ironic.getPortgroupPorts(portgroup.uuid).then(function(ports) { + portgroup.ports = ports; + }); + }); }); } diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html b/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html index 287c19cc..0950bc0e 100644 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/sections/configuration.html @@ -168,6 +168,9 @@ Name + + Ports + Actions @@ -194,6 +197,9 @@ {$ portgroup.name | noValue $} + + {$ portgroup.ports.length | noValue $} + -
  • +
  • 0 || + ctrl.deletePortgroups([portgroup]); $event.stopPropagation(); $event.preventDefault()"> diff --git a/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js b/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js index d9c0f154..08e605e2 100644 --- a/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/update-patch.service.js @@ -140,7 +140,11 @@ if (isProperty(source) && isProperty(target)) { if (source !== target) { - patcher.patch.push({op: "replace", path: path, value: target}); + if (target === null) { + patcher.patch.push({op: "remove", path: path}); + } else { + patcher.patch.push({op: "replace", path: path, value: target}); + } } } else if (isCollection(source) && isCollection(target)) { angular.forEach(source, function(sourceItem, sourceItemName) {