| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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;
|
|
|