foreman_fog_proxmox 0.15.1 → 0.16.1

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/proxmox_compute_resources_helper.rb +1 -1
  3. data/app/helpers/proxmox_form_helper.rb +2 -2
  4. data/app/helpers/proxmox_vm_attrs_helper.rb +131 -0
  5. data/app/helpers/proxmox_vm_interfaces_helper.rb +3 -3
  6. data/app/helpers/proxmox_vm_volumes_helper.rb +3 -3
  7. data/app/models/concerns/fog_extensions/proxmox/node.rb +1 -1
  8. data/app/models/foreman_fog_proxmox/proxmox_compute_attributes.rb +3 -3
  9. data/app/models/foreman_fog_proxmox/proxmox_images.rb +5 -0
  10. data/app/models/foreman_fog_proxmox/proxmox_version.rb +1 -1
  11. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +1 -1
  12. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +1 -1
  13. data/app/overrides/compute_resources_vms/form/add_from_profile_to_compute_attributes_form.rb +8 -0
  14. data/app/overrides/compute_resources_vms/form/add_react_component_to_host.rb +25 -0
  15. data/app/overrides/compute_resources_vms/form/update_react_component_to_host_form.rb +25 -0
  16. data/app/views/compute_resources_vms/form/proxmox/_add_react_component_to_host_form.html.erb +5 -0
  17. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +21 -21
  18. data/app/views/compute_resources_vms/form/proxmox/_update_react_component_to_host_form.html.erb +26 -0
  19. data/config/routes.rb +3 -3
  20. data/lib/foreman_fog_proxmox/version.rb +1 -1
  21. data/package.json +42 -0
  22. data/test/factories/proxmox_factory.rb +7 -7
  23. data/test/unit/foreman_fog_proxmox/proxmox_compute_attributes_test.rb +1 -1
  24. data/test/unit/foreman_fog_proxmox/proxmox_interfaces_test.rb +6 -6
  25. data/webpack/components/GeneralTabContent.js +107 -0
  26. data/webpack/components/ProxmoxComputeSelectors.js +141 -0
  27. data/webpack/components/ProxmoxContainer/MountPoint.js +91 -0
  28. data/webpack/components/ProxmoxContainer/ProxmoxContainerHardware.js +85 -0
  29. data/webpack/components/ProxmoxContainer/ProxmoxContainerNetwork.js +179 -0
  30. data/webpack/components/ProxmoxContainer/ProxmoxContainerOptions.js +104 -0
  31. data/webpack/components/ProxmoxContainer/ProxmoxContainerStorage.js +194 -0
  32. data/webpack/components/ProxmoxContainer/components/NetworkInterface.js +193 -0
  33. data/webpack/components/ProxmoxServer/ProxmoxServerHardware.js +204 -0
  34. data/webpack/components/ProxmoxServer/ProxmoxServerNetwork.js +161 -0
  35. data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +105 -0
  36. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +272 -0
  37. data/webpack/components/ProxmoxServer/components/CDRom.js +149 -0
  38. data/webpack/components/ProxmoxServer/components/CPUFlagsModal.js +88 -0
  39. data/webpack/components/ProxmoxServer/components/HardDisk.js +143 -0
  40. data/webpack/components/ProxmoxServer/components/NetworkInterface.js +150 -0
  41. data/webpack/components/ProxmoxStoragesUtils.js +50 -0
  42. data/webpack/components/ProxmoxVmType.js +256 -0
  43. data/webpack/components/ProxmoxVmUtils.js +62 -0
  44. data/webpack/components/common/FormInputs.js +143 -0
  45. data/webpack/global_index.js +15 -0
  46. data/webpack/index.js +7 -0
  47. metadata +49 -21
@@ -0,0 +1,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;