foreman_resource_quota 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +51 -0
  4. data/Rakefile +49 -0
  5. data/app/controllers/concerns/foreman/controller/parameters/resource_quota.rb +28 -0
  6. data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +96 -0
  7. data/app/controllers/foreman_resource_quota/application_controller.rb +9 -0
  8. data/app/controllers/foreman_resource_quota/resource_quotas_controller.rb +50 -0
  9. data/app/helpers/foreman_resource_quota/hosts_helper.rb +18 -0
  10. data/app/helpers/foreman_resource_quota/resource_quota_helper.rb +107 -0
  11. data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +115 -0
  12. data/app/models/concerns/foreman_resource_quota/user_extensions.rb +15 -0
  13. data/app/models/concerns/foreman_resource_quota/usergroup_extensions.rb +14 -0
  14. data/app/models/foreman_resource_quota/resource_quota.rb +83 -0
  15. data/app/models/foreman_resource_quota/resource_quota_user.rb +10 -0
  16. data/app/models/foreman_resource_quota/resource_quota_usergroup.rb +10 -0
  17. data/app/services/foreman_resource_quota/resource_origin.rb +97 -0
  18. data/app/services/foreman_resource_quota/resource_origins/compute_attributes_origin.rb +64 -0
  19. data/app/services/foreman_resource_quota/resource_origins/compute_resource_origin.rb +82 -0
  20. data/app/services/foreman_resource_quota/resource_origins/facts_origin.rb +68 -0
  21. data/app/services/foreman_resource_quota/resource_origins/vm_attributes_origin.rb +40 -0
  22. data/app/views/foreman_resource_quota/api/v2/hosts/resource_quota.json.rabl +3 -0
  23. data/app/views/foreman_resource_quota/api/v2/resource_quotas/base.json.rabl +6 -0
  24. data/app/views/foreman_resource_quota/api/v2/resource_quotas/create.json.rabl +5 -0
  25. data/app/views/foreman_resource_quota/api/v2/resource_quotas/hosts.json.rabl +7 -0
  26. data/app/views/foreman_resource_quota/api/v2/resource_quotas/index.json.rabl +5 -0
  27. data/app/views/foreman_resource_quota/api/v2/resource_quotas/main.json.rabl +7 -0
  28. data/app/views/foreman_resource_quota/api/v2/resource_quotas/show.json.rabl +5 -0
  29. data/app/views/foreman_resource_quota/api/v2/resource_quotas/update.json.rabl +5 -0
  30. data/app/views/foreman_resource_quota/api/v2/resource_quotas/usergroups.json.rabl +7 -0
  31. data/app/views/foreman_resource_quota/api/v2/resource_quotas/users.json.rabl +7 -0
  32. data/app/views/foreman_resource_quota/api/v2/resource_quotas/utilization.json.rabl +7 -0
  33. data/app/views/foreman_resource_quota/api/v2/usergroups/resource_quota.json.rabl +3 -0
  34. data/app/views/foreman_resource_quota/api/v2/users/resource_quota.json.rabl +3 -0
  35. data/app/views/foreman_resource_quota/resource_quotas/_form.html.erb +21 -0
  36. data/app/views/foreman_resource_quota/resource_quotas/edit.html.erb +12 -0
  37. data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +55 -0
  38. data/app/views/foreman_resource_quota/resource_quotas/new.html.erb +10 -0
  39. data/app/views/foreman_resource_quota/resource_quotas/welcome.html.erb +10 -0
  40. data/app/views/hosts/_form_quota_fields.html.erb +4 -0
  41. data/app/views/users/_form_quota_tab.html.erb +45 -0
  42. data/config/initializers/inflections.rb +5 -0
  43. data/config/routes.rb +43 -0
  44. data/db/migrate/20230306120001_create_resource_quotas.rb +31 -0
  45. data/lib/foreman_resource_quota/engine.rb +56 -0
  46. data/lib/foreman_resource_quota/exceptions.rb +11 -0
  47. data/lib/foreman_resource_quota/register.rb +106 -0
  48. data/lib/foreman_resource_quota/version.rb +5 -0
  49. data/lib/foreman_resource_quota.rb +6 -0
  50. data/lib/tasks/foreman_resource_quota_tasks.rake +50 -0
  51. data/locale/Makefile +60 -0
  52. data/locale/en/foreman_resource_quota.po +18 -0
  53. data/locale/foreman_resource_quota.pot +19 -0
  54. data/locale/gemspec.rb +4 -0
  55. data/package.json +44 -0
  56. data/webpack/api_helper.js +113 -0
  57. data/webpack/api_helper.test.js +96 -0
  58. data/webpack/components/CreateResourceQuotaModal.js +46 -0
  59. data/webpack/components/ResourceQuotaEmptyState/index.js +58 -0
  60. data/webpack/components/ResourceQuotaForm/ResourceQuotaForm.scss +1 -0
  61. data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +71 -0
  62. data/webpack/components/ResourceQuotaForm/components/Properties/Properties.scss +9 -0
  63. data/webpack/components/ResourceQuotaForm/components/Properties/StaticDetail.js +72 -0
  64. data/webpack/components/ResourceQuotaForm/components/Properties/StatusPropertiesLabel.js +71 -0
  65. data/webpack/components/ResourceQuotaForm/components/Properties/StatusPropertiesLabel.test.js +50 -0
  66. data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +131 -0
  67. data/webpack/components/ResourceQuotaForm/components/Properties/index.js +190 -0
  68. data/webpack/components/ResourceQuotaForm/components/QuotaState.js +157 -0
  69. data/webpack/components/ResourceQuotaForm/components/Resource/Resource.scss +13 -0
  70. data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +224 -0
  71. data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +151 -0
  72. data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.scss +10 -0
  73. data/webpack/components/ResourceQuotaForm/components/Resource/index.js +239 -0
  74. data/webpack/components/ResourceQuotaForm/components/Resources.js +105 -0
  75. data/webpack/components/ResourceQuotaForm/components/Submit.js +72 -0
  76. data/webpack/components/ResourceQuotaForm/index.js +185 -0
  77. data/webpack/components/UpdateResourceQuotaModal.js +143 -0
  78. data/webpack/global_index.js +15 -0
  79. data/webpack/global_test_setup.js +11 -0
  80. data/webpack/helper.js +86 -0
  81. data/webpack/index.js +23 -0
  82. data/webpack/lib/ActionableDetail.js +115 -0
  83. data/webpack/lib/ActionableDetail.scss +4 -0
  84. data/webpack/lib/EditableSwitch.js +47 -0
  85. data/webpack/lib/EditableTextInput/EditableTextInput.js +206 -0
  86. data/webpack/lib/EditableTextInput/PencilEditButton.js +27 -0
  87. data/webpack/lib/EditableTextInput/__tests__/editableTextInput.test.js +193 -0
  88. data/webpack/lib/EditableTextInput/editableTextInput.scss +38 -0
  89. data/webpack/lib/EditableTextInput/index.js +4 -0
  90. data/webpack/lib/react-testing-lib-wrapper.js +80 -0
  91. data/webpack/test_setup.js +17 -0
  92. 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;