katello 4.5.0.rc1 → 4.5.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/assets/javascripts/katello/hosts/activation_key_edit.js +9 -2
- data/app/controllers/katello/api/registry/registry_proxies_controller.rb +3 -0
- data/app/controllers/katello/api/v2/alternate_content_sources_bulk_actions_controller.rb +44 -0
- data/app/controllers/katello/api/v2/alternate_content_sources_controller.rb +29 -6
- data/app/controllers/katello/api/v2/content_view_components_controller.rb +1 -1
- data/app/controllers/katello/api/v2/content_view_repositories_controller.rb +1 -1
- data/app/controllers/katello/api/v2/repositories_controller.rb +2 -8
- data/app/lib/actions/katello/alternate_content_source/refresh.rb +27 -0
- data/app/lib/actions/katello/cdn_configuration/update.rb +1 -1
- data/app/lib/actions/katello/content_view/publish.rb +1 -1
- data/app/lib/actions/katello/organization/manifest_refresh.rb +1 -1
- data/app/lib/actions/pulp3/alternate_content_source/delete.rb +2 -2
- data/app/lib/actions/pulp3/alternate_content_source/delete_remote.rb +2 -2
- data/app/lib/actions/pulp3/alternate_content_source/refresh.rb +23 -0
- data/app/lib/actions/pulp3/alternate_content_source/update.rb +2 -2
- data/app/lib/actions/pulp3/alternate_content_source/update_remote.rb +2 -2
- data/app/lib/actions/pulp3/orchestration/alternate_content_source/create.rb +0 -2
- data/app/lib/actions/pulp3/orchestration/alternate_content_source/refresh.rb +15 -0
- data/app/lib/actions/pulp3/orchestration/alternate_content_source/update.rb +0 -2
- data/app/lib/actions/pulp3/repository/refresh_distribution.rb +1 -4
- data/app/lib/actions/pulp3/repository/save_artifact.rb +1 -1
- data/app/lib/actions/pulp3/repository/save_distribution_references.rb +0 -2
- data/app/models/katello/alternate_content_source.rb +5 -0
- data/app/services/katello/pulp3/alternate_content_source.rb +6 -0
- data/app/services/katello/pulp3/content_view_version/metadata_map.rb +1 -1
- data/app/services/katello/pulp3/repository.rb +29 -1
- data/app/views/katello/api/v2/alternate_content_sources/base.json.rabl +10 -1
- data/app/views/katello/api/v2/content_facet/show.json.rabl +12 -0
- data/app/views/katello/api/v2/repository_sets/show.json.rabl +4 -0
- data/config/routes/api/v2.rb +16 -4
- data/db/migrate/20220303160220_remove_duplicate_errata.rb +1 -1
- data/db/migrate/20220428203334_add_last_refreshed_to_katello_alternate_content_sources.rb +5 -0
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/capsule-content/capsule-content.controller.js +1 -1
- data/lib/katello/permission_creator.rb +4 -2
- data/lib/katello/tasks/refresh_alternate_content_sources.rake +10 -0
- data/lib/katello/version.rb +1 -1
- data/webpack/components/Bookmark/index.js +22 -14
- data/webpack/components/Search/Search.js +4 -0
- data/webpack/components/Table/MainTable.scss +5 -1
- data/webpack/components/Table/TableWrapper.js +5 -1
- data/webpack/components/TypeAhead/TypeAhead.js +4 -0
- data/webpack/components/TypeAhead/pf4Search/TypeAheadSearch.js +2 -0
- data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ChangeHostCVModal.js +2 -8
- data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ContentViewDetailsCard.js +41 -11
- data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/HostCollectionsActions.js +2 -2
- data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/HostCollectionsCard.js +32 -13
- data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/__tests__/hostCollectionsCard.test.js +8 -0
- data/webpack/components/extensions/HostDetails/DetailsTabCards/RecentCommunicationCardExtensions.js +37 -0
- data/webpack/components/extensions/HostDetails/HostDetailsActions.js +11 -0
- data/webpack/components/extensions/HostDetails/Tabs/ContentTab/SecondaryTabsRoutes.js +4 -0
- data/webpack/components/extensions/HostDetails/Tabs/ContentTab/constants.js +2 -0
- data/webpack/components/extensions/HostDetails/Tabs/ContentTab/index.js +6 -1
- data/webpack/components/extensions/HostDetails/Tabs/ErrataTab/ErrataTab.js +120 -51
- data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/ModuleStreamsTab.js +71 -37
- data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackageInstallModal.js +4 -3
- data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackagesTab.js +117 -40
- data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionActions.js +25 -3
- data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionHooks.js +85 -0
- data/webpack/components/extensions/HostDetails/Tabs/RepositorySetsTab/RepositorySetsTab.js +87 -33
- data/webpack/components/extensions/HostDetails/Tabs/TracesTab/EnableTracerModal.js +14 -7
- data/webpack/components/extensions/HostDetails/Tabs/TracesTab/HostTracesActions.js +2 -1
- data/webpack/components/extensions/HostDetails/Tabs/TracesTab/TracesEnabler.js +104 -0
- data/webpack/components/extensions/HostDetails/Tabs/TracesTab/TracesTab.js +92 -51
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/errataTab.test.js +13 -23
- data/webpack/components/extensions/HostDetails/Tabs/{ModuleStreamsTab/__tests__/modules.fixtures.json → __tests__/moduleStreams.fixtures.json} +0 -0
- data/webpack/components/extensions/HostDetails/Tabs/{ModuleStreamsTab/__tests__ → __tests__}/moduleStreamsTab.test.js +13 -6
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/packageInstallModal.test.js +21 -15
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/packagesTab.test.js +8 -0
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/repositorySets.fixtures.json +4 -1
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/repositorySetsTab.test.js +26 -0
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/tracesTab.test.js +7 -4
- data/webpack/components/extensions/HostDetails/hostDetailsHelpers.js +18 -0
- data/webpack/global_index.js +2 -2
- data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -1
- data/webpack/scenes/AlternateContentSources/ACSActions.js +13 -1
- data/webpack/scenes/AlternateContentSources/ACSConstants.js +14 -0
- data/webpack/scenes/AlternateContentSources/ACSSelectors.js +10 -1
- data/webpack/scenes/AlternateContentSources/Create/ACSCreateContext.js +4 -0
- data/webpack/scenes/AlternateContentSources/Create/ACSCreateWizard.js +160 -0
- data/webpack/scenes/AlternateContentSources/Create/Steps/ACSCreateFinish.js +79 -0
- data/webpack/scenes/AlternateContentSources/Create/Steps/ACSCredentials.js +199 -0
- data/webpack/scenes/AlternateContentSources/Create/Steps/ACSReview.js +104 -0
- data/webpack/scenes/AlternateContentSources/Create/Steps/ACSSmartProxies.js +41 -0
- data/webpack/scenes/AlternateContentSources/Create/Steps/AcsUrlPaths.js +71 -0
- data/webpack/scenes/AlternateContentSources/Create/Steps/NameACS.js +57 -0
- data/webpack/scenes/AlternateContentSources/Create/Steps/SelectSource.js +77 -0
- data/webpack/scenes/AlternateContentSources/Create/__tests__/acsCreate.test.js +149 -0
- data/webpack/scenes/AlternateContentSources/Create/__tests__/acsCreateData.fixtures.json +3 -0
- data/webpack/scenes/AlternateContentSources/Create/__tests__/contentCredentials.fixtures.json +69 -0
- data/webpack/scenes/AlternateContentSources/Create/__tests__/smartProxy.fixtures.json +65 -0
- data/webpack/scenes/AlternateContentSources/MainTable/ACSTable.js +33 -23
- data/webpack/scenes/ContentCredentials/ContentCredentialSelectors.js +4 -1
- data/webpack/scenes/ContentViews/Create/CreateContentViewForm.js +2 -2
- data/webpack/scenes/ContentViews/Create/__tests__/createContentView.test.js +1 -1
- data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentContentViewAddModal.js +1 -1
- data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentContentViewBulkAddModal.js +2 -2
- data/webpack/scenes/ContentViews/Details/ComponentContentViews/__tests__/contentViewComponents.test.js +4 -4
- data/webpack/scenes/ContentViews/Details/ContentViewInfo.js +1 -1
- data/webpack/scenes/ContentViews/__tests__/contentViewPage.test.js +4 -4
- data/webpack/scenes/ContentViews/components/ContentViewIcon.js +1 -1
- data/webpack/scenes/ContentViews/components/ContentViewsCounter.js +1 -1
- data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths.js +1 -1
- data/webpack/scenes/ContentViews/expansions/DetailsExpansion.js +2 -2
- data/webpack/scenes/ContentViews/expansions/RelatedContentViewComponentsModal.js +2 -2
- data/webpack/scenes/ContentViews/expansions/__tests__/contentViewComponentsModal.test.js +1 -1
- data/webpack/scenes/RedHatRepositories/components/Search.js +4 -4
- data/webpack/scenes/SmartProxy/SmartProxyContentActions.js +9 -2
- data/webpack/scenes/SmartProxy/SmartProxyContentConstants.js +1 -1
- data/webpack/scenes/SmartProxy/SmartProxyContentSelectors.js +10 -1
- data/webpack/scenes/Tasks/helpers.js +30 -3
- metadata +34 -14
- data/db/seeds.d/107-enable_dynflow.rb +0 -8
- data/webpack/components/extensions/HostDetails/Tabs/TracesTab/EnableTracerEmptyState.js +0 -42
@@ -12,6 +12,7 @@ import {
|
|
12
12
|
Skeleton,
|
13
13
|
Split,
|
14
14
|
SplitItem,
|
15
|
+
Spinner,
|
15
16
|
} from '@patternfly/react-core';
|
16
17
|
import { TableVariant, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
17
18
|
import { translate as __ } from 'foremanReact/common/I18n';
|
@@ -32,13 +33,22 @@ import { selectHostPackagesStatus } from './HostPackagesSelectors';
|
|
32
33
|
import {
|
33
34
|
HOST_PACKAGES_KEY, PACKAGES_VERSION_STATUSES, VERSION_STATUSES_TO_PARAM,
|
34
35
|
} from './HostPackagesConstants';
|
35
|
-
import { removePackage, updatePackage, removePackages, updatePackages } from '../RemoteExecutionActions';
|
36
|
+
import { removePackage, updatePackage, removePackages, updatePackages, installPackageBySearch } from '../RemoteExecutionActions';
|
36
37
|
import { katelloPackageUpdateUrl, packagesUpdateUrl } from '../customizedRexUrlHelpers';
|
37
38
|
import './PackagesTab.scss';
|
38
39
|
import hostIdNotReady from '../../HostDetailsActions';
|
39
40
|
import PackageInstallModal from './PackageInstallModal';
|
40
|
-
import { defaultRemoteActionMethod,
|
41
|
+
import { defaultRemoteActionMethod,
|
42
|
+
hasRequiredPermissions as can,
|
43
|
+
missingRequiredPermissions as cannot,
|
44
|
+
KATELLO_AGENT,
|
45
|
+
userPermissionsFromHostDetails } from '../../hostDetailsHelpers';
|
41
46
|
import SortableColumnHeaders from '../../../../Table/components/SortableColumnHeaders';
|
47
|
+
import { useRexJobPolling } from '../RemoteExecutionHooks';
|
48
|
+
|
49
|
+
const invokeRexJobs = ['create_job_invocations'];
|
50
|
+
const doKatelloAgentActions = ['edit_hosts'];
|
51
|
+
const createBookmarks = ['create_bookmarks'];
|
42
52
|
|
43
53
|
export const PackagesTab = () => {
|
44
54
|
const hostDetails = useSelector(state => selectAPIResponse(state, 'HOST_DETAILS'));
|
@@ -58,6 +68,9 @@ export const PackagesTab = () => {
|
|
58
68
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
59
69
|
const closeModal = () => setIsModalOpen(false);
|
60
70
|
const showKatelloAgent = (defaultRemoteActionMethod({ hostDetails }) === KATELLO_AGENT);
|
71
|
+
const showActions = showKatelloAgent ?
|
72
|
+
can(doKatelloAgentActions, userPermissionsFromHostDetails({ hostDetails })) :
|
73
|
+
can(invokeRexJobs, userPermissionsFromHostDetails({ hostDetails }));
|
61
74
|
|
62
75
|
const [isActionOpen, setIsActionOpen] = useState(false);
|
63
76
|
const onActionSelect = () => {
|
@@ -128,6 +141,62 @@ export const PackagesTab = () => {
|
|
128
141
|
initialSearchQuery: searchParam || '',
|
129
142
|
});
|
130
143
|
|
144
|
+
const packageRemoveAction = packageName => removePackage({
|
145
|
+
hostname,
|
146
|
+
packageName,
|
147
|
+
});
|
148
|
+
|
149
|
+
const {
|
150
|
+
triggerJobStart: triggerPackageRemove, lastCompletedJob: lastCompletedPackageRemove,
|
151
|
+
isPolling: isRemoveInProgress,
|
152
|
+
} = useRexJobPolling(packageRemoveAction);
|
153
|
+
|
154
|
+
const packageBulkRemoveAction = bulkParams => removePackages({
|
155
|
+
hostname,
|
156
|
+
search: bulkParams,
|
157
|
+
});
|
158
|
+
|
159
|
+
const {
|
160
|
+
triggerJobStart: triggerBulkPackageRemove,
|
161
|
+
lastCompletedJob: lastCompletedBulkPackageRemove,
|
162
|
+
isPolling: isBulkRemoveInProgress,
|
163
|
+
} = useRexJobPolling(packageBulkRemoveAction);
|
164
|
+
|
165
|
+
const packageUpgradeAction = packageName => updatePackage({
|
166
|
+
hostname,
|
167
|
+
packageName,
|
168
|
+
});
|
169
|
+
|
170
|
+
const {
|
171
|
+
triggerJobStart: triggerPackageUpgrade,
|
172
|
+
lastCompletedJob: lastCompletedPackageUpgrade,
|
173
|
+
isPolling: isUpgradeInProgress,
|
174
|
+
} = useRexJobPolling(packageUpgradeAction);
|
175
|
+
|
176
|
+
const packageBulkUpgradeAction = bulkParams => updatePackages({
|
177
|
+
hostname,
|
178
|
+
search: bulkParams,
|
179
|
+
});
|
180
|
+
|
181
|
+
const {
|
182
|
+
triggerJobStart: triggerBulkPackageUpgrade,
|
183
|
+
lastCompletedJob: lastCompletedBulkPackageUpgrade,
|
184
|
+
isPolling: isBulkUpgradeInProgress,
|
185
|
+
} = useRexJobPolling(packageBulkUpgradeAction);
|
186
|
+
|
187
|
+
const packageInstallAction
|
188
|
+
= bulkParams => installPackageBySearch({ hostname, search: bulkParams });
|
189
|
+
|
190
|
+
const {
|
191
|
+
triggerJobStart: triggerPackageInstall,
|
192
|
+
lastCompletedJob: lastCompletedPackageInstall,
|
193
|
+
isPolling: isInstallInProgress,
|
194
|
+
} = useRexJobPolling(packageInstallAction);
|
195
|
+
|
196
|
+
const actionInProgress = (isRemoveInProgress || isUpgradeInProgress
|
197
|
+
|| isBulkRemoveInProgress || isBulkUpgradeInProgress || isInstallInProgress);
|
198
|
+
const disabledReason = __('A remote execution job is in progress.');
|
199
|
+
|
131
200
|
if (!hostId) return <Skeleton />;
|
132
201
|
|
133
202
|
const handleInstallPackagesClick = () => {
|
@@ -135,10 +204,7 @@ export const PackagesTab = () => {
|
|
135
204
|
setIsModalOpen(true);
|
136
205
|
};
|
137
206
|
|
138
|
-
const removePackageViaRemoteExecution = packageName =>
|
139
|
-
hostname,
|
140
|
-
packageName,
|
141
|
-
}));
|
207
|
+
const removePackageViaRemoteExecution = packageName => triggerPackageRemove(packageName);
|
142
208
|
|
143
209
|
const removeViaKatelloAgent = (packageName) => {
|
144
210
|
dispatch(removePackageViaKatelloAgent(hostId, { packages: [packageName] }));
|
@@ -149,7 +215,7 @@ export const PackagesTab = () => {
|
|
149
215
|
const selected = fetchBulkParams();
|
150
216
|
setIsBulkActionOpen(false);
|
151
217
|
selectNone();
|
152
|
-
|
218
|
+
triggerBulkPackageRemove(selected);
|
153
219
|
};
|
154
220
|
|
155
221
|
const selectedPackageNames = () => selectedResults.map(({ name }) => name);
|
@@ -178,19 +244,13 @@ export const PackagesTab = () => {
|
|
178
244
|
}
|
179
245
|
};
|
180
246
|
|
181
|
-
const upgradeViaRemoteExecution = packageName =>
|
182
|
-
hostname,
|
183
|
-
packageName,
|
184
|
-
}));
|
247
|
+
const upgradeViaRemoteExecution = packageName => triggerPackageUpgrade(packageName);
|
185
248
|
|
186
249
|
const upgradeBulkViaRemoteExecution = () => {
|
187
250
|
const selected = fetchBulkParams();
|
188
251
|
setIsBulkActionOpen(false);
|
189
252
|
selectNone();
|
190
|
-
|
191
|
-
hostname,
|
192
|
-
search: selected,
|
193
|
-
}));
|
253
|
+
triggerBulkPackageUpgrade(selected);
|
194
254
|
};
|
195
255
|
|
196
256
|
const upgradeBulkViaKatelloAgent = () => {
|
@@ -223,6 +283,9 @@ export const PackagesTab = () => {
|
|
223
283
|
(defaultRemoteAction === KATELLO_AGENT && selectAllMode && !areAllRowsSelected()) ||
|
224
284
|
(!selectAllMode && !allUpgradable());
|
225
285
|
|
286
|
+
const readOnlyBookmarks =
|
287
|
+
cannot(createBookmarks, userPermissionsFromHostDetails({ hostDetails }));
|
288
|
+
|
226
289
|
const dropdownUpgradeItems = [
|
227
290
|
<DropdownItem
|
228
291
|
aria-label="bulk_upgrade_rex"
|
@@ -270,7 +333,7 @@ export const PackagesTab = () => {
|
|
270
333
|
return newStatus;
|
271
334
|
});
|
272
335
|
|
273
|
-
const actionButtons = (
|
336
|
+
const actionButtons = showActions ? (
|
274
337
|
<Split hasGutter>
|
275
338
|
<SplitItem>
|
276
339
|
<ActionList isIconList>
|
@@ -284,7 +347,7 @@ export const PackagesTab = () => {
|
|
284
347
|
{__('Upgrade')}
|
285
348
|
</DropdownToggleAction>,
|
286
349
|
]}
|
287
|
-
isDisabled={disableUpgrade()}
|
350
|
+
isDisabled={actionInProgress || disableUpgrade()}
|
288
351
|
splitButtonVariant="action"
|
289
352
|
toggleVariant="primary"
|
290
353
|
onToggle={onActionToggle}
|
@@ -295,17 +358,19 @@ export const PackagesTab = () => {
|
|
295
358
|
/>
|
296
359
|
</ActionListItem>
|
297
360
|
<ActionListItem>
|
298
|
-
<
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
361
|
+
{actionInProgress ? <Spinner size="lg" style={{ marginLeft: '1em', marginTop: '4px' }} /> : (
|
362
|
+
<Dropdown
|
363
|
+
toggle={<KebabToggle aria-label="bulk_actions" onToggle={toggleBulkAction} />}
|
364
|
+
isOpen={isBulkActionOpen}
|
365
|
+
isPlain
|
366
|
+
dropdownItems={dropdownRemoveItems}
|
367
|
+
/>
|
368
|
+
)}
|
304
369
|
</ActionListItem>
|
305
370
|
</ActionList>
|
306
371
|
</SplitItem>
|
307
372
|
</Split>
|
308
|
-
);
|
373
|
+
) : null;
|
309
374
|
|
310
375
|
const statusFilters = (
|
311
376
|
<Split hasGutter>
|
@@ -348,15 +413,18 @@ export const PackagesTab = () => {
|
|
348
413
|
}
|
349
414
|
ouiaId="host-packages-table"
|
350
415
|
additionalListeners={[hostId, packageStatusSelected,
|
351
|
-
activeSortDirection, activeSortColumn
|
416
|
+
activeSortDirection, activeSortColumn, lastCompletedPackageUpgrade,
|
417
|
+
lastCompletedPackageRemove, lastCompletedBulkPackageRemove,
|
418
|
+
lastCompletedBulkPackageUpgrade, lastCompletedPackageInstall]}
|
352
419
|
fetchItems={fetchItems}
|
353
420
|
bookmarkController="katello_host_installed_packages"
|
421
|
+
readOnlyBookmarks={readOnlyBookmarks}
|
354
422
|
autocompleteEndpoint={`/hosts/${hostId}/packages/auto_complete_search`}
|
355
423
|
foremanApiAutoComplete
|
356
424
|
rowsCount={results?.length}
|
357
425
|
variant={TableVariant.compact}
|
358
426
|
{...selectAll}
|
359
|
-
displaySelectAllCheckbox
|
427
|
+
displaySelectAllCheckbox={showActions}
|
360
428
|
>
|
361
429
|
<Thead>
|
362
430
|
<Tr>
|
@@ -382,6 +450,7 @@ export const PackagesTab = () => {
|
|
382
450
|
const rowActions = [
|
383
451
|
{
|
384
452
|
title: __('Remove'),
|
453
|
+
isDisabled: actionInProgress,
|
385
454
|
onClick: () => handlePackageRemove(packageName),
|
386
455
|
},
|
387
456
|
];
|
@@ -391,6 +460,7 @@ export const PackagesTab = () => {
|
|
391
460
|
{
|
392
461
|
title: __('Upgrade via remote execution'),
|
393
462
|
onClick: () => upgradeViaRemoteExecution(upgradableVersion),
|
463
|
+
isDisabled: actionInProgress,
|
394
464
|
},
|
395
465
|
{
|
396
466
|
title: __('Upgrade via customized remote execution'),
|
@@ -402,14 +472,18 @@ export const PackagesTab = () => {
|
|
402
472
|
|
403
473
|
return (
|
404
474
|
<Tr key={`${id}`}>
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
475
|
+
{showActions ? (
|
476
|
+
<Td
|
477
|
+
select={{
|
478
|
+
disable: actionInProgress,
|
479
|
+
isSelected: isSelected(id),
|
480
|
+
onSelect: (event, selected) => selectOne(selected, id, pkg),
|
481
|
+
rowIndex,
|
482
|
+
variant: 'checkbox',
|
483
|
+
}}
|
484
|
+
title={actionInProgress ? disabledReason : undefined}
|
485
|
+
/>
|
486
|
+
) : <Td> </Td>}
|
413
487
|
<Td>
|
414
488
|
{rpmId
|
415
489
|
? <a href={urlBuilder(`packages/${rpmId}`, '')}>{packageName}</a>
|
@@ -419,12 +493,14 @@ export const PackagesTab = () => {
|
|
419
493
|
<Td><PackagesStatus {...pkg} /></Td>
|
420
494
|
<Td>{installedVersion.replace(`${packageName}-`, '')}</Td>
|
421
495
|
<Td><PackagesLatestVersion {...pkg} /></Td>
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
496
|
+
{showActions ? (
|
497
|
+
<Td
|
498
|
+
key={`rowActions-${id}`}
|
499
|
+
actions={{
|
500
|
+
items: rowActions,
|
501
|
+
}}
|
502
|
+
/>
|
503
|
+
) : null}
|
428
504
|
</Tr>
|
429
505
|
);
|
430
506
|
})
|
@@ -440,6 +516,7 @@ export const PackagesTab = () => {
|
|
440
516
|
key={hostId}
|
441
517
|
hostName={hostname}
|
442
518
|
showKatelloAgent={showKatelloAgent}
|
519
|
+
triggerPackageInstall={triggerPackageInstall}
|
443
520
|
/>
|
444
521
|
}
|
445
522
|
</div>
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import { API_OPERATIONS, post } from 'foremanReact/redux/API';
|
1
|
+
import { API_OPERATIONS, get, post } from 'foremanReact/redux/API';
|
2
|
+
import { stopInterval, withInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
2
3
|
import { REX_JOB_INVOCATIONS_KEY, REX_FEATURES } from './RemoteExecutionConstants';
|
3
4
|
import { foremanApi } from '../../../../services/api';
|
4
5
|
import { errorToast, renderRexJobStartedToast } from '../../../../scenes/Tasks/helpers';
|
@@ -7,6 +8,7 @@ import { TRACES_SEARCH_QUERY } from './TracesTab/HostTracesConstants';
|
|
7
8
|
import { PACKAGE_SEARCH_QUERY } from './PackagesTab/YumInstallablePackagesConstants';
|
8
9
|
import { PACKAGES_SEARCH_QUERY } from './PackagesTab/HostPackagesConstants';
|
9
10
|
|
11
|
+
// PARAM BUILDING
|
10
12
|
const baseParams = ({ feature, hostname, inputs = {} }) => ({
|
11
13
|
job_invocation: {
|
12
14
|
feature,
|
@@ -83,12 +85,32 @@ const katelloModuleStreamActionsParams = ({ hostname, action, moduleSpec }) =>
|
|
83
85
|
|
84
86
|
const showRexToast = response => renderRexJobStartedToast(response.data);
|
85
87
|
|
86
|
-
|
88
|
+
// JOB POLLING
|
89
|
+
const pollJobKey = key => `${key}_POLL_REX_JOB`;
|
90
|
+
|
91
|
+
export const getJob = (key, jobId, handleSuccess) => get({
|
92
|
+
key,
|
93
|
+
url: foremanApi.getApiUrl(`/job_invocations/${jobId}`),
|
94
|
+
handleSuccess,
|
95
|
+
});
|
96
|
+
|
97
|
+
export const startPollingJob = ({
|
98
|
+
key, jobId, handleSuccess, interval = 1000,
|
99
|
+
}) =>
|
100
|
+
withInterval(getJob(pollJobKey(key), jobId, handleSuccess), interval);
|
101
|
+
|
102
|
+
export const stopPollingJob = ({ key }) => stopInterval(pollJobKey(key));
|
103
|
+
|
104
|
+
// JOB INVOCATIONS
|
105
|
+
export const installPackage = ({ hostname, packageName, handleSuccess }) => post({
|
87
106
|
type: API_OPERATIONS.POST,
|
88
107
|
key: REX_JOB_INVOCATIONS_KEY,
|
89
108
|
url: foremanApi.getApiUrl('/job_invocations'),
|
90
109
|
params: katelloPackageInstallParams({ hostname, packageName }),
|
91
|
-
handleSuccess:
|
110
|
+
handleSuccess: (response) => {
|
111
|
+
showRexToast(response);
|
112
|
+
if (handleSuccess) handleSuccess(response);
|
113
|
+
},
|
92
114
|
errorToast,
|
93
115
|
});
|
94
116
|
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
2
|
+
import { useDispatch } from 'react-redux';
|
3
|
+
import { propsToCamelCase } from 'foremanReact/common/helpers';
|
4
|
+
import { deleteToast } from 'foremanReact/components/ToastsList/slice';
|
5
|
+
import { startPollingJob, stopPollingJob } from './RemoteExecutionActions';
|
6
|
+
import { renderRexJobFailedToast, renderRexJobStartedToast, renderRexJobSucceededToast } from '../../../../scenes/Tasks/helpers';
|
7
|
+
|
8
|
+
export const useRexJobPolling = (initialAction, successAction = null, failureAction = null) => {
|
9
|
+
const [isPolling, setIsPolling] = useState(null);
|
10
|
+
const [succeeded, setSucceeded] = useState(null);
|
11
|
+
const [rexJobId, setRexJobId] = useState(null);
|
12
|
+
// A value that only changes when the job succeeds. Pass to TableWrapper as an additionalListener
|
13
|
+
// to reload results.
|
14
|
+
const [lastCompletedJob, setLastCompletedJob] = useState(null);
|
15
|
+
const dispatch = useDispatch();
|
16
|
+
|
17
|
+
const stopRexJobPolling = useCallback(({ jobId, statusLabel }) => {
|
18
|
+
if (statusLabel) setIsPolling(false);
|
19
|
+
if (statusLabel === 'succeeded') {
|
20
|
+
setSucceeded(true);
|
21
|
+
setLastCompletedJob(jobId);
|
22
|
+
} else {
|
23
|
+
setSucceeded(false);
|
24
|
+
}
|
25
|
+
dispatch(stopPollingJob({ key: `REX_JOB_POLLING_${jobId}` }));
|
26
|
+
dispatch(deleteToast(`REX_TOAST_${jobId}`));
|
27
|
+
}, [dispatch]);
|
28
|
+
|
29
|
+
const tick = (resp) => {
|
30
|
+
const { data } = resp;
|
31
|
+
const { statusLabel, id, description } = propsToCamelCase(data);
|
32
|
+
setRexJobId(id);
|
33
|
+
if (statusLabel && statusLabel !== 'running') {
|
34
|
+
stopRexJobPolling({ jobId: id, statusLabel });
|
35
|
+
if (statusLabel === 'succeeded') {
|
36
|
+
renderRexJobSucceededToast({ id, description });
|
37
|
+
if (successAction) dispatch(typeof successAction === 'function' ? successAction() : successAction);
|
38
|
+
} else {
|
39
|
+
renderRexJobFailedToast({ id, description });
|
40
|
+
if (failureAction) dispatch(typeof failureAction === 'function' ? failureAction() : failureAction);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
};
|
44
|
+
const startRexJobPolling = ({ jobId }) => {
|
45
|
+
dispatch(startPollingJob({ key: `REX_JOB_POLLING_${jobId}`, jobId, handleSuccess: tick }));
|
46
|
+
};
|
47
|
+
const pollingStarted = !!(isPolling || succeeded);
|
48
|
+
|
49
|
+
const dispatchInitialAction = (...args) => {
|
50
|
+
const originalAction = typeof initialAction === 'function' ? initialAction(...args) : initialAction;
|
51
|
+
const modifiedAction = {
|
52
|
+
...originalAction,
|
53
|
+
payload: {
|
54
|
+
...originalAction.payload,
|
55
|
+
handleSuccess: (resp) => {
|
56
|
+
const jobId = resp?.data?.id;
|
57
|
+
if (!jobId) return;
|
58
|
+
renderRexJobStartedToast({ key: `REX_TOAST_${jobId}`, ...resp.data });
|
59
|
+
startRexJobPolling({ jobId });
|
60
|
+
},
|
61
|
+
},
|
62
|
+
};
|
63
|
+
setIsPolling(true);
|
64
|
+
dispatch(modifiedAction);
|
65
|
+
};
|
66
|
+
|
67
|
+
// eslint-disable-next-line arrow-body-style
|
68
|
+
useEffect(() => {
|
69
|
+
// clean up polling when component unmounts
|
70
|
+
return function cleanupRexPolling() {
|
71
|
+
if (rexJobId) stopRexJobPolling({ jobId: rexJobId });
|
72
|
+
};
|
73
|
+
}, [rexJobId, stopRexJobPolling]);
|
74
|
+
return ({
|
75
|
+
pollingStarted,
|
76
|
+
isPolling,
|
77
|
+
succeeded,
|
78
|
+
rexJobId,
|
79
|
+
lastCompletedJob,
|
80
|
+
startRexJobPolling,
|
81
|
+
triggerJobStart: dispatchInitialAction,
|
82
|
+
});
|
83
|
+
};
|
84
|
+
|
85
|
+
export default useRexJobPolling;
|
@@ -59,7 +59,22 @@ import { selectRepositorySetsStatus } from './RepositorySetsSelectors';
|
|
59
59
|
import './RepositorySetsTab.scss';
|
60
60
|
import SortableColumnHeaders from '../../../../Table/components/SortableColumnHeaders';
|
61
61
|
import SelectableDropdown from '../../../../SelectableDropdown';
|
62
|
+
import { hasRequiredPermissions as can,
|
63
|
+
missingRequiredPermissions as cannot,
|
64
|
+
userPermissionsFromHostDetails } from '../../hostDetailsHelpers';
|
62
65
|
|
66
|
+
const viewRepoSets = [
|
67
|
+
'view_hosts', 'view_activation_keys', 'view_products',
|
68
|
+
];
|
69
|
+
const createBookmarks = ['create_bookmarks'];
|
70
|
+
|
71
|
+
export const hideRepoSetsTab = ({ hostDetails }) =>
|
72
|
+
cannot(
|
73
|
+
viewRepoSets,
|
74
|
+
userPermissionsFromHostDetails({ hostDetails }),
|
75
|
+
);
|
76
|
+
|
77
|
+
const editHosts = ['edit_hosts'];
|
63
78
|
const getEnabledValue = ({ enabled, enabledContentOverride }) => {
|
64
79
|
const isOverridden = (enabledContentOverride !== null);
|
65
80
|
return {
|
@@ -96,6 +111,29 @@ EnabledIcon.propTypes = {
|
|
96
111
|
isOverridden: PropTypes.bool.isRequired,
|
97
112
|
};
|
98
113
|
|
114
|
+
const OsRestrictedIcon = ({ osRestricted }) => (
|
115
|
+
<Tooltip
|
116
|
+
position="right"
|
117
|
+
content={<FormattedMessage
|
118
|
+
id="os-restricted-tooltip"
|
119
|
+
defaultMessage={__('OS restricted to {osRestricted}. If host OS does not match, the repository will not be available on this host.')}
|
120
|
+
values={{ osRestricted }}
|
121
|
+
/>}
|
122
|
+
>
|
123
|
+
<Label color="blue" className="os-restricted-label" style={{ marginLeft: '8px' }}>
|
124
|
+
{__(osRestricted)}
|
125
|
+
</Label>
|
126
|
+
</Tooltip>
|
127
|
+
);
|
128
|
+
|
129
|
+
OsRestrictedIcon.propTypes = {
|
130
|
+
osRestricted: PropTypes.string,
|
131
|
+
};
|
132
|
+
|
133
|
+
OsRestrictedIcon.defaultProps = {
|
134
|
+
osRestricted: null,
|
135
|
+
};
|
136
|
+
|
99
137
|
const RepositorySetsTab = () => {
|
100
138
|
const hostDetails = useSelector(state => selectAPIResponse(state, 'HOST_DETAILS'));
|
101
139
|
const {
|
@@ -103,6 +141,10 @@ const RepositorySetsTab = () => {
|
|
103
141
|
subscription_status: subscriptionStatus,
|
104
142
|
content_facet_attributes: contentFacetAttributes,
|
105
143
|
} = hostDetails;
|
144
|
+
const canDoContentOverrides = can(
|
145
|
+
editHosts,
|
146
|
+
userPermissionsFromHostDetails({ hostDetails }),
|
147
|
+
);
|
106
148
|
const STATUS_LABEL = __('Status');
|
107
149
|
|
108
150
|
const contentFacet = propsToCamelCase(contentFacetAttributes ?? {});
|
@@ -260,6 +302,9 @@ const RepositorySetsTab = () => {
|
|
260
302
|
singular: true,
|
261
303
|
});
|
262
304
|
|
305
|
+
const readOnlyBookmarks =
|
306
|
+
cannot(createBookmarks, userPermissionsFromHostDetails({ hostDetails }));
|
307
|
+
|
263
308
|
const dropdownItems = [
|
264
309
|
<DropdownItem aria-label="bulk_enable" key="bulk_enable" component="button" onClick={enableRepoSets} isDisabled={selectedCount === 0}>
|
265
310
|
{__('Override to enabled')}
|
@@ -307,7 +352,7 @@ const RepositorySetsTab = () => {
|
|
307
352
|
</Split>
|
308
353
|
);
|
309
354
|
|
310
|
-
const actionButtons = (
|
355
|
+
const actionButtons = canDoContentOverrides ? (
|
311
356
|
<Split hasGutter>
|
312
357
|
<SplitItem>
|
313
358
|
<ActionList isIconList>
|
@@ -322,7 +367,7 @@ const RepositorySetsTab = () => {
|
|
322
367
|
</ActionList>
|
323
368
|
</SplitItem>
|
324
369
|
</Split>
|
325
|
-
);
|
370
|
+
) : null;
|
326
371
|
|
327
372
|
const hostEnvText = 'the "{contentViewName}" content view and "{lifecycleEnvironmentName}" environment';
|
328
373
|
|
@@ -400,10 +445,11 @@ const RepositorySetsTab = () => {
|
|
400
445
|
fetchItems={fetchItems}
|
401
446
|
autocompleteEndpoint="/repository_sets/auto_complete_search"
|
402
447
|
bookmarkController="katello_product_contents" // Katello::ProductContent.table_name
|
448
|
+
readOnlyBookmarks={readOnlyBookmarks}
|
403
449
|
rowsCount={results?.length}
|
404
450
|
variant={TableVariant.compact}
|
405
451
|
{...selectAll}
|
406
|
-
displaySelectAllCheckbox
|
452
|
+
displaySelectAllCheckbox={canDoContentOverrides}
|
407
453
|
>
|
408
454
|
<Thead>
|
409
455
|
<Tr>
|
@@ -426,19 +472,22 @@ const RepositorySetsTab = () => {
|
|
426
472
|
enabled_content_override: enabledContentOverride,
|
427
473
|
contentUrl: repoPath,
|
428
474
|
product: { name: productName, id: productId },
|
475
|
+
osRestricted,
|
429
476
|
} = repoSet;
|
430
477
|
const { isEnabled, isOverridden } =
|
431
478
|
getEnabledValue({ enabled, enabledContentOverride });
|
432
479
|
return (
|
433
480
|
<Tr key={id}>
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
481
|
+
{canDoContentOverrides ? (
|
482
|
+
<Td select={{
|
483
|
+
disable: !isSelectable(id),
|
484
|
+
isSelected: isSelected(id),
|
485
|
+
onSelect: (event, selected) => selectOne(selected, id),
|
486
|
+
rowIndex,
|
487
|
+
variant: 'checkbox',
|
488
|
+
}}
|
489
|
+
/>
|
490
|
+
) : <Td> </Td>}
|
442
491
|
<Td>
|
443
492
|
<span>{repoName}</span>
|
444
493
|
</Td>
|
@@ -450,29 +499,34 @@ const RepositorySetsTab = () => {
|
|
450
499
|
</Td>
|
451
500
|
<Td>
|
452
501
|
<span><EnabledIcon key={`enabled-icon-${id}`} {...{ isEnabled, isOverridden }} /></span>
|
502
|
+
{osRestricted &&
|
503
|
+
<span><OsRestrictedIcon key={`os-restricted-icon-${id}`} {...{ osRestricted }} /></span>
|
504
|
+
}
|
453
505
|
</Td>
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
506
|
+
{canDoContentOverrides ? (
|
507
|
+
<Td
|
508
|
+
key={`rowActions-${id}`}
|
509
|
+
actions={{
|
510
|
+
items: [
|
511
|
+
{
|
512
|
+
title: __('Override to disabled'),
|
513
|
+
isDisabled: isOverridden && !isEnabled,
|
514
|
+
onClick: () => disableRepoSet(id),
|
515
|
+
},
|
516
|
+
{
|
517
|
+
title: __('Override to enabled'),
|
518
|
+
isDisabled: isOverridden && isEnabled,
|
519
|
+
onClick: () => enableRepoSet(id),
|
520
|
+
},
|
521
|
+
{
|
522
|
+
title: __('Reset to default'),
|
523
|
+
isDisabled: !isOverridden,
|
524
|
+
onClick: () => resetToDefaultRepoSet(id),
|
525
|
+
},
|
526
|
+
],
|
527
|
+
}}
|
528
|
+
/>
|
529
|
+
) : <Td />}
|
476
530
|
</Tr>
|
477
531
|
);
|
478
532
|
})
|