SetSecgroup.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <template>
  2. <base-dialog @cancel="cancelDialog">
  3. <div slot="header">{{$t('compute.text_1116')}}</div>
  4. <div slot="body">
  5. <a-alert class="mb-2" type="warning" :message="message" />
  6. <dialog-selected-tips :name="$t('dictionary.server')" :count="params.data.length" :action="$t('compute.text_1116')" />
  7. <dialog-table :data="params.data" :columns="params.columns.slice(0, 3)" />
  8. <loader loading v-if="!(bindedSecgroupsLoaded && secgroupsInitLoaded)" />
  9. <a-form :form="form.fc" hideRequiredMark v-show="bindedSecgroupsLoaded && secgroupsInitLoaded" v-bind="formItemLayout">
  10. <a-form-item v-if="params.type === 'vminstance'" :label="$t('compute.nic')" :extra="$t('compute.nic_empty_secgroups_tip')">
  11. <base-select
  12. class="w-100"
  13. v-decorator="decorators.nic"
  14. :options="nicOptions"
  15. :select-props="{ allowClear: true, placeholder: $t('compute.text_193') }"
  16. @change="handleNicChange" />
  17. </a-form-item>
  18. <a-form-item :label="$t('compute.text_105')">
  19. <div slot="extra">{{$t('compute.text_1242', [max])}}<!-- <help-link :href="href">{{$t('compute.text_189')}}</help-link> -->
  20. <dialog-trigger :vm="params.vm" :extParams="{ tenant, domain }" :name="$t('compute.text_189')" value="CreateSecgroupDialog" resource="secgroups" @success="successCallback" />
  21. </div>
  22. <base-select
  23. ref="secgroupRef"
  24. class="w-100"
  25. remote
  26. show-sync
  27. v-decorator="decorators.secgroups"
  28. resource="secgroups"
  29. :resList.sync="secgroupOptions"
  30. :mapper="mapperSecgroups"
  31. :params="secgroupsParams"
  32. :init-loaded.sync="secgroupsInitLoaded"
  33. :select-props="{ allowClear: true, placeholder: $t('compute.text_190'), mode: 'multiple' }" />
  34. </a-form-item>
  35. </a-form>
  36. </div>
  37. <div slot="footer">
  38. <a-button type="primary" @click="handleConfirm" :loading="loading">{{ $t('dialog.ok') }}</a-button>
  39. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  40. </div>
  41. </base-dialog>
  42. </template>
  43. <script>
  44. import { mapGetters } from 'vuex'
  45. import * as R from 'ramda'
  46. import DialogMixin from '@/mixins/dialog'
  47. import WindowsMixin from '@/mixins/windows'
  48. import { HYPERVISORS_MAP } from '@/constants'
  49. import { SECGROUP_LIST_FOR_VMINSTANCE_SIDEPAGE_REFRESH } from '@/constants/event-bus'
  50. export default {
  51. name: 'VmSetSecgroupDialog',
  52. mixins: [DialogMixin, WindowsMixin],
  53. data () {
  54. const validateSecgroups = (rule, value, callback) => {
  55. const maxError = this.isBindOne ? this.$t('compute.text_1243') : this.$t('compute.text_1244')
  56. const minError = this.$t('compute.text_192')
  57. const max = this.isBindOne ? 1 : 5
  58. if (value.length > max) {
  59. return callback(maxError)
  60. }
  61. if (this.params.type === 'vminstance' && this.form.fd.nic) {
  62. return callback()
  63. }
  64. if (value.length < 1) {
  65. return callback(minError)
  66. }
  67. return callback()
  68. }
  69. return {
  70. loading: false,
  71. form: {
  72. fc: this.$form.createForm(this, {
  73. onValuesChange: (props, values) => {
  74. Object.keys(values).forEach((key) => {
  75. this.form.fd[key] = values[key]
  76. })
  77. },
  78. }),
  79. fd: {
  80. nic: null,
  81. },
  82. },
  83. decorators: {
  84. nic: [
  85. 'nic',
  86. {
  87. initialValue: undefined,
  88. rules: [
  89. { required: false, message: this.$t('compute.text_193') },
  90. ],
  91. },
  92. ],
  93. secgroups: [
  94. 'secgroups',
  95. {
  96. initialValue: this.params.data[0].secgroups && this.params.data[0].secgroups.map(item => item.id),
  97. validateFirst: true,
  98. rules: [
  99. { validator: validateSecgroups, trigger: 'change' },
  100. ],
  101. },
  102. ],
  103. },
  104. formItemLayout: {
  105. wrapperCol: {
  106. span: 20,
  107. },
  108. labelCol: {
  109. span: 4,
  110. },
  111. },
  112. secgroupsInitLoaded: false,
  113. bindedSecgroups: [],
  114. bindedSecgroupsLoaded: false,
  115. secgroupOptions: [],
  116. nicOptions: [],
  117. }
  118. },
  119. computed: {
  120. selectedNic () {
  121. if (!this.form.fd.nic) return null
  122. return this.nicOptions.find(item => item.value === this.form.fd.nic)
  123. },
  124. ...mapGetters(['isAdminMode', 'scope']),
  125. isAzure () {
  126. return this.params.data[0].provider === HYPERVISORS_MAP.azure.provider
  127. },
  128. isUCloud () {
  129. return this.params.data[0].provider === HYPERVISORS_MAP.ucloud.provider
  130. },
  131. isZStack () {
  132. return this.params.data[0].provider === HYPERVISORS_MAP.zstack.provider
  133. },
  134. isBindOne () {
  135. return this.isAzure || this.isUCloud || this.isZStack
  136. },
  137. message () {
  138. let str = this.$t('compute.text_1245')
  139. if (this.isBindOne) {
  140. str = this.$t('compute.text_1246')
  141. }
  142. return str
  143. },
  144. href () {
  145. const url = this.$router.resolve('/secgroup')
  146. return url.href
  147. },
  148. max () {
  149. if (this.isBindOne) {
  150. return 1
  151. }
  152. return 5
  153. },
  154. domain () {
  155. return this.params.data[0].domain_id
  156. },
  157. tenant () {
  158. return this.params.data[0].tenant_id
  159. },
  160. secgroupsParams () {
  161. const params = { limit: 20 }
  162. if (this.isAdminMode) {
  163. params.project_domain = this.params.data[0].domain_id
  164. } else {
  165. params.scope = this.scope
  166. }
  167. if (this.params.data[0] && this.params.data[0].vpc_id) {
  168. params.vpc_id = this.params.data[0].vpc_id.split(',')[0]
  169. }
  170. return params
  171. },
  172. },
  173. created () {
  174. this.initNicOptions()
  175. this.fetchBindedSecgroups()
  176. },
  177. methods: {
  178. initNicOptions () {
  179. const nics = this.params.data[0].nics || []
  180. this.nicOptions = nics.map(nic => {
  181. const label = `${this.$t('compute.text_193')}${nic.index} (${nic.ip_addr || nic.mac})`
  182. return {
  183. label,
  184. value: nic.mac,
  185. ...nic,
  186. }
  187. })
  188. },
  189. async handleNicChange (value) {
  190. // 更新 fd.nic
  191. this.form.fd.nic = value
  192. // 先清空当前选中的安全组
  193. this.form.fc.setFieldsValue({ secgroups: [] })
  194. // 等待安全组选择器更新
  195. await this.$nextTick()
  196. // 加载安全组数据
  197. await this.fetchBindedSecgroups()
  198. // 确保安全组选择器已加载选项
  199. if (this.$refs.secgroupRef) {
  200. await this.$refs.secgroupRef.loadOpts()
  201. }
  202. // 设置安全组
  203. await this.$nextTick()
  204. if (this.selectedNic) {
  205. // 如果选择了网卡,使用网卡绑定的安全组
  206. const bindedSecgroupIds = this.getBindedSecgroupIds()
  207. if (bindedSecgroupIds.length > 0) {
  208. this.form.fc.setFieldsValue({ secgroups: bindedSecgroupIds })
  209. }
  210. } else {
  211. // 如果清空了网卡,使用默认的虚拟机安全组
  212. const defaultSecgroupIds = this.params.data[0].secgroups && this.params.data[0].secgroups.map(item => item.id)
  213. if (defaultSecgroupIds && defaultSecgroupIds.length > 0) {
  214. this.form.fc.setFieldsValue({ secgroups: defaultSecgroupIds })
  215. }
  216. }
  217. },
  218. getBindedSecgroupIds () {
  219. if (!this.selectedNic) {
  220. return []
  221. }
  222. const selectedMac = this.selectedNic.value
  223. // 从数据中获取已绑定的安全组
  224. const networkSecgroups = this.params.data[0].network_secgroups || []
  225. const currentNicSecgroup = networkSecgroups.find(item => item.mac === selectedMac)
  226. if (currentNicSecgroup && currentNicSecgroup.secgroups) {
  227. return currentNicSecgroup.secgroups.map(item => item.id)
  228. }
  229. return []
  230. },
  231. mapperSecgroups (data) {
  232. let newData = [...data, ...this.bindedSecgroups]
  233. newData = R.uniqBy(item => item.id, newData)
  234. return newData
  235. },
  236. async fetchBindedSecgroups () {
  237. let bindedSecgroupIds = []
  238. if (this.selectedNic) {
  239. // 如果选择了网卡,获取网卡绑定的安全组
  240. bindedSecgroupIds = this.getBindedSecgroupIds()
  241. } else {
  242. // 如果没有选择网卡,使用虚拟机级别的安全组
  243. bindedSecgroupIds = this.params.data[0].secgroups ? this.params.data[0].secgroups.map(item => item.id) : []
  244. }
  245. // 获取安全组详情
  246. if (bindedSecgroupIds.length > 0) {
  247. const manager = new this.$Manager('secgroups')
  248. try {
  249. const { data: { data = [] } } = await manager.list({
  250. params: {
  251. scope: this.scope,
  252. filter: `id.in(${bindedSecgroupIds.join(',')})`,
  253. },
  254. })
  255. this.bindedSecgroups = data
  256. } catch (error) {
  257. console.error('Failed to fetch secgroups:', error)
  258. this.bindedSecgroups = []
  259. }
  260. } else {
  261. this.bindedSecgroups = []
  262. }
  263. this.decorators.secgroups[1].initialValue = bindedSecgroupIds
  264. this.bindedSecgroupsLoaded = true
  265. },
  266. async handleConfirm () {
  267. this.loading = true
  268. try {
  269. const values = await this.form.fc.validateFields()
  270. // 如果选择了网卡,使用 set-network-secgroup 操作
  271. if (this.selectedNic) {
  272. const manager = new this.$Manager('servers')
  273. const data = {
  274. network_index: this.selectedNic.index,
  275. guest: this.params.data[0].id,
  276. secgroup_ids: values.secgroups,
  277. }
  278. await manager.performAction({
  279. id: this.params.data[0].id,
  280. action: 'set-network-secgroup',
  281. data,
  282. })
  283. } else {
  284. // 如果没有选择网卡,使用原来的 set-secgroup 操作
  285. const ids = this.params.data.map(item => item.id)
  286. const data = {
  287. secgroup_ids: values.secgroups,
  288. }
  289. if (this.params.manager) {
  290. await this.params.manager.batchPerformAction({
  291. ids,
  292. action: 'set-secgroup',
  293. data,
  294. })
  295. } else {
  296. await this.params.onManager('batchPerformAction', {
  297. id: ids,
  298. managerArgs: {
  299. action: 'set-secgroup',
  300. data,
  301. },
  302. })
  303. }
  304. }
  305. this.params.refresh && this.params.refresh()
  306. this.$bus.$emit(SECGROUP_LIST_FOR_VMINSTANCE_SIDEPAGE_REFRESH)
  307. this.cancelDialog()
  308. this.$message.success(this.$t('common_360'))
  309. } finally {
  310. this.loading = false
  311. }
  312. },
  313. async successCallback () {
  314. await this.$refs.secgroupRef.loadOpts()
  315. const secgroups = this.form.fc.getFieldValue('secgroups')
  316. const newSecgroup = this.secgroupOptions[0]
  317. secgroups.push(newSecgroup.id)
  318. this.form.fc.setFieldsValue({ secgroups: secgroups })
  319. },
  320. },
  321. }
  322. </script>