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.

Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/hosts/activation_key_edit.js +9 -2
  3. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +3 -0
  4. data/app/controllers/katello/api/v2/alternate_content_sources_bulk_actions_controller.rb +44 -0
  5. data/app/controllers/katello/api/v2/alternate_content_sources_controller.rb +29 -6
  6. data/app/controllers/katello/api/v2/content_view_components_controller.rb +1 -1
  7. data/app/controllers/katello/api/v2/content_view_repositories_controller.rb +1 -1
  8. data/app/controllers/katello/api/v2/repositories_controller.rb +2 -8
  9. data/app/lib/actions/katello/alternate_content_source/refresh.rb +27 -0
  10. data/app/lib/actions/katello/cdn_configuration/update.rb +1 -1
  11. data/app/lib/actions/katello/content_view/publish.rb +1 -1
  12. data/app/lib/actions/katello/organization/manifest_refresh.rb +1 -1
  13. data/app/lib/actions/pulp3/alternate_content_source/delete.rb +2 -2
  14. data/app/lib/actions/pulp3/alternate_content_source/delete_remote.rb +2 -2
  15. data/app/lib/actions/pulp3/alternate_content_source/refresh.rb +23 -0
  16. data/app/lib/actions/pulp3/alternate_content_source/update.rb +2 -2
  17. data/app/lib/actions/pulp3/alternate_content_source/update_remote.rb +2 -2
  18. data/app/lib/actions/pulp3/orchestration/alternate_content_source/create.rb +0 -2
  19. data/app/lib/actions/pulp3/orchestration/alternate_content_source/refresh.rb +15 -0
  20. data/app/lib/actions/pulp3/orchestration/alternate_content_source/update.rb +0 -2
  21. data/app/lib/actions/pulp3/repository/refresh_distribution.rb +1 -4
  22. data/app/lib/actions/pulp3/repository/save_artifact.rb +1 -1
  23. data/app/lib/actions/pulp3/repository/save_distribution_references.rb +0 -2
  24. data/app/models/katello/alternate_content_source.rb +5 -0
  25. data/app/services/katello/pulp3/alternate_content_source.rb +6 -0
  26. data/app/services/katello/pulp3/content_view_version/metadata_map.rb +1 -1
  27. data/app/services/katello/pulp3/repository.rb +29 -1
  28. data/app/views/katello/api/v2/alternate_content_sources/base.json.rabl +10 -1
  29. data/app/views/katello/api/v2/content_facet/show.json.rabl +12 -0
  30. data/app/views/katello/api/v2/repository_sets/show.json.rabl +4 -0
  31. data/config/routes/api/v2.rb +16 -4
  32. data/db/migrate/20220303160220_remove_duplicate_errata.rb +1 -1
  33. data/db/migrate/20220428203334_add_last_refreshed_to_katello_alternate_content_sources.rb +5 -0
  34. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/capsule-content/capsule-content.controller.js +1 -1
  35. data/lib/katello/permission_creator.rb +4 -2
  36. data/lib/katello/tasks/refresh_alternate_content_sources.rake +10 -0
  37. data/lib/katello/version.rb +1 -1
  38. data/webpack/components/Bookmark/index.js +22 -14
  39. data/webpack/components/Search/Search.js +4 -0
  40. data/webpack/components/Table/MainTable.scss +5 -1
  41. data/webpack/components/Table/TableWrapper.js +5 -1
  42. data/webpack/components/TypeAhead/TypeAhead.js +4 -0
  43. data/webpack/components/TypeAhead/pf4Search/TypeAheadSearch.js +2 -0
  44. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ChangeHostCVModal.js +2 -8
  45. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ContentViewDetailsCard.js +41 -11
  46. data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/HostCollectionsActions.js +2 -2
  47. data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/HostCollectionsCard.js +32 -13
  48. data/webpack/components/extensions/HostDetails/Cards/HostCollectionsCard/__tests__/hostCollectionsCard.test.js +8 -0
  49. data/webpack/components/extensions/HostDetails/DetailsTabCards/RecentCommunicationCardExtensions.js +37 -0
  50. data/webpack/components/extensions/HostDetails/HostDetailsActions.js +11 -0
  51. data/webpack/components/extensions/HostDetails/Tabs/ContentTab/SecondaryTabsRoutes.js +4 -0
  52. data/webpack/components/extensions/HostDetails/Tabs/ContentTab/constants.js +2 -0
  53. data/webpack/components/extensions/HostDetails/Tabs/ContentTab/index.js +6 -1
  54. data/webpack/components/extensions/HostDetails/Tabs/ErrataTab/ErrataTab.js +120 -51
  55. data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/ModuleStreamsTab.js +71 -37
  56. data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackageInstallModal.js +4 -3
  57. data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackagesTab.js +117 -40
  58. data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionActions.js +25 -3
  59. data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionHooks.js +85 -0
  60. data/webpack/components/extensions/HostDetails/Tabs/RepositorySetsTab/RepositorySetsTab.js +87 -33
  61. data/webpack/components/extensions/HostDetails/Tabs/TracesTab/EnableTracerModal.js +14 -7
  62. data/webpack/components/extensions/HostDetails/Tabs/TracesTab/HostTracesActions.js +2 -1
  63. data/webpack/components/extensions/HostDetails/Tabs/TracesTab/TracesEnabler.js +104 -0
  64. data/webpack/components/extensions/HostDetails/Tabs/TracesTab/TracesTab.js +92 -51
  65. data/webpack/components/extensions/HostDetails/Tabs/__tests__/errataTab.test.js +13 -23
  66. data/webpack/components/extensions/HostDetails/Tabs/{ModuleStreamsTab/__tests__/modules.fixtures.json → __tests__/moduleStreams.fixtures.json} +0 -0
  67. data/webpack/components/extensions/HostDetails/Tabs/{ModuleStreamsTab/__tests__ → __tests__}/moduleStreamsTab.test.js +13 -6
  68. data/webpack/components/extensions/HostDetails/Tabs/__tests__/packageInstallModal.test.js +21 -15
  69. data/webpack/components/extensions/HostDetails/Tabs/__tests__/packagesTab.test.js +8 -0
  70. data/webpack/components/extensions/HostDetails/Tabs/__tests__/repositorySets.fixtures.json +4 -1
  71. data/webpack/components/extensions/HostDetails/Tabs/__tests__/repositorySetsTab.test.js +26 -0
  72. data/webpack/components/extensions/HostDetails/Tabs/__tests__/tracesTab.test.js +7 -4
  73. data/webpack/components/extensions/HostDetails/hostDetailsHelpers.js +18 -0
  74. data/webpack/global_index.js +2 -2
  75. data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -1
  76. data/webpack/scenes/AlternateContentSources/ACSActions.js +13 -1
  77. data/webpack/scenes/AlternateContentSources/ACSConstants.js +14 -0
  78. data/webpack/scenes/AlternateContentSources/ACSSelectors.js +10 -1
  79. data/webpack/scenes/AlternateContentSources/Create/ACSCreateContext.js +4 -0
  80. data/webpack/scenes/AlternateContentSources/Create/ACSCreateWizard.js +160 -0
  81. data/webpack/scenes/AlternateContentSources/Create/Steps/ACSCreateFinish.js +79 -0
  82. data/webpack/scenes/AlternateContentSources/Create/Steps/ACSCredentials.js +199 -0
  83. data/webpack/scenes/AlternateContentSources/Create/Steps/ACSReview.js +104 -0
  84. data/webpack/scenes/AlternateContentSources/Create/Steps/ACSSmartProxies.js +41 -0
  85. data/webpack/scenes/AlternateContentSources/Create/Steps/AcsUrlPaths.js +71 -0
  86. data/webpack/scenes/AlternateContentSources/Create/Steps/NameACS.js +57 -0
  87. data/webpack/scenes/AlternateContentSources/Create/Steps/SelectSource.js +77 -0
  88. data/webpack/scenes/AlternateContentSources/Create/__tests__/acsCreate.test.js +149 -0
  89. data/webpack/scenes/AlternateContentSources/Create/__tests__/acsCreateData.fixtures.json +3 -0
  90. data/webpack/scenes/AlternateContentSources/Create/__tests__/contentCredentials.fixtures.json +69 -0
  91. data/webpack/scenes/AlternateContentSources/Create/__tests__/smartProxy.fixtures.json +65 -0
  92. data/webpack/scenes/AlternateContentSources/MainTable/ACSTable.js +33 -23
  93. data/webpack/scenes/ContentCredentials/ContentCredentialSelectors.js +4 -1
  94. data/webpack/scenes/ContentViews/Create/CreateContentViewForm.js +2 -2
  95. data/webpack/scenes/ContentViews/Create/__tests__/createContentView.test.js +1 -1
  96. data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentContentViewAddModal.js +1 -1
  97. data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentContentViewBulkAddModal.js +2 -2
  98. data/webpack/scenes/ContentViews/Details/ComponentContentViews/__tests__/contentViewComponents.test.js +4 -4
  99. data/webpack/scenes/ContentViews/Details/ContentViewInfo.js +1 -1
  100. data/webpack/scenes/ContentViews/__tests__/contentViewPage.test.js +4 -4
  101. data/webpack/scenes/ContentViews/components/ContentViewIcon.js +1 -1
  102. data/webpack/scenes/ContentViews/components/ContentViewsCounter.js +1 -1
  103. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths.js +1 -1
  104. data/webpack/scenes/ContentViews/expansions/DetailsExpansion.js +2 -2
  105. data/webpack/scenes/ContentViews/expansions/RelatedContentViewComponentsModal.js +2 -2
  106. data/webpack/scenes/ContentViews/expansions/__tests__/contentViewComponentsModal.test.js +1 -1
  107. data/webpack/scenes/RedHatRepositories/components/Search.js +4 -4
  108. data/webpack/scenes/SmartProxy/SmartProxyContentActions.js +9 -2
  109. data/webpack/scenes/SmartProxy/SmartProxyContentConstants.js +1 -1
  110. data/webpack/scenes/SmartProxy/SmartProxyContentSelectors.js +10 -1
  111. data/webpack/scenes/Tasks/helpers.js +30 -3
  112. metadata +34 -14
  113. data/db/seeds.d/107-enable_dynflow.rb +0 -8
  114. data/webpack/components/extensions/HostDetails/Tabs/TracesTab/EnableTracerEmptyState.js +0 -42
@@ -4,9 +4,17 @@ import { nockInstance, assertNockRequest, mockForemanAutocomplete, mockSetting }
4
4
  import katelloApi, { foremanApi } from '../../../../../services/api';
5
5
  import mockPackagesData from './yumInstallablePackages.fixtures.json';
6
6
  import PackageInstallModal from '../PackagesTab/PackageInstallModal';
7
- import { HOST_YUM_INSTALLABLE_PACKAGES_KEY, PACKAGE_SEARCH_QUERY } from '../PackagesTab/YumInstallablePackagesConstants';
7
+ import { HOST_YUM_INSTALLABLE_PACKAGES_KEY } from '../PackagesTab/YumInstallablePackagesConstants';
8
8
  import { REX_FEATURES } from '../RemoteExecutionConstants';
9
9
 
10
+ jest.mock('../../hostDetailsHelpers', () => ({
11
+ ...jest.requireActual('../../hostDetailsHelpers'),
12
+ userPermissionsFromHostDetails: () => ({
13
+ create_job_invocations: true,
14
+ edit_hosts: true,
15
+ }),
16
+ }));
17
+
10
18
  const contentFacetAttributes = {
11
19
  id: 11,
12
20
  uuid: 'e5761ea3-4117-4ecf-83d0-b694f99b389e',
@@ -32,7 +40,6 @@ const renderOptions = (facetAttributes = contentFacetAttributes) => ({
32
40
 
33
41
  const hostYumInstallablePackages = katelloApi.getApiUrl('/packages');
34
42
  const hostPackages = foremanApi.getApiUrl('/hosts/1/packages/install');
35
- const jobInvocations = foremanApi.getApiUrl('/job_invocations');
36
43
  const autocompleteUrl = '/hosts/1/packages/auto_complete_search';
37
44
  const fakeTask = { id: '21c0f9e4-b27b-49aa-8774-6be66126043b' };
38
45
 
@@ -77,6 +84,7 @@ test('Can call API for installable packages and show on screen on page load', as
77
84
  hostId={1}
78
85
  hostName="test-host"
79
86
  showKatelloAgent={false}
87
+ triggerPackageInstall={jest.fn()}
80
88
  />, renderOptions());
81
89
 
82
90
  // Assert that the packages are now showing on the screen, but wait for them to appear.
@@ -109,6 +117,7 @@ test('Can handle no installable packages being present', async (done) => {
109
117
  hostId={1}
110
118
  hostName="test-host"
111
119
  showKatelloAgent={false}
120
+ triggerPackageInstall={jest.fn()}
112
121
  />, renderOptions());
113
122
 
114
123
  // Assert that there are not any packages showing on the screen.
@@ -134,6 +143,7 @@ test('Does not show katello-agent option when disabled', async (done) => {
134
143
  hostId={1}
135
144
  hostName="test-host"
136
145
  showKatelloAgent={false}
146
+ triggerPackageInstall={jest.fn()}
137
147
  />, renderOptions());
138
148
 
139
149
  // Assert that the packages are now showing on the screen, but wait for them to appear.
@@ -172,6 +182,7 @@ test('Shows the katello-agent option when enabled', async (done) => {
172
182
  hostId={1}
173
183
  hostName="test-host"
174
184
  showKatelloAgent
185
+ triggerPackageInstall={jest.fn()}
175
186
  />, renderOptions());
176
187
 
177
188
  // Assert that the packages are now showing on the screen, but wait for them to appear.
@@ -209,6 +220,7 @@ test('Can install packages via katello-agent', async (done) => {
209
220
  hostId={1}
210
221
  hostName="test-host"
211
222
  showKatelloAgent
223
+ triggerPackageInstall={jest.fn()}
212
224
  />, renderOptions());
213
225
 
214
226
  await patientlyWaitFor(() => expect(getAllByText(firstPackages.name)[0]).toBeInTheDocument());
@@ -236,17 +248,7 @@ test('Can install a package via remote execution', async (done) => {
236
248
  .get(hostYumInstallablePackages)
237
249
  .query(defaultQuery)
238
250
  .reply(200, mockPackagesData);
239
- const installScope = nockInstance
240
- .post(jobInvocations, {
241
- job_invocation: {
242
- inputs: {
243
- [PACKAGE_SEARCH_QUERY]: `id ^ (${firstPackages.id},${secondPackages.id})`,
244
- },
245
- search_query: 'name ^ (test-host)',
246
- feature: REX_FEATURES.KATELLO_PACKAGE_INSTALL_BY_SEARCH,
247
- },
248
- })
249
- .reply(201);
251
+ const triggerPackageInstall = jest.fn();
250
252
 
251
253
  const {
252
254
  getAllByText, getByText, getByRole,
@@ -256,6 +258,7 @@ test('Can install a package via remote execution', async (done) => {
256
258
  hostId={1}
257
259
  hostName="test-host"
258
260
  showKatelloAgent
261
+ triggerPackageInstall={triggerPackageInstall}
259
262
  />, renderOptions());
260
263
 
261
264
  await patientlyWaitFor(() => expect(getAllByText(firstPackages.name)[0]).toBeInTheDocument());
@@ -271,9 +274,9 @@ test('Can install a package via remote execution', async (done) => {
271
274
  const rexOption = getByText('Install via remote execution');
272
275
  fireEvent.click(rexOption);
273
276
 
277
+ expect(triggerPackageInstall).toHaveBeenCalled();
274
278
  assertNockRequest(autocompleteScope);
275
- assertNockRequest(scope);
276
- assertNockRequest(installScope, done);
279
+ assertNockRequest(scope, done);
277
280
  });
278
281
 
279
282
  test('Can install a package via customized remote execution', async (done) => {
@@ -290,6 +293,7 @@ test('Can install a package via customized remote execution', async (done) => {
290
293
  closeModal={jest.fn()}
291
294
  hostId={1}
292
295
  hostName="test-host"
296
+ triggerPackageInstall={jest.fn()}
293
297
  />, renderOptions());
294
298
 
295
299
  await patientlyWaitFor(() => expect(getAllByText(firstPackages.name)[0]).toBeInTheDocument());
@@ -326,6 +330,7 @@ test('Uses package_install_by_search_query template when in select all mode', as
326
330
  closeModal={jest.fn()}
327
331
  hostId={1}
328
332
  hostName="test-host"
333
+ triggerPackageInstall={jest.fn()}
329
334
  />, renderOptions());
330
335
 
331
336
  await patientlyWaitFor(() => expect(getAllByText(firstPackages.name)[0]).toBeInTheDocument());
@@ -364,6 +369,7 @@ test('Disables the katello-agent option when in select all mode', async (done) =
364
369
  hostId={1}
365
370
  hostName="test-host"
366
371
  showKatelloAgent
372
+ triggerPackageInstall={jest.fn()}
367
373
  />, renderOptions());
368
374
 
369
375
  await patientlyWaitFor(() => expect(getAllByText(firstPackages.name)[0]).toBeInTheDocument());
@@ -9,6 +9,14 @@ import { REX_FEATURES } from '../RemoteExecutionConstants';
9
9
  import * as hooks from '../../../../Table/TableHooks';
10
10
  import mockBookmarkData from './bookmarks.fixtures.json';
11
11
 
12
+ jest.mock('../../hostDetailsHelpers', () => ({
13
+ ...jest.requireActual('../../hostDetailsHelpers'),
14
+ userPermissionsFromHostDetails: () => ({
15
+ create_job_invocations: true,
16
+ edit_hosts: true,
17
+ }),
18
+ }));
19
+
12
20
  const contentFacetAttributes = {
13
21
  id: 11,
14
22
  uuid: 'e5761ea3-4117-4ecf-83d0-b694f99b389e',
@@ -41,6 +41,7 @@
41
41
  "type": "yum",
42
42
  "gpgUrl": null,
43
43
  "contentUrl": "/custom/ParthaProduct/empty_repo",
44
+ "osRestricted": null,
44
45
  "override": "default",
45
46
  "overrides": [],
46
47
  "enabled_content_override": null
@@ -75,6 +76,7 @@
75
76
  "type": "yum",
76
77
  "gpgUrl": null,
77
78
  "contentUrl": "/custom/ParthaProduct/partha_multi-errata",
79
+ "osRestricted": "rhel-7",
78
80
  "override": "0",
79
81
  "overrides": [
80
82
  {
@@ -107,6 +109,7 @@
107
109
  "type": "yum",
108
110
  "gpgUrl": null,
109
111
  "contentUrl": "/custom/Pull_Provider/yggdrasil",
112
+ "osRestricted": null,
110
113
  "override": "1",
111
114
  "overrides": [
112
115
  {
@@ -117,4 +120,4 @@
117
120
  "enabled_content_override": true
118
121
  }
119
122
  ]
120
- }
123
+ }
@@ -8,6 +8,16 @@ import mockRepoSetData from './repositorySets.fixtures.json';
8
8
  import mockBookmarkData from './bookmarks.fixtures.json';
9
9
  import mockContentOverride from './contentOverrides.fixtures.json';
10
10
 
11
+ jest.mock('../../hostDetailsHelpers', () => ({
12
+ ...jest.requireActual('../../hostDetailsHelpers'),
13
+ userPermissionsFromHostDetails: () => ({
14
+ view_hosts: true,
15
+ view_activation_keys: true,
16
+ view_products: true,
17
+ edit_hosts: true,
18
+ }),
19
+ }));
20
+
11
21
  const contentFacetAttributes = {
12
22
  id: 11,
13
23
  uuid: 'e5761ea3-4117-4ecf-83d0-b694f99b389e',
@@ -378,3 +388,19 @@ test('Can filter by status', async (done) => {
378
388
  assertNockRequest(autoSearchScope);
379
389
  assertNockRequest(scope2, done); // Pass jest callback to confirm test is done
380
390
  });
391
+
392
+ test('Can display osRestricted as a label', async (done) => {
393
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
394
+ const scope = nockInstance
395
+ .get(hostRepositorySets)
396
+ .query(defaultQuery)
397
+ .reply(200, mockRepoSetData);
398
+
399
+ const { getByText } = renderWithRedux(<RepositorySetsTab />, renderOptions());
400
+
401
+ await patientlyWaitFor(() => expect(getByText(secondRepoSet.contentUrl)).toBeInTheDocument());
402
+ expect(secondRepoSet.osRestricted).not.toBeNull();
403
+ expect(getByText(secondRepoSet.osRestricted)).toBeInTheDocument();
404
+ assertNockRequest(autocompleteScope);
405
+ assertNockRequest(scope, done); // Pass jest callback to confirm test is done
406
+ });
@@ -14,6 +14,12 @@ import mockBookmarkData from './bookmarks.fixtures.json';
14
14
  const hostName = 'client.example.com';
15
15
  const tracesBookmarks = foremanApi.getApiUrl('/bookmarks?search=controller%3Dkatello_host_tracers');
16
16
 
17
+ jest.mock('../../hostDetailsHelpers', () => ({
18
+ ...jest.requireActual('../../hostDetailsHelpers'),
19
+ userPermissionsFromHostDetails: () => ({
20
+ create_job_invocations: true,
21
+ }),
22
+ }));
17
23
 
18
24
  const tracerInstalledResponse = {
19
25
  id: 1,
@@ -117,7 +123,6 @@ describe('With tracer installed', () => {
117
123
  const scope = nockInstance
118
124
  .get(hostTraces)
119
125
  .query(true)
120
- .times(2)
121
126
  .reply(200, mockTraceData);
122
127
  const resolveTracesScope = nockInstance
123
128
  .post(jobInvocations)
@@ -140,7 +145,7 @@ describe('With tracer installed', () => {
140
145
  const restartAppButton = getByText('Restart app');
141
146
  // wait 50ms so that the button is enabled
142
147
  await waitFor(() => {
143
- expect(getByText('Restart app')).not.toBeDisabled();
148
+ expect(restartAppButton.parentElement).not.toHaveClass('pf-m-disabled');
144
149
  restartAppButton.click();
145
150
  });
146
151
 
@@ -157,7 +162,6 @@ describe('With tracer installed', () => {
157
162
  const scope = nockInstance
158
163
  .get(hostTraces)
159
164
  .query(true)
160
- .times(2)
161
165
  .reply(200, mockTraceData);
162
166
  const resolveTracesScope = nockInstance
163
167
  .post(jobInvocations)
@@ -195,7 +199,6 @@ describe('With tracer installed', () => {
195
199
  const scope = nockInstance
196
200
  .get(hostTraces)
197
201
  .query(true)
198
- .times(2)
199
202
  .reply(200, mockTraceData);
200
203
  const jobInvocationBody = ({ job_invocation: { inputs } }) =>
201
204
  inputs[TRACES_SEARCH_QUERY] === `id !^ (${firstTrace.id},${thirdTrace.id})`;
@@ -25,4 +25,22 @@ export const hostIsNotRegistered = ({ hostDetails }) => {
25
25
 
26
26
  export const hostIsRegistered = ({ hostDetails }) => !hostIsNotRegistered({ hostDetails });
27
27
 
28
+ export const userPermissionsFromHostDetails = ({ hostDetails }) => {
29
+ const {
30
+ permissions: hostPermissions,
31
+ content_facet_attributes: cfAttributes = {},
32
+ } = hostDetails;
33
+ return { ...hostPermissions, ...cfAttributes?.permissions };
34
+ };
35
+
36
+ // requiredPermissions is an array
37
+ // userPermissions is an object, e.g. { view_hosts: true }
38
+ export const hasRequiredPermissions = (requiredPermissions = [], userPermissions) => {
39
+ const permittedActions = Object.keys(userPermissions).filter(key => userPermissions[key]);
40
+ return requiredPermissions.every(permission => permittedActions.includes(permission));
41
+ };
42
+
43
+ export const missingRequiredPermissions = (requiredPermissions = [], userPermissions) =>
44
+ !hasRequiredPermissions(requiredPermissions, userPermissions);
45
+
28
46
  export default defaultRemoteActionMethod;
@@ -11,7 +11,6 @@ import ErrataOverviewCard from './components/extensions/HostDetails/Cards/Errata
11
11
  import InstalledProductsCard from './components/extensions/HostDetails/DetailsTabCards/InstalledProductsCard';
12
12
  import RegistrationCard from './components/extensions/HostDetails/DetailsTabCards/RegistrationCard';
13
13
 
14
- import RepositorySetsTab from './components/extensions/HostDetails/Tabs/RepositorySetsTab/RepositorySetsTab';
15
14
  import TracesTab from './components/extensions/HostDetails/Tabs/TracesTab/TracesTab.js';
16
15
  import extendReducer from './components/extensions/reducers';
17
16
  import rootReducer from './redux/reducers';
@@ -19,6 +18,7 @@ import HostCollectionsCard from './components/extensions/HostDetails/Cards/HostC
19
18
  import { hostIsNotRegistered } from './components/extensions/HostDetails/hostDetailsHelpers';
20
19
  import SystemPropertiesCardExtensions from './components/extensions/HostDetails/DetailsTabCards/SystemPropertiesCardExtensions';
21
20
  import HostActionsBar from './components/extensions/HostDetails/ActionsBar';
21
+ import RecentCommunicationCardExtensions from './components/extensions/HostDetails/DetailsTabCards/RecentCommunicationCardExtensions';
22
22
 
23
23
  registerReducer('katelloExtends', extendReducer);
24
24
  registerReducer('katello', rootReducer);
@@ -29,7 +29,6 @@ addGlobalFill('registrationAdvanced', '[katello]RegistrationCommands', <Registra
29
29
  // Host details page tabs
30
30
  addGlobalFill('host-details-page-tabs', 'Content', <ContentTab key="content" />, 900, { title: __('Content'), hideTab: hostIsNotRegistered });
31
31
  addGlobalFill('host-details-page-tabs', 'Traces', <TracesTab key="traces" />, 800, { title: __('Traces'), hideTab: hostIsNotRegistered });
32
- addGlobalFill('host-details-page-tabs', 'Repository sets', <RepositorySetsTab key="repository-sets" />, 700, { title: __('Repository sets'), hideTab: hostIsNotRegistered });
33
32
 
34
33
  // Overview tab cards
35
34
  addGlobalFill(
@@ -45,6 +44,7 @@ addGlobalFill(
45
44
  700,
46
45
  );
47
46
  addGlobalFill('host-overview-cards', 'Installable errata', <ErrataOverviewCard key="errata-overview" />, 1900);
47
+ addGlobalFill('recent-communication-card-item', 'Recent communication', <RecentCommunicationCardExtensions key="recent-communication" />, 3000);
48
48
 
49
49
  // Details tab cards & card extensions
50
50
  addGlobalFill('host-tab-details-cards', 'Installed products', <InstalledProductsCard key="installed-products" />, 100);
@@ -23,9 +23,9 @@ const recommendedRepositoriesRHEL = [
23
23
  ];
24
24
 
25
25
  const recommendedRepositoriesSatTools = [
26
+ 'satellite-client-6-for-rhel-9-x86_64-rpms',
26
27
  'satellite-client-6-for-rhel-8-x86_64-rpms',
27
28
  'rhel-7-server-satellite-client-6-rpms',
28
- 'rhel-7-server-satellite-maintenance-6.11-rpms',
29
29
  'rhel-6-server-els-satellite-client-6-rpms',
30
30
  ];
31
31
 
@@ -35,6 +35,10 @@ const recommendedRepositoriesMisc = [
35
35
  'satellite-capsule-6.11-for-rhel-8-x86_64-rpms',
36
36
  'rhel-7-server-ansible-2.9-rpms',
37
37
  'ansible-2-for-rhel-8-x86_64-rpms',
38
+ 'rhel-7-server-satellite-maintenance-6.11-rpms',
39
+ 'rhel-7-server-satellite-utils-6.11-rpms',
40
+ 'satellite-maintenance-6.11-for-rhel-8-x86_64-rpms',
41
+ 'satellite-utils-6.11-for-rhel-8-x86_64-rpms',
38
42
  ];
39
43
 
40
44
  const recommendedRepositorySetLables = recommendedRepositoriesRHEL
@@ -1,8 +1,9 @@
1
1
  import { API_OPERATIONS, APIActions, get, post } from 'foremanReact/redux/API';
2
2
  import { translate as __ } from 'foremanReact/common/I18n';
3
3
  import api, { orgId } from '../../services/api';
4
- import ACS_KEY, { CREATE_ACS_KEY, DELETE_ACS_KEY } from './ACSConstants';
4
+ import ACS_KEY, { acsRefreshKey, CREATE_ACS_KEY, DELETE_ACS_KEY } from './ACSConstants';
5
5
  import { getResponseErrorMsgs } from '../../utils/helpers';
6
+ import { renderTaskStartedToast } from '../Tasks/helpers';
6
7
 
7
8
  const acsSuccessToast = (response) => {
8
9
  const { data: { name } } = response;
@@ -47,6 +48,17 @@ export const deleteACS = (acsId, handleSuccess) => APIActions.delete({
47
48
  errorToast: error => __(`Something went wrong while deleting this alternate content source! ${getResponseErrorMsgs(error.response)}`),
48
49
  });
49
50
 
51
+ export const refreshACS = (acsId, handleSuccess) => post({
52
+ type: API_OPERATIONS.POST,
53
+ key: acsRefreshKey(acsId),
54
+ url: api.getApiUrl(`/alternate_content_sources/${acsId}/refresh`),
55
+ params: { id: acsId },
56
+ handleSuccess: (response) => {
57
+ if (handleSuccess) handleSuccess();
58
+ return renderTaskStartedToast(response.data);
59
+ },
60
+ errorToast: error => __(`Something went wrong while refreshing this alternate content source! ${getResponseErrorMsgs(error.response)}`),
61
+ });
50
62
  export default getAlternateContentSources;
51
63
 
52
64
  // acs = Katello::AlternateContentSource.new
@@ -1,4 +1,18 @@
1
+ import { translate as __ } from 'foremanReact/common/I18n';
2
+
1
3
  const ACS_KEY = 'ACS';
2
4
  export const CREATE_ACS_KEY = 'ACS_CREATE';
3
5
  export const DELETE_ACS_KEY = 'ACS_DELETE';
6
+ export const SMART_PROXY_KEY = 'SMART_PROXY';
7
+ export const SSL_CERTS = 'SSL_CERTS';
8
+ export const acsRefreshKey = acsId => `${ACS_KEY}_REFRESH_${acsId}`;
9
+
10
+ export const YUM = __('Yum');
11
+ export const FILE = __('File');
12
+
13
+ export const ACS_TYPE_TRANSLATIONS_ENUM = {
14
+ [YUM]: 'yum',
15
+ [FILE]: 'file',
16
+ };
17
+
4
18
  export default ACS_KEY;
@@ -4,7 +4,7 @@ import {
4
4
  selectAPIResponse,
5
5
  } from 'foremanReact/redux/API/APISelectors';
6
6
  import { STATUS } from 'foremanReact/constants';
7
- import ACS_KEY from './ACSConstants';
7
+ import ACS_KEY, { CREATE_ACS_KEY } from './ACSConstants';
8
8
 
9
9
  export const selectAlternateContentSources = (state, index = '') => selectAPIResponse(state, ACS_KEY + index) || {};
10
10
 
@@ -13,3 +13,12 @@ export const selectAlternateContentSourcesStatus = (state, index = '') =>
13
13
 
14
14
  export const selectAlternateContentSourcesError = (state, index = '') =>
15
15
  selectAPIError(state, ACS_KEY + index);
16
+
17
+ export const selectCreateACS = state =>
18
+ selectAPIResponse(state, CREATE_ACS_KEY) || {};
19
+
20
+ export const selectCreateACSStatus = state =>
21
+ selectAPIStatus(state, CREATE_ACS_KEY) || STATUS.PENDING;
22
+
23
+ export const selectCreateACSError = state =>
24
+ selectAPIError(state, CREATE_ACS_KEY);
@@ -0,0 +1,4 @@
1
+ import { createContext } from 'react';
2
+
3
+ const ACSCreateContext = createContext({});
4
+ export default ACSCreateContext;
@@ -0,0 +1,160 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+ import { Wizard } from '@patternfly/react-core';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import ACSCreateContext from './ACSCreateContext';
7
+ import SelectSource from './Steps/SelectSource';
8
+ import NameACS from './Steps/NameACS';
9
+ import AcsUrlPaths from './Steps/AcsUrlPaths';
10
+ import ACSCredentials from './Steps/ACSCredentials';
11
+ import ACSSmartProxies from './Steps/ACSSmartProxies';
12
+ import ACSReview from './Steps/ACSReview';
13
+ import ACSCreateFinish from './Steps/ACSCreateFinish';
14
+ import { getContentCredentials } from '../../ContentCredentials/ContentCredentialActions';
15
+ import { getSmartProxies } from '../../SmartProxy/SmartProxyContentActions';
16
+ import { CONTENT_CREDENTIAL_CERT_TYPE } from '../../ContentCredentials/ContentCredentialConstants';
17
+
18
+ const ACSCreateWizard = ({ show, setIsOpen }) => {
19
+ const [acsType, setAcsType] = useState(null);
20
+ const [contentType, setContentType] = useState('yum');
21
+ const [name, setName] = useState('');
22
+ const [description, setDescription] = useState('');
23
+ const [smartProxies, setSmartProxies] = useState([]);
24
+ const [url, setUrl] = useState('');
25
+ const [subpaths, setSubpaths] = useState('');
26
+ const [verifySSL, setVerifySSL] = useState(false);
27
+ const [authentication, setAuthentication] = useState('');
28
+ const [sslCert, setSslCert] = useState('');
29
+ const [sslKey, setSslKey] = useState('');
30
+ const [sslCertName, setSslCertName] = useState('');
31
+ const [sslKeyName, setSslKeyName] = useState('');
32
+ const [username, setUsername] = useState('');
33
+ const [password, setPassword] = useState('');
34
+ const [caCert, setCACert] = useState('');
35
+ const [caCertName, setCACertName] = useState('');
36
+ const [currentStep, setCurrentStep] = useState(1);
37
+ const dispatch = useDispatch();
38
+
39
+ useEffect(
40
+ () => {
41
+ dispatch(getContentCredentials({ content_type: CONTENT_CREDENTIAL_CERT_TYPE }));
42
+ dispatch(getSmartProxies());
43
+ },
44
+ [dispatch],
45
+ );
46
+
47
+ const steps = [
48
+ {
49
+ id: 1,
50
+ name: __('Select source type'),
51
+ component: <SelectSource />,
52
+ enableNext: acsType && contentType,
53
+ },
54
+ {
55
+ id: 2,
56
+ name: __('Name source'),
57
+ component: <NameACS />,
58
+ enableNext: name !== '',
59
+ },
60
+ {
61
+ id: 3,
62
+ name: __('Select smart proxy'),
63
+ component: <ACSSmartProxies />,
64
+ enableNext: smartProxies.length,
65
+ },
66
+ {
67
+ id: 4,
68
+ name: __('URL and paths'),
69
+ component: <AcsUrlPaths />,
70
+ enableNext: url !== '' && subpaths !== '',
71
+ },
72
+ {
73
+ id: 5,
74
+ name: __('Credentials'),
75
+ component: <ACSCredentials />,
76
+ },
77
+ {
78
+ id: 6,
79
+ name: __('Review details'),
80
+ component: <ACSReview />,
81
+ nextButtonText: __('Add'),
82
+ },
83
+ {
84
+ id: 7,
85
+ name: __('Create ACS'),
86
+ component: <ACSCreateFinish />,
87
+ isFinishedStep: true,
88
+ },
89
+
90
+ ];
91
+
92
+ return (
93
+ <ACSCreateContext.Provider value={{
94
+ show,
95
+ setIsOpen,
96
+ currentStep,
97
+ setCurrentStep,
98
+ acsType,
99
+ setAcsType,
100
+ contentType,
101
+ setContentType,
102
+ name,
103
+ setName,
104
+ description,
105
+ setDescription,
106
+ smartProxies,
107
+ setSmartProxies,
108
+ url,
109
+ setUrl,
110
+ subpaths,
111
+ setSubpaths,
112
+ verifySSL,
113
+ setVerifySSL,
114
+ authentication,
115
+ setAuthentication,
116
+ sslCert,
117
+ setSslCert,
118
+ sslKey,
119
+ setSslKey,
120
+ sslCertName,
121
+ setSslCertName,
122
+ sslKeyName,
123
+ setSslKeyName,
124
+ username,
125
+ setUsername,
126
+ password,
127
+ setPassword,
128
+ caCert,
129
+ setCACert,
130
+ caCertName,
131
+ setCACertName,
132
+ }}
133
+ >
134
+ <Wizard
135
+ title={__('Add an alternate content source')}
136
+ steps={steps}
137
+ startAtStep={currentStep}
138
+ onGoToStep={({ id }) => setCurrentStep(id)}
139
+ onNext={({ id }) => setCurrentStep(id)}
140
+ onBack={({ id }) => setCurrentStep(id)}
141
+ onClose={() => {
142
+ setIsOpen(false);
143
+ }}
144
+ isOpen={show}
145
+ />
146
+ </ACSCreateContext.Provider>
147
+ );
148
+ };
149
+
150
+ ACSCreateWizard.propTypes = {
151
+ show: PropTypes.bool,
152
+ setIsOpen: PropTypes.func,
153
+ };
154
+
155
+ ACSCreateWizard.defaultProps = {
156
+ show: false,
157
+ setIsOpen: null,
158
+ };
159
+
160
+ export default ACSCreateWizard;
@@ -0,0 +1,79 @@
1
+ import React, { useContext, useState } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import useDeepCompareEffect from 'use-deep-compare-effect';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { STATUS } from 'foremanReact/constants';
6
+ import ACSCreateContext from '../ACSCreateContext';
7
+ import { selectCreateACS, selectCreateACSError, selectCreateACSStatus } from '../../ACSSelectors';
8
+ import getAlternateContentSources, { createACS } from '../../ACSActions';
9
+ import Loading from '../../../../components/Loading';
10
+
11
+ const ACSCreateFinish = () => {
12
+ const {
13
+ currentStep,
14
+ setIsOpen,
15
+ acsType,
16
+ contentType,
17
+ name,
18
+ description,
19
+ smartProxies,
20
+ url,
21
+ subpaths,
22
+ verifySSL,
23
+ authentication,
24
+ sslCert,
25
+ sslKey,
26
+ username,
27
+ password,
28
+ caCert,
29
+ } = useContext(ACSCreateContext);
30
+ const dispatch = useDispatch();
31
+ const response = useSelector(state => selectCreateACS(state));
32
+ const status = useSelector(state => selectCreateACSStatus(state));
33
+ const error = useSelector(state => selectCreateACSError(state));
34
+ const [createACSDispatched, setCreateACSDispatched] = useState(false);
35
+ const [saving, setSaving] = useState(true);
36
+
37
+ useDeepCompareEffect(() => {
38
+ if (currentStep === 7 && !createACSDispatched) {
39
+ setCreateACSDispatched(true);
40
+ let params = {
41
+ name,
42
+ description,
43
+ base_url: url,
44
+ subpaths: subpaths.split(','),
45
+ smart_proxy_names: smartProxies,
46
+ content_type: contentType,
47
+ alternate_content_source_type: acsType,
48
+ verify_ssl: verifySSL,
49
+ ssl_ca_cert_id: caCert,
50
+ };
51
+ if (authentication === 'content_credentials') {
52
+ params = { ssl_client_cert_id: sslCert, ssl_client_key_id: sslKey, ...params };
53
+ }
54
+ if (authentication === 'manual') {
55
+ params = { upstream_username: username, upstream_password: password, ...params };
56
+ }
57
+ dispatch(createACS(params));
58
+ }
59
+ }, [dispatch, createACSDispatched, setCreateACSDispatched,
60
+ acsType, authentication, name, description, url, subpaths,
61
+ smartProxies, contentType, verifySSL, caCert, sslCert, sslKey,
62
+ username, password, currentStep]);
63
+
64
+ useDeepCompareEffect(() => {
65
+ const { id } = response;
66
+ if (id && status === STATUS.RESOLVED && saving) {
67
+ setSaving(false);
68
+ dispatch(getAlternateContentSources());
69
+ setIsOpen(false);
70
+ } else if (status === STATUS.ERROR) {
71
+ setSaving(false);
72
+ setIsOpen(false);
73
+ }
74
+ }, [response, status, error, saving, dispatch, setIsOpen]);
75
+
76
+ return <Loading loadingText={__('Saving alternate content source...')} />;
77
+ };
78
+
79
+ export default ACSCreateFinish;