foreman_resource_quota 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +51 -0
- data/Rakefile +49 -0
- data/app/controllers/concerns/foreman/controller/parameters/resource_quota.rb +28 -0
- data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +96 -0
- data/app/controllers/foreman_resource_quota/application_controller.rb +9 -0
- data/app/controllers/foreman_resource_quota/resource_quotas_controller.rb +50 -0
- data/app/helpers/foreman_resource_quota/hosts_helper.rb +18 -0
- data/app/helpers/foreman_resource_quota/resource_quota_helper.rb +107 -0
- data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +115 -0
- data/app/models/concerns/foreman_resource_quota/user_extensions.rb +15 -0
- data/app/models/concerns/foreman_resource_quota/usergroup_extensions.rb +14 -0
- data/app/models/foreman_resource_quota/resource_quota.rb +83 -0
- data/app/models/foreman_resource_quota/resource_quota_user.rb +10 -0
- data/app/models/foreman_resource_quota/resource_quota_usergroup.rb +10 -0
- data/app/services/foreman_resource_quota/resource_origin.rb +97 -0
- data/app/services/foreman_resource_quota/resource_origins/compute_attributes_origin.rb +64 -0
- data/app/services/foreman_resource_quota/resource_origins/compute_resource_origin.rb +82 -0
- data/app/services/foreman_resource_quota/resource_origins/facts_origin.rb +68 -0
- data/app/services/foreman_resource_quota/resource_origins/vm_attributes_origin.rb +40 -0
- data/app/views/foreman_resource_quota/api/v2/hosts/resource_quota.json.rabl +3 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/base.json.rabl +6 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/create.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/hosts.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/index.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/main.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/show.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/update.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/usergroups.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/users.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/utilization.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/usergroups/resource_quota.json.rabl +3 -0
- data/app/views/foreman_resource_quota/api/v2/users/resource_quota.json.rabl +3 -0
- data/app/views/foreman_resource_quota/resource_quotas/_form.html.erb +21 -0
- data/app/views/foreman_resource_quota/resource_quotas/edit.html.erb +12 -0
- data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +55 -0
- data/app/views/foreman_resource_quota/resource_quotas/new.html.erb +10 -0
- data/app/views/foreman_resource_quota/resource_quotas/welcome.html.erb +10 -0
- data/app/views/hosts/_form_quota_fields.html.erb +4 -0
- data/app/views/users/_form_quota_tab.html.erb +45 -0
- data/config/initializers/inflections.rb +5 -0
- data/config/routes.rb +43 -0
- data/db/migrate/20230306120001_create_resource_quotas.rb +31 -0
- data/lib/foreman_resource_quota/engine.rb +56 -0
- data/lib/foreman_resource_quota/exceptions.rb +11 -0
- data/lib/foreman_resource_quota/register.rb +106 -0
- data/lib/foreman_resource_quota/version.rb +5 -0
- data/lib/foreman_resource_quota.rb +6 -0
- data/lib/tasks/foreman_resource_quota_tasks.rake +50 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_resource_quota.po +18 -0
- data/locale/foreman_resource_quota.pot +19 -0
- data/locale/gemspec.rb +4 -0
- data/package.json +44 -0
- data/webpack/api_helper.js +113 -0
- data/webpack/api_helper.test.js +96 -0
- data/webpack/components/CreateResourceQuotaModal.js +46 -0
- data/webpack/components/ResourceQuotaEmptyState/index.js +58 -0
- data/webpack/components/ResourceQuotaForm/ResourceQuotaForm.scss +1 -0
- data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +71 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/Properties.scss +9 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/StaticDetail.js +72 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/StatusPropertiesLabel.js +71 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/StatusPropertiesLabel.test.js +50 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +131 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/index.js +190 -0
- data/webpack/components/ResourceQuotaForm/components/QuotaState.js +157 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/Resource.scss +13 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +224 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +151 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.scss +10 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/index.js +239 -0
- data/webpack/components/ResourceQuotaForm/components/Resources.js +105 -0
- data/webpack/components/ResourceQuotaForm/components/Submit.js +72 -0
- data/webpack/components/ResourceQuotaForm/index.js +185 -0
- data/webpack/components/UpdateResourceQuotaModal.js +143 -0
- data/webpack/global_index.js +15 -0
- data/webpack/global_test_setup.js +11 -0
- data/webpack/helper.js +86 -0
- data/webpack/index.js +23 -0
- data/webpack/lib/ActionableDetail.js +115 -0
- data/webpack/lib/ActionableDetail.scss +4 -0
- data/webpack/lib/EditableSwitch.js +47 -0
- data/webpack/lib/EditableTextInput/EditableTextInput.js +206 -0
- data/webpack/lib/EditableTextInput/PencilEditButton.js +27 -0
- data/webpack/lib/EditableTextInput/__tests__/editableTextInput.test.js +193 -0
- data/webpack/lib/EditableTextInput/editableTextInput.scss +38 -0
- data/webpack/lib/EditableTextInput/index.js +4 -0
- data/webpack/lib/react-testing-lib-wrapper.js +80 -0
- data/webpack/test_setup.js +17 -0
- metadata +134 -0
@@ -0,0 +1,239 @@
|
|
1
|
+
import PropTypes from 'prop-types';
|
2
|
+
import React, { useState, useEffect } from 'react';
|
3
|
+
import { useDispatch } from 'react-redux';
|
4
|
+
import {
|
5
|
+
Button,
|
6
|
+
Card,
|
7
|
+
CardActions,
|
8
|
+
CardExpandableContent,
|
9
|
+
CardHeader,
|
10
|
+
CardTitle,
|
11
|
+
CardBody,
|
12
|
+
Switch,
|
13
|
+
Flex,
|
14
|
+
FlexItem,
|
15
|
+
Grid,
|
16
|
+
GridItem,
|
17
|
+
FormGroup,
|
18
|
+
} from '@patternfly/react-core';
|
19
|
+
|
20
|
+
import LabelIcon from 'foremanReact/components/common/LabelIcon';
|
21
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
22
|
+
|
23
|
+
import './Resource.scss';
|
24
|
+
import UnitInputField from './UnitInputField';
|
25
|
+
import UtilizationProgress from './UtilizationProgress';
|
26
|
+
|
27
|
+
import { resourceAttributesByIdentifier } from '../../ResourceQuotaFormConstants';
|
28
|
+
import dispatchAPICallbackToast from '../../../../api_helper';
|
29
|
+
|
30
|
+
// TODO: Visualize maximum resource (tooltip?)
|
31
|
+
// TODO: Add error message if given quota limit exceeds present quota utilization (consumed resources)
|
32
|
+
|
33
|
+
const Resource = ({
|
34
|
+
resourceIdentifier,
|
35
|
+
resourceUtilization,
|
36
|
+
initialValue,
|
37
|
+
isNewQuota,
|
38
|
+
handleInputValidation,
|
39
|
+
onChange,
|
40
|
+
onApply,
|
41
|
+
}) => {
|
42
|
+
const dispatch = useDispatch();
|
43
|
+
const cardId = resourceIdentifier;
|
44
|
+
const resourceAttributes = resourceAttributesByIdentifier(resourceIdentifier);
|
45
|
+
const resourceTitle = resourceAttributes.name;
|
46
|
+
const resourceMinValue = resourceAttributes.minValue;
|
47
|
+
const resourceMaxValue = resourceAttributes.maxValue;
|
48
|
+
const resourceUnits = resourceAttributes.unit;
|
49
|
+
const [constInitialValue, setConstInitialValue] = useState(initialValue);
|
50
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
51
|
+
const [isInputValid, setIsInputValid] = useState(true);
|
52
|
+
const [isEnabled, setIsEnabled] = useState(initialValue !== null);
|
53
|
+
const [inputValue, setInputValue] = useState(
|
54
|
+
initialValue !== null ? initialValue : 0
|
55
|
+
);
|
56
|
+
const [isApplyLoading, setIsApplyLoading] = useState(false);
|
57
|
+
const [isUpdateApplicable, setIsUpdateApplicable] = useState(false);
|
58
|
+
|
59
|
+
/* in case of editing an existing quota: onApply action */
|
60
|
+
const onClickApply = () => {
|
61
|
+
if (isEnabled) onApply(applyCallback, { [resourceIdentifier]: inputValue });
|
62
|
+
else onApply(applyCallback, { [resourceIdentifier]: null });
|
63
|
+
|
64
|
+
setIsApplyLoading(true);
|
65
|
+
};
|
66
|
+
const applyCallback = (success, response) => {
|
67
|
+
setIsApplyLoading(false);
|
68
|
+
if (response.data.hasOwnProperty(resourceIdentifier)) {
|
69
|
+
setConstInitialValue(response.data[resourceIdentifier]);
|
70
|
+
setInputValue(response.data[resourceIdentifier]);
|
71
|
+
setIsEnabled(response.data[resourceIdentifier] !== null);
|
72
|
+
}
|
73
|
+
dispatchAPICallbackToast(
|
74
|
+
dispatch,
|
75
|
+
success,
|
76
|
+
response,
|
77
|
+
`Sucessfully applied ${resourceTitle}.`,
|
78
|
+
`An error occurred appyling ${resourceTitle}.`
|
79
|
+
);
|
80
|
+
};
|
81
|
+
|
82
|
+
/* apply user input changes to state variables */
|
83
|
+
const onChangeEnabled = value => {
|
84
|
+
setIsEnabled(value);
|
85
|
+
};
|
86
|
+
const onExpand = () => {
|
87
|
+
setIsExpanded(!isExpanded);
|
88
|
+
};
|
89
|
+
const onInputChange = value => {
|
90
|
+
setInputValue(value);
|
91
|
+
};
|
92
|
+
const handleInputValidationForInputField = isValid => {
|
93
|
+
if (isInputValid !== isValid) {
|
94
|
+
setIsInputValid(isValid);
|
95
|
+
handleInputValidation({ [resourceIdentifier]: isValid });
|
96
|
+
}
|
97
|
+
};
|
98
|
+
|
99
|
+
/* operations for changes on enabled, valid or inputValue */
|
100
|
+
useEffect(() => {
|
101
|
+
if (isEnabled) {
|
102
|
+
// card enabled
|
103
|
+
if (isInputValid) {
|
104
|
+
onChange({ [resourceIdentifier]: inputValue });
|
105
|
+
if (inputValue !== constInitialValue) {
|
106
|
+
setIsUpdateApplicable(true);
|
107
|
+
} else {
|
108
|
+
// same value as initial
|
109
|
+
setIsUpdateApplicable(false);
|
110
|
+
}
|
111
|
+
} else {
|
112
|
+
// no valid input
|
113
|
+
setIsUpdateApplicable(false);
|
114
|
+
}
|
115
|
+
} else {
|
116
|
+
// card disabled
|
117
|
+
onChange({ [resourceIdentifier]: null });
|
118
|
+
if (constInitialValue !== null) {
|
119
|
+
setIsUpdateApplicable(true);
|
120
|
+
} else {
|
121
|
+
// card has been disabled initially
|
122
|
+
setIsUpdateApplicable(false);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}, [
|
126
|
+
isEnabled,
|
127
|
+
isInputValid,
|
128
|
+
inputValue,
|
129
|
+
constInitialValue,
|
130
|
+
resourceIdentifier,
|
131
|
+
onChange,
|
132
|
+
]);
|
133
|
+
|
134
|
+
return (
|
135
|
+
<Card
|
136
|
+
isExpanded={isExpanded}
|
137
|
+
isDisabledRaised={!isEnabled}
|
138
|
+
id={`resource-card-${cardId}`}
|
139
|
+
>
|
140
|
+
<CardHeader onExpand={onExpand} isToggleRightAligned={false}>
|
141
|
+
<Flex>
|
142
|
+
<FlexItem>
|
143
|
+
<Switch
|
144
|
+
id={`switch-${cardId}`}
|
145
|
+
aria-label={`switch-${cardId}`}
|
146
|
+
onChange={onChangeEnabled}
|
147
|
+
isChecked={isEnabled}
|
148
|
+
/>
|
149
|
+
</FlexItem>
|
150
|
+
<FlexItem>
|
151
|
+
<CardTitle>{resourceTitle}</CardTitle>
|
152
|
+
</FlexItem>
|
153
|
+
</Flex>
|
154
|
+
{!isNewQuota && (
|
155
|
+
<CardActions>
|
156
|
+
<Button
|
157
|
+
isDisabled={!isUpdateApplicable}
|
158
|
+
isSmall
|
159
|
+
isActive={isApplyLoading}
|
160
|
+
variant="primary"
|
161
|
+
onClick={onClickApply}
|
162
|
+
isLoading={isApplyLoading}
|
163
|
+
>
|
164
|
+
{__('Apply')}
|
165
|
+
</Button>
|
166
|
+
</CardActions>
|
167
|
+
)}
|
168
|
+
</CardHeader>
|
169
|
+
<CardExpandableContent>
|
170
|
+
<CardBody>
|
171
|
+
<Grid hasGutter>
|
172
|
+
<GridItem span={6}>
|
173
|
+
<UnitInputField
|
174
|
+
initialValue={inputValue}
|
175
|
+
onChange={onInputChange}
|
176
|
+
isDisabled={!isEnabled}
|
177
|
+
handleInputValidation={handleInputValidationForInputField}
|
178
|
+
minValue={resourceMinValue}
|
179
|
+
maxValue={resourceMaxValue}
|
180
|
+
units={resourceUnits}
|
181
|
+
labelIcon={
|
182
|
+
<LabelIcon
|
183
|
+
text={__(
|
184
|
+
`The total amount of ${resourceTitle} for this quota.`
|
185
|
+
)}
|
186
|
+
/>
|
187
|
+
}
|
188
|
+
/>
|
189
|
+
</GridItem>
|
190
|
+
<GridItem span={6}>
|
191
|
+
<FormGroup
|
192
|
+
label={__('Consumed resources')}
|
193
|
+
fieldId="card-resource-quota-progress-form-group"
|
194
|
+
labelIcon={
|
195
|
+
<LabelIcon
|
196
|
+
text={__(
|
197
|
+
`Total ${resourceTitle} currently in use by all hosts assigned to this quota.`
|
198
|
+
)}
|
199
|
+
/>
|
200
|
+
}
|
201
|
+
/>
|
202
|
+
<UtilizationProgress
|
203
|
+
cardId={cardId}
|
204
|
+
isNewQuota={isNewQuota}
|
205
|
+
resourceUnits={resourceUnits}
|
206
|
+
resourceValue={initialValue}
|
207
|
+
resourceUtilization={resourceUtilization}
|
208
|
+
isEnabled={isEnabled}
|
209
|
+
/>
|
210
|
+
</GridItem>
|
211
|
+
</Grid>
|
212
|
+
</CardBody>
|
213
|
+
</CardExpandableContent>
|
214
|
+
</Card>
|
215
|
+
);
|
216
|
+
};
|
217
|
+
|
218
|
+
Resource.propTypes = {
|
219
|
+
isNewQuota: PropTypes.bool.isRequired,
|
220
|
+
resourceIdentifier: PropTypes.string.isRequired,
|
221
|
+
initialValue: PropTypes.oneOfType([
|
222
|
+
PropTypes.number,
|
223
|
+
PropTypes.oneOf([null]),
|
224
|
+
]),
|
225
|
+
resourceUtilization: PropTypes.oneOfType([
|
226
|
+
PropTypes.number,
|
227
|
+
PropTypes.oneOf([null]),
|
228
|
+
]),
|
229
|
+
handleInputValidation: PropTypes.func.isRequired,
|
230
|
+
onChange: PropTypes.func.isRequired,
|
231
|
+
onApply: PropTypes.func.isRequired,
|
232
|
+
};
|
233
|
+
|
234
|
+
Resource.defaultProps = {
|
235
|
+
resourceUtilization: null,
|
236
|
+
initialValue: null,
|
237
|
+
};
|
238
|
+
|
239
|
+
export default Resource;
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Grid, GridItem } from '@patternfly/react-core';
|
4
|
+
|
5
|
+
import Resource from './Resource/';
|
6
|
+
import {
|
7
|
+
RESOURCE_IDENTIFIER_CPU,
|
8
|
+
RESOURCE_IDENTIFIER_MEMORY,
|
9
|
+
RESOURCE_IDENTIFIER_DISK,
|
10
|
+
} from '../ResourceQuotaFormConstants';
|
11
|
+
|
12
|
+
const Resources = ({
|
13
|
+
initialStatus,
|
14
|
+
initialProperties,
|
15
|
+
isNewQuota,
|
16
|
+
handleInputValidation,
|
17
|
+
onChange,
|
18
|
+
onApply,
|
19
|
+
}) => (
|
20
|
+
<Grid span={12} hasGutter>
|
21
|
+
<GridItem span={12} key="resources-resource-quota-cpu-item">
|
22
|
+
<Resource
|
23
|
+
isNewQuota={isNewQuota}
|
24
|
+
resourceIdentifier={RESOURCE_IDENTIFIER_CPU}
|
25
|
+
initialValue={initialProperties[RESOURCE_IDENTIFIER_CPU]}
|
26
|
+
resourceUtilization={initialStatus[RESOURCE_IDENTIFIER_CPU]}
|
27
|
+
handleInputValidation={handleInputValidation}
|
28
|
+
onChange={onChange}
|
29
|
+
onApply={onApply}
|
30
|
+
/>
|
31
|
+
</GridItem>
|
32
|
+
<GridItem span={12} key="resources-resource-quota-memory-item">
|
33
|
+
<Resource
|
34
|
+
isNewQuota={isNewQuota}
|
35
|
+
resourceIdentifier={RESOURCE_IDENTIFIER_MEMORY}
|
36
|
+
initialValue={initialProperties[RESOURCE_IDENTIFIER_MEMORY]}
|
37
|
+
resourceUtilization={initialStatus[RESOURCE_IDENTIFIER_MEMORY]}
|
38
|
+
handleInputValidation={handleInputValidation}
|
39
|
+
onChange={onChange}
|
40
|
+
onApply={onApply}
|
41
|
+
/>
|
42
|
+
</GridItem>
|
43
|
+
<GridItem span={12} key="resources-resource-quota-disk-item">
|
44
|
+
<Resource
|
45
|
+
isNewQuota={isNewQuota}
|
46
|
+
resourceIdentifier={RESOURCE_IDENTIFIER_DISK}
|
47
|
+
initialValue={initialProperties[RESOURCE_IDENTIFIER_DISK]}
|
48
|
+
resourceUtilization={initialStatus[RESOURCE_IDENTIFIER_DISK]}
|
49
|
+
handleInputValidation={handleInputValidation}
|
50
|
+
onChange={onChange}
|
51
|
+
onApply={onApply}
|
52
|
+
/>
|
53
|
+
</GridItem>
|
54
|
+
</Grid>
|
55
|
+
);
|
56
|
+
|
57
|
+
Resources.defaultProps = {
|
58
|
+
initialStatus: {
|
59
|
+
[RESOURCE_IDENTIFIER_CPU]: null,
|
60
|
+
[RESOURCE_IDENTIFIER_MEMORY]: null,
|
61
|
+
[RESOURCE_IDENTIFIER_DISK]: null,
|
62
|
+
},
|
63
|
+
initialProperties: {
|
64
|
+
[RESOURCE_IDENTIFIER_CPU]: null,
|
65
|
+
[RESOURCE_IDENTIFIER_MEMORY]: null,
|
66
|
+
[RESOURCE_IDENTIFIER_DISK]: null,
|
67
|
+
},
|
68
|
+
};
|
69
|
+
|
70
|
+
Resources.propTypes = {
|
71
|
+
initialStatus: PropTypes.shape({
|
72
|
+
[RESOURCE_IDENTIFIER_CPU]: PropTypes.oneOfType([
|
73
|
+
PropTypes.number,
|
74
|
+
PropTypes.oneOf([null]),
|
75
|
+
]),
|
76
|
+
[RESOURCE_IDENTIFIER_MEMORY]: PropTypes.oneOfType([
|
77
|
+
PropTypes.number,
|
78
|
+
PropTypes.oneOf([null]),
|
79
|
+
]),
|
80
|
+
[RESOURCE_IDENTIFIER_DISK]: PropTypes.oneOfType([
|
81
|
+
PropTypes.number,
|
82
|
+
PropTypes.oneOf([null]),
|
83
|
+
]),
|
84
|
+
}),
|
85
|
+
initialProperties: PropTypes.shape({
|
86
|
+
[RESOURCE_IDENTIFIER_CPU]: PropTypes.oneOfType([
|
87
|
+
PropTypes.number,
|
88
|
+
PropTypes.oneOf([null]),
|
89
|
+
]),
|
90
|
+
[RESOURCE_IDENTIFIER_MEMORY]: PropTypes.oneOfType([
|
91
|
+
PropTypes.number,
|
92
|
+
PropTypes.oneOf([null]),
|
93
|
+
]),
|
94
|
+
[RESOURCE_IDENTIFIER_DISK]: PropTypes.oneOfType([
|
95
|
+
PropTypes.number,
|
96
|
+
PropTypes.oneOf([null]),
|
97
|
+
]),
|
98
|
+
}),
|
99
|
+
isNewQuota: PropTypes.bool.isRequired,
|
100
|
+
handleInputValidation: PropTypes.func.isRequired,
|
101
|
+
onChange: PropTypes.func.isRequired,
|
102
|
+
onApply: PropTypes.func.isRequired,
|
103
|
+
};
|
104
|
+
|
105
|
+
export default Resources;
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import PropTypes from 'prop-types';
|
2
|
+
import React, { useState } from 'react';
|
3
|
+
import { useDispatch } from 'react-redux';
|
4
|
+
import { Button, Flex, FlexItem } from '@patternfly/react-core';
|
5
|
+
|
6
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
7
|
+
|
8
|
+
import dispatchAPICallbackToast from '../../../api_helper';
|
9
|
+
|
10
|
+
import {
|
11
|
+
RESOURCE_IDENTIFIER_ID,
|
12
|
+
RESOURCE_IDENTIFIER_NAME,
|
13
|
+
RESOURCE_IDENTIFIER_DESCRIPTION,
|
14
|
+
RESOURCE_IDENTIFIER_CPU,
|
15
|
+
RESOURCE_IDENTIFIER_MEMORY,
|
16
|
+
RESOURCE_IDENTIFIER_DISK,
|
17
|
+
} from '../ResourceQuotaFormConstants';
|
18
|
+
|
19
|
+
const Submit = ({ isValid, onCreate, onSubmit }) => {
|
20
|
+
const dispatch = useDispatch();
|
21
|
+
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
22
|
+
|
23
|
+
const handleOnSubmit = () => {
|
24
|
+
setIsSubmitLoading(true);
|
25
|
+
onCreate(onCreateCallback);
|
26
|
+
};
|
27
|
+
|
28
|
+
const onCreateCallback = (success, response) => {
|
29
|
+
setIsSubmitLoading(false);
|
30
|
+
dispatchAPICallbackToast(
|
31
|
+
dispatch,
|
32
|
+
success,
|
33
|
+
response,
|
34
|
+
`Sucessfully created new Resource Quota`,
|
35
|
+
`An error occurred while creating new Resource Quota.`
|
36
|
+
);
|
37
|
+
if (onSubmit) onSubmit(success);
|
38
|
+
};
|
39
|
+
|
40
|
+
const isAnyInvalid = () => Object.values(isValid).some(value => !value);
|
41
|
+
|
42
|
+
return (
|
43
|
+
<Flex>
|
44
|
+
<FlexItem align={{ default: 'alignLeft' }}>
|
45
|
+
<Button
|
46
|
+
isDisabled={isAnyInvalid()}
|
47
|
+
onClick={handleOnSubmit}
|
48
|
+
isLoading={isSubmitLoading}
|
49
|
+
variant="primary"
|
50
|
+
id="submit-button"
|
51
|
+
>
|
52
|
+
{__('Create resource quota')}
|
53
|
+
</Button>
|
54
|
+
</FlexItem>
|
55
|
+
</Flex>
|
56
|
+
);
|
57
|
+
};
|
58
|
+
|
59
|
+
Submit.propTypes = {
|
60
|
+
isValid: PropTypes.shape({
|
61
|
+
[RESOURCE_IDENTIFIER_NAME]: PropTypes.bool,
|
62
|
+
[RESOURCE_IDENTIFIER_ID]: PropTypes.bool,
|
63
|
+
[RESOURCE_IDENTIFIER_DESCRIPTION]: PropTypes.bool,
|
64
|
+
[RESOURCE_IDENTIFIER_CPU]: PropTypes.bool,
|
65
|
+
[RESOURCE_IDENTIFIER_MEMORY]: PropTypes.bool,
|
66
|
+
[RESOURCE_IDENTIFIER_DISK]: PropTypes.bool,
|
67
|
+
}).isRequired,
|
68
|
+
onCreate: PropTypes.func.isRequired,
|
69
|
+
onSubmit: PropTypes.func.isRequired,
|
70
|
+
};
|
71
|
+
|
72
|
+
export default Submit;
|
@@ -0,0 +1,185 @@
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Gallery, GalleryItem } from '@patternfly/react-core';
|
4
|
+
|
5
|
+
import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
|
6
|
+
import { STATUS } from 'foremanReact/constants';
|
7
|
+
|
8
|
+
import Properties from './components/Properties/';
|
9
|
+
import Resources from './components/Resources';
|
10
|
+
import Submit from './components/Submit';
|
11
|
+
import QuotaState from './components/QuotaState';
|
12
|
+
import {
|
13
|
+
RESOURCE_IDENTIFIER_ID,
|
14
|
+
RESOURCE_IDENTIFIER_NAME,
|
15
|
+
RESOURCE_IDENTIFIER_DESCRIPTION,
|
16
|
+
RESOURCE_IDENTIFIER_CPU,
|
17
|
+
RESOURCE_IDENTIFIER_MEMORY,
|
18
|
+
RESOURCE_IDENTIFIER_DISK,
|
19
|
+
RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS,
|
20
|
+
RESOURCE_IDENTIFIER_STATUS_NUM_USERS,
|
21
|
+
RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS,
|
22
|
+
RESOURCE_IDENTIFIER_STATUS_MISSING_HOSTS,
|
23
|
+
RESOURCE_IDENTIFIER_STATUS_UTILIZATION,
|
24
|
+
} from './ResourceQuotaFormConstants';
|
25
|
+
|
26
|
+
const ResourceQuotaForm = ({
|
27
|
+
isNewQuota,
|
28
|
+
initialProperties,
|
29
|
+
initialStatus,
|
30
|
+
onSubmit,
|
31
|
+
quotaChangesCallback,
|
32
|
+
}) => {
|
33
|
+
const modelState = QuotaState({
|
34
|
+
initialProperties,
|
35
|
+
initialStatus,
|
36
|
+
changesCallback: quotaChangesCallback,
|
37
|
+
});
|
38
|
+
|
39
|
+
const [isLoading, setIsLoading] = useState(!isNewQuota);
|
40
|
+
const [fetchedOnce, setFetchedOnce] = useState(false);
|
41
|
+
|
42
|
+
const fetchCallback = useCallback(() => {
|
43
|
+
setIsLoading(false);
|
44
|
+
}, []);
|
45
|
+
|
46
|
+
const fetchQuotaOnce = useCallback(() => {
|
47
|
+
if (!isNewQuota && !fetchedOnce) {
|
48
|
+
modelState.onFetch(fetchCallback);
|
49
|
+
setFetchedOnce(true);
|
50
|
+
}
|
51
|
+
}, [modelState, isNewQuota, fetchedOnce, fetchCallback]);
|
52
|
+
|
53
|
+
useEffect(() => {
|
54
|
+
fetchQuotaOnce(); // this will be executed only on the first render (valid is ignored)
|
55
|
+
}, [fetchQuotaOnce]);
|
56
|
+
|
57
|
+
return (
|
58
|
+
<Gallery maxWidths={{ md: '800px' }}>
|
59
|
+
<Gallery hasGutter>
|
60
|
+
<GalleryItem key="edit-resource-quota-properties-item">
|
61
|
+
<SkeletonLoader
|
62
|
+
skeletonProps={{ width: 300 }}
|
63
|
+
status={isNewQuota || !isLoading ? STATUS.RESOLVED : STATUS.PENDING}
|
64
|
+
>
|
65
|
+
{(!isLoading || isNewQuota) && (
|
66
|
+
<Properties
|
67
|
+
isNewQuota={isNewQuota}
|
68
|
+
initialName={modelState.getQuotaProperties(
|
69
|
+
RESOURCE_IDENTIFIER_NAME
|
70
|
+
)}
|
71
|
+
initialDescription={modelState.getQuotaProperties(
|
72
|
+
RESOURCE_IDENTIFIER_DESCRIPTION
|
73
|
+
)}
|
74
|
+
initialStatus={modelState.getQuotaStatus()}
|
75
|
+
handleInputValidation={modelState.handleInputValidation}
|
76
|
+
onChange={modelState.onChange}
|
77
|
+
onApply={modelState.onApply}
|
78
|
+
onFetch={modelState.onFetchUtilization}
|
79
|
+
/>
|
80
|
+
)}
|
81
|
+
</SkeletonLoader>
|
82
|
+
</GalleryItem>
|
83
|
+
<GalleryItem key="edit-resource-quota-resources-item">
|
84
|
+
<SkeletonLoader
|
85
|
+
skeletonProps={{ width: 400 }}
|
86
|
+
status={isNewQuota || !isLoading ? STATUS.RESOLVED : STATUS.PENDING}
|
87
|
+
>
|
88
|
+
{(!isLoading || isNewQuota) && (
|
89
|
+
<Resources
|
90
|
+
isNewQuota={isNewQuota}
|
91
|
+
initialProperties={modelState.getQuotaProperties()}
|
92
|
+
initialStatus={modelState.getQuotaStatus(
|
93
|
+
RESOURCE_IDENTIFIER_STATUS_UTILIZATION
|
94
|
+
)}
|
95
|
+
handleInputValidation={modelState.handleInputValidation}
|
96
|
+
onChange={modelState.onChange}
|
97
|
+
onApply={modelState.onApply}
|
98
|
+
/>
|
99
|
+
)}
|
100
|
+
</SkeletonLoader>
|
101
|
+
</GalleryItem>
|
102
|
+
{isNewQuota && (
|
103
|
+
<GalleryItem key="edit-resource-quota-submit-item">
|
104
|
+
<Submit
|
105
|
+
isValid={modelState.getIsValid()}
|
106
|
+
onCreate={modelState.onCreate}
|
107
|
+
onSubmit={onSubmit}
|
108
|
+
/>
|
109
|
+
</GalleryItem>
|
110
|
+
)}
|
111
|
+
</Gallery>
|
112
|
+
</Gallery>
|
113
|
+
);
|
114
|
+
};
|
115
|
+
|
116
|
+
ResourceQuotaForm.defaultProps = {
|
117
|
+
onSubmit: null,
|
118
|
+
quotaChangesCallback: null,
|
119
|
+
initialProperties: {
|
120
|
+
[RESOURCE_IDENTIFIER_NAME]: '',
|
121
|
+
[RESOURCE_IDENTIFIER_DESCRIPTION]: '',
|
122
|
+
[RESOURCE_IDENTIFIER_CPU]: null,
|
123
|
+
[RESOURCE_IDENTIFIER_MEMORY]: null,
|
124
|
+
[RESOURCE_IDENTIFIER_DISK]: null,
|
125
|
+
},
|
126
|
+
initialStatus: {
|
127
|
+
[RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]: null,
|
128
|
+
[RESOURCE_IDENTIFIER_STATUS_NUM_USERS]: null,
|
129
|
+
[RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]: null,
|
130
|
+
[RESOURCE_IDENTIFIER_STATUS_MISSING_HOSTS]: null,
|
131
|
+
[RESOURCE_IDENTIFIER_STATUS_UTILIZATION]: {
|
132
|
+
[RESOURCE_IDENTIFIER_CPU]: null,
|
133
|
+
[RESOURCE_IDENTIFIER_MEMORY]: null,
|
134
|
+
[RESOURCE_IDENTIFIER_DISK]: null,
|
135
|
+
},
|
136
|
+
},
|
137
|
+
};
|
138
|
+
|
139
|
+
ResourceQuotaForm.propTypes = {
|
140
|
+
isNewQuota: PropTypes.bool.isRequired,
|
141
|
+
onSubmit: PropTypes.func,
|
142
|
+
quotaChangesCallback: PropTypes.func,
|
143
|
+
initialProperties: PropTypes.shape({
|
144
|
+
[RESOURCE_IDENTIFIER_ID]: PropTypes.number,
|
145
|
+
[RESOURCE_IDENTIFIER_NAME]: PropTypes.string,
|
146
|
+
[RESOURCE_IDENTIFIER_DESCRIPTION]: PropTypes.string,
|
147
|
+
[RESOURCE_IDENTIFIER_CPU]: PropTypes.number,
|
148
|
+
[RESOURCE_IDENTIFIER_MEMORY]: PropTypes.number,
|
149
|
+
[RESOURCE_IDENTIFIER_DISK]: PropTypes.number,
|
150
|
+
}),
|
151
|
+
initialStatus: PropTypes.shape({
|
152
|
+
[RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]: PropTypes.oneOfType([
|
153
|
+
PropTypes.number,
|
154
|
+
PropTypes.oneOf([null]),
|
155
|
+
]),
|
156
|
+
[RESOURCE_IDENTIFIER_STATUS_NUM_USERS]: PropTypes.oneOfType([
|
157
|
+
PropTypes.number,
|
158
|
+
PropTypes.oneOf([null]),
|
159
|
+
]),
|
160
|
+
[RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]: PropTypes.oneOfType([
|
161
|
+
PropTypes.number,
|
162
|
+
PropTypes.oneOf([null]),
|
163
|
+
]),
|
164
|
+
[RESOURCE_IDENTIFIER_STATUS_MISSING_HOSTS]: PropTypes.oneOfType([
|
165
|
+
PropTypes.number,
|
166
|
+
PropTypes.oneOf([null]),
|
167
|
+
]),
|
168
|
+
[RESOURCE_IDENTIFIER_STATUS_UTILIZATION]: PropTypes.shape({
|
169
|
+
[RESOURCE_IDENTIFIER_CPU]: PropTypes.oneOfType([
|
170
|
+
PropTypes.number,
|
171
|
+
PropTypes.oneOf([null]),
|
172
|
+
]),
|
173
|
+
[RESOURCE_IDENTIFIER_MEMORY]: PropTypes.oneOfType([
|
174
|
+
PropTypes.number,
|
175
|
+
PropTypes.oneOf([null]),
|
176
|
+
]),
|
177
|
+
[RESOURCE_IDENTIFIER_DISK]: PropTypes.oneOfType([
|
178
|
+
PropTypes.number,
|
179
|
+
PropTypes.oneOf([null]),
|
180
|
+
]),
|
181
|
+
}),
|
182
|
+
}),
|
183
|
+
};
|
184
|
+
|
185
|
+
export default ResourceQuotaForm;
|