Storage.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <template>
  2. <a-form-item :wrapperCol="{ span: 24 }" style="flex: 1;" class="mb-0 mr-1 network-item">
  3. <base-select
  4. class="w-100"
  5. v-decorator="decorators.storage"
  6. filterable
  7. resource="storages"
  8. @change="change"
  9. :params="{ ...storageParams, $t: diskKey }"
  10. :options="options"
  11. :dropdownMenuStyle="{ 'min-height': '20px' }">
  12. <template #optionLabelTemplate="{ item }">
  13. <div>
  14. <div class="d-flex">
  15. <span class="text-truncate flex-fill mr-2" :title="item.name">{{ item.name }}</span>
  16. <span style="color: #8492a6; font-size: 13px">{{ item.capacityLabel }}</span>
  17. </div>
  18. <div class="mt-1" v-if="item.statusErrors && item.statusErrors.length > 0">
  19. <a-tag color="red" v-for="(err, idx) in item.statusErrors" :key="idx">{{ err }}</a-tag>
  20. </div>
  21. </div>
  22. </template>
  23. </base-select>
  24. </a-form-item>
  25. </template>
  26. <script>
  27. import * as R from 'ramda'
  28. import { sizestr } from '@/utils/utils'
  29. export default {
  30. name: 'DiskStorageSelect',
  31. props: {
  32. diskKey: String,
  33. decorators: {
  34. type: Object,
  35. validator: val => val.storage,
  36. },
  37. storageParams: {
  38. type: Object,
  39. required: true,
  40. },
  41. form: {
  42. type: Object,
  43. validator: val => !val || val.fc, // 不传 或者 传就有fc
  44. },
  45. storageHostParams: {
  46. type: Object,
  47. default: () => {
  48. return {}
  49. },
  50. },
  51. },
  52. data () {
  53. return {
  54. storageCache: undefined,
  55. opts: [],
  56. options: [],
  57. }
  58. },
  59. computed: {
  60. diskSizeParams () {
  61. const { systemDiskStorage = '', systemDiskSize = 0, dataDiskSizes = {} } = this.form.fd
  62. const ret = {
  63. systemDiskStorage,
  64. systemDiskSize,
  65. dataDiskSizes,
  66. storageHostParams: this.storageHostParams,
  67. }
  68. if (this.diskKey !== 'system' && this.form.fd.hasOwnProperty(`dataDiskStorages[${this.diskKey}]`)) {
  69. ret.currentDiskStorage = this.form.fd[`dataDiskStorages[${this.diskKey}]`]
  70. }
  71. return ret
  72. },
  73. },
  74. watch: {
  75. storageParams: {
  76. handler: function (val, oldVal) {
  77. if (!R.equals(val, oldVal)) {
  78. this.fetchData()
  79. }
  80. },
  81. immediate: true,
  82. },
  83. diskSizeParams: {
  84. handler: function (val, oldVal) {
  85. if (!R.equals(val, oldVal)) {
  86. this.filterOptions()
  87. }
  88. },
  89. deep: true,
  90. },
  91. },
  92. created () {
  93. this.$s = new this.$Manager('storages', 'v1')
  94. },
  95. methods: {
  96. change (v) {
  97. if (v) {
  98. this.storageCache = v // 只缓存有值的时候
  99. const disk = this.options.filter(item => item.id === v)
  100. this.$emit('storageHostChange', {
  101. disk: this.diskKey,
  102. storageHosts: disk.length ? disk[0].hosts || [] : [],
  103. })
  104. } else {
  105. this.$emit('storageHostChange', {
  106. disk: this.diskKey,
  107. storageHosts: [],
  108. })
  109. }
  110. },
  111. async fetchData () {
  112. try {
  113. const { data } = await new this.$Manager('storages', 'v1').list({ params: { ...this.storageParams, $t: this.diskKey } })
  114. const list = R.type(data) === 'Array' ? data : (R.type(data) === 'Object' && (data.data || []))
  115. const opts = list.map((o, idx) => {
  116. const statusErrors = this.getStatusErrors(o)
  117. return {
  118. ...o,
  119. capacityLabel: this.getCapacityLabel(o),
  120. __disabled: statusErrors.length > 0,
  121. statusErrors,
  122. order: statusErrors.length > 0 ? -1 : idx,
  123. optClass: 'storage-option-item',
  124. }
  125. })
  126. opts.sort((a, b) => b.order - a.order)
  127. this.opts = opts
  128. this.$nextTick(() => {
  129. this.filterOptions()
  130. })
  131. } catch (err) {
  132. console.error(err)
  133. }
  134. },
  135. filterOptions () {
  136. let filterdList = R.clone(this.opts)
  137. if (this.diskKey && this.form && this.form.fd) {
  138. // 获取所有已选存储的storeageId和选择这块存储的diskSize
  139. const { systemDiskStorage = '', systemDiskSize = 0, dataDiskSizes = {} } = this.form.fd
  140. const s_d_map = {}
  141. if (systemDiskStorage && this.diskKey !== 'system') {
  142. s_d_map[systemDiskStorage] = systemDiskSize
  143. }
  144. for (const key in dataDiskSizes) {
  145. const decoratorKey = this.form.fd[`dataDiskStorages[${key}]`]
  146. if (decoratorKey && key !== this.diskKey) { // 当前数据盘外的其他数据盘
  147. if (!s_d_map[decoratorKey]) {
  148. s_d_map[decoratorKey] = dataDiskSizes[key]
  149. } else {
  150. s_d_map[decoratorKey] += dataDiskSizes[key]
  151. }
  152. }
  153. }
  154. // 当前磁盘大小
  155. const currentDiskSize = this.diskKey === 'system' ? systemDiskSize : (dataDiskSizes[this.diskKey] || this.form.fd[`dataDiskSizes[${this.diskKey}]`])
  156. // 当前系统盘块存储host
  157. const { storageHosts = [], disk } = this.storageHostParams
  158. // 保留 (容量 - 已经选用此块存储的磁盘大小 > 当前磁盘大小)&& 与系统盘相同host 的块存储
  159. filterdList = filterdList.filter(storage => {
  160. let needSize = currentDiskSize
  161. if (s_d_map[storage.id]) {
  162. needSize += s_d_map[storage.id]
  163. }
  164. if (storageHosts.length && disk && this.diskKey !== disk) {
  165. let has = false
  166. const { hosts = [] } = storage
  167. hosts.map(host => {
  168. if (storageHosts.some(item => item.id === host.id)) {
  169. has = true
  170. }
  171. })
  172. return has && storage.free_capacity > needSize * 1024
  173. }
  174. return storage.free_capacity > needSize * 1024
  175. })
  176. }
  177. if (this.form && this.form.fc) {
  178. if (this.storageCache) {
  179. const hasCache = filterdList.some(val => val.id === this.storageCache)
  180. if (hasCache) { // 如果新的list存在缓存值的话,取缓存值
  181. this.form.fc.setFieldsValue({
  182. [this.decorators.storage[0]]: this.storageCache,
  183. })
  184. }
  185. }
  186. }
  187. this.options = filterdList
  188. },
  189. getCapacityLabel (val) {
  190. const capacity = sizestr(val.capacity, 'M', 1024)
  191. const actual_capacity_used = val.actual_capacity_used ? sizestr(val.actual_capacity_used, 'M', 1024) : '-'
  192. const allocated = sizestr(val.used_capacity, 'M', 1024)
  193. return `${this.$t('storage.text_180', [capacity])} / ${this.$t('storage.text_181', [allocated])} / ${this.$t('storage.text_178', [actual_capacity_used])}`
  194. },
  195. getStatusErrors (val) {
  196. const statusErrors = []
  197. if (val.status !== 'online') {
  198. statusErrors.push(this.$t('compute.storage.check_status.status'))
  199. }
  200. if (!val.enabled) {
  201. statusErrors.push(this.$t('compute.storage.check_status.enabled'))
  202. }
  203. if (val.account_health_status && !['normal', 'no permission'].includes(val.account_health_status)) {
  204. statusErrors.push(this.$t('compute.storage.check_status.account_health_status'))
  205. }
  206. if (val.account_status && val.account_status !== 'connected') {
  207. statusErrors.push(this.$t('compute.storage.check_status.account_status'))
  208. }
  209. const isSomeHostOk = val.hosts?.some(item => {
  210. return item.host_status === 'online' && item.status === 'running'
  211. })
  212. if (!isSomeHostOk) {
  213. statusErrors.push(this.$t('compute.storage.check_status.host_status'))
  214. }
  215. return statusErrors
  216. },
  217. },
  218. }
  219. </script>
  220. <style lang="scss">
  221. .storage-option-item {
  222. min-height: 46px;
  223. }
  224. </style>