AdjustConfigForm.vue 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  1. <template>
  2. <div>
  3. <page-header :title="title" style="margin-bottom: 7px;" />
  4. <a-alert class="mb-2" type="warning" v-if="tips">
  5. <div slot="message">
  6. {{ tips }}
  7. </div>
  8. </a-alert>
  9. <a-card :bordered="false" size="small">
  10. <template #title>
  11. <dialog-selected-tips :name="$t('dictionary.server')" :count="dataList.length" :action="$t('compute.text_1100')" />
  12. </template>
  13. <dialog-table :data="dataList" :columns="columns" />
  14. </a-card>
  15. <page-body needMarginBottom>
  16. <div class="form-wrapper">
  17. <a-form
  18. v-bind="formItemLayout"
  19. :form="form.fc">
  20. <a-form-item v-if="isShowCpu" :label="$t('compute.text_1058')" class="mb-0">
  21. <cpu-radio
  22. :decorator="decorators.vcpu"
  23. :options="form.fi.cpuMem.cpus || []"
  24. :disable-options="disableCpus"
  25. :disabled="runningArm || runningOther"
  26. :extra="cpuExtra"
  27. :max="form.fd.vcpu < 32 ? 32 : 128"
  28. :form="form"
  29. :hypervisor="hypervisor"
  30. :showCpuSocketsInit="form.fi.showCpuSockets"
  31. :cpuSocketsInit="selectedItem.vcpu_count / selectedItem.cpu_sockets"
  32. :serverStatus="selectedItem.status"
  33. @change="cpuChange" />
  34. </a-form-item>
  35. <a-form-item :label="$t('compute.text_369')" class="mb-0">
  36. <mem-radio :decorator="decorators.vmem" :options="form.fi.cpuMem.mems_mb || []" :disable-options="disableMems" :disabled="runningArm || runningOther" :extra="cpuExtra" />
  37. </a-form-item>
  38. <a-form-item :label="$t('compute.text_109')">
  39. <sku
  40. v-decorator="decorators.sku"
  41. :type="type"
  42. :priceUnit="skuPriceUnit"
  43. :sku-params="skuParam"
  44. :sku-filter="skuFilter"
  45. :require-sys-disk-type="requireSysDiskType"
  46. :require-data-disk-types="requireDataDiskTypes"
  47. :instance-type="instanceType"
  48. :hypervisor="hypervisor"
  49. :canSkuShow="diskLoaded"
  50. :hasMeterService="hasMeterService"
  51. :skuDisabled="runningOther"
  52. :dataSku="dataSku"
  53. :dataList="dataList"
  54. :supportSkuTypes="supportSkuTypes"
  55. :disableSkuType="disableSkuTypeValue"
  56. isAdjustConfig />
  57. </a-form-item>
  58. <a-form-item :label="$t('compute.text_49')" v-show="selectedItems.length === 1 && form.fd.defaultType">
  59. <system-disk
  60. v-if="isRenderSystemDisk"
  61. :decorator="decorators.systemDisk"
  62. :type="type"
  63. :hypervisor="hypervisor"
  64. :sku="form.fd.sku"
  65. :form="form"
  66. :defaultSize="sysdisk.value"
  67. :defaultType="form.fd.defaultType"
  68. :defaultIops="sysdisk.iops"
  69. :defaultThroughput="sysdisk.throughput"
  70. :capability-data="form.fi.capability"
  71. :disabled="true"
  72. :ignoreStorageStatus="true"
  73. is-iops-show
  74. is-throughput-show />
  75. </a-form-item>
  76. <a-form-item :label="$t('compute.text_50')" v-show="selectedItems.length === 1 && selectedItems[0].hypervisor !== 'zettakit'">
  77. <data-disk
  78. v-if="isRenderDataDisk"
  79. ref="dataDiskRef"
  80. :decorator="decorators.dataDisk"
  81. :type="type"
  82. :form="form"
  83. :hypervisor="hypervisor"
  84. :capability-data="form.fi.capability"
  85. :defaultType="form.fd.systemDiskType"
  86. :sku="form.fd.sku"
  87. :image="form.fi.imageMsg"
  88. :domain="domain"
  89. :storageParams="dataDiskStorageParams"
  90. is-iops-show
  91. is-throughput-show />
  92. </a-form-item>
  93. <a-form-item :label="$t('compute.text_1041')" v-if="isOpenWorkflow">
  94. <a-input v-decorator="decorators.reason" :placeholder="$t('compute.text_1105')" />
  95. </a-form-item>
  96. <a-form-item :label="$t('compute.text_494')" :extra="$t('compute.text_1106')">
  97. <a-switch :checkedChildren="$t('compute.text_115')" :unCheckedChildren="$t('compute.text_116')" v-decorator="decorators.autoStart" :disabled="isSomeRunning" />
  98. </a-form-item>
  99. </a-form>
  100. </div>
  101. </page-body>
  102. <page-footer>
  103. <div slot="right">
  104. <div class="d-flex align-items-center">
  105. <div v-if="hasMeterService" class="mr-4 d-flex align-items-center">
  106. <div class="text-truncate">{{$t('compute.text_286')}}</div>
  107. <div class="ml-2 prices">
  108. <div class="hour error-color d-flex">
  109. <template v-if="price">
  110. <m-animated-number :value="price" :formatValue="priceFormat" />
  111. <discount-price class="ml-2 mini-text" :discount="discount" :origin="originPrice" />
  112. </template>
  113. <template v-else>---</template>
  114. </div>
  115. <div class="tips text-truncate">
  116. <span v-html="priceTips" />
  117. </div>
  118. </div>
  119. </div>
  120. <a-button class="mr-3" type="primary" @click="handleConfirm" :loading="loading">{{confirmText}}</a-button>
  121. <a-button @click="cancel">{{$t('compute.text_908')}}</a-button>
  122. </div>
  123. </div>
  124. </page-footer>
  125. </div>
  126. </template>
  127. <script>
  128. import { mapGetters } from 'vuex'
  129. import * as R from 'ramda'
  130. import _ from 'lodash'
  131. import CpuRadio from '@Compute/sections/CpuRadio'
  132. import MemRadio from '@Compute/sections/MemRadio'
  133. import DataDisk from '@Compute/sections/DataDisk'
  134. import SystemDisk from '@Compute/views/vminstance/create/components/SystemDisk'
  135. import sku from '@Compute/sections/SKU'
  136. import { SERVER_TYPE, EIP_TYPES_MAP, MEDIUM_MAP } from '@Compute/constants'
  137. import SystemIcon from '@/sections/SystemIcon'
  138. import { Manager } from '@/utils/manager'
  139. import WindowsMixin from '@/mixins/windows'
  140. import WorkflowMixin from '@/mixins/workflow'
  141. import { HYPERVISORS_MAP } from '@/constants'
  142. import { PriceFetcher } from '@/utils/common/price'
  143. import {
  144. getNameDescriptionTableColumn,
  145. getIpsTableColumn,
  146. getProjectTableColumn,
  147. getStatusTableColumn,
  148. getRegionTableColumn,
  149. } from '@/utils/common/tableColumn'
  150. import { findPlatform, diskSupportTypeMedium, getOriginDiskKey } from '@/utils/common/hypervisor'
  151. import { isRequired } from '@/utils/validate'
  152. import { sizestr } from '@/utils/utils'
  153. import { STORAGE_TYPES, HOST_CPU_ARCHS } from '@/constants/compute'
  154. import DiscountPrice from '@/sections/DiscountPrice'
  155. export default {
  156. name: 'VMInstanceAdjustConfig',
  157. components: {
  158. CpuRadio,
  159. MemRadio,
  160. sku,
  161. DataDisk,
  162. SystemDisk,
  163. DiscountPrice,
  164. },
  165. mixins: [WindowsMixin, WorkflowMixin],
  166. props: {
  167. params: {
  168. type: Object,
  169. },
  170. },
  171. data () {
  172. function diskValidator (rule, value, callback) {
  173. if (R.isNil(value) || R.isEmpty(value)) {
  174. return callback(new Error(this.$t('compute.text_206')))
  175. }
  176. if (!value.startsWith('/')) {
  177. return callback(new Error(this.$t('compute.text_207')))
  178. }
  179. if (value === '/') {
  180. return callback(new Error(this.$t('compute.text_208')))
  181. }
  182. callback()
  183. }
  184. const dataList = [...this.params.data]
  185. dataList.sort((a, b) => b.vcpu_count - a.vcpu_count)
  186. const itemData = dataList[0]
  187. const autoStart = dataList.some(val => val.status === 'running')
  188. return {
  189. loading: false,
  190. action: this.$t('compute.text_1100'),
  191. dataList,
  192. form: {
  193. fc: this.$form.createForm(this, {
  194. onValuesChange: this.onValuesChange,
  195. }),
  196. fd: {
  197. vcpu: 2,
  198. vmem: 2048,
  199. diskType: null,
  200. dataDiskSizes: {},
  201. dataDiskTypes: [],
  202. hypervisor: itemData.hypervisor,
  203. },
  204. fi: {
  205. cpuMem: {}, // cpu 和 内存 的关联关系
  206. capability: {},
  207. imageMsg: {}, // 当前选中的 image
  208. showCpuSockets: false,
  209. cpuSockets: 1,
  210. modificationTypes: [], // 多选实例时,各主机 modification_types.name 的交集
  211. },
  212. },
  213. beforeDataDisks: [],
  214. decorators: {
  215. vcpu: [
  216. 'vcpu',
  217. {
  218. initialValue: itemData.vcpu_count,
  219. },
  220. ],
  221. vmem: [
  222. 'vmem',
  223. {
  224. initialValue: itemData.vmem_size,
  225. },
  226. ],
  227. sku: [
  228. 'sku',
  229. {
  230. rules: [
  231. { required: true, message: this.$t('compute.text_216') },
  232. ],
  233. },
  234. ],
  235. systemDisk: {
  236. type: [
  237. 'systemDiskType',
  238. {
  239. rules: [
  240. { validator: isRequired(), message: this.$t('compute.text_121') },
  241. ],
  242. },
  243. ],
  244. size: [
  245. 'systemDiskSize',
  246. {
  247. rules: [
  248. { required: true, message: this.$t('compute.text_122') },
  249. ],
  250. },
  251. ],
  252. schedtag: [
  253. 'systemDiskSchedtag',
  254. {
  255. validateTrigger: ['change', 'blur'],
  256. rules: [{
  257. required: true,
  258. message: this.$t('compute.text_123'),
  259. }],
  260. },
  261. ],
  262. policy: [
  263. 'systemDiskPolicy',
  264. {
  265. validateTrigger: ['blur', 'change'],
  266. rules: [{
  267. required: true,
  268. message: this.$t('compute.text_123'),
  269. }],
  270. },
  271. ],
  272. iops: [
  273. 'systemDiskIops',
  274. {
  275. rules: [{
  276. required: true,
  277. message: this.$t('compute.iops_input_tip'),
  278. }],
  279. },
  280. ],
  281. throughput: [
  282. 'systemDiskThroughput',
  283. {
  284. rules: [{
  285. required: true,
  286. message: this.$t('compute.throuthput_input_tip'),
  287. }],
  288. },
  289. ],
  290. },
  291. dataDisk: {
  292. type: i => [
  293. `dataDiskTypes[${i}]`,
  294. {
  295. rules: [
  296. { validator: isRequired(), message: this.$t('compute.text_121') },
  297. ],
  298. },
  299. ],
  300. size: i => [
  301. `dataDiskSizes[${i}]`,
  302. {
  303. rules: [
  304. { required: true, message: this.$t('compute.text_122') },
  305. ],
  306. },
  307. ],
  308. schedtag: i => [
  309. `dataDiskSchedtags[${i}]`,
  310. {
  311. validateTrigger: ['change', 'blur'],
  312. rules: [{
  313. required: true,
  314. message: this.$t('compute.text_123'),
  315. }],
  316. },
  317. ],
  318. policy: i => [
  319. `dataDiskPolicys[${i}]`,
  320. {
  321. validateTrigger: ['blur', 'change'],
  322. rules: [{
  323. required: true,
  324. message: this.$t('compute.text_123'),
  325. }],
  326. },
  327. ],
  328. snapshot: i => [
  329. `dataDiskSnapshots[${i}]`,
  330. {
  331. validateTrigger: ['blur', 'change'],
  332. rules: [{
  333. required: true,
  334. message: this.$t('compute.text_124'),
  335. }],
  336. },
  337. ],
  338. filetype: i => [
  339. `dataDiskFiletypes[${i}]`,
  340. {
  341. validateTrigger: ['blur', 'change'],
  342. rules: [{
  343. required: true,
  344. message: this.$t('compute.text_125'),
  345. }],
  346. },
  347. ],
  348. mountPath: i => [
  349. `dataDiskMountPaths[${i}]`,
  350. {
  351. validateTrigger: ['blur', 'change'],
  352. rules: [{
  353. required: true,
  354. message: this.$t('compute.text_126'),
  355. }, {
  356. validator: diskValidator,
  357. }],
  358. },
  359. ],
  360. storage: i => [
  361. `dataDiskStorages[${i}]`,
  362. {
  363. rules: [{
  364. required: true,
  365. message: this.$t('compute.text_1351'),
  366. }],
  367. },
  368. ],
  369. preallocation: i => [
  370. `dataDiskPreallocation[${i}]`,
  371. ],
  372. iops: i => [
  373. `dataDiskIops[${i}]`,
  374. {
  375. rules: [{
  376. required: true,
  377. message: this.$t('compute.iops_input_tip'),
  378. }],
  379. },
  380. ],
  381. throughput: i => [
  382. `dataDiskThroughputs[${i}]`,
  383. {
  384. rules: [{
  385. required: true,
  386. message: this.$t('compute.throughput_input_tip'),
  387. }],
  388. },
  389. ],
  390. },
  391. reason: [
  392. 'reason',
  393. {
  394. initialValue: '',
  395. },
  396. ],
  397. autoStart: [
  398. 'autoStart',
  399. {
  400. valuePropName: 'checked',
  401. initialValue: autoStart,
  402. },
  403. ],
  404. },
  405. formItemLayout: {
  406. wrapperCol: {
  407. span: 20,
  408. xxl: {
  409. span: 22,
  410. },
  411. },
  412. labelCol: {
  413. span: 4,
  414. xxl: {
  415. span: 2,
  416. },
  417. },
  418. },
  419. diskLoaded: false,
  420. domain: itemData.domain_id,
  421. cloudaccountId: itemData.account_id,
  422. sysdisk: {},
  423. origin_price: null,
  424. price: null,
  425. priceFormat: null,
  426. currency: '',
  427. priceTips: '--',
  428. discount: 0,
  429. dataDiskType: '',
  430. dataDiskInterval: null,
  431. supportSkuTypes: [],
  432. disableSkuTypeValue: false,
  433. }
  434. },
  435. computed: {
  436. ...mapGetters(['isAdminMode', 'scope', 'userInfo']),
  437. title () {
  438. return this.isOpenWorkflow ? `${this.$t('compute.text_1100')} ${this.$route.query.workflow ? `(${this.$t('common.modify_workflow')})` : ''}` : this.$t('compute.text_1100')
  439. },
  440. scopeParams () {
  441. if (this.$store.getters.isAdminMode) {
  442. return {
  443. project_domain: this.dataList[0].domain_id,
  444. }
  445. }
  446. return { scope: this.$store.getters.scope }
  447. },
  448. selectedItems () {
  449. return this.dataList
  450. },
  451. selectedItem () {
  452. return this.dataList[0]
  453. },
  454. dataSku () {
  455. const target = (this.selectedItem.disksInfo || []).filter(item => item.disk_type === 'sys')
  456. return {
  457. name: this.selectedItem?.instance_type,
  458. sys_disk_type: target.length ? target[0].storage_type : '',
  459. }
  460. },
  461. count () {
  462. return this.selectedItems.length || 1
  463. },
  464. isSomeRunning () {
  465. return this.dataList.some(val => val.status === 'running')
  466. },
  467. isSomeArm () {
  468. return this.selectedItem.os_arch === 'arm'
  469. },
  470. runningArm () {
  471. return this.isSomeArm && this.isSomeRunning
  472. },
  473. runningOther () {
  474. return this.dataList.some(val => {
  475. if (val.status === 'running' && [HYPERVISORS_MAP.aliyun.hypervisor, HYPERVISORS_MAP.aws.hypervisor, HYPERVISORS_MAP.google.hypervisor, HYPERVISORS_MAP.huawei.hypervisor, HYPERVISORS_MAP.ctyun.hypervisor, HYPERVISORS_MAP.volcengine.hypervisor, HYPERVISORS_MAP.ksyun.hypervisor].includes(val.hypervisor)) {
  476. return true
  477. }
  478. return false
  479. })
  480. },
  481. disableSkuType () {
  482. return [HYPERVISORS_MAP.aliyun.hypervisor, HYPERVISORS_MAP.huawei.hypervisor, HYPERVISORS_MAP.qcloud.hypervisor].includes(this.dataList[0].hypervisor)
  483. },
  484. hotplug () { // 做热扩容校验,true 表示置灰 CPU 和 内存,不支持热扩容
  485. if (this.dataList.every(val => val.status === 'ready')) {
  486. return false
  487. } else {
  488. if (this.dataList.every(val => {
  489. if ([HYPERVISORS_MAP.kvm.hypervisor, HYPERVISORS_MAP.zstack.hypervisor].includes(val.hypervisor)) {
  490. if (val.status === 'ready') {
  491. return true
  492. } else {
  493. return val.metadata && val.metadata.hotplug_cpu_mem === 'enable'
  494. }
  495. }
  496. return true
  497. })) {
  498. return false
  499. } else {
  500. return true
  501. }
  502. }
  503. },
  504. hypervisor () {
  505. return this.selectedItem.hypervisor
  506. },
  507. tips () {
  508. if (this.hotplug) {
  509. return this.$t('compute.text_1107')
  510. }
  511. if ([HYPERVISORS_MAP.kvm.hypervisor, HYPERVISORS_MAP.azure.hypervisor].includes(this.hypervisor)) {
  512. return this.$t('compute.text_1108')
  513. }
  514. return ''
  515. },
  516. type () {
  517. const brand = this.selectedItem.brand
  518. return findPlatform(brand)
  519. },
  520. skuParam () {
  521. const params = {
  522. limit: 0,
  523. usable: true,
  524. enabled: true,
  525. cpu_core_count: this.form.fd.vcpu || this.decorators.vcpu[1].initialValue,
  526. memory_size_mb: this.form.fd.vmem || this.decorators.vmem[1].initialValue,
  527. }
  528. if (this.type === SERVER_TYPE.idc) {
  529. params.provider = HYPERVISORS_MAP.kvm.provider
  530. params.public_cloud = false
  531. params.postpaid_status = 'available'
  532. if (this.selectedItem) {
  533. params.cloudregion = this.selectedItem.cloudregion_id
  534. }
  535. }
  536. if (this.type === SERVER_TYPE.private) {
  537. // nutanix vmware incloudshpere proxmox sangfor
  538. if (this.selectedItem && (this.selectedItem.provider === HYPERVISORS_MAP.nutanix.provider || this.selectedItem.provider === HYPERVISORS_MAP.incloudsphere.provider || this.selectedItem.provider === HYPERVISORS_MAP.proxmox.provider || this.selectedItem.provider === HYPERVISORS_MAP.sangfor.provider || this.selectedItem.provider === HYPERVISORS_MAP.uis.provider)) {
  539. params['provider.0'] = HYPERVISORS_MAP.kvm.provider
  540. } else if (this.selectedItem.provider === HYPERVISORS_MAP.cnware.provider) {
  541. params.usable = false
  542. } else {
  543. params.cloudregion_id = this.selectedItem.cloudregion_id
  544. }
  545. params.postpaid_status = 'available'
  546. if (this.selectedItem.provider === HYPERVISORS_MAP.sangfor.provider) {
  547. delete params.usable
  548. }
  549. }
  550. if (this.type === SERVER_TYPE.public) {
  551. params.public_cloud = true
  552. params.zone_id = this.selectedItem.zone_id
  553. if (this.selectedItem.billing_type === 'postpaid') {
  554. params.postpaid_status = 'available'
  555. } else if (this.selectedItem.billing_type === 'prepaid') {
  556. params.prepaid_status = 'available'
  557. }
  558. }
  559. if (this.selectedItem.os_arch) {
  560. if (this.selectedItem.os_arch.includes('x86')) {
  561. params.cpu_arch = HOST_CPU_ARCHS.x86.key
  562. } else if (this.selectedItem.os_arch.includes('arm') || this.selectedItem.os_arch.includes('aarch64')) {
  563. params.cpu_arch = HOST_CPU_ARCHS.arm.key
  564. }
  565. }
  566. return params
  567. },
  568. disableCpus () {
  569. const runningList = this.dataList.filter(item => item.status === 'running')
  570. const cpu = runningList.length ? runningList[0].vcpu_count : this.selectedItem.vcpu_count
  571. const cpus = this.form.fi.cpuMem.cpus || []
  572. if (this.isSomeRunning && cpus.length > 0) {
  573. return cpus.filter((item) => { return item < cpu })
  574. }
  575. return []
  576. },
  577. disableMems () {
  578. const runningList = this.dataList.filter(item => item.status === 'running')
  579. runningList.sort((a, b) => b.vmem_size - a.vmem_size)
  580. const vmem = runningList.length ? runningList[0].vmem_size : this.selectedItem.vmem_size
  581. const mems = this.form.fi.cpuMem.mems_mb || []
  582. if (this.isSomeRunning && mems.length > 0) {
  583. return mems.filter((item) => { return item < vmem })
  584. }
  585. return []
  586. },
  587. isOpenWorkflow () {
  588. return this.checkWorkflowEnabled(this.WORKFLOW_TYPES.APPLY_SERVER_CHANGECONFIG)
  589. },
  590. columns () {
  591. return [
  592. getNameDescriptionTableColumn({
  593. hideField: true,
  594. addLock: true,
  595. addBackup: true,
  596. edit: false,
  597. editDesc: false,
  598. slotCallback: row => {
  599. return (
  600. <side-page-trigger>{ row.name }</side-page-trigger>
  601. )
  602. },
  603. }),
  604. getIpsTableColumn({ field: 'ip', title: 'IP' }),
  605. {
  606. field: 'instance_type',
  607. title: this.$t('compute.text_295'),
  608. showOverflow: 'ellipsis',
  609. minWidth: 120,
  610. sortable: true,
  611. slots: {
  612. default: ({ row }) => {
  613. const ret = []
  614. if (row.instance_type) {
  615. ret.push(<div class='text-truncate' style={{ color: '#0A1F44' }}>{ row.instance_type }</div>)
  616. }
  617. const config = row.vcpu_count + 'C' + sizestr(row.vmem_size, 'M', 1024) + (row.disk ? sizestr(row.disk, 'M', 1024) : '')
  618. return ret.concat(<div class='text-truncate' style={{ color: '#53627C' }}>{ config }</div>)
  619. },
  620. },
  621. },
  622. {
  623. field: 'os_type',
  624. title: this.$t('table.title.os'),
  625. width: 50,
  626. slots: {
  627. default: ({ row }) => {
  628. let name = (row.metadata && row.metadata.os_distribution) ? row.metadata.os_distribution : row.os_type || ''
  629. if (name.includes('Windows') || name.includes('windows')) {
  630. name = 'Windows'
  631. }
  632. const version = (row.metadata && row.metadata.os_version) ? `${row.metadata.os_version}` : ''
  633. const tooltip = (version.includes(name) ? version : `${name} ${version}`) || this.$t('compute.text_339') // 去重
  634. return [
  635. <SystemIcon tooltip={ tooltip } name={ name } />,
  636. ]
  637. },
  638. },
  639. },
  640. getStatusTableColumn({ statusModule: 'server' }),
  641. getProjectTableColumn(),
  642. getRegionTableColumn(),
  643. ]
  644. },
  645. instanceType () {
  646. return this.selectedItem.instance_type
  647. },
  648. hasMeterService () { // 是否有计费的服务
  649. const { services } = this.$store.getters.userInfo
  650. const meterService = services.find(val => val.type === 'meter')
  651. if (meterService && meterService.status === true) {
  652. return true
  653. }
  654. return false
  655. },
  656. // 是否为包年包月
  657. isPackage () {
  658. return this.selectedItem.billing_type === 'prepaid'
  659. },
  660. durationNum () {
  661. if (this.isPackage) {
  662. const { duration } = this.form.fd
  663. let num = parseInt(duration)
  664. if (num && duration.endsWith('Y')) {
  665. num *= 12 // 1年=12月
  666. } else if (num && duration.endsWith('W')) {
  667. num *= 0.25 // 1周=0.25月
  668. }
  669. return num
  670. }
  671. return 0
  672. },
  673. disk () {
  674. const diskValueArr = [this.form.fd.systemDiskSize]
  675. R.forEachObjIndexed(value => {
  676. diskValueArr.push(value)
  677. }, this.form.fd.dataDiskSizes)
  678. return diskValueArr.reduce((prevDisk, diskValue) => prevDisk + diskValue, 0)
  679. },
  680. confirmText () {
  681. return this.isOpenWorkflow ? (this.$route.query.workflow ? this.$t('common.modify_workflow') : this.$t('compute.text_288')) : this.$t('compute.text_907')
  682. },
  683. cpuExtra () {
  684. if (this.runningArm) {
  685. return this.$t('compute.text_1366')
  686. }
  687. return null
  688. },
  689. memExtra () {
  690. if (this.runningArm) {
  691. return this.$t('compute.text_1367')
  692. }
  693. return null
  694. },
  695. isPublic () {
  696. return this.dataList[0].cloud_env === SERVER_TYPE.public
  697. },
  698. originPrice () {
  699. if (this.origin_price) {
  700. this.$emit('getOriginPrice', this.origin_price)
  701. }
  702. return this.origin_price
  703. },
  704. requireSysDiskType () {
  705. if (this.sysdisk && this.sysdisk.type) {
  706. return [this.sysdisk.type]
  707. }
  708. return []
  709. },
  710. requireDataDiskTypes () {
  711. const types = []
  712. if (this.form && this.form.fd && this.form.fd.datadisks) {
  713. for (let i = 0; i < this.form.fd.datadisks.length; i++) {
  714. if (this.form.fd.datadisks[i].type && !types.includes(this.form.fd.datadisks[i].type)) {
  715. types.push(this.form.fd.datadisks[i].type)
  716. }
  717. }
  718. }
  719. return types
  720. },
  721. dataDiskStorageParams () {
  722. const { systemDiskType = {}, hypervisor } = this.form.fd
  723. let key = systemDiskType.key || ''
  724. // 磁盘区分介质
  725. if (diskSupportTypeMedium(hypervisor)) {
  726. key = getOriginDiskKey(key)
  727. }
  728. const { prefer_manager, schedtag } = this.form.fd
  729. const params = {
  730. ...this.scopeParams,
  731. usable: true, // 包含了 enable:true, status为online的数据
  732. brand: this.selectedItem.brand, // kvm,vmware支持指定存储
  733. manager: prefer_manager,
  734. host_schedtag_id: schedtag,
  735. host_id: this.dataList[0].host_id,
  736. }
  737. if (key) {
  738. params.filter = [`storage_type.contains("${key}")`]
  739. }
  740. return params
  741. },
  742. skuPriceUnit () {
  743. if (this.isPackage) {
  744. return {
  745. key: 'month_price',
  746. unit: this.$t('compute.text_173'),
  747. }
  748. }
  749. return {
  750. key: 'hour_price',
  751. unit: this.$t('compute.text_172'),
  752. }
  753. },
  754. isRenderSystemDisk () {
  755. return this.hypervisor && this.form.fi.capability.storage_types3 && this.form.fd.defaultType
  756. },
  757. isRenderDataDisk () {
  758. if (this.hypervisor === HYPERVISORS_MAP.cnware.key) {
  759. return false
  760. }
  761. return this.hypervisor && this.form.fi.capability.storage_types3 && this.form.fd.sku
  762. },
  763. isShowCpu () {
  764. return (this.form.fi.cpuMem?.cpus || []).includes(this.form.fd.vcpu)
  765. },
  766. isShowMem () {
  767. return (this.form.fi.cpuMem?.cpus || []).includes(this.form.fd.vcpu) && ((this.form.fi.cpuMem?.cpu_mems_mb && this.form.fi.cpuMem?.cpu_mems_mb[this.form.fd.vcpu]) || []).includes(this.form.fd.vmem)
  768. },
  769. },
  770. watch: {
  771. priceTips: {
  772. handler (val) {
  773. let ret = `${this.currency} ${this.price && this.price.toFixed(2)}`
  774. ret += !this.isPackage ? this.$t('compute.text_296') : ''
  775. this.$bus.$emit('VMGetPrice', `${ret} ${val}`)
  776. },
  777. immediate: true,
  778. },
  779. dataDiskType (val, oldV) {
  780. if (val !== oldV) {
  781. this.getPriceList()
  782. }
  783. },
  784. },
  785. created () {
  786. this.serversManager = new Manager('servers', 'v2')
  787. this.zonesM2 = new Manager('zones', 'v2')
  788. this.serverskusM = new Manager('serverskus')
  789. this.loadData(this.dataList)
  790. this.fetchInstanceSpecs()
  791. this.fetchModificationTypes()
  792. this.getPriceList = _.debounce(this._getPriceList2, 500)
  793. this.baywatch([
  794. 'form.fd.sku.id',
  795. 'form.fd.dataDiskSizes',
  796. ], (val) => {
  797. if (val) {
  798. this.getPriceList()
  799. }
  800. })
  801. },
  802. beforeDestroy () {
  803. clearInterval(this.dataDiskInterval)
  804. },
  805. methods: {
  806. fetchModificationTypes () {
  807. if (this.disableSkuType) {
  808. Promise.all(this.dataList.map(item => this.serversManager.getSpecific({ id: item.id, spec: 'modification-types' }))).then((data) => {
  809. const list = data.map((res) => {
  810. return (res.data.modification_types || []).map((mt) => mt.name)
  811. })
  812. this.supportSkuTypes = (list.length
  813. ? list.reduce((acc, names) => acc.filter((n) => names.includes(n)), [...list[0]])
  814. : [])
  815. }).finally(() => {
  816. this.disableSkuTypeValue = true
  817. })
  818. }
  819. },
  820. skuFilter (items) {
  821. if (!items) return []
  822. return items
  823. },
  824. async loadData (data) {
  825. this.data = data
  826. if (this.data.length > 0) {
  827. try {
  828. const { data } = await this.capability(this.data[0].zone_id)
  829. this.form.fi.capability = data
  830. } catch (error) {}
  831. }
  832. const conf = this.maxConfig()
  833. this.form.fd.vcpu_count = conf[0]
  834. this.form.fd.vmem = conf[1] * 1024
  835. this.form.fd.datadisks = conf[2]
  836. this.form.fd.sysdisks = conf[3]
  837. this.beforeDataDisks = [...this.form.fd.datadisks]
  838. if (this.form.fd.sysdisks && this.form.fd.sysdisks.length === 1) {
  839. this.sysdisk = this.form.fd.sysdisks[0]
  840. const storageItem = STORAGE_TYPES[this.selectedItem.hypervisor]
  841. // 磁盘区分介质
  842. let diskKey = this.sysdisk.type
  843. let diskLabel = R.is(Object, storageItem) ? (storageItem[diskKey]?.label || diskKey) : diskKey
  844. const { disk_type, medium_type } = this.selectedItem.disks_info[0] || {}
  845. if (diskSupportTypeMedium(this.selectedItem.hypervisor) && disk_type === 'sys' && medium_type) {
  846. diskLabel = `${diskLabel}(${MEDIUM_MAP[medium_type]})`
  847. diskKey = `${diskKey}/${medium_type}`
  848. }
  849. this.form.fd.defaultType = {
  850. [this.decorators.systemDisk.type[0]]: { key: diskKey, label: diskLabel },
  851. }
  852. }
  853. const dataDisks = this.selectedItem.disks_info.filter(item => item.disk_type === 'data' || item.disk_type === 'swap')
  854. const { medium_type: dataDiskMedium } = dataDisks[0] || {}
  855. if ([HYPERVISORS_MAP.esxi.key].includes(this.hypervisor)) {
  856. if (this.selectedItem.cpu_sockets) {
  857. this.form.fi.showCpuSockets = true
  858. this.form.fi.cpuSockets = this.selectedItem.cpu_sockets
  859. }
  860. }
  861. this.$nextTick(() => {
  862. this.diskLoaded = true
  863. this.form.fc.setFieldsValue({ vcpu: this.form.fd.vcpu_count, vmem: this.form.fd.vmem })
  864. this.dataDiskInterval = setInterval(() => {
  865. if (this.isRenderDataDisk) {
  866. this.form.fd.datadisks.forEach((v, i) => {
  867. this.$refs.dataDiskRef.add({ size: v.value, min: v.value, diskType: v.type, disabled: true, sizeDisabled: true, medium: dataDiskMedium, ...v })
  868. })
  869. clearInterval(this.dataDiskInterval)
  870. this.dataDiskInterval = null
  871. }
  872. }, 500)
  873. })
  874. },
  875. maxConfig () {
  876. let cpu = 0
  877. let mem = 0
  878. const datadisks = []
  879. const sysdisks = []
  880. for (let i = 0; i < this.data.length; i++) {
  881. if (cpu < this.data[i].vcpu_count) {
  882. cpu = this.data[i].vcpu_count
  883. }
  884. if (mem < this.data[i].vmem_size) {
  885. mem = this.data[i].vmem_size
  886. }
  887. if (this.data[i].disks_info) {
  888. this.data[i].disks_info.forEach((item) => {
  889. if (item.disk_type !== 'sys') { // 数据盘
  890. datadisks.push({ value: item.size / 1024, type: item.storage_type, medium_type: item.medium_type, iops: item.iops, throughput: item.throughput })
  891. } else { // 系统盘
  892. sysdisks.push({ value: item.size / 1024, type: item.storage_type, medium_type: item.medium_type, iops: item.iops, throughput: item.throughput })
  893. }
  894. })
  895. }
  896. }
  897. return [cpu, mem / 1024, datadisks, sysdisks]
  898. },
  899. async doChangeSettingsByWorkflowSubmit (values) {
  900. const params = {
  901. auto_start: values.autoStart,
  902. }
  903. if (this.selectedItem.instance_type !== values.sku.name) {
  904. params.sku = values.sku.name
  905. }
  906. const ids = this.dataList.map(item => item.id)
  907. if (ids.length === 1) {
  908. params.disks = this.genDiskData(values)
  909. }
  910. const datadisks = this.form.fc.getFieldValue('dataDiskSizes')
  911. let diskSize = 0
  912. if (datadisks) {
  913. R.forEachObjIndexed((value, key) => {
  914. diskSize += value
  915. }, datadisks)
  916. }
  917. const beforeDataDisks = this.beforeDataDisks.map((item) => { return item.value })
  918. let beforeDiskSize = 0
  919. if (beforeDataDisks && beforeDataDisks.length > 0) {
  920. beforeDiskSize = beforeDataDisks.reduce((sum, size) => { return sum + size })
  921. }
  922. const serverConf = this.selectedItems.map((item) => {
  923. const beforeSysDisks = (item.disks_info || []).filter(item => item.disk_type === 'sys').map(item => {
  924. return {
  925. medium_type: item.medium_type,
  926. size: item.size,
  927. type: item.storage_type,
  928. }
  929. })
  930. const beforeDataDisks = (item.disks_info || []).filter(item => item.disk_type === 'data').map(item => {
  931. return {
  932. medium_type: item.medium_type,
  933. size: item.size,
  934. type: item.storage_type,
  935. }
  936. })
  937. return {
  938. name: item.name,
  939. project: item.tenant,
  940. hypervisor: this.selectedItem.hypervisor,
  941. before: {
  942. cpu: item.vcpu_count,
  943. memory: item.vmem_size,
  944. disk: item.disk,
  945. dataDisks: beforeDataDisks,
  946. sysDisks: beforeSysDisks,
  947. sku: item.instance_type,
  948. },
  949. after: {
  950. cpu: this.form.fd.vcpu,
  951. memory: this.form.fd.vmem,
  952. disk: this.selectedItems.length === 1 ? (+item.disk + (+diskSize - beforeDiskSize) * 1024) : null,
  953. dataDisks: this.selectedItems.length === 1 ? params.disks : null,
  954. sysDisks: this.selectedItems.length === 1 ? beforeSysDisks : null,
  955. sku: values.sku.name,
  956. },
  957. }
  958. })
  959. params.project_id = this.userInfo.projectId
  960. params.domain = this.userInfo.projectDomainId
  961. const variables = {
  962. project: this.dataList[0].tenant_id,
  963. project_domain: this.dataList[0].domain_id,
  964. process_definition_key: this.WORKFLOW_TYPES.APPLY_SERVER_CHANGECONFIG,
  965. initiator: this.userInfo.id,
  966. paramter: JSON.stringify(params),
  967. ids: ids.join(','),
  968. serverConf: JSON.stringify(serverConf),
  969. description: values.reason,
  970. }
  971. if (this.$route.query.workflow) {
  972. await this.updateWorkflow(variables, this.$route.query.workflow)
  973. } else {
  974. await this.createWorkflow(variables)
  975. }
  976. this.$message.success(this.$t('compute.text_1109'))
  977. this.$router.push('/workflow')
  978. },
  979. async doChangeSettingsSubmit (values) {
  980. const params = {
  981. sku: values.sku.name,
  982. auto_start: values.autoStart,
  983. }
  984. const { showCpuSockets, cpuSockets } = this.form.fi
  985. const ids = this.dataList.map(item => item.id)
  986. if (ids.length === 1 && this.selectedItem.provider !== HYPERVISORS_MAP.cnware.provider) {
  987. params.disks = this.genDiskData(values)
  988. }
  989. if (showCpuSockets) {
  990. params.cpu_sockets = cpuSockets
  991. }
  992. return this.serversManager.batchPerformAction({
  993. ids,
  994. steadyStatus: ['running', 'ready'],
  995. action: 'change-config',
  996. data: params,
  997. })
  998. },
  999. async handleConfirm () {
  1000. this.loading = true
  1001. try {
  1002. if (!this.form.fd.sku?.name) {
  1003. // 修复套餐为空时,sku值为空对象导致非空校验失效问题
  1004. this.form.fc.setFieldsValue({ sku: null })
  1005. }
  1006. const values = await this.form.fc.validateFields()
  1007. if (this.isOpenWorkflow) {
  1008. const projects = new Set(this.dataList.map(item => item.tenant_id))
  1009. if (projects.size > 1) {
  1010. this.$message.error(this.$t('compute.text_1348'))
  1011. this.loading = false
  1012. return
  1013. }
  1014. await this.doChangeSettingsByWorkflowSubmit(values)
  1015. } else {
  1016. const res = await this.doChangeSettingsSubmit(values)
  1017. const isOk = res.data.data.every(item => item.status === 200)
  1018. if (isOk) {
  1019. this.$message.success(this.$t('compute.text_423'))
  1020. this.$store.commit('keepAlive/ADD_DELAY_EVENT', { name: 'ResourceListSingleRefresh', params: this.data.map(item => item.id) })
  1021. this.cancel()
  1022. }
  1023. }
  1024. } catch (error) {
  1025. throw error
  1026. } finally {
  1027. this.loading = false
  1028. }
  1029. },
  1030. cpuChange (cpu) {
  1031. if (cpu) {
  1032. if (R.is(Object, this.form.fi.cpuMem)) {
  1033. const memOpts = _.get(this.form.fi, `cpuMem.cpu_mems_mb[${cpu}]`) || []
  1034. this.form.fi.cpuMem.mems_mb = memOpts
  1035. this.form.fc.setFieldsValue({
  1036. vmem: Math.max(this.selectedItem.vmem_size, memOpts[0]),
  1037. vcpu: cpu,
  1038. })
  1039. }
  1040. }
  1041. },
  1042. fetchInstanceSpecs () {
  1043. const params = {
  1044. usable: true,
  1045. zone: this.selectedItem.zone_id,
  1046. provider: this.selectedItem.provider === HYPERVISORS_MAP.esxi.provider ? HYPERVISORS_MAP.kvm.provider : this.selectedItem.provider,
  1047. }
  1048. if (this.type === SERVER_TYPE.private) {
  1049. if (this.selectedItem && (this.selectedItem.provider === HYPERVISORS_MAP.hcso.provider || this.selectedItem.provider === HYPERVISORS_MAP.hcs.provider)) {
  1050. params.cloudregion_id = this.selectedItem.cloudregion_id
  1051. } else if (this.selectedItem.provider === HYPERVISORS_MAP.cnware.provider) {
  1052. params.usable = false
  1053. delete params.provider
  1054. params['provider.0'] = HYPERVISORS_MAP.kvm.provider
  1055. params['provider.1'] = this.selectedItem.provider
  1056. } else {
  1057. params.provider = HYPERVISORS_MAP.kvm.provider
  1058. }
  1059. if (this.selectedItem.provider === HYPERVISORS_MAP.sangfor.provider) {
  1060. delete params.usable
  1061. }
  1062. delete params.zone
  1063. }
  1064. if (this.type === SERVER_TYPE.public) {
  1065. if (this.selectedItem.billing_type === 'postpaid') {
  1066. params.postpaid_status = 'available'
  1067. } else if (this.selectedItem.billing_type === 'prepaid') {
  1068. params.prepaid_status = 'available'
  1069. }
  1070. }
  1071. this.serverskusM.get({ id: 'instance-specs', params })
  1072. .then(({ data }) => {
  1073. this.form.fi.cpuMem = data
  1074. const vcpuDecorator = this.decorators.vcpu
  1075. const vcpuInit = vcpuDecorator[1].initialValue
  1076. this.cpuChange(vcpuInit)
  1077. })
  1078. },
  1079. onValuesChange (props, values) {
  1080. Object.keys(values).forEach((key) => {
  1081. let value = values[key]
  1082. if (key === 'dataDiskSizes' && R.is(Object, values[key]) && R.is(Object, this.form.fd.dataDiskSizes)) {
  1083. value = { ...this.form.fd.dataDiskSizes, ...values[key] }
  1084. }
  1085. this.$set(this.form.fd, key, value)
  1086. if (~key.indexOf('dataDiskTypes') && R.is(Object, values)) {
  1087. this.dataDiskType = values[key].key
  1088. }
  1089. if (~key.indexOf('dataDiskSizes[')) {
  1090. const uid = key.replace(/dataDiskSizes\[(.+)\]/, '$1')
  1091. this.$set(this.form.fd.dataDiskSizes, uid, values[key])
  1092. }
  1093. })
  1094. },
  1095. async capability (v) { // 可用区查询
  1096. const params = {
  1097. show_emulated: true,
  1098. }
  1099. if (this.isAdminMode) {
  1100. params.project_domain = this.selectedItem.domain_id
  1101. }
  1102. return this.zonesM2.get({ id: `${v}/capability`, params })
  1103. },
  1104. genDiskData (values) {
  1105. const dataDisk = []
  1106. const len = this.form.fd.sysdisks?.length || -1
  1107. let index = len >= 1 ? len - 1 : len
  1108. const dataDisks = this.$refs.dataDiskRef.dataDisks
  1109. R.forEachObjIndexed((value, key) => {
  1110. const diskObj = {
  1111. disk_type: 'data',
  1112. index: ++index,
  1113. }
  1114. if (values.dataDiskSizes && values.dataDiskSizes[key]) {
  1115. diskObj.size = values.dataDiskSizes[key] * 1024
  1116. }
  1117. if (values.dataDiskTypes) {
  1118. if (values.dataDiskTypes[key]) {
  1119. // 磁盘区分介质
  1120. let diskKey = values.dataDiskTypes[key].key
  1121. if (diskSupportTypeMedium(this.selectedItem.hypervisor)) {
  1122. diskKey = getOriginDiskKey(diskKey)
  1123. }
  1124. diskObj.backend = diskKey
  1125. } else {
  1126. if (_.get(dataDisks, '[0].diskType.key')) {
  1127. // 磁盘区分介质
  1128. let diskKey = _.get(dataDisks, '[0].diskType.key') // 默认添加的盘和第一块保持一致
  1129. if (diskSupportTypeMedium(this.selectedItem.hypervisor)) {
  1130. diskKey = getOriginDiskKey(diskKey)
  1131. }
  1132. diskObj.backend = diskKey
  1133. }
  1134. }
  1135. }
  1136. if (values.dataDiskFiletypes && values.dataDiskFiletypes[key]) {
  1137. diskObj.filetype = values.dataDiskFiletypes[key]
  1138. }
  1139. if (values.dataDiskMountPaths && values.dataDiskMountPaths[key]) {
  1140. diskObj.mountpoint = values.dataDiskMountPaths[key]
  1141. }
  1142. if (values.dataDiskSnapshots && values.dataDiskSnapshots[key]) {
  1143. diskObj.snapshot_id = values.dataDiskSnapshots[key]
  1144. }
  1145. if (values.dataDiskSchedtags && values.dataDiskSchedtags[key]) {
  1146. diskObj.schedtags = [
  1147. { id: values.dataDiskSchedtags[key] },
  1148. ]
  1149. if (values.dataDiskPolicys && values.dataDiskPolicys[key]) {
  1150. diskObj.schedtags[0].strategy = values.dataDiskPolicys[key]
  1151. }
  1152. }
  1153. if (values.dataDiskStorages && values.dataDiskStorages[key]) {
  1154. diskObj.storage_id = values.dataDiskStorages[key]
  1155. }
  1156. if (values.dataDiskPreallocation && values.dataDiskPreallocation[key]) {
  1157. diskObj.preallocation = values.dataDiskPreallocation[key]
  1158. }
  1159. if (values.dataDiskIops && values.dataDiskIops[key]) {
  1160. diskObj.iops = values.dataDiskIops[key]
  1161. }
  1162. if (values.dataDiskThroughputs && values.dataDiskThroughputs[key]) {
  1163. diskObj.throughput = values.dataDiskThroughputs[key]
  1164. }
  1165. // 磁盘区分介质
  1166. if (values.dataDiskTypes && values.dataDiskTypes[key]) {
  1167. const { key: dataDiskKey = '' } = values.dataDiskTypes[key] || {}
  1168. const medium = dataDiskKey.split('/')[1]
  1169. if (diskSupportTypeMedium(this.selectedItem.hypervisor) && medium) {
  1170. diskObj.medium = medium
  1171. }
  1172. }
  1173. dataDisk.push(diskObj)
  1174. }, values.dataDiskSizes)
  1175. if (this.selectedItems.length === 1 && _.get(this.params, 'data[0].disks_info[0].disk_type') === 'data') {
  1176. dataDisk.shift() // 因为第一块盘的disk_type是data,说明无系统盘,第一块盘是ISO启动的,需要去掉
  1177. }
  1178. return dataDisk
  1179. },
  1180. cancel () {
  1181. this.$router.push({ name: 'VMInstanceIndex' })
  1182. },
  1183. baywatch (props, watcher) {
  1184. const iterator = function (prop) {
  1185. this.$watch(prop, watcher)
  1186. }
  1187. props.forEach(iterator, this)
  1188. },
  1189. async _getPriceList2 () {
  1190. const f = this.form.fd
  1191. if (!this.hasMeterService) return // 如果没有 meter 服务则取消调用
  1192. if (R.isEmpty(f.sku) || R.isNil(f.sku)) return
  1193. const isPublic = this.dataList[0].cloud_env === SERVER_TYPE.public
  1194. if (isPublic && (R.isNil(f.sku.region_ext_id) || R.isEmpty(f.sku.region_ext_id))) return
  1195. if (R.isNil(f.systemDiskSize)) return
  1196. const pf = new PriceFetcher()
  1197. pf.initialForm(this.$store.getters.scope, f.sku, f.duration || '1M', this.selectedItem?.billing_type, this.isPublic, this.cloudaccountId)
  1198. // add price items
  1199. if (!isPublic) {
  1200. // server instance
  1201. pf.addCpu(f.vcpu)
  1202. pf.addMem(f.vmem / 1024)
  1203. // gpu
  1204. if (f.gpuEnable && f.gpu && f.gpu.indexOf('=') >= 0) {
  1205. const tmps = f.gpu.split('=')[1].split(':')
  1206. if (tmps.length >= 2) {
  1207. pf.addGpu(`${tmps[0]}.${tmps[1]}`, f.gpuCount || 0)
  1208. }
  1209. }
  1210. } else {
  1211. // server instance
  1212. pf.addServer(f.sku.name, 1)
  1213. // others
  1214. }
  1215. // disks
  1216. const { systemDiskSize, systemDiskType } = f
  1217. const { systemDiskMedium, dataDiskMedium } = this.form.fi
  1218. let systemDisk = systemDiskType.key
  1219. // 磁盘区分介质
  1220. if (diskSupportTypeMedium(this.selectedItem.hypervisor)) {
  1221. systemDisk = getOriginDiskKey(systemDisk)
  1222. }
  1223. if (!isPublic) systemDisk = `${systemDiskMedium}::${systemDisk}`
  1224. pf.addDisk(systemDisk, systemDiskSize)
  1225. if (this.dataDiskType) {
  1226. const datadisks = Object.values(this.form.fd.dataDiskSizes || {})
  1227. let dataDisk = this.dataDiskType
  1228. // 磁盘区分介质
  1229. if (diskSupportTypeMedium(this.selectedItem.hypervisor)) {
  1230. dataDisk = getOriginDiskKey(dataDisk)
  1231. }
  1232. if (!isPublic) dataDisk = `${dataDiskMedium}::${dataDisk}`
  1233. pf.addDisks(dataDisk, datadisks)
  1234. }
  1235. // eip
  1236. if (f.eip_bw && f.eip_type === EIP_TYPES_MAP.new.key) {
  1237. pf.addEipBandwidth(f.eip_bgp_type || '', f.eip_bw)
  1238. }
  1239. const price = await pf.getPriceObj()
  1240. price.setOptions({ count: this.count || 0 })
  1241. this.currency = price.currency
  1242. this.price = price.price
  1243. this.priceFormat = price.priceFormat
  1244. this.origin_price = price.originPrice
  1245. this.priceTips = price.priceTips
  1246. this.discount = price.discount
  1247. },
  1248. isSomeLocal () {
  1249. const { capability = {} } = this.form.fi
  1250. const { storage_types2 = {} } = capability
  1251. const types = storage_types2[this.hypervisor] || []
  1252. const localTypes = types.filter(item => item.indexOf('local') !== -1)
  1253. return localTypes.length > 1
  1254. },
  1255. },
  1256. }
  1257. </script>
  1258. <style lang="less" scoped>
  1259. .form-wrapper {
  1260. padding-left: 22px;
  1261. }
  1262. .prices {
  1263. .hour {
  1264. font-size: 24px;
  1265. }
  1266. .tips {
  1267. color: #999;
  1268. font-size: 12px;
  1269. }
  1270. }
  1271. </style>