Create.vue 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147
  1. <template>
  2. <div>
  3. <page-header :title="$t('network.text_570')" :tabs="cloudEnvOptions" :current-tab.sync="cloudEnv" />
  4. <page-body need-margin-bottom>
  5. <a-form class="mt-3" :form="form.fc" @submit.prevent="handleSubmit" v-bind="formItemLayout" hideRequiredMark>
  6. <a-form-item :label="$t('network.text_205', [$t('dictionary.project')])" class="mt-3" v-bind="formItemLayout">
  7. <domain-project :fc="form.fc" :decorators="{ project: decorators.project, domain: decorators.domain }" @update:domain="handleDomainChange" />
  8. </a-form-item>
  9. <area-selects
  10. class="mb-0"
  11. ref="areaSelects"
  12. :wrapperCol="formItemLayout.wrapperCol"
  13. :labelCol="formItemLayout.labelCol"
  14. :names="areaselectsName"
  15. :providerParams="providerParams"
  16. :cloudregionParams="cloudregionParams"
  17. :isRequired="true"
  18. filterBrandResource="network_manage"
  19. @change="handleRegionChange" />
  20. <a-form-item :label="$t('network.text_21')" v-bind="formItemLayout">
  21. <a-input v-decorator="decorators.name" :placeholder="$t('validator.resourceName')" />
  22. </a-form-item>
  23. <a-form-item :label="$t('common.description')" v-bind="formItemLayout">
  24. <a-textarea :auto-size="{ minRows: 1, maxRows: 3 }" v-decorator="decorators.description" :placeholder="$t('common_367')" />
  25. </a-form-item>
  26. <a-form-item label="VPC" v-bind="formItemLayout">
  27. <base-select
  28. v-decorator="decorators.vpc"
  29. resource="vpcs"
  30. :params="vpcParams"
  31. :isDefaultSelect="true"
  32. :needParams="true"
  33. @change="vpcChange"
  34. :item.sync="curVpc"
  35. :labelFormat="vpcLabelFormat"
  36. :select-props="{ placeholder: $t('common_226') }" />
  37. </a-form-item>
  38. <a-form-item :label="$t('network.text_571')" v-bind="formItemLayout" v-if="show || isShowWire">
  39. <base-select
  40. resource="wires"
  41. v-decorator="decorators.wire"
  42. :selectProps="{ 'placeholder': $t('network.text_572') }"
  43. :isDefaultSelect="true"
  44. :item.sync="curWire"
  45. :labelFormat="wireLabelFormat"
  46. :params="wireParams" />
  47. </a-form-item>
  48. <a-form-item :label="$t('network.text_24')" :extra="$t('network.text_573')" v-bind="formItemLayout" v-if="!show && !isShowWire">
  49. <a-select v-decorator="decorators.zone" :placeholder="$t('network.text_287')" :filterOption="filterOption" show-search>
  50. <a-select-option v-for="item in zoneList" :key="item.id" :value="item.id">{{item.name}}</a-select-option>
  51. </a-select>
  52. </a-form-item>
  53. <a-form-item :label="$t('network.text_574')" v-bind="formItemLayout" v-if="show">
  54. <a-radio-group v-decorator="decorators.server_type">
  55. <a-radio-button
  56. v-for="item of serverTypeOpts"
  57. :disabled="item.disabled"
  58. :key="item.key"
  59. :value="item.key">{{ item.label }}</a-radio-button>
  60. </a-radio-group>
  61. </a-form-item>
  62. <!-- 网段 -->
  63. <a-form-item :label="$t('network.text_575')" v-bind="formItemLayout" required v-if="show">
  64. <template slot="extra">
  65. <div>{{$t('network.text_576')}}</div>
  66. <div>{{$t('network.text_577')}}</div>
  67. </template>
  68. <ip-subnets :decorator="decorators.ipSubnets" @clear-error="clearIpSubnetsError" />
  69. <div v-if="ipSubnetsHelp" class="error-tips">{{ ipSubnetsHelp }}</div>
  70. </a-form-item>
  71. <!-- 输入 网段 -->
  72. <a-form-item :label="$t('network.text_575')" :extra="$t('network.prefix_in_cidr_range.prompt', [curVpcCidrText])" v-bind="formItemLayout" v-if="!show && !isGroupGuestIpPrefix">
  73. <a-row :gutter="8">
  74. <a-col :span="12" v-if="curVpc && curVpc.cidr_block">
  75. <a-form-item class="mb-0">
  76. <a-input v-decorator="decorators.guest_ip_prefix(0)" :placeholder="$t('network.ipv4.prefix.prompt')" />
  77. </a-form-item>
  78. </a-col>
  79. <a-col :span="12" v-if="curVpc && curVpc.cidr_block6">
  80. <a-form-item class="mb-0">
  81. <a-input v-decorator="decorators.guest_ip6_prefix(0)" :placeholder="$t('network.ipv6.prefix.prompt')" />
  82. </a-form-item>
  83. </a-col>
  84. </a-row>
  85. <div v-if="guestIpPrefixHelp" class="error-tips">{{ guestIpPrefixHelp }}</div>
  86. </a-form-item>
  87. <a-form-item :label="$t('network.text_575')" v-bind="formItemLayout" :validate-status="guestIpPrefixValidateStatus" :help="guestIpPrefixHelp" required v-if="isGroupGuestIpPrefix">
  88. <template slot="extra">
  89. <div>{{$t('network.text_578')}}</div>
  90. <div>{{$t('network.text_580')}}</div>
  91. </template>
  92. <!-- 网段 -->
  93. <a-row :gutter="8" v-for="(item, i) in guestIpPrefix" :key="item.key">
  94. <a-col :span="11">
  95. <a-form-item>
  96. <a-input v-decorator="decorators.guest_ip_prefix(i)" :placeholder="$t('network.ipv4.subnet.input.prompt')" />
  97. </a-form-item>
  98. </a-col>
  99. <a-col :span="11">
  100. <a-form-item>
  101. <a-input v-decorator="decorators.guest_ip6_prefix(i)" :placeholder="$t('network.ipv6.subnet.input.prompt')" />
  102. </a-form-item>
  103. </a-col>
  104. <a-col :span="2">
  105. <a-button shape="circle" icon="minus" size="small" v-if="guestIpPrefix.length > 1" @click="decrease(i)" class="mt-2 ml-2" />
  106. </a-col>
  107. </a-row>
  108. <div class="d-flex align-items-center" v-if="remain > 0">
  109. <a-button type="primary" shape="circle" icon="plus" size="small" @click="addGuestIpPrefix" />
  110. <a-button type="link" @click="addGuestIpPrefix">{{$t('network.text_582')}}</a-button>
  111. <span class="count-tips">{{$t('network.text_169')}}<span class="remain-num">{{ remain }}</span>{{$t('network.text_170')}}</span>
  112. </div>
  113. </a-form-item>
  114. <a-form-item :label="$t('common_498')" v-if="isShowIsAutoAlloc">
  115. <a-switch v-decorator="decorators.is_auto_alloc" />
  116. <template slot="extra">{{$t('common_500')}}</template>
  117. </a-form-item>
  118. <a-form-item :label="$t('common.text00012')" class="mb-0">
  119. <tag
  120. v-decorator="decorators.__meta__" />
  121. </a-form-item>
  122. <a-collapse :bordered="false" v-if="isShowAdvanceOptions">
  123. <a-collapse-panel :header="$t('network.text_94')" key="1" forceRender>
  124. <a-form-item :label="$t('network.text_743')" v-bind="formItemLayout" v-if="hasBgpType">
  125. <a-input v-decorator="decorators.bgp_type" />
  126. <span slot="extra">{{$t('network.text_744')}}</span>
  127. </a-form-item>
  128. <a-form-item v-bind="formItemLayout">
  129. <span slot="label">{{$t('network.text_583')}}</span>
  130. <a-radio-group v-decorator="decorators.alloc_policy">
  131. <a-radio-button
  132. v-for="item of allocPolicyoptions"
  133. :key="item.key"
  134. :value="item.key">{{ item.label }}</a-radio-button>
  135. </a-radio-group>
  136. <span slot="extra" v-if="form.fc.getFieldValue('alloc_policy') === 'none'">{{$t('network.text_584')}}</span>
  137. </a-form-item>
  138. <a-form-item :label="$t('network.dns_server')" v-bind="formItemLayout">
  139. <a-input :placeholder="$t('validator.IPs')" v-decorator="decorators.guest_dns" />
  140. </a-form-item>
  141. <a-form-item v-bind="formItemLayout">
  142. <span slot="label">{{$t('network.text_586')}}</span>
  143. <template slot="extra">
  144. <div>{{$t('network.text_587')}}</div>
  145. <div>{{$t('network.text_588')}}</div>
  146. <div>{{$t('network.text_589')}}</div>
  147. <div>{{$t('network.text_590')}}</div>
  148. </template>
  149. <a-input :placeholder="$t('validator.domain')" v-decorator="decorators.guest_domain" />
  150. </a-form-item>
  151. <a-form-item :label="$t('network.ntp_server')" v-bind="formItemLayout">
  152. <a-input :placeholder="$t('validator.IPs_or_domains')" v-decorator="decorators.guest_ntp" />
  153. </a-form-item>
  154. <a-form-item label="dhcp_relay" v-if="show">
  155. <a-input class="w-50" v-decorator="decorators.guest_dhcp" :placeholder="$t('validator.IPs')" />
  156. </a-form-item>
  157. </a-collapse-panel>
  158. </a-collapse>
  159. <page-footer>
  160. <template v-slot:right>
  161. <a-button type="primary" html-type="submit" class="ml-3" :loading="submiting">{{$t('network.text_30')}}</a-button>
  162. <a-button class="ml-3" @click="() => $router.back()">{{$t('common.cancel')}}</a-button>
  163. </template>
  164. </page-footer>
  165. </a-form>
  166. </page-body>
  167. </div>
  168. </template>
  169. <script>
  170. import * as R from 'ramda'
  171. import { mapGetters } from 'vuex'
  172. import validateForm, { isRequired, REGEXP } from '@/utils/validate'
  173. import { Manager } from '@/utils/manager'
  174. import { uuid } from '@/utils/utils'
  175. import { typeClouds, getCloudEnvOptions } from '@/utils/common/hypervisor'
  176. import DomainProject from '@/sections/DomainProject'
  177. import AreaSelects from '@/sections/AreaSelects'
  178. import i18n from '@/locales'
  179. import { HYPERVISORS_MAP } from '@/constants'
  180. import Tag from '@/sections/Tag'
  181. import IpSubnets from './components/IpSubnets'
  182. const { networkSegment, networkSegment6 } = REGEXP
  183. const masks = {
  184. azure: { min: 8, max: 29 },
  185. qcloud: { min: 16, max: 28 },
  186. aliyun: { min: 17, max: 29 },
  187. aws: { min: 16, max: 28 },
  188. ucloud: { min: 16, max: 29 },
  189. huawei: { min: 16, max: 29 },
  190. onecloud: undefined,
  191. vmware: undefined,
  192. baremetal: undefined,
  193. openstack: undefined,
  194. zstack: undefined,
  195. dstack: undefined,
  196. }
  197. function validateGateway (rule, value, callback) {
  198. if (!value) {
  199. return callback()
  200. }
  201. // 只需要查看是否是以 0 结尾
  202. const ipItems = value.split('.')
  203. const msg = i18n.t('network.text_591')
  204. if (ipItems[ipItems.length - 1] === '0') {
  205. return callback(msg)
  206. }
  207. return callback()
  208. }
  209. function validateGateway6 (rule, value, callback) {
  210. if (!value) {
  211. return callback()
  212. }
  213. // 只需要查看是否是以 0 结尾
  214. const ipItems = value.split(':')
  215. const msg = i18n.t('network.text_591')
  216. if (ipItems[ipItems.length - 1] === '0') {
  217. return callback(msg)
  218. }
  219. return callback()
  220. }
  221. export default {
  222. name: 'NetworkCreate',
  223. components: {
  224. IpSubnets,
  225. DomainProject,
  226. AreaSelects,
  227. Tag,
  228. },
  229. data () {
  230. const cloudEnvOptions = getCloudEnvOptions('network_manage_brands', true)
  231. const queryType = this.$route.query.type
  232. let cloudEnv = queryType === 'idc' ? 'onpremise' : this.$route.query.type
  233. let routerQuery = this.$route.query.type
  234. if (!cloudEnvOptions.find(val => val.key === cloudEnv)) {
  235. cloudEnv = cloudEnvOptions[0].key
  236. routerQuery = cloudEnv === 'onpremise' ? 'idc' : cloudEnv
  237. }
  238. const prefixKey = uuid()
  239. return {
  240. submiting: false,
  241. cloudEnvOptions,
  242. cloudEnv,
  243. routerQuery,
  244. form: {
  245. fc: this.$form.createForm(this, { onValuesChange: this.handleValuesChange }),
  246. fd: {
  247. guest_ip_prefix: [],
  248. guest_ip6_prefix: [],
  249. },
  250. },
  251. ipSubnetsValidateStatus: '',
  252. guestIpPrefixValidateStatus: '',
  253. ipSubnetsHelp: '',
  254. guestIpPrefixHelp: '',
  255. formItemLayout: {
  256. wrapperCol: {
  257. md: { span: 18 },
  258. xl: { span: 20 },
  259. xxl: { span: 22 },
  260. },
  261. labelCol: {
  262. md: { span: 6 },
  263. xl: { span: 4 },
  264. xxl: { span: 2 },
  265. },
  266. },
  267. decorators: {
  268. domain: [
  269. 'domain',
  270. {
  271. rules: [
  272. { validator: isRequired(), message: i18n.t('rules.domain'), trigger: 'change' },
  273. ],
  274. },
  275. ],
  276. project: [
  277. 'project',
  278. {
  279. rules: [
  280. { validator: isRequired(), message: i18n.t('rules.project'), trigger: 'change' },
  281. ],
  282. },
  283. ],
  284. name: [
  285. 'name',
  286. {
  287. initialValue: '',
  288. validateFirst: true,
  289. rules: [
  290. { required: true, message: this.$t('network.text_116') },
  291. { validator: this.$validate('resourceName') },
  292. ],
  293. },
  294. ],
  295. description: ['description'],
  296. vpc: [
  297. 'vpc',
  298. {
  299. rules: [
  300. { required: true, message: this.$t('network.text_274') },
  301. ],
  302. },
  303. ],
  304. wire: [
  305. 'wire',
  306. {
  307. rules: [
  308. { required: true, message: this.$t('network.text_572') },
  309. ],
  310. },
  311. ],
  312. zone: [
  313. 'zone',
  314. {
  315. rules: [
  316. { required: true, message: this.$t('network.text_287') },
  317. ],
  318. },
  319. ],
  320. server_type: [
  321. 'server_type',
  322. {
  323. initialValue: '',
  324. rules: [
  325. { required: true, message: this.$t('network.text_592') },
  326. ],
  327. },
  328. ],
  329. alloc_policy: [
  330. 'alloc_policy',
  331. {
  332. initialValue: 'none',
  333. },
  334. ],
  335. guest_dns: [
  336. 'guest_dns',
  337. {
  338. initialValue: '',
  339. validateTrigger: ['change', 'blur'],
  340. rules: [
  341. { validator: this.$validate('IPs', false) },
  342. ],
  343. },
  344. ],
  345. guest_domain: [
  346. 'guest_domain',
  347. {
  348. initialValue: '',
  349. rules: [
  350. { validator: this.$validate('domain', false) },
  351. ],
  352. },
  353. ],
  354. guest_ntp: [
  355. 'guest_ntp',
  356. {
  357. initialValue: '',
  358. validateTrigger: ['change', 'blur'],
  359. rules: [
  360. { validator: this.$validate('IPs_or_domains', false) },
  361. ],
  362. },
  363. ],
  364. ipSubnets: {
  365. startip: i => [
  366. `startip[${i}]`,
  367. {
  368. initialValue: '',
  369. validateFirst: true,
  370. rules: [
  371. // { required: true, message: this.$t('network.text_593') },
  372. { validator: this.validateIpv4 },
  373. ],
  374. },
  375. ],
  376. endip: i => [
  377. `endip[${i}]`,
  378. {
  379. initialValue: '',
  380. validateFirst: true,
  381. rules: [
  382. // { required: true, message: this.$t('network.text_594') },
  383. { validator: this.validateIpv4 },
  384. ],
  385. },
  386. ],
  387. netmask: i => [
  388. `netmask[${i}]`,
  389. {
  390. initialValue: '24',
  391. rules: [
  392. { required: true, message: this.$t('network.text_595') },
  393. ],
  394. },
  395. ],
  396. gateway: i => [
  397. `gateway[${i}]`,
  398. {
  399. initialValue: '',
  400. validateTrigger: ['change', 'blur'],
  401. validateFirst: true,
  402. rules: [
  403. { validator: this.validateIpv4 },
  404. { validator: validateGateway },
  405. ],
  406. },
  407. ],
  408. startip6: i => [
  409. `startip6[${i}]`,
  410. {
  411. initialValue: '',
  412. validateFirst: true,
  413. rules: [
  414. { validator: this.validateIpv6 },
  415. ],
  416. },
  417. ],
  418. endip6: i => [
  419. `endip6[${i}]`,
  420. {
  421. initialValue: '',
  422. validateFirst: true,
  423. rules: [
  424. { validator: this.validateIpv6 },
  425. ],
  426. },
  427. ],
  428. netmask6: i => [
  429. `netmask6[${i}]`,
  430. {
  431. initialValue: '64',
  432. rules: [
  433. { required: true, message: this.$t('network.text_595') },
  434. ],
  435. },
  436. ],
  437. gateway6: i => [
  438. `gateway6[${i}]`,
  439. {
  440. initialValue: '',
  441. validateTrigger: ['change', 'blur'],
  442. validateFirst: true,
  443. rules: [
  444. { validator: this.validateIpv6 },
  445. { validator: validateGateway6 },
  446. ],
  447. },
  448. ],
  449. vlan: i => [
  450. `vlan[${i}]`,
  451. {
  452. initialValue: '',
  453. },
  454. ],
  455. },
  456. guest_ip_prefix: i => [
  457. `guest_ip_prefix[${i}]`,
  458. {
  459. initialValue: '',
  460. validateFirst: true,
  461. rules: [
  462. { validator: this.validatePublicIpPrefix },
  463. ],
  464. },
  465. ],
  466. guest_ip6_prefix: i => [
  467. `guest_ip6_prefix[${i}]`,
  468. {
  469. initialValue: '',
  470. validateFirst: true,
  471. rules: [
  472. { validator: this.validatePublicIpPrefix6 },
  473. ],
  474. },
  475. ],
  476. guest_dhcp: [
  477. 'guest_dhcp',
  478. {
  479. validateFirst: true,
  480. rules: [
  481. { validator: this.$validate('IPs', false) },
  482. ],
  483. },
  484. ],
  485. is_auto_alloc: ['is_auto_alloc', {
  486. valuePropName: 'checked',
  487. }],
  488. bgp_type: [
  489. 'bgp_type',
  490. ],
  491. __meta__: [
  492. '__meta__',
  493. {
  494. rules: [
  495. { validator: validateForm('tagName') },
  496. ],
  497. },
  498. ],
  499. },
  500. allocPolicyoptions: [
  501. { label: this.$t('network.text_600'), key: 'none' },
  502. { label: this.$t('network.text_601'), key: 'stepdown' },
  503. { label: this.$t('network.text_602'), key: 'stepup' },
  504. { label: this.$t('network.text_603'), key: 'random' },
  505. ],
  506. isShowWire: true,
  507. isGroupGuestIpPrefix: false,
  508. show: true,
  509. regionProvider: '',
  510. regionId: '',
  511. guestIpPrefix: [{ key: prefixKey }],
  512. guestIp6Prefix: [{ key: prefixKey }],
  513. zoneList: [],
  514. project_domain: '',
  515. vpcId: '',
  516. curVpc: null,
  517. curWire: null,
  518. }
  519. },
  520. computed: {
  521. ...mapGetters(['isAdminMode', 'scope', 'userInfo']),
  522. // 是否显示加入自动分配地址池
  523. isShowIsAutoAlloc () {
  524. const { vpc, server_type } = this.form.fd
  525. if (this.cloudEnv === 'onpremise' && vpc === 'default') {
  526. return ['guest', undefined].includes(server_type)
  527. }
  528. return true
  529. },
  530. // 是否显示高级配置
  531. isShowAdvanceOptions () {
  532. return this.cloudEnv === 'onpremise'
  533. },
  534. remain () {
  535. const remain = 6 - this.guestIpPrefix.length
  536. return Math.max(remain, 0)
  537. },
  538. vpcParams () {
  539. const params = {
  540. limit: 0,
  541. usable_vpc: true,
  542. scope: this.scope,
  543. cloudregion_id: this.regionId,
  544. }
  545. if (this.cloudEnv === 'private') {
  546. params.show_emulated = true
  547. }
  548. if (this.isAdminMode) {
  549. params.project_domain = this.project_domain
  550. delete params.scope
  551. }
  552. if (!this.regionId) return {}
  553. return params
  554. },
  555. cloudregionParams () {
  556. const params = {
  557. scope: this.scope,
  558. limit: 0,
  559. is_on_premise: true,
  560. // usable_vpc: true,
  561. show_emulated: true,
  562. usable: false,
  563. }
  564. if (this.cloudEnv === 'private') {
  565. params.is_private = true
  566. delete params.is_public
  567. delete params.is_on_premise
  568. } else if (this.cloudEnv === 'public') {
  569. params.is_public = true
  570. delete params.is_private
  571. delete params.is_on_premise
  572. } else {
  573. params.is_on_premise = true
  574. delete params.is_private
  575. delete params.is_public
  576. }
  577. if (this.isAdminMode) {
  578. params.project_domain = this.project_domain
  579. delete params.scope
  580. }
  581. return params
  582. },
  583. wireParams () {
  584. const params = {
  585. scope: this.scope,
  586. vpcId: this.vpcId,
  587. }
  588. if (this.isAdminMode) {
  589. params.project_domain = this.project_domain
  590. delete params.scope
  591. }
  592. return params
  593. },
  594. zoneParams () {
  595. const params = {
  596. scope: this.scope,
  597. show_emulated: true,
  598. 'filter.0': 'status.equals(enable)',
  599. order: 'asc',
  600. order_by: 'external_id',
  601. }
  602. if (this.isAdminMode) {
  603. params.project_domain = this.project_domain
  604. delete params.scope
  605. }
  606. return params
  607. },
  608. areaselectsName () {
  609. if (this.cloudEnv === 'private' || this.cloudEnv === 'onpremise') {
  610. return ['cloudregion']
  611. }
  612. return ['provider', 'cloudregion']
  613. },
  614. providerParams () {
  615. return {
  616. cloud_env: 'public',
  617. usable: false,
  618. usable_vpc: true,
  619. project_domain: this.form.fd.domain?.key,
  620. filter: 'provider.notequals("Google")',
  621. }
  622. },
  623. curVpcCidrText () {
  624. const cidrs = []
  625. if (this.curVpc?.cidr_block) {
  626. cidrs.push(this.curVpc.cidr_block)
  627. }
  628. if (this.curVpc?.cidr_block6) {
  629. cidrs.push(this.curVpc.cidr_block6)
  630. }
  631. return cidrs.join(', ')
  632. },
  633. hasBgpType () {
  634. return this.form.fd.server_type === 'eip' || this.form.fd.server_type === 'guest' || this.form.fd.server_type === 'baremetal' || this.form.fd.server_type === 'hostlocal'
  635. },
  636. serverTypeOpts () {
  637. const opts = [
  638. { label: this.$t('network.text_226'), key: 'guest', disabled: false },
  639. { label: this.$t('network.text_598'), key: 'baremetal', disabled: false },
  640. // { label: this.$t('network.text_599'), key: 'container' },
  641. { label: 'PXE', key: 'pxe', disabled: false },
  642. { label: 'IPMI', key: 'ipmi', disabled: false },
  643. { label: this.$t('network.text_221'), key: 'eip', disabled: false },
  644. { label: this.$t('network.server_type.hostlocal.text'), key: 'hostlocal', disabled: false },
  645. ]
  646. const isHostLocalWire = this.curWire?.id === '__host_local__'
  647. for (let i = 0; i < opts.length; i++) {
  648. if (opts[i].key === 'hostlocal') {
  649. if (isHostLocalWire) {
  650. opts[i].disabled = false
  651. } else {
  652. opts[i].disabled = true
  653. }
  654. } else {
  655. if (isHostLocalWire) {
  656. opts[i].disabled = true
  657. } else {
  658. opts[i].disabled = false
  659. }
  660. }
  661. }
  662. return opts
  663. },
  664. },
  665. provide () {
  666. return {
  667. form: this.form,
  668. }
  669. },
  670. watch: {
  671. cloudEnv (newValue) {
  672. this.$refs.areaSelects.fetchs(this.areaselectsName)
  673. if (newValue === 'private') {
  674. this.show = false
  675. this.isGroupGuestIpPrefix = false
  676. } else if (newValue === 'public') {
  677. this.show = false
  678. this.isGroupGuestIpPrefix = false
  679. } else {
  680. this.show = true
  681. this.isGroupGuestIpPrefix = true
  682. }
  683. },
  684. 'form.fd.guest_ip_prefix': {
  685. handler (newValue) {
  686. if (newValue.filter(item => item).length > 0) {
  687. this.guestIpPrefixHelp = ''
  688. }
  689. },
  690. },
  691. 'form.fd.guest_ip6_prefix': {
  692. handler (newValue) {
  693. if (newValue.filter(item => item).length > 0) {
  694. this.guestIpPrefixHelp = ''
  695. }
  696. },
  697. },
  698. 'form.fd.vpc': {
  699. handler (newValue) {
  700. if (newValue === 'default') {
  701. this.$nextTick(() => {
  702. if (this.curWire?.id === '__host_local__') { // 如果当前选择的网线是主机本地网线,则设置服务器类型为主机本地
  703. this.form.fc.setFieldsValue({
  704. server_type: 'hostlocal',
  705. })
  706. } else { // 如果当前选择的网线不是主机本地网线,则设置服务器类型为虚拟机
  707. this.form.fc.setFieldsValue({
  708. server_type: 'guest',
  709. })
  710. }
  711. })
  712. }
  713. },
  714. },
  715. 'form.fd.wire': {
  716. handler (newValue) {
  717. console.log('newValue', newValue)
  718. if (newValue === '__host_local__') {
  719. this.$nextTick(() => {
  720. this.form.fc.setFieldsValue({
  721. server_type: 'hostlocal',
  722. })
  723. })
  724. } else {
  725. this.$nextTick(() => {
  726. this.form.fc.setFieldsValue({
  727. server_type: 'guest',
  728. })
  729. })
  730. }
  731. },
  732. },
  733. },
  734. created () {
  735. this.initState()
  736. },
  737. methods: {
  738. validateIpv4 (rule, value, callback) {
  739. if (!value) {
  740. callback()
  741. } else if (!REGEXP.IPv4.regexp.test(value)) {
  742. callback(new Error(this.$t('common.tips.input', ['IPv4'])))
  743. }
  744. callback()
  745. },
  746. validateIpv6 (rule, value, callback) {
  747. if (!value) {
  748. callback()
  749. } else if (!REGEXP.IPv6.regexp.test(value)) {
  750. callback(new Error(this.$t('common.tips.input', ['IPv6'])))
  751. }
  752. callback()
  753. },
  754. validateDhcpRelay (rule, value, callback) {
  755. if (!value) {
  756. callback()
  757. } else if (!REGEXP.IPv4s.regexp.test(value)) {
  758. callback(new Error(this.$t('common.tips.input', ['IPv4'])))
  759. }
  760. callback()
  761. },
  762. validateDhcpRelay6 (rule, value, callback) {
  763. if (!value) {
  764. callback()
  765. } else if (!REGEXP.IPv6s.regexp.test(value)) {
  766. callback(new Error(this.$t('common.tips.input', ['IPv6'])))
  767. }
  768. callback()
  769. },
  770. initState () {
  771. if (this.cloudEnv === 'private') {
  772. this.show = false
  773. this.isGroupGuestIpPrefix = false
  774. } else if (this.cloudEnv === 'public') {
  775. this.show = false
  776. this.isGroupGuestIpPrefix = false
  777. } else {
  778. this.show = true
  779. this.isGroupGuestIpPrefix = true
  780. }
  781. },
  782. handleValuesChange (props, values) {
  783. this.form.fd = {
  784. ...this.form.fd,
  785. ...values,
  786. }
  787. },
  788. wireLabelFormat (item) {
  789. if (item) {
  790. const { name, zone } = item
  791. return (
  792. <div class='d-flex'>
  793. <span class='text-truncate flex-fill mr-2' title={ name }>{ name }</span>
  794. <span style="color: #8492a6; font-size: 13px">可用区:{zone}</span>
  795. </div>
  796. )
  797. }
  798. return null
  799. },
  800. handleDomainChange (val) {
  801. this.project_domain = val.key
  802. },
  803. addGuestIpPrefix () {
  804. this.clearGuestIpPrefixError()
  805. const key = uuid()
  806. this.guestIpPrefix.push({
  807. key,
  808. })
  809. this.guestIp6Prefix.push({
  810. key,
  811. })
  812. },
  813. decrease (index) {
  814. this.guestIpPrefix.splice(index, 1)
  815. this.guestIp6Prefix.splice(index, 1)
  816. },
  817. handleRegionChange (data) {
  818. const hasCloudRegion = R.has('cloudregion')(data)
  819. if (hasCloudRegion && !R.isEmpty(data.cloudregion)) {
  820. this.fetchZone(data.cloudregion.id)
  821. } else {
  822. this.zoneList = []
  823. this.form.fc.resetFields(['zone'])
  824. return
  825. }
  826. const { provider } = data.cloudregion.value
  827. this.regionProvider = provider
  828. this.regionId = data.cloudregion.id
  829. if (provider === typeClouds.providerMap.ZStack.key || provider === typeClouds.providerMap.CNware.key) {
  830. this.isShowWire = true
  831. } else {
  832. this.isShowWire = false
  833. }
  834. },
  835. vpcChange (vpcId) {
  836. this.vpcId = vpcId
  837. this.show = false
  838. if (this.cloudEnv === 'onpremise') {
  839. if (vpcId !== 'default') { // vpc network
  840. this.isGroupGuestIpPrefix = true
  841. this.show = false
  842. } else { // classic network
  843. this.isGroupGuestIpPrefix = false
  844. this.show = true
  845. this.form.fc.setFieldsValue({
  846. server_type: 'guest',
  847. })
  848. }
  849. }
  850. if (this.cloudEnv === 'private' && this.curVpc?.brand === 'Cloudpods' && this.curVpc?.external_id === 'default') {
  851. this.isShowWire = true
  852. this.isGroupGuestIpPrefix = true
  853. } else {
  854. this.isShowWire = false
  855. this.isGroupGuestIpPrefix = false
  856. }
  857. },
  858. vpcLabelFormat (item) {
  859. if (this.cloudEnv === 'public' || this.regionProvider === HYPERVISORS_MAP.hcso.provider || this.regionProvider === HYPERVISORS_MAP.hcs.provider) {
  860. if (item.manager) {
  861. if (item.cidr_block || item.cidr_block6) {
  862. return (<div>{ item.name }<span v-if="item.cidr_block">({ item.cidr_block })</span><span v-if="item.cidr_block6">({ item.cidr_block6 })</span><span class="ml-2 text-color-secondary">{ this.$t('common.cloudprovider_1var', [item.manager]) }</span></div>)
  863. }
  864. return (<div>{ item.name }<span class="ml-2 text-color-secondary">{ this.$t('common.cloudprovider_1var', [item.manager]) }</span></div>)
  865. }
  866. } else if (this.cloudEnv === 'onpremise') {
  867. if (item.cidr_block || item.cidr_block6) {
  868. const cidrs = []
  869. if (item.cidr_block) {
  870. cidrs.push(item.cidr_block)
  871. }
  872. if (item.cidr_block6) {
  873. cidrs.push(item.cidr_block6)
  874. }
  875. return (<div>{ item.name } ({ cidrs.join(', ') })</div>)
  876. }
  877. if (item.id === 'default') return (<div>{ item.name }<span v-if="item.cidr_block">({this.$t('common.text00047')})</span></div>)
  878. }
  879. return (<div>{ item.name }</div>)
  880. },
  881. validatePublicIpPrefix (rule, value, callback) {
  882. if (value) {
  883. if (!networkSegment.regexp.test(value)) {
  884. callback(new Error(networkSegment.message))
  885. }
  886. const maskNum = (value && value.split('/').length > 1) ? value.split('/')[1] : null
  887. const publicWire = this.form.fc.getFieldValue('cloudregion')
  888. if (maskNum && publicWire && publicWire.provider) {
  889. const provider = publicWire.provider.toLowerCase()
  890. if (masks[provider]) {
  891. const min = masks[provider].min
  892. const max = masks[provider].max
  893. if (masks[provider] && (maskNum < min || maskNum > max)) {
  894. callback(new Error(this.$t('network.ipaddr.mask.error', [min, max])))
  895. }
  896. }
  897. }
  898. }
  899. callback()
  900. },
  901. validatePublicIpPrefix6 (rule, value, callback) {
  902. if (value) {
  903. if (!networkSegment6.regexp.test(value)) {
  904. callback(new Error(networkSegment6.message))
  905. return
  906. }
  907. const maskNum = (value && value.split('/').length > 1) ? value.split('/')[1] : null
  908. const min = 64
  909. const max = 124
  910. if (maskNum < min || maskNum > max) {
  911. callback(new Error(this.$t('network.ipaddr.mask.error', [min, max])))
  912. return
  913. }
  914. }
  915. callback()
  916. },
  917. fetchZone (regionId) {
  918. new this.$Manager('cloudregions')
  919. .getSpecific({ id: regionId, spec: 'zones', params: this.zoneParams })
  920. .then(({ data: { data = [] } }) => {
  921. this.form.fc.resetFields(['zone'])
  922. this.zoneList = data
  923. })
  924. },
  925. genData (values) {
  926. const guest_dhcp = values.guest_dhcp
  927. if (this.cloudEnv === 'onpremise') {
  928. const data = []
  929. if (this.isGroupGuestIpPrefix) {
  930. R.forEachObjIndexed((value, key) => {
  931. const obj = {
  932. alloc_policy: values.alloc_policy,
  933. guest_dns: values.guest_dns,
  934. guest_domain: values.guest_domain,
  935. guest_ntp: values.guest_ntp,
  936. guest_ip_prefix: value,
  937. guest_ip6_prefix: values.guest_ip6_prefix && values.guest_ip6_prefix[key],
  938. name: values.name,
  939. description: values.description,
  940. vpc: values.vpc,
  941. zone: values.zone,
  942. project_id: values.project?.key,
  943. is_auto_alloc: values.is_auto_alloc,
  944. guest_dhcp,
  945. __meta__: values.__meta__,
  946. }
  947. data.push(obj)
  948. }, values.guest_ip_prefix)
  949. } else if (this.show) {
  950. R.forEachObjIndexed((value, key) => {
  951. const obj = {
  952. bgp_type: values.bgp_type,
  953. alloc_policy: values.alloc_policy,
  954. guest_dns: values.guest_dns,
  955. guest_domain: values.guest_domain,
  956. guest_ntp: values.guest_ntp,
  957. guest_ip_start: values.startip[key],
  958. guest_ip_end: values.endip[key],
  959. guest_ip_mask: values.netmask[key],
  960. guest_gateway: values.gateway[key],
  961. guest_ip6_start: values.startip6 && values.startip6[key],
  962. guest_ip6_end: values.endip6 && values.endip6[key],
  963. guest_ip6_mask: values.netmask6 && values.netmask6[key],
  964. guest_gateway6: values.gateway6 && values.gateway6[key],
  965. vlan_id: values.vlan[key] === '' ? '1' : values.vlan[key],
  966. name: values.name,
  967. description: values.description,
  968. project_id: values.project?.key,
  969. server_type: values.server_type,
  970. wire_id: values.wire,
  971. is_auto_alloc: values.is_auto_alloc,
  972. guest_dhcp,
  973. __meta__: values.__meta__,
  974. }
  975. data.push(obj)
  976. }, values.startip)
  977. } else {
  978. var ipPrefix = null
  979. var ip6Prefix = null
  980. if (values.guest_ip_prefix) {
  981. ipPrefix = values.guest_ip_prefix[0]
  982. if (values.guest_ip6_prefix) {
  983. ip6Prefix = values.guest_ip6_prefix[0]
  984. }
  985. } else if (values.guest_ip6_prefix) {
  986. ip6Prefix = values.guest_ip6_prefix[0]
  987. }
  988. const obj = {
  989. alloc_policy: values.alloc_policy,
  990. guest_dns: values.guest_dns,
  991. guest_domain: values.guest_domain,
  992. guest_ntp: values.guest_ntp,
  993. name: values.name,
  994. description: values.description,
  995. vpc: values.vpc,
  996. zone: values.zone,
  997. project_id: values.project?.key,
  998. is_auto_alloc: values.is_auto_alloc,
  999. guest_dhcp,
  1000. __meta__: values.__meta__,
  1001. }
  1002. if (ipPrefix) {
  1003. obj.guest_ip_prefix = ipPrefix
  1004. }
  1005. if (ip6Prefix) {
  1006. obj.guest_ip6_prefix = ip6Prefix
  1007. }
  1008. data.push(obj)
  1009. }
  1010. return data
  1011. }
  1012. if (this.regionProvider === typeClouds.providerMap.ZStack.key ||
  1013. (this.cloudEnv === 'private' && this.curVpc?.brand === 'Cloudpods' && this.curVpc?.external_id === 'default')) {
  1014. return {
  1015. project_id: values.project?.key,
  1016. guest_ip_prefix: values.guest_ip_prefix[0],
  1017. guest_ip6_prefix: values.guest_ip6_prefix[0],
  1018. name: values.name,
  1019. description: values.description,
  1020. wire_id: values.wire,
  1021. is_auto_alloc: values.is_auto_alloc,
  1022. guest_dhcp,
  1023. __meta__: values.__meta__,
  1024. }
  1025. } else if (this.regionProvider === typeClouds.providerMap.CNware.key) {
  1026. return {
  1027. project_id: values.project?.key,
  1028. guest_ip_prefix: values.guest_ip_prefix && values.guest_ip_prefix[0],
  1029. guest_ip6_prefix: values.guest_ip6_prefix && values.guest_ip6_prefix[0],
  1030. name: values.name,
  1031. wire_id: values.wire,
  1032. description: values.description,
  1033. vpc: values.vpc,
  1034. zone: values?.zone,
  1035. is_auto_alloc: values.is_auto_alloc,
  1036. guest_dhcp,
  1037. __meta__: values.__meta__,
  1038. }
  1039. }
  1040. return {
  1041. project_id: values.project?.key,
  1042. guest_ip_prefix: values.guest_ip_prefix && values.guest_ip_prefix[0],
  1043. guest_ip6_prefix: values.guest_ip6_prefix && values.guest_ip6_prefix[0],
  1044. name: values.name,
  1045. description: values.description,
  1046. vpc: values.vpc,
  1047. zone: values?.zone,
  1048. is_auto_alloc: values.is_auto_alloc,
  1049. guest_dhcp,
  1050. __meta__: values.__meta__,
  1051. }
  1052. },
  1053. clearIpSubnetsError () {
  1054. this.ipSubnetsValidateStatus = ''
  1055. this.ipSubnetsHelp = ''
  1056. },
  1057. clearGuestIpPrefixError () {
  1058. this.guestIpPrefixValidateStatus = ''
  1059. this.guestIpPrefixHelp = ''
  1060. },
  1061. async handleSubmit () {
  1062. const ListPath = this.$router.resolve(this.$route.path)
  1063. try {
  1064. const values = await this.form.fc.validateFields()
  1065. this.submiting = true
  1066. if (this.cloudEnv === 'onpremise' && this.show && !this.isGroupGuestIpPrefix) {
  1067. const keys = Object.keys(values.startip)
  1068. let error = false
  1069. keys.forEach(key => {
  1070. if (!((values.startip[key] && values.endip[key] && values.gateway[key] && !values.startip6[key] && !values.endip6[key] && !values.gateway6[key]) || (values.startip6[key] && values.endip6[key] && values.gateway6[key] && !values.startip[key] && !values.endip[key] && !values.gateway[key]) || (values.startip[key] && values.endip[key] && values.gateway[key] && values.startip6[key] && values.endip6[key] && values.gateway6[key]))) {
  1071. error = true
  1072. }
  1073. })
  1074. if (error) {
  1075. this.ipSubnetsHelp = this.$t('network.required_ipv4_or_ipv6_1')
  1076. return
  1077. }
  1078. }
  1079. // 验证ipv4 和 ipv6 网段 有一个必填
  1080. if (!this.show && !this.isGroupGuestIpPrefix) {
  1081. const guest_ip_prefix = R.is(Array, values.guest_ip_prefix) ? values.guest_ip_prefix[0] : values.guest_ip_prefix
  1082. const guest_ip_prefix6 = R.is(Array, values.guest_ip6_prefix) ? values.guest_ip6_prefix[0] : values.guest_ip6_prefix
  1083. if (!guest_ip_prefix && !guest_ip_prefix6) {
  1084. this.guestIpPrefixValidateStatus = 'error'
  1085. if (this.curVpc && this.curVpc.cidr_block && this.curVpc.cidr_block6) {
  1086. this.guestIpPrefixHelp = this.$t('network.required_ipv4_or_ipv6')
  1087. } else {
  1088. this.guestIpPrefixHelp = this.$t('network.text_597')
  1089. }
  1090. return
  1091. }
  1092. }
  1093. if (this.isGroupGuestIpPrefix && (R.isNil(values.guest_ip_prefix) || R.isEmpty(values.guest_ip_prefix))) {
  1094. this.guestIpPrefixValidateStatus = 'error'
  1095. this.guestIpPrefixHelp = this.$t('network.text_605')
  1096. return
  1097. }
  1098. this.guestIpPrefixHelp = ''
  1099. this.ipSubnetsHelp = ''
  1100. const data = this.genData(values)
  1101. const manager = new Manager('networks')
  1102. if (this.cloudEnv === 'onpremise') {
  1103. for (let i = 0, len = data.length; i < len; i++) {
  1104. const bodyData = { ...data[i] }
  1105. if (i > 0) {
  1106. bodyData.name = `${bodyData.name}-${i + 1}`
  1107. }
  1108. await manager.create({
  1109. data: {
  1110. ...bodyData,
  1111. dry_run: true,
  1112. },
  1113. })
  1114. await manager.create({ data: bodyData })
  1115. }
  1116. } else {
  1117. await manager.create({ data })
  1118. }
  1119. this.$router.push({ path: ListPath.resolved.matched[0].path })
  1120. } catch (error) {
  1121. throw error
  1122. } finally {
  1123. this.submiting = false
  1124. }
  1125. },
  1126. filterOption (input, option) {
  1127. const lastIdx = option.componentOptions.children.length - 1
  1128. return (
  1129. option.componentOptions.children[lastIdx].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
  1130. )
  1131. },
  1132. },
  1133. }
  1134. </script>
  1135. <style lang="less" scoped>
  1136. .error-tips {
  1137. color: #f5222d;
  1138. margin-bottom: 3px;
  1139. line-height: 16px;
  1140. }
  1141. </style>