| @@ -271,7 +271,18 @@ export default [ | |||||
| { | { | ||||
| name: '镜像详情', | name: '镜像详情', | ||||
| path: 'info/:id', | path: 'info/:id', | ||||
| component: './Mirror/Info', | |||||
| routes: [ | |||||
| { | |||||
| name: '镜像详情', | |||||
| path: '', | |||||
| component: './Mirror/Info', | |||||
| }, | |||||
| { | |||||
| name: '新增镜像版本', | |||||
| path: 'add-version', | |||||
| component: './Mirror/Create', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| name: '创建镜像', | name: '创建镜像', | ||||
| @@ -67,6 +67,7 @@ | |||||
| "@types/crypto-js": "^4.2.2", | "@types/crypto-js": "^4.2.2", | ||||
| "@umijs/route-utils": "^4.0.1", | "@umijs/route-utils": "^4.0.1", | ||||
| "antd": "~5.21.4", | "antd": "~5.21.4", | ||||
| "caniuse-lite": "~1.0.30001707", | |||||
| "classnames": "^2.3.2", | "classnames": "^2.3.2", | ||||
| "crypto-js": "^4.2.0", | "crypto-js": "^4.2.0", | ||||
| "echarts": "^5.5.0", | "echarts": "^5.5.0", | ||||
| @@ -4,16 +4,6 @@ | |||||
| font-display: swap; | font-display: swap; | ||||
| } | } | ||||
| @font-face { | |||||
| font-family: 'TaoBaoMaiCaiTi'; | |||||
| src: url('./TaoBaoMaiCaiTi-Regular.woff2') format('woff2'), /* 最优先使用 woff2 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.woff') format('woff'), /* 兼容性较好的 woff */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.ttf') format('truetype'), /* ttf 作为备选 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.otf') format('opentype'); /* otf 作为最后选项 */ | |||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||||
| } | |||||
| @font-face { | @font-face { | ||||
| font-family: 'DingTalk-JinBuTi'; | font-family: 'DingTalk-JinBuTi'; | ||||
| src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | ||||
| @@ -2,6 +2,7 @@ import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { Flex, Typography } from 'antd'; | import { Flex, Typography } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CodeConfigItemProps = { | type CodeConfigItemProps = { | ||||
| @@ -10,6 +11,7 @@ type CodeConfigItemProps = { | |||||
| }; | }; | ||||
| function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | ||||
| const [isEllipsis, setIsEllipsis] = useState(false); | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | ||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | ||||
| @@ -32,11 +34,20 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| </Flex> | </Flex> | ||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| className={styles['code-config-item__url']} | className={styles['code-config-item__url']} | ||||
| ellipsis={{ rows: 2, tooltip: item.git_url }} | |||||
| ellipsis={{ | |||||
| rows: 2, | |||||
| tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip | |||||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), | |||||
| }} | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__branch']} | |||||
| ellipsis={{ tooltip: item.git_branch }} | |||||
| > | |||||
| {item.git_branch} | |||||
| </Typography.Paragraph> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -48,12 +48,9 @@ function IframePage({ type, className, style }: IframePageProps) { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const requestIframeUrl = async () => { | const requestIframeUrl = async () => { | ||||
| setLoading(true); | |||||
| const [res] = await to(getRequestAPI(type)()); | const [res] = await to(getRequestAPI(type)()); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setIframeUrl(res.data); | setIframeUrl(res.data); | ||||
| } else { | |||||
| setLoading(false); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -6,7 +6,7 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| ResourceSelectorResponse, | |||||
| type ResourceSelectorResponse, | |||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| } from '@/components/ResourceSelectorModal'; | } from '@/components/ResourceSelectorModal'; | ||||
| @@ -28,7 +28,6 @@ function AutoMLInstance() { | |||||
| const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | ||||
| const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | ||||
| const params = useParams(); | const params = useParams(); | ||||
| // const autoMLId = safeInvoke(Number)(params.autoMLId); | |||||
| const instanceId = safeInvoke(Number)(params.id); | const instanceId = safeInvoke(Number)(params.id); | ||||
| const evtSourceRef = useRef<EventSource | null>(null); | const evtSourceRef = useRef<EventSource | null>(null); | ||||
| @@ -187,6 +186,7 @@ function AutoMLInstance() { | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | icon: <KFIcon type="icon-Trialliebiao" />, | ||||
| children: ( | children: ( | ||||
| <ExperimentHistory | <ExperimentHistory | ||||
| calcMetrics={autoMLInfo?.scoring_functions} | |||||
| fileUrl={instanceInfo?.run_history_path} | fileUrl={instanceInfo?.run_history_path} | ||||
| isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | ||||
| /> | /> | ||||
| @@ -8,8 +8,9 @@ import TrialStatusCell from '../TrialStatusCell'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentHistoryProps = { | type ExperimentHistoryProps = { | ||||
| fileUrl?: string; | |||||
| isClassification: boolean; | |||||
| calcMetrics?: string; // 计算指标 | |||||
| fileUrl?: string; // 文件url | |||||
| isClassification: boolean; // 是否是分类 | |||||
| }; | }; | ||||
| type TableData = { | type TableData = { | ||||
| @@ -22,7 +23,7 @@ type TableData = { | |||||
| althorithm?: string; | althorithm?: string; | ||||
| }; | }; | ||||
| function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { | |||||
| function ExperimentHistory({ calcMetrics, fileUrl, isClassification }: ExperimentHistoryProps) { | |||||
| const [tableData, setTableData] = useState<TableData[]>([]); | const [tableData, setTableData] = useState<TableData[]>([]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取实验运行历史记录 | // 获取实验运行历史记录 | ||||
| @@ -33,7 +34,7 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| const list: TableData[] = data.map((item) => { | const list: TableData[] = data.map((item) => { | ||||
| return { | return { | ||||
| id: item[0]?.[0], | id: item[0]?.[0], | ||||
| accuracy: item[1]?.[5]?.accuracy, | |||||
| accuracy: calcMetrics ? item[1]?.[5]?.[calcMetrics] : undefined, | |||||
| duration: item[1]?.[5]?.duration, | duration: item[1]?.[5]?.duration, | ||||
| train_loss: item[1]?.[5]?.train_loss, | train_loss: item[1]?.[5]?.train_loss, | ||||
| status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], | ||||
| @@ -64,12 +65,6 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| width: 80, | width: 80, | ||||
| render: tableCellRender(false), | render: tableCellRender(false), | ||||
| }, | }, | ||||
| { | |||||
| title: '准确率', | |||||
| dataIndex: 'accuracy', | |||||
| key: 'accuracy', | |||||
| render: tableCellRender(true), | |||||
| }, | |||||
| { | { | ||||
| title: '耗时', | title: '耗时', | ||||
| dataIndex: 'duration', | dataIndex: 'duration', | ||||
| @@ -103,6 +98,15 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| if (calcMetrics) { | |||||
| columns.splice(0, 0, { | |||||
| title: `指标:${calcMetrics}`, | |||||
| dataIndex: 'accuracy', | |||||
| key: 'accuracy', | |||||
| render: tableCellRender(true), | |||||
| }); | |||||
| } | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-history']}> | <div className={styles['experiment-history']}> | ||||
| <div className={styles['experiment-history__content']}> | <div className={styles['experiment-history__content']}> | ||||
| @@ -107,7 +107,7 @@ function ExperimentInstanceComponent({ | |||||
| }; | }; | ||||
| if (!experimentInsList || experimentInsList.length === 0) { | if (!experimentInsList || experimentInsList.length === 0) { | ||||
| return <div style={{ textAlign: 'center' }}>暂无实验实例</div>; | |||||
| return <div style={{ textAlign: 'center' }}>暂无数据</div>; | |||||
| } | } | ||||
| return ( | return ( | ||||
| @@ -188,6 +188,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| if (expanded) { | if (expanded) { | ||||
| setExpandedRowKeys([record.id]); | setExpandedRowKeys([record.id]); | ||||
| getExperimentInsList(record.id, 0); | getExperimentInsList(record.id, 0); | ||||
| refreshExperimentList(); | |||||
| } else { | } else { | ||||
| setExpandedRowKeys([]); | setExpandedRowKeys([]); | ||||
| } | } | ||||
| @@ -70,7 +70,12 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__branch']} | |||||
| ellipsis={{ tooltip: item.git_branch }} | |||||
| > | |||||
| {item.git_branch} | |||||
| </Typography.Paragraph> | |||||
| </div> | </div> | ||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['code-config-item__user']}> | <div className={styles['code-config-item__user']}> | ||||
| @@ -159,27 +159,42 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {/* <Form.Item label="集群版本" name="available_cluster"> | |||||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | |||||
| </Form.Item> */} | |||||
| <Form.Item | <Form.Item | ||||
| label="数据集简介" | |||||
| label="数据集描述" | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入数据集简介', | |||||
| message: '请输入数据集描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入数据集简介" | |||||
| placeholder="请输入数据集描述" | |||||
| showCount | showCount | ||||
| maxLength={200} | maxLength={200} | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="version_desc" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={200} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -143,23 +143,41 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型简介" | |||||
| label="模型描述" | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入模型简介', | |||||
| message: '请输入模型描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入模型简介" | |||||
| placeholder="请输入模型描述" | |||||
| maxLength={200} | maxLength={200} | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="版本描述" | |||||
| name="version_desc" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入版本描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入版本描述" | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| maxLength={200} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -21,7 +21,7 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ | |||||
| value: data.name, | value: data.name, | ||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | |||||
| label: '数据集版本', | |||||
| value: data.version, | value: data.version, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -64,7 +64,7 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [ | |||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | |||||
| label: '模型版本', | |||||
| value: data.version, | value: data.version, | ||||
| ellipsis: true, | ellipsis: true, | ||||
| }, | }, | ||||
| @@ -51,8 +51,8 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) { | |||||
| message: '请输入镜像名称', | message: '请输入镜像名称', | ||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-z0-9/_-]*$/, | |||||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||||
| pattern: /^[a-z0-9/._-]*$/, | |||||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| @@ -179,11 +179,12 @@ function ExperimentText() { | |||||
| if (!statusNode) { | if (!statusNode) { | ||||
| return; | return; | ||||
| } | } | ||||
| const { finishedAt, startedAt, phase, id } = statusNode; | |||||
| const { finishedAt, startedAt, phase, id, message } = statusNode; | |||||
| workflowNode.experimentStartTime = startedAt; | workflowNode.experimentStartTime = startedAt; | ||||
| workflowNode.experimentEndTime = finishedAt; | workflowNode.experimentEndTime = finishedAt; | ||||
| workflowNode.experimentStatus = phase; | workflowNode.experimentStatus = phase; | ||||
| workflowNode.workflowId = id; | workflowNode.workflowId = id; | ||||
| workflowNode.message = message; | |||||
| workflowNode.img = phase | workflowNode.img = phase | ||||
| ? `${workflowNode.imgName}-${phase}.png` | ? `${workflowNode.imgName}-${phase}.png` | ||||
| : `${workflowNode.imgName}.png`; | : `${workflowNode.imgName}.png`; | ||||
| @@ -3,7 +3,7 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Form, Input, Radio, Select, type FormRule } from 'antd'; | |||||
| import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | |||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -63,13 +63,14 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||||
| }; | }; | ||||
| // 根据参数设置 label | // 根据参数设置 label | ||||
| export const getParamType = (param: PipelineGlobalParam): string => { | |||||
| export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | |||||
| const paramTypes: Readonly<Record<number, string>> = { | const paramTypes: Readonly<Record<number, string>> = { | ||||
| 1: '字符串', | 1: '字符串', | ||||
| 2: '整型', | 2: '整型', | ||||
| 3: '布尔类型', | 3: '布尔类型', | ||||
| }; | }; | ||||
| return param.param_name + `(${paramTypes[param.param_type]})`; | |||||
| const label = param.param_name + `(${paramTypes[param.param_type]})`; | |||||
| return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | |||||
| }; | }; | ||||
| function AddExperimentModal({ | function AddExperimentModal({ | ||||
| @@ -99,8 +100,8 @@ function AddExperimentModal({ | |||||
| }; | }; | ||||
| const paramLayout = { | const paramLayout = { | ||||
| labelCol: { span: 8 }, | |||||
| wrapperCol: { span: 16 }, | |||||
| labelCol: { span: 6 }, | |||||
| wrapperCol: { span: 18 }, | |||||
| }; | }; | ||||
| // 除了流水线选择发生变化 | // 除了流水线选择发生变化 | ||||
| @@ -157,7 +158,6 @@ function AddExperimentModal({ | |||||
| form={form} | form={form} | ||||
| {...layout} | {...layout} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| labelWrap | |||||
| > | > | ||||
| <Form.Item | <Form.Item | ||||
| label="实验名称" | label="实验名称" | ||||
| @@ -215,9 +215,9 @@ function AddExperimentModal({ | |||||
| {...restField} | {...restField} | ||||
| {...paramLayout} | {...paramLayout} | ||||
| key={key} | key={key} | ||||
| label={getParamType(globalParam[name])} | |||||
| label={getParamLabel(globalParam[name])} | |||||
| name={[name, 'param_value']} | name={[name, 'param_value']} | ||||
| rules={getParamRules(globalParam[name]['param_type'])} | |||||
| rules={getParamRules(globalParam[name]['param_type'], true)} | |||||
| > | > | ||||
| {getParamComponent( | {getParamComponent( | ||||
| globalParam[name]['param_type'], | globalParam[name]['param_type'], | ||||
| @@ -13,7 +13,6 @@ | |||||
| } | } | ||||
| &__tabs { | &__tabs { | ||||
| height: calc(100% - 169px); | |||||
| :global { | :global { | ||||
| .ant-tabs-nav { | .ant-tabs-nav { | ||||
| padding-left: 24px; | padding-left: 24px; | ||||
| @@ -35,7 +34,7 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| margin-bottom: 15px; | margin-bottom: 15px; | ||||
| padding-left: 24px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 15px; | font-size: 15px; | ||||
| } | } | ||||
| @@ -3,7 +3,7 @@ import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | import { PipelineNodeModelSerialize } from '@/types'; | ||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | ||||
| import { Drawer, Tabs } from 'antd'; | |||||
| import { Drawer, Tabs, Typography } from 'antd'; | |||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import ExperimentParameter from '../ExperimentParameter'; | import ExperimentParameter from '../ExperimentParameter'; | ||||
| import ExperimentResult from '../ExperimentResult'; | import ExperimentResult from '../ExperimentResult'; | ||||
| @@ -129,6 +129,14 @@ const ExperimentDrawer = ({ | |||||
| '--' | '--' | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| {instanceNodeData.message && ( | |||||
| <div className={styles['experiment-drawer__info']}> | |||||
| <div style={{ flex: 'none' }}>消息:</div> | |||||
| <Typography.Text ellipsis={{ tooltip: instanceNodeData.message }}> | |||||
| {instanceNodeData.message ?? '--'} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| )} | |||||
| <div className={styles['experiment-drawer__info']}> | <div className={styles['experiment-drawer__info']}> | ||||
| 启动时间:{formatDate(instanceNodeStartTime)} | 启动时间:{formatDate(instanceNodeStartTime)} | ||||
| </div> | </div> | ||||
| @@ -137,7 +145,14 @@ const ExperimentDrawer = ({ | |||||
| {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} | {elapsedTime(instanceNodeStartTime, instanceNodeEndTime)} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} /> | |||||
| <Tabs | |||||
| defaultActiveKey="1" | |||||
| items={items} | |||||
| className={styles['experiment-drawer__tabs']} | |||||
| style={{ | |||||
| height: instanceNodeData.message ? 'calc(100% - 169px - 39px)' : 'calc(100% - 169px)', | |||||
| }} | |||||
| /> | |||||
| </Drawer> | </Drawer> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,31 +1,14 @@ | |||||
| .params_container { | |||||
| max-height: 230px; | |||||
| padding: 15px 15px 0; | |||||
| .params-container { | |||||
| max-height: calc(100vh - 300px); | |||||
| padding: 24px 24px 0; | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| &_line { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 15px; | |||||
| &_label { | |||||
| width: 180px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &_value { | |||||
| flex: 1; | |||||
| width: 100px; | |||||
| margin-left: 15px; | |||||
| padding: 10px 20px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 20px; | |||||
| background: #f6f6f6; | |||||
| border: 1px solid #e0e0e1; | |||||
| border-radius: 4px; | |||||
| } | |||||
| .params-empty { | |||||
| :global { | |||||
| .kf-empty__image { | |||||
| width: 300px; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,9 +4,11 @@ | |||||
| * @Description: 查看实验使用的参数 | * @Description: 查看实验使用的参数 | ||||
| */ | */ | ||||
| import parameterImg from '@/assets/img/modal-parameter.png'; | import parameterImg from '@/assets/img/modal-parameter.png'; | ||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { getParamType } from '../AddExperimentModal'; | |||||
| import { Form } from 'antd'; | |||||
| import { getParamComponent, getParamLabel } from '../AddExperimentModal'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ParamsModalProps = { | type ParamsModalProps = { | ||||
| @@ -26,14 +28,44 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| cancelButtonProps={{ style: { display: 'none' } }} | cancelButtonProps={{ style: { display: 'none' } }} | ||||
| width={825} | width={825} | ||||
| > | > | ||||
| <div className={styles.params_container}> | |||||
| {globalParam?.map((item) => ( | |||||
| <div key={item.param_name} className={styles.params_container_line}> | |||||
| <span className={styles.params_container_line_label}>{getParamType(item)}</span> | |||||
| <span className={styles.params_container_line_value}>{item.param_value}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| {Array.isArray(globalParam) && globalParam.length > 0 ? ( | |||||
| <div className={styles['params-container']}> | |||||
| <Form | |||||
| name="view_params_form" | |||||
| labelCol={{ span: 6 }} | |||||
| wrapperCol={{ span: 18 }} | |||||
| initialValues={{ global_param: globalParam }} | |||||
| labelAlign="left" | |||||
| disabled | |||||
| > | |||||
| <Form.List name="global_param"> | |||||
| {(fields) => | |||||
| fields.map(({ key, name, ...restField }) => ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| key={key} | |||||
| name={[name, 'param_value']} | |||||
| label={getParamLabel(globalParam[name])} | |||||
| > | |||||
| {getParamComponent( | |||||
| globalParam[name]['param_type'], | |||||
| globalParam[name]['is_sensitive'], | |||||
| )} | |||||
| </Form.Item> | |||||
| )) | |||||
| } | |||||
| </Form.List> | |||||
| </Form> | |||||
| </div> | |||||
| ) : ( | |||||
| <KFEmpty | |||||
| className={styles['params-empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content="该流水线没有设置全局参数" | |||||
| hasFooter={false} | |||||
| /> | |||||
| )} | |||||
| </KFModal> | </KFModal> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -206,6 +206,7 @@ function Experiment() { | |||||
| setExpandedRowKeys(null); | setExpandedRowKeys(null); | ||||
| } else { | } else { | ||||
| getQueryByExperiment(record.id, 0); | getQueryByExperiment(record.id, 0); | ||||
| refreshExperimentList(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -44,7 +44,7 @@ const mirrorRadioItems: KFRadioItem[] = [ | |||||
| function MirrorCreate() { | function MirrorCreate() { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [nameDisabled, setNameDisabled] = useState(false); | |||||
| const [isAddVersion, setIsAddVersion] = useState(false); // 是制作镜像还是新增镜像版本 | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -60,7 +60,7 @@ function MirrorCreate() { | |||||
| const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | ||||
| if (name) { | if (name) { | ||||
| form.setFieldValue('name', name); | form.setFieldValue('name', name); | ||||
| setNameDisabled(true); | |||||
| setIsAddVersion(true); | |||||
| } | } | ||||
| return () => { | return () => { | ||||
| SessionStorage.removeItem(SessionStorage.mirrorNameKey); | SessionStorage.removeItem(SessionStorage.mirrorNameKey); | ||||
| @@ -70,32 +70,36 @@ function MirrorCreate() { | |||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| const createPublicMirror = async (formData: FormData) => { | const createPublicMirror = async (formData: FormData) => { | ||||
| const upload_type = formData['upload_type']; | const upload_type = formData['upload_type']; | ||||
| let params; | |||||
| if (upload_type === CommonTabKeys.Public) { | if (upload_type === CommonTabKeys.Public) { | ||||
| params = { | |||||
| const params = { | |||||
| ...omit(formData, ['upload_type']), | ...omit(formData, ['upload_type']), | ||||
| upload_type: 0, | upload_type: 0, | ||||
| image_type: 0, | image_type: 0, | ||||
| }; | }; | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| } else { | } else { | ||||
| const fileList = formData['fileList'] ?? []; | const fileList = formData['fileList'] ?? []; | ||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const file = fileList[0]; | const file = fileList[0]; | ||||
| params = { | |||||
| const params = { | |||||
| ...omit(formData, ['fileList', 'upload_type']), | ...omit(formData, ['fileList', 'upload_type']), | ||||
| path: file.response.data.url, | path: file.response.data.url, | ||||
| file_size: file.response.data.fileSize, | file_size: file.response.data.fileSize, | ||||
| upload_type: 1, | upload_type: 1, | ||||
| image_type: 0, | image_type: 0, | ||||
| }; | }; | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| const [res] = await to(createMirrorReq(params)); | |||||
| if (res) { | |||||
| message.success('创建成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| }; | }; | ||||
| // 提交 | // 提交 | ||||
| @@ -118,14 +122,16 @@ function MirrorCreate() { | |||||
| return true; | return true; | ||||
| }; | }; | ||||
| const descTitle = isAddVersion ? '版本描述' : '镜像描述'; | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-create']}> | <div className={styles['mirror-create']}> | ||||
| <PageTitle title="创建镜像"></PageTitle> | |||||
| <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> | |||||
| <div className={styles['mirror-create__content']}> | <div className={styles['mirror-create__content']}> | ||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| name="mirror-create" | name="mirror-create" | ||||
| labelCol={{ flex: '130px' }} | |||||
| labelCol={{ flex: '135px' }} | |||||
| wrapperCol={{ flex: 1 }} | wrapperCol={{ flex: 1 }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| @@ -142,7 +148,7 @@ function MirrorCreate() { | |||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像名称及Tag" | |||||
| label="镜像名称和版本" | |||||
| name="name" | name="name" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| @@ -150,15 +156,15 @@ function MirrorCreate() { | |||||
| message: '请输入镜像名称', | message: '请输入镜像名称', | ||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-z0-9/_-]*$/, | |||||
| message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)', | |||||
| pattern: /^[a-z0-9/._-]*$/, | |||||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input | <Input | ||||
| placeholder="请输入镜像名称" | placeholder="请输入镜像名称" | ||||
| maxLength={64} | maxLength={64} | ||||
| disabled={nameDisabled} | |||||
| disabled={isAddVersion} | |||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| @@ -174,33 +180,33 @@ function MirrorCreate() { | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像Tag', | |||||
| message: '请输入镜像版本', | |||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-zA-Z0-9._-]+$/, | pattern: /^[a-zA-Z0-9._-]+$/, | ||||
| message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| message: '镜像版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={20}> | <Col span={20}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像描述" | |||||
| label={descTitle} | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像描述', | |||||
| message: `请输入${descTitle}`, | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| placeholder="请输入镜像描述,最长128字符" | |||||
| placeholder={`请输入${descTitle}`} | |||||
| maxLength={128} | maxLength={128} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| @@ -303,7 +309,7 @@ function MirrorCreate() { | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | ||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| 创建镜像 | |||||
| 确定 | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| @@ -155,7 +155,7 @@ function MirrorInfo() { | |||||
| }; | }; | ||||
| const createMirrorVersion = () => { | const createMirrorVersion = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`add-version`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| @@ -174,20 +174,27 @@ function MirrorInfo() { | |||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| render: tableCellRender(), | |||||
| width: '25%', | |||||
| render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }), | |||||
| }, | |||||
| { | |||||
| title: '版本描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: tableCellRender(true), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| key: 'status', | key: 'status', | ||||
| width: 150, | |||||
| width: 100, | |||||
| render: MirrorStatusCell, | render: MirrorStatusCell, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像大小', | title: '镜像大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: 150, | |||||
| width: 120, | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -200,7 +207,7 @@ function MirrorInfo() { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: 150, | |||||
| width: 120, | |||||
| key: 'operation', | key: 'operation', | ||||
| hidden: isPublic, | hidden: isPublic, | ||||
| render: (_: any, record: MirrorVersionData) => ( | render: (_: any, record: MirrorVersionData) => ( | ||||
| @@ -128,7 +128,7 @@ function MirrorList() { | |||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: MirrorData) => { | const toDetail = (record: MirrorData) => { | ||||
| navigate(`/dataset/mirror/info/${record.id}`); | |||||
| navigate(`info/${record.id}`); | |||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| pagination, | pagination, | ||||
| @@ -149,7 +149,7 @@ function MirrorList() { | |||||
| // 创建镜像 | // 创建镜像 | ||||
| const createMirror = () => { | const createMirror = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`create`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| @@ -262,7 +262,7 @@ function MirrorList() { | |||||
| onClick={createMirror} | onClick={createMirror} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 制作镜像 | |||||
| 创建镜像 | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| <Button | <Button | ||||
| @@ -18,6 +18,7 @@ | |||||
| &__table { | &__table { | ||||
| flex: 1; | flex: 1; | ||||
| min-height: 0; | |||||
| margin-top: 24px; | margin-top: 24px; | ||||
| } | } | ||||
| } | } | ||||
| @@ -133,17 +133,18 @@ function ServiceInfo() { | |||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | // 如果是一页的唯一数据,删除时,请求第一页的数据 | ||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| // 否则直接刷新这一页的数据,避免回到第一页 | |||||
| if (tableData.length === 1) { | |||||
| setPagination((prev) => ({ | setPagination((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| current: 1, | current: 1, | ||||
| })); | })); | ||||
| } else { | } else { | ||||
| getServiceInfo(); | |||||
| getServiceVersions(); | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| })); | |||||
| } | } | ||||
| getServiceInfo(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -432,7 +433,7 @@ function ServiceInfo() { | |||||
| onClick={() => createServiceVersion(ServiceOperationType.Create)} | onClick={() => createServiceVersion(ServiceOperationType.Create)} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 新增版本 | |||||
| 新增服务版本 | |||||
| </Button> | </Button> | ||||
| <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | ||||
| 版本对比 | 版本对比 | ||||
| @@ -136,31 +136,35 @@ const GlobalParamsDrawer = forwardRef( | |||||
| cur.global_param?.[name]?.param_type | cur.global_param?.[name]?.param_type | ||||
| } | } | ||||
| > | > | ||||
| {({ getFieldValue }) => ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_value']} | |||||
| label="值" | |||||
| rules={getParamRules( | |||||
| getFieldValue(['global_param', name, 'param_type']), | |||||
| true, | |||||
| )} | |||||
| > | |||||
| {getParamComponent(getFieldValue(['global_param', name, 'param_type']))} | |||||
| </Form.Item> | |||||
| )} | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'is_sensitive']} | |||||
| label="脱敏显示" | |||||
| rules={[{ required: true, message: '请选择' }]} | |||||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>是</Radio> | |||||
| <Radio value={0}>否</Radio> | |||||
| </Radio.Group> | |||||
| {({ getFieldValue }) => { | |||||
| const type = getFieldValue(['global_param', name, 'param_type']); | |||||
| return ( | |||||
| <> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_value']} | |||||
| label="值" | |||||
| rules={getParamRules(type, true)} | |||||
| > | |||||
| {getParamComponent(type)} | |||||
| </Form.Item> | |||||
| {type !== 3 && ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'is_sensitive']} | |||||
| label="脱敏显示" | |||||
| rules={[{ required: true, message: '请选择' }]} | |||||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>是</Radio> | |||||
| <Radio value={0}>否</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | </Form.Item> | ||||
| <Tooltip title="删除参数"> | <Tooltip title="删除参数"> | ||||
| <Button | <Button | ||||
| @@ -216,6 +216,11 @@ function PointsDetail() { | |||||
| label: '进行中', | label: '进行中', | ||||
| color: themes['primaryColor'], | color: themes['primaryColor'], | ||||
| }, | }, | ||||
| { | |||||
| value: 0, | |||||
| label: '准备中', | |||||
| color: themes['pendingColor'], | |||||
| }, | |||||
| ]), | ]), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -1,12 +1,18 @@ | |||||
| .total-statistics { | .total-statistics { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: center; | |||||
| height: 140px; | height: 140px; | ||||
| padding: 0 16px; | padding: 0 16px; | ||||
| // 媒体查询 | |||||
| @media screen and (max-width: 1600px) { | |||||
| flex: 1 1 content; | |||||
| } | |||||
| &__icon { | &__icon { | ||||
| width: 63px; | width: 63px; | ||||
| margin-right: 15px; | |||||
| margin-right: 16px; | |||||
| } | } | ||||
| &__title { | &__title { | ||||
| @@ -8,7 +8,6 @@ | |||||
| &__overview { | &__overview { | ||||
| flex: 1; | flex: 1; | ||||
| gap: 15px; | gap: 15px; | ||||
| margin-bottom: 16px; | |||||
| padding: 20px 30px; | padding: 20px 30px; | ||||
| background-color: white; | background-color: white; | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| @@ -22,7 +21,6 @@ | |||||
| &__content { | &__content { | ||||
| display: flex; | display: flex; | ||||
| flex-wrap: wrap; | flex-wrap: wrap; | ||||
| gap: 15px; | gap: 15px; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -35,6 +33,11 @@ | |||||
| rgba(22, 100, 255, 0.02) 58.35% | rgba(22, 100, 255, 0.02) 58.35% | ||||
| ); | ); | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| // 媒体查询 | |||||
| @media screen and (max-width: 1600px) { | |||||
| flex: 1 1 content; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -44,6 +47,7 @@ | |||||
| gap: 15px; | gap: 15px; | ||||
| align-items: flex-start; | align-items: flex-start; | ||||
| width: 100%; | width: 100%; | ||||
| margin-top: 16px; | |||||
| } | } | ||||
| &__user { | &__user { | ||||
| @@ -46,7 +46,7 @@ function Workspace() { | |||||
| return ( | return ( | ||||
| <div className={styles.workspace}> | <div className={styles.workspace}> | ||||
| <WorkspaceIntro></WorkspaceIntro> | <WorkspaceIntro></WorkspaceIntro> | ||||
| <Flex gap={'0 15px'} wrap> | |||||
| <Flex gap={'15px'} wrap> | |||||
| <div className={styles['workspace__overview']}> | <div className={styles['workspace__overview']}> | ||||
| <div className={styles['workspace__overview__title']}>运行概览</div> | <div className={styles['workspace__overview__title']}>运行概览</div> | ||||
| <div className={styles['workspace__overview__content']}> | <div className={styles['workspace__overview__content']}> | ||||
| @@ -59,7 +59,7 @@ function Workspace() { | |||||
| <Divider | <Divider | ||||
| type="vertical" | type="vertical" | ||||
| dashed | dashed | ||||
| style={{ color: '1px dashed rgba(96, 107, 122, 0.19)', height: 68 }} | |||||
| style={{ color: '1px dashed rgba(96, 107, 122, 0.19)', height: 68, margin: 0 }} | |||||
| /> | /> | ||||
| <TotalStatistics | <TotalStatistics | ||||
| icon={require('@/assets/img/workspace-experiment.png')} | icon={require('@/assets/img/workspace-experiment.png')} | ||||
| @@ -71,6 +71,7 @@ export type PipelineNodeModel = { | |||||
| component_label: string; | component_label: string; | ||||
| icon_path: string; | icon_path: string; | ||||
| workflowId?: string; | workflowId?: string; | ||||
| message?: string; | |||||
| }; | }; | ||||
| // 流水线节点模型数据 | // 流水线节点模型数据 | ||||
| @@ -41,7 +41,10 @@ export const elapsedTime = (begin?: string | null, end?: string | null): string | |||||
| if (hours !== 0) { | if (hours !== 0) { | ||||
| return `${hours}小时${minutes}分`; | return `${hours}小时${minutes}分`; | ||||
| } | } | ||||
| return `${minutes}分${seconds}秒`; | |||||
| if (minutes !== 0) { | |||||
| return `${minutes}分${seconds}秒`; | |||||
| } | |||||
| return `${seconds}秒`; | |||||
| }; | }; | ||||
| /** | /** | ||||
| @@ -25,6 +25,7 @@ export type TableCellValueOptions<T> = { | |||||
| dateFormat?: string; // 类型为 Date 时有效 | dateFormat?: string; // 类型为 Date 时有效 | ||||
| onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | ||||
| format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | ||||
| copyable?: boolean; // 省略时是否可以复制 | |||||
| }; | }; | ||||
| type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | ||||
| @@ -93,17 +94,17 @@ function tableCellRender<T>( | |||||
| } | } | ||||
| if (ellipsis === 'auto' && text) { | if (ellipsis === 'auto' && text) { | ||||
| return renderCell(type, text, 'auto', record, options?.onClick); | |||||
| return renderCell(type, text, 'auto', record, options); | |||||
| } else if (ellipsis && text) { | } else if (ellipsis && text) { | ||||
| const tooltipProps = typeof ellipsis === 'object' ? ellipsis : {}; | const tooltipProps = typeof ellipsis === 'object' ? ellipsis : {}; | ||||
| const { overlayStyle, ...rest } = tooltipProps; | const { overlayStyle, ...rest } = tooltipProps; | ||||
| return ( | return ( | ||||
| <Tooltip {...rest} overlayStyle={{ maxWidth: 400, ...overlayStyle }} title={text}> | <Tooltip {...rest} overlayStyle={{ maxWidth: 400, ...overlayStyle }} title={text}> | ||||
| {renderCell(type, text, true, record, options?.onClick)} | |||||
| {renderCell(type, text, true, record, options)} | |||||
| </Tooltip> | </Tooltip> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| return renderCell(type, text, false, record, options?.onClick); | |||||
| return renderCell(type, text, false, record, options); | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| @@ -113,31 +114,38 @@ function renderCell<T>( | |||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| ellipsis: boolean | 'auto', | ellipsis: boolean | 'auto', | ||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | ) { | ||||
| return type === TableCellValueType.Link | return type === TableCellValueType.Link | ||||
| ? renderLink(text, ellipsis, record, onClick) | |||||
| : renderText(text, ellipsis); | |||||
| ? renderLink(text, ellipsis, record, options) | |||||
| : renderText(text, ellipsis, options); | |||||
| } | } | ||||
| function renderLink<T>( | function renderLink<T>( | ||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| ellipsis: boolean | 'auto', | ellipsis: boolean | 'auto', | ||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | ) { | ||||
| const { onClick } = options ?? {}; | |||||
| return ( | return ( | ||||
| <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | ||||
| {renderText(text, ellipsis)} | |||||
| {renderText(text, ellipsis, options)} | |||||
| </a> | </a> | ||||
| ); | ); | ||||
| } | } | ||||
| function renderText(text: any | undefined | null, ellipsis: boolean | 'auto') { | |||||
| function renderText<T>( | |||||
| text: any | undefined | null, | |||||
| ellipsis: boolean | 'auto', | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | |||||
| const { copyable } = options ?? {}; | |||||
| if (ellipsis === 'auto') { | if (ellipsis === 'auto') { | ||||
| return ( | return ( | ||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| style={{ marginBottom: 0 }} | style={{ marginBottom: 0 }} | ||||
| copyable={copyable} | |||||
| ellipsis={{ | ellipsis={{ | ||||
| tooltip: { | tooltip: { | ||||
| title: text, | title: text, | ||||