katello 3.17.0 → 3.18.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 (272) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +38 -21
  3. data/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb +3 -1
  4. data/app/controllers/katello/api/v2/activation_keys_controller.rb +10 -15
  5. data/app/controllers/katello/api/v2/capsule_content_controller.rb +2 -2
  6. data/app/controllers/katello/api/v2/content_credentials_controller.rb +1 -8
  7. data/app/controllers/katello/api/v2/content_export_incrementals_controller.rb +98 -0
  8. data/app/controllers/katello/api/v2/content_exports_controller.rb +84 -0
  9. data/app/controllers/katello/api/v2/content_imports_controller.rb +59 -0
  10. data/app/controllers/katello/api/v2/content_view_components_controller.rb +31 -14
  11. data/app/controllers/katello/api/v2/content_view_filters_controller.rb +17 -8
  12. data/app/controllers/katello/api/v2/content_view_repositories_controller.rb +1 -0
  13. data/app/controllers/katello/api/v2/content_view_versions_controller.rb +65 -71
  14. data/app/controllers/katello/api/v2/content_views_controller.rb +37 -26
  15. data/app/controllers/katello/api/v2/environments_controller.rb +8 -8
  16. data/app/controllers/katello/api/v2/gpg_keys_controller.rb +5 -5
  17. data/app/controllers/katello/api/v2/host_collections_controller.rb +19 -16
  18. data/app/controllers/katello/api/v2/host_debs_controller.rb +1 -0
  19. data/app/controllers/katello/api/v2/host_errata_controller.rb +2 -2
  20. data/app/controllers/katello/api/v2/hosts_bulk_actions_controller.rb +35 -6
  21. data/app/controllers/katello/api/v2/products_bulk_actions_controller.rb +1 -1
  22. data/app/controllers/katello/api/v2/products_controller.rb +9 -9
  23. data/app/controllers/katello/api/v2/repositories_bulk_actions_controller.rb +1 -1
  24. data/app/controllers/katello/api/v2/repositories_controller.rb +10 -5
  25. data/app/controllers/katello/api/v2/repository_sets_controller.rb +24 -14
  26. data/app/controllers/katello/api/v2/subscriptions_controller.rb +1 -1
  27. data/app/controllers/katello/api/v2/sync_plans_controller.rb +8 -9
  28. data/app/controllers/katello/api/v2/upstream_subscriptions_controller.rb +9 -2
  29. data/app/controllers/katello/concerns/api/v2/authorization.rb +19 -5
  30. data/app/controllers/katello/concerns/api/v2/bulk_hosts_extensions.rb +22 -18
  31. data/app/controllers/katello/concerns/api/v2/registration_controller_extensions.rb +21 -0
  32. data/app/controllers/katello/concerns/api/v2/repository_content_controller.rb +1 -1
  33. data/app/controllers/katello/concerns/organizations_controller_extensions.rb +2 -1
  34. data/app/controllers/katello/concerns/registration_controller_extensions.rb +16 -0
  35. data/app/helpers/katello/katello_urls_helper.rb +5 -2
  36. data/app/lib/actions/candlepin/product/content_create.rb +2 -0
  37. data/app/lib/actions/candlepin/product/content_update.rb +2 -0
  38. data/app/lib/actions/helpers/smart_proxy_sync_history_helper.rb +24 -0
  39. data/app/lib/actions/katello/applicability/hosts/bulk_generate.rb +6 -2
  40. data/app/lib/actions/katello/capsule_content/sync.rb +1 -1
  41. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +16 -7
  42. data/app/lib/actions/katello/content_view/promote_to_environment.rb +1 -1
  43. data/app/lib/actions/katello/content_view/publish.rb +9 -9
  44. data/app/lib/actions/katello/content_view_version/import.rb +8 -13
  45. data/app/lib/actions/katello/content_view_version/import_library.rb +17 -0
  46. data/app/lib/actions/katello/content_view_version/incremental_update.rb +18 -3
  47. data/app/lib/actions/katello/host/hypervisors_update.rb +18 -0
  48. data/app/lib/actions/katello/host/update_system_purpose.rb +31 -0
  49. data/app/lib/actions/katello/host/upload_package_profile.rb +3 -1
  50. data/app/lib/actions/katello/host/upload_profiles.rb +8 -6
  51. data/app/lib/actions/katello/organization/manifest_delete.rb +0 -1
  52. data/app/lib/actions/katello/organization/manifest_import.rb +0 -1
  53. data/app/lib/actions/katello/organization/manifest_refresh.rb +0 -1
  54. data/app/lib/actions/katello/product/content_create.rb +7 -6
  55. data/app/lib/actions/katello/repository/filtered_index_content.rb +10 -1
  56. data/app/lib/actions/katello/repository/import_upload.rb +4 -1
  57. data/app/lib/actions/katello/repository/remove_content.rb +1 -1
  58. data/app/lib/actions/katello/repository/sync.rb +3 -1
  59. data/app/lib/actions/katello/repository/update.rb +5 -1
  60. data/app/lib/actions/katello/repository/upload_files.rb +1 -0
  61. data/app/lib/actions/middleware/record_smart_proxy_sync_history.rb +15 -0
  62. data/app/lib/actions/pulp/consumer/sync_capsule.rb +4 -2
  63. data/app/lib/actions/pulp/repository/distributor_publish.rb +1 -1
  64. data/app/lib/actions/pulp3/capsule_content/sync.rb +1 -0
  65. data/app/lib/actions/pulp3/content_migration.rb +10 -0
  66. data/app/lib/actions/pulp3/content_migration_presenter.rb +59 -0
  67. data/app/lib/actions/pulp3/content_view/delete_repository_references.rb +1 -1
  68. data/app/lib/actions/pulp3/content_view_version/create_importer.rb +7 -3
  69. data/app/lib/actions/pulp3/content_view_version/export.rb +7 -1
  70. data/app/lib/actions/pulp3/content_view_version/import.rb +7 -3
  71. data/app/lib/actions/pulp3/import_migration.rb +6 -1
  72. data/app/lib/actions/pulp3/orchestration/content_view_version/copy_version_units_to_library.rb +2 -1
  73. data/app/lib/actions/pulp3/orchestration/content_view_version/export.rb +38 -14
  74. data/app/lib/actions/pulp3/orchestration/content_view_version/export_library.rb +60 -0
  75. data/app/lib/actions/pulp3/orchestration/content_view_version/import.rb +16 -10
  76. data/app/lib/actions/pulp3/orchestration/repository/generate_metadata.rb +4 -1
  77. data/app/lib/actions/pulp3/orchestration/repository/import_upload.rb +16 -3
  78. data/app/lib/actions/pulp3/repository/commit_upload.rb +2 -1
  79. data/app/lib/actions/pulp3/repository/copy_content.rb +1 -1
  80. data/app/lib/actions/pulp3/repository/delete.rb +1 -1
  81. data/app/lib/actions/pulp3/repository/save_artifact.rb +1 -1
  82. data/app/lib/actions/pulp3/repository/save_version.rb +1 -1
  83. data/app/lib/actions/pulp3/repository/upload_tag.rb +18 -0
  84. data/app/lib/katello/resources/candlepin/consumer.rb +2 -2
  85. data/app/lib/katello/resources/candlepin/owner.rb +5 -0
  86. data/app/lib/katello/resources/candlepin/upstream_consumer.rb +6 -0
  87. data/app/lib/katello/resources/registry.rb +3 -3
  88. data/app/models/katello/authorization/activation_key.rb +4 -0
  89. data/app/models/katello/authorization/content_view.rb +13 -0
  90. data/app/models/katello/authorization/content_view_component.rb +15 -0
  91. data/app/models/katello/authorization/content_view_filter.rb +15 -0
  92. data/app/models/katello/authorization/content_view_version.rb +25 -2
  93. data/app/models/katello/authorization/content_view_version_export_history.rb +1 -1
  94. data/app/models/katello/authorization/gpg_key.rb +12 -4
  95. data/app/models/katello/authorization/lifecycle_environment.rb +8 -0
  96. data/app/models/katello/authorization/organization.rb +8 -0
  97. data/app/models/katello/authorization/sync_plan.rb +16 -0
  98. data/app/models/katello/concerns/operatingsystem_extensions.rb +2 -0
  99. data/app/models/katello/concerns/organization_extensions.rb +4 -5
  100. data/app/models/katello/concerns/smart_proxy_extensions.rb +6 -4
  101. data/app/models/katello/content_migration_progress.rb +4 -0
  102. data/app/models/katello/content_view.rb +30 -4
  103. data/app/models/katello/content_view_component.rb +2 -0
  104. data/app/models/katello/content_view_filter.rb +5 -0
  105. data/app/models/katello/content_view_history.rb +2 -1
  106. data/app/models/katello/content_view_package_filter.rb +1 -1
  107. data/app/models/katello/content_view_puppet_module.rb +8 -0
  108. data/app/models/katello/content_view_repository.rb +13 -1
  109. data/app/models/katello/content_view_version_export_history.rb +8 -1
  110. data/app/models/katello/glue/candlepin/pool.rb +9 -14
  111. data/app/models/katello/glue/pulp/repo.rb +8 -0
  112. data/app/models/katello/gpg_key.rb +1 -1
  113. data/app/models/katello/ping.rb +8 -3
  114. data/app/models/katello/repository.rb +33 -0
  115. data/app/models/katello/root_repository.rb +26 -1
  116. data/app/models/katello/smart_proxy_sync_history.rb +8 -0
  117. data/app/services/katello/candlepin/event_handler.rb +2 -0
  118. data/app/services/katello/candlepin/message_handler.rb +34 -0
  119. data/app/services/katello/candlepin/upstream_consumer.rb +28 -0
  120. data/app/services/katello/host_status_manager.rb +9 -0
  121. data/app/services/katello/pulp3/api/apt.rb +57 -0
  122. data/app/services/katello/pulp3/api/core.rb +8 -0
  123. data/app/services/katello/pulp3/api/docker.rb +4 -0
  124. data/app/services/katello/pulp3/content_view_version/export.rb +125 -8
  125. data/app/services/katello/pulp3/content_view_version/import.rb +39 -34
  126. data/app/services/katello/pulp3/content_view_version/import_export_common.rb +6 -16
  127. data/app/services/katello/pulp3/content_view_version/import_validator.rb +114 -0
  128. data/app/services/katello/pulp3/deb.rb +38 -0
  129. data/app/services/katello/pulp3/docker_manifest.rb +1 -0
  130. data/app/services/katello/pulp3/docker_tag.rb +1 -0
  131. data/app/services/katello/pulp3/migration.rb +51 -10
  132. data/app/services/katello/pulp3/pulp_content_unit.rb +5 -0
  133. data/app/services/katello/pulp3/repository.rb +10 -4
  134. data/app/services/katello/pulp3/repository/ansible_collection.rb +9 -0
  135. data/app/services/katello/pulp3/repository/apt.rb +63 -0
  136. data/app/services/katello/pulp3/repository/docker.rb +9 -0
  137. data/app/services/katello/pulp3/repository/yum.rb +14 -9
  138. data/app/services/katello/pulp3/repository_mirror.rb +9 -4
  139. data/app/services/katello/pulp3/task.rb +4 -0
  140. data/app/services/katello/pulp3/task_group.rb +4 -0
  141. data/app/services/katello/repository_type.rb +2 -1
  142. data/app/services/katello/smart_proxy_helper.rb +9 -0
  143. data/app/views/dashboard/_subscription_widget.html.erb +0 -5
  144. data/app/views/foreman/hosts/_registration.html.erb +12 -0
  145. data/app/views/katello/api/v2/content_view_version_export_histories/show.json.rabl +1 -1
  146. data/app/views/katello/api/v2/content_views/base.json.rabl +1 -0
  147. data/app/views/katello/api/v2/repositories/base.json.rabl +1 -1
  148. data/app/views/katello/layouts/react.html.erb +3 -2
  149. data/app/views/overrides/activation_keys/_host_tab_pane.html.erb +1 -5
  150. data/app/views/overrides/organizations/_index_row_override.html.erb +1 -1
  151. data/config/routes/api/v2.rb +24 -2
  152. data/config/routes/overrides.rb +1 -0
  153. data/db/migrate/20200929200357_create_katello_smart_proxy_sync_history.rb +13 -0
  154. data/db/migrate/20201008204114_add_os_versions_to_katello_root_repositories.rb +5 -0
  155. data/db/migrate/20201012172713_remove_gpg_key_perms.rb +23 -0
  156. data/db/migrate/20201012192035_add_metadata_to_katello_content_view_version_export_history.rb +5 -0
  157. data/db/migrate/20201021150008_add_import_only_to_katello_content_view.rb +5 -0
  158. data/db/migrate/20201119211133_pulp3_migration_progress.rb +9 -0
  159. data/db/seeds.d/111-upgrade_tasks.rb +2 -1
  160. data/engines/bastion/app/assets/javascripts/bastion/components/notification.service.js +1 -1
  161. data/engines/bastion/app/assets/javascripts/bastion/components/nutupane.factory.js +8 -13
  162. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/content-hosts-bulk-system-purpose-modal.controller.js +112 -0
  163. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/views/content-hosts-bulk-system-purpose-modal.html +78 -0
  164. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content-host-modal-helper.service.js +11 -0
  165. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content-hosts.controller.js +5 -0
  166. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content/content-host-debs-installed.controller.js +2 -42
  167. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/views/content-hosts.html +4 -0
  168. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/content-views.controller.js +6 -2
  169. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/details/content-view-details.controller.js +12 -0
  170. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/details/views/content-view-details.html +7 -7
  171. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/details/views/content-view-info.html +7 -1
  172. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/new/content-view-new.controller.js +17 -3
  173. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/new/views/content-view-new.html +16 -2
  174. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/views/content-views.html +5 -0
  175. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/host-collections/details/host-collection-details.controller.js +4 -0
  176. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/host-collections/details/views/host-collection-info.html +6 -0
  177. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/hosts/host-bulk-action.factory.js +2 -1
  178. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/bastion_katello.pot +92 -19
  179. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/de.po +17 -20
  180. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/es.po +17 -24
  181. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/fr.po +1292 -1170
  182. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/it.po +17 -20
  183. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ja.po +858 -807
  184. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ko.po +18 -19
  185. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pt_BR.po +17 -24
  186. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ru.po +17 -18
  187. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh_CN.po +986 -971
  188. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh_TW.po +19 -20
  189. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/translations.js +9 -9
  190. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/bulk/products-bulk-advanced-sync-modal.controller.js +6 -7
  191. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/repository-details-info.controller.js +168 -155
  192. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +17 -2
  193. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/new-repository.controller.js +125 -113
  194. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +15 -3
  195. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/os-versions.service.js +46 -0
  196. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/repository-types.service.js +8 -1
  197. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/subscriptions/views/content-access-mode-banner.html +1 -1
  198. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/sync-plans/details/views/sync-plan-details.html +1 -1
  199. data/lib/katello/engine.rb +2 -0
  200. data/lib/katello/permission_creator.rb +98 -69
  201. data/lib/katello/permissions/host_permissions.rb +1 -0
  202. data/lib/katello/plugin.rb +10 -2
  203. data/lib/katello/repository_types/deb.rb +9 -1
  204. data/lib/katello/tasks/pulp3_migration.rake +17 -3
  205. data/lib/katello/tasks/pulp3_migration_abort.rake +22 -0
  206. data/lib/katello/tasks/pulp3_migration_stats.rake +41 -0
  207. data/lib/katello/tasks/receptor/extract_orgs.rake +1 -1
  208. data/lib/katello/tasks/reset.rake +2 -1
  209. data/lib/katello/tasks/upgrades/3.18/add_cvv_export_history_metadata.rb +18 -0
  210. data/lib/katello/version.rb +1 -1
  211. data/locale/action_names.rb +51 -44
  212. data/locale/bn/katello.po +279 -55
  213. data/locale/cs/katello.po +278 -51
  214. data/locale/de/katello.po +279 -52
  215. data/locale/en/katello.po +278 -49
  216. data/locale/es/katello.po +279 -51
  217. data/locale/fr/katello.po +279 -51
  218. data/locale/gu/katello.po +279 -55
  219. data/locale/hi/katello.po +279 -55
  220. data/locale/it/katello.po +279 -51
  221. data/locale/ja/katello.po +279 -52
  222. data/locale/katello.pot +1379 -971
  223. data/locale/kn/katello.po +279 -55
  224. data/locale/ko/katello.po +279 -51
  225. data/locale/mr/katello.po +279 -55
  226. data/locale/or/katello.po +279 -55
  227. data/locale/pa/katello.po +279 -55
  228. data/locale/pt/katello.po +278 -52
  229. data/locale/pt_BR/katello.po +279 -51
  230. data/locale/ru/katello.po +279 -51
  231. data/locale/ta/katello.po +279 -55
  232. data/locale/te/katello.po +279 -55
  233. data/locale/zh_CN/katello.po +279 -51
  234. data/locale/zh_TW/katello.po +279 -52
  235. data/webpack/components/ActionableDetail.js +2 -1
  236. data/webpack/components/Search/Search.js +1 -1
  237. data/webpack/components/Table/MainTable.js +6 -2
  238. data/webpack/components/Table/TableWrapper.js +46 -9
  239. data/webpack/scenes/ContentViews/ContentViewSelectors.js +7 -3
  240. data/webpack/scenes/ContentViews/ContentViewsConstants.js +8 -0
  241. data/webpack/scenes/ContentViews/ContentViewsPage.js +2 -9
  242. data/webpack/scenes/ContentViews/Details/ContentViewDetailActions.js +25 -3
  243. data/webpack/scenes/ContentViews/Details/ContentViewDetailSelectors.js +14 -4
  244. data/webpack/scenes/ContentViews/Details/ContentViewDetails.js +2 -1
  245. data/webpack/scenes/ContentViews/Details/Repositories/ContentCounts.js +56 -0
  246. data/webpack/scenes/ContentViews/Details/Repositories/ContentViewRepositories.js +169 -0
  247. data/webpack/scenes/ContentViews/Details/Repositories/LastSync.js +47 -0
  248. data/webpack/scenes/ContentViews/Details/Repositories/RepoAddedStatus.js +17 -0
  249. data/webpack/scenes/ContentViews/Details/Repositories/RepoIcon.js +23 -0
  250. data/webpack/scenes/ContentViews/Details/Repositories/SelectableDropdown.js +49 -0
  251. data/webpack/scenes/ContentViews/Details/Repositories/__tests__/contentViewDetailRepos.fixtures.json +154 -0
  252. data/webpack/scenes/ContentViews/Details/Repositories/__tests__/contentViewDetailRepos.test.js +131 -0
  253. data/webpack/scenes/ContentViews/Details/__tests__/contentViewDetail.test.js +3 -0
  254. data/webpack/scenes/ContentViews/Table/ContentViewsTable.js +4 -1
  255. data/webpack/scenes/ContentViews/__tests__/contentViewPage.test.js +2 -2
  256. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +29 -19
  257. data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +17 -0
  258. data/webpack/scenes/Subscriptions/Manifest/ManifestConstants.js +4 -0
  259. data/webpack/scenes/Subscriptions/Manifest/SimpleContentAccess.js +19 -2
  260. data/webpack/scenes/Subscriptions/Manifest/__tests__/SimpleContentAccess.test.js +9 -1
  261. data/webpack/scenes/Subscriptions/Manifest/index.js +2 -1
  262. data/webpack/scenes/Subscriptions/SubscriptionConstants.js +1 -1
  263. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +3 -0
  264. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +8 -2
  265. data/webpack/scenes/Subscriptions/SubscriptionsSelectors.js +3 -0
  266. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +3 -0
  267. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsSelectors.test.js +6 -0
  268. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsSelectors.test.js.snap +6 -0
  269. data/webpack/scenes/Subscriptions/components/SubscriptionsToolbar/SubscriptionsToolbar.js +1 -13
  270. data/webpack/test-utils/react-testing-lib-wrapper.js +3 -0
  271. metadata +74 -13
  272. data/webpack/__mocks__/foremanReact/components/common/Fill/GlobalFill.js +0 -3
@@ -47,7 +47,7 @@ ActionableDetail.propTypes = {
47
47
  value: PropTypes.oneOfType([ // displayed value
48
48
  PropTypes.string,
49
49
  PropTypes.bool,
50
- ]).isRequired,
50
+ ]),
51
51
  onEdit: PropTypes.func.isRequired,
52
52
  textArea: PropTypes.bool,
53
53
  boolean: PropTypes.bool,
@@ -58,6 +58,7 @@ ActionableDetail.defaultProps = {
58
58
  textArea: false,
59
59
  boolean: false,
60
60
  tooltip: null,
61
+ value: null,
61
62
  };
62
63
 
63
64
  export default ActionableDetail;
@@ -64,7 +64,7 @@ class Search extends Component {
64
64
  if (this.state.typingTimeout) clearTimeout(this.state.typingTimeout);
65
65
 
66
66
  this.setState({
67
- typingTimeout: setTimeout(() => this.props.onSearch(searchTerm), autoSearchDelay),
67
+ typingTimeout: setTimeout(() => this.onSearch(searchTerm), autoSearchDelay),
68
68
  });
69
69
  }
70
70
 
@@ -12,12 +12,14 @@ import Loading from '../../components/Loading';
12
12
 
13
13
  const MainTable = ({
14
14
  status, cells, rows, error, emptyContentTitle, emptyContentBody,
15
- emptySearchTitle, emptySearchBody, searchIsActive, ...extraTableProps
15
+ emptySearchTitle, emptySearchBody, searchIsActive, activeFilters,
16
+ ...extraTableProps
16
17
  }) => {
18
+ const isFiltering = activeFilters || searchIsActive;
17
19
  if (status === STATUS.PENDING) return (<Loading />);
18
20
  // Can we display the error message?
19
21
  if (status === STATUS.ERROR) return (<EmptyStateMessage error={error} />);
20
- if (status === STATUS.RESOLVED && searchIsActive && rows.length === 0) {
22
+ if (status === STATUS.RESOLVED && isFiltering && rows.length === 0) {
21
23
  return (<EmptyStateMessage
22
24
  title={emptySearchTitle}
23
25
  body={emptySearchBody}
@@ -56,11 +58,13 @@ MainTable.propTypes = {
56
58
  emptySearchTitle: PropTypes.string.isRequired,
57
59
  emptySearchBody: PropTypes.string.isRequired,
58
60
  searchIsActive: PropTypes.bool,
61
+ activeFilters: PropTypes.bool,
59
62
  };
60
63
 
61
64
  MainTable.defaultProps = {
62
65
  error: null,
63
66
  searchIsActive: false,
67
+ activeFilters: false,
64
68
  };
65
69
 
66
70
  export default MainTable;
@@ -11,9 +11,16 @@ import { orgId } from '../../services/api';
11
11
 
12
12
  /* Patternfly 4 table wrapper */
13
13
  const TableWrapper = ({
14
- metadata, fetchItems, autocompleteEndpoint, ...allTableProps
14
+ children,
15
+ metadata,
16
+ fetchItems,
17
+ autocompleteEndpoint,
18
+ searchQuery,
19
+ updateSearchQuery,
20
+ additionalListeners,
21
+ activeFilters,
22
+ ...allTableProps
15
23
  }) => {
16
- const { search: currentSearch } = metadata;
17
24
  const dispatch = useDispatch();
18
25
  const { foremanPerPage = 20 } = useForemanSettings();
19
26
  // setting pagination to local state so it doesn't disappear when page reloads
@@ -22,15 +29,28 @@ const TableWrapper = ({
22
29
  const [total, setTotal] = useState(0);
23
30
 
24
31
  const updatePagination = (data) => {
25
- const { total: newTotal, page: newPage, per_page: newPerPage } = data;
32
+ const { subtotal: newTotal, page: newPage, per_page: newPerPage } = data;
26
33
  if (newTotal) setTotal(parseInt(newTotal, 10));
27
34
  if (newPage) setPage(parseInt(newPage, 10));
28
35
  if (newPerPage) setPerPage(parseInt(newPerPage, 10));
29
36
  };
37
+ const paginationParams = () => ({ per_page: perPage, page });
38
+ const fetchWithParams = (allParams = {}) => {
39
+ dispatch(fetchItems({ ...paginationParams(), ...allParams }));
40
+ };
30
41
 
31
42
  useEffect(() => updatePagination(metadata), [metadata]);
32
43
 
33
- const paginationParams = () => ({ per_page: perPage, page });
44
+ // The search component will update the search query when a search is performed, listen for that
45
+ // and perform the search so we can be sure the searchQuery is updated when search is performed.
46
+ useEffect(() => {
47
+ if (searchQuery || activeFilters) {
48
+ // Reset page back to 1 when filter or search changes
49
+ fetchWithParams({ search: searchQuery, page: 1 });
50
+ } else {
51
+ fetchWithParams();
52
+ }
53
+ }, [searchQuery, ...additionalListeners]);
34
54
 
35
55
  const getAutoCompleteParams = search => ({
36
56
  endpoint: autocompleteEndpoint,
@@ -40,18 +60,23 @@ const TableWrapper = ({
40
60
  },
41
61
  });
42
62
 
43
- const onSearch = search => dispatch(fetchItems({ ...paginationParams(), search }));
44
-
45
63
  const onPaginationUpdate = (updatedPagination) => {
46
64
  updatePagination(updatedPagination);
47
- dispatch(fetchItems({ ...paginationParams(), ...updatedPagination, search: currentSearch }));
65
+ dispatch(fetchItems({ ...paginationParams(), ...updatedPagination, search: searchQuery }));
48
66
  };
49
67
 
50
68
  return (
51
69
  <React.Fragment>
52
70
  <Flex>
53
71
  <FlexItem>
54
- <Search patternfly4 {...{ onSearch, getAutoCompleteParams }} />
72
+ <Search
73
+ patternfly4
74
+ onSearch={search => updateSearchQuery(search)}
75
+ getAutoCompleteParams={getAutoCompleteParams}
76
+ />
77
+ </FlexItem>
78
+ <FlexItem>
79
+ {children}
55
80
  </FlexItem>
56
81
  <FlexItem align={{ default: 'alignRight' }}>
57
82
  <Pagination
@@ -65,12 +90,14 @@ const TableWrapper = ({
65
90
  />
66
91
  </FlexItem>
67
92
  </Flex>
68
- <MainTable searchIsActive={!!currentSearch} {...allTableProps} />
93
+ <MainTable searchIsActive={!!searchQuery} activeFilters={activeFilters} {...allTableProps} />
69
94
  </React.Fragment>
70
95
  );
71
96
  };
72
97
 
73
98
  TableWrapper.propTypes = {
99
+ searchQuery: PropTypes.string.isRequired,
100
+ updateSearchQuery: PropTypes.func.isRequired,
74
101
  fetchItems: PropTypes.func.isRequired,
75
102
  metadata: PropTypes.shape({
76
103
  total: PropTypes.number,
@@ -85,10 +112,20 @@ TableWrapper.propTypes = {
85
112
  search: PropTypes.string,
86
113
  }),
87
114
  autocompleteEndpoint: PropTypes.string.isRequired,
115
+ children: PropTypes.node,
116
+ // additionalListeners are anything that can trigger another API call, e.g. a filter
117
+ additionalListeners: PropTypes.arrayOf(PropTypes.oneOfType([
118
+ PropTypes.number,
119
+ PropTypes.string,
120
+ ])),
121
+ activeFilters: PropTypes.bool,
88
122
  };
89
123
 
90
124
  TableWrapper.defaultProps = {
91
125
  metadata: {},
126
+ children: null,
127
+ additionalListeners: [],
128
+ activeFilters: false,
92
129
  };
93
130
 
94
131
  export default TableWrapper;
@@ -3,10 +3,14 @@ import {
3
3
  selectAPIError,
4
4
  selectAPIResponse,
5
5
  } from 'foremanReact/redux/API/APISelectors';
6
+ import { STATUS } from 'foremanReact/constants';
6
7
  import CONTENT_VIEWS_KEY from './ContentViewsConstants';
7
8
 
8
- export const selectContentViews = state => selectAPIResponse(state, CONTENT_VIEWS_KEY) || {};
9
+ export const selectContentViews = state =>
10
+ selectAPIResponse(state, CONTENT_VIEWS_KEY) || {};
9
11
 
10
- export const selectContentViewStatus = state => selectAPIStatus(state, CONTENT_VIEWS_KEY);
12
+ export const selectContentViewStatus = state =>
13
+ selectAPIStatus(state, CONTENT_VIEWS_KEY) || STATUS.PENDING;
11
14
 
12
- export const selectContentViewError = state => selectAPIError(state, CONTENT_VIEWS_KEY);
15
+ export const selectContentViewError = state =>
16
+ selectAPIError(state, CONTENT_VIEWS_KEY);
@@ -3,4 +3,12 @@ export const UPDATE_CONTENT_VIEW = 'UPDATE_CONTENT_VIEW';
3
3
  export const UPDATE_CONTENT_VIEW_SUCCESS = 'UPDATE_CONTENT_VIEW_SUCCESS';
4
4
  export const UPDATE_CONTENT_VIEW_FAILURE = 'UPDATE_CONTENT_VIEW_FAILURE';
5
5
 
6
+ export const cvDetailsKey = cvId => `${CONTENT_VIEWS_KEY}_${cvId}`;
7
+ export const cvDetailsRepoKey = cvId => `${CONTENT_VIEWS_KEY}_REPOSITORIES_${cvId}`;
8
+
9
+ // Repo added to content view status display and key
10
+ export const ADDED = 'Added';
11
+ export const NOT_ADDED = 'Not added';
12
+ export const ALL_STATUSES = 'All';
13
+
6
14
  export default CONTENT_VIEWS_KEY;
@@ -1,8 +1,7 @@
1
- import React, { useEffect } from 'react';
1
+ import React from 'react';
2
2
  import { translate as __ } from 'foremanReact/common/I18n';
3
- import { useSelector, useDispatch } from 'react-redux';
3
+ import { useSelector } from 'react-redux';
4
4
  import { Grid, GridItem, TextContent, Text, TextVariants } from '@patternfly/react-core';
5
- import getContentViews from './ContentViewsActions';
6
5
  import { selectContentViews,
7
6
  selectContentViewStatus,
8
7
  selectContentViewError } from './ContentViewSelectors';
@@ -13,12 +12,6 @@ const ContentViewsPage = () => {
13
12
  const status = useSelector(selectContentViewStatus);
14
13
  const error = useSelector(selectContentViewError);
15
14
 
16
- const dispatch = useDispatch();
17
-
18
- useEffect(() => {
19
- dispatch(getContentViews());
20
- }, []);
21
-
22
15
  return (
23
16
  <Grid className="grid-with-margin">
24
17
  <GridItem span={12}>
@@ -2,10 +2,14 @@ import { API_OPERATIONS, get, put } from 'foremanReact/redux/API';
2
2
  import { addToast } from 'foremanReact/redux/actions/toasts';
3
3
  import { translate as __ } from 'foremanReact/common/I18n';
4
4
 
5
- import CONTENT_VIEWS_KEY, {
5
+ import {
6
6
  UPDATE_CONTENT_VIEW,
7
7
  UPDATE_CONTENT_VIEW_FAILURE,
8
8
  UPDATE_CONTENT_VIEW_SUCCESS,
9
+ NOT_ADDED,
10
+ ALL_STATUSES,
11
+ cvDetailsKey,
12
+ cvDetailsRepoKey,
9
13
  } from '../ContentViewsConstants';
10
14
  import api from '../../../services/api';
11
15
 
@@ -13,7 +17,7 @@ import { apiError } from '../../../utils/helpers';
13
17
 
14
18
  const getContentViewDetails = cvId => get({
15
19
  type: API_OPERATIONS.GET,
16
- key: `${CONTENT_VIEWS_KEY}_${cvId}`,
20
+ key: cvDetailsKey(cvId),
17
21
  url: api.getApiUrl(`/content_views/${cvId}`),
18
22
  });
19
23
 
@@ -29,7 +33,7 @@ const cvUpdateSuccess = (response, dispatch) => {
29
33
 
30
34
  export const updateContentView = (cvId, params) => async dispatch => dispatch(put({
31
35
  type: API_OPERATIONS.PUT,
32
- key: `${CONTENT_VIEWS_KEY}_${cvId}`,
36
+ key: cvDetailsKey(cvId),
33
37
  url: api.getApiUrl(`/content_views/${cvId}`),
34
38
  params,
35
39
  handleSuccess: response => cvUpdateSuccess(response, dispatch),
@@ -41,4 +45,22 @@ export const updateContentView = (cvId, params) => async dispatch => dispatch(pu
41
45
  },
42
46
  }));
43
47
 
48
+ export const getContentViewRepositories = (cvId, params, status) => {
49
+ const apiParams = { ...params };
50
+ let apiUrl = `/content_views/${cvId}/repositories`;
51
+
52
+ if (status === ALL_STATUSES) {
53
+ apiUrl += '/show_all';
54
+ } else if (status === NOT_ADDED) {
55
+ apiParams.available_for = 'content_view';
56
+ }
57
+
58
+ return get({
59
+ type: API_OPERATIONS.GET,
60
+ key: cvDetailsRepoKey(cvId),
61
+ url: api.getApiUrl(apiUrl),
62
+ params: apiParams,
63
+ });
64
+ };
65
+
44
66
  export default getContentViewDetails;
@@ -3,16 +3,26 @@ import {
3
3
  selectAPIError,
4
4
  selectAPIResponse,
5
5
  } from 'foremanReact/redux/API/APISelectors';
6
- import CONTENT_VIEWS_KEY from '../ContentViewsConstants';
6
+ import { STATUS } from 'foremanReact/constants';
7
+ import { cvDetailsKey, cvDetailsRepoKey } from '../ContentViewsConstants';
7
8
 
8
9
  export const selectCVDetails = (state, cvId) =>
9
- selectAPIResponse(state, `${CONTENT_VIEWS_KEY}_${cvId}`) || {};
10
+ selectAPIResponse(state, cvDetailsKey(cvId)) || {};
10
11
 
11
12
  export const selectCVDetailStatus =
12
- (state, cvId) => selectAPIStatus(state, `${CONTENT_VIEWS_KEY}_${cvId}`);
13
+ (state, cvId) => selectAPIStatus(state, cvDetailsKey(cvId)) || STATUS.PENDING;
13
14
 
14
15
  export const selectCVDetailError =
15
- (state, cvId) => selectAPIError(state, `${CONTENT_VIEWS_KEY}_${cvId}`);
16
+ (state, cvId) => selectAPIError(state, cvDetailsKey(cvId));
17
+
18
+ export const selectCVRepos = (state, cvId) =>
19
+ selectAPIResponse(state, cvDetailsRepoKey(cvId)) || {};
20
+
21
+ export const selectCVReposStatus = (state, cvId) =>
22
+ selectAPIStatus(state, cvDetailsRepoKey(cvId)) || STATUS.PENDING;
23
+
24
+ export const selectCVReposError = (state, cvId) =>
25
+ selectAPIError(state, cvDetailsRepoKey(cvId));
16
26
 
17
27
 
18
28
  export const selectIsCVUpdating = state => state.katello?.contentViewDetails?.updating;
@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
6
6
 
7
7
  import DetailsContainer from './DetailsContainer';
8
8
  import ContentViewInfo from './ContentViewInfo';
9
+ import ContentViewRepositories from './Repositories/ContentViewRepositories';
9
10
  import { selectCVDetails } from './ContentViewDetailSelectors';
10
11
  import TabbedView from '../../../components/TabbedView';
11
12
 
@@ -25,7 +26,7 @@ const ContentViewDetails = ({ match }) => {
25
26
  },
26
27
  {
27
28
  title: __('Repositories'),
28
- content: <React.Fragment>Repositories</React.Fragment>,
29
+ content: <ContentViewRepositories {...{ cvId, details }} />,
29
30
  },
30
31
  {
31
32
  title: __('Filters'),
@@ -0,0 +1,56 @@
1
+ import React, { Fragment } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { urlBuilder } from 'foremanReact/common/urlHelpers';
4
+
5
+ // type: [plural_name, singular_name, link]
6
+ const repoLabels = {
7
+ rpm: ['RPM packages', 'RPM package', 'packages'],
8
+ module_stream: ['module streams', 'module stream', 'module_streams'],
9
+ erratum: ['errata', 'erratum', 'errata'], // need to handle link, its $URL/errata?repositoryId=107
10
+ deb: ['deb packages', 'deb package', 'debs'],
11
+ ansible_collection: ['Ansible collections', 'Ansible collection', 'ansible_collections'],
12
+ docker_manifest: ['container manifests', 'container manifest', 'content/docker_manifests'],
13
+ docker_manifest_list: ['container manifest lists', 'container manifest list', 'content/docker_manifest_lists'],
14
+ docker_tag: ['container tags', 'container tag', 'content/docker_tags'],
15
+ file: ['files', 'file', 'content/files'],
16
+ ostree_branch: ['ostree branches', 'ostree branch', 'content/ostree_branches'],
17
+ package_group: ['package groups', 'package group', 'package_groups'],
18
+ puppet_module: ['puppet modules', 'puppet module', 'content/puppet_modules'],
19
+ srpm: ['source RPMs', 'source RPM', 'source_rpms'], // no link?
20
+ };
21
+
22
+ const appendCount = (type, count, info, productId, repoId) => {
23
+ const [repoPlural, repoSingular, link] = info;
24
+ const displayName = count > 1 ? repoPlural : repoSingular;
25
+ let url = urlBuilder(`products/${productId}/repositories/${repoId}/content`, '', link);
26
+ const displayInfo = `${count} ${displayName}`;
27
+ if (type === 'source_rpm') return displayInfo;
28
+ if (type === 'erratum') url = urlBuilder(`errata?repositoryId=${repoId}`);
29
+
30
+ return (
31
+ <div key={`${type}${count}`}>
32
+ <a href={url}>{displayInfo}</a>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ const ContentCounts = ({ productId, repoId, counts }) => {
38
+ const allCounts = [];
39
+
40
+ Object.keys(counts).forEach((type) => {
41
+ const count = counts[type];
42
+ const info = repoLabels[type];
43
+ // package and rpm are the same
44
+ if (type !== 'package' && count > 0) allCounts.push(appendCount(type, count, info, productId, repoId));
45
+ });
46
+
47
+ return <Fragment>{allCounts}</Fragment>;
48
+ };
49
+
50
+ ContentCounts.propTypes = {
51
+ productId: PropTypes.number.isRequired,
52
+ repoId: PropTypes.number.isRequired,
53
+ counts: PropTypes.shape({}).isRequired,
54
+ };
55
+
56
+ export default ContentCounts;
@@ -0,0 +1,169 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useSelector, shallowEqual } from 'react-redux';
3
+ import { Bullseye, Split, SplitItem } from '@patternfly/react-core';
4
+ import { TableVariant, fitContent } from '@patternfly/react-table';
5
+ import { STATUS } from 'foremanReact/constants';
6
+ import { translate as __ } from 'foremanReact/common/I18n';
7
+ import { urlBuilder } from 'foremanReact/common/urlHelpers';
8
+ import PropTypes from 'prop-types';
9
+
10
+ import TableWrapper from '../../../../components/Table/TableWrapper';
11
+ import { getContentViewRepositories } from '../ContentViewDetailActions';
12
+ import { selectCVRepos, selectCVReposStatus, selectCVReposError } from '../ContentViewDetailSelectors';
13
+ import { ADDED, NOT_ADDED, ALL_STATUSES } from '../../ContentViewsConstants';
14
+ import ContentCounts from './ContentCounts';
15
+ import LastSync from './LastSync';
16
+ import RepoAddedStatus from './RepoAddedStatus';
17
+ import RepoIcon from './RepoIcon';
18
+ import SelectableDropdown from './SelectableDropdown';
19
+
20
+ const allRepositories = 'All repositories';
21
+
22
+ // checkbox_name: API_name
23
+ const repoTypes = {
24
+ [allRepositories]: 'all',
25
+ 'Yum repositories': 'yum',
26
+ 'File repositories': 'file',
27
+ 'Container repositories': 'docker',
28
+ 'OSTree repositories': 'ostree', // ostree is deprecated?
29
+ };
30
+
31
+ const ContentViewRepositories = ({ cvId }) => {
32
+ const response = useSelector(state => selectCVRepos(state, cvId), shallowEqual);
33
+ const status = useSelector(state => selectCVReposStatus(state, cvId), shallowEqual);
34
+ const error = useSelector(state => selectCVReposError(state, cvId), shallowEqual);
35
+
36
+ const [rows, setRows] = useState([]);
37
+ const [metadata, setMetadata] = useState({});
38
+ const [searchQuery, updateSearchQuery] = useState('');
39
+ const [typeSelected, setTypeSelected] = useState(allRepositories);
40
+ const [statusSelected, setStatusSelected] = useState(ALL_STATUSES);
41
+
42
+
43
+ const columnHeaders = [
44
+ { title: __('Type'), transforms: [fitContent] },
45
+ __('Name'),
46
+ __('Product'),
47
+ __('Sync state'),
48
+ __('Content'),
49
+ { title: __('Status') },
50
+ ];
51
+ const loading = status === STATUS.PENDING;
52
+
53
+ const buildRows = (results) => {
54
+ const newRows = [];
55
+ results.forEach((repo) => {
56
+ const {
57
+ id,
58
+ content_type: contentType,
59
+ name,
60
+ added_to_content_view: addedToCV,
61
+ product: { id: productId, name: productName },
62
+ content_counts: counts,
63
+ last_sync_words: lastSyncWords,
64
+ last_sync: lastSync,
65
+ } = repo;
66
+
67
+ const cells = [
68
+ { title: <Bullseye><RepoIcon type={contentType} /></Bullseye> },
69
+ { title: <a href={urlBuilder(`products/${productId}/repositories`, '', id)}>{name}</a> },
70
+ productName,
71
+ { title: <LastSync {...{ lastSyncWords, lastSync }} /> },
72
+ { title: <ContentCounts {...{ counts, productId }} repoId={id} /> },
73
+ {
74
+ title: <RepoAddedStatus added={addedToCV || statusSelected === ADDED} />,
75
+ },
76
+ ];
77
+
78
+ newRows.push({ cells });
79
+ });
80
+ return newRows;
81
+ };
82
+
83
+ const onSelect = (_event, isSelected, rowId) => {
84
+ let newRows;
85
+ if (rowId === -1) {
86
+ newRows = rows.map(row => ({ ...row, selected: isSelected }));
87
+ } else {
88
+ newRows = [...rows];
89
+ newRows[rowId].selected = isSelected;
90
+ }
91
+
92
+ setRows(newRows);
93
+ };
94
+
95
+ const getCVReposWithOptions = (params = {}) => {
96
+ const allParams = { ...params };
97
+ if (typeSelected !== 'All repositories') allParams.content_type = repoTypes[typeSelected];
98
+
99
+ return getContentViewRepositories(cvId, allParams, statusSelected);
100
+ };
101
+
102
+ useEffect(() => {
103
+ const { results, ...meta } = response;
104
+ setMetadata(meta);
105
+
106
+ if (!loading && results) {
107
+ const newRows = buildRows(results);
108
+ setRows(newRows);
109
+ }
110
+ }, [JSON.stringify(response)]);
111
+
112
+ const emptyContentTitle = __("You currently don't have any repositories to add to this content view.");
113
+ const emptyContentBody = __('Please add some repositories.'); // needs link
114
+ const emptySearchTitle = __('No matching repositories found');
115
+ const emptySearchBody = __('Try changing your search settings.');
116
+ const activeFilters = (typeSelected && typeSelected !== allRepositories) ||
117
+ (statusSelected && statusSelected !== ALL_STATUSES);
118
+
119
+ return (
120
+ <TableWrapper
121
+ {...{
122
+ rows,
123
+ metadata,
124
+ onSelect,
125
+ emptyContentTitle,
126
+ emptyContentBody,
127
+ emptySearchTitle,
128
+ emptySearchBody,
129
+ searchQuery,
130
+ updateSearchQuery,
131
+ error,
132
+ status,
133
+ activeFilters,
134
+ }}
135
+ cells={columnHeaders}
136
+ variant={TableVariant.compact}
137
+ autocompleteEndpoint="/repositories/auto_complete_search"
138
+ fetchItems={params => getCVReposWithOptions(params)}
139
+ additionalListeners={[typeSelected, statusSelected]}
140
+ >
141
+ <Split hasGutter>
142
+ <SplitItem>
143
+ <SelectableDropdown
144
+ items={Object.keys(repoTypes)}
145
+ title="Type"
146
+ selected={typeSelected}
147
+ setSelected={setTypeSelected}
148
+ placeholderText="Type"
149
+ />
150
+ </SplitItem>
151
+ <SplitItem>
152
+ <SelectableDropdown
153
+ items={[ADDED, NOT_ADDED, ALL_STATUSES]}
154
+ title="Status"
155
+ selected={statusSelected}
156
+ setSelected={setStatusSelected}
157
+ placeholderText="Status"
158
+ />
159
+ </SplitItem>
160
+ </Split>
161
+ </TableWrapper>
162
+ );
163
+ };
164
+
165
+ ContentViewRepositories.propTypes = {
166
+ cvId: PropTypes.number.isRequired,
167
+ };
168
+
169
+ export default ContentViewRepositories;