List.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. <template>
  2. <page-list
  3. show-tag-columns
  4. show-tag-filter
  5. :list="list"
  6. :columns="templateListColumns || columns"
  7. :group-actions="groupActions"
  8. :single-actions="singleActions"
  9. :export-data-options="exportDataOptions"
  10. :extra-export-params="extraExportParams"
  11. :showSearchbox="showSearchbox"
  12. :defaultSearchKey="defaultSearchKey"
  13. :tableOverviewIndexs="tableOverviewIndexs"
  14. :showGroupActions="showGroupActions"
  15. :show-single-actions="!isTemplate"
  16. :show-page="!isTemplate" />
  17. </template>
  18. <script>
  19. import * as R from 'ramda'
  20. import { getNameFilter, getDescriptionFilter, getStatusFilter, getEnabledFilter, getBrandFilter, getProjectDomainFilter, getAccountFilter, getOsArchFilter, getCreatedAtFilter } from '@/utils/common/tableFilter'
  21. import WindowsMixin from '@/mixins/windows'
  22. import GlobalSearchMixin from '@/mixins/globalSearch'
  23. import ListMixin from '@/mixins/list'
  24. import ResTemplateListMixin from '@/mixins/resTemplateList'
  25. import { typeClouds, getDisabledProvidersActionMeta } from '@/utils/common/hypervisor'
  26. import { getDomainChangeOwnerAction, getSetPublicAction, getEnabledSwitchActions } from '@/utils/common/tableActions'
  27. import { HYPERVISORS_MAP, EXTRA_HYPERVISORS } from '@/constants'
  28. import regexp from '@/utils/regexp'
  29. import { getSignature } from '@/utils/crypto'
  30. import SingleActionsMixin from '../mixins/singleActions'
  31. import ColumnsMixin from '../mixins/columns'
  32. export default {
  33. name: 'HostList',
  34. mixins: [WindowsMixin, ListMixin, GlobalSearchMixin, ColumnsMixin, SingleActionsMixin, ResTemplateListMixin],
  35. props: {
  36. id: String,
  37. getParams: {
  38. type: [Function, Object],
  39. },
  40. frontGroupActions: {
  41. type: Function,
  42. },
  43. frontSingleActions: {
  44. type: Function,
  45. },
  46. hiddenFilterOptions: {
  47. type: Array,
  48. default: () => ([]),
  49. },
  50. },
  51. data () {
  52. const filter = {}
  53. if (this.$route.query.hasOwnProperty('enabled')) {
  54. filter.enabled = [this.$route.query.enabled]
  55. }
  56. const brandFilter = getBrandFilter()
  57. const notSupportBrand = [
  58. ...Object.values(HYPERVISORS_MAP).filter(item => item.cloud_env === 'public').map(item => item.brand),
  59. ...Object.values(EXTRA_HYPERVISORS).map(item => item.brand),
  60. ]
  61. const filterOptions = {
  62. id: {
  63. label: this.$t('table.title.id'),
  64. },
  65. name: getNameFilter(),
  66. description: getDescriptionFilter(),
  67. status: getStatusFilter('host'),
  68. enabled: getEnabledFilter(),
  69. host_status: {
  70. label: this.$t('compute.text_502'),
  71. dropdown: true,
  72. items: Object.keys(this.$t('status.host_status')).map(key => {
  73. return { label: this.$t('status.host_status')[key], key }
  74. }),
  75. },
  76. sn: {
  77. label: 'SN',
  78. distinctField: {
  79. type: 'extra_field',
  80. key: 'sn',
  81. },
  82. },
  83. any_mac: {
  84. label: 'MAC',
  85. },
  86. any_ip: {
  87. label: 'IP',
  88. },
  89. access_ip: {
  90. label: this.$t('compute.host_access_ip'),
  91. filter: true,
  92. formatter: val => {
  93. return `access_ip.contains("${val}")`
  94. },
  95. },
  96. host_type: {
  97. label: this.$t('compute.host.host_type.title'),
  98. dropdown: true,
  99. multiple: true,
  100. distinctField: {
  101. type: 'field',
  102. key: 'host_type',
  103. },
  104. },
  105. region: {
  106. label: this.$t('compute.text_177'),
  107. },
  108. zone: {
  109. label: this.$t('compute.text_270'),
  110. },
  111. brand: {
  112. ...brandFilter,
  113. items: brandFilter.items.filter(val => !notSupportBrand.includes(val.key)),
  114. },
  115. project_domains: getProjectDomainFilter(),
  116. account: getAccountFilter(),
  117. cpu_architecture: getOsArchFilter(true),
  118. created_at: getCreatedAtFilter(),
  119. }
  120. this.hiddenFilterOptions.forEach(key => {
  121. delete filterOptions[key]
  122. })
  123. const monitorManager = new this.$Manager('unifiedmonitors/resource-metrics', 'v1')
  124. return {
  125. monitorManager,
  126. list: this.$list.createList(this, {
  127. ctx: this,
  128. id: this.id,
  129. resource: 'hosts',
  130. getParams: this.getParam,
  131. isTemplate: this.isTemplate,
  132. templateLimit: this.templateLimit,
  133. filterOptions,
  134. filter,
  135. responseData: this.responseData,
  136. hiddenColumns: ['metadata', 'id', 'server_id', 'sn', 'manufacture', 'model', 'schedtag', 'nonsystem_guests', 'public_scope', 'project_domain', 'region', 'os_arch', 'created_at'],
  137. fetchDataCb: async (response) => {
  138. if (response.data?.data?.length) {
  139. const list = response.data?.data || []
  140. const ids = list.map(item => item.id).filter(Boolean)
  141. if (ids.length) {
  142. const res = await monitorManager.create({
  143. data: {
  144. res_ids: ids,
  145. res_type: 'host',
  146. },
  147. })
  148. const { resource_metrics = {} } = res.data || {}
  149. for (const key in resource_metrics) {
  150. const metrics = resource_metrics[key]
  151. const row = list.find(item => item.id === key)
  152. if (row) row.alert_data = metrics
  153. }
  154. }
  155. response.data.data = list
  156. }
  157. return response
  158. },
  159. }),
  160. }
  161. },
  162. computed: {
  163. groupActions () {
  164. const _frontGroupActions = this.frontGroupActions ? this.frontGroupActions.bind(this)() || [] : []
  165. const ownerDomain = this.$store.getters.isAdminMode || this.list.selectedItems.every(obj => obj.domain_id === this.$store.getters.userInfo.projectDomainId)
  166. return _frontGroupActions.concat(
  167. [
  168. ...getEnabledSwitchActions(this, undefined, ['hosts_perform_enable', 'hosts_perform_disable'], {
  169. actions: [
  170. async (obj) => {
  171. const ids = this.list.selectedItems.map(item => item.id)
  172. await this.onManager('batchPerformAction', {
  173. id: ids,
  174. managerArgs: {
  175. action: 'enable',
  176. },
  177. })
  178. this.$store.dispatch('auth/getCapabilities')
  179. },
  180. async (obj) => {
  181. const ids = this.list.selectedItems.map(item => item.id)
  182. await this.onManager('batchPerformAction', {
  183. id: ids,
  184. managerArgs: {
  185. action: 'disable',
  186. },
  187. })
  188. this.$store.dispatch('auth/getCapabilities')
  189. },
  190. ],
  191. metas: [
  192. () => {
  193. const isDisable = !!this.list.selectedItems.find(item => !item.enabled)
  194. return {
  195. validate: this.list.selectedItems.length && ownerDomain && isDisable,
  196. }
  197. },
  198. () => {
  199. const isEnable = !!this.list.selectedItems.find(item => item.enabled)
  200. return {
  201. validate: this.list.selectedItems.length && ownerDomain && isEnable,
  202. }
  203. },
  204. ],
  205. extraMetas: [
  206. (obj) => {
  207. return getDisabledProvidersActionMeta({
  208. rows: this.list.selectedItems,
  209. disabledProviders: ['BingoCloud'],
  210. })
  211. },
  212. (obj) => {
  213. return getDisabledProvidersActionMeta({
  214. rows: this.list.selectedItems,
  215. disabledProviders: ['BingoCloud'],
  216. })
  217. },
  218. ],
  219. }),
  220. {
  221. label: this.$t('common.batchAction'),
  222. actions: () => {
  223. return [
  224. getDomainChangeOwnerAction(this, {
  225. name: this.$t('dictionary.host'),
  226. resource: 'hosts',
  227. }, {
  228. permission: 'hosts_perform_change_owner',
  229. meta: function () {
  230. return {
  231. validate: ownerDomain,
  232. }
  233. },
  234. extraMeta: obj => {
  235. return getDisabledProvidersActionMeta({
  236. rows: this.list.selectedItems,
  237. disabledProviders: ['BingoCloud'],
  238. })
  239. },
  240. }),
  241. getSetPublicAction(this, {
  242. name: this.$t('dictionary.host'),
  243. scope: 'domain',
  244. resource: 'hosts',
  245. }, {
  246. permission: 'hosts_perform_public',
  247. extraMeta: obj => {
  248. return getDisabledProvidersActionMeta({
  249. rows: this.list.selectedItems,
  250. disabledProviders: ['BingoCloud'],
  251. })
  252. },
  253. }),
  254. {
  255. label: this.$t('compute.text_540'),
  256. permission: 'hosts_perform_set_schedtag',
  257. action: (obj) => {
  258. this.createDialog('HostsAdjustLabelDialog', {
  259. data: this.list.selectedItems,
  260. columns: this.columns,
  261. name: this.$t('dictionary.host'),
  262. onManager: this.onManager,
  263. })
  264. },
  265. meta: () => ({
  266. validate: this.list.selectedItems.length && ownerDomain,
  267. }),
  268. extraMeta: obj => {
  269. return getDisabledProvidersActionMeta({
  270. rows: this.list.selectedItems,
  271. disabledProviders: ['BingoCloud'],
  272. })
  273. },
  274. },
  275. {
  276. label: this.$t('compute.text_508'),
  277. permission: 'hosts_perform_undo_convert',
  278. action: (obj) => {
  279. // this.list.batchPerformAction('disable', null)
  280. this.createDialog('HostUnconvertDialog', {
  281. data: this.list.selectedItems,
  282. columns: this.columns,
  283. onManager: this.onManager,
  284. name: this.$t('dictionary.host'),
  285. refresh: this.refresh,
  286. })
  287. },
  288. meta: () => {
  289. if (!this.list.selectedItems.length) {
  290. return {
  291. validate: false,
  292. tooltip: this.$t('compute.text_509'),
  293. }
  294. }
  295. for (let i = 0; i < this.list.selectedItems.length; i++) {
  296. const obj = this.list.selectedItems[i]
  297. if (obj.host_type !== 'hypervisor') {
  298. return {
  299. validate: false,
  300. tooltip: this.$t('compute.text_510'),
  301. }
  302. } else if (obj.nonsystem_guests > 0) {
  303. return {
  304. validate: false,
  305. tooltip: this.$t('compute.text_511'),
  306. }
  307. } else if (obj.enabled) {
  308. return {
  309. validate: false,
  310. tooltip: this.$t('compute.text_512'),
  311. }
  312. } else if (!obj.is_baremetal) {
  313. return {
  314. validate: false,
  315. tooltip: '',
  316. }
  317. } else if (!ownerDomain) {
  318. return {
  319. validate: false,
  320. tooltip: '',
  321. }
  322. }
  323. }
  324. return {
  325. validate: true,
  326. tooltip: '',
  327. }
  328. },
  329. extraMeta: obj => {
  330. return getDisabledProvidersActionMeta({
  331. rows: this.list.selectedItems,
  332. disabledProviders: ['BingoCloud'],
  333. })
  334. },
  335. },
  336. {
  337. label: this.$t('compute.text_513'),
  338. permission: 'hosts_update,hosts_perform_set_commit_bound',
  339. action: () => {
  340. this.createDialog('HostAdjustOversoldRatioDialog', {
  341. data: this.list.selectedItems,
  342. columns: this.columns,
  343. onManager: this.onManager,
  344. name: this.$t('dictionary.host'),
  345. refresh: this.refresh,
  346. })
  347. },
  348. meta: () => ({
  349. validate: this.list.selectedItems.every(item => { return item.brand.toLowerCase() !== 'zstack' }) && ownerDomain,
  350. }),
  351. extraMeta: obj => {
  352. return getDisabledProvidersActionMeta({
  353. rows: this.list.selectedItems,
  354. disabledProviders: ['BingoCloud'],
  355. })
  356. },
  357. },
  358. // {
  359. // label: this.$t('compute.host.cpu.revert.resource'),
  360. // action: obj => {
  361. // this.createDialog('SetHostCpuReserveResourceDialog', {
  362. // onManager: this.onManager,
  363. // data: this.list.selectedItems,
  364. // columns: this.columns,
  365. // refresh: this.refresh,
  366. // })
  367. // },
  368. // meta: () => {
  369. // const ret = {
  370. // validate: false,
  371. // tooltip: null,
  372. // }
  373. // if (!ownerDomain) {
  374. // ret.tooltip = this.$t('compute.host.cpu.revert.share')
  375. // return ret
  376. // }
  377. // const isAllOneCloud = this.list.selectedItems.every((item) => { return item.provider === typeClouds.providerMap.OneCloud.key })
  378. // if (!isAllOneCloud) {
  379. // ret.tooltip = this.$t('compute.text_515')
  380. // return ret
  381. // }
  382. // const isSomeRunning = this.list.selectedItems.some(item => item.running_guests > 0)
  383. // if (isSomeRunning) {
  384. // ret.tooltip = this.$t('compute.host.cpu.revert.running_guest_tooltip')
  385. // return ret
  386. // }
  387. // return {
  388. // validate: true,
  389. // }
  390. // },
  391. // },
  392. {
  393. label: this.$t('compute.setup_passthrough_reserve'),
  394. permission: 'hosts_perform_set_reserved_resource_for_isolated_device',
  395. action: () => {
  396. this.createDialog('SetHostReserveResourceDialog', {
  397. onManager: this.onManager,
  398. data: this.list.selectedItems,
  399. columns: this.columns,
  400. refresh: this.refresh,
  401. })
  402. },
  403. meta: () => {
  404. const ret = {
  405. validate: false,
  406. tooltip: null,
  407. }
  408. const isAllOneCloud = this.list.selectedItems.every((item) => { return item.provider === typeClouds.providerMap.OneCloud.key })
  409. if (!isAllOneCloud) {
  410. ret.tooltip = this.$t('compute.text_515')
  411. return ret
  412. }
  413. const isAllReservedResource = this.list.selectedItems.every((item) => { return item.reserved_resource_for_gpu })
  414. if (!isAllReservedResource) {
  415. ret.tooltip = this.$t('compute.text_516')
  416. return ret
  417. }
  418. return {
  419. validate: ownerDomain,
  420. }
  421. },
  422. extraMeta: obj => {
  423. return getDisabledProvidersActionMeta({
  424. rows: this.list.selectedItems,
  425. disabledProviders: ['BingoCloud'],
  426. })
  427. },
  428. },
  429. {
  430. label: this.$t('table.action.set_tag'),
  431. permission: 'hosts_perform_set_user_metadata',
  432. action: () => {
  433. this.createDialog('SetTagDialog', {
  434. data: this.list.selectedItems,
  435. columns: this.columns,
  436. onManager: this.onManager,
  437. mode: 'add',
  438. params: {
  439. resources: 'host',
  440. },
  441. tipName: this.$t('dictionary.host'),
  442. })
  443. },
  444. extraMeta: obj => {
  445. return getDisabledProvidersActionMeta({
  446. rows: this.list.selectedItems,
  447. disabledProviders: ['BingoCloud'],
  448. })
  449. },
  450. },
  451. {
  452. label: this.$t('compute.perform_delete'),
  453. permission: 'hosts_delete',
  454. action: () => {
  455. this.createDialog('DeleteResDialog', {
  456. vm: this,
  457. data: this.list.selectedItems,
  458. columns: this.columns,
  459. title: this.$t('compute.perform_delete'),
  460. name: this.$t('dictionary.host'),
  461. onManager: this.onManager,
  462. success: () => {
  463. this.$store.dispatch('auth/getCapabilities')
  464. },
  465. })
  466. },
  467. meta: () => {
  468. const deleteResult = this.$getDeleteResult(this.list.selectedItems)
  469. if (!deleteResult.validate) {
  470. return deleteResult
  471. }
  472. return {
  473. validate: ownerDomain,
  474. }
  475. },
  476. extraMeta: obj => {
  477. return getDisabledProvidersActionMeta({
  478. rows: this.list.selectedItems,
  479. disabledProviders: ['BingoCloud'],
  480. })
  481. },
  482. },
  483. ]
  484. },
  485. meta: () => {
  486. return {
  487. validate: this.list.selected.length,
  488. }
  489. },
  490. },
  491. ],
  492. )
  493. },
  494. exportDataOptions () {
  495. return {
  496. title: this.$t('compute.text_111'),
  497. downloadType: 'local',
  498. items: [
  499. { field: 'id', title: 'ID' },
  500. { field: 'external_id', title: this.$t('table.title.external_id') },
  501. ...(this.columns.filter(item => !['server_id', 'id'].includes(item.field))),
  502. { label: this.$t('compute.text_523'), key: 'cpu_commit_rate' },
  503. { label: this.$t('compute.text_518'), key: 'mem_commit_rate' },
  504. { label: this.$t('compute.storage_commit_rate'), key: 'storage_commit_rate' },
  505. ],
  506. hiddenFields: ['cpu_commit', 'mem_commit', 'cpu_commit_rate', 'mem_commit_rate', 'storage_commit_rate', 'model', 'custom_ip', 'manufacture'],
  507. fixedItems: [
  508. { key: 'mem_size', label: this.$t('compute.text_564') + '(M)' },
  509. { key: 'storage_size', label: this.$t('compute.text_565') + '(M)' },
  510. { key: 'storage_virtual', label: this.$t('compute.text_565_1') + '(M)' },
  511. { key: 'sys_info.model', label: this.$t('compute.text_580') },
  512. { key: 'sys_info.oem_name', label: this.$t('compute.text_847') },
  513. { key: 'ipmi_ip', label: 'IPMI IP' },
  514. { key: 'public_ip', label: this.$t('compute.text_1374') },
  515. { key: 'access_ip', label: 'IP' },
  516. ],
  517. }
  518. },
  519. },
  520. watch: {
  521. filterParams: {
  522. handler: function (val) {
  523. if (!val.isFirstLoad) {
  524. const filterStatus = this.list.filter.status || []
  525. val.statusCheckArr.forEach((item) => {
  526. if (!filterStatus.includes(item)) {
  527. filterStatus.push(item)
  528. }
  529. })
  530. if (val.statusCheckArr && val.statusCheckArr.length > 0) {
  531. this.list.changeFilter({ ...this.list.filter, status: val.statusCheckArr })
  532. this.list.filterOptions.status.items = []
  533. const statusArrTem = this.list.filterOptions.status.items || []
  534. val.statusArr.forEach((item) => {
  535. const isExist = statusArrTem.some((obj) => { return obj.key === item })
  536. if (!isExist) {
  537. statusArrTem.push({
  538. key: item,
  539. label: this.$t(`status.host.${item}`),
  540. })
  541. }
  542. })
  543. this.list.filterOptions.status.items = statusArrTem
  544. } else {
  545. delete this.list.filter.status
  546. this.list.changeFilter({ ...this.list.filter })
  547. }
  548. }
  549. },
  550. deep: true,
  551. },
  552. 'list.filter' (val) {
  553. this.$bus.$emit('ServerFilterChange', val)
  554. },
  555. },
  556. created () {
  557. this.initSidePageTab('host-detail')
  558. this.list.fetchData()
  559. },
  560. methods: {
  561. refresh () {
  562. this.list.fetchData()
  563. },
  564. extraExportParams ({ currentExportType }) {
  565. if (currentExportType === 'all') return { baremetal: false }
  566. return {}
  567. },
  568. getParam () {
  569. const ret = {
  570. ...(R.is(Function, this.getParams) ? this.getParams() : this.getParams),
  571. }
  572. ret.hide_cpu_topo_info = true
  573. return ret
  574. },
  575. handleOpenSidepage (row, tab) {
  576. this.sidePageTriggerHandle(this, 'HostSidePage', {
  577. id: row.id,
  578. resource: 'hosts',
  579. getParams: {
  580. ...this.getParam(),
  581. hide_cpu_topo_info: false,
  582. },
  583. }, {
  584. list: this.list,
  585. tab,
  586. })
  587. },
  588. defaultSearchKey (search) {
  589. if (regexp.isIPv4(search)) {
  590. return 'any_ip'
  591. }
  592. if (regexp.isMAC(search)) {
  593. return 'any_mac'
  594. }
  595. },
  596. genQueryData (val, list) {
  597. const select = [
  598. {
  599. type: 'field',
  600. params: [val.seleteItem],
  601. },
  602. { // 对应 mean(val.seleteItem)
  603. type: 'max',
  604. params: [],
  605. },
  606. ]
  607. const tags = []
  608. list.map((item, index) => {
  609. if (item.access_ip) {
  610. const l = { key: 'host_ip', operator: '=', value: item.access_ip }
  611. if (index) {
  612. l.condition = 'OR'
  613. }
  614. tags.push(l)
  615. }
  616. })
  617. const data = {
  618. metric_query: [
  619. {
  620. model: {
  621. measurement: val.fromItem,
  622. database: 'telegraf',
  623. select: [select],
  624. group_by: [{ type: 'tag', params: ['host_ip'] }],
  625. tags,
  626. },
  627. },
  628. ],
  629. scope: this.$store.getters.scope,
  630. from: '1h',
  631. interval: '5m',
  632. soffset: 0,
  633. }
  634. data.signature = getSignature(data)
  635. return data
  636. },
  637. },
  638. }
  639. </script>