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