Skip to content

Commit

Permalink
Convert the subnet form to DDF
Browse files Browse the repository at this point in the history
  • Loading branch information
skateman authored and DavidResende0 committed May 19, 2021
1 parent b04f88b commit 3b005ba
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 667 deletions.
193 changes: 0 additions & 193 deletions app/controllers/cloud_subnet_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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?
Expand Down
99 changes: 99 additions & 0 deletions app/javascript/components/subnet-form/index.jsx
Original file line number Diff line number Diff line change
@@ -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 <Loading className='export-spinner' withOverlay={false} small />;
return !isLoading && (
<MiqFormRenderer
schema={createSchema(!!recordId, fields, loadSchema)}
initialValues={initialValues}
canReset={!!recordId}
onSubmit={onSubmit}
onCancel={onCancel}
buttonsLabels={{ submitLabel }}
/>
);
};

SubnetForm.propTypes = {
recordId: PropTypes.string,
};
SubnetForm.defaultProps = {
recordId: undefined,
};

export default SubnetForm;
73 changes: 73 additions & 0 deletions app/javascript/components/subnet-form/subnet-form.schema.js
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 3b005ba

Please sign in to comment.