diff --git a/app/controllers/cloud_subnet_controller.rb b/app/controllers/cloud_subnet_controller.rb index 4fdc4edfa6a0..5d4262239f5c 100644 --- a/app/controllers/cloud_subnet_controller.rb +++ b/app/controllers/cloud_subnet_controller.rb @@ -35,73 +35,10 @@ def button end def new - assert_privileges("cloud_subnet_new") - assert_privileges("ems_network_show_list") - assert_privileges("cloud_tenant_show_list") - assert_privileges("cloud_network_show_list") - @in_a_form = true drop_breadcrumb(:name => _("Add New Subnet"), :url => "/cloud_subnet/new") end - def create - assert_privileges("cloud_subnet_new") - case params[:button] - when "cancel" - javascript_redirect(:action => 'show_list', - :flash_msg => _("Creation of a Cloud Subnet was cancelled by the user")) - - when "add" - @subnet = CloudSubnet.new - begin - options = new_form_params - ems = ExtManagementSystem.find(options[:ems_id]) - if CloudSubnet.class_by_ems(ems).supports_create? - options.delete(:ems_id) - task_id = ems.create_cloud_subnet_queue(session[:userid], options) - - if task_id.kind_of?(Integer) - initiate_wait_for_task(:task_id => task_id, :action => "create_finished") - else - javascript_flash( - :text => _("Cloud Subnet creation: Task start failed"), - :severity => :error, - :spinner_off => true - ) - end - else - @in_a_form = true - add_flash(_(CloudSubnet.unsupported_reason(:create)), :error) - drop_breadcrumb(:name => _("Add new Cloud Subnet "), :url => "/subnet/new") - javascript_flash - end - rescue ArgumentError => err - javascript_flash( - :text => _("Parameter Error: %{error_message}") % {:error_message => err.message}, - :severity => :error, - :spinner_off => true - ) - end - end - end - - def create_finished - task_id = session[:async][:params][:task_id] - subnet_name = session[:async][:params][:name] - task = MiqTask.find(task_id) - if MiqTask.status_ok?(task.status) - add_flash(_("Cloud Subnet \"%{name}\" created") % {:name => subnet_name}) - else - add_flash(_("Unable to create Cloud Subnet: %{details}") % - { :name => subnet_name, :details => task.message }, :error) - end - - @breadcrumbs&.pop - session[:edit] = nil - flash_to_session - javascript_redirect(:action => "show_list") - end - def delete_subnets assert_privileges("cloud_subnet_delete") subnets = find_records_with_rbac(CloudSubnet, checked_or_params) @@ -138,7 +75,6 @@ def delete_subnets def edit params[:id] = checked_item_id if params[:id].blank? - assert_privileges("cloud_subnet_edit") @subnet = find_record_with_rbac(CloudSubnet, params[:id]) @in_a_form = true drop_breadcrumb( @@ -147,56 +83,6 @@ def edit ) end - def update - assert_privileges("cloud_subnet_edit") - @subnet = find_record_with_rbac(CloudSubnet, params[:id]) - case params[:button] - when "cancel" - flash_and_redirect(_("Edit of Subnet \"%{name}\" was cancelled by the user") % {:name => @subnet.name}) - - when "save" - if @subnet.supports_create? - begin - options = changed_form_params - task_id = @subnet.update_cloud_subnet_queue(session[:userid], options) - - if task_id.kind_of?(Integer) - initiate_wait_for_task(:task_id => task_id, :action => "update_finished") - else - javascript_flash( - :text => _("Cloud Subnet update failed: Task start failed"), - :severity => :error, - :spinner_off => true - ) - end - rescue ArgumentError => err - javascript_flash( - :text => _("Parameter Error: %{error_message}") % {:error_message => err.message}, - :severity => :error, - :spinner_off => true - ) - end - else - add_flash(_("Couldn't initiate update of Cloud Subnet \"%{name}\": %{details}") % { - :name => @subnet.name, - :details => @subnet.unsupported_reason(:update) - }, :error) - end - end - end - - def update_finished - task_id = session[:async][:params][:task_id] - subnet_name = session[:async][:params][:name] - task = MiqTask.find(task_id) - if MiqTask.status_ok?(task.status) - flash_and_redirect(_("Cloud Subnet \"%{name}\" updated") % {:name => subnet_name}) - else - flash_and_redirect(_("Unable to update Cloud Subnet \"%{name}\": %{details}") % {:name => subnet_name, - :details => task.message}, :error) - end - end - def download_data assert_privileges('cloud_subnet_view') super @@ -222,85 +108,6 @@ def switch_to_bol(option) end end - def parse_allocation_pools(option) - return [] unless option - - option.lines.map do |pool| - start_addr, end_addr, extra_entry = pool.split(",") - raise ArgumentError, _("Too few addresses in line. Proper format is start_ip_address,end_ip_address (one Allocation Pool per line)") unless end_addr - raise ArgumentError, _("Too many addresses in line. Proper format is start_ip_address,end_ip_address (one Allocation Pool per line)") if extra_entry - - {"start" => start_addr.strip, "end" => end_addr.strip} - end - end - - def parse_host_routes(option) - return [] unless option - - option.lines.map do |route| - dest_addr, nexthop_addr, extra_entry = route.split(",") - raise ArgumentError, _("Too few entries in line. Proper format is destination_cidr,nexthop (one Host Route per line)") unless nexthop_addr - raise ArgumentError, _("Too many entries in line. Proper format is destination_cidr,nexthop (one Host Route per line)") if extra_entry - - {"destination" => dest_addr.strip, "nexthop" => nexthop_addr.strip} - end - end - - def parse_dns_nameservers(option) - return [] unless option - - option.lines.map do |nameserver| - one_nameserver, extra_entry = nameserver.strip.split(/\s+|,/) - raise ArgumentError, _("One DNS Name Server per line is required.") if !one_nameserver || extra_entry - - one_nameserver - end - end - - def changed_form_params - # Allowed fields for update: name, enable_dhcp, dns_nameservers, allocation_pools, host_routes, gateway_ip - options = {} - options[:name] = params[:name] unless @subnet.name == params[:name] - - # Provider to automatically assign gateway address unless provided - unless @subnet.gateway == params[:gateway] - options[:gateway_ip] = params[:gateway].presence - end - - unless @subnet.dhcp_enabled == switch_to_bol(params[:dhcp_enabled]) - options[:enable_dhcp] = switch_to_bol(params[:dhcp_enabled]) - end - unless @subnet.allocation_pools == (pools = parse_allocation_pools(params[:allocation_pools])) || (@subnet.allocation_pools.blank? && pools.blank?) - options[:allocation_pools] = pools - end - unless @subnet.host_routes == (routes = parse_host_routes(params[:host_routes])) || (@subnet.host_routes.blank? && routes.blank?) - options[:host_routes] = routes - end - unless @subnet.dns_nameservers == (nameservers = parse_dns_nameservers(params[:dns_nameservers])) || (@subnet.dns_nameservers.blank? && nameservers.blank?) - options[:dns_nameservers] = nameservers - end - options - end - - def new_form_params - params[:network_protocol] ||= "ipv4" - params[:dhcp_enabled] ||= false - options = {} - copy_params_if_present(options, params, %i[name ems_id cidr network_id availability_zone_id ipv6_router_advertisement_mode ipv6_address_mode network_group_id parent_cloud_subnet_id]) - # Provider to automatically assign gateway address unless provided - if params[:gateway] - options[:gateway_ip] = params[:gateway].presence - end - options[:ip_version] = /4/.match?(params[:network_protocol]) ? 4 : 6 - options[:cloud_tenant] = find_record_with_rbac(CloudTenant, params[:cloud_tenant][:id]) if params.fetch_path(:cloud_tenant, :id) - options[:enable_dhcp] = params[:dhcp_enabled] - # TODO: Add dns_nameservers, allocation_pools, host_routes - options[:allocation_pools] = parse_allocation_pools(params[:allocation_pools]) if params[:allocation_pools].present? - options[:dns_nameservers] = parse_dns_nameservers(params[:dns_nameservers]) if params[:dns_nameservers].present? - options[:host_routes] = parse_host_routes(params[:host_routes]) if params[:host_routes].present? - options - end - # dispatches operations to multiple subnets def process_cloud_subnets(subnets, operation) return if subnets.empty? diff --git a/app/javascript/components/subnet-form/index.jsx b/app/javascript/components/subnet-form/index.jsx new file mode 100644 index 000000000000..ff742511313e --- /dev/null +++ b/app/javascript/components/subnet-form/index.jsx @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; + +import MiqFormRenderer from '@@ddf'; +import createSchema from './subnet-form.schema'; +import miqRedirectBack from '../../helpers/miq-redirect-back'; +import { API } from '../../http_api'; +import { Loading } from 'carbon-components-react'; + +const SubnetForm = ({ recordId }) => { + const [{ initialValues, isLoading, fields }, setState] = useState({ isLoading: !!recordId, fields: [] }); + const submitLabel = !!recordId ? __('Save') : __('Add'); + + const loadSchema = (appendState = {}) => ({ data: { form_schema: { fields } } }) => { + if (!!recordId && appendState.initialValues.type === 'ManageIQ::Providers::Openstack::NetworkManager::CloudSubnet') { + Object.assign(fields[0], {isDisabled: true}); + Object.assign(fields[1], {isDisabled: true}); + Object.assign(fields[4], {isDisabled: true}); + } + setState((state) => ({ + ...state, + ...appendState, + fields, + })); + }; + + useEffect(() => { + if (recordId) { + API.get(`/api/cloud_subnets/${recordId}`).then((initialValues) => { + if(typeof initialValues.cloud_network_id ==="string") { + initialValues.cloud_network_id=Number(initialValues.cloud_network_id); + } + if(typeof initialValues.cloud_tenant_id ==="string") { + initialValues.cloud_tenant_id=Number(initialValues.cloud_tenant_id); + } + API.options(`/api/cloud_subnets?ems_id=${initialValues.ems_id}`).then(loadSchema({ initialValues, isLoading: false })); + }); + } + }, [recordId]); + + const onSubmit = (values) => { + API.get(`/api/providers/${values.ems_id}`).then(({ type }) => { + if (type === 'ManageIQ::Providers::Openstack::NetworkManager') { + if (values.ip_version === undefined) { + values.ip_version = '4'; + } + if (values.dhcp_enabled === undefined) { + values.dhcp_enabled = false; + } + values.enable_dhcp = values.dhcp_enabled; + delete values.dhcp_enabled; + delete Object.assign(values, values.extra_attributes).extra_attributes; + } + + miqSparkleOn(); + const request = recordId ? API.patch(`/api/cloud_subnets/${recordId}`, values) : API.post('/api/cloud_subnets', values); + + request.then(() => { + const message = sprintf(recordId + ? __('Modification of Cloud Subnet %s has been successfully queued') + : __('Add of Cloud Subnet "%s" has been successfully queued.'), + values.name); + + miqRedirectBack(message, 'success', '/cloud_subnet/show_list'); + }).catch(miqSparkleOff); + }); + }; + + const onCancel = () => { + const message = sprintf( + recordId + ? __('Edit of Cloud Subnet "%s" was canceled by the user.') + : __('Creation of new Cloud Subnet was canceled by the user.'), + initialValues && initialValues.name, + ); + miqRedirectBack(message, 'warning', '/cloud_subnet/show_list'); + }; + + if (isLoading) return ; + return !isLoading && ( + + ); +}; + +SubnetForm.propTypes = { + recordId: PropTypes.string, +}; +SubnetForm.defaultProps = { + recordId: undefined, +}; + +export default SubnetForm; diff --git a/app/javascript/components/subnet-form/subnet-form.schema.js b/app/javascript/components/subnet-form/subnet-form.schema.js new file mode 100644 index 000000000000..c15b88db7a83 --- /dev/null +++ b/app/javascript/components/subnet-form/subnet-form.schema.js @@ -0,0 +1,73 @@ +import { componentTypes, validatorTypes } from '@@ddf'; +import { API } from '../../http_api'; + +const emsUrl = '/api/providers?expand=resources&attributes=id,name,supports_cloud_subnet_create&filter[]=supports_cloud_subnet_create=true'; + +const createSchema = (edit, fields = [], loadSchema) => ({ + fields: [ + { + component: componentTypes.SELECT, + name: 'ems_id', + id: 'ems_id', + label: __('Network Manager'), + onChange: (value) => API.options(`/api/cloud_subnets?ems_id=${value}`).then(loadSchema()), + loadOptions: () => API.get(emsUrl).then(({ resources }) => resources.map(({ id, name }) => ({ label: name, value: id }))), + includeEmpty: true, + isDisabled: edit, + isRequired: true, + validate: [{ type: validatorTypes.REQUIRED }], + }, + { + component: componentTypes.TEXT_FIELD, + name: 'name', + id: 'name', + label: __('Name'), + isRequired: true, + validate: [{ type: validatorTypes.REQUIRED }], + condition: { + when: 'ems_id', + isNotEmpty: true, + }, + }, + { + component: componentTypes.TEXT_FIELD, + name: 'cidr', + id: 'cidr', + label: __('CIDR'), + isRequired: true, + isDisabled: edit, + validate: [{ type: validatorTypes.REQUIRED }], // TODO: pattern for validating IPv4/mask or IPv6/mask + condition: { + when: 'ems_id', + isNotEmpty: true, + }, + }, + { + component: componentTypes.FIELD_ARRAY, + name: 'dns_nameservers', + id: 'dns_nameservers', + label: __('DNS Servers'), + noItemsMessage: __('None'), + buttonLabels: { + add: __('Add'), + remove: __('Remove'), + }, + AddButtonProps: { + size: 'small', + }, + RemoveButtonProps: { + size: 'small', + }, + fields: [{ // TODO: pattern for validating IPv4 or IPv6 + component: componentTypes.TEXT_FIELD, + }], + condition: { + when: 'ems_id', + isNotEmpty: true, + }, + }, + ...fields, + ], +}); + +export default createSchema; diff --git a/app/javascript/oldjs/components/cloud_subnet/cloud-subnet-form.js b/app/javascript/oldjs/components/cloud_subnet/cloud-subnet-form.js deleted file mode 100644 index a0c3fcaf99d7..000000000000 --- a/app/javascript/oldjs/components/cloud_subnet/cloud-subnet-form.js +++ /dev/null @@ -1,111 +0,0 @@ -ManageIQ.angular.app.component('cloudSubnetForm', { - bindings: { - cloudSubnetFormId: '@', - }, - controllerAs: 'vm', - controller: cloudSubnetFormController, - templateUrl: '/static/cloud_subnet/cloud-subnet-form.html.haml', -}); - -cloudSubnetFormController.$inject = ['API', 'miqService', '$scope']; - -function cloudSubnetFormController(API, miqService, $scope) { - var vm = this; - - this.$onInit = function() { - vm.afterGet = false; - - vm.cloudSubnetModel = { name: '' }; - vm.networkProtocols = ['ipv4', 'ipv6']; - - vm.formId = vm.cloudSubnetFormId; - vm.model = 'cloudSubnetModel'; - ManageIQ.angular.scope = $scope; - vm.saveable = miqService.saveable; - - vm.newRecord = vm.cloudSubnetFormId === 'new'; - - miqService.sparkleOn(); - if (vm.newRecord) { - vm.cloudSubnetModel.ems_id = ''; - vm.cloudSubnetModel.network_id = ''; - vm.cloudSubnetModel.dhcp_enabled = true; - vm.cloudSubnetModel.network_protocol = 'ipv4'; - API.get("/api/providers?expand=resources&attributes=name&filter[]=type='*NetworkManager'").then(function(data) { - vm.available_ems = data.resources; - vm.afterGet = true; - vm.modelCopy = angular.copy( vm.cloudSubnetModel ); - miqService.sparkleOff(); - }).catch(miqService.handleFailure); - } else { - API.get('/api/cloud_subnets/' + vm.cloudSubnetFormId + '?expand=resources&attributes=ext_management_system.name,cloud_tenant.name,cloud_network.name').then(function(data) { - Object.assign(vm.cloudSubnetModel, _.pick(data, 'name', 'ext_management_system', 'cloud_network', 'cloud_tenant', 'network_protocol', 'cidr', 'dhcp_enabled', 'gateway', 'extra_attributes', 'dns_nameservers')); - vm.cloudSubnetModel.allocation_pools = ""; - vm.cloudSubnetModel.host_routes = ""; - if (vm.cloudSubnetModel.extra_attributes !== undefined) { - if (vm.cloudSubnetModel.extra_attributes.allocation_pools !== undefined) { - vm.cloudSubnetModel.extra_attributes.allocation_pools.forEach(function(val, i) { - if (i > 0) { - vm.cloudSubnetModel.allocation_pools += "\n"; - } - vm.cloudSubnetModel.allocation_pools += val["start"] + "," + val["end"]; - }); - } - if (vm.cloudSubnetModel.extra_attributes.host_routes !== undefined) { - vm.cloudSubnetModel.extra_attributes.host_routes.forEach(function(val, i) { - if (i > 0) { - vm.cloudSubnetModel.host_routes += "\n"; - } - vm.cloudSubnetModel.host_routes += val["destination"] + "," + val["nexthop"]; - }); - } - } - if (vm.cloudSubnetModel.dns_nameservers !== undefined) { - vm.cloudSubnetModel.dns_nameservers = vm.cloudSubnetModel.dns_nameservers.join("\n"); - } else { - vm.cloudSubnetModel.dns_nameservers = ""; - } - vm.afterGet = true; - vm.modelCopy = angular.copy( vm.cloudSubnetModel ); - miqService.sparkleOff(); - }).catch(miqService.handleFailure); - } - }; - - vm.addClicked = function() { - var url = 'create/new?button=add'; - miqService.miqAjaxButton(url, vm.cloudSubnetModel, { complete: false }); - }; - - vm.cancelClicked = function() { - var url = ''; - if (vm.newRecord) { - url = '/cloud_subnet/create/new?button=cancel'; - } else { - url = '/cloud_subnet/update/' + vm.cloudSubnetFormId + '?button=cancel'; - } - miqService.miqAjaxButton(url); - }; - - vm.saveClicked = function() { - var url = '/cloud_subnet/update/' + vm.cloudSubnetFormId + '?button=save'; - miqService.miqAjaxButton(url, _.pick(vm.cloudSubnetModel, 'name', 'dhcp_enabled', 'gateway', 'allocation_pools', 'host_routes', 'dns_nameservers'), { complete: false }); - }; - - vm.resetClicked = function(angularForm) { - vm.cloudSubnetModel = angular.copy( vm.modelCopy ); - angularForm.$setPristine(true); - miqService.miqFlash('warn', __('All changes have been reset')); - }; - - vm.filterNetworkManagerChanged = function(id) { - if (id) { - API.get('/api/cloud_networks?expand=resources&attributes=name,ems_ref&filter[]=ems_id=' + id).then(function(data) { - vm.available_networks = data.resources; - }).catch(miqService.handleFailure); - miqService.getProviderTenants(function(data) { - vm.available_tenants = data.resources; - })(id); - } - }; -} diff --git a/app/javascript/oldjs/components/index.js b/app/javascript/oldjs/components/index.js index a8863be04d57..392ff5131d9a 100644 --- a/app/javascript/oldjs/components/index.js +++ b/app/javascript/oldjs/components/index.js @@ -1,7 +1,6 @@ require('./ae_resolve_options/ae-resolve-options.js'); require('./ansible-raw-stdout.js'); require('./auth-credentials.js'); -require('./cloud_subnet/cloud-subnet-form.js'); require('./cloud_volume_backup/cloud-volume-backup-form.js'); require('./datetime-delay-picker.js'); require('./generic_object/assign-buttons.js'); diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js index 475243331c91..bba022623d49 100644 --- a/app/javascript/packs/component-definitions-common.js +++ b/app/javascript/packs/component-definitions-common.js @@ -46,6 +46,7 @@ import ReportChartWidget from '../components/create-report-chart-form'; import ReportDataTable from '../components/report-data-table'; import RetirementForm from '../components/retirement-form'; import ServiceDialogFromForm from '../components/service-dialog-from-form/service-dialog-from'; +import SubnetForm from '../components/subnet-form'; import EditServiceForm from '../components/edit-service-form'; import SetOwnershipForm from '../components/set-ownership-form'; import ServersDataChart from '../components/provider-dashboard-charts/servers-pie-chart'; @@ -118,6 +119,7 @@ ManageIQ.component.addReact('ServiceDialogFromForm', ServiceDialogFromForm); ManageIQ.component.addReact('EditServiceForm', EditServiceForm); ManageIQ.component.addReact('SetOwnershipForm', SetOwnershipForm); ManageIQ.component.addReact('ServersDataChart', ServersDataChart); +ManageIQ.component.addReact('SubnetForm', SubnetForm); ManageIQ.component.addReact('TableListView', TableListView); ManageIQ.component.addReact('TableListViewWrapper', TableListViewWrapper); ManageIQ.component.addReact('TagGroup', props => ); diff --git a/app/javascript/spec/subnet-form/__snapshots__/subnet-form.spec.js.snap b/app/javascript/spec/subnet-form/__snapshots__/subnet-form.spec.js.snap new file mode 100644 index 000000000000..5a79ae554140 --- /dev/null +++ b/app/javascript/spec/subnet-form/__snapshots__/subnet-form.spec.js.snap @@ -0,0 +1,160 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Subnet form component renders the adding form variant 1`] = ` + +`; + +exports[`Subnet form component renders the editing form variant 1`] = ` + + + + + + Active loading indicator + + + + Active loading indicator + + + + + + + + +`; diff --git a/app/javascript/spec/subnet-form/subnet-form.spec.js b/app/javascript/spec/subnet-form/subnet-form.spec.js new file mode 100644 index 000000000000..b384378107f4 --- /dev/null +++ b/app/javascript/spec/subnet-form/subnet-form.spec.js @@ -0,0 +1,37 @@ +import React from 'react'; +import toJson from 'enzyme-to-json'; +import fetchMock from 'fetch-mock'; +import { shallow } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import SubnetForm from '../../components/subnet-form'; +import { mount } from '../helpers/mountForm'; + +require('../helpers/miqSparkle.js'); +require('../helpers/miqAjaxButton.js'); + +describe('Subnet form component', () => { + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it('renders the adding form variant', () => { + const wrapper = shallow(); + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + it('renders the editing form variant', async(done) => { + fetchMock.get('/api/providers?expand=resources&attributes=id,name,supports_cloud_subnet_create&filter[]=supports_cloud_subnet_create=true', { + resources: [{ label: 'foo', value: 1 }], + }); + fetchMock.getOnce('/api/cloud_subnets/1', { name: 'foo', ems_id: 1 }); + fetchMock.mock('/api/cloud_subnets?ems_id=1', { data: { form_schema: { fields: [] } } }, { method: 'OPTIONS' }); + let wrapper; + await act(async() => { + wrapper = mount(); + }); + expect(fetchMock.called('/api/cloud_subnets/1')).toBe(true); + expect(toJson(wrapper)).toMatchSnapshot(); + done(); + }); +}); diff --git a/app/views/cloud_subnet/edit.html.haml b/app/views/cloud_subnet/edit.html.haml index 885cb265960a..ab4da8504753 100644 --- a/app/views/cloud_subnet/edit.html.haml +++ b/app/views/cloud_subnet/edit.html.haml @@ -1,6 +1,2 @@ = render :partial => "layouts/flash_msg" - -%cloud-subnet-form{'cloud-subnet-form-id' => @subnet.id} - -:javascript - miq_bootstrap('cloud-subnet-form'); += react 'SubnetForm', :recordId => @subnet.try(:id).try(:to_s) diff --git a/app/views/cloud_subnet/new.html.haml b/app/views/cloud_subnet/new.html.haml index baa43ef1062b..f7d9ea2caa03 100644 --- a/app/views/cloud_subnet/new.html.haml +++ b/app/views/cloud_subnet/new.html.haml @@ -1,6 +1,2 @@ = render :partial => "layouts/flash_msg" - -%cloud-subnet-form{'cloud-subnet-form-id' => 'new'} - -:javascript - miq_bootstrap('cloud-subnet-form'); += react 'SubnetForm' diff --git a/app/views/static/cloud_subnet/cloud-subnet-form.html.haml b/app/views/static/cloud_subnet/cloud-subnet-form.html.haml deleted file mode 100644 index 6d8fc35d2315..000000000000 --- a/app/views/static/cloud_subnet/cloud-subnet-form.html.haml +++ /dev/null @@ -1,154 +0,0 @@ -%form#form_div{'name' => 'angularForm', - 'form-changed' => true, - 'miq-form' => true, - 'model' => 'vm.cloudSubnetModel', - 'model-copy' => 'vm.modelCopy', - 'ng-cloak' => '', - 'ng-show' => 'vm.afterGet'} - - %h3 - = _('Network Management Provider') - .form-horizontal - .form-group{'ng-class' => '{"has-error": angularForm.ems_id.$invalid}'} - %label.col-md-2.control-label - = _('Network Manager') - .col-md-8{'ng-if' => 'vm.newRecord'} - %select{'name' => 'ems_id', - 'ng-change' => 'vm.filterNetworkManagerChanged(vm.cloudSubnetModel.ems_id)', - 'ng-model' => 'vm.cloudSubnetModel.ems_id', - 'ng-options' => 'ems.id as ems.name for ems in vm.available_ems', - 'miq-select' => true, - 'required' => '', - 'selectpicker-for-select-tag' => ''} - %option{'value' => ''} - = "<#{_('Choose')}>" - %span.help-block{'ng-show' => 'angularForm.ems_id.$error.required'} - = _('Required') - .col-md-8{'ng-if' => '!vm.newRecord'} - %input.form-control{'type' => 'text', - 'name' => 'ems_name', - 'disabled' => true, - 'maxlength' => 128, - 'ng-model' => 'vm.cloudSubnetModel.ext_management_system.name'} - .form-group{'ng-class' => '{"has-error": angularForm.cloud_tenant_id.$invalid}', - 'ng-if' => 'vm.cloudSubnetModel.ems_id && vm.available_tenants.length'} - %label.col-md-2.control-label - = _('Cloud Tenant Placement') - .col-md-8{'ng-if' => 'vm.newRecord'} - %select{'name' => 'cloud_tenant_id', - 'ng-model' => 'vm.cloudSubnetModel.cloud_tenant.id', - 'ng-options' => 'tenant.id as tenant.name for tenant in vm.available_tenants', - 'miq-select' => true, - 'required' => '', - 'selectpicker-for-select-tag' => ''} - %option{'value' => ''} - = "<#{_('Choose')}>" - %span.help-block{'ng-show' => "angularForm.cloud_tenant_id.$error.required"} - = _('Required') - .col-md-8{'ng-if' => '!vm.newRecord'} - %input.form-control{'type' => 'text', - 'name' => ' clout_tenant_name', - 'maxlength' => 128, - 'ng-model' => 'vm.cloudSubnetModel.cloud_tenant.name', - 'disabled' => true} - %h3 - = _('Cloud Subnet details') - .form-horizontal - .form-group{'ng-class' => '{"has-error": angularForm.network_id.$invalid}'} - %label.col-md-2.control-label - = _('Network') - .col-md-8{'ng-if' => 'vm.newRecord'} - %select{'name' => 'network_id', - 'ng-model' => 'vm.cloudSubnetModel.network_id', - 'ng-options' => 'network.ems_ref as network.name for network in vm.available_networks', - 'miq-select' => true, - 'required' => '', - 'selectpicker-for-select-tag' => ''} - %option{'value' => ''} - = "<#{_('Choose')}>" - %span.help-block{'ng-show' => 'angularForm.network_id.$error.required'} - = _('Required') - .col-md-8{'ng-if' => '!vm.newRecord'} - %input.form-control{'type' => 'text', - 'name' => 'network_name', - 'maxlength' => 128, - 'ng-model' => 'vm.cloudSubnetModel.cloud_network.name', - 'disabled' => true} - .form-horizontal - .form-group - %label.col-md-2.control-label - = _('Subnet Name') - .col-md-8 - %input.form-control{'type' => 'text', - 'name' => 'name', - 'ng-model' => 'vm.cloudSubnetModel.name', - 'maxlength' => 128} - .form-group - %label.col-md-2.control-label - = _('Gateway') - .col-md-8 - %input.form-control{'type' => 'text', - 'name' => 'gateway_ip', - 'ng-model' => 'vm.cloudSubnetModel.gateway', - 'maxlength' => 20} - .form-group - %label.control-label.col-md-2{'for' => 'dhcp'} - = _('DHCP') - .col-md-8 - %input{'type' => 'checkbox', - 'name' => 'dhcp', - 'bs-switch' => '', - 'data' => {:on_text => _('Enabled'), :off_text => _('Disabled'), :size => 'mini'}, - 'ng-model' => 'vm.cloudSubnetModel.dhcp_enabled'} - .form-group{'ng-class' => '{"has-error": angularForm.network_protocol.$invalid}'} - %label.col-md-2.control-label - = _('IP Version') - .col-md-8 - %select{'name' => 'network_protocol', - 'ng-model' => 'vm.cloudSubnetModel.network_protocol', - 'ng-disabled' => '!vm.newRecord', - 'ng-options' => 'protocol as protocol for protocol in vm.networkProtocols', - 'miq-select' => true, - 'required' => '', - 'selectpicker-for-select-tag' => ''} - %span.help-block{'ng-show' => 'angularForm.network_protocol.$error.required'} - = _('Required') - - .form-group{'ng-class' => "{'has-error': angularForm.cidr.$invalid}"} - %label.col-md-2.control-label - = _('Subnet CIDR') - .col-md-8 - %input.form-control{'type' => 'text', - 'name' => 'cidr', - 'ng-disabled' => '!vm.newRecord', - 'ng-model' => 'vm.cloudSubnetModel.cidr', - 'maxlength' => 20, - 'required' => ''} - %span.help-block{'ng-show' => 'angularForm.cidr.$error.required'} - = _('Required') - .form-group - %label.col-md-2.control-label - = _('Allocation Pools') - .col-md-8 - %textarea.form-control{'type' => 'textarea', - 'name' => 'allocation_pools', - 'ng-model' => 'vm.cloudSubnetModel.allocation_pools', - 'rows' => 4} - .form-group - %label.col-md-2.control-label - = _('DNS Name Servers') - .col-md-8 - %textarea.form-control{'type' => 'textarea', - 'name' => 'dns_nameservers', - 'ng-model' => 'vm.cloudSubnetModel.dns_nameservers', - 'rows' => 4} - .form-group - %label.col-md-2.control-label - = _('Host Routes') - .col-md-8 - %textarea.form-control{'type' => 'textarea', - 'name' => 'host_routes', - 'ng-model' => 'vm.cloudSubnetModel.host_routes', - 'rows' => 4} - - = render :partial => 'layouts/angular/generic_form_buttons' diff --git a/config/routes.rb b/config/routes.rb index a6b32b4ff69f..b445ecad6bc7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1556,7 +1556,6 @@ compare_get, :post => %w( button - create dynamic_checkbox_refresh listnav_search_selected protect @@ -1565,7 +1564,6 @@ show show_list tagging_edit - update wait_for_task ) + adv_search_post + diff --git a/package.json b/package.json index 5be6b186b905..2f5829a24c9b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@carbon/charts-react": "~0.41.38", "@carbon/icons-react": "~10.26.0", "@carbon/themes": "~10.28.0", - "@data-driven-forms/carbon-component-mapper": "~2.24.3", + "@data-driven-forms/carbon-component-mapper": "~2.24.4", "@data-driven-forms/pf3-component-mapper": "~2.20.3", "@data-driven-forms/react-form-renderer": "~2.24.1", "@manageiq/ui-components": "1.4.4", diff --git a/spec/controllers/cloud_subnet_controller_spec.rb b/spec/controllers/cloud_subnet_controller_spec.rb index 97a231ba566b..efc8d5b7d768 100644 --- a/spec/controllers/cloud_subnet_controller_spec.rb +++ b/spec/controllers/cloud_subnet_controller_spec.rb @@ -74,40 +74,10 @@ login_as FactoryBot.create(:user, :miq_groups => [group]) end - it "raises exception wheh used have not privilege" do - expect { post :new, :params => { :button => "new", :format => :js } }.to raise_error(MiqException::RbacPrivilegeException) - end - - context "user don't have privilege for cloud tenants" do - let(:feature) { MiqProductFeature.find_all_by_identifier(%w(cloud_subnet_new ems_network_show_list)) } - - it "raises exception" do - expect { post :new, :params => { :button => "new", :format => :js } }.to raise_error(MiqException::RbacPrivilegeException) - end - end - - context "user don't have privilege for cloud networks" do - let(:feature) { MiqProductFeature.find_all_by_identifier(%w(cloud_subnet_new ems_network_show_list cloud_tenant_show_list)) } - - it "raises exception" do - expect { post :new, :params => { :button => "new", :format => :js } }.to raise_error(MiqException::RbacPrivilegeException) - end - end - - it 'asserts privileges and calls drop_breadcrumb' do - expect(controller).to receive(:assert_privileges).with('cloud_subnet_new') - expect(controller).to receive(:assert_privileges).with('ems_network_show_list') - expect(controller).to receive(:assert_privileges).with('cloud_tenant_show_list') - expect(controller).to receive(:assert_privileges).with('cloud_network_show_list') + it 'calls drop_breadcrumb' do expect(controller).to receive(:drop_breadcrumb).with(:name => 'Add New Subnet', :url => '/cloud_subnet/new') controller.send(:new) end - - it 'renders cloud_subnet/new partial for user with privileges' do - stub_user(:features => :all) - post :new, :params => { :button => "new", :format => :js } - expect(response.body).to include("") - end end describe "#create" do @@ -139,26 +109,6 @@ end before { stub_user(:features => :all) } - - it "queues the create action" do - expect(MiqTask).to receive(:generic_action_with_callback).with(task_options, hash_including(queue_options)) - - post :create, :params => { - :button => 'add', - :controller => 'cloud_subnet', - :format => :js, - :cloud_tenant => {:id => cloud_tenant.id}, - :dhcp_enabled => true, - :ems_id => ems.id, - :id => 'new', - :name => 'test', - :network_protocol => 'ipv4', - :network_id => cloud_network.ems_ref, - :allocation_pools => "172.10.1.10,172.10.1.20", - :host_routes => "172.12.1.0/24,172.12.1.1", - :dns_nameservers => "172.11.1.1" - } - end end describe "#edit" do @@ -190,58 +140,6 @@ expect(assigns(:flash_array)).to be_nil end - - it "queues the update action" do - expect(MiqTask).to receive(:generic_action_with_callback).with(task_options, hash_including(queue_options)) - - post :update, :params => { - :button => 'save', - :format => :js, - :id => cloud_subnet.id, - :name => 'test2', - :allocation_pools => "172.20.1.10,172.20.1.20", - :host_routes => "172.22.1.0/24,172.22.1.1", - :dns_nameservers => "172.21.1.1" - } - end - end - - describe '#update' do - before do - allow(controller).to receive(:assert_privileges) - controller.params = {:button => 'cancel', :id => cloud_subnet.id} - end - - it 'calls flash_and_redirect for canceling editing Cloud Subnet' do - expect(controller).to receive(:flash_and_redirect).with(_("Edit of Subnet \"%{name}\" was cancelled by the user") % {:name => cloud_subnet.name}) - controller.send(:update) - end - end - - describe '#update_finished' do - let(:miq_task) { double("MiqTask", :state => 'Finished', :status => 'ok', :message => 'some message') } - - before do - allow(MiqTask).to receive(:find).with(123).and_return(miq_task) - allow(controller).to receive(:session).and_return(:async => {:params => {:task_id => 123, :name => cloud_subnet.name}}) - end - - it 'calls flash_and_redirect with appropriate arguments for succesful updating of a Cloud Subnet' do - expect(controller).to receive(:flash_and_redirect).with(_("Cloud Subnet \"%{name}\" updated") % {:name => cloud_subnet.name}) - controller.send(:update_finished) - end - - context 'unsuccesful updating of a Cloud Subnet' do - let(:miq_task) { double("MiqTask", :state => 'Finished', :status => 'Error', :message => 'some message') } - - it 'calls flash_and_redirect with appropriate arguments' do - expect(controller).to receive(:flash_and_redirect).with(_("Unable to update Cloud Subnet \"%{name}\": %{details}") % { - :name => cloud_subnet.name, - :details => miq_task.message - }, :error) - controller.send(:update_finished) - end - end end describe "#delete" do diff --git a/spec/javascripts/components/cloud_subnet/cloud_subnet_form_spec.js b/spec/javascripts/components/cloud_subnet/cloud_subnet_form_spec.js deleted file mode 100644 index 0c7962cd189c..000000000000 --- a/spec/javascripts/components/cloud_subnet/cloud_subnet_form_spec.js +++ /dev/null @@ -1,92 +0,0 @@ -describe('cloud-subnet-form', function() { - var $componentController, vm, miqService, API; - - describe('when vm.recordId is not defined', function () { - beforeEach(module('ManageIQ')); - beforeEach(inject(function (_$componentController_, _API_, _miqService_, $q) { - $componentController = _$componentController_; - API = _API_; - miqService = _miqService_; - - spyOn(miqService, 'miqAjaxButton'); - - var deferred = $q.defer(); - spyOn(API, 'get').and.callFake(function() {return deferred.promise;}); - - var bindings = {cloudSubnetFormId: 'new'}; - vm = $componentController('cloudSubnetForm', null, bindings); - vm.$onInit(); - })); - - it('sets newRecord to true', function () { - expect(vm.newRecord).toBe(true); - }); - - it('sets afterGet', function () { - expect(vm.afterGet).toBe(false); - }); - - it('adds a Cloud Subnet record', function () { - vm.cloudSubnetModel.name = 'newSubnet'; - vm.cloudSubnetModel.ip_version = 'ipv4'; - vm.cloudSubnetModel.ems_id = 1; - vm.addClicked(); - expect(miqService.miqAjaxButton).toHaveBeenCalledWith('create/new?button=add', vm.cloudSubnetModel, { complete: false }); - }); - }); - - describe('when vm.recordId is defined', function () { - beforeEach(module('ManageIQ')); - beforeEach(inject(function (_$componentController_, _API_, _miqService_, $q) { - $componentController = _$componentController_; - API = _API_; - miqService = _miqService_; - - spyOn(miqService, 'miqAjaxButton'); - - var cloudSubnetFormResponse = { - name: 'abc' - }; - - spyOn(API, 'get').and.callFake(function() { - return { - then: function (callback) { - callback(cloudSubnetFormResponse); - return {catch: function() {}}; - } - }; - }); - - var bindings = {cloudSubnetFormId: 1111}; - vm = $componentController('cloudSubnetForm', null, bindings); - vm.$onInit(); - })); - - it('sets newRecord to false', function () { - expect(vm.newRecord).toBe(false); - }); - - it('calls API.get with the appropriate URL', function () { - expect(API.get).toHaveBeenCalledWith('/api/cloud_subnets/1111?expand=resources&attributes=ext_management_system.name,cloud_tenant.name,cloud_network.name'); - }); - - it('sets vm.cloudSubnetModel.name', function () { - expect(vm.cloudSubnetModel.name).toBe('abc'); - }); - - it('updates a Cloud Subnet record', function () { - - vm.cloudSubnetModel.name = 'xyz'; - vm.cloudSubnetModel.gateway = '172.10.1.2'; - vm.cloudSubnetModel.dhcp_enabled = '172.10.1.2'; - vm.cloudSubnetModel.random_field_from_API = "random data from API"; - vm.cloudSubnetModel.allocation_pools = '172.10.1.10,172.10.1.20'; - vm.cloudSubnetModel.host_routes = '172.12.1.0/24,172.12.1.1'; - vm.cloudSubnetModel.dns_nameservers = '172.11.1.1'; - var correct_data = _.pick(vm.cloudSubnetModel, 'name', 'gateway', 'dhcp_enabled', 'allocation_pools', 'host_routes', 'dns_nameservers'); - vm.saveClicked(); - - expect(miqService.miqAjaxButton).toHaveBeenCalledWith('/cloud_subnet/update/1111?button=save', correct_data, { complete: false }); - }); - }); -});