debian-image-builder-frontend/src/Components/ImagesTable/ImageDetails.tsx
Gianluca Zuccarelli 9a17373234 Hooks: extract auth.getUser to its own hook
This code was being called in multiple places and was causing issues
with the on-prem frontend. Extract the logic to a single hook and only
get the `userData` for the hosted frontend.
2025-08-21 16:12:09 +00:00

538 lines
16 KiB
TypeScript

import React from 'react';
import {
Alert,
Button,
ClipboardCopy,
DescriptionList,
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
Popover,
Skeleton,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import ClonesTable from './ClonesTable';
import { AMPLITUDE_MODULE_NAME } from '../../constants';
import { useGetUser } from '../../Hooks';
import { useGetComposeStatusQuery } from '../../store/backendApi';
import { extractProvisioningList } from '../../store/helpers';
import {
ComposesResponseItem,
GcpUploadRequestOptions,
} 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-v6-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-v6-u-p-0 pf-v6-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>;
}
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>;
}
return <SourceNotFoundPopover />;
};
export 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;
const { analytics, auth } = useChrome();
const { userData } = useGetUser(auth);
if (!isAwsUploadRequestOptions(options)) {
throw TypeError(
`Error: options must be of type AwsUploadRequestOptions, not ${typeof options}.`,
);
}
return (
<>
<div className='pf-v6-u-font-weight-bold pf-v6-u-pb-md'>
Build Information
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-u-pl-xl'>
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip='Copy'
clickTip='Copied'
variant='inline-compact'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Copy UUID`, {
module: AMPLITUDE_MODULE_NAME,
link_name: compose.id,
current_path: window.location.pathname,
account_id:
userData?.identity.internal?.account_id || 'Not found',
});
}
}}
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
<DescriptionListTerm>Architecture</DescriptionListTerm>
<DescriptionListDescription>
{compose.request.image_requests[0].architecture}
</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/`}
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Link Clicked`, {
module: AMPLITUDE_MODULE_NAME,
link_name: options.share_with_accounts
? options.share_with_accounts[0]
: '',
current_path: window.location.pathname,
account_id:
userData?.identity.internal?.account_id || 'Not found',
});
}
}}
>
{options.share_with_accounts[0]}
</Button>
</DescriptionListDescription>
</DescriptionListGroup>
)}
</DescriptionList>
<>
<br />
<div className='pf-v6-u-font-weight-bold pf-v6-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-v6-u-font-weight-bold pf-v6-u-pb-md'>
Build Information
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-u-pl-xl'>
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip='Copy'
clickTip='Copied'
variant='inline-compact'
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
<DescriptionListTerm>Architecture</DescriptionListTerm>
<DescriptionListDescription>
{compose.request.image_requests[0].architecture}
</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-v6-u-font-weight-bold pf-v6-u-pb-md'>
Cloud Provider Identifiers
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-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-v6-u-font-weight-bold pf-v6-u-pb-md'>
Build Information
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-u-pl-xl'>
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip='Copy'
clickTip='Copied'
variant='inline-compact'
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
<DescriptionListTerm>Architecture</DescriptionListTerm>
<DescriptionListDescription>
{compose.request.image_requests[0].architecture}
</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-v6-u-font-weight-bold pf-v6-u-pb-md'>
Cloud Provider Identifiers
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-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-v6-u-font-weight-bold pf-v6-u-pb-md'>
Build Information
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-u-pl-xl'>
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip='Copy'
clickTip='Copied'
variant='inline-compact'
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
<DescriptionListTerm>Architecture</DescriptionListTerm>
<DescriptionListDescription>
{compose.request.image_requests[0].architecture}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
<br />
<div className='pf-v6-u-font-weight-bold pf-v6-u-pb-md'>
Cloud Provider Identifiers
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-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-v6-u-font-weight-bold pf-v6-u-pb-md'>
Build Information
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-u-pl-xl'>
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip='Copy'
clickTip='Copied'
variant='inline-compact'
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
<DescriptionListTerm>Architecture</DescriptionListTerm>
<DescriptionListDescription>
{compose.request.image_requests[0].architecture}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
);
};
type LocalDetailsPropTypes = {
compose: ComposesResponseItem;
};
export const LocalDetails = ({ compose }: LocalDetailsPropTypes) => {
return (
<>
<div className='pf-v6-u-font-weight-bold pf-v6-u-pb-md'>
Build Information
</div>
<DescriptionList isHorizontal isCompact className=' pf-v6-u-pl-xl'>
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip='Copy'
clickTip='Copied'
variant='inline-compact'
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
<DescriptionListTerm>Architecture</DescriptionListTerm>
<DescriptionListDescription>
{compose.request.image_requests[0].architecture}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
);
};