| @@ -17,6 +17,7 @@ export enum ModelDeploymentStatus { | |||
| Running = 'Running', // 运行中 | |||
| Stopped = 'Stopped', // 已停止 | |||
| Failed = 'Failed', // 失败 | |||
| Pending = 'Pending', // 挂起中 | |||
| } | |||
| export const modelDeploymentStatusOptions = [ | |||
| @@ -25,4 +26,5 @@ export const modelDeploymentStatusOptions = [ | |||
| { label: '运行中', value: ModelDeploymentStatus.Running }, | |||
| { label: '已停止', value: ModelDeploymentStatus.Stopped }, | |||
| { label: '失败', value: ModelDeploymentStatus.Failed }, | |||
| { label: '挂起中', value: ModelDeploymentStatus.Pending }, | |||
| ]; | |||
| @@ -126,13 +126,3 @@ export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => { | |||
| } | |||
| }, [form, prevOpen, open]); | |||
| }; | |||
| export const useInputModel = <T>(initialValue: T) => { | |||
| const [value, setValue] = useState<T>(initialValue); | |||
| const updateValue = useCallback((e: any) => { | |||
| setValue(e.target?.value); | |||
| }, []); | |||
| return [value, updateValue]; | |||
| }; | |||
| @@ -4,6 +4,7 @@ import { to } from '@/utils/promise'; | |||
| import { type SelectProps } from 'antd'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| // 获取资源规格 | |||
| export function useComputingResource() { | |||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||
| @@ -1,6 +1,7 @@ | |||
| import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { useEffect, useState } from 'react'; | |||
| // 获取缓存数据 | |||
| export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) { | |||
| const [storage, setStorage] = useState<T>(initialValue); | |||
| @@ -97,6 +97,11 @@ function AddExperimentModal({ | |||
| wrapperCol: { span: 20 }, | |||
| }; | |||
| const paramLayout = { | |||
| labelCol: { span: 8 }, | |||
| wrapperCol: { span: 16 }, | |||
| }; | |||
| // 除了流水线选择发生变化 | |||
| const handleWorkflowChange = (id: string | number) => { | |||
| const pipeline: Workflow | undefined = workflowList.find((v) => v.id === id); | |||
| @@ -187,7 +192,7 @@ function AddExperimentModal({ | |||
| fields.map(({ key, name, ...restField }) => ( | |||
| <Form.Item | |||
| {...restField} | |||
| {...layout} | |||
| {...paramLayout} | |||
| key={key} | |||
| label={getParamType(globalParam[name])} | |||
| name={[name, 'param_value']} | |||
| @@ -1,9 +1,7 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { PipelineNodeModelSerialize } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Form, Input, Select, type FormProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| const { TextArea } = Input; | |||
| @@ -13,24 +11,7 @@ type ExperimentParameterProps = { | |||
| }; | |||
| function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||
| const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 | |||
| useEffect(() => { | |||
| getComputingResource(); | |||
| }, []); | |||
| // 获取资源规格列表数据 | |||
| const getComputingResource = async () => { | |||
| const params = { | |||
| page: 0, | |||
| size: 1000, | |||
| resource_type: '', | |||
| }; | |||
| const [res] = await to(getComputingResourceReq(params)); | |||
| if (res && res.data && res.data.content) { | |||
| setResourceStandardList(res.data.content); | |||
| } | |||
| }; | |||
| const [resourceStandardList] = useComputingResource(); // 资源规模 | |||
| // 控制策略 | |||
| const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | |||
| @@ -18,7 +18,7 @@ export enum ExperimentStatus { | |||
| export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = { | |||
| Running: { | |||
| label: '运行中', | |||
| color: '#165bff', | |||
| color: '#1664ff', | |||
| icon: '/assets/images/running-icon.png', | |||
| }, | |||
| Succeeded: { | |||
| @@ -53,7 +53,7 @@ export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefin | |||
| }, | |||
| Omitted: { | |||
| label: '未执行', | |||
| color: '#8a8a8ae', | |||
| color: '#8a8a8a', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| }; | |||
| @@ -174,13 +174,14 @@ function MirrorList() { | |||
| title: '版本数据', | |||
| dataIndex: 'version_count', | |||
| key: 'version_count', | |||
| width: 100, | |||
| width: '15%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '镜像描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| width: '35%', | |||
| render: CommonTableCell(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| @@ -188,7 +189,7 @@ function MirrorList() { | |||
| title: '创建时间', | |||
| dataIndex: 'create_time', | |||
| key: 'create_time', | |||
| width: 200, | |||
| width: '20%', | |||
| render: DateTableCell, | |||
| }, | |||
| { | |||
| @@ -1,7 +1,7 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-18 18:35:41 | |||
| * @Description: | |||
| * @Description: 镜像状态组件 | |||
| */ | |||
| import { MirrorVersionStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| @@ -26,7 +26,7 @@ const statusInfo: Record<MirrorVersionStatus, MirrorVersionStatusInfo> = { | |||
| }, | |||
| }; | |||
| function MirrorStatusCell(status: MirrorVersionStatus) { | |||
| function MirrorStatusCell(status?: MirrorVersionStatus | null) { | |||
| if (status === null || status === undefined || !statusInfo[status]) { | |||
| return <span>--</span>; | |||
| } | |||
| @@ -164,7 +164,7 @@ function ModelDeploymentInfo() { | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>资源规格</div> | |||
| <div className={styles['label']}>资源规格:</div> | |||
| <div className={styles['value']}> | |||
| {modelDeployementInfo?.resource | |||
| ? getResourceDescription(modelDeployementInfo.resource) | |||
| @@ -174,7 +174,7 @@ function ModelDeploymentInfo() { | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40}> | |||
| <Col span={24}> | |||
| <Col span={18}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>描 述:</div> | |||
| <div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div> | |||
| @@ -249,7 +249,8 @@ function ModelDeployment() { | |||
| </Button> | |||
| )} | |||
| {(record.status === ModelDeploymentStatus.Running || | |||
| record.status === ModelDeploymentStatus.Init) && ( | |||
| record.status === ModelDeploymentStatus.Init || | |||
| record.status === ModelDeploymentStatus.Pending) && ( | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| @@ -6,10 +6,14 @@ | |||
| } | |||
| &--stopped { | |||
| color: @warning-color; | |||
| color: @abort-color; | |||
| } | |||
| &--error { | |||
| color: @error-color; | |||
| } | |||
| &--pending { | |||
| color: @warning-color; | |||
| } | |||
| } | |||
| @@ -28,9 +28,13 @@ export const statusInfo: Record<ModelDeploymentStatus, ModelDeploymentStatusInfo | |||
| classname: styles['model-deployment-status-cell--error'], | |||
| text: '失败', | |||
| }, | |||
| [ModelDeploymentStatus.Pending]: { | |||
| classname: styles['model-deployment-status-cell--pending'], | |||
| text: '挂起中', | |||
| }, | |||
| }; | |||
| function ModelDeploymentStatusCell(status: ModelDeploymentStatus | undefined) { | |||
| function ModelDeploymentStatusCell(status?: ModelDeploymentStatus | null) { | |||
| if (status === null || status === undefined || !statusInfo[status]) { | |||
| return <span>--</span>; | |||
| } | |||
| @@ -26,13 +26,7 @@ export type ModelDeploymentData = { | |||
| // 操作类型 | |||
| export enum ModelDeploymentOperationType { | |||
| Create = 'create', | |||
| Update = 'update', | |||
| Restart = 'restart', | |||
| Create = 'Create', | |||
| Update = 'Update', | |||
| Restart = 'Restart', | |||
| } | |||
| // 状态 | |||
| export type ModelDeploymentStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| @@ -1,14 +1,17 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import ParameterInput from '@/components/ParameterInput'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | |||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||
| import PropsLabel from '../components/PropsLabel'; | |||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '../components/ResourceSelectorModal'; | |||
| import styles from './props.less'; | |||
| import { canInput, createMenuItems } from './utils'; | |||
| const { TextArea } = Input; | |||
| @@ -19,26 +22,9 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| const [open, setOpen] = useState(false); | |||
| const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | |||
| const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | |||
| const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | |||
| const [menuItems, setMenuItems] = useState([]); | |||
| useEffect(() => { | |||
| getComputingResource(); | |||
| }, []); | |||
| // 获取资源规格列表数据 | |||
| const getComputingResource = async () => { | |||
| const params = { | |||
| page: 0, | |||
| size: 1000, | |||
| resource_type: '', | |||
| }; | |||
| const [res] = await to(getComputingResourceReq(params)); | |||
| if (res && res.data && res.data.content) { | |||
| setResourceStandardList(res.data.content); | |||
| } | |||
| }; | |||
| const afterOpenChange = () => { | |||
| if (!open) { | |||
| console.log('zzzzz', form.getFieldsValue()); | |||
| @@ -57,6 +43,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| const onClose = () => { | |||
| setOpen(false); | |||
| }; | |||
| useImperativeHandle(ref, () => ({ | |||
| getFieldsValue: async () => { | |||
| const [propsRes, propsError] = await to(form.validateFields()); | |||
| @@ -155,18 +142,16 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (item) => { | |||
| const type = item.item_type; | |||
| let selectorType; | |||
| if (type === 'dataset') { | |||
| return <KFIcon type="icon-xuanzeshujuji" />; | |||
| selectorType = ResourceSelectorType.Dataset; | |||
| } else if (type === 'model') { | |||
| return <KFIcon type="icon-xuanzemoxing" />; | |||
| selectorType = ResourceSelectorType.Model; | |||
| } else { | |||
| return <KFIcon type="icon-xuanzejingxiang" />; | |||
| selectorType = ResourceSelectorType.Mirror; | |||
| } | |||
| }; | |||
| // 筛选资源规格 | |||
| const filterResourceStandard = (input, { computing_resource = '' }) => { | |||
| return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | |||
| return <KFIcon type={selectorTypeConfig[selectorType].buttonIcon} />; | |||
| }; | |||
| // 参数回填 | |||
| @@ -3,7 +3,7 @@ | |||
| * @Date: 2024-03-25 13:52:54 | |||
| * @Description: 网络请求配置,详情请参考 https://umijs.org/docs/max/request | |||
| */ | |||
| import type { RequestConfig } from '@umijs/max'; | |||
| import type { AxiosRequestConfig, AxiosResponse, RequestConfig } from '@umijs/max'; | |||
| import { message } from 'antd'; | |||
| import { clearSessionToken, getAccessToken } from './access'; | |||
| import { setRemoteMenu } from './services/session'; | |||
| @@ -16,8 +16,8 @@ import { gotoLoginPage } from './utils/ui'; | |||
| export const requestConfig: RequestConfig = { | |||
| errorConfig: {}, | |||
| requestInterceptors: [ | |||
| (url: any, options: { headers: any }) => { | |||
| const headers = options.headers ? options.headers : []; | |||
| (url: string, options: AxiosRequestConfig) => { | |||
| const headers = options.headers ?? {}; | |||
| const authHeader = headers['Authorization']; | |||
| const isToken = headers['isToken']; | |||
| if (!authHeader && isToken !== false) { | |||
| @@ -30,7 +30,7 @@ export const requestConfig: RequestConfig = { | |||
| }, | |||
| ], | |||
| responseInterceptors: [ | |||
| (response: any) => { | |||
| (response: AxiosResponse) => { | |||
| const { status, data } = response || {}; | |||
| if (status >= 200 && status < 300) { | |||
| if (data && (data instanceof Blob || data.code === 200)) { | |||
| @@ -14,6 +14,7 @@ | |||
| @success-color: #1ace62; | |||
| @error-color: #c73131; | |||
| @warning-color: #f98e1b; | |||
| @abort-color: #8a8a8a; | |||
| @border-color: rgba(22, 100, 255, 0.3); | |||
| @border-color-secondary: rgba(22, 100, 255, 0.1); | |||
| @@ -29,7 +29,7 @@ export function parseJsonText(text?: string | null): any | null { | |||
| } | |||
| } | |||
| // Underscore-to-camelCase | |||
| // underscore-to-camelCase | |||
| export function underscoreToCamelCase(obj: Record<string, any>) { | |||
| const newObj: Record<string, any> = {}; | |||
| for (const key in obj) { | |||
| @@ -47,6 +47,7 @@ export function underscoreToCamelCase(obj: Record<string, any>) { | |||
| return newObj; | |||
| } | |||
| // camelCase-to-underscore | |||
| export function camelCaseToUnderscore(obj: Record<string, any>) { | |||
| const newObj: Record<string, any> = {}; | |||
| for (const key in obj) { | |||
| @@ -61,3 +62,21 @@ export function camelCaseToUnderscore(obj: Record<string, any>) { | |||
| } | |||
| return newObj; | |||
| } | |||
| // null 转 undefined | |||
| export function nullToUndefined(obj: Record<string, any>) { | |||
| const newObj: Record<string, any> = {}; | |||
| for (const key in obj) { | |||
| if (obj.hasOwnProperty(key)) { | |||
| const value = obj[key]; | |||
| if (value === null) { | |||
| newObj[key] = undefined; | |||
| } else if (typeof value === 'object' && value !== null) { | |||
| newObj[key] = nullToUndefined(value); | |||
| } else { | |||
| newObj[key] = value; | |||
| } | |||
| } | |||
| } | |||
| return newObj; | |||
| } | |||
| @@ -9,6 +9,8 @@ import zhCN from 'antd/locale/zh_CN'; | |||
| import React, { useState } from 'react'; | |||
| import { createRoot } from 'react-dom/client'; | |||
| const destroyFns: (() => void)[] = []; | |||
| /** | |||
| * Function to open an Ant Design modal. | |||
| * | |||
| @@ -16,7 +18,6 @@ import { createRoot } from 'react-dom/client'; | |||
| * @param modalProps - The modal properties. | |||
| * @return An object with a destroy method to close the modal. | |||
| */ | |||
| export const openAntdModal = <T extends Omit<ModalProps, 'onOk'>>( | |||
| modal: (props: T) => React.ReactNode, | |||
| modalProps: T, | |||
| @@ -29,6 +30,11 @@ export const openAntdModal = <T extends Omit<ModalProps, 'onOk'>>( | |||
| let timeoutId: ReturnType<typeof setTimeout>; | |||
| function destroy() { | |||
| const index = destroyFns.indexOf(close); | |||
| if (index !== -1) { | |||
| destroyFns.splice(index, 1); | |||
| } | |||
| root.unmount(); | |||
| } | |||
| @@ -79,6 +85,8 @@ export const openAntdModal = <T extends Omit<ModalProps, 'onOk'>>( | |||
| render({ ...modalProps, open: true }); | |||
| destroyFns.push(close); | |||
| return { | |||
| close, | |||
| }; | |||
| @@ -88,19 +96,23 @@ export const openAntdModal = <T extends Omit<ModalProps, 'onOk'>>( | |||
| * Generates a custom hook for managing an Ant Design modal. | |||
| * | |||
| * @param modal - The function that renders the modal content. | |||
| * @param key - The key for the modal. | |||
| * @param defaultProps - The default modal properties. | |||
| * @return The modal component, open function, and close function. | |||
| */ | |||
| export const useAntdModal = <T extends ModalProps>( | |||
| export const useModal = <T extends ModalProps>( | |||
| modal: (props: T) => React.ReactNode, | |||
| key: React.Key, | |||
| defaultProps?: T, | |||
| ) => { | |||
| const [visible, setVisible] = useState(false); | |||
| const [props, setProps] = useState<T>({} as T); | |||
| const [props, setProps] = useState<T>(defaultProps || ({} as T)); | |||
| const CustomModel = modal; | |||
| const open = (props: T) => { | |||
| setProps(props); | |||
| setProps((prev) => ({ | |||
| ...prev, | |||
| ...props, | |||
| })); | |||
| setVisible(true); | |||
| }; | |||
| @@ -108,5 +120,14 @@ export const useAntdModal = <T extends ModalProps>( | |||
| setVisible(false); | |||
| }; | |||
| return [<CustomModel key={key} open={visible} {...props} />, open, close] as const; | |||
| return [<CustomModel key="modal" open={visible} {...props} />, open, close] as const; | |||
| }; | |||
| // 关闭没有手动关闭的 Modal | |||
| export const closeAllModals = () => { | |||
| let close = destroyFns.pop(); | |||
| while (close) { | |||
| close(); | |||
| close = destroyFns.pop(); | |||
| } | |||
| }; | |||
| @@ -7,6 +7,7 @@ import { PageEnum } from '@/enums/pagesEnums'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { history } from '@umijs/max'; | |||
| import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; | |||
| import { closeAllModals } from './modal'; | |||
| // 自定义 Confirm 弹框 | |||
| export function modalConfirm({ title, content, onOk, ...rest }: ModalFuncProps) { | |||
| @@ -58,6 +59,7 @@ export const gotoLoginPage = (toHome: boolean = true) => { | |||
| console.log('pathname', pathname); | |||
| console.log('search', search); | |||
| if (window.location.pathname !== PageEnum.LOGIN) { | |||
| closeAllModals(); | |||
| history.replace({ | |||
| pathname: PageEnum.LOGIN, | |||
| search: newSearch, | |||