foreman_fog_proxmox 0.15.1 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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,104 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { createStoragesMap, imagesByStorage } from '../ProxmoxStoragesUtils';
5
+ import ProxmoxComputeSelectors from '../ProxmoxComputeSelectors';
6
+ import InputField from '../common/FormInputs';
7
+
8
+ const ProxmoxContainerOptions = ({ options, storages, nodeId }) => {
9
+ const [opts, setOpts] = useState(options);
10
+ const storagesMap = createStoragesMap(storages, 'vztmpl', nodeId);
11
+ const volumesMap = imagesByStorage(storages, nodeId, 'local', 'vztmpl');
12
+ const handleChange = e => {
13
+ const { name, type, checked, value: targetValue } = e.target;
14
+ let value;
15
+ if (type === 'checkbox') {
16
+ value = checked ? '1' : '0';
17
+ } else {
18
+ value = targetValue;
19
+ }
20
+ const updatedKey = Object.keys(opts).find(key => opts[key].name === name);
21
+
22
+ setOpts(prevOpts => ({
23
+ ...prevOpts,
24
+ [updatedKey]: { ...prevOpts[updatedKey], value },
25
+ }));
26
+ };
27
+ return (
28
+ <div>
29
+ <InputField
30
+ name={opts?.ostemplateStorage?.name}
31
+ label={__('Template Storage')}
32
+ value={opts?.ostemplateStorage?.value}
33
+ options={storagesMap}
34
+ type="select"
35
+ onChange={handleChange}
36
+ />
37
+ <InputField
38
+ name={opts?.ostemplateFile?.name}
39
+ label={__('OS Template')}
40
+ required
41
+ options={volumesMap}
42
+ value={opts?.ostemplateFile?.value}
43
+ type="select"
44
+ onChange={handleChange}
45
+ />
46
+ <InputField
47
+ name={opts?.password?.name}
48
+ label={__('Root Password')}
49
+ required
50
+ type="password"
51
+ value={opts?.password?.value}
52
+ onChange={handleChange}
53
+ />
54
+ <InputField
55
+ name={opts?.onboot?.name}
56
+ label={__('Start at boot')}
57
+ type="checkbox"
58
+ value={opts?.onboot?.value}
59
+ checked={opts?.onboot?.value === '1'}
60
+ onChange={handleChange}
61
+ />
62
+ <InputField
63
+ name={opts?.ostype?.name}
64
+ label={__('OS Type')}
65
+ type="select"
66
+ options={ProxmoxComputeSelectors.proxmoxOperatingSystemsMap}
67
+ value={opts?.ostype?.value}
68
+ onChange={handleChange}
69
+ />
70
+ <InputField
71
+ name={opts?.hostname?.name}
72
+ label={__('Hostname')}
73
+ value={opts?.hostname?.value}
74
+ onChange={handleChange}
75
+ />
76
+ <InputField
77
+ name={opts?.nameserver?.name}
78
+ label={__('DNS server')}
79
+ value={opts?.nameserver?.value}
80
+ onChange={handleChange}
81
+ />
82
+ <InputField
83
+ name={opts?.searchdomain?.name}
84
+ label={__('Search Domain')}
85
+ value={opts?.searchdomain?.value}
86
+ onChange={handleChange}
87
+ />
88
+ </div>
89
+ );
90
+ };
91
+
92
+ ProxmoxContainerOptions.propTypes = {
93
+ options: PropTypes.object,
94
+ storages: PropTypes.array,
95
+ nodeId: PropTypes.string,
96
+ };
97
+
98
+ ProxmoxContainerOptions.defaultProps = {
99
+ options: {},
100
+ storages: [],
101
+ nodeId: '',
102
+ };
103
+
104
+ export default ProxmoxContainerOptions;
@@ -0,0 +1,194 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { Button, Title, Divider, PageSection } 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 { createStoragesMap } from '../ProxmoxStoragesUtils';
7
+ import InputField from '../common/FormInputs';
8
+ import MountPoint from './MountPoint';
9
+
10
+ const ProxmoxContainerStorage = ({ storage, storages, nodeId, paramScope }) => {
11
+ const initData = {
12
+ id: {
13
+ name: `${paramScope}[volumes_attributes][0][id]`,
14
+ value: 'rootfs',
15
+ },
16
+ device: {
17
+ name: `${paramScope}[volumes_attributes][0][device]`,
18
+ value: '8',
19
+ },
20
+ storage: {
21
+ name: `${paramScope}[volumes_attributes][0][storage]`,
22
+ value: '',
23
+ },
24
+ size: {
25
+ name: `${paramScope}[volumes_attributes][0][size]`,
26
+ value: 8,
27
+ },
28
+ volid: {
29
+ name: `${paramScope}[volumes_attributes][0][volid]`,
30
+ value: null,
31
+ },
32
+ };
33
+ const [rootfs, setRootfs] = useState(initData);
34
+ useEffect(() => {
35
+ if (storage && storage.length > 0) {
36
+ storage.forEach(disk => {
37
+ if (disk.name === 'rootfs') {
38
+ setRootfs(disk.value);
39
+ }
40
+ if (disk.name === 'mount_point') {
41
+ addMountPoint(null, disk.value);
42
+ }
43
+ });
44
+ }
45
+ }, [storage]);
46
+
47
+ const handleChange = e => {
48
+ const { name, value } = e.target;
49
+ const updatedKey = Object.keys(rootfs).find(
50
+ key => rootfs[key].name === name
51
+ );
52
+ const updatedData = {
53
+ ...rootfs,
54
+ [updatedKey]: { ...rootfs[updatedKey], value },
55
+ };
56
+ setRootfs(updatedData);
57
+ };
58
+ const [mountPoints, setMountPoints] = useState([]);
59
+ const [nextId, setNextId] = useState(0);
60
+ const storagesMap = createStoragesMap(storages, null, nodeId);
61
+ const addMountPoint = useCallback(
62
+ (event, initialData = null) => {
63
+ if (event) event.preventDefault();
64
+ const initMP = initialData || {
65
+ id: {
66
+ name: `${paramScope}[volumes_attributes][${nextId}][id]`,
67
+ value: `mp${nextId}`,
68
+ },
69
+ device: {
70
+ name: `${paramScope}[volumes_attributes][${nextId}][device]`,
71
+ value: `${nextId}`,
72
+ },
73
+ storage: {
74
+ name: `${paramScope}[volumes_attributes][${nextId}][storage]`,
75
+ value: '',
76
+ },
77
+ size: {
78
+ name: `${paramScope}[volumes_attributes][${nextId}][size]`,
79
+ value: 8,
80
+ },
81
+ mp: {
82
+ name: `${paramScope}[volumes_attributes][${nextId}][mp]`,
83
+ value: '',
84
+ },
85
+ volid: {
86
+ name: `${paramScope}[volumes_attributes][${nextId}][volid]`,
87
+ value: null,
88
+ },
89
+ };
90
+
91
+ setNextId(prevNextId => {
92
+ const newNextId = prevNextId + 1;
93
+ const newMountPoint = {
94
+ id: newNextId,
95
+ data: initMP,
96
+ storagesMap,
97
+ };
98
+ setMountPoints(prevMountPoints => [...prevMountPoints, newMountPoint]);
99
+ return newNextId;
100
+ });
101
+ },
102
+ [nextId, paramScope, setMountPoints, setNextId, storagesMap]
103
+ );
104
+
105
+ const removeMountPoint = idToRemove => {
106
+ const newMountPoints = mountPoints.filter(
107
+ mountPoint => mountPoint.props.id !== idToRemove
108
+ );
109
+ setMountPoints(newMountPoints);
110
+ };
111
+
112
+ return (
113
+ <div>
114
+ <PageSection padding={{ default: 'noPadding' }}>
115
+ <Title headingLevel="h3">{__('Rootfs')}</Title>
116
+ <Divider component="li" style={{ marginBottom: '2rem' }} />
117
+ <InputField
118
+ name={rootfs?.storage?.name}
119
+ label="Storage"
120
+ type="select"
121
+ value={rootfs?.storage?.value}
122
+ options={storagesMap}
123
+ onChange={handleChange}
124
+ />
125
+ <InputField
126
+ name={rootfs?.size?.name}
127
+ label={__('Size')}
128
+ type="number"
129
+ value={rootfs?.size?.value}
130
+ onChange={handleChange}
131
+ />
132
+ <input
133
+ name={rootfs?.id?.name}
134
+ type="hidden"
135
+ value={rootfs?.id?.value || 'rootfs'}
136
+ onChange={handleChange}
137
+ />
138
+ <input
139
+ name={rootfs?.volid?.name}
140
+ type="hidden"
141
+ value={rootfs?.volid?.value}
142
+ />
143
+ </PageSection>
144
+ <PageSection padding={{ default: 'noPadding' }}>
145
+ <Title headingLevel="h3">Storage</Title>
146
+ <Divider component="li" style={{ marginBottom: '2rem' }} />
147
+ <Button onClick={addMountPoint} variant="secondary">
148
+ {__('Add MountPoint')}
149
+ </Button>
150
+ {mountPoints.map(mountPoint => (
151
+ <div key={mountPoint.id} style={{ position: 'relative' }}>
152
+ <div
153
+ style={{
154
+ marginTop: '10px',
155
+ display: 'flex',
156
+ justifyContent: 'space-between',
157
+ alignItems: 'center',
158
+ }}
159
+ >
160
+ <Title headingLevel="h4">
161
+ {sprintf(__('Mount Point %(mp)s'), { mp: mountPoint.id })}
162
+ </Title>
163
+ <button onClick={() => removeMountPoint(mountPoint.id)}>
164
+ <TimesIcon />
165
+ </button>
166
+ </div>
167
+ <MountPoint
168
+ key={mountPoint.id}
169
+ id={mountPoint.id}
170
+ data={mountPoint.data}
171
+ storagesMap={mountPoint.storagesMap}
172
+ />
173
+ </div>
174
+ ))}
175
+ </PageSection>
176
+ </div>
177
+ );
178
+ };
179
+
180
+ ProxmoxContainerStorage.propTypes = {
181
+ storage: PropTypes.object,
182
+ storages: PropTypes.array,
183
+ nodeId: PropTypes.string,
184
+ paramScope: PropTypes.string,
185
+ };
186
+
187
+ ProxmoxContainerStorage.defaultProps = {
188
+ storage: {},
189
+ storages: [],
190
+ nodeId: '',
191
+ paramScope: '',
192
+ };
193
+
194
+ export default ProxmoxContainerStorage;
@@ -0,0 +1,193 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Divider } from '@patternfly/react-core';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import InputField from '../../common/FormInputs';
6
+
7
+ const NetworkInterface = ({
8
+ id,
9
+ networks,
10
+ bridges,
11
+ data,
12
+ updateNetworkData,
13
+ existingInterfaces,
14
+ }) => {
15
+ const [network, setNetwork] = useState(data);
16
+ const [error, setError] = useState('');
17
+ useEffect(() => {
18
+ const currentNetData = JSON.stringify(network);
19
+ const parentNetData = JSON.stringify(data);
20
+
21
+ if (currentNetData !== parentNetData) {
22
+ updateNetworkData(id, network);
23
+ }
24
+ }, [network, id, data, updateNetworkData]);
25
+ const handleChange = e => {
26
+ const { name, type, checked, value: targetValue } = e.target;
27
+ let value;
28
+ if (type === 'checkbox') {
29
+ value = checked ? '1' : '0';
30
+ } else {
31
+ value = targetValue;
32
+ }
33
+ const updatedKey = Object.keys(network).find(
34
+ key => network[key].name === name
35
+ );
36
+ const updatedData = {
37
+ ...network,
38
+ [updatedKey]: { ...network[updatedKey], value },
39
+ };
40
+ setNetwork(updatedData);
41
+
42
+ if (updatedKey === 'id') {
43
+ const idValue = value;
44
+ if (
45
+ Object.values(existingInterfaces).some(
46
+ net =>
47
+ net.data.id.value === idValue &&
48
+ net.data.id.value !== network.id.value
49
+ )
50
+ ) {
51
+ setError(__('Error: Duplicate ID found.'));
52
+ return;
53
+ }
54
+ setError('');
55
+ }
56
+ if (updatedKey === 'name') {
57
+ const idValue = value;
58
+ if (
59
+ Object.values(existingInterfaces).some(
60
+ net =>
61
+ net.data.id.value === idValue &&
62
+ net.data.id.value !== network.id.value
63
+ )
64
+ ) {
65
+ setError(__('Error: Duplicate Name found.'));
66
+ } else {
67
+ setError('');
68
+ }
69
+ }
70
+ };
71
+ const bridgesMap = bridges.map(bridge => ({
72
+ value: bridge.iface,
73
+ label: bridge.iface,
74
+ }));
75
+
76
+ return (
77
+ <div style={{ position: 'relative' }}>
78
+ <Divider component="li" style={{ marginBottom: '2rem' }} />
79
+ <InputField
80
+ name={network?.id?.name}
81
+ label={__('Identifier')}
82
+ info={__('net[n] with n integer >= 0, e.g. net0')}
83
+ type="text"
84
+ value={network?.id?.value}
85
+ onChange={handleChange}
86
+ error={error}
87
+ />
88
+ <InputField
89
+ name={network?.name?.name}
90
+ label={__('Name')}
91
+ info={__('eth[n] with n integer >= 0, e.g. eth0')}
92
+ type="text"
93
+ value={network?.name?.value}
94
+ onChange={handleChange}
95
+ error={error}
96
+ />
97
+ <InputField
98
+ name={network?.bridge?.name}
99
+ label={__('Bridge')}
100
+ type="select"
101
+ options={bridgesMap}
102
+ value={network?.bridge?.value}
103
+ onChange={handleChange}
104
+ />
105
+ <InputField
106
+ name={network?.dhcp?.name}
107
+ label={__('DHCP IPv4')}
108
+ type="checkbox"
109
+ value={network?.dhcp?.value}
110
+ checked={network?.dhcp?.value === '1'}
111
+ onChange={handleChange}
112
+ />
113
+ <InputField
114
+ name={network?.cidr?.name}
115
+ label={__('CIDR IPv4')}
116
+ info={__('integer within [0..32]')}
117
+ type="text"
118
+ value={network?.cidr?.value}
119
+ onChange={handleChange}
120
+ />
121
+ <InputField
122
+ name={network?.gw?.name}
123
+ label={__('Gateway IPv4')}
124
+ type="text"
125
+ value={network?.gw?.value}
126
+ onChange={handleChange}
127
+ />
128
+ <InputField
129
+ name={network?.dhcp6?.name}
130
+ label={__('DHCP IPv6')}
131
+ type="checkbox"
132
+ value={network?.dhcp6?.value}
133
+ checked={network?.dhcp6?.value === '1'}
134
+ onChange={handleChange}
135
+ />
136
+ <InputField
137
+ name={network?.cidr6?.name}
138
+ label={__('CIDR IPv6')}
139
+ info={__('integer within [0..128]')}
140
+ type="text"
141
+ value={network?.cidr6?.value}
142
+ onChange={handleChange}
143
+ />
144
+ <InputField
145
+ name={network?.gw6?.name}
146
+ label={__('Gateway IPv6')}
147
+ type="text"
148
+ value={network?.gw6?.value}
149
+ onChange={handleChange}
150
+ />
151
+ <InputField
152
+ name={network?.tag?.name}
153
+ label={__('VLAN Tag')}
154
+ type="text"
155
+ value={network?.tag?.value}
156
+ onChange={handleChange}
157
+ />
158
+ <InputField
159
+ name={network?.rate?.name}
160
+ label={__('Rate limit')}
161
+ type="text"
162
+ value={network?.rate?.value}
163
+ onChange={handleChange}
164
+ />
165
+ <InputField
166
+ name={network?.firewall?.name}
167
+ label={__('Firewall')}
168
+ type="checkbox"
169
+ value={network?.firewall?.value}
170
+ checked={network?.firewall?.value === '1'}
171
+ onChange={handleChange}
172
+ />
173
+ </div>
174
+ );
175
+ };
176
+
177
+ NetworkInterface.propTypes = {
178
+ id: PropTypes.number.isRequired,
179
+ networks: PropTypes.array,
180
+ bridges: PropTypes.array,
181
+ data: PropTypes.object,
182
+ updateNetworkData: PropTypes.func,
183
+ existingInterfaces: PropTypes.array,
184
+ };
185
+
186
+ NetworkInterface.defaultProps = {
187
+ networks: [],
188
+ bridges: [],
189
+ data: {},
190
+ updateNetworkData: Function.prototype,
191
+ existingInterfaces: [],
192
+ };
193
+ export default NetworkInterface;
@@ -0,0 +1,204 @@
1
+ import React, { useState } from 'react';
2
+ import { Button, Title, Divider, PageSection } from '@patternfly/react-core';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import PropTypes from 'prop-types';
5
+ import InputField from '../common/FormInputs';
6
+ import ProxmoxComputeSelectors from '../ProxmoxComputeSelectors';
7
+ import CPUFlagsModal from './components/CPUFlagsModal';
8
+
9
+ const cpuFlagNames = [
10
+ 'md_clear',
11
+ 'pcid',
12
+ 'spec_ctrl',
13
+ 'ssbd',
14
+ 'ibpb',
15
+ 'virt_ssbd',
16
+ 'amd_ssbd',
17
+ 'amd_no_ssb',
18
+ 'pdpe1gb',
19
+ 'hv_tlbflush',
20
+ 'hv_evmcs',
21
+ 'aes',
22
+ ];
23
+
24
+ const cpuFlagDescriptions = {
25
+ md_clear: __(
26
+ 'Required to let the guest OS know if MDS is mitigated correctly'
27
+ ),
28
+ pcid: __(
29
+ 'Meltdown fix cost reduction on Westmere, Sandy-, and IvyBridge Intel CPUs'
30
+ ),
31
+ spec_ctrl: __('Allows improved Spectre mitigation with Intel CPUs'),
32
+ ssbd: __('Protection for "Speculative Store Bypass" for Intel models'),
33
+ ibpb: __('Allows improved Spectre mitigation with AMD CPUs'),
34
+ virt_ssbd: __(
35
+ 'Basis for "Speculative Store Bypass" protection for AMD models'
36
+ ),
37
+ amd_ssbd: __(
38
+ 'Improves Spectre mitigation performance with AMD CPUs, best used with "virt-ssbd"'
39
+ ),
40
+ amd_no_ssb: __(
41
+ 'Notifies guest OS that host is not vulnerable for Spectre on AMD CPUs'
42
+ ),
43
+ pdpe1gb: __('Allow guest OS to use 1GB size pages, if host HW supports it'),
44
+ hv_tlbflush: __(
45
+ 'Improve performance in overcommitted Windows guests. May lead to guest bluescreens on old CPUs.'
46
+ ),
47
+ hv_evmcs: __(
48
+ 'Improve performance for nested virtualization. Only supported on Intel CPUs.'
49
+ ),
50
+ aes: __('Activate AES instruction set for HW instruction'),
51
+ };
52
+
53
+ const filterAndAddDescriptions = hardware =>
54
+ Object.keys(hardware)
55
+ .filter(key => cpuFlagNames.includes(key))
56
+ .reduce((acc, key) => {
57
+ acc[key] = {
58
+ ...hardware[key],
59
+ description: cpuFlagDescriptions[key] || '',
60
+ label: key,
61
+ };
62
+ return acc;
63
+ }, {});
64
+
65
+ const ProxmoxServerHardware = ({ hardware }) => {
66
+ const [hw, setHw] = useState(hardware);
67
+ const [isModalOpen, setIsModalOpen] = useState(false);
68
+
69
+ const handleChange = e => {
70
+ const { name, type, checked, value: targetValue } = e.target;
71
+ let value;
72
+ if (type === 'checkbox') {
73
+ value = checked ? '1' : '0';
74
+ } else {
75
+ value = targetValue;
76
+ }
77
+ const updatedKey = Object.keys(hw).find(key => hw[key].name === name);
78
+
79
+ setHw(prevHw => ({
80
+ ...prevHw,
81
+ [updatedKey]: { ...prevHw[updatedKey], value },
82
+ }));
83
+ };
84
+
85
+ const handleModalToggle = _event => {
86
+ setIsModalOpen(prevIsModalOpen => !prevIsModalOpen);
87
+ };
88
+
89
+ const cpuFlags = filterAndAddDescriptions(hw);
90
+
91
+ return (
92
+ <div>
93
+ <PageSection padding={{ default: 'noPadding' }}>
94
+ <Title headingLevel="h3">{__('CPU')}</Title>
95
+ <Divider component="li" style={{ marginBottom: '2rem' }} />
96
+ <InputField
97
+ name={hw?.cpuType?.name}
98
+ label={__('Type')}
99
+ type="select"
100
+ value={hw?.cpuType?.value}
101
+ options={ProxmoxComputeSelectors.proxmoxCpusMap}
102
+ onChange={handleChange}
103
+ />
104
+ <InputField
105
+ name={hw?.sockets?.name}
106
+ label={__('Sockets')}
107
+ type="number"
108
+ value={hw?.sockets?.value}
109
+ onChange={handleChange}
110
+ />
111
+ <InputField
112
+ name={hw?.cores?.name}
113
+ label={__('Cores')}
114
+ type="number"
115
+ value={hw?.cores?.value}
116
+ onChange={handleChange}
117
+ />
118
+ <InputField
119
+ name={hw?.vcpus?.name}
120
+ label={__('VCPUs')}
121
+ type="number"
122
+ value={hw?.vcpus?.value}
123
+ onChange={handleChange}
124
+ />
125
+ <InputField
126
+ name={hw?.cpulimit?.name}
127
+ label={__('CPU limit')}
128
+ type="number"
129
+ value={hw?.cpulimit?.value}
130
+ onChange={handleChange}
131
+ />
132
+ <InputField
133
+ name={hw?.cpuunits?.name}
134
+ label={__('CPU units')}
135
+ type="number"
136
+ value={hw?.cpuunits?.value}
137
+ onChange={handleChange}
138
+ />
139
+ <InputField
140
+ name={hw?.numa?.name}
141
+ label={__('Enable NUMA')}
142
+ type="checkbox"
143
+ value={hw?.numa?.value}
144
+ checked={hw?.numa?.value === '1'}
145
+ onChange={handleChange}
146
+ />
147
+ <div style={{ marginLeft: '5%', display: 'inline-block' }}>
148
+ <Button variant="link" onClick={handleModalToggle}>
149
+ {__('Extra CPU Flags')}
150
+ </Button>
151
+ </div>
152
+ <CPUFlagsModal
153
+ isOpen={isModalOpen}
154
+ onClose={handleModalToggle}
155
+ flags={cpuFlags}
156
+ handleChange={handleChange}
157
+ />
158
+ {Object.keys(cpuFlags).map(key => (
159
+ <input
160
+ key={hw[key].name}
161
+ name={hw[key].name}
162
+ type="hidden"
163
+ value={hw[key].value}
164
+ />
165
+ ))}
166
+ </PageSection>
167
+ <PageSection padding={{ default: 'noPadding' }}>
168
+ <Title headingLevel="h3">{__('Memory')}</Title>
169
+ <Divider component="li" style={{ marginBottom: '2rem' }} />
170
+ <InputField
171
+ name={hw?.memory?.name}
172
+ label={__('Memory (MB)')}
173
+ type="text"
174
+ value={hw?.memory?.value}
175
+ onChange={handleChange}
176
+ />
177
+ <InputField
178
+ name={hw?.balloon?.name}
179
+ label={__('Minimum Memory (MB)')}
180
+ type="text"
181
+ value={hw?.balloon?.value}
182
+ onChange={handleChange}
183
+ />
184
+ <InputField
185
+ name={hw?.shares?.name}
186
+ label={__('Shares (MB)')}
187
+ type="text"
188
+ value={hw?.shares?.value}
189
+ onChange={handleChange}
190
+ />
191
+ </PageSection>
192
+ </div>
193
+ );
194
+ };
195
+
196
+ ProxmoxServerHardware.propTypes = {
197
+ hardware: PropTypes.object,
198
+ };
199
+
200
+ ProxmoxServerHardware.defaultProps = {
201
+ hardware: {},
202
+ };
203
+
204
+ export default ProxmoxServerHardware;