List.vue 18 KB

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