Detail.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. <template>
  2. <detail
  3. :on-manager="onManager"
  4. :data="data"
  5. :extra-info="extraInfo"
  6. :base-info="baseInfo"
  7. :name-rules="[{ required: true, message: $t('compute.text_210') }]"
  8. :columns="serverColumns"
  9. showStatusProgress
  10. auto-hidden-columns-key="server_hidden_columns"
  11. status-module="server"
  12. resource="servers" />
  13. </template>
  14. <script>
  15. import 'codemirror/theme/material.css'
  16. import * as R from 'ramda'
  17. import { ALL_STORAGE, SERVER_TYPE, GPU_DEV_TYPE_OPTION_MAP } from '@Compute/constants/index'
  18. import PasswordFetcher from '@Compute/sections/PasswordFetcher'
  19. import {
  20. getUserTagColumn,
  21. // getExtTagColumn,
  22. } from '@/utils/common/detailColumn'
  23. import {
  24. getCopyWithContentTableColumn,
  25. getBrandTableColumn,
  26. getSwitchTableColumn,
  27. getOsArch,
  28. getIpsTableColumn,
  29. getServerMonitorAgentInstallStatus,
  30. getStatusTableColumn,
  31. getNameDescriptionTableColumn,
  32. } from '@/utils/common/tableColumn'
  33. import WindowsMixin from '@/mixins/windows'
  34. import { findPlatform } from '@/utils/common/hypervisor'
  35. import { BRAND_MAP, HYPERVISORS_MAP } from '@/constants'
  36. import { sizestr, sizestrWithUnit } from '@/utils/utils'
  37. import { hasPermission } from '@/utils/auth'
  38. import { formatCpuNumaPin } from '@Compute/views/vminstance/utils'
  39. export default {
  40. name: 'VmInstanceDetail',
  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. alertData: null,
  56. baseInfo: [
  57. getStatusTableColumn({
  58. field: 'power_states',
  59. title: this.$t('compute.power_states'),
  60. statusModule: 'server',
  61. }),
  62. {
  63. field: 'project_domain',
  64. hiddenField: 'tenant',
  65. title: this.$t('dictionary.domain'),
  66. formatter: ({ row }) => {
  67. if (!row.domain_id) return '-'
  68. return <side-page-trigger permission="domains_get" name="DomainSidePage" id={row.domain_id} vm={this}>{row.project_domain}</side-page-trigger>
  69. },
  70. },
  71. {
  72. field: 'tenant',
  73. title: this.$t('dictionary.project'),
  74. formatter: ({ row }) => {
  75. if (!row.tenant_id) return '-'
  76. return <side-page-trigger permission="projects_get" name="ProjectSidePage" id={row.tenant_id} vm={this}>{row.tenant}</side-page-trigger>
  77. },
  78. },
  79. getNameDescriptionTableColumn({
  80. onManager: this.onManager,
  81. field: 'hostname',
  82. hiddenField: 'name',
  83. title: this.$t('common_388'),
  84. label: this.$t('common_388'),
  85. showDesc: false,
  86. resource: 'servers',
  87. formRules: [{ required: true, message: this.$t('common.tips.input', [this.$t('common_388')]) }],
  88. }),
  89. getOsArch(),
  90. getUserTagColumn({
  91. onManager: this.onManager,
  92. resource: 'server',
  93. columns: () => this.serverColumns,
  94. tipName: this.$t('dictionary.server'),
  95. editCheck: (row) => row.hypervisor !== 'bingocloud',
  96. }),
  97. // getExtTagColumn({ onManager: this.onManager, resource: 'server', columns: () => this.serverColumns, tipName: this.$t('dictionary.server') }),
  98. getServerMonitorAgentInstallStatus(),
  99. {
  100. field: 'alert_data',
  101. title: (h) => [
  102. <span style="margin-right:5px">{this.$t('compute.alert_status')}</span>,
  103. <help-tooltip name="alertDataTimeRange" />,
  104. ],
  105. slots: {
  106. default: () => {
  107. const state = this.alertData?.alert_state
  108. if (state) {
  109. return [<status status={state} statusModule="monitorresources" />]
  110. }
  111. return '-'
  112. },
  113. },
  114. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.alert_data'),
  115. },
  116. {
  117. field: 'keypair',
  118. hiddenField: 'password',
  119. title: this.$t('compute.text_33'),
  120. },
  121. getBrandTableColumn(),
  122. {
  123. field: 'billing_type',
  124. title: this.$t('table.title.bill_type'),
  125. showOverflow: 'ellipsis',
  126. slots: {
  127. default: ({ row }, h) => {
  128. const ret = []
  129. if (row.billing_type === 'postpaid') {
  130. ret.push(<div style={{ color: '#0A1F44' }}>{this.$t('billingType.postpaid')}</div>)
  131. } else if (row.billing_type === 'prepaid') {
  132. ret.push(<div style={{ color: '#0A1F44' }}>{this.$t('billingType.prepaid')}({row.auto_renew ? this.$t('compute.text_1233') : this.$t('compute.manual_renewal')})</div>)
  133. }
  134. if (row.expired_at) {
  135. const dateArr = this.$moment(row.expired_at).fromNow().split(' ')
  136. const date = dateArr.join(' ')
  137. const seconds = this.$moment(row.expired_at).diff(new Date()) / 1000
  138. const textColor = seconds / 24 / 60 / 60 < 7 ? '#DD2727' : '#53627C'
  139. const text = seconds < 0 ? this.$t('common_296') : this.$t('common_297', [date])
  140. ret.push(<div style={{ color: textColor }}>{text}</div>)
  141. }
  142. return ret
  143. },
  144. },
  145. },
  146. {
  147. field: 'password',
  148. title: this.$t('table.title.init_keypair'),
  149. minWidth: 50,
  150. slots: {
  151. default: ({ row }) => {
  152. return [<PasswordFetcher serverId={row.id} resourceType='servers' />]
  153. },
  154. },
  155. },
  156. ],
  157. imageExist: false,
  158. cmOptions: {
  159. tabSize: 2,
  160. styleActiveLine: true,
  161. lineNumbers: true,
  162. lineWrapping: true,
  163. line: true,
  164. theme: 'material',
  165. mode: 'text/x-yaml',
  166. readOnly: true,
  167. },
  168. cmdline: '',
  169. showCmdline: false,
  170. }
  171. },
  172. computed: {
  173. isOpenStack () {
  174. const brand = this.data.brand
  175. return brand === BRAND_MAP.OpenStack.brand
  176. },
  177. isKvm () {
  178. const { brand } = this.data
  179. return brand === BRAND_MAP.OneCloud.brand
  180. },
  181. diskInfos () {
  182. const disksInfo = this.data.disks_info
  183. if (!disksInfo) return {}
  184. const dataDisk = {}
  185. const sysDisk = {}
  186. let image = '-'
  187. let imageId
  188. const sysDisks = disksInfo.filter(v => v.disk_type === 'sys')
  189. const dataDisks = disksInfo.filter(v => v.disk_type === 'data')
  190. if (sysDisks && sysDisks.length > 0) {
  191. const sysKey = sysDisks[0].storage_type
  192. image = sysDisks[0].image || '-'
  193. imageId = sysDisks[0].image_id
  194. sysDisk[sysKey] = this._dealSize(sysDisks)
  195. if (sysDisks[0].auto_reset) {
  196. sysDisk.auto_reset = true
  197. }
  198. }
  199. if (dataDisks && dataDisks.length > 0) {
  200. for (const k in ALL_STORAGE) {
  201. const e = ALL_STORAGE[k]
  202. let sameType = dataDisks.filter(v => v.storage_type === e.value)
  203. if (this.isOpenStack) {
  204. sameType = dataDisks.filter(v => v.storage_type.includes(e.value))
  205. }
  206. if (sameType && sameType.length) {
  207. dataDisk[k] = this._dealSize(sameType)
  208. }
  209. }
  210. if (dataDisks.some(v => v.auto_reset)) {
  211. dataDisk.auto_reset_some = true
  212. }
  213. if (dataDisks.every(v => v.auto_reset)) {
  214. dataDisk.auto_reset_some = false
  215. dataDisk.auto_reset = true
  216. }
  217. }
  218. if (this.data.cdrom && dataDisks.length > 0) {
  219. image = dataDisks[0].image
  220. imageId = dataDisks[0].image_id
  221. }
  222. return {
  223. sysDisk: this._diskStringify(sysDisk),
  224. dataDisk: this._diskStringify(dataDisk),
  225. image,
  226. imageId,
  227. }
  228. },
  229. extraInfo () {
  230. const backupInfo = []
  231. if (this.data.backup_host_name) {
  232. backupInfo.push({
  233. title: this.$t('compute.backup_setting'),
  234. items: [
  235. {
  236. field: 'backup_host_name',
  237. title: this.$t('compute.text_1163'),
  238. slots: {
  239. default: ({ row }) => {
  240. if (!row.backup_host_name) return '-'
  241. return [
  242. <side-page-trigger permission='hosts_get' name='HostSidePage' id={row.backup_host_id} vm={this}>{row.backup_host_name}</side-page-trigger>,
  243. ]
  244. },
  245. },
  246. hidden: () => this.$store.getters.isProjectMode,
  247. },
  248. getStatusTableColumn({
  249. field: 'backup_host_status',
  250. title: this.$t('compute.backup_host_status'),
  251. statusModule: 'host',
  252. hidden: () => this.$store.getters.isProjectMode,
  253. }),
  254. {
  255. field: 'backup_guest_status',
  256. title: this.$t('compute.backup_status'),
  257. slots: {
  258. default: ({ row }) => {
  259. return [
  260. <div class='d-flex'>
  261. <div class='text-truncate'>
  262. <status status={row.backup_guest_status} statusModule={'server'} />
  263. </div>
  264. <div>
  265. <a-button type='link' style="height: 14px" disabled={row.backup_guest_status !== 'ready'} onClick={this.startBackup}><icon type='start' style="transform:translateX(4px)" />{this.$t('compute.start_backup')}</a-button>
  266. </div>
  267. </div>,
  268. ]
  269. },
  270. },
  271. },
  272. {
  273. field: 'backup_sync_status',
  274. title: this.$t('compute.backup_sync_status'),
  275. slots: {
  276. default: ({ row }) => {
  277. return [
  278. <div class='d-flex'>
  279. <div class='text-truncate'>
  280. <status status={row.backup_guest_sync_status} statusModule={'backup_sync'} />
  281. </div>
  282. <div>
  283. <a-button type='link' style="height: 14px" disabled={row.backup_guest_sync_status !== 'ready'} onClick={this.switchBackup}><icon type='switch' style="transform:translateX(4px)" />{this.$t('compute.switch_backup')}</a-button>
  284. </div>
  285. </div>,
  286. ]
  287. },
  288. },
  289. },
  290. ],
  291. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.os_arch'),
  292. })
  293. }
  294. const infos = [
  295. {
  296. title: this.$t('compute.text_368'),
  297. items: [
  298. {
  299. field: 'os_type',
  300. title: this.$t('compute.text_267'),
  301. formatter: ({ row }) => {
  302. const distribution = (row.metadata && row.metadata.os_distribution) ? row.metadata.os_distribution : row.os_type
  303. const { os_version: version = '' } = row.metadata || {}
  304. return distribution + ' ' + (version === '-' ? '' : version)
  305. },
  306. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.os_type'),
  307. },
  308. getIpsTableColumn({ field: 'ip', title: 'IP', vm: this, hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.ips') }),
  309. {
  310. field: 'sub_ips',
  311. title: this.$t('compute.sub_ips.title'),
  312. slots: {
  313. default: ({ row }, h) => {
  314. if (!row.sub_ips) {
  315. return '-'
  316. }
  317. const ret = []
  318. for (var i = 0; i < row.sub_ips.length; i++) {
  319. ret.push(<list-body-cell-wrap copy field='ip' row={{ ip: row.sub_ips[i] }} />)
  320. }
  321. return ret
  322. },
  323. },
  324. hidden: (row) => this.$isScopedPolicyMenuHidden('server_hidden_columns.ips'),
  325. },
  326. getCopyWithContentTableColumn({
  327. field: 'macs',
  328. title: 'MAC',
  329. hideField: true,
  330. slotCallback: row => {
  331. return row.macs || '-'
  332. },
  333. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.macs'),
  334. }),
  335. getCopyWithContentTableColumn({
  336. field: 'image',
  337. title: this.$t('compute.text_97'),
  338. hideField: true,
  339. message: this.diskInfos.image,
  340. customEdit: hasPermission({ key: 'server_perform_rebuild_root' }) && this.data.status === 'ready',
  341. customEditCallback: (row) => {
  342. this.createDialog('VmRebuildRootDialog', {
  343. data: [row],
  344. columns: this.columns,
  345. onManager: this.onManager,
  346. })
  347. },
  348. slotCallback: (row) => {
  349. if (!this.diskInfos.image || this.diskInfos.image === '-') return '-'
  350. if (!this.imageExist) return this.diskInfos.image
  351. return [
  352. <side-page-trigger permission='images_get' name='SystemImageSidePage' id={this.diskInfos.imageId} vm={this}>{this.diskInfos.image}</side-page-trigger>,
  353. ]
  354. },
  355. }),
  356. {
  357. field: 'host',
  358. title: this.$t('compute.text_111'),
  359. sortable: true,
  360. showOverflow: 'ellipsis',
  361. minWidth: 100,
  362. slots: {
  363. default: ({ row }) => {
  364. if (findPlatform(row.hypervisor, 'hypervisor') === SERVER_TYPE.public || row.hypervisor === HYPERVISORS_MAP.hcso.hypervisor || row.hypervisor === HYPERVISORS_MAP.hcs.hypervisor) {
  365. return '-'
  366. }
  367. const text = row.host || '-'
  368. return [
  369. <list-body-cell-wrap copy hideField={true} field='host' row={row} message={text}>
  370. <side-page-trigger permission='hosts_get' name='HostSidePage' id={row.host_id} vm={this}>{row.host}</side-page-trigger>
  371. </list-body-cell-wrap>,
  372. ]
  373. },
  374. },
  375. hidden: () => this.$store.getters.isProjectMode || this.$isScopedPolicyMenuHidden('server_hidden_columns.host'),
  376. },
  377. {
  378. field: 'secgroups',
  379. title: this.$t('compute.text_105'),
  380. slots: {
  381. default: ({ row }) => {
  382. if (!row.secgroups) return '-'
  383. return row.secgroups.map((item) => {
  384. return <list-body-cell-wrap copy hideField={true} field='name' row={item} message={item.name}>
  385. <side-page-trigger permission='secgroups_get' name='SecGroupSidePage' id={item.id} vm={this}>{item.name}</side-page-trigger>
  386. </list-body-cell-wrap>
  387. })
  388. },
  389. },
  390. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.secgroups'),
  391. },
  392. {
  393. field: 'network_secgroups',
  394. title: this.$t('compute.nic_secgroups'),
  395. slots: {
  396. default: ({ row }) => {
  397. if (!row.network_secgroups) return '-'
  398. const secgroups = []
  399. row.network_secgroups.forEach((item) => {
  400. item.secgroups.forEach((secgroup) => {
  401. secgroups.push({ ...secgroup, network_index: item.network_index })
  402. })
  403. })
  404. return secgroups.map((item) => {
  405. return <list-body-cell-wrap copy hideField={true} field='name' row={item} message={item.name}>
  406. {this.$t('compute.text_375')} {item.network_index}:
  407. <side-page-trigger permission='secgroups_get' name='SecGroupSidePage' id={item.id} vm={this}>{item.name}</side-page-trigger>
  408. </list-body-cell-wrap>
  409. })
  410. },
  411. },
  412. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.secgroups') || !this.isKvm,
  413. },
  414. getCopyWithContentTableColumn({
  415. field: 'vpc',
  416. title: 'VPC',
  417. hideField: true,
  418. slotCallback: row => {
  419. if (!row.vpc) return '-'
  420. return [
  421. <side-page-trigger permission='vpcs_get' name='VpcSidePage' id={row.vpc_id} vm={this}>{row.vpc}</side-page-trigger>,
  422. ]
  423. },
  424. hidden: () => this.$store.getters.isProjectMode || this.$isScopedPolicyMenuHidden('server_hidden_columns.vpc'),
  425. }),
  426. {
  427. field: 'vcpu_count',
  428. title: 'CPU',
  429. formatter: ({ row }) => {
  430. if (row.hypervisor === HYPERVISORS_MAP.esxi.key && row.cpu_sockets) {
  431. return `CPU: ${row.vcpu_count}${this.$t('compute.text_167')}(${this.$t('compute.slots_number')}:${row.cpu_sockets})`
  432. }
  433. return row.vcpu_count + this.$t('compute.text_167')
  434. },
  435. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.vcpu_count'),
  436. },
  437. {
  438. field: 'vmem_size',
  439. title: this.$t('compute.text_369'),
  440. formatter: ({ row }) => {
  441. return (row.vmem_size / 1024) + 'GB'
  442. },
  443. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.vmem_size'),
  444. },
  445. {
  446. field: 'cpu_numa_pin',
  447. title: this.$t('compute.text_609'),
  448. formatter: ({ row }) => {
  449. return formatCpuNumaPin(row)
  450. },
  451. },
  452. {
  453. field: 'sysDisk',
  454. title: this.$t('compute.text_49'),
  455. formatter: ({ row }) => {
  456. if (!this.diskInfos.sysDisk) return '-'
  457. return <a onClick={() => this.$emit('tab-change', 'disk-list-for-vm-instance-sidepage')}>{this.diskInfos.sysDisk}</a>
  458. },
  459. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.disk'),
  460. },
  461. {
  462. field: 'dataDisk',
  463. title: this.$t('compute.text_50'),
  464. formatter: ({ row }) => {
  465. if (!this.diskInfos.dataDisk) return '-'
  466. return <a onClick={() => this.$emit('tab-change', 'disk-list-for-vm-instance-sidepage')}>{this.diskInfos.dataDisk}</a>
  467. },
  468. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.disk'),
  469. },
  470. (() => {
  471. function getCdromInfo (row) {
  472. if (!row.cdrom) return '-'
  473. let cdrom = `${row.cdrom}`
  474. if (Array.isArray(row.cdrom) && row.cdrom.length > 0) {
  475. cdrom = row.cdrom[0].detail
  476. }
  477. const idx = cdrom.indexOf('(')
  478. return cdrom.substring(0, idx)
  479. }
  480. return getCopyWithContentTableColumn({
  481. field: 'cdrom',
  482. title: 'ISO',
  483. hideField: true,
  484. message: getCdromInfo,
  485. slotCallback: row => {
  486. if (!row.cdrom) return '-'
  487. let cdrom = `${row.cdrom}`
  488. if (Array.isArray(row.cdrom) && row.cdrom.length > 0) {
  489. cdrom = row.cdrom[0].detail
  490. }
  491. const idx = cdrom.indexOf('(')
  492. const id = cdrom.substring(idx + 1, cdrom.indexOf('/'))
  493. return [
  494. <side-page-trigger permission='images_get' name='SystemImageSidePage' id={id} vm={this}>{cdrom.substring(0, idx) || '-'}</side-page-trigger>,
  495. ]
  496. },
  497. })
  498. })(),
  499. {
  500. field: 'isolated_devices',
  501. title: this.$t('compute.text_113'),
  502. formatter: ({ row }) => {
  503. if (!row.isolated_devices && !row.gpu_count) return '-'
  504. const gpuArr = row.isolated_devices || []
  505. const obj = {}
  506. const devTypeMap = {}
  507. const ids = {}
  508. gpuArr.forEach(val => {
  509. if (val.dev_type !== 'USB') {
  510. if (!obj[val.model]) {
  511. obj[val.model] = 1
  512. } else {
  513. obj[val.model] += 1
  514. }
  515. ids[val.model] = val.id
  516. devTypeMap[val.model] = val.dev_type
  517. }
  518. })
  519. if (Object.keys(obj).length === 0) {
  520. if (row.gpu_count && row.gpu_model) {
  521. return this.$t('compute.text_370', [row.gpu_count, row.gpu_model])
  522. } else {
  523. return '-'
  524. }
  525. }
  526. return Object.keys(obj).map(k => {
  527. const gpuLabel = `${GPU_DEV_TYPE_OPTION_MAP[devTypeMap[k]]?.label || devTypeMap[k]}-${k}`
  528. 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>
  529. })
  530. },
  531. },
  532. {
  533. field: 'isolated_devices',
  534. title: 'USB',
  535. formatter: ({ row }) => {
  536. if (!row.isolated_devices) return '-'
  537. const gpuArr = row.isolated_devices
  538. const obj = {}
  539. const ids = {}
  540. gpuArr.forEach(val => {
  541. if (val.dev_type === 'USB') {
  542. if (!obj[val.model]) {
  543. obj[val.model] = 1
  544. } else {
  545. obj[val.model] += 1
  546. }
  547. ids[val.model] = val.id
  548. }
  549. })
  550. if (Object.keys(obj).length === 0) {
  551. return '-'
  552. }
  553. return Object.keys(obj).map(k => {
  554. return <side-page-trigger permission='isolated_devices_get' name='GpuSidePage' id={ids[k]} vm={this}>{this.$t('compute.text_370', [obj[k], k])}</side-page-trigger>
  555. })
  556. },
  557. },
  558. {
  559. field: 'is_daemon',
  560. title: () => {
  561. return [
  562. this.$t('compute.text_494'),
  563. <help-tooltip class="ml-1" text={this.$t('compute.daemon.tooltip')} />,
  564. ]
  565. },
  566. formatter: ({ row }) => {
  567. if (row.is_daemon) {
  568. return this.$t('table.title.on')
  569. } else {
  570. return this.$t('table.title.off')
  571. }
  572. },
  573. },
  574. {
  575. field: 'bandwidth',
  576. title: this.$t('compute.max_bandwidth'),
  577. slots: {
  578. default: ({ row }) => {
  579. return row.internet_max_bandwidth_out ? sizestrWithUnit(row.internet_max_bandwidth_out, 'M', 1024) + '/s' : '-'
  580. },
  581. },
  582. },
  583. {
  584. field: 'monitor_url',
  585. title: this.$t('compute.monitor_url.prompt'),
  586. formatter: ({ row }) => {
  587. return row.monitor_url
  588. },
  589. },
  590. {
  591. field: 'bios',
  592. title: this.$t('compute.bios'),
  593. formatter: ({ row }) => {
  594. return row.bios || 'BIOS'
  595. },
  596. },
  597. ],
  598. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.os_arch'),
  599. },
  600. {
  601. title: this.$t('compute.title.encryption'),
  602. items: [
  603. {
  604. field: 'encrypt_key_id',
  605. title: this.$t('compute.title.encryption_key'),
  606. formatter: ({ callValue, row }) => {
  607. if (row.encrypt_key_id) {
  608. if (row.encrypt_key && row.encrypt_alg) {
  609. return row.encrypt_key + ' (' + row.encrypt_key_id + ')'
  610. } else {
  611. return row.encrypt_key_id
  612. }
  613. } else {
  614. return this.$t('compute.no_encryption')
  615. }
  616. },
  617. },
  618. {
  619. field: 'encrypt_alg',
  620. title: this.$t('compute.title.encrypt_alg'),
  621. formatter: ({ callValue, row }) => {
  622. if (row.encrypt_alg) {
  623. return row.encrypt_alg.toUpperCase()
  624. } else {
  625. return '-'
  626. }
  627. },
  628. },
  629. {
  630. field: 'encrypt_key_user',
  631. title: this.$t('compute.title.encrypt_key_user'),
  632. formatter: ({ callValue, row }) => {
  633. if (row.encrypt_key_user) {
  634. return row.encrypt_key_user + ' / ' + row.encrypt_key_user_domain
  635. } else {
  636. return '-'
  637. }
  638. },
  639. },
  640. ],
  641. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.password'),
  642. },
  643. ...backupInfo,
  644. {
  645. title: this.$t('compute.text_371'),
  646. items: [
  647. getSwitchTableColumn({
  648. field: 'disable_delete',
  649. title: this.$t('common.text00076'),
  650. change: val => {
  651. this.onManager('update', {
  652. id: this.data.id,
  653. managerArgs: {
  654. data: { disable_delete: val },
  655. },
  656. })
  657. },
  658. }),
  659. ],
  660. hidden: () => this.$isScopedPolicyMenuHidden('server_hidden_columns.perform_action'),
  661. },
  662. ]
  663. if (this.isKvm && this.cmdline) {
  664. infos[infos.length - 1].items.unshift({
  665. field: 'metadata',
  666. title: this.$t('compute.qemu_cmdline'),
  667. slots: {
  668. default: ({ row }, h) => {
  669. return [
  670. <a-button type="link" class="mb-2" style="height: 21px;padding:0" onclick={this.viewCmdline}>{this.showCmdline ? this.$t('table.title.off') : this.$t('compute.text_958')}</a-button>,
  671. <code-mirror style={this.showCmdline ? {} : { visibility: 'hidden', height: '0px' }} value={this.cmdline} view-height="300px" options={this.cmOptions} />]
  672. },
  673. },
  674. })
  675. }
  676. return infos
  677. },
  678. },
  679. watch: {
  680. diskInfos: {
  681. handler: 'checkImage',
  682. immediate: true,
  683. },
  684. 'data.agent_status': {
  685. handler: function (val, oldval) {
  686. if (oldval === 'applying') {
  687. if (val === 'succeed' || val === 'failed') {
  688. this.baseInfo[6] = getServerMonitorAgentInstallStatus()
  689. }
  690. }
  691. },
  692. immediate: true,
  693. deep: true,
  694. },
  695. 'data.id': {
  696. handler () {
  697. this.fetchAlertData()
  698. },
  699. immediate: true,
  700. },
  701. },
  702. created () {
  703. this.initQemuInfo()
  704. },
  705. methods: {
  706. async fetchAlertData () {
  707. this.alertData = null
  708. const id = this.data?.id
  709. if (!id) return
  710. if (this.$isScopedPolicyMenuHidden('server_hidden_columns.alert_data')) return
  711. try {
  712. const monitorManager = new this.$Manager('unifiedmonitors/resource-metrics', 'v1')
  713. const res = await monitorManager.create({
  714. data: {
  715. res_ids: [id],
  716. res_type: 'guest',
  717. },
  718. })
  719. const metrics = res.data?.resource_metrics?.[id]
  720. this.alertData = metrics || null
  721. } catch (e) {
  722. this.alertData = null
  723. }
  724. },
  725. _diskStringify (diskObj) {
  726. let str = ''
  727. const storageArr = Object.values(ALL_STORAGE)
  728. for (const k in diskObj) {
  729. if (k === 'auto_reset' || k === 'auto_reset_some') continue
  730. const num = diskObj[k]
  731. const disk = storageArr.find(v => v.value === k)
  732. if (disk) {
  733. str += `、${sizestr(num, 'M', 1024)}(${disk.label}${diskObj.auto_reset_some ? ' ' + this.$t('compute.shutdown_auto_reset_some') : (diskObj.auto_reset ? ' ' + this.$t('compute.shutdown_auto_reset') : '')})`
  734. } else {
  735. str += `、${sizestr(num, 'M', 1024)}(${k}${diskObj.auto_reset_some ? ' ' + this.$t('compute.shutdown_auto_reset_some') : (diskObj.auto_reset ? ' ' + this.$t('compute.shutdown_auto_reset') : '')})`
  736. }
  737. }
  738. return str.slice(1)
  739. },
  740. _dealSize (sameType) {
  741. const sameType1 = sameType.map(v => {
  742. const size = +v.size
  743. return size
  744. })
  745. return sameType1.reduce((a, b) => {
  746. return a + b
  747. })
  748. },
  749. checkImage () {
  750. new this.$Manager('images', 'v1')
  751. .list({ params: { id: this.diskInfos.imageId, scope: this.$store.getters.scope } })
  752. .then(({ data }) => {
  753. this.imageExist = !R.isEmpty(data.data)
  754. })
  755. .catch(() => {
  756. this.imageExist = false
  757. })
  758. },
  759. viewCmdline () {
  760. this.showCmdline = !this.showCmdline
  761. },
  762. async initQemuInfo () {
  763. try {
  764. if (this.isKvm) {
  765. const res = await this.onManager('getSpecific', {
  766. id: this.data.id,
  767. managerArgs: {
  768. spec: 'qemu-info',
  769. },
  770. })
  771. const { cmdline = '' } = res.data
  772. this.cmdline = cmdline
  773. }
  774. } catch (err) {
  775. console.error(err)
  776. }
  777. },
  778. switchBackup () {
  779. this.createDialog('VmSwitchBackup2Dialog', {
  780. data: [this.data],
  781. onManager: this.onManager,
  782. columns: this.serverColumns,
  783. })
  784. },
  785. startBackup () {
  786. this.createDialog('VmStartBackupDialog', {
  787. data: [this.data],
  788. onManager: this.onManager,
  789. columns: this.serverColumns,
  790. })
  791. },
  792. },
  793. }
  794. </script>