index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. <template>
  2. <div class="disk-wrapper d-flex w-auto">
  3. <a-form-item :wrapperCol="{ span: 24 }" :validate-status="storageStatusMap.type">
  4. <a-tag color="blue" v-if="diskTypeLabel && !disabled">{{ diskTypeLabel }}</a-tag>
  5. <a-select v-else v-decorator="decorator.type" labelInValue :style="{minWidth: '300px'}" @change="typeChange" :disabled="disabled || imageType === 'snapshot'">
  6. <a-select-option v-for="(item, key) of typesMap" :key="key" :value="key">{{ item.label }}</a-select-option>
  7. </a-select>
  8. </a-form-item>
  9. <a-form-item class="mx-1" :wrapperCol="{ span: 24 }">
  10. <a-tooltip :title="tooltip" placement="top">
  11. <disk-size-input
  12. v-decorator="decorator.size"
  13. :step="10"
  14. :min="minSize"
  15. :max="max"
  16. :normalizeGb="normalizeDiskSizeGb"
  17. :disabled="sizeDisabled || (imageType === 'backup' || imageType === 'snapshot')" />
  18. </a-tooltip>
  19. </a-form-item>
  20. <!-- 高级 -->
  21. <template v-if="showAdvanced">
  22. <!-- 快照和挂载点不能共存 -->
  23. <template v-if="!showMountpoint && has('snapshot') && !disabled && imageType !== 'backup' && imageType !== 'snapshot'">
  24. <a-form-item v-if="showSnapshot" class="mx-1" :wrapperCol="{ span: 24 }">
  25. <base-select
  26. v-decorator="decorator.snapshot"
  27. resource="snapshots"
  28. :params="snapshotsParams"
  29. :item.sync="snapshotObj"
  30. :select-props="{ placeholder: $t('compute.text_124') }" />
  31. </a-form-item>
  32. <a-button class="mt-1" type="link" v-show="!simplify" @click="() => showSnapshot = !showSnapshot">{{ showSnapshot ? $t('compute.text_135') : $t('compute.text_133') }}</a-button>
  33. </template>
  34. <template v-if="!showSnapshot && has('mount-point') && !disabled && imageType !== 'backup' && imageType !== 'snapshot'">
  35. <disk-mountpoint
  36. class="mx-1"
  37. v-if="showMountpoint"
  38. :decorators="{ filetype: decorator.filetype, mountPath: decorator.mountPath }" />
  39. <a-button class="mt-1" type="link" @click="() => showMountpoint = !showMountpoint">{{ showMountpoint ? $t('compute.text_135') : $t('compute.text_134') }}</a-button>
  40. </template>
  41. <template v-if="has('schedtag') && !showStorage && !isStorageShow && imageType !== 'backup' && imageType !== 'snapshot'">
  42. <schedtag-policy v-if="showSchedtag" :form="form" :decorators="{ schedtag: decorator.schedtag, policy: decorator.policy }" :schedtag-params="schedtagParams" :policyReactInSchedtag="false" />
  43. <a-button v-if="!disabled" v-show="!simplify" class="mt-1" type="link" @click="() => showSchedtag = !showSchedtag">{{ showSchedtag ? $t('compute.text_135') : $t('compute.text_1315') }}</a-button>
  44. </template>
  45. <template v-if="has('storage') && !showSchedtag && imageType !== 'snapshot'">
  46. <storage style="min-width: 480px; max-width: 500px;" :diskKey="diskKey" :decorators="decorator" :storageParams="storageParams" v-if="showStorage" :form="form" :storageHostParams="storageHostParams" @storageHostChange="(val) => $emit('storageHostChange', val)" />
  47. <a-button v-if="!disabled" class="mt-1" type="link" @click="storageShowClick">{{ showStorage ? $t('compute.text_135') : $t('compute.text_1350') }}</a-button>
  48. </template>
  49. <!-- 关机重置 -->
  50. <a-form-item v-if="isAutoResetShow">
  51. <a-checkbox v-decorator="decorator.auto_reset">{{ $t('compute.shutdown_auto_reset') }}</a-checkbox>
  52. </a-form-item>
  53. <template v-if="isVMware && imageType !== 'backup' && imageType !== 'snapshot'">
  54. <a-form-item class="mx-1" :wrapperCol="{ span: 24 }">
  55. <base-select
  56. v-if="showPreallocation"
  57. v-decorator="decorator.preallocation"
  58. :options="preallocationOptions"
  59. :select-props="{ allowClear: true, placeholder: $t('common.select') }" />
  60. </a-form-item>
  61. <a-button v-if="!disabled" class="mt-1" type="link" @click="preallocationShowClick">{{ showPreallocation ? $t('compute.text_135') : $t('compute.assign_preallocation') }}</a-button>
  62. </template>
  63. <!-- iops 创建时可设置,修改时禁用 -->
  64. <template v-if="has('iops') && !disabled && isIopsShow">
  65. <a-form-item>
  66. <a-tooltip :title="iopsTooltip" placement="top">
  67. <a-input-number
  68. v-if="showIops"
  69. v-decorator="decorator.iops"
  70. placeholder="IOPS"
  71. :min="iopsLimit.min"
  72. :max="iopsLimit.max"
  73. :precision="0" />
  74. </a-tooltip>
  75. </a-form-item>
  76. <a-button class="mt-1" type="link" @click="() => changeIopsShow(!showIops)">{{ showIops ? $t('compute.text_135') : $t('compute.set_iops') }}</a-button>
  77. </template>
  78. <!-- throughput 创建时可设置,修改时禁用 -->
  79. <template v-if="has('throughput') && !disabled && isThroughputShow">
  80. <a-form-item>
  81. <a-tooltip title="125 ~ 1000MiB/s" placement="top">
  82. <a-input-number
  83. v-if="showThroughput"
  84. v-decorator="decorator.throughput"
  85. :placeholder="$t('compute.throughput')"
  86. :min="125"
  87. :max="1000"
  88. :precision="0" />
  89. </a-tooltip>
  90. </a-form-item>
  91. <a-button class="mt-1" type="link" @click="() => changeThroughputShow(!showThroughput)">{{ showThroughput ? $t('compute.text_135') : $t('compute.set_throughput') }}</a-button>
  92. </template>
  93. </template>
  94. <template v-if="has('iops') && disabled && isIopsShow && defaultIops && iamgeType !== 'backup' && imageType !== 'snapshot'">
  95. <span class="ml-2">{{ $t('compute.iops') }}: {{ defaultIops }}</span>
  96. </template>
  97. <template v-if="has('throughput') && disabled && isThroughputShow && defaultThroughput && imageType !== 'backup' && imageType !== 'snapshot'">
  98. <span class="ml-2">{{ $t('compute.throughput') }}: {{ defaultThroughput }}</span>
  99. </template>
  100. <!-- 磁盘容量预警信息提示 -->
  101. <a-tooltip v-if="storageStatusMap.tooltip">
  102. <template slot="title">
  103. <div slot="help">{{ storageStatusMap.tooltip }}</div>
  104. </template>
  105. <a-icon type="exclamation-circle" class="storage-icon" :class="storageClass" />
  106. </a-tooltip>
  107. <a-button v-if="!disabled && hasAdvanced" class="mt-1" type="link" @click="() => showAdvanced = !showAdvanced">{{ showAdvanced ? $t('compute.hide_advanced') : $t('compute.advanced') }}</a-button>
  108. </div>
  109. </template>
  110. <script>
  111. import * as R from 'ramda'
  112. import { PREALLOCATION_OPTIONS } from '@Compute/constants'
  113. import { HYPERVISORS_MAP } from '@/constants'
  114. import SchedtagPolicy from '@/sections/SchedtagPolicy'
  115. import DiskMountpoint from '@/sections/DiskMountpoint'
  116. import DiskSizeInput from '@/sections/DiskSizeInput'
  117. import { diskSupportTypeMedium } from '@/utils/common/hypervisor'
  118. import Storage from './components/Storage'
  119. export default {
  120. name: 'Disk',
  121. components: {
  122. SchedtagPolicy,
  123. DiskMountpoint,
  124. Storage,
  125. DiskSizeInput,
  126. },
  127. props: {
  128. diskKey: String,
  129. decorator: {
  130. type: Object,
  131. required: true,
  132. validator: val => val.type && val.size,
  133. },
  134. typesMap: {
  135. type: Object,
  136. default: () => ({}),
  137. },
  138. hypervisor: {
  139. type: String,
  140. },
  141. min: {
  142. type: Number,
  143. required: true,
  144. },
  145. max: {
  146. type: Number,
  147. required: true,
  148. },
  149. elements: {
  150. type: Array,
  151. required: true,
  152. },
  153. diskTypeLabel: {
  154. type: String,
  155. default: '',
  156. },
  157. disabled: {
  158. type: Boolean,
  159. default: false,
  160. },
  161. simplify: {
  162. type: Boolean,
  163. default: false,
  164. },
  165. sizeDisabled: { // 磁盘大小的限制
  166. type: Boolean,
  167. default: false,
  168. },
  169. snapshotsParams: {
  170. type: Object,
  171. default: () => ({
  172. with_meta: true,
  173. cloud_env: 'onpremise',
  174. limit: 0,
  175. }),
  176. },
  177. schedtagParams: {
  178. type: Object,
  179. default: () => ({
  180. with_meta: true,
  181. cloud_env: 'onpremise',
  182. resource_type: 'storages',
  183. limit: 0,
  184. }),
  185. },
  186. storageStatusMap: {
  187. type: Object,
  188. default: () => ({}),
  189. },
  190. form: {
  191. type: Object,
  192. validator: val => !val || val.fc, // 不传 或者 传就有fc
  193. },
  194. storageParams: {
  195. type: Object,
  196. },
  197. storageHostParams: Object,
  198. isStorageShow: {
  199. type: Boolean,
  200. default: false,
  201. },
  202. isIopsShow: {
  203. type: Boolean,
  204. default: false,
  205. },
  206. isThroughputShow: {
  207. type: Boolean,
  208. default: false,
  209. },
  210. iopsLimit: {
  211. type: Object,
  212. default: () => ({ min: 0 }),
  213. },
  214. isAutoResetShow: {
  215. type: Boolean,
  216. default: false,
  217. },
  218. defaultIops: {
  219. type: Number,
  220. default: 0,
  221. },
  222. defaultThroughput: {
  223. type: Number,
  224. default: 0,
  225. },
  226. imageType: {
  227. type: String,
  228. },
  229. },
  230. data () {
  231. return {
  232. showSchedtag: false,
  233. showMountpoint: false,
  234. showSnapshot: false,
  235. showStorage: false,
  236. showIops: false,
  237. showThroughput: false,
  238. showPreallocation: false,
  239. snapshotObj: {},
  240. preallocationOptions: PREALLOCATION_OPTIONS.filter(item => item.value !== 'off').map(item => {
  241. return {
  242. id: item.value,
  243. name: item.label,
  244. }
  245. }),
  246. showAdvanced: false,
  247. }
  248. },
  249. computed: {
  250. tooltip () {
  251. return this.$t('compute.text_137', [this.minSize, this.max])
  252. },
  253. iopsTooltip () {
  254. if (this.iopsLimit.min && this.iopsLimit.max) {
  255. return `${this.iopsLimit.min} ~ ${this.iopsLimit.max}`
  256. }
  257. return ''
  258. },
  259. minSize () {
  260. let snapshotSize = this.snapshotObj.size || 0
  261. if (R.is(Number, snapshotSize)) {
  262. snapshotSize = snapshotSize / 1024
  263. }
  264. return Math.max(this.min, snapshotSize)
  265. },
  266. storageClass () {
  267. return `${this.storageStatusMap.type}-color`
  268. },
  269. isVMware () {
  270. return this.hypervisor === HYPERVISORS_MAP.esxi.key
  271. },
  272. hasAdvanced () {
  273. return this.has('snapshot') || this.has('mount-point') || this.has('schedtag') || this.has('storage') || this.has('iops') || this.has('throughput') || this.isAutoResetShow || this.isVMware
  274. },
  275. },
  276. watch: {
  277. 'snapshotObj.size' (val) {
  278. if (val) {
  279. const size = val / 1024
  280. this.$emit('snapshotChange', size)
  281. }
  282. },
  283. showStorage (v) {
  284. this.$emit('showStorageChange', v)
  285. },
  286. elements (val, oldV) {
  287. if (!R.equals(val, oldV)) this.init()
  288. },
  289. },
  290. methods: {
  291. normalizeDiskSizeGb (gb) {
  292. let num = gb
  293. if (this.hypervisor === HYPERVISORS_MAP.qcloud.key) {
  294. num = Math.floor(num / 10) * 10
  295. }
  296. return num
  297. },
  298. initData (data, hyper) {
  299. setTimeout(() => {
  300. this.form.fc.setFieldsValue({
  301. [this.decorator.type[0]]: { key: diskSupportTypeMedium(hyper) ? `${data.backend}/${data.medium}` : data.backend, label: '' },
  302. [this.decorator.size[0]]: data.size / 1024,
  303. })
  304. if (data.schedtags || data.storage_id || data.auto_reset || data.iops || data.throughput || data.preallocation) {
  305. this.showAdvanced = true
  306. if (data.schedtags && data.schedtags.length) {
  307. this.showSchedtag = true
  308. this.$nextTick(() => {
  309. this.form.fc.setFieldsValue({
  310. [this.decorator.schedtag[0]]: data.schedtags[0].id,
  311. [this.decorator.policy[0]]: data.schedtags[0].strategy,
  312. })
  313. })
  314. }
  315. if (data.storage_id) {
  316. this.showStorage = true
  317. this.$nextTick(() => {
  318. this.form.fc.setFieldsValue({
  319. [this.decorator.storage[0]]: data.storage_id,
  320. })
  321. })
  322. }
  323. if (data.auto_reset) {
  324. this.$nextTick(() => {
  325. this.form.fc.setFieldsValue({
  326. [this.decorator.auto_reset[0]]: data.auto_reset,
  327. })
  328. })
  329. }
  330. if (data.iops) {
  331. this.showIops = true
  332. this.$nextTick(() => {
  333. this.form.fc.setFieldsValue({
  334. [this.decorator.iops[0]]: data.iops,
  335. })
  336. })
  337. }
  338. if (data.throughput) {
  339. this.showThroughput = true
  340. this.$nextTick(() => {
  341. this.form.fc.setFieldsValue({
  342. [this.decorator.throughput[0]]: data.throughput,
  343. })
  344. })
  345. }
  346. if (data.preallocation) {
  347. this.showPreallocation = true
  348. this.$nextTick(() => {
  349. this.form.fc.setFieldsValue({
  350. [this.decorator.preallocation[0]]: data.preallocation,
  351. })
  352. })
  353. }
  354. }
  355. }, 1000)
  356. },
  357. setValues (values) {
  358. for (const key in values) {
  359. this[key] = values[key]
  360. }
  361. },
  362. has (element) {
  363. return this.elements.includes(element)
  364. },
  365. parser (value) {
  366. value = String(value)
  367. return value.replace(/[GB]*/g, '')
  368. },
  369. formatter (num) {
  370. const n = this.parser(num)
  371. if (this.hypervisor === HYPERVISORS_MAP.qcloud.key) {
  372. num = Math.floor(num / 10) * 10
  373. }
  374. return `${n}GB`
  375. },
  376. typeChange (val) {
  377. this.$emit('diskTypeChange', val)
  378. if (this.showStorage) {
  379. this.$emit('storageHostChange', { disk: this.diskKey, storageHosts: [] })
  380. }
  381. this.snapshotObj = {}
  382. },
  383. init () {
  384. this.showSchedtag = false
  385. this.showMountpoint = false
  386. this.showSnapshot = false
  387. this.showStorage = false
  388. this.snapshotObj = {}
  389. },
  390. formatterLabel (row) {
  391. return row.description ? `${row.name} / ${row.description}` : row.name
  392. },
  393. storageShowClick () {
  394. if (this.showStorage) {
  395. this.$emit('storageHostChange', { disk: this.diskKey, storageHosts: [] })
  396. }
  397. this.showStorage = !this.showStorage
  398. },
  399. preallocationShowClick () {
  400. this.showPreallocation = !this.showPreallocation
  401. if (this.isVMware) {
  402. const systemDiskPreallocation = this.form.fd.systemDiskPreallocation
  403. this.$nextTick(() => {
  404. if (this.diskKey !== 'system') {
  405. this.form.fc.setFieldsValue({
  406. [`dataDiskPreallocation[${this.diskKey}]`]: systemDiskPreallocation,
  407. })
  408. }
  409. })
  410. }
  411. },
  412. changeIopsShow (show) {
  413. this.showIops = show
  414. },
  415. changeThroughputShow (show) {
  416. this.showThroughput = show
  417. },
  418. },
  419. }
  420. </script>
  421. <style lang="less" scoped>
  422. .disk-wrapper{
  423. .storage-icon{
  424. position: relative;
  425. top: 12px;
  426. margin-left: 10px;
  427. }
  428. }
  429. </style>