mixins.js 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537
  1. import * as R from 'ramda'
  2. import _ from 'lodash'
  3. import { mapGetters } from 'vuex'
  4. import { LOGIN_TYPES_MAP, NETWORK_OPTIONS_MAP, FORECAST_FILTERS_MAP } from '@Compute/constants'
  5. import OsSelect from '@Compute/sections/OsSelect'
  6. import ServerPassword from '@Compute/sections/ServerPassword'
  7. import ServerNetwork from '@Compute/sections/ServerNetwork'
  8. import SchedPolicy from '@Compute/sections/SchedPolicy'
  9. import DomainProject from '@/sections/DomainProject'
  10. import CloudregionZone from '@/sections/CloudregionZone'
  11. import Tag from '@/sections/Tag'
  12. import validateForm, { isRequired, isWithinRange } from '@/utils/validate'
  13. import { IMAGES_TYPE_MAP } from '@/constants/compute'
  14. import { sizestr } from '@/utils/utils'
  15. import { WORKFLOW_TYPES } from '@/constants/workflow'
  16. import i18n from '@/locales'
  17. import { checkIpV6, getIpv6Start } from '@Compute/utils/createServer'
  18. import BottomBar from './BottomBar'
  19. function checkIpInSegment (i, networkData) {
  20. return (rule, value, cb) => {
  21. const isIn = isWithinRange(value, networkData.guest_ip_start, networkData.guest_ip_end)
  22. if (isIn) {
  23. cb()
  24. } else {
  25. cb(new Error(i18n.t('compute.text_205')))
  26. }
  27. }
  28. }
  29. export default {
  30. props: {
  31. cloudEnv: {
  32. type: String,
  33. required: true,
  34. },
  35. initFormData: {
  36. type: Object,
  37. default: () => ({}),
  38. },
  39. isInitForm: {
  40. type: Boolean,
  41. default: false,
  42. },
  43. },
  44. components: {
  45. BottomBar,
  46. CloudregionZone,
  47. OsSelect,
  48. ServerPassword,
  49. ServerNetwork,
  50. SchedPolicy,
  51. DomainProject,
  52. Tag,
  53. },
  54. data () {
  55. const initData = this.isInitForm && this.initFormData
  56. const imageTypeInitValue = initData.extraData?.image_type || IMAGES_TYPE_MAP.standard.key
  57. let initImage = ''
  58. if (initData.disks) {
  59. initData.disks.forEach(item => {
  60. if (item.image_id) {
  61. initImage = item.image_id
  62. }
  63. })
  64. }
  65. if (initData.extraData?.image) {
  66. initImage = initData.extraData.image
  67. }
  68. let initLoginType = 'random'
  69. if (initData.keypair) {
  70. initLoginType = 'keypair'
  71. }
  72. if (initData.hasOwnProperty('reset_password') && !initData.reset_password) {
  73. initLoginType = 'image'
  74. }
  75. if (initData.hasOwnProperty('password') && initData.password) {
  76. initLoginType = 'password'
  77. }
  78. let initNetworkType = NETWORK_OPTIONS_MAP.default.key
  79. let initBonding = false
  80. if (initData.nets) {
  81. if (initData.nets[0] && initData.nets[0].hasOwnProperty('exit') && !initData.nets[0].exit) {
  82. if (initData.nets[0].require_teaming) {
  83. initBonding = true
  84. }
  85. initNetworkType = NETWORK_OPTIONS_MAP.default.key
  86. } else if (initData.nets[0] && initData.nets[0].hasOwnProperty('network') && initData.extraData?.extraNets && initData.extraData?.extraNets[0] && initData.extraData?.extraNets[0].hasOwnProperty('network')) {
  87. initNetworkType = NETWORK_OPTIONS_MAP.manual.key
  88. if (initData.nets[0].require_teaming) {
  89. initBonding = true
  90. }
  91. } else {
  92. initNetworkType = NETWORK_OPTIONS_MAP.schedtag.key
  93. if (initData.nets[0].schedtags && initData.nets[0].schedtags.length && initData.nets[0].schedtags[0].require_teaming) {
  94. initBonding = true
  95. }
  96. }
  97. }
  98. let initSchedPolicyType = 'default'
  99. if (initData.prefer_host) {
  100. initSchedPolicyType = 'host'
  101. }
  102. if (initData.schedtags && initData.schedtags.length) {
  103. initSchedPolicyType = 'schedtag'
  104. }
  105. return {
  106. submiting: false,
  107. errors: {},
  108. formItemLayout: {
  109. wrapperCol: {
  110. md: { span: 18 },
  111. xl: { span: 20 },
  112. xxl: { span: 22 },
  113. },
  114. labelCol: {
  115. md: { span: 6 },
  116. xl: { span: 4 },
  117. xxl: { span: 2 },
  118. },
  119. },
  120. offsetFormItemLayout: {
  121. wrapperCol: {
  122. md: { span: 18, offset: 6 },
  123. xl: { span: 20, offset: 4 },
  124. xxl: { span: 22, offset: 2 },
  125. },
  126. },
  127. form: {
  128. fc: this.$form.createForm(this, {
  129. onValuesChange: (props, values) => {
  130. this.$bus.$emit('updateForm', values)
  131. if (values.hasOwnProperty('cloudregion') && values.cloudregion.key) {
  132. this.cloudregion = values.cloudregion.key
  133. }
  134. if (values.hasOwnProperty('zone') && values.zone.key) {
  135. this.capability(values.zone.key)
  136. this.zone = values.zone.key
  137. }
  138. if (values.hasOwnProperty('imageType')) {
  139. if (values.imageType === 'iso') {
  140. this.capability(this.zone, true)
  141. } else {
  142. this.capability(this.zone)
  143. }
  144. }
  145. if (values.domain) {
  146. this.project_domain = values.domain.key
  147. this.params.region = {
  148. ...this.params.region,
  149. project_domain: values.domain.key,
  150. }
  151. }
  152. },
  153. }),
  154. fi: {
  155. capability: {},
  156. imageMsg: {},
  157. networkList: [],
  158. createType: 'baremetal',
  159. },
  160. },
  161. decorators: {
  162. domain: [
  163. 'domain',
  164. {
  165. initialValue: initData.extraData?.domain_id || this.$route.query.domain_id || '',
  166. rules: [
  167. { validator: isRequired(), message: i18n.t('rules.domain'), trigger: 'change' },
  168. ],
  169. },
  170. ],
  171. project: [
  172. 'project',
  173. {
  174. initialValue: initData.project_id || '',
  175. rules: [
  176. { validator: isRequired(), message: i18n.t('rules.project'), trigger: 'change' },
  177. ],
  178. },
  179. ],
  180. name: [
  181. 'name',
  182. {
  183. initialValue: initData.generate_name || '',
  184. validateTrigger: 'blur',
  185. validateFirst: true,
  186. rules: [
  187. { required: true, message: i18n.t('compute.text_210') },
  188. { validator: this.$validate('resourceCreateName') },
  189. ],
  190. },
  191. ],
  192. regionZone: {
  193. cloudregion: [
  194. 'cloudregion',
  195. {
  196. initialValue: { key: initData.prefer_region || '', label: '' },
  197. rules: [
  198. { required: true, message: i18n.t('compute.text_212') },
  199. ],
  200. },
  201. ],
  202. zone: [
  203. 'zone',
  204. {
  205. initialValue: { key: initData.prefer_zone || '', label: '' },
  206. rules: [
  207. { required: true, message: i18n.t('compute.text_213') },
  208. ],
  209. },
  210. ],
  211. },
  212. count: [
  213. 'count',
  214. {
  215. initialValue: initData.count || 1,
  216. },
  217. ],
  218. imageOS: {
  219. os: [
  220. 'os',
  221. {
  222. initialValue: initData.extraData?.os || '',
  223. rules: [
  224. { required: true, message: i18n.t('compute.text_153') },
  225. ],
  226. },
  227. ],
  228. image: [
  229. 'image',
  230. {
  231. initialValue: { key: initImage || '', label: '' },
  232. rules: [
  233. { validator: isRequired(), message: i18n.t('compute.text_214') },
  234. ],
  235. },
  236. ],
  237. imageType: [
  238. 'imageType',
  239. {
  240. initialValue: imageTypeInitValue,
  241. },
  242. ],
  243. },
  244. specifications: [
  245. 'specifications',
  246. {
  247. initialValue: initData.extraData?.specifications || '',
  248. rules: [
  249. { required: true, message: i18n.t('compute.text_313') },
  250. ],
  251. },
  252. ],
  253. loginConfig: {
  254. loginType: [
  255. 'loginType',
  256. {
  257. initialValue: initLoginType,
  258. },
  259. ],
  260. keypair: [
  261. 'loginKeypair',
  262. {
  263. initialValue: initData.keypair || '',
  264. rules: [
  265. { required: true, message: i18n.t('compute.text_203') },
  266. ],
  267. },
  268. ],
  269. password: [
  270. 'loginPassword',
  271. {
  272. initialValue: initData.password || '',
  273. validateFirst: true,
  274. rules: [
  275. { required: true, message: i18n.t('compute.text_204') },
  276. { validator: validateForm('sshPassword') },
  277. ],
  278. },
  279. ],
  280. },
  281. network: {
  282. networkType: [
  283. 'networkType',
  284. {
  285. initialValue: initNetworkType,
  286. },
  287. ],
  288. networkConfig: {
  289. vpcs: i => [
  290. `vpcs[${i}]`,
  291. {
  292. validateTrigger: ['change', 'blur'],
  293. rules: [{
  294. required: true,
  295. message: i18n.t('compute.text_194'),
  296. }],
  297. },
  298. ],
  299. networks: i => [
  300. `networks[${i}]`,
  301. {
  302. validateTrigger: ['change', 'blur'],
  303. rules: [{
  304. required: true,
  305. message: i18n.t('compute.text_217'),
  306. }],
  307. },
  308. ],
  309. ips: (i, networkData) => [
  310. `networkIps[${i}]`,
  311. {
  312. validateFirst: true,
  313. validateTrigger: ['blur', 'change'],
  314. rules: [{
  315. required: true,
  316. message: this.$t('compute.text_218'),
  317. }, {
  318. validator: checkIpInSegment(i, networkData),
  319. }],
  320. },
  321. ],
  322. ips6: (i, networkData) => [
  323. `networkIpsAddress6[${i}]`,
  324. {
  325. validateFirst: true,
  326. validateTrigger: ['blur', 'change'],
  327. rules: [
  328. {
  329. required: true,
  330. message: this.$t('compute.complete_ipv6_address'),
  331. },
  332. {
  333. validator: checkIpV6(i, networkData),
  334. },
  335. ],
  336. },
  337. ],
  338. ipv6_mode: (i, networkData) => [
  339. `networkIPv6Modes[${i}]`,
  340. {
  341. initialValue: 'all',
  342. },
  343. ],
  344. ipv6s: (i, networkData) => [
  345. `networkIPv6s[${i}]`,
  346. {
  347. validateFirst: true,
  348. validateTrigger: ['blur', 'change'],
  349. },
  350. ],
  351. },
  352. networkSchedtag: {
  353. schedtags: i => [
  354. `networkSchedtags[${i}]`,
  355. {
  356. validateTrigger: ['change', 'blur'],
  357. rules: [{
  358. required: true,
  359. message: i18n.t('compute.text_123'),
  360. }],
  361. },
  362. ],
  363. policys: (i, networkData) => [
  364. `networkPolicys[${i}]`,
  365. {
  366. validateTrigger: ['blur', 'change'],
  367. rules: [{
  368. required: true,
  369. message: i18n.t('compute.text_123'),
  370. }],
  371. },
  372. ],
  373. },
  374. },
  375. schedPolicy: {
  376. schedPolicyType: [
  377. 'schedPolicyType',
  378. {
  379. initialValue: initSchedPolicyType,
  380. },
  381. ],
  382. schedPolicyHost: [
  383. 'schedPolicyHost',
  384. {
  385. initialValue: initData.prefer_host,
  386. rules: [
  387. { required: true, message: i18n.t('compute.text_314') },
  388. ],
  389. },
  390. ],
  391. policySchedtag: {
  392. schedtags: i => [
  393. `policySchedtagSchedtags[${i}]`,
  394. {
  395. validateTrigger: ['change', 'blur'],
  396. rules: [{
  397. required: true,
  398. message: i18n.t('compute.text_123'),
  399. }],
  400. },
  401. ],
  402. policys: (i, networkData) => [
  403. `policySchedtagPolicys[${i}]`,
  404. {
  405. validateTrigger: ['blur', 'change'],
  406. rules: [{
  407. required: true,
  408. message: i18n.t('compute.text_123'),
  409. }],
  410. },
  411. ],
  412. },
  413. },
  414. description: [
  415. 'description',
  416. ],
  417. __meta__: [
  418. '__meta__',
  419. {
  420. initialValue: initData.__meta__ || {},
  421. rules: [
  422. { validator: validateForm('tagName') },
  423. ],
  424. },
  425. ],
  426. },
  427. zone: '',
  428. cloudregion: '',
  429. params: {
  430. zone: {},
  431. region: {
  432. usable: true,
  433. is_on_premise: true,
  434. scope: this.$store.getters.scope,
  435. },
  436. policySchedtag: {
  437. limit: 1024,
  438. 'filter.0': 'resource_type.equals(hosts)',
  439. scope: this.$store.getters.scope,
  440. },
  441. policyHostParams: {
  442. enabled: 1,
  443. usable: true,
  444. is_empty: true,
  445. host_type: 'baremetal',
  446. scope: this.$store.getters.scope,
  447. },
  448. vpcParams: {
  449. usable: true,
  450. scope: this.$store.getters.scope,
  451. // limit: 0,
  452. // show_emulated: true,
  453. filter: 'id.equals("default")',
  454. },
  455. },
  456. selectedImage: {},
  457. specOptions: [],
  458. selectedSpecItem: {},
  459. resourceType: 'shared',
  460. policyHostDisabled: [],
  461. diskData: {},
  462. diskOptionsDate: [],
  463. chartSettings: {
  464. limitShowNum: 5,
  465. radius: 50,
  466. selectedMode: 'single',
  467. labelLine: {
  468. normal: {
  469. show: true,
  470. },
  471. },
  472. label: {
  473. position: 'inside',
  474. },
  475. itemStyle: {
  476. color: function (params) {
  477. const colorList = ['#afa3f5', '#00d488', '#3feed4', '#3bafff', '#f1bb4c', 'rgba(250,250,250,0.5)']
  478. if (params.data.name === i18n.t('compute.text_315')) {
  479. return '#e3e3e3'
  480. } else {
  481. return colorList[params.dataIndex]
  482. }
  483. },
  484. },
  485. offsetY: 100,
  486. dataType: function (v) {
  487. return v + ' G'
  488. },
  489. },
  490. isBonding: initBonding,
  491. isShowFalseIcon: false,
  492. count: 1,
  493. hostData: [],
  494. filterHostData: [],
  495. isSupportIso: false,
  496. project_domain: '',
  497. projectId: '',
  498. domainId: '',
  499. osSelectImageType: 'standard',
  500. wires: [],
  501. tagDefaultChecked: {},
  502. }
  503. },
  504. computed: {
  505. ...mapGetters([
  506. 'isAdminMode',
  507. 'scope',
  508. 'isDomainMode',
  509. ]),
  510. routerQuery () {
  511. return this.$route.query
  512. },
  513. policySchedtagParams () {
  514. const ret = {
  515. limit: 1024,
  516. 'filter.0': 'resource_type.equals(hosts)',
  517. scope: this.$store.getters.scope,
  518. }
  519. const zone = this.zone
  520. if (zone) {
  521. ret.zone_id = zone
  522. }
  523. return ret
  524. },
  525. imageParams () {
  526. return {
  527. status: 'active',
  528. details: true,
  529. limit: 0,
  530. 'filter.0': 'disk_format.notequals(iso)',
  531. scope: this.$store.getters.scope,
  532. is_standard: true,
  533. }
  534. },
  535. networkParam () {
  536. let ret = {
  537. zone: this.zone,
  538. usable: true,
  539. filter: ['server_type.notin(hostlocal, container)'],
  540. }
  541. if (this.isInstallOperationSystem) {
  542. if (this.$route.query.wire_id) ret.filter = `wire_id.in(${this.$route.query.wire_id})`
  543. ret = {
  544. ...ret,
  545. scope: this.$store.getters.scope,
  546. host: this.$route.query.host_id,
  547. }
  548. return ret
  549. } else {
  550. ret = {
  551. ...ret,
  552. ...this.scopeParams,
  553. }
  554. }
  555. if (!R.isEmpty(this.wires)) {
  556. ret.filter.push(`wire_id.in(${this.wires.join(',')})`)
  557. }
  558. return ret
  559. },
  560. vpcResource () {
  561. if (this.isInstallOperationSystem) {
  562. return 'vpcs'
  563. }
  564. return `cloudregions/${this.cloudregion}/vpcs`
  565. },
  566. scopeParams () {
  567. if (this.isDomainMode) {
  568. return {
  569. scope: this.scope,
  570. }
  571. }
  572. return { project_domain: this.project_domain || this.projectId }
  573. },
  574. hasMeterService () { // 是否有计费的服务
  575. const { services } = this.$store.getters.userInfo
  576. const meterService = services.find(val => val.type === 'meter')
  577. if (meterService && meterService.status === true) {
  578. return true
  579. }
  580. return false
  581. },
  582. isInstallOperationSystem () { // 是否是安装操作系统
  583. if (this.$route.query.host_id) {
  584. return true
  585. }
  586. return false
  587. },
  588. osSelectTypes () {
  589. const types = ['standard', 'customize']
  590. if (this.isInstallOperationSystem && this.isSupportIso) {
  591. types.push('iso')
  592. }
  593. if (!this.isInstallOperationSystem) {
  594. types.push('iso')
  595. }
  596. return types
  597. },
  598. isOpenWorkflow () {
  599. return this.checkWorkflowEnabled(WORKFLOW_TYPES.APPLY_MACHINE)
  600. },
  601. isCheckedIso () {
  602. return this.osSelectImageType === 'iso'
  603. },
  604. isWindows () {
  605. let isWindows = false
  606. if (this.selectedImage.os && this.form.fd.os.toLowerCase() === 'windows') {
  607. isWindows = true
  608. }
  609. return isWindows
  610. },
  611. loginTypes () { // 主机模板隐藏手工输入密码
  612. const maps = R.clone(LOGIN_TYPES_MAP)
  613. if (this.isWindows) {
  614. delete maps.keypair
  615. }
  616. const loginTypes = Object.keys(maps)
  617. return loginTypes
  618. },
  619. },
  620. provide () {
  621. return {
  622. form: this.form,
  623. fi: this.form.fi,
  624. }
  625. },
  626. watch: {
  627. diskOptionsDate: {
  628. handler (val) {
  629. let isDistribution = false
  630. let isDiff = false // 是否存在不通的raid盘
  631. for (var i = 0; i < this.diskOptionsDate.length; i++) {
  632. // 每一项是否有分配磁盘
  633. if (i > 0) {
  634. const rowsLength = this.diskOptionsDate[i].chartData.rows.length
  635. if ((rowsLength === 1 && this.diskOptionsDate[i].chartData.rows[0].name !== i18n.t('compute.text_315')) || (rowsLength > 1)) {
  636. isDistribution = true
  637. }
  638. }
  639. if (this.diskOptionsDate[0].diskInfo[1] !== this.diskOptionsDate[i].diskInfo[1]) {
  640. isDiff = true
  641. }
  642. if (isDiff && this.diskOptionsDate[0].remainder > 0 && isDistribution) {
  643. this.isShowFalseIcon = true
  644. } else {
  645. this.isShowFalseIcon = false
  646. }
  647. }
  648. },
  649. deep: true,
  650. },
  651. project_domain (newVal, oldVal) {
  652. if (this.isInstallOperationSystem) this.fetchSpec()
  653. this.capability(this.zone)
  654. },
  655. },
  656. created () {
  657. this.zonesM2 = new this.$Manager('zones')
  658. this.serverM = new this.$Manager('servers')
  659. this.schedulerM = new this.$Manager('schedulers', 'v1')
  660. this.fetchSpec = _.debounce(this._fetchSpec, 500)
  661. if (this.$route.query.id) {
  662. this.fetchSpec()
  663. this.hostDetail()
  664. }
  665. if (this.$route.query.zone_id) {
  666. this.zone = this.$route.query.zone_id
  667. this.capability(this.$route.query.zone_id)
  668. }
  669. if (this.scope !== 'project') {
  670. this.loadHostOpt()
  671. }
  672. },
  673. mounted () {
  674. this.initForm()
  675. },
  676. methods: {
  677. async getCapability (v, data) { // 可用区查询
  678. const params = {
  679. show_emulated: true,
  680. resource_type: this.resourceType,
  681. host_type: 'baremetal',
  682. ...this.scopeParams,
  683. $t: 1,
  684. }
  685. if (this.$store.getters.isAdminMode) {
  686. params.project_domain = data?.extraData?.domain_id
  687. }
  688. return this.zonesM2.get({ id: `${v}/capability`, params })
  689. },
  690. async initForm () {
  691. const initData = this.initFormData
  692. let cData = {}
  693. if (this.isInitForm && initData.extraData && this.form?.fc) {
  694. try {
  695. const { data: capabilityData } = await this.getCapability(initData.prefer_zone, initData)
  696. this.form.fi.capability = capabilityData
  697. cData = capabilityData
  698. } catch (error) {
  699. throw error
  700. }
  701. this.$nextTick(() => {
  702. // 初始化磁盘
  703. if (initData.baremetal_disk_configs && initData.baremetal_disk_configs.length) {
  704. let diskData = null
  705. if (initData.extraData?.specifications && cData?.specs?.hosts) {
  706. const keys = Object.keys(cData.specs.hosts)
  707. for (const key of keys) {
  708. if (key.replace(/model:.+\//, '') === initData.extraData?.specifications) {
  709. diskData = cData.specs.hosts[key].disk
  710. }
  711. }
  712. }
  713. if (diskData) {
  714. initData.baremetal_disk_configs.forEach(item => {
  715. const key = `${item.driver}.adapter${item.adapter}`
  716. const info = _.get(diskData, key) || []
  717. if (info.length) {
  718. const opt1 = `${info[0].type}:${sizestr(info[0].size, 'M', 1024)}`
  719. this.addDiskCallBack({
  720. computeCount: item.count, // TODO
  721. count: item.count,
  722. start_index: item.range[0],
  723. end_index: info[0].index,
  724. option: [`${item.driver}:adapter${item.adapter}`, opt1, item.conf],
  725. })
  726. }
  727. })
  728. }
  729. }
  730. // 初始化网络
  731. if (this.$refs.networkRef && initData.nets) {
  732. let initNetworkType = NETWORK_OPTIONS_MAP.default.key
  733. if (initData.nets[0] && initData.nets[0].hasOwnProperty('exit') && !initData.nets[0].exit) {
  734. initNetworkType = NETWORK_OPTIONS_MAP.default.key
  735. } else if (initData.nets[0] && initData.nets[0].hasOwnProperty('network') && initData.extraData?.extraNets && initData.extraData?.extraNets[0] && initData.extraData?.extraNets[0].hasOwnProperty('network')) {
  736. initNetworkType = NETWORK_OPTIONS_MAP.manual.key
  737. } else {
  738. initNetworkType = NETWORK_OPTIONS_MAP.schedtag.key
  739. }
  740. this.form.fc.setFieldsValue({
  741. networkType: initNetworkType,
  742. })
  743. this.$refs.networkRef.change({ target: { value: initNetworkType }, name: 'default' })
  744. if (initNetworkType === NETWORK_OPTIONS_MAP.manual.key) {
  745. this.$nextTick(() => {
  746. if (this.$refs.networkRef.$refs.networkConfigRef) {
  747. this.$refs.networkRef.$refs.networkConfigRef.initData(initData.extraData.extraNets)
  748. }
  749. })
  750. }
  751. if (initNetworkType === NETWORK_OPTIONS_MAP.schedtag.key) {
  752. this.$nextTick(() => {
  753. if (this.$refs.networkRef.$refs.networkSchedtagRef) {
  754. this.$refs.networkRef.$refs.networkSchedtagRef.initData(initData.nets)
  755. }
  756. })
  757. }
  758. }
  759. // 高级配置
  760. this.$nextTick(() => {
  761. // 调度策略
  762. if (this.$refs.schedPolicyRef) {
  763. if (initData.prefer_host) {
  764. this.$refs.schedPolicyRef.change({ target: { value: 'host' }, name: 'default' })
  765. }
  766. if (initData.schedtags && initData.schedtags.length) {
  767. this.$refs.schedPolicyRef.change({ target: { value: 'schedtag' }, name: 'default' })
  768. setTimeout(() => {
  769. if (this.$refs.schedPolicyRef.$refs.policySchedtagRef) {
  770. this.$refs.schedPolicyRef.$refs.policySchedtagRef.initData(initData.schedtags)
  771. }
  772. }, 1000)
  773. }
  774. }
  775. })
  776. // 初始化标签
  777. if (initData.__meta__) {
  778. const ret = {}
  779. R.forEachObjIndexed((value, key) => {
  780. ret[key] = R.is(Array, value) ? value : [value]
  781. }, initData.__meta__)
  782. this.tagDefaultChecked = ret
  783. }
  784. })
  785. }
  786. },
  787. vpcResourceMapper (list) {
  788. return list.filter(val => val.id === 'default')
  789. },
  790. setSelectedImage ({ imageMsg }) {
  791. this.selectedImage = imageMsg
  792. },
  793. // 过滤network数据
  794. networkResourceMapper (data) {
  795. data = data.filter((d) => d.server_type !== 'ipmi' && d.server_type !== 'pxe')
  796. return data
  797. },
  798. // 指定物理机改变
  799. hostChange (e) {
  800. this.hostDetail(e)
  801. },
  802. // 规格变动
  803. specificationChange (value) {
  804. const str = value.replace(/\//g, ',')
  805. const arr = str.split(',')
  806. const obj = {}
  807. for (var i = 0; i < arr.length; i++) {
  808. const arr2 = arr[i].split(':')
  809. obj[arr2[0]] = arr2[1]
  810. }
  811. this.selectedSpecItem = obj
  812. const currentSpec = this.form.fi.capability.specs.hosts[value]
  813. if (R.has('isolated_devices')(currentSpec)) {
  814. this.selectedSpecItem.isolated_devices = currentSpec.isolated_devices
  815. }
  816. this.diskData = this.form.fi.capability.specs.hosts[value].disk
  817. // 过滤包含此规格的物理机
  818. this.hostResourceMapper(this.hostData)
  819. // 获取此规格的包含的wire
  820. this.getSpecWire(value)
  821. // 规格变动清空历史硬盘配置
  822. this.diskOptionsDate = []
  823. },
  824. // 获取物理机数据
  825. loadHostOpt () {
  826. const manager = new this.$Manager('hosts')
  827. const params = { ...this.params.policyHostParams }
  828. manager.list({ params })
  829. .then(({ data: { data = [] } }) => {
  830. this.hostData = data
  831. this.filterHostData = data
  832. })
  833. .catch((error) => {
  834. throw error
  835. })
  836. },
  837. // 如果是安装操作系统--查询物理机详情
  838. hostDetail (hostId) {
  839. const hostManager = new this.$Manager('hosts')
  840. hostManager.get({ id: this.$route.query.id || hostId })
  841. .then(({ data }) => {
  842. if (data.ipmi_info && data.ipmi_info.cdrom_boot) {
  843. this.isSupportIso = true
  844. } else {
  845. this.isSupportIso = false
  846. }
  847. })
  848. },
  849. // 如果有指定物理机的情况下过滤物理机数据
  850. hostResourceMapper (data) {
  851. this.filterHostData = data.filter((d) => R.equals(d.spec.disk, this.diskData))
  852. },
  853. // 安装操作系统下获取规格
  854. _fetchSpec () {
  855. const manager = new this.$Manager('specs')
  856. const params = { host_type: 'baremetal', filter: `id.equals(${this.$route.query.id})`, ...this.scopeParams }
  857. manager.rpc({ methodname: 'GetHostSpecs', params }).then(res => {
  858. const specs = res.data
  859. this.$set(this.form.fi.capability, 'specs', {
  860. hosts: specs,
  861. })
  862. if (!R.isNil(specs) && !R.isEmpty(specs)) {
  863. this._loadSpecificationOptions(specs)
  864. } else {
  865. this.specOptions = []
  866. // 清空选中规格
  867. this.$nextTick(() => {
  868. this.form.fc.setFieldsValue({ specifications: '' })
  869. })
  870. }
  871. })
  872. },
  873. capability (v, isIso = false) { // 可用区查询
  874. const data = {
  875. show_emulated: true,
  876. resource_type: this.resourceType,
  877. host_type: 'baremetal',
  878. ...this.scopeParams,
  879. }
  880. if (isIso) {
  881. data.cdrom_boot = true
  882. }
  883. // init 虚拟化平台并默认选择第一项
  884. this.zonesM2.get({
  885. id: `${v}/capability`,
  886. params: data,
  887. }).then(({ data = {} }) => {
  888. data.hypervisors = Array.from(new Set(data.hypervisors))
  889. const specs = data.specs.hosts
  890. // 如果是安装操作系统,只需要拿取public_network_count
  891. if (this.isInstallOperationSystem) {
  892. this.form.fi.capability = {
  893. ...this.form.fi.capability,
  894. public_network_count: data.public_network_count,
  895. auto_alloc_network_count: data.auto_alloc_network_count,
  896. }
  897. } else {
  898. this.form.fi.capability = {
  899. ...data,
  900. }
  901. if (!R.isNil(specs) && !R.isEmpty(specs)) {
  902. this._loadSpecificationOptions(specs)
  903. } else {
  904. this.specOptions = []
  905. // 清空选中规格
  906. this.$nextTick(() => {
  907. this.form.fc.setFieldsValue({ specifications: '' })
  908. })
  909. }
  910. }
  911. })
  912. },
  913. _loadSpecificationOptions (data) {
  914. const specs = {}
  915. let entries = Object.entries(data)
  916. entries = entries.map(item => {
  917. const newKey = item[0].replace(/model:.+\//, '')
  918. return [newKey, item[1]]
  919. })
  920. entries.forEach(item => {
  921. specs[item[0]] = item[1]
  922. })
  923. const options = []
  924. for (const k in specs) {
  925. const spec = {
  926. text: this.__getSpecification(specs[k]),
  927. value: k,
  928. }
  929. options.push(spec)
  930. }
  931. this.form.fi.capability.specs.hosts = specs
  932. this.specOptions = this.__ignoreModel(options)
  933. if (this.specOptions && this.specOptions.length) {
  934. // 规格默认选中第一项
  935. this.$nextTick(() => {
  936. this.form.fc.setFieldsValue({ specifications: this.specOptions[0].value })
  937. })
  938. // 存储选中规格中的信息
  939. this.diskData = this.form.fi.capability.specs.hosts[this.specOptions[0].value].disk
  940. this.hostResourceMapper(this.hostData)
  941. // 根据规格读取wire数据
  942. this.getSpecWire(this.specOptions[0].value)
  943. const originalValue = this.specOptions[0].value
  944. const str = this.specOptions[0].value.replace(/\//g, ',')
  945. const arr = str.split(',')
  946. const obj = {}
  947. for (var i = 0; i < arr.length; i++) {
  948. const arr2 = arr[i].split(':')
  949. obj[arr2[0]] = arr2[1]
  950. }
  951. obj.value = originalValue
  952. this.selectedSpecItem = obj
  953. const currentSpec = this.form.fi.capability.specs.hosts[originalValue]
  954. if (R.has('isolated_devices')(currentSpec)) {
  955. this.selectedSpecItem.isolated_devices = currentSpec.isolated_devices
  956. }
  957. }
  958. },
  959. __getSpecification (spec) {
  960. const cpu = spec.cpu
  961. const mem = sizestr(spec.mem, 'M', 1024)
  962. // 按类型和容量归并磁盘信息
  963. const disksObj = {}
  964. _.forEach(spec.disk, function (adapters, driver) {
  965. _.forEach(adapters, function (disks, adapter) {
  966. _.forEach(disks, function (disk) {
  967. disksObj[disk.type] = disksObj[disk.type] || {}
  968. disksObj[disk.type][disk.size] = disksObj[disk.type][disk.size] || 0
  969. disksObj[disk.type][disk.size] += disk.count
  970. })
  971. })
  972. })
  973. // disk string
  974. let disks = ''
  975. _.forEach(disksObj, function (caps, d) {
  976. disks += '_' + d
  977. const sizes = []
  978. _.forEach(caps, function (num, cap) {
  979. sizes.push(sizestr(cap, 'M', 1024) + 'x' + num)
  980. })
  981. disks += sizes.join('_')
  982. })
  983. // isolated_devices 根据 model 去重并添加count字段
  984. if (spec.isolated_devices && spec.isolated_devices.length > 0) {
  985. const gpuList = R.clone(spec.isolated_devices)
  986. for (let i = 0; i < gpuList.length; i++) {
  987. gpuList[i].count = 1
  988. for (let j = i + 1; j < gpuList.length; j++) {
  989. if (gpuList[i].model === gpuList[j].model) {
  990. gpuList[i].count++
  991. gpuList.splice(j, 1)
  992. }
  993. }
  994. }
  995. // gpu string
  996. let gpuString = '_'
  997. gpuList.map(item => {
  998. gpuString += `${item.model}X${item.count}、`
  999. })
  1000. gpuString = gpuString.substr(0, gpuString.length - 1)
  1001. return `${cpu}C${mem}${disks}${gpuString}`
  1002. }
  1003. return `${cpu}C${mem}${disks}`
  1004. },
  1005. __ignoreModel (options) {
  1006. options = options.map(item => {
  1007. return {
  1008. text: item.text,
  1009. value: item.value.replace(/model:.+\//, ''),
  1010. }
  1011. })
  1012. return this.uniqueArr(options, 'value')
  1013. },
  1014. uniqueArr (arr, field) {
  1015. if (field) {
  1016. const obj = {}
  1017. arr.forEach(item => {
  1018. if (!obj[item[field]]) {
  1019. obj[item[field]] = item
  1020. }
  1021. })
  1022. const newArr = Object.values(obj)
  1023. return Array.from(new Set(newArr))
  1024. } else {
  1025. return Array.from(new Set(arr))
  1026. }
  1027. },
  1028. // 添加硬盘配置
  1029. addDisk () {
  1030. this.createDialog('BaremetalCreateDiskDialog', {
  1031. title: i18n.t('compute.perform_create'),
  1032. list: this.list,
  1033. diskData: this.diskData,
  1034. diskOptionsDate: this.diskOptionsDate,
  1035. updateData: (data) => {
  1036. this.addDiskCallBack(data)
  1037. },
  1038. })
  1039. },
  1040. _isNoneRaid (raidOpt) {
  1041. return raidOpt === 'none'
  1042. },
  1043. // 添加硬盘配置后的回调
  1044. addDiskCallBack (data) {
  1045. let arr = []
  1046. // data.option format: `["HPSARaid:adapter0", "HDD:279G", "raid10"]`
  1047. data.option.forEach(item => {
  1048. arr = arr.concat(item.split(':'))
  1049. })
  1050. // arr format: `["HPSARaid", "adapter0", "HDD", "279G", "raid10"]`
  1051. const raidOpt = data.option[2]
  1052. const raidType = arr[0]
  1053. const adapter = arr[1]
  1054. const diskType = arr[2]
  1055. const diskSize = arr[3]
  1056. let range = []
  1057. let k = data.start_index
  1058. if (this._isNoneRaid(raidOpt)) {
  1059. range = [data.start_index]
  1060. } else {
  1061. while (k < data.start_index + data.count) {
  1062. range.push(k)
  1063. k++
  1064. }
  1065. }
  1066. let sizeNumber = 0
  1067. let n = 0
  1068. if (diskSize.substr(diskSize.length - 1, 1) === 'T') {
  1069. n = Number(diskSize.substr(0, diskSize.length - 1)) * 1024
  1070. } else {
  1071. n = Number(diskSize.substr(0, diskSize.length - 1))
  1072. }
  1073. if (this._isNoneRaid(raidOpt)) {
  1074. sizeNumber = n
  1075. } else {
  1076. sizeNumber = this.raidUtil(n, raidOpt, data.count)
  1077. }
  1078. const option = {
  1079. title: diskSize + ' ' + diskType + ' X ' + `${raidOpt === 'none' ? 1 : data.count}`,
  1080. size: sizestr(sizeNumber, 'G', 1024),
  1081. unitSize: sizestr(n, 'G', 1024),
  1082. chartData: {
  1083. columns: ['name', 'size'],
  1084. rows: [],
  1085. },
  1086. diskInfo: [raidType, adapter, raidOpt],
  1087. count: this._isNoneRaid(raidOpt) ? 1 : data.count,
  1088. type: diskType,
  1089. range,
  1090. }
  1091. if (this.diskOptionsDate.length === 0) {
  1092. const defaultSize = 30
  1093. const imageDiskSize = this.selectedImage.min_disk / 1024
  1094. if (imageDiskSize >= defaultSize) {
  1095. sizeNumber = sizeNumber - imageDiskSize
  1096. option.chartData.rows.push({ name: i18n.t('compute.text_316'), size: imageDiskSize })
  1097. } else {
  1098. sizeNumber = sizeNumber - defaultSize
  1099. option.chartData.rows.push({ name: '/', size: defaultSize })
  1100. }
  1101. }
  1102. option.remainder = sizeNumber
  1103. option.chartData.rows.push({ name: i18n.t('compute.text_315'), size: sizeNumber })
  1104. this.diskOptionsDate.push(option)
  1105. data.computeCount--
  1106. if (this._isNoneRaid(raidOpt) && data.computeCount > 0) {
  1107. data.start_index += 1
  1108. this.addDiskCallBack(data)
  1109. }
  1110. },
  1111. handleDiskItemRemove (idx) {
  1112. this.diskOptionsDate.splice(idx, 1)
  1113. },
  1114. chartFun (idx) {
  1115. return {
  1116. click: (e, index) => this.handleChartEvents(e, idx),
  1117. }
  1118. },
  1119. handleChartEvents (e, idx) {
  1120. const selectedArea = this.diskOptionsDate[idx].chartData.rows.filter(item => item.name === e.name)
  1121. let nameArr = []
  1122. this.diskOptionsDate.forEach(item => {
  1123. nameArr = nameArr.concat(item.chartData.rows)
  1124. })
  1125. nameArr = nameArr.filter(item => item.name !== i18n.t('compute.text_315'))
  1126. this.createDialog('DiskOptionsUpdateDialog', {
  1127. title: e.name === i18n.t('compute.text_315') ? i18n.t('compute.text_317') : i18n.t('compute.text_318'),
  1128. list: this.list,
  1129. item: this.diskOptionsDate[idx],
  1130. nameArr,
  1131. selectedArea: selectedArea[0],
  1132. updateData: (values) => {
  1133. const updateItem = this.diskOptionsDate[idx].chartData.rows
  1134. if (e.name === i18n.t('compute.text_315')) {
  1135. // 创建新分区
  1136. updateItem.unshift({ name: values.name, size: values.size, format: values.format })
  1137. if (values.size === this.diskOptionsDate[idx].remainder || values.method === 'autoextend') {
  1138. this.diskOptionsDate[idx].remainder = 0
  1139. updateItem.pop()
  1140. return
  1141. }
  1142. this.diskOptionsDate[idx].remainder = this.diskOptionsDate[idx].remainder - values.size
  1143. updateItem[updateItem.length - 1].size = updateItem[updateItem.length - 1].size - values.size
  1144. } else {
  1145. // 更新分区
  1146. let oldSize = 0
  1147. updateItem.forEach(item => {
  1148. if (item.name === e.name) {
  1149. item.name = values.name
  1150. oldSize = item.size
  1151. item.size = values.size
  1152. }
  1153. })
  1154. // 如何剩余比更新的大
  1155. if (this.diskOptionsDate[idx].remainder > values.size) {
  1156. updateItem[updateItem.length - 1].size = updateItem[updateItem.length - 1].size + oldSize - values.size
  1157. this.diskOptionsDate[idx].remainder = this.diskOptionsDate[idx].remainder + oldSize - values.size
  1158. if (updateItem[updateItem.length - 1].name === i18n.t('compute.text_315')) {
  1159. updateItem[updateItem.length - 1].size = this.diskOptionsDate[idx].remainder
  1160. } else {
  1161. updateItem.push({ name: i18n.t('compute.text_315'), size: this.diskOptionsDate[idx].remainder })
  1162. }
  1163. } else {
  1164. if (values.method === 'autoextend') {
  1165. this.diskOptionsDate[idx].remainder = 0
  1166. updateItem.pop()
  1167. return
  1168. }
  1169. this.diskOptionsDate[idx].remainder = (oldSize - values.size) + this.diskOptionsDate[idx].remainder
  1170. if (this.diskOptionsDate[idx].remainder === 0) return
  1171. if (updateItem[updateItem.length - 1].name === i18n.t('compute.text_315')) {
  1172. updateItem[updateItem.length - 1].size = this.diskOptionsDate[idx].remainder
  1173. } else {
  1174. updateItem.push({ name: i18n.t('compute.text_315'), size: this.diskOptionsDate[idx].remainder })
  1175. }
  1176. }
  1177. }
  1178. },
  1179. })
  1180. },
  1181. validateForm () {
  1182. return new Promise((resolve, reject) => {
  1183. this.form.fc.validateFields((err, values) => {
  1184. if (!err) {
  1185. resolve(values)
  1186. } else {
  1187. reject(err)
  1188. }
  1189. })
  1190. })
  1191. },
  1192. // raid计算大小公式
  1193. raidUtil (n, raid, m) {
  1194. let size = 0
  1195. switch (raid) {
  1196. case 'raid0':
  1197. size = n * m
  1198. break
  1199. case 'raid1':
  1200. size = n * m / m
  1201. break
  1202. case 'raid5':
  1203. size = n * (m - 1)
  1204. break
  1205. case 'raid10':
  1206. size = n * m / 2
  1207. break
  1208. }
  1209. return size
  1210. },
  1211. async handleConfirm (e) {
  1212. e.preventDefault()
  1213. const diskConfigs = []
  1214. const values = await this.validateForm()
  1215. const disks = []
  1216. const nets = []
  1217. const extraData = {
  1218. formType: this.cloudEnv,
  1219. __resource_type__: 'baremetal',
  1220. image_type: values.imageType,
  1221. os: values.os,
  1222. image: values.image.key,
  1223. domain_id: values.domain?.key || this.$store.getters.userInfo.projectDomainId,
  1224. specifications: values.specifications,
  1225. extraNets: [],
  1226. }
  1227. // 判断数据盘是否合法
  1228. if (this.diskOptionsDate.length > 0) {
  1229. if (this.isShowFalseIcon) {
  1230. this.$message.error(i18n.t('compute.text_319'))
  1231. throw new Error(i18n.t('compute.text_319'))
  1232. }
  1233. // 将系统盘放置首位
  1234. const systemDisk = this.diskOptionsDate[0].chartData.rows.pop()
  1235. this.diskOptionsDate[0].chartData.rows.unshift(systemDisk)
  1236. for (var i = 0; i < this.diskOptionsDate.length; i++) {
  1237. const rows = this.diskOptionsDate[i].chartData.rows
  1238. const adapter = Number(this.diskOptionsDate[i].diskInfo[1].charAt(this.diskOptionsDate[i].diskInfo[1].length - 1))
  1239. const configOption = {
  1240. conf: this.diskOptionsDate[i].diskInfo[2],
  1241. driver: this.diskOptionsDate[i].diskInfo[0],
  1242. count: this.diskOptionsDate[i].count,
  1243. range: this.diskOptionsDate[i].range,
  1244. adapter,
  1245. type: this.diskOptionsDate[i].type === 'HDD' ? 'rotate' : 'ssd',
  1246. }
  1247. diskConfigs.push(configOption)
  1248. for (var j = 0; j < rows.length; j++) {
  1249. let option = {
  1250. size: rows[j].size * 1024,
  1251. fs: rows[j].format,
  1252. mountpoint: rows[j].name,
  1253. }
  1254. if (i === 0 && j === 0) {
  1255. // 判断是否是iso导入
  1256. if (values.imageType === 'iso') {
  1257. option = {
  1258. size: rows[j].size * 1024,
  1259. }
  1260. } else {
  1261. option = {
  1262. size: rows[j].size * 1024,
  1263. image_id: values.image.key,
  1264. }
  1265. }
  1266. }
  1267. if (j === rows.length - 1) {
  1268. option.size = -1
  1269. if (!rows[j].format) {
  1270. Reflect.deleteProperty(option, 'fs')
  1271. }
  1272. if (rows[j].name === i18n.t('compute.text_315')) {
  1273. Reflect.deleteProperty(option, 'mountpoint')
  1274. }
  1275. }
  1276. disks.push(option)
  1277. }
  1278. }
  1279. // 根据adapter排序diskConfigs
  1280. diskConfigs.sort((a, b) => { return a.adapter - b.adapter })
  1281. }
  1282. if (values.networks) {
  1283. const networks = values.networks
  1284. for (const key in networks) {
  1285. const option = {
  1286. network: networks[key],
  1287. }
  1288. if (!R.isNil(values.networkIps) && !R.isEmpty(values.networkIps)) {
  1289. option.address = values.networkIps[key]
  1290. }
  1291. if (values.networkIPv6s && values.networkIPv6s[key]) {
  1292. option.require_ipv6 = true
  1293. }
  1294. if (values.networkIpsAddress6 && values.networkIpsAddress6[key]) {
  1295. const ipv6Last = values.networkIpsAddress6[key]
  1296. const target = this.form.fi.networkList.filter(item => item.key === key)
  1297. const ipv6First = getIpv6Start(target[0]?.network?.guest_ip6_start)
  1298. option.address6 = ipv6First + ipv6Last
  1299. }
  1300. if (values.networkIPv6Modes && values.networkIPv6Modes[key] === 'only' && option.require_ipv6) {
  1301. option.strict_ipv6 = true
  1302. }
  1303. // 是否启用bonding
  1304. if (this.isBonding) {
  1305. option.require_teaming = true
  1306. if (this.isInstallOperationSystem) option.private = false
  1307. nets.push(option)
  1308. if (values.vpcs && values.vpcs[key]) {
  1309. const extraOption = { ...option, vpc: values.vpcs[key] }
  1310. extraData.extraNets.push(extraOption)
  1311. } else {
  1312. extraData.extraNets.push(option)
  1313. }
  1314. } else {
  1315. nets.push(option)
  1316. if (values.vpcs && values.vpcs[key]) {
  1317. const extraOption = { ...option, vpc: values.vpcs[key] }
  1318. extraData.extraNets.push(extraOption)
  1319. } else {
  1320. extraData.extraNets.push(option)
  1321. }
  1322. }
  1323. }
  1324. } else if (values.networkSchedtags) {
  1325. R.forEachObjIndexed((value, key) => {
  1326. const obj = {
  1327. id: value,
  1328. }
  1329. if (this.isBonding) {
  1330. obj.require_teaming = true
  1331. }
  1332. const strategy = values.networkPolicys[key]
  1333. if (strategy) {
  1334. obj.strategy = strategy
  1335. }
  1336. nets.push({
  1337. schedtags: [obj],
  1338. })
  1339. }, values.networkSchedtags)
  1340. } else {
  1341. // 是否启用bonding
  1342. if (this.isBonding) {
  1343. nets.push({ exit: false, require_teaming: true })
  1344. } else {
  1345. nets.push({ exit: false })
  1346. }
  1347. }
  1348. // 判断是否是安装操作系统
  1349. let params = {
  1350. project_id: this.projectId?.key,
  1351. count: values.count,
  1352. vmem_size: Number(this.selectedSpecItem.mem.substr(0, this.selectedSpecItem.mem.length - 1)),
  1353. vcpu_count: Number(this.selectedSpecItem.cpu),
  1354. generate_name: values.name,
  1355. hypervisor: 'baremetal',
  1356. auto_start: true,
  1357. vdi: 'vnc',
  1358. disks,
  1359. baremetal_disk_configs: diskConfigs,
  1360. nets,
  1361. prefer_host: this.isInstallOperationSystem ? this.$route.query.id : values.schedPolicyHost,
  1362. description: values.description,
  1363. prefer_region: values.cloudregion ? values.cloudregion.key : this.$route.query.region_id,
  1364. prefer_zone: values.zone ? values.zone.key : this.$route.query.zone_id,
  1365. __meta__: values.__meta__,
  1366. }
  1367. if (values.policySchedtagSchedtags) {
  1368. const schedtags = []
  1369. for (const key in values.policySchedtagSchedtags) {
  1370. if (values.policySchedtagSchedtags[key]) {
  1371. schedtags.push({
  1372. id: values.policySchedtagSchedtags[key],
  1373. strategy: values.policySchedtagPolicys[key],
  1374. })
  1375. }
  1376. }
  1377. params.schedtags = schedtags
  1378. }
  1379. if (values.loginPassword) params.password = values.loginPassword
  1380. if (values.loginKeypair) params.keypair = values.loginKeypair.key
  1381. if (values.loginType === 'image') params.reset_password = false
  1382. if (this.selectedSpecItem.isolated_devices) params.isolated_devices = this.selectedSpecItem.isolated_devices
  1383. // 判断是否是iso导入
  1384. if (values.imageType === 'iso') {
  1385. params = {
  1386. ...params,
  1387. cdrom: values.image.key,
  1388. }
  1389. }
  1390. if (this.isInstallOperationSystem) {
  1391. // Reflect.deleteProperty(params, 'project_id')
  1392. this.createBaremetal(params)
  1393. } else {
  1394. if (this.isOpenWorkflow) { // 提交工单
  1395. const workflowParams = {
  1396. ...params,
  1397. extraData,
  1398. }
  1399. const variables = {
  1400. process_definition_key: WORKFLOW_TYPES.APPLY_MACHINE,
  1401. initiator: this.$store.getters.userInfo.id,
  1402. 'server-create-paramter': JSON.stringify(workflowParams),
  1403. project: this.projectId?.key,
  1404. project_domain: this.domainId?.key || this.$store.getters.userInfo.projectDomainId,
  1405. }
  1406. this.doCreateWorkflow(variables, workflowParams)
  1407. } else { // 创建裸金属
  1408. this.doForecast(params)
  1409. }
  1410. }
  1411. },
  1412. // 创建工单
  1413. doCreateWorkflow (variables, params) {
  1414. this.submiting = true
  1415. if (this.$route.query.workflow) {
  1416. new this.$Manager('historic-process-instances', 'v1')
  1417. .update({ id: `${this.$route.query.workflow}/variables`, data: { variables } })
  1418. .then(() => {
  1419. this.submiting = false
  1420. this.$message.success(i18n.t('compute.text_320', [params.name]))
  1421. this.$router.push('/workflow')
  1422. })
  1423. } else {
  1424. new this.$Manager('process-instances', 'v1')
  1425. .create({ data: { variables } })
  1426. .then(() => {
  1427. this.submiting = false
  1428. this.$message.success(i18n.t('compute.text_320', [params.name]))
  1429. this.$router.push('/workflow')
  1430. })
  1431. .catch(() => {
  1432. this.submiting = false
  1433. })
  1434. }
  1435. },
  1436. doForecast (params) {
  1437. this.submiting = true
  1438. this.schedulerM.rpc({ methodname: 'DoForecast', params })
  1439. .then(res => {
  1440. if (res.data.can_create) {
  1441. this.createBaremetal(params)
  1442. } else {
  1443. this.errors = this.getForecastErrors(res.data)
  1444. this.submiting = false
  1445. }
  1446. })
  1447. .catch(err => {
  1448. this.$message.error(i18n.t('compute.text_321', [err]))
  1449. this.submiting = false
  1450. })
  1451. },
  1452. // 创建裸金属
  1453. createBaremetal (data) {
  1454. const { count } = data
  1455. if (count > 1) {
  1456. this.serverM.batchCreate({ data, count })
  1457. .then(res => {
  1458. this.submiting = false
  1459. this.$message.success(i18n.t('compute.text_322'))
  1460. if (this.isInstallOperationSystem) {
  1461. this.$router.push('/physicalmachine')
  1462. } else {
  1463. this.$router.push('/baremetal')
  1464. }
  1465. })
  1466. .catch(() => {
  1467. this.submiting = false
  1468. })
  1469. } else {
  1470. this.serverM.create({ data })
  1471. .then(res => {
  1472. this.submiting = false
  1473. this.$message.success(i18n.t('compute.text_322'))
  1474. if (this.isInstallOperationSystem) {
  1475. this.$router.push('/physicalmachine')
  1476. } else {
  1477. this.$router.push('/baremetal')
  1478. }
  1479. })
  1480. .catch(() => {
  1481. this.submiting = false
  1482. })
  1483. }
  1484. },
  1485. getForecastErrors (data) {
  1486. const errors = []
  1487. if (data.filtered_candidates && data.filtered_candidates.length > 0) {
  1488. for (let i = 0, len = data.filtered_candidates.length; i < len; i++) {
  1489. const item = data.filtered_candidates[i]
  1490. let message = `${i18n.t('dictionary.physicalmachine')}【${item.name}】`
  1491. const filterMapItem = FORECAST_FILTERS_MAP[item.filter_name]
  1492. if (filterMapItem) {
  1493. message += filterMapItem
  1494. } else {
  1495. message += this.$t('compute.text_1325', [item.filter_name])
  1496. }
  1497. errors.push({
  1498. message,
  1499. children: item.reasons,
  1500. })
  1501. }
  1502. } else {
  1503. errors.push({
  1504. message: i18n.t('compute.text_227'),
  1505. })
  1506. }
  1507. return {
  1508. errors,
  1509. allow_count: data.allow_count,
  1510. req_count: data.req_count,
  1511. not_allow_reasons: data.not_allow_reasons,
  1512. }
  1513. },
  1514. getSpecWire (currentSpec) {
  1515. const manager = new this.$Manager('specs')
  1516. const params = { host_type: 'baremetal', kind: 'hosts', key: currentSpec, ...this.scopeParams }
  1517. manager.rpc({ methodname: 'GetObjects', params }).then(res => {
  1518. const hosts = res.data || []
  1519. this.wires = []
  1520. for (const host of hosts) {
  1521. let nicInfos = host.nic_info || []
  1522. nicInfos = nicInfos.filter(item => !['ipmi', 'pxe'].includes(item.nic_type) && item.wire_id)
  1523. const wireIds = nicInfos.map(item => item.wire_id)
  1524. const newWireIds = Array.from(new Set(wireIds))
  1525. this.wires = newWireIds
  1526. }
  1527. })
  1528. },
  1529. handleCancel () {
  1530. this.$router.push({ name: 'Baremetal' })
  1531. },
  1532. },
  1533. }