Wizard: add support for content templates
This commit is contained in:
parent
aa545382e8
commit
0238c04dfe
21 changed files with 1160 additions and 225 deletions
|
|
@ -17,6 +17,8 @@ const config: ConfigFile = {
|
||||||
'listFeatures',
|
'listFeatures',
|
||||||
'listSnapshotsByDate',
|
'listSnapshotsByDate',
|
||||||
'bulkImportRepositories',
|
'bulkImportRepositories',
|
||||||
|
'listTemplates',
|
||||||
|
'getTemplate',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1218,6 +1218,11 @@ components:
|
||||||
be used. If no snapshots made before the specified date can be found, the snapshot
|
be used. If no snapshots made before the specified date can be found, the snapshot
|
||||||
closest to, but after the specified date will be used. If no snapshots can be found at
|
closest to, but after the specified date will be used. If no snapshots can be found at
|
||||||
all, the request will fail. The format must be YYYY-MM-DD (ISO 8601 extended).
|
all, the request will fail. The format must be YYYY-MM-DD (ISO 8601 extended).
|
||||||
|
content_template:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
ID of the content template. A content template and snapshot date cannot both be specified.
|
||||||
|
If a content template is specified, the snapshot date used will be the one from the content template.
|
||||||
ImageTypes:
|
ImageTypes:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
|
|
|
||||||
|
|
@ -31,15 +31,21 @@ import {
|
||||||
import RepositoriesStatus from './RepositoriesStatus';
|
import RepositoriesStatus from './RepositoriesStatus';
|
||||||
import RepositoryUnavailable from './RepositoryUnavailable';
|
import RepositoryUnavailable from './RepositoryUnavailable';
|
||||||
|
|
||||||
import { ContentOrigin, PAGINATION_COUNT } from '../../../../constants';
|
import {
|
||||||
|
ContentOrigin,
|
||||||
|
PAGINATION_COUNT,
|
||||||
|
TEMPLATES_URL,
|
||||||
|
} from '../../../../constants';
|
||||||
import {
|
import {
|
||||||
ApiRepositoryResponseRead,
|
ApiRepositoryResponseRead,
|
||||||
useListRepositoriesQuery,
|
useListRepositoriesQuery,
|
||||||
|
useGetTemplateQuery,
|
||||||
} from '../../../../store/contentSourcesApi';
|
} from '../../../../store/contentSourcesApi';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||||
import {
|
import {
|
||||||
changeCustomRepositories,
|
changeCustomRepositories,
|
||||||
changePayloadRepositories,
|
changePayloadRepositories,
|
||||||
|
changeRedHatRepositories,
|
||||||
selectArchitecture,
|
selectArchitecture,
|
||||||
selectCustomRepositories,
|
selectCustomRepositories,
|
||||||
selectDistribution,
|
selectDistribution,
|
||||||
|
|
@ -47,6 +53,7 @@ import {
|
||||||
selectPackages,
|
selectPackages,
|
||||||
selectPayloadRepositories,
|
selectPayloadRepositories,
|
||||||
selectRecommendedRepositories,
|
selectRecommendedRepositories,
|
||||||
|
selectTemplate,
|
||||||
selectUseLatest,
|
selectUseLatest,
|
||||||
selectWizardMode,
|
selectWizardMode,
|
||||||
} from '../../../../store/wizardSlice';
|
} from '../../../../store/wizardSlice';
|
||||||
|
|
@ -66,6 +73,7 @@ const Repositories = () => {
|
||||||
|
|
||||||
const payloadRepositories = useAppSelector(selectPayloadRepositories);
|
const payloadRepositories = useAppSelector(selectPayloadRepositories);
|
||||||
const recommendedRepos = useAppSelector(selectRecommendedRepositories);
|
const recommendedRepos = useAppSelector(selectRecommendedRepositories);
|
||||||
|
const templateUuid = useAppSelector(selectTemplate);
|
||||||
|
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [reposToRemove, setReposToRemove] = useState<string[]>([]);
|
const [reposToRemove, setReposToRemove] = useState<string[]>([]);
|
||||||
|
|
@ -75,6 +83,7 @@ const Repositories = () => {
|
||||||
const [toggleSelected, setToggleSelected] = useState<
|
const [toggleSelected, setToggleSelected] = useState<
|
||||||
'toggle-group-all' | 'toggle-group-selected'
|
'toggle-group-all' | 'toggle-group-selected'
|
||||||
>('toggle-group-all');
|
>('toggle-group-all');
|
||||||
|
const [isTemplateSelected, setIsTemplateSelected] = useState(false);
|
||||||
|
|
||||||
const debouncedFilterValue = useDebounce(filterValue);
|
const debouncedFilterValue = useDebounce(filterValue);
|
||||||
|
|
||||||
|
|
@ -107,7 +116,7 @@ const Repositories = () => {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
uuid: [...initialSelectedState].join(','),
|
uuid: [...initialSelectedState].join(','),
|
||||||
},
|
},
|
||||||
{ refetchOnMountOrArgChange: false }
|
{ refetchOnMountOrArgChange: false, skip: isTemplateSelected }
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -116,6 +125,10 @@ const Repositories = () => {
|
||||||
}
|
}
|
||||||
}, [selected, toggleSelected]);
|
}, [selected, toggleSelected]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTemplateSelected(templateUuid !== '');
|
||||||
|
}, [templateUuid]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { data: contentList = [], meta: { count } = { count: 0 } } = {},
|
data: { data: contentList = [], meta: { count } = { count: 0 } } = {},
|
||||||
isError,
|
isError,
|
||||||
|
|
@ -136,7 +149,7 @@ const Repositories = () => {
|
||||||
? [...selected].join(',')
|
? [...selected].join(',')
|
||||||
: '',
|
: '',
|
||||||
},
|
},
|
||||||
{ refetchOnMountOrArgChange: 60 }
|
{ refetchOnMountOrArgChange: 60, skip: isTemplateSelected }
|
||||||
);
|
);
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
|
|
@ -389,207 +402,400 @@ const Repositories = () => {
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isError) return <Error />;
|
const {
|
||||||
if (isLoading) return <Loading />;
|
data: selectedTemplateData,
|
||||||
return (
|
isError: isTemplateError,
|
||||||
<Grid>
|
isLoading: isTemplateLoading,
|
||||||
<Modal
|
} = useGetTemplateQuery(
|
||||||
titleIconVariant="warning"
|
{
|
||||||
title="Are you sure?"
|
uuid: templateUuid,
|
||||||
isOpen={modalOpen}
|
},
|
||||||
onClose={onClose}
|
{ refetchOnMountOrArgChange: true, skip: templateUuid === '' }
|
||||||
variant="small"
|
|
||||||
actions={[
|
|
||||||
<Button key="remove" variant="primary" onClick={handleRemoveAnyway}>
|
|
||||||
Remove anyway
|
|
||||||
</Button>,
|
|
||||||
<Button key="back" variant="link" onClick={onClose}>
|
|
||||||
Back
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
You are removing a previously added repository.
|
|
||||||
<br />
|
|
||||||
We do not recommend removing repositories if you have added packages
|
|
||||||
from them.
|
|
||||||
</Modal>
|
|
||||||
{wizardMode === 'edit' && (
|
|
||||||
<Alert
|
|
||||||
title="Removing previously added repositories may lead to issues with selected packages"
|
|
||||||
variant="warning"
|
|
||||||
isPlain
|
|
||||||
isInline
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Toolbar>
|
|
||||||
<ToolbarContent>
|
|
||||||
<ToolbarItem variant="bulk-select">
|
|
||||||
<BulkSelect
|
|
||||||
selected={selected}
|
|
||||||
contentList={contentList}
|
|
||||||
deselectAll={clearSelected}
|
|
||||||
perPage={perPage}
|
|
||||||
handleAddRemove={handleAddRemove}
|
|
||||||
isDisabled={
|
|
||||||
isFetching ||
|
|
||||||
(!selected.size && !contentList.length) ||
|
|
||||||
contentList.every(
|
|
||||||
(repo) =>
|
|
||||||
repo.uuid &&
|
|
||||||
isRepoDisabled(repo, selected.has(repo.uuid))[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ToolbarItem>
|
|
||||||
<ToolbarItem variant="search-filter">
|
|
||||||
<SearchInput
|
|
||||||
aria-label="Search repositories"
|
|
||||||
onChange={handleFilterRepositories}
|
|
||||||
value={filterValue}
|
|
||||||
onClear={() => setFilterValue('')}
|
|
||||||
/>
|
|
||||||
</ToolbarItem>
|
|
||||||
<ToolbarItem>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
isInline
|
|
||||||
onClick={() => refresh()}
|
|
||||||
isLoading={isFetching}
|
|
||||||
>
|
|
||||||
{isFetching ? 'Refreshing' : 'Refresh'}
|
|
||||||
</Button>
|
|
||||||
</ToolbarItem>
|
|
||||||
<ToolbarItem>
|
|
||||||
<ToggleGroup aria-label="Filter repositories list">
|
|
||||||
<ToggleGroupItem
|
|
||||||
text="All"
|
|
||||||
aria-label="All repositories"
|
|
||||||
buttonId="toggle-group-all"
|
|
||||||
isSelected={toggleSelected === 'toggle-group-all'}
|
|
||||||
onChange={() => handleToggleClick('toggle-group-all')}
|
|
||||||
/>
|
|
||||||
<ToggleGroupItem
|
|
||||||
text="Selected"
|
|
||||||
isDisabled={!selected.size}
|
|
||||||
aria-label="Selected repositories"
|
|
||||||
buttonId="toggle-group-selected"
|
|
||||||
isSelected={toggleSelected === 'toggle-group-selected'}
|
|
||||||
onChange={() => handleToggleClick('toggle-group-selected')}
|
|
||||||
/>
|
|
||||||
</ToggleGroup>
|
|
||||||
</ToolbarItem>
|
|
||||||
</ToolbarContent>
|
|
||||||
</Toolbar>
|
|
||||||
<Panel>
|
|
||||||
<PanelMain>
|
|
||||||
{previousReposNowUnavailable ? (
|
|
||||||
<RepositoryUnavailable quantity={previousReposNowUnavailable} />
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
{contentList.length === 0 ? (
|
|
||||||
<Empty hasFilterValue={!!debouncedFilterValue} refetch={refresh} />
|
|
||||||
) : (
|
|
||||||
<Table variant="compact" data-testid="repositories-table">
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th aria-label="Selected" />
|
|
||||||
<Th width={45}>Name</Th>
|
|
||||||
<Th width={15}>Architecture</Th>
|
|
||||||
<Th>Version</Th>
|
|
||||||
<Th width={10}>Packages</Th>
|
|
||||||
<Th>Status</Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>
|
|
||||||
{contentList.map((repo, rowIndex) => {
|
|
||||||
const {
|
|
||||||
uuid = '',
|
|
||||||
url = '',
|
|
||||||
name,
|
|
||||||
status = '',
|
|
||||||
origin = '',
|
|
||||||
distribution_arch,
|
|
||||||
distribution_versions,
|
|
||||||
package_count,
|
|
||||||
last_introspection_time,
|
|
||||||
failed_introspections_count,
|
|
||||||
} = repo;
|
|
||||||
|
|
||||||
const [isDisabled, disabledReason] = isRepoDisabled(
|
|
||||||
repo,
|
|
||||||
selected.has(uuid)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tr
|
|
||||||
key={`${uuid}-${rowIndex}`}
|
|
||||||
data-testid="repositories-row"
|
|
||||||
>
|
|
||||||
<Td
|
|
||||||
select={{
|
|
||||||
isSelected: selected.has(uuid),
|
|
||||||
rowIndex: rowIndex,
|
|
||||||
onSelect: (_, isSelecting) =>
|
|
||||||
handleAddRemove(repo, isSelecting),
|
|
||||||
isDisabled: isDisabled,
|
|
||||||
}}
|
|
||||||
title={disabledReason}
|
|
||||||
/>
|
|
||||||
<Td dataLabel={'Name'}>
|
|
||||||
{name}
|
|
||||||
{origin === ContentOrigin.UPLOAD ? (
|
|
||||||
<UploadRepositoryLabel />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
target="_blank"
|
|
||||||
variant="link"
|
|
||||||
icon={<ExternalLinkAltIcon />}
|
|
||||||
iconPosition="right"
|
|
||||||
isInline
|
|
||||||
href={url}
|
|
||||||
>
|
|
||||||
{url}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
<Td dataLabel={'Architecture'}>
|
|
||||||
{distribution_arch || '-'}
|
|
||||||
</Td>
|
|
||||||
<Td dataLabel={'Version'}>
|
|
||||||
{distribution_versions || '-'}
|
|
||||||
</Td>
|
|
||||||
<Td dataLabel={'Packages'}>{package_count || '-'}</Td>
|
|
||||||
<Td dataLabel={'Status'}>
|
|
||||||
<RepositoriesStatus
|
|
||||||
repoStatus={status || 'Unavailable'}
|
|
||||||
repoUrl={url}
|
|
||||||
repoIntrospections={last_introspection_time}
|
|
||||||
repoFailCount={failed_introspections_count}
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</PanelMain>
|
|
||||||
</Panel>
|
|
||||||
<Pagination
|
|
||||||
itemCount={count ?? PAGINATION_COUNT}
|
|
||||||
perPage={perPage}
|
|
||||||
page={page}
|
|
||||||
onSetPage={(_, newPage) => setPage(newPage)}
|
|
||||||
onPerPageSelect={handlePerPageSelect}
|
|
||||||
variant={PaginationVariant.bottom}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: {
|
||||||
|
data: reposInTemplate = [],
|
||||||
|
meta: { count: reposInTemplateCount } = { count: 0 },
|
||||||
|
} = {},
|
||||||
|
isError: isReposInTemplateError,
|
||||||
|
isLoading: isReposInTemplateLoading,
|
||||||
|
} = useListRepositoriesQuery(
|
||||||
|
{
|
||||||
|
contentType: 'rpm',
|
||||||
|
limit: perPage,
|
||||||
|
offset: perPage * (page - 1),
|
||||||
|
uuid:
|
||||||
|
selectedTemplateData && selectedTemplateData.repository_uuids
|
||||||
|
? selectedTemplateData.repository_uuids?.join(',')
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
{ refetchOnMountOrArgChange: true, skip: !isTemplateSelected }
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isTemplateSelected && reposInTemplate.length > 0) {
|
||||||
|
const customReposInTemplate = reposInTemplate.filter(
|
||||||
|
(repo) => repo.origin !== ContentOrigin.REDHAT
|
||||||
|
);
|
||||||
|
const redHatReposInTemplate = reposInTemplate.filter(
|
||||||
|
(repo) => repo.origin === ContentOrigin.REDHAT
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
changeCustomRepositories(
|
||||||
|
customReposInTemplate.map((repo) =>
|
||||||
|
convertSchemaToIBCustomRepo(repo!)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
changePayloadRepositories(
|
||||||
|
customReposInTemplate.map((repo) =>
|
||||||
|
convertSchemaToIBPayloadRepo(repo!)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
changeRedHatRepositories(
|
||||||
|
redHatReposInTemplate.map((repo) =>
|
||||||
|
convertSchemaToIBPayloadRepo(repo!)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [templateUuid, reposInTemplate]);
|
||||||
|
|
||||||
|
if (isError || isTemplateError || isReposInTemplateError) return <Error />;
|
||||||
|
if (isLoading || isTemplateLoading || isReposInTemplateLoading)
|
||||||
|
return <Loading />;
|
||||||
|
if (!isTemplateSelected) {
|
||||||
|
return (
|
||||||
|
<Grid>
|
||||||
|
<Modal
|
||||||
|
titleIconVariant="warning"
|
||||||
|
title="Are you sure?"
|
||||||
|
isOpen={modalOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
variant="small"
|
||||||
|
actions={[
|
||||||
|
<Button key="remove" variant="primary" onClick={handleRemoveAnyway}>
|
||||||
|
Remove anyway
|
||||||
|
</Button>,
|
||||||
|
<Button key="back" variant="link" onClick={onClose}>
|
||||||
|
Back
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
You are removing a previously added repository.
|
||||||
|
<br />
|
||||||
|
We do not recommend removing repositories if you have added packages
|
||||||
|
from them.
|
||||||
|
</Modal>
|
||||||
|
{wizardMode === 'edit' && (
|
||||||
|
<Alert
|
||||||
|
title="Removing previously added repositories may lead to issues with selected packages"
|
||||||
|
variant="warning"
|
||||||
|
isPlain
|
||||||
|
isInline
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarContent>
|
||||||
|
<ToolbarItem variant="bulk-select">
|
||||||
|
<BulkSelect
|
||||||
|
selected={selected}
|
||||||
|
contentList={contentList}
|
||||||
|
deselectAll={clearSelected}
|
||||||
|
perPage={perPage}
|
||||||
|
handleAddRemove={handleAddRemove}
|
||||||
|
isDisabled={
|
||||||
|
isFetching ||
|
||||||
|
(!selected.size && !contentList.length) ||
|
||||||
|
contentList.every(
|
||||||
|
(repo) =>
|
||||||
|
repo.uuid &&
|
||||||
|
isRepoDisabled(repo, selected.has(repo.uuid))[0]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ToolbarItem>
|
||||||
|
<ToolbarItem variant="search-filter">
|
||||||
|
<SearchInput
|
||||||
|
aria-label="Search repositories"
|
||||||
|
onChange={handleFilterRepositories}
|
||||||
|
value={filterValue}
|
||||||
|
onClear={() => setFilterValue('')}
|
||||||
|
/>
|
||||||
|
</ToolbarItem>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
isInline
|
||||||
|
onClick={() => refresh()}
|
||||||
|
isLoading={isFetching}
|
||||||
|
>
|
||||||
|
{isFetching ? 'Refreshing' : 'Refresh'}
|
||||||
|
</Button>
|
||||||
|
</ToolbarItem>
|
||||||
|
<ToolbarItem>
|
||||||
|
<ToggleGroup aria-label="Filter repositories list">
|
||||||
|
<ToggleGroupItem
|
||||||
|
text="All"
|
||||||
|
aria-label="All repositories"
|
||||||
|
buttonId="toggle-group-all"
|
||||||
|
isSelected={toggleSelected === 'toggle-group-all'}
|
||||||
|
onChange={() => handleToggleClick('toggle-group-all')}
|
||||||
|
/>
|
||||||
|
<ToggleGroupItem
|
||||||
|
text="Selected"
|
||||||
|
isDisabled={!selected.size}
|
||||||
|
aria-label="Selected repositories"
|
||||||
|
buttonId="toggle-group-selected"
|
||||||
|
isSelected={toggleSelected === 'toggle-group-selected'}
|
||||||
|
onChange={() => handleToggleClick('toggle-group-selected')}
|
||||||
|
/>
|
||||||
|
</ToggleGroup>
|
||||||
|
</ToolbarItem>
|
||||||
|
</ToolbarContent>
|
||||||
|
</Toolbar>
|
||||||
|
<Panel>
|
||||||
|
<PanelMain>
|
||||||
|
{previousReposNowUnavailable ? (
|
||||||
|
<RepositoryUnavailable quantity={previousReposNowUnavailable} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{contentList.length === 0 ? (
|
||||||
|
<Empty
|
||||||
|
hasFilterValue={!!debouncedFilterValue}
|
||||||
|
refetch={refresh}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Table variant="compact" data-testid="repositories-table">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th aria-label="Selected" />
|
||||||
|
<Th width={45}>Name</Th>
|
||||||
|
<Th width={15}>Architecture</Th>
|
||||||
|
<Th>Version</Th>
|
||||||
|
<Th width={10}>Packages</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{contentList.map((repo, rowIndex) => {
|
||||||
|
const {
|
||||||
|
uuid = '',
|
||||||
|
url = '',
|
||||||
|
name,
|
||||||
|
status = '',
|
||||||
|
origin = '',
|
||||||
|
distribution_arch,
|
||||||
|
distribution_versions,
|
||||||
|
package_count,
|
||||||
|
last_introspection_time,
|
||||||
|
failed_introspections_count,
|
||||||
|
} = repo;
|
||||||
|
|
||||||
|
const [isDisabled, disabledReason] = isRepoDisabled(
|
||||||
|
repo,
|
||||||
|
selected.has(uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
key={`${uuid}-${rowIndex}`}
|
||||||
|
data-testid="repositories-row"
|
||||||
|
>
|
||||||
|
<Td
|
||||||
|
select={{
|
||||||
|
isSelected: selected.has(uuid),
|
||||||
|
rowIndex: rowIndex,
|
||||||
|
onSelect: (_, isSelecting) =>
|
||||||
|
handleAddRemove(repo, isSelecting),
|
||||||
|
isDisabled: isDisabled,
|
||||||
|
}}
|
||||||
|
title={disabledReason}
|
||||||
|
/>
|
||||||
|
<Td dataLabel={'Name'}>
|
||||||
|
{name}
|
||||||
|
{origin === ContentOrigin.UPLOAD ? (
|
||||||
|
<UploadRepositoryLabel />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
variant="link"
|
||||||
|
icon={<ExternalLinkAltIcon />}
|
||||||
|
iconPosition="right"
|
||||||
|
isInline
|
||||||
|
href={url}
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td dataLabel={'Architecture'}>
|
||||||
|
{distribution_arch || '-'}
|
||||||
|
</Td>
|
||||||
|
<Td dataLabel={'Version'}>
|
||||||
|
{distribution_versions || '-'}
|
||||||
|
</Td>
|
||||||
|
<Td dataLabel={'Packages'}>{package_count || '-'}</Td>
|
||||||
|
<Td dataLabel={'Status'}>
|
||||||
|
<RepositoriesStatus
|
||||||
|
repoStatus={status || 'Unavailable'}
|
||||||
|
repoUrl={url}
|
||||||
|
repoIntrospections={last_introspection_time}
|
||||||
|
repoFailCount={failed_introspections_count}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</PanelMain>
|
||||||
|
</Panel>
|
||||||
|
<Pagination
|
||||||
|
itemCount={count ?? PAGINATION_COUNT}
|
||||||
|
perPage={perPage}
|
||||||
|
page={page}
|
||||||
|
onSetPage={(_, newPage) => setPage(newPage)}
|
||||||
|
onPerPageSelect={handlePerPageSelect}
|
||||||
|
variant={PaginationVariant.bottom}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Alert
|
||||||
|
variant="info"
|
||||||
|
isInline
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
The repositories seen below are from the selected content template
|
||||||
|
and have been added automatically. If you do not want these
|
||||||
|
repositories in your image, you can{' '}
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
variant="link"
|
||||||
|
isInline
|
||||||
|
icon={<ExternalLinkAltIcon />}
|
||||||
|
href={`${TEMPLATES_URL}/${templateUuid}/edit`}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
modify this content template
|
||||||
|
</Button>{' '}
|
||||||
|
or choose another snapshot option.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Grid>
|
||||||
|
<Panel>
|
||||||
|
<PanelMain>
|
||||||
|
<Table variant="compact" data-testid="repositories-table">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th aria-label="Selected" />
|
||||||
|
<Th width={45}>Name</Th>
|
||||||
|
<Th width={15}>Architecture</Th>
|
||||||
|
<Th>Version</Th>
|
||||||
|
<Th width={10}>Packages</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{reposInTemplate.map((repo, rowIndex) => {
|
||||||
|
const {
|
||||||
|
uuid = '',
|
||||||
|
url = '',
|
||||||
|
name,
|
||||||
|
status = '',
|
||||||
|
origin = '',
|
||||||
|
distribution_arch,
|
||||||
|
distribution_versions,
|
||||||
|
package_count,
|
||||||
|
last_introspection_time,
|
||||||
|
failed_introspections_count,
|
||||||
|
} = repo;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
key={`${uuid}-${rowIndex}`}
|
||||||
|
data-testid="repositories-row"
|
||||||
|
>
|
||||||
|
<Td
|
||||||
|
select={{
|
||||||
|
isSelected: true,
|
||||||
|
rowIndex: rowIndex,
|
||||||
|
isDisabled: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Td dataLabel={'Name'}>
|
||||||
|
{name}
|
||||||
|
{origin === ContentOrigin.UPLOAD ? (
|
||||||
|
<UploadRepositoryLabel />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
variant="link"
|
||||||
|
icon={<ExternalLinkAltIcon />}
|
||||||
|
iconPosition="right"
|
||||||
|
isInline
|
||||||
|
href={url}
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td dataLabel={'Architecture'}>
|
||||||
|
{distribution_arch || '-'}
|
||||||
|
</Td>
|
||||||
|
<Td dataLabel={'Version'}>
|
||||||
|
{distribution_versions || '-'}
|
||||||
|
</Td>
|
||||||
|
<Td dataLabel={'Packages'}>{package_count || '-'}</Td>
|
||||||
|
<Td dataLabel={'Status'}>
|
||||||
|
<RepositoriesStatus
|
||||||
|
repoStatus={status || 'Unavailable'}
|
||||||
|
repoUrl={url}
|
||||||
|
repoIntrospections={last_introspection_time}
|
||||||
|
repoFailCount={failed_introspections_count}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</PanelMain>
|
||||||
|
</Panel>
|
||||||
|
<Pagination
|
||||||
|
itemCount={reposInTemplateCount ?? PAGINATION_COUNT}
|
||||||
|
perPage={perPage}
|
||||||
|
page={page}
|
||||||
|
onSetPage={(_, newPage) => setPage(newPage)}
|
||||||
|
onPerPageSelect={handlePerPageSelect}
|
||||||
|
variant={PaginationVariant.bottom}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Repositories;
|
export default Repositories;
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,14 @@ type EmptyProps = {
|
||||||
hasFilterValue: boolean;
|
hasFilterValue: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Empty({ hasFilterValue, refetch }: EmptyProps) {
|
const Empty = ({ hasFilterValue, refetch }: EmptyProps) => {
|
||||||
return (
|
return (
|
||||||
<EmptyState variant={EmptyStateVariant.lg} data-testid="empty-state">
|
<EmptyState variant={EmptyStateVariant.lg} data-testid="empty-state">
|
||||||
<EmptyStateHeader
|
<EmptyStateHeader
|
||||||
titleText={
|
titleText={
|
||||||
hasFilterValue
|
hasFilterValue
|
||||||
? 'No matching repositories found'
|
? 'No matching repositories found'
|
||||||
: 'No Custom Repositories'
|
: 'No custom repositories'
|
||||||
}
|
}
|
||||||
icon={<EmptyStateIcon icon={RepositoryIcon} />}
|
icon={<EmptyStateIcon icon={RepositoryIcon} />}
|
||||||
headingLevel="h4"
|
headingLevel="h4"
|
||||||
|
|
@ -52,4 +52,6 @@ export default function Empty({ hasFilterValue, refetch }: EmptyProps) {
|
||||||
</EmptyStateFooter>
|
</EmptyStateFooter>
|
||||||
</EmptyState>
|
</EmptyState>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Empty;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
|
||||||
import { ContentOrigin } from '../../../../constants';
|
import { ContentOrigin } from '../../../../constants';
|
||||||
import {
|
import {
|
||||||
ApiSnapshotForDate,
|
ApiSnapshotForDate,
|
||||||
|
useGetTemplateQuery,
|
||||||
useListRepositoriesQuery,
|
useListRepositoriesQuery,
|
||||||
} from '../../../../store/contentSourcesApi';
|
} from '../../../../store/contentSourcesApi';
|
||||||
import { useAppSelector } from '../../../../store/hooks';
|
import { useAppSelector } from '../../../../store/hooks';
|
||||||
|
|
@ -24,6 +25,7 @@ import {
|
||||||
selectGroups,
|
selectGroups,
|
||||||
selectPartitions,
|
selectPartitions,
|
||||||
selectRecommendedRepositories,
|
selectRecommendedRepositories,
|
||||||
|
selectTemplate,
|
||||||
} from '../../../../store/wizardSlice';
|
} from '../../../../store/wizardSlice';
|
||||||
import PackageInfoNotAvailablePopover from '../Packages/components/PackageInfoNotAvailablePopover';
|
import PackageInfoNotAvailablePopover from '../Packages/components/PackageInfoNotAvailablePopover';
|
||||||
|
|
||||||
|
|
@ -37,7 +39,7 @@ const RepoName = ({ repoUuid }: repoPropType) => {
|
||||||
// @ts-ignore if repoUrl is undefined the query is going to get skipped, so it's safe to ignore the linter here
|
// @ts-ignore if repoUrl is undefined the query is going to get skipped, so it's safe to ignore the linter here
|
||||||
uuid: repoUuid ?? '',
|
uuid: repoUuid ?? '',
|
||||||
contentType: 'rpm',
|
contentType: 'rpm',
|
||||||
origin: ContentOrigin.CUSTOM,
|
origin: ContentOrigin.ALL,
|
||||||
},
|
},
|
||||||
{ skip: !repoUuid }
|
{ skip: !repoUuid }
|
||||||
);
|
);
|
||||||
|
|
@ -127,8 +129,22 @@ export const SnapshotTable = ({
|
||||||
}: {
|
}: {
|
||||||
snapshotForDate: ApiSnapshotForDate[];
|
snapshotForDate: ApiSnapshotForDate[];
|
||||||
}) => {
|
}) => {
|
||||||
|
const template = useAppSelector(selectTemplate);
|
||||||
|
|
||||||
|
const { data: templateData } = useGetTemplateQuery(
|
||||||
|
{
|
||||||
|
uuid: template,
|
||||||
|
},
|
||||||
|
{ refetchOnMountOrArgChange: true, skip: template === '' }
|
||||||
|
);
|
||||||
|
|
||||||
const { data, isSuccess, isLoading, isError } = useListRepositoriesQuery({
|
const { data, isSuccess, isLoading, isError } = useListRepositoriesQuery({
|
||||||
uuid: snapshotForDate.map(({ repository_uuid }) => repository_uuid).join(),
|
uuid:
|
||||||
|
snapshotForDate.length > 0
|
||||||
|
? snapshotForDate.map(({ repository_uuid }) => repository_uuid).join()
|
||||||
|
: template && templateData && templateData.repository_uuids
|
||||||
|
? templateData.repository_uuids.join(',')
|
||||||
|
: '',
|
||||||
origin: ContentOrigin.REDHAT + ',' + ContentOrigin.CUSTOM, // Make sure to show both redhat and external
|
origin: ContentOrigin.REDHAT + ',' + ContentOrigin.CUSTOM, // Make sure to show both redhat and external
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,10 @@ import {
|
||||||
targetOptions,
|
targetOptions,
|
||||||
UNIT_GIB,
|
UNIT_GIB,
|
||||||
} from '../../../../constants';
|
} from '../../../../constants';
|
||||||
import { useListSnapshotsByDateMutation } from '../../../../store/contentSourcesApi';
|
import {
|
||||||
|
useListSnapshotsByDateMutation,
|
||||||
|
useGetTemplateQuery,
|
||||||
|
} from '../../../../store/contentSourcesApi';
|
||||||
import { useAppSelector } from '../../../../store/hooks';
|
import { useAppSelector } from '../../../../store/hooks';
|
||||||
import { useGetSourceListQuery } from '../../../../store/provisioningApi';
|
import { useGetSourceListQuery } from '../../../../store/provisioningApi';
|
||||||
import { useShowActivationKeyQuery } from '../../../../store/rhsmApi';
|
import { useShowActivationKeyQuery } from '../../../../store/rhsmApi';
|
||||||
|
|
@ -76,6 +79,8 @@ import {
|
||||||
selectFirewall,
|
selectFirewall,
|
||||||
selectServices,
|
selectServices,
|
||||||
selectUsers,
|
selectUsers,
|
||||||
|
selectTemplate,
|
||||||
|
selectRedHatRepositories,
|
||||||
} from '../../../../store/wizardSlice';
|
} from '../../../../store/wizardSlice';
|
||||||
import { toMonthAndYear, yyyyMMddFormat } from '../../../../Utilities/time';
|
import { toMonthAndYear, yyyyMMddFormat } from '../../../../Utilities/time';
|
||||||
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
|
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
|
||||||
|
|
@ -444,6 +449,8 @@ export const ContentList = () => {
|
||||||
const recommendedRepositories = useAppSelector(selectRecommendedRepositories);
|
const recommendedRepositories = useAppSelector(selectRecommendedRepositories);
|
||||||
const snapshotDate = useAppSelector(selectSnapshotDate);
|
const snapshotDate = useAppSelector(selectSnapshotDate);
|
||||||
const useLatest = useAppSelector(selectUseLatest);
|
const useLatest = useAppSelector(selectUseLatest);
|
||||||
|
const template = useAppSelector(selectTemplate);
|
||||||
|
const redHatRepositories = useAppSelector(selectRedHatRepositories);
|
||||||
|
|
||||||
const customAndRecommendedRepositoryUUIDS = useMemo(
|
const customAndRecommendedRepositoryUUIDS = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -458,10 +465,14 @@ export const ContentList = () => {
|
||||||
useListSnapshotsByDateMutation();
|
useListSnapshotsByDateMutation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!snapshotDate && !useLatest) return;
|
||||||
|
|
||||||
listSnapshotsByDate({
|
listSnapshotsByDate({
|
||||||
apiListSnapshotByDateRequest: {
|
apiListSnapshotByDateRequest: {
|
||||||
repository_uuids: customAndRecommendedRepositoryUUIDS,
|
repository_uuids: customAndRecommendedRepositoryUUIDS,
|
||||||
date: useLatest ? yyyyMMddFormat(new Date()) : snapshotDate,
|
date: useLatest
|
||||||
|
? yyyyMMddFormat(new Date()) + 'T00:00:00Z'
|
||||||
|
: snapshotDate,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
|
|
@ -476,22 +487,33 @@ export const ContentList = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const noRepositoriesSelected =
|
const noRepositoriesSelected =
|
||||||
customAndRecommendedRepositoryUUIDS.length === 0;
|
customAndRecommendedRepositoryUUIDS.length === 0 &&
|
||||||
|
redHatRepositories.length === 0;
|
||||||
|
|
||||||
const hasSnapshotDateAfter = data?.data?.some(({ is_after }) => is_after);
|
const hasSnapshotDateAfter = data?.data?.some(({ is_after }) => is_after);
|
||||||
|
|
||||||
|
const { data: templateData, isLoading: isTemplateLoading } =
|
||||||
|
useGetTemplateQuery(
|
||||||
|
{
|
||||||
|
uuid: template,
|
||||||
|
},
|
||||||
|
{ refetchOnMountOrArgChange: true, skip: template === '' }
|
||||||
|
);
|
||||||
|
|
||||||
const snapshottingText = useMemo(() => {
|
const snapshottingText = useMemo(() => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isLoading:
|
case isLoading || isTemplateLoading:
|
||||||
return '';
|
return '';
|
||||||
case useLatest:
|
case useLatest:
|
||||||
return 'Use latest';
|
return 'Use latest';
|
||||||
case !!snapshotDate:
|
case !!snapshotDate:
|
||||||
return `State as of ${yyyyMMddFormat(new Date(snapshotDate))}`;
|
return `State as of ${yyyyMMddFormat(new Date(snapshotDate))}`;
|
||||||
|
case !!template:
|
||||||
|
return `Use a content template: ${templateData?.name}`;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}, [isLoading, useLatest, snapshotDate]);
|
}, [isLoading, isTemplateLoading, useLatest, snapshotDate, template]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -513,6 +535,8 @@ export const ContentList = () => {
|
||||||
headerContent={
|
headerContent={
|
||||||
useLatest
|
useLatest
|
||||||
? 'Use the latest repository content'
|
? 'Use the latest repository content'
|
||||||
|
: template
|
||||||
|
? 'Use content from the content template'
|
||||||
: `Repositories as of ${yyyyMMddFormat(
|
: `Repositories as of ${yyyyMMddFormat(
|
||||||
new Date(snapshotDate)
|
new Date(snapshotDate)
|
||||||
)}`
|
)}`
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -11,14 +11,19 @@ import {
|
||||||
Title,
|
Title,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import Templates from './components/Templates';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||||
import {
|
import {
|
||||||
selectSnapshotDate,
|
selectSnapshotDate,
|
||||||
selectUseLatest,
|
|
||||||
changeUseLatest,
|
changeUseLatest,
|
||||||
changeSnapshotDate,
|
changeSnapshotDate,
|
||||||
|
changeTemplate,
|
||||||
|
selectUseLatest,
|
||||||
|
selectTemplate,
|
||||||
} from '../../../../store/wizardSlice';
|
} from '../../../../store/wizardSlice';
|
||||||
import { yyyyMMddFormat } from '../../../../Utilities/time';
|
import { yyyyMMddFormat } from '../../../../Utilities/time';
|
||||||
|
import { useFlag } from '../../../../Utilities/useGetEnvironment';
|
||||||
import { isSnapshotDateValid } from '../../validators';
|
import { isSnapshotDateValid } from '../../validators';
|
||||||
|
|
||||||
export default function Snapshot() {
|
export default function Snapshot() {
|
||||||
|
|
@ -26,6 +31,34 @@ export default function Snapshot() {
|
||||||
const snapshotDate = useAppSelector(selectSnapshotDate);
|
const snapshotDate = useAppSelector(selectSnapshotDate);
|
||||||
|
|
||||||
const useLatest = useAppSelector(selectUseLatest);
|
const useLatest = useAppSelector(selectUseLatest);
|
||||||
|
const templateUuid = useAppSelector(selectTemplate);
|
||||||
|
const [selectedOption, setSelectedOption] = useState<
|
||||||
|
'latest' | 'snapshotDate' | 'template'
|
||||||
|
>(useLatest ? 'latest' : templateUuid ? 'template' : 'snapshotDate');
|
||||||
|
|
||||||
|
const isTemplatesEnabled = useFlag('image-builder.templates.enabled');
|
||||||
|
|
||||||
|
const handleOptionChange = (
|
||||||
|
option: 'latest' | 'snapshotDate' | 'template'
|
||||||
|
): void => {
|
||||||
|
setSelectedOption(option);
|
||||||
|
switch (option) {
|
||||||
|
case 'latest':
|
||||||
|
dispatch(changeUseLatest(true));
|
||||||
|
dispatch(changeTemplate(''));
|
||||||
|
dispatch(changeSnapshotDate(''));
|
||||||
|
break;
|
||||||
|
case 'snapshotDate':
|
||||||
|
dispatch(changeUseLatest(false));
|
||||||
|
dispatch(changeTemplate(''));
|
||||||
|
break;
|
||||||
|
case 'template':
|
||||||
|
dispatch(changeUseLatest(false));
|
||||||
|
dispatch(changeSnapshotDate(''));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
|
|
@ -35,8 +68,8 @@ export default function Snapshot() {
|
||||||
name="use-latest-snapshot"
|
name="use-latest-snapshot"
|
||||||
label="Disable repeatable build"
|
label="Disable repeatable build"
|
||||||
description="Use the newest repository content available when building this image"
|
description="Use the newest repository content available when building this image"
|
||||||
isChecked={useLatest}
|
isChecked={selectedOption === 'latest'}
|
||||||
onChange={() => !useLatest && dispatch(changeUseLatest(true))}
|
onChange={() => handleOptionChange('latest')}
|
||||||
/>
|
/>
|
||||||
<Radio
|
<Radio
|
||||||
id="use snapshot date radio"
|
id="use snapshot date radio"
|
||||||
|
|
@ -44,11 +77,25 @@ export default function Snapshot() {
|
||||||
name="use-snapshot-date"
|
name="use-snapshot-date"
|
||||||
label="Enable repeatable build"
|
label="Enable repeatable build"
|
||||||
description="Build this image with the repository content of a selected date"
|
description="Build this image with the repository content of a selected date"
|
||||||
isChecked={!useLatest}
|
isChecked={selectedOption === 'snapshotDate'}
|
||||||
onChange={() => useLatest && dispatch(changeUseLatest(false))}
|
onChange={() => handleOptionChange('snapshotDate')}
|
||||||
/>
|
/>
|
||||||
|
{isTemplatesEnabled ? (
|
||||||
|
<Radio
|
||||||
|
id="use content template radio"
|
||||||
|
ouiaId="use-content-template-radio"
|
||||||
|
name="use-content-template"
|
||||||
|
label="Use a content template"
|
||||||
|
description="Select a content template and build this image with repository snapshots included in that template"
|
||||||
|
isChecked={selectedOption === 'template'}
|
||||||
|
onChange={() => handleOptionChange('template')}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
{useLatest ? (
|
|
||||||
|
{selectedOption === 'latest' ? (
|
||||||
<>
|
<>
|
||||||
<Title headingLevel="h1" size="xl">
|
<Title headingLevel="h1" size="xl">
|
||||||
Use latest content
|
Use latest content
|
||||||
|
|
@ -60,7 +107,7 @@ export default function Snapshot() {
|
||||||
</Text>
|
</Text>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : selectedOption === 'snapshotDate' ? (
|
||||||
<>
|
<>
|
||||||
<Title headingLevel="h1" size="xl">
|
<Title headingLevel="h1" size="xl">
|
||||||
Use a snapshot
|
Use a snapshot
|
||||||
|
|
@ -110,6 +157,15 @@ export default function Snapshot() {
|
||||||
</Text>
|
</Text>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
|
) : isTemplatesEnabled && selectedOption === 'template' ? (
|
||||||
|
<>
|
||||||
|
<Title headingLevel="h1" size="xl">
|
||||||
|
Use a content template
|
||||||
|
</Title>
|
||||||
|
<Templates />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
Pagination,
|
||||||
|
PaginationVariant,
|
||||||
|
Panel,
|
||||||
|
PanelMain,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
|
||||||
|
|
||||||
|
import TemplatesEmpty from './TemplatesEmpty';
|
||||||
|
|
||||||
|
import { PAGINATION_COUNT } from '../../../../../constants';
|
||||||
|
import { useListTemplatesQuery } from '../../../../../store/contentSourcesApi';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||||
|
import {
|
||||||
|
selectArchitecture,
|
||||||
|
selectDistribution,
|
||||||
|
selectTemplate,
|
||||||
|
changeTemplate,
|
||||||
|
} from '../../../../../store/wizardSlice';
|
||||||
|
import { releaseToVersion } from '../../../../../Utilities/releaseToVersion';
|
||||||
|
import { Error } from '../../Repositories/components/Error';
|
||||||
|
import { Loading } from '../../Repositories/components/Loading';
|
||||||
|
|
||||||
|
const Templates = () => {
|
||||||
|
const arch = useAppSelector(selectArchitecture);
|
||||||
|
const distribution = useAppSelector(selectDistribution);
|
||||||
|
const version = releaseToVersion(distribution);
|
||||||
|
const [perPage, setPerPage] = useState(10);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const templateUuid = useAppSelector(selectTemplate);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: {
|
||||||
|
data: templateList = [],
|
||||||
|
meta: { count: templateCount } = { count: 0 },
|
||||||
|
} = {},
|
||||||
|
isError,
|
||||||
|
isFetching,
|
||||||
|
isLoading,
|
||||||
|
refetch: refetchTemplates,
|
||||||
|
} = useListTemplatesQuery(
|
||||||
|
{
|
||||||
|
arch: arch,
|
||||||
|
version: version,
|
||||||
|
limit: perPage,
|
||||||
|
offset: perPage * (page - 1),
|
||||||
|
},
|
||||||
|
{ refetchOnMountOrArgChange: 60 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRowSelect = (templateUuid: string | undefined): void => {
|
||||||
|
if (templateUuid) {
|
||||||
|
dispatch(changeTemplate(templateUuid));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePerPageSelect = (
|
||||||
|
_: React.MouseEvent,
|
||||||
|
newPerPage: number,
|
||||||
|
newPage: number
|
||||||
|
) => {
|
||||||
|
setPerPage(newPerPage);
|
||||||
|
setPage(newPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
refetchTemplates();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isError) return <Error />;
|
||||||
|
if (isLoading) return <Loading />;
|
||||||
|
return (
|
||||||
|
<Grid>
|
||||||
|
<Panel>
|
||||||
|
{templateList.length > 0 ? (
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
isInline
|
||||||
|
onClick={() => refresh()}
|
||||||
|
isLoading={isFetching}
|
||||||
|
>
|
||||||
|
{isFetching ? 'Refreshing' : 'Refresh'}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<PanelMain>
|
||||||
|
{templateList.length === 0 ? (
|
||||||
|
<TemplatesEmpty refetch={refresh} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Pagination
|
||||||
|
itemCount={templateCount ?? PAGINATION_COUNT}
|
||||||
|
perPage={perPage}
|
||||||
|
page={page}
|
||||||
|
onSetPage={(_, newPage) => setPage(newPage)}
|
||||||
|
onPerPageSelect={handlePerPageSelect}
|
||||||
|
isCompact
|
||||||
|
/>
|
||||||
|
<Table variant="compact" data-testid="templates-table">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th aria-label="Selected" />
|
||||||
|
<Th width={15}>Name</Th>
|
||||||
|
<Th width={50}>Description</Th>
|
||||||
|
<Th width={15}>Snapshot date</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{templateList.map((template, rowIndex) => {
|
||||||
|
const { uuid, name, description, date, use_latest } =
|
||||||
|
template;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr key={uuid}>
|
||||||
|
<Td
|
||||||
|
select={{
|
||||||
|
variant: 'radio',
|
||||||
|
isSelected: uuid === templateUuid,
|
||||||
|
rowIndex: rowIndex,
|
||||||
|
onSelect: () => handleRowSelect(uuid),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Td dataLabel={'Name'}>{name}</Td>
|
||||||
|
<Td dataLabel={'Description'}>{description}</Td>
|
||||||
|
<Td dataLabel={'Snapshot date'}>
|
||||||
|
{use_latest ? 'Use latest' : date?.split('T')[0]}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
<Pagination
|
||||||
|
itemCount={templateCount ?? PAGINATION_COUNT}
|
||||||
|
perPage={perPage}
|
||||||
|
page={page}
|
||||||
|
onSetPage={(_, newPage) => setPage(newPage)}
|
||||||
|
onPerPageSelect={handlePerPageSelect}
|
||||||
|
variant={PaginationVariant.bottom}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PanelMain>
|
||||||
|
</Panel>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Templates;
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EmptyState,
|
||||||
|
EmptyStateVariant,
|
||||||
|
EmptyStateHeader,
|
||||||
|
EmptyStateBody,
|
||||||
|
EmptyStateFooter,
|
||||||
|
Button,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
import { TEMPLATES_URL } from '../../../../../constants';
|
||||||
|
|
||||||
|
type TemplatesEmptyProps = {
|
||||||
|
refetch: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TemplatesEmpty = ({ refetch }: TemplatesEmptyProps) => {
|
||||||
|
const GoToTemplatesButton = () => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
variant="link"
|
||||||
|
icon={<ExternalLinkAltIcon />}
|
||||||
|
href={TEMPLATES_URL}
|
||||||
|
>
|
||||||
|
Go to content templates
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EmptyState variant={EmptyStateVariant.lg} data-testid="empty-state">
|
||||||
|
<EmptyStateHeader titleText={'No content templates'} headingLevel="h4" />
|
||||||
|
<EmptyStateBody>
|
||||||
|
{`Content templates can be added in the "Templates" area of the
|
||||||
|
console.`}
|
||||||
|
</EmptyStateBody>
|
||||||
|
<EmptyStateFooter>
|
||||||
|
<GoToTemplatesButton />
|
||||||
|
<Button variant="secondary" isInline onClick={() => refetch()}>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</EmptyStateFooter>
|
||||||
|
</EmptyState>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TemplatesEmpty;
|
||||||
|
|
@ -85,6 +85,7 @@ import {
|
||||||
selectUsers,
|
selectUsers,
|
||||||
selectMetadata,
|
selectMetadata,
|
||||||
selectFirewall,
|
selectFirewall,
|
||||||
|
selectTemplate,
|
||||||
selectSatelliteCaCertificate,
|
selectSatelliteCaCertificate,
|
||||||
selectSatelliteRegistrationCommand,
|
selectSatelliteRegistrationCommand,
|
||||||
} from '../../../store/wizardSlice';
|
} from '../../../store/wizardSlice';
|
||||||
|
|
@ -301,13 +302,15 @@ function commonRequestToState(
|
||||||
sourceId: awsUploadOptions?.share_with_sources?.[0],
|
sourceId: awsUploadOptions?.share_with_sources?.[0],
|
||||||
},
|
},
|
||||||
snapshotting: {
|
snapshotting: {
|
||||||
useLatest: !snapshot_date,
|
useLatest: !snapshot_date && !request.image_requests[0]?.content_template,
|
||||||
snapshotDate: snapshot_date,
|
snapshotDate: snapshot_date,
|
||||||
|
template: request.image_requests[0]?.content_template || '',
|
||||||
},
|
},
|
||||||
repositories: {
|
repositories: {
|
||||||
customRepositories: request.customizations?.custom_repositories || [],
|
customRepositories: request.customizations?.custom_repositories || [],
|
||||||
payloadRepositories: request.customizations?.payload_repositories || [],
|
payloadRepositories: request.customizations?.payload_repositories || [],
|
||||||
recommendedRepositories: [],
|
recommendedRepositories: [],
|
||||||
|
redHatRepositories: [],
|
||||||
},
|
},
|
||||||
packages:
|
packages:
|
||||||
request.customizations?.packages
|
request.customizations?.packages
|
||||||
|
|
@ -432,6 +435,7 @@ const getImageRequests = (state: RootState): ImageRequest[] => {
|
||||||
const imageTypes = selectImageTypes(state);
|
const imageTypes = selectImageTypes(state);
|
||||||
const snapshotDate = selectSnapshotDate(state);
|
const snapshotDate = selectSnapshotDate(state);
|
||||||
const useLatest = selectUseLatest(state);
|
const useLatest = selectUseLatest(state);
|
||||||
|
const template = selectTemplate(state);
|
||||||
return imageTypes.map((type) => ({
|
return imageTypes.map((type) => ({
|
||||||
architecture: selectArchitecture(state),
|
architecture: selectArchitecture(state),
|
||||||
image_type: type,
|
image_type: type,
|
||||||
|
|
@ -439,7 +443,8 @@ const getImageRequests = (state: RootState): ImageRequest[] => {
|
||||||
type: uploadTypeByTargetEnv(type),
|
type: uploadTypeByTargetEnv(type),
|
||||||
options: getImageOptions(type, state),
|
options: getImageOptions(type, state),
|
||||||
},
|
},
|
||||||
snapshot_date: useLatest ? undefined : snapshotDate,
|
snapshot_date: !useLatest && !template ? snapshotDate : undefined,
|
||||||
|
content_template: template || undefined,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import {
|
||||||
selectSatelliteRegistrationCommand,
|
selectSatelliteRegistrationCommand,
|
||||||
selectImageTypes,
|
selectImageTypes,
|
||||||
UserWithAdditionalInfo,
|
UserWithAdditionalInfo,
|
||||||
|
selectTemplate,
|
||||||
} from '../../../store/wizardSlice';
|
} from '../../../store/wizardSlice';
|
||||||
import { keyboardsList } from '../steps/Locale/keyboardsList';
|
import { keyboardsList } from '../steps/Locale/keyboardsList';
|
||||||
import { languagesList } from '../steps/Locale/languagesList';
|
import { languagesList } from '../steps/Locale/languagesList';
|
||||||
|
|
@ -226,8 +227,9 @@ export function useFilesystemValidation(): StepValidation {
|
||||||
export function useSnapshotValidation(): StepValidation {
|
export function useSnapshotValidation(): StepValidation {
|
||||||
const snapshotDate = useAppSelector(selectSnapshotDate);
|
const snapshotDate = useAppSelector(selectSnapshotDate);
|
||||||
const useLatest = useAppSelector(selectUseLatest);
|
const useLatest = useAppSelector(selectUseLatest);
|
||||||
|
const template = useAppSelector(selectTemplate);
|
||||||
|
|
||||||
if (!useLatest && !isSnapshotValid(snapshotDate)) {
|
if (!useLatest && !isSnapshotValid(snapshotDate) && template === '') {
|
||||||
return {
|
return {
|
||||||
errors: { snapshotDate: 'Invalid snapshot date' },
|
errors: { snapshotDate: 'Invalid snapshot date' },
|
||||||
disabledNext: true,
|
disabledNext: true,
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ const onPremFlag = (flag: string): boolean => {
|
||||||
case 'image-builder.kernel.enabled':
|
case 'image-builder.kernel.enabled':
|
||||||
case 'image-builder.firewall.enabled':
|
case 'image-builder.firewall.enabled':
|
||||||
case 'image-builder.services.enabled':
|
case 'image-builder.services.enabled':
|
||||||
|
case 'image-builder.templates.enabled':
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export const EDIT_BLUEPRINT = `${IMAGE_BUILDER_API}/blueprints`;
|
||||||
export const CDN_PROD_URL = 'https://cdn.redhat.com/';
|
export const CDN_PROD_URL = 'https://cdn.redhat.com/';
|
||||||
export const CDN_STAGE_URL = 'https://cdn.stage.redhat.com/';
|
export const CDN_STAGE_URL = 'https://cdn.stage.redhat.com/';
|
||||||
export const CONTENT_URL = '/insights/content/repositories';
|
export const CONTENT_URL = '/insights/content/repositories';
|
||||||
|
export const TEMPLATES_URL = '/insights/content/templates';
|
||||||
export const DEVELOPERS_URL = 'https://developers.redhat.com/about';
|
export const DEVELOPERS_URL = 'https://developers.redhat.com/about';
|
||||||
export const FILE_SYSTEM_CUSTOMIZATION_URL =
|
export const FILE_SYSTEM_CUSTOMIZATION_URL =
|
||||||
'https://docs.redhat.com/en/documentation/red_hat_insights/1-latest/html/deploying_and_managing_rhel_systems_in_hybrid_clouds/index#creating-a-blueprint_creating-blueprints-and-blueprint-images';
|
'https://docs.redhat.com/en/documentation/red_hat_insights/1-latest/html/deploying_and_managing_rhel_systems_in_hybrid_clouds/index#creating-a-blueprint_creating-blueprints-and-blueprint-images';
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ export const {
|
||||||
useCreateRepositoryMutation,
|
useCreateRepositoryMutation,
|
||||||
useBulkImportRepositoriesMutation,
|
useBulkImportRepositoriesMutation,
|
||||||
useListRepositoriesRpmsQuery,
|
useListRepositoriesRpmsQuery,
|
||||||
|
useListTemplatesQuery,
|
||||||
|
useGetTemplateQuery,
|
||||||
contentSourcesApi,
|
contentSourcesApi,
|
||||||
} = serviceQueries;
|
} = serviceQueries;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,24 @@ const injectedRtkApi = api.injectEndpoints({
|
||||||
body: queryArg.apiListSnapshotByDateRequest,
|
body: queryArg.apiListSnapshotByDateRequest,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
listTemplates: build.query<ListTemplatesApiResponse, ListTemplatesApiArg>({
|
||||||
|
query: (queryArg) => ({
|
||||||
|
url: `/templates/`,
|
||||||
|
params: {
|
||||||
|
offset: queryArg.offset,
|
||||||
|
limit: queryArg.limit,
|
||||||
|
version: queryArg.version,
|
||||||
|
arch: queryArg.arch,
|
||||||
|
name: queryArg.name,
|
||||||
|
repository_uuids: queryArg.repositoryUuids,
|
||||||
|
snapshot_uuids: queryArg.snapshotUuids,
|
||||||
|
sort_by: queryArg.sortBy,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
getTemplate: build.query<GetTemplateApiResponse, GetTemplateApiArg>({
|
||||||
|
query: (queryArg) => ({ url: `/templates/${queryArg.uuid}` }),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
overrideExisting: false,
|
overrideExisting: false,
|
||||||
});
|
});
|
||||||
|
|
@ -170,6 +188,32 @@ export type ListSnapshotsByDateApiArg = {
|
||||||
/** request body */
|
/** request body */
|
||||||
apiListSnapshotByDateRequest: ApiListSnapshotByDateRequest;
|
apiListSnapshotByDateRequest: ApiListSnapshotByDateRequest;
|
||||||
};
|
};
|
||||||
|
export type ListTemplatesApiResponse =
|
||||||
|
/** status 200 OK */ ApiTemplateCollectionResponseRead;
|
||||||
|
export type ListTemplatesApiArg = {
|
||||||
|
/** Starting point for retrieving a subset of results. Determines how many items to skip from the beginning of the result set. Default value:`0`. */
|
||||||
|
offset?: number;
|
||||||
|
/** Number of items to include in response. Use it to control the number of items, particularly when dealing with large datasets. Default value: `100`. */
|
||||||
|
limit?: number;
|
||||||
|
/** Filter templates by version. */
|
||||||
|
version?: string;
|
||||||
|
/** Filter templates by architecture. */
|
||||||
|
arch?: string;
|
||||||
|
/** Filter templates by name. */
|
||||||
|
name?: string;
|
||||||
|
/** Filter templates by associated repositories using a comma separated list of repository UUIDs */
|
||||||
|
repositoryUuids?: string;
|
||||||
|
/** Filter templates by associated snapshots using a comma separated list of snapshot UUIDs */
|
||||||
|
snapshotUuids?: string;
|
||||||
|
/** Sort the response data based on specific parameters. Sort criteria can include `name`, `arch`, and `version`. */
|
||||||
|
sortBy?: string;
|
||||||
|
};
|
||||||
|
export type GetTemplateApiResponse =
|
||||||
|
/** status 200 OK */ ApiTemplateResponseRead;
|
||||||
|
export type GetTemplateApiArg = {
|
||||||
|
/** Template ID. */
|
||||||
|
uuid: string;
|
||||||
|
};
|
||||||
export type ApiFeature = {
|
export type ApiFeature = {
|
||||||
/** Whether the current user can access the feature */
|
/** Whether the current user can access the feature */
|
||||||
accessible?: boolean | undefined;
|
accessible?: boolean | undefined;
|
||||||
|
|
@ -636,6 +680,91 @@ export type ApiListSnapshotByDateRequest = {
|
||||||
/** Repository UUIDs to find snapshots for */
|
/** Repository UUIDs to find snapshots for */
|
||||||
repository_uuids: string[];
|
repository_uuids: string[];
|
||||||
};
|
};
|
||||||
|
export type ApiTemplateResponse = {
|
||||||
|
/** Architecture of the template */
|
||||||
|
arch?: string | undefined;
|
||||||
|
/** Datetime template was created */
|
||||||
|
created_at?: string | undefined;
|
||||||
|
/** User that created the template */
|
||||||
|
created_by?: string | undefined;
|
||||||
|
/** Latest date to include snapshots for */
|
||||||
|
date?: string | undefined;
|
||||||
|
/** Description of the template */
|
||||||
|
description?: string | undefined;
|
||||||
|
/** Error of last update_latest_snapshot task that updated the template */
|
||||||
|
last_update_snapshot_error?: string | undefined;
|
||||||
|
last_update_task?: ApiTaskInfoResponse | undefined;
|
||||||
|
/** UUID of the last update_template_content task that updated the template */
|
||||||
|
last_update_task_uuid?: string | undefined;
|
||||||
|
/** User that most recently updated the template */
|
||||||
|
last_updated_by?: string | undefined;
|
||||||
|
/** Name of the template */
|
||||||
|
name?: string | undefined;
|
||||||
|
/** Organization ID of the owner */
|
||||||
|
org_id?: string | undefined;
|
||||||
|
/** Repositories added to the template */
|
||||||
|
repository_uuids?: string[] | undefined;
|
||||||
|
/** Environment ID used by subscription-manager and candlepin */
|
||||||
|
rhsm_environment_id?: string | undefined;
|
||||||
|
/** Datetime template was last updated */
|
||||||
|
updated_at?: string | undefined;
|
||||||
|
/** Use latest snapshot for all repositories in the template */
|
||||||
|
use_latest?: boolean | undefined;
|
||||||
|
/** Version of the template */
|
||||||
|
version?: string | undefined;
|
||||||
|
};
|
||||||
|
export type ApiTemplateResponseRead = {
|
||||||
|
/** Architecture of the template */
|
||||||
|
arch?: string | undefined;
|
||||||
|
/** Datetime template was created */
|
||||||
|
created_at?: string | undefined;
|
||||||
|
/** User that created the template */
|
||||||
|
created_by?: string | undefined;
|
||||||
|
/** Latest date to include snapshots for */
|
||||||
|
date?: string | undefined;
|
||||||
|
/** Description of the template */
|
||||||
|
description?: string | undefined;
|
||||||
|
/** Error of last update_latest_snapshot task that updated the template */
|
||||||
|
last_update_snapshot_error?: string | undefined;
|
||||||
|
last_update_task?: ApiTaskInfoResponse | undefined;
|
||||||
|
/** UUID of the last update_template_content task that updated the template */
|
||||||
|
last_update_task_uuid?: string | undefined;
|
||||||
|
/** User that most recently updated the template */
|
||||||
|
last_updated_by?: string | undefined;
|
||||||
|
/** Name of the template */
|
||||||
|
name?: string | undefined;
|
||||||
|
/** Organization ID of the owner */
|
||||||
|
org_id?: string | undefined;
|
||||||
|
/** Repositories added to the template */
|
||||||
|
repository_uuids?: string[] | undefined;
|
||||||
|
/** Whether the candlepin environment is created and systems can be added */
|
||||||
|
rhsm_environment_created?: boolean | undefined;
|
||||||
|
/** Environment ID used by subscription-manager and candlepin */
|
||||||
|
rhsm_environment_id?: string | undefined;
|
||||||
|
/** The list of snapshots in use by the template */
|
||||||
|
snapshots?: ApiSnapshotResponse[] | undefined;
|
||||||
|
/** List of snapshots used by this template which are going to be deleted soon */
|
||||||
|
to_be_deleted_snapshots?: ApiSnapshotResponse[] | undefined;
|
||||||
|
/** Datetime template was last updated */
|
||||||
|
updated_at?: string | undefined;
|
||||||
|
/** Use latest snapshot for all repositories in the template */
|
||||||
|
use_latest?: boolean | undefined;
|
||||||
|
uuid?: string | undefined;
|
||||||
|
/** Version of the template */
|
||||||
|
version?: string | undefined;
|
||||||
|
};
|
||||||
|
export type ApiTemplateCollectionResponse = {
|
||||||
|
/** Requested Data */
|
||||||
|
data?: ApiTemplateResponse[] | undefined;
|
||||||
|
links?: ApiLinks | undefined;
|
||||||
|
meta?: ApiResponseMetadata | undefined;
|
||||||
|
};
|
||||||
|
export type ApiTemplateCollectionResponseRead = {
|
||||||
|
/** Requested Data */
|
||||||
|
data?: ApiTemplateResponseRead[] | undefined;
|
||||||
|
links?: ApiLinks | undefined;
|
||||||
|
meta?: ApiResponseMetadata | undefined;
|
||||||
|
};
|
||||||
export const {
|
export const {
|
||||||
useListFeaturesQuery,
|
useListFeaturesQuery,
|
||||||
useSearchPackageGroupMutation,
|
useSearchPackageGroupMutation,
|
||||||
|
|
@ -645,4 +774,6 @@ export const {
|
||||||
useListRepositoriesRpmsQuery,
|
useListRepositoriesRpmsQuery,
|
||||||
useSearchRpmMutation,
|
useSearchRpmMutation,
|
||||||
useListSnapshotsByDateMutation,
|
useListSnapshotsByDateMutation,
|
||||||
|
useListTemplatesQuery,
|
||||||
|
useGetTemplateQuery,
|
||||||
} = injectedRtkApi;
|
} = injectedRtkApi;
|
||||||
|
|
|
||||||
|
|
@ -549,6 +549,10 @@ export type ImageRequest = {
|
||||||
all, the request will fail. The format must be YYYY-MM-DD (ISO 8601 extended).
|
all, the request will fail. The format must be YYYY-MM-DD (ISO 8601 extended).
|
||||||
*/
|
*/
|
||||||
snapshot_date?: string | undefined;
|
snapshot_date?: string | undefined;
|
||||||
|
/** ID of the content template. A content template and snapshot date cannot both be specified.
|
||||||
|
If a content template is specified, the snapshot date used will be the one from the content template.
|
||||||
|
*/
|
||||||
|
content_template?: string | undefined;
|
||||||
};
|
};
|
||||||
export type Container = {
|
export type Container = {
|
||||||
/** Reference to the container to embed */
|
/** Reference to the container to embed */
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ export type wizardState = {
|
||||||
snapshotting: {
|
snapshotting: {
|
||||||
useLatest: boolean;
|
useLatest: boolean;
|
||||||
snapshotDate: string;
|
snapshotDate: string;
|
||||||
|
template: string;
|
||||||
};
|
};
|
||||||
users: UserWithAdditionalInfo[];
|
users: UserWithAdditionalInfo[];
|
||||||
firstBoot: {
|
firstBoot: {
|
||||||
|
|
@ -137,6 +138,7 @@ export type wizardState = {
|
||||||
customRepositories: CustomRepository[];
|
customRepositories: CustomRepository[];
|
||||||
payloadRepositories: Repository[];
|
payloadRepositories: Repository[];
|
||||||
recommendedRepositories: ApiRepositoryResponseRead[];
|
recommendedRepositories: ApiRepositoryResponseRead[];
|
||||||
|
redHatRepositories: Repository[];
|
||||||
};
|
};
|
||||||
packages: IBPackageWithRepositoryInfo[];
|
packages: IBPackageWithRepositoryInfo[];
|
||||||
groups: GroupWithRepositoryInfo[];
|
groups: GroupWithRepositoryInfo[];
|
||||||
|
|
@ -221,11 +223,13 @@ export const initialState: wizardState = {
|
||||||
snapshotting: {
|
snapshotting: {
|
||||||
useLatest: true,
|
useLatest: true,
|
||||||
snapshotDate: '',
|
snapshotDate: '',
|
||||||
|
template: '',
|
||||||
},
|
},
|
||||||
repositories: {
|
repositories: {
|
||||||
customRepositories: [],
|
customRepositories: [],
|
||||||
payloadRepositories: [],
|
payloadRepositories: [],
|
||||||
recommendedRepositories: [],
|
recommendedRepositories: [],
|
||||||
|
redHatRepositories: [],
|
||||||
},
|
},
|
||||||
packages: [],
|
packages: [],
|
||||||
groups: [],
|
groups: [],
|
||||||
|
|
@ -382,10 +386,15 @@ export const selectPartitions = (state: RootState) => {
|
||||||
export const selectUseLatest = (state: RootState) => {
|
export const selectUseLatest = (state: RootState) => {
|
||||||
return state.wizard.snapshotting.useLatest;
|
return state.wizard.snapshotting.useLatest;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const selectSnapshotDate = (state: RootState) => {
|
export const selectSnapshotDate = (state: RootState) => {
|
||||||
return state.wizard.snapshotting.snapshotDate;
|
return state.wizard.snapshotting.snapshotDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectTemplate = (state: RootState) => {
|
||||||
|
return state.wizard.snapshotting.template;
|
||||||
|
};
|
||||||
|
|
||||||
export const selectCustomRepositories = (state: RootState) => {
|
export const selectCustomRepositories = (state: RootState) => {
|
||||||
return state.wizard.repositories.customRepositories;
|
return state.wizard.repositories.customRepositories;
|
||||||
};
|
};
|
||||||
|
|
@ -398,6 +407,10 @@ export const selectRecommendedRepositories = (state: RootState) => {
|
||||||
return state.wizard.repositories.recommendedRepositories;
|
return state.wizard.repositories.recommendedRepositories;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectRedHatRepositories = (state: RootState) => {
|
||||||
|
return state.wizard.repositories.redHatRepositories;
|
||||||
|
};
|
||||||
|
|
||||||
export const selectPackages = (state: RootState) => {
|
export const selectPackages = (state: RootState) => {
|
||||||
return state.wizard.packages;
|
return state.wizard.packages;
|
||||||
};
|
};
|
||||||
|
|
@ -728,6 +741,9 @@ export const wizardSlice = createSlice({
|
||||||
state.snapshotting.snapshotDate = date.toISOString();
|
state.snapshotting.snapshotDate = date.toISOString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
changeTemplate: (state, action: PayloadAction<string>) => {
|
||||||
|
state.snapshotting.template = action.payload;
|
||||||
|
},
|
||||||
importCustomRepositories: (
|
importCustomRepositories: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<CustomRepository[]>
|
action: PayloadAction<CustomRepository[]>
|
||||||
|
|
@ -746,6 +762,9 @@ export const wizardSlice = createSlice({
|
||||||
changePayloadRepositories: (state, action: PayloadAction<Repository[]>) => {
|
changePayloadRepositories: (state, action: PayloadAction<Repository[]>) => {
|
||||||
state.repositories.payloadRepositories = action.payload;
|
state.repositories.payloadRepositories = action.payload;
|
||||||
},
|
},
|
||||||
|
changeRedHatRepositories: (state, action: PayloadAction<Repository[]>) => {
|
||||||
|
state.repositories.redHatRepositories = action.payload;
|
||||||
|
},
|
||||||
addRecommendedRepository: (
|
addRecommendedRepository: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<ApiRepositoryResponseRead>
|
action: PayloadAction<ApiRepositoryResponseRead>
|
||||||
|
|
@ -1102,6 +1121,7 @@ export const {
|
||||||
changePartitionOrder,
|
changePartitionOrder,
|
||||||
changeUseLatest,
|
changeUseLatest,
|
||||||
changeSnapshotDate,
|
changeSnapshotDate,
|
||||||
|
changeTemplate,
|
||||||
changeCustomRepositories,
|
changeCustomRepositories,
|
||||||
importCustomRepositories,
|
importCustomRepositories,
|
||||||
changePayloadRepositories,
|
changePayloadRepositories,
|
||||||
|
|
@ -1153,5 +1173,6 @@ export const {
|
||||||
setUserAdministratorByIndex,
|
setUserAdministratorByIndex,
|
||||||
addUserGroupByIndex,
|
addUserGroupByIndex,
|
||||||
removeUserGroupByIndex,
|
removeUserGroupByIndex,
|
||||||
|
changeRedHatRepositories,
|
||||||
} = wizardSlice.actions;
|
} = wizardSlice.actions;
|
||||||
export default wizardSlice.reducer;
|
export default wizardSlice.reducer;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@ import {
|
||||||
expectedPayloadRepositories,
|
expectedPayloadRepositories,
|
||||||
snapshotCreateBlueprintRequest,
|
snapshotCreateBlueprintRequest,
|
||||||
} from '../../../../fixtures/editMode';
|
} from '../../../../fixtures/editMode';
|
||||||
import { clickNext, clickReviewAndFinish } from '../../wizardTestUtils';
|
import {
|
||||||
|
clickNext,
|
||||||
|
clickReviewAndFinish,
|
||||||
|
getNextButton,
|
||||||
|
} from '../../wizardTestUtils';
|
||||||
import {
|
import {
|
||||||
blueprintRequest,
|
blueprintRequest,
|
||||||
clickRegisterLater,
|
clickRegisterLater,
|
||||||
|
|
@ -123,6 +127,22 @@ const clickReset = async () => {
|
||||||
await waitFor(async () => user.click(resetButton));
|
await waitFor(async () => user.click(resetButton));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectUseTemplate = async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const templateRadio = await screen.findByRole('radio', {
|
||||||
|
name: /Use a content template/i,
|
||||||
|
});
|
||||||
|
await waitFor(async () => user.click(templateRadio));
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectFirstTemplate = async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const row0Radio = await screen.findByRole('radio', {
|
||||||
|
name: /select row 0/i,
|
||||||
|
});
|
||||||
|
await waitFor(async () => user.click(row0Radio));
|
||||||
|
};
|
||||||
|
|
||||||
describe('repository snapshot tab - ', () => {
|
describe('repository snapshot tab - ', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|
@ -286,6 +306,23 @@ describe('repository snapshot tab - ', () => {
|
||||||
await clickRevisitButton();
|
await clickRevisitButton();
|
||||||
await screen.findByRole('heading', { name: /Custom repositories/i });
|
await screen.findByRole('heading', { name: /Custom repositories/i });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('select use a content template', async () => {
|
||||||
|
await renderCreateMode();
|
||||||
|
await goToSnapshotStep();
|
||||||
|
await selectUseTemplate();
|
||||||
|
const nextBtn = await getNextButton();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nextBtn).toHaveAttribute('aria-disabled', 'true');
|
||||||
|
});
|
||||||
|
await selectFirstTemplate();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nextBtn).toHaveAttribute('aria-disabled', 'false');
|
||||||
|
});
|
||||||
|
await clickNext();
|
||||||
|
await goToReviewStep();
|
||||||
|
await screen.findByText(/Use a content template/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Snapshot edit mode', () => {
|
describe('Snapshot edit mode', () => {
|
||||||
|
|
|
||||||
195
src/test/fixtures/templates.ts
vendored
Normal file
195
src/test/fixtures/templates.ts
vendored
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
import {
|
||||||
|
ApiLinks,
|
||||||
|
ApiResponseMetadata,
|
||||||
|
ApiTemplateResponse,
|
||||||
|
ApiTemplateResponseRead,
|
||||||
|
ListTemplatesApiArg,
|
||||||
|
} from '../../store/contentSourcesApi';
|
||||||
|
|
||||||
|
type templateArgs = {
|
||||||
|
arch: ListTemplatesApiArg['arch'];
|
||||||
|
version: ListTemplatesApiArg['version'];
|
||||||
|
limit: ListTemplatesApiArg['limit'];
|
||||||
|
offset: ListTemplatesApiArg['offset'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockTemplateResults = (request: templateArgs) => {
|
||||||
|
const templates = filterTemplates(request);
|
||||||
|
const limit = request.limit ? request.limit : 100;
|
||||||
|
const data = templates.slice(request.offset, limit);
|
||||||
|
const meta = generateMeta(request.limit, request.offset, templates.length);
|
||||||
|
const links = generateLinks(request.limit, request.offset);
|
||||||
|
const response = {
|
||||||
|
data: data,
|
||||||
|
meta: meta,
|
||||||
|
links: links,
|
||||||
|
};
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterTemplates = (args: templateArgs): ApiTemplateResponse[] => {
|
||||||
|
let templates = testingTemplates;
|
||||||
|
|
||||||
|
if (args.arch) {
|
||||||
|
templates = templates.filter((template) => template.arch === args.arch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.version) {
|
||||||
|
templates = templates.filter(
|
||||||
|
(template) => template.version === args.version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return templates;
|
||||||
|
};
|
||||||
|
|
||||||
|
const testingTemplates: ApiTemplateResponseRead[] = [
|
||||||
|
{
|
||||||
|
uuid: 'c40e221b-93d6-4f7e-a704-f3041b8d75c3',
|
||||||
|
name: 'template-abc',
|
||||||
|
org_id: '13476545',
|
||||||
|
description: 'description-abc',
|
||||||
|
arch: 'x86_64',
|
||||||
|
version: '9',
|
||||||
|
date: '0001-01-01T00:00:00Z',
|
||||||
|
repository_uuids: [
|
||||||
|
'828e7db8-c0d4-48fc-a887-9070e0e75c45',
|
||||||
|
'ae39f556-6986-478a-95d1-f9c7e33d066c',
|
||||||
|
],
|
||||||
|
snapshots: [
|
||||||
|
{
|
||||||
|
uuid: '90302927-848a-4fa9-ba44-c58bb162a009',
|
||||||
|
created_at: '2025-02-27T16:23:59.148649Z',
|
||||||
|
repository_path: 'test/snapshot1',
|
||||||
|
content_counts: {
|
||||||
|
'rpm.advisory': 5,
|
||||||
|
'rpm.package': 5,
|
||||||
|
'rpm.repo_metadata_file': 1,
|
||||||
|
},
|
||||||
|
added_counts: {
|
||||||
|
'rpm.advisory': 5,
|
||||||
|
'rpm.package': 5,
|
||||||
|
'rpm.repo_metadata_file': 1,
|
||||||
|
},
|
||||||
|
removed_counts: {},
|
||||||
|
url: 'http://test.com/test/snapshot1/',
|
||||||
|
repository_name: '2zmya',
|
||||||
|
repository_uuid: '828e7db8-c0d4-48fc-a887-9070e0e75c45',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uuid: '80303926-948a-4fa8-ba44-c59bb162a008',
|
||||||
|
created_at: '2025-02-27T16:23:59.148649Z',
|
||||||
|
repository_path: 'test/snapshot2',
|
||||||
|
content_counts: {
|
||||||
|
'rpm.advisory': 5,
|
||||||
|
'rpm.package': 5,
|
||||||
|
'rpm.repo_metadata_file': 1,
|
||||||
|
},
|
||||||
|
added_counts: {
|
||||||
|
'rpm.advisory': 5,
|
||||||
|
'rpm.package': 5,
|
||||||
|
'rpm.repo_metadata_file': 1,
|
||||||
|
},
|
||||||
|
removed_counts: {},
|
||||||
|
url: 'http://test.com/test/snapshot2/',
|
||||||
|
repository_name: '01-test-valid-repo',
|
||||||
|
repository_uuid: 'ae39f556-6986-478a-95d1-f9c7e33d066c',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rhsm_environment_id: '4202ed8d725e46079cc7454a64b69093',
|
||||||
|
created_by: 'test',
|
||||||
|
last_updated_by: 'test',
|
||||||
|
created_at: '2025-02-28T17:34:33.598161Z',
|
||||||
|
updated_at: '2025-02-28T17:34:33.598161Z',
|
||||||
|
use_latest: true,
|
||||||
|
last_update_snapshot_error: '',
|
||||||
|
last_update_task_uuid: '9aa99713-65d1-4057-908e-96150573a22f',
|
||||||
|
last_update_task: {
|
||||||
|
uuid: '9aa99713-65d1-4057-908e-96150573a22f',
|
||||||
|
status: 'completed',
|
||||||
|
created_at: '2025-02-28T17:34:33Z',
|
||||||
|
ended_at: '2025-02-28T17:34:34Z',
|
||||||
|
error: '',
|
||||||
|
org_id: '13476545',
|
||||||
|
type: 'update-template-content',
|
||||||
|
object_type: 'template',
|
||||||
|
object_name: 'template-abc',
|
||||||
|
object_uuid: 'c40e221b-93d6-4f7e-a704-f3041b8d75c3',
|
||||||
|
},
|
||||||
|
rhsm_environment_created: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uuid: '4202ed8d-725e-4607-9cc7-454a64b69093',
|
||||||
|
name: 'template-xyz',
|
||||||
|
org_id: '13476545',
|
||||||
|
description: 'description-xyz',
|
||||||
|
arch: 'x86_64',
|
||||||
|
version: '9',
|
||||||
|
date: '2025-02-28T05:00:00Z',
|
||||||
|
repository_uuids: ['828e7db8-c0d4-48fc-a887-9070e0e75c45'],
|
||||||
|
snapshots: [
|
||||||
|
{
|
||||||
|
uuid: '90302927-848a-4fa9-ba44-c58bb162a009',
|
||||||
|
created_at: '2025-02-27T16:23:59.148649Z',
|
||||||
|
repository_path: 'test/snapshot1',
|
||||||
|
content_counts: {
|
||||||
|
'rpm.advisory': 5,
|
||||||
|
'rpm.package': 5,
|
||||||
|
'rpm.repo_metadata_file': 1,
|
||||||
|
},
|
||||||
|
added_counts: {
|
||||||
|
'rpm.advisory': 5,
|
||||||
|
'rpm.package': 5,
|
||||||
|
'rpm.repo_metadata_file': 1,
|
||||||
|
},
|
||||||
|
removed_counts: {},
|
||||||
|
url: 'http://test.com/test/snapshot1/',
|
||||||
|
repository_name: '2zmya',
|
||||||
|
repository_uuid: '828e7db8-c0d4-48fc-a887-9070e0e75c45',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rhsm_environment_id: '4202ed8d725e46079cc7454a64b69093',
|
||||||
|
created_by: 'test',
|
||||||
|
last_updated_by: 'test',
|
||||||
|
created_at: '2025-02-28T18:35:34.792223Z',
|
||||||
|
updated_at: '2025-02-28T18:35:34.792223Z',
|
||||||
|
use_latest: false,
|
||||||
|
last_update_snapshot_error: '',
|
||||||
|
last_update_task_uuid: '8bn99713-65d1-4057-908e-96150573a22f',
|
||||||
|
last_update_task: {
|
||||||
|
uuid: '8bn99713-65d1-4057-908e-96150573a22f',
|
||||||
|
status: 'completed',
|
||||||
|
created_at: '2025-02-28T17:34:33Z',
|
||||||
|
ended_at: '2025-02-28T17:34:34Z',
|
||||||
|
error: '',
|
||||||
|
org_id: '13476545',
|
||||||
|
type: 'update-template-content',
|
||||||
|
object_type: 'template',
|
||||||
|
object_name: 'template-xyz',
|
||||||
|
object_uuid: '4202ed8d-725e-4607-9cc7-454a64b69093',
|
||||||
|
},
|
||||||
|
rhsm_environment_created: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const generateMeta = (
|
||||||
|
limit: ApiResponseMetadata['limit'],
|
||||||
|
offset: ApiResponseMetadata['offset'],
|
||||||
|
count: ApiResponseMetadata['count']
|
||||||
|
): ApiResponseMetadata => {
|
||||||
|
return {
|
||||||
|
limit: limit,
|
||||||
|
offset: offset,
|
||||||
|
count: count,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateLinks = (
|
||||||
|
limit: ApiResponseMetadata['limit'],
|
||||||
|
offset: ApiResponseMetadata['offset']
|
||||||
|
): ApiLinks => {
|
||||||
|
return {
|
||||||
|
first: `/api/content-sources/v1/templates/?limit=${limit}&offset=${offset}`,
|
||||||
|
last: `/api/content-sources/v1/templates/?limit=${limit}&offset=${offset}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -43,6 +43,7 @@ import {
|
||||||
mockRepositoryResults,
|
mockRepositoryResults,
|
||||||
} from '../fixtures/repositories';
|
} from '../fixtures/repositories';
|
||||||
import { mockSourcesByProvider, mockUploadInfo } from '../fixtures/sources';
|
import { mockSourcesByProvider, mockUploadInfo } from '../fixtures/sources';
|
||||||
|
import { mockTemplateResults } from '../fixtures/templates';
|
||||||
|
|
||||||
export const handlers = [
|
export const handlers = [
|
||||||
http.get(`${PROVISIONING_API}/sources`, ({ request }) => {
|
http.get(`${PROVISIONING_API}/sources`, ({ request }) => {
|
||||||
|
|
@ -101,15 +102,31 @@ export const handlers = [
|
||||||
const limit = url.searchParams.get('limit');
|
const limit = url.searchParams.get('limit');
|
||||||
const offset = url.searchParams.get('offset');
|
const offset = url.searchParams.get('offset');
|
||||||
const search = url.searchParams.get('search');
|
const search = url.searchParams.get('search');
|
||||||
|
const uuid = url.searchParams.get('uuid');
|
||||||
const args = {
|
const args = {
|
||||||
available_for_arch,
|
available_for_arch,
|
||||||
available_for_version,
|
available_for_version,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
search,
|
search,
|
||||||
|
uuid,
|
||||||
};
|
};
|
||||||
return HttpResponse.json(mockRepositoryResults(args));
|
return HttpResponse.json(mockRepositoryResults(args));
|
||||||
}),
|
}),
|
||||||
|
http.get(`${CONTENT_SOURCES_API}/templates/`, ({ request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const arch = url.searchParams.get('arch');
|
||||||
|
const version = url.searchParams.get('version');
|
||||||
|
const limit = url.searchParams.get('limit');
|
||||||
|
const offset = url.searchParams.get('offset');
|
||||||
|
const args = {
|
||||||
|
arch,
|
||||||
|
version,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
};
|
||||||
|
return HttpResponse.json(mockTemplateResults(args));
|
||||||
|
}),
|
||||||
http.get(`${CONTENT_SOURCES_API}/repositories/:repo_id`, ({ params }) => {
|
http.get(`${CONTENT_SOURCES_API}/repositories/:repo_id`, ({ params }) => {
|
||||||
const { repo_id } = params;
|
const { repo_id } = params;
|
||||||
return HttpResponse.json(mockPopularRepo(repo_id));
|
return HttpResponse.json(mockPopularRepo(repo_id));
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ vi.mock('@unleash/proxy-client-react', () => ({
|
||||||
return true;
|
return true;
|
||||||
case 'image-builder.satellite.enabled':
|
case 'image-builder.satellite.enabled':
|
||||||
return true;
|
return true;
|
||||||
|
case 'image-builder.templates.enabled':
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue