| @@ -50,5 +50,6 @@ Thumbs.db | |||||
| mvnw.cmd | mvnw.cmd | ||||
| mvnw | mvnw | ||||
| # Files or folders need to be retained | |||||
| # ... | |||||
| # web | |||||
| **/node_modules | |||||
| @@ -0,0 +1,45 @@ | |||||
| { | |||||
| "name": "ci4sManagement-cloud", | |||||
| "lockfileVersion": 3, | |||||
| "requires": true, | |||||
| "packages": { | |||||
| "": { | |||||
| "dependencies": { | |||||
| "clipboard": "~2.0.11" | |||||
| } | |||||
| }, | |||||
| "node_modules/clipboard": { | |||||
| "version": "2.0.11", | |||||
| "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", | |||||
| "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", | |||||
| "dependencies": { | |||||
| "good-listener": "^1.2.2", | |||||
| "select": "^1.1.2", | |||||
| "tiny-emitter": "^2.0.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/delegate": { | |||||
| "version": "3.2.0", | |||||
| "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", | |||||
| "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" | |||||
| }, | |||||
| "node_modules/good-listener": { | |||||
| "version": "1.2.2", | |||||
| "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", | |||||
| "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", | |||||
| "dependencies": { | |||||
| "delegate": "^3.1.2" | |||||
| } | |||||
| }, | |||||
| "node_modules/select": { | |||||
| "version": "1.1.2", | |||||
| "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", | |||||
| "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" | |||||
| }, | |||||
| "node_modules/tiny-emitter": { | |||||
| "version": "2.1.0", | |||||
| "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", | |||||
| "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,5 @@ | |||||
| { | |||||
| "dependencies": { | |||||
| "clipboard": "~2.0.11" | |||||
| } | |||||
| } | |||||
| @@ -135,6 +135,27 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: '自动机器学习', | |||||
| path: 'automl', | |||||
| routes: [ | |||||
| { | |||||
| name: '自动机器学习', | |||||
| path: '', | |||||
| component: './AutoML/List/index', | |||||
| }, | |||||
| { | |||||
| name: '自动机器学习详情', | |||||
| path: 'info/:id', | |||||
| component: './AutoML/Info/index', | |||||
| }, | |||||
| { | |||||
| name: '创建自动机器学习', | |||||
| path: 'create', | |||||
| component: './AutoML/Create/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -21,6 +21,7 @@ import './styles/menu.less'; | |||||
| export { requestConfig as request } from './requestConfig'; | export { requestConfig as request } from './requestConfig'; | ||||
| // const isDev = process.env.NODE_ENV === 'development'; | // const isDev = process.env.NODE_ENV === 'development'; | ||||
| import { type GlobalInitialState } from '@/types'; | import { type GlobalInitialState } from '@/types'; | ||||
| import '@/utils/clipboard'; | |||||
| import { menuItemRender } from '@/utils/menuRender'; | import { menuItemRender } from '@/utils/menuRender'; | ||||
| import ErrorBoundary from './components/ErrorBoundary'; | import ErrorBoundary from './components/ErrorBoundary'; | ||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| @@ -1,6 +1,7 @@ | |||||
| import { Link } from '@umijs/max'; | import { Link } from '@umijs/max'; | ||||
| import { Typography } from 'antd'; | import { Typography } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import React from 'react'; | |||||
| import './index.less'; | import './index.less'; | ||||
| export type BasicInfoLink = { | export type BasicInfoLink = { | ||||
| @@ -29,9 +30,12 @@ type BasicInfoItemProps = { | |||||
| classPrefix: string; | classPrefix: string; | ||||
| }; | }; | ||||
| type BasicInfoItemValueProps = BasicInfoLink & { | |||||
| type BasicInfoItemValueProps = { | |||||
| ellipsis?: boolean; | ellipsis?: boolean; | ||||
| classPrefix: string; | classPrefix: string; | ||||
| value: string | React.ReactNode; | |||||
| link?: string; | |||||
| url?: string; | |||||
| }; | }; | ||||
| export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { | export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { | ||||
| @@ -69,6 +73,11 @@ export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemPr | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| } else if (React.isValidElement(formatValue)) { | |||||
| // 这个判断必须在下面的判断之前 | |||||
| valueComponent = ( | |||||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||||
| ); | |||||
| } else if (typeof formatValue === 'object' && formatValue) { | } else if (typeof formatValue === 'object' && formatValue) { | ||||
| valueComponent = ( | valueComponent = ( | ||||
| <BasicInfoItemValue | <BasicInfoItemValue | ||||
| @@ -115,13 +124,18 @@ export function BasicInfoItemValue({ | |||||
| {value} | {value} | ||||
| </Link> | </Link> | ||||
| ); | ); | ||||
| } else if (React.isValidElement(value)) { | |||||
| return value; | |||||
| } else { | } else { | ||||
| component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | ||||
| } | } | ||||
| return ( | return ( | ||||
| <div className={myClassName}> | <div className={myClassName}> | ||||
| <Typography.Text ellipsis={ellipsis ? { tooltip: value } : false}> | |||||
| <Typography.Text | |||||
| ellipsis={ellipsis ? { tooltip: value } : false} | |||||
| style={{ fontSize: 'inherit' }} | |||||
| > | |||||
| {component} | {component} | ||||
| </Typography.Text> | </Typography.Text> | ||||
| </div> | </div> | ||||
| @@ -21,13 +21,13 @@ interface KFIconProps extends IconFontProps { | |||||
| className?: string; | className?: string; | ||||
| } | } | ||||
| function KFIcon({ type, font = 15, color = '', style = {}, className }: KFIconProps) { | |||||
| function KFIcon({ type, font = 15, color = '', style = {}, className, ...rest }: KFIconProps) { | |||||
| const iconStyle = { | const iconStyle = { | ||||
| ...style, | ...style, | ||||
| fontSize: font, | fontSize: font, | ||||
| color, | color, | ||||
| }; | }; | ||||
| return <Icon type={type} className={className} style={iconStyle} />; | |||||
| return <Icon {...rest} type={type} className={className} style={iconStyle} />; | |||||
| } | } | ||||
| export default KFIcon; | export default KFIcon; | ||||
| @@ -0,0 +1,47 @@ | |||||
| .create-service-version { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 10px; | |||||
| overflow: auto; | |||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__type { | |||||
| color: @text-color; | |||||
| font-size: @font-size-input-lg; | |||||
| } | |||||
| :global { | |||||
| .ant-input-number { | |||||
| width: 100%; | |||||
| } | |||||
| .ant-form-item { | |||||
| margin-bottom: 20px; | |||||
| } | |||||
| .image-url { | |||||
| margin-top: -15px; | |||||
| .ant-form-item-label > label::after { | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| .ant-btn.ant-btn-icon-only .anticon { | |||||
| color: #565658; | |||||
| font-size: 20px; | |||||
| } | |||||
| .anticon-question-circle { | |||||
| margin-top: -12px; | |||||
| color: @text-color-tertiary !important; | |||||
| font-size: 12px !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,220 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 创建服务版本 | |||||
| */ | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import { type ParameterInputObject } from '@/components/ResourceSelect'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { | |||||
| createServiceVersionReq, | |||||
| getServiceInfoReq, | |||||
| updateServiceVersionReq, | |||||
| } from '@/services/modelDeployment'; | |||||
| import { changePropertyName } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import { useNavigate, useParams } from '@umijs/max'; | |||||
| import { App, Button, Form } from 'antd'; | |||||
| import { omit, pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import BasicConfig from '../components/CreateForm/BasicConfig'; | |||||
| import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; | |||||
| import SearchConfig from '../components/CreateForm/SearchConfig'; | |||||
| import TrialConfig from '../components/CreateForm/TrialConfig'; | |||||
| import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; | |||||
| import styles from './index.less'; | |||||
| // 表单数据 | |||||
| export type FormData = { | |||||
| service_name: string; // 服务名称 | |||||
| version: string; // 服务版本 | |||||
| description: string; // 描述 | |||||
| model: ParameterInputObject; // 模型 | |||||
| image: ParameterInputObject; // 镜像 | |||||
| code_config: ParameterInputObject; // 代码 | |||||
| resource: string; // 资源规格 | |||||
| replicas: string; // 副本数量 | |||||
| mount_path: string; // 模型路径 | |||||
| env_variables: { key: string; value: string }[]; // 环境变量 | |||||
| }; | |||||
| function CreateAutoML() { | |||||
| const navigate = useNavigate(); | |||||
| const [form] = Form.useForm(); | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||||
| const [operationType, setOperationType] = useState(ServiceOperationType.Create); | |||||
| const { message } = App.useApp(); | |||||
| const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined); | |||||
| const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined); | |||||
| const params = useParams(); | |||||
| const id = params.id; | |||||
| useEffect(() => { | |||||
| const res: | |||||
| | (ServiceVersionData & { | |||||
| operationType: ServiceOperationType; | |||||
| }) | |||||
| | undefined = SessionStorage.getItem(SessionStorage.serviceVersionInfoKey, true); | |||||
| if (res) { | |||||
| setOperationType(res.operationType); | |||||
| setVersionInfo(res); | |||||
| let model, codeConfig, envVariables; | |||||
| if (res.model && typeof res.model === 'object') { | |||||
| model = changePropertyName(res.model, { show_value: 'showValue' }); | |||||
| // 接口返回是数据没有 value 值,但是 form 需要 value | |||||
| model.value = model.showValue; | |||||
| } | |||||
| if (res.code_config && typeof res.code_config === 'object') { | |||||
| codeConfig = changePropertyName(res.code_config, { show_value: 'showValue' }); | |||||
| // 接口返回是数据没有 value 值,但是 form 需要 value | |||||
| codeConfig.value = codeConfig.showValue; | |||||
| } | |||||
| if (res.env_variables && typeof res.env_variables === 'object') { | |||||
| envVariables = Object.entries(res.env_variables).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| } | |||||
| const formData = { | |||||
| ...omit(res, 'model', 'code_config', 'env_variables'), | |||||
| model: model, | |||||
| code_config: codeConfig, | |||||
| env_variables: envVariables, | |||||
| }; | |||||
| form.setFieldsValue(formData); | |||||
| } | |||||
| return () => { | |||||
| SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); | |||||
| }; | |||||
| }, []); | |||||
| // 获取服务详情 | |||||
| const getServiceInfo = async () => { | |||||
| const [res] = await to(getServiceInfoReq(id)); | |||||
| if (res && res.data) { | |||||
| setServiceInfo(res.data); | |||||
| form.setFieldsValue({ | |||||
| service_name: res.data.service_name, | |||||
| }); | |||||
| } | |||||
| }; | |||||
| // 创建版本 | |||||
| const createServiceVersion = async (formData: FormData) => { | |||||
| const envList = formData['env_variables'] ?? []; | |||||
| const image = formData['image']; | |||||
| const model = formData['model']; | |||||
| const codeConfig = formData['code_config']; | |||||
| const envVariables = envList.reduce((acc, cur) => { | |||||
| acc[cur.key] = cur.value; | |||||
| return acc; | |||||
| }, {} as Record<string, string>); | |||||
| // 根据后台要求,修改表单数据 | |||||
| const object = { | |||||
| ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), | |||||
| replicas: Number(formData.replicas), | |||||
| env_variables: envVariables, | |||||
| image: image.value, | |||||
| model: changePropertyName( | |||||
| pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), | |||||
| { showValue: 'show_value' }, | |||||
| ), | |||||
| code_config: changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), { | |||||
| showValue: 'show_value', | |||||
| }), | |||||
| service_id: serviceInfo?.id, | |||||
| }; | |||||
| const params = | |||||
| operationType === ServiceOperationType.Create | |||||
| ? object | |||||
| : { | |||||
| id: versionInfo?.id, | |||||
| rerun: operationType === ServiceOperationType.Restart ? true : false, | |||||
| deployment_name: versionInfo?.deployment_name, | |||||
| ...object, | |||||
| }; | |||||
| const request = | |||||
| operationType === ServiceOperationType.Create | |||||
| ? createServiceVersionReq | |||||
| : updateServiceVersionReq; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const handleSubmit = (values: FormData) => { | |||||
| console.log('values', values); | |||||
| }; | |||||
| // 取消 | |||||
| const cancel = () => { | |||||
| navigate(-1); | |||||
| }; | |||||
| const disabled = operationType !== ServiceOperationType.Create; | |||||
| let buttonText = '新建'; | |||||
| let title = '新增服务版本'; | |||||
| if (operationType === ServiceOperationType.Update) { | |||||
| title = '更新服务版本'; | |||||
| buttonText = '更新'; | |||||
| } else if (operationType === ServiceOperationType.Restart) { | |||||
| title = '重启服务版本'; | |||||
| buttonText = '重启'; | |||||
| } | |||||
| return ( | |||||
| <div className={styles['create-service-version']}> | |||||
| <PageTitle title={title}></PageTitle> | |||||
| <div className={styles['create-service-version__content']}> | |||||
| <div> | |||||
| <Form | |||||
| name="create-service-version" | |||||
| labelCol={{ flex: '140px' }} | |||||
| labelAlign="left" | |||||
| form={form} | |||||
| onFinish={handleSubmit} | |||||
| size="large" | |||||
| autoComplete="off" | |||||
| initialValues={{ | |||||
| execute_type: 'DLC', | |||||
| image_type: 3, | |||||
| advanced_config: [{ key: '', value: '' }], | |||||
| }} | |||||
| > | |||||
| <BasicConfig /> | |||||
| <ExecuteConfig disabled={disabled} /> | |||||
| <TrialConfig /> | |||||
| <SearchConfig /> | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||||
| <Button type="primary" htmlType="submit"> | |||||
| {buttonText} | |||||
| </Button> | |||||
| <Button | |||||
| type="default" | |||||
| htmlType="button" | |||||
| onClick={cancel} | |||||
| style={{ marginLeft: '20px' }} | |||||
| > | |||||
| 取消 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CreateAutoML; | |||||
| @@ -0,0 +1,40 @@ | |||||
| .auto-ml-info { | |||||
| position: relative; | |||||
| height: 100%; | |||||
| &__tabs { | |||||
| height: 50px; | |||||
| padding-left: 25px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| } | |||||
| &__tips { | |||||
| position: absolute; | |||||
| top: 11px; | |||||
| left: 256px; | |||||
| padding: 3px 12px; | |||||
| color: #565658; | |||||
| font-size: @font-size-content; | |||||
| background: .addAlpha(@primary-color, 0.09) []; | |||||
| border-radius: 4px; | |||||
| &::before { | |||||
| position: absolute; | |||||
| top: 10px; | |||||
| left: -6px; | |||||
| width: 0; | |||||
| height: 0; | |||||
| border-top: 4px solid transparent; | |||||
| border-right: 6px solid .addAlpha(@primary-color, 0.09) []; | |||||
| border-bottom: 4px solid transparent; | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 自主机器学习详情 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { Tabs } from 'antd'; | |||||
| import { useState } from 'react'; | |||||
| import AutoMLBasic from '../components/AutoMLBasic'; | |||||
| import AutoMLTable from '../components/AutoMLTable'; | |||||
| import styles from './index.less'; | |||||
| export type MirrorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| description: string; | |||||
| create_time: string; | |||||
| }; | |||||
| function AutoMLInfo() { | |||||
| const navigate = useNavigate(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | |||||
| const tabItems = [ | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '基本信息', | |||||
| icon: <KFIcon type="icon-jibenxinxi" />, | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: 'Trial列表', | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['auto-ml-info']}> | |||||
| <div className={styles['auto-ml-info__tabs']}> | |||||
| <Tabs items={tabItems} activeKey={activeTab} onChange={setActiveTab} /> | |||||
| </div> | |||||
| <div className={styles['auto-ml-info__content']}> | |||||
| {activeTab === CommonTabKeys.Public && <AutoMLBasic />} | |||||
| {activeTab === CommonTabKeys.Private && <AutoMLTable />} | |||||
| </div> | |||||
| {activeTab === CommonTabKeys.Private && ( | |||||
| <div className={styles['auto-ml-info__tips']}> | |||||
| <KFIcon type="icon-tishi" color={themes['warningColor']} font={17} /> | |||||
| <span style={{ marginLeft: '4px' }}>Trial是一次独立的尝试,他会使用某组超参来运行</span> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default AutoMLInfo; | |||||
| @@ -0,0 +1,20 @@ | |||||
| .auto-ml-list { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 20px @content-padding 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__filter { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| &__table { | |||||
| height: calc(100% - 32px - 28px); | |||||
| margin-top: 28px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,311 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 自主机器学习 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { | |||||
| App, | |||||
| Button, | |||||
| ConfigProvider, | |||||
| Input, | |||||
| Table, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| } from 'antd'; | |||||
| import { type SearchProps } from 'antd/es/input'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; | |||||
| import RunStatusCell from '../components/RunStatusCell'; | |||||
| import { ServiceData, ServiceOperationType } from '../types'; | |||||
| import styles from './index.less'; | |||||
| function AutoMLList() { | |||||
| const navigate = useNavigate(); | |||||
| const { message } = App.useApp(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||||
| const [tableData, setTableData] = useState<ServiceData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| useEffect(() => { | |||||
| getServiceList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取模型部署服务列表 | |||||
| const getServiceList = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| service_name: searchText, | |||||
| }; | |||||
| const [res] = await to(getServiceListReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除模型部署 | |||||
| const deleteService = async (record: ServiceData) => { | |||||
| const [res] = await to(deleteServiceReq(record.id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getServiceList(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||||
| setSearchText(value); | |||||
| }; | |||||
| // 处理删除 | |||||
| const handleServiceDelete = (record: ServiceData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该服务将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteService(record); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建、更新服务 | |||||
| const createService = (type: ServiceOperationType, record?: ServiceData) => { | |||||
| SessionStorage.setItem( | |||||
| SessionStorage.serviceInfoKey, | |||||
| { | |||||
| ...record, | |||||
| operationType: type, | |||||
| }, | |||||
| true, | |||||
| ); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| }); | |||||
| navigate(`/pipeline/autoML/create`); | |||||
| }; | |||||
| // 查看详情 | |||||
| const toDetail = (record: ServiceData) => { | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| }); | |||||
| navigate(`/pipeline/autoML/info/${record.id}`); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps<ServiceData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<ServiceData>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pagination.current! - 1, | |||||
| pageSize: pagination.pageSize!, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '实验名称', | |||||
| dataIndex: 'service_name', | |||||
| key: 'service_name', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Link, { | |||||
| onClick: toDetail, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '实验描述', | |||||
| dataIndex: 'service_type_name', | |||||
| key: 'service_type_name', | |||||
| width: '20%', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'run_status', | |||||
| key: 'run_status', | |||||
| width: '20%', | |||||
| render: RunStatusCell, | |||||
| }, | |||||
| { | |||||
| title: '实验实例执行进度', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: ExecuteScheduleCell, | |||||
| width: 180, | |||||
| }, | |||||
| { | |||||
| title: '创建时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| width: '20%', | |||||
| render: tableCellRender(true, TableCellValueType.Date), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '修改时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| width: '20%', | |||||
| render: tableCellRender(true, TableCellValueType.Date), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 400, | |||||
| key: 'operation', | |||||
| render: (_: any, record: ServiceData) => ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| icon={<KFIcon type="icon-jingxiang" />} | |||||
| onClick={() => createService(ServiceOperationType.Update, record)} | |||||
| > | |||||
| 镜像 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={() => createService(ServiceOperationType.Update, record)} | |||||
| > | |||||
| 编辑 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-fuzhi" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 复制 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-tingzhi" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 停止 | |||||
| </Button> | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleServiceDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['auto-ml-list']}> | |||||
| <PageTitle title="自动机器学习列表"></PageTitle> | |||||
| <div className={styles['auto-ml-list__content']}> | |||||
| <div className={styles['auto-ml-list__content__filter']}> | |||||
| <Input.Search | |||||
| placeholder="按实验名称筛选" | |||||
| onSearch={onSearch} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| value={inputText} | |||||
| allowClear | |||||
| /> | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| type="default" | |||||
| onClick={() => createService(ServiceOperationType.Create)} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 新建实验 | |||||
| </Button> | |||||
| </div> | |||||
| <div | |||||
| className={classNames('vertical-scroll-table', styles['auto-ml-list__content__table'])} | |||||
| > | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default AutoMLList; | |||||
| @@ -0,0 +1,7 @@ | |||||
| .auto-ml-basic { | |||||
| height: 100%; | |||||
| padding: 20px @content-padding; | |||||
| overflow-y: auto; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| } | |||||
| @@ -0,0 +1,81 @@ | |||||
| import { Flex } from 'antd'; | |||||
| import { useEffect } from 'react'; | |||||
| import ConfigInfo, { type BasicInfoData } from '../ConfigInfo'; | |||||
| import CopyingText from '../CopyingText'; | |||||
| import StatusChart from '../StatusChart'; | |||||
| import styles from './index.less'; | |||||
| function AutoMLBasic() { | |||||
| useEffect(() => {}, []); | |||||
| const datas: BasicInfoData[] = [ | |||||
| { | |||||
| label: '项目名称', | |||||
| value: '测试项目名称', | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: '项目名称', | |||||
| value: '测试项目名称', | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: '项目名称', | |||||
| value: '测试项目名称', | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: '项目名称', | |||||
| value: '测试项目名称', | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: '项目名称', | |||||
| value: '测试项目名称', | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: '项目名称', | |||||
| value: '测试项目名称', | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: '项目名称', | |||||
| value: '测试项目名称', | |||||
| ellipsis: true, | |||||
| }, | |||||
| { | |||||
| label: '项目名称', | |||||
| value: <CopyingText text="测试项目名称测试项目名称测试项目名称"></CopyingText>, | |||||
| ellipsis: false, | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['auto-ml-basic']}> | |||||
| <Flex gap={15} align="stretch"> | |||||
| <ConfigInfo title="基本信息" data={datas} labelWidth={70} /> | |||||
| <StatusChart | |||||
| chartData={{ Failed: 10, Pending: 20, Running: 30, Succeeded: 40, Terminated: 50 }} | |||||
| /> | |||||
| <ConfigInfo title="Trial 配置" data={datas} labelWidth={70} /> | |||||
| </Flex> | |||||
| <ConfigInfo | |||||
| title="搜索配置" | |||||
| data={datas} | |||||
| style={{ marginTop: '16px' }} | |||||
| labelWidth={70} | |||||
| threeColumn | |||||
| /> | |||||
| <ConfigInfo | |||||
| title="执行配置" | |||||
| data={datas} | |||||
| style={{ marginTop: '16px' }} | |||||
| labelWidth={70} | |||||
| threeColumn | |||||
| /> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default AutoMLBasic; | |||||
| @@ -0,0 +1,15 @@ | |||||
| .auto-ml-table { | |||||
| height: 100%; | |||||
| padding: 20px @content-padding 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__filter { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| &__table { | |||||
| height: calc(100% - 32px - 28px); | |||||
| margin-top: 28px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,305 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 自主机器学习 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { | |||||
| App, | |||||
| Button, | |||||
| ConfigProvider, | |||||
| Input, | |||||
| Table, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| } from 'antd'; | |||||
| import { type SearchProps } from 'antd/es/input'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| // import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; | |||||
| import { ServiceData, ServiceOperationType } from '@/pages/AutoML/types'; | |||||
| import RunStatusCell from '../RunStatusCell'; | |||||
| import styles from './index.less'; | |||||
| function AutoMLTable() { | |||||
| const navigate = useNavigate(); | |||||
| const { message } = App.useApp(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||||
| const [tableData, setTableData] = useState<ServiceData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| useEffect(() => { | |||||
| getServiceList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取模型部署服务列表 | |||||
| const getServiceList = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| service_name: searchText, | |||||
| }; | |||||
| const [res] = await to(getServiceListReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除模型部署 | |||||
| const deleteService = async (record: ServiceData) => { | |||||
| const [res] = await to(deleteServiceReq(record.id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getServiceList(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||||
| setSearchText(value); | |||||
| }; | |||||
| // 处理删除 | |||||
| const handleServiceDelete = (record: ServiceData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该服务将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteService(record); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建、更新服务 | |||||
| const createService = (type: ServiceOperationType, record?: ServiceData) => { | |||||
| SessionStorage.setItem( | |||||
| SessionStorage.serviceInfoKey, | |||||
| { | |||||
| ...record, | |||||
| operationType: type, | |||||
| }, | |||||
| true, | |||||
| ); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| }); | |||||
| navigate(`/modelDeployment/createService`); | |||||
| }; | |||||
| // 查看详情 | |||||
| const toDetail = (record: ServiceData) => { | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| }); | |||||
| navigate(`/modelDeployment/serviceInfo/${record.id}`); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps<ServiceData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<ServiceData>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pagination.current! - 1, | |||||
| pageSize: pagination.pageSize!, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: 'Trial ID', | |||||
| dataIndex: 'service_name', | |||||
| key: 'service_name', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Link, { | |||||
| onClick: toDetail, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'run_status', | |||||
| key: 'run_status', | |||||
| width: '20%', | |||||
| render: RunStatusCell, | |||||
| }, | |||||
| { | |||||
| title: '最终指标', | |||||
| dataIndex: 'service_type_name', | |||||
| key: 'service_type_name', | |||||
| width: '20%', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '当前指标', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| width: '20%', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: 'lr', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| width: '20%', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: 'batch_size', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| width: '20%', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '修改时间', | |||||
| dataIndex: 'update_time', | |||||
| key: 'update_time', | |||||
| width: '20%', | |||||
| render: tableCellRender(true, TableCellValueType.Date), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '执行时长', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| width: '20%', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 400, | |||||
| key: 'operation', | |||||
| render: (_: any, record: ServiceData) => ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| icon={<KFIcon type="icon-rizhi" />} | |||||
| onClick={() => createService(ServiceOperationType.Update, record)} | |||||
| > | |||||
| 日志 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-tingzhi" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 停止 | |||||
| </Button> | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['textColorTertiary'], | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-zhongpao" />} | |||||
| onClick={() => handleServiceDelete(record)} | |||||
| > | |||||
| 重跑 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['auto-ml-table']}> | |||||
| <div className={styles['auto-ml-table__filter']}> | |||||
| <Input.Search | |||||
| placeholder="按服务名称筛选" | |||||
| onSearch={onSearch} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| value={inputText} | |||||
| allowClear | |||||
| /> | |||||
| <Button | |||||
| style={{ marginRight: 0, marginLeft: 'auto' }} | |||||
| type="default" | |||||
| onClick={() => {}} | |||||
| icon={<KFIcon type="icon-shuaxin" />} | |||||
| > | |||||
| 刷新 | |||||
| </Button> | |||||
| </div> | |||||
| <div className={classNames('vertical-scroll-table', styles['auto-ml-table__table'])}> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default AutoMLTable; | |||||
| @@ -0,0 +1,42 @@ | |||||
| .config-info { | |||||
| width: 100%; | |||||
| border: 1px solid @border-color-base; | |||||
| border-radius: 4px; | |||||
| &__content { | |||||
| padding: 20px; | |||||
| padding: 20px @content-padding; | |||||
| background-color: white; | |||||
| } | |||||
| :global { | |||||
| .kf-basic-info { | |||||
| gap: 15px 40px; | |||||
| width: 100%; | |||||
| &__item { | |||||
| &__label { | |||||
| font-size: @font-size; | |||||
| text-align: left; | |||||
| text-align-last: left; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| &__value { | |||||
| font-size: @font-size; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &--three-column { | |||||
| :global { | |||||
| .kf-basic-info { | |||||
| &__item { | |||||
| width: calc((100% - 80px) / 3); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,47 @@ | |||||
| import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect } from 'react'; | |||||
| import ConfigTitle from '../ConfigTitle'; | |||||
| import styles from './index.less'; | |||||
| export { type BasicInfoData }; | |||||
| type ConfigInfoProps = { | |||||
| title: string; | |||||
| data: BasicInfoData[]; | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| children?: React.ReactNode; | |||||
| labelWidth: number; | |||||
| threeColumn?: boolean; | |||||
| }; | |||||
| function ConfigInfo({ | |||||
| title, | |||||
| data, | |||||
| className, | |||||
| style, | |||||
| children, | |||||
| labelWidth, | |||||
| threeColumn = false, | |||||
| }: ConfigInfoProps) { | |||||
| useEffect(() => {}, []); | |||||
| return ( | |||||
| <div | |||||
| className={classNames( | |||||
| styles['config-info'], | |||||
| { [styles['config-info--three-column']]: threeColumn }, | |||||
| className, | |||||
| )} | |||||
| style={style} | |||||
| > | |||||
| <ConfigTitle title={title} /> | |||||
| <div className={styles['config-info__content']}> | |||||
| <BasicInfo datas={data} labelWidth={labelWidth} /> | |||||
| {children} | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ConfigInfo; | |||||
| @@ -0,0 +1,38 @@ | |||||
| .config-title { | |||||
| width: 100%; | |||||
| height: 56px; | |||||
| padding-left: @content-padding; | |||||
| background: linear-gradient( | |||||
| 179.03deg, | |||||
| rgba(199, 223, 255, 0.12) 0%, | |||||
| rgba(22, 100, 255, 0.04) 100% | |||||
| ); | |||||
| border: 1px solid #e8effb; | |||||
| &__img { | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 10px; | |||||
| } | |||||
| &__text { | |||||
| position: relative; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size-title; | |||||
| &::after { | |||||
| position: absolute; | |||||
| bottom: 6px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.4) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| import { Flex } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| type ConfigTitleProps = { | |||||
| title: string; | |||||
| }; | |||||
| function ConfigTitle({ title }: ConfigTitleProps) { | |||||
| return ( | |||||
| <Flex align="center" className={styles['config-title']}> | |||||
| <img | |||||
| src={require('@/assets/img/code-name-icon.png')} | |||||
| className={styles['config-title__img']} | |||||
| alt="" | |||||
| draggable={false} | |||||
| /> | |||||
| <span className={styles['config-title__text']}>{title}</span> | |||||
| </Flex> | |||||
| ); | |||||
| } | |||||
| export default ConfigTitle; | |||||
| @@ -0,0 +1,18 @@ | |||||
| .copying-text { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| align-items: center; | |||||
| min-width: 0; | |||||
| margin-left: 16px; | |||||
| &__text { | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &__icon { | |||||
| margin-left: 6px; | |||||
| font-size: 14px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { Typography } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| export type CopyingTextProps = { | |||||
| text: string; | |||||
| onCopySuccess?: () => void; | |||||
| onCopyFailed?: () => void; | |||||
| }; | |||||
| function CopyingText({ text }: CopyingTextProps) { | |||||
| return ( | |||||
| <div className={styles['copying-text']}> | |||||
| <Typography.Text | |||||
| ellipsis={{ tooltip: text }} | |||||
| style={{ color: 'inherit' }} | |||||
| className={styles['copying-text__text']} | |||||
| > | |||||
| {text} | |||||
| </Typography.Text> | |||||
| <KFIcon | |||||
| id="copying" | |||||
| data-clipboard-text={text} | |||||
| type="icon-fuzhi2" | |||||
| className={styles['copying-text__icon']} | |||||
| color="#606b7a" | |||||
| /> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CopyingText; | |||||
| @@ -0,0 +1,85 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { Col, Form, Input, Row, Select } from 'antd'; | |||||
| function BasicConfig() { | |||||
| return ( | |||||
| <> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="服务名称" | |||||
| name="service_name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入服务名称', | |||||
| }, | |||||
| { | |||||
| pattern: /^(?!\d)[\u4e00-\u9fa5\w-]+$/, | |||||
| message: '不能以数字开头,不能存在除了-_以外的特殊字符', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="不能以数字开头,不能存在除了-_以外的特殊字符" | |||||
| maxLength={256} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={20}> | |||||
| <Form.Item | |||||
| label="描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| placeholder="请输入描述" | |||||
| maxLength={256} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="可见范围" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择可见范围', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择可见范围" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| export default BasicConfig; | |||||
| @@ -0,0 +1,134 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||||
| import { Button, Col, Flex, Form, Input, Radio, Row } from 'antd'; | |||||
| import ExecuteConfigDLC from './ExecuteConfigDLC'; | |||||
| import ExecuteConfigMC from './ExecuteConfigMC'; | |||||
| import styles from './index.less'; | |||||
| type ExecuteConfigProps = { | |||||
| disabled?: boolean; | |||||
| }; | |||||
| function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { | |||||
| const form = Form.useFormInstance(); | |||||
| const image_type = Form.useWatch('image_type', form); | |||||
| console.log(image_type); | |||||
| return ( | |||||
| <> | |||||
| <SubAreaTitle | |||||
| title="执行配置" | |||||
| image={require('@/assets/img/model-deployment.png')} | |||||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="任务类型" | |||||
| name="execute_type" | |||||
| rules={[{ required: true, message: '请选择任务类型' }]} | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={'DLC'}>DLC</Radio> | |||||
| <Radio value={'MaxCompute'}>MaxCompute</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Form.Item dependencies={['execute_type']} noStyle> | |||||
| {({ getFieldValue }) => { | |||||
| return getFieldValue('execute_type') === 'DLC' ? ( | |||||
| <ExecuteConfigDLC disabled={disabled}></ExecuteConfigDLC> | |||||
| ) : ( | |||||
| <ExecuteConfigMC disabled={disabled}></ExecuteConfigMC> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | |||||
| <Form.List name="hyper-parameter"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="超参数" | |||||
| style={{ marginBottom: 0, marginTop: '-14px' }} | |||||
| tooltip="超参数" | |||||
| ></Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <div className={styles['hyper-parameter']}> | |||||
| <Flex align="center" className={styles['hyper-parameter__header']}> | |||||
| <div className={styles['hyper-parameter__header__name']}>参数名称</div> | |||||
| <div className={styles['hyper-parameter__header__type']}>约束类型</div> | |||||
| <div className={styles['hyper-parameter__header__space']}>搜索空间</div> | |||||
| <div className={styles['hyper-parameter__header__operation']}>操作</div> | |||||
| </Flex> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex key={key} align="center" className={styles['hyper-parameter__body']}> | |||||
| <Form.Item | |||||
| className={styles['hyper-parameter__body__name']} | |||||
| {...restField} | |||||
| name={[name, 'name']} | |||||
| rules={[{ required: true, message: 'Missing first name' }]} | |||||
| > | |||||
| <Input placeholder="Key" /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| className={styles['hyper-parameter__body__name']} | |||||
| {...restField} | |||||
| name={[name, 'type']} | |||||
| rules={[{ required: true, message: 'Missing last name' }]} | |||||
| > | |||||
| <Input placeholder="Value" /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| className={styles['hyper-parameter__body__name']} | |||||
| {...restField} | |||||
| name={[name, 'space']} | |||||
| rules={[{ required: true, message: 'Missing last name' }]} | |||||
| > | |||||
| <Input placeholder="Value" /> | |||||
| </Form.Item> | |||||
| <div className={styles['hyper-parameter__body__operation']}> | |||||
| <Button | |||||
| style={{ | |||||
| marginRight: '3px', | |||||
| }} | |||||
| shape="circle" | |||||
| disabled={fields.length === 1} | |||||
| type="text" | |||||
| size="middle" | |||||
| onClick={() => remove(name)} | |||||
| icon={<MinusCircleOutlined />} | |||||
| ></Button> | |||||
| {index === fields.length - 1 && ( | |||||
| <Button | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| onClick={() => add()} | |||||
| icon={<PlusCircleOutlined />} | |||||
| ></Button> | |||||
| )} | |||||
| </div> | |||||
| </Flex> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <div className={styles['hyper-parameter__add']}> | |||||
| <Button type="link" onClick={() => add()} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 添加一行 | |||||
| </Button> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| export default ExecuteConfig; | |||||
| @@ -0,0 +1,258 @@ | |||||
| import CodeSelect from '@/components/CodeSelect'; | |||||
| import ResourceSelect, { ResourceSelectorType } from '@/components/ResourceSelect'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||||
| import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| type ExecuteConfigDLCProps = { | |||||
| disabled?: boolean; | |||||
| }; | |||||
| function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { | |||||
| return ( | |||||
| <> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="资源组" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: false, | |||||
| message: '请选择资源组', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择资源组" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="框架" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: false, | |||||
| message: '请选择框架', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择框架" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="数据集" | |||||
| name="model" | |||||
| // rules={[ | |||||
| // { | |||||
| // validator: requiredValidator, | |||||
| // message: '请选择数据集', | |||||
| // }, | |||||
| // ]} | |||||
| required | |||||
| > | |||||
| <ResourceSelect | |||||
| type={ResourceSelectorType.Dataset} | |||||
| placeholder="请选择数据集" | |||||
| canInput={false} | |||||
| size="large" | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="代码配置" | |||||
| name="code_config" | |||||
| // rules={[ | |||||
| // { | |||||
| // validator: requiredValidator, | |||||
| // message: '请选择代码配置', | |||||
| // }, | |||||
| // ]} | |||||
| required | |||||
| > | |||||
| <CodeSelect | |||||
| placeholder="请选择代码配置" | |||||
| canInput={false} | |||||
| size="large" | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="节点镜像" | |||||
| name="image_type" | |||||
| rules={[{ required: true, message: '请选择节点镜像' }]} | |||||
| tooltip="节点镜像" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>官方镜像</Radio> | |||||
| <Radio value={2}>自定义镜像</Radio> | |||||
| <Radio value={3}>镜像地址</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item dependencies={['image_type']} noStyle> | |||||
| {({ getFieldValue }) => { | |||||
| const imageType = getFieldValue('image_type'); | |||||
| if (imageType === 1 || imageType === 2) { | |||||
| return ( | |||||
| <Form.Item name="image_url" label=" " className="image-url"> | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择框架" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| ); | |||||
| } else { | |||||
| return ( | |||||
| <Form.Item name="image_url" label=" " className="image-url"> | |||||
| <Input placeholder="请输入" disabled={disabled} /> | |||||
| </Form.Item> | |||||
| ); | |||||
| } | |||||
| }} | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="节点数量" | |||||
| name="replicas" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入节点数量', | |||||
| }, | |||||
| { | |||||
| pattern: /^[1-9]\d*$/, | |||||
| message: '节点数量必须是正整数', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <InputNumber placeholder="请输入节点数量" min={1} precision={0} /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item label="高级配置" tooltip="高级配置"> | |||||
| <Form.List name="advanced_config"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex key={key} align="center" className={styles['advanced-config']}> | |||||
| <Form.Item | |||||
| style={{ flex: 1, marginBottom: 0 }} | |||||
| {...restField} | |||||
| name={[name, 'key']} | |||||
| rules={[{ required: true, message: 'Missing first name' }]} | |||||
| > | |||||
| <Input placeholder="Key" /> | |||||
| </Form.Item> | |||||
| <span style={{ margin: '0 8px' }}>:</span> | |||||
| <Form.Item | |||||
| style={{ flex: 1, marginBottom: 0 }} | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| rules={[{ required: true, message: 'Missing last name' }]} | |||||
| > | |||||
| <Input placeholder="Value" /> | |||||
| </Form.Item> | |||||
| <div style={{ width: '76px', marginLeft: '18px' }}> | |||||
| <Button | |||||
| style={{ | |||||
| marginRight: '3px', | |||||
| }} | |||||
| size="middle" | |||||
| shape="circle" | |||||
| disabled={fields.length === 1} | |||||
| type="text" | |||||
| onClick={() => remove(name)} | |||||
| icon={<MinusCircleOutlined />} | |||||
| ></Button> | |||||
| {index === fields.length - 1 && ( | |||||
| <Button | |||||
| size="middle" | |||||
| shape="circle" | |||||
| type="text" | |||||
| onClick={() => add()} | |||||
| icon={<PlusCircleOutlined />} | |||||
| ></Button> | |||||
| )} | |||||
| </div> | |||||
| </Flex> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <Form.Item style={{ marginBottom: 0 }}> | |||||
| <Button | |||||
| style={{ background: 'white' }} | |||||
| type="dashed" | |||||
| onClick={() => add()} | |||||
| block | |||||
| icon={<PlusCircleOutlined />} | |||||
| > | |||||
| 添加高级配置 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="节点启动命令" | |||||
| name="mount_path" | |||||
| // rules={[ | |||||
| // { | |||||
| // required: true, | |||||
| // message: '请输入节点启动命令', | |||||
| // }, | |||||
| // ]} | |||||
| tooltip="节点启动命令" | |||||
| > | |||||
| <Input placeholder="请输入节点启动命令" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| export default ExecuteConfigDLC; | |||||
| @@ -0,0 +1,82 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||||
| import { Button, Col, Flex, Form, Input, Row } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| type ExecuteConfigMCProps = { | |||||
| disabled?: boolean; | |||||
| }; | |||||
| function ExecuteConfigMC({ disabled = false }: ExecuteConfigMCProps) { | |||||
| return ( | |||||
| <> | |||||
| <Form.List name="command"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="命令" | |||||
| style={{ marginBottom: 0, marginTop: '-14px' }} | |||||
| tooltip="命令" | |||||
| ></Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <div className={styles['command']}> | |||||
| <Flex align="center" className={styles['command__header']}> | |||||
| <div className={styles['command__header__name']}>Key</div> | |||||
| <div className={styles['command__header__command']}>命令</div> | |||||
| <div className={styles['command__header__operation']}>操作</div> | |||||
| </Flex> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex key={key} align="center" className={styles['command__body']}> | |||||
| <span className={styles['command__body__name']}>cmd{index + 1}</span> | |||||
| <Form.Item | |||||
| className={styles['command__body__command']} | |||||
| {...restField} | |||||
| name={[name, 'command']} | |||||
| rules={[{ required: true, message: 'Missing last name' }]} | |||||
| > | |||||
| <Input.TextArea placeholder="Value" autoSize={{ minRows: 2, maxRows: 3 }} /> | |||||
| </Form.Item> | |||||
| <div className={styles['command__body__operation']}> | |||||
| <Button | |||||
| style={{ | |||||
| marginRight: '3px', | |||||
| }} | |||||
| shape="circle" | |||||
| disabled={fields.length === 1} | |||||
| type="text" | |||||
| size="middle" | |||||
| onClick={() => remove(name)} | |||||
| icon={<MinusCircleOutlined />} | |||||
| ></Button> | |||||
| {index === fields.length - 1 && ( | |||||
| <Button | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| onClick={() => add()} | |||||
| icon={<PlusCircleOutlined />} | |||||
| ></Button> | |||||
| )} | |||||
| </div> | |||||
| </Flex> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <div className={styles['hyper-parameter__add']}> | |||||
| <Button type="link" onClick={() => add()} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 添加一行 | |||||
| </Button> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| export default ExecuteConfigMC; | |||||
| @@ -0,0 +1,119 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { Col, Form, InputNumber, Row, Select, Switch } from 'antd'; | |||||
| function SearchConfig() { | |||||
| return ( | |||||
| <> | |||||
| <SubAreaTitle | |||||
| title="搜索配置" | |||||
| image={require('@/assets/img/search-config-icon.png')} | |||||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="搜索算法" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择搜索算法', | |||||
| }, | |||||
| ]} | |||||
| tooltip="搜索算法" | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择搜索算法" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="最大搜素次数" | |||||
| name="replicas" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入最大搜素次数', | |||||
| }, | |||||
| { | |||||
| pattern: /^[1-9]\d*$/, | |||||
| message: '最大搜素次数必须是正整数', | |||||
| }, | |||||
| ]} | |||||
| tooltip="最大搜素次数" | |||||
| > | |||||
| <InputNumber placeholder="请输入最大搜素次数" min={1} precision={0} /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="最大并发量" | |||||
| name="replicas" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入最大并发量', | |||||
| }, | |||||
| { | |||||
| pattern: /^[1-9]\d*$/, | |||||
| message: '最大并发量必须是正整数', | |||||
| }, | |||||
| ]} | |||||
| tooltip="最大并发量" | |||||
| > | |||||
| <InputNumber placeholder="请输入最大并发量" min={1} precision={0} /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="开启EarlyStop" | |||||
| name="replicas" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择是否开启EarlyStop', | |||||
| }, | |||||
| ]} | |||||
| tooltip="开启EarlyStop" | |||||
| > | |||||
| <Switch checkedChildren="开启" unCheckedChildren="关闭" defaultChecked /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="Start step" | |||||
| name="replicas" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入Start step', | |||||
| }, | |||||
| { | |||||
| pattern: /^[1-9]\d*$/, | |||||
| message: 'Start step必须是正整数', | |||||
| }, | |||||
| ]} | |||||
| tooltip="Start step" | |||||
| > | |||||
| <InputNumber placeholder="请输入Start step" min={1} precision={0} /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| export default SearchConfig; | |||||
| @@ -0,0 +1,193 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||||
| import { Button, Col, Flex, Form, Input, Radio, Row, Select } from 'antd'; | |||||
| import styles from './index.less'; | |||||
| function TrialConfig() { | |||||
| return ( | |||||
| <> | |||||
| <SubAreaTitle | |||||
| title="Trial配置" | |||||
| image={require('@/assets/img/trial-config-icon.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['trial-metrics']}> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item label="优化指标"></Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={24}> | |||||
| <Form.Item | |||||
| label="指标类型" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择指标类型', | |||||
| }, | |||||
| ]} | |||||
| labelCol={{ flex: '120px' }} | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择指标类型" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={24}> | |||||
| <Form.Item | |||||
| label="计算方式" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择计算方式', | |||||
| }, | |||||
| ]} | |||||
| labelCol={{ flex: '120px' }} | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择计算方式" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={24}> | |||||
| <Form.Item label="指标权重" tooltip="指标权重" labelCol={{ flex: '120px' }}> | |||||
| <Form.List name="metrics_weight"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex key={key} align="center" className={styles['advanced-config']}> | |||||
| <Form.Item | |||||
| style={{ flex: 1, marginBottom: 0 }} | |||||
| {...restField} | |||||
| name={[name, 'key']} | |||||
| rules={[{ required: true, message: 'Missing first name' }]} | |||||
| > | |||||
| <Input placeholder="Key" /> | |||||
| </Form.Item> | |||||
| <span style={{ margin: '0 8px' }}>:</span> | |||||
| <Form.Item | |||||
| style={{ flex: 1, marginBottom: 0 }} | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| rules={[{ required: true, message: 'Missing last name' }]} | |||||
| > | |||||
| <Input placeholder="Value" /> | |||||
| </Form.Item> | |||||
| <div style={{ width: '76px', marginLeft: '18px' }}> | |||||
| <Button | |||||
| style={{ | |||||
| marginRight: '3px', | |||||
| }} | |||||
| shape="circle" | |||||
| size="middle" | |||||
| disabled={fields.length === 1} | |||||
| type="text" | |||||
| onClick={() => remove(name)} | |||||
| icon={<MinusCircleOutlined />} | |||||
| ></Button> | |||||
| {index === fields.length - 1 && ( | |||||
| <Button | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| onClick={() => add()} | |||||
| icon={<PlusCircleOutlined />} | |||||
| ></Button> | |||||
| )} | |||||
| </div> | |||||
| </Flex> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <Form.Item style={{ marginBottom: 0 }}> | |||||
| <Button | |||||
| style={{ background: 'white' }} | |||||
| type="dashed" | |||||
| onClick={() => add()} | |||||
| block | |||||
| icon={<PlusCircleOutlined />} | |||||
| > | |||||
| 添加指标权重 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={0}> | |||||
| <Col span={24}> | |||||
| <Form.Item | |||||
| label="指标来源" | |||||
| name="service_name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入指标来源', | |||||
| }, | |||||
| ]} | |||||
| tooltip="指标来源" | |||||
| labelCol={{ flex: '120px' }} | |||||
| > | |||||
| <Input placeholder="请输入指标来源" maxLength={256} showCount allowClear /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={0}> | |||||
| <Col span={24}> | |||||
| <Form.Item | |||||
| label="优化方向" | |||||
| name="execute_type" | |||||
| rules={[{ required: true, message: '请选择优化方向' }]} | |||||
| labelCol={{ flex: '120px' }} | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>越大越好</Radio> | |||||
| <Radio value={2}>越小越好</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| </div> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="模型名称" | |||||
| name="service_name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型名称', | |||||
| }, | |||||
| ]} | |||||
| tooltip="模型名称" | |||||
| > | |||||
| <Input placeholder="请输入模型名称" maxLength={256} showCount allowClear /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| export default TrialConfig; | |||||
| @@ -0,0 +1,130 @@ | |||||
| .advanced-config { | |||||
| margin-bottom: 20px; | |||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| } | |||||
| .command { | |||||
| width: 83.33%; | |||||
| margin-bottom: 20px; | |||||
| border: 1px solid rgba(234, 234, 234, 0.8); | |||||
| border-radius: 4px; | |||||
| &__header { | |||||
| height: 50px; | |||||
| padding-left: 8px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8f8f9; | |||||
| border-radius: 4px 4px 0px 0px; | |||||
| &__name { | |||||
| flex: none; | |||||
| width: 100px; | |||||
| } | |||||
| &__command { | |||||
| flex: 1; | |||||
| margin-right: 15px; | |||||
| } | |||||
| &__operation { | |||||
| flex: none; | |||||
| width: 100px; | |||||
| } | |||||
| } | |||||
| &__body { | |||||
| padding: 8px; | |||||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | |||||
| &:last-child { | |||||
| border-bottom: none; | |||||
| } | |||||
| &__name { | |||||
| flex: none; | |||||
| width: 100px; | |||||
| } | |||||
| &__command { | |||||
| flex: 1; | |||||
| margin-right: 15px; | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| &__operation { | |||||
| flex: none; | |||||
| width: 100px; | |||||
| } | |||||
| } | |||||
| &__add { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| padding: 15px 0; | |||||
| } | |||||
| } | |||||
| .hyper-parameter { | |||||
| width: 83.33%; | |||||
| margin-bottom: 20px; | |||||
| border: 1px solid rgba(234, 234, 234, 0.8); | |||||
| border-radius: 4px; | |||||
| &__header { | |||||
| height: 50px; | |||||
| padding-left: 8px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8f8f9; | |||||
| border-radius: 4px 4px 0px 0px; | |||||
| &__name, | |||||
| &__type, | |||||
| &__space { | |||||
| flex: 1; | |||||
| margin-right: 15px; | |||||
| } | |||||
| &__operation { | |||||
| flex: none; | |||||
| width: 100px; | |||||
| } | |||||
| } | |||||
| &__body { | |||||
| padding: 8px; | |||||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | |||||
| &:last-child { | |||||
| border-bottom: none; | |||||
| } | |||||
| &__name, | |||||
| &__type, | |||||
| &__space { | |||||
| flex: 1; | |||||
| margin-right: 15px; | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| &__operation { | |||||
| flex: none; | |||||
| width: 100px; | |||||
| } | |||||
| } | |||||
| &__add { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| padding: 15px 0; | |||||
| } | |||||
| } | |||||
| .trial-metrics { | |||||
| width: calc(41.67% - 6px); | |||||
| margin-bottom: 20px; | |||||
| padding: 0 20px; | |||||
| border: 1px dashed #e0e0e0; | |||||
| border-radius: 8px; | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| .execute-schedule-cell { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| align-items: center; | |||||
| &__progress { | |||||
| width: 112px; | |||||
| height: 16px; | |||||
| padding: 4px 5px; | |||||
| background: rgba(96, 107, 122, 0.15); | |||||
| border-radius: 8px; | |||||
| &__bar { | |||||
| height: 7px; | |||||
| background: linear-gradient(270deg, #1664ff 0%, rgba(22, 100, 255, 0.1) 100%); | |||||
| border-radius: 9px; | |||||
| } | |||||
| } | |||||
| &__text { | |||||
| margin-left: 6px; | |||||
| color: @text-color-tertiary; | |||||
| font-size: 12px; | |||||
| letter-spacing: 2px; | |||||
| strong { | |||||
| color: @text-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-29 18:35:41 | |||||
| * @Description: 实验实例执行进度 | |||||
| */ | |||||
| import styles from './index.less'; | |||||
| function ExecuteScheduleCell(status?: any) { | |||||
| return ( | |||||
| <div className={styles['execute-schedule-cell']}> | |||||
| <div className={styles['execute-schedule-cell__progress']}> | |||||
| <div | |||||
| className={styles['execute-schedule-cell__progress__bar']} | |||||
| style={{ width: '80%' }} | |||||
| ></div> | |||||
| </div> | |||||
| <span className={styles['execute-schedule-cell__text']}> | |||||
| 1/<strong>2</strong> | |||||
| </span> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ExecuteScheduleCell; | |||||
| @@ -0,0 +1,19 @@ | |||||
| .run-status-cell { | |||||
| color: @text-color; | |||||
| &--running { | |||||
| color: @primary-color; | |||||
| } | |||||
| &--stopped { | |||||
| 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 { ServiceRunStatus } from '@/enums'; | |||||
| import styles from './index.less'; | |||||
| export type ServiceRunStatusInfo = { | |||||
| text: string; | |||||
| classname: string; | |||||
| }; | |||||
| export const statusInfo: Record<ServiceRunStatus, ServiceRunStatusInfo> = { | |||||
| [ServiceRunStatus.Init]: { | |||||
| text: '启动中', | |||||
| classname: styles['run-status-cell'], | |||||
| }, | |||||
| [ServiceRunStatus.Running]: { | |||||
| classname: styles['run-status-cell--running'], | |||||
| text: '运行中', | |||||
| }, | |||||
| [ServiceRunStatus.Stopped]: { | |||||
| classname: styles['run-status-cell--stopped'], | |||||
| text: '已停止', | |||||
| }, | |||||
| [ServiceRunStatus.Failed]: { | |||||
| classname: styles['run-status-cell--error'], | |||||
| text: '失败', | |||||
| }, | |||||
| [ServiceRunStatus.Pending]: { | |||||
| classname: styles['run-status-cell--pending'], | |||||
| text: '挂起中', | |||||
| }, | |||||
| }; | |||||
| function RunStatusCell(status?: ServiceRunStatus | null) { | |||||
| if (status === null || status === undefined || !statusInfo[status]) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||||
| } | |||||
| export default RunStatusCell; | |||||
| @@ -0,0 +1,8 @@ | |||||
| .status-chart { | |||||
| flex: none; | |||||
| width: 380px; | |||||
| min-width: 380px; | |||||
| background: #ffffff; | |||||
| border: 1px solid @border-color-base; | |||||
| border-radius: 4px; | |||||
| } | |||||
| @@ -0,0 +1,217 @@ | |||||
| import themes from '@/styles/theme.less'; | |||||
| import * as echarts from 'echarts'; | |||||
| import React, { useEffect, useRef } from 'react'; | |||||
| import ConfigTitle from '../ConfigTitle'; | |||||
| import styles from './index.less'; | |||||
| const color1 = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: '#c73131' }, | |||||
| { offset: 1, color: '#ff7e96' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| const color2 = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: '#6ac21d' }, | |||||
| { offset: 1, color: '#96e850' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| const color3 = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: '#8c8c8c' }, | |||||
| { offset: 1, color: '#c8c6c6' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| const color4 = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: '#ecb934' }, | |||||
| { offset: 1, color: '#f0864d' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| const color5 = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: '#7ea9fe' }, | |||||
| { offset: 1, color: '#1664ff' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| const color6 = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: 'rgba(255, 255, 255, 0.62)' }, | |||||
| { offset: 1, color: '#ebf2ff ' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| export type ExperimentStatistics = { | |||||
| Failed: number; | |||||
| Pending: number; | |||||
| Running: number; | |||||
| Succeeded: number; | |||||
| Terminated: number; | |||||
| }; | |||||
| type ExperimentChartProps = { | |||||
| style?: React.CSSProperties; | |||||
| chartData: ExperimentStatistics; | |||||
| }; | |||||
| function StatusChart({ chartData, style }: ExperimentChartProps) { | |||||
| const chartRef = useRef<HTMLDivElement>(null); | |||||
| const total = | |||||
| chartData.Failed + | |||||
| chartData.Pending + | |||||
| chartData.Running + | |||||
| chartData.Succeeded + | |||||
| chartData.Terminated; | |||||
| const options: echarts.EChartsOption = { | |||||
| title: { | |||||
| show: true, | |||||
| left: '29%', | |||||
| top: 'center', | |||||
| textAlign: 'center', | |||||
| text: [`{a|${total}}`, '{b|Trials}'].join('\n'), | |||||
| textStyle: { | |||||
| rich: { | |||||
| a: { | |||||
| color: themes['textColor'], | |||||
| fontSize: 20, | |||||
| fontWeight: 700, | |||||
| lineHeight: 28, | |||||
| }, | |||||
| b: { | |||||
| color: themes['textColorSecondary'], | |||||
| fontSize: 10, | |||||
| fontWeight: 'normal', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| tooltip: { | |||||
| trigger: 'item', | |||||
| }, | |||||
| legend: { | |||||
| top: 'center', | |||||
| right: '5%', | |||||
| orient: 'vertical', | |||||
| icon: 'circle', | |||||
| itemWidth: 6, | |||||
| itemGap: 20, | |||||
| height: 100, | |||||
| }, | |||||
| color: [color1, color2, color3, color4, color5], | |||||
| series: [ | |||||
| { | |||||
| type: 'pie', | |||||
| radius: ['70%', '80%'], | |||||
| center: ['30%', '50%'], | |||||
| avoidLabelOverlap: false, | |||||
| padAngle: 3, | |||||
| itemStyle: { | |||||
| borderRadius: 3, | |||||
| }, | |||||
| minAngle: 5, | |||||
| label: { | |||||
| show: false, | |||||
| }, | |||||
| emphasis: { | |||||
| label: { | |||||
| show: false, | |||||
| }, | |||||
| }, | |||||
| labelLine: { | |||||
| show: false, | |||||
| }, | |||||
| data: [ | |||||
| { value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' }, | |||||
| { value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' }, | |||||
| { value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' }, | |||||
| { value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' }, | |||||
| { value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| type: 'pie', | |||||
| radius: '60%', | |||||
| center: ['30%', '50%'], | |||||
| avoidLabelOverlap: false, | |||||
| label: { | |||||
| show: false, | |||||
| }, | |||||
| tooltip: { | |||||
| show: false, | |||||
| }, | |||||
| emphasis: { | |||||
| label: { | |||||
| show: false, | |||||
| }, | |||||
| disabled: true, | |||||
| }, | |||||
| animation: false, | |||||
| labelLine: { | |||||
| show: false, | |||||
| }, | |||||
| data: [ | |||||
| { | |||||
| value: 100, | |||||
| itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 }, | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| useEffect(() => { | |||||
| // 创建一个echarts实例,返回echarts实例 | |||||
| const chart = echarts.init(chartRef.current); | |||||
| // 设置图表实例的配置项和数据 | |||||
| chart.setOption(options); | |||||
| // 组件卸载 | |||||
| return () => { | |||||
| // myChart.dispose() 销毁实例 | |||||
| chart.dispose(); | |||||
| }; | |||||
| }, []); | |||||
| return ( | |||||
| <div className={styles['status-chart']} style={style}> | |||||
| <ConfigTitle title="Trial 状态统计" /> | |||||
| <div style={{ width: '100%', height: '185px' }} ref={chartRef}></div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default StatusChart; | |||||
| @@ -0,0 +1,6 @@ | |||||
| // 操作类型 | |||||
| export enum ServiceOperationType { | |||||
| Create = 'Create', // 创建 | |||||
| Update = 'Update', // 更新 | |||||
| Restart = 'Restart', // 重启 | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| import ClipboardJS from 'clipboard'; | |||||
| import { message } from "antd"; | |||||
| const clipboard = new ClipboardJS('#copying'); | |||||
| clipboard.on('success', () => { | |||||
| message.success('复制成功'); | |||||
| }); | |||||
| clipboard.on('error', () => { | |||||
| message.error('复制失败'); | |||||
| }); | |||||