foreman_resource_quota 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +1 -1
  4. data/app/controllers/foreman_resource_quota/concerns/api/v2/hosts_controller_extensions.rb +21 -0
  5. data/app/helpers/foreman_resource_quota/hosts_helper.rb +18 -7
  6. data/app/lib/foreman_resource_quota/exceptions.rb +1 -0
  7. data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +9 -10
  8. data/app/models/concerns/foreman_resource_quota/user_extensions.rb +27 -0
  9. data/app/models/concerns/foreman_resource_quota/usergroup_extensions.rb +18 -0
  10. data/app/models/foreman_resource_quota/resource_quota.rb +23 -0
  11. data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +9 -1
  12. data/app/views/hosts/_form_quota_fields.html.erb +14 -2
  13. data/app/views/users/_form_quota_tab.html.erb +2 -2
  14. data/db/migrate/20240611141939_drop_missing_hosts.rb +9 -2
  15. data/db/migrate/20250410082728_add_unassigned_flag_to_resource_quota.rb +7 -0
  16. data/db/seeds.d/030-unassigned_quota.rb +36 -0
  17. data/lib/foreman_resource_quota/register.rb +8 -0
  18. data/lib/foreman_resource_quota/version.rb +1 -1
  19. data/lib/tasks/foreman_resource_quota_tasks.rake +4 -5
  20. data/package.json +9 -10
  21. data/webpack/components/CreateResourceQuotaModal.js +1 -0
  22. data/webpack/components/ResourceQuotaEmptyState/__test__/__snapshots__/ResourceQuotaEmptyState.test.js.snap +4 -0
  23. data/webpack/components/ResourceQuotaEmptyState/index.js +1 -0
  24. data/webpack/components/ResourceQuotaForm/ResourceQuotaForm.scss +1 -1
  25. data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +1 -0
  26. data/webpack/components/ResourceQuotaForm/components/Properties/Properties.scss +4 -3
  27. data/webpack/components/ResourceQuotaForm/components/Properties/StaticDetail.js +3 -2
  28. data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +4 -1
  29. data/webpack/components/ResourceQuotaForm/components/Properties/index.js +86 -45
  30. data/webpack/components/ResourceQuotaForm/components/Resource/Resource.scss +5 -5
  31. data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +84 -37
  32. data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.scss +7 -0
  33. data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +14 -13
  34. data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.scss +4 -4
  35. data/webpack/components/ResourceQuotaForm/components/Resource/__test__/__snapshots__/UnitInputField.test.js.snap +149 -140
  36. data/webpack/components/ResourceQuotaForm/components/Resource/index.js +28 -17
  37. data/webpack/components/ResourceQuotaForm/components/Submit.js +2 -1
  38. data/webpack/components/ResourceQuotaForm/index.js +33 -19
  39. data/webpack/components/UpdateResourceQuotaModal.js +7 -1
  40. data/webpack/lib/ActionableDetail.scss +1 -1
  41. data/webpack/lib/EditableSwitch.js +1 -1
  42. data/webpack/lib/EditableTextInput/EditableTextInput.js +81 -77
  43. data/webpack/lib/EditableTextInput/editableTextInput.scss +30 -28
  44. metadata +8 -8
@@ -1,155 +1,164 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`UnitInputField should render as disabled field 1`] = `
4
- <FormGroup
5
- fieldId="quota-limit-resource-quota-form-group"
6
- helperText={
7
- <FormHelperText
8
- isHidden={true}
9
- >
10
-
11
- </FormHelperText>
12
- }
13
- helperTextInvalid=""
14
- label="Quota Limit"
15
- labelIcon={
16
- <LabelIcon
17
- text="Descriptive title."
18
- />
19
- }
20
- validated="default"
4
+ <div
5
+ className="container-unit-input-field"
21
6
  >
22
- <InputGroup>
23
- <TextInput
24
- id="reg_token_life_time_input"
25
- isDisabled={true}
26
- max={5}
27
- min={0}
28
- onChange={[Function]}
29
- validated="default"
30
- value={0}
31
- />
32
- <Dropdown
33
- dropdownItems={
34
- Array [
35
- <DropdownItem
36
- id="unit-dropdownitem-mib"
37
- >
38
- MiB
39
- </DropdownItem>,
40
- <DropdownItem
41
- id="unit-dropdownitem-gib"
42
- >
43
- GiB
44
- </DropdownItem>,
45
- ]
46
- }
47
- isOpen={false}
48
- onSelect={[Function]}
49
- toggle={
50
- <DropdownToggle
7
+ <FormGroup
8
+ fieldId="quota-limit-resource-quota-form-group"
9
+ label="Quota Limit"
10
+ labelIcon={
11
+ <LabelIcon
12
+ text="Descriptive title."
13
+ />
14
+ }
15
+ validated="default"
16
+ >
17
+ <InputGroup>
18
+ <InputGroupItem>
19
+ <TextInput
20
+ id="resource-quota-reg-token-life-time-input"
51
21
  isDisabled={true}
52
- onToggle={[Function]}
53
- >
54
- MiB
55
- </DropdownToggle>
56
- }
57
- />
58
- </InputGroup>
59
- </FormGroup>
22
+ max={5}
23
+ min={0}
24
+ onChange={[Function]}
25
+ ouiaId="resource-quota-reg-token-life-time-input"
26
+ validated="default"
27
+ value={0}
28
+ />
29
+ </InputGroupItem>
30
+ <InputGroupItem>
31
+ <Dropdown
32
+ dropdownItems={
33
+ Array [
34
+ <DropdownItem
35
+ id="unit-dropdownitem-mib"
36
+ ouiaId="unit-dropdownitem-mib"
37
+ >
38
+ MiB
39
+ </DropdownItem>,
40
+ <DropdownItem
41
+ id="unit-dropdownitem-gib"
42
+ ouiaId="unit-dropdownitem-gib"
43
+ >
44
+ GiB
45
+ </DropdownItem>,
46
+ ]
47
+ }
48
+ isOpen={false}
49
+ onSelect={[Function]}
50
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdown"
51
+ toggle={
52
+ <DropdownToggle
53
+ isDisabled={true}
54
+ onToggle={[Function]}
55
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdowni-toggle"
56
+ >
57
+ MiB
58
+ </DropdownToggle>
59
+ }
60
+ />
61
+ </InputGroupItem>
62
+ </InputGroup>
63
+ </FormGroup>
64
+ </div>
60
65
  `;
61
66
 
62
67
  exports[`UnitInputField should render default 1`] = `
63
- <FormGroup
64
- fieldId="quota-limit-resource-quota-form-group"
65
- helperText={
66
- <FormHelperText
67
- isHidden={true}
68
- >
69
-
70
- </FormHelperText>
71
- }
72
- helperTextInvalid=""
73
- label="Quota Limit"
74
- labelIcon={
75
- <LabelIcon
76
- text="Descriptive title."
77
- />
78
- }
79
- validated="default"
68
+ <div
69
+ className="container-unit-input-field"
80
70
  >
81
- <InputGroup>
82
- <TextInput
83
- id="reg_token_life_time_input"
84
- isDisabled={false}
85
- max={5}
86
- min={0}
87
- onChange={[Function]}
88
- validated="default"
89
- value={0}
90
- />
91
- <Dropdown
92
- dropdownItems={
93
- Array [
94
- <DropdownItem
95
- id="unit-dropdownitem-mib"
96
- >
97
- MiB
98
- </DropdownItem>,
99
- <DropdownItem
100
- id="unit-dropdownitem-gib"
101
- >
102
- GiB
103
- </DropdownItem>,
104
- ]
105
- }
106
- isOpen={false}
107
- onSelect={[Function]}
108
- toggle={
109
- <DropdownToggle
71
+ <FormGroup
72
+ fieldId="quota-limit-resource-quota-form-group"
73
+ label="Quota Limit"
74
+ labelIcon={
75
+ <LabelIcon
76
+ text="Descriptive title."
77
+ />
78
+ }
79
+ validated="default"
80
+ >
81
+ <InputGroup>
82
+ <InputGroupItem>
83
+ <TextInput
84
+ id="resource-quota-reg-token-life-time-input"
110
85
  isDisabled={false}
111
- onToggle={[Function]}
112
- >
113
- MiB
114
- </DropdownToggle>
115
- }
116
- />
117
- </InputGroup>
118
- </FormGroup>
86
+ max={5}
87
+ min={0}
88
+ onChange={[Function]}
89
+ ouiaId="resource-quota-reg-token-life-time-input"
90
+ validated="default"
91
+ value={0}
92
+ />
93
+ </InputGroupItem>
94
+ <InputGroupItem>
95
+ <Dropdown
96
+ dropdownItems={
97
+ Array [
98
+ <DropdownItem
99
+ id="unit-dropdownitem-mib"
100
+ ouiaId="unit-dropdownitem-mib"
101
+ >
102
+ MiB
103
+ </DropdownItem>,
104
+ <DropdownItem
105
+ id="unit-dropdownitem-gib"
106
+ ouiaId="unit-dropdownitem-gib"
107
+ >
108
+ GiB
109
+ </DropdownItem>,
110
+ ]
111
+ }
112
+ isOpen={false}
113
+ onSelect={[Function]}
114
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdown"
115
+ toggle={
116
+ <DropdownToggle
117
+ isDisabled={false}
118
+ onToggle={[Function]}
119
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdowni-toggle"
120
+ >
121
+ MiB
122
+ </DropdownToggle>
123
+ }
124
+ />
125
+ </InputGroupItem>
126
+ </InputGroup>
127
+ </FormGroup>
128
+ </div>
119
129
  `;
120
130
 
121
131
  exports[`UnitInputField should render without dropdown (single unit) 1`] = `
122
- <FormGroup
123
- fieldId="quota-limit-resource-quota-form-group"
124
- helperText={
125
- <FormHelperText
126
- isHidden={true}
127
- >
128
-
129
- </FormHelperText>
130
- }
131
- helperTextInvalid=""
132
- label="Quota Limit"
133
- labelIcon={
134
- <LabelIcon
135
- text="Descriptive title."
136
- />
137
- }
138
- validated="default"
132
+ <div
133
+ className="container-unit-input-field"
139
134
  >
140
- <InputGroup>
141
- <TextInput
142
- id="reg_token_life_time_input"
143
- isDisabled={false}
144
- max={5}
145
- min={0}
146
- onChange={[Function]}
147
- validated="default"
148
- value={0}
149
- />
150
- <InputGroupText>
151
- cores
152
- </InputGroupText>
153
- </InputGroup>
154
- </FormGroup>
135
+ <FormGroup
136
+ fieldId="quota-limit-resource-quota-form-group"
137
+ label="Quota Limit"
138
+ labelIcon={
139
+ <LabelIcon
140
+ text="Descriptive title."
141
+ />
142
+ }
143
+ validated="default"
144
+ >
145
+ <InputGroup>
146
+ <InputGroupItem>
147
+ <TextInput
148
+ id="resource-quota-reg-token-life-time-input"
149
+ isDisabled={false}
150
+ max={5}
151
+ min={0}
152
+ onChange={[Function]}
153
+ ouiaId="resource-quota-reg-token-life-time-input"
154
+ validated="default"
155
+ value={0}
156
+ />
157
+ </InputGroupItem>
158
+ <InputGroupText>
159
+ cores
160
+ </InputGroupText>
161
+ </InputGroup>
162
+ </FormGroup>
163
+ </div>
155
164
  `;
@@ -4,7 +4,6 @@ import { useDispatch } from 'react-redux';
4
4
  import {
5
5
  Button,
6
6
  Card,
7
- CardActions,
8
7
  CardExpandableContent,
9
8
  CardHeader,
10
9
  CardTitle,
@@ -132,19 +131,45 @@ const Resource = ({
132
131
  onChange,
133
132
  ]);
134
133
 
134
+ const renderApplyButton = () => {
135
+ if (isNewQuota) {
136
+ return <></>;
137
+ }
138
+ return (
139
+ <Button
140
+ isDisabled={!isUpdateApplicable}
141
+ size="sm"
142
+ isActive={isApplyLoading}
143
+ variant="primary"
144
+ onClick={onClickApply}
145
+ isLoading={isApplyLoading}
146
+ id="resource-quota-resource-index-button-apply"
147
+ ouiaId="resource-quota-resource-index-button-apply"
148
+ >
149
+ {__('Apply')}
150
+ </Button>
151
+ );
152
+ };
153
+
135
154
  return (
136
155
  <Card
137
156
  isExpanded={isExpanded}
138
157
  isDisabledRaised={!isEnabled}
139
158
  id={`resource-card-${cardId}`}
159
+ ouiaId={`resource-card-${cardId}`}
140
160
  >
141
- <CardHeader onExpand={onExpand} isToggleRightAligned={false}>
161
+ <CardHeader
162
+ actions={{ actions: renderApplyButton() }}
163
+ onExpand={onExpand}
164
+ isToggleRightAligned={false}
165
+ >
142
166
  <Flex>
143
167
  <FlexItem>
144
168
  <Switch
145
169
  id={`switch-${cardId}`}
170
+ ouiaId={`switch-${cardId}`}
146
171
  aria-label={`switch-${cardId}`}
147
- onChange={onChangeEnabled}
172
+ onChange={(_event, val) => onChangeEnabled(val)}
148
173
  isChecked={isEnabled}
149
174
  />
150
175
  </FlexItem>
@@ -152,20 +177,6 @@ const Resource = ({
152
177
  <CardTitle>{resourceTitle}</CardTitle>
153
178
  </FlexItem>
154
179
  </Flex>
155
- {!isNewQuota && (
156
- <CardActions>
157
- <Button
158
- isDisabled={!isUpdateApplicable}
159
- isSmall
160
- isActive={isApplyLoading}
161
- variant="primary"
162
- onClick={onClickApply}
163
- isLoading={isApplyLoading}
164
- >
165
- {__('Apply')}
166
- </Button>
167
- </CardActions>
168
- )}
169
180
  </CardHeader>
170
181
  <CardExpandableContent>
171
182
  <CardBody>
@@ -48,7 +48,8 @@ const Submit = ({ isValid, onCreate, onSubmit }) => {
48
48
  onClick={handleOnSubmit}
49
49
  isLoading={isSubmitLoading}
50
50
  variant="primary"
51
- id="submit-button"
51
+ id="resource-quota-submit-button"
52
+ ouiaId="resource-quota-submit-button"
52
53
  >
53
54
  {__('Create resource quota')}
54
55
  </Button>
@@ -16,6 +16,7 @@ import {
16
16
  RESOURCE_IDENTIFIER_CPU,
17
17
  RESOURCE_IDENTIFIER_MEMORY,
18
18
  RESOURCE_IDENTIFIER_DISK,
19
+ RESOURCE_IDENTIFIER_UNASSIGNED,
19
20
  RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS,
20
21
  RESOURCE_IDENTIFIER_STATUS_NUM_USERS,
21
22
  RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS,
@@ -25,6 +26,7 @@ import {
25
26
 
26
27
  const ResourceQuotaForm = ({
27
28
  isNewQuota,
29
+ showAssignmentWarning,
28
30
  initialProperties,
29
31
  initialStatus,
30
32
  onSubmit,
@@ -72,33 +74,41 @@ const ResourceQuotaForm = ({
72
74
  RESOURCE_IDENTIFIER_DESCRIPTION
73
75
  )}
74
76
  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
77
+ unassigned={modelState.getQuotaProperties(
78
+ RESOURCE_IDENTIFIER_UNASSIGNED
94
79
  )}
80
+ showAssignmentWarning={showAssignmentWarning}
95
81
  handleInputValidation={modelState.handleInputValidation}
96
82
  onChange={modelState.onChange}
97
83
  onApply={modelState.onApply}
84
+ onFetch={modelState.onFetchUtilization}
98
85
  />
99
86
  )}
100
87
  </SkeletonLoader>
101
88
  </GalleryItem>
89
+ {!modelState.getQuotaProperties(RESOURCE_IDENTIFIER_UNASSIGNED) && (
90
+ <GalleryItem key="edit-resource-quota-resources-item">
91
+ <SkeletonLoader
92
+ skeletonProps={{ width: 400 }}
93
+ status={
94
+ isNewQuota || !isLoading ? STATUS.RESOLVED : STATUS.PENDING
95
+ }
96
+ >
97
+ {(!isLoading || isNewQuota) && (
98
+ <Resources
99
+ isNewQuota={isNewQuota}
100
+ initialProperties={modelState.getQuotaProperties()}
101
+ initialStatus={modelState.getQuotaStatus(
102
+ RESOURCE_IDENTIFIER_STATUS_UTILIZATION
103
+ )}
104
+ handleInputValidation={modelState.handleInputValidation}
105
+ onChange={modelState.onChange}
106
+ onApply={modelState.onApply}
107
+ />
108
+ )}
109
+ </SkeletonLoader>
110
+ </GalleryItem>
111
+ )}
102
112
  {isNewQuota && (
103
113
  <GalleryItem key="edit-resource-quota-submit-item">
104
114
  <Submit
@@ -114,6 +124,7 @@ const ResourceQuotaForm = ({
114
124
  };
115
125
 
116
126
  ResourceQuotaForm.defaultProps = {
127
+ showAssignmentWarning: false,
117
128
  onSubmit: null,
118
129
  quotaChangesCallback: null,
119
130
  initialProperties: {
@@ -122,6 +133,7 @@ ResourceQuotaForm.defaultProps = {
122
133
  [RESOURCE_IDENTIFIER_CPU]: null,
123
134
  [RESOURCE_IDENTIFIER_MEMORY]: null,
124
135
  [RESOURCE_IDENTIFIER_DISK]: null,
136
+ [RESOURCE_IDENTIFIER_UNASSIGNED]: false,
125
137
  },
126
138
  initialStatus: {
127
139
  [RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]: null,
@@ -138,6 +150,7 @@ ResourceQuotaForm.defaultProps = {
138
150
 
139
151
  ResourceQuotaForm.propTypes = {
140
152
  isNewQuota: PropTypes.bool.isRequired,
153
+ showAssignmentWarning: PropTypes.bool,
141
154
  onSubmit: PropTypes.func,
142
155
  quotaChangesCallback: PropTypes.func,
143
156
  initialProperties: PropTypes.shape({
@@ -147,6 +160,7 @@ ResourceQuotaForm.propTypes = {
147
160
  [RESOURCE_IDENTIFIER_CPU]: PropTypes.number,
148
161
  [RESOURCE_IDENTIFIER_MEMORY]: PropTypes.number,
149
162
  [RESOURCE_IDENTIFIER_DISK]: PropTypes.number,
163
+ [RESOURCE_IDENTIFIER_UNASSIGNED]: PropTypes.bool,
150
164
  }),
151
165
  initialStatus: PropTypes.shape({
152
166
  [RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]: PropTypes.oneOfType([
@@ -20,7 +20,11 @@ import {
20
20
  RESOURCE_IDENTIFIER_STATUS_UTILIZATION,
21
21
  } from './ResourceQuotaForm/ResourceQuotaFormConstants';
22
22
 
23
- const UpdateResourceQuotaModal = ({ initialProperties, initialStatus }) => {
23
+ const UpdateResourceQuotaModal = ({
24
+ initialProperties,
25
+ initialStatus,
26
+ showAssignmentWarning,
27
+ }) => {
24
28
  const staticId = `${MODAL_ID_UPDATE_RESOURCE_QUOTA}-${initialProperties[RESOURCE_IDENTIFIER_ID]}`;
25
29
  const [isOpen, setIsOpen] = useState(false);
26
30
  const [quotaProperties, setQuotaProperties] = useState(initialProperties);
@@ -52,6 +56,7 @@ const UpdateResourceQuotaModal = ({ initialProperties, initialStatus }) => {
52
56
  >
53
57
  <ResourceQuotaForm
54
58
  isNewQuota={false}
59
+ showAssignmentWarning={showAssignmentWarning}
55
60
  initialProperties={quotaProperties}
56
61
  initialStatus={quotaStatus}
57
62
  quotaChangesCallback={onQuotaChangesCallback}
@@ -138,6 +143,7 @@ UpdateResourceQuotaModal.propTypes = {
138
143
  ]),
139
144
  }),
140
145
  }),
146
+ showAssignmentWarning: PropTypes.bool.isRequired,
141
147
  };
142
148
 
143
149
  export default UpdateResourceQuotaModal;
@@ -1,4 +1,4 @@
1
1
 
2
2
  dt {
3
- font-size: var(--pf-global--FontSize--sm);
3
+ font-size: var(--pf-v5-global--FontSize--sm);
4
4
  }
@@ -23,7 +23,7 @@ const EditableSwitch = ({
23
23
  aria-label={identifier}
24
24
  ouiaId={`switch-${identifier}`}
25
25
  isChecked={value}
26
- onChange={onSwitch}
26
+ onChange={(_event, val) => onSwitch(val)}
27
27
  disabled={disabled}
28
28
  />
29
29
  );