diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json index 1159c5868d0..300c54901a8 100644 --- a/ui/src/locales/en.json +++ b/ui/src/locales/en.json @@ -1254,6 +1254,7 @@ "label.lun.number": "LUN #", "label.lxcnetworklabel": "LXC Traffic Label", "label.macaddress": "MAC Address", +"label.macaddress.example": "The MAC Address. Example: 01:23:45:67:89:ab", "label.macaddresschanges": "MAC Address Changes", "label.macos": "MacOS", "label.make.project.owner": "Make account project owner", @@ -2743,6 +2744,7 @@ "message.error.internallb.name": "Please specify a name for the Internal LB", "message.error.internallb.source.port": "Please specify a Source Port", "message.error.invalid.range": "Please enter values from {min} to {max}", +"message.error.ip.range": "Please enter valid range", "message.error.ipv4.address": "Please enter a valid IPv4 address", "message.error.ipv4.dns1": "Please enter IpV4 DNS 1", "message.error.ipv4.dns2": "Please enter IpV4 DNS 2", @@ -2757,6 +2759,7 @@ "message.error.limit.value": "The value must not be less than", "message.error.loading.setting": "There was an error loading these settings.", "message.error.lun": "Please enter LUN #", +"message.error.macaddress": "Please enter a valid MAC Address.", "message.error.name": "Please enter name", "message.error.netmask": "Please enter Netmask", "message.error.network.domain": "Please enter Network domain", diff --git a/ui/src/views/compute/wizard/NetworkConfiguration.vue b/ui/src/views/compute/wizard/NetworkConfiguration.vue index 823f1b1f3ad..d19cec9a762 100644 --- a/ui/src/views/compute/wizard/NetworkConfiguration.vue +++ b/ui/src/views/compute/wizard/NetworkConfiguration.vue @@ -25,17 +25,45 @@ size="middle" :scroll="{ y: 225 }" > + @@ -64,7 +92,8 @@ export default { { dataIndex: 'name', title: this.$t('label.defaultnetwork'), - width: '30%' + width: '30%', + scopedSlots: { customRender: 'name' } }, { dataIndex: 'ip', @@ -80,7 +109,9 @@ export default { } ], selectedRowKeys: [], - dataItems: [] + dataItems: [], + macRegex: /^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$/i, + ipV4Regex: /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i } }, beforeCreate () { @@ -150,6 +181,49 @@ export default { this.$emit('select-default-network-item', this.dataItems[0].id) } } + }, + validatorMacAddress (rule, value, callback) { + if (!value || value === '') { + callback() + } else if (!this.macRegex.test(value)) { + callback(this.$t('message.error.macaddress')) + } else { + callback() + } + }, + validatorIpAddress (rule, value, callback) { + if (!value || value === '') { + callback() + } else if (!this.ipV4Regex.test(value)) { + callback(this.$t('message.error.ipv4.address')) + } else if (rule.networkType !== 'L2' && !this.isIp4InCidr(value, rule.cidr)) { + const rangeIps = this.calculateCidrRange(rule.cidr) + const message = `${this.$t('message.error.ip.range')} ${this.$t('label.from')} ${rangeIps[0]} ${this.$t('label.to')} ${rangeIps[1]}` + callback(message) + } else { + callback() + } + }, + getIpRangeDescription (network) { + const rangeIps = this.calculateCidrRange(network.cidr) + const rangeIpDescription = [`${this.$t('label.ip.range')}:`, rangeIps[0], '-', rangeIps[1]].join(' ') + return rangeIpDescription + }, + isIp4InCidr (ip, cidr) { + const [range, bits = 32] = cidr.split('/') + const mask = ~(2 ** (32 - bits) - 1) + return (this.ip4ToInt(ip) & mask) === (this.ip4ToInt(range) & mask) + }, + calculateCidrRange (cidr) { + const [range, bits = 32] = cidr.split('/') + const mask = ~(2 ** (32 - bits) - 1) + return [this.intToIp4(this.ip4ToInt(range) & mask), this.intToIp4(this.ip4ToInt(range) | ~mask)] + }, + ip4ToInt (ip) { + return ip.split('.').reduce((int, oct) => (int << 8) + parseInt(oct, 10), 0) >>> 0 + }, + intToIp4 (int) { + return [(int >>> 24) & 0xFF, (int >>> 16) & 0xFF, (int >>> 8) & 0xFF, int & 0xFF].join('.') } } } @@ -159,4 +233,13 @@ export default { .ant-table-wrapper { margin: 2rem 0; } + + /deep/.ant-table-tbody > tr td:not(:first-child) { + vertical-align: baseline; + } + + .ant-form .ant-form-item { + margin-bottom: 0; + padding-bottom: 0; + } diff --git a/ui/src/views/compute/wizard/NetworkSelection.vue b/ui/src/views/compute/wizard/NetworkSelection.vue index 9b7dd12a45f..cf8df5a18f6 100644 --- a/ui/src/views/compute/wizard/NetworkSelection.vue +++ b/ui/src/views/compute/wizard/NetworkSelection.vue @@ -237,7 +237,7 @@ export default { inject: ['vmFetchNetworks'], methods: { getDetails (network) { - return [ + const detail = [ { title: this.$t('label.description'), description: network.displaytext @@ -247,6 +247,13 @@ export default { description: network.networkofferingdisplaytext } ] + if (network.type !== 'L2') { + detail.push({ + title: this.$t('label.cidr'), + description: network.cidr + }) + } + return detail }, handleSearch (value) { this.filter = value