update style across the project
The eslint updates require style changes in all components.
This commit is contained in:
parent
7959f2a563
commit
4fa71cede8
56 changed files with 5973 additions and 5177 deletions
|
|
@ -3,35 +3,35 @@ import PropTypes from 'prop-types';
|
|||
import { Alert } from '@patternfly/react-core';
|
||||
|
||||
const useGetErrorReason = (err) => {
|
||||
if (!err?.reason) {
|
||||
return 'An unknown error occured';
|
||||
}
|
||||
if (!err?.reason) {
|
||||
return 'An unknown error occured';
|
||||
}
|
||||
|
||||
if (err.details?.reason) {
|
||||
return err.details.reason;
|
||||
}
|
||||
if (err.details?.reason) {
|
||||
return err.details.reason;
|
||||
}
|
||||
|
||||
return err.reason;
|
||||
return err.reason;
|
||||
};
|
||||
|
||||
const ErrorDetails = ({ status }) => {
|
||||
if (!status || status.status !== 'failure') {
|
||||
return <></>;
|
||||
}
|
||||
if (!status || status.status !== 'failure') {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const reason = useGetErrorReason(status.error);
|
||||
const reason = useGetErrorReason(status.error);
|
||||
|
||||
return (
|
||||
<div className='pf-u-mt-sm'>
|
||||
<strong>Status</strong>
|
||||
<Alert variant="danger" title="Image build failed" isInline isPlain />
|
||||
<p className='pf-u-danger-color-200 pf-u-w-33-on-md'>{reason}</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="pf-u-mt-sm">
|
||||
<strong>Status</strong>
|
||||
<Alert variant="danger" title="Image build failed" isInline isPlain />
|
||||
<p className="pf-u-danger-color-200 pf-u-w-33-on-md">{reason}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorDetails.propTypes = {
|
||||
status: PropTypes.object,
|
||||
status: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ErrorDetails;
|
||||
|
|
|
|||
|
|
@ -2,72 +2,76 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Flex } from '@patternfly/react-core';
|
||||
import { CheckCircleIcon, PendingIcon, ExclamationCircleIcon, InProgressIcon } from '@patternfly/react-icons';
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
PendingIcon,
|
||||
ExclamationCircleIcon,
|
||||
InProgressIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
|
||||
import './ImageBuildStatus.scss';
|
||||
|
||||
const ImageBuildStatus = (props) => {
|
||||
const messages = {
|
||||
success: [
|
||||
{
|
||||
icon: <CheckCircleIcon className="success" />,
|
||||
text: 'Ready'
|
||||
}
|
||||
],
|
||||
failure: [
|
||||
{
|
||||
icon: <ExclamationCircleIcon className="error" />,
|
||||
text: 'Image build failed'
|
||||
}
|
||||
],
|
||||
pending: [
|
||||
{
|
||||
icon: <PendingIcon />,
|
||||
text: 'Image build is pending'
|
||||
}
|
||||
],
|
||||
// Keep "running" for backward compatibility
|
||||
running: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Image build in progress'
|
||||
}
|
||||
],
|
||||
building: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Image build in progress'
|
||||
}
|
||||
],
|
||||
uploading: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Image upload in progress'
|
||||
}
|
||||
],
|
||||
registering: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Cloud registration in progress'
|
||||
}
|
||||
]
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
{messages[props.status] &&
|
||||
messages[props.status].map((message, key) => (
|
||||
<Flex key={ key } className="pf-u-align-items-baseline pf-m-nowrap">
|
||||
<div className="pf-u-mr-sm">{message.icon}</div>
|
||||
<small>{message.text}</small>
|
||||
</Flex>
|
||||
))
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
const messages = {
|
||||
success: [
|
||||
{
|
||||
icon: <CheckCircleIcon className="success" />,
|
||||
text: 'Ready',
|
||||
},
|
||||
],
|
||||
failure: [
|
||||
{
|
||||
icon: <ExclamationCircleIcon className="error" />,
|
||||
text: 'Image build failed',
|
||||
},
|
||||
],
|
||||
pending: [
|
||||
{
|
||||
icon: <PendingIcon />,
|
||||
text: 'Image build is pending',
|
||||
},
|
||||
],
|
||||
// Keep "running" for backward compatibility
|
||||
running: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Image build in progress',
|
||||
},
|
||||
],
|
||||
building: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Image build in progress',
|
||||
},
|
||||
],
|
||||
uploading: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Image upload in progress',
|
||||
},
|
||||
],
|
||||
registering: [
|
||||
{
|
||||
icon: <InProgressIcon className="pending" />,
|
||||
text: 'Cloud registration in progress',
|
||||
},
|
||||
],
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
{messages[props.status] &&
|
||||
messages[props.status].map((message, key) => (
|
||||
<Flex key={key} className="pf-u-align-items-baseline pf-m-nowrap">
|
||||
<div className="pf-u-mr-sm">{message.icon}</div>
|
||||
<small>{message.text}</small>
|
||||
</Flex>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ImageBuildStatus.propTypes = {
|
||||
status: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ImageBuildStatus;
|
||||
|
|
|
|||
|
|
@ -1,112 +1,129 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button, TextContent, Text, TextVariants, Popover } from '@patternfly/react-core';
|
||||
import {
|
||||
Button,
|
||||
TextContent,
|
||||
Text,
|
||||
TextVariants,
|
||||
Popover,
|
||||
} from '@patternfly/react-core';
|
||||
import { DownloadIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
|
||||
const ImageLink = (props) => {
|
||||
const fileExtensions = {
|
||||
vsphere: '.vmdk',
|
||||
'guest-image': '.qcow2',
|
||||
'image-installer': '.iso',
|
||||
};
|
||||
const fileExtensions = {
|
||||
vsphere: '.vmdk',
|
||||
'guest-image': '.qcow2',
|
||||
'image-installer': '.iso',
|
||||
};
|
||||
|
||||
const uploadStatus = props.imageStatus ? props.imageStatus.upload_status : undefined;
|
||||
if (uploadStatus) {
|
||||
if (uploadStatus.type === 'aws') {
|
||||
const url = 'https://console.aws.amazon.com/ec2/v2/home?region=' +
|
||||
uploadStatus.options.region +
|
||||
'#LaunchInstanceWizard:ami=' +
|
||||
uploadStatus.options.ami;
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={ <ExternalLinkAltIcon /> }
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={ url }>
|
||||
Launch instance
|
||||
</Button>
|
||||
);
|
||||
} else if (uploadStatus.type === 'azure') {
|
||||
const url = 'https://portal.azure.com/#@' + props.uploadOptions.tenant_id +
|
||||
'/resource/subscriptions/' + props.uploadOptions.subscription_id +
|
||||
'/resourceGroups/' + props.uploadOptions.resource_group +
|
||||
'/providers/Microsoft.Compute/images/' + uploadStatus.options.image_name;
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={ <ExternalLinkAltIcon /> }
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={ url }>
|
||||
View uploaded image
|
||||
</Button>
|
||||
);
|
||||
} else if (uploadStatus.type === 'gcp') {
|
||||
return (
|
||||
<Popover
|
||||
aria-label="Popover with google cloud platform image details"
|
||||
maxWidth='30rem'
|
||||
headerContent={ 'GCP image details' }
|
||||
bodyContent={ <TextContent>
|
||||
<Text component={ TextVariants.p }>
|
||||
To use an Image Builder created Google Cloud Platform (GCP) image in your project,
|
||||
specify the project ID and image name in your templates and configurations.
|
||||
</Text>
|
||||
<Text>
|
||||
<strong>Project ID</strong>
|
||||
<br />
|
||||
{uploadStatus.options.project_id}
|
||||
</Text>
|
||||
<Text>
|
||||
<strong>Image Name</strong>
|
||||
<br />
|
||||
{uploadStatus.options.image_name}
|
||||
</Text>
|
||||
<Text>
|
||||
<strong>Shared with</strong>
|
||||
<br />
|
||||
{/* the account the image is shared with is stored in the form type:account so this extracts the account */}
|
||||
{props.uploadOptions.share_with_accounts[0].split(':')[1]}
|
||||
</Text>
|
||||
</TextContent> }>
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
isInline>
|
||||
Image details
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
} else if (uploadStatus.type === 'aws.s3') {
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={ <DownloadIcon /> }
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={ uploadStatus.options.url }>
|
||||
Download {fileExtensions[props.imageType]}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
const uploadStatus = props.imageStatus
|
||||
? props.imageStatus.upload_status
|
||||
: undefined;
|
||||
if (uploadStatus) {
|
||||
if (uploadStatus.type === 'aws') {
|
||||
const url =
|
||||
'https://console.aws.amazon.com/ec2/v2/home?region=' +
|
||||
uploadStatus.options.region +
|
||||
'#LaunchInstanceWizard:ami=' +
|
||||
uploadStatus.options.ami;
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={url}
|
||||
>
|
||||
Launch instance
|
||||
</Button>
|
||||
);
|
||||
} else if (uploadStatus.type === 'azure') {
|
||||
const url =
|
||||
'https://portal.azure.com/#@' +
|
||||
props.uploadOptions.tenant_id +
|
||||
'/resource/subscriptions/' +
|
||||
props.uploadOptions.subscription_id +
|
||||
'/resourceGroups/' +
|
||||
props.uploadOptions.resource_group +
|
||||
'/providers/Microsoft.Compute/images/' +
|
||||
uploadStatus.options.image_name;
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={url}
|
||||
>
|
||||
View uploaded image
|
||||
</Button>
|
||||
);
|
||||
} else if (uploadStatus.type === 'gcp') {
|
||||
return (
|
||||
<Popover
|
||||
aria-label="Popover with google cloud platform image details"
|
||||
maxWidth="30rem"
|
||||
headerContent={'GCP image details'}
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text component={TextVariants.p}>
|
||||
To use an Image Builder created Google Cloud Platform (GCP)
|
||||
image in your project, specify the project ID and image name in
|
||||
your templates and configurations.
|
||||
</Text>
|
||||
<Text>
|
||||
<strong>Project ID</strong>
|
||||
<br />
|
||||
{uploadStatus.options.project_id}
|
||||
</Text>
|
||||
<Text>
|
||||
<strong>Image Name</strong>
|
||||
<br />
|
||||
{uploadStatus.options.image_name}
|
||||
</Text>
|
||||
<Text>
|
||||
<strong>Shared with</strong>
|
||||
<br />
|
||||
{/* the account the image is shared with is stored in the form type:account so this extracts the account */}
|
||||
{props.uploadOptions.share_with_accounts[0].split(':')[1]}
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button component="a" target="_blank" variant="link" isInline>
|
||||
Image details
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
} else if (uploadStatus.type === 'aws.s3') {
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<DownloadIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={uploadStatus.options.url}
|
||||
>
|
||||
Download {fileExtensions[props.imageType]}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
};
|
||||
|
||||
ImageLink.propTypes = {
|
||||
imageStatus: PropTypes.object,
|
||||
imageType: PropTypes.string,
|
||||
uploadOptions: PropTypes.object,
|
||||
imageStatus: PropTypes.object,
|
||||
imageType: PropTypes.string,
|
||||
uploadOptions: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ImageLink;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,28 @@ import PropTypes from 'prop-types';
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { TableComposable, Thead, Tr, Th, Tbody, Td, ActionsColumn, ExpandableRowContent } from '@patternfly/react-table';
|
||||
import { EmptyState, EmptyStateVariant, EmptyStateIcon, EmptyStateBody, EmptyStateSecondaryActions,
|
||||
Pagination,
|
||||
Toolbar, ToolbarContent, ToolbarItem,
|
||||
Title } from '@patternfly/react-core';
|
||||
import {
|
||||
TableComposable,
|
||||
Thead,
|
||||
Tr,
|
||||
Th,
|
||||
Tbody,
|
||||
Td,
|
||||
ActionsColumn,
|
||||
ExpandableRowContent,
|
||||
} from '@patternfly/react-table';
|
||||
import {
|
||||
EmptyState,
|
||||
EmptyStateVariant,
|
||||
EmptyStateIcon,
|
||||
EmptyStateBody,
|
||||
EmptyStateSecondaryActions,
|
||||
Pagination,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarItem,
|
||||
Title,
|
||||
} from '@patternfly/react-core';
|
||||
import { PlusCircleIcon } from '@patternfly/react-icons';
|
||||
import './ImagesTable.scss';
|
||||
import { composesGet, composeGetStatus } from '../../store/actions/actions';
|
||||
|
|
@ -18,196 +35,255 @@ import ImageLink from './ImageLink';
|
|||
import ErrorDetails from './ImageBuildErrorDetails';
|
||||
|
||||
const ImagesTable = () => {
|
||||
const [ page, setPage ] = useState(1);
|
||||
const [ perPage, setPerPage ] = useState(10);
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(10);
|
||||
|
||||
const [ expandedComposeIds, setExpandedComposeIds ] = useState([]);
|
||||
const isExpanded = compose => expandedComposeIds.includes(compose.id);
|
||||
const [expandedComposeIds, setExpandedComposeIds] = useState([]);
|
||||
const isExpanded = (compose) => expandedComposeIds.includes(compose.id);
|
||||
|
||||
const handleToggle = (compose, isExpanding) => {
|
||||
if (isExpanding) {
|
||||
setExpandedComposeIds([ ...expandedComposeIds, compose.id ]);
|
||||
} else {
|
||||
setExpandedComposeIds(expandedComposeIds.filter(id => id !== compose.id));
|
||||
}};
|
||||
const handleToggle = (compose, isExpanding) => {
|
||||
if (isExpanding) {
|
||||
setExpandedComposeIds([...expandedComposeIds, compose.id]);
|
||||
} else {
|
||||
setExpandedComposeIds(
|
||||
expandedComposeIds.filter((id) => id !== compose.id)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const composes = useSelector((state) => state.composes);
|
||||
const dispatch = useDispatch();
|
||||
const composes = useSelector((state) => state.composes);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const pollComposeStatuses = () => {
|
||||
Object.entries(composes.byId).map(([ id, compose ]) => {
|
||||
/* Skip composes that have been complete */
|
||||
if (compose.image_status.status === 'success' || compose.image_status.status === 'failure') {
|
||||
return;
|
||||
}
|
||||
const pollComposeStatuses = () => {
|
||||
Object.entries(composes.byId).map(([id, compose]) => {
|
||||
/* Skip composes that have been complete */
|
||||
if (
|
||||
compose.image_status.status === 'success' ||
|
||||
compose.image_status.status === 'failure'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(composeGetStatus(id));
|
||||
});
|
||||
};
|
||||
dispatch(composeGetStatus(id));
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(composesGet(perPage, 0));
|
||||
const intervalId = setInterval(() => pollComposeStatuses(), 8000);
|
||||
useEffect(() => {
|
||||
dispatch(composesGet(perPage, 0));
|
||||
const intervalId = setInterval(() => pollComposeStatuses(), 8000);
|
||||
|
||||
// clean up interval on unmount
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
// clean up interval on unmount
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const onSetPage = (_, page) => {
|
||||
// if the next page's composes haven't been fetched from api yet
|
||||
// then fetch them with proper page index and offset
|
||||
if (composes.count > composes.allIds.length) {
|
||||
const pageIndex = page - 1;
|
||||
const offset = pageIndex * perPage;
|
||||
dispatch(composesGet(perPage, offset));
|
||||
}
|
||||
const onSetPage = (_, page) => {
|
||||
// if the next page's composes haven't been fetched from api yet
|
||||
// then fetch them with proper page index and offset
|
||||
if (composes.count > composes.allIds.length) {
|
||||
const pageIndex = page - 1;
|
||||
const offset = pageIndex * perPage;
|
||||
dispatch(composesGet(perPage, offset));
|
||||
}
|
||||
|
||||
setPage(page);
|
||||
};
|
||||
setPage(page);
|
||||
};
|
||||
|
||||
const onPerPageSelect = (_, perPage) => {
|
||||
// if the new per page quantity is greater than the number of already fetched composes fetch more composes
|
||||
// if all composes haven't already been fetched
|
||||
if (composes.count > composes.allIds.length && perPage > composes.allIds.length) {
|
||||
dispatch(composesGet(perPage, 0));
|
||||
}
|
||||
const onPerPageSelect = (_, perPage) => {
|
||||
// if the new per page quantity is greater than the number of already fetched composes fetch more composes
|
||||
// if all composes haven't already been fetched
|
||||
if (
|
||||
composes.count > composes.allIds.length &&
|
||||
perPage > composes.allIds.length
|
||||
) {
|
||||
dispatch(composesGet(perPage, 0));
|
||||
}
|
||||
|
||||
// page should be reset to the first page when the page size is changed.
|
||||
setPerPage(perPage);
|
||||
setPage(1);
|
||||
};
|
||||
// page should be reset to the first page when the page size is changed.
|
||||
setPerPage(perPage);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const timestampToDisplayString = (ts) => {
|
||||
// timestamp has format 2021-04-27 12:31:12.794809 +0000 UTC
|
||||
// must be converted to ms timestamp and then reformatted to Apr 27, 2021
|
||||
if (!ts) {
|
||||
return '';
|
||||
}
|
||||
const timestampToDisplayString = (ts) => {
|
||||
// timestamp has format 2021-04-27 12:31:12.794809 +0000 UTC
|
||||
// must be converted to ms timestamp and then reformatted to Apr 27, 2021
|
||||
if (!ts) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// get YYYY-MM-DD format
|
||||
const date = ts.slice(0, 10);
|
||||
const ms = Date.parse(date);
|
||||
const options = { month: 'short', day: 'numeric', year: 'numeric' };
|
||||
const tsDisplay = new Intl.DateTimeFormat('en-US', options).format(ms);
|
||||
return tsDisplay;
|
||||
};
|
||||
// get YYYY-MM-DD format
|
||||
const date = ts.slice(0, 10);
|
||||
const ms = Date.parse(date);
|
||||
const options = { month: 'short', day: 'numeric', year: 'numeric' };
|
||||
const tsDisplay = new Intl.DateTimeFormat('en-US', options).format(ms);
|
||||
return tsDisplay;
|
||||
};
|
||||
|
||||
const actions = (compose) => [
|
||||
{
|
||||
title: 'Recreate image',
|
||||
onClick: () => navigate(
|
||||
'/imagewizard',
|
||||
{ state: { composeRequest: compose.request, initialStep: 'review' }}
|
||||
)
|
||||
}
|
||||
];
|
||||
const actions = (compose) => [
|
||||
{
|
||||
title: 'Recreate image',
|
||||
onClick: () =>
|
||||
navigate('/imagewizard', {
|
||||
state: { composeRequest: compose.request, initialStep: 'review' },
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
// the state.page is not an index so must be reduced by 1 get the starting index
|
||||
const itemsStartInclusive = (page - 1) * perPage;
|
||||
const itemsEndExclusive = itemsStartInclusive + perPage;
|
||||
// the state.page is not an index so must be reduced by 1 get the starting index
|
||||
const itemsStartInclusive = (page - 1) * perPage;
|
||||
const itemsEndExclusive = itemsStartInclusive + perPage;
|
||||
|
||||
return (
|
||||
return (
|
||||
<React.Fragment>
|
||||
{(composes.allIds.length === 0 && (
|
||||
<EmptyState variant={EmptyStateVariant.large} data-testid="empty-state">
|
||||
<EmptyStateIcon icon={PlusCircleIcon} />
|
||||
<Title headingLevel="h4" size="lg">
|
||||
Create an image
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
Create OS images for deployment in Amazon Web Services, Microsoft
|
||||
Azure and Google Cloud Platform. Images can include a custom package
|
||||
set and an activation key to automate the registration process.
|
||||
</EmptyStateBody>
|
||||
<Link
|
||||
to="/imagewizard"
|
||||
className="pf-c-button pf-m-primary"
|
||||
data-testid="create-image-action"
|
||||
>
|
||||
Create image
|
||||
</Link>
|
||||
<EmptyStateSecondaryActions>
|
||||
<DocumentationButton />
|
||||
</EmptyStateSecondaryActions>
|
||||
</EmptyState>
|
||||
)) || (
|
||||
<React.Fragment>
|
||||
{ composes.allIds.length === 0 && (
|
||||
<EmptyState variant={ EmptyStateVariant.large } data-testid="empty-state">
|
||||
<EmptyStateIcon icon={ PlusCircleIcon } />
|
||||
<Title headingLevel="h4" size="lg">
|
||||
Create an image
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
Create OS images for deployment in Amazon Web Services,
|
||||
Microsoft Azure and Google Cloud Platform. Images can
|
||||
include a custom package set and an activation key to
|
||||
automate the registration process.
|
||||
</EmptyStateBody>
|
||||
<Link to="/imagewizard" className="pf-c-button pf-m-primary" data-testid="create-image-action">
|
||||
Create image
|
||||
</Link>
|
||||
<EmptyStateSecondaryActions>
|
||||
<DocumentationButton />
|
||||
</EmptyStateSecondaryActions>
|
||||
</EmptyState>
|
||||
) || (
|
||||
<React.Fragment>
|
||||
<Toolbar>
|
||||
<ToolbarContent>
|
||||
<ToolbarItem>
|
||||
<Link to="/imagewizard" className="pf-c-button pf-m-primary" data-testid="create-image-action">
|
||||
Create image
|
||||
</Link>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem variant="pagination" align={ { default: 'alignRight' } }>
|
||||
<Pagination
|
||||
itemCount={ composes.count }
|
||||
perPage={ perPage }
|
||||
page={ page }
|
||||
onSetPage={ onSetPage }
|
||||
onPerPageSelect={ onPerPageSelect }
|
||||
widgetId="compose-pagination"
|
||||
data-testid="images-pagination"
|
||||
isCompact />
|
||||
</ToolbarItem>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
<TableComposable variant="compact" data-testid="images-table">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th>Image name</Th>
|
||||
<Th>Created</Th>
|
||||
<Th>Release</Th>
|
||||
<Th>Target</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>Instance</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
{ composes.allIds.slice(itemsStartInclusive, itemsEndExclusive).map((id, rowIndex) => {
|
||||
const compose = composes.byId[id];
|
||||
return (
|
||||
<Tbody key={ id } isExpanded={ isExpanded(compose) }>
|
||||
<Tr>
|
||||
<Td expand={ { rowIndex, isExpanded: isExpanded(compose),
|
||||
onToggle: () => handleToggle(compose, !isExpanded(compose)) } } />
|
||||
<Td dataLabel="Image name">{compose.request.image_name || id}</Td>
|
||||
<Td dataLabel="Created">{timestampToDisplayString(compose.created_at)}</Td>
|
||||
<Td dataLabel="Release"><Release release={ compose.request.distribution } /></Td>
|
||||
<Td dataLabel="Target"><Target
|
||||
uploadType={ compose.request.image_requests[0].upload_request.type }
|
||||
imageType={ compose.request.image_requests[0].image_type } /></Td>
|
||||
<Td dataLabel="Status"><ImageBuildStatus
|
||||
status={ compose.image_status ? compose.image_status.status : '' } /></Td>
|
||||
<Td dataLabel="Instance"><ImageLink
|
||||
imageStatus={ compose.image_status }
|
||||
imageType={ compose.request.image_requests[0].image_type }
|
||||
uploadOptions={ compose.request.image_requests[0].upload_request.options } /></Td>
|
||||
<Td><ActionsColumn items={ actions(compose) } /></Td>
|
||||
</Tr>
|
||||
<Tr isExpanded={ isExpanded(compose) }>
|
||||
<Td colSpan={ 8 }>
|
||||
<ExpandableRowContent>
|
||||
<strong>UUID</strong>
|
||||
<div>{ id }</div>
|
||||
<ErrorDetails status={ compose.image_status } />
|
||||
</ExpandableRowContent>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
);
|
||||
}) }
|
||||
</TableComposable>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Toolbar>
|
||||
<ToolbarContent>
|
||||
<ToolbarItem>
|
||||
<Link
|
||||
to="/imagewizard"
|
||||
className="pf-c-button pf-m-primary"
|
||||
data-testid="create-image-action"
|
||||
>
|
||||
Create image
|
||||
</Link>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem
|
||||
variant="pagination"
|
||||
align={{ default: 'alignRight' }}
|
||||
>
|
||||
<Pagination
|
||||
itemCount={composes.count}
|
||||
perPage={perPage}
|
||||
page={page}
|
||||
onSetPage={onSetPage}
|
||||
onPerPageSelect={onPerPageSelect}
|
||||
widgetId="compose-pagination"
|
||||
data-testid="images-pagination"
|
||||
isCompact
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
<TableComposable variant="compact" data-testid="images-table">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th>Image name</Th>
|
||||
<Th>Created</Th>
|
||||
<Th>Release</Th>
|
||||
<Th>Target</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>Instance</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
{composes.allIds
|
||||
.slice(itemsStartInclusive, itemsEndExclusive)
|
||||
.map((id, rowIndex) => {
|
||||
const compose = composes.byId[id];
|
||||
return (
|
||||
<Tbody key={id} isExpanded={isExpanded(compose)}>
|
||||
<Tr>
|
||||
<Td
|
||||
expand={{
|
||||
rowIndex,
|
||||
isExpanded: isExpanded(compose),
|
||||
onToggle: () =>
|
||||
handleToggle(compose, !isExpanded(compose)),
|
||||
}}
|
||||
/>
|
||||
<Td dataLabel="Image name">
|
||||
{compose.request.image_name || id}
|
||||
</Td>
|
||||
<Td dataLabel="Created">
|
||||
{timestampToDisplayString(compose.created_at)}
|
||||
</Td>
|
||||
<Td dataLabel="Release">
|
||||
<Release release={compose.request.distribution} />
|
||||
</Td>
|
||||
<Td dataLabel="Target">
|
||||
<Target
|
||||
uploadType={
|
||||
compose.request.image_requests[0].upload_request
|
||||
.type
|
||||
}
|
||||
imageType={
|
||||
compose.request.image_requests[0].image_type
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td dataLabel="Status">
|
||||
<ImageBuildStatus
|
||||
status={
|
||||
compose.image_status
|
||||
? compose.image_status.status
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td dataLabel="Instance">
|
||||
<ImageLink
|
||||
imageStatus={compose.image_status}
|
||||
imageType={
|
||||
compose.request.image_requests[0].image_type
|
||||
}
|
||||
uploadOptions={
|
||||
compose.request.image_requests[0].upload_request
|
||||
.options
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<ActionsColumn items={actions(compose)} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr isExpanded={isExpanded(compose)}>
|
||||
<Td colSpan={8}>
|
||||
<ExpandableRowContent>
|
||||
<strong>UUID</strong>
|
||||
<div>{id}</div>
|
||||
<ErrorDetails status={compose.image_status} />
|
||||
</ExpandableRowContent>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
);
|
||||
})}
|
||||
</TableComposable>
|
||||
</React.Fragment>
|
||||
);
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ImagesTable.propTypes = {
|
||||
composes: PropTypes.object,
|
||||
composesGet: PropTypes.func,
|
||||
composeGetStatus: PropTypes.func,
|
||||
composes: PropTypes.object,
|
||||
composesGet: PropTypes.func,
|
||||
composeGetStatus: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ImagesTable;
|
||||
|
|
|
|||
|
|
@ -5,18 +5,20 @@ import { Label } from '@patternfly/react-core';
|
|||
import { RHEL_8, RHEL_9 } from '../../constants.js';
|
||||
|
||||
const Release = (props) => {
|
||||
const releaseOptions = {
|
||||
[RHEL_8]: 'RHEL 8',
|
||||
[RHEL_9]: 'RHEL 9',
|
||||
'centos-8': 'CentOS Stream 8',
|
||||
'centos-9': 'CentOS Stream 9',
|
||||
};
|
||||
const release = releaseOptions[props.release] ? releaseOptions[props.release] : props.release;
|
||||
return <Label color='blue'>{release}</Label>;
|
||||
const releaseOptions = {
|
||||
[RHEL_8]: 'RHEL 8',
|
||||
[RHEL_9]: 'RHEL 9',
|
||||
'centos-8': 'CentOS Stream 8',
|
||||
'centos-9': 'CentOS Stream 9',
|
||||
};
|
||||
const release = releaseOptions[props.release]
|
||||
? releaseOptions[props.release]
|
||||
: props.release;
|
||||
return <Label color="blue">{release}</Label>;
|
||||
};
|
||||
|
||||
Release.propTypes = {
|
||||
release: PropTypes.string,
|
||||
release: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Release;
|
||||
|
|
|
|||
|
|
@ -2,32 +2,28 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
const Target = (props) => {
|
||||
const targetOptions = {
|
||||
aws: 'Amazon Web Services',
|
||||
azure: 'Microsoft Azure',
|
||||
gcp: 'Google Cloud Platform',
|
||||
vsphere: 'VMWare',
|
||||
'guest-image': 'Virtualization - Guest image',
|
||||
'image-installer': 'Bare metal - Installer'
|
||||
};
|
||||
const targetOptions = {
|
||||
aws: 'Amazon Web Services',
|
||||
azure: 'Microsoft Azure',
|
||||
gcp: 'Google Cloud Platform',
|
||||
vsphere: 'VMWare',
|
||||
'guest-image': 'Virtualization - Guest image',
|
||||
'image-installer': 'Bare metal - Installer',
|
||||
};
|
||||
|
||||
let target;
|
||||
if (props.uploadType === 'aws.s3') {
|
||||
target = targetOptions[props.imageType];
|
||||
} else {
|
||||
target = targetOptions[props.uploadType];
|
||||
}
|
||||
let target;
|
||||
if (props.uploadType === 'aws.s3') {
|
||||
target = targetOptions[props.imageType];
|
||||
} else {
|
||||
target = targetOptions[props.uploadType];
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{target}
|
||||
</>
|
||||
);
|
||||
return <>{target}</>;
|
||||
};
|
||||
|
||||
Target.propTypes = {
|
||||
uploadType: PropTypes.string,
|
||||
imageType: PropTypes.string
|
||||
uploadType: PropTypes.string,
|
||||
imageType: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Target;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue