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,107 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { PageSection, Divider } from '@patternfly/react-core';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import InputField from './common/FormInputs';
|
6
|
+
|
7
|
+
const GeneralTabContent = ({
|
8
|
+
general,
|
9
|
+
fromProfile,
|
10
|
+
newVm,
|
11
|
+
nodesMap,
|
12
|
+
poolsMap,
|
13
|
+
imagesMap,
|
14
|
+
handleChange,
|
15
|
+
untemplatable,
|
16
|
+
}) => (
|
17
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
18
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
19
|
+
<InputField
|
20
|
+
name={general?.vmid?.name}
|
21
|
+
label={__('VM ID')}
|
22
|
+
required
|
23
|
+
type="number"
|
24
|
+
value={general?.vmid?.value}
|
25
|
+
disabled={fromProfile}
|
26
|
+
onChange={handleChange}
|
27
|
+
/>
|
28
|
+
<InputField
|
29
|
+
name={general?.nodeId?.name}
|
30
|
+
label={__('Node')}
|
31
|
+
required
|
32
|
+
type="select"
|
33
|
+
value={general?.nodeId?.value}
|
34
|
+
options={nodesMap}
|
35
|
+
onChange={handleChange}
|
36
|
+
/>
|
37
|
+
<InputField
|
38
|
+
name={general?.pool?.name}
|
39
|
+
label={__('Pool')}
|
40
|
+
type="select"
|
41
|
+
value={general?.pool?.value}
|
42
|
+
options={poolsMap}
|
43
|
+
onChange={handleChange}
|
44
|
+
/>
|
45
|
+
{newVm && !fromProfile && (
|
46
|
+
<InputField
|
47
|
+
name={general?.startAfterCreate?.name}
|
48
|
+
label={__('Start after creation?')}
|
49
|
+
type="checkbox"
|
50
|
+
value={general?.startAfterCreate?.value}
|
51
|
+
checked={general?.startAfterCreate?.value === '1'}
|
52
|
+
onChange={handleChange}
|
53
|
+
/>
|
54
|
+
)}
|
55
|
+
{!fromProfile && !untemplatable && (
|
56
|
+
<InputField
|
57
|
+
name={general?.templated?.name}
|
58
|
+
label={__('Create image?')}
|
59
|
+
type="checkbox"
|
60
|
+
value={general?.templated?.value}
|
61
|
+
checked={general?.templated?.value === '1'}
|
62
|
+
disabled={untemplatable}
|
63
|
+
onChange={handleChange}
|
64
|
+
/>
|
65
|
+
)}
|
66
|
+
{fromProfile && (
|
67
|
+
<InputField
|
68
|
+
name={general?.imageId?.name}
|
69
|
+
label={__('Image')}
|
70
|
+
type="select"
|
71
|
+
value={general?.imageId?.value}
|
72
|
+
disabled={imagesMap.length === 0}
|
73
|
+
options={imagesMap}
|
74
|
+
onChange={handleChange}
|
75
|
+
/>
|
76
|
+
)}
|
77
|
+
<InputField
|
78
|
+
name={general?.description?.name}
|
79
|
+
label={__('Description')}
|
80
|
+
type="textarea"
|
81
|
+
value={general?.description?.value}
|
82
|
+
onChange={handleChange}
|
83
|
+
/>
|
84
|
+
</PageSection>
|
85
|
+
);
|
86
|
+
|
87
|
+
GeneralTabContent.propTypes = {
|
88
|
+
general: PropTypes.object.isRequired,
|
89
|
+
fromProfile: PropTypes.bool,
|
90
|
+
newVm: PropTypes.bool,
|
91
|
+
nodesMap: PropTypes.array,
|
92
|
+
poolsMap: PropTypes.array,
|
93
|
+
imagesMap: PropTypes.array,
|
94
|
+
handleChange: PropTypes.func.isRequired,
|
95
|
+
untemplatable: PropTypes.bool,
|
96
|
+
};
|
97
|
+
|
98
|
+
GeneralTabContent.defaultProps = {
|
99
|
+
fromProfile: false,
|
100
|
+
newVm: false,
|
101
|
+
nodesMap: [],
|
102
|
+
poolsMap: [],
|
103
|
+
imagesMap: [],
|
104
|
+
untemplatable: false,
|
105
|
+
};
|
106
|
+
|
107
|
+
export default GeneralTabContent;
|
@@ -0,0 +1,141 @@
|
|
1
|
+
const ProxmoxComputeSelectors = {
|
2
|
+
proxmoxTypesMap: [
|
3
|
+
{ value: 'qemu', label: 'KVM/Qemu server' },
|
4
|
+
{ value: 'lxc', label: 'LXC container' },
|
5
|
+
],
|
6
|
+
|
7
|
+
proxmoxControllersCloudinitMap: [
|
8
|
+
{ value: 'ide', label: 'IDE' },
|
9
|
+
{ value: 'sata', label: 'SATA' },
|
10
|
+
{ value: 'scsi', label: 'SCSI' },
|
11
|
+
],
|
12
|
+
|
13
|
+
proxmoxScsiControllersMap: [
|
14
|
+
{ value: 'lsi', label: 'LSI 53C895A (Default)' },
|
15
|
+
{ value: 'lsi53c810', label: 'LSI 53C810' },
|
16
|
+
{ value: 'virtio-scsi-pci', label: 'VirtIO SCSI' },
|
17
|
+
{ value: 'virtio-scsi-single', label: 'VirtIO SCSI Single' },
|
18
|
+
{ value: 'megasas', label: 'MegaRAID SAS 8708EM2' },
|
19
|
+
{ value: 'pvscsi', label: 'VMware PVSCSI' },
|
20
|
+
],
|
21
|
+
|
22
|
+
proxmoxArchsMap: [
|
23
|
+
{ value: 'amd64', label: '64 bits' },
|
24
|
+
{ value: 'i386', label: '32 bits' },
|
25
|
+
],
|
26
|
+
|
27
|
+
proxmoxOstypesMap: [
|
28
|
+
{ value: 'debian', label: 'Debian' },
|
29
|
+
{ value: 'ubuntu', label: 'Ubuntu' },
|
30
|
+
{ value: 'centos', label: 'CentOS' },
|
31
|
+
{ value: 'fedora', label: 'Fedora' },
|
32
|
+
{ value: 'opensuse', label: 'OpenSuse' },
|
33
|
+
{ value: 'archlinux', label: 'ArchLinux' },
|
34
|
+
{ value: 'gentoo', label: 'Gentoo' },
|
35
|
+
{ value: 'alpine', label: 'Alpine' },
|
36
|
+
{ value: 'unmanaged', label: 'Unmanaged' },
|
37
|
+
],
|
38
|
+
|
39
|
+
proxmoxOperatingSystemsMap: [
|
40
|
+
{ value: 'other', label: 'Unspecified OS' },
|
41
|
+
{ value: 'wxp', label: 'Microsoft Windows XP' },
|
42
|
+
{ value: 'w2k', label: 'Microsoft Windows 2000' },
|
43
|
+
{ value: 'w2k3', label: 'Microsoft Windows 2003' },
|
44
|
+
{ value: 'w2k8', label: 'Microsoft Windows 2008' },
|
45
|
+
{ value: 'wvista', label: 'Microsoft Windows Vista' },
|
46
|
+
{ value: 'win7', label: 'Microsoft Windows 7' },
|
47
|
+
{ value: 'win8', label: 'Microsoft Windows 8/2012/2012r2' },
|
48
|
+
{ value: 'win10', label: 'Microsoft Windows 10/2016' },
|
49
|
+
{ value: 'l24', label: 'Linux 2.4 Kernel' },
|
50
|
+
{ value: 'l26', label: 'Linux 2.6/3.X + Kernel' },
|
51
|
+
{ value: 'solaris', label: 'Solaris/OpenSolaris/OpenIndiania kernel' },
|
52
|
+
],
|
53
|
+
|
54
|
+
proxmoxVgasMap: [
|
55
|
+
{ value: 'std', label: 'Standard VGA' },
|
56
|
+
{ value: 'vmware', label: 'Vmware compatible' },
|
57
|
+
{ value: 'qxl', label: 'SPICE' },
|
58
|
+
{ value: 'qxl2', label: 'SPICE 2 monitors' },
|
59
|
+
{ value: 'qxl3', label: 'SPICE 3 monitors' },
|
60
|
+
{ value: 'qxl4', label: 'SPICE 4 monitors' },
|
61
|
+
{ value: 'serial0', label: 'Serial terminal 0' },
|
62
|
+
{ value: 'serial1', label: 'Serial terminal 1' },
|
63
|
+
{ value: 'serial2', label: 'Serial terminal 2' },
|
64
|
+
{ value: 'serial3', label: 'Serial terminal 3' },
|
65
|
+
],
|
66
|
+
|
67
|
+
proxmoxCachesMap: [
|
68
|
+
{ value: '', labal: '' },
|
69
|
+
{ value: 'directsync', label: 'Direct sync' },
|
70
|
+
{ value: 'writethrough', label: 'Write through' },
|
71
|
+
{ value: 'writeback', label: 'Write back' },
|
72
|
+
{ value: 'unsafe', label: 'Write back unsafe' },
|
73
|
+
{ value: 'none', label: 'No cache' },
|
74
|
+
],
|
75
|
+
|
76
|
+
proxmoxCpusMap: [
|
77
|
+
{ value: '486', label: '486' },
|
78
|
+
{ value: 'athlon', label: 'athlon' },
|
79
|
+
{ value: 'core2duo', label: 'core2duo' },
|
80
|
+
{ value: 'coreduo', label: 'coreduo' },
|
81
|
+
{ value: 'kvm32', label: 'kvm32' },
|
82
|
+
{ value: 'kvm64', label: '(Default) kvm64' },
|
83
|
+
{ value: 'pentium', label: 'pentium' },
|
84
|
+
{ value: 'pentium2', label: 'pentium2' },
|
85
|
+
{ value: 'pentium3', label: 'pentium3' },
|
86
|
+
{ value: 'phenom', label: 'phenom' },
|
87
|
+
{ value: 'qemu32', label: 'qemu32' },
|
88
|
+
{ value: 'qemu64', label: 'qemu64' },
|
89
|
+
{ value: 'Conroe', label: 'Conroe' },
|
90
|
+
{ value: 'Penryn', label: 'Penryn' },
|
91
|
+
{ value: 'Nehalem', label: 'Nehalem' },
|
92
|
+
{ value: 'Westmere', label: 'Westmere' },
|
93
|
+
{ value: 'SandyBridge', label: 'SandyBridge' },
|
94
|
+
{ value: 'IvyBridge', label: 'IvyBridge' },
|
95
|
+
{ value: 'Haswell', label: 'Haswell' },
|
96
|
+
{ value: 'Haswell-noTSX', label: 'Haswell-noTSX' },
|
97
|
+
{ value: 'Broadwell', label: 'Broadwell' },
|
98
|
+
{ value: 'Broadwell-noTSX', label: 'Broadwell-noTSX' },
|
99
|
+
{ value: 'Skylake-Client', label: 'Skylake-Client' },
|
100
|
+
{ value: 'Opteron_G1', label: 'Opteron_G1' },
|
101
|
+
{ value: 'Opteron_G2', label: 'Opteron_G2' },
|
102
|
+
{ value: 'Opteron_G3', label: 'Opteron_G3' },
|
103
|
+
{ value: 'Opteron_G4', label: 'Opteron_G4' },
|
104
|
+
{ value: 'Opteron_G5', label: 'Opteron_G5' },
|
105
|
+
{ value: 'host', label: 'host' },
|
106
|
+
],
|
107
|
+
|
108
|
+
proxmoxCpuFlagsMap: [
|
109
|
+
{ value: '-1', label: 'Off' },
|
110
|
+
{ value: '0', label: 'Default' },
|
111
|
+
{ value: '+1', label: 'On' },
|
112
|
+
],
|
113
|
+
|
114
|
+
proxmoxScsihwMap: [
|
115
|
+
{ value: 'lsi', label: 'lsi' },
|
116
|
+
{ value: 'lsi53c810', label: 'lsi53c810' },
|
117
|
+
{ value: 'megasas', label: 'megasas' },
|
118
|
+
{ value: 'virtio-scsi-pci', label: 'virtio-scsi-pci' },
|
119
|
+
{ value: 'virtio-scsi-single', label: 'virtio-scsi-single' },
|
120
|
+
{ value: 'pvscsi', label: 'pvscsi' },
|
121
|
+
],
|
122
|
+
|
123
|
+
proxmoxNetworkcardsMap: [
|
124
|
+
{ value: 'e1000', label: 'Intel E1000' },
|
125
|
+
{ value: 'virtio', label: 'VirtIO (paravirtualized)' },
|
126
|
+
{ value: 'rtl8139', label: 'Realtek RTL8139' },
|
127
|
+
{ value: 'vmxnet3', label: 'VMware vmxnet3' },
|
128
|
+
],
|
129
|
+
|
130
|
+
proxmoxBiosMap: [
|
131
|
+
{ value: 'seabios', label: '(Default) Seabios' },
|
132
|
+
{ value: 'ovmf', label: 'OVMF (UEFI)' },
|
133
|
+
],
|
134
|
+
};
|
135
|
+
|
136
|
+
ProxmoxComputeSelectors.proxmoxControllersHDDMap = [
|
137
|
+
...ProxmoxComputeSelectors.proxmoxControllersCloudinitMap,
|
138
|
+
{ value: 'virtio', label: 'VirtIO Block' },
|
139
|
+
];
|
140
|
+
|
141
|
+
export default ProxmoxComputeSelectors;
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { Divider } 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
|
+
|
7
|
+
const MountPoint = ({ id, data, storagesMap }) => {
|
8
|
+
const [mp, setMp] = useState(data);
|
9
|
+
const [error, setError] = useState('');
|
10
|
+
|
11
|
+
const handleChange = e => {
|
12
|
+
const { name, value } = e.target;
|
13
|
+
const updatedKey = Object.keys(mp).find(key => mp[key].name === name);
|
14
|
+
const updatedData = {
|
15
|
+
...mp,
|
16
|
+
[updatedKey]: { ...mp[updatedKey], value },
|
17
|
+
};
|
18
|
+
setMp(updatedData);
|
19
|
+
|
20
|
+
if (updatedKey === 'mp' && value.trim() === '') {
|
21
|
+
setError(__('Path cannot be empty'));
|
22
|
+
} else {
|
23
|
+
setError('');
|
24
|
+
}
|
25
|
+
};
|
26
|
+
|
27
|
+
return (
|
28
|
+
<div>
|
29
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
30
|
+
<InputField
|
31
|
+
name={mp?.storage?.name}
|
32
|
+
label={__('Storage')}
|
33
|
+
type="select"
|
34
|
+
options={storagesMap}
|
35
|
+
value={mp?.storage?.value}
|
36
|
+
onChange={handleChange}
|
37
|
+
/>
|
38
|
+
<InputField
|
39
|
+
name={mp?.mp?.name}
|
40
|
+
label={__('Path')}
|
41
|
+
required
|
42
|
+
value={mp?.mp?.value}
|
43
|
+
onChange={handleChange}
|
44
|
+
error={error}
|
45
|
+
/>
|
46
|
+
<InputField
|
47
|
+
name={mp?.size?.name}
|
48
|
+
label={__('Size')}
|
49
|
+
type="number"
|
50
|
+
value={mp?.size?.value}
|
51
|
+
onChange={handleChange}
|
52
|
+
/>
|
53
|
+
<input
|
54
|
+
name={mp?.device?.name}
|
55
|
+
type="hidden"
|
56
|
+
value={mp?.device?.value}
|
57
|
+
onChange={handleChange}
|
58
|
+
/>
|
59
|
+
<input
|
60
|
+
name={mp?.id?.name}
|
61
|
+
type="hidden"
|
62
|
+
value={mp?.id?.value}
|
63
|
+
onChange={handleChange}
|
64
|
+
/>
|
65
|
+
<input
|
66
|
+
name={mp?.volid?.name}
|
67
|
+
type="hidden"
|
68
|
+
value={mp?.volid?.value}
|
69
|
+
onChange={handleChange}
|
70
|
+
/>
|
71
|
+
<input
|
72
|
+
name={mp?.storageType?.name}
|
73
|
+
type="hidden"
|
74
|
+
value={mp?.storageType?.value}
|
75
|
+
/>
|
76
|
+
</div>
|
77
|
+
);
|
78
|
+
};
|
79
|
+
|
80
|
+
MountPoint.propTypes = {
|
81
|
+
id: PropTypes.any.isRequired,
|
82
|
+
data: PropTypes.object,
|
83
|
+
storagesMap: PropTypes.array,
|
84
|
+
};
|
85
|
+
|
86
|
+
MountPoint.defaultProps = {
|
87
|
+
data: {},
|
88
|
+
storagesMap: [],
|
89
|
+
};
|
90
|
+
|
91
|
+
export default MountPoint;
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { PageSection, Title, Divider } 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
|
+
|
8
|
+
const ProxmoxContainerHardware = ({ hardware }) => {
|
9
|
+
const [hw, setHw] = useState(hardware);
|
10
|
+
const handleChange = e => {
|
11
|
+
const { name, value } = e.target;
|
12
|
+
const updatedKey = Object.keys(hw).find(key => hw[key].name === name);
|
13
|
+
|
14
|
+
setHw(prevHw => ({
|
15
|
+
...prevHw,
|
16
|
+
[updatedKey]: { ...prevHw[updatedKey], value },
|
17
|
+
}));
|
18
|
+
};
|
19
|
+
|
20
|
+
return (
|
21
|
+
<div>
|
22
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
23
|
+
<Title headingLevel="h3">{__('CPU')}</Title>
|
24
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
25
|
+
<InputField
|
26
|
+
name={hw?.arch?.name}
|
27
|
+
label={__('Architecture')}
|
28
|
+
type="select"
|
29
|
+
options={ProxmoxComputeSelectors.proxmoxArchsMap}
|
30
|
+
value={hw?.arch?.value}
|
31
|
+
onChange={handleChange}
|
32
|
+
/>
|
33
|
+
<InputField
|
34
|
+
name={hw?.cores?.name}
|
35
|
+
label={__('Cores')}
|
36
|
+
type="number"
|
37
|
+
value={hw?.cores?.value}
|
38
|
+
onChange={handleChange}
|
39
|
+
/>
|
40
|
+
<InputField
|
41
|
+
name={hw?.cpulimit?.name}
|
42
|
+
label={__('CPU limit')}
|
43
|
+
type="number"
|
44
|
+
value={hw?.cpulimit?.value}
|
45
|
+
onChange={handleChange}
|
46
|
+
/>
|
47
|
+
<InputField
|
48
|
+
name={hw?.cpuunits?.name}
|
49
|
+
label={__('CPU units')}
|
50
|
+
type="number"
|
51
|
+
value={hw?.cpuunits?.value}
|
52
|
+
onChange={handleChange}
|
53
|
+
/>
|
54
|
+
</PageSection>
|
55
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
56
|
+
<Title headingLevel="h3">{__('Memory')}</Title>
|
57
|
+
<Divider component="li" style={{ marginBottom: '2rem' }} />
|
58
|
+
<InputField
|
59
|
+
name={hw?.memory?.name}
|
60
|
+
label={__('Memory (MB)')}
|
61
|
+
type="text"
|
62
|
+
value={hw?.memory?.value}
|
63
|
+
onChange={handleChange}
|
64
|
+
/>
|
65
|
+
<InputField
|
66
|
+
name={hw?.swap?.name}
|
67
|
+
label={__('Swap (MB)')}
|
68
|
+
type="text"
|
69
|
+
value={hw?.swap?.value}
|
70
|
+
onChange={handleChange}
|
71
|
+
/>
|
72
|
+
</PageSection>
|
73
|
+
</div>
|
74
|
+
);
|
75
|
+
};
|
76
|
+
|
77
|
+
ProxmoxContainerHardware.propTypes = {
|
78
|
+
hardware: PropTypes.object,
|
79
|
+
};
|
80
|
+
|
81
|
+
ProxmoxContainerHardware.defaultProps = {
|
82
|
+
hardware: {},
|
83
|
+
};
|
84
|
+
|
85
|
+
export default ProxmoxContainerHardware;
|
@@ -0,0 +1,179 @@
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
2
|
+
import { Title, PageSection, Button } 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 NetworkInterface from './components/NetworkInterface';
|
7
|
+
|
8
|
+
const ProxmoxContainerNetwork = ({ network, bridges, paramScope }) => {
|
9
|
+
const [interfaces, setInterfaces] = useState([]);
|
10
|
+
const [nextId, setNextId] = useState(0);
|
11
|
+
const [availableIds, setAvailableIds] = useState([]);
|
12
|
+
const [usedIds, setUsedIds] = useState(new Set());
|
13
|
+
useEffect(() => {
|
14
|
+
if (network?.length > 0) {
|
15
|
+
const existingIds = new Set();
|
16
|
+
network.forEach(net => {
|
17
|
+
if (!net.value.name.value) return;
|
18
|
+
const id = parseInt(net.value.id.value.replace('net', ''), 10);
|
19
|
+
existingIds.add(id);
|
20
|
+
addInterface(null, net.value);
|
21
|
+
});
|
22
|
+
setUsedIds(existingIds);
|
23
|
+
}
|
24
|
+
}, [network]);
|
25
|
+
|
26
|
+
const getLowestAvailableId = useCallback(() => {
|
27
|
+
let id = 0;
|
28
|
+
while (usedIds.has(id)) {
|
29
|
+
id += 1;
|
30
|
+
}
|
31
|
+
return id;
|
32
|
+
}, [usedIds]);
|
33
|
+
|
34
|
+
const addInterface = useCallback(
|
35
|
+
(event, initialData = null) => {
|
36
|
+
if (event) event.preventDefault();
|
37
|
+
const netId = getLowestAvailableId();
|
38
|
+
const initData = initialData || {
|
39
|
+
id: {
|
40
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][id]`,
|
41
|
+
value: `net${netId}`,
|
42
|
+
},
|
43
|
+
name: {
|
44
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][name]`,
|
45
|
+
value: `eth${netId}`,
|
46
|
+
},
|
47
|
+
bridge: {
|
48
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][bridge]`,
|
49
|
+
value: bridges?.[0]?.iface || '',
|
50
|
+
},
|
51
|
+
dhcp: {
|
52
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][dhcp]`,
|
53
|
+
value: '0',
|
54
|
+
},
|
55
|
+
cidr: {
|
56
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][cidr]`,
|
57
|
+
value: '0',
|
58
|
+
},
|
59
|
+
gw: {
|
60
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][gw]`,
|
61
|
+
value: '',
|
62
|
+
},
|
63
|
+
dhcp6: {
|
64
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][dhcp6]`,
|
65
|
+
value: '0',
|
66
|
+
},
|
67
|
+
cidr6: {
|
68
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][cidr6]`,
|
69
|
+
value: '0',
|
70
|
+
},
|
71
|
+
gw6: {
|
72
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][gw6]`,
|
73
|
+
value: '',
|
74
|
+
},
|
75
|
+
tag: {
|
76
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][tag]`,
|
77
|
+
value: '',
|
78
|
+
},
|
79
|
+
rate: {
|
80
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][rate]`,
|
81
|
+
value: '',
|
82
|
+
},
|
83
|
+
firewall: {
|
84
|
+
name: `${paramScope}[interfaces_attributes][${nextId}][firewall]`,
|
85
|
+
value: '0',
|
86
|
+
},
|
87
|
+
};
|
88
|
+
setNextId(prevId => {
|
89
|
+
if (availableIds.length > 0) {
|
90
|
+
setAvailableIds(availableIds.slice(1));
|
91
|
+
} else {
|
92
|
+
prevId += 1;
|
93
|
+
}
|
94
|
+
setUsedIds(prevIds => new Set(prevIds).add(netId));
|
95
|
+
const newId = availableIds.length > 0 ? availableIds[0] : prevId;
|
96
|
+
const newInterface = {
|
97
|
+
id: newId,
|
98
|
+
bridges,
|
99
|
+
data: initData,
|
100
|
+
networks: network,
|
101
|
+
};
|
102
|
+
|
103
|
+
setInterfaces(prevInterfaces => [...prevInterfaces, newInterface]);
|
104
|
+
return prevId;
|
105
|
+
});
|
106
|
+
},
|
107
|
+
[availableIds, bridges, network, nextId, paramScope, getLowestAvailableId]
|
108
|
+
);
|
109
|
+
|
110
|
+
const removeInterface = idToRemove => {
|
111
|
+
const newInterfaces = interfaces.filter(nic => nic.id !== idToRemove);
|
112
|
+
setInterfaces(newInterfaces);
|
113
|
+
setAvailableIds([...availableIds, idToRemove].sort((a, b) => a - b));
|
114
|
+
setUsedIds(prevIds => {
|
115
|
+
const newIds = new Set(prevIds);
|
116
|
+
newIds.delete(idToRemove);
|
117
|
+
return newIds;
|
118
|
+
});
|
119
|
+
};
|
120
|
+
|
121
|
+
const updateNetworkData = (id, updatedData) => {
|
122
|
+
setInterfaces(
|
123
|
+
interfaces.map(net =>
|
124
|
+
net.id === id ? { ...net, data: updatedData } : net
|
125
|
+
)
|
126
|
+
);
|
127
|
+
};
|
128
|
+
|
129
|
+
return (
|
130
|
+
<div>
|
131
|
+
<PageSection padding={{ default: 'noPadding' }}>
|
132
|
+
<Button onClick={addInterface} variant="secondary">
|
133
|
+
{__('Add Interface')}
|
134
|
+
</Button>
|
135
|
+
{interfaces.map(nic => (
|
136
|
+
<div key={nic.id} style={{ position: 'relative' }}>
|
137
|
+
<div
|
138
|
+
style={{
|
139
|
+
marginTop: '10px',
|
140
|
+
display: 'flex',
|
141
|
+
justifyContent: 'space-between',
|
142
|
+
alignItems: 'center',
|
143
|
+
}}
|
144
|
+
>
|
145
|
+
<Title headingLevel="h4">
|
146
|
+
{sprintf(__('Nic %(nicId)s'), { nicId: nic.id })}
|
147
|
+
</Title>
|
148
|
+
<button onClick={() => removeInterface(nic.id)} type="button">
|
149
|
+
<TimesIcon />
|
150
|
+
</button>
|
151
|
+
</div>
|
152
|
+
<NetworkInterface
|
153
|
+
id={nic.id}
|
154
|
+
data={nic.data}
|
155
|
+
bridges={nic.bridges}
|
156
|
+
networks={nic.networks}
|
157
|
+
updateNetworkData={updateNetworkData}
|
158
|
+
existingInterfaces={interfaces}
|
159
|
+
/>
|
160
|
+
</div>
|
161
|
+
))}
|
162
|
+
</PageSection>
|
163
|
+
</div>
|
164
|
+
);
|
165
|
+
};
|
166
|
+
|
167
|
+
ProxmoxContainerNetwork.propTypes = {
|
168
|
+
network: PropTypes.object,
|
169
|
+
bridges: PropTypes.array,
|
170
|
+
paramScope: PropTypes.string,
|
171
|
+
};
|
172
|
+
|
173
|
+
ProxmoxContainerNetwork.defaultProps = {
|
174
|
+
network: {},
|
175
|
+
bridges: [],
|
176
|
+
paramScope: '',
|
177
|
+
};
|
178
|
+
|
179
|
+
export default ProxmoxContainerNetwork;
|