RebuildRoot.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <template>
  2. <base-dialog @cancel="cancelDialog" :width="1000">
  3. <div slot="header">{{action}}</div>
  4. <div slot="body">
  5. <a-alert type="warning" class="mb-2" v-if="tips.length">
  6. <template v-slot:message>
  7. <div class="messages-list">
  8. <p v-for="(item,index) in tips" :key="index" class="mb-1">{{ item }}</p>
  9. </div>
  10. </template>
  11. </a-alert>
  12. <template v-if="isShowColumns">
  13. <dialog-selected-tips :name="$t('dictionary.server')" :count="params.data.length" :action="action" />
  14. <dialog-table :data="params.data" :columns="params.columns.filter(item => ['name', 'os_dist', 'instance_type'].includes(item.field))" />
  15. </template>
  16. <a-form
  17. v-bind="formItemLayout"
  18. :form="form.fc">
  19. <a-form-item v-show="!imgHidden" :label="$t('compute.text_267')">
  20. <div slot="help">
  21. <div class="help-color">{{$t('compute.text_302')}}</div>
  22. </div>
  23. <os-select
  24. :type="type"
  25. :form="form"
  26. :types="osSelectTypes"
  27. :hypervisor="hypervisor"
  28. :image-params="image"
  29. :ignoreOptions="ignoreImageOptions"
  30. :osType="osType"
  31. :os-arch="osArch"
  32. :cache-image-params="cacheImageParams"
  33. :decorator="decorators.imageOS"
  34. :sys-disk-size="sysDiskSize"
  35. :imageCloudproviderDisabled="true"
  36. :cloudproviderParamsExtra="cloudproviderParamsExtra"
  37. @updateImageMsg="updateImageMsgDebounce"
  38. :imageTypeMap="imageTypeMap"
  39. :edit="true" />
  40. </a-form-item>
  41. <a-form-item v-show="imgHidden" :label="$t('compute.text_267')">
  42. <div>{{ imgHidden.text }}</div>
  43. </a-form-item>
  44. <a-form-item :label="$t('compute.text_308')" v-if="!isZStack" class="mb-0">
  45. <server-password :decorator="decorators.loginConfig" :loginTypes="loginTypes" :form="form" />
  46. </a-form-item>
  47. <a-form-item v-if="isShowAgent" :label="$t('compute.agent.label')" :extra="$t('compute.agent.extra')">
  48. <a-checkbox v-decorator="decorators.deploy_telegraf">{{ $t('compute.agent.install.plugin') }}</a-checkbox>
  49. </a-form-item>
  50. <a-form-item :label="$t('compute.text_494')" :extra="$t('compute.text_1220')">
  51. <a-switch :checkedChildren="$t('compute.text_115')" :unCheckedChildren="$t('compute.text_116')" v-decorator="decorators.autoStart" />
  52. </a-form-item>
  53. </a-form>
  54. </div>
  55. <div slot="footer">
  56. <a-button type="primary" @click="handleConfirm" :loading="loading">{{ $t('dialog.ok') }}</a-button>
  57. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  58. </div>
  59. </base-dialog>
  60. </template>
  61. <script>
  62. import _ from 'lodash'
  63. import { mapGetters } from 'vuex'
  64. import OsSelect from '@Compute/sections/OsSelect'
  65. import ServerPassword from '@Compute/sections/ServerPassword'
  66. import { SERVER_TYPE, LOGIN_TYPES_MAP } from '@Compute/constants'
  67. import DialogMixin from '@/mixins/dialog'
  68. import WindowsMixin from '@/mixins/windows'
  69. import { HYPERVISORS_MAP } from '@/constants'
  70. import { Manager } from '@/utils/manager'
  71. import { IMAGES_TYPE_MAP, HOST_CPU_ARCHS } from '@/constants/compute'
  72. import { isRequired, passwordValidator } from '@/utils/validate'
  73. import { findPlatform } from '@/utils/common/hypervisor'
  74. export default {
  75. name: 'VmRebuildRootDialog',
  76. components: {
  77. OsSelect,
  78. ServerPassword,
  79. },
  80. provide: function () {
  81. return {
  82. form: this.form,
  83. }
  84. },
  85. mixins: [DialogMixin, WindowsMixin],
  86. data () {
  87. return {
  88. loading: false,
  89. action: this.$t('compute.text_357'),
  90. form: {
  91. fc: this.$form.createForm(this, {
  92. onValuesChange: (props, values) => {
  93. Object.keys(values).forEach((key) => {
  94. this.$set(this.form.fd, key, values[key])
  95. })
  96. },
  97. }),
  98. fd: {},
  99. },
  100. ignoreImageOptions: [
  101. IMAGES_TYPE_MAP.iso.key,
  102. IMAGES_TYPE_MAP.host.key,
  103. IMAGES_TYPE_MAP.snapshot.key,
  104. ],
  105. formItemLayout: {
  106. wrapperCol: {
  107. span: 20,
  108. },
  109. labelCol: {
  110. span: 4,
  111. },
  112. },
  113. detailData: {},
  114. firstLoad: true,
  115. cloudproviderParamsExtra: {
  116. scope: this.$store.getters.scope,
  117. filter: `id.equals("${this.params.data[0].manager_id}")`,
  118. },
  119. imageTypeMap: {},
  120. }
  121. },
  122. computed: {
  123. ...mapGetters(['userInfo', 'auth']),
  124. enableMFA () {
  125. return this.userInfo.enable_mfa && this.auth.auth.system_totp_on
  126. },
  127. type () {
  128. const brand = this.params.data[0].brand
  129. return findPlatform(brand)
  130. },
  131. osSelectTypes () {
  132. if (HYPERVISORS_MAP.ctyun.key === this.params.data[0].hypervisor) {
  133. return ['public', 'public_customize']
  134. }
  135. return []
  136. },
  137. imageType () {
  138. if (this.type === SERVER_TYPE.idc && this.hypervisor === HYPERVISORS_MAP.esxi.key) {
  139. return IMAGES_TYPE_MAP.vmware.key
  140. } else if (this.type === SERVER_TYPE.public) {
  141. return IMAGES_TYPE_MAP.public.key
  142. } else if (this.type === SERVER_TYPE.private) {
  143. return IMAGES_TYPE_MAP.private.key
  144. } else {
  145. return IMAGES_TYPE_MAP.standard.key
  146. }
  147. },
  148. hypervisor () {
  149. return this.params.data[0].hypervisor
  150. },
  151. osArch () {
  152. const { instance_type = '', os_arch } = this.params.data[0]
  153. if (instance_type.startsWith('k') || os_arch === HOST_CPU_ARCHS.arm.capabilityKey) {
  154. return HOST_CPU_ARCHS.arm.capabilityKey
  155. }
  156. return ''
  157. },
  158. image () {
  159. const params = {
  160. limit: 0,
  161. details: true,
  162. status: 'active',
  163. os_arch: HOST_CPU_ARCHS.x86.key,
  164. }
  165. if (this.osArch === HOST_CPU_ARCHS.arm.capabilityKey) {
  166. params.os_arch = HOST_CPU_ARCHS.arm.key
  167. }
  168. return params
  169. },
  170. isZStack () {
  171. return this.hypervisor === HYPERVISORS_MAP.zstack.key
  172. },
  173. decorators () {
  174. const validateToImage = (isZStack) => {
  175. return (rule, value, callback) => {
  176. if (isZStack) {
  177. callback()
  178. } else {
  179. isRequired()(rule, value, callback)
  180. }
  181. }
  182. }
  183. return {
  184. imageOS: {
  185. prefer_manager: [
  186. 'prefer_manager',
  187. {
  188. initialValue: _.get(this.params, 'data[0].manager_id') || '',
  189. rules: [
  190. { required: true, message: this.$t('compute.text_149') },
  191. ],
  192. },
  193. ],
  194. os: [
  195. 'os',
  196. {
  197. initialValue: '',
  198. rules: [
  199. { required: !this.isZStack, message: this.$t('compute.text_153') },
  200. ],
  201. },
  202. ],
  203. image: [
  204. 'image',
  205. {
  206. initialValue: { key: '', label: '' },
  207. rules: [
  208. { validator: validateToImage(this.isZStack), message: this.$t('compute.text_214') },
  209. ],
  210. },
  211. ],
  212. imageType: [
  213. 'imageType',
  214. {
  215. initialValue: this.imageType,
  216. },
  217. ],
  218. },
  219. loginConfig: {
  220. loginType: [
  221. 'loginType',
  222. {
  223. initialValue: 'random',
  224. },
  225. ],
  226. keypair: [
  227. 'loginKeypair',
  228. {
  229. initialValue: undefined,
  230. rules: [
  231. { required: true, message: this.$t('compute.text_203') },
  232. ],
  233. },
  234. ],
  235. password: [
  236. 'loginPassword',
  237. {
  238. initialValue: '',
  239. rules: [
  240. { required: true, message: this.$t('compute.text_204') },
  241. { validator: passwordValidator, trigger: 'blur' },
  242. ],
  243. },
  244. ],
  245. },
  246. autoStart: [
  247. 'autoStart',
  248. {
  249. valuePropName: 'checked',
  250. initialValue: true,
  251. },
  252. ],
  253. deploy_telegraf: [
  254. 'deploy_telegraf',
  255. {
  256. valuePropName: 'checked',
  257. initialValue: true,
  258. },
  259. ],
  260. }
  261. },
  262. cacheImageParams () {
  263. const params = {
  264. details: false,
  265. order_by: 'ref_count',
  266. order: 'desc',
  267. zone: this.params.data[0].zone_id,
  268. }
  269. if (this.hypervisor === HYPERVISORS_MAP.esxi.key) {
  270. params.image_type = 'system'
  271. params.manager = this.params.data[0].manager_id
  272. }
  273. return params
  274. },
  275. isFreezeImg () {
  276. // if (this.hypervisor) {
  277. // return [HYPERVISORS_MAP.openstack.key].includes(this.hypervisor.toLowerCase())
  278. // }
  279. return false
  280. },
  281. imgHidden () {
  282. if (this.hypervisor) {
  283. if (this.isFreezeImg) {
  284. const pdata = this.detailData[0]
  285. if (pdata && pdata.disks_info && pdata.disks_info[0] && pdata.disks_info[0].image) {
  286. return {
  287. text: pdata.disks_info[0].image,
  288. isImage: true,
  289. }
  290. } else {
  291. return {
  292. text: this.$t('compute.text_1221'),
  293. isImage: false,
  294. }
  295. }
  296. } else {
  297. return false
  298. }
  299. }
  300. return false
  301. },
  302. tips () {
  303. if (this.hypervisor === HYPERVISORS_MAP.openstack.key) {
  304. return [this.$t('compute.text_1222')]
  305. }
  306. if (this.hypervisor === HYPERVISORS_MAP.zstack.key) {
  307. return [this.$t('compute.text_1223')]
  308. }
  309. if (this.params.data.every(item => item.hypervisor === HYPERVISORS_MAP.kvm.key)) {
  310. return [
  311. this.$t('compute.kvm_rebuild_tip1'),
  312. this.$t('compute.kvm_rebuild_tip2'),
  313. this.$t('compute.kvm_rebuild_tip3'),
  314. this.$t('compute.kvm_rebuild_tip4'),
  315. this.$t('compute.kvm_rebuild_tip5'),
  316. this.$t('compute.kvm_rebuild_tip6'),
  317. ]
  318. }
  319. if (this.params.data.length === 1) {
  320. return [this.$t('compute.text_1224')]
  321. }
  322. return ''
  323. },
  324. osType () {
  325. return this.params.data[0].os_type
  326. },
  327. loginTypes () {
  328. const loginTypes = { ...LOGIN_TYPES_MAP }
  329. const hypervisor = this.hypervisor
  330. if (HYPERVISORS_MAP.ucloud.key === hypervisor) {
  331. delete loginTypes[LOGIN_TYPES_MAP.image.key]
  332. delete loginTypes[LOGIN_TYPES_MAP.keypair.key]
  333. }
  334. if (HYPERVISORS_MAP.aws.key === hypervisor) {
  335. delete loginTypes[LOGIN_TYPES_MAP.random.key]
  336. delete loginTypes[LOGIN_TYPES_MAP.password.key]
  337. }
  338. if ([HYPERVISORS_MAP.azure.key, HYPERVISORS_MAP.huawei.key].includes(hypervisor)) {
  339. delete loginTypes[LOGIN_TYPES_MAP.image.key]
  340. }
  341. if (HYPERVISORS_MAP.ctyun.key === hypervisor) {
  342. delete loginTypes[LOGIN_TYPES_MAP.keypair.key]
  343. delete loginTypes[LOGIN_TYPES_MAP.image.key]
  344. }
  345. if (HYPERVISORS_MAP.google.key === hypervisor) {
  346. delete loginTypes[LOGIN_TYPES_MAP.image.key]
  347. }
  348. if (HYPERVISORS_MAP.qcloud.key === hypervisor) {
  349. delete loginTypes[LOGIN_TYPES_MAP.image.key]
  350. }
  351. if (this.osType === 'Windows') {
  352. // 以下平台在选择 windows 镜像时禁用关联密钥
  353. delete loginTypes[LOGIN_TYPES_MAP.keypair.key]
  354. }
  355. return Object.keys(loginTypes)
  356. },
  357. sysDiskSize () {
  358. if (this.detailData.length === 1) {
  359. const diskInfos = this.detailData[0].disks_info
  360. const sysDisk = diskInfos.find((item) => { return item.disk_type === 'sys' || item.index === 0 })
  361. if (sysDisk) {
  362. return sysDisk.size
  363. }
  364. }
  365. return 0
  366. },
  367. isShowColumns () {
  368. return this.params.columns?.length > 0
  369. },
  370. isShowAgent () {
  371. const { os } = this.form.fd
  372. if (![HYPERVISORS_MAP.kvm.key, HYPERVISORS_MAP.esxi.key].includes(this.hypervisor)) return false
  373. if (os === 'Windows') {
  374. return this.osArch !== HOST_CPU_ARCHS.arm.capabilityKey
  375. }
  376. return true
  377. },
  378. },
  379. // watch: {
  380. // type: {
  381. // handler (val) {
  382. // if (val === SERVER_TYPE.public) {
  383. // this.$nextTick(() => {
  384. // this.form.fc.setFieldsValue({ 'imageType': IMAGES_TYPE_MAP.public.key })
  385. // })
  386. // }
  387. // },
  388. // immediate: true,
  389. // },
  390. // },
  391. created () {
  392. this.serversManager = new Manager('servers', 'v2')
  393. this.fetchData()
  394. this.updateImageMsgDebounce = _.debounce(this.updateImageMsg, 600)
  395. },
  396. methods: {
  397. async doRebuildRootSubmit (data) {
  398. const { autoStart, image, loginType, loginKeypair, loginPassword, deploy_telegraf } = data
  399. const ids = this.params.data.map(item => item.id)
  400. const params = {
  401. reset_password: true,
  402. auto_start: autoStart,
  403. image_id: image.key,
  404. }
  405. // if (this.isZStack) {
  406. // params.image_id = this.detailData[0].disks_info[0].image_id
  407. // }
  408. if (loginType === 'keypair') {
  409. params.keypair = loginKeypair
  410. params.reset_password = false
  411. } else if (loginType === 'image') {
  412. params.reset_password = false // 如果登录方式为创建后设置, 则增加参数 reset_password = false
  413. } else if (loginType === 'password') {
  414. params.password = loginPassword
  415. }
  416. if (this.isZStack) {
  417. params.reset_password = false
  418. }
  419. // 安装监控 agent
  420. if (this.isShowAgent && deploy_telegraf) {
  421. params.deploy_telegraf = deploy_telegraf
  422. }
  423. return this.params.onManager('batchPerformAction', {
  424. steadyStatus: ['running', 'ready'],
  425. id: ids,
  426. managerArgs: {
  427. action: 'rebuild-root',
  428. data: params,
  429. },
  430. })
  431. },
  432. async handleConfirm () {
  433. this.loading = true
  434. try {
  435. const values = await this.form.fc.validateFields()
  436. const success = async () => {
  437. this.loading = true
  438. await this.doRebuildRootSubmit(values)
  439. this.loading = false
  440. this.cancelDialog()
  441. }
  442. if (this.enableMFA) {
  443. this.createDialog('SecretVertifyDialog', {
  444. action: this.$t('table.title.mfa_validate'),
  445. success,
  446. })
  447. } else {
  448. success()
  449. }
  450. this.loading = false
  451. } catch (error) {
  452. this.loading = false
  453. }
  454. },
  455. fetchData () {
  456. const ids = []
  457. this.params.data.forEach((item) => {
  458. ids.push(item.id)
  459. })
  460. this.fetchdone = false
  461. this.serversManager.batchGet({ id: ids })
  462. .then((res) => {
  463. this.fetchdone = true
  464. this.detailData = res.data.data
  465. this.$set(this.form.fd, 'vmem', _.get(res.data, 'data[0].vmem_size'))
  466. this.setImageTypeMap(this.detailData)
  467. })
  468. .catch(() => {
  469. this.fetchdone = true
  470. })
  471. },
  472. setImageTypeMap (serverlist) {
  473. const firstManagerId = serverlist[0].manager_id
  474. if (serverlist.some(val => val.manager_id !== firstManagerId)) { // 说明多台主机是不在同一个订阅下
  475. this.imageTypeMap = {
  476. public_customize: {
  477. ...IMAGES_TYPE_MAP.public_customize,
  478. disabled: true,
  479. tooltip: this.$t('compute.text_1225', [this.$t('dictionary.server'), IMAGES_TYPE_MAP.public_customize.label]),
  480. },
  481. }
  482. }
  483. },
  484. updateImageMsg (imageMsg) {
  485. if (this.firstLoad) {
  486. const detailData = this.detailData || []
  487. const diskInfo = (detailData[0] && detailData[0].disks_info) || []
  488. if (diskInfo.length > 0) {
  489. const image = diskInfo[0] || {}
  490. if (imageMsg.name !== image.image) return
  491. if (!image.image_id) return
  492. this.$nextTick(() => {
  493. this.form.fc.setFieldsValue({
  494. image: {
  495. key: image.image_id,
  496. label: image.image || '',
  497. },
  498. })
  499. })
  500. }
  501. this.firstLoad = false
  502. }
  503. },
  504. },
  505. }
  506. </script>