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.
- checksums.yaml +4 -4
- 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_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_compute_attributes.rb +3 -3
- 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 +1 -1
- 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/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 +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;
|