|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect, useRef } from 'react'; |
|
|
import { |
|
|
SideSheet, |
|
|
Form, |
|
|
Button, |
|
|
Space, |
|
|
Spin, |
|
|
Typography, |
|
|
Card, |
|
|
InputNumber, |
|
|
Select, |
|
|
Input, |
|
|
Row, |
|
|
Col, |
|
|
Divider, |
|
|
Tag, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import { Save, X, Server } from 'lucide-react'; |
|
|
import { API, showError, showSuccess } from '../../../../helpers'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
import { useIsMobile } from '../../../../hooks/common/useIsMobile'; |
|
|
|
|
|
const { Text, Title } = Typography; |
|
|
|
|
|
const EditDeploymentModal = ({ |
|
|
refresh, |
|
|
editingDeployment, |
|
|
visible, |
|
|
handleClose, |
|
|
}) => { |
|
|
const { t } = useTranslation(); |
|
|
const isMobile = useIsMobile(); |
|
|
const [loading, setLoading] = useState(false); |
|
|
const [models, setModels] = useState([]); |
|
|
const [loadingModels, setLoadingModels] = useState(false); |
|
|
const formRef = useRef(); |
|
|
|
|
|
const isEdit = Boolean(editingDeployment?.id); |
|
|
const title = t('重命名部署'); |
|
|
|
|
|
|
|
|
const cpuOptions = [ |
|
|
{ label: '0.5 Core', value: '0.5' }, |
|
|
{ label: '1 Core', value: '1' }, |
|
|
{ label: '2 Cores', value: '2' }, |
|
|
{ label: '4 Cores', value: '4' }, |
|
|
{ label: '8 Cores', value: '8' }, |
|
|
]; |
|
|
|
|
|
const memoryOptions = [ |
|
|
{ label: '1GB', value: '1Gi' }, |
|
|
{ label: '2GB', value: '2Gi' }, |
|
|
{ label: '4GB', value: '4Gi' }, |
|
|
{ label: '8GB', value: '8Gi' }, |
|
|
{ label: '16GB', value: '16Gi' }, |
|
|
{ label: '32GB', value: '32Gi' }, |
|
|
]; |
|
|
|
|
|
const gpuOptions = [ |
|
|
{ label: t('无GPU'), value: '' }, |
|
|
{ label: '1 GPU', value: '1' }, |
|
|
{ label: '2 GPUs', value: '2' }, |
|
|
{ label: '4 GPUs', value: '4' }, |
|
|
]; |
|
|
|
|
|
|
|
|
const loadModels = async () => { |
|
|
setLoadingModels(true); |
|
|
try { |
|
|
const res = await API.get('/api/models/?page_size=1000'); |
|
|
if (res.data.success) { |
|
|
const items = res.data.data.items || res.data.data || []; |
|
|
const modelOptions = items.map((model) => ({ |
|
|
label: `${model.model_name} (${model.vendor?.name || 'Unknown'})`, |
|
|
value: model.model_name, |
|
|
model_id: model.id, |
|
|
})); |
|
|
setModels(modelOptions); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Failed to load models:', error); |
|
|
showError(t('加载模型列表失败')); |
|
|
} |
|
|
setLoadingModels(false); |
|
|
}; |
|
|
|
|
|
|
|
|
const handleSubmit = async (values) => { |
|
|
if (!isEdit || !editingDeployment?.id) { |
|
|
showError(t('无效的部署信息')); |
|
|
return; |
|
|
} |
|
|
|
|
|
setLoading(true); |
|
|
try { |
|
|
|
|
|
const res = await API.put( |
|
|
`/api/deployments/${editingDeployment.id}/name`, |
|
|
{ |
|
|
name: values.deployment_name, |
|
|
}, |
|
|
); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess(t('部署名称更新成功')); |
|
|
handleClose(); |
|
|
refresh(); |
|
|
} else { |
|
|
showError(res.data.message || t('更新失败')); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Submit error:', error); |
|
|
showError(t('更新失败,请检查输入信息')); |
|
|
} |
|
|
setLoading(false); |
|
|
}; |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (visible) { |
|
|
loadModels(); |
|
|
} |
|
|
}, [visible]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (formRef.current && editingDeployment && visible && isEdit) { |
|
|
formRef.current.setValues({ |
|
|
deployment_name: editingDeployment.deployment_name || '', |
|
|
}); |
|
|
} |
|
|
}, [editingDeployment, visible, isEdit]); |
|
|
|
|
|
return ( |
|
|
<SideSheet |
|
|
title={ |
|
|
<div className='flex items-center gap-2'> |
|
|
<Server size={20} /> |
|
|
<span>{title}</span> |
|
|
</div> |
|
|
} |
|
|
visible={visible} |
|
|
onCancel={handleClose} |
|
|
width={isMobile ? '100%' : 600} |
|
|
bodyStyle={{ padding: 0 }} |
|
|
maskClosable={false} |
|
|
closeOnEsc={true} |
|
|
> |
|
|
<div className='p-6 h-full overflow-auto'> |
|
|
<Spin spinning={loading} style={{ width: '100%' }}> |
|
|
<Form |
|
|
ref={formRef} |
|
|
onSubmit={handleSubmit} |
|
|
labelPosition='top' |
|
|
style={{ width: '100%' }} |
|
|
> |
|
|
<Card> |
|
|
<Title heading={5} style={{ marginBottom: 16 }}> |
|
|
{t('修改部署名称')} |
|
|
</Title> |
|
|
|
|
|
<Row gutter={16}> |
|
|
<Col span={24}> |
|
|
<Form.Input |
|
|
field='deployment_name' |
|
|
label={t('部署名称')} |
|
|
placeholder={t('请输入新的部署名称')} |
|
|
rules={[ |
|
|
{ required: true, message: t('请输入部署名称') }, |
|
|
{ |
|
|
pattern: /^[a-zA-Z0-9-_\u4e00-\u9fa5]+$/, |
|
|
message: t( |
|
|
'部署名称只能包含字母、数字、横线、下划线和中文', |
|
|
), |
|
|
}, |
|
|
]} |
|
|
/> |
|
|
</Col> |
|
|
</Row> |
|
|
|
|
|
{isEdit && ( |
|
|
<div className='mt-4 p-3 bg-gray-50 rounded'> |
|
|
<Text type='secondary'>{t('部署ID')}: </Text> |
|
|
<Text code>{editingDeployment.id}</Text> |
|
|
<br /> |
|
|
<Text type='secondary'>{t('当前状态')}: </Text> |
|
|
<Tag |
|
|
color={ |
|
|
editingDeployment.status === 'running' ? 'green' : 'grey' |
|
|
} |
|
|
> |
|
|
{editingDeployment.status} |
|
|
</Tag> |
|
|
</div> |
|
|
)} |
|
|
</Card> |
|
|
</Form> |
|
|
</Spin> |
|
|
</div> |
|
|
|
|
|
<div className='p-4 border-t border-gray-200 bg-gray-50 flex justify-end'> |
|
|
<Space> |
|
|
<Button theme='outline' onClick={handleClose} disabled={loading}> |
|
|
<X size={16} className='mr-1' /> |
|
|
{t('取消')} |
|
|
</Button> |
|
|
<Button |
|
|
theme='solid' |
|
|
type='primary' |
|
|
loading={loading} |
|
|
onClick={() => formRef.current?.submitForm()} |
|
|
> |
|
|
<Save size={16} className='mr-1' /> |
|
|
{isEdit ? t('更新') : t('创建')} |
|
|
</Button> |
|
|
</Space> |
|
|
</div> |
|
|
</SideSheet> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default EditDeploymentModal; |
|
|
|