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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/foreman_fog_proxmox/proxmox_vm.js +54 -33
- data/app/assets/javascripts/foreman_fog_proxmox/proxmox_vm_server.js +25 -5
- data/app/assets/stylesheets/foreman_fog_proxmox/accordion.css +21 -0
- data/app/helpers/proxmox_compute_resources_helper.rb +1 -1
- data/app/helpers/proxmox_form_helper.rb +2 -2
- data/app/helpers/proxmox_vm_attrs_helper.rb +131 -0
- data/app/helpers/proxmox_vm_cloudinit_helper.rb +1 -1
- data/app/helpers/proxmox_vm_interfaces_helper.rb +3 -3
- data/app/helpers/proxmox_vm_volumes_helper.rb +3 -3
- data/app/models/concerns/fog_extensions/proxmox/node.rb +1 -1
- data/app/models/foreman_fog_proxmox/proxmox.rb +1 -1
- data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +3 -3
- data/app/models/foreman_fog_proxmox/proxmox_console.rb +1 -1
- data/app/models/foreman_fog_proxmox/proxmox_images.rb +5 -0
- data/app/models/foreman_fog_proxmox/proxmox_version.rb +1 -1
- data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +5 -2
- data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
- data/app/overrides/compute_resources_vms/form/add_from_profile_to_compute_attributes_form.rb +8 -0
- data/app/overrides/compute_resources_vms/form/add_react_component_to_host.rb +25 -0
- data/app/overrides/compute_resources_vms/form/update_react_component_to_host_form.rb +25 -0
- data/app/views/compute_resources_vms/form/proxmox/_add_react_component_to_host_form.html.erb +5 -0
- data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +21 -21
- data/app/views/compute_resources_vms/form/proxmox/_update_react_component_to_host_form.html.erb +26 -0
- data/app/views/compute_resources_vms/form/proxmox/container/_config.html.erb +17 -13
- data/app/views/compute_resources_vms/form/proxmox/server/_config.html.erb +20 -20
- data/app/views/compute_resources_vms/form/proxmox/server/_volume_hard_disk.html.erb +4 -1
- data/config/routes.rb +3 -3
- data/lib/foreman_fog_proxmox/version.rb +1 -1
- data/package.json +42 -0
- data/test/factories/proxmox_factory.rb +7 -7
- data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +1 -1
- data/test/unit/foreman_fog_proxmox/proxmox_interfaces_test.rb +6 -6
- data/webpack/components/GeneralTabContent.js +107 -0
- data/webpack/components/ProxmoxComputeSelectors.js +141 -0
- data/webpack/components/ProxmoxContainer/MountPoint.js +91 -0
- data/webpack/components/ProxmoxContainer/ProxmoxContainerHardware.js +85 -0
- data/webpack/components/ProxmoxContainer/ProxmoxContainerNetwork.js +179 -0
- data/webpack/components/ProxmoxContainer/ProxmoxContainerOptions.js +104 -0
- data/webpack/components/ProxmoxContainer/ProxmoxContainerStorage.js +194 -0
- data/webpack/components/ProxmoxContainer/components/NetworkInterface.js +193 -0
- data/webpack/components/ProxmoxServer/ProxmoxServerHardware.js +204 -0
- data/webpack/components/ProxmoxServer/ProxmoxServerNetwork.js +161 -0
- data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +105 -0
- data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +272 -0
- data/webpack/components/ProxmoxServer/components/CDRom.js +149 -0
- data/webpack/components/ProxmoxServer/components/CPUFlagsModal.js +88 -0
- data/webpack/components/ProxmoxServer/components/HardDisk.js +143 -0
- data/webpack/components/ProxmoxServer/components/NetworkInterface.js +150 -0
- data/webpack/components/ProxmoxStoragesUtils.js +50 -0
- data/webpack/components/ProxmoxVmType.js +256 -0
- data/webpack/components/ProxmoxVmUtils.js +62 -0
- data/webpack/components/common/FormInputs.js +143 -0
- data/webpack/global_index.js +15 -0
- data/webpack/index.js +7 -0
- 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;
|