Skip to content
289 changes: 166 additions & 123 deletions react/src/components/DeploymentPresetDetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,36 @@
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
*/
import type { DeploymentPresetDetailContentFragment$key } from '../__generated__/DeploymentPresetDetailContentFragment.graphql';
import { Descriptions, Divider, Typography } from 'antd';
import { BAIFlex } from 'backend.ai-ui';
import React from 'react';
import type { DeploymentPresetDetailContentImageQuery } from '../__generated__/DeploymentPresetDetailContentImageQuery.graphql';
import { convertToBinaryUnit } from '../helper';
import { ResourceNumbersOfSession } from '../pages/SessionLauncherPage';
import { Descriptions, Skeleton, Typography } from 'antd';
import { BAICard, BAIFlex } from 'backend.ai-ui';
import React, { Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useFragment } from 'react-relay';
import { graphql, useFragment, useLazyLoadQuery } from 'react-relay';

const ImageCanonicalName: React.FC<{ imageId: string }> = ({ imageId }) => {
'use memo';
const data = useLazyLoadQuery<DeploymentPresetDetailContentImageQuery>(
graphql`
query DeploymentPresetDetailContentImageQuery($id: ID!) {
imageV2(id: $id) {
identity {
canonicalName
}
}
}
`,
{ id: imageId },
{ fetchPolicy: 'store-or-network' },
);
return (
<Typography.Text copyable>
{data.imageV2?.identity.canonicalName ?? imageId}
</Typography.Text>
);
};

interface DeploymentPresetDetailContentProps {
presetFrgmt: DeploymentPresetDetailContentFragment$key | null | undefined;
Expand All @@ -26,7 +51,6 @@ const DeploymentPresetDetailContent: React.FC<
id
name
description
rank
runtimeVariantId
runtimeVariant {
id
Expand All @@ -37,7 +61,7 @@ const DeploymentPresetDetailContent: React.FC<
clusterSize
}
execution {
image
imageId
startupCommand
bootstrapScript
environ {
Expand All @@ -51,6 +75,14 @@ const DeploymentPresetDetailContent: React.FC<
value
}
}
resourceSlots {
edges {
node {
slotName
quantity
}
}
}
deploymentDefaults {
openToPublic
replicaCount
Expand All @@ -77,131 +109,142 @@ const DeploymentPresetDetailContent: React.FC<
{preset.description && (
<Typography.Text type="secondary">{preset.description}</Typography.Text>
)}
<Descriptions
<BAICard
size="small"
column={2}
items={[
{
label: t('adminDeploymentPreset.Rank'),
children: preset.rank,
},
{
label: t('adminDeploymentPreset.Runtime'),
children: preset.runtimeVariant?.name ?? preset.runtimeVariantId,
},
]}
/>

<Divider
style={{ margin: '4px 0' }}
titlePlacement="left"
orientationMargin={0}
title={t('adminDeploymentPreset.SectionImage')}
styles={{ body: { paddingTop: 0 } }}
>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{t('adminDeploymentPreset.SectionImage')}
</Typography.Text>
</Divider>
<Descriptions
<Descriptions
size="small"
column={1}
items={[
{
label: t('adminDeploymentPreset.Image'),
children: preset.execution?.imageId ? (
<Suspense fallback={<Skeleton.Input size="small" active />}>
<ImageCanonicalName imageId={preset.execution.imageId} />
</Suspense>
) : (
'-'
),
},
{
label: t('adminDeploymentPreset.Runtime'),
children: preset.runtimeVariant?.name ?? preset.runtimeVariantId,
},
]}
/>
</BAICard>
<BAICard
size="small"
column={1}
items={[
{
label: t('adminDeploymentPreset.Image'),
children: preset.execution?.image || '-',
},
]}
/>

<Divider
style={{ margin: '4px 0' }}
titlePlacement="left"
orientationMargin={0}
title={t('adminDeploymentPreset.SectionCluster')}
styles={{ body: { paddingTop: 0 } }}
>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{t('adminDeploymentPreset.SectionCluster')}
</Typography.Text>
</Divider>
<Descriptions
<Descriptions
size="small"
column={2}
items={[
{
label: t('adminDeploymentPreset.ClusterMode'),
children: preset.cluster?.clusterMode || '-',
},
{
label: t('adminDeploymentPreset.ClusterSize'),
children: preset.cluster?.clusterSize ?? '-',
},
]}
/>
</BAICard>
<BAICard
size="small"
column={2}
items={[
{
label: t('adminDeploymentPreset.ClusterMode'),
children: preset.cluster?.clusterMode || '-',
},
{
label: t('adminDeploymentPreset.ClusterSize'),
children: preset.cluster?.clusterSize ?? '-',
},
]}
/>

<Divider
style={{ margin: '4px 0' }}
titlePlacement="left"
orientationMargin={0}
title={t('adminDeploymentPreset.SectionResources')}
styles={{ body: { paddingTop: 0 } }}
>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{t('adminDeploymentPreset.SectionResources')}
</Typography.Text>
</Divider>
<Descriptions
<BAIFlex direction="column" align="stretch" gap="xs">
<ResourceNumbersOfSession
resource={(() => {
const slots = (preset.resourceSlots?.edges ?? [])
.map((e) => e?.node)
.filter((n) => n != null);
const cpuSlot = slots.find((n) => n!.slotName === 'cpu');
const memSlot = slots.find((n) => n!.slotName === 'mem');
const accelSlot = slots.find(
(n) => n!.slotName !== 'cpu' && n!.slotName !== 'mem',
);
const memBytes = memSlot ? parseFloat(memSlot.quantity) : 0;
const memGiB =
convertToBinaryUnit(memBytes, 'g', 0, true)?.number ?? 0;
return {
cpu: cpuSlot ? parseFloat(cpuSlot.quantity) : 0,
mem: `${memGiB}g`,
...(accelSlot
Comment on lines +174 to +180
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convertToBinaryUnit(memBytes, 'g', 0, true) is called with fixed=0/round=true, but the code reads .number, which is the unrounded value. This can produce inconsistent/over-precise mem values (e.g., 1.5g) despite requesting 0 decimals. Use the converted numberFixed (or value) instead, or drop the rounding parameters if decimals are desired.

Copilot uses AI. Check for mistakes.
? {
accelerator: parseFloat(accelSlot.quantity),
acceleratorType: accelSlot.slotName,
}
: {}),
};
})()}
/>
{(shmem ||
(preset.resource?.resourceOpts?.filter((o) => o.name !== 'shmem')
.length ?? 0) > 0) && (
<Descriptions
size="small"
column={2}
items={[
...(shmem
? [
{
label: t('adminDeploymentPreset.Shmem'),
children: `${shmem} GiB`,
},
]
: []),
...(preset.resource?.resourceOpts
?.filter((opt) => opt.name !== 'shmem')
.map((opt) => ({
label: opt.name,
children: opt.value,
})) ?? []),
]}
/>
)}
</BAIFlex>
</BAICard>
<BAICard
size="small"
column={2}
items={[
...(shmem
? [
{
label: t('adminDeploymentPreset.Shmem'),
children: `${shmem} GiB`,
},
]
: []),
...(preset.resource?.resourceOpts
?.filter((opt) => opt.name !== 'shmem')
.map((opt) => ({
label: opt.name,
children: opt.value,
})) ?? []),
]}
/>

<Divider
style={{ margin: '4px 0' }}
titlePlacement="left"
orientationMargin={0}
title={t('adminDeploymentPreset.SectionDeploymentDefaults')}
styles={{ body: { paddingTop: 0 } }}
>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{t('adminDeploymentPreset.SectionDeploymentDefaults')}
</Typography.Text>
</Divider>
<Descriptions
size="small"
column={2}
items={[
{
label: t('adminDeploymentPreset.Replicas'),
children: preset.deploymentDefaults?.replicaCount ?? '-',
},
{
label: t('adminDeploymentPreset.RevisionHistoryLimit'),
children: preset.deploymentDefaults?.revisionHistoryLimit ?? '-',
},
{
label: t('adminDeploymentPreset.Strategy'),
children: preset.deploymentDefaults?.deploymentStrategy ?? '-',
},
{
label: t('adminDeploymentPreset.OpenToPublic'),
children:
preset.deploymentDefaults?.openToPublic != null
? preset.deploymentDefaults.openToPublic
? t('button.Yes')
: t('button.No')
: '-',
},
]}
/>
<Descriptions
size="small"
column={2}
items={[
{
label: t('adminDeploymentPreset.Replicas'),
children: preset.deploymentDefaults?.replicaCount ?? '-',
},
{
label: t('adminDeploymentPreset.RevisionHistoryLimit'),
children: preset.deploymentDefaults?.revisionHistoryLimit ?? '-',
},
{
label: t('adminDeploymentPreset.Strategy'),
children: preset.deploymentDefaults?.deploymentStrategy ?? '-',
},
{
label: t('adminDeploymentPreset.OpenToPublic'),
children:
preset.deploymentDefaults?.openToPublic != null
? preset.deploymentDefaults.openToPublic
? t('button.Yes')
: t('button.No')
: '-',
},
]}
/>
</BAICard>
</BAIFlex>
);
};
Expand Down
Loading
Loading