Detail.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <template>
  2. <detail
  3. :on-manager="onManager"
  4. :data="detailData"
  5. :extra-info="extraInfo"
  6. :base-info="baseInfo"
  7. :name-rules="[{ required: true, message: $t('compute.text_210') }]"
  8. :columns="serverColumns"
  9. auto-hidden-columns-key="server_hidden_columns"
  10. status-module="container"
  11. resource="servers" />
  12. </template>
  13. <script>
  14. import 'codemirror/theme/material.css'
  15. import {
  16. ALL_STORAGE,
  17. SERVER_TYPE,
  18. GPU_DEV_TYPE_OPTION_MAP,
  19. } from '@Compute/constants/index'
  20. import PasswordFetcher from '@Compute/sections/PasswordFetcher'
  21. import {
  22. getUserTagColumn,
  23. } from '@/utils/common/detailColumn'
  24. import {
  25. getCopyWithContentTableColumn,
  26. getBrandTableColumn,
  27. getSwitchTableColumn,
  28. getBillingTypeTableColumn,
  29. getOsArch,
  30. getIpsTableColumn,
  31. getStatusTableColumn,
  32. getNameDescriptionTableColumn,
  33. } from '@/utils/common/tableColumn'
  34. import WindowsMixin from '@/mixins/windows'
  35. import { findPlatform } from '@/utils/common/hypervisor'
  36. import { HYPERVISORS_MAP } from '@/constants'
  37. import { sizestr } from '@/utils/utils'
  38. import { formatCpuNumaPin } from '@Compute/views/vminstance/utils'
  39. export default {
  40. name: 'VmContainerInstanceDetail',
  41. mixins: [WindowsMixin],
  42. props: {
  43. onManager: {
  44. type: Function,
  45. required: true,
  46. },
  47. data: {
  48. type: Object,
  49. required: true,
  50. },
  51. serverColumns: Array,
  52. },
  53. data () {
  54. return {
  55. baseInfo: [
  56. getStatusTableColumn({
  57. field: 'power_states',
  58. title: this.$t('compute.power_states'),
  59. statusModule: 'container',
  60. }),
  61. {
  62. field: 'project_domain',
  63. hiddenField: 'tenant',
  64. title: this.$t('dictionary.domain'),
  65. formatter: ({ row }) => {
  66. if (!row.domain_id) return '-'
  67. return <side-page-trigger permission="domains_get" name="DomainSidePage" id={row.domain_id} vm={this}>{row.project_domain}</side-page-trigger>
  68. },
  69. },
  70. {
  71. field: 'tenant',
  72. title: this.$t('dictionary.project'),
  73. formatter: ({ row }) => {
  74. if (!row.tenant_id) return '-'
  75. return <side-page-trigger permission="projects_get" name="ProjectSidePage" id={row.tenant_id} vm={this}>{row.tenant}</side-page-trigger>
  76. },
  77. },
  78. getNameDescriptionTableColumn({
  79. onManager: this.onManager,
  80. field: 'hostname',
  81. hiddenField: 'name',
  82. title: this.$t('common_388'),
  83. label: this.$t('common_388'),
  84. showDesc: false,
  85. resource: 'servers',
  86. formRules: [{ required: true, message: this.$t('common.tips.input', [this.$t('common_388')]) }],
  87. }),
  88. getOsArch(),
  89. getUserTagColumn({
  90. onManager: this.onManager,
  91. resource: 'server',
  92. columns: () => this.serverColumns,
  93. tipName: this.$t('dictionary.server'),
  94. editCheck: (row) => row.hypervisor !== 'bingocloud',
  95. }),
  96. {
  97. field: 'containers',
  98. title: this.$t('compute.container', []),
  99. slots: {
  100. default: ({ row }) => {
  101. if (!row.containerTotal) return 0
  102. return [<a onClick={() => this.$emit('tab-change', 'container-list')}>{row.containerTotal}</a>]
  103. },
  104. },
  105. },
  106. getBrandTableColumn(),
  107. getBillingTypeTableColumn(),
  108. {
  109. field: 'password',
  110. title: this.$t('table.title.init_keypair'),
  111. minWidth: 50,
  112. slots: {
  113. default: ({ row }) => {
  114. return [<PasswordFetcher serverId={row.id} resourceType='servers' />]
  115. },
  116. },
  117. },
  118. ],
  119. containerTotal: 0,
  120. }
  121. },
  122. computed: {
  123. detailData () {
  124. return {
  125. ...this.data,
  126. containerTotal: this.containerTotal,
  127. }
  128. },
  129. diskInfos () {
  130. const disksInfo = this.data.disks_info
  131. if (!disksInfo) return {}
  132. const dataDisk = {}
  133. const dataDisks = disksInfo.filter(v => v.disk_type === 'data')
  134. if (dataDisks && dataDisks.length > 0) {
  135. for (const k in ALL_STORAGE) {
  136. const e = ALL_STORAGE[k]
  137. const sameType = dataDisks.filter(v => v.storage_type === e.value)
  138. if (sameType && sameType.length) {
  139. dataDisk[k] = this._dealSize(sameType)
  140. }
  141. }
  142. }
  143. return {
  144. dataDisk: this._diskStringify(dataDisk),
  145. }
  146. },
  147. extraInfo () {
  148. const infos = [
  149. {
  150. title: this.$t('compute.text_368'),
  151. items: [
  152. getIpsTableColumn({ field: 'ip', title: 'IP', vm: this, hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.ips') }),
  153. getCopyWithContentTableColumn({
  154. field: 'macs',
  155. title: 'MAC',
  156. hideField: true,
  157. slotCallback: row => {
  158. return row.macs || '-'
  159. },
  160. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.macs'),
  161. }),
  162. {
  163. field: 'host',
  164. title: this.$t('compute.text_111'),
  165. sortable: true,
  166. showOverflow: 'ellipsis',
  167. minWidth: 100,
  168. slots: {
  169. default: ({ row }) => {
  170. if (findPlatform(row.hypervisor, 'hypervisor') === SERVER_TYPE.public || row.hypervisor === HYPERVISORS_MAP.hcso.hypervisor || row.hypervisor === HYPERVISORS_MAP.hcs.hypervisor) {
  171. return '-'
  172. }
  173. const text = row.host || '-'
  174. return [
  175. <list-body-cell-wrap copy hideField={true} field='host' row={row} message={text}>
  176. <side-page-trigger permission='hosts_get' name='HostSidePage' id={row.host_id} vm={this}>{row.host}</side-page-trigger>
  177. </list-body-cell-wrap>,
  178. ]
  179. },
  180. },
  181. hidden: () => this.$store.getters.isProjectMode || this.$isScopedPolicyMenuHidden('server_hidden_columns.host'),
  182. },
  183. {
  184. field: 'secgroups',
  185. title: this.$t('compute.text_105'),
  186. slots: {
  187. default: ({ row }) => {
  188. if (!row.secgroups) return '-'
  189. return row.secgroups.map((item) => {
  190. return <list-body-cell-wrap copy hideField={true} field='name' row={item} message={item.name}>
  191. <side-page-trigger permission='secgroups_get' name='SecGroupSidePage' id={item.id} vm={this}>{item.name}</side-page-trigger>
  192. </list-body-cell-wrap>
  193. })
  194. },
  195. },
  196. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.secgroups'),
  197. },
  198. getCopyWithContentTableColumn({
  199. field: 'vpc',
  200. title: 'VPC',
  201. hideField: true,
  202. slotCallback: row => {
  203. if (!row.vpc) return '-'
  204. return [
  205. <side-page-trigger permission='vpcs_get' name='VpcSidePage' id={row.vpc_id} vm={this}>{row.vpc}</side-page-trigger>,
  206. ]
  207. },
  208. hidden: () => this.$store.getters.isProjectMode || this.$isScopedPolicyMenuHidden('server_hidden_columns.vpc'),
  209. }),
  210. {
  211. field: 'vcpu_count',
  212. title: 'CPU',
  213. formatter: ({ row }) => {
  214. if (row.hypervisor === HYPERVISORS_MAP.esxi.key && row.cpu_sockets) {
  215. return `CPU: ${row.vcpu_count}${this.$t('compute.text_167')}(${this.$t('compute.slots_number')}:${row.cpu_sockets})`
  216. }
  217. return row.vcpu_count + this.$t('compute.text_167')
  218. },
  219. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.vcpu_count'),
  220. },
  221. {
  222. field: 'vmem_size',
  223. title: this.$t('compute.text_369'),
  224. formatter: ({ row }) => {
  225. return (row.vmem_size / 1024) + 'GB'
  226. },
  227. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.vmem_size'),
  228. },
  229. {
  230. field: 'cpu_numa_pin',
  231. title: this.$t('compute.text_609'),
  232. formatter: ({ row }) => {
  233. return formatCpuNumaPin(row)
  234. },
  235. },
  236. {
  237. field: 'dataDisk',
  238. title: this.$t('compute.text_50'),
  239. formatter: ({ row }) => {
  240. if (!this.diskInfos.dataDisk) return '-'
  241. return <a onClick={() => this.$emit('tab-change', 'disk-list')}>{this.diskInfos.dataDisk}</a>
  242. },
  243. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.disk'),
  244. },
  245. {
  246. field: 'isolated_devices',
  247. title: this.$t('compute.text_113'),
  248. formatter: ({ row }) => {
  249. if (!row.isolated_devices && !row.gpu_count) return '-'
  250. const gpuArr = row.isolated_devices || []
  251. const obj = {}
  252. const devTypeMap = {}
  253. const ids = {}
  254. gpuArr.forEach(val => {
  255. if (val.dev_type !== 'USB') {
  256. if (!obj[val.model]) {
  257. obj[val.model] = 1
  258. } else {
  259. obj[val.model] += 1
  260. }
  261. ids[val.model] = val.id
  262. devTypeMap[val.model] = val.dev_type
  263. }
  264. })
  265. if (Object.keys(obj).length === 0) {
  266. if (row.gpu_count && row.gpu_model) {
  267. return this.$t('compute.text_370', [row.gpu_count, row.gpu_model])
  268. } else {
  269. return '-'
  270. }
  271. }
  272. return Object.keys(obj).map(k => {
  273. const gpuLabel = `${GPU_DEV_TYPE_OPTION_MAP[devTypeMap[k]]?.label || devTypeMap[k]}-${k}`
  274. return <side-page-trigger permission='isolated_devices_get' name='GpuSidePage' id={ids[k]} vm={this}>{this.$t('compute.text_370', [obj[k], gpuLabel])}</side-page-trigger>
  275. })
  276. },
  277. },
  278. {
  279. field: 'monitor_url',
  280. title: this.$t('compute.monitor_url.prompt'),
  281. formatter: ({ row }) => {
  282. return row.monitor_url
  283. },
  284. },
  285. {
  286. field: 'port_mapping',
  287. title: this.$t('compute.repo.port_mapping'),
  288. slots: {
  289. default: ({ row }) => {
  290. const colors = ['pink', 'red', 'orange', 'green', 'cyan', 'blue', 'purple']
  291. let index = 0
  292. const ret = []
  293. if (row.nics) {
  294. row.nics.forEach(item => {
  295. if (item.port_mappings) {
  296. for (let i = 0; i < item.port_mappings.length; i++) {
  297. index++
  298. const color = colors[index % 7]
  299. const port_mapping = item.port_mappings[i]
  300. ret.push(
  301. <p>
  302. <a-tag color={color}>
  303. {this.$t('compute.repo.container_port')}: {item.ip_addr}:{port_mapping.port} = {this.$t('compute.repo.host_port')}: {row.host_access_ip}:{port_mapping.host_port} ({port_mapping.protocol.toUpperCase()})
  304. </a-tag>
  305. </p>,
  306. )
  307. }
  308. }
  309. })
  310. }
  311. return ret.length ? ret : '-'
  312. },
  313. },
  314. },
  315. ],
  316. },
  317. {
  318. title: this.$t('compute.text_371'),
  319. items: [
  320. getSwitchTableColumn({
  321. field: 'disable_delete',
  322. title: this.$t('common.text00076'),
  323. change: val => {
  324. this.onManager('update', {
  325. id: this.data.id,
  326. managerArgs: {
  327. data: { disable_delete: val },
  328. },
  329. })
  330. },
  331. }),
  332. ],
  333. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.perform_action'),
  334. },
  335. ]
  336. return infos
  337. },
  338. },
  339. created () {
  340. this.fetchContainers()
  341. },
  342. methods: {
  343. async fetchContainers () {
  344. try {
  345. const containerManager = new this.$Manager('containers')
  346. const res = await containerManager.list({
  347. params: {
  348. scope: this.$store.getters.scope,
  349. guest_id: this.data.id,
  350. },
  351. })
  352. this.containerTotal = res.data.total
  353. } catch (error) {
  354. this.containerTotal = 0
  355. throw error
  356. }
  357. },
  358. _diskStringify (diskObj) {
  359. let str = ''
  360. const storageArr = Object.values(ALL_STORAGE)
  361. for (const k in diskObj) {
  362. const num = diskObj[k]
  363. const disk = storageArr.find(v => v.value === k)
  364. if (disk) {
  365. str += `、${sizestr(num, 'M', 1024)}(${disk.label})`
  366. } else {
  367. str += `、${sizestr(num, 'M', 1024)}(${k})`
  368. }
  369. }
  370. return str.slice(1)
  371. },
  372. _dealSize (sameType) {
  373. const sameType1 = sameType.map(v => {
  374. const size = +v.size
  375. return size
  376. })
  377. return sameType1.reduce((a, b) => {
  378. return a + b
  379. })
  380. },
  381. },
  382. }
  383. </script>