This adds v5 PF slug to classNames throughout the code base, making the formatting work properly.
352 lines
8.1 KiB
TypeScript
352 lines
8.1 KiB
TypeScript
import React from 'react';
|
|
|
|
import './ImageBuildStatus.scss';
|
|
import {
|
|
Alert,
|
|
Button,
|
|
CodeBlock,
|
|
CodeBlockCode,
|
|
Flex,
|
|
Panel,
|
|
PanelMain,
|
|
Popover,
|
|
Skeleton,
|
|
Text,
|
|
} from '@patternfly/react-core';
|
|
import {
|
|
CheckCircleIcon,
|
|
CopyIcon,
|
|
ExclamationCircleIcon,
|
|
ExclamationTriangleIcon,
|
|
InProgressIcon,
|
|
OffIcon,
|
|
PendingIcon,
|
|
} from '@patternfly/react-icons';
|
|
|
|
import {
|
|
AWS_S3_EXPIRATION_TIME_IN_HOURS,
|
|
OCI_STORAGE_EXPIRATION_TIME_IN_DAYS,
|
|
} from '../../constants';
|
|
import {
|
|
ClonesResponseItem,
|
|
ComposeStatus,
|
|
ComposeStatusError,
|
|
ComposesResponseItem,
|
|
UploadStatus,
|
|
useGetComposeStatusQuery,
|
|
} from '../../store/imageBuilderApi';
|
|
|
|
type StatusClonePropTypes = {
|
|
clone: ClonesResponseItem;
|
|
status: UploadStatus | undefined;
|
|
};
|
|
|
|
export const StatusClone = ({ clone, status }: StatusClonePropTypes) => {
|
|
switch (status?.status) {
|
|
case 'failure':
|
|
return (
|
|
<ErrorStatus
|
|
icon={statuses.failureSharing.icon}
|
|
text={statuses.failureSharing.text}
|
|
error={`Failed to share image to ${clone.request.region}`}
|
|
/>
|
|
);
|
|
case 'success':
|
|
case 'running':
|
|
case 'pending':
|
|
return (
|
|
<Status
|
|
icon={statuses[status.status].icon}
|
|
text={statuses[status.status].text}
|
|
/>
|
|
);
|
|
default:
|
|
return <></>;
|
|
}
|
|
};
|
|
|
|
type ComposeStatusPropTypes = {
|
|
compose: ComposesResponseItem;
|
|
};
|
|
|
|
export const AwsDetailsStatus = ({ compose }: ComposeStatusPropTypes) => {
|
|
const { data, isSuccess } = useGetComposeStatusQuery({
|
|
composeId: compose.id,
|
|
});
|
|
|
|
if (!isSuccess) {
|
|
return <></>;
|
|
}
|
|
|
|
switch (data.image_status.status) {
|
|
case 'failure':
|
|
return (
|
|
<ErrorStatus
|
|
icon={statuses[data.image_status.status].icon}
|
|
text={statuses[data.image_status.status].text}
|
|
error={data?.image_status?.error || ''}
|
|
/>
|
|
);
|
|
default:
|
|
return (
|
|
<Status
|
|
icon={statuses[data.image_status.status].icon}
|
|
text={statuses[data.image_status.status].text}
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
type CloudStatusPropTypes = {
|
|
compose: ComposesResponseItem;
|
|
};
|
|
|
|
export const CloudStatus = ({ compose }: CloudStatusPropTypes) => {
|
|
const { data, isSuccess } = useGetComposeStatusQuery({
|
|
composeId: compose.id,
|
|
});
|
|
|
|
if (!isSuccess) {
|
|
return <Skeleton />;
|
|
}
|
|
|
|
switch (data.image_status.status) {
|
|
case 'failure':
|
|
return (
|
|
<ErrorStatus
|
|
icon={statuses['failure'].icon}
|
|
text={statuses['failure'].text}
|
|
error={data.image_status.error || ''}
|
|
/>
|
|
);
|
|
default:
|
|
return (
|
|
<Status
|
|
icon={statuses[data.image_status.status].icon}
|
|
text={statuses[data.image_status.status].text}
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
type AzureStatusPropTypes = {
|
|
status: ComposeStatus;
|
|
};
|
|
|
|
export const AzureStatus = ({ status }: AzureStatusPropTypes) => {
|
|
switch (status.image_status.status) {
|
|
case 'failure':
|
|
return (
|
|
<ErrorStatus
|
|
icon={statuses[status.image_status.status].icon}
|
|
text={statuses[status.image_status.status].text}
|
|
error={status.image_status.error || ''}
|
|
/>
|
|
);
|
|
default:
|
|
return (
|
|
<Status
|
|
icon={statuses[status.image_status.status].icon}
|
|
text={statuses[status.image_status.status].text}
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
type ExpiringStatusPropTypes = {
|
|
compose: ComposesResponseItem;
|
|
isExpired: boolean;
|
|
timeToExpiration: number;
|
|
};
|
|
|
|
export const ExpiringStatus = ({
|
|
compose,
|
|
isExpired,
|
|
timeToExpiration,
|
|
}: ExpiringStatusPropTypes) => {
|
|
const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({
|
|
composeId: compose.id,
|
|
});
|
|
|
|
if (!isSuccess) {
|
|
return <Skeleton />;
|
|
}
|
|
|
|
const status = composeStatus.image_status.status;
|
|
const remainingHours = AWS_S3_EXPIRATION_TIME_IN_HOURS - timeToExpiration;
|
|
const remainingDays = OCI_STORAGE_EXPIRATION_TIME_IN_DAYS - timeToExpiration;
|
|
|
|
const imageType = compose.request.image_requests[0].upload_request.type;
|
|
|
|
if (isExpired) {
|
|
return (
|
|
<Status icon={statuses['expired'].icon} text={statuses['expired'].text} />
|
|
);
|
|
} else if (imageType === 'aws.s3' && status === 'success') {
|
|
return (
|
|
<Status
|
|
icon={statuses['expiring'].icon}
|
|
text={`Expires in ${remainingHours} ${
|
|
remainingHours > 1 ? 'hours' : 'hour'
|
|
}`}
|
|
/>
|
|
);
|
|
} else if (imageType === 'oci.objectstorage' && status === 'success') {
|
|
return (
|
|
<Status
|
|
icon={statuses['expiring'].icon}
|
|
text={`Expires in ${remainingDays} ${
|
|
remainingDays > 1 ? 'days' : 'day'
|
|
}`}
|
|
/>
|
|
);
|
|
} else if (status === 'failure') {
|
|
return (
|
|
<ErrorStatus
|
|
icon={statuses[status].icon}
|
|
text={statuses[status].text}
|
|
error={composeStatus.image_status.error || ''}
|
|
/>
|
|
);
|
|
} else {
|
|
return <Status icon={statuses[status].icon} text={statuses[status].text} />;
|
|
}
|
|
};
|
|
|
|
const statuses = {
|
|
failure: {
|
|
icon: <ExclamationCircleIcon className="error" />,
|
|
text: 'Image build failed',
|
|
},
|
|
|
|
pending: {
|
|
icon: <PendingIcon />,
|
|
text: 'Image build is pending',
|
|
},
|
|
|
|
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',
|
|
},
|
|
|
|
running: {
|
|
icon: <InProgressIcon className="pending" />,
|
|
text: 'Running',
|
|
},
|
|
|
|
success: {
|
|
icon: <CheckCircleIcon className="success" />,
|
|
text: 'Ready',
|
|
},
|
|
|
|
expired: {
|
|
icon: <OffIcon />,
|
|
text: 'Expired',
|
|
},
|
|
|
|
expiring: {
|
|
icon: <ExclamationTriangleIcon className="expiring" />,
|
|
},
|
|
|
|
failureSharing: {
|
|
icon: <ExclamationCircleIcon className="error" />,
|
|
text: 'Sharing image failed',
|
|
},
|
|
|
|
failedClone: {
|
|
icon: <ExclamationCircleIcon className="error" />,
|
|
text: 'Failure sharing',
|
|
},
|
|
};
|
|
|
|
type StatusPropTypes = {
|
|
icon: JSX.Element;
|
|
text: string;
|
|
};
|
|
|
|
const Status = ({ icon, text }: StatusPropTypes) => {
|
|
return (
|
|
<Flex className="pf-v5-u-align-items-baseline pf-m-nowrap">
|
|
<div className="pf-v5-u-mr-sm">{icon}</div>
|
|
<p>{text}</p>
|
|
</Flex>
|
|
);
|
|
};
|
|
|
|
type ErrorStatusPropTypes = {
|
|
icon: JSX.Element;
|
|
text: string;
|
|
error: ComposeStatusError | string;
|
|
};
|
|
|
|
const ErrorStatus = ({ icon, text, error }: ErrorStatusPropTypes) => {
|
|
let reason = '';
|
|
const detailsArray: string[] = [];
|
|
if (typeof error === 'string') {
|
|
reason = error;
|
|
} else {
|
|
if (error.reason) {
|
|
reason = error.reason;
|
|
}
|
|
if (Array.isArray(error.details)) {
|
|
for (const line in error.details) {
|
|
detailsArray.push(`${error.details[line]}`);
|
|
}
|
|
}
|
|
if (typeof error.details === 'string') {
|
|
detailsArray.push(error.details);
|
|
}
|
|
if (error.details?.reason) {
|
|
detailsArray.push(`${error.details.reason}`);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Flex className="pf-v5-u-align-items-baseline pf-m-nowrap">
|
|
<div className="pf-v5-u-mr-sm">{icon}</div>
|
|
<Popover
|
|
data-testid="errorstatus-popover"
|
|
position="bottom"
|
|
minWidth="40rem"
|
|
bodyContent={
|
|
<>
|
|
<Alert variant="danger" title={text} isInline isPlain />
|
|
<Text className="pf-v5-u-pt-md pf-v5-u-pb-md">{reason}</Text>
|
|
<Panel isScrollable>
|
|
<PanelMain maxHeight="25rem">
|
|
<CodeBlock>
|
|
<CodeBlockCode>{detailsArray.join('\n')}</CodeBlockCode>
|
|
</CodeBlock>
|
|
</PanelMain>
|
|
</Panel>
|
|
<Button
|
|
variant="link"
|
|
onClick={() =>
|
|
navigator.clipboard.writeText(
|
|
reason + '\n\n' + detailsArray.join('\n')
|
|
)
|
|
}
|
|
className="pf-v5-u-pl-0 pf-v5-u-mt-md"
|
|
>
|
|
Copy error text to clipboard <CopyIcon />
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<Button variant="link" className="pf-v5-u-p-0 pf-v5-u-font-size-sm">
|
|
<div className="failure-button">{text}</div>
|
|
</Button>
|
|
</Popover>
|
|
</Flex>
|
|
);
|
|
};
|