debian-image-builder-frontend/src/Components/ImagesTable/Status.tsx
regexowl d8bb3485d8 Wizard: Add v5 PF slug to classNames
This adds v5 PF slug to classNames throughout the code base, making the formatting work properly.
2024-11-25 16:54:59 +01:00

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>
);
};