AttachUsb.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <template>
  2. <base-dialog @cancel="cancelDialog">
  3. <div slot="header">{{action}}</div>
  4. <div slot="body">
  5. <a-alert class="mb-2" type="warning">
  6. <div slot="message" v-if="params.data.length === 1">{{$t('compute.text_1400')}}</div>
  7. <div slot="message" v-else>
  8. <p>{{$t('compute.text_1168')}}</p>
  9. <p>{{$t('compute.text_1169')}}</p>
  10. </div>
  11. </a-alert>
  12. <dialog-selected-tips :name="$t('dictionary.server')" :count="params.data.length" :action="action" />
  13. <dialog-table :data="params.data" :columns="columns" />
  14. <a-form
  15. :form="form.fc"
  16. v-bind="formItemLayout">
  17. <a-form-item :label="$t('compute.text_1170')">
  18. <a-radio-group name="radioGroup" :defaultValue="true" v-if="isGroupAction" v-model="isOpenUsb">
  19. <a-radio :value="true">{{$t('compute.text_902')}}</a-radio>
  20. <a-radio :value="false">{{$t('compute.text_723')}}</a-radio>
  21. </a-radio-group>
  22. <a-switch :checkedChildren="$t('compute.text_115')" :unCheckedChildren="$t('compute.text_116')" v-model="isOpenUsb" v-else />
  23. </a-form-item>
  24. <a-form-item :label="$t('compute.text_1401')" v-show="isOpenUsb" :extra="$t('compute.text_1402')">
  25. <!-- 批量设置 -->
  26. <base-select
  27. v-if="isGroupAction"
  28. v-decorator="decorators.device"
  29. :params="usbParams"
  30. :need-params="false"
  31. :labelFormat="labelFormat"
  32. :disabled-items="disabledItems"
  33. filterable
  34. :resList.sync="usbOpt"
  35. :mapper="mapper"
  36. resource="isolated_devices"
  37. :select-props="{ allowClear: true, placeholder: $t('compute.text_1172'), mode: 'default' }">
  38. <template v-slot:optionTemplate>
  39. <a-select-option v-for="item in usbOpt" :key="item.id" :value="item.id" :disabled="item.__disabled">
  40. <div class="d-flex">
  41. <span class="text-truncate flex-fill mr-2" :title="item.model">{{ item.model }}</span>
  42. <span style="color: #8492a6; font-size: 13px" v-show="item.totalCount > item.usedCount">{{$t('compute.text_1173', [ item.totalCount - item.usedCount , item.totalCount ])}}</span>
  43. <span style="color: #8492a6; font-size: 13px" v-show="item.totalCount === item.usedCount">{{$t('compute.text_1174')}}</span>
  44. </div>
  45. </a-select-option>
  46. </template>
  47. </base-select>
  48. <!-- 单条操作 -->
  49. <base-select
  50. v-else
  51. v-decorator="decorators.device"
  52. :params="usbParams"
  53. :need-params="false"
  54. filterable
  55. :options="usbOptions"
  56. :select-props="{ allowClear: true, placeholder: $t('compute.text_1403'), mode: 'multiple', loading: usbOptionsLoading }" />
  57. </a-form-item>
  58. <a-form-item :label="$t('compute.text_294')" v-show="isOpenUsb && isGroupAction" :extra="$t('compute.text_1175')">
  59. <a-input-number :min="1" v-decorator="decorators.number" />
  60. </a-form-item>
  61. <a-form-item :label="$t('compute.text_494')" :extra="$t('compute.text_495')" v-if="isOpenAutoStart">
  62. <a-switch :checkedChildren="$t('compute.text_115')" :unCheckedChildren="$t('compute.text_116')" v-decorator="decorators.autoStart" />
  63. </a-form-item>
  64. </a-form>
  65. </div>
  66. <div slot="footer">
  67. <a-button type="primary" @click="handleConfirm" :loading="loading">{{ $t('dialog.ok') }}</a-button>
  68. <a-button @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  69. </div>
  70. </base-dialog>
  71. </template>
  72. <script>
  73. // import * as R from 'ramda'
  74. import {
  75. getIpsTableColumn,
  76. } from '@/utils/common/tableColumn'
  77. import DialogMixin from '@/mixins/dialog'
  78. import WindowsMixin from '@/mixins/windows'
  79. export default {
  80. name: 'VmAttachUsbDialog', // 目前只支持单挑操作,批量逻辑未调整
  81. mixins: [DialogMixin, WindowsMixin],
  82. data () {
  83. return {
  84. loading: false,
  85. action: this.$t('compute.text_1399'),
  86. form: {
  87. fc: this.$form.createForm(this, { onValuesChange: this.onValuesChange }),
  88. fd: {
  89. device: [],
  90. },
  91. },
  92. decorators: {
  93. device: [
  94. 'device',
  95. {
  96. rules: [
  97. { required: true, type: 'any', message: this.$t('compute.text_1403'), trigger: 'change' },
  98. ],
  99. },
  100. ],
  101. autoStart: [
  102. 'autoStart',
  103. {
  104. valuePropName: 'checked',
  105. initialValue: false,
  106. },
  107. ],
  108. number: [
  109. 'number',
  110. {
  111. initialValue: 1,
  112. },
  113. ],
  114. },
  115. formItemLayout: {
  116. wrapperCol: {
  117. span: 20,
  118. },
  119. labelCol: {
  120. span: 4,
  121. },
  122. },
  123. usbOpt: [],
  124. usbOptions: [],
  125. isOpenUsb: false,
  126. bindUsbs: [],
  127. usbOptionsLoading: false,
  128. columns: [
  129. {
  130. field: 'name',
  131. title: this.$t('compute.text_228'),
  132. },
  133. getIpsTableColumn({ field: 'ip', title: 'IP' }),
  134. {
  135. field: 'usb',
  136. title: 'USB',
  137. slots: {
  138. default: ({ row }) => {
  139. const ret = []
  140. if (row.isolated_devices) {
  141. row.isolated_devices.map(item => {
  142. if (item.dev_type === 'USB') {
  143. ret.push(<list-body-cell-wrap row={{ showName: `${item.addr || ''} ${item.model || ''}` }} field="showName" />)
  144. }
  145. })
  146. }
  147. return ret
  148. },
  149. },
  150. },
  151. ],
  152. }
  153. },
  154. computed: {
  155. selectedItems () {
  156. return this.params.data
  157. },
  158. isOpenAutoStart () {
  159. return this.selectedItems.every(item => item.status === 'ready')
  160. },
  161. },
  162. created () {
  163. this.$D = new this.$Manager('servers', 'v1')
  164. this.initUsb()
  165. },
  166. methods: {
  167. initUsb () {
  168. const { isolated_devices = [] } = this.params.data[0]
  169. const devices = isolated_devices.filter(item => item.dev_type === 'USB')
  170. if (devices?.length > 0) {
  171. this.isOpenUsb = true
  172. }
  173. this.initUsbOptions()
  174. },
  175. async initUsbOptions () {
  176. try {
  177. this.usbOptionsLoading = true
  178. const acttachedRes = await new this.$Manager('isolated_devices', 'v2').list({
  179. params: {
  180. $t: 2,
  181. guest_id: this.params.data[0].id,
  182. },
  183. })
  184. const { data: acttachedList = [] } = acttachedRes.data
  185. const probleDevRes = await new this.$Manager('isolated_devices', 'v2').list({
  186. params: {
  187. $t: 1,
  188. host_id: this.params.data[0].host_id,
  189. },
  190. })
  191. const { data: probleDevList = [] } = probleDevRes.data
  192. const device = acttachedList.filter(item => {
  193. return item.dev_type === 'USB'
  194. }).map(item => {
  195. return item.id
  196. })
  197. this.bindUsbs = device
  198. this.form.fc.setFieldsValue({
  199. device,
  200. })
  201. const list = [...acttachedList]
  202. probleDevList.forEach(item => {
  203. if (!item.guest_id && !list.some(l => l.id === item.id)) {
  204. list.push(item)
  205. }
  206. })
  207. const usbOptions = list.filter(item => {
  208. return item.dev_type === 'USB'
  209. }).map(item => {
  210. return {
  211. key: item.id,
  212. id: item.id,
  213. name: `${item.addr || ''} ${item.model || ''}`,
  214. }
  215. })
  216. usbOptions.sort((a, b) => {
  217. return a.key - b.key
  218. })
  219. this.usbOptions = usbOptions
  220. } catch (err) {
  221. throw err
  222. } finally {
  223. this.usbOptionsLoading = false
  224. }
  225. },
  226. async doAttachSubmit (data) {
  227. const add_devices = []
  228. const del_devices = []
  229. data.device.map(item => {
  230. if (!this.bindUsbs.includes(item)) {
  231. add_devices.push(item)
  232. }
  233. })
  234. this.bindUsbs.map(item => {
  235. if (!data.device.includes(item)) {
  236. del_devices.push(item)
  237. }
  238. })
  239. const params = {
  240. add_devices,
  241. del_devices,
  242. auto_start: data.autoStart,
  243. }
  244. const ids = this.params.data.map(item => item.id)
  245. return this.params.onManager('batchPerformAction', {
  246. id: ids,
  247. steadyStatus: ['running', 'ready'],
  248. managerArgs: {
  249. action: 'set-isolated-device',
  250. data: params,
  251. },
  252. })
  253. },
  254. async doDetachSubmit (data) {
  255. // 批量解除绑定
  256. const params = {
  257. add_devices: [],
  258. del_devices: this.bindUsbs,
  259. auto_start: data.autoStart,
  260. }
  261. const ids = this.params.data.map(item => item.id)
  262. return this.params.onManager('batchPerformAction', {
  263. id: ids,
  264. steadyStatus: ['running', 'ready'],
  265. managerArgs: {
  266. action: 'set-isolated-device',
  267. data: params,
  268. },
  269. })
  270. },
  271. async handleConfirm () {
  272. this.loading = true
  273. try {
  274. if (this.isOpenUsb) {
  275. const values = await this.form.fc.validateFields()
  276. await this.doAttachSubmit(values)
  277. } else {
  278. const values = await this.form.fc.getFieldsValue()
  279. await this.doDetachSubmit(values)
  280. }
  281. this.loading = false
  282. this.cancelDialog()
  283. } catch (error) {
  284. this.loading = false
  285. throw error
  286. }
  287. },
  288. onValuesChange (props, values) {
  289. Object.keys(values).forEach((key) => {
  290. if (key === 'device') {
  291. this.form.fd[key] = values[key]
  292. } else {
  293. this.form.fd[key] = values[key]
  294. }
  295. })
  296. },
  297. },
  298. }
  299. </script>