foreman_fog_proxmox 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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,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;
@@ -0,0 +1,62 @@
1
+ import $ from 'jquery';
2
+
3
+ function networkSelected(value) {
4
+ const fieldSets = [];
5
+ fieldSets.push({
6
+ id: 'network',
7
+ toggle: true,
8
+ newVm: true,
9
+ selected: value,
10
+ });
11
+ fieldSets.forEach(toggleFieldsets);
12
+ return false;
13
+ }
14
+
15
+ function enableFieldSet(fieldSetId, fieldSet) {
16
+ if (fieldSet.toggle && fieldSet.newVm) {
17
+ getFieldSetById(fieldSetId, fieldSet).show();
18
+ }
19
+ getFieldSetById(fieldSetId, fieldSet).removeAttr('disabled');
20
+ getInputHiddenById(fieldSetId).removeAttr('disabled');
21
+ }
22
+
23
+ function disableFieldSet(fieldSetId, fieldSet) {
24
+ if (fieldSet.toggle && fieldSet.newVm) {
25
+ getFieldSetById(fieldSetId, fieldSet).hide();
26
+ }
27
+ getFieldSetById(fieldSetId, fieldSet).attr('disabled', 'disabled');
28
+ getInputHiddenById(fieldSetId).attr('disabled', 'disabled');
29
+ }
30
+
31
+ function toggleFieldSet(fieldSetId, fieldSet, type1, type2) {
32
+ type1 === type2
33
+ ? enableFieldSet(fieldSetId, fieldSet)
34
+ : disableFieldSet(fieldSetId, fieldSet);
35
+ }
36
+
37
+ function getInputHiddenById(volumeId) {
38
+ return $(`div[id^='${volumeId}_volumes'] + input:hidden`);
39
+ }
40
+
41
+ function getFieldSetById(fieldSetId, fieldSet) {
42
+ return $(`fieldset[id^='${fieldSetId}_${fieldSet.id}']`);
43
+ }
44
+
45
+ function getFieldSets(type) {
46
+ return type === 'qemu' ? ['server'] : ['container'];
47
+ }
48
+
49
+ function toggleFieldsets(fieldSet) {
50
+ const removableInputHidden = $(
51
+ `div.removable-item[style='display: none;'] + input:hidden`
52
+ );
53
+ removableInputHidden.attr('disabled', 'disabled');
54
+ ['qemu', 'lxc'].forEach(type => {
55
+ getFieldSets(type).forEach(fieldSetId => {
56
+ toggleFieldSet(fieldSetId, fieldSet, fieldSet.selected, type);
57
+ });
58
+ });
59
+ }
60
+
61
+ window.networkSelected = networkSelected;
62
+ export { networkSelected };
@@ -0,0 +1,143 @@
1
+ import React, { Fragment } from 'react';
2
+ import { FieldLevelHelp } from 'patternfly-react';
3
+ import PropTypes from 'prop-types';
4
+ import CommonForm from 'foremanReact/components/common/forms/CommonForm';
5
+
6
+ const InputField = ({
7
+ name,
8
+ label,
9
+ info,
10
+ value,
11
+ onChange,
12
+ required,
13
+ type,
14
+ disabled,
15
+ options,
16
+ checked,
17
+ error,
18
+ }) => {
19
+ const renderOptions = opts =>
20
+ opts.map(option => <option value={option.value}>{option.label}</option>);
21
+
22
+ let renderComponent;
23
+
24
+ switch (type) {
25
+ case 'textarea':
26
+ renderComponent = (
27
+ <textarea
28
+ name={name}
29
+ className="form-control"
30
+ rows="3"
31
+ cols="50"
32
+ value={value}
33
+ onChange={onChange}
34
+ />
35
+ );
36
+ break;
37
+ case 'select':
38
+ renderComponent = (
39
+ <select
40
+ disabled={disabled}
41
+ name={name}
42
+ className="form-control"
43
+ value={value}
44
+ onChange={onChange}
45
+ >
46
+ {renderOptions(options)}
47
+ </select>
48
+ );
49
+ break;
50
+ case 'checkbox':
51
+ renderComponent = (
52
+ <input
53
+ name={name}
54
+ type={type}
55
+ className=""
56
+ value={value}
57
+ checked={checked}
58
+ onChange={onChange}
59
+ disabled={disabled}
60
+ />
61
+ );
62
+ break;
63
+ default:
64
+ renderComponent = (
65
+ <input
66
+ name={name}
67
+ type={type}
68
+ className="form-control"
69
+ value={value}
70
+ onChange={onChange}
71
+ disabled={disabled}
72
+ />
73
+ );
74
+ break;
75
+ }
76
+
77
+ return (
78
+ <CommonForm
79
+ label={label}
80
+ required={required}
81
+ className="common-textInput"
82
+ tooltipHelp={
83
+ info && (
84
+ <FieldLevelHelp
85
+ buttonClass="field-help"
86
+ content={<Fragment>{info}</Fragment>}
87
+ />
88
+ )
89
+ }
90
+ >
91
+ {renderComponent}
92
+ {error && (
93
+ <div style={{ color: 'red', marginTop: '0.5rem' }}>{error}</div>
94
+ )}
95
+ </CommonForm>
96
+ );
97
+ };
98
+
99
+ InputField.propTypes = {
100
+ name: PropTypes.string,
101
+ label: PropTypes.string,
102
+ info: PropTypes.string,
103
+ value: PropTypes.oneOfType([
104
+ PropTypes.string,
105
+ PropTypes.number,
106
+ PropTypes.bool,
107
+ ]),
108
+ onChange: PropTypes.func.isRequired,
109
+ required: PropTypes.bool,
110
+ type: PropTypes.oneOf([
111
+ 'text',
112
+ 'number',
113
+ 'password',
114
+ 'textarea',
115
+ 'select',
116
+ 'checkbox',
117
+ ]),
118
+ disabled: PropTypes.bool,
119
+ checked: PropTypes.bool,
120
+ options: PropTypes.arrayOf(
121
+ PropTypes.shape({
122
+ label: PropTypes.string.isRequired,
123
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
124
+ .isRequired,
125
+ })
126
+ ),
127
+ error: PropTypes.string,
128
+ };
129
+
130
+ InputField.defaultProps = {
131
+ name: '',
132
+ label: '',
133
+ info: undefined,
134
+ value: '',
135
+ required: false,
136
+ type: 'text',
137
+ disabled: false,
138
+ checked: false,
139
+ options: [],
140
+ error: '',
141
+ };
142
+
143
+ export default InputField;
@@ -0,0 +1,15 @@
1
+ // Placeholder to initialize routes (compare foreman_ansible)
2
+
3
+ /*
4
+ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
5
+ import { registerReducer } from 'foremanReact/common/MountingService';
6
+ import { registerRoutes } from 'foremanReact/routes/RoutingService';
7
+ import Routes from './src/Router/routes'
8
+
9
+ register client routes
10
+ registerRoutes('PluginTemplate', Routes);
11
+
12
+ register fills for extending foreman core
13
+ http://foreman.surge.sh/?path=/docs/introduction-slot-and-fill--page
14
+ addGlobalFill('<slotId>', '<fillId>', <div key='plugin-template-example' />, 300);
15
+ */
data/webpack/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import componentRegistry from 'foremanReact/components/componentRegistry';
2
+ import ProxmoxVmType from './components/ProxmoxVmType';
3
+
4
+ componentRegistry.register({
5
+ name: 'ProxmoxVmType',
6
+ type: ProxmoxVmType,
7
+ });