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.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +1 -1
- data/app/controllers/foreman_resource_quota/concerns/api/v2/hosts_controller_extensions.rb +21 -0
- data/app/helpers/foreman_resource_quota/hosts_helper.rb +18 -7
- data/app/lib/foreman_resource_quota/exceptions.rb +1 -0
- data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +9 -10
- data/app/models/concerns/foreman_resource_quota/user_extensions.rb +27 -0
- data/app/models/concerns/foreman_resource_quota/usergroup_extensions.rb +18 -0
- data/app/models/foreman_resource_quota/resource_quota.rb +23 -0
- data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +9 -1
- data/app/views/hosts/_form_quota_fields.html.erb +14 -2
- data/app/views/users/_form_quota_tab.html.erb +2 -2
- data/db/migrate/20240611141939_drop_missing_hosts.rb +9 -2
- data/db/migrate/20250410082728_add_unassigned_flag_to_resource_quota.rb +7 -0
- data/db/seeds.d/030-unassigned_quota.rb +36 -0
- data/lib/foreman_resource_quota/register.rb +8 -0
- data/lib/foreman_resource_quota/version.rb +1 -1
- data/lib/tasks/foreman_resource_quota_tasks.rake +4 -5
- data/package.json +9 -10
- data/webpack/components/CreateResourceQuotaModal.js +1 -0
- data/webpack/components/ResourceQuotaEmptyState/__test__/__snapshots__/ResourceQuotaEmptyState.test.js.snap +4 -0
- data/webpack/components/ResourceQuotaEmptyState/index.js +1 -0
- data/webpack/components/ResourceQuotaForm/ResourceQuotaForm.scss +1 -1
- data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +1 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/Properties.scss +4 -3
- data/webpack/components/ResourceQuotaForm/components/Properties/StaticDetail.js +3 -2
- data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +4 -1
- data/webpack/components/ResourceQuotaForm/components/Properties/index.js +86 -45
- data/webpack/components/ResourceQuotaForm/components/Resource/Resource.scss +5 -5
- data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +84 -37
- data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.scss +7 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +14 -13
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.scss +4 -4
- data/webpack/components/ResourceQuotaForm/components/Resource/__test__/__snapshots__/UnitInputField.test.js.snap +149 -140
- data/webpack/components/ResourceQuotaForm/components/Resource/index.js +28 -17
- data/webpack/components/ResourceQuotaForm/components/Submit.js +2 -1
- data/webpack/components/ResourceQuotaForm/index.js +33 -19
- data/webpack/components/UpdateResourceQuotaModal.js +7 -1
- data/webpack/lib/ActionableDetail.scss +1 -1
- data/webpack/lib/EditableSwitch.js +1 -1
- data/webpack/lib/EditableTextInput/EditableTextInput.js +81 -77
- data/webpack/lib/EditableTextInput/editableTextInput.scss +30 -28
- metadata +8 -8
@@ -8,15 +8,16 @@ import {
|
|
8
8
|
Card,
|
9
9
|
CardBody,
|
10
10
|
CardHeader,
|
11
|
-
CardActions,
|
12
11
|
CardTitle,
|
13
|
-
|
12
|
+
Flex,
|
13
|
+
FlexItem,
|
14
14
|
LabelGroup,
|
15
15
|
Button,
|
16
16
|
Tooltip,
|
17
17
|
} from '@patternfly/react-core';
|
18
18
|
|
19
19
|
import {
|
20
|
+
ExclamationCircleIcon,
|
20
21
|
UserIcon,
|
21
22
|
UsersIcon,
|
22
23
|
ClusterIcon,
|
@@ -42,6 +43,8 @@ const Properties = ({
|
|
42
43
|
initialName,
|
43
44
|
initialDescription,
|
44
45
|
initialStatus,
|
46
|
+
unassigned,
|
47
|
+
showAssignmentWarning,
|
45
48
|
handleInputValidation,
|
46
49
|
onChange,
|
47
50
|
onApply,
|
@@ -70,48 +73,70 @@ const Properties = ({
|
|
70
73
|
);
|
71
74
|
};
|
72
75
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
76
|
+
const renderSyncButton = () => {
|
77
|
+
if (isNewQuota) {
|
78
|
+
return <></>;
|
79
|
+
}
|
80
|
+
return (
|
81
|
+
<Tooltip
|
82
|
+
content={
|
83
|
+
<div>
|
84
|
+
<b> {__('Fetch quota utilization')} </b>
|
85
|
+
<div>
|
86
|
+
{__(
|
87
|
+
'This can take some time since the resources of every host, assigned to this quota, must be requested.'
|
88
|
+
)}
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
}
|
92
|
+
reference={tooltipRefFetchButton}
|
93
|
+
>
|
94
|
+
<Button
|
95
|
+
id="resource-quota-index-button"
|
96
|
+
ouiaId="resource-quota-index-button"
|
97
|
+
isLoading={isFetchLoading}
|
98
|
+
icon={<SyncAltIcon />}
|
99
|
+
size="sm"
|
100
|
+
onClick={onClickFetch}
|
101
|
+
ref={tooltipRefFetchButton}
|
102
|
+
/>
|
103
|
+
</Tooltip>
|
104
|
+
);
|
105
|
+
};
|
106
|
+
|
107
|
+
const renderHeaderTitle = () => {
|
108
|
+
if (isNewQuota) {
|
109
|
+
return <CardTitle>{__('Properties')}</CardTitle>;
|
110
|
+
}
|
111
|
+
return (
|
112
|
+
<Flex>
|
113
|
+
<FlexItem>
|
114
|
+
<CardTitle>{__('Properties')}</CardTitle>
|
115
|
+
</FlexItem>
|
116
|
+
<FlexItem>
|
117
|
+
<LabelGroup isCompact>
|
118
|
+
<StatusPropertiesLabel
|
119
|
+
color={showAssignmentWarning ? 'red' : 'blue'}
|
120
|
+
iconChild={
|
121
|
+
showAssignmentWarning ? (
|
122
|
+
<ExclamationCircleIcon />
|
123
|
+
) : (
|
124
|
+
<ClusterIcon />
|
125
|
+
)
|
126
|
+
}
|
127
|
+
statusContent={
|
128
|
+
statusProperties[RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]
|
129
|
+
}
|
130
|
+
linkUrl={`/hosts?search=resource_quota="${initialName}"`}
|
131
|
+
tooltipText={
|
132
|
+
showAssignmentWarning
|
133
|
+
? __(
|
134
|
+
"The setting 'resource_quota_optional_assignment' is set to 'No' but there are hosts with no quota assignment. Please check your hosts' quota assignments!"
|
135
|
+
)
|
136
|
+
: __('Number of assigned hosts')
|
95
137
|
}
|
96
|
-
reference={tooltipRefFetchButton}
|
97
138
|
/>
|
98
|
-
|
99
|
-
)}
|
100
|
-
{isNewQuota ? (
|
101
|
-
<CardTitle>{__('Properties')}</CardTitle>
|
102
|
-
) : (
|
103
|
-
<Level hasGutter>
|
104
|
-
<CardTitle>{__('Properties')}</CardTitle>
|
105
|
-
<LabelGroup isCompact>
|
106
|
-
<StatusPropertiesLabel
|
107
|
-
color="blue"
|
108
|
-
iconChild={<ClusterIcon />}
|
109
|
-
statusContent={
|
110
|
-
statusProperties[RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]
|
111
|
-
}
|
112
|
-
linkUrl={`/hosts?search=resource_quota="${initialName}"`}
|
113
|
-
tooltipText="Number of assigned hosts"
|
114
|
-
/>
|
139
|
+
{!unassigned && (
|
115
140
|
<StatusPropertiesLabel
|
116
141
|
color="blue"
|
117
142
|
iconChild={<UserIcon />}
|
@@ -121,6 +146,8 @@ const Properties = ({
|
|
121
146
|
linkUrl={`/users?search=resource_quota="${initialName}"`}
|
122
147
|
tooltipText="Number of assigned users"
|
123
148
|
/>
|
149
|
+
)}
|
150
|
+
{!unassigned && (
|
124
151
|
<StatusPropertiesLabel
|
125
152
|
color="blue"
|
126
153
|
iconChild={<UsersIcon />}
|
@@ -130,9 +157,17 @@ const Properties = ({
|
|
130
157
|
linkUrl={`/usergroups?search=resource_quota="${initialName}"`}
|
131
158
|
tooltipText="Number of assigned usergroups"
|
132
159
|
/>
|
133
|
-
|
134
|
-
</
|
135
|
-
|
160
|
+
)}
|
161
|
+
</LabelGroup>
|
162
|
+
</FlexItem>
|
163
|
+
</Flex>
|
164
|
+
);
|
165
|
+
};
|
166
|
+
|
167
|
+
return (
|
168
|
+
<Card id="resource-quota-index-card" ouiaId="resource-quota-index-card">
|
169
|
+
<CardHeader actions={{ actions: renderSyncButton() }}>
|
170
|
+
{renderHeaderTitle()}
|
136
171
|
</CardHeader>
|
137
172
|
<CardBody>
|
138
173
|
<TextContent>
|
@@ -147,6 +182,7 @@ const Properties = ({
|
|
147
182
|
onChange={onChange}
|
148
183
|
isRestrictInputValidation
|
149
184
|
isRequired
|
185
|
+
isDisabled={unassigned}
|
150
186
|
/>
|
151
187
|
<TextInputField
|
152
188
|
initialValue={initialDescription}
|
@@ -157,6 +193,7 @@ const Properties = ({
|
|
157
193
|
onApply={onApply}
|
158
194
|
onChange={onChange}
|
159
195
|
isTextArea
|
196
|
+
isDisabled={unassigned}
|
160
197
|
/>
|
161
198
|
</TextList>
|
162
199
|
</TextContent>
|
@@ -173,6 +210,8 @@ Properties.defaultProps = {
|
|
173
210
|
[RESOURCE_IDENTIFIER_STATUS_NUM_USERS]: null,
|
174
211
|
[RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]: null,
|
175
212
|
},
|
213
|
+
unassigned: false,
|
214
|
+
showAssignmentWarning: false,
|
176
215
|
};
|
177
216
|
|
178
217
|
Properties.propTypes = {
|
@@ -184,6 +223,8 @@ Properties.propTypes = {
|
|
184
223
|
[RESOURCE_IDENTIFIER_STATUS_NUM_USERS]: PropTypes.number,
|
185
224
|
[RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]: PropTypes.number,
|
186
225
|
}),
|
226
|
+
unassigned: PropTypes.bool,
|
227
|
+
showAssignmentWarning: PropTypes.bool,
|
187
228
|
handleInputValidation: PropTypes.func.isRequired,
|
188
229
|
onChange: PropTypes.func.isRequired,
|
189
230
|
onApply: PropTypes.func.isRequired,
|
@@ -1,13 +1,13 @@
|
|
1
1
|
//@import '~@theforeman/vendor/scss/variables';
|
2
2
|
|
3
|
-
.pf-c-card__body:first-child {
|
3
|
+
.pf-v5-c-card__body:first-child {
|
4
4
|
padding-top: 1rem;
|
5
5
|
}
|
6
6
|
|
7
|
-
.pf-c-form__label {
|
8
|
-
font-size: var(--pf-global--FontSize--sm);
|
7
|
+
.pf-v5-c-form__label {
|
8
|
+
font-size: var(--pf-v5-global--FontSize--sm);
|
9
9
|
}
|
10
10
|
|
11
|
-
.pf-c-form__group-label-help {
|
12
|
-
font-size: var(--pf-global--FontSize--sm);
|
11
|
+
.pf-v5-c-form__group-label-help {
|
12
|
+
font-size: var(--pf-v5-global--FontSize--sm);
|
13
13
|
}
|
@@ -3,16 +3,24 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import {
|
4
4
|
FormGroup,
|
5
5
|
FormHelperText,
|
6
|
+
HelperText,
|
7
|
+
HelperTextItem,
|
6
8
|
TextInput,
|
7
9
|
InputGroup,
|
10
|
+
InputGroupItem,
|
8
11
|
InputGroupText,
|
12
|
+
} from '@patternfly/react-core';
|
13
|
+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
14
|
+
|
15
|
+
import {
|
9
16
|
Dropdown,
|
10
17
|
DropdownItem,
|
11
18
|
DropdownToggle,
|
12
|
-
} from '@patternfly/react-core';
|
19
|
+
} from '@patternfly/react-core/deprecated';
|
13
20
|
|
14
21
|
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
15
22
|
|
23
|
+
import './UnitInputField.scss';
|
16
24
|
import { findLargestFittingUnit } from '../../../../helper';
|
17
25
|
|
18
26
|
const UnitInputField = ({
|
@@ -41,6 +49,7 @@ const UnitInputField = ({
|
|
41
49
|
unitDropdownItems = units.map(unit => (
|
42
50
|
<DropdownItem
|
43
51
|
id={`unit-dropdownitem-${unit.symbol.toLowerCase()}`}
|
52
|
+
ouiaId={`unit-dropdownitem-${unit.symbol.toLowerCase()}`}
|
44
53
|
key={unit.symbol.toLowerCase()}
|
45
54
|
>
|
46
55
|
{unit.symbol}
|
@@ -69,11 +78,6 @@ const UnitInputField = ({
|
|
69
78
|
[units]
|
70
79
|
);
|
71
80
|
|
72
|
-
/* warning text displayed beneath value input field (built-in is used for errors) */
|
73
|
-
const helperTextWarning = (text, isHidden) => (
|
74
|
-
<FormHelperText isHidden={isHidden}>{text}</FormHelperText>
|
75
|
-
);
|
76
|
-
|
77
81
|
/* applies the selected unit and checks the bounds */
|
78
82
|
const isValid = useCallback(
|
79
83
|
val => {
|
@@ -164,41 +168,84 @@ const UnitInputField = ({
|
|
164
168
|
}
|
165
169
|
|
166
170
|
return (
|
167
|
-
<
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
171
|
+
<InputGroupItem>
|
172
|
+
<Dropdown
|
173
|
+
ouiaId="resource-quota-unit-input-field-input-group-item-dropdown"
|
174
|
+
onSelect={onUnitSelect}
|
175
|
+
toggle={
|
176
|
+
<DropdownToggle
|
177
|
+
isDisabled={isDisabled}
|
178
|
+
ouiaId="resource-quota-unit-input-field-input-group-item-dropdowni-toggle"
|
179
|
+
onToggle={(_event, _val) => onUnitToggle()}
|
180
|
+
>
|
181
|
+
{__(`${selectedUnit.symbol}`)}
|
182
|
+
</DropdownToggle>
|
183
|
+
}
|
184
|
+
isOpen={isUnitOpen}
|
185
|
+
dropdownItems={unitDropdownItems}
|
186
|
+
/>
|
187
|
+
</InputGroupItem>
|
177
188
|
);
|
178
189
|
};
|
179
190
|
|
191
|
+
const renderFormHelperText = () => {
|
192
|
+
if (validated === 'error') {
|
193
|
+
return (
|
194
|
+
<FormHelperText>
|
195
|
+
<HelperText>
|
196
|
+
<HelperTextItem
|
197
|
+
icon={<ExclamationCircleIcon />}
|
198
|
+
variant={validated}
|
199
|
+
>
|
200
|
+
{errorText}
|
201
|
+
</HelperTextItem>
|
202
|
+
</HelperText>
|
203
|
+
</FormHelperText>
|
204
|
+
);
|
205
|
+
}
|
206
|
+
if (validated === 'warning') {
|
207
|
+
return (
|
208
|
+
<FormHelperText>
|
209
|
+
<HelperText>
|
210
|
+
<HelperTextItem
|
211
|
+
icon={<ExclamationCircleIcon />}
|
212
|
+
variant={validated}
|
213
|
+
>
|
214
|
+
{errorText}
|
215
|
+
</HelperTextItem>
|
216
|
+
</HelperText>
|
217
|
+
</FormHelperText>
|
218
|
+
);
|
219
|
+
}
|
220
|
+
return <></>;
|
221
|
+
};
|
222
|
+
|
180
223
|
return (
|
181
|
-
<
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
224
|
+
<div className="container-unit-input-field">
|
225
|
+
<FormGroup
|
226
|
+
label={__('Quota Limit')}
|
227
|
+
validated={validated}
|
228
|
+
fieldId="quota-limit-resource-quota-form-group"
|
229
|
+
labelIcon={labelIcon || {}}
|
230
|
+
>
|
231
|
+
<InputGroup>
|
232
|
+
<InputGroupItem>
|
233
|
+
<TextInput
|
234
|
+
isDisabled={isDisabled}
|
235
|
+
value={inputValue}
|
236
|
+
min={minValue}
|
237
|
+
max={maxValue}
|
238
|
+
validated={validated}
|
239
|
+
id="resource-quota-reg-token-life-time-input"
|
240
|
+
ouiaId="resource-quota-reg-token-life-time-input"
|
241
|
+
onChange={(_event, val) => setInputValue(val)}
|
242
|
+
/>
|
243
|
+
</InputGroupItem>
|
244
|
+
{unitView()}
|
245
|
+
</InputGroup>
|
246
|
+
{renderFormHelperText()}
|
247
|
+
</FormGroup>
|
248
|
+
</div>
|
202
249
|
);
|
203
250
|
};
|
204
251
|
|
@@ -7,7 +7,7 @@ import {
|
|
7
7
|
ProgressMeasureLocation,
|
8
8
|
Tooltip,
|
9
9
|
} from '@patternfly/react-core';
|
10
|
-
import SyncAltIcon from '@patternfly/react-icons';
|
10
|
+
import { SyncAltIcon } from '@patternfly/react-icons';
|
11
11
|
|
12
12
|
import { translate as __ } from 'foremanReact/common/I18n';
|
13
13
|
|
@@ -105,19 +105,20 @@ const UtilizationProgress = ({
|
|
105
105
|
<Tooltip
|
106
106
|
content={resourceUtilizationTooltipText}
|
107
107
|
reference={tooltipRefUtilization}
|
108
|
-
/>
|
109
|
-
<div
|
110
|
-
className={isEnabled ? '' : 'progress-disabled'}
|
111
|
-
ref={tooltipRefUtilization}
|
112
108
|
>
|
113
|
-
<
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
109
|
+
<div
|
110
|
+
className={isEnabled ? '' : 'progress-disabled'}
|
111
|
+
ref={tooltipRefUtilization}
|
112
|
+
>
|
113
|
+
<Progress
|
114
|
+
aria-label={`resource-card-${cardId}-progress`}
|
115
|
+
value={resourceUtilizationPercent}
|
116
|
+
measureLocation={ProgressMeasureLocation.inside}
|
117
|
+
size={ProgressSize.lg}
|
118
|
+
variant={resourceProgressVariant()}
|
119
|
+
/>
|
120
|
+
</div>
|
121
|
+
</Tooltip>
|
121
122
|
</div>
|
122
123
|
);
|
123
124
|
};
|
@@ -1,10 +1,10 @@
|
|
1
1
|
.progress-disabled {
|
2
2
|
background-color: transparent;
|
3
|
-
.pf-c-progress .pf-c-progress__bar::before {
|
4
|
-
background-color: var(--pf-global--disabled-color--100);
|
3
|
+
.pf-v5-c-progress .pf-v5-c-progress__bar::before {
|
4
|
+
background-color: var(--pf-v5-global--disabled-color--100);
|
5
5
|
}
|
6
6
|
}
|
7
7
|
|
8
|
-
.progress-disabled .pf-c-progress.pf-m-inside .pf-c-progress__indicator {
|
9
|
-
background-color: var(--pf-global--palette--black-500);
|
8
|
+
.progress-disabled .pf-v5-c-progress.pf-v5-m-inside .pf-v5-c-progress__indicator {
|
9
|
+
background-color: var(--pf-v5-global--palette--black-500);
|
10
10
|
}
|