| @@ -1,6 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import iconData from '@/iconfont/iconfont.json'; | |||||
| import iconData from '@/iconfont/iconfont-menu.json'; | |||||
| import { type ModalProps } from 'antd'; | import { type ModalProps } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -5,6 +5,48 @@ | |||||
| "css_prefix_text": "icon-", | "css_prefix_text": "icon-", | ||||
| "description": "", | "description": "", | ||||
| "glyphs": [ | "glyphs": [ | ||||
| { | |||||
| "icon_id": "41643218", | |||||
| "name": "模型开发", | |||||
| "font_class": "model-icon", | |||||
| "unicode": "e624", | |||||
| "unicode_decimal": 58916 | |||||
| }, | |||||
| { | |||||
| "icon_id": "41643132", | |||||
| "name": "工作空间", | |||||
| "font_class": "workspace-icon", | |||||
| "unicode": "e628", | |||||
| "unicode_decimal": 58920 | |||||
| }, | |||||
| { | |||||
| "icon_id": "41643135", | |||||
| "name": "系统管理", | |||||
| "font_class": "system-icon", | |||||
| "unicode": "e622", | |||||
| "unicode_decimal": 58914 | |||||
| }, | |||||
| { | |||||
| "icon_id": "41643131", | |||||
| "name": "数据准备", | |||||
| "font_class": "datasetPreparation-icon", | |||||
| "unicode": "e625", | |||||
| "unicode_decimal": 58917 | |||||
| }, | |||||
| { | |||||
| "icon_id": "41643133", | |||||
| "name": "开发环境", | |||||
| "font_class": "developmentEnvironment-icon", | |||||
| "unicode": "e626", | |||||
| "unicode_decimal": 58918 | |||||
| }, | |||||
| { | |||||
| "icon_id": "41642989", | |||||
| "name": "使用手册", | |||||
| "font_class": "manual-icon", | |||||
| "unicode": "e623", | |||||
| "unicode_decimal": 58915 | |||||
| }, | |||||
| { | { | ||||
| "icon_id": "40233218", | "icon_id": "40233218", | ||||
| "name": "操作手册-active", | "name": "操作手册-active", | ||||
| @@ -12,13 +54,6 @@ | |||||
| "unicode": "e62c", | "unicode": "e62c", | ||||
| "unicode_decimal": 58924 | "unicode_decimal": 58924 | ||||
| }, | }, | ||||
| { | |||||
| "icon_id": "40233217", | |||||
| "name": "操作手册", | |||||
| "font_class": "manual-icon", | |||||
| "unicode": "e62d", | |||||
| "unicode_decimal": 58925 | |||||
| }, | |||||
| { | { | ||||
| "icon_id": "40171713", | "icon_id": "40171713", | ||||
| "name": "监控运维-active", | "name": "监控运维-active", | ||||
| @@ -40,20 +75,6 @@ | |||||
| "unicode": "e62a", | "unicode": "e62a", | ||||
| "unicode_decimal": 58922 | "unicode_decimal": 58922 | ||||
| }, | }, | ||||
| { | |||||
| "icon_id": "40171699", | |||||
| "name": "开发环境", | |||||
| "font_class": "developmentEnvironment-icon", | |||||
| "unicode": "e62b", | |||||
| "unicode_decimal": 58923 | |||||
| }, | |||||
| { | |||||
| "icon_id": "39969575", | |||||
| "name": "系统管理", | |||||
| "font_class": "system-icon", | |||||
| "unicode": "e618", | |||||
| "unicode_decimal": 58904 | |||||
| }, | |||||
| { | { | ||||
| "icon_id": "39969573", | "icon_id": "39969573", | ||||
| "name": "流水线-active", | "name": "流水线-active", | ||||
| @@ -68,13 +89,6 @@ | |||||
| "unicode": "e61c", | "unicode": "e61c", | ||||
| "unicode_decimal": 58908 | "unicode_decimal": 58908 | ||||
| }, | }, | ||||
| { | |||||
| "icon_id": "39969568", | |||||
| "name": "数据准备", | |||||
| "font_class": "datasetPreparation-icon", | |||||
| "unicode": "e61d", | |||||
| "unicode_decimal": 58909 | |||||
| }, | |||||
| { | { | ||||
| "icon_id": "39969570", | "icon_id": "39969570", | ||||
| "name": "模型在线部署", | "name": "模型在线部署", | ||||
| @@ -103,13 +117,6 @@ | |||||
| "unicode": "e621", | "unicode": "e621", | ||||
| "unicode_decimal": 58913 | "unicode_decimal": 58913 | ||||
| }, | }, | ||||
| { | |||||
| "icon_id": "39969580", | |||||
| "name": "工作空间", | |||||
| "font_class": "workspace-icon", | |||||
| "unicode": "e611", | |||||
| "unicode_decimal": 58897 | |||||
| }, | |||||
| { | { | ||||
| "icon_id": "39969572", | "icon_id": "39969572", | ||||
| "name": "模型开发-active", | "name": "模型开发-active", | ||||
| @@ -131,13 +138,6 @@ | |||||
| "unicode": "e613", | "unicode": "e613", | ||||
| "unicode_decimal": 58899 | "unicode_decimal": 58899 | ||||
| }, | }, | ||||
| { | |||||
| "icon_id": "39969565", | |||||
| "name": "模型开发", | |||||
| "font_class": "model-icon", | |||||
| "unicode": "e614", | |||||
| "unicode_decimal": 58900 | |||||
| }, | |||||
| { | { | ||||
| "icon_id": "39969577", | "icon_id": "39969577", | ||||
| "name": "应用开发", | "name": "应用开发", | ||||
| @@ -3,6 +3,7 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 开发环境列表 | * @Description: 开发环境列表 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | import CommonTableCell from '@/components/CommonTableCell'; | ||||
| import DateTableCell from '@/components/DateTableCell'; | import DateTableCell from '@/components/DateTableCell'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| @@ -15,6 +16,7 @@ import { | |||||
| stopEditorReq, | stopEditorReq, | ||||
| } from '@/services/developmentEnvironment'; | } from '@/services/developmentEnvironment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { editorUrlKey, setSessionStorageItem } from '@/utils/sessionStorage'; | import { editorUrlKey, setSessionStorageItem } from '@/utils/sessionStorage'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| @@ -29,6 +31,7 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import CreateMirrorModal from '../components/CreateMirrorModal'; | |||||
| import EditorStatusCell from '../components/EditorStatusCell'; | import EditorStatusCell from '../components/EditorStatusCell'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -110,6 +113,16 @@ function EditorList() { | |||||
| } | } | ||||
| }; | }; | ||||
| // 制作镜像 | |||||
| const createMirror = (id: number) => { | |||||
| const { close } = openAntdModal(CreateMirrorModal, { | |||||
| envId: id, | |||||
| onOk: () => { | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | // 处理删除 | ||||
| const handleEditorDelete = (record: EditorData) => { | const handleEditorDelete = (record: EditorData) => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| @@ -218,6 +231,17 @@ function EditorList() { | |||||
| 启动 | 启动 | ||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| {record.status === DevEditorStatus.Running ? ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="jingxiang" | |||||
| icon={<KFIcon type="icon-jingxiang" />} | |||||
| onClick={() => createMirror(record.id)} | |||||
| > | |||||
| 制作镜像 | |||||
| </Button> | |||||
| ) : null} | |||||
| <ConfigProvider | <ConfigProvider | ||||
| theme={{ | theme={{ | ||||
| token: { | token: { | ||||
| @@ -0,0 +1,100 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { createEditorMirrorReq } from '@/services/developmentEnvironment'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, message, type ModalProps } from 'antd'; | |||||
| interface CreateMirrorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| envId: number; // 开发环境id | |||||
| onOk: () => void; | |||||
| } | |||||
| function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||||
| // 上传请求 | |||||
| const createDatasetVersion = async (params: any) => { | |||||
| const [res] = await to( | |||||
| createEditorMirrorReq({ | |||||
| ...params, | |||||
| dev_environment_id: envId, | |||||
| upload_type: 1, | |||||
| version: params['tagName'], | |||||
| }), | |||||
| ); | |||||
| if (res) { | |||||
| message.success('创建成功,请到 “AI资产” - “个人镜像” 中查看'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: any) => { | |||||
| createDatasetVersion(formData); | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="制作镜像" | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| > | |||||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||||
| <Form.Item | |||||
| label="镜像名称" | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像名称', | |||||
| }, | |||||
| { | |||||
| pattern: /^[a-zA-Z0-9_-]*$/, | |||||
| message: '只支持字母、数字、下划线、中横线', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="镜像Tag" | |||||
| name="tag_name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像Tag', | |||||
| }, | |||||
| { | |||||
| pattern: /^[a-zA-Z0-9_-]*$/, | |||||
| message: '只支持字母、数字、下划线、中横线', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="镜像描述描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入镜像描述" | |||||
| autoSize={{ minRows: 3, maxRows: 6 }} | |||||
| maxLength={256} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default CreateMirrorModal; | |||||
| @@ -51,7 +51,7 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||||
| // 防止后台返回不是 number 类型 | // 防止后台返回不是 number 类型 | ||||
| if (Number(paramType) === 2) { | if (Number(paramType) === 2) { | ||||
| rules.push({ | rules.push({ | ||||
| pattern: /^-?\d+(\.\d+)?$/, | |||||
| pattern: /^-?((0(\.0*[1-9]\d*)?)|([1-9]\d*(\.\d+)?))$/, | |||||
| message: '整型必须是数字', | message: '整型必须是数字', | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -74,7 +74,7 @@ function Experiment() { | |||||
| page: pageOption.current.page - 1, | page: pageOption.current.page - 1, | ||||
| size: pageOption.current.size, | size: pageOption.current.size, | ||||
| }; | }; | ||||
| const [res, _] = await to(getExperiment(params)); | |||||
| const [res] = await to(getExperiment(params)); | |||||
| if (res && res.data && Array.isArray(res.data.content)) { | if (res && res.data && Array.isArray(res.data.content)) { | ||||
| setExperimentList( | setExperimentList( | ||||
| res.data.content.map((item) => { | res.data.content.map((item) => { | ||||
| @@ -88,7 +88,7 @@ function Experiment() { | |||||
| // 获取流水线列表 | // 获取流水线列表 | ||||
| const getWorkflowList = async () => { | const getWorkflowList = async () => { | ||||
| const [res, _] = await to(getWorkflow(queryFlow)); | |||||
| const [res] = await to(getWorkflow(queryFlow)); | |||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| setWorkflowList(res.data.content); | setWorkflowList(res.data.content); | ||||
| } | } | ||||
| @@ -236,7 +236,7 @@ function Experiment() { | |||||
| ...values, | ...values, | ||||
| global_param, | global_param, | ||||
| }; | }; | ||||
| const [res, _] = await to(postExperiment(params)); | |||||
| const [res] = await to(postExperiment(params)); | |||||
| if (res) { | if (res) { | ||||
| message.success('新建实验成功'); | message.success('新建实验成功'); | ||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| @@ -244,7 +244,7 @@ function Experiment() { | |||||
| } | } | ||||
| } else { | } else { | ||||
| const params = { ...values, global_param, id: experimentId }; | const params = { ...values, global_param, id: experimentId }; | ||||
| const [res, _] = await to(putExperiment(params)); | |||||
| const [res] = await to(putExperiment(params)); | |||||
| if (res) { | if (res) { | ||||
| message.success('编辑实验成功'); | message.success('编辑实验成功'); | ||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| @@ -153,6 +153,10 @@ function MirrorCreate() { | |||||
| required: true, | required: true, | ||||
| message: '请输入镜像名称', | message: '请输入镜像名称', | ||||
| }, | }, | ||||
| { | |||||
| pattern: /^[a-zA-Z0-9_-]*$/, | |||||
| message: '只支持字母、数字、下划线、中横线', | |||||
| }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input | <Input | ||||
| @@ -176,6 +180,10 @@ function MirrorCreate() { | |||||
| required: true, | required: true, | ||||
| message: '请输入镜像Tag', | message: '请输入镜像Tag', | ||||
| }, | }, | ||||
| { | |||||
| pattern: /^[a-zA-Z0-9_-]*$/, | |||||
| message: '只支持字母、数字、下划线、中横线', | |||||
| }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | ||||
| @@ -3,8 +3,8 @@ import { getCaptchaImg, login } from '@/services/system/auth'; | |||||
| import { loginPasswordKey, loginUserKey, rememberPasswordKey } from '@/utils/localStorage'; | import { loginPasswordKey, loginUserKey, rememberPasswordKey } from '@/utils/localStorage'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { history, useModel } from '@umijs/max'; | import { history, useModel } from '@umijs/max'; | ||||
| import { Button, Checkbox, Flex, Form, Image, Input, message } from 'antd'; | |||||
| import React, { useEffect, useState } from 'react'; | |||||
| import { Button, Checkbox, Flex, Form, Image, Input, message, type InputRef } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { flushSync } from 'react-dom'; | import { flushSync } from 'react-dom'; | ||||
| import styles from './login.less'; | import styles from './login.less'; | ||||
| @@ -17,12 +17,13 @@ const LoginInputPrefix = ({ icon }: { icon: string }) => { | |||||
| ); | ); | ||||
| }; | }; | ||||
| const Login: React.FC = () => { | |||||
| const Login = () => { | |||||
| const { initialState, setInitialState } = useModel('@@initialState'); | const { initialState, setInitialState } = useModel('@@initialState'); | ||||
| const [captchaCode, setCaptchaCode] = useState<string>(''); | const [captchaCode, setCaptchaCode] = useState<string>(''); | ||||
| const [uuid, setUuid] = useState<string>(''); | const [uuid, setUuid] = useState<string>(''); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [usernameReadOnly, setUsernameReadOnly] = useState<boolean>(true); | const [usernameReadOnly, setUsernameReadOnly] = useState<boolean>(true); | ||||
| const captchaInputRef = useRef<InputRef>(null); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getCaptchaCode(); | getCaptchaCode(); | ||||
| @@ -59,11 +60,11 @@ const Login: React.FC = () => { | |||||
| // 登录 | // 登录 | ||||
| const handleSubmit = async (values: API.LoginParams) => { | const handleSubmit = async (values: API.LoginParams) => { | ||||
| const [response] = await to(login({ ...values, uuid })); | |||||
| if (response && response.data) { | |||||
| const [res, error] = await to(login({ ...values, uuid })); | |||||
| if (res && res.data) { | |||||
| const current = new Date(); | const current = new Date(); | ||||
| const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60); | const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60); | ||||
| const { access_token } = response.data; | |||||
| const { access_token } = res.data; | |||||
| setSessionToken(access_token, access_token, expireTime); | setSessionToken(access_token, access_token, expireTime); | ||||
| message.success('登录成功!'); | message.success('登录成功!'); | ||||
| @@ -80,6 +81,10 @@ const Login: React.FC = () => { | |||||
| const urlParams = new URL(window.location.href).searchParams; | const urlParams = new URL(window.location.href).searchParams; | ||||
| history.push(urlParams.get('redirect') || '/'); | history.push(urlParams.get('redirect') || '/'); | ||||
| } else { | } else { | ||||
| if (error?.data?.code === 500 && error?.data?.msg === '验证码错误') { | |||||
| captchaInputRef.current?.focus(); | |||||
| } | |||||
| clearSessionToken(); | clearSessionToken(); | ||||
| getCaptchaCode(); | getCaptchaCode(); | ||||
| } | } | ||||
| @@ -156,6 +161,7 @@ const Login: React.FC = () => { | |||||
| prefix={ | prefix={ | ||||
| <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} /> | <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} /> | ||||
| } | } | ||||
| ref={captchaInputRef} | |||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -10,7 +10,7 @@ import { setRemoteMenu } from './services/session'; | |||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| // [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. | // [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. | ||||
| const popupError = (error: string, skipErrorHandler?: boolean = false) => { | |||||
| const popupError = (error: string, skipErrorHandler: boolean | undefined = false) => { | |||||
| if (skipErrorHandler) { | if (skipErrorHandler) { | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -49,9 +49,18 @@ export function startEditorReq(id: number) { | |||||
| method: 'POST', | method: 'POST', | ||||
| }); | }); | ||||
| } | } | ||||
| // 停止编辑器 | // 停止编辑器 | ||||
| export function stopEditorReq(id: number) { | export function stopEditorReq(id: number) { | ||||
| return request(`/api/mmp/jupyter/stop/${id}`, { | return request(`/api/mmp/jupyter/stop/${id}`, { | ||||
| method: 'DELETE', | method: 'DELETE', | ||||
| }); | }); | ||||
| } | } | ||||
| // 制作镜像 | |||||
| export function createEditorMirrorReq(data: any) { | |||||
| return request(`/api/mmp/image/saveImage`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| @@ -2,7 +2,7 @@ | |||||
| * @param { Promise } promise | * @param { Promise } promise | ||||
| * @return { Promise } | * @return { Promise } | ||||
| */ | */ | ||||
| export async function to<T, U = Error>(promise: Promise<T>): Promise<[T, null] | [null, U]> { | |||||
| export async function to<T, U = any>(promise: Promise<T>): Promise<[T, null] | [null, U]> { | |||||
| try { | try { | ||||
| const data = await promise; | const data = await promise; | ||||
| return [data, null]; | return [data, null]; | ||||