katello 4.12.1 → 4.13.0

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 (245) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/locale/bn/katello.js +3365 -3350
  3. data/app/assets/javascripts/katello/locale/bn_IN/katello.js +3136 -3121
  4. data/app/assets/javascripts/katello/locale/ca/katello.js +3588 -3576
  5. data/app/assets/javascripts/katello/locale/cs/katello.js +3499 -3487
  6. data/app/assets/javascripts/katello/locale/cs_CZ/katello.js +4186 -4186
  7. data/app/assets/javascripts/katello/locale/de/katello.js +5553 -5562
  8. data/app/assets/javascripts/katello/locale/de_AT/katello.js +3008 -2993
  9. data/app/assets/javascripts/katello/locale/de_DE/katello.js +3066 -3051
  10. data/app/assets/javascripts/katello/locale/el/katello.js +3376 -3370
  11. data/app/assets/javascripts/katello/locale/en/katello.js +3008 -2993
  12. data/app/assets/javascripts/katello/locale/en_GB/katello.js +3076 -3073
  13. data/app/assets/javascripts/katello/locale/en_US/katello.js +3008 -2993
  14. data/app/assets/javascripts/katello/locale/es/katello.js +5366 -5372
  15. data/app/assets/javascripts/katello/locale/et_EE/katello.js +3008 -2993
  16. data/app/assets/javascripts/katello/locale/fr/katello.js +5975 -5984
  17. data/app/assets/javascripts/katello/locale/gl/katello.js +3125 -3113
  18. data/app/assets/javascripts/katello/locale/gu/katello.js +3119 -3104
  19. data/app/assets/javascripts/katello/locale/he_IL/katello.js +3020 -3005
  20. data/app/assets/javascripts/katello/locale/hi/katello.js +3137 -3122
  21. data/app/assets/javascripts/katello/locale/id/katello.js +3008 -2993
  22. data/app/assets/javascripts/katello/locale/it/katello.js +4469 -4466
  23. data/app/assets/javascripts/katello/locale/ja/katello.js +5969 -5978
  24. data/app/assets/javascripts/katello/locale/ka/katello.js +5649 -5652
  25. data/app/assets/javascripts/katello/locale/kn/katello.js +3136 -3121
  26. data/app/assets/javascripts/katello/locale/ko/katello.js +4717 -4720
  27. data/app/assets/javascripts/katello/locale/locale/katello.js +1050 -1084
  28. data/app/assets/javascripts/katello/locale/ml_IN/katello.js +3008 -2993
  29. data/app/assets/javascripts/katello/locale/mr/katello.js +3136 -3121
  30. data/app/assets/javascripts/katello/locale/nl_NL/katello.js +3116 -3101
  31. data/app/assets/javascripts/katello/locale/or/katello.js +3137 -3122
  32. data/app/assets/javascripts/katello/locale/pa/katello.js +3136 -3121
  33. data/app/assets/javascripts/katello/locale/pl/katello.js +3210 -3195
  34. data/app/assets/javascripts/katello/locale/pl_PL/katello.js +3008 -2993
  35. data/app/assets/javascripts/katello/locale/pt/katello.js +3009 -2994
  36. data/app/assets/javascripts/katello/locale/pt_BR/katello.js +5362 -5368
  37. data/app/assets/javascripts/katello/locale/ro/katello.js +3008 -2993
  38. data/app/assets/javascripts/katello/locale/ro_RO/katello.js +3008 -2993
  39. data/app/assets/javascripts/katello/locale/ru/katello.js +4638 -4641
  40. data/app/assets/javascripts/katello/locale/sl/katello.js +3051 -3036
  41. data/app/assets/javascripts/katello/locale/sv_SE/katello.js +3156 -3144
  42. data/app/assets/javascripts/katello/locale/ta/katello.js +3365 -3350
  43. data/app/assets/javascripts/katello/locale/ta_IN/katello.js +3121 -3106
  44. data/app/assets/javascripts/katello/locale/te/katello.js +3136 -3121
  45. data/app/assets/javascripts/katello/locale/tr/katello.js +3025 -3010
  46. data/app/assets/javascripts/katello/locale/vi/katello.js +3008 -2993
  47. data/app/assets/javascripts/katello/locale/vi_VN/katello.js +3008 -2993
  48. data/app/assets/javascripts/katello/locale/zh/katello.js +3008 -2993
  49. data/app/assets/javascripts/katello/locale/zh_CN/katello.js +5968 -5977
  50. data/app/assets/javascripts/katello/locale/zh_TW/katello.js +4694 -4697
  51. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +370 -132
  52. data/app/controllers/katello/api/rhsm/candlepin_dynflow_proxy_controller.rb +12 -20
  53. data/app/controllers/katello/api/v2/activation_keys_controller.rb +10 -4
  54. data/app/controllers/katello/api/v2/capsule_content_controller.rb +24 -0
  55. data/app/controllers/katello/api/v2/content_view_versions_controller.rb +9 -2
  56. data/app/controllers/katello/api/v2/debs_controller.rb +1 -1
  57. data/app/controllers/katello/api/v2/errata_controller.rb +1 -1
  58. data/app/controllers/katello/api/v2/host_subscriptions_controller.rb +12 -4
  59. data/app/controllers/katello/api/v2/hosts_bulk_actions_controller.rb +3 -3
  60. data/app/controllers/katello/api/v2/organizations_controller.rb +0 -11
  61. data/app/controllers/katello/api/v2/packages_controller.rb +1 -1
  62. data/app/controllers/katello/api/v2/repositories_controller.rb +19 -13
  63. data/app/controllers/katello/api/v2/repository_sets_controller.rb +2 -1
  64. data/app/controllers/katello/api/v2/simple_content_access_controller.rb +9 -22
  65. data/app/controllers/katello/concerns/api/v2/authorization.rb +1 -1
  66. data/app/helpers/katello/subscription_mailer_helper.rb +1 -1
  67. data/app/jobs/create_manifest_expire_soon_warning_notifications.rb +11 -0
  68. data/app/lib/actions/candlepin/owner/regenerate_upstream_identity_cert.rb +21 -0
  69. data/app/lib/actions/katello/capsule_content/sync.rb +1 -1
  70. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +7 -2
  71. data/app/lib/actions/katello/capsule_content/verify_checksum.rb +75 -0
  72. data/app/lib/actions/katello/content_view/promote.rb +1 -1
  73. data/app/lib/actions/katello/content_view/publish.rb +1 -1
  74. data/app/lib/actions/katello/content_view_version/verify_checksum.rb +29 -0
  75. data/app/lib/actions/katello/host/hypervisors_update.rb +1 -0
  76. data/app/lib/actions/katello/host/update_content_view.rb +2 -2
  77. data/app/lib/actions/katello/organization/manifest_delete.rb +6 -1
  78. data/app/lib/actions/katello/organization/manifest_import.rb +5 -0
  79. data/app/lib/actions/katello/organization/manifest_refresh.rb +3 -0
  80. data/app/lib/actions/katello/repository/create.rb +17 -11
  81. data/app/lib/actions/katello/repository/create_root.rb +4 -2
  82. data/app/lib/actions/katello/repository/metadata_generate.rb +7 -1
  83. data/app/lib/actions/katello/repository/remove_content.rb +1 -0
  84. data/app/lib/actions/katello/repository/sync.rb +2 -1
  85. data/app/lib/actions/katello/repository/upload_files.rb +1 -0
  86. data/app/lib/actions/katello/upstream_subscriptions/bind_entitlement.rb +1 -1
  87. data/app/lib/actions/pulp3/capsule_content/verify_checksum.rb +27 -0
  88. data/app/lib/actions/pulp3/orchestration/content_view_version/export_repository.rb +7 -9
  89. data/app/lib/actions/pulp3/orchestration/content_view_version/syncable_export.rb +5 -4
  90. data/app/lib/actions/pulp3/orchestration/orphan_cleanup/remove_orphans.rb +1 -0
  91. data/app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb +15 -0
  92. data/app/lib/katello/concerns/base_template_scope_extensions.rb +7 -2
  93. data/app/lib/katello/http_resource.rb +6 -1
  94. data/app/lib/katello/resources/candlepin/consumer.rb +1 -1
  95. data/app/lib/katello/resources/candlepin/upstream_consumer.rb +18 -6
  96. data/app/lib/katello/resources/candlepin/upstream_job.rb +1 -1
  97. data/app/lib/katello/resources/registry.rb +25 -0
  98. data/app/mailers/katello/subscription_mailer.rb +3 -6
  99. data/app/models/katello/concerns/organization_extensions.rb +42 -3
  100. data/app/models/katello/content_view.rb +30 -0
  101. data/app/models/katello/content_view_environment_content_facet.rb +4 -2
  102. data/app/models/katello/glue/provider.rb +19 -12
  103. data/app/models/katello/glue/pulp/repos.rb +11 -3
  104. data/app/models/katello/host/content_facet.rb +1 -1
  105. data/app/models/katello/host/subscription_facet.rb +1 -1
  106. data/app/models/katello/ping.rb +1 -1
  107. data/app/models/katello/repository.rb +32 -1
  108. data/app/models/katello/root_repository.rb +4 -6
  109. data/app/models/katello/trace_status.rb +1 -1
  110. data/app/services/katello/content_unit_indexer.rb +9 -0
  111. data/app/services/katello/pulp3/alternate_content_source.rb +4 -6
  112. data/app/services/katello/pulp3/api/core.rb +21 -0
  113. data/app/services/katello/pulp3/api/docker.rb +4 -0
  114. data/app/services/katello/pulp3/api/yum.rb +11 -0
  115. data/app/services/katello/pulp3/docker_manifest.rb +5 -1
  116. data/app/services/katello/pulp3/repository/generic.rb +1 -1
  117. data/app/services/katello/pulp3/repository/yum.rb +1 -6
  118. data/app/services/katello/pulp3/repository.rb +26 -6
  119. data/app/services/katello/pulp3/repository_mirror.rb +13 -12
  120. data/app/services/katello/pulp3/service_common.rb +2 -10
  121. data/app/services/katello/pulp3/smart_proxy_repository.rb +0 -2
  122. data/app/services/katello/ui_notifications/subscriptions/manifest_expire_soon_warning.rb +75 -0
  123. data/app/views/foreman/job_templates/update_package_-_katello_ansible_default.erb +5 -1
  124. data/app/views/foreman/job_templates/update_packages_by_search_query_-_katello_ansible_default.erb +2 -2
  125. data/app/views/foreman/job_templates/upload_profile.erb +16 -0
  126. data/app/views/foreman/smart_proxies/_content_tab.html.erb +3 -1
  127. data/app/views/katello/api/v2/content_view_filter_rules/show.json.rabl +9 -0
  128. data/app/views/katello/api/v2/docker_manifests/show.json.rabl +1 -0
  129. data/app/views/katello/api/v2/organizations/show.json.rabl +9 -1
  130. data/app/views/overrides/activation_keys/_host_environment_select.html.erb +1 -1
  131. data/app/views/overrides/activation_keys/_host_media_type_select.html.erb +15 -5
  132. data/config/routes/api/registry.rb +4 -8
  133. data/config/routes/api/v2.rb +2 -0
  134. data/db/migrate/20240423112842_add_fields_to_katello_docker_manifest.rb +8 -0
  135. data/db/migrate/20240502192021_change_katello_repository_rpms_id_seq_to_big_int.rb +9 -0
  136. data/db/migrate/20240520142245_add_container_push_props_to_repo.rb +7 -0
  137. data/db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb +10 -0
  138. data/db/seeds.d/109-katello-notification-blueprints.rb +6 -0
  139. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/activation-keys/details/activation-key-repository-sets.controller.js +3 -3
  140. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-credentials/new/views/new-content-credential.html +2 -1
  141. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/details/content-host-repository-sets.controller.js +3 -3
  142. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/bastion_katello.pot +0 -15
  143. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/checksum.service.js +6 -1
  144. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +8 -6
  145. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +12 -13
  146. data/lib/katello/permission_creator.rb +3 -3
  147. data/lib/katello/permissions/registry_permissions.rb +4 -7
  148. data/lib/katello/plugin.rb +21 -8
  149. data/lib/katello/repository_types/ostree.rb +7 -0
  150. data/lib/katello/scheduled_jobs.rb +7 -1
  151. data/lib/katello/tasks/clean_backend_objects.rake +1 -1
  152. data/lib/katello/tasks/repository.rake +22 -0
  153. data/lib/katello/version.rb +1 -1
  154. data/locale/action_names.rb +4 -3
  155. data/locale/bn/katello.po +166 -151
  156. data/locale/bn_IN/katello.po +166 -151
  157. data/locale/ca/katello.po +166 -151
  158. data/locale/cs/katello.po +166 -151
  159. data/locale/cs_CZ/LC_MESSAGES/katello.mo +0 -0
  160. data/locale/cs_CZ/katello.po +172 -157
  161. data/locale/de/LC_MESSAGES/katello.mo +0 -0
  162. data/locale/de/katello.po +178 -163
  163. data/locale/de_AT/katello.po +166 -151
  164. data/locale/de_DE/katello.po +166 -151
  165. data/locale/el/katello.po +166 -151
  166. data/locale/en/katello.po +166 -151
  167. data/locale/en_GB/katello.po +166 -151
  168. data/locale/en_US/katello.po +166 -151
  169. data/locale/es/LC_MESSAGES/katello.mo +0 -0
  170. data/locale/es/katello.po +178 -163
  171. data/locale/et_EE/katello.po +166 -151
  172. data/locale/fr/LC_MESSAGES/katello.mo +0 -0
  173. data/locale/fr/katello.po +179 -164
  174. data/locale/gl/katello.po +166 -151
  175. data/locale/gu/katello.po +166 -151
  176. data/locale/he_IL/katello.po +166 -151
  177. data/locale/hi/katello.po +166 -151
  178. data/locale/id/katello.po +166 -151
  179. data/locale/it/LC_MESSAGES/katello.mo +0 -0
  180. data/locale/it/katello.po +169 -154
  181. data/locale/ja/LC_MESSAGES/katello.mo +0 -0
  182. data/locale/ja/katello.po +179 -164
  183. data/locale/ka/LC_MESSAGES/katello.mo +0 -0
  184. data/locale/ka/katello.po +177 -162
  185. data/locale/katello.pot +1119 -1062
  186. data/locale/kn/katello.po +166 -151
  187. data/locale/ko/LC_MESSAGES/katello.mo +0 -0
  188. data/locale/ko/katello.po +174 -159
  189. data/locale/ml_IN/katello.po +166 -151
  190. data/locale/mr/katello.po +166 -151
  191. data/locale/nl_NL/katello.po +166 -151
  192. data/locale/or/katello.po +166 -151
  193. data/locale/pa/katello.po +166 -151
  194. data/locale/pl/katello.po +166 -151
  195. data/locale/pl_PL/katello.po +166 -151
  196. data/locale/pt/katello.po +166 -151
  197. data/locale/pt_BR/LC_MESSAGES/katello.mo +0 -0
  198. data/locale/pt_BR/katello.po +178 -163
  199. data/locale/ro/katello.po +166 -151
  200. data/locale/ro_RO/katello.po +166 -151
  201. data/locale/ru/LC_MESSAGES/katello.mo +0 -0
  202. data/locale/ru/katello.po +171 -156
  203. data/locale/sl/katello.po +166 -151
  204. data/locale/sv_SE/katello.po +166 -151
  205. data/locale/ta/katello.po +166 -151
  206. data/locale/ta_IN/katello.po +166 -151
  207. data/locale/te/katello.po +166 -151
  208. data/locale/tr/katello.po +166 -151
  209. data/locale/vi/katello.po +166 -151
  210. data/locale/vi_VN/katello.po +166 -151
  211. data/locale/zh/katello.po +166 -151
  212. data/locale/zh_CN/LC_MESSAGES/katello.mo +0 -0
  213. data/locale/zh_CN/katello.po +179 -164
  214. data/locale/zh_TW/LC_MESSAGES/katello.mo +0 -0
  215. data/locale/zh_TW/katello.po +171 -156
  216. data/package.json +0 -1
  217. data/webpack/ForemanColumnExtensions/index.js +129 -0
  218. data/webpack/components/Content/ContentTable.js +0 -1
  219. data/webpack/components/Content/__tests__/__snapshots__/ContentTable.test.js.snap +0 -1
  220. data/webpack/components/Table/TableWrapper.js +14 -0
  221. data/webpack/components/extensions/HostDetails/ActionsBar/index.js +1 -1
  222. data/webpack/components/extensions/Hosts/ActionsBar/index.js +20 -1
  223. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/BulkChangeHostCVModal.js +220 -0
  224. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/actions.js +23 -0
  225. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/index.js +25 -0
  226. data/webpack/components/extensions/Hosts/BulkActions/__tests__/bulkChangeHostCVModal.test.js +133 -0
  227. data/webpack/global_index.js +9 -0
  228. data/webpack/scenes/Hosts/ChangeContentSource/actions.js +3 -1
  229. data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +62 -24
  230. data/webpack/scenes/Hosts/ChangeContentSource/index.js +24 -16
  231. data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +33 -39
  232. data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +4 -2
  233. data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamsTable.test.js.snap +0 -1
  234. data/webpack/scenes/RedHatRepositories/__tests__/__snapshots__/RedHatRepositoriesPage.test.js.snap +1 -0
  235. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +66 -5
  236. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +16 -13
  237. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +14 -8
  238. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +1 -0
  239. data/webpack/scenes/Subscriptions/components/SubscriptionsToolbar/SubscriptionsToolbar.js +1 -1
  240. metadata +60 -42
  241. data/app/lib/actions/katello/host/upload_package_profile.rb +0 -45
  242. data/app/lib/actions/katello/host/upload_profiles.rb +0 -47
  243. data/webpack/utils/__tests__/useParamsWithHash.test.js +0 -22
  244. data/webpack/utils/paramsFromHash.js +0 -16
  245. data/webpack/utils/useUrlParams.js +0 -14
@@ -2,8 +2,10 @@ import React from 'react';
2
2
  import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
3
  import { registerReducer } from 'foremanReact/common/MountingService';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { registerColumns } from 'foremanReact/components/HostsIndex/Columns/core';
5
6
  import componentRegistry from 'foremanReact/components/componentRegistry';
6
7
 
8
+ import hostsIndexColumnExtensions from './ForemanColumnExtensions/index';
7
9
  import SystemStatuses from './components/extensions/about';
8
10
  import {
9
11
  RegistrationCommands,
@@ -30,6 +32,7 @@ import HostDetailsActionsBar from './components/extensions/HostDetails/ActionsBa
30
32
  import HostsIndexActionsBar from './components/extensions/Hosts/ActionsBar';
31
33
  import RecentCommunicationCardExtensions from './components/extensions/HostDetails/DetailsTabCards/RecentCommunicationCardExtensions';
32
34
  import SystemPurposeCard from './components/extensions/HostDetails/Cards/SystemPurposeCard/SystemPurposeCard';
35
+ import BulkChangeHostCVModal from './components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/index.js';
33
36
 
34
37
 
35
38
  import ActivationKeysSearch from './components/ActivationKeysSearch';
@@ -85,6 +88,12 @@ addGlobalFill(
85
88
 
86
89
  addGlobalFill('host-tab-details-cards', 'HW properties', <HwPropertiesCard key="hw-properties" />, 200);
87
90
 
91
+ // Hosts Index page extensions
92
+ addGlobalFill('_all-hosts-modals', 'BulkChangeHostCVModal', <BulkChangeHostCVModal key="bulk-change-host-cv-modal" />, 100);
93
+
94
+ registerColumns(hostsIndexColumnExtensions);
95
+
96
+
88
97
  componentRegistry.register({
89
98
  name: 'ActivationKeysSearch',
90
99
  type: ActivationKeysSearch,
@@ -18,7 +18,7 @@ export const getFormData = (hostIds, search) => (post({
18
18
  }));
19
19
 
20
20
  export const changeContentSource =
21
- (environmentId, contentViewId, contentSourceId, hostIds, handleSuccess) =>
21
+ (environmentId, contentViewId, contentSourceId, hostIds, handleSuccess, successToast) =>
22
22
  put({
23
23
  key: CHANGE_CONTENT_SOURCE,
24
24
  url: foremanUrl('/api/v2/hosts/bulk/change_content_source'),
@@ -29,6 +29,7 @@ export const changeContentSource =
29
29
  host_ids: hostIds,
30
30
  },
31
31
  errorToast: () => __('Something went wrong while updating the content source. See the logs for more information'),
32
+ successToast,
32
33
  handleSuccess,
33
34
  });
34
35
 
@@ -46,6 +47,7 @@ export const getContentViews = environmentId =>
46
47
  params: {
47
48
  environment_id: environmentId,
48
49
  full_result: true,
50
+ order: 'default DESC', // shows the default CV before all other options
49
51
  },
50
52
  errorToast: () => __('Something went wrong while loading the content views. See the logs for more information'),
51
53
  });
@@ -23,7 +23,7 @@ import { ENVIRONMENT_PATHS_KEY } from '../../../../scenes/ContentViews/component
23
23
  import EnvironmentPaths from '../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths';
24
24
  import ContentViewSelect from '../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelect';
25
25
  import ContentViewSelectOption from '../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelectOption';
26
- import { selectContentViewsStatus } from '../selectors';
26
+ import { selectContentViewsStatus, selectJobInvocationPath } from '../selectors';
27
27
  import { getCVPlaceholderText, shouldDisableCVSelect } from '../../../ContentViews/components/ContentViewSelect/helpers';
28
28
  import { selectEnvironmentPaths } from '../../../ContentViews/components/EnvironmentPaths/EnvironmentPathSelectors';
29
29
 
@@ -95,6 +95,9 @@ const ContentSourceForm = ({
95
95
  contentSources,
96
96
  handleContentSource,
97
97
  contentSourceId,
98
+ showCVOnlyAlert,
99
+ hostDetailsPath,
100
+ hostEditPath,
98
101
  contentHosts,
99
102
  isLoading,
100
103
  hostsUpdated,
@@ -108,6 +111,7 @@ const ContentSourceForm = ({
108
111
  );
109
112
  const contentViewsStatus = useSelector(selectContentViewsStatus);
110
113
  const environmentPathResponse = useSelector(selectEnvironmentPaths);
114
+ const jobInvocationPath = useSelector(selectJobInvocationPath);
111
115
  const envList = environmentPathResponse?.results?.map(path => path.environments).flat();
112
116
  const [csSelectOpen, setCSSelectOpen] = useState(false);
113
117
  const [cvSelectOpen, setCVSelectOpen] = useState(false);
@@ -127,13 +131,12 @@ const ContentSourceForm = ({
127
131
  !!contentViewName &&
128
132
  !!contentSourceId &&
129
133
  hostCount !== 0);
130
-
131
134
  const contentSourcesIsDisabled = (isLoading || contentSources.length === 0 ||
132
135
  hostCount === 0);
133
- const environmentIsDisabled = (isLoading || environments === [] ||
136
+ const environmentIsDisabled = (isLoading ||
134
137
  contentSourceId === '');
135
138
  const viewIsDisabled = (isLoading || contentViews.length === 0 ||
136
- contentSourceId === '' || environments === []);
139
+ contentSourceId === '');
137
140
 
138
141
  const cvPlaceholderText = getCVPlaceholderText({
139
142
  contentSourceId,
@@ -229,6 +232,15 @@ const ContentSourceForm = ({
229
232
  env={environments[0]}
230
233
  />))}
231
234
  </ContentViewSelect>
235
+ {showCVOnlyAlert &&
236
+ <Alert
237
+ ouiaId="cv-only-alert"
238
+ variant="info"
239
+ className="margin-top-20"
240
+ title={__('Host content source will remain the same. Click Save below to update the host\'s content view environment.')}
241
+ />
242
+ }
243
+ {!showCVOnlyAlert &&
232
244
  <TextContent>
233
245
  <Text
234
246
  ouiaId="ccs-options-description"
@@ -252,27 +264,49 @@ const ContentSourceForm = ({
252
264
  />
253
265
  </Text>
254
266
  </TextContent>
267
+ }
255
268
  <ActionGroup style={{ display: 'block' }}>
256
- <Button
257
- variant="primary"
258
- id="generate_btn"
259
- ouiaId="update-source-button"
260
- onClick={e => handleSubmit(e, { shouldRedirect: true })}
261
- isDisabled={isLoading || !formIsValid() || hostsUpdated}
262
- isLoading={isLoading}
263
- >
264
- {__('Run job invocation')}
265
- </Button>
266
- <Button
267
- variant="secondary"
268
- id="generate_btn"
269
- ouiaId="update-source-button"
270
- onClick={showTemplate}
271
- isDisabled={isLoading || !formIsValid() || hostsUpdated}
272
- isLoading={isLoading}
273
- >
274
- {__('Update hosts manually')}
275
- </Button>
269
+ {!showCVOnlyAlert &&
270
+ <>
271
+ <Button
272
+ variant="primary"
273
+ id="generate_btn"
274
+ ouiaId="run-job-invocation-button"
275
+ onClick={e => handleSubmit(e, { redirectTo: jobInvocationPath })}
276
+ isDisabled={isLoading || !formIsValid() || hostsUpdated}
277
+ isLoading={isLoading}
278
+ >
279
+ {__('Run job invocation')}
280
+ </Button>
281
+ <Button
282
+ variant="secondary"
283
+ id="generate_btn"
284
+ ouiaId="update-source-button"
285
+ onClick={showTemplate}
286
+ isDisabled={isLoading || !formIsValid() || hostsUpdated}
287
+ isLoading={isLoading}
288
+ >
289
+ {__('Update hosts manually')}
290
+ </Button>
291
+ </>
292
+ }
293
+ {showCVOnlyAlert &&
294
+ <Button
295
+ variant="primary"
296
+ id="generate_btn"
297
+ ouiaId="change-cv-button"
298
+ onClick={e =>
299
+ handleSubmit(e, {
300
+ redirectTo: hostEditPath || hostDetailsPath,
301
+ showSuccessToast: true,
302
+ })
303
+ }
304
+ isDisabled={isLoading || !formIsValid() || hostsUpdated}
305
+ isLoading={isLoading}
306
+ >
307
+ {__('Save')}
308
+ </Button>
309
+ }
276
310
 
277
311
  </ActionGroup>
278
312
  </Form>);
@@ -288,6 +322,9 @@ ContentSourceForm.propTypes = {
288
322
  contentSources: PropTypes.arrayOf(PropTypes.shape({})),
289
323
  handleContentSource: PropTypes.func.isRequired,
290
324
  contentSourceId: PropTypes.string,
325
+ showCVOnlyAlert: PropTypes.bool,
326
+ hostDetailsPath: PropTypes.string.isRequired,
327
+ hostEditPath: PropTypes.string.isRequired,
291
328
  contentHosts: PropTypes.arrayOf(PropTypes.shape({})),
292
329
  isLoading: PropTypes.bool,
293
330
  hostsUpdated: PropTypes.bool,
@@ -300,6 +337,7 @@ ContentSourceForm.defaultProps = {
300
337
  contentViewName: '',
301
338
  contentSources: [],
302
339
  contentSourceId: '',
340
+ showCVOnlyAlert: false,
303
341
  contentHosts: [],
304
342
  isLoading: false,
305
343
  hostsUpdated: false,
@@ -17,7 +17,6 @@ import { selectApiDataStatus,
17
17
  selectContentHosts,
18
18
  selectContentHostsWithoutContent,
19
19
  selectContentSources,
20
- selectJobInvocationPath,
21
20
  selectContentViews,
22
21
  selectTemplate } from './selectors';
23
22
 
@@ -46,31 +45,36 @@ const ChangeContentSourcePage = () => {
46
45
  const contentHosts = useSelector(selectContentHosts);
47
46
  const hostsWithoutContent = useSelector(selectContentHostsWithoutContent);
48
47
  const contentSources = useSelector(selectContentSources);
49
- const jobInvocationPath = useSelector(selectJobInvocationPath);
50
48
 
51
49
  const template = useSelector(selectTemplate);
52
50
  const contentViews = useSelector(selectContentViews);
53
-
54
- const [contentSourceId, setCapsuleId] = useState('');
51
+ const { initialContentSourceId } = urlParams;
52
+ const [contentSourceId, setCapsuleId] = useState(initialContentSourceId ?? '');
53
+ // if this matches, we'll trust you that initialContentSourceId is the host's content source
54
+ const showCVOnlyAlert = (contentHosts.length === 1 &&
55
+ hostsWithoutContent.length === 0 &&
56
+ !!initialContentSourceId &&
57
+ initialContentSourceId === contentSourceId
58
+ );
59
+ const hostDetailsPath = showCVOnlyAlert ? `new/hosts/${contentHosts[0].name}` : '';
60
+ const hostEditPath = urlParams.fromPage === 'hostEdit' ? foremanUrl(`/hosts/${contentHosts[0]?.name}/edit`) : '';
55
61
  const [selectedEnvironment, setSelectedEnvironment] = useState([]);
56
62
  const [contentViewName, setContentViewName] = useState('');
57
63
  const [shouldShowTemplate, setShouldShowTemplate] = useState(false);
58
- const [redirect, setRedirect] = useState(false);
64
+ const [redirect, setRedirect] = useState('');
59
65
 
60
66
  const contentViewId = contentViews?.find(cv => cv.name === contentViewName)?.id;
61
67
  const hostIds = useMemo(() => getHostIds(urlParams.host_id), [urlParams.host_id]);
62
68
  const noHostSpecified = (hostIds.length === 0 && urlParams.searchParam === '');
63
69
  const environmentId = selectedEnvironment[0]?.id;
64
70
 
65
- const redirectToJobInvocationForm = () => setRedirect(true);
66
-
67
- const handleSuccess = ({ shouldRedirect }) => {
68
- if (shouldRedirect) {
69
- redirectToJobInvocationForm();
71
+ const handleSuccess = ({ redirectTo = '' }) => {
72
+ if (redirectTo) {
73
+ setRedirect(redirectTo);
70
74
  }
71
75
  };
72
76
 
73
- const handleSubmit = (e, { shouldRedirect = false }) => {
77
+ const handleSubmit = (e, { redirectTo = '', showSuccessToast = false } = {}) => {
74
78
  e.preventDefault();
75
79
 
76
80
  dispatch(changeContentSource(
@@ -78,7 +82,8 @@ const ChangeContentSourcePage = () => {
78
82
  contentViewId,
79
83
  contentSourceId,
80
84
  contentHosts.map(h => h.id),
81
- () => handleSuccess({ shouldRedirect }),
85
+ () => handleSuccess({ redirectTo }),
86
+ showSuccessToast ? () => __('Host content view environment(s) updated') : undefined,
82
87
  ));
83
88
  };
84
89
 
@@ -93,7 +98,7 @@ const ChangeContentSourcePage = () => {
93
98
  };
94
99
 
95
100
  const showTemplate = (e) => {
96
- handleSubmit(e, { shouldRedirect: false });
101
+ handleSubmit(e);
97
102
  setShouldShowTemplate(true);
98
103
  };
99
104
 
@@ -122,8 +127,8 @@ const ChangeContentSourcePage = () => {
122
127
  dispatch(getFormData(hostIds, urlParams.searchParam));
123
128
  }, [dispatch, hostIds, urlParams.searchParam]);
124
129
 
125
- if (redirect && jobInvocationPath) {
126
- window.location.assign(jobInvocationPath); // redirect to job invocation wizard
130
+ if (redirect && redirect !== '') {
131
+ window.location.assign(redirect);
127
132
  }
128
133
 
129
134
  return (
@@ -165,7 +170,7 @@ const ChangeContentSourcePage = () => {
165
170
  title={__('No hosts were specified')}
166
171
  />
167
172
  </GridItem>
168
- }
173
+ }
169
174
  { !noHostSpecified &&
170
175
  <>
171
176
  <Hosts
@@ -182,6 +187,9 @@ const ChangeContentSourcePage = () => {
182
187
  contentViewName={contentViewName}
183
188
  contentSources={contentSources}
184
189
  contentSourceId={contentSourceId}
190
+ showCVOnlyAlert={showCVOnlyAlert}
191
+ hostDetailsPath={hostDetailsPath}
192
+ hostEditPath={hostEditPath}
185
193
  handleContentSource={handleContentSource}
186
194
  contentHosts={contentHosts}
187
195
  isLoading={isLoading}
@@ -1,59 +1,53 @@
1
- import React, { Component } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import qs from 'query-string';
3
+ import { useUrlParams } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
5
  import { orgId } from '../../services/api';
6
6
  import TableSchema from '../ModuleStreams/ModuleStreamsTableSchema';
7
7
  import GenericContentPage from '../../components/Content/GenericContentPage';
8
8
 
9
- class ModuleStreamsPage extends Component {
10
- constructor(props) {
11
- super(props);
9
+ const ModuleStreamsPage = (props) => {
10
+ const { searchParam } = useUrlParams();
11
+ const [searchQuery, setSearchQuery] = useState(searchParam || '');
12
+ const { getModuleStreams } = props;
12
13
 
13
- const queryParams = qs.parse(this.props.location.search);
14
- this.state = {
15
- searchQuery: queryParams.search || '',
16
- };
17
- }
18
-
19
- componentDidMount() {
20
- this.props.getModuleStreams({
21
- search: this.state.searchQuery,
14
+ useEffect(() => {
15
+ getModuleStreams({
16
+ search: searchQuery,
22
17
  });
23
- }
18
+ }, [getModuleStreams, searchQuery]);
24
19
 
25
- onPaginationChange = (pagination) => {
26
- this.props.getModuleStreams({
20
+ const onPaginationChange = (pagination) => {
21
+ props.getModuleStreams({
27
22
  ...pagination,
28
23
  });
29
24
  };
30
25
 
31
- onSearch = (search) => {
32
- this.props.getModuleStreams({ search });
26
+ const onSearch = (search) => {
27
+ props.getModuleStreams({ search });
33
28
  };
34
29
 
35
- updateSearchQuery = (searchQuery) => {
36
- this.setState({ searchQuery });
30
+ const updateSearchQuery = (newSearchQuery) => {
31
+ setSearchQuery(newSearchQuery);
37
32
  };
38
33
 
39
- render() {
40
- const { moduleStreams } = this.props;
41
- return (
42
- <GenericContentPage
43
- header={__('Module Streams')}
44
- content={moduleStreams}
45
- tableSchema={TableSchema}
46
- onSearch={this.onSearch}
47
- autocompleteEndpoint="/katello/api/v2/module_streams"
48
- autocompleteQueryParams={{ organization_id: orgId() }}
49
- bookmarkController="katello_module_streams"
50
- updateSearchQuery={this.updateSearchQuery}
51
- initialInputValue={this.state.searchQuery}
52
- onPaginationChange={this.onPaginationChange}
53
- />
54
- );
55
- }
56
- }
34
+ const { moduleStreams } = props;
35
+ return (
36
+ <GenericContentPage
37
+ header={__('Module Streams')}
38
+ content={moduleStreams}
39
+ tableSchema={TableSchema}
40
+ onSearch={onSearch}
41
+ autocompleteEndpoint="/katello/api/v2/module_streams"
42
+ autocompleteQueryParams={{ organization_id: orgId() }}
43
+ bookmarkController="katello_module_streams"
44
+ updateSearchQuery={updateSearchQuery}
45
+ initialInputValue={searchQuery}
46
+ onPaginationChange={onPaginationChange}
47
+ />
48
+ );
49
+ };
50
+
57
51
 
58
52
  ModuleStreamsPage.propTypes = {
59
53
  location: PropTypes.shape({
@@ -1,19 +1,21 @@
1
1
  import React from 'react';
2
2
  import { shallow } from 'enzyme';
3
3
  import toJson from 'enzyme-to-json';
4
+ import * as hooks from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
4
5
  import ModuleStreamsPage from '../ModuleStreamsPage';
5
6
  import GenericContentPage from '../../../components/Content/GenericContentPage';
6
7
 
7
8
  describe('Module streams page', () => {
8
9
  it('should render and contain appropiate components', async () => {
9
10
  const moduleStreams = {};
10
- const mockLocation = { search: '' };
11
+ jest.spyOn(hooks, 'useUrlParams').mockImplementation(() => ({
12
+ searchParam: '',
13
+ }));
11
14
  const getModuleStreams = () => {};
12
15
 
13
16
  const wrapper = shallow(<ModuleStreamsPage
14
17
  moduleStreams={moduleStreams}
15
18
  getModuleStreams={getModuleStreams}
16
- location={mockLocation}
17
19
  />);
18
20
 
19
21
  expect(toJson(wrapper)).toMatchSnapshot();
@@ -88,7 +88,6 @@ exports[`Module streams table should render and contain appropiate components 1`
88
88
  }
89
89
  itemCount={0}
90
90
  onPaginationChange={[Function]}
91
- ouiaId="content-table-table"
92
91
  pagination={Object {}}
93
92
  rows={Array []}
94
93
  />
@@ -7,6 +7,7 @@ exports[`RedHatRepositories page should render <PermissionDenied /> when permiss
7
7
  "view_organizations",
8
8
  ]
9
9
  }
10
+ primaryButton={null}
10
11
  />
11
12
  `;
12
13
 
@@ -1,7 +1,10 @@
1
1
  import React, { Component } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Grid, Col, Row, Tabs, Tab, FormControl, ControlLabel } from 'react-bootstrap';
4
+ import { FormattedMessage } from 'react-intl';
4
5
  import { Button, Spinner } from 'patternfly-react';
6
+ import { Alert } from '@patternfly/react-core';
7
+ import { propsToCamelCase } from 'foremanReact/common/helpers';
5
8
  import ForemanModal from 'foremanReact/components/ForemanModal';
6
9
  import Slot from 'foremanReact/components/common/Slot';
7
10
  import { translate as __ } from 'foremanReact/common/I18n';
@@ -92,22 +95,29 @@ class ManageManifestModal extends Component {
92
95
  contentCredentials,
93
96
  } = this.props;
94
97
 
98
+ const {
99
+ manifestExpiringSoon,
100
+ manifestExpired,
101
+ manifestExpirationDate,
102
+ manifestExpireDaysRemaining,
103
+ } = propsToCamelCase(organization);
104
+
95
105
  const actionInProgress = (taskInProgress || manifestActionStarted);
96
106
  const showCdnConfigurationTab = canEditOrganizations;
97
107
  const showSubscriptionManifest = (canImportManifest || canDeleteManifest);
98
108
  const showManifestTab = (canEditOrganizations || showSubscriptionManifest);
99
109
 
100
110
  const emptyStateData = () => ({
101
- header: __('There is no Manifest History to display.'),
102
- description: __('Import a Manifest using the manifest tab above.'),
111
+ header: __('There is no manifest history to display.'),
112
+ description: __('Import a manifest using the Manifest tab above.'),
103
113
  documentation: {
104
- label: __('Learn more about adding Subscription Manifests '),
114
+ label: __('Learn more about adding subscription manifests '),
105
115
  url: 'https://access.redhat.com/solutions/3410771',
106
116
  },
107
117
  });
108
118
 
109
119
  const getManifestName = () => {
110
- let name = __('No Manifest Uploaded');
120
+ let name = __('No manifest imported');
111
121
 
112
122
  if (
113
123
  organization.owner_details &&
@@ -127,6 +137,8 @@ class ManageManifestModal extends Component {
127
137
  return name;
128
138
  };
129
139
 
140
+ const manifestExpiredMessage = manifestExpirationDate ? __('Your manifest expired on {expirationDate}. To continue using Red Hat content, import a new manifest.') : __('Your manifest has expired. To continue using Red Hat content, import a new manifest.');
141
+
130
142
  return (
131
143
  <ForemanModal id={MANAGE_MANIFEST_MODAL_ID} title={__('Manage Manifest')}>
132
144
  <Tabs id="manifest-history-tabs">
@@ -139,6 +151,46 @@ class ManageManifestModal extends Component {
139
151
  <React.Fragment>
140
152
  <Grid>
141
153
  <h3>{__('Subscription Manifest')}</h3>
154
+ {manifestExpiringSoon &&
155
+ <Alert
156
+ ouiaId="manifest-expiring-soon-alert"
157
+ variant="warning"
158
+ title={__('Manifest expiring soon')}
159
+ >
160
+ <FormattedMessage
161
+ defaultMessage={__('Your manifest will expire in {daysMessage}. To extend the expiration date, refresh your manifest. Or, if your Foreman is disconnected, import a new manifest.')}
162
+ values={{
163
+ daysMessage: (
164
+ <FormattedMessage
165
+ defaultMessage="{daysRemaining, plural, one {{singular}} other {# {plural}}}"
166
+ values={{
167
+ daysRemaining: manifestExpireDaysRemaining,
168
+ singular: __('day'),
169
+ plural: __('days'),
170
+ }}
171
+ id="manage-manifest-expire-days-i18n"
172
+ />
173
+ ),
174
+ }}
175
+ id="manage-manifest-expire-i18n"
176
+ />
177
+ </Alert>
178
+ }
179
+ {manifestExpired && isManifestImported &&
180
+ <Alert
181
+ ouiaId="manifest-expired-alert"
182
+ variant="danger"
183
+ title={__('Manifest expired')}
184
+ >
185
+ <FormattedMessage
186
+ defaultMessage={manifestExpiredMessage}
187
+ values={{
188
+ expirationDate: new Date(manifestExpirationDate).toDateString(),
189
+ }}
190
+ id="manage-manifest-expired-i18n"
191
+ />
192
+ </Alert>
193
+ }
142
194
  <hr />
143
195
  <Row>
144
196
  <Col sm={5}>
@@ -148,13 +200,22 @@ class ManageManifestModal extends Component {
148
200
  {getManifestName()}
149
201
  </Col>
150
202
  </Row>
203
+ {isManifestImported && Boolean(manifestExpirationDate) &&
204
+ <Row>
205
+ <Col sm={5} />
206
+ <Col sm={7}>
207
+ {manifestExpired ? __('Expired ') : __('Expires ')}
208
+ {new Date(manifestExpirationDate).toDateString()}
209
+ </Col>
210
+ </Row>
211
+ }
151
212
  <Row>
152
213
  <Col sm={5}>
153
214
  {canImportManifest &&
154
215
  <ControlLabel
155
216
  style={{ paddingTop: '10px' }}
156
217
  >
157
- <div>{__('Import New Manifest')}</div>
218
+ <div>{__('Import new manifest')}</div>
158
219
  </ControlLabel>
159
220
  }
160
221
  </Col>
@@ -222,19 +222,22 @@ class UpstreamSubscriptionsPage extends Component {
222
222
 
223
223
  return (
224
224
  <Grid bsClass="container-fluid">
225
- <BreadcrumbsBar data={{
226
- isSwitchable: false,
227
- breadcrumbItems: [
228
- {
229
- caption: __('Subscriptions'),
230
- onClick: () => this.props.history.push('/subscriptions'),
231
- },
232
- {
233
- caption: __('Add Subscriptions'),
234
- },
235
- ],
236
- }}
237
- />
225
+ {!upstreamSubscriptions.loading &&
226
+ <div style={{ marginBottom: '10px' }}>
227
+ <BreadcrumbsBar
228
+ isLoadingResources={upstreamSubscriptions.loading}
229
+ breadcrumbItems={[
230
+ {
231
+ caption: __('Subscriptions'),
232
+ url: '/subscriptions/',
233
+ },
234
+ {
235
+ caption: String(__('Add Subscriptions')),
236
+ },
237
+ ]}
238
+ />
239
+ </div>
240
+ }
238
241
 
239
242
  <LoadingState loading={upstreamSubscriptions.loading} loadingText={__('Loading')}>
240
243
  <Row>
@@ -6,22 +6,28 @@ exports[`upstream subscriptions page should render 1`] = `
6
6
  componentClass="div"
7
7
  fluid={false}
8
8
  >
9
- <BreadcrumbsBar
10
- data={
9
+ <div
10
+ style={
11
11
  Object {
12
- "breadcrumbItems": Array [
12
+ "marginBottom": "10px",
13
+ }
14
+ }
15
+ >
16
+ <BreadcrumbsBar
17
+ breadcrumbItems={
18
+ Array [
13
19
  Object {
14
20
  "caption": "Subscriptions",
15
- "onClick": [Function],
21
+ "url": "/subscriptions/",
16
22
  },
17
23
  Object {
18
24
  "caption": "Add Subscriptions",
19
25
  },
20
- ],
21
- "isSwitchable": false,
26
+ ]
22
27
  }
23
- }
24
- />
28
+ isLoadingResources={false}
29
+ />
30
+ </div>
25
31
  <LoadingState
26
32
  loading={false}
27
33
  loadingText="Loading"
@@ -7,6 +7,7 @@ exports[`subscriptions page should render <PermissionDenied /> when permissions
7
7
  "view_subscriptions",
8
8
  ]
9
9
  }
10
+ primaryButton={null}
10
11
  />
11
12
  `;
12
13
 
@@ -49,7 +49,7 @@ const SubscriptionsToolbar = ({
49
49
  <FormGroup>
50
50
  {canManageSubscriptionAllocations &&
51
51
  <LinkContainer
52
- to="subscriptions/add"
52
+ to="/subscriptions/add"
53
53
  disabled={disableManifestActions || disableAddButton}
54
54
  >
55
55
  <TooltipButton