ChangeIp.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <template>
  2. <base-dialog @cancel="cancelDialog">
  3. <div slot="header">{{$t('compute.text_390')}}</div>
  4. <div slot="body">
  5. <dialog-selected-tips :count="params.data.length" :action="$t('compute.text_390')" />
  6. <dialog-table :data="params.data" :columns="columns" />
  7. <a-form :form="form.fc" hideRequiredMark v-bind="formItemLayout">
  8. <a-form-model-item :label="$t('compute.text_106')" class="mb-0">
  9. <a-form-item :help="help">
  10. <base-select
  11. class="w-100"
  12. v-decorator="decorators.network"
  13. resource="networks"
  14. :params="networkParams"
  15. :item.sync="form.fi.network"
  16. remote
  17. :label-format="networkLabelFormat"
  18. :remote-fn="q => ({ filter: `name.contains(${q})` })"
  19. :select-props="{ placeholder: $t('compute.text_195') }"
  20. :mapper="mapper" />
  21. </a-form-item>
  22. <a-form-model-item v-if="isSupportIPv4 && !(hasIpv6 && ipv6Mode === 'only')">
  23. <div class="d-flex">
  24. <a-form-item class="mb-0">
  25. <a-checkbox v-model="ipv4ConfigShow" style="display:inline-block;min-width:200px"><a-button type="link" class="pl-1">{{ $t('compute.text_198') }}</a-button></a-checkbox>
  26. </a-form-item>
  27. <a-form-item class="mb-0">
  28. <ip-select v-if="ipv4ConfigShow" v-decorator="decorators.ip" :value="form.fi.ip" :network="form.fi.network" @change="ipChange" />
  29. </a-form-item>
  30. </div>
  31. </a-form-model-item>
  32. <template v-if="isSupportIPv6">
  33. <a-form-model-item class="mb-0">
  34. <div class="d-flex">
  35. <div>
  36. <a-checkbox v-if="isSupportIPv4" v-model="hasIpv6" @change="requireIpv6Change" class="mr-1" />
  37. <a-dropdown>
  38. <a-menu slot="overlay" @click="triggerIpv6Mode" v-if="isSupportIPv4">
  39. <a-menu-item key="all">{{ $t('compute.server_create.require_ipv6_all') }}</a-menu-item>
  40. <a-menu-item key="only">{{ $t('compute.server_create.require_ipv6_only') }}</a-menu-item>
  41. </a-menu>
  42. <a-button type="link" class="pl-1" v-if="isSupportIPv4">{{ ipv6Mode === 'only' ? $t('compute.server_create.require_ipv6_only') : $t('compute.server_create.require_ipv6_all') }}<a-icon type="down" /> </a-button>
  43. </a-dropdown>
  44. </div>
  45. <template v-if="isSupportIPv6 && ipv6ConfigShow">
  46. <template v-if="ipv6InputShow">
  47. <div class="mb-0 ml-1" style="display:flex">
  48. <span class="mr-1">{{ getIpv6Prefix(form.fi.network?.guest_ip6_start) }}</span>
  49. <a-form-item class="mb-0" style="display:inline-block">
  50. <a-input
  51. style="width: 164px"
  52. :placeholder="$t('compute.complete_ipv6_address')"
  53. v-decorator="decorators.address6" />
  54. </a-form-item>
  55. <a-button type="link" class="mt-1" @click="triggerShowIpv6">{{$t('compute.text_135')}}</a-button>
  56. </div>
  57. </template>
  58. <a-button v-else type="link" class="mt-1" @click="triggerShowIpv6">{{$t('compute.ipv6_config')}}</a-button>
  59. </template>
  60. </div>
  61. </a-form-model-item>
  62. </template>
  63. </a-form-model-item>
  64. <a-form-item>
  65. <a-checkbox v-decorator="decorators.restartNetwork">{{$t('compute.restart_network')}}</a-checkbox>
  66. <help-tooltip name="restartNetworkToEffectIp" />
  67. </a-form-item>
  68. </a-form>
  69. </div>
  70. <div slot="footer">
  71. <a-button type="primary" @click="handleConfirm" :loading="loading">{{ $t('dialog.ok') }}</a-button>
  72. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  73. </div>
  74. </base-dialog>
  75. </template>
  76. <script>
  77. import * as R from 'ramda'
  78. import { mapGetters } from 'vuex'
  79. import ipaddr from 'ipaddr.js'
  80. import IpSelect from '@Compute/sections/ServerNetwork/IpSelect'
  81. import { validate, isWithinRange } from '@/utils/validate'
  82. import DialogMixin from '@/mixins/dialog'
  83. import WindowsMixin from '@/mixins/windows'
  84. import { HYPERVISORS_MAP } from '@/constants'
  85. import expectStatus from '@/constants/expectStatus'
  86. import { getIpv6Start, ipv6ToHex } from '@Compute/utils/createServer'
  87. export default {
  88. name: 'VmChangeIpDialog',
  89. components: {
  90. IpSelect,
  91. },
  92. mixins: [DialogMixin, WindowsMixin],
  93. data () {
  94. const validateIp = (rule, value, callback) => {
  95. let msg
  96. if (!R.isNil(value) && !R.isEmpty(value)) {
  97. if (R.isEmpty(this.form.fi.network)) {
  98. msg = this.$t('compute.text_1191')
  99. return callback(msg)
  100. }
  101. if (validate(value, 'IPv4') !== true) {
  102. msg = this.$t('compute.text_1192')
  103. return callback(msg)
  104. }
  105. if (!isWithinRange(value, this.form.fi.network.guest_ip_start, this.form.fi.network.guest_ip_end)) {
  106. msg = this.$t('compute.text_1193')
  107. return callback(msg)
  108. }
  109. }
  110. return callback()
  111. }
  112. const validateIpv6 = (rule, value, cb) => {
  113. const networkData = this.form.fi.network
  114. const ipv6First = getIpv6Start(networkData.guest_ip6_start)
  115. try {
  116. const ipv6 = ipv6First + value
  117. const ipAddr = ipaddr.IPv6.parse(ipv6)
  118. const subnet1Addr = ipaddr.IPv6.parse(networkData.guest_ip6_start)
  119. const subnet2Addr = ipaddr.IPv6.parse(networkData.guest_ip6_end)
  120. if (ipAddr.kind() !== 'ipv6') {
  121. cb(new Error(this.$t('compute.error_ipv6')))
  122. }
  123. const target = ipv6ToHex(ipAddr)
  124. const start = ipv6ToHex(subnet1Addr)
  125. const end = ipv6ToHex(subnet2Addr)
  126. // 检查IP是否在两个子网之间
  127. if (!((target >= start && target <= end))) {
  128. cb(new Error(this.$t('compute.ipv6_within_range')))
  129. }
  130. cb()
  131. } catch (err) {
  132. cb(new Error(this.$t('compute.error_ipv6')))
  133. }
  134. }
  135. let hasIpv6 = false
  136. let initIpv6Value = ''
  137. let initIpv4Value = ''
  138. if (this.params.data[0].ip6_addr) {
  139. hasIpv6 = true
  140. initIpv6Value = this.getIpv6Value(this.params.data[0].ip6_addr)
  141. }
  142. if (this.params.data[0].ip_addr) {
  143. initIpv4Value = this.params.data[0].ip_addr
  144. }
  145. return {
  146. loading: false,
  147. form: {
  148. fc: this.$form.createForm(this),
  149. fi: {
  150. network: {},
  151. ip: this.params.data[0].ip_addr,
  152. },
  153. },
  154. hasIpv6,
  155. decorators: {
  156. network: [
  157. 'network',
  158. {
  159. initialValue: this.params.data[0].network_id,
  160. rules: [
  161. { required: true, message: this.$t('compute.text_1191') },
  162. ],
  163. },
  164. ],
  165. ip: [
  166. 'ip',
  167. {
  168. initialValue: this.params.data[0].ip_addr,
  169. rules: [
  170. { required: true, message: this.$t('common.tips.select', ['IP']), validator: validateIp },
  171. ],
  172. },
  173. ],
  174. address6: [
  175. 'address6',
  176. {
  177. initialValue: initIpv6Value,
  178. validateFirst: true,
  179. validateTrigger: ['blur', 'change'],
  180. rules: [
  181. {
  182. required: true,
  183. message: this.$t('compute.complete_ipv6_address'),
  184. },
  185. {
  186. validator: validateIpv6,
  187. },
  188. ],
  189. },
  190. ],
  191. restartNetwork: [
  192. 'restartNetwork',
  193. {
  194. valuePropName: 'checked',
  195. initialValue: true,
  196. },
  197. ],
  198. },
  199. formItemLayout: {
  200. wrapperCol: {
  201. span: 21,
  202. },
  203. labelCol: {
  204. span: 3,
  205. },
  206. },
  207. ipv4ConfigShow: !!initIpv4Value,
  208. ipv6ConfigShow: hasIpv6,
  209. ipv6InputShow: hasIpv6,
  210. ipv6Mode: 'all',
  211. }
  212. },
  213. computed: {
  214. ...mapGetters(['scope']),
  215. networkParams () {
  216. const params = {
  217. zone: this.params.zone,
  218. scope: this.scope,
  219. wire: this.params.data[0].wire_id,
  220. filter: 'server_type.notin(ipmi, pxe)',
  221. }
  222. if (this.params.hypervisor === HYPERVISORS_MAP.esxi.key) {
  223. params.vpc_id = 'default'
  224. }
  225. return params
  226. },
  227. columns () {
  228. const showFields = ['ifname', 'ip_addr', 'mac_addr']
  229. return this.params.columns.filter((item) => { return showFields.includes(item.field) })
  230. },
  231. help () {
  232. if (this.params.hypervisor === HYPERVISORS_MAP.esxi.key) {
  233. return this.$t('compute.text_1194')
  234. }
  235. return ''
  236. },
  237. isSupportIPv4 () {
  238. return !!this.form.fi.network.guest_ip_start && !!this.form.fi.network.guest_ip_end
  239. },
  240. isSupportIPv6 () {
  241. return !!this.form.fi.network.guest_ip6_start && !!this.form.fi.network.guest_ip6_end
  242. },
  243. },
  244. methods: {
  245. triggerIpv6Mode (e) {
  246. this.ipv6Mode = e.key
  247. },
  248. networkLabelFormat (item) {
  249. let label = item.name
  250. const details = []
  251. if (item.guest_ip_start && item.guest_ip_end) {
  252. details.push(`${item.guest_ip_start} - ${item.guest_ip_end}/${item.guest_ip_mask}`)
  253. }
  254. if (item.guest_ip6_start && item.guest_ip6_end) {
  255. details.push(`${item.guest_ip6_start} - ${item.guest_ip6_end}/${item.guest_ip6_mask}`)
  256. }
  257. details.push(`vlan=${item.vlan_id}`)
  258. label += `(${details.join(',')})`
  259. return label
  260. },
  261. ipChange (e) {
  262. this.form.fi.ip = e
  263. },
  264. requireIpv6Change (e) {
  265. this.ipv6ConfigShow = e.target.checked
  266. },
  267. triggerShowIpv6 () {
  268. this.ipv6InputShow = !this.ipv6InputShow
  269. },
  270. getIpv6Prefix (ipv6 = '') {
  271. if (ipv6) {
  272. const list = ipaddr.parse(ipv6).toNormalizedString().split(':')
  273. return list.slice(0, 4).join(':') + ':'
  274. }
  275. return ''
  276. },
  277. getIpv6Value (ipv6 = '') {
  278. if (ipv6) {
  279. const list = ipaddr.parse(ipv6).toNormalizedString().split(':')
  280. return list.slice(4, 8).join(':')
  281. }
  282. return ''
  283. },
  284. async handleConfirm () {
  285. this.loading = true
  286. let manager = new this.$Manager('servers')
  287. try {
  288. const values = await this.form.fc.validateFields()
  289. const net_desc = {}
  290. if (values.network) {
  291. net_desc.network = values.network
  292. }
  293. if (values.ip) {
  294. net_desc.address = values.ip
  295. }
  296. if (this.hasIpv6) {
  297. net_desc.require_ipv6 = true
  298. } else {
  299. net_desc.require_ipv6 = false
  300. }
  301. if (!this.isSupportIPv4 && net_desc.require_ipv6) {
  302. net_desc.strict_ipv6 = true
  303. }
  304. const address6 = values.address6
  305. if (address6) {
  306. const ipv6Last = address6
  307. const target = this.form.fi.network
  308. const ipv6First = getIpv6Start(target?.guest_ip6_start)
  309. net_desc.address6 = ipv6First + ipv6Last
  310. }
  311. if (!net_desc.address6 && net_desc.require_ipv6) {
  312. net_desc.address6 = ''
  313. }
  314. if (this.ipv6Mode === 'only' && net_desc.require_ipv6) {
  315. net_desc.strict_ipv6 = true
  316. }
  317. const data = {
  318. ip_addr: this.params.data[0].ip_addr,
  319. net_desc,
  320. restart_network: values.restartNetwork,
  321. }
  322. await manager.performAction({
  323. id: this.params.resId,
  324. action: 'change-ipaddr',
  325. data,
  326. })
  327. this.params.refresh()
  328. this.$bus.$emit('VMInstanceListSingleRefresh', [this.params.resId, Object.values(expectStatus.server).flat()])
  329. this.cancelDialog()
  330. this.$message.success(this.$t('compute.text_423'))
  331. } finally {
  332. this.loading = false
  333. manager = null
  334. }
  335. },
  336. mapper (list) {
  337. return list.sort((a, b) => { return (b.ports - b.ports_used) - (a.ports - a.ports_used) })
  338. },
  339. },
  340. }
  341. </script>