foreman_fog_proxmox 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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;