List.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <template>
  2. <page-list
  3. show-tag-columns
  4. show-tag-columns2
  5. show-tag-filter
  6. :id="id"
  7. :list="list"
  8. :columns="templateListColumns || columns"
  9. :group-actions="groupActions"
  10. :single-actions="singleActions"
  11. :show-single-actions="!isTemplate"
  12. :export-data-options="exportDataOptions"
  13. :defaultSearchKey="defaultSearchKey"
  14. :showSearchbox="showSearchbox"
  15. :showGroupActions="showGroupActions"
  16. :show-page="!isTemplate" />
  17. </template>
  18. <script>
  19. import * as R from 'ramda'
  20. import { disableDeleteAction } from '@/utils/common/tableActions'
  21. import { getNameFilter, getTenantFilter, getStatusFilter, getOsTypeFilter, getDomainFilter, getRegionFilter, getDescriptionFilter, getCreatedAtFilter, getBrandFilter, getAccountFilter } from '@/utils/common/tableFilter'
  22. import WindowsMixin from '@/mixins/windows'
  23. import ListMixin from '@/mixins/list'
  24. import ResTemplateListMixin from '@/mixins/resTemplateList'
  25. import expectStatus from '@/constants/expectStatus'
  26. import GlobalSearchMixin from '@/mixins/globalSearch'
  27. import regexp from '@/utils/regexp'
  28. import SingleActionsMixin from '../mixins/singleActions'
  29. import ColumnsMixin from '../mixins/columns'
  30. import { cloudEnabled, cloudUnabledTip } from '../../vminstance/utils'
  31. export default {
  32. name: 'BaremetalList',
  33. mixins: [WindowsMixin, ListMixin, GlobalSearchMixin, ColumnsMixin, SingleActionsMixin, ResTemplateListMixin],
  34. props: {
  35. id: String,
  36. getParams: {
  37. type: [Function, Object],
  38. },
  39. cloudEnv: String,
  40. hiddenFilterOptions: {
  41. type: Array,
  42. default: () => ([]),
  43. },
  44. hostInfo: Object,
  45. },
  46. data () {
  47. const filterOptions = {
  48. id: {
  49. label: this.$t('table.title.id'),
  50. },
  51. name: getNameFilter(),
  52. description: getDescriptionFilter(),
  53. ips: {
  54. label: 'IP',
  55. filter: true,
  56. formatter: val => {
  57. return `guestnetworks.guest_id(id).ip_addr.contains("${val}")`
  58. },
  59. jointFilter: true,
  60. },
  61. host_sn: {
  62. label: 'SN',
  63. distinctField: {
  64. type: 'extra_field',
  65. key: 'account',
  66. },
  67. },
  68. host: {
  69. label: this.$t('res.machine'),
  70. hidden: () => this.$store.getters.isProjectMode,
  71. },
  72. brand: getBrandFilter(),
  73. account: getAccountFilter(),
  74. region: getRegionFilter(),
  75. zone: {
  76. label: this.$t('res.zone'),
  77. },
  78. projects: getTenantFilter(),
  79. project_domains: getDomainFilter(),
  80. status: getStatusFilter({ statusModule: 'server' }),
  81. os_type: getOsTypeFilter(),
  82. created_at: getCreatedAtFilter(),
  83. }
  84. this.hiddenFilterOptions.forEach(key => {
  85. delete filterOptions[key]
  86. })
  87. return {
  88. list: this.$list.createList(this, {
  89. id: this.id,
  90. resource: 'servers',
  91. ctx: this,
  92. getParams: this.getParam,
  93. isTemplate: this.isTemplate,
  94. templateLimit: this.templateLimit,
  95. filterOptions,
  96. steadyStatus: Object.values(expectStatus.server).flat(),
  97. responseData: this.responseData,
  98. hiddenColumns: ['host_sn', 'created_at'],
  99. }),
  100. groupActions: [
  101. {
  102. label: this.$t('compute.perform_create'),
  103. permission: 'server_create',
  104. action: () => {
  105. this.$router.push({
  106. path: '/baremetal/create',
  107. query: {
  108. type: 'baremetal',
  109. cloud_env: this.hostInfo?.brand === 'Cloudpods' ? 'private' : 'onpremise',
  110. },
  111. })
  112. },
  113. meta: () => {
  114. return {
  115. buttonType: 'primary',
  116. }
  117. },
  118. },
  119. {
  120. label: this.$t('compute.text_272'),
  121. permission: 'server_perform_start',
  122. action: () => {
  123. const ids = this.list.selectedItems.map(item => item.id)
  124. this.onManager('batchPerformAction', {
  125. steadyStatus: 'running',
  126. id: ids,
  127. managerArgs: {
  128. action: 'start',
  129. },
  130. })
  131. },
  132. meta: () => {
  133. let ret = {
  134. validate: true,
  135. tooltip: null,
  136. }
  137. ret.validate = this.list.selectedItems.length > 0
  138. if (!ret.validate) return ret
  139. ret = this.$isValidateResourceLock(this.list.selectedItems, () => {
  140. ret.validate = this.list.selectedItems.every(item => item.status === 'ready')
  141. return ret
  142. })
  143. return ret
  144. },
  145. },
  146. {
  147. label: this.$t('compute.text_273'),
  148. permission: 'server_perform_stop',
  149. action: () => {
  150. this.createDialog('VmShutDownDialog', {
  151. data: this.list.selectedItems,
  152. columns: this.columns,
  153. onManager: this.onManager,
  154. })
  155. },
  156. meta: () => {
  157. let ret = {
  158. validate: true,
  159. tooltip: null,
  160. }
  161. ret.validate = this.list.selectedItems.length > 0
  162. if (!ret.validate) return ret
  163. ret = this.$isValidateResourceLock(this.list.selectedItems, () => {
  164. ret.validate = this.list.selectedItems.every(item => item.status === 'running')
  165. return ret
  166. })
  167. return ret
  168. },
  169. },
  170. {
  171. label: this.$t('compute.text_274'),
  172. permission: 'server_perform_restart',
  173. action: () => {
  174. this.createDialog('VmRestartDialog', {
  175. data: this.list.selectedItems,
  176. columns: this.columns,
  177. onManager: this.onManager,
  178. })
  179. },
  180. meta: () => {
  181. let ret = {
  182. validate: true,
  183. tooltip: null,
  184. }
  185. ret.validate = this.list.selectedItems.length > 0
  186. if (!ret.validate) return ret
  187. ret = this.$isValidateResourceLock(this.list.selectedItems, () => {
  188. ret.validate = this.list.selectedItems.every(item => ['running', 'stop_fail'].includes(item.status))
  189. return ret
  190. })
  191. return ret
  192. },
  193. },
  194. {
  195. label: this.$t('compute.text_275'),
  196. actions: () => {
  197. return [
  198. {
  199. label: this.$t('compute.text_276'),
  200. permission: 'server_perform_deploy',
  201. action: () => {
  202. this.createDialog('VmResetPasswordDialog', {
  203. data: this.list.selectedItems,
  204. columns: this.columns,
  205. onManager: this.onManager,
  206. })
  207. },
  208. meta: () => {
  209. if (this.isSameHyper) {
  210. const hasKeypair = this.list.selectedItems.some(obj => obj.keypair_id && obj.keypair_id.toLowerCase() !== 'none')
  211. if (hasKeypair) {
  212. return {
  213. validate: false, // 已绑定密钥的虚拟机无法重置密码
  214. tooltip: this.$t('compute.text_277'),
  215. }
  216. }
  217. const items = this.list.selectedItems.map(item => {
  218. return { ...item, brand: 'baremetal' }
  219. })
  220. return {
  221. validate: cloudEnabled('resetPassword', items),
  222. tooltip: cloudUnabledTip('resetPassword', items),
  223. }
  224. }
  225. return {
  226. validate: false,
  227. tooltip: this.$t('compute.text_278'),
  228. }
  229. },
  230. },
  231. {
  232. label: this.$t('compute.perform_change_owner', [this.$t('dictionary.project')]),
  233. permission: 'server_perform_change_owner',
  234. action: () => {
  235. this.createDialog('ChangeOwenrDialog', {
  236. data: this.list.selectedItems,
  237. columns: this.columns,
  238. onManager: this.onManager,
  239. resource: 'servers',
  240. })
  241. },
  242. meta: () => {
  243. const ret = {
  244. validate: true,
  245. tooltip: null,
  246. }
  247. const domains = this.list.selectedItems.map(item => item.domain_id)
  248. if (R.uniq(domains).length !== 1) {
  249. ret.validate = false
  250. ret.tooltip = this.$t('compute.text_280', [this.$t('dictionary.domain')])
  251. return ret
  252. }
  253. return ret
  254. },
  255. },
  256. {
  257. label: this.$t('compute.perform_sync_status'),
  258. permission: 'server_perform_syncstatus',
  259. action: () => {
  260. this.onManager('batchPerformAction', {
  261. steadyStatus: Object.values(expectStatus.server).flat(),
  262. managerArgs: {
  263. action: 'syncstatus',
  264. },
  265. })
  266. },
  267. },
  268. {
  269. label: this.$t('compute.vminstance.monitor.install_agent'),
  270. permission: 'server_perform_install_agent',
  271. action: () => {
  272. this.createDialog('InstallAgentDialog', {
  273. data: this.list.selectedItems,
  274. columns: this.columns,
  275. onManager: this.onManager,
  276. })
  277. },
  278. meta: () => {
  279. let ret = {
  280. validate: true,
  281. tooltip: null,
  282. }
  283. ret.validate = this.list.selectedItems.length > 0
  284. if (!ret.validate) return ret
  285. ret = this.$isValidateResourceLock(this.list.selectedItems, () => {
  286. ret.validate = this.list.selectedItems.every(item => ['running'].includes(item.status))
  287. return ret
  288. })
  289. return ret
  290. },
  291. hidden: () => this.$isScopedPolicyMenuHidden('vminstance_hidden_menus.server_perform_detect_ssh_proxy'),
  292. },
  293. {
  294. label: this.$t('table.action.set_tag'),
  295. permission: 'server_perform_set_user_metadata',
  296. action: () => {
  297. this.createDialog('SetTagDialog', {
  298. data: this.list.selectedItems,
  299. columns: this.columns,
  300. onManager: this.onManager,
  301. mode: 'add',
  302. params: {
  303. resources: 'server',
  304. },
  305. })
  306. },
  307. },
  308. disableDeleteAction(Object.assign(this, { permission: 'server_update' })),
  309. {
  310. label: this.$t('compute.perform_delete'),
  311. permission: 'server_delete',
  312. action: () => {
  313. this.createDialog('DeleteResDialog', {
  314. vm: this,
  315. data: this.list.selectedItems,
  316. columns: this.columns,
  317. onManager: this.onManager,
  318. title: this.$t('compute.perform_delete'),
  319. })
  320. },
  321. meta: () => {
  322. const ret = {
  323. validate: true,
  324. tooltip: null,
  325. }
  326. if (this.list.selectedItems.some(item => item.billing_type === 'prepaid')) {
  327. ret.validate = false
  328. ret.tooltip = this.$t('compute.text_285')
  329. return ret
  330. }
  331. return this.$getDeleteResult(this.list.selectedItems)
  332. },
  333. },
  334. ]
  335. },
  336. meta: () => {
  337. let ret = {
  338. validate: true,
  339. tooltip: null,
  340. }
  341. ret.validate = this.list.selectedItems.length > 0
  342. if (!ret.validate) return ret
  343. ret = this.$isValidateResourceLock(this.list.selectedItems)
  344. return ret
  345. },
  346. },
  347. ],
  348. }
  349. },
  350. computed: {
  351. isSameHyper () {
  352. if (this.list.selectedItems.length > 0) {
  353. const arr = this.list.selectedItems.map(v => v.hypervisor)
  354. const noRepeatArr = Array.from(new Set(arr))
  355. return noRepeatArr.length === 1
  356. }
  357. return true
  358. },
  359. exportDataOptions () {
  360. return {
  361. items: this.columns,
  362. downloadType: 'local',
  363. title: this.$t('compute.text_92'),
  364. getParams: () => ({ hypervisor: 'baremetal' }),
  365. fixedItems: [
  366. { key: 'vcpu_count', label: 'CPU' },
  367. { key: 'disk', label: this.$t('table.title.disk') + '(M)' },
  368. { key: 'vmem_size', label: this.$t('table.title.vmem_size') + '(M)' },
  369. { key: 'eip', title: this.$t('common.eip') },
  370. { key: 'ips', title: 'IP' },
  371. ],
  372. hiddenFields: ['ip'],
  373. }
  374. },
  375. },
  376. watch: {
  377. cloudEnv (val) {
  378. this.$nextTick(() => {
  379. this.list.fetchData(0)
  380. })
  381. },
  382. },
  383. created () {
  384. this.initSidePageTab('baremetal-detail')
  385. this.list.fetchData()
  386. },
  387. methods: {
  388. getParam () {
  389. const ret = {
  390. hypervisor: 'baremetal',
  391. ...this.getParams,
  392. details: true,
  393. with_meta: true,
  394. }
  395. if (this.cloudEnv) ret.cloud_env = this.cloudEnv
  396. return ret
  397. },
  398. handleOpenSidepage (row, tab) {
  399. this.sidePageTriggerHandle(this, 'BaremetalSidePage', {
  400. id: row.id,
  401. resource: 'servers',
  402. getParams: this.getParam,
  403. steadyStatus: Object.values(expectStatus.image).flat(),
  404. }, {
  405. list: this.list,
  406. tab,
  407. })
  408. },
  409. defaultSearchKey (search) {
  410. if (regexp.isIPv4(search)) {
  411. return 'ips'
  412. }
  413. },
  414. },
  415. }
  416. </script>