foreman_fog_proxmox 0.15.0 → 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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_fog_proxmox/proxmox_vm.js +54 -33
  3. data/app/assets/javascripts/foreman_fog_proxmox/proxmox_vm_server.js +25 -5
  4. data/app/assets/stylesheets/foreman_fog_proxmox/accordion.css +21 -0
  5. data/app/helpers/proxmox_compute_resources_helper.rb +1 -1
  6. data/app/helpers/proxmox_form_helper.rb +2 -2
  7. data/app/helpers/proxmox_vm_attrs_helper.rb +131 -0
  8. data/app/helpers/proxmox_vm_cloudinit_helper.rb +1 -1
  9. data/app/helpers/proxmox_vm_interfaces_helper.rb +3 -3
  10. data/app/helpers/proxmox_vm_volumes_helper.rb +3 -3
  11. data/app/models/concerns/fog_extensions/proxmox/node.rb +1 -1
  12. data/app/models/foreman_fog_proxmox/proxmox.rb +1 -1
  13. data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +3 -3
  14. data/app/models/foreman_fog_proxmox/proxmox_console.rb +1 -1
  15. data/app/models/foreman_fog_proxmox/proxmox_images.rb +5 -0
  16. data/app/models/foreman_fog_proxmox/proxmox_version.rb +1 -1
  17. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +5 -2
  18. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
  19. data/app/overrides/compute_resources_vms/form/add_from_profile_to_compute_attributes_form.rb +8 -0
  20. data/app/overrides/compute_resources_vms/form/add_react_component_to_host.rb +25 -0
  21. data/app/overrides/compute_resources_vms/form/update_react_component_to_host_form.rb +25 -0
  22. data/app/views/compute_resources_vms/form/proxmox/_add_react_component_to_host_form.html.erb +5 -0
  23. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +21 -21
  24. data/app/views/compute_resources_vms/form/proxmox/_update_react_component_to_host_form.html.erb +26 -0
  25. data/app/views/compute_resources_vms/form/proxmox/container/_config.html.erb +17 -13
  26. data/app/views/compute_resources_vms/form/proxmox/server/_config.html.erb +20 -20
  27. data/app/views/compute_resources_vms/form/proxmox/server/_volume_hard_disk.html.erb +4 -1
  28. data/config/routes.rb +3 -3
  29. data/lib/foreman_fog_proxmox/version.rb +1 -1
  30. data/package.json +42 -0
  31. data/test/factories/proxmox_factory.rb +7 -7
  32. data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +1 -1
  33. data/test/unit/foreman_fog_proxmox/proxmox_interfaces_test.rb +6 -6
  34. data/webpack/components/GeneralTabContent.js +107 -0
  35. data/webpack/components/ProxmoxComputeSelectors.js +141 -0
  36. data/webpack/components/ProxmoxContainer/MountPoint.js +91 -0
  37. data/webpack/components/ProxmoxContainer/ProxmoxContainerHardware.js +85 -0
  38. data/webpack/components/ProxmoxContainer/ProxmoxContainerNetwork.js +179 -0
  39. data/webpack/components/ProxmoxContainer/ProxmoxContainerOptions.js +104 -0
  40. data/webpack/components/ProxmoxContainer/ProxmoxContainerStorage.js +194 -0
  41. data/webpack/components/ProxmoxContainer/components/NetworkInterface.js +193 -0
  42. data/webpack/components/ProxmoxServer/ProxmoxServerHardware.js +204 -0
  43. data/webpack/components/ProxmoxServer/ProxmoxServerNetwork.js +161 -0
  44. data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +105 -0
  45. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +272 -0
  46. data/webpack/components/ProxmoxServer/components/CDRom.js +149 -0
  47. data/webpack/components/ProxmoxServer/components/CPUFlagsModal.js +88 -0
  48. data/webpack/components/ProxmoxServer/components/HardDisk.js +143 -0
  49. data/webpack/components/ProxmoxServer/components/NetworkInterface.js +150 -0
  50. data/webpack/components/ProxmoxStoragesUtils.js +50 -0
  51. data/webpack/components/ProxmoxVmType.js +256 -0
  52. data/webpack/components/ProxmoxVmUtils.js +62 -0
  53. data/webpack/components/common/FormInputs.js +143 -0
  54. data/webpack/global_index.js +15 -0
  55. data/webpack/index.js +7 -0
  56. metadata +50 -21
@@ -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;
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import { Modal, Button } from '@patternfly/react-core';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
5
+ import PropTypes from 'prop-types';
6
+ import ProxmoxComputeSelectors from '../../ProxmoxComputeSelectors';
7
+
8
+ const CPUFlagsModal = ({ isOpen, onClose, flags, handleChange }) => {
9
+ const resetFlags = () => {
10
+ Object.keys(flags).forEach(key => {
11
+ handleChange({
12
+ target: {
13
+ name: flags[key].name,
14
+ value: '0',
15
+ },
16
+ });
17
+ });
18
+ };
19
+
20
+ return (
21
+ <div>
22
+ <Modal
23
+ bodyAriaLabel="Scrollable modal content"
24
+ width="60%"
25
+ tabIndex={0}
26
+ title="CPU Flags"
27
+ isOpen={isOpen}
28
+ onClose={onClose}
29
+ actions={[
30
+ <Button key="confirm" variant="primary" onClick={onClose}>
31
+ {__('Confirm')}
32
+ </Button>,
33
+ <Button key="reset" variant="secondary" onClick={resetFlags}>
34
+ {__('Reset')}
35
+ </Button>,
36
+ ]}
37
+ >
38
+ <Table aria-label="Simple table" variant="compact">
39
+ <Thead>
40
+ <Tr>
41
+ <Th>Name</Th>
42
+ <Th />
43
+ <Th>Description</Th>
44
+ </Tr>
45
+ </Thead>
46
+ <Tbody>
47
+ {Object.keys(flags).map(key => {
48
+ const item = flags[key];
49
+ return (
50
+ <Tr key={item.label}>
51
+ <Td>{item.label}</Td>
52
+ <Td>
53
+ <select
54
+ name={item.name}
55
+ value={item.value}
56
+ onChange={handleChange}
57
+ >
58
+ {ProxmoxComputeSelectors.proxmoxCpuFlagsMap.map(flag => (
59
+ <option value={flag.value}>{flag.label}</option>
60
+ ))}
61
+ </select>
62
+ </Td>
63
+ <Td>{item.description}</Td>
64
+ </Tr>
65
+ );
66
+ })}
67
+ </Tbody>
68
+ </Table>
69
+ </Modal>
70
+ </div>
71
+ );
72
+ };
73
+
74
+ CPUFlagsModal.propTypes = {
75
+ isOpen: PropTypes.bool.isRequired,
76
+ onClose: PropTypes.func.isRequired,
77
+ flags: PropTypes.objectOf(
78
+ PropTypes.shape({
79
+ name: PropTypes.string.isRequired,
80
+ label: PropTypes.string.isRequired,
81
+ value: PropTypes.string.isRequired,
82
+ description: PropTypes.string.isRequired,
83
+ })
84
+ ).isRequired,
85
+ handleChange: PropTypes.func.isRequired,
86
+ };
87
+
88
+ export default CPUFlagsModal;
@@ -0,0 +1,143 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Divider } from '@patternfly/react-core';
4
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
5
+ import InputField from '../../common/FormInputs';
6
+ import ProxmoxComputeSelectors from '../../ProxmoxComputeSelectors';
7
+ import { createStoragesMap } from '../../ProxmoxStoragesUtils';
8
+
9
+ const HardDisk = ({
10
+ id,
11
+ data,
12
+ storages,
13
+ disks,
14
+ updateHardDiskData,
15
+ createUniqueDevice,
16
+ nodeId,
17
+ }) => {
18
+ const [hdd, setHdd] = useState(data);
19
+ const [error, setError] = useState(null);
20
+ const storagesMap = createStoragesMap(storages, null, nodeId);
21
+ useEffect(() => {
22
+ const currentHddData = JSON.stringify(hdd);
23
+ const parentHddData = JSON.stringify(data);
24
+
25
+ if (currentHddData !== parentHddData) {
26
+ updateHardDiskData(id, hdd);
27
+ }
28
+ }, [hdd, id, data, updateHardDiskData]);
29
+ const handleChange = e => {
30
+ const { name, value } = e.target;
31
+ const updatedKey = Object.keys(hdd).find(key => hdd[key].name === name);
32
+
33
+ if (updatedKey === 'controller') {
34
+ const updatedDeviceInfo = createUniqueDevice('hard_disk', value);
35
+ if (updatedDeviceInfo) {
36
+ setHdd({
37
+ ...hdd,
38
+ controller: { ...hdd.controller, value },
39
+ device: { ...hdd.device, value: updatedDeviceInfo.device },
40
+ id: { ...hdd.id, value: updatedDeviceInfo.id },
41
+ });
42
+ setError(null);
43
+ } else {
44
+ setError(
45
+ sprintf(
46
+ __(
47
+ 'Reached maximum number of devices for controller %(value)s. Please select another controller.'
48
+ ),
49
+ { value }
50
+ )
51
+ );
52
+ setHdd({
53
+ ...hdd,
54
+ controller: { ...hdd.controller, value },
55
+ device: { ...hdd.device, value: '' },
56
+ id: { ...hdd.id, value: '' },
57
+ });
58
+ }
59
+ } else {
60
+ const updatedData = {
61
+ ...hdd,
62
+ [updatedKey]: { ...hdd[updatedKey], value },
63
+ };
64
+ setHdd(updatedData);
65
+ }
66
+ };
67
+ return (
68
+ <div>
69
+ <Divider component="li" style={{ marginBottom: '2rem' }} />
70
+ <input
71
+ name={hdd?.storageType?.name}
72
+ type="hidden"
73
+ value={hdd?.storageType?.value}
74
+ onChange={handleChange}
75
+ />
76
+ <input
77
+ name={hdd?.device?.name}
78
+ type="hidden"
79
+ value={hdd?.device?.value}
80
+ onChange={handleChange}
81
+ />
82
+ <input
83
+ name={hdd?.id?.name}
84
+ type="hidden"
85
+ value={hdd?.id?.value}
86
+ onChange={handleChange}
87
+ />
88
+ <InputField
89
+ name={hdd?.storage?.name}
90
+ label={__('Storage')}
91
+ type="select"
92
+ value={hdd?.storage?.value}
93
+ options={storagesMap}
94
+ onChange={handleChange}
95
+ />
96
+ <InputField
97
+ name={hdd?.controller?.name}
98
+ label={__('Controller')}
99
+ type="select"
100
+ value={hdd?.controller?.value}
101
+ options={ProxmoxComputeSelectors.proxmoxControllersHDDMap}
102
+ onChange={handleChange}
103
+ error={error}
104
+ />
105
+ <InputField
106
+ name={hdd?.cache?.name}
107
+ label={__('Cache')}
108
+ type="select"
109
+ value={hdd?.cache?.value}
110
+ options={ProxmoxComputeSelectors.proxmoxCachesMap}
111
+ onChange={handleChange}
112
+ />
113
+ <InputField
114
+ name={hdd?.size?.name}
115
+ label={__('Size')}
116
+ type="number"
117
+ value={hdd?.size?.value}
118
+ onChange={handleChange}
119
+ />
120
+ </div>
121
+ );
122
+ };
123
+
124
+ HardDisk.propTypes = {
125
+ id: PropTypes.number.isRequired,
126
+ data: PropTypes.object,
127
+ storages: PropTypes.array,
128
+ disks: PropTypes.array,
129
+ updateHardDiskData: PropTypes.func,
130
+ createUniqueDevice: PropTypes.func,
131
+ nodeId: PropTypes.string,
132
+ };
133
+
134
+ HardDisk.defaultProps = {
135
+ data: {},
136
+ storages: [],
137
+ disks: [],
138
+ nodeId: '',
139
+ updateHardDiskData: Function.prototype,
140
+ createUniqueDevice: Function.prototype,
141
+ };
142
+
143
+ export default HardDisk;