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,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;
|
@@ -0,0 +1,150 @@
|
|
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
|
+
import ProxmoxComputeSelectors from '../../ProxmoxComputeSelectors';
|
7
|
+
|
8
|
+
const NetworkInterface = ({
|
9
|
+
id,
|
10
|
+
networks,
|
11
|
+
bridges,
|
12
|
+
data,
|
13
|
+
updateNetworkData,
|
14
|
+
existingInterfaces,
|
15
|
+
}) => {
|
16
|
+
const [network, setNetwork] = useState(data);
|
17
|
+
const [error, setError] = useState('');
|
18
|
+
useEffect(() => {
|
19
|
+
const currentNetData = JSON.stringify(network);
|
20
|
+
const parentNetData = JSON.stringify(data);
|
21
|
+
|
22
|
+
if (currentNetData !== parentNetData) {
|
23
|
+
updateNetworkData(id, network);
|
24
|
+
}
|
25
|
+
}, [network, id, data, updateNetworkData]);
|
26
|
+
|
27
|
+
const handleChange = e => {
|
28
|
+
const { name, type, checked, value: targetValue } = e.target;
|
29
|
+
let value;
|
30
|
+
if (type === 'checkbox') {
|
31
|
+
value = checked ? '1' : '0';
|
32
|
+
} else {
|
33
|
+
value = targetValue;
|
34
|
+
}
|
35
|
+
const updatedKey = Object.keys(network).find(
|
36
|
+
key => network[key].name === name
|
37
|
+
);
|
38
|
+
const updatedData = {
|
39
|
+
...network,
|
40
|
+
[updatedKey]: { ...network[updatedKey], value },
|
41
|
+
};
|
42
|
+
setNetwork(updatedData);
|
43
|
+
|
44
|
+
if (updatedKey === 'id') {
|
45
|
+
const idValue = value;
|
46
|
+
if (
|
47
|
+
Object.values(existingInterfaces).some(
|
48
|
+
net =>
|
49
|
+
net.data.id.value === idValue &&
|
50
|
+
net.data.id.value !== network.id.value
|
51
|
+
)
|
52
|
+
) {
|
53
|
+
setError(__('Error: Duplicate ID found.'));
|
54
|
+
return;
|
55
|
+
}
|
56
|
+
setError('');
|
57
|
+
}
|
58
|
+
};
|
59
|
+
const bridgesMap = bridges.map(bridge => ({
|
60
|
+
value: bridge.iface,
|
61
|
+
label: bridge.iface,
|
62
|
+
}));
|
63
|
+
|
64
|
+
return (
|
65
|
+
<div style={{ position: 'relative' }}>
|
66
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
67
|
+
<InputField
|
68
|
+
name={network?.id?.name}
|
69
|
+
label={__('Identifier')}
|
70
|
+
info={__('net[n] with n integer >= 0, e.g. net0')}
|
71
|
+
type="text"
|
72
|
+
value={network?.id?.value}
|
73
|
+
onChange={handleChange}
|
74
|
+
error={error}
|
75
|
+
/>
|
76
|
+
<InputField
|
77
|
+
name={network?.model?.name}
|
78
|
+
label={__('Card')}
|
79
|
+
type="select"
|
80
|
+
options={ProxmoxComputeSelectors.proxmoxNetworkcardsMap}
|
81
|
+
value={network?.model?.value}
|
82
|
+
onChange={handleChange}
|
83
|
+
/>
|
84
|
+
<InputField
|
85
|
+
name={network?.bridge?.name}
|
86
|
+
label={__('Bridge')}
|
87
|
+
type="select"
|
88
|
+
options={bridgesMap}
|
89
|
+
value={network?.bridge?.value}
|
90
|
+
onChange={handleChange}
|
91
|
+
/>
|
92
|
+
<InputField
|
93
|
+
name={network?.tag?.name}
|
94
|
+
label={__('VLAN Tag')}
|
95
|
+
type="text"
|
96
|
+
value={network?.tag?.value}
|
97
|
+
onChange={handleChange}
|
98
|
+
/>
|
99
|
+
<InputField
|
100
|
+
name={network?.rate?.name}
|
101
|
+
label={__('Rate limit')}
|
102
|
+
type="text"
|
103
|
+
value={network?.rate?.value}
|
104
|
+
onChange={handleChange}
|
105
|
+
/>
|
106
|
+
<InputField
|
107
|
+
name={network?.queues?.name}
|
108
|
+
label={__('Multiqueue')}
|
109
|
+
type="text"
|
110
|
+
value={network?.queues?.value}
|
111
|
+
onChange={handleChange}
|
112
|
+
/>
|
113
|
+
<InputField
|
114
|
+
name={network?.firewall?.name}
|
115
|
+
label={__('Firewall')}
|
116
|
+
type="checkbox"
|
117
|
+
value={network?.firewall?.value}
|
118
|
+
checked={network?.firewall?.value === '1'}
|
119
|
+
onChange={handleChange}
|
120
|
+
/>
|
121
|
+
<InputField
|
122
|
+
name={network?.linkDown?.name}
|
123
|
+
label={__('Disconnect')}
|
124
|
+
type="checkbox"
|
125
|
+
value={network?.linkDown?.value}
|
126
|
+
checked={network?.linkDown?.value === '1'}
|
127
|
+
onChange={handleChange}
|
128
|
+
/>
|
129
|
+
</div>
|
130
|
+
);
|
131
|
+
};
|
132
|
+
|
133
|
+
NetworkInterface.propTypes = {
|
134
|
+
id: PropTypes.number.isRequired,
|
135
|
+
networks: PropTypes.array,
|
136
|
+
bridges: PropTypes.array,
|
137
|
+
data: PropTypes.object,
|
138
|
+
updateNetworkData: PropTypes.func,
|
139
|
+
existingInterfaces: PropTypes.array,
|
140
|
+
};
|
141
|
+
|
142
|
+
NetworkInterface.defaultProps = {
|
143
|
+
networks: [],
|
144
|
+
bridges: [],
|
145
|
+
data: {},
|
146
|
+
updateNetworkData: Function.prototype,
|
147
|
+
existingInterfaces: [],
|
148
|
+
};
|
149
|
+
|
150
|
+
export default NetworkInterface;
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
2
|
+
|
3
|
+
const humanSize = size => {
|
4
|
+
const i = Math.floor(Math.log(size) / Math.log(1024));
|
5
|
+
return `${(size / 1000 ** i).toFixed(2) * 1} ${
|
6
|
+
['B', 'kB', 'MB', 'GB', 'TB'][i]
|
7
|
+
}`;
|
8
|
+
};
|
9
|
+
|
10
|
+
export const createStoragesMap = (
|
11
|
+
storages,
|
12
|
+
filterContent = null,
|
13
|
+
nodeId = null
|
14
|
+
) =>
|
15
|
+
storages
|
16
|
+
.filter(st => {
|
17
|
+
const contentMatch = filterContent
|
18
|
+
? st.content.includes(filterContent)
|
19
|
+
: true;
|
20
|
+
const nodeMatch = nodeId ? st.node_id === nodeId : true;
|
21
|
+
return contentMatch && nodeMatch;
|
22
|
+
})
|
23
|
+
.map(st => ({
|
24
|
+
value: st.storage,
|
25
|
+
label: sprintf(
|
26
|
+
__('%(name)s (free: %(free)s, used: %(used)s, total: %(total)s)'),
|
27
|
+
{
|
28
|
+
name: st.storage,
|
29
|
+
free: humanSize(st.avail),
|
30
|
+
used: humanSize(st.used),
|
31
|
+
total: humanSize(st.total),
|
32
|
+
}
|
33
|
+
),
|
34
|
+
}));
|
35
|
+
|
36
|
+
export const imagesByStorage = (storages, nodeId, storageId, type = 'iso') => {
|
37
|
+
const storage = storages.find(
|
38
|
+
st => st.node_id === nodeId && st.storage === storageId
|
39
|
+
);
|
40
|
+
|
41
|
+
const filteredVolumes = storage.volumes
|
42
|
+
.filter(volume => volume.content.includes(type))
|
43
|
+
.sort((a, b) => a.volid.localeCompare(b.volid))
|
44
|
+
.map(volume => ({
|
45
|
+
value: volume.volid,
|
46
|
+
label: volume.volid,
|
47
|
+
}));
|
48
|
+
|
49
|
+
return filteredVolumes;
|
50
|
+
};
|
@@ -0,0 +1,256 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import {
|
3
|
+
PageSection,
|
4
|
+
Divider,
|
5
|
+
Tabs,
|
6
|
+
Tab,
|
7
|
+
TabTitleText,
|
8
|
+
} from '@patternfly/react-core';
|
9
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
10
|
+
import PropTypes from 'prop-types';
|
11
|
+
import { networkSelected } from './ProxmoxVmUtils';
|
12
|
+
import ProxmoxComputeSelectors from './ProxmoxComputeSelectors';
|
13
|
+
import ProxmoxServerStorage from './ProxmoxServer/ProxmoxServerStorage';
|
14
|
+
import ProxmoxServerOptions from './ProxmoxServer/ProxmoxServerOptions';
|
15
|
+
import ProxmoxServerNetwork from './ProxmoxServer/ProxmoxServerNetwork';
|
16
|
+
import ProxmoxServerHardware from './ProxmoxServer/ProxmoxServerHardware';
|
17
|
+
import ProxmoxContainerNetwork from './ProxmoxContainer/ProxmoxContainerNetwork';
|
18
|
+
import ProxmoxContainerOptions from './ProxmoxContainer/ProxmoxContainerOptions';
|
19
|
+
import ProxmoxContainerStorage from './ProxmoxContainer/ProxmoxContainerStorage';
|
20
|
+
import ProxmoxContainerHardware from './ProxmoxContainer/ProxmoxContainerHardware';
|
21
|
+
import InputField from './common/FormInputs';
|
22
|
+
import GeneralTabContent from './GeneralTabContent';
|
23
|
+
|
24
|
+
const ProxmoxVmType = ({
|
25
|
+
vmAttrs,
|
26
|
+
nodes,
|
27
|
+
images,
|
28
|
+
pools,
|
29
|
+
fromProfile,
|
30
|
+
newVm,
|
31
|
+
storages,
|
32
|
+
bridges,
|
33
|
+
registerComp,
|
34
|
+
untemplatable,
|
35
|
+
}) => {
|
36
|
+
const nodesMap = nodes
|
37
|
+
? nodes.map(node => ({ value: node.node, label: node.node }))
|
38
|
+
: [];
|
39
|
+
const imagesMap = images
|
40
|
+
? [
|
41
|
+
{ value: '', label: '' },
|
42
|
+
...images.map(image => ({
|
43
|
+
value: image.uuid,
|
44
|
+
label: image.name,
|
45
|
+
})),
|
46
|
+
]
|
47
|
+
: [];
|
48
|
+
const poolsMap = pools
|
49
|
+
? [
|
50
|
+
{ value: '', label: '' },
|
51
|
+
...pools.map(pool => ({
|
52
|
+
value: pool.poolid,
|
53
|
+
label: pool.poolid,
|
54
|
+
})),
|
55
|
+
]
|
56
|
+
: [];
|
57
|
+
const [activeTabKey, setActiveTabKey] = useState(0);
|
58
|
+
const handleTabClick = (event, tabIndex) => {
|
59
|
+
setActiveTabKey(tabIndex);
|
60
|
+
};
|
61
|
+
const [general, setGeneral] = useState(vmAttrs);
|
62
|
+
const paramScope = fromProfile
|
63
|
+
? 'compute_attribute[vm_attrs]'
|
64
|
+
: 'host[compute_attributes]';
|
65
|
+
const [filteredBridges, setFilteredBridges] = useState([]);
|
66
|
+
useEffect(() => {
|
67
|
+
if (!registerComp && !fromProfile) {
|
68
|
+
networkSelected(general?.type?.value);
|
69
|
+
}
|
70
|
+
}, [general?.type?.value]);
|
71
|
+
|
72
|
+
useEffect(() => {
|
73
|
+
if (!registerComp) {
|
74
|
+
const filtered = bridges.filter(
|
75
|
+
bridge => bridge.node_id === general?.nodeId?.value
|
76
|
+
);
|
77
|
+
setFilteredBridges(filtered);
|
78
|
+
}
|
79
|
+
}, [general?.nodeId?.value, bridges]);
|
80
|
+
if (registerComp) {
|
81
|
+
return null;
|
82
|
+
}
|
83
|
+
const componentMap = {
|
84
|
+
qemu: {
|
85
|
+
options: <ProxmoxServerOptions options={vmAttrs} />,
|
86
|
+
hardware: <ProxmoxServerHardware hardware={vmAttrs} />,
|
87
|
+
network: (
|
88
|
+
<ProxmoxServerNetwork
|
89
|
+
network={vmAttrs?.interfaces || {}}
|
90
|
+
bridges={filteredBridges}
|
91
|
+
paramScope={paramScope}
|
92
|
+
/>
|
93
|
+
),
|
94
|
+
storage: (
|
95
|
+
<ProxmoxServerStorage
|
96
|
+
storage={vmAttrs?.disks || {}}
|
97
|
+
storages={storages}
|
98
|
+
nodeId={general?.nodeId?.value}
|
99
|
+
paramScope={paramScope}
|
100
|
+
/>
|
101
|
+
),
|
102
|
+
},
|
103
|
+
lxc: {
|
104
|
+
options: (
|
105
|
+
<ProxmoxContainerOptions
|
106
|
+
options={vmAttrs}
|
107
|
+
storages={storages}
|
108
|
+
paramScope={paramScope}
|
109
|
+
nodeId={general?.nodeId?.value}
|
110
|
+
/>
|
111
|
+
),
|
112
|
+
hardware: <ProxmoxContainerHardware hardware={vmAttrs} />,
|
113
|
+
network: (
|
114
|
+
<ProxmoxContainerNetwork
|
115
|
+
network={vmAttrs?.interfaces || {}}
|
116
|
+
bridges={filteredBridges}
|
117
|
+
paramScope={paramScope}
|
118
|
+
/>
|
119
|
+
),
|
120
|
+
storage: (
|
121
|
+
<ProxmoxContainerStorage
|
122
|
+
storage={vmAttrs?.disks || {}}
|
123
|
+
storages={storages}
|
124
|
+
nodeId={general?.nodeId?.value}
|
125
|
+
paramScope={paramScope}
|
126
|
+
/>
|
127
|
+
),
|
128
|
+
},
|
129
|
+
};
|
130
|
+
|
131
|
+
const handleChange = e => {
|
132
|
+
const { name, type, checked, value: targetValue } = e.target;
|
133
|
+
let value;
|
134
|
+
if (type === 'checkbox') {
|
135
|
+
value = checked ? '1' : '0';
|
136
|
+
} else {
|
137
|
+
value = targetValue;
|
138
|
+
}
|
139
|
+
const updatedKey = Object.keys(general).find(
|
140
|
+
key => general[key].name === name
|
141
|
+
);
|
142
|
+
|
143
|
+
setGeneral(prevGeneral => ({
|
144
|
+
...prevGeneral,
|
145
|
+
[updatedKey]: { ...prevGeneral[updatedKey], value },
|
146
|
+
}));
|
147
|
+
};
|
148
|
+
|
149
|
+
return (
|
150
|
+
<div>
|
151
|
+
<InputField
|
152
|
+
name={general?.type?.name}
|
153
|
+
label={__('Type')}
|
154
|
+
required
|
155
|
+
onChange={handleChange}
|
156
|
+
value={general?.type?.value}
|
157
|
+
options={ProxmoxComputeSelectors.proxmoxTypesMap}
|
158
|
+
disabled={!newVm}
|
159
|
+
type="select"
|
160
|
+
/>
|
161
|
+
<Tabs
|
162
|
+
activeKey={activeTabKey}
|
163
|
+
onSelect={handleTabClick}
|
164
|
+
aria-label="Options tabs"
|
165
|
+
role="region"
|
166
|
+
>
|
167
|
+
<Tab
|
168
|
+
eventKey={0}
|
169
|
+
title={<TabTitleText>{__('General')}</TabTitleText>}
|
170
|
+
aria-label="Default content - general"
|
171
|
+
>
|
172
|
+
<GeneralTabContent
|
173
|
+
general={general}
|
174
|
+
fromProfile={fromProfile}
|
175
|
+
newVm={newVm}
|
176
|
+
nodesMap={nodesMap}
|
177
|
+
poolsMap={poolsMap}
|
178
|
+
imagesMap={imagesMap}
|
179
|
+
handleChange={handleChange}
|
180
|
+
untemplatable={untemplatable}
|
181
|
+
/>
|
182
|
+
</Tab>
|
183
|
+
<Tab
|
184
|
+
eventKey={1}
|
185
|
+
title={<TabTitleText>{__('Advanced Options')}</TabTitleText>}
|
186
|
+
aria-label="advanced options"
|
187
|
+
>
|
188
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
189
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
190
|
+
{componentMap[general?.type?.value]?.options}
|
191
|
+
</PageSection>
|
192
|
+
</Tab>
|
193
|
+
<Tab
|
194
|
+
eventKey={2}
|
195
|
+
title={<TabTitleText>{__('Hardware')}</TabTitleText>}
|
196
|
+
aria-label="hardware"
|
197
|
+
>
|
198
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
199
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
200
|
+
{componentMap[general?.type?.value]?.hardware}
|
201
|
+
</PageSection>
|
202
|
+
</Tab>
|
203
|
+
{fromProfile && (
|
204
|
+
<Tab
|
205
|
+
eventKey={3}
|
206
|
+
title={<TabTitleText>{__('Network Interfaces')}</TabTitleText>}
|
207
|
+
aria-label="Network interface"
|
208
|
+
>
|
209
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
210
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
211
|
+
{componentMap[general?.type?.value]?.network}
|
212
|
+
</PageSection>
|
213
|
+
</Tab>
|
214
|
+
)}
|
215
|
+
<Tab
|
216
|
+
eventKey={4}
|
217
|
+
title={<TabTitleText>{__('Storage')}</TabTitleText>}
|
218
|
+
aria-label="storage"
|
219
|
+
>
|
220
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
221
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
222
|
+
{componentMap[general?.type?.value]?.storage}
|
223
|
+
</PageSection>
|
224
|
+
</Tab>
|
225
|
+
</Tabs>
|
226
|
+
</div>
|
227
|
+
);
|
228
|
+
};
|
229
|
+
|
230
|
+
ProxmoxVmType.propTypes = {
|
231
|
+
vmAttrs: PropTypes.object,
|
232
|
+
nodes: PropTypes.array,
|
233
|
+
images: PropTypes.array,
|
234
|
+
pools: PropTypes.array,
|
235
|
+
fromProfile: PropTypes.bool,
|
236
|
+
newVm: PropTypes.bool,
|
237
|
+
storages: PropTypes.array,
|
238
|
+
bridges: PropTypes.array,
|
239
|
+
registerComp: PropTypes.bool,
|
240
|
+
untemplatable: PropTypes.bool,
|
241
|
+
};
|
242
|
+
|
243
|
+
ProxmoxVmType.defaultProps = {
|
244
|
+
vmAttrs: {},
|
245
|
+
nodes: [],
|
246
|
+
images: [],
|
247
|
+
pools: [],
|
248
|
+
fromProfile: false,
|
249
|
+
newVm: false,
|
250
|
+
storages: [],
|
251
|
+
bridges: [],
|
252
|
+
registerComp: false,
|
253
|
+
untemplatable: false,
|
254
|
+
};
|
255
|
+
|
256
|
+
export default ProxmoxVmType;
|