Skip to content
Draft
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
1 change: 1 addition & 0 deletions packages/backend.ai-ui/src/components/Table/BAITable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ const BAITable = <RecordType extends object = any>({
opacity: loading ? 0.7 : 1,
transition: 'opacity 0.3s ease',
}}
scroll={tableProps.scroll || { x: 'max-content' }}
components={
resizable
? _.merge(components || {}, {
Expand Down
2 changes: 1 addition & 1 deletion react/src/components/AccessTokenList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const AccessTokenList: React.FC<AccessTokenListProps> = ({
},
{
key: 'validUntil',
title: t('deployment.ExpiredDate'),
title: t('deployment.ExpirationDate'),
dataIndex: 'validUntil',
render: (value) => dayjs(value).format('LLL'),
},
Expand Down
139 changes: 139 additions & 0 deletions react/src/components/DeploymentModifyModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import BAIModal, { BAIModalProps } from './BAIModal';
import DeploymentMetadataFormItem from './DeploymentMetadataFormItem';
import DeploymentNetworkAccessFormItem from './DeploymentNetworkAccessFormItem';
import DeploymentStrategyFormItem from './DeploymentStrategyFormItem';
import { App, Form, FormInstance } from 'antd';
import { toLocalId } from 'backend.ai-ui';
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useFragment, useMutation } from 'react-relay';
import {
DeploymentModifyModalFragment$data,
DeploymentModifyModalFragment$key,
} from 'src/__generated__/DeploymentModifyModalFragment.graphql';
import { DeploymentModifyModalMutation } from 'src/__generated__/DeploymentModifyModalMutation.graphql';

interface DeploymentModifyModalProps extends BAIModalProps {
deploymentFrgmt?: DeploymentModifyModalFragment$key | null;
onRequestClose: (success?: boolean) => void;
}

const DeploymentModifyModal: React.FC<DeploymentModifyModalProps> = ({
onRequestClose,
deploymentFrgmt,
...baiModalProps
}) => {
const { t } = useTranslation();
const { message } = App.useApp();
const formRef =
useRef<FormInstance<DeploymentModifyModalFragment$data>>(null);

const deployment = useFragment(
graphql`
fragment DeploymentModifyModalFragment on ModelDeployment {
id
metadata {
name
tags
}
networkAccess {
openToPublic
preferredDomainName
}
defaultDeploymentStrategy {
type
}
replicaState {
desiredReplicaCount
}
}
`,
deploymentFrgmt,
);

const [commitUpdateDeployment, isInFlightUpdateDeployment] =
useMutation<DeploymentModifyModalMutation>(graphql`
mutation DeploymentModifyModalMutation(
$input: UpdateModelDeploymentInput!
) {
updateModelDeployment(input: $input) {
deployment {
id
metadata {
name
tags
}
networkAccess {
openToPublic
}
defaultDeploymentStrategy {
type
}
}
}
}
`);

const handleOk = () => {
formRef.current?.validateFields().then((values) => {
commitUpdateDeployment({
variables: {
input: {
id: toLocalId(deployment?.id || ''),
name: values.metadata?.name,
tags: values.metadata?.tags,
defaultDeploymentStrategy: values?.defaultDeploymentStrategy,
desiredReplicaCount: values.replicaState?.desiredReplicaCount,
preferredDomainName: values.networkAccess?.preferredDomainName,
openToPublic: values.networkAccess?.openToPublic,
},
},
onCompleted: (res, errors) => {
if (!res?.updateModelDeployment?.deployment?.id) {
message.error(t('message.FailedToUpdate'));
return;
}
if (errors && errors.length > 0) {
const errorMsgList = errors.map((error) => error.message);
for (const error of errorMsgList) {
message.error(error);
}
} else {
message.success(t('message.SuccessfullyUpdated'));
onRequestClose(true);
}
},
onError: (err) => {
message.error(err.message || t('message.FailedToUpdate'));
},
});
});
};

return (
<BAIModal
{...baiModalProps}
destroyOnClose
onOk={handleOk}
onCancel={() => onRequestClose(false)}
okText={t('button.Update')}
confirmLoading={isInFlightUpdateDeployment}
title={t('deployment.ModifyDeployment')}
>
<Form
ref={formRef}
layout="vertical"
initialValues={{
...deployment,
}}
style={{ maxWidth: '100%' }}
>
<DeploymentMetadataFormItem />
<DeploymentStrategyFormItem />
<DeploymentNetworkAccessFormItem />
</Form>
</BAIModal>
);
};

export default DeploymentModifyModal;
4 changes: 1 addition & 3 deletions react/src/components/DeploymentNetworkAccessFormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ const DeploymentNetworkAccessFormItem: React.FC = () => {
name={['networkAccess', 'preferredDomainName']}
label={t('deployment.launcher.PreferredDomainName')}
>
<Input
placeholder={t('deployment.launcher.PreferredDomainNamePlaceholder')}
/>
<Input placeholder={'my-model.example.com'} />
</Form.Item>

<Form.Item
Expand Down
2 changes: 1 addition & 1 deletion react/src/components/DeploymentStrategyFormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const DeploymentStrategyFormItem: React.FC = () => {
}}
</Form.Item>
<Form.Item
name={['desiredReplicaCount']}
name={['replicaState', 'desiredReplicaCount']}
label={t('deployment.NumberOfDesiredReplicas')}
rules={[
{
Expand Down
165 changes: 103 additions & 62 deletions react/src/components/DeploymentTokenGenerationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BAIModal, { BAIModalProps } from './BAIModal';
import { DatePicker, Form, FormInstance, message } from 'antd';
import { DatePicker, Form, FormInstance, message, Select } from 'antd';
import { BAIFlex } from 'backend.ai-ui';
import dayjs from 'dayjs';
import React, { useRef } from 'react';
Expand All @@ -18,6 +18,7 @@ const DeploymentTokenGenerationModal: React.FC<
> = ({ onRequestClose, onCancel, deploymentId, ...baiModalProps }) => {
const { t } = useTranslation();
const formRef = useRef<FormInstance>(null);
// expiryOption은 Form.Item에서 관리

const [commitCreateAccessToken, isInFlightCreateAccessToken] =
useMutation<DeploymentTokenGenerationModalMutation>(graphql`
Expand All @@ -36,41 +37,51 @@ const DeploymentTokenGenerationModal: React.FC<
`);

const handleOk = () => {
formRef.current?.validateFields().then((values) => {
const validUntil = values.datetime.unix();
commitCreateAccessToken({
variables: {
input: {
validUntil: validUntil,
modelDeploymentId: deploymentId,
formRef.current
?.validateFields()
.then((values) => {
let validUntil;
if (values.expiryOption === 'custom') {
validUntil = values.datetime.unix();
} else {
const daysToAdd = parseInt(values.expiryOption.replace('days', ''));
validUntil = dayjs().add(daysToAdd, 'day').unix();
}

commitCreateAccessToken({
variables: {
input: {
validUntil: validUntil,
modelDeploymentId: deploymentId,
},
},
},
onCompleted: (res, errors) => {
if (!res?.createAccessToken?.accessToken) {
message.error(t('deployment.TokenGenerationFailed'));
return;
}
if (errors && errors.length > 0) {
const errorMsgList = errors.map((error) => error.message);
for (const error of errorMsgList) {
message.error(error);
onCompleted: (res, errors) => {
if (!res?.createAccessToken?.accessToken) {
message.error(t('deployment.TokenGenerationFailed'));
return;
}
} else {
message.success(t('deployment.TokenGenerated'));
onRequestClose(true);
}
},
onError: (err) => {
if (err?.message?.includes('valid_until is older than now')) {
message.error(t('deployment.TokenExpiredDateError'));
return;
} else {
message.error(t('deployment.TokenGenerationFailed'));
console.log(err);
}
},
});
});
if (errors && errors.length > 0) {
const errorMsgList = errors.map((error) => error.message);
for (const error of errorMsgList) {
message.error(error);
}
} else {
message.success(t('deployment.TokenGenerated'));
onRequestClose(true);
}
},
onError: (err) => {
if (err?.message?.includes('valid_until is older than now')) {
message.error(t('deployment.TokenExpiredDateError'));
return;
} else {
message.error(t('deployment.TokenGenerationFailed'));
console.log(err);
}
},
});
})
.catch(() => {});
};

return (
Expand All @@ -81,45 +92,75 @@ const DeploymentTokenGenerationModal: React.FC<
onCancel={() => onRequestClose(false)}
okText={t('button.Generate')}
confirmLoading={isInFlightCreateAccessToken}
centered
title={t('deployment.GenerateNewToken')}
>
<Form
ref={formRef}
preserve={false}
labelCol={{ span: 10 }}
labelCol={{ span: 12 }}
initialValues={{
datetime: dayjs().add(24, 'hour'),
expiryOption: '7days',
datetime: dayjs().add(7, 'day'),
}}
validateTrigger={['onChange', 'onBlur']}
>
<BAIFlex direction="column" gap="sm" align="stretch" justify="center">
<Form.Item
name="datetime"
label={t('deployment.ExpiredDate')}
rules={[
{
type: 'object',
required: true,
message: t('deployment.PleaseSelectTime'),
},
() => ({
validator(_, value) {
if (value.isAfter(dayjs())) {
return Promise.resolve();
}
return Promise.reject(
new Error(t('deployment.TokenExpiredDateError')),
);
<BAIFlex direction="row" align="stretch" justify="around">
<Form.Item
name="expiryOption"
label={t('deployment.ExpirationDate')}
rules={[
{
required: true,
message: t('deployment.PleaseSelectTime'),
},
}),
]}
>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
style={{ width: 200 }}
/>
]}
>
<Select
style={{ width: 200 }}
options={[
{ value: '7days', label: t('general.Days', { num: 7 }) },
{ value: '30days', label: t('general.Days', { num: 30 }) },
{ value: '90days', label: t('general.Days', { num: 90 }) },
{ value: 'custom', label: t('deployment.Custom') },
]}
/>
</Form.Item>
</BAIFlex>
<Form.Item dependencies={['expiryOption']} noStyle>
{({ getFieldValue }) =>
getFieldValue('expiryOption') === 'custom' ? (
<BAIFlex direction="row" align="stretch" justify="around">
<Form.Item
name="datetime"
label={t('deployment.CustomExpirationDate')}
rules={[
{
type: 'object' as const,
required: true,
message: t('deployment.PleaseSelectTime'),
},
() => ({
validator(_, value) {
if (value && value.isAfter(dayjs())) {
return Promise.resolve();
}
return Promise.reject(
new Error(t('deployment.TokenExpiredDateError')),
);
},
}),
]}
>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
style={{ width: 200 }}
/>
</Form.Item>
</BAIFlex>
) : null
}
</Form.Item>
</BAIFlex>
</Form>
Expand Down
Loading
Loading