Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 83 additions & 34 deletions react/src/components/DeploymentAccessTokensTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
*/
import { DeploymentAccessTokensTabCreateMutation } from '../__generated__/DeploymentAccessTokensTabCreateMutation.graphql';
import { DeploymentAccessTokensTabDeleteMutation } from '../__generated__/DeploymentAccessTokensTabDeleteMutation.graphql';
import { DeploymentAccessTokensTabListQuery } from '../__generated__/DeploymentAccessTokensTabListQuery.graphql';
import { DeploymentAccessTokensTab_deployment$key } from '../__generated__/DeploymentAccessTokensTab_deployment.graphql';
import { PlusOutlined } from '@ant-design/icons';
import { App, Button, DatePicker, Form, Typography } from 'antd';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { App, Button, DatePicker, Form, Select, Typography } from 'antd';
import {
BAIFetchKeyButton,
BAIFlex,
Expand All @@ -23,7 +24,12 @@ import {
import dayjs from 'dayjs';
import React, { useState, useTransition } from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useFragment, useLazyLoadQuery } from 'react-relay';
import {
graphql,
useFragment,
useLazyLoadQuery,
useMutation,
} from 'react-relay';

interface DeploymentAccessTokensTabProps {
deploymentFrgmt: DeploymentAccessTokensTab_deployment$key;
Expand All @@ -40,7 +46,7 @@ const DeploymentAccessTokensTab: React.FC<DeploymentAccessTokensTabProps> = ({
}) => {
'use memo';
const { t } = useTranslation();
const { message } = App.useApp();
const { message, modal } = App.useApp();
const { logger } = useBAILogger();
const [isPendingRefetch, startRefetchTransition] = useTransition();
const [fetchKey, setFetchKey] = useState(0);
Expand Down Expand Up @@ -110,6 +116,17 @@ const DeploymentAccessTokensTab: React.FC<DeploymentAccessTokensTabProps> = ({
}
`);

const [commitDelete, isDeletingToken] =
useMutation<DeploymentAccessTokensTabDeleteMutation>(graphql`
mutation DeploymentAccessTokensTabDeleteMutation(
$input: DeleteAccessTokenInput!
) {
deleteAccessToken(input: $input) {
id
}
}
`);

const handleRefetch = () => {
startRefetchTransition(() => {
setFetchKey((k) => k + 1);
Expand Down Expand Up @@ -137,7 +154,7 @@ const DeploymentAccessTokensTab: React.FC<DeploymentAccessTokensTabProps> = ({
<BAITable<AccessTokenNode>
scroll={{ x: 'max-content' }}
rowKey="id"
loading={isPendingRefetch}
loading={isPendingRefetch || isDeletingToken}
dataSource={accessTokens}
pagination={false}
columns={[
Expand All @@ -160,10 +177,53 @@ const DeploymentAccessTokensTab: React.FC<DeploymentAccessTokensTabProps> = ({
</BAIText>
}
showActions="always"
// TODO(needs-backend): deleteAccessToken mutation is
// defined in the schema but not yet deployed to the
// running supergraph. Restore once the server is updated.
actions={[]}
actions={[
{
key: 'delete',
title: t('deployment.accessToken.Delete'),
icon: <DeleteOutlined />,
type: 'danger',
disabled:
isDeploymentDestroying || !isOwnedByCurrentUser,
onClick: () => {
modal.confirm({
title: t('deployment.accessToken.Delete'),
content: t('deployment.accessToken.DeleteConfirm'),
okText: t('button.Delete'),
okButtonProps: { danger: true },
onOk: () => {
commitDelete({
variables: {
input: {
id: toLocalId(row.id) ?? row.id,
},
},
onCompleted: (_res, errors) => {
if (errors && errors.length > 0) {
logger.error(errors[0]);
message.error(
errors[0]?.message ??
t('dialog.ErrorOccurred'),
);
return;
}
message.success(
t('deployment.accessToken.Deleted'),
);
handleRefetch();
},
onError: (err) => {
logger.error(err);
message.error(
err.message ?? t('dialog.ErrorOccurred'),
);
},
});
},
});
},
},
]}
/>
);
},
Expand Down Expand Up @@ -361,30 +421,20 @@ const CreateAccessTokenModal: React.FC<CreateAccessTokenModalProps> = ({
}}
validateTrigger={['onChange', 'onBlur']}
>
{/* Hidden field so validateFields() includes expiryOption. */}
<Form.Item name="expiryOption" hidden rules={[{ required: true }]}>
<input type="hidden" />
</Form.Item>
<Form.Item label={t('deployment.accessToken.Expiration')} required>
<BAIFlex direction="column" align="stretch" gap="xs">
{options.map((opt) => (
<Button
key={String(opt.value)}
type={expiryOption === opt.value ? 'primary' : 'default'}
onClick={() => {
form.setFieldValue('expiryOption', opt.value);
if (typeof opt.value === 'number') {
form.setFieldValue(
'datetime',
dayjs().add(opt.value, 'day'),
);
}
}}
>
{opt.label}
</Button>
))}
</BAIFlex>
<Form.Item
name="expiryOption"
label={t('deployment.accessToken.Expiration')}
rules={[{ required: true }]}
>
<Select<ExpiryOption>
style={{ width: 200 }}
options={options}
onChange={(value) => {
if (typeof value === 'number') {
form.setFieldValue('datetime', dayjs().add(value, 'day'));
}
}}
/>
</Form.Item>
{expiryOption === 'custom' && (
<Form.Item
Expand All @@ -394,7 +444,6 @@ const CreateAccessTokenModal: React.FC<CreateAccessTokenModalProps> = ({
{
type: 'object' as const,
required: true,
message: t('dialog.ErrorOccurred'),
},
() => ({
validator(_rule, value) {
Expand Down
19 changes: 17 additions & 2 deletions react/src/components/DeploymentAddRevisionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,15 @@ const DeploymentAddRevisionModalFormBody: React.FC<
onCompleted: (_, errors) => {
onIsAddingChange(false);
if (errors && errors.length > 0) {
message.error(errors[0]?.message ?? t('general.ErrorOccurred'));
const err = errors[0];
const isInProgress = err?.message?.includes(
'Another deployment is already in progress',
);
message.error(
isInProgress
? t('deployment.AnotherDeploymentInProgress')
: (err?.message ?? t('general.ErrorOccurred')),
);
return;
}
form.resetFields();
Expand All @@ -412,7 +420,14 @@ const DeploymentAddRevisionModalFormBody: React.FC<
},
onError: (err) => {
onIsAddingChange(false);
message.error(err.message ?? t('general.ErrorOccurred'));
const isInProgress = err.message?.includes(
'Another deployment is already in progress',
);
message.error(
isInProgress
? t('deployment.AnotherDeploymentInProgress')
: (err.message ?? t('general.ErrorOccurred')),
);
},
});
};
Expand Down
9 changes: 4 additions & 5 deletions react/src/components/DeploymentLauncherPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ const DeploymentLauncherPageContent: React.FC<
<Form.Item name="tags" label={t('deployment.Tags')}>
<Select
mode="tags"
open={false}
tokenSeparators={[',']}
placeholder={t('deployment.TagsPlaceholder')}
notFoundContent={null}
Expand Down Expand Up @@ -647,9 +648,7 @@ const DeploymentLauncherPageContent: React.FC<
<VFolderSelect
valuePropName="id"
filter={(vf) =>
vf.usage_mode === 'model' &&
vf.status === 'ready' &&
vf.ownership_type !== 'group'
vf.usage_mode === 'model' && vf.status === 'ready'
}
disabled={mode === 'edit'}
showOpenButton
Expand Down Expand Up @@ -868,9 +867,9 @@ const DeploymentLauncherPageContent: React.FC<
<Form.Item
name="desiredReplicaCount"
label={t('deployment.DesiredReplicas')}
rules={[{ required: true }]}
rules={[{ required: true, type: 'number', min: 1 }]}
>
<InputNumber min={0} style={{ width: '100%' }} />
<InputNumber min={1} style={{ width: '100%' }} />
</Form.Item>
</Card>

Expand Down
1 change: 0 additions & 1 deletion react/src/components/DeploymentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,6 @@ const DeploymentList: React.FC<DeploymentListProps> = ({
rowKey="id"
scroll={{ x: 'max-content' }}
showSorterTooltip={false}
locale={{ emptyText: t('deployment.NoDeployments') }}
{...tableProps}
dataSource={deployments}
columns={columns}
Expand Down
15 changes: 9 additions & 6 deletions react/src/components/DeploymentRevisionHistoryTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
BAIGraphQLPropertyFilter,
BAINameActionCell,
BAITable,
BAIUnmountAfterClose,
type GraphQLFilter,
filterOutNullAndUndefined,
toLocalId,
Expand Down Expand Up @@ -457,12 +458,14 @@ const DeploymentRevisionHistoryTab: React.FC<

return (
<>
<DeploymentRevisionDetailDrawer
revisionId={drawerRevisionId}
currentRevisionId={currentRevisionId}
open={!!drawerRevisionId}
onClose={() => setDrawerRevisionId(null)}
/>
<BAIUnmountAfterClose>
<DeploymentRevisionDetailDrawer
revisionId={drawerRevisionId}
currentRevisionId={currentRevisionId}
open={!!drawerRevisionId}
onClose={() => setDrawerRevisionId(null)}
/>
</BAIUnmountAfterClose>
<DeploymentAddRevisionModal
open={isAddRevisionModalOpen}
onClose={() => setIsAddRevisionModalOpen(false)}
Expand Down
2 changes: 1 addition & 1 deletion react/src/components/ResourcePresetSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const ResourcePresetSelect: React.FC<ResourcePresetSelectProps> = ({
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autoSelectDefault]);
}, [autoSelectDefault, firstAvailablePresetName]);

return (
<Select
Expand Down
Loading
Loading