debian-image-builder-frontend/src/Components/ImagesTable/ImageDetails.tsx
regexowl b8372eeaf5 ImagesTable: Add OCI images
This adds a row for OCI images in the ImagesTable. Details about the images show UUID of the image and the Object Storage URL which is needed to deploy the image.

"Image link" button in the Instance column contains instrucion on how to run an OCI image built by Image Builder in Oracle Cloud. The documentation link in the popover is just a placeholder for now as the documentation is being prepared.

Until the build is finished the "Image link" button is disabled as it would be missing the Object Storage URL which is creating on upload.
2023-11-07 11:13:28 +01:00

450 lines
13 KiB
TypeScript

import React from 'react';
import {
ClipboardCopy,
DescriptionList,
DescriptionListGroup,
DescriptionListDescription,
DescriptionListTerm,
Button,
Popover,
Alert,
Skeleton,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import ClonesTable from './ClonesTable';
import { extractProvisioningList } from '../../store/helpers';
import {
ComposesResponseItem,
GcpUploadRequestOptions,
useGetComposeStatusQuery,
} from '../../store/imageBuilderApi';
import { useGetSourceListQuery } from '../../store/provisioningApi';
import {
isAwsUploadRequestOptions,
isAzureUploadRequestOptions,
isAzureUploadStatus,
isGcpUploadRequestOptions,
isGcpUploadStatus,
isOciUploadStatus,
} from '../../store/typeGuards';
const SourceNotFoundPopover = () => {
return (
<Popover
position="bottom"
bodyContent={
<>
<Alert
variant="danger"
title="Source name cannot be loaded"
className="pf-u-pb-md"
isInline
isPlain
/>
<p>
The information about the source cannot be loaded. Please check the
source was not removed and try again later.
</p>
<br />
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href={'settings/sources'}
>
Manage sources here
</Button>
</>
}
>
<Button variant="link" className="pf-u-p-0 pf-u-font-size-sm">
<div className="failure-button">Source name cannot be loaded</div>
</Button>
</Popover>
);
};
type AzureSourceNamePropTypes = {
id: string;
};
const AzureSourceName = ({ id }: AzureSourceNamePropTypes) => {
const { data: rawSources, isSuccess } = useGetSourceListQuery({
provider: 'azure',
});
if (!isSuccess) {
return <Skeleton />;
}
const sources = extractProvisioningList(rawSources);
const sourcename = sources?.find((source) => source.id === id);
if (sourcename) {
return <p>{sourcename.name}</p>;
} else {
return <SourceNotFoundPopover />;
}
};
type AwsSourceNamePropTypes = {
id: string;
};
const AwsSourceName = ({ id }: AwsSourceNamePropTypes) => {
const { data: rawSources, isSuccess } = useGetSourceListQuery({
provider: 'aws',
});
if (!isSuccess) {
return <Skeleton />;
}
const sources = extractProvisioningList(rawSources);
const sourcename = sources?.find((source) => source.id === id);
if (sourcename) {
return <p>{sourcename.name}</p>;
} else {
return <SourceNotFoundPopover />;
}
};
const parseGcpSharedWith = (
sharedWith: GcpUploadRequestOptions['share_with_accounts']
) => {
if (sharedWith) {
const splitGCPSharedWith = sharedWith[0].split(':');
return splitGCPSharedWith[1];
}
};
type AwsDetailsPropTypes = {
compose: ComposesResponseItem;
};
export const AwsDetails = ({ compose }: AwsDetailsPropTypes) => {
const options = compose.request.image_requests[0].upload_request.options;
if (!isAwsUploadRequestOptions(options)) {
throw TypeError(
`Error: options must be of type AwsUploadRequestOptions, not ${typeof options}.`
);
}
return (
<>
<div className="pf-u-font-weight-bold pf-u-pb-md">Build Information</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
ouiaId="aws-uuid"
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
</DescriptionListGroup>
{options.share_with_sources?.[0] && (
<DescriptionListGroup>
<DescriptionListTerm>Source</DescriptionListTerm>
<DescriptionListDescription>
<AwsSourceName id={options.share_with_sources[0]} />
</DescriptionListDescription>
</DescriptionListGroup>
)}
{options.share_with_accounts?.[0] && (
<DescriptionListGroup>
<DescriptionListTerm>Shared with</DescriptionListTerm>
<DescriptionListDescription>
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
// the format of an account link is taken from
// https://docs.aws.amazon.com/signin/latest/userguide/sign-in-urls-defined.html
href={`https://${options.share_with_accounts[0]}.signin.aws.amazon.com/console/`}
>
{options.share_with_accounts[0]}
</Button>
</DescriptionListDescription>
</DescriptionListGroup>
)}
</DescriptionList>
<>
<br />
<div className="pf-u-font-weight-bold pf-u-pb-md">
Cloud Provider Identifiers
</div>
</>
<ClonesTable compose={compose} />
</>
);
};
type AzureDetailsPropTypes = {
compose: ComposesResponseItem;
};
export const AzureDetails = ({ compose }: AzureDetailsPropTypes) => {
const { data: composeStatus } = useGetComposeStatusQuery({
composeId: compose.id,
});
const options = compose.request.image_requests[0].upload_request.options;
if (!isAzureUploadRequestOptions(options)) {
throw TypeError(
`Error: options must be of type AzureUploadRequestOptions, not ${typeof options}.`
);
}
const sourceId = options.source_id;
const resourceGroup = options.resource_group;
const uploadStatus = composeStatus?.image_status.upload_status?.options;
if (uploadStatus && !isAzureUploadStatus(uploadStatus)) {
throw TypeError(
`Error: uploadStatus must be of type AzureUploadStatus, not ${typeof uploadStatus}.`
);
}
return (
<>
<div className="pf-u-font-weight-bold pf-u-pb-md">Build Information</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
ouiaId="azure-uuid"
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
</DescriptionListGroup>
{sourceId && (
<DescriptionListGroup>
<DescriptionListTerm>Source</DescriptionListTerm>
<DescriptionListDescription>
<AzureSourceName id={sourceId} />
</DescriptionListDescription>
</DescriptionListGroup>
)}
<DescriptionListGroup>
<DescriptionListTerm>Resource Group</DescriptionListTerm>
<DescriptionListDescription>
{resourceGroup}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
<br />
<div className="pf-u-font-weight-bold pf-u-pb-md">
Cloud Provider Identifiers
</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>Image name</DescriptionListTerm>
<DescriptionListDescription>
{composeStatus?.image_status.status === 'success' && (
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
>
{uploadStatus?.image_name}
</ClipboardCopy>
)}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
);
};
type GcpDetailsPropTypes = {
compose: ComposesResponseItem;
};
export const GcpDetails = ({ compose }: GcpDetailsPropTypes) => {
const { data: composeStatus } = useGetComposeStatusQuery({
composeId: compose.id,
});
const options = compose.request.image_requests[0].upload_request.options;
if (!isGcpUploadRequestOptions(options)) {
throw TypeError(
`Error: options must be of type GcpUploadRequestOptions, not ${typeof options}.`
);
}
const uploadStatus = composeStatus?.image_status.upload_status?.options;
if (uploadStatus && !isGcpUploadStatus(uploadStatus)) {
throw TypeError(
`Error: uploadStatus must be of type GcpUploadStatus, not ${typeof uploadStatus}.`
);
}
return (
<>
<div className="pf-u-font-weight-bold pf-u-pb-md">Build Information</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
ouiaId="gcp-uuid"
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
</DescriptionListGroup>
{composeStatus?.image_status.status === 'success' && (
<DescriptionListGroup>
<DescriptionListTerm>Project ID</DescriptionListTerm>
<DescriptionListDescription>
{uploadStatus?.project_id}
</DescriptionListDescription>
</DescriptionListGroup>
)}
{options.share_with_accounts && (
<DescriptionListGroup>
<DescriptionListTerm>Shared with</DescriptionListTerm>
<DescriptionListDescription>
{parseGcpSharedWith(options.share_with_accounts)}
</DescriptionListDescription>
</DescriptionListGroup>
)}
</DescriptionList>
<br />
<div className="pf-u-font-weight-bold pf-u-pb-md">
Cloud Provider Identifiers
</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>Image name</DescriptionListTerm>
<DescriptionListDescription>
{composeStatus?.image_status.status === 'success' && (
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
>
{uploadStatus?.image_name}
</ClipboardCopy>
)}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
);
};
type OciDetailsPropTypes = {
compose: ComposesResponseItem;
};
export const OciDetails = ({ compose }: OciDetailsPropTypes) => {
const { data: composeStatus } = useGetComposeStatusQuery({
composeId: compose.id,
});
const options = composeStatus?.image_status.upload_status?.options;
if (options && !isOciUploadStatus(options)) {
throw TypeError(
`Error: uploadStatus must be of type OciUploadStatus, not ${typeof options}.`
);
}
return (
<>
<div className="pf-u-font-weight-bold pf-u-pb-md">Build Information</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
ouiaId="gcp-uuid"
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
<br />
<div className="pf-u-font-weight-bold pf-u-pb-md">
Cloud Provider Identifiers
</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>Object Storage URL</DescriptionListTerm>
<DescriptionListDescription>
{composeStatus?.image_status.status === 'success' && (
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
isBlock
>
{options?.url}
</ClipboardCopy>
)}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
);
};
type AwsS3DetailsPropTypes = {
compose: ComposesResponseItem;
};
export const AwsS3Details = ({ compose }: AwsS3DetailsPropTypes) => {
return (
<>
<div className="pf-u-font-weight-bold pf-u-pb-md">Build Information</div>
<DescriptionList isHorizontal isCompact className=" pf-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
ouiaId="other-targets-uuid"
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
);
};