index.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. <template>
  2. <div>
  3. <a-alert type="warning" v-if="isCE || $store.getters.isSysCE">
  4. <span slot="message">{{ $t('common.image_url_tip') }} <a href="https://www.cloudpods.org/docs/guides/onpremise/glance/common-image-url" target="_blank">{{ $t('common.normal_open_image') }}</a></span>
  5. </a-alert>
  6. <a-alert type="warning" v-else>
  7. <span slot="message">{{ $t('common.image_url_tip2') }}</span>
  8. </a-alert>
  9. <page-header :title="$t('compute.open_image_market')" />
  10. <a-form-model class="mt-3 mb-2" v-bind="layout">
  11. <a-form-model-item>
  12. <a-radio-group v-model="form.os_arch">
  13. <a-radio-button v-for="(item, index) in archList" :key="index" :value="item.value">{{ item.label }}</a-radio-button>
  14. </a-radio-group>
  15. </a-form-model-item>
  16. <a-form-model-item>
  17. <a-radio-group v-model="form.source">
  18. <a-radio-button v-for="(item, index) in sourceList" :key="index" :value="item.value">{{ item.label }}</a-radio-button>
  19. </a-radio-group>
  20. </a-form-model-item>
  21. <a-form-model-item>
  22. <a-radio-group v-model="form.distribution" size="large">
  23. <a-radio-button v-for="(item, index) in distributionList" :key="index" :value="item.value" style="width:60px;height:60px;text-align:center;line-height:60px;vertical-align:middle;padding:0;"><img v-if="item.os" :src="item.os" style="height:40px;" /></a-radio-button>
  24. </a-radio-group>
  25. </a-form-model-item>
  26. </a-form-model>
  27. <page-card-list
  28. :list="list"
  29. :card-fields="cardFields"
  30. :showPageer="false"
  31. :isRefreshed="false"
  32. :singleActions="singleActions" />
  33. </div>
  34. </template>
  35. <script>
  36. import { mapGetters } from 'vuex'
  37. import axios from 'axios'
  38. import WindowsMixin from '@/mixins/windows'
  39. import { isCE } from '@/utils/utils'
  40. const path = require('path')
  41. const imagesLogoFiles = require.context('@/assets/images/os-images', false, /.svg$/)
  42. const imagesLogos = []
  43. imagesLogoFiles.keys().forEach(key => {
  44. const name = path.basename(key, '.svg') // 返回文件名 不含后缀名
  45. imagesLogos.push(name)
  46. })
  47. const fileUrl = 'https://www.cloudpods.org/openimages.yaml'
  48. const attributes = ['distribution', 'os_name', 'os_arch', 'os_version', 'build', 'source', 'url']
  49. export default {
  50. name: 'ImageMarket',
  51. mixins: [WindowsMixin],
  52. data () {
  53. return {
  54. loading: false,
  55. imageList: [],
  56. cardFields: {
  57. url: 'os',
  58. title: 'title',
  59. description: 'os_name',
  60. desc: 'desc',
  61. },
  62. list: {
  63. data: [],
  64. loading: false,
  65. },
  66. layout: {
  67. wrapperCol: {
  68. span: 20,
  69. },
  70. labelCol: {
  71. span: 4,
  72. },
  73. },
  74. singleActions: [
  75. {
  76. label: this.$t('compute.text_679'),
  77. action: async (data) => {
  78. try {
  79. await new this.$Manager('images', 'v2').create({
  80. data: {
  81. domain_id: this.userInfo.projectDomainId,
  82. project_id: this.userInfo.projectId,
  83. copy_from: data.url,
  84. name: data.name,
  85. os_arch: data.os_arch,
  86. properties: {
  87. os_type: data.os_name,
  88. os_version: data.os_version,
  89. os_arch: data.os_arch,
  90. },
  91. },
  92. })
  93. this.$message.success(this.$t('compute.text_423'))
  94. this.$router.push({ name: 'Image' })
  95. } catch (error) {
  96. throw error
  97. }
  98. },
  99. meta: (obj) => {
  100. return {
  101. buttonType: 'primary',
  102. }
  103. },
  104. },
  105. ],
  106. imagesLogos,
  107. form: {
  108. os_arch: 'x86_64',
  109. source: 'all',
  110. distribution: 'Debian',
  111. },
  112. }
  113. },
  114. computed: {
  115. ...mapGetters(['userInfo']),
  116. archList () {
  117. const ret = []
  118. this.imageList.map(item => {
  119. if (item.data.os_arch && !ret.some(l => l.value === item.data.os_arch)) {
  120. ret.push({ value: item.data.os_arch, label: item.data.os_arch })
  121. }
  122. })
  123. return ret
  124. },
  125. distributionList () {
  126. const ret = []
  127. this.imageList.map(item => {
  128. if (item.data.os_arch === this.form.os_arch && item.data.source_type.includes(this.form.source) && item.data.distribution && !ret.some(l => l.value === item.data.distribution)) {
  129. ret.push({ value: item.data.distribution, label: item.data.distribution, os: item.data.os })
  130. }
  131. })
  132. return ret
  133. },
  134. isce () {
  135. return isCE()
  136. },
  137. sourceList () {
  138. return [
  139. { value: 'all', label: this.$t('compute.image_source_global') },
  140. { value: 'cn', label: this.$t('compute.image_source_cn') },
  141. { value: 'ov', label: this.$t('compute.image_source_ov') },
  142. ]
  143. },
  144. },
  145. watch: {
  146. form: {
  147. handler (val) {
  148. this.initList()
  149. },
  150. deep: true,
  151. },
  152. distributionList (val) {
  153. if (val.length) {
  154. if (!val.some(item => item.value === this.form.distribution)) {
  155. this.form.distribution = val[0].value
  156. }
  157. }
  158. },
  159. },
  160. created () {
  161. this.fetchData()
  162. },
  163. methods: {
  164. parseImageOption (str) {
  165. let key = ''
  166. let value = ''
  167. attributes.map(field => {
  168. const fieldStart = field + ': '
  169. if (str.startsWith(fieldStart)) {
  170. key = field
  171. value = str.substring(fieldStart.length)
  172. }
  173. })
  174. return { key, value }
  175. },
  176. parseImageData (str) {
  177. const list = str.split('- distribution:')
  178. const imageList = []
  179. list.map((str, index) => {
  180. if (index !== 0) {
  181. const obj = {}
  182. const originStr = 'distribution:' + str
  183. const optsList = originStr.split('\n').filter(item => item.trim() !== '').map(item => item.trim())
  184. optsList.map(item => {
  185. const { key, value } = this.parseImageOption(item)
  186. obj[key] = value
  187. })
  188. imageList.push({ ...obj, id: imageList.length + 1 })
  189. }
  190. })
  191. return imageList
  192. },
  193. handleFilterChange (filter) {
  194. const keys = Object.keys(filter)
  195. this.list.responseData = {
  196. data: this.imageList.filter(item => {
  197. return keys.every(key => {
  198. return item[key] && item[key].toLowerCase().includes(filter[key][0].toLowerCase())
  199. })
  200. }),
  201. }
  202. this.selectedImages = []
  203. this.list.fetchData()
  204. },
  205. handleSelected (selected) {
  206. this.selectedImages = selected
  207. },
  208. getOsName (item) {
  209. let ret = ''
  210. const os_name = (item.distribution || '').toLowerCase()
  211. ret = this.imagesLogos.includes(os_name) ? os_name : ''
  212. if (!ret && os_name.includes('ubuntu')) {
  213. ret = 'ubuntu'
  214. }
  215. if (!ret && os_name.includes('anolis')) {
  216. ret = 'anolis'
  217. }
  218. return ret || 'unknow'
  219. },
  220. fetchData () {
  221. this.list.loading = true
  222. axios.get(fileUrl).then(res => {
  223. const { data = '' } = res
  224. this.imageList = this.parseImageData(data).map(item => {
  225. const ret = { data: { ...item } }
  226. ret.data.title = `${item.distribution} ${item.os_version}`
  227. ret.data.desc = this.$t('compute.image_market.desc', [item.os_arch, item.source, item.build || '-'])
  228. ret.data.name = `${item.distribution}-${item.os_name}-${item.os_version}-${item.os_arch}`
  229. if (item.build) {
  230. ret.data.name += `-${item.build}`
  231. }
  232. const os_name = this.getOsName(item)
  233. ret.data.os = require(`@/assets/images/os-images/${this.imagesLogos.includes(os_name) ? os_name : 'unknow'}.svg`) || ''
  234. ret.data.source_type = this.isChinaDomain(item.url) ? ['all', 'cn'] : ['all', 'ov']
  235. return ret
  236. })
  237. this.initList()
  238. this.list.loading = false
  239. }).catch(err => {
  240. this.list.loading = false
  241. throw err
  242. })
  243. },
  244. isChinaDomain (url) {
  245. try {
  246. const { hostname } = new URL(url)
  247. return (
  248. hostname.endsWith('.cn') ||
  249. hostname.endsWith('.com.cn') ||
  250. hostname.endsWith('.net.cn') ||
  251. hostname.endsWith('.gov.cn') ||
  252. hostname.endsWith('.edu.cn') ||
  253. hostname.endsWith('.org.cn') ||
  254. hostname.includes('opencloudos')
  255. )
  256. } catch (e) {
  257. return false
  258. }
  259. },
  260. initList () {
  261. this.list.data = this.imageList.filter(item => {
  262. const { os_arch, distribution, source_type } = item.data
  263. if (this.form.os_arch && this.form.os_arch !== 'all' && os_arch !== this.form.os_arch) {
  264. return false
  265. }
  266. if (this.form.distribution && this.form.distribution !== 'all' && distribution !== this.form.distribution) {
  267. return false
  268. }
  269. if (this.form.source && !source_type.includes(this.form.source)) {
  270. return false
  271. }
  272. return true
  273. })
  274. },
  275. async handleConfirm () {
  276. this.loading = true
  277. try {
  278. for (let i = 0; i < this.selectedImages.length; i++) {
  279. const data = this.selectedImages[i]
  280. await new this.$Manager('images', 'v2').create({
  281. data: {
  282. domain_id: this.userInfo.projectDomainId,
  283. project_id: this.userInfo.projectId,
  284. copy_from: data.url,
  285. name: data.name,
  286. os_arch: data.os_arch,
  287. properties: {
  288. os_type: data.os_name,
  289. os_version: data.os_version,
  290. os_arch: data.os_arch,
  291. },
  292. },
  293. })
  294. }
  295. this.loading = false
  296. this.$router.push({ name: 'Image' })
  297. } catch (err) {
  298. this.$message.error(this.$t('common.failed'))
  299. this.loading = false
  300. throw err
  301. }
  302. },
  303. },
  304. }
  305. </script>