store/cockpitApi: compose status support

Finds the relevant compose, asks for its status and reads the original
compose request from disk.
This commit is contained in:
Sanne Raymaekers 2025-01-23 13:18:32 +01:00
parent 7bd5e2fa95
commit c8c62dda9d
6 changed files with 171 additions and 10 deletions

View file

@ -15,11 +15,11 @@ import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import ClonesTable from './ClonesTable';
import { useGetComposeStatusQuery } from '../../store/backendApi';
import { extractProvisioningList } from '../../store/helpers';
import {
ComposesResponseItem,
GcpUploadRequestOptions,
useGetComposeStatusQuery,
} from '../../store/imageBuilderApi';
import { useGetSourceListQuery } from '../../store/provisioningApi';
import {
@ -478,3 +478,36 @@ export const AwsS3Details = ({ compose }: AwsS3DetailsPropTypes) => {
</>
);
};
type LocalDetailsPropTypes = {
compose: ComposesResponseItem;
};
export const LocalDetails = ({ compose }: LocalDetailsPropTypes) => {
return (
<>
<div className="pf-v5-u-font-weight-bold pf-v5-u-pb-md">
Build Information
</div>
<DescriptionList isHorizontal isCompact className=" pf-v5-u-pl-xl">
<DescriptionListGroup>
<DescriptionListTerm>UUID</DescriptionListTerm>
<DescriptionListDescription>
<ClipboardCopy
hoverTip="Copy"
clickTip="Copied"
variant="inline-compact"
ouiaId="other-targets-uuid"
>
{compose.id}
</ClipboardCopy>
</DescriptionListDescription>
<DescriptionListTerm>Architecture</DescriptionListTerm>
<DescriptionListDescription>
{compose.request.image_requests[0].architecture}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
);
};

View file

@ -33,12 +33,18 @@ import {
AwsS3Details,
AzureDetails,
GcpDetails,
LocalDetails,
OciDetails,
} from './ImageDetails';
import ImagesTableToolbar from './ImagesTableToolbar';
import { AwsS3Instance, CloudInstance, OciInstance } from './Instance';
import {
AwsS3Instance,
CloudInstance,
OciInstance,
LocalInstance,
} from './Instance';
import Release from './Release';
import { ExpiringStatus, CloudStatus } from './Status';
import { ExpiringStatus, CloudStatus, LocalStatus } from './Status';
import { AwsTarget, Target } from './Target';
import {
@ -50,6 +56,7 @@ import {
STATUS_POLLING_INTERVAL,
} from '../../constants';
import {
useGetComposeStatusQuery,
useGetComposesQuery,
useGetBlueprintsQuery,
useGetBlueprintComposesQuery,
@ -70,7 +77,6 @@ import {
ComposeStatus,
GetBlueprintComposesApiArg,
GetBlueprintsApiArg,
useGetComposeStatusQuery,
} from '../../store/imageBuilderApi';
import { resolveRelPath } from '../../Utilities/path';
import {
@ -300,10 +306,9 @@ const ImagesTableRow = ({ compose, rowIndex }: ImagesTableRowPropTypes) => {
}
}, [setPollingInterval, composeStatus]);
const type =
compose.request?.image_requests[0]?.upload_request?.type || 'local';
const type = compose.request?.image_requests[0]?.upload_request?.type;
switch (type) {
switch (type as string) {
case 'aws':
return (
<AwsRow
@ -321,7 +326,7 @@ const ImagesTableRow = ({ compose, rowIndex }: ImagesTableRowPropTypes) => {
case 'aws.s3':
return <AwsS3Row compose={compose} rowIndex={rowIndex} />;
case 'local':
return <Row compose={compose} rowIndex={rowIndex} />;
return <LocalRow compose={compose} rowIndex={rowIndex} />;
}
};
@ -463,6 +468,26 @@ const AwsRow = ({ compose, composeStatus, rowIndex }: AwsRowPropTypes) => {
);
};
type LocalRowPropTypes = {
compose: ComposesResponseItem;
rowIndex: number;
};
const LocalRow = ({ compose, rowIndex }: LocalRowPropTypes) => {
const details = <LocalDetails compose={compose} />;
const instance = <LocalInstance compose={compose} />;
const status = <LocalStatus compose={compose} />;
return (
<Row
compose={compose}
rowIndex={rowIndex}
details={details}
instance={instance}
status={status}
/>
);
};
type RowPropTypes = {
compose: ComposesResponseItem;
rowIndex: number;

View file

@ -27,6 +27,7 @@ import {
SEARCH_INPUT,
} from '../../constants';
import { useGetBlueprintsQuery } from '../../store/backendApi';
import { useGetComposeStatusQuery } from '../../store/backendApi';
import {
selectSelectedBlueprintId,
selectBlueprintSearchInput,
@ -37,7 +38,6 @@ import {
ComposesResponseItem,
ComposeStatus,
ImageTypes,
useGetComposeStatusQuery,
} from '../../store/imageBuilderApi';
import {
isAwsUploadRequestOptions,
@ -403,3 +403,31 @@ export const AwsS3Instance = ({
);
}
};
type LocalInstancePropTypes = {
compose: ComposesResponseItem;
};
export const LocalInstance = ({ compose }: LocalInstancePropTypes) => {
const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({
composeId: compose.id,
});
if (!isSuccess) {
return <Skeleton />;
}
// Hacky to define the type here, but local upload is not available in
// the image builder api, only in the composer api.
type LocalUploadStatusOptions = {
filename: string;
};
const status = composeStatus?.image_status.status;
const options = composeStatus?.image_status.upload_status
?.options as unknown as LocalUploadStatusOptions;
if (status !== 'success') {
return <></>;
}
return <div>Filepath to disk: {options.filename}</div>;
};

View file

@ -27,13 +27,13 @@ import {
AWS_S3_EXPIRATION_TIME_IN_HOURS,
OCI_STORAGE_EXPIRATION_TIME_IN_DAYS,
} from '../../constants';
import { useGetComposeStatusQuery } from '../../store/backendApi';
import {
ClonesResponseItem,
ComposeStatus,
ComposeStatusError,
ComposesResponseItem,
UploadStatus,
useGetComposeStatusQuery,
} from '../../store/imageBuilderApi';
type StatusClonePropTypes = {
@ -213,6 +213,33 @@ export const ExpiringStatus = ({
}
};
type LocalStatusPropTypes = {
compose: ComposesResponseItem;
};
export const LocalStatus = ({ compose }: LocalStatusPropTypes) => {
const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({
composeId: compose.id,
});
if (!isSuccess) {
return <Skeleton />;
}
const status = composeStatus?.image_status.status || 'failure';
if (status === 'failure') {
return (
<ErrorStatus
icon={statuses[status].icon}
text={statuses[status].text}
error={composeStatus?.image_status.error || ''}
/>
);
}
return <Status icon={statuses[status].icon} text={statuses[status].text} />;
};
const statuses = {
failure: {
icon: <ExclamationCircleIcon className="error" />,

View file

@ -48,6 +48,10 @@ export const useGetBlueprintComposesQuery = process.env.IS_ON_PREMISE
? cockpitQueries.useGetBlueprintComposesQuery
: imageBuilderQueries.useGetBlueprintComposesQuery;
export const useGetComposeStatusQuery = process.env.IS_ON_PREMISE
? cockpitQueries.useGetComposeStatusQuery
: imageBuilderQueries.useGetComposeStatusQuery;
export const useBackendPrefetch = process.env.IS_ON_PREMISE
? cockpitApi.usePrefetch
: imageBuilderApi.usePrefetch;

View file

@ -27,6 +27,8 @@ import {
GetBlueprintComposesApiResponse,
GetComposesApiArg,
GetComposesApiResponse,
GetComposeStatusApiArg,
GetComposeStatusApiResponse,
DeleteBlueprintApiResponse,
DeleteBlueprintApiArg,
BlueprintItem,
@ -418,6 +420,47 @@ export const cockpitApi = emptyCockpitApi.injectEndpoints({
}
},
}),
getComposeStatus: builder.query<
GetComposeStatusApiResponse,
GetComposeStatusApiArg
>({
queryFn: async (queryArg) => {
try {
const cloudapi = cockpit.http('/run/cloudapi/api.socket', {
superuser: 'require',
});
const resp = JSON.parse(
await cloudapi.get(
`/api/image-builder-composer/v2/composes/${queryArg.composeId}`
)
);
const blueprintsDir = await getBlueprintsPath();
const info = await fsinfo(blueprintsDir, ['entries'], {
superuser: 'try',
});
const entries = Object.entries(info?.entries || {});
for (const bpEntry of entries) {
const request = await cockpit
.file(path.join(blueprintsDir, bpEntry[0], queryArg.composeId))
.read();
return {
data: {
image_status: resp.image_status,
request: JSON.parse(request),
},
};
}
return {
data: {
image_status: '',
request: {},
},
};
} catch (error) {
return { error };
}
},
}),
};
},
});
@ -434,4 +477,5 @@ export const {
useComposeBlueprintMutation,
useGetComposesQuery,
useGetBlueprintComposesQuery,
useGetComposeStatusQuery,
} = cockpitApi;