bfg-common 1.5.823 → 1.5.824

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,573 +1,573 @@
1
- <template>
2
- <div class="virtual-hardware">
3
- <div class="action-wrap py-3">
4
- <span class="device-count">{{ deviceCountText }}</span>
5
-
6
- <div class="add-device-dropdown-wrap">
7
- <button
8
- id="add-device-dropdown"
9
- :class="['cursor-pointer', { active: isShowAddDeviceDropdown }]"
10
- data-id="add-device-dropdown"
11
- @click="isShowAddDeviceDropdown = !isShowAddDeviceDropdown"
12
- >
13
- <ui-icon name="plus" width="18" height="18" />
14
- <span class="add-device-dropdown-text">
15
- {{ addNewDeviceText }}
16
- </span>
17
- </button>
18
- <ui-dropdown
19
- :items="dropdownItems"
20
- :show="isShowAddDeviceDropdown"
21
- :margin-between-trigger="4"
22
- show-from-left
23
- width="max-content"
24
- elem-id="add-device-dropdown"
25
- test-id="add-device-dropdown"
26
- left
27
- max-height="400px"
28
- @select="onAddDevice"
29
- @hide="isShowAddDeviceDropdown = false"
30
- />
31
- </div>
32
- </div>
33
-
34
- <div class="overflow-auto virtual-hardware-content-block">
35
- <div class="stack-view">
36
- <common-vm-actions-common-customize-hardware-virtual-hardware-cpu
37
- v-model="model"
38
- :cpu-models="props.cpuModels"
39
- :is-edit="props.isEdit"
40
- :state="props.state"
41
- :error-validation-fields="props.errorValidationFields"
42
- :vm-cpu-help-text-second="props.vmCpuHelpTextSecond"
43
- @remove-error-by-title="emits('remove-error-by-title', $event)"
44
- @invalid="cpuInvalid = $event"
45
- />
46
- <common-vm-actions-common-customize-hardware-virtual-hardware-memory
47
- v-model="model"
48
- :max-memory="props.maxMemory"
49
- :is-edit="props.isEdit"
50
- :state="props.state"
51
- :error-validation-fields="props.errorValidationFields"
52
- @invalid="memoryInvalid = $event"
53
- @remove-error-by-title="emits('remove-error-by-title', $event)"
54
- />
55
- <div
56
- v-for="(item, key) in model.disk_devices.filter(
57
- (disk) => disk.device_type !== 'cdrom'
58
- )"
59
- :key="props.hardDisksIndex[key]"
60
- >
61
- <!-- TODO 111-->
62
- <!-- v-model:vm-storage-policy="item.vm_storage_policy"-->
63
- <common-vm-actions-common-customize-hardware-virtual-hardware-new-hard-disk
64
- v-model:size="item.size"
65
- v-model:provision-type="item.provision_type"
66
- v-model:sharing="item.sharing"
67
- v-model:shares="item.shares"
68
- v-model:limit-iops="item.limit_iops"
69
- v-model:disk-mode="item.disk_mode"
70
- v-model:cache="item.cache"
71
- v-model:bus="item.bus"
72
- v-model:read-only="item.readOnly"
73
- v-model:discard="item.discard"
74
- v-model:io="item.io"
75
- v-model:target="item.target"
76
- v-model:source="item.source"
77
- v-model:storage="item.storage"
78
- :index="props.hardDisksIndex[key]"
79
- :type="props.hardDisksType[key]"
80
- :main-storage="props.storage"
81
- :main-storage-free="props.storageFree"
82
- :error-validation-fields="props.errorValidationFields"
83
- :get-datastore-table-func="props.getDatastoreTableFunc"
84
- :datastore="props.datastore"
85
- :is-datastore-loading="props.isDatastoreLoading"
86
- :is-edit="props.isEdit"
87
- :state="props.state"
88
- :guest-machine-type="props.guestMachineType"
89
- :compute-resource="props.computeResource"
90
- @remove="
91
- emits('remove-hard-disk', [props.hardDisksIndex[key], item])
92
- "
93
- @roll-back="emits('roll-back-hard-disk', props.hardDisksIndex[key])"
94
- @invalid="
95
- emits('set-invalid-hard-disk', [
96
- $event,
97
- props.hardDisksIndex[key],
98
- ])
99
- "
100
- @remove-error-by-title="emits('remove-error-by-title', $event)"
101
- />
102
- </div>
103
- <div
104
- v-for="(item, key) in model.network_devices"
105
- :key="props.networksIndex[key]"
106
- >
107
- <common-vm-actions-common-customize-hardware-virtual-hardware-new-network
108
- v-model:network="item.network"
109
- v-model:network-value="item[props.networkValueProp]"
110
- v-model:mac="item.mac"
111
- v-model:model="item.model"
112
- :index="props.networksIndex[key]"
113
- :type="props.networksType[key]"
114
- :networks-table="props.networksTable"
115
- :error-validation-fields="props.errorValidationFields"
116
- :is-edit="props.isEdit"
117
- :state="props.state"
118
- :project="props.project"
119
- :network-value-prop="props.networkValueProp"
120
- @remove="emits('remove-network', props.networksIndex[key])"
121
- @invalid="
122
- emits('set-invalid-network', [$event, props.networksIndex[key]])
123
- "
124
- @remove-error-by-title="emits('remove-error-by-title', $event)"
125
- @get-networks-table="emits('get-networks-table', $event)"
126
- />
127
- </div>
128
- <!-- <common-vm-actions-common-customize-hardware-virtual-hardware-cd-dvd-drive-->
129
- <!-- v-for="(item, key) in props.cdDvdDrives"-->
130
- <!-- :key="props.cdDvdDrivesIndex[key]"-->
131
- <!-- :index="props.cdDvdDrivesIndex[key]"-->
132
- <!-- :hard-disks-count="props.hardDisks?.length || 0"-->
133
- <!-- :type="props.cdDvdDrivesType[key]"-->
134
- <!-- :cd-dvd-drive="item"-->
135
- <!-- :nodes="props.nodes"-->
136
- <!-- :files="props.files"-->
137
- <!-- :error-validation-fields="props.errorValidationFields"-->
138
- <!-- :is-edit="props.isEdit"-->
139
- <!-- :state="props.state"-->
140
- <!-- :guest-machine-type="props.guestMachineType"-->
141
- <!-- :compute-resource="props.computeResource"-->
142
- <!-- @remove-error-by-title="emits('remove-error-by-title', $event)"-->
143
- <!-- @get-storage="emits('get-storage', $event)"-->
144
- <!-- @get-folders-or-files="emits('get-folders-or-files', $event)"-->
145
- <!-- @get-active-device-child="emits('get-active-device-child', $event)"-->
146
- <!-- @show-datastore-child="emits('show-datastore-child', $event)"-->
147
- <!-- @remove="-->
148
- <!-- emits('remove-cd-dvd-drive', [props.cdDvdDrivesIndex[key], item])-->
149
- <!-- "-->
150
- <!-- @roll-back="-->
151
- <!-- emits('roll-back-cd-dvd-drive', props.cdDvdDrivesIndex[key])-->
152
- <!-- "-->
153
- <!-- @send-data="-->
154
- <!-- emits('send-data-new-cd-dvd-drive-method', [-->
155
- <!-- $event,-->
156
- <!-- props.cdDvdDrivesIndex[key],-->
157
- <!-- ])-->
158
- <!-- "-->
159
- <!-- />-->
160
- <div
161
- v-for="(item, key) in model.disk_devices.filter(
162
- (disk) => disk.device_type === 'cdrom'
163
- )"
164
- :key="props.hardDisksIndex[key]"
165
- >
166
- <!-- TODO count-->
167
- <common-vm-actions-common-customize-hardware-virtual-hardware-cd-dvd-drive
168
- v-model:source="item.source"
169
- v-model:bus="item.bus"
170
- v-model:remove="item.remove"
171
- v-model:detach="item.detach"
172
- :index="props.cdDvdDrivesIndex[key]"
173
- :hard-disks-count="
174
- model.disk_devices.filter((disk) => disk.device_type !== 'cdrom')
175
- ?.length || 0
176
- "
177
- :type="props.cdDvdDrivesType[key]"
178
- :nodes="props.nodes"
179
- :files="props.files"
180
- :error-validation-fields="props.errorValidationFields"
181
- :is-edit="props.isEdit"
182
- :state="props.state"
183
- :guest-machine-type="props.guestMachineType"
184
- :compute-resource="props.computeResource"
185
- @remove-error-by-title="emits('remove-error-by-title', $event)"
186
- @get-storage="emits('get-storage', $event)"
187
- @get-folders-or-files="emits('get-folders-or-files', $event)"
188
- @get-active-device-child="emits('get-active-device-child', $event)"
189
- @show-datastore-child="emits('show-datastore-child', $event)"
190
- @remove="
191
- emits('remove-cd-dvd-drive', [props.cdDvdDrivesIndex[key], item])
192
- "
193
- @roll-back="
194
- emits('roll-back-cd-dvd-drive', props.cdDvdDrivesIndex[key])
195
- "
196
- @send-data="
197
- emits('send-data-new-cd-dvd-drive-method', [
198
- $event,
199
- props.cdDvdDrivesIndex[key],
200
- ])
201
- "
202
- />
203
- </div>
204
- <div v-for="(item, key) in model.passthrough_usb_devices" :key="key">
205
- <common-vm-actions-common-customize-hardware-virtual-hardware-new-usb-device
206
- :item="item"
207
- :index="props.usbDevicesIndex[key]"
208
- :type="props.usbDevicesType[key]"
209
- :available-usb-devices="props.availableUsbDevices"
210
- :state="props.state"
211
- @change="
212
- emits('change-usb-device', [props.usbDevicesIndex[key], $event])
213
- "
214
- @remove="emits('remove-usb-device', props.usbDevicesIndex[key])"
215
- />
216
- </div>
217
- <div v-for="(item, key) in model.usb_controllers" :key="key">
218
- <common-vm-actions-common-customize-hardware-virtual-hardware-new-usb-controller
219
- v-model="item.type"
220
- :is-edit="props.isEdit"
221
- :state="props.state"
222
- />
223
- </div>
224
- <template v-if="props.passthroughDevices || props.mediatedDevices">
225
- <common-vm-actions-common-customize-hardware-virtual-hardware-new-pci-device
226
- v-for="(item, key) in model.passthrough_pci_devices"
227
- v-model:address="item.address"
228
- v-model:vendor_name="item.vendor_name"
229
- v-model:class_name="item.class_name"
230
- v-model:device_name="item.device_name"
231
- v-model:mediated_device="item.mediated_device"
232
- v-model:mediated_device_uuid="item.mediated_device_uuid"
233
- :key="props.pciDevicesIndex[key]"
234
- :index="props.pciDevicesIndex[key]"
235
- :passthrough-devices="props.passthroughDevices"
236
- :mediated-devices="props.mediatedDevices"
237
- :type="props.pciDevicesType[key]"
238
- :is-edit="props.isEdit"
239
- :state="props.state"
240
- @remove="emits('remove-pci-device', props.pciDevicesIndex[key])"
241
- />
242
- </template>
243
- <common-vm-actions-common-customize-hardware-virtual-hardware-video-card
244
- v-model="model.video_card"
245
- :is-edit="props.isEdit"
246
- :video-card="props.videoCard"
247
- :error-validation-fields="props.errorValidationFields"
248
- :state="props.state"
249
- @invalid="videoCardInvalid = $event"
250
- @remove-error-by-title="emits('remove-error-by-title', $event)"
251
- />
252
- <common-vm-actions-common-customize-hardware-virtual-hardware-other />
253
- <div
254
- v-if="props.compatibilityInfo"
255
- class="vm-hardware-version-wrap justify-end flex flex-1 mt-1"
256
- >
257
- <span class="vm-hardware-version">
258
- {{ props.compatibilityInfo }}
259
- </span>
260
- </div>
261
- </div>
262
- </div>
263
-
264
- <Teleport to="body">
265
- <common-vm-actions-common-customize-hardware-virtual-hardware-browse-view
266
- v-if="props.isShowFileModal"
267
- :nodes="props.nodes"
268
- :files="props.files"
269
- show
270
- @submit="emits('add-exist-hard-disk', $event)"
271
- @get-storage="emits('get-storage', $event)"
272
- @get-folders-or-files="emits('get-folders-or-files', $event)"
273
- @get-active-device-child="emits('get-active-device-child', $event)"
274
- @show-datastore-child="emits('show-datastore-child', $event)"
275
- @hide="emits('hide-file-modal')"
276
- />
277
- </Teleport>
278
- </div>
279
- </template>
280
-
281
- <script setup lang="ts">
282
- import type { UI_I_Dropdown } from '~/node_modules/bfg-uikit/components/ui/dropdown/models/interfaces'
283
- import type { UI_T_Project } from '~/lib/models/types'
284
- import type { UI_I_DatastoreTableItem } from '~/lib/models/store/storage/interfaces'
285
- import type { UI_I_NetworkTableItem } from '~/lib/models/store/network/interfaces'
286
- import type { UI_I_TablePayload } from '~/lib/models/table/interfaces'
287
- import type { UI_I_FolderOrFileTreePayload } from '~/lib/models/store/storage/interfaces'
288
- import type { UI_I_FileTreeNode } from '~/components/lib/models/interfaces'
289
- import type {
290
- UI_I_SendDataNewHardDisk,
291
- UI_I_SendDataVideoCard,
292
- UI_I_SendDataNewCdDvdDrive,
293
- UI_I_SendDataNewPciDevice,
294
- } from '~/components/common/vm/actions/common/customizeHardware/virtualHardware/lib/models/interfaces'
295
- import type { UI_I_ErrorValidationField } from '~/lib/models/store/interfaces'
296
- import type {
297
- UI_T_HardDiskType,
298
- UI_T_NetworkType,
299
- UI_T_PciDeviceType,
300
- } from '~/components/common/vm/actions/common/customizeHardware/virtualHardware/lib/models/types'
301
- import type { UI_I_OptionItem } from '~/components/atoms/lib/models/interfaces'
302
- import type { UI_I_Localization } from '~/lib/models/interfaces'
303
- import type {
304
- API_UI_I_VmEditCpu,
305
- UI_I_MediatedDevice,
306
- UI_I_PciDevice,
307
- } from '~/lib/models/store/vm/interfaces'
308
- import type { UI_I_TreeNode } from '~/components/common/recursionTree/lib/models/interfaces'
309
- import type { UI_I_CreateVmData } from '~/components/common/vm/actions/common/lib/models/interfaces'
310
- import { dropdownItemsNewFunc } from './lib/config/dropdownItems'
311
-
312
- const model = defineModel<UI_I_CreateVmData>({ required: true })
313
-
314
- const cpuInvalid = defineModel<boolean>('cpuInvalid', { required: true })
315
- const memoryInvalid = defineModel<boolean>('memoryInvalid', { required: true })
316
- const videoCardInvalid = defineModel<boolean>('videoCardInvalid', {
317
- required: true,
318
- })
319
-
320
- const props = withDefaults(
321
- defineProps<{
322
- nodes: UI_I_FileTreeNode[]
323
- files: UI_I_FileTreeNode[]
324
- isEdit: boolean
325
- storage: UI_I_DatastoreTableItem | null
326
- storageFree: number
327
- project: UI_T_Project
328
- maxMemory: number
329
- cpuModels: UI_I_OptionItem[]
330
- datastore: UI_I_DatastoreTableItem[]
331
- isDatastoreLoading: boolean
332
- networksType: UI_T_NetworkType[]
333
- networksTable: UI_I_NetworkTableItem[]
334
- // dropdownItems: UI_I_DropdownTreeItem[]
335
- hardDisksType: UI_T_HardDiskType[]
336
- networksIndex: number[]
337
- hardDisksIndex: number[]
338
- pciDevicesType: UI_T_NetworkType[]
339
- isShowFileModal: boolean
340
- pciDevicesIndex: number[]
341
- availableUsbDevices: any[]
342
- usbDevicesType: UI_T_PciDeviceType[]
343
- usbDevicesIndex: number[]
344
- cdDvdDrivesType: UI_T_NetworkType[]
345
- cdDvdDrivesIndex: number[]
346
- errorValidationFields: UI_I_ErrorValidationField<string>[]
347
- getDatastoreTableFunc: (payload: UI_I_TablePayload) => Promise<void>
348
- networkValueProp: 'network' | 'net_bridge'
349
- cpu?: API_UI_I_VmEditCpu
350
- state?: string | number
351
- // hardDisks?: UI_I_SendDataNewHardDisk[] | null
352
- videoCard?: UI_I_SendDataVideoCard
353
- // cdDvdDrives?: UI_I_SendDataNewCdDvdDrive[] | null
354
- guestMachineType?: UI_I_OptionItem | null
355
- passthroughDevices?: UI_I_PciDevice[]
356
- mediatedDevices?: UI_I_MediatedDevice[]
357
- vmCpuHelpTextSecond?: string
358
- computeResource?: UI_I_TreeNode | null
359
- compatibilityInfo?: string
360
- }>(),
361
- {
362
- cpu: undefined,
363
- state: undefined,
364
- videoCard: undefined,
365
- guestMachineType: undefined,
366
- passthroughDevices: undefined,
367
- mediatedDevices: undefined,
368
- vmCpuHelpTextSecond: undefined,
369
- computeResource: undefined,
370
- compatibilityInfo: undefined,
371
- }
372
- )
373
- const emits = defineEmits<{
374
- (event: 'add-device', value: { value: number }): void
375
- (event: 'get-storage', value: UI_I_TablePayload): void
376
- (event: 'remove-network', value: number): void
377
- (event: 'hide-file-modal'): void
378
- (event: 'remove-hard-disk', value: [number, UI_I_SendDataNewHardDisk]): void
379
- (event: 'remove-pci-device', value: number): void
380
- (event: 'change-usb-device', value: [number, any]): void
381
- (event: 'remove-usb-device', value: number): void
382
- (event: 'get-networks-table', value: UI_I_TablePayload): void
383
- (event: 'add-exist-hard-disk', value: UI_I_FileTreeNode): void
384
- (event: 'roll-back-hard-disk', value: number): void
385
- (event: 'set-invalid-network', value: [boolean, number]): void
386
- (
387
- event: 'remove-cd-dvd-drive',
388
- value: [number, UI_I_SendDataNewCdDvdDrive]
389
- ): void
390
- (event: 'show-datastore-child', value: UI_I_FileTreeNode): void
391
- (event: 'get-folders-or-files', value: UI_I_FolderOrFileTreePayload): void
392
- (event: 'set-invalid-hard-disk', value: [boolean, number]): void
393
- (event: 'remove-error-by-title', value: string): void
394
- (event: 'roll-back-cd-dvd-drive', value: number): void
395
- (event: 'get-active-device-child', value: UI_I_FileTreeNode): void
396
- (
397
- event: 'send-data-new-cd-dvd-drive-method',
398
- value: [UI_I_SendDataNewCdDvdDrive, number]
399
- ): void
400
- }>()
401
-
402
- const { $text } = useNuxtApp()
403
-
404
- const localization = computed<UI_I_Localization>(() => useLocal())
405
-
406
- const isShowAddDeviceDropdown = ref<boolean>(false)
407
-
408
- // TODO
409
- const addNewDeviceText = computed<string>(() =>
410
- $text.toCapitalize(localization.value.common.addNewDevice)
411
- )
412
- const deviceCount = computed<number>(
413
- () =>
414
- // (props.hardDisks?.length || 0) +
415
- // (props.networks?.length || 0) +
416
- // (props.cdDvdDrives?.length || 0) +
417
- // (props.pciDevices?.length || 0) +
418
- (model.value.disk_devices?.length || 0) +
419
- (model.value.network_devices?.length || 0) +
420
- (model.value.passthrough_pci_devices?.length || 0) +
421
- (model.value.passthrough_usb_devices?.length || 0) +
422
- 6
423
- )
424
- const deviceCountText = computed<string>(() =>
425
- localization.value.vmWizard.devices.replace(
426
- '{0}',
427
- deviceCount.value.toString()
428
- )
429
- )
430
- const dropdownItems = computed<UI_I_Dropdown[]>(() =>
431
- dropdownItemsNewFunc(
432
- localization.value,
433
- props.state,
434
- props.passthroughDevices,
435
- props.mediatedDevices
436
- )
437
- )
438
-
439
- const onAddDevice = (data: number): void => {
440
- isShowAddDeviceDropdown.value = false
441
- // из-за старого компонента
442
- emits('add-device', { value: data })
443
- }
444
- </script>
445
-
446
- <style>
447
- :root {
448
- --vm-wizard-block-view-bg-color: #fafafa;
449
- --add-device-dropdown-bg-color: #fff;
450
- --add-device-dropdown-bg-color-hover: #f6f7f8;
451
- --add-device-dropdown-active-bg-color-hover: #f0f8fd;
452
- --add-device-dropdown-border-color: #e9ebed;
453
- --add-device-dropdown-border-color-hover: #e9ebed;
454
- --add-device-dropdown-color: #4d5d69;
455
- --add-device-dropdown-color-hover: #4d5d69;
456
- --add-device-dropdown-item-title-color: #182531;
457
- }
458
- :root.dark-theme {
459
- --vm-wizard-block-view-bg-color: transparent;
460
- --add-device-dropdown-bg-color: #1b2a373d;
461
- //--add-device-dropdown-bg-color-hover: #2585b614;
462
- --add-device-dropdown-bg-color-hover: #b8bcc10a;
463
- --add-device-dropdown-active-bg-color-hover: #2785b614;
464
- --add-device-dropdown-border-color: #e9ebed1f;
465
- --add-device-dropdown-border-color-hover: #2ba2de;
466
- --add-device-dropdown-color: #e9eaec;
467
- --add-device-dropdown-color-hover: #2ba2de;
468
- --add-device-dropdown-item-title-color: #e9eaec;
469
- }
470
- </style>
471
-
472
- <style scoped lang="scss">
473
- .virtual-hardware {
474
- height: 100%;
475
- display: grid;
476
-
477
- .action-wrap {
478
- display: flex;
479
- align-items: center;
480
- justify-content: space-between;
481
- //padding: 12px;
482
-
483
- .device-count {
484
- font-size: 13px;
485
- font-weight: 500;
486
- line-height: 15.73px;
487
- color: #9da6ad;
488
- }
489
-
490
- .add-device-dropdown-wrap {
491
- #add-device-dropdown {
492
- display: flex;
493
- align-items: center;
494
- gap: 8px;
495
- background-color: var(--add-device-dropdown-bg-color);
496
- border: 1px solid var(--add-device-dropdown-border-color);
497
- border-radius: 6px;
498
- padding: 7px 12px;
499
- color: var(--add-device-dropdown-color);
500
- font-size: 13px;
501
- font-weight: 500;
502
- line-height: 15.73px;
503
- height: 32px;
504
-
505
- &:hover {
506
- background-color: var(--add-device-dropdown-bg-color-hover);
507
- border-color: var(--add-device-dropdown-border-color-hover);
508
- color: var(--add-device-dropdown-color-hover);
509
- }
510
-
511
- &.active {
512
- color: #008fd6;
513
- border-color: #008fd6;
514
-
515
- &:hover {
516
- background-color: var(--add-device-dropdown-active-bg-color-hover);
517
- }
518
- }
519
-
520
- .add-device-dropdown-text {
521
- font-weight: 500;
522
- font-family: 'Inter', sans-serif;
523
- text-transform: capitalize;
524
- }
525
- }
526
- :deep(.ui-popup-window) {
527
- overflow: hidden;
528
- padding: 8px 0;
529
-
530
- .ui-dropdown {
531
- overflow: auto;
532
- //max-height: calc(300px - 24px);
533
-
534
- .ui-dropdown-menu.md {
535
- padding: 0 8px;
536
-
537
- li {
538
- &.has-items .group-title {
539
- padding: 4px 8px 0;
540
- color: var(--add-device-dropdown-item-title-color);
541
- }
542
- &:not(:last-child) {
543
- margin-bottom: 4px;
544
- }
545
- //&:last-child ul li:last-child {
546
- //height: 26px;
547
- //align-items: flex-end;
548
- //}
549
- }
550
- }
551
- }
552
- }
553
- }
554
- }
555
- }
556
- .stack-view {
557
- display: flex;
558
- flex-direction: column;
559
- gap: 12px;
560
- padding-bottom: 16px;
561
- background-color: var(--vm-wizard-block-view-bg-color);
562
-
563
- .vm-hardware-version-wrap {
564
- align-items: flex-end;
565
- text-align: right;
566
-
567
- .vm-hardware-version {
568
- font-size: 13px;
569
- color: #9da6ad; // for light and dark
570
- }
571
- }
572
- }
573
- </style>
1
+ <template>
2
+ <div class="virtual-hardware">
3
+ <div class="action-wrap py-3">
4
+ <span class="device-count">{{ deviceCountText }}</span>
5
+
6
+ <div class="add-device-dropdown-wrap">
7
+ <button
8
+ id="add-device-dropdown"
9
+ :class="['cursor-pointer', { active: isShowAddDeviceDropdown }]"
10
+ data-id="add-device-dropdown"
11
+ @click="isShowAddDeviceDropdown = !isShowAddDeviceDropdown"
12
+ >
13
+ <ui-icon name="plus" width="18" height="18" />
14
+ <span class="add-device-dropdown-text">
15
+ {{ addNewDeviceText }}
16
+ </span>
17
+ </button>
18
+ <ui-dropdown
19
+ :items="dropdownItems"
20
+ :show="isShowAddDeviceDropdown"
21
+ :margin-between-trigger="4"
22
+ show-from-left
23
+ width="max-content"
24
+ elem-id="add-device-dropdown"
25
+ test-id="add-device-dropdown"
26
+ left
27
+ max-height="400px"
28
+ @select="onAddDevice"
29
+ @hide="isShowAddDeviceDropdown = false"
30
+ />
31
+ </div>
32
+ </div>
33
+
34
+ <div class="overflow-auto virtual-hardware-content-block">
35
+ <div class="stack-view">
36
+ <common-vm-actions-common-customize-hardware-virtual-hardware-cpu
37
+ v-model="model"
38
+ :cpu-models="props.cpuModels"
39
+ :is-edit="props.isEdit"
40
+ :state="props.state"
41
+ :error-validation-fields="props.errorValidationFields"
42
+ :vm-cpu-help-text-second="props.vmCpuHelpTextSecond"
43
+ @remove-error-by-title="emits('remove-error-by-title', $event)"
44
+ @invalid="cpuInvalid = $event"
45
+ />
46
+ <common-vm-actions-common-customize-hardware-virtual-hardware-memory
47
+ v-model="model"
48
+ :max-memory="props.maxMemory"
49
+ :is-edit="props.isEdit"
50
+ :state="props.state"
51
+ :error-validation-fields="props.errorValidationFields"
52
+ @invalid="memoryInvalid = $event"
53
+ @remove-error-by-title="emits('remove-error-by-title', $event)"
54
+ />
55
+ <div
56
+ v-for="(item, key) in model.disk_devices.filter(
57
+ (disk) => disk.device_type !== 'cdrom'
58
+ )"
59
+ :key="props.hardDisksIndex[key]"
60
+ >
61
+ <!-- TODO 111-->
62
+ <!-- v-model:vm-storage-policy="item.vm_storage_policy"-->
63
+ <common-vm-actions-common-customize-hardware-virtual-hardware-new-hard-disk
64
+ v-model:size="item.size"
65
+ v-model:provision-type="item.provision_type"
66
+ v-model:sharing="item.sharing"
67
+ v-model:shares="item.shares"
68
+ v-model:limit-iops="item.limit_iops"
69
+ v-model:disk-mode="item.disk_mode"
70
+ v-model:cache="item.cache"
71
+ v-model:bus="item.bus"
72
+ v-model:read-only="item.readOnly"
73
+ v-model:discard="item.discard"
74
+ v-model:io="item.io"
75
+ v-model:target="item.target"
76
+ v-model:source="item.source"
77
+ v-model:storage="item.storage"
78
+ :index="props.hardDisksIndex[key]"
79
+ :type="props.hardDisksType[key]"
80
+ :main-storage="props.storage"
81
+ :main-storage-free="props.storageFree"
82
+ :error-validation-fields="props.errorValidationFields"
83
+ :get-datastore-table-func="props.getDatastoreTableFunc"
84
+ :datastore="props.datastore"
85
+ :is-datastore-loading="props.isDatastoreLoading"
86
+ :is-edit="props.isEdit"
87
+ :state="props.state"
88
+ :guest-machine-type="props.guestMachineType"
89
+ :compute-resource="props.computeResource"
90
+ @remove="
91
+ emits('remove-hard-disk', [props.hardDisksIndex[key], item])
92
+ "
93
+ @roll-back="emits('roll-back-hard-disk', props.hardDisksIndex[key])"
94
+ @invalid="
95
+ emits('set-invalid-hard-disk', [
96
+ $event,
97
+ props.hardDisksIndex[key],
98
+ ])
99
+ "
100
+ @remove-error-by-title="emits('remove-error-by-title', $event)"
101
+ />
102
+ </div>
103
+ <div
104
+ v-for="(item, key) in model.network_devices"
105
+ :key="props.networksIndex[key]"
106
+ >
107
+ <common-vm-actions-common-customize-hardware-virtual-hardware-new-network
108
+ v-model:network="item.network"
109
+ v-model:network-value="item[props.networkValueProp]"
110
+ v-model:mac="item.mac"
111
+ v-model:model="item.model"
112
+ :index="props.networksIndex[key]"
113
+ :type="props.networksType[key]"
114
+ :networks-table="props.networksTable"
115
+ :error-validation-fields="props.errorValidationFields"
116
+ :is-edit="props.isEdit"
117
+ :state="props.state"
118
+ :project="props.project"
119
+ :network-value-prop="props.networkValueProp"
120
+ @remove="emits('remove-network', props.networksIndex[key])"
121
+ @invalid="
122
+ emits('set-invalid-network', [$event, props.networksIndex[key]])
123
+ "
124
+ @remove-error-by-title="emits('remove-error-by-title', $event)"
125
+ @get-networks-table="emits('get-networks-table', $event)"
126
+ />
127
+ </div>
128
+ <!-- <common-vm-actions-common-customize-hardware-virtual-hardware-cd-dvd-drive-->
129
+ <!-- v-for="(item, key) in props.cdDvdDrives"-->
130
+ <!-- :key="props.cdDvdDrivesIndex[key]"-->
131
+ <!-- :index="props.cdDvdDrivesIndex[key]"-->
132
+ <!-- :hard-disks-count="props.hardDisks?.length || 0"-->
133
+ <!-- :type="props.cdDvdDrivesType[key]"-->
134
+ <!-- :cd-dvd-drive="item"-->
135
+ <!-- :nodes="props.nodes"-->
136
+ <!-- :files="props.files"-->
137
+ <!-- :error-validation-fields="props.errorValidationFields"-->
138
+ <!-- :is-edit="props.isEdit"-->
139
+ <!-- :state="props.state"-->
140
+ <!-- :guest-machine-type="props.guestMachineType"-->
141
+ <!-- :compute-resource="props.computeResource"-->
142
+ <!-- @remove-error-by-title="emits('remove-error-by-title', $event)"-->
143
+ <!-- @get-storage="emits('get-storage', $event)"-->
144
+ <!-- @get-folders-or-files="emits('get-folders-or-files', $event)"-->
145
+ <!-- @get-active-device-child="emits('get-active-device-child', $event)"-->
146
+ <!-- @show-datastore-child="emits('show-datastore-child', $event)"-->
147
+ <!-- @remove="-->
148
+ <!-- emits('remove-cd-dvd-drive', [props.cdDvdDrivesIndex[key], item])-->
149
+ <!-- "-->
150
+ <!-- @roll-back="-->
151
+ <!-- emits('roll-back-cd-dvd-drive', props.cdDvdDrivesIndex[key])-->
152
+ <!-- "-->
153
+ <!-- @send-data="-->
154
+ <!-- emits('send-data-new-cd-dvd-drive-method', [-->
155
+ <!-- $event,-->
156
+ <!-- props.cdDvdDrivesIndex[key],-->
157
+ <!-- ])-->
158
+ <!-- "-->
159
+ <!-- />-->
160
+ <div
161
+ v-for="(item, key) in model.disk_devices.filter(
162
+ (disk) => disk.device_type === 'cdrom'
163
+ )"
164
+ :key="props.hardDisksIndex[key]"
165
+ >
166
+ <!-- TODO count-->
167
+ <common-vm-actions-common-customize-hardware-virtual-hardware-cd-dvd-drive
168
+ v-model:source="item.source"
169
+ v-model:bus="item.bus"
170
+ v-model:remove="item.remove"
171
+ v-model:detach="item.detach"
172
+ :index="props.cdDvdDrivesIndex[key]"
173
+ :hard-disks-count="
174
+ model.disk_devices.filter((disk) => disk.device_type !== 'cdrom')
175
+ ?.length || 0
176
+ "
177
+ :type="props.cdDvdDrivesType[key]"
178
+ :nodes="props.nodes"
179
+ :files="props.files"
180
+ :error-validation-fields="props.errorValidationFields"
181
+ :is-edit="props.isEdit"
182
+ :state="props.state"
183
+ :guest-machine-type="props.guestMachineType"
184
+ :compute-resource="props.computeResource"
185
+ @remove-error-by-title="emits('remove-error-by-title', $event)"
186
+ @get-storage="emits('get-storage', $event)"
187
+ @get-folders-or-files="emits('get-folders-or-files', $event)"
188
+ @get-active-device-child="emits('get-active-device-child', $event)"
189
+ @show-datastore-child="emits('show-datastore-child', $event)"
190
+ @remove="
191
+ emits('remove-cd-dvd-drive', [props.cdDvdDrivesIndex[key], item])
192
+ "
193
+ @roll-back="
194
+ emits('roll-back-cd-dvd-drive', props.cdDvdDrivesIndex[key])
195
+ "
196
+ @send-data="
197
+ emits('send-data-new-cd-dvd-drive-method', [
198
+ $event,
199
+ props.cdDvdDrivesIndex[key],
200
+ ])
201
+ "
202
+ />
203
+ </div>
204
+ <div v-for="(item, key) in model.passthrough_usb_devices" :key="key">
205
+ <common-vm-actions-common-customize-hardware-virtual-hardware-new-usb-device
206
+ :item="item"
207
+ :index="props.usbDevicesIndex[key]"
208
+ :type="props.usbDevicesType[key]"
209
+ :available-usb-devices="props.availableUsbDevices"
210
+ :state="props.state"
211
+ @change="
212
+ emits('change-usb-device', [props.usbDevicesIndex[key], $event])
213
+ "
214
+ @remove="emits('remove-usb-device', props.usbDevicesIndex[key])"
215
+ />
216
+ </div>
217
+ <div v-for="(item, key) in model.usb_controllers" :key="key">
218
+ <common-vm-actions-common-customize-hardware-virtual-hardware-new-usb-controller
219
+ v-model="item.type"
220
+ :is-edit="props.isEdit"
221
+ :state="props.state"
222
+ />
223
+ </div>
224
+ <template v-if="props.passthroughDevices || props.mediatedDevices">
225
+ <common-vm-actions-common-customize-hardware-virtual-hardware-new-pci-device
226
+ v-for="(item, key) in model.passthrough_pci_devices"
227
+ v-model:address="item.address"
228
+ v-model:vendor_name="item.vendor_name"
229
+ v-model:class_name="item.class_name"
230
+ v-model:device_name="item.device_name"
231
+ v-model:mediated_device="item.mediated_device"
232
+ v-model:mediated_device_uuid="item.mediated_device_uuid"
233
+ :key="props.pciDevicesIndex[key]"
234
+ :index="props.pciDevicesIndex[key]"
235
+ :passthrough-devices="props.passthroughDevices"
236
+ :mediated-devices="props.mediatedDevices"
237
+ :type="props.pciDevicesType[key]"
238
+ :is-edit="props.isEdit"
239
+ :state="props.state"
240
+ @remove="emits('remove-pci-device', props.pciDevicesIndex[key])"
241
+ />
242
+ </template>
243
+ <common-vm-actions-common-customize-hardware-virtual-hardware-video-card
244
+ v-model="model.video_card"
245
+ :is-edit="props.isEdit"
246
+ :video-card="props.videoCard"
247
+ :error-validation-fields="props.errorValidationFields"
248
+ :state="props.state"
249
+ @invalid="videoCardInvalid = $event"
250
+ @remove-error-by-title="emits('remove-error-by-title', $event)"
251
+ />
252
+ <common-vm-actions-common-customize-hardware-virtual-hardware-other />
253
+ <div
254
+ v-if="props.compatibilityInfo"
255
+ class="vm-hardware-version-wrap justify-end flex flex-1 mt-1"
256
+ >
257
+ <span class="vm-hardware-version">
258
+ {{ props.compatibilityInfo }}
259
+ </span>
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ <Teleport to="body">
265
+ <common-vm-actions-common-customize-hardware-virtual-hardware-browse-view
266
+ v-if="props.isShowFileModal"
267
+ :nodes="props.nodes"
268
+ :files="props.files"
269
+ show
270
+ @submit="emits('add-exist-hard-disk', $event)"
271
+ @get-storage="emits('get-storage', $event)"
272
+ @get-folders-or-files="emits('get-folders-or-files', $event)"
273
+ @get-active-device-child="emits('get-active-device-child', $event)"
274
+ @show-datastore-child="emits('show-datastore-child', $event)"
275
+ @hide="emits('hide-file-modal')"
276
+ />
277
+ </Teleport>
278
+ </div>
279
+ </template>
280
+
281
+ <script setup lang="ts">
282
+ import type { UI_I_Dropdown } from '~/node_modules/bfg-uikit/components/ui/dropdown/models/interfaces'
283
+ import type { UI_T_Project } from '~/lib/models/types'
284
+ import type { UI_I_DatastoreTableItem } from '~/lib/models/store/storage/interfaces'
285
+ import type { UI_I_NetworkTableItem } from '~/lib/models/store/network/interfaces'
286
+ import type { UI_I_TablePayload } from '~/lib/models/table/interfaces'
287
+ import type { UI_I_FolderOrFileTreePayload } from '~/lib/models/store/storage/interfaces'
288
+ import type { UI_I_FileTreeNode } from '~/components/lib/models/interfaces'
289
+ import type {
290
+ UI_I_SendDataNewHardDisk,
291
+ UI_I_SendDataVideoCard,
292
+ UI_I_SendDataNewCdDvdDrive,
293
+ UI_I_SendDataNewPciDevice,
294
+ } from '~/components/common/vm/actions/common/customizeHardware/virtualHardware/lib/models/interfaces'
295
+ import type { UI_I_ErrorValidationField } from '~/lib/models/store/interfaces'
296
+ import type {
297
+ UI_T_HardDiskType,
298
+ UI_T_NetworkType,
299
+ UI_T_PciDeviceType,
300
+ } from '~/components/common/vm/actions/common/customizeHardware/virtualHardware/lib/models/types'
301
+ import type { UI_I_OptionItem } from '~/components/atoms/lib/models/interfaces'
302
+ import type { UI_I_Localization } from '~/lib/models/interfaces'
303
+ import type {
304
+ API_UI_I_VmEditCpu,
305
+ UI_I_MediatedDevice,
306
+ UI_I_PciDevice,
307
+ } from '~/lib/models/store/vm/interfaces'
308
+ import type { UI_I_TreeNode } from '~/components/common/recursionTree/lib/models/interfaces'
309
+ import type { UI_I_CreateVmData } from '~/components/common/vm/actions/common/lib/models/interfaces'
310
+ import { dropdownItemsNewFunc } from './lib/config/dropdownItems'
311
+
312
+ const model = defineModel<UI_I_CreateVmData>({ required: true })
313
+
314
+ const cpuInvalid = defineModel<boolean>('cpuInvalid', { required: true })
315
+ const memoryInvalid = defineModel<boolean>('memoryInvalid', { required: true })
316
+ const videoCardInvalid = defineModel<boolean>('videoCardInvalid', {
317
+ required: true,
318
+ })
319
+
320
+ const props = withDefaults(
321
+ defineProps<{
322
+ nodes: UI_I_FileTreeNode[]
323
+ files: UI_I_FileTreeNode[]
324
+ isEdit: boolean
325
+ storage: UI_I_DatastoreTableItem | null
326
+ storageFree: number
327
+ project: UI_T_Project
328
+ maxMemory: number
329
+ cpuModels: UI_I_OptionItem[]
330
+ datastore: UI_I_DatastoreTableItem[]
331
+ isDatastoreLoading: boolean
332
+ networksType: UI_T_NetworkType[]
333
+ networksTable: UI_I_NetworkTableItem[]
334
+ // dropdownItems: UI_I_DropdownTreeItem[]
335
+ hardDisksType: UI_T_HardDiskType[]
336
+ networksIndex: number[]
337
+ hardDisksIndex: number[]
338
+ pciDevicesType: UI_T_NetworkType[]
339
+ isShowFileModal: boolean
340
+ pciDevicesIndex: number[]
341
+ availableUsbDevices: any[]
342
+ usbDevicesType: UI_T_PciDeviceType[]
343
+ usbDevicesIndex: number[]
344
+ cdDvdDrivesType: UI_T_NetworkType[]
345
+ cdDvdDrivesIndex: number[]
346
+ errorValidationFields: UI_I_ErrorValidationField<string>[]
347
+ getDatastoreTableFunc: (payload: UI_I_TablePayload) => Promise<void>
348
+ networkValueProp: 'network' | 'net_bridge'
349
+ cpu?: API_UI_I_VmEditCpu
350
+ state?: string | number
351
+ // hardDisks?: UI_I_SendDataNewHardDisk[] | null
352
+ videoCard?: UI_I_SendDataVideoCard
353
+ // cdDvdDrives?: UI_I_SendDataNewCdDvdDrive[] | null
354
+ guestMachineType?: UI_I_OptionItem | null
355
+ passthroughDevices?: UI_I_PciDevice[]
356
+ mediatedDevices?: UI_I_MediatedDevice[]
357
+ vmCpuHelpTextSecond?: string
358
+ computeResource?: UI_I_TreeNode | null
359
+ compatibilityInfo?: string
360
+ }>(),
361
+ {
362
+ cpu: undefined,
363
+ state: undefined,
364
+ videoCard: undefined,
365
+ guestMachineType: undefined,
366
+ passthroughDevices: undefined,
367
+ mediatedDevices: undefined,
368
+ vmCpuHelpTextSecond: undefined,
369
+ computeResource: undefined,
370
+ compatibilityInfo: undefined,
371
+ }
372
+ )
373
+ const emits = defineEmits<{
374
+ (event: 'add-device', value: { value: number }): void
375
+ (event: 'get-storage', value: UI_I_TablePayload): void
376
+ (event: 'remove-network', value: number): void
377
+ (event: 'hide-file-modal'): void
378
+ (event: 'remove-hard-disk', value: [number, UI_I_SendDataNewHardDisk]): void
379
+ (event: 'remove-pci-device', value: number): void
380
+ (event: 'change-usb-device', value: [number, any]): void
381
+ (event: 'remove-usb-device', value: number): void
382
+ (event: 'get-networks-table', value: UI_I_TablePayload): void
383
+ (event: 'add-exist-hard-disk', value: UI_I_FileTreeNode): void
384
+ (event: 'roll-back-hard-disk', value: number): void
385
+ (event: 'set-invalid-network', value: [boolean, number]): void
386
+ (
387
+ event: 'remove-cd-dvd-drive',
388
+ value: [number, UI_I_SendDataNewCdDvdDrive]
389
+ ): void
390
+ (event: 'show-datastore-child', value: UI_I_FileTreeNode): void
391
+ (event: 'get-folders-or-files', value: UI_I_FolderOrFileTreePayload): void
392
+ (event: 'set-invalid-hard-disk', value: [boolean, number]): void
393
+ (event: 'remove-error-by-title', value: string): void
394
+ (event: 'roll-back-cd-dvd-drive', value: number): void
395
+ (event: 'get-active-device-child', value: UI_I_FileTreeNode): void
396
+ (
397
+ event: 'send-data-new-cd-dvd-drive-method',
398
+ value: [UI_I_SendDataNewCdDvdDrive, number]
399
+ ): void
400
+ }>()
401
+
402
+ const { $text } = useNuxtApp()
403
+
404
+ const localization = computed<UI_I_Localization>(() => useLocal())
405
+
406
+ const isShowAddDeviceDropdown = ref<boolean>(false)
407
+
408
+ // TODO
409
+ const addNewDeviceText = computed<string>(() =>
410
+ $text.toCapitalize(localization.value.common.addNewDevice)
411
+ )
412
+ const deviceCount = computed<number>(
413
+ () =>
414
+ // (props.hardDisks?.length || 0) +
415
+ // (props.networks?.length || 0) +
416
+ // (props.cdDvdDrives?.length || 0) +
417
+ // (props.pciDevices?.length || 0) +
418
+ (model.value.disk_devices?.length || 0) +
419
+ (model.value.network_devices?.length || 0) +
420
+ (model.value.passthrough_pci_devices?.length || 0) +
421
+ (model.value.passthrough_usb_devices?.length || 0) +
422
+ 6
423
+ )
424
+ const deviceCountText = computed<string>(() =>
425
+ localization.value.vmWizard.devices.replace(
426
+ '{0}',
427
+ deviceCount.value.toString()
428
+ )
429
+ )
430
+ const dropdownItems = computed<UI_I_Dropdown[]>(() =>
431
+ dropdownItemsNewFunc(
432
+ localization.value,
433
+ props.state,
434
+ props.passthroughDevices,
435
+ props.mediatedDevices
436
+ )
437
+ )
438
+
439
+ const onAddDevice = (data: number): void => {
440
+ isShowAddDeviceDropdown.value = false
441
+ // из-за старого компонента
442
+ emits('add-device', { value: data })
443
+ }
444
+ </script>
445
+
446
+ <style>
447
+ :root {
448
+ --vm-wizard-block-view-bg-color: #fafafa;
449
+ --add-device-dropdown-bg-color: #fff;
450
+ --add-device-dropdown-bg-color-hover: #f6f7f8;
451
+ --add-device-dropdown-active-bg-color-hover: #f0f8fd;
452
+ --add-device-dropdown-border-color: #e9ebed;
453
+ --add-device-dropdown-border-color-hover: #e9ebed;
454
+ --add-device-dropdown-color: #4d5d69;
455
+ --add-device-dropdown-color-hover: #4d5d69;
456
+ --add-device-dropdown-item-title-color: #182531;
457
+ }
458
+ :root.dark-theme {
459
+ --vm-wizard-block-view-bg-color: transparent;
460
+ --add-device-dropdown-bg-color: #1b2a373d;
461
+ //--add-device-dropdown-bg-color-hover: #2585b614;
462
+ --add-device-dropdown-bg-color-hover: #b8bcc10a;
463
+ --add-device-dropdown-active-bg-color-hover: #2785b614;
464
+ --add-device-dropdown-border-color: #e9ebed1f;
465
+ --add-device-dropdown-border-color-hover: #2ba2de;
466
+ --add-device-dropdown-color: #e9eaec;
467
+ --add-device-dropdown-color-hover: #2ba2de;
468
+ --add-device-dropdown-item-title-color: #e9eaec;
469
+ }
470
+ </style>
471
+
472
+ <style scoped lang="scss">
473
+ .virtual-hardware {
474
+ height: 100%;
475
+ display: grid;
476
+
477
+ .action-wrap {
478
+ display: flex;
479
+ align-items: center;
480
+ justify-content: space-between;
481
+ //padding: 12px;
482
+
483
+ .device-count {
484
+ font-size: 13px;
485
+ font-weight: 500;
486
+ line-height: 15.73px;
487
+ color: #9da6ad;
488
+ }
489
+
490
+ .add-device-dropdown-wrap {
491
+ #add-device-dropdown {
492
+ display: flex;
493
+ align-items: center;
494
+ gap: 8px;
495
+ background-color: var(--add-device-dropdown-bg-color);
496
+ border: 1px solid var(--add-device-dropdown-border-color);
497
+ border-radius: 6px;
498
+ padding: 7px 12px;
499
+ color: var(--add-device-dropdown-color);
500
+ font-size: 13px;
501
+ font-weight: 500;
502
+ line-height: 15.73px;
503
+ height: 32px;
504
+
505
+ &:hover {
506
+ background-color: var(--add-device-dropdown-bg-color-hover);
507
+ border-color: var(--add-device-dropdown-border-color-hover);
508
+ color: var(--add-device-dropdown-color-hover);
509
+ }
510
+
511
+ &.active {
512
+ color: #008fd6;
513
+ border-color: #008fd6;
514
+
515
+ &:hover {
516
+ background-color: var(--add-device-dropdown-active-bg-color-hover);
517
+ }
518
+ }
519
+
520
+ .add-device-dropdown-text {
521
+ font-weight: 500;
522
+ font-family: 'Inter', sans-serif;
523
+ text-transform: capitalize;
524
+ }
525
+ }
526
+ :deep(.ui-popup-window) {
527
+ overflow: hidden;
528
+ padding: 8px 0;
529
+
530
+ .ui-dropdown {
531
+ overflow: auto;
532
+ //max-height: calc(300px - 24px);
533
+
534
+ .ui-dropdown-menu.md {
535
+ padding: 0 8px;
536
+
537
+ li {
538
+ &.has-items .group-title {
539
+ padding: 4px 8px 0;
540
+ color: var(--add-device-dropdown-item-title-color);
541
+ }
542
+ &:not(:last-child) {
543
+ margin-bottom: 4px;
544
+ }
545
+ //&:last-child ul li:last-child {
546
+ //height: 26px;
547
+ //align-items: flex-end;
548
+ //}
549
+ }
550
+ }
551
+ }
552
+ }
553
+ }
554
+ }
555
+ }
556
+ .stack-view {
557
+ display: flex;
558
+ flex-direction: column;
559
+ gap: 12px;
560
+ padding-bottom: 16px;
561
+ background-color: var(--vm-wizard-block-view-bg-color);
562
+
563
+ .vm-hardware-version-wrap {
564
+ align-items: flex-end;
565
+ text-align: right;
566
+
567
+ .vm-hardware-version {
568
+ font-size: 13px;
569
+ color: #9da6ad; // for light and dark
570
+ }
571
+ }
572
+ }
573
+ </style>