| @@ -43,7 +43,7 @@ function ParameterInput({ | |||||
| } | } | ||||
| const isSelect = valueObj?.fromSelect; | const isSelect = valueObj?.fromSelect; | ||||
| const InputComponent = textArea ? Input.TextArea : Input; | const InputComponent = textArea ? Input.TextArea : Input; | ||||
| const placeholder = valueObj?.placeholder; | |||||
| const placeholder = valueObj?.placeholder || rest?.placeholder; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -0,0 +1,17 @@ | |||||
| .editor-create { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 10px; | |||||
| overflow: auto; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__type { | |||||
| color: @text-color; | |||||
| font-size: @font-size-input-lg; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,344 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 创建镜像 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import ParameterInput from '@/components/ParameterInput'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import ResourceSelectorModal, { | |||||
| ResourceSelectorResponse, | |||||
| ResourceSelectorType, | |||||
| selectorTypeConfig, | |||||
| } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||||
| import { createEditorReq } from '@/services/developmentEnvironment'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type FormData = { | |||||
| name: string; | |||||
| computing_resource: string; | |||||
| standard: string; | |||||
| image: string; | |||||
| model: ResourceSelectorResponse; | |||||
| dataset: ResourceSelectorResponse; | |||||
| }; | |||||
| enum ComputingResourceType { | |||||
| GPU = 'GPU', | |||||
| NPU = 'NPU', | |||||
| } | |||||
| const EditorRadioItems: KFRadioItem[] = [ | |||||
| { | |||||
| key: ComputingResourceType.GPU, | |||||
| title: '英伟达GPU', | |||||
| icon: <KFIcon type="icon-jiyugongwangjingxiang" />, | |||||
| }, | |||||
| { | |||||
| key: ComputingResourceType.NPU, | |||||
| title: '昇腾NPU', | |||||
| icon: <KFIcon type="icon-bendishangchuan" />, | |||||
| }, | |||||
| ]; | |||||
| function EditorCreate() { | |||||
| const navgite = useNavigate(); | |||||
| const [form] = Form.useForm(); | |||||
| const { message } = App.useApp(); | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||||
| const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的模型,为了再次打开时恢复原来的选择 | |||||
| const [selectedDataset, setSelectedDataset] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的数据集,为了再次打开时恢复原来的选择 | |||||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||||
| // 创建编辑器 | |||||
| const createEditor = async (formData: FormData) => { | |||||
| // const { model, dataset } = formData; | |||||
| // const params = { | |||||
| // ...formData, | |||||
| // model: JSON.stringify(omit(model, ['showValue'])), | |||||
| // dataset: JSON.stringify(dataset, ['showValue']), | |||||
| // }; | |||||
| const [res] = await to(createEditorReq(formData)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navgite(-1); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const handleSubmit = (values: FormData) => { | |||||
| createEditor(values); | |||||
| }; | |||||
| // 取消 | |||||
| const cancel = () => { | |||||
| navgite(-1); | |||||
| }; | |||||
| // 获取选择数据集、模型后面按钮 icon | |||||
| const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | |||||
| }; | |||||
| // 选择模型、镜像、数据集 | |||||
| const selectResource = (name: string, type: ResourceSelectorType) => { | |||||
| let resource: ResourceSelectorResponse | undefined; | |||||
| switch (type) { | |||||
| case ResourceSelectorType.Model: | |||||
| resource = selectedModel; | |||||
| break; | |||||
| case ResourceSelectorType.Dataset: | |||||
| resource = selectedDataset; | |||||
| break; | |||||
| default: | |||||
| resource = selectedMirror; | |||||
| break; | |||||
| } | |||||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||||
| type, | |||||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||||
| defaultActiveTab: resource?.activeTab, | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| form.setFieldValue(name, res.path); | |||||
| setSelectedMirror(res); | |||||
| } else { | |||||
| const showValue = `${res.name}:${res.version}`; | |||||
| form.setFieldValue(name, { | |||||
| ...pick(res, ['id', 'version', 'path']), | |||||
| showValue, | |||||
| }); | |||||
| if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| } else if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } | |||||
| } | |||||
| } else { | |||||
| if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(undefined); | |||||
| } else if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(undefined); | |||||
| } else if (type === ResourceSelectorType.Mirror) { | |||||
| setSelectedMirror(undefined); | |||||
| } | |||||
| form.setFieldValue(name, ''); | |||||
| } | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['editor-create']}> | |||||
| <PageTitle title="创建编辑器"></PageTitle> | |||||
| <div className={styles['editor-create__content']}> | |||||
| <div> | |||||
| <Form | |||||
| name="editor-create" | |||||
| labelCol={{ flex: '100px' }} | |||||
| wrapperCol={{ flex: 1 }} | |||||
| labelAlign="left" | |||||
| form={form} | |||||
| initialValues={{ computing_resource: ComputingResourceType.GPU }} | |||||
| onFinish={handleSubmit} | |||||
| size="large" | |||||
| > | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="任务名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入任务名称" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={10}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="计算资源" | |||||
| name="computing_resource" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择计算资源', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <KFRadio items={EditorRadioItems}></KFRadio> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| showSearch | |||||
| placeholder="请选择资源规格" | |||||
| filterOption={filterResourceStandard} | |||||
| options={resourceStandardList} | |||||
| fieldNames={{ | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <SubAreaTitle | |||||
| title="参数设置" | |||||
| image={require('@/assets/img/editor-parameter.png')} | |||||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="镜像" | |||||
| name="image" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterInput | |||||
| placeholder="请选择镜像" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <Button | |||||
| size="large" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| > | |||||
| 选择镜像 | |||||
| </Button> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="模型" | |||||
| name="model" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择模型', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterInput | |||||
| placeholder="请选择模型" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <Button | |||||
| size="large" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | |||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| > | |||||
| 选择模型 | |||||
| </Button> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="数据集" | |||||
| name="dataset" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择数据集', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterInput | |||||
| placeholder="请选择数据集" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <Button | |||||
| size="large" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(ResourceSelectorType.Dataset)} | |||||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||||
| > | |||||
| 选择数据集 | |||||
| </Button> | |||||
| </Col> | |||||
| </Row> | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||||
| <Button type="primary" htmlType="submit"> | |||||
| 确定 | |||||
| </Button> | |||||
| <Button | |||||
| type="default" | |||||
| htmlType="button" | |||||
| onClick={cancel} | |||||
| style={{ marginLeft: '20px' }} | |||||
| > | |||||
| 取消 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default EditorCreate; | |||||
| @@ -0,0 +1,22 @@ | |||||
| .develop-env { | |||||
| height: 100%; | |||||
| &__header { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: flex-end; | |||||
| height: 50px; | |||||
| margin-bottom: 10px; | |||||
| padding: 0 30px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| &__table { | |||||
| height: calc(100% - 60px); | |||||
| padding: 20px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,263 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 开发环境 | |||||
| */ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { DevEditorStatus } from '@/enums'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { | |||||
| deleteEditorReq, | |||||
| getEditorListReq, | |||||
| startEditorReq, | |||||
| stopEditorReq, | |||||
| } from '@/services/developmentEnvironment'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { | |||||
| App, | |||||
| Button, | |||||
| ConfigProvider, | |||||
| Table, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import EditorStatusCell from '../components/EditorStatusCell'; | |||||
| import styles from './index.less'; | |||||
| export type EditorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| status: string; | |||||
| computing_resource: string; | |||||
| update_by: string; | |||||
| create_time: string; | |||||
| }; | |||||
| function EditorList() { | |||||
| const navigate = useNavigate(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const { message } = App.useApp(); | |||||
| const [tableData, setTableData] = useState<EditorData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| useEffect(() => { | |||||
| getEditorList(); | |||||
| }, [pagination]); | |||||
| // 获取编辑器列表 | |||||
| const getEditorList = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| }; | |||||
| const [res] = await to(getEditorListReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除编辑器 | |||||
| const deleteEditor = async (id: number) => { | |||||
| const [res] = await to(deleteEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getEditorList(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 启动编辑器 | |||||
| const startEditor = async (id: number) => { | |||||
| const [res] = await to(startEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getEditorList(); | |||||
| } | |||||
| }; | |||||
| // 停止编辑器 | |||||
| const stopEditor = async (id: number) => { | |||||
| const [res] = await to(stopEditorReq(id)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getEditorList(); | |||||
| } | |||||
| }; | |||||
| // 处理删除 | |||||
| const handleEditorDelete = (record: EditorData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该编辑器将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteEditor(record.id); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建编辑器 | |||||
| const createEditor = () => { | |||||
| navigate(`/developmentEnvironment/create`); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| }); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<EditorData>['columns'] = [ | |||||
| { | |||||
| title: '编辑器名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '30%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: '10%', | |||||
| render: EditorStatusCell, | |||||
| }, | |||||
| { | |||||
| title: '资源', | |||||
| dataIndex: 'computing_resource', | |||||
| key: 'computing_resource', | |||||
| width: '20%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '创建者', | |||||
| dataIndex: 'update_by', | |||||
| key: 'update_by', | |||||
| width: '20%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '创建时间', | |||||
| dataIndex: 'create_time', | |||||
| key: 'create_time', | |||||
| width: '20%', | |||||
| render: DateTableCell, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 300, | |||||
| key: 'operation', | |||||
| render: (_: any, record: EditorData) => ( | |||||
| <div> | |||||
| {record.status === DevEditorStatus.Pending || | |||||
| record.status === DevEditorStatus.Running ? ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="stop" | |||||
| icon={<KFIcon type="icon-tingzhi" />} | |||||
| onClick={() => stopEditor(record.id)} | |||||
| > | |||||
| 停止 | |||||
| </Button> | |||||
| ) : ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="debug" | |||||
| icon={<KFIcon type="icon-tiaoshi" />} | |||||
| onClick={() => startEditor(record.id)} | |||||
| > | |||||
| 再次调试 | |||||
| </Button> | |||||
| )} | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleEditorDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['develop-env']}> | |||||
| <div className={styles['develop-env__header']}> | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| type="default" | |||||
| onClick={createEditor} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 创建编辑器 | |||||
| </Button> | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| type="default" | |||||
| onClick={getEditorList} | |||||
| icon={<KFIcon type="icon-shuaxin" />} | |||||
| > | |||||
| 刷新 | |||||
| </Button> | |||||
| </div> | |||||
| <div className={classNames('vertical-scroll-table', styles['develop-env__table'])}> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default EditorList; | |||||
| @@ -0,0 +1,19 @@ | |||||
| .model-deployment-status-cell { | |||||
| color: @text-color; | |||||
| &--running { | |||||
| color: @primary-color; | |||||
| } | |||||
| &--terminated { | |||||
| color: @abort-color; | |||||
| } | |||||
| &--error { | |||||
| color: @error-color; | |||||
| } | |||||
| &--pending { | |||||
| color: @warning-color; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-18 18:35:41 | |||||
| * @Description: 编辑器状态组件 | |||||
| */ | |||||
| import { DevEditorStatus } from '@/enums'; | |||||
| import styles from './index.less'; | |||||
| export type DevEditorStatusInfo = { | |||||
| text: string; | |||||
| classname: string; | |||||
| }; | |||||
| export const statusInfo: Record<DevEditorStatus, DevEditorStatusInfo> = { | |||||
| [DevEditorStatus.Unknown]: { | |||||
| text: '未启动', | |||||
| classname: styles['model-deployment-status-cell'], | |||||
| }, | |||||
| [DevEditorStatus.Running]: { | |||||
| classname: styles['model-deployment-status-cell--running'], | |||||
| text: '运行中', | |||||
| }, | |||||
| [DevEditorStatus.Terminated]: { | |||||
| classname: styles['model-deployment-status-cell--terminated'], | |||||
| text: '已停止', | |||||
| }, | |||||
| [DevEditorStatus.Failed]: { | |||||
| classname: styles['model-deployment-status-cell--error'], | |||||
| text: '失败', | |||||
| }, | |||||
| [DevEditorStatus.Pending]: { | |||||
| classname: styles['model-deployment-status-cell--pending'], | |||||
| text: '启动中', | |||||
| }, | |||||
| }; | |||||
| function EditorStatusCell(status?: DevEditorStatus | null) { | |||||
| if (status === null || status === undefined || !statusInfo[status]) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||||
| } | |||||
| export default EditorStatusCell; | |||||
| @@ -31,23 +31,5 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| } | } | ||||
| &__table { | |||||
| :global { | |||||
| .ant-table-wrapper { | |||||
| height: 100%; | |||||
| .ant-spin-nested-loading { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-spin-container { | |||||
| height: 100%; | |||||
| } | |||||
| .ant-table { | |||||
| height: calc(100% - 74px); | |||||
| overflow: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -32,7 +32,6 @@ import { | |||||
| type TablePaginationConfig, | type TablePaginationConfig, | ||||
| type TableProps, | type TableProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | import { useEffect, useMemo, useState } from 'react'; | ||||
| import MirrorStatusCell from '../components/MirrorStatusCell'; | import MirrorStatusCell from '../components/MirrorStatusCell'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -282,7 +281,7 @@ function MirrorInfo() { | |||||
| </Flex> | </Flex> | ||||
| </div> | </div> | ||||
| <div | <div | ||||
| className={classNames('vertical-scroll-table', styles['mirror-info__content__table'])} | |||||
| className="vertical-scroll-table" | |||||
| style={{ marginTop: '24px', height: `calc(100% - ${topHeight + 24}px)` }} | style={{ marginTop: '24px', height: `calc(100% - ${topHeight + 24}px)` }} | ||||
| > | > | ||||
| <Table | <Table | ||||
| @@ -63,6 +63,9 @@ function ModelDeploymentCreate() { | |||||
| const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | ||||
| undefined, | undefined, | ||||
| ); | ); | ||||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||||
| undefined, | |||||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -84,16 +87,14 @@ function ModelDeploymentCreate() { | |||||
| }; | }; | ||||
| // 选择模型、镜像 | // 选择模型、镜像 | ||||
| const selectResource = (name: string, selectType: string) => { | |||||
| let type; | |||||
| const selectResource = (name: string, type: ResourceSelectorType) => { | |||||
| let resource: ResourceSelectorResponse | undefined; | let resource: ResourceSelectorResponse | undefined; | ||||
| switch (selectType) { | |||||
| case 'model': | |||||
| type = ResourceSelectorType.Model; | |||||
| switch (type) { | |||||
| case ResourceSelectorType.Model: | |||||
| resource = selectedModel; | resource = selectedModel; | ||||
| break; | break; | ||||
| default: | default: | ||||
| type = ResourceSelectorType.Mirror; | |||||
| resource = selectedMirror; | |||||
| break; | break; | ||||
| } | } | ||||
| const { close } = openAntdModal(ResourceSelectorModal, { | const { close } = openAntdModal(ResourceSelectorModal, { | ||||
| @@ -105,18 +106,20 @@ function ModelDeploymentCreate() { | |||||
| if (res) { | if (res) { | ||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| form.setFieldValue(name, res.path); | form.setFieldValue(name, res.path); | ||||
| setSelectedMirror(res); | |||||
| } else { | } else { | ||||
| const response = res as ResourceSelectorResponse; | |||||
| const showValue = `${response.name}:${response.version}`; | |||||
| const showValue = `${res.name}:${res.version}`; | |||||
| form.setFieldValue(name, { | form.setFieldValue(name, { | ||||
| ...pick(response, ['id', 'version', 'path']), | |||||
| ...pick(res, ['id', 'version', 'path']), | |||||
| showValue, | showValue, | ||||
| }); | }); | ||||
| setSelectedModel(response); | |||||
| setSelectedModel(res); | |||||
| } | } | ||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Model) { | if (type === ResourceSelectorType.Model) { | ||||
| setSelectedModel(undefined); | setSelectedModel(undefined); | ||||
| } else { | |||||
| setSelectedMirror(undefined); | |||||
| } | } | ||||
| form.setFieldValue(name, ''); | form.setFieldValue(name, ''); | ||||
| } | } | ||||
| @@ -248,7 +251,6 @@ function ModelDeploymentCreate() { | |||||
| image={require('@/assets/img/model-deployment.png')} | image={require('@/assets/img/model-deployment.png')} | ||||
| style={{ marginTop: '20px', marginBottom: '24px' }} | style={{ marginTop: '20px', marginBottom: '24px' }} | ||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| <Row gutter={8}> | <Row gutter={8}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -266,6 +268,7 @@ function ModelDeploymentCreate() { | |||||
| disabled={disabled} | disabled={disabled} | ||||
| canInput={false} | canInput={false} | ||||
| size="large" | size="large" | ||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| @@ -275,7 +278,7 @@ function ModelDeploymentCreate() { | |||||
| size="large" | size="large" | ||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | icon={getSelectBtnIcon(ResourceSelectorType.Model)} | ||||
| onClick={() => selectResource('model', 'model')} | |||||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||||
| > | > | ||||
| 选择模型 | 选择模型 | ||||
| </Button> | </Button> | ||||
| @@ -293,7 +296,12 @@ function ModelDeploymentCreate() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <ParameterInput placeholder="请选择镜像" canInput={false} size="large" /> | |||||
| <ParameterInput | |||||
| placeholder="请选择镜像" | |||||
| canInput={false} | |||||
| size="large" | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| @@ -301,7 +309,7 @@ function ModelDeploymentCreate() { | |||||
| size="large" | size="large" | ||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | ||||
| onClick={() => selectResource('image', 'image')} | |||||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||||
| > | > | ||||
| 选择镜像 | 选择镜像 | ||||
| </Button> | </Button> | ||||
| @@ -1,16 +0,0 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| // 查询开发环境url | |||||
| export function getJupyterUrl(params) { | |||||
| return request(`/api/mmp/jupyter/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 查询 labelStudio url | |||||
| export function getLabelStudioUrl(params) { | |||||
| return request(`/api/mmp/labelStudio/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,59 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| // 查询开发环境url | |||||
| export function getJupyterUrl(params: any) { | |||||
| return request(`/api/mmp/jupyter/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 查询 labelStudio url | |||||
| export function getLabelStudioUrl(params: any) { | |||||
| return request(`/api/mmp/labelStudio/getURL`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 创建编辑器 | |||||
| export function createEditorReq(data: any) { | |||||
| return request(`/api/mmp/devEnvironment`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 获取编辑器列表 | |||||
| export function getEditorListReq(params: any) { | |||||
| return request(`/api/mmp/devEnvironment`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 获取编辑器详情 | |||||
| export function getEditorInfoReq(id: number) { | |||||
| return request(`/api/mmp/devEnvironment/${id}`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 创建编辑器 | |||||
| export function deleteEditorReq(id: number) { | |||||
| return request(`/api/mmp/devEnvironment/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 启动编辑器 | |||||
| export function startEditorReq(id: number) { | |||||
| return request(`/api/mmp/jupyter/run/${id}`, { | |||||
| method: 'POST', | |||||
| }); | |||||
| } | |||||
| // 停止编辑器 | |||||
| export function stopEditorReq(id: number) { | |||||
| return request(`/api/mmp/jupyter/stop/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||