Upload.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <template>
  2. <base-dialog @cancel="cancelDialog">
  3. <div slot="header">{{$t('compute.text_664')}}</div>
  4. <div slot="body">
  5. <a-alert class="mb-2" type="warning">
  6. <div slot="message">{{$t('compute.text_665')}}</div>
  7. </a-alert>
  8. <a-form
  9. v-bind="formItemLayout"
  10. :form="form.fc">
  11. <a-form-item :label="$t('compute.text_297', [$t('dictionary.project')])" class="mb-0" v-bind="formItemLayout">
  12. <domain-project :fc="form.fc" :decorators="{ project: decorators.project, domain: decorators.domain }" />
  13. </a-form-item>
  14. <a-form-item :label="$t('compute.text_627')" v-bind="formItemLayout">
  15. <a-input :placeholder="$t('compute.text_416')" v-decorator="decorators.name" />
  16. </a-form-item>
  17. <a-form-item :label="$t('compute.text_1365')">
  18. <os-arch
  19. v-decorator="decorators.os_arch"
  20. :form="form" />
  21. </a-form-item>
  22. <a-form-item :label="$t('compute.text_667')" v-bind="formItemLayout">
  23. <a-radio-group @change="handleUploadTypeChange" v-decorator="decorators.uploadType">
  24. <a-radio-button value="file">{{$t('compute.text_668')}}</a-radio-button>
  25. <a-radio-button value="url">{{$t('compute.text_669')}}</a-radio-button>
  26. </a-radio-group>
  27. </a-form-item>
  28. <a-form-item :label="$t('compute.text_670')" v-bind="formItemLayout" v-if="byUpload" :help="$t('compute.text_671')">
  29. <a-upload
  30. @change="handleUploadChange"
  31. :fileList="fileList"
  32. :beforeUpload="beforeUpload">
  33. <a-button> <a-icon type="upload" />{{$t('compute.text_245')}}</a-button>
  34. </a-upload>
  35. <a-progress
  36. v-if="loading"
  37. :strokeColor="{ from: '#108ee9', to: '#87d068' }"
  38. :percent="imageUploadPercent"
  39. status="active" />
  40. </a-form-item>
  41. <a-form-item :label="$t('compute.text_672')" v-bind="formItemLayout" v-if="!byUpload">
  42. <a-input :placeholder="$t('compute.text_673')" v-decorator="decorators.copy_from" />
  43. </a-form-item>
  44. <a-form-item :label="$t('compute.text_267')" v-bind="formItemLayout">
  45. <a-radio-group v-decorator="decorators.os_type">
  46. <a-radio-button value="Linux">
  47. Linux
  48. </a-radio-button>
  49. <a-radio-button value="Windows">
  50. Windows
  51. </a-radio-button>
  52. <a-radio-button value="other">{{$t('compute.text_674')}}</a-radio-button>
  53. </a-radio-group>
  54. </a-form-item>
  55. <a-form-item v-if="enableEncryption" :label="$t('compute.image.encryption')" :extra="$t('compute.image.encryption.extra')">
  56. <encrypt-keys :decorators="decorators.encrypt_keys" />
  57. </a-form-item>
  58. </a-form>
  59. </div>
  60. <div slot="footer">
  61. <a-button type="primary" @click="handleConfirm" :loading="loading">{{ $t('dialog.ok') }}</a-button>
  62. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  63. </div>
  64. </base-dialog>
  65. </template>
  66. <script>
  67. import { mapGetters } from 'vuex'
  68. import * as R from 'ramda'
  69. // import _ from 'lodash'
  70. import { isRequired } from '@/utils/validate'
  71. import DialogMixin from '@/mixins/dialog'
  72. import WindowsMixin from '@/mixins/windows'
  73. import DomainProject from '@/sections/DomainProject'
  74. import i18n from '@/locales'
  75. import OsArch from '@/sections/OsArch'
  76. import { HOST_CPU_ARCHS } from '@/constants/compute'
  77. import EncryptKeys from '@Compute/sections/encryptkeys'
  78. export default {
  79. name: 'ImageUploadDialog',
  80. components: {
  81. DomainProject,
  82. OsArch,
  83. EncryptKeys,
  84. },
  85. mixins: [DialogMixin, WindowsMixin],
  86. data () {
  87. return {
  88. loading: false,
  89. form: {
  90. fc: this.$form.createForm(this),
  91. },
  92. decorators: {
  93. domain: [
  94. 'domain',
  95. {
  96. rules: [
  97. { validator: isRequired(), message: i18n.t('rules.domain'), trigger: 'change' },
  98. ],
  99. },
  100. ],
  101. project: [
  102. 'project',
  103. {
  104. rules: [
  105. { validator: isRequired(), message: i18n.t('rules.project'), trigger: 'change' },
  106. ],
  107. },
  108. ],
  109. name: [
  110. 'name',
  111. {
  112. validateFirst: true,
  113. rules: [
  114. { required: true, message: this.$t('compute.text_660') },
  115. { validator: this.$validate('imageName') },
  116. { validator: this.checkTemplateName },
  117. ],
  118. },
  119. ],
  120. uploadType: [
  121. 'uploadType',
  122. {
  123. initialValue: 'file',
  124. },
  125. ],
  126. copy_from: [
  127. 'copy_from',
  128. {
  129. validateFirst: true,
  130. rules: [
  131. { required: true, message: this.$t('compute.text_673') },
  132. { validator: this.validateUrl },
  133. ],
  134. },
  135. ],
  136. os_type: [
  137. 'os_type',
  138. {
  139. initialValue: 'Linux',
  140. },
  141. ],
  142. os_arch: [
  143. 'os_arch',
  144. {
  145. initialValue: HOST_CPU_ARCHS.x86.key,
  146. },
  147. ],
  148. // os_bit: [
  149. // 'os_bit',
  150. // {
  151. // initialValue: '64',
  152. // },
  153. // ],
  154. encrypt_keys: {
  155. encryptEnable: [
  156. 'encryptEnable',
  157. {
  158. initialValue: '',
  159. },
  160. ],
  161. encrypt_key_alg: [
  162. 'encrypt_key_alg',
  163. {
  164. initialValue: '',
  165. },
  166. ],
  167. encrypt_key_id: [
  168. 'encrypt_key_id',
  169. ],
  170. },
  171. },
  172. formItemLayout: {
  173. wrapperCol: {
  174. span: 20,
  175. },
  176. labelCol: {
  177. span: 4,
  178. },
  179. },
  180. byUpload: true,
  181. fileList: [],
  182. imageUploadPercent: 0,
  183. }
  184. },
  185. computed: {
  186. ...mapGetters(['userInfo']),
  187. headers () {
  188. return { Authorization: `Bearer ${this.$store.getters.userInfo.session}` }
  189. },
  190. enableEncryption () {
  191. return this.$appConfig.isPrivate && !this.$store.getters.isSysCE
  192. },
  193. },
  194. destroyed () {
  195. this.clearTimer()
  196. },
  197. methods: {
  198. clearTimer () {
  199. clearTimeout(this.timer)
  200. clearTimeout(this.percentTimer)
  201. this.timer = null
  202. this.percentTimer = null
  203. },
  204. beforeUpload (file) {
  205. this.fileList = [file]
  206. return false
  207. },
  208. handleUploadTypeChange (e) {
  209. if (e.target.value === 'url') {
  210. this.byUpload = false
  211. } else {
  212. this.byUpload = true
  213. }
  214. },
  215. handleUploadChange ({ file, fileList }) {
  216. this.fileList = fileList
  217. },
  218. checkTemplateName (rule, value, callback) {
  219. if (!value) {
  220. return callback(new Error(this.$t('compute.text_660')))
  221. }
  222. return new this.$Manager('images', 'v1').list({
  223. params: {
  224. name: value,
  225. scope: this.$store.getters.scope,
  226. },
  227. }).then(res => {
  228. const data = res.data.data
  229. if (!R.isNil(data) && !R.isEmpty(data)) {
  230. callback(new Error(this.$t('compute.text_662')))
  231. } else {
  232. callback()
  233. }
  234. })
  235. },
  236. validateUrl (rule, value, callback) {
  237. if (value.startsWith('http://') || value.startsWith('https://')) {
  238. callback()
  239. } else {
  240. callback(new Error(this.$t('compute.text_675')))
  241. }
  242. },
  243. handleUpload (data) {
  244. return this.$http({
  245. url: '/v1/imageutils/upload',
  246. method: 'post',
  247. processData: false,
  248. data,
  249. timeout: 0,
  250. onUploadProgress: (progressEvent) => {
  251. // 计算上传进度百分比
  252. const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
  253. this.imageUploadPercent = percent
  254. if (this.imageUploadPercent >= 100) {
  255. setTimeout(() => {
  256. this.cancelDialog()
  257. this.params.refresh()
  258. }, 2000)
  259. }
  260. },
  261. })
  262. },
  263. doImportUrl (data) {
  264. const params = {
  265. domain_id: (data.domain && data.domain.key) || this.userInfo.projectDomainId,
  266. project_id: (data.project && data.project.key) || this.userInfo.projectId,
  267. copy_from: data.copy_from,
  268. name: data.name,
  269. os_arch: data.os_arch,
  270. properties: {
  271. os_type: data.os_type,
  272. os_version: '',
  273. os_arch: data.os_arch,
  274. },
  275. }
  276. if (data.encryptEnable === 'existing' && data.encrypt_key_id) {
  277. params.encrypt_key_id = data.encrypt_key_id
  278. } else if (data.encryptEnable === 'new') {
  279. params.encrypt_key_new = true
  280. params.encrypt_key_alg = data.encrypt_key_alg
  281. params.encrypt_key_user_id = this.userInfo.id
  282. }
  283. return this.params.onManager('create', {
  284. managerArgs: {
  285. data: params,
  286. },
  287. })
  288. },
  289. async handleConfirm () {
  290. this.loading = true
  291. try {
  292. const { fileList } = this
  293. const formData = new FormData()
  294. const values = await this.form.fc.validateFields()
  295. formData.append('domain_id', (values.domain && values.domain.key) || this.userInfo.projectDomainId)
  296. formData.append('project_id', (values.project && values.project.key) || this.userInfo.projectId)
  297. // const os_bit = values.os_bit
  298. // if (values.os_arch === HOST_CPU_ARCHS.arm.key) {
  299. // values.os_arch = `aarch${os_bit}`
  300. // } else {
  301. // if (os_bit === '64') { // else 情况就是x86,既 os_arch 本身的值
  302. // values.os_arch = 'x86-64'
  303. // }
  304. // }
  305. if (values.uploadType === 'file') {
  306. formData.append('name', values.name)
  307. formData.append('os_version', '')
  308. formData.append('os_arch', values.os_arch)
  309. formData.append('properties.os_type', values.os_type)
  310. if (values.encryptEnable === 'existing' && values.encrypt_key_id) {
  311. formData.append('encrypt_key_id', values.encrypt_key_id)
  312. } else if (values.encryptEnable === 'new') {
  313. formData.append('encrypt_key_new', true)
  314. formData.append('encrypt_key_alg', values.encrypt_key_alg)
  315. formData.append('encrypt_key_user_id', this.userInfo.id)
  316. }
  317. if (fileList.length > 0) {
  318. formData.append('image_size', fileList[0].size)
  319. fileList.forEach(file => {
  320. formData.append('image', file.originFileObj)
  321. })
  322. } else {
  323. this.$message.error(this.$t('compute.text_676'))
  324. this.loading = false
  325. return false
  326. }
  327. this.handleUpload(formData)
  328. .then(() => {})
  329. .catch(() => {
  330. this.loading = false
  331. this.clearTimer()
  332. })
  333. // this.getProcessBarInfo()
  334. } else {
  335. await this.doImportUrl(values)
  336. this.cancelDialog()
  337. this.params.refresh()
  338. }
  339. } catch (error) {
  340. this.loading = false
  341. throw error
  342. }
  343. },
  344. fetchImageInfoByName () {
  345. const imageManager = new this.$Manager('images', 'v1')
  346. const name = this.form.fc.getFieldValue('name')
  347. return imageManager.list({ params: { name, scope: this.$store.getters.scope } })
  348. },
  349. // getProcessBarInfo () {
  350. // this.clearTimer()
  351. // this.timer = setInterval(() => {
  352. // this.fetchImageInfoByName()
  353. // .then((res) => {
  354. // const imageInfo = res.data && res.data.data && res.data.data[0]
  355. // if (this.fileList && this.fileList.length > 0) {
  356. // if (imageInfo) {
  357. // const percent = (imageInfo.size / this.fileList[0].size) * 100
  358. // if (percent === 100) {
  359. // this.percentTimer = setTimeout(() => {
  360. // this.imageUploadPercent = _.floor(percent)
  361. // }, 5000)
  362. // } else {
  363. // this.imageUploadPercent = _.floor(percent)
  364. // }
  365. // }
  366. // }
  367. // if (this.imageUploadPercent >= 100) {
  368. // this.cancelDialog()
  369. // this.params.refresh()
  370. // }
  371. // })
  372. // }, 5000)
  373. // },
  374. },
  375. }
  376. </script>