foreman_fog_proxmox 0.15.1 → 0.16.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/proxmox_compute_resources_helper.rb +1 -1
  3. data/app/helpers/proxmox_form_helper.rb +2 -2
  4. data/app/helpers/proxmox_vm_attrs_helper.rb +131 -0
  5. data/app/helpers/proxmox_vm_interfaces_helper.rb +3 -3
  6. data/app/helpers/proxmox_vm_volumes_helper.rb +3 -3
  7. data/app/models/concerns/fog_extensions/proxmox/node.rb +1 -1
  8. data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +3 -3
  9. data/app/models/foreman_fog_proxmox/proxmox_images.rb +5 -0
  10. data/app/models/foreman_fog_proxmox/proxmox_version.rb +1 -1
  11. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +1 -1
  12. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
  13. data/app/overrides/compute_resources_vms/form/add_from_profile_to_compute_attributes_form.rb +8 -0
  14. data/app/overrides/compute_resources_vms/form/add_react_component_to_host.rb +25 -0
  15. data/app/overrides/compute_resources_vms/form/update_react_component_to_host_form.rb +25 -0
  16. data/app/views/compute_resources_vms/form/proxmox/_add_react_component_to_host_form.html.erb +5 -0
  17. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +21 -21
  18. data/app/views/compute_resources_vms/form/proxmox/_update_react_component_to_host_form.html.erb +26 -0
  19. data/config/routes.rb +3 -3
  20. data/lib/foreman_fog_proxmox/version.rb +1 -1
  21. data/package.json +42 -0
  22. data/test/factories/proxmox_factory.rb +7 -7
  23. data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +1 -1
  24. data/test/unit/foreman_fog_proxmox/proxmox_interfaces_test.rb +6 -6
  25. data/webpack/components/GeneralTabContent.js +107 -0
  26. data/webpack/components/ProxmoxComputeSelectors.js +141 -0
  27. data/webpack/components/ProxmoxContainer/MountPoint.js +91 -0
  28. data/webpack/components/ProxmoxContainer/ProxmoxContainerHardware.js +85 -0
  29. data/webpack/components/ProxmoxContainer/ProxmoxContainerNetwork.js +179 -0
  30. data/webpack/components/ProxmoxContainer/ProxmoxContainerOptions.js +104 -0
  31. data/webpack/components/ProxmoxContainer/ProxmoxContainerStorage.js +194 -0
  32. data/webpack/components/ProxmoxContainer/components/NetworkInterface.js +193 -0
  33. data/webpack/components/ProxmoxServer/ProxmoxServerHardware.js +204 -0
  34. data/webpack/components/ProxmoxServer/ProxmoxServerNetwork.js +161 -0
  35. data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +105 -0
  36. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +272 -0
  37. data/webpack/components/ProxmoxServer/components/CDRom.js +149 -0
  38. data/webpack/components/ProxmoxServer/components/CPUFlagsModal.js +88 -0
  39. data/webpack/components/ProxmoxServer/components/HardDisk.js +143 -0
  40. data/webpack/components/ProxmoxServer/components/NetworkInterface.js +150 -0
  41. data/webpack/components/ProxmoxStoragesUtils.js +50 -0
  42. data/webpack/components/ProxmoxVmType.js +256 -0
  43. data/webpack/components/ProxmoxVmUtils.js +62 -0
  44. data/webpack/components/common/FormInputs.js +143 -0
  45. data/webpack/global_index.js +15 -0
  46. data/webpack/index.js +7 -0
  47. metadata +49 -21
@@ -0,0 +1,161 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { Title, PageSection, Button } from '@patternfly/react-core';
3
+ import { TimesIcon } from '@patternfly/react-icons';
4
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
5
+ import PropTypes from 'prop-types';
6
+ import NetworkInterface from './components/NetworkInterface';
7
+
8
+ const ProxmoxServerNetwork = ({ network, bridges, paramScope }) => {
9
+ const [interfaces, setInterfaces] = useState([]);
10
+ const [nextId, setNextId] = useState(0);
11
+ const [availableIds, setAvailableIds] = useState([]);
12
+ const [usedIds, setUsedIds] = useState(new Set());
13
+ useEffect(() => {
14
+ if (network?.length > 0) {
15
+ const existingIds = new Set();
16
+ network.forEach(net => {
17
+ if (!net.value.model.value) return;
18
+ const id = parseInt(net.value.id.value.replace('net', ''), 10);
19
+ existingIds.add(id);
20
+ addInterface(null, net.value);
21
+ });
22
+ setUsedIds(existingIds);
23
+ }
24
+ }, [network]);
25
+
26
+ const getLowestAvailableId = useCallback(() => {
27
+ let id = 0;
28
+ while (usedIds.has(id)) {
29
+ id += 1;
30
+ }
31
+ return id;
32
+ }, [usedIds]);
33
+ const addInterface = useCallback(
34
+ (event, initialData = null) => {
35
+ if (event) event.preventDefault();
36
+ const netId = getLowestAvailableId();
37
+ const initData = initialData || {
38
+ id: {
39
+ name: `${paramScope}[interfaces_attributes][${nextId}][id]`,
40
+ value: `net${netId}`,
41
+ },
42
+ model: {
43
+ name: `${paramScope}[interfaces_attributes][${nextId}][model]`,
44
+ value: 'virtio',
45
+ },
46
+ bridge: {
47
+ name: `${paramScope}[interfaces_attributes][${nextId}][bridge]`,
48
+ value: bridges?.[0]?.iface || '',
49
+ },
50
+ tag: {
51
+ name: `${paramScope}[interfaces_attributes][${nextId}][tag]`,
52
+ value: '',
53
+ },
54
+ rate: {
55
+ name: `${paramScope}[interfaces_attributes][${nextId}][rate]`,
56
+ value: '',
57
+ },
58
+ queues: {
59
+ name: `${paramScope}[interfaces_attributes][${nextId}][queues]`,
60
+ value: '',
61
+ },
62
+ firewall: {
63
+ name: `${paramScope}[interfaces_attributes][${nextId}][firewall]`,
64
+ value: '0',
65
+ },
66
+ link_down: {
67
+ name: `${paramScope}[interfaces_attributes][${nextId}][link_down]`,
68
+ value: '0',
69
+ },
70
+ };
71
+ setNextId(prevId => {
72
+ if (availableIds.length > 0) {
73
+ setAvailableIds(availableIds.slice(1));
74
+ } else {
75
+ prevId += 1;
76
+ }
77
+ setUsedIds(prevIds => new Set(prevIds).add(netId));
78
+ const newId = availableIds.length > 0 ? availableIds[0] : prevId;
79
+ const newInterface = {
80
+ id: newId,
81
+ data: initData,
82
+ networks: network,
83
+ };
84
+
85
+ setInterfaces(prevInterfaces => [...prevInterfaces, newInterface]);
86
+ return prevId;
87
+ });
88
+ },
89
+ [bridges, network, paramScope, availableIds, nextId, getLowestAvailableId]
90
+ );
91
+
92
+ const removeInterface = idToRemove => {
93
+ const newInterfaces = interfaces.filter(nic => nic.id !== idToRemove);
94
+ setInterfaces(newInterfaces);
95
+ setAvailableIds([...availableIds, idToRemove].sort((a, b) => a - b));
96
+ setUsedIds(prevIds => {
97
+ const newIds = new Set(prevIds);
98
+ newIds.delete(idToRemove);
99
+ return newIds;
100
+ });
101
+ };
102
+
103
+ const updateNetworkData = (id, updatedData) => {
104
+ setInterfaces(
105
+ interfaces.map(net =>
106
+ net.id === id ? { ...net, data: updatedData } : net
107
+ )
108
+ );
109
+ };
110
+
111
+ return (
112
+ <div>
113
+ <PageSection padding={{ default: 'noPadding' }}>
114
+ <Button onClick={addInterface} variant="secondary">
115
+ {__('Add Interface')}
116
+ </Button>
117
+ {interfaces.map(nic => (
118
+ <div key={nic.id} style={{ position: 'relative' }}>
119
+ <div
120
+ style={{
121
+ marginTop: '10px',
122
+ display: 'flex',
123
+ justifyContent: 'space-between',
124
+ alignItems: 'center',
125
+ }}
126
+ >
127
+ <Title headingLevel="h4">
128
+ {sprintf(__('Nic %(nicId)s'), { nicId: nic.id })}
129
+ </Title>
130
+ <button onClick={() => removeInterface(nic.id)} type="button">
131
+ <TimesIcon />
132
+ </button>
133
+ </div>
134
+ <NetworkInterface
135
+ id={nic.id}
136
+ data={nic.data}
137
+ bridges={bridges}
138
+ networks={nic.networks}
139
+ updateNetworkData={updateNetworkData}
140
+ existingInterfaces={interfaces}
141
+ />
142
+ </div>
143
+ ))}
144
+ </PageSection>
145
+ </div>
146
+ );
147
+ };
148
+
149
+ ProxmoxServerNetwork.propTypes = {
150
+ network: PropTypes.object,
151
+ bridges: PropTypes.array,
152
+ paramScope: PropTypes.string,
153
+ };
154
+
155
+ ProxmoxServerNetwork.defaultProps = {
156
+ network: {},
157
+ bridges: [],
158
+ paramScope: '',
159
+ };
160
+
161
+ export default ProxmoxServerNetwork;
@@ -0,0 +1,105 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import InputField from '../common/FormInputs';
5
+ import ProxmoxComputeSelectors from '../ProxmoxComputeSelectors';
6
+
7
+ const ProxmoxServerOptions = ({ options }) => {
8
+ const [opts, setOpts] = useState(options);
9
+
10
+ const handleChange = e => {
11
+ const { name, type, checked, value: targetValue } = e.target;
12
+ let value;
13
+ if (type === 'checkbox') {
14
+ value = checked ? '1' : '0';
15
+ } else {
16
+ value = targetValue;
17
+ }
18
+ const updatedKey = Object.keys(opts).find(key => opts[key].name === name);
19
+ setOpts(prevOpts => ({
20
+ ...prevOpts,
21
+ [updatedKey]: { ...prevOpts[updatedKey], value },
22
+ }));
23
+ };
24
+
25
+ return (
26
+ <div>
27
+ <InputField
28
+ name={opts?.boot?.name}
29
+ label={__('Boot device order')}
30
+ info={__(
31
+ 'Order your devices, e.g. order=net0;ide2;scsi0. Default empty (any)'
32
+ )}
33
+ value={opts?.boot?.value}
34
+ onChange={handleChange}
35
+ />
36
+ <InputField
37
+ name={opts?.onboot?.name}
38
+ label={__('Start at boot')}
39
+ type="checkbox"
40
+ value={opts?.onboot?.value}
41
+ checked={opts?.onboot?.value === '1'}
42
+ onChange={handleChange}
43
+ />
44
+ <InputField
45
+ name={opts?.agent?.name}
46
+ label={__('Qemu Agent')}
47
+ type="checkbox"
48
+ value={opts?.agent?.value}
49
+ checked={opts?.agent?.value === '1'}
50
+ onChange={handleChange}
51
+ />
52
+ <InputField
53
+ name={opts?.kvm?.name}
54
+ label={__('KVM')}
55
+ info={__('Enable/disable KVM hardware virtualization')}
56
+ type="checkbox"
57
+ value={opts?.kvm?.value}
58
+ checked={opts?.kvm?.value === '1'}
59
+ onChange={handleChange}
60
+ />
61
+ <InputField
62
+ name={opts?.vga?.name}
63
+ label={__('VGA')}
64
+ type="select"
65
+ value={opts?.vga?.value}
66
+ options={ProxmoxComputeSelectors.proxmoxVgasMap}
67
+ onChange={handleChange}
68
+ />
69
+ <InputField
70
+ name={opts?.scsihw?.name}
71
+ label={__('SCSI Controller')}
72
+ type="select"
73
+ value={opts?.scsihw?.value}
74
+ options={ProxmoxComputeSelectors.proxmoxScsiControllersMap}
75
+ onChange={handleChange}
76
+ />
77
+ <InputField
78
+ name={opts?.bios?.name}
79
+ label={__('BIOS')}
80
+ type="select"
81
+ options={ProxmoxComputeSelectors.proxmoxBiosMap}
82
+ value={opts?.bios?.value}
83
+ onChange={handleChange}
84
+ />
85
+ <InputField
86
+ name={opts?.ostype?.name}
87
+ label={__('OS Type')}
88
+ type="select"
89
+ options={ProxmoxComputeSelectors.proxmoxOperatingSystemsMap}
90
+ value={opts?.ostype?.value}
91
+ onChange={handleChange}
92
+ />
93
+ </div>
94
+ );
95
+ };
96
+
97
+ ProxmoxServerOptions.propTypes = {
98
+ options: PropTypes.object,
99
+ };
100
+
101
+ ProxmoxServerOptions.defaultProps = {
102
+ options: {},
103
+ };
104
+
105
+ export default ProxmoxServerOptions;
@@ -0,0 +1,272 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Title, PageSection, Button } from '@patternfly/react-core';
4
+ import { TimesIcon } from '@patternfly/react-icons';
5
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
6
+ import HardDisk from './components/HardDisk';
7
+ import CDRom from './components/CDRom';
8
+
9
+ const ProxmoxServerStorage = ({ storage, storages, paramScope, nodeId }) => {
10
+ const [hardDisks, setHardDisks] = useState([]);
11
+ const [nextId, setNextId] = useState(0);
12
+ const [cdRom, setCdRom] = useState(false);
13
+ const [cdRomData, setCdRomData] = useState(null);
14
+ const [nextDeviceNumbers, setNextDeviceNumbers] = useState({
15
+ ide: 0,
16
+ sata: 0,
17
+ scsi: 0,
18
+ virtio: 0,
19
+ });
20
+ const controllerRanges = {
21
+ ide: { min: 0, max: 3 },
22
+ sata: { min: 0, max: 5 },
23
+ scsi: { min: 0, max: 30 },
24
+ virtio: { min: 0, max: 15 },
25
+ };
26
+
27
+ useEffect(() => {
28
+ if (storage?.length > 0) {
29
+ const updatedCounts = { ...nextDeviceNumbers };
30
+ storage.forEach(disk => {
31
+ if (disk.name === 'hard_disk') {
32
+ const controller = disk.value.controller.value;
33
+ const device = parseInt(disk.value.device.value, 10);
34
+ if (device >= updatedCounts[controller]) {
35
+ updatedCounts[controller] = device + 1;
36
+ }
37
+ addHardDisk(null, disk.value, true);
38
+ }
39
+ if (disk.name === 'cdrom') {
40
+ addCDRom(null, disk.value, true);
41
+ }
42
+ });
43
+ setNextDeviceNumbers(updatedCounts);
44
+ }
45
+ }, [storage]);
46
+
47
+ const getNextDevice = useCallback(
48
+ (controller, type = null) => {
49
+ const isDevice2Reserved = controller === 'ide' && type !== 'cdrom';
50
+ for (
51
+ let i = controllerRanges[controller].min;
52
+ i <= controllerRanges[controller].max;
53
+ i++
54
+ ) {
55
+ if (!(isDevice2Reserved && i === 2)) {
56
+ if (
57
+ !hardDisks.some(
58
+ disk =>
59
+ disk.data.controller.value === controller &&
60
+ disk.data.device.value === i
61
+ )
62
+ ) {
63
+ return i;
64
+ }
65
+ }
66
+ }
67
+ return null;
68
+ },
69
+ [hardDisks, controllerRanges]
70
+ );
71
+
72
+ const createUniqueDevice = useCallback(
73
+ (type, selectedController = 'virtio') => {
74
+ let controller = selectedController;
75
+ if (type === 'cdrom') {
76
+ controller = 'ide';
77
+ return { controller, device: 2, id: 'ide2' };
78
+ }
79
+
80
+ const device = getNextDevice(controller, type);
81
+ if (device !== null) {
82
+ const id = `${controller}${device}`;
83
+ const newCounts = {
84
+ ...nextDeviceNumbers,
85
+ [controller]: nextDeviceNumbers[controller] + 1,
86
+ };
87
+ setNextDeviceNumbers(newCounts);
88
+ return { controller, device, id };
89
+ }
90
+ return null;
91
+ },
92
+ [getNextDevice, nextDeviceNumbers]
93
+ );
94
+
95
+ const addHardDisk = useCallback(
96
+ (event, initialData = null, isPreExisting = false) => {
97
+ if (event) event.preventDefault();
98
+ let deviceInfo = null;
99
+ if (!isPreExisting) {
100
+ const selectedController = initialData?.controller?.value || 'virtio';
101
+ deviceInfo = createUniqueDevice('hard_disk', selectedController);
102
+ if (!deviceInfo) return;
103
+ }
104
+ const { controller, device, id } = deviceInfo || {};
105
+ const initHdd = initialData || {
106
+ id: {
107
+ name: `${paramScope}[volumes_attributes][${nextId}][id]`,
108
+ value: id,
109
+ },
110
+ device: {
111
+ name: `${paramScope}[volumes_attributes][${nextId}][device]`,
112
+ value: device,
113
+ },
114
+ storageType: {
115
+ name: `${paramScope}[volumes_attributes][${nextId}][storage_type]`,
116
+ value: 'hard_disk',
117
+ },
118
+ storage: {
119
+ name: `${paramScope}[volumes_attributes][${nextId}][storage]`,
120
+ value: 'local',
121
+ },
122
+ cache: {
123
+ name: `${paramScope}[volumes_attributes][${nextId}][cache]`,
124
+ value: null,
125
+ },
126
+ size: {
127
+ name: `${paramScope}[volumes_attributes][${nextId}][size]`,
128
+ value: 8,
129
+ },
130
+ controller: {
131
+ name: `${paramScope}[volumes_attributes][${nextId}][controller]`,
132
+ value: controller,
133
+ },
134
+ };
135
+
136
+ setNextId(prevNextId => {
137
+ const newNextId = prevNextId + 1;
138
+ const newHardDisk = {
139
+ id: newNextId,
140
+ storages,
141
+ data: initHdd,
142
+ disks: storage,
143
+ };
144
+ setHardDisks(prevHardDisks => [...prevHardDisks, newHardDisk]);
145
+ return newNextId;
146
+ });
147
+ },
148
+ [nextId, paramScope, storage, storages, createUniqueDevice]
149
+ );
150
+
151
+ const removeHardDisk = idToRemove => {
152
+ const newHardDisks = hardDisks.filter(
153
+ hardDisk => hardDisk.id !== idToRemove
154
+ );
155
+ setHardDisks(newHardDisks);
156
+ };
157
+
158
+ const updateHardDiskData = (id, updatedData) => {
159
+ setHardDisks(
160
+ hardDisks.map(disk =>
161
+ disk.id === id ? { ...disk, data: updatedData } : disk
162
+ )
163
+ );
164
+ };
165
+
166
+ const addCDRom = useCallback(
167
+ (event, initialData = null, isPreExisting = false) => {
168
+ if (event) event.preventDefault();
169
+ if (!initialData && cdRom) return;
170
+
171
+ const deviceInfo = initialData
172
+ ? { controller: 'ide', device: 2, id: 'ide2' }
173
+ : createUniqueDevice('cdrom');
174
+ if (!deviceInfo) return;
175
+
176
+ const initCDRom = initialData || {
177
+ id: {
178
+ name: `${paramScope}[volumes_attributes][${nextId}][id]`,
179
+ value: deviceInfo.id,
180
+ },
181
+ volid: {
182
+ name: `${paramScope}[volumes_attributes][${nextId}][volid]`,
183
+ value: '',
184
+ },
185
+ storageType: {
186
+ name: `${paramScope}[volumes_attributes][${nextId}][storageType]`,
187
+ value: 'cdrom',
188
+ },
189
+ storage: {
190
+ name: `${paramScope}[volumes_attributes][${nextId}][storage]`,
191
+ value: 'local',
192
+ },
193
+ cdrom: {
194
+ name: `${paramScope}[volumes_attributes][${nextId}][cdrom]`,
195
+ value: '',
196
+ },
197
+ };
198
+ setCdRom(true);
199
+ setCdRomData(initCDRom);
200
+ },
201
+ [cdRom, nextId, paramScope, createUniqueDevice]
202
+ );
203
+
204
+ const removeCDRom = () => {
205
+ setCdRom(false);
206
+ };
207
+ return (
208
+ <div>
209
+ <PageSection padding={{ default: 'noPadding' }}>
210
+ <Button onClick={addCDRom} variant="secondary" isDisabled={cdRom}>
211
+ {' '}
212
+ {__('Add CD-ROM')}
213
+ </Button>
214
+ {' '}
215
+ <Button onClick={addHardDisk} variant="secondary">
216
+ {__('Add HardDisk')}
217
+ </Button>
218
+ {cdRom && cdRomData && (
219
+ <CDRom
220
+ onRemove={removeCDRom}
221
+ data={cdRomData}
222
+ storages={storages}
223
+ nodeId={nodeId}
224
+ />
225
+ )}
226
+ {hardDisks.map(hardDisk => (
227
+ <div style={{ position: 'relative' }}>
228
+ <div
229
+ style={{
230
+ marginTop: '10px',
231
+ display: 'flex',
232
+ justifyContent: 'space-between',
233
+ alignItems: 'center',
234
+ }}
235
+ >
236
+ <Title headingLevel="h4">
237
+ {sprintf(__('Hard Disk %(hddId)s'), { hddId: hardDisk.id })}
238
+ </Title>
239
+ <button onClick={() => removeHardDisk(hardDisk.id)} type="button">
240
+ <TimesIcon />
241
+ </button>
242
+ </div>
243
+ <HardDisk
244
+ id={hardDisk.id}
245
+ data={hardDisk.data}
246
+ storages={hardDisk.storages}
247
+ disks={hardDisk.disks}
248
+ updateHardDiskData={updateHardDiskData}
249
+ createUniqueDevice={createUniqueDevice}
250
+ />
251
+ </div>
252
+ ))}
253
+ </PageSection>
254
+ </div>
255
+ );
256
+ };
257
+
258
+ ProxmoxServerStorage.propTypes = {
259
+ storage: PropTypes.object,
260
+ storages: PropTypes.array,
261
+ paramScope: PropTypes.string,
262
+ nodeId: PropTypes.string,
263
+ };
264
+
265
+ ProxmoxServerStorage.defaultProps = {
266
+ storage: {},
267
+ storages: [],
268
+ paramScope: '',
269
+ nodeId: '',
270
+ };
271
+
272
+ export default ProxmoxServerStorage;
@@ -0,0 +1,149 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Title, Divider, PageSection, Radio } from '@patternfly/react-core';
4
+ import { TimesIcon } from '@patternfly/react-icons';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import { imagesByStorage, createStoragesMap } from '../../ProxmoxStoragesUtils';
7
+ import InputField from '../../common/FormInputs';
8
+
9
+ const CDRom = ({ onRemove, data, storages, nodeId }) => {
10
+ const [cdrom, setCdrom] = useState(data);
11
+ const storagesMap = createStoragesMap(storages, 'iso', nodeId);
12
+ const imagesMap = imagesByStorage(
13
+ storages,
14
+ nodeId,
15
+ cdrom?.storage?.value,
16
+ 'iso'
17
+ );
18
+
19
+ const handleMediaChange = (_, e) => {
20
+ setCdrom({
21
+ ...cdrom,
22
+ cdrom: {
23
+ ...cdrom.cdrom,
24
+ value: e.target.value,
25
+ },
26
+ });
27
+ };
28
+
29
+ const handleChange = e => {
30
+ const { name, type, checked, value: targetValue } = e.target;
31
+ let value;
32
+ if (type === 'checkbox') {
33
+ value = checked ? '1' : '0';
34
+ } else {
35
+ value = targetValue;
36
+ }
37
+ const updatedKey = Object.keys(cdrom).find(key => cdrom[key].name === name);
38
+ const updatedData = {
39
+ ...cdrom,
40
+ [updatedKey]: { ...cdrom[updatedKey], value },
41
+ };
42
+ setCdrom(updatedData);
43
+ };
44
+
45
+ return (
46
+ <div style={{ position: 'relative' }}>
47
+ <div
48
+ style={{
49
+ display: 'flex',
50
+ justifyContent: 'space-between',
51
+ alignItems: 'center',
52
+ }}
53
+ >
54
+ <Title headingLevel="h4">{__('CD-ROM')}</Title>
55
+ <button onClick={onRemove}>
56
+ <TimesIcon />
57
+ </button>
58
+ </div>
59
+ <Divider component="li" style={{ marginBottom: '1rem' }} />
60
+ <div
61
+ style={{
62
+ display: 'flex',
63
+ justifyContent: 'space-between',
64
+ alignItems: 'center',
65
+ }}
66
+ >
67
+ <Title headingLevel="h5">{__('Media')}</Title>
68
+ </div>
69
+ <Divider component="li" style={{ marginBottom: '1rem' }} />
70
+ <div style={{ display: 'flex', gap: '1rem' }}>
71
+ <Radio
72
+ id="radio-none"
73
+ name={cdrom?.cdrom?.name}
74
+ label={__('None')}
75
+ value="none"
76
+ isChecked={cdrom?.cdrom?.value === 'none'}
77
+ onChange={handleMediaChange}
78
+ />
79
+ <Radio
80
+ id="radio-physical"
81
+ name={cdrom?.cdrom?.name}
82
+ label={__('Physical')}
83
+ value="physical"
84
+ isChecked={cdrom?.cdrom?.value === 'physical'}
85
+ onChange={handleMediaChange}
86
+ />
87
+ <Radio
88
+ id="radio-image"
89
+ name={cdrom?.cdrom?.name}
90
+ label={__('Image')}
91
+ value="image"
92
+ isChecked={cdrom?.cdrom?.value === 'image'}
93
+ onChange={handleMediaChange}
94
+ />
95
+ </div>
96
+ {cdrom?.cdrom?.value === 'image' && (
97
+ <PageSection padding={{ default: 'noPadding' }}>
98
+ <Title headingLevel="h5">{__('Image')}</Title>
99
+ <Divider component="li" style={{ marginBottom: '2rem' }} />
100
+ <InputField
101
+ label={__('Storage')}
102
+ name={cdrom?.storage?.name}
103
+ type="select"
104
+ value={
105
+ cdrom?.storage?.value ||
106
+ (storagesMap?.length > 0 ? storagesMap[0].value : '')
107
+ }
108
+ options={storagesMap}
109
+ onChange={handleChange}
110
+ />
111
+ <InputField
112
+ name={cdrom?.volid?.name}
113
+ label={__('Image ISO')}
114
+ type="select"
115
+ value={cdrom?.volid?.value}
116
+ options={imagesMap}
117
+ onChange={handleChange}
118
+ />
119
+ <input name={cdrom?.storageType?.name} type="hidden" value="cdrom" />
120
+ </PageSection>
121
+ )}
122
+ </div>
123
+ );
124
+ };
125
+
126
+ CDRom.propTypes = {
127
+ onRemove: PropTypes.func.isRequired,
128
+ data: PropTypes.shape({
129
+ cdrom: PropTypes.shape({
130
+ name: PropTypes.string.isRequired,
131
+ value: PropTypes.string.isRequired,
132
+ }).isRequired,
133
+ storage: PropTypes.shape({
134
+ name: PropTypes.string.isRequired,
135
+ value: PropTypes.string,
136
+ }).isRequired,
137
+ volid: PropTypes.shape({
138
+ name: PropTypes.string.isRequired,
139
+ value: PropTypes.string,
140
+ }).isRequired,
141
+ storageType: PropTypes.shape({
142
+ name: PropTypes.string.isRequired,
143
+ }).isRequired,
144
+ }).isRequired,
145
+ storages: PropTypes.array.isRequired,
146
+ nodeId: PropTypes.string.isRequired,
147
+ };
148
+
149
+ export default CDRom;