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