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.
- 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;
|