Convert.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. <template>
  2. <base-dialog @cancel="cancelDialog" :width="900">
  3. <div slot="header">{{$t('compute.text_828')}}</div>
  4. <div slot="body">
  5. <dialog-selected-tips :name="$t('dictionary.physicalmachine')" :count="params.data.length" :action="$t('compute.text_828')" />
  6. <dialog-table :data="params.data" :columns="params.columns.slice(0, 2)" />
  7. <a-form
  8. :form="form.fc">
  9. <a-form-item :label="$t('compute.text_829')" v-bind="formItemLayout">
  10. <a-input v-decorator="decorators.name" :placeholder="$t('compute.text_830')" />
  11. </a-form-item>
  12. <a-form-item :label="$t('compute.text_831')" v-bind="formItemLayout">
  13. <a-select v-decorator="decorators.host_type" :placeholder="$t('compute.text_831')">
  14. <a-select-option v-for="item in hostTypeOptions" :key="item.value" :value="item.value">
  15. {{item.text}}
  16. </a-select-option>
  17. </a-select>
  18. </a-form-item>
  19. <a-form-item :label="$t('compute.text_832')" v-bind="formItemLayout">
  20. <a-select v-decorator="decorators.raid" :placeholder="$t('compute.text_832')" @change="raidChange">
  21. <a-select-option v-for="item in raidOptions" :key="item.value" :value="item.value">
  22. {{item.text}}
  23. </a-select-option>
  24. </a-select>
  25. </a-form-item>
  26. <a-form-item :label="$t('compute.text_96')" v-bind="formItemLayout" v-if="isShowImages">
  27. <base-select
  28. :filterable="true"
  29. v-decorator="decorators.image"
  30. needParams
  31. resource="images"
  32. version="v1"
  33. :params="imagesParams"
  34. :mapper="imagesResourceMapper"
  35. :resList.sync="imagesData"
  36. @update:item="imagechange"
  37. :select-props="{ placeholder: $t('compute.text_833') }" />
  38. </a-form-item>
  39. <a-form-item v-bind="formItemLayout" :label="$t('compute.text_834')" v-if="isShowImages">
  40. <div class="d-flex flex-wrap">
  41. <template v-for="(item, idx) of diskOptionsDate">
  42. <div :key="idx" class="disk-option-item">
  43. <a-card hoverable>
  44. <template slot="title">
  45. <icon type="res-disk" />
  46. {{ item.title }}
  47. <a-tooltip :title="$t('compute.text_304')">
  48. <a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" v-show="(idx === 0 && !isShowFalseIcon) || idx !== 0" />
  49. </a-tooltip>
  50. <a-tooltip :title="$t('compute.text_305')">
  51. <a-icon type="close-circle" theme="twoTone" twoToneColor="#eb2f96" v-show="idx === 0 && isShowFalseIcon" />
  52. </a-tooltip>
  53. </template>
  54. <a href="javascript:;" slot="extra" @click="handleDiskItemRemove(idx)" v-show="idx === diskOptionsDate.length - 1">{{$t('compute.perform_delete')}}</a>
  55. <div class="d-flex align-items-center">
  56. <ve-pie :data="item.chartData" :settings="chartSettings" :events="chartFun(idx)" width="200px" height="200px" :legend-visible="false" :tooltip="tooltip" />
  57. <div class="flex-fill ml-2">
  58. <template v-for="k in item.diskInfo">
  59. <div :key="k">
  60. <a-checkbox defaultChecked disabled>
  61. {{k}}
  62. </a-checkbox>
  63. </div>
  64. </template>
  65. <a-tag color="blue">{{$t('compute.text_306', [item.size])}}</a-tag>
  66. </div>
  67. </div>
  68. </a-card>
  69. </div>
  70. </template>
  71. </div>
  72. </a-form-item>
  73. <a-form-item :wrapper-col="{ span: 18, offset: 4 }" v-if="isShowImages">
  74. <a-button type="primary" @click="addDisk">{{$t('compute.text_307')}}</a-button>
  75. </a-form-item>
  76. <a-form-item :label="$t('compute.text_104')" v-bind="formItemLayout" class="mb-0" v-if="isShowNetwork">
  77. <server-network
  78. :form="form"
  79. :decorator="decorators.network"
  80. :isBonding="isBonding"
  81. :network-resource-mapper="networkResourceMapper"
  82. :network-list-params="networkParams"
  83. :schedtag-params="resourcesParams.schedtag"
  84. :networkVpcParams="resourcesParams.vpcParams"
  85. :vpcResource="vpcResource"
  86. :vpcResourceMapper="vpcResourceMapper"
  87. :isDialog="true" />
  88. </a-form-item>
  89. <a-form-item :wrapper-col="{ span: 18, offset: 4 }" v-if="isShowNetwork">
  90. <a-checkbox v-model="isBonding">{{$t('compute.text_310')}}</a-checkbox>
  91. </a-form-item>
  92. </a-form>
  93. </div>
  94. <div slot="footer">
  95. <a-button type="primary" @click="handleConfirm" :loading="loading">{{ $t('dialog.ok') }}</a-button>
  96. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  97. </div>
  98. </base-dialog>
  99. </template>
  100. <script>
  101. import _ from 'lodash'
  102. import * as R from 'ramda'
  103. import { NETWORK_OPTIONS_MAP } from '@Compute/constants'
  104. import ServerNetwork from '@Compute/sections/ServerNetwork'
  105. import { sizestr } from '@/utils/utils'
  106. import { isWithinRange } from '@/utils/validate'
  107. import DialogMixin from '@/mixins/dialog'
  108. import WindowsMixin from '@/mixins/windows'
  109. import i18n from '@/locales'
  110. function checkIpInSegment (i, networkData) {
  111. return (rule, value, cb) => {
  112. const isIn = isWithinRange(value, networkData.guest_ip_start, networkData.guest_ip_end)
  113. if (isIn) {
  114. cb()
  115. } else {
  116. cb(new Error(this.$t('compute.text_205')))
  117. }
  118. }
  119. }
  120. export default {
  121. name: 'HostsConvertDialog',
  122. components: {
  123. ServerNetwork,
  124. },
  125. mixins: [DialogMixin, WindowsMixin],
  126. data () {
  127. return {
  128. loading: false,
  129. scope: this.$store.getters.scope,
  130. form: {
  131. fc: this.$form.createForm(this),
  132. fi: {
  133. capability: {},
  134. },
  135. },
  136. decorators: {
  137. name: [
  138. 'name',
  139. {
  140. initialValue: this.params.data[0].name,
  141. validateFirst: true,
  142. rules: [
  143. {
  144. required: true, message: this.$t('compute.text_835'),
  145. },
  146. {
  147. validator: this.$validate('serverCreateName'),
  148. },
  149. ],
  150. },
  151. ],
  152. host_type: [
  153. 'host_type',
  154. {
  155. initialValue: 'hypervisor',
  156. rules: [
  157. { required: true },
  158. ],
  159. },
  160. ],
  161. raid: [
  162. 'raid',
  163. ],
  164. image: [
  165. 'image',
  166. ],
  167. network: {
  168. networkType: [
  169. 'networkType',
  170. {
  171. initialValue: NETWORK_OPTIONS_MAP.default.key,
  172. },
  173. ],
  174. networkConfig: {
  175. vpcs: i => [
  176. `vpcs[${i}]`,
  177. {
  178. validateTrigger: ['change', 'blur'],
  179. rules: [{
  180. required: true,
  181. message: this.$t('compute.text_194'),
  182. }],
  183. },
  184. ],
  185. networks: i => [
  186. `networks[${i}]`,
  187. {
  188. validateTrigger: ['change', 'blur'],
  189. rules: [{
  190. required: true,
  191. message: this.$t('compute.text_217'),
  192. }],
  193. },
  194. ],
  195. ips: (i, networkData) => [
  196. `networkIps[${i}]`,
  197. {
  198. validateFirst: true,
  199. validateTrigger: ['blur', 'change'],
  200. rules: [{
  201. required: true,
  202. message: this.$t('compute.text_218'),
  203. }, {
  204. validator: checkIpInSegment(i, networkData),
  205. }],
  206. },
  207. ],
  208. },
  209. networkSchedtag: {
  210. schedtags: i => [
  211. `networkSchedtags[${i}]`,
  212. {
  213. validateTrigger: ['change', 'blur'],
  214. rules: [{
  215. required: true,
  216. message: this.$t('compute.text_123'),
  217. }],
  218. },
  219. ],
  220. policys: (i, networkData) => [
  221. `networkPolicys[${i}]`,
  222. {
  223. validateTrigger: ['blur', 'change'],
  224. rules: [{
  225. required: true,
  226. message: this.$t('compute.text_123'),
  227. }],
  228. },
  229. ],
  230. },
  231. },
  232. },
  233. formItemLayout: {
  234. wrapperCol: {
  235. span: 21,
  236. },
  237. labelCol: {
  238. span: 3,
  239. },
  240. },
  241. hostTypeOptions: [
  242. { text: this.$t('compute.text_836'), value: 'hypervisor' },
  243. ],
  244. imagesParams: {},
  245. isShowImages: false,
  246. isShowNetwork: false,
  247. imagesData: [],
  248. diskOptionsDate: [], // 自定义磁盘配置数据
  249. chartSettings: { // echart配置信息
  250. limitShowNum: 5,
  251. radius: 50,
  252. selectedMode: 'single',
  253. labelLine: {
  254. normal: {
  255. show: true,
  256. },
  257. },
  258. label: {
  259. position: 'inside',
  260. },
  261. itemStyle: {
  262. color: function (params) {
  263. const colorList = ['#afa3f5', '#00d488', '#3feed4', '#3bafff', '#f1bb4c', 'rgba(250,250,250,0.5)']
  264. if (params.data.name === i18n.t('compute.text_315')) {
  265. return '#e3e3e3'
  266. } else {
  267. return colorList[params.dataIndex]
  268. }
  269. },
  270. },
  271. offsetY: 100,
  272. dataType: function (v) {
  273. return v + ' G'
  274. },
  275. },
  276. tooltip: {
  277. trigger: 'item',
  278. axisPointer: {
  279. type: 'line',
  280. shadowStyle: {
  281. color: 'rgb(77, 161, 255)',
  282. opacity: 0.1,
  283. },
  284. },
  285. position: (point, params, dom, rect, size) => {
  286. return { left: point[0] - 30, top: point[1] - 20 }
  287. },
  288. },
  289. diskData: this.params.data[0].spec.disk, // 规格中的硬盘数据
  290. selectedImage: {}, // 选中的镜像
  291. isBonding: false,
  292. resourcesParams: {
  293. schedtag: {
  294. limit: 1024,
  295. 'filter.0': 'resource_type.equals(networks)',
  296. },
  297. vpcParams: {
  298. usable: true,
  299. scope: this.$store.getters.scope,
  300. // limit: 0,
  301. // show_emulated: true,
  302. filter: 'id.equals("default")',
  303. },
  304. },
  305. isShowFalseIcon: false,
  306. count: 1,
  307. }
  308. },
  309. provide () {
  310. return {
  311. form: this.form,
  312. fi: this.form.fi,
  313. }
  314. },
  315. computed: {
  316. networkParams () {
  317. const ret = {
  318. scope: this.$store.getters.scope,
  319. zone: this.params.data[0].zone_id,
  320. host: this.params.data[0].id,
  321. usable: true,
  322. }
  323. if (this.wireIds) ret.filter = `wire_id.in(${this.wireIds})`
  324. return ret
  325. },
  326. wireIds () {
  327. if (this.params.data[0].nic_info) {
  328. const arr = this.params.data[0].nic_info.filter(item => {
  329. return item.nic_type !== 'ipmi' && item.wire_id
  330. })
  331. let str = ''
  332. arr.map(item => {
  333. str += `${item.wire_id},`
  334. })
  335. str = str.substr(0, str.length - 1)
  336. return str
  337. }
  338. return null
  339. },
  340. raidOptions () {
  341. let flag = false
  342. const items = this.params.data[0]
  343. if (Object.keys(items.spec.disk).length > 1) {
  344. flag = true
  345. } else {
  346. _.forEach(items.spec.disk, function (adapters, driver) {
  347. if (Object.keys(adapters).length > 1) {
  348. flag = true
  349. } else {
  350. _.forEach(adapters, function (disks, adapter) {
  351. if (disks.length > 1) {
  352. flag = true
  353. }
  354. })
  355. }
  356. })
  357. }
  358. if (flag) {
  359. return [
  360. { text: this.$t('compute.text_837'), value: 'custom' },
  361. ]
  362. }
  363. return [
  364. { text: this.$t('compute.text_838'), value: '' },
  365. { text: this.$t('compute.text_839'), value: 'raid10' },
  366. { text: this.$t('compute.text_840'), value: 'raid5' },
  367. { text: this.$t('compute.text_841'), value: 'raid0' },
  368. { text: this.$t('compute.text_837'), value: 'custom' },
  369. ]
  370. },
  371. vpcResource () {
  372. return `cloudregions/${this.params.data[0].cloudregion_id}/vpcs`
  373. },
  374. },
  375. watch: {
  376. imagesData: {
  377. handler (val) {
  378. this.form.fc.setFieldsValue({ image: val[0].id })
  379. this.selectedImage = val[0]
  380. },
  381. },
  382. isShowImages: {
  383. handler (val) {
  384. if (val) {
  385. this.capability()
  386. }
  387. },
  388. },
  389. diskOptionsDate: {
  390. handler (val) {
  391. let isDistribution = false
  392. let isDiff = false // 是否存在不通的raid盘
  393. for (var i = 0; i < this.diskOptionsDate.length; i++) {
  394. // 每一项是否有分配磁盘
  395. if (i > 0) {
  396. const rowsLength = this.diskOptionsDate[i].chartData.rows.length
  397. if ((rowsLength === 1 && this.diskOptionsDate[i].chartData.rows[0].name !== this.$t('compute.text_315')) || (rowsLength > 1)) {
  398. isDistribution = true
  399. }
  400. }
  401. if (this.diskOptionsDate[0].diskInfo[1] !== this.diskOptionsDate[i].diskInfo[1]) {
  402. isDiff = true
  403. }
  404. if (isDiff && this.diskOptionsDate[0].remainder > 0 && isDistribution) {
  405. this.isShowFalseIcon = true
  406. } else {
  407. this.isShowFalseIcon = false
  408. }
  409. }
  410. },
  411. deep: true,
  412. },
  413. },
  414. created () {
  415. this.zonesM2 = new this.$Manager('zones')
  416. },
  417. methods: {
  418. vpcResourceMapper (list) {
  419. return list.filter(val => val.id === 'default')
  420. },
  421. // 过滤network数据
  422. networkResourceMapper (data) {
  423. data = data.filter((d) => d.server_type !== 'ipmi' && d.server_type !== 'pxe')
  424. return data
  425. },
  426. capability () { // 可用区查询
  427. const data = { show_emulated: true, resource_type: 'shared', scope: this.$store.getters.scope, host_type: 'baremetal' }
  428. this.zonesM2.get({
  429. id: `${this.params.data[0].zone_id}/capability`,
  430. params: data,
  431. }).then(({ data = {} }) => {
  432. this.form.fi.capability = {
  433. ...data,
  434. }
  435. })
  436. },
  437. raidChange (e) {
  438. if (e === 'custom') {
  439. this.isShowImages = true
  440. this.imagesParams = {
  441. is_standard: 'false',
  442. status: 'active',
  443. details: true,
  444. 'filter.0': 'disk_format.notequals(iso)',
  445. }
  446. } else {
  447. this.isShowImages = false
  448. }
  449. if (e === 'custom' || e === '' || e === 'raid10' || e === 'raid5' || e === 'raid0') {
  450. this.isShowNetwork = true
  451. }
  452. },
  453. // 镜像改变
  454. imagechange (e) {
  455. this.imagesData.forEach(item => {
  456. if (item.id === e.id) {
  457. this.selectedImage = item
  458. }
  459. })
  460. },
  461. imagesResourceMapper (data) {
  462. data = data.filter((d) => d.properties?.os_type === 'Linux')
  463. return data
  464. },
  465. // 添加硬盘配置
  466. addDisk () {
  467. this.createDialog('BaremetalCreateDiskDialog', {
  468. title: this.$t('compute.perform_create'),
  469. diskData: this.diskData,
  470. diskOptionsDate: this.diskOptionsDate,
  471. updateData: (data) => {
  472. this.addDiskCallBack(data)
  473. },
  474. })
  475. },
  476. // 添加硬盘配置后的回调
  477. addDiskCallBack (data) {
  478. let arr = []
  479. data.option.forEach(item => {
  480. arr = arr.concat(item.split(':'))
  481. })
  482. let range = []
  483. if (data.option[2] === 'none') {
  484. range = [data.start_index]
  485. } else {
  486. let k = data.start_index
  487. while (k < data.start_index + data.count) {
  488. range.push(k)
  489. k++
  490. }
  491. }
  492. const isRepeat = this.diskOptionsDate.filter(item => item.diskInfo[1] === arr[1] && item.type === arr[2] && item.unitSize === arr[3])
  493. if (isRepeat.length > 0) {
  494. if (data.option[2] === 'none') {
  495. range = [data.start_index + this.count + 1]
  496. } else {
  497. if (data.start_index === 0) {
  498. const lastIndexRange = isRepeat[isRepeat.length - 1].range
  499. let i = lastIndexRange[lastIndexRange.length - 1]
  500. let p = 0
  501. range = []
  502. while (p < data.count) {
  503. i++
  504. range.push(i)
  505. p++
  506. }
  507. } else {
  508. let j = data.start_index
  509. range = []
  510. while (j < data.start_index + data.count) {
  511. range.push(j)
  512. j++
  513. }
  514. }
  515. }
  516. }
  517. let sizeNumber = 0
  518. let n = 0
  519. if (arr[3].substr(arr[3].length - 1, 1) === 'T') {
  520. n = Number(arr[3].substr(0, arr[3].length - 1)) * 1024
  521. } else {
  522. n = Number(arr[3].substr(0, arr[3].length - 1))
  523. }
  524. if (arr[4] === 'none') {
  525. sizeNumber = n
  526. } else {
  527. sizeNumber = this.raidUtil(n, arr[4], data.count)
  528. }
  529. const option = {
  530. title: arr[3] + ' ' + arr[2] + ' X ' + `${data.option[2] === 'none' ? 1 : data.count}`,
  531. size: sizestr(sizeNumber, 'G', 1024),
  532. unitSize: sizestr(n, 'G', 1024),
  533. chartData: {
  534. columns: ['name', 'size'],
  535. rows: [],
  536. },
  537. diskInfo: [arr[0], arr[1], arr[4]],
  538. count: data.option[2] === 'none' ? 1 : data.count,
  539. type: arr[2],
  540. range,
  541. }
  542. if (this.diskOptionsDate.length === 0) {
  543. const defaultSize = 30
  544. const imageDiskSize = this.selectedImage.min_disk / 1024
  545. if (imageDiskSize >= defaultSize) {
  546. sizeNumber = sizeNumber - imageDiskSize
  547. option.chartData.rows.push({ name: this.$t('compute.text_316'), size: imageDiskSize })
  548. } else {
  549. sizeNumber = sizeNumber - defaultSize
  550. option.chartData.rows.push({ name: '/', size: defaultSize })
  551. }
  552. }
  553. option.remainder = sizeNumber
  554. option.chartData.rows.push({ name: this.$t('compute.text_315'), size: sizeNumber })
  555. this.diskOptionsDate.push(option)
  556. data.computeCount--
  557. if (data.option[2] === 'none' && data.computeCount > 0) {
  558. this.addDiskCallBack(data)
  559. }
  560. },
  561. // 删除硬盘配置
  562. handleDiskItemRemove (idx) {
  563. this.diskOptionsDate.splice(idx, 1)
  564. },
  565. // 绑定echart点击扇形区域触发的回调
  566. chartFun (idx) {
  567. return {
  568. click: (e, index) => this.handleChartEvents(e, idx),
  569. }
  570. },
  571. // echart点击扇形区域触发的回调
  572. handleChartEvents (e, idx) {
  573. const selectedArea = this.diskOptionsDate[idx].chartData.rows.filter(item => item.name === e.name)
  574. let nameArr = []
  575. this.diskOptionsDate.forEach(item => {
  576. nameArr = nameArr.concat(item.chartData.rows)
  577. })
  578. nameArr = nameArr.filter(item => item.name !== this.$t('compute.text_315'))
  579. this.createDialog('DiskOptionsUpdateDialog', {
  580. title: e.name === this.$t('compute.text_315') ? this.$t('compute.text_317') : this.$t('compute.text_318'),
  581. item: this.diskOptionsDate[idx],
  582. nameArr,
  583. type: 'Convert',
  584. selectedArea: selectedArea[0],
  585. updateData: (values) => {
  586. const updateItem = this.diskOptionsDate[idx].chartData.rows
  587. if (e.name === this.$t('compute.text_315')) {
  588. // 创建新分区
  589. updateItem.unshift({ name: values.name, size: values.size, format: values.format })
  590. if (values.size === this.diskOptionsDate[idx].remainder || values.method === 'autoextend') {
  591. this.diskOptionsDate[idx].remainder = 0
  592. updateItem.pop()
  593. return
  594. }
  595. this.diskOptionsDate[idx].remainder = this.diskOptionsDate[idx].remainder - values.size
  596. updateItem[updateItem.length - 1].size = updateItem[updateItem.length - 1].size - values.size
  597. } else {
  598. // 更新分区
  599. let oldSize = 0
  600. updateItem.forEach(item => {
  601. if (item.name === e.name) {
  602. item.name = values.name
  603. oldSize = item.size
  604. item.size = values.size
  605. }
  606. })
  607. // 如何剩余比更新的大
  608. if (this.diskOptionsDate[idx].remainder > values.size) {
  609. updateItem[updateItem.length - 1].size = updateItem[updateItem.length - 1].size + oldSize - values.size
  610. this.diskOptionsDate[idx].remainder = this.diskOptionsDate[idx].remainder + oldSize - values.size
  611. if (updateItem[updateItem.length - 1].name === this.$t('compute.text_315')) {
  612. updateItem[updateItem.length - 1].size = this.diskOptionsDate[idx].remainder
  613. } else {
  614. updateItem.push({ name: this.$t('compute.text_315'), size: this.diskOptionsDate[idx].remainder })
  615. }
  616. } else {
  617. if (values.method === 'autoextend') {
  618. this.diskOptionsDate[idx].remainder = 0
  619. updateItem.pop()
  620. return
  621. }
  622. this.diskOptionsDate[idx].remainder = (oldSize - values.size) + this.diskOptionsDate[idx].remainder
  623. if (this.diskOptionsDate[idx].remainder === 0) return
  624. if (updateItem[updateItem.length - 1].name === this.$t('compute.text_315')) {
  625. updateItem[updateItem.length - 1].size = this.diskOptionsDate[idx].remainder
  626. } else {
  627. updateItem.push({ name: this.$t('compute.text_315'), size: this.diskOptionsDate[idx].remainder })
  628. }
  629. }
  630. }
  631. },
  632. })
  633. },
  634. // raid计算大小公式
  635. raidUtil (n, raid, m) {
  636. let size = 0
  637. switch (raid) {
  638. case 'raid0':
  639. size = n * m
  640. break
  641. case 'raid1':
  642. size = n * m / m
  643. break
  644. case 'raid5':
  645. size = n * (m - 1)
  646. break
  647. case 'raid10':
  648. size = n * m / 2
  649. break
  650. }
  651. return size
  652. },
  653. validateForm () {
  654. return new Promise((resolve, reject) => {
  655. this.form.fc.validateFields((err, values) => {
  656. if (!err) {
  657. resolve(values)
  658. } else {
  659. reject(err)
  660. }
  661. })
  662. })
  663. },
  664. doConvert (data) {
  665. return this.params.onManager('performAction', {
  666. id: this.params.data[0].id,
  667. managerArgs: {
  668. action: 'convert-hypervisor',
  669. data,
  670. },
  671. })
  672. },
  673. genParmas (values) {
  674. const diskConfigs = []
  675. const disks = []
  676. const nets = []
  677. const params = {}
  678. if (values.networks) {
  679. const networks = values.networks
  680. for (const key in networks) {
  681. const option = {
  682. network: networks[key],
  683. }
  684. if (!R.isNil(values.networkIps) && !R.isEmpty(values.networkIps)) {
  685. option.address = values.networkIps[key]
  686. }
  687. // 是否启用bonding
  688. if (this.isBonding) {
  689. option.require_teaming = true
  690. if (this.isInstallOperationSystem) option.private = false
  691. nets.push(option)
  692. } else {
  693. nets.push(option)
  694. }
  695. }
  696. } else {
  697. // 是否启用bonding
  698. if (this.isBonding) {
  699. nets.push({ exit: false, require_teaming: true })
  700. } else {
  701. nets.push({ exit: false })
  702. }
  703. }
  704. if (nets.length > 0) {
  705. params.nets = nets
  706. }
  707. if (this.isShowImages) {
  708. // 判断数据盘是否合法
  709. if (this.diskOptionsDate.length > 0) {
  710. if (this.isShowFalseIcon) {
  711. this.$message.error(this.$t('compute.text_319'))
  712. throw new Error(this.$t('compute.text_319'))
  713. }
  714. // 将系统盘放置首位
  715. const systemDisk = this.diskOptionsDate[0].chartData.rows.pop()
  716. this.diskOptionsDate[0].chartData.rows.unshift(systemDisk)
  717. for (var i = 0; i < this.diskOptionsDate.length; i++) {
  718. const rows = this.diskOptionsDate[i].chartData.rows
  719. const adapter = Number(this.diskOptionsDate[i].diskInfo[1].charAt(this.diskOptionsDate[i].diskInfo[1].length - 1))
  720. const configOption = {
  721. conf: this.diskOptionsDate[i].diskInfo[2],
  722. driver: this.diskOptionsDate[i].diskInfo[0],
  723. count: this.diskOptionsDate[i].count,
  724. range: this.diskOptionsDate[i].range,
  725. adapter,
  726. type: this.diskOptionsDate[i].type === 'HDD' ? 'rotate' : 'ssd',
  727. }
  728. diskConfigs.push(configOption)
  729. for (var j = 0; j < rows.length; j++) {
  730. let option = {
  731. size: rows[j].size * 1024,
  732. fs: rows[j].format,
  733. mountpoint: rows[j].name,
  734. }
  735. if (i === 0 && j === 0) {
  736. option = {
  737. size: rows[j].size * 1024,
  738. image_id: this.selectedImage.id,
  739. }
  740. }
  741. if (j === rows.length - 1) {
  742. option.size = -1
  743. if (!rows[j].format) {
  744. Reflect.deleteProperty(option, 'fs')
  745. }
  746. if (rows[j].name === this.$t('compute.text_315')) {
  747. Reflect.deleteProperty(option, 'mountpoint')
  748. }
  749. }
  750. disks.push(option)
  751. }
  752. }
  753. // 根据adapter排序diskConfigs
  754. diskConfigs.sort((a, b) => { return a.adapter - b.adapter })
  755. return {
  756. ...params,
  757. name: values.name,
  758. host_type: values.host_type,
  759. disks,
  760. baremetal_disk_configs: diskConfigs,
  761. }
  762. } else {
  763. this.$message.error(this.$t('compute.text_842'))
  764. throw new Error(this.$t('compute.text_842'))
  765. }
  766. }
  767. return {
  768. ...params,
  769. name: values.name,
  770. host_type: values.host_type,
  771. raid: values.raid,
  772. }
  773. },
  774. async handleConfirm () {
  775. this.loading = true
  776. try {
  777. const values = await this.validateForm()
  778. const params = this.genParmas(values)
  779. await this.doConvert(params)
  780. this.loading = false
  781. this.cancelDialog()
  782. } catch (error) {
  783. this.loading = false
  784. }
  785. },
  786. },
  787. }
  788. </script>
  789. <style lang="less" scoped>
  790. /deep/ .network-item {
  791. width: 260px;
  792. }
  793. </style>