From c96180b7236b1c982c3b2fc54337c1e2c364807d Mon Sep 17 00:00:00 2001 From: "Jingwei.Zhang" Date: Thu, 6 Jan 2022 17:27:27 +0800 Subject: [PATCH] feat: Support the use of ports when creating an instance 1. Add port select in network step when creating an instance 2. Show port in confirm step when creating an instance Change-Id: I4adb832d194433f2b201c02c067e72251c6e61e6 --- src/components/FormItem/index.jsx | 3 +- src/locales/en.json | 3 +- src/locales/zh.json | 3 +- .../actions/StepCreate/ConfirmStep/index.jsx | 40 ++++++++-- .../actions/StepCreate/NetworkStep/index.jsx | 73 ++++++++++++++----- .../Instance/actions/StepCreate/index.jsx | 53 ++++++++++---- src/resources/port.jsx | 51 +++++++++++++ 7 files changed, 184 insertions(+), 42 deletions(-) diff --git a/src/components/FormItem/index.jsx b/src/components/FormItem/index.jsx index 160e6d9f..5421551c 100644 --- a/src/components/FormItem/index.jsx +++ b/src/components/FormItem/index.jsx @@ -258,7 +258,8 @@ export default class FormItem extends React.Component { } getSelectTableValidator = (rule, value) => { - if (!value || value.selectedRowKeys.length === 0) { + const { selectedRowKeys = [] } = value || {}; + if (selectedRowKeys.length === 0) { return Promise.reject( new Error(rule.placeholder || `${t('Please select')}${rule.label}!`) ); diff --git a/src/locales/en.json b/src/locales/en.json index e6478e61..e290f648 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1254,6 +1254,7 @@ "Neutron Agent Detail": "Neutron Agent Detail", "Neutron Agents": "Neutron Agents", "Neutron Service": "Neutron Service", + "New": "New", "New Availability Zone": "New Availability Zone", "New Caledonia": "New Caledonia", "New Zealand": "New Zealand", @@ -1451,7 +1452,6 @@ "Please select your Domain!": "Please select your Domain!", "Please select your Region!": "Please select your Region!", "Please select {name} first": "Please select {name} first", - "Please select!": "Please select!", "Please set CPU && Ram first.": "Please set CPU && Ram first.", "Please set MUNA": "Please set MUNA", "Please upload files smaller than { size }G on the page. It is recommended to upload files over { size }G using API.": "Please upload files smaller than { size }G on the page. It is recommended to upload files over { size }G using API.", @@ -1482,6 +1482,7 @@ "Port Security Enabled": "Port Security Enabled", "Port Type": "Port Type", "Ports": "Ports", + "Ports provide extra communication channels to your instances. You can select ports instead of networks or a mix of both (The port executes its own security group rules by default).": "Ports provide extra communication channels to your instances. You can select ports instead of networks or a mix of both (The port executes its own security group rules by default).", "Portugal": "Portugal", "Power Off": "Power Off", "Power On": "Power On", diff --git a/src/locales/zh.json b/src/locales/zh.json index f83e01ab..4cea53a3 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1254,6 +1254,7 @@ "Neutron Agent Detail": "网络服务详情", "Neutron Agents": "网络服务", "Neutron Service": "网络服务", + "New": "新建", "New Availability Zone": "新可用域", "New Caledonia": "新喀里多尼亚", "New Zealand": "新西兰", @@ -1451,7 +1452,6 @@ "Please select your Domain!": "请选择Domain!", "Please select your Region!": "请选择Region!", "Please select {name} first": "请先选择{name}", - "Please select!": "请选择!", "Please set CPU && Ram first.": "请先设置CPU、内存。", "Please set MUNA": "请设置NUMA节点", "Please upload files smaller than { size }G on the page. It is recommended to upload files over { size }G using API.": "页面请上传小于{ size }G的文件,超过{ size }G的文件建议使用API上传。", @@ -1482,6 +1482,7 @@ "Port Security Enabled": "启用端口安全", "Port Type": "端口方式", "Ports": "端口", + "Ports provide extra communication channels to your instances. You can select ports instead of networks or a mix of both (The port executes its own security group rules by default).": "端口为您的云主机提供了额外的通信渠道。您可以选择已创建的端口而非网络或者二者都选(端口默认执行本身的安全组规则)。", "Portugal": "葡萄牙", "Power Off": "关机", "Power On": "开机", diff --git a/src/pages/compute/containers/Instance/actions/StepCreate/ConfirmStep/index.jsx b/src/pages/compute/containers/Instance/actions/StepCreate/ConfirmStep/index.jsx index 6651cdc4..80d130f7 100644 --- a/src/pages/compute/containers/Instance/actions/StepCreate/ConfirmStep/index.jsx +++ b/src/pages/compute/containers/Instance/actions/StepCreate/ConfirmStep/index.jsx @@ -74,7 +74,7 @@ export class ConfirmStep extends Base { getVirtualLANs() { const { context } = this.props; - const { networks } = context; + const { networks = [] } = context; const values = networks.map((it) => { const { networkOption, subnetOption, ipTypeOption, ip } = it.value; const subnet = @@ -86,19 +86,42 @@ export class ConfirmStep extends Base { return ( {values.map((i) => ( - {i} + + {i} + + ))} + + ); + } + + getPorts() { + const { context } = this.props; + const { ports: { selectedRows = [] } = {} } = context; + const values = selectedRows.map((it) => it.name || it.id); + return ( + + {values.map((i) => ( + + {i} + ))} ); - // return values.join(
); } getSecurityGroups() { const { context } = this.props; const { securityGroup: { selectedRows = [] } = {} } = context; const values = selectedRows.map((it) => it.name); - return values; - // return values.join(
); + return ( + + {values.map((i) => ( + + {i} + + ))} + + ); } getLoginType() { @@ -215,10 +238,15 @@ export class ConfirmStep extends Base { }, items: [ { - label: t('Virtual LAN'), + label: `${t('Virtual LAN')}(${t('New')})`, value: this.getVirtualLANs(), span: 1, }, + { + label: `${t('Virtual LAN')}(${t('Created')})`, + value: this.getPorts(), + span: 1, + }, { label: t('Security Group'), value: this.getSecurityGroups(), diff --git a/src/pages/compute/containers/Instance/actions/StepCreate/NetworkStep/index.jsx b/src/pages/compute/containers/Instance/actions/StepCreate/NetworkStep/index.jsx index 1e64d72b..b8451b7e 100644 --- a/src/pages/compute/containers/Instance/actions/StepCreate/NetworkStep/index.jsx +++ b/src/pages/compute/containers/Instance/actions/StepCreate/NetworkStep/index.jsx @@ -15,12 +15,11 @@ import React from 'react'; import { inject, observer } from 'mobx-react'; import { Link } from 'react-router-dom'; -import { Button } from 'antd'; -import { FormOutlined } from '@ant-design/icons'; import { isEmpty, isArray } from 'lodash'; import { NetworkStore } from 'stores/neutron/network'; import { SubnetStore } from 'stores/neutron/subnet'; import { SecurityGroupStore } from 'stores/neutron/security-group'; +import { VirtualAdapterStore } from 'stores/neutron/virtual-adapter'; import { ipValidate } from 'utils/validate'; import Base from 'components/Form'; import NetworkSelect from 'components/FormItem/NetworkSelect'; @@ -29,6 +28,7 @@ import { securityGroupColumns, securityGroupFilter, } from 'resources/security-group'; +import { portColumns, portFilters } from 'resources/port'; // import EditYamlModal from 'components/Modals/EditYaml'; const { isIPv4, isIpv6 } = ipValidate; @@ -38,6 +38,7 @@ export class NetworkStep extends Base { this.networkStore = new NetworkStore(); this.subnetStore = new SubnetStore(); this.securityGroupStore = new SecurityGroupStore(); + this.portStore = new VirtualAdapterStore(); this.subnetMap = {}; } @@ -153,16 +154,43 @@ export class NetworkStep extends Base { }); }; + checkNetworkAndPort = ({ getFieldValue }) => ({ + validator() { + const networkSelect = getFieldValue('networkSelect'); + const ports = getFieldValue('ports'); + const { selectedRowKeys: networkSelected = [] } = networkSelect || {}; + const { selectedRowKeys: portsSelected = [] } = ports || {}; + if (networkSelected.length === 0 && portsSelected === 0) { + return Promise.reject(t('Please select')); + } + return Promise.resolve(); + }, + }); + + onPortChange = (value) => { + const { selectedRows = [] } = value || {}; + this.updateContext({ + portSelectRows: selectedRows, + }); + }; + get nameForStateUpdate() { - return ['networkSelect', 'networks']; + return ['networkSelect', 'networks', 'ports']; } get formItems() { - const { networkSelectRows = [], subnets, initValue = [] } = this.state; + const { + networkSelectRows = [], + subnets, + initValue = [], + ports = [], + } = this.state; const showNetworks = networkSelectRows.length > 0; const showSecurityGroups = networkSelectRows.length && networkSelectRows.every((it) => it.port_security_enabled); + const networkRequired = ports.length === 0; + const portRequired = networkSelectRows.length === 0; return [ { name: 'networkSelect', @@ -172,7 +200,9 @@ export class NetworkStep extends Base { onChange: this.onNetworkChange, showExternal: true, isMulti: true, - required: true, + required: networkRequired, + otherRule: this.checkNetworkAndPort, + dependencies: ['ports'], header: (
{t( @@ -208,20 +238,29 @@ export class NetworkStep extends Base { }, }, { - name: 'ipv6', - label: 'IPv6', - type: 'label', - hidden: true, - content: ( - - {t('The selected VPC/ subnet does not have IPv6 enabled.')}{' '} - {' '} - - ), + name: 'divider1', + type: 'divider', }, { + name: 'ports', + type: 'select-table', + // type: 'input', + label: t('Ports'), + extraParams: { project_id: this.currentProjectId, status: 'DOWN' }, + backendPageStore: this.portStore, + isMulti: true, + header: t( + 'Ports provide extra communication channels to your instances. You can select ports instead of networks or a mix of both (The port executes its own security group rules by default).' + ), + filterParams: portFilters, + columns: portColumns, + dependencies: ['networkSelect'], + otherRule: this.checkNetworkAndPort, + required: portRequired, + onChange: this.onPortChange, + }, + { + name: 'divider2', type: 'divider', }, { diff --git a/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx b/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx index 6ca053df..601633dd 100644 --- a/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx +++ b/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx @@ -409,16 +409,43 @@ export class StepCreate extends StepAction { }; } + getNetworkData(values) { + const { networks = [], ports = {} } = values; + let hasIp = false; + const data = []; + networks.forEach((it) => { + const net = { + uuid: it.value.network, + }; + if (it.value.ipType === 1 && it.value.ip) { + net.fixed_ip = it.value.ip; + hasIp = true; + } + data.push(net); + }); + const { selectedRowKeys = [] } = ports || {}; + selectedRowKeys.forEach((it) => { + const net = { + port: it, + }; + data.push(net); + }); + return { + data, + hasIp, + }; + } + getSubmitData(values) { if (this.status === 'error') { return null; } const { volumes, imageRef } = this.getVolumeAndImageData(values); + const { data: networks, hasIp } = this.getNetworkData(values); const { availableZone, keypair, loginType, - networks, password, physicalNode, physicalNodeType, @@ -429,7 +456,10 @@ export class StepCreate extends StepAction { name, count = 1, } = values; - let hasIp = false; + if (hasIp && count > 1) { + this.ipBatchError = true; + return null; + } const { selectedRows: securityGroupSelectedRows = [] } = securityGroup || {}; const server = { @@ -440,21 +470,8 @@ export class StepCreate extends StepAction { flavorRef: flavor.selectedRowKeys[0], availability_zone: availableZone.value, block_device_mapping_v2: volumes, - networks: networks.map((it) => { - const net = { - uuid: it.value.network, - }; - if (it.value.ipType === 1 && it.value.ip) { - net.fixed_ip = it.value.ip; - hasIp = true; - } - return net; - }), + networks, }; - if (hasIp && count > 1) { - this.ipBatchError = true; - return Promise.reject(); - } if (imageRef) { server.imageRef = imageRef; } @@ -501,6 +518,10 @@ export class StepCreate extends StepAction { const { data } = this.state; this.values = data; const submitData = this.getSubmitData(data); + if (!submitData) { + Notify.errorWithDetail(null, this.errorText); + return; + } this.onSubmit(submitData).then( () => { this.routing.push(this.listUrl); diff --git a/src/resources/port.jsx b/src/resources/port.jsx index fd0463bd..e09c9bc8 100644 --- a/src/resources/port.jsx +++ b/src/resources/port.jsx @@ -174,3 +174,54 @@ export function getPortFormItem(device_owner) { }, ]; } + +export const portColumns = [ + { + title: t('ID/Name'), + dataIndex: 'name', + sorter: false, + render: (value, record) => ( +
+
{record.id}
+
{value || '-'}
+
+ ), + }, + { + title: t('Owned Network'), + dataIndex: 'network_name', + isLink: true, + idKey: 'network_id', + sorter: false, + }, + { + title: t('IPv4 Address'), + dataIndex: 'ipv4', + render: (value) => value.map((it) =>
{it}
), + sorter: false, + }, + { + title: t('IPv6 Address'), + dataIndex: 'ipv6', + render: (value) => value.map((it) =>
{it}
), + sorter: false, + }, + { + title: t('Mac Address'), + dataIndex: 'mac_address', + sorter: false, + }, + { + title: t('Status'), + dataIndex: 'status', + render: (value) => portStatus[value] || value, + sorter: false, + }, +]; + +export const portFilters = [ + { + label: t('Name'), + name: 'name', + }, +];