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
@@ -43,7 +43,7 @@ class RemoveDuplicateErrata < ActiveRecord::Migration[6.0]
|
|
43
43
|
::Katello::ModuleStreamErratumPackage.where(erratum_package_id: dup_err_package).each do |dup_mod_errata_package|
|
44
44
|
if ::Katello::ModuleStreamErratumPackage.find_by(module_stream_id: dup_mod_errata_package.module_stream_id, erratum_package_id: erratum_package_to_keep&.id)
|
45
45
|
dup_mod_errata_package.delete
|
46
|
-
|
46
|
+
elsif erratum_package_to_keep&.id
|
47
47
|
begin
|
48
48
|
dup_mod_errata_package.update(erratum_package_id: erratum_package_to_keep&.id)
|
49
49
|
rescue
|
@@ -109,7 +109,7 @@ angular.module('Bastion.capsule-content').controller('CapsuleContentController',
|
|
109
109
|
} else if (errorCount > 1) {
|
110
110
|
errorMessage += " " + translate("Plus 1 more error");
|
111
111
|
}
|
112
|
-
Notification.setErrorMessage(errorMessage);
|
112
|
+
Notification.setErrorMessage(translate('Last sync failed: ') + errorMessage);
|
113
113
|
}
|
114
114
|
}
|
115
115
|
$scope.syncState.set(stateFromTask(activeOrFailedTask));
|
@@ -404,13 +404,15 @@ module Katello
|
|
404
404
|
:finder_scope => :editable
|
405
405
|
@plugin.permission :edit_alternate_content_sources,
|
406
406
|
{
|
407
|
-
'katello/api/v2/alternate_content_sources' => [:update]
|
407
|
+
'katello/api/v2/alternate_content_sources' => [:update, :refresh],
|
408
|
+
'katello/api/v2/alternate_content_sources_bulk_actions' => [:refresh_alternate_content_sources]
|
408
409
|
},
|
409
410
|
:resource_type => 'Katello::AlternateContentSource',
|
410
411
|
:finder_scope => :editable
|
411
412
|
@plugin.permission :destroy_alternate_content_sources,
|
412
413
|
{
|
413
|
-
'katello/api/v2/alternate_content_sources' => [:destroy]
|
414
|
+
'katello/api/v2/alternate_content_sources' => [:destroy],
|
415
|
+
'katello/api/v2/alternate_content_sources_bulk_actions' => [:destroy_alternate_content_sources]
|
414
416
|
},
|
415
417
|
:resource_type => 'Katello::AlternateContentSource',
|
416
418
|
:finder_scope => :deletable
|
@@ -0,0 +1,10 @@
|
|
1
|
+
namespace :katello do
|
2
|
+
desc 'Refresh all alternate content sources'
|
3
|
+
task :refresh_alternate_content_sources => ["dynflow:client"] do
|
4
|
+
User.current = User.anonymous_admin
|
5
|
+
::ForemanTasks.async_task(::Actions::BulkAction,
|
6
|
+
::Actions::Katello::AlternateContentSource::Refresh,
|
7
|
+
::Katello::AlternateContentSource.all)
|
8
|
+
puts _("Alternate content source refreshing started in the background.")
|
9
|
+
end
|
10
|
+
end
|
data/lib/katello/version.rb
CHANGED
@@ -4,14 +4,14 @@ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
|
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
import { STATUS } from 'foremanReact/constants';
|
6
6
|
import { Dropdown, DropdownItem, DropdownToggle, DropdownSeparator } from '@patternfly/react-core';
|
7
|
-
import {
|
7
|
+
import { OutlinedBookmarkIcon } from '@patternfly/react-icons';
|
8
8
|
import { getBookmarks } from './BookmarkActions';
|
9
9
|
import { selectBookmarks, selectBookmarkStatus } from './BookmarkSelectors';
|
10
10
|
import './Bookmark.scss';
|
11
11
|
import AddBookmarkModal from './AddBookmarkModal';
|
12
12
|
|
13
13
|
const Bookmark = ({
|
14
|
-
selectItem, selectedItem, controller = '', isDisabled,
|
14
|
+
selectItem, selectedItem, controller = '', isDisabled, readOnlyBookmarks,
|
15
15
|
}) => {
|
16
16
|
const dispatch = useDispatch();
|
17
17
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
@@ -20,6 +20,7 @@ const Bookmark = ({
|
|
20
20
|
useSelector(state => selectBookmarks(state, controller), shallowEqual);
|
21
21
|
const status =
|
22
22
|
useSelector(state => selectBookmarkStatus(state, controller), shallowEqual);
|
23
|
+
const showActions = !readOnlyBookmarks;
|
23
24
|
|
24
25
|
useEffect(() => {
|
25
26
|
dispatch(getBookmarks(controller));
|
@@ -31,6 +32,7 @@ const Bookmark = ({
|
|
31
32
|
}
|
32
33
|
setDropdownOpen(false);
|
33
34
|
};
|
35
|
+
if (!results.length && readOnlyBookmarks) return null;
|
34
36
|
|
35
37
|
const dropDownItems = [
|
36
38
|
...results.map(({ name, id, query }) => (
|
@@ -41,16 +43,21 @@ const Bookmark = ({
|
|
41
43
|
>
|
42
44
|
{name}
|
43
45
|
</DropdownItem >)),
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
46
|
+
];
|
47
|
+
if (showActions) {
|
48
|
+
dropDownItems.push(
|
49
|
+
<DropdownSeparator key="separator" />,
|
50
|
+
<DropdownItem
|
51
|
+
onClick={() => {
|
52
|
+
setDropdownOpen(false);
|
53
|
+
setModalOpen(true);
|
54
|
+
}}
|
55
|
+
key="ADD_BOOKMARK"
|
56
|
+
>
|
57
|
+
{selectedItem ? __('Bookmark this search') : __('Add new bookmark')}
|
58
|
+
</DropdownItem >,
|
59
|
+
);
|
60
|
+
}
|
54
61
|
|
55
62
|
|
56
63
|
return (
|
@@ -62,9 +69,8 @@ const Bookmark = ({
|
|
62
69
|
isDisabled={isDisabled || status !== STATUS.RESOLVED}
|
63
70
|
onToggle={setDropdownOpen}
|
64
71
|
id="toggle-id"
|
65
|
-
toggleIndicator={CaretDownIcon}
|
66
72
|
>
|
67
|
-
<
|
73
|
+
<OutlinedBookmarkIcon />
|
68
74
|
</DropdownToggle>
|
69
75
|
}
|
70
76
|
isOpen={dropdownOpen}
|
@@ -85,10 +91,12 @@ Bookmark.propTypes = {
|
|
85
91
|
controller: PropTypes.string.isRequired,
|
86
92
|
selectItem: PropTypes.func.isRequired,
|
87
93
|
selectedItem: PropTypes.string.isRequired,
|
94
|
+
readOnlyBookmarks: PropTypes.bool,
|
88
95
|
};
|
89
96
|
|
90
97
|
Bookmark.defaultProps = {
|
91
98
|
isDisabled: undefined,
|
99
|
+
readOnlyBookmarks: false,
|
92
100
|
};
|
93
101
|
|
94
102
|
export default Bookmark;
|
@@ -22,6 +22,7 @@ const Search = ({
|
|
22
22
|
getAutoCompleteParams,
|
23
23
|
foremanApiAutoComplete,
|
24
24
|
bookmarkController,
|
25
|
+
readOnlyBookmarks,
|
25
26
|
placeholder,
|
26
27
|
isTextInput,
|
27
28
|
setTextInputValue,
|
@@ -84,6 +85,7 @@ const Search = ({
|
|
84
85
|
<TypeAhead
|
85
86
|
autoSearchDelay={autoSearchDelay}
|
86
87
|
bookmarkController={bookmarkController}
|
88
|
+
readOnlyBookmarks={readOnlyBookmarks}
|
87
89
|
isDisabled={isDisabled}
|
88
90
|
items={items}
|
89
91
|
onInputUpdate={onInputUpdate}
|
@@ -112,6 +114,7 @@ Search.propTypes = {
|
|
112
114
|
autoSearchDelay: PropTypes.number,
|
113
115
|
}),
|
114
116
|
bookmarkController: PropTypes.string,
|
117
|
+
readOnlyBookmarks: PropTypes.bool,
|
115
118
|
placeholder: PropTypes.string,
|
116
119
|
isTextInput: PropTypes.bool,
|
117
120
|
setTextInputValue: PropTypes.func,
|
@@ -127,6 +130,7 @@ Search.defaultProps = {
|
|
127
130
|
},
|
128
131
|
isDisabled: undefined,
|
129
132
|
bookmarkController: undefined,
|
133
|
+
readOnlyBookmarks: false,
|
130
134
|
placeholder: undefined,
|
131
135
|
isTextInput: false,
|
132
136
|
setTextInputValue: undefined,
|
@@ -43,6 +43,7 @@ const TableWrapper = ({
|
|
43
43
|
disableSearch,
|
44
44
|
nodesBelowSearch,
|
45
45
|
bookmarkController,
|
46
|
+
readOnlyBookmarks,
|
46
47
|
...allTableProps
|
47
48
|
}) => {
|
48
49
|
const dispatch = useDispatch();
|
@@ -191,6 +192,7 @@ const TableWrapper = ({
|
|
191
192
|
getAutoCompleteParams={getAutoCompleteParams}
|
192
193
|
foremanApiAutoComplete={foremanApiAutoComplete}
|
193
194
|
bookmarkController={bookmarkController}
|
195
|
+
readOnlyBookmarks={readOnlyBookmarks}
|
194
196
|
placeholder={searchPlaceholderText}
|
195
197
|
/>
|
196
198
|
</FlexItem>
|
@@ -272,7 +274,7 @@ TableWrapper.propTypes = {
|
|
272
274
|
actionButtons: PropTypes.node,
|
273
275
|
toggleGroup: PropTypes.node,
|
274
276
|
children: PropTypes.node,
|
275
|
-
// additionalListeners are anything that
|
277
|
+
// additionalListeners are anything that should trigger another API call, e.g. a filter
|
276
278
|
additionalListeners: PropTypes.arrayOf(PropTypes.oneOfType([
|
277
279
|
PropTypes.number,
|
278
280
|
PropTypes.string,
|
@@ -300,6 +302,7 @@ TableWrapper.propTypes = {
|
|
300
302
|
disableSearch: PropTypes.bool,
|
301
303
|
nodesBelowSearch: PropTypes.node,
|
302
304
|
bookmarkController: PropTypes.string,
|
305
|
+
readOnlyBookmarks: PropTypes.bool,
|
303
306
|
};
|
304
307
|
|
305
308
|
TableWrapper.defaultProps = {
|
@@ -326,6 +329,7 @@ TableWrapper.defaultProps = {
|
|
326
329
|
disableSearch: false,
|
327
330
|
nodesBelowSearch: null,
|
328
331
|
bookmarkController: undefined,
|
332
|
+
readOnlyBookmarks: false,
|
329
333
|
};
|
330
334
|
|
331
335
|
export default TableWrapper;
|
@@ -22,6 +22,7 @@ const TypeAhead = ({
|
|
22
22
|
autoSearchEnabled,
|
23
23
|
autoSearchDelay,
|
24
24
|
bookmarkController,
|
25
|
+
readOnlyBookmarks,
|
25
26
|
placeholder,
|
26
27
|
isTextInput,
|
27
28
|
setTextInputValue,
|
@@ -69,6 +70,7 @@ const TypeAhead = ({
|
|
69
70
|
}) => {
|
70
71
|
const typeAheadProps = {
|
71
72
|
bookmarkController,
|
73
|
+
readOnlyBookmarks,
|
72
74
|
isDisabled,
|
73
75
|
userInputValue: inputValue,
|
74
76
|
clearSearch,
|
@@ -118,6 +120,7 @@ TypeAhead.propTypes = {
|
|
118
120
|
autoSearchEnabled: PropTypes.bool.isRequired,
|
119
121
|
autoSearchDelay: PropTypes.number,
|
120
122
|
bookmarkController: PropTypes.string,
|
123
|
+
readOnlyBookmarks: PropTypes.bool,
|
121
124
|
placeholder: PropTypes.string,
|
122
125
|
isTextInput: PropTypes.bool,
|
123
126
|
setTextInputValue: PropTypes.func,
|
@@ -130,6 +133,7 @@ TypeAhead.defaultProps = {
|
|
130
133
|
isDisabled: undefined,
|
131
134
|
autoSearchDelay: 500,
|
132
135
|
bookmarkController: undefined,
|
136
|
+
readOnlyBookmarks: false,
|
133
137
|
placeholder: undefined,
|
134
138
|
isTextInput: false,
|
135
139
|
setTextInputValue: undefined,
|
@@ -13,6 +13,7 @@ const TypeAheadSearch = ({
|
|
13
13
|
userInputValue, clearSearch, getInputProps, getItemProps, isOpen, highlightedIndex,
|
14
14
|
selectedItem, selectItem, openMenu, onSearch, items, activeItems, shouldShowItems,
|
15
15
|
autoSearchEnabled, isDisabled, bookmarkController, inputValue, placeholder, isTextInput,
|
16
|
+
readOnlyBookmarks,
|
16
17
|
}) => (
|
17
18
|
<>
|
18
19
|
<InputGroup>
|
@@ -44,6 +45,7 @@ const TypeAheadSearch = ({
|
|
44
45
|
isDisabled,
|
45
46
|
selectedItem,
|
46
47
|
selectItem,
|
48
|
+
readOnlyBookmarks,
|
47
49
|
}}
|
48
50
|
controller={bookmarkController}
|
49
51
|
/>}
|
data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ChangeHostCVModal.js
CHANGED
@@ -9,7 +9,6 @@ import {
|
|
9
9
|
import { translate as __ } from 'foremanReact/common/I18n';
|
10
10
|
import { STATUS } from 'foremanReact/constants';
|
11
11
|
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
12
|
-
import { HOST_DETAILS_KEY } from 'foremanReact/components/HostDetails/consts';
|
13
12
|
import { selectAPIStatus } from 'foremanReact/redux/API/APISelectors';
|
14
13
|
import EnvironmentPaths from '../../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths';
|
15
14
|
import { ENVIRONMENT_PATHS_KEY } from '../../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPathConstants';
|
@@ -20,6 +19,7 @@ import { uniq } from '../../../../../utils/helpers';
|
|
20
19
|
import ContentViewIcon from '../../../../../scenes/ContentViews/components/ContentViewIcon';
|
21
20
|
import updateHostContentViewAndEnvironment from './HostContentViewActions';
|
22
21
|
import HOST_CV_AND_ENV_KEY from './HostContentViewConstants';
|
22
|
+
import { getHostDetails } from '../../HostDetailsActions';
|
23
23
|
|
24
24
|
const ENV_PATH_OPTIONS = { key: ENVIRONMENT_PATHS_KEY };
|
25
25
|
|
@@ -110,13 +110,7 @@ const ChangeHostCVModal = ({
|
|
110
110
|
|
111
111
|
const refreshHostDetails = () => {
|
112
112
|
handleModalClose();
|
113
|
-
return dispatch({
|
114
|
-
type: 'API_GET',
|
115
|
-
payload: {
|
116
|
-
key: HOST_DETAILS_KEY,
|
117
|
-
url: `/api/hosts/${hostName}`,
|
118
|
-
},
|
119
|
-
});
|
113
|
+
return dispatch(getHostDetails({ hostname: hostName }));
|
120
114
|
};
|
121
115
|
|
122
116
|
const handleSave = () => {
|
@@ -20,12 +20,18 @@ import { translate as __ } from 'foremanReact/common/I18n';
|
|
20
20
|
import { propsToCamelCase } from 'foremanReact/common/helpers';
|
21
21
|
import PropTypes from 'prop-types';
|
22
22
|
import ContentViewIcon from '../../../../../scenes/ContentViews/components/ContentViewIcon';
|
23
|
-
import { hostIsRegistered } from '../../hostDetailsHelpers';
|
23
|
+
import { hasRequiredPermissions, hostIsRegistered } from '../../hostDetailsHelpers';
|
24
24
|
import ChangeHostCVModal from './ChangeHostCVModal';
|
25
25
|
|
26
|
+
const requiredPermissions = [
|
27
|
+
'view_lifecycle_environments', 'view_content_views',
|
28
|
+
'promote_or_remove_content_views_to_environments',
|
29
|
+
];
|
30
|
+
|
26
31
|
const HostContentViewDetails = ({
|
27
32
|
contentView, lifecycleEnvironment, contentViewVersionId, contentViewDefault,
|
28
33
|
contentViewVersion, contentViewVersionLatest, hostId, hostName, orgId, hostEnvId,
|
34
|
+
hostPermissions, permissions,
|
29
35
|
}) => {
|
30
36
|
let versionLabel = `Version ${contentViewVersion}`;
|
31
37
|
if (contentViewVersionLatest) {
|
@@ -41,6 +47,9 @@ const HostContentViewDetails = ({
|
|
41
47
|
setIsModalOpen(true);
|
42
48
|
};
|
43
49
|
|
50
|
+
const userPermissions = { ...hostPermissions, ...permissions };
|
51
|
+
const showKebab = hasRequiredPermissions(requiredPermissions, userPermissions);
|
52
|
+
|
44
53
|
const dropdownItems = [
|
45
54
|
<DropdownItem
|
46
55
|
aria-label="change-host-content-view"
|
@@ -71,16 +80,18 @@ const HostContentViewDetails = ({
|
|
71
80
|
</FlexItem>
|
72
81
|
</Flex>
|
73
82
|
</FlexItem>
|
74
|
-
|
75
|
-
<
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
83
|
+
{showKebab && (
|
84
|
+
<FlexItem>
|
85
|
+
<Dropdown
|
86
|
+
toggle={<KebabToggle aria-label="change_content_view_hamburger" onToggle={toggleHamburger} />}
|
87
|
+
isOpen={isDropdownOpen}
|
88
|
+
isPlain
|
89
|
+
ouiaId="change-host-content-view-kebab"
|
90
|
+
position="right"
|
91
|
+
dropdownItems={dropdownItems}
|
92
|
+
/>
|
93
|
+
</FlexItem>
|
94
|
+
)}
|
84
95
|
</Flex>
|
85
96
|
</CardHeader>
|
86
97
|
<CardBody>
|
@@ -146,6 +157,7 @@ const ContentViewDetailsCard = ({ hostDetails }) => {
|
|
146
157
|
hostName={hostDetails.name}
|
147
158
|
orgId={hostDetails.organization_id}
|
148
159
|
hostEnvId={hostDetails.content_facet_attributes.lifecycle_environment_id}
|
160
|
+
hostPermissions={hostDetails.permissions}
|
149
161
|
{...propsToCamelCase(hostDetails.content_facet_attributes)}
|
150
162
|
/>);
|
151
163
|
}
|
@@ -172,6 +184,14 @@ HostContentViewDetails.propTypes = {
|
|
172
184
|
hostName: PropTypes.string,
|
173
185
|
orgId: PropTypes.number,
|
174
186
|
hostEnvId: PropTypes.number,
|
187
|
+
hostPermissions: PropTypes.shape({
|
188
|
+
edit_hosts: PropTypes.bool,
|
189
|
+
}),
|
190
|
+
permissions: PropTypes.shape({
|
191
|
+
view_content_views: PropTypes.bool,
|
192
|
+
view_lifecycle_environments: PropTypes.bool,
|
193
|
+
promote_or_remove_content_views_to_environments: PropTypes.bool,
|
194
|
+
}),
|
175
195
|
};
|
176
196
|
|
177
197
|
HostContentViewDetails.defaultProps = {
|
@@ -182,6 +202,8 @@ HostContentViewDetails.defaultProps = {
|
|
182
202
|
hostName: '',
|
183
203
|
orgId: null,
|
184
204
|
contentViewDefault: false,
|
205
|
+
hostPermissions: {},
|
206
|
+
permissions: {},
|
185
207
|
};
|
186
208
|
|
187
209
|
ContentViewDetailsCard.propTypes = {
|
@@ -191,6 +213,14 @@ ContentViewDetailsCard.propTypes = {
|
|
191
213
|
organization_id: PropTypes.number,
|
192
214
|
content_facet_attributes: PropTypes.shape({
|
193
215
|
lifecycle_environment_id: PropTypes.number,
|
216
|
+
permissions: PropTypes.shape({
|
217
|
+
view_content_views: PropTypes.bool,
|
218
|
+
view_lifecycle_environments: PropTypes.bool,
|
219
|
+
promote_or_remove_content_views_to_environments: PropTypes.bool,
|
220
|
+
}),
|
221
|
+
}),
|
222
|
+
permissions: PropTypes.shape({
|
223
|
+
edit_hosts: PropTypes.bool,
|
194
224
|
}),
|
195
225
|
}),
|
196
226
|
};
|
data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/HostCollectionsActions.js
CHANGED
@@ -24,7 +24,7 @@ export const alterHostCollections = (hostId, params, refreshHostDetails) => put(
|
|
24
24
|
url: foremanApi.getApiUrl(`/hosts/${hostId}/host_collections`),
|
25
25
|
params,
|
26
26
|
successToast: () => __('Host collections updated'),
|
27
|
-
errorToast
|
28
|
-
handleSuccess:
|
27
|
+
errorToast,
|
28
|
+
handleSuccess: refreshHostDetails,
|
29
29
|
});
|
30
30
|
|
data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/HostCollectionsCard.js
CHANGED
@@ -18,10 +18,13 @@ import { propsToCamelCase } from 'foremanReact/common/helpers';
|
|
18
18
|
import PropTypes from 'prop-types';
|
19
19
|
import { useSet } from '../../../../Table/TableHooks';
|
20
20
|
import { HostCollectionsAddModal, HostCollectionsRemoveModal } from './HostCollectionsModal';
|
21
|
-
import { hostIsRegistered } from '../../hostDetailsHelpers';
|
21
|
+
import { hasRequiredPermissions, hostIsRegistered, userPermissionsFromHostDetails } from '../../hostDetailsHelpers';
|
22
|
+
|
23
|
+
const requiredPermissions = ['edit_hosts', 'view_host_collections'];
|
22
24
|
|
23
25
|
const HostCollectionsDetails = ({
|
24
26
|
hostCollections, id: hostId, name: hostName,
|
27
|
+
showKebab,
|
25
28
|
}) => {
|
26
29
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
27
30
|
const toggleBulkAction = () => setIsDropdownOpen(prev => !prev);
|
@@ -85,15 +88,17 @@ const HostCollectionsDetails = ({
|
|
85
88
|
</FlexItem>
|
86
89
|
</Flex>
|
87
90
|
</FlexItem>
|
88
|
-
|
89
|
-
<
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
{showKebab && (
|
92
|
+
<FlexItem>
|
93
|
+
<Dropdown
|
94
|
+
toggle={<KebabToggle aria-label="host_collections_bulk_actions" onToggle={toggleBulkAction} />}
|
95
|
+
isOpen={isDropdownOpen}
|
96
|
+
isPlain
|
97
|
+
position="right"
|
98
|
+
dropdownItems={dropdownItems}
|
99
|
+
/>
|
100
|
+
</FlexItem>)
|
101
|
+
}
|
97
102
|
</Flex>
|
98
103
|
</CardHeader>
|
99
104
|
<CardBody>
|
@@ -159,7 +164,9 @@ const HostCollectionsDetails = ({
|
|
159
164
|
|
160
165
|
const HostCollectionsCard = ({ hostDetails }) => {
|
161
166
|
if (hostIsRegistered({ hostDetails })) {
|
162
|
-
|
167
|
+
const showKebab =
|
168
|
+
hasRequiredPermissions(requiredPermissions, userPermissionsFromHostDetails({ hostDetails }));
|
169
|
+
return <HostCollectionsDetails showKebab={showKebab} {...propsToCamelCase(hostDetails)} />;
|
163
170
|
}
|
164
171
|
return null;
|
165
172
|
};
|
@@ -168,20 +175,32 @@ HostCollectionsDetails.propTypes = {
|
|
168
175
|
hostCollections: PropTypes.arrayOf(PropTypes.shape({})),
|
169
176
|
id: PropTypes.number,
|
170
177
|
name: PropTypes.string,
|
178
|
+
showKebab: PropTypes.bool,
|
171
179
|
};
|
172
180
|
|
173
181
|
HostCollectionsDetails.defaultProps = {
|
174
182
|
hostCollections: [],
|
175
183
|
id: null,
|
176
184
|
name: '',
|
185
|
+
showKebab: false,
|
177
186
|
};
|
178
187
|
|
179
188
|
HostCollectionsCard.propTypes = {
|
180
|
-
hostDetails: PropTypes.shape({
|
189
|
+
hostDetails: PropTypes.shape({
|
190
|
+
permissions: PropTypes.shape({}),
|
191
|
+
contentFacetAttributes: PropTypes.shape({
|
192
|
+
permissions: PropTypes.shape({}),
|
193
|
+
}),
|
194
|
+
}),
|
181
195
|
};
|
182
196
|
|
183
197
|
HostCollectionsCard.defaultProps = {
|
184
|
-
hostDetails:
|
198
|
+
hostDetails: {
|
199
|
+
permissions: {},
|
200
|
+
contentFacetAttributes: {
|
201
|
+
permissions: {},
|
202
|
+
},
|
203
|
+
},
|
185
204
|
};
|
186
205
|
|
187
206
|
export default HostCollectionsCard;
|
@@ -41,6 +41,14 @@ const emptyHostDetails = {
|
|
41
41
|
},
|
42
42
|
};
|
43
43
|
|
44
|
+
jest.mock('../../../hostDetailsHelpers', () => ({
|
45
|
+
...jest.requireActual('../../../hostDetailsHelpers'),
|
46
|
+
userPermissionsFromHostDetails: () => ({
|
47
|
+
view_host_collections: true,
|
48
|
+
edit_hosts: true,
|
49
|
+
}),
|
50
|
+
}));
|
51
|
+
|
44
52
|
test('shows host collections and host limits when present', () => {
|
45
53
|
const { getByText } = render(<HostCollectionsCard hostDetails={hostDetails} />);
|
46
54
|
expect(getByText('Host collections')).toBeInTheDocument();
|
data/webpack/components/extensions/HostDetails/DetailsTabCards/RecentCommunicationCardExtensions.js
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
|
4
|
+
import {
|
5
|
+
DescriptionListGroup,
|
6
|
+
DescriptionListTerm,
|
7
|
+
DescriptionListDescription,
|
8
|
+
} from '@patternfly/react-core';
|
9
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
10
|
+
|
11
|
+
const RecentCommunicationCardExtensions = ({ hostDetails }) => {
|
12
|
+
const { subscription_facet_attributes: subscriptionFacetAttributes } = hostDetails;
|
13
|
+
if (!Object.keys(subscriptionFacetAttributes ?? {}).includes('last_checkin')) return null;
|
14
|
+
const lastCheckin = subscriptionFacetAttributes?.last_checkin;
|
15
|
+
return (
|
16
|
+
<DescriptionListGroup>
|
17
|
+
<DescriptionListTerm>{__('Last check-in:')}</DescriptionListTerm>
|
18
|
+
<DescriptionListDescription>
|
19
|
+
<RelativeDateTime date={lastCheckin} defaultValue={__('Never')} />
|
20
|
+
</DescriptionListDescription>
|
21
|
+
</DescriptionListGroup>
|
22
|
+
);
|
23
|
+
};
|
24
|
+
|
25
|
+
RecentCommunicationCardExtensions.propTypes = {
|
26
|
+
hostDetails: PropTypes.shape({
|
27
|
+
subscription_facet_attributes: PropTypes.shape({
|
28
|
+
last_checkin: PropTypes.string,
|
29
|
+
}),
|
30
|
+
}),
|
31
|
+
};
|
32
|
+
|
33
|
+
RecentCommunicationCardExtensions.defaultProps = {
|
34
|
+
hostDetails: {},
|
35
|
+
};
|
36
|
+
|
37
|
+
export default RecentCommunicationCardExtensions;
|
@@ -1,2 +1,13 @@
|
|
1
|
+
import HOST_DETAILS_KEY from './HostDetailsConstants';
|
2
|
+
|
1
3
|
const hostIdNotReady = { type: 'NOOP_HOST_ID_NOT_READY' };
|
4
|
+
|
5
|
+
export const getHostDetails = ({ hostname }) => ({
|
6
|
+
type: 'API_GET',
|
7
|
+
payload: {
|
8
|
+
key: HOST_DETAILS_KEY,
|
9
|
+
url: `/api/hosts/${hostname}`,
|
10
|
+
},
|
11
|
+
});
|
12
|
+
|
2
13
|
export default hostIdNotReady;
|
@@ -3,6 +3,7 @@ import { Route, Switch, Redirect } from 'react-router-dom';
|
|
3
3
|
import { PackagesTab } from '../PackagesTab/PackagesTab.js';
|
4
4
|
import { ErrataTab } from '../ErrataTab/ErrataTab.js';
|
5
5
|
import { ModuleStreamsTab } from '../ModuleStreamsTab/ModuleStreamsTab';
|
6
|
+
import RepositorySetsTab from '../RepositorySetsTab/RepositorySetsTab';
|
6
7
|
import { route } from './helpers';
|
7
8
|
|
8
9
|
const SecondaryTabRoutes = () => (
|
@@ -16,6 +17,9 @@ const SecondaryTabRoutes = () => (
|
|
16
17
|
<Route path={route('module-streams')}>
|
17
18
|
<ModuleStreamsTab />
|
18
19
|
</Route>
|
20
|
+
<Route path={route('Repository sets')}>
|
21
|
+
<RepositorySetsTab />
|
22
|
+
</Route>
|
19
23
|
<Redirect to={route('errata')} />
|
20
24
|
</Switch>
|
21
25
|
);
|
@@ -1,9 +1,11 @@
|
|
1
1
|
import { translate as __ } from 'foremanReact/common/I18n';
|
2
|
+
import { hideRepoSetsTab } from '../RepositorySetsTab/RepositorySetsTab';
|
2
3
|
|
3
4
|
const SECONDARY_TABS = [
|
4
5
|
{ key: 'packages', title: __('Packages') },
|
5
6
|
{ key: 'errata', title: __('Errata') },
|
6
7
|
{ key: 'module-streams', title: __('Module streams') },
|
8
|
+
{ key: 'Repository sets', hideTab: hideRepoSetsTab, title: __('Repository sets') },
|
7
9
|
];
|
8
10
|
|
9
11
|
export default SECONDARY_TABS;
|
@@ -2,12 +2,17 @@ import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import { useHistory } from 'react-router-dom';
|
4
4
|
import { Tabs, Tab, TabTitleText } from '@patternfly/react-core';
|
5
|
+
import { useSelector } from 'react-redux';
|
6
|
+
import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors';
|
5
7
|
import SecondaryTabRoutes from './SecondaryTabsRoutes';
|
6
8
|
import { activeTab } from './helpers';
|
7
9
|
import SECONDARY_TABS from './constants';
|
8
10
|
|
9
11
|
const ContentTab = ({ location: { pathname } }) => {
|
10
12
|
const hashHistory = useHistory();
|
13
|
+
const hostDetails = useSelector(state => selectAPIResponse(state, 'HOST_DETAILS'));
|
14
|
+
const filteredTabs =
|
15
|
+
SECONDARY_TABS?.filter(tab => !tab.hideTab?.({ hostDetails })) ?? [];
|
11
16
|
return (
|
12
17
|
<>
|
13
18
|
<Tabs
|
@@ -16,7 +21,7 @@ const ContentTab = ({ location: { pathname } }) => {
|
|
16
21
|
isSecondary
|
17
22
|
activeKey={activeTab(pathname)}
|
18
23
|
>
|
19
|
-
{
|
24
|
+
{filteredTabs.map(({ key, title }) => (
|
20
25
|
<Tab
|
21
26
|
key={key}
|
22
27
|
eventKey={key}
|