From b95fda7c95a10249ed52d5e37683353933a7439f Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Mon, 16 Dec 2024 22:44:50 +0000 Subject: [PATCH 1/5] Fixes #38107 - add booted container images page --- config/routes.rb | 2 ++ lib/katello/plugin.rb | 9 +++++++ webpack/containers/Application/config.js | 5 ++++ .../BootedContainerImagesConstants.js | 6 +++++ .../BootedContainerImagesPage.js | 25 +++++++++++++++++++ webpack/scenes/BootedContainerImages/index.js | 4 +++ 6 files changed, 51 insertions(+) create mode 100644 webpack/scenes/BootedContainerImages/BootedContainerImagesConstants.js create mode 100644 webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js create mode 100644 webpack/scenes/BootedContainerImages/index.js diff --git a/config/routes.rb b/config/routes.rb index e963a1cd5d8..1e816fb57af 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,6 +33,8 @@ match '/alternate_content_sources' => 'react#index', :via => [:get] match '/alternate_content_sources/*page' => 'react#index', :via => [:get] + match '/booted_container_images' => 'react#index', :via => [:get] + Katello::RepositoryTypeManager.generic_ui_content_types(false).each do |type| get "/#{type.pluralize}", to: redirect("/content/#{type.pluralize}") get "/#{type.pluralize}/:page", to: redirect("/content/#{type.pluralize}/%{page}") diff --git a/lib/katello/plugin.rb b/lib/katello/plugin.rb index d1354d19bd8..b305e181827 100644 --- a/lib/katello/plugin.rb +++ b/lib/katello/plugin.rb @@ -70,6 +70,15 @@ :engine => Katello::Engine, :turbolinks => false + menu :top_menu, + :booted_container_images, + :caption => N_('Booted container images'), + :url_hash => {:controller => 'katello/api/v2/host_bootc_images', + :action => 'bootc_images'}, + :url => '/booted_container_images', + :engine => Katello::Engine, + :turbolinks => false + divider :top_menu, :caption => N_('Lifecycle'), :parent => :content_menu menu :top_menu, diff --git a/webpack/containers/Application/config.js b/webpack/containers/Application/config.js index b23d84e8da8..618fbb2a918 100644 --- a/webpack/containers/Application/config.js +++ b/webpack/containers/Application/config.js @@ -15,6 +15,7 @@ import ContentDetails from '../../scenes/Content/Details'; import withHeader from './withHeaders'; import ChangeContentSource from '../../scenes/Hosts/ChangeContentSource'; import AlternateContentSource from '../../scenes/AlternateContentSources'; +import BootedContainerImages from '../../scenes/BootedContainerImages'; // eslint-disable-next-line import/prefer-default-export export const links = [ @@ -85,4 +86,8 @@ export const links = [ component: WithOrganization(withHeader(AlternateContentSource, { title: __('Alternate Content Sources') })), exact: false, }, + { + path: 'booted_container_images', + component: WithOrganization(withHeader(BootedContainerImages, { title: __('Booted container images') })), + }, ]; diff --git a/webpack/scenes/BootedContainerImages/BootedContainerImagesConstants.js b/webpack/scenes/BootedContainerImages/BootedContainerImagesConstants.js new file mode 100644 index 00000000000..b64816c6d91 --- /dev/null +++ b/webpack/scenes/BootedContainerImages/BootedContainerImagesConstants.js @@ -0,0 +1,6 @@ +import { translate as __ } from 'foremanReact/common/I18n'; +import { foremanApi } from '../../services/api'; + +const BOOTED_CONTAINER_IMAGES_KEY = 'BOOTED_CONTAINER_IMAGES'; +export const BOOTED_CONTAINER_IMAGES_API_PATH = foremanApi.getApiUrl('/hosts/bootc_images'); +export default BOOTED_CONTAINER_IMAGES_KEY; diff --git a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js new file mode 100644 index 00000000000..24ecd43e7f8 --- /dev/null +++ b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js @@ -0,0 +1,25 @@ +import React from 'react'; +import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage'; +import { translate as __ } from 'foremanReact/common/I18n'; +import BOOTED_CONTAINER_IMAGES_KEY, { BOOTED_CONTAINER_IMAGES_API_PATH } from './BootedContainerImagesConstants'; + +const BootedContainerImagesPage = () => { + const columns = { + image_name: { + title: __('Image name'), + }, + }; + return ( + + ); +}; + +export default BootedContainerImagesPage; diff --git a/webpack/scenes/BootedContainerImages/index.js b/webpack/scenes/BootedContainerImages/index.js new file mode 100644 index 00000000000..7c72938a1ba --- /dev/null +++ b/webpack/scenes/BootedContainerImages/index.js @@ -0,0 +1,4 @@ +import { withRouter } from 'react-router-dom'; +import BootedContainerImagesPage from './BootedContainerImagesPage'; + +export default withRouter(BootedContainerImagesPage); From 575550f990a622a9227152d84d4f6925262dedc1 Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Wed, 18 Dec 2024 21:37:33 +0000 Subject: [PATCH 2/5] Refs #38107 - use Table on bootc images page --- .../BootedContainerImagesPage.js | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js index 24ecd43e7f8..e699cb8e161 100644 --- a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js +++ b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js @@ -1,5 +1,13 @@ import React from 'react'; import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage'; +import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table'; +import { + useSetParamsAndApiAndSearch, + useTableIndexAPIResponse, +} from 'foremanReact/components/PF4/TableIndexPage/Table/TableIndexHooks'; +import { + useUrlParams, +} from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks'; import { translate as __ } from 'foremanReact/common/I18n'; import BOOTED_CONTAINER_IMAGES_KEY, { BOOTED_CONTAINER_IMAGES_API_PATH } from './BootedContainerImagesConstants'; @@ -9,6 +17,46 @@ const BootedContainerImagesPage = () => { title: __('Image name'), }, }; + + const STATUS = { + PENDING: 'PENDING', + RESOLVED: 'RESOLVED', + ERROR: 'ERROR', + }; + + const { + searchParam: urlSearchQuery = '', + page: urlPage, + per_page: urlPerPage, + } = useUrlParams(); + const defaultParams = { search: urlSearchQuery }; + if (urlPage) defaultParams.page = Number(urlPage); + if (urlPerPage) defaultParams.per_page = Number(urlPerPage); + const apiOptions = { key: BOOTED_CONTAINER_IMAGES_KEY }; + const response = useTableIndexAPIResponse({ + apiUrl: BOOTED_CONTAINER_IMAGES_API_PATH, + apiOptions, + defaultParams, + }); + + const { + response: { + results, + per_page: perPage, + page, + subtotal, + message: errorMessage, + }, + status = STATUS.PENDING, + setAPIOptions, + } = response; + + const { setParamsAndAPI, params } = useSetParamsAndApiAndSearch({ + defaultParams, + apiOptions, + setAPIOptions: response.setAPIOptions, + }); + return ( { createable={false} isDeleteable={false} controller="/katello/api/v2/host_bootc_images" - columns={columns} - /> + > + + setAPIOptions({ + ...apiOptions, + params: { urlSearchQuery }, + }) + } + columns={columns} + errorMessage={ + status === STATUS.ERROR && errorMessage ? errorMessage : null + } + isPending={status === STATUS.PENDING} + /> + ); }; From 568aa28883298287fa89efa3ff5c4f97e41e8afe Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Thu, 19 Dec 2024 19:03:24 +0000 Subject: [PATCH 3/5] Refs #38107 - add digests + hosts columns to bootc containers page --- .../BootedContainerImagesPage.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js index e699cb8e161..7129ec48b02 100644 --- a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js +++ b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js @@ -15,6 +15,17 @@ const BootedContainerImagesPage = () => { const columns = { image_name: { title: __('Image name'), + isSorted: true, + }, + digest: { + title: __('Image digests'), + wrapper: ({digests}) => digests.length, + }, + hosts: { + title: __('Hosts'), + wrapper: ({image_name, digests}) => ( + {digests.reduce((total, digest) => total + digest.host_count, 0)} + ), }, }; From 01a6c0eea7d5e84ec04df3a5af6ae60f5c6da40c Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Thu, 19 Dec 2024 23:11:26 +0000 Subject: [PATCH 4/5] Refs #38107 - Make booted container images rows expandable --- .../BootedContainerImagesPage.js | 195 +++++++++++++++--- 1 file changed, 161 insertions(+), 34 deletions(-) diff --git a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js index 7129ec48b02..e3836e9e751 100644 --- a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js +++ b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js @@ -1,13 +1,22 @@ import React from 'react'; +import { TableComposable, Thead, Th, Tbody, Tr, Td, ExpandableRowContent } from '@patternfly/react-table'; import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage'; -import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table'; import { useSetParamsAndApiAndSearch, useTableIndexAPIResponse, } from 'foremanReact/components/PF4/TableIndexPage/Table/TableIndexHooks'; import { useUrlParams, + useSet, } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks'; +import { + getColumnHelpers, +} from 'foremanReact/components/PF4/TableIndexPage/Table/helpers'; +import { + useTableSort, +} from 'foremanReact/components/PF4/Helpers/useTableSort'; +import Pagination from 'foremanReact/components/Pagination'; +import EmptyPage from 'foremanReact/routes/common/EmptyPage'; import { translate as __ } from 'foremanReact/common/I18n'; import BOOTED_CONTAINER_IMAGES_KEY, { BOOTED_CONTAINER_IMAGES_API_PATH } from './BootedContainerImagesConstants'; @@ -29,12 +38,6 @@ const BootedContainerImagesPage = () => { }, }; - const STATUS = { - PENDING: 'PENDING', - RESOLVED: 'RESOLVED', - ERROR: 'ERROR', - }; - const { searchParam: urlSearchQuery = '', page: urlPage, @@ -44,15 +47,34 @@ const BootedContainerImagesPage = () => { if (urlPage) defaultParams.page = Number(urlPage); if (urlPerPage) defaultParams.per_page = Number(urlPerPage); const apiOptions = { key: BOOTED_CONTAINER_IMAGES_KEY }; + const response = useTableIndexAPIResponse({ apiUrl: BOOTED_CONTAINER_IMAGES_API_PATH, apiOptions, defaultParams, }); + const columnsToSortParams = {}; + Object.keys(columns).forEach(key => { + if (columns[key].isSorted) { + columnsToSortParams[columns[key].title] = key; + } + }); + const { pfSortParams } = useTableSort({ + allColumns: Object.keys(columns).map(k => columns[k].title), + columnsToSortParams, + onSort, + }); + const expandedImages = useSet([]); + const imageIsExpanded = image_name => expandedImages.has(image_name); + const STATUS = { + PENDING: 'PENDING', + RESOLVED: 'RESOLVED', + ERROR: 'ERROR', + }; const { response: { - results, + results = [], per_page: perPage, page, subtotal, @@ -68,40 +90,145 @@ const BootedContainerImagesPage = () => { setAPIOptions: response.setAPIOptions, }); + const [columnNamesKeys, keysToColumnNames] = getColumnHelpers(columns); + const onSort = (_event, index, direction) => { + setParamsAndAPI({ + ...params, + order: `${Object.keys(columns)[index]} ${direction}`, + }); + }; + const onPagination = newPagination => { + setParamsAndAPI({ ...params, ...newPagination }); + }; + const bottomPagination = ( + + ); + return ( -
- setAPIOptions({ - ...apiOptions, - params: { urlSearchQuery }, - }) - } - columns={columns} - errorMessage={ - status === STATUS.ERROR && errorMessage ? errorMessage : null - } - isPending={status === STATUS.PENDING} - /> + <> + + + + <> + + ))} + + + + + {status === STATUS.PENDING && results.length === 0 && ( + + + + )} + {!status === STATUS.PENDING && + results.length === 0 && + !errorMessage && ( + + + + )} + {errorMessage && ( + + + + )} + + {results?.map((result, rowIndex) => { + const { image_name, digests } = result; + const isExpanded = imageIsExpanded(image_name); + return ( + + + <> + + ))} + + + {digests ? + + + + + + + + {digests.map((digest, index) => ( + + + + + ))} + + + + + : null} + + ); + })} + + {results.length > 0 && !errorMessage && bottomPagination} + ); }; From f6341012ffc90ec241d0b6bb04dd854c9022a340 Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Mon, 23 Dec 2024 15:45:03 +0000 Subject: [PATCH 5/5] Refs #38107 - sorting on booted container images page --- .../BootedContainerImagesPage.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js index e3836e9e751..cc634c0aee5 100644 --- a/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js +++ b/webpack/scenes/BootedContainerImages/BootedContainerImagesPage.js @@ -22,7 +22,7 @@ import BOOTED_CONTAINER_IMAGES_KEY, { BOOTED_CONTAINER_IMAGES_API_PATH } from '. const BootedContainerImagesPage = () => { const columns = { - image_name: { + bootc_booted_image: { title: __('Image name'), isSorted: true, }, @@ -32,8 +32,8 @@ const BootedContainerImagesPage = () => { }, hosts: { title: __('Hosts'), - wrapper: ({image_name, digests}) => ( - {digests.reduce((total, digest) => total + digest.host_count, 0)} + wrapper: ({bootc_booted_image, digests}) => ( + {digests.reduce((total, digest) => total + digest.host_count, 0)} ), }, }; @@ -59,13 +59,19 @@ const BootedContainerImagesPage = () => { columnsToSortParams[columns[key].title] = key; } }); + const onSort = (_event, index, direction) => { + setParamsAndAPI({ + ...params, + order: `${Object.keys(columns)[index]} ${direction}`, + }); + }; const { pfSortParams } = useTableSort({ allColumns: Object.keys(columns).map(k => columns[k].title), columnsToSortParams, onSort, }); const expandedImages = useSet([]); - const imageIsExpanded = image_name => expandedImages.has(image_name); + const imageIsExpanded = bootc_booted_image => expandedImages.has(bootc_booted_image); const STATUS = { PENDING: 'PENDING', RESOLVED: 'RESOLVED', @@ -91,12 +97,6 @@ const BootedContainerImagesPage = () => { }); const [columnNamesKeys, keysToColumnNames] = getColumnHelpers(columns); - const onSort = (_event, index, direction) => { - setParamsAndAPI({ - ...params, - order: `${Object.keys(columns)[index]} ${direction}`, - }); - }; const onPagination = newPagination => { setParamsAndAPI({ ...params, ...newPagination }); }; @@ -175,17 +175,17 @@ const BootedContainerImagesPage = () => { )} {results?.map((result, rowIndex) => { - const { image_name, digests } = result; - const isExpanded = imageIsExpanded(image_name); + const { bootc_booted_image, digests } = result; + const isExpanded = imageIsExpanded(bootc_booted_image); return ( - + <>
+ {columnNamesKeys.map(k => ( + + {keysToColumnNames[k]} +
+ +
+ +
+ +
0 && { + rowIndex, + isExpanded, + onToggle: (_event, _rInx, isOpen,) => expandedImages.onToggle(isOpen, image_name), + expandId: 'booted-containers-expander' + }} + /> + {columnNamesKeys.map(k => ( + + {columns[k].wrapper ? columns[k].wrapper(result) : result[k]} +
+ + +
{__('Image digest')}{__('Hosts')}
{digest.bootc_booted_digest} + {digest.host_count} +
0 && { rowIndex, isExpanded, - onToggle: (_event, _rInx, isOpen,) => expandedImages.onToggle(isOpen, image_name), + onToggle: (_event, _rInx, isOpen,) => expandedImages.onToggle(isOpen, bootc_booted_image), expandId: 'booted-containers-expander' }} />