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