List.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <template>
  2. <page-list
  3. show-tag-columns
  4. show-tag-columns2
  5. show-tag-filter
  6. :list="list"
  7. :columns="changedColumns"
  8. :export-data-options="exportDataOptions"
  9. :showSearchbox="showSearchbox"
  10. :before-show-menu="beforeShowMenu" />
  11. </template>
  12. <script>
  13. import { mapGetters } from 'vuex'
  14. import * as R from 'ramda'
  15. import expectStatus from '@/constants/expectStatus'
  16. import WindowsMixin from '@/mixins/windows'
  17. import ListMixin from '@/mixins/list'
  18. import GlobalSearchMixin from '@/mixins/globalSearch'
  19. import {
  20. getNameFilter,
  21. getTenantFilter,
  22. getDomainFilter,
  23. getDescriptionFilter,
  24. getStatusFilter,
  25. } from '@/utils/common/tableFilter'
  26. import { getSetPublicAction } from '@/utils/common/tableActions'
  27. import { isCE } from '@/utils/utils'
  28. import ColumnsMixin from '@Compute/views/image/mixins/columns'
  29. import { HOST_CPU_ARCHS } from '@/constants/compute'
  30. import { getOsArch } from '@/utils/common/tableColumn'
  31. const archItems = Object.values(HOST_CPU_ARCHS)
  32. export default {
  33. name: 'CachedImageList',
  34. mixins: [WindowsMixin, ListMixin, GlobalSearchMixin, ColumnsMixin],
  35. props: {
  36. id: String,
  37. getParams: {
  38. type: Object,
  39. default: () => ({}),
  40. },
  41. cloudEnv: String,
  42. diskFormats: {
  43. type: Array,
  44. },
  45. },
  46. data () {
  47. return {
  48. list: this.$list.createList(this, {
  49. id: this.id,
  50. resource: 'cachedimages',
  51. apiVersion: 'v1',
  52. getParams: this.getParam,
  53. steadyStatus: Object.values(expectStatus.image).flat(),
  54. filterOptions: {
  55. id: {
  56. label: this.$t('table.title.id'),
  57. },
  58. name: getNameFilter(),
  59. status: getStatusFilter('image'),
  60. description: getDescriptionFilter(),
  61. distributions: {
  62. label: this.$t('table.title.os'),
  63. filter: true,
  64. formatter: (val) => {
  65. return `info.contains(${val})`
  66. },
  67. },
  68. disk_formats: {
  69. label: this.$t('table.title.disk_format'),
  70. filter: true,
  71. formatter: (val) => {
  72. return `info.contains(${val})`
  73. },
  74. },
  75. image_type: {
  76. label: this.$t('table.title.image_type'),
  77. dropdown: true,
  78. items: [
  79. { label: this.$t('compute.text_620'), key: 'custom' },
  80. { label: this.$t('compute.text_97'), key: 'system' },
  81. ],
  82. },
  83. projects: getTenantFilter(),
  84. project_domains: getDomainFilter(),
  85. os_arch: {
  86. label: this.$t('table.title.os_arch'),
  87. dropdown: true,
  88. items: archItems,
  89. filter: true,
  90. formatter: (val) => {
  91. return `info.contains(${val})`
  92. },
  93. },
  94. },
  95. responseData: this.responseData,
  96. hiddenColumns: ['metadata', 'created_at', 'os_arch'],
  97. }),
  98. }
  99. },
  100. computed: {
  101. ...mapGetters(['isAdminMode', 'isDomainMode', 'userInfo']),
  102. groupActions () {
  103. const ownerDomain = obj => this.$store.getters.isAdminMode || obj.domain_id === this.$store.getters.userInfo.projectDomainId
  104. const isOwnerProject = project => project === this.$store.getters.userInfo.projectId || project === this.$store.getters.userInfo.projectName
  105. const ImageImport = {
  106. label: this.$t('compute.text_642'),
  107. permission: 'images_create',
  108. action: () => {
  109. // eslint-disable-next-line vue/no-side-effects-in-computed-properties
  110. this.$router.push({ name: 'ImageImport' })
  111. },
  112. hidden: () => this.$isScopedPolicyMenuHidden('image_hidden_menus.image_store'),
  113. }
  114. const ImageImportCe = {
  115. label: this.$t('compute.open_image_market'),
  116. permission: 'images_create',
  117. action: () => {
  118. // eslint-disable-next-line vue/no-side-effects-in-computed-properties
  119. this.$router.push({ name: 'ImageImportCe' })
  120. },
  121. hidden: () => this.$isScopedPolicyMenuHidden('image_hidden_menus.image_store'),
  122. }
  123. const ImageUpload = {
  124. label: this.$t('compute.text_643'),
  125. permission: 'images_create',
  126. action: () => {
  127. this.createDialog('ImageUploadDialog', {
  128. title: this.$t('compute.text_643'),
  129. onManager: this.onManager,
  130. refresh: this.refresh,
  131. })
  132. },
  133. meta: () => ({
  134. buttonType: 'primary',
  135. }),
  136. hidden: () => this.$isScopedPolicyMenuHidden('image_hidden_menus.image_upload'),
  137. }
  138. const batchActions = [
  139. {
  140. label: this.$t('compute.text_275'),
  141. actions: obj => {
  142. return [
  143. getSetPublicAction(this, {
  144. name: this.$t('dictionary.image'),
  145. scope: 'project',
  146. resource: 'images',
  147. apiVersion: 'v1',
  148. }, {
  149. permission: 'images_perform_public,images_perform_private',
  150. meta: () => {
  151. let ret = {
  152. validate: false,
  153. tooltip: '',
  154. }
  155. const actions = new Map([
  156. ['admin', () => {
  157. if (this.list.selectedItems.some(item => this.booleanTransfer(item.is_standard))) {
  158. ret.tooltip = this.$t('compute.text_644')
  159. }
  160. return ret
  161. }],
  162. ['domain', () => {
  163. if (this.list.selectedItems.some(item => this.booleanTransfer(item.is_standard))) {
  164. ret.tooltip = this.$t('compute.text_644')
  165. return ret
  166. }
  167. if (this.list.selectedItems.some(item => item.public_scope === 'system')) {
  168. ret.tooltip = this.$t('compute.text_645')
  169. return ret
  170. }
  171. return ret
  172. }],
  173. ['user', () => {
  174. ret.tooltip = this.$t('compute.text_613')
  175. if (this.list.selectedItems.some(item => !this.booleanTransfer(item.is_standard) && item.public_scope === 'system')) {
  176. ret.tooltip = this.$t('compute.text_646')
  177. return ret
  178. }
  179. return ret
  180. }],
  181. ])
  182. const action = actions.get(this.isAdminMode ? 'admin' : '') || actions.get(this.isDomainMode ? 'domain' : 'user')
  183. ret = action.call(this)
  184. if (ret.tooltip) return ret
  185. return { validate: true }
  186. },
  187. }),
  188. {
  189. label: this.$t('compute.perform_change_owner', [this.$t('dictionary.project')]),
  190. permission: 'images_perform_change_owner',
  191. action: () => {
  192. this.createDialog('ChangeOwenrDialog', {
  193. data: this.list.selectedItems,
  194. columns: this.columns,
  195. onManager: this.onManager,
  196. name: this.$t('dictionary.image'),
  197. resource: 'images',
  198. apiVersion: 'v1',
  199. })
  200. },
  201. meta: () => {
  202. const ret = {
  203. validate: true,
  204. tooltip: null,
  205. }
  206. if (!this.isAdminMode && !this.isDomainMode) {
  207. ret.validate = false
  208. ret.tooltip = this.$t('compute.text_613')
  209. return ret
  210. }
  211. if (this.list.selectedItems.some(item => item.is_public)) {
  212. ret.validate = false
  213. ret.tooltip = this.$t('compute.text_614')
  214. return ret
  215. }
  216. return ret
  217. },
  218. hidden: () => this.$isScopedPolicyMenuHidden('image_hidden_menus.image_change_project'),
  219. },
  220. {
  221. label: this.$t('table.action.set_tag'),
  222. permission: 'images_perform_set_user_metadata',
  223. action: () => {
  224. this.createDialog('SetTagDialog', {
  225. data: this.list.selectedItems,
  226. columns: this.columns,
  227. onManager: this.onManager,
  228. mode: 'add',
  229. params: {
  230. resources: 'image',
  231. },
  232. tipName: this.$t('compute.text_97'),
  233. })
  234. },
  235. },
  236. {
  237. label: this.$t('compute.image.merge_mirror'),
  238. action: () => {
  239. this.createDialog('MergeMirrorDialog', {
  240. data: this.list.selectedItems,
  241. columns: this.columns,
  242. onManager: this.onManager,
  243. refresh: this.refresh,
  244. })
  245. },
  246. meta: () => {
  247. const ret = { validate: true }
  248. if (this.list.selectedItems.length < 2) {
  249. ret.validate = false
  250. ret.tooltip = this.$t('compute.image.merge_mirror.action.tooltip')
  251. return ret
  252. }
  253. const isSomeISO = this.list.selectedItems.some(item => item.disk_format === 'iso')
  254. if (isSomeISO) {
  255. ret.validate = false
  256. ret.tooltip = this.$t('compute.image.merge_mirror.system_image.select_validate')
  257. return ret
  258. }
  259. return ret
  260. },
  261. },
  262. {
  263. label: this.$t('common_277'),
  264. permission: 'images_update',
  265. action: (row) => {
  266. this.createDialog('ChangeDisableDelete', {
  267. name: this.$t('compute.text_97'),
  268. columns: this.columns,
  269. onManager: this.onManager,
  270. data: this.list.selectedItems,
  271. })
  272. },
  273. meta: () => {
  274. let ret = {
  275. validate: false,
  276. tooltip: '',
  277. }
  278. for (const obj of this.list.selectedItems) {
  279. const actions = new Map([
  280. ['admin', () => {
  281. if (this.booleanTransfer(obj.is_standard)) {
  282. ret.tooltip = this.$t('compute.text_687')
  283. return ret
  284. }
  285. return ret
  286. }],
  287. ['domain', () => {
  288. if (this.booleanTransfer(obj.is_standard)) {
  289. ret.tooltip = this.$t('compute.text_688')
  290. return ret
  291. }
  292. if (!ownerDomain(obj)) {
  293. ret.tooltip = this.$t('compute.text_689')
  294. return ret
  295. }
  296. return ret
  297. }],
  298. ['user', () => {
  299. if (this.booleanTransfer(obj.is_standard)) {
  300. ret.tooltip = this.$t('compute.text_688')
  301. return ret
  302. }
  303. if (!isOwnerProject(obj.tenant_id)) {
  304. ret.tooltip = this.$t('compute.text_690')
  305. return ret
  306. }
  307. return ret
  308. }],
  309. ])
  310. const action = actions.get(this.isAdminMode ? 'admin' : '') || actions.get(this.isDomainMode ? 'domain' : 'user')
  311. ret = action.call(this)
  312. if (ret.tooltip) {
  313. break
  314. }
  315. }
  316. if (ret.tooltip) return ret
  317. return { validate: true, tooltip: '' }
  318. },
  319. hidden: () => this.$isScopedPolicyMenuHidden('image_hidden_menus.image_set_delete_protection'),
  320. },
  321. {
  322. label: this.$t('compute.perform_delete'),
  323. permission: 'images_delete',
  324. action: () => {
  325. this.createDialog('DeleteResDialog', {
  326. vm: this,
  327. data: this.list.selectedItems,
  328. alert: this.$t('compute.text_1393'),
  329. columns: this.columns,
  330. title: this.$t('compute.text_617'),
  331. name: this.$t('dictionary.image'),
  332. onManager: this.onManager,
  333. })
  334. },
  335. meta: () => {
  336. const ret = {
  337. validate: false,
  338. tooltip: '',
  339. }
  340. const someStandard = this.list.selectedItems.some((item) => {
  341. return this.booleanTransfer(item.is_standard)
  342. })
  343. const someDisableDelete = this.list.selectedItems.some((item) => {
  344. return this.booleanTransfer(item.disable_delete) && this.booleanTransfer(item.protected)
  345. })
  346. if (this.isAdminMode) {
  347. if (someStandard) {
  348. ret.tooltip = this.$t('compute.text_648')
  349. return ret
  350. }
  351. } else if (this.isDomainMode) {
  352. const allOwnerDomain = this.list.selectedItems.every((item) => {
  353. return item.domain_id === this.userInfo.projectDomainId
  354. })
  355. if (someStandard) {
  356. ret.tooltip = this.$t('compute.text_649')
  357. return ret
  358. }
  359. if (!allOwnerDomain) {
  360. ret.tooltip = this.$t('compute.text_650')
  361. return ret
  362. }
  363. } else {
  364. const allOwnerProject = this.list.selectedItems.every((item) => {
  365. return item.tenant_id === this.userInfo.projectId
  366. })
  367. if (someStandard) {
  368. ret.tooltip = this.$t('compute.text_649')
  369. return ret
  370. }
  371. if (!allOwnerProject) {
  372. ret.tooltip = this.$t('compute.text_651')
  373. return ret
  374. }
  375. }
  376. if (someDisableDelete) {
  377. ret.tooltip = this.$t('common.text00008')
  378. return ret
  379. }
  380. return this.$getDeleteResult(this.list.selectedItems)
  381. },
  382. hidden: () => this.$isScopedPolicyMenuHidden('image_hidden_menus.image_delete'),
  383. },
  384. ]
  385. },
  386. meta: () => {
  387. return {
  388. validate: this.list.selectedItems && this.list.selectedItems.length > 0,
  389. }
  390. },
  391. },
  392. ]
  393. if (this.isAdminMode) {
  394. batchActions.unshift(ImageImportCe)
  395. }
  396. if (this.isAdminMode && !isCE() && !this.$store.getters.isSysCE) {
  397. batchActions.unshift(ImageImport)
  398. }
  399. batchActions.unshift(ImageUpload)
  400. return batchActions
  401. },
  402. exportDataOptions () {
  403. return {
  404. downloadType: 'local',
  405. title: this.$t('compute.text_97'),
  406. items: this.columns,
  407. hiddenFields: ['os_type'],
  408. fixedItems: [
  409. { key: 'properties.os_distribution', label: this.$t('table.title.os') },
  410. { key: 'size', label: this.$t('table.title.image_size') + '(B)' },
  411. ],
  412. }
  413. },
  414. changedColumns () {
  415. return [...this.columns.filter(item => item.field !== 'is_standard' && item.field !== 'properties.os_arch'),
  416. getOsArch({ field: 'info.properties.os_arch' }),
  417. {
  418. field: 'image_type',
  419. label: this.$t('table.title.image_type'),
  420. formatter: (val) => {
  421. return val === 'custom' ? this.$t('compute.text_620') : this.$t('compute.text_97')
  422. },
  423. },
  424. ]
  425. },
  426. },
  427. watch: {
  428. cloudEnv: {
  429. handler (val) {
  430. this.list.fetchData()
  431. },
  432. },
  433. },
  434. created () {
  435. this.initSidePageTab('detail')
  436. this.list.fetchData()
  437. },
  438. methods: {
  439. getParam () {
  440. const ret = {
  441. details: true,
  442. is_guest_image: false,
  443. ...this.getParams,
  444. }
  445. if (this.cloudEnv) ret.cloud_env = this.cloudEnv
  446. if (this.diskFormats) {
  447. if (!ret.disk_formats) {
  448. ret.disk_formats = []
  449. }
  450. for (let i = 0; i < this.diskFormats.length; i++) {
  451. ret.disk_formats.push(this.diskFormats[i])
  452. }
  453. }
  454. return ret
  455. },
  456. handleOpenSidepage (row) {
  457. this.sidePageTriggerHandle(this, 'SystemCachedImageSidePage', {
  458. id: row.id,
  459. resource: 'cachedimages',
  460. apiVersion: 'v1',
  461. getParams: this.getParam,
  462. steadyStatus: Object.values(expectStatus.image).flat(),
  463. }, {
  464. list: this.list,
  465. })
  466. },
  467. booleanTransfer (val) {
  468. if (R.is(String, val)) {
  469. return val === 'true'
  470. }
  471. return val
  472. },
  473. beforeShowMenu () {
  474. return this.$store.dispatch('scopedPolicy/get', {
  475. category: ['image_hidden_menus'],
  476. })
  477. },
  478. },
  479. }
  480. </script>