| @@ -22,7 +22,7 @@ enum TabKeys { | |||||
| History = 'history', | History = 'history', | ||||
| } | } | ||||
| const NodePrefix = 'auto-hpo'; | |||||
| const NodePrefix = 'auto-ml'; | |||||
| function AutoMLInstance() { | function AutoMLInstance() { | ||||
| const [activeTab, setActiveTab] = useState<string>(TabKeys.Params); | const [activeTab, setActiveTab] = useState<string>(TabKeys.Params); | ||||
| @@ -26,7 +26,7 @@ | |||||
| &__text { | &__text { | ||||
| font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; | font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; | ||||
| white-space: pre-wrap; | |||||
| white-space: pre; | |||||
| } | } | ||||
| &__images { | &__images { | ||||
| @@ -17,6 +17,7 @@ import styles from './index.less'; | |||||
| export type LogGroupProps = ExperimentLog & { | export type LogGroupProps = ExperimentLog & { | ||||
| status?: ExperimentStatus; // 实验状态 | status?: ExperimentStatus; // 实验状态 | ||||
| listId: string; | |||||
| }; | }; | ||||
| type Log = { | type Log = { | ||||
| @@ -25,25 +26,13 @@ type Log = { | |||||
| pod_name: string; // pod名称 | pod_name: string; // pod名称 | ||||
| }; | }; | ||||
| // 滚动到底部 | |||||
| const scrollToBottom = (smooth: boolean = true) => { | |||||
| const element = document.getElementById('log-list'); | |||||
| if (element) { | |||||
| const optons: ScrollToOptions = { | |||||
| top: element.scrollHeight, | |||||
| behavior: smooth ? 'smooth' : 'instant', | |||||
| }; | |||||
| element.scrollTo(optons); | |||||
| } | |||||
| }; | |||||
| function LogGroup({ | function LogGroup({ | ||||
| log_type = 'normal', | log_type = 'normal', | ||||
| pod_name = '', | pod_name = '', | ||||
| log_content = '', | log_content = '', | ||||
| start_time, | start_time, | ||||
| status, | status, | ||||
| listId, | |||||
| }: LogGroupProps) { | }: LogGroupProps) { | ||||
| const [collapse, setCollapse] = useState(true); | const [collapse, setCollapse] = useState(true); | ||||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | ||||
| @@ -135,7 +124,7 @@ function LogGroup({ | |||||
| const setupSockect = () => { | const setupSockect = () => { | ||||
| let { host } = location; | let { host } = location; | ||||
| if (process.env.NODE_ENV === 'development') { | if (process.env.NODE_ENV === 'development') { | ||||
| host = '172.20.32.197:31213'; | |||||
| host = '172.20.32.181:31213'; | |||||
| } | } | ||||
| const socket = new WebSocket( | const socket = new WebSocket( | ||||
| `ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`, | `ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`, | ||||
| @@ -210,6 +199,18 @@ function LogGroup({ | |||||
| } | } | ||||
| }; | }; | ||||
| // 滚动到底部 | |||||
| const scrollToBottom = (smooth: boolean = true) => { | |||||
| const element = document.getElementById(listId); | |||||
| if (element) { | |||||
| const optons: ScrollToOptions = { | |||||
| top: element.scrollHeight, | |||||
| behavior: smooth ? 'smooth' : 'instant', | |||||
| }; | |||||
| element.scrollTo(optons); | |||||
| } | |||||
| }; | |||||
| const showLog = (log_type === 'resource' && !collapse) || log_type === 'normal'; | const showLog = (log_type === 'resource' && !collapse) || log_type === 'normal'; | ||||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | const logText = log_content + logList.map((v) => v.log_content).join(''); | ||||
| const showMoreBtn = | const showMoreBtn = | ||||
| @@ -14,6 +14,7 @@ export type ExperimentLog = { | |||||
| }; | }; | ||||
| type LogListProps = { | type LogListProps = { | ||||
| idPrefix?: string; // 当一个页面有多个日志组件时,使用这个变量作为唯一性标识 | |||||
| instanceName: string; // 实验实例 name | instanceName: string; // 实验实例 name | ||||
| instanceNamespace: string; // 实验实例 namespace | instanceNamespace: string; // 实验实例 namespace | ||||
| pipelineNodeId: string; // 流水线节点 id | pipelineNodeId: string; // 流水线节点 id | ||||
| @@ -23,6 +24,7 @@ type LogListProps = { | |||||
| }; | }; | ||||
| function LogList({ | function LogList({ | ||||
| idPrefix, | |||||
| instanceName, | instanceName, | ||||
| instanceNamespace, | instanceNamespace, | ||||
| pipelineNodeId, | pipelineNodeId, | ||||
| @@ -86,10 +88,15 @@ function LogList({ | |||||
| } | } | ||||
| }; | }; | ||||
| // 当一个页面有多个日志组件时,使用这个变量作为唯一性标识 | |||||
| const listId = idPrefix ? `${idPrefix}-log-list` : 'log-list'; | |||||
| return ( | return ( | ||||
| <div className={styles['log-list']} id="log-list"> | |||||
| <div className={styles['log-list']} id={listId}> | |||||
| {logList.length > 0 ? ( | {logList.length > 0 ? ( | ||||
| logList.map((v) => <LogGroup key={v.pod_name} {...v} status={instanceNodeStatus} />) | |||||
| logList.map((v) => ( | |||||
| <LogGroup key={v.pod_name} {...v} listId={listId} status={instanceNodeStatus} /> | |||||
| )) | |||||
| ) : ( | ) : ( | ||||
| <div className={styles['log-list__empty']}>暂无日志</div> | <div className={styles['log-list__empty']}>暂无日志</div> | ||||
| )} | )} | ||||
| @@ -34,7 +34,7 @@ | |||||
| &__log { | &__log { | ||||
| height: calc(100% - 10px); | height: calc(100% - 10px); | ||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 20px calc(@content-padding - 8px); | |||||
| padding: 8px calc(@content-padding - 8px) 20px; | |||||
| overflow-y: visible; | overflow-y: visible; | ||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| @@ -1,6 +1,5 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import LogList from '@/pages/Experiment/components/LogList'; | |||||
| import { getRayInsReq } from '@/services/hyperParameter'; | import { getRayInsReq } from '@/services/hyperParameter'; | ||||
| import { NodeStatus } from '@/types'; | import { NodeStatus } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | import { parseJsonText } from '@/utils'; | ||||
| @@ -10,6 +9,7 @@ import { useParams } from '@umijs/max'; | |||||
| import { Tabs } from 'antd'; | import { Tabs } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import ExperimentHistory from '../components/ExperimentHistory'; | import ExperimentHistory from '../components/ExperimentHistory'; | ||||
| import ExperimentLog from '../components/ExperimentLog'; | |||||
| import ExperimentResult from '../components/ExperimentResult'; | import ExperimentResult from '../components/ExperimentResult'; | ||||
| import HyperParameterBasic from '../components/HyperParameterBasic'; | import HyperParameterBasic from '../components/HyperParameterBasic'; | ||||
| import { HyperParameterData, HyperParameterInstanceData } from '../types'; | import { HyperParameterData, HyperParameterInstanceData } from '../types'; | ||||
| @@ -22,8 +22,6 @@ enum TabKeys { | |||||
| History = 'history', | History = 'history', | ||||
| } | } | ||||
| const NodePrefix = 'auto-hpo'; | |||||
| function HyperParameterInstance() { | function HyperParameterInstance() { | ||||
| const [activeTab, setActiveTab] = useState<string>(TabKeys.Params); | const [activeTab, setActiveTab] = useState<string>(TabKeys.Params); | ||||
| const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined); | const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined); | ||||
| @@ -32,6 +30,7 @@ function HyperParameterInstance() { | |||||
| ); | ); | ||||
| // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态 | // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态 | ||||
| const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | ||||
| const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const instanceId = safeInvoke(Number)(params.id); | const instanceId = safeInvoke(Number)(params.id); | ||||
| const evtSourceRef = useRef<EventSource | null>(null); | const evtSourceRef = useRef<EventSource | null>(null); | ||||
| @@ -72,30 +71,27 @@ function HyperParameterInstance() { | |||||
| setExperimentInfo(paramJson); | setExperimentInfo(paramJson); | ||||
| } | } | ||||
| setInstanceInfo(info); | |||||
| // 这个接口返回的状态有延时,SSE 返回的状态是最新的 | // 这个接口返回的状态有延时,SSE 返回的状态是最新的 | ||||
| // SSE 调用时,不需要解析 node_status, 也不要重新建立 SSE | |||||
| // SSE 调用时,不需要解析 node_status,也不要重新建立 SSE | |||||
| if (isStatusDetermined) { | if (isStatusDetermined) { | ||||
| setInstanceInfo((prev) => ({ | |||||
| ...info, | |||||
| nodeStatus: prev!.nodeStatus, | |||||
| })); | |||||
| return; | return; | ||||
| } | } | ||||
| // 进行节点状态 | // 进行节点状态 | ||||
| const nodeStatusJson = parseJsonText(node_status); | const nodeStatusJson = parseJsonText(node_status); | ||||
| if (nodeStatusJson) { | if (nodeStatusJson) { | ||||
| Object.keys(nodeStatusJson).forEach((key) => { | |||||
| if (key.startsWith(NodePrefix)) { | |||||
| const nodeStatus = nodeStatusJson[key]; | |||||
| info.nodeStatus = nodeStatus; | |||||
| } else if (key.startsWith('workflow')) { | |||||
| setNodes(nodeStatusJson); | |||||
| Object.keys(nodeStatusJson).some((key) => { | |||||
| if (key.startsWith('workflow')) { | |||||
| const workflowStatus = nodeStatusJson[key]; | const workflowStatus = nodeStatusJson[key]; | ||||
| setWorkflowStatus(workflowStatus); | setWorkflowStatus(workflowStatus); | ||||
| return true; | |||||
| } | } | ||||
| return false; | |||||
| }); | }); | ||||
| } | } | ||||
| setInstanceInfo(info); | |||||
| // 运行中或者等待中,开启 SSE | // 运行中或者等待中,开启 SSE | ||||
| if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { | if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { | ||||
| @@ -106,9 +102,9 @@ function HyperParameterInstance() { | |||||
| const setupSSE = (name: string, namespace: string) => { | const setupSSE = (name: string, namespace: string) => { | ||||
| let { origin } = location; | let { origin } = location; | ||||
| if (process.env.NODE_ENV === 'development') { | |||||
| origin = 'http://172.20.32.197:31213'; | |||||
| } | |||||
| // if (process.env.NODE_ENV === 'development') { | |||||
| // origin = 'http://172.20.32.197:31213'; | |||||
| // } | |||||
| const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | ||||
| const evtSource = new EventSource( | const evtSource = new EventSource( | ||||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | ||||
| @@ -123,20 +119,12 @@ function HyperParameterInstance() { | |||||
| if (dataJson) { | if (dataJson) { | ||||
| const nodes = dataJson?.result?.object?.status?.nodes; | const nodes = dataJson?.result?.object?.status?.nodes; | ||||
| if (nodes) { | if (nodes) { | ||||
| const nodeStatus = Object.values(nodes).find((node: any) => | |||||
| node.displayName.startsWith(NodePrefix), | |||||
| ) as NodeStatus; | |||||
| const workflowStatus = Object.values(nodes).find((node: any) => | const workflowStatus = Object.values(nodes).find((node: any) => | ||||
| node.displayName.startsWith('workflow'), | node.displayName.startsWith('workflow'), | ||||
| ) as NodeStatus; | ) as NodeStatus; | ||||
| // 节点状态 | |||||
| if (nodeStatus) { | |||||
| setInstanceInfo((prev) => ({ | |||||
| ...prev!, | |||||
| nodeStatus: nodeStatus, | |||||
| })); | |||||
| } | |||||
| // 节点 | |||||
| setNodes(nodes); | |||||
| // 设置工作流状态 | // 设置工作流状态 | ||||
| if (workflowStatus) { | if (workflowStatus) { | ||||
| @@ -188,16 +176,7 @@ function HyperParameterInstance() { | |||||
| icon: <KFIcon type="icon-rizhi1" />, | icon: <KFIcon type="icon-rizhi1" />, | ||||
| children: ( | children: ( | ||||
| <div className={styles['hyper-parameter-instance__log']}> | <div className={styles['hyper-parameter-instance__log']}> | ||||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||||
| <LogList | |||||
| instanceName={instanceInfo.argo_ins_name} | |||||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||||
| workflowId={instanceInfo.nodeStatus.id} | |||||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||||
| ></LogList> | |||||
| )} | |||||
| {instanceInfo && nodes && <ExperimentLog instanceInfo={instanceInfo} nodes={nodes} />} | |||||
| </div> | </div> | ||||
| ), | ), | ||||
| }, | }, | ||||
| @@ -208,9 +187,7 @@ function HyperParameterInstance() { | |||||
| key: TabKeys.Result, | key: TabKeys.Result, | ||||
| label: '实验结果', | label: '实验结果', | ||||
| icon: <KFIcon type="icon-shiyanjieguo1" />, | icon: <KFIcon type="icon-shiyanjieguo1" />, | ||||
| children: ( | |||||
| <ExperimentResult fileUrl={instanceInfo?.result_txt} fileList={instanceInfo?.file_list} /> | |||||
| ), | |||||
| children: <ExperimentResult fileUrl={instanceInfo?.result_txt} />, | |||||
| }, | }, | ||||
| { | { | ||||
| key: TabKeys.History, | key: TabKeys.History, | ||||
| @@ -33,3 +33,26 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .cell-index { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| padding-left: 20px; | |||||
| text-align: left; | |||||
| &__best-tag { | |||||
| margin-left: 8px; | |||||
| padding: 1px 10px; | |||||
| color: @primary-color; | |||||
| font-weight: normal; | |||||
| font-size: 13px; | |||||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||||
| border: 1px solid .addAlpha(@primary-color, 0.5) []; | |||||
| border-radius: 2px; | |||||
| } | |||||
| } | |||||
| .table-best-row { | |||||
| color: @primary-color; | |||||
| font-weight: bold; | |||||
| } | |||||
| @@ -1,6 +1,8 @@ | |||||
| import { HyperParameterTrialList } from '@/pages/HyperParameter/types'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { HyperParameterFileList, HyperParameterTrialList } from '@/pages/HyperParameter/types'; | |||||
| import { downLoadZip } from '@/utils/downloadfile'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| import { Table, Tooltip, type TableProps } from 'antd'; | |||||
| import { Button, Table, Tooltip, type TableProps } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -15,14 +17,21 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| const paramsNames = Object.keys(config); | const paramsNames = Object.keys(config); | ||||
| const metricNames = Object.keys(metricAnalysis); | const metricNames = Object.keys(metricAnalysis); | ||||
| const columns: TableProps<HyperParameterTrialList>['columns'] = [ | |||||
| const trialColumns: TableProps<HyperParameterTrialList>['columns'] = [ | |||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: 100, | |||||
| width: 120, | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(false, TableCellValueType.Index), | |||||
| render: (_text, record, index: number) => { | |||||
| return ( | |||||
| <div className={styles['cell-index']}> | |||||
| <span className={styles['cell-index__text']}>{index + 1}</span> | |||||
| {record.is_best && <span className={styles['cell-index__best-tag']}>最佳</span>} | |||||
| </div> | |||||
| ); | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '运行次数', | title: '运行次数', | ||||
| @@ -51,7 +60,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| ]; | ]; | ||||
| if (paramsNames.length) { | if (paramsNames.length) { | ||||
| columns.push({ | |||||
| trialColumns.push({ | |||||
| title: '运行参数', | title: '运行参数', | ||||
| dataIndex: 'config', | dataIndex: 'config', | ||||
| key: 'config', | key: 'config', | ||||
| @@ -74,7 +83,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| } | } | ||||
| if (metricNames.length) { | if (metricNames.length) { | ||||
| columns.push({ | |||||
| trialColumns.push({ | |||||
| title: `指标分析(${first.metric ?? ''})`, | title: `指标分析(${first.metric ?? ''})`, | ||||
| dataIndex: 'metrics', | dataIndex: 'metrics', | ||||
| key: 'metrics', | key: 'metrics', | ||||
| @@ -96,6 +105,51 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| }); | }); | ||||
| } | } | ||||
| const fileColumns: TableProps<HyperParameterFileList>['columns'] = [ | |||||
| { | |||||
| title: '文件名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| { | |||||
| title: '文件大小', | |||||
| dataIndex: 'size', | |||||
| key: 'size', | |||||
| width: 200, | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'option', | |||||
| width: 160, | |||||
| key: 'option', | |||||
| render: (_: any, record: HyperParameterFileList) => { | |||||
| return ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="download" | |||||
| icon={<KFIcon type="icon-xiazai" />} | |||||
| onClick={() => { | |||||
| if (record.isFile) { | |||||
| downLoadZip(`/api/mmp/minioStorage/downloadFile`, { path: record.url }); | |||||
| } else { | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: record.url }); | |||||
| } | |||||
| }} | |||||
| > | |||||
| 下载 | |||||
| </Button> | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| ]; | |||||
| const expandedRowRender = (record: HyperParameterTrialList) => ( | |||||
| <Table columns={fileColumns} dataSource={[record.file]} pagination={false} rowKey="name" /> | |||||
| ); | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-history']}> | <div className={styles['experiment-history']}> | ||||
| <div className={styles['experiment-history__content']}> | <div className={styles['experiment-history__content']}> | ||||
| @@ -106,12 +160,14 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| )} | )} | ||||
| > | > | ||||
| <Table | <Table | ||||
| rowClassName={(record) => (record.is_best ? styles['table-best-row'] : '')} | |||||
| dataSource={trialList} | dataSource={trialList} | ||||
| columns={columns} | |||||
| columns={trialColumns} | |||||
| pagination={false} | pagination={false} | ||||
| bordered={true} | bordered={true} | ||||
| scroll={{ y: 'calc(100% - 110px)', x: '100%' }} | scroll={{ y: 'calc(100% - 110px)', x: '100%' }} | ||||
| rowKey="trial_id" | rowKey="trial_id" | ||||
| expandable={{ expandedRowRender }} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -0,0 +1,16 @@ | |||||
| .experiment-log { | |||||
| height: 100%; | |||||
| &__tabs { | |||||
| height: 100%; | |||||
| :global { | |||||
| .ant-tabs-nav-list { | |||||
| padding-left: 0 !important; | |||||
| background: none !important; | |||||
| } | |||||
| } | |||||
| &__log { | |||||
| height: 100%; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,50 +1,78 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import LogList from '@/pages/Experiment/components/LogList'; | import LogList from '@/pages/Experiment/components/LogList'; | ||||
| import { HyperParameterInstanceData } from '@/pages/HyperParameter/types'; | import { HyperParameterInstanceData } from '@/pages/HyperParameter/types'; | ||||
| import { NodeStatus } from '@/types'; | |||||
| import { Tabs } from 'antd'; | import { Tabs } from 'antd'; | ||||
| import { useEffect } from 'react'; | import { useEffect } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentLogProps = { | type ExperimentLogProps = { | ||||
| instanceInfo: HyperParameterInstanceData; | instanceInfo: HyperParameterInstanceData; | ||||
| nodes: Record<string, NodeStatus>; | |||||
| }; | }; | ||||
| function ExperimentLog({ instanceInfo }: ExperimentLogProps) { | |||||
| function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||||
| let hpoNodeStatus: NodeStatus | undefined; | |||||
| let frameworkCloneNodeStatus: NodeStatus | undefined; | |||||
| let trainCloneNodeStatus: NodeStatus | undefined; | |||||
| Object.keys(nodes) | |||||
| .sort((key1, key2) => { | |||||
| const node1 = nodes[key1]; | |||||
| const node2 = nodes[key2]; | |||||
| return new Date(node1.startedAt).getTime() - new Date(node2.startedAt).getTime(); | |||||
| }) | |||||
| .forEach((key) => { | |||||
| const node = nodes[key]; | |||||
| if (node.displayName.startsWith('auto-hpo')) { | |||||
| hpoNodeStatus = node; | |||||
| } else if (node.displayName.startsWith('git-clone') && !frameworkCloneNodeStatus) { | |||||
| frameworkCloneNodeStatus = node; | |||||
| } else if ( | |||||
| node.displayName.startsWith('git-clone') && | |||||
| frameworkCloneNodeStatus && | |||||
| node.displayName !== frameworkCloneNodeStatus?.displayName | |||||
| ) { | |||||
| trainCloneNodeStatus = node; | |||||
| } | |||||
| }); | |||||
| const tabItems = [ | const tabItems = [ | ||||
| { | { | ||||
| key: 'git-clone-1', | |||||
| key: 'git-clone-framework', | |||||
| label: '框架代码日志', | label: '框架代码日志', | ||||
| icon: <KFIcon type="icon-jibenxinxi" />, | |||||
| // icon: <KFIcon type="icon-rizhi1" />, | |||||
| children: ( | children: ( | ||||
| <div className={styles['auto-ml-instance__log']}> | |||||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||||
| <div className={styles['experiment-log__tabs__log']}> | |||||
| {frameworkCloneNodeStatus && ( | |||||
| <LogList | <LogList | ||||
| idPrefix="git-clone-framework" | |||||
| instanceName={instanceInfo.argo_ins_name} | instanceName={instanceInfo.argo_ins_name} | ||||
| instanceNamespace={instanceInfo.argo_ins_ns} | instanceNamespace={instanceInfo.argo_ins_ns} | ||||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||||
| workflowId={instanceInfo.nodeStatus.id} | |||||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||||
| pipelineNodeId={frameworkCloneNodeStatus.displayName} | |||||
| workflowId={frameworkCloneNodeStatus.id} | |||||
| instanceNodeStartTime={frameworkCloneNodeStatus.startedAt} | |||||
| instanceNodeStatus={frameworkCloneNodeStatus.phase as ExperimentStatus} | |||||
| ></LogList> | ></LogList> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | { | ||||
| key: 'git-clone-2', | |||||
| key: 'git-clone-train', | |||||
| label: '训练代码日志', | label: '训练代码日志', | ||||
| icon: <KFIcon type="icon-rizhi1" />, | |||||
| // icon: <KFIcon type="icon-rizhi1" />, | |||||
| children: ( | children: ( | ||||
| <div className={styles['auto-ml-instance__log']}> | |||||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||||
| <div className={styles['experiment-log__tabs__log']}> | |||||
| {trainCloneNodeStatus && ( | |||||
| <LogList | <LogList | ||||
| idPrefix="git-clone-train" | |||||
| instanceName={instanceInfo.argo_ins_name} | instanceName={instanceInfo.argo_ins_name} | ||||
| instanceNamespace={instanceInfo.argo_ins_ns} | instanceNamespace={instanceInfo.argo_ins_ns} | ||||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||||
| workflowId={instanceInfo.nodeStatus.id} | |||||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||||
| pipelineNodeId={trainCloneNodeStatus.displayName} | |||||
| workflowId={trainCloneNodeStatus.id} | |||||
| instanceNodeStartTime={trainCloneNodeStatus.startedAt} | |||||
| instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus} | |||||
| ></LogList> | ></LogList> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| @@ -53,17 +81,18 @@ function ExperimentLog({ instanceInfo }: ExperimentLogProps) { | |||||
| { | { | ||||
| key: 'auto-hpo', | key: 'auto-hpo', | ||||
| label: '超参寻优日志', | label: '超参寻优日志', | ||||
| icon: <KFIcon type="icon-rizhi1" />, | |||||
| // icon: <KFIcon type="icon-rizhi1" />, | |||||
| children: ( | children: ( | ||||
| <div className={styles['auto-ml-instance__log']}> | |||||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||||
| <div className={styles['experiment-log__tabs__log']}> | |||||
| {hpoNodeStatus && ( | |||||
| <LogList | <LogList | ||||
| idPrefix="auto-hpo" | |||||
| instanceName={instanceInfo.argo_ins_name} | instanceName={instanceInfo.argo_ins_name} | ||||
| instanceNamespace={instanceInfo.argo_ins_ns} | instanceNamespace={instanceInfo.argo_ins_ns} | ||||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||||
| workflowId={instanceInfo.nodeStatus.id} | |||||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||||
| pipelineNodeId={hpoNodeStatus.displayName} | |||||
| workflowId={hpoNodeStatus.id} | |||||
| instanceNodeStartTime={hpoNodeStatus.startedAt} | |||||
| instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus} | |||||
| ></LogList> | ></LogList> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| @@ -75,7 +104,7 @@ function ExperimentLog({ instanceInfo }: ExperimentLogProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-log']}> | <div className={styles['experiment-log']}> | ||||
| <Tabs className={styles['auto-ml-instance__tabs']} items={tabItems} /> | |||||
| <Tabs className={styles['experiment-log__tabs']} items={tabItems} /> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -12,6 +12,6 @@ | |||||
| &__text { | &__text { | ||||
| font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; | font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; | ||||
| white-space: pre-wrap; | |||||
| white-space: pre; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,45 +1,16 @@ | |||||
| import InfoGroup from '@/components/InfoGroup'; | import InfoGroup from '@/components/InfoGroup'; | ||||
| import { HyperParameterFileList } from '@/pages/HyperParameter/types'; | |||||
| import { getFileReq } from '@/services/file'; | import { getFileReq } from '@/services/file'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { Table, type TableProps } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentResultProps = { | type ExperimentResultProps = { | ||||
| fileList?: HyperParameterFileList[]; | |||||
| fileUrl?: string; | fileUrl?: string; | ||||
| }; | }; | ||||
| function ExperimentResult({ fileList, fileUrl }: ExperimentResultProps) { | |||||
| function ExperimentResult({ fileUrl }: ExperimentResultProps) { | |||||
| const [result, setResult] = useState<string | undefined>(''); | const [result, setResult] = useState<string | undefined>(''); | ||||
| const columns: TableProps<HyperParameterFileList>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 120, | |||||
| align: 'center', | |||||
| render: tableCellRender(false, TableCellValueType.Index), | |||||
| }, | |||||
| { | |||||
| title: '文件名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| { | |||||
| title: '文件大小', | |||||
| dataIndex: 'size', | |||||
| key: 'size', | |||||
| width: 200, | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| ]; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (fileUrl) { | if (fileUrl) { | ||||
| getResultFile(); | getResultFile(); | ||||
| @@ -56,23 +27,7 @@ function ExperimentResult({ fileList, fileUrl }: ExperimentResultProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-result']}> | <div className={styles['experiment-result']}> | ||||
| <InfoGroup title="文件列表" style={{ margin: '16px 0' }}> | |||||
| <div | |||||
| className={classNames( | |||||
| 'vertical-scroll-table-no-page', | |||||
| styles['experiment-result__table'], | |||||
| )} | |||||
| > | |||||
| <Table | |||||
| dataSource={fileList} | |||||
| columns={columns} | |||||
| pagination={false} | |||||
| scroll={{ y: 'calc(100% - 55px)', x: '100%' }} | |||||
| rowKey="name" | |||||
| /> | |||||
| </div> | |||||
| </InfoGroup> | |||||
| <InfoGroup title="实验结果" height={420} width="100%"> | |||||
| <InfoGroup title="最佳实验结果" width="100%"> | |||||
| <div className={styles['experiment-result__text']}>{result}</div> | <div className={styles['experiment-result__text']}>{result}</div> | ||||
| </InfoGroup> | </InfoGroup> | ||||
| </div> | </div> | ||||
| @@ -71,9 +71,14 @@ export type HyperParameterTrialList = { | |||||
| config?: Record<string, any>; | config?: Record<string, any>; | ||||
| metric_analysis?: Record<string, any>; | metric_analysis?: Record<string, any>; | ||||
| metric: string; | metric: string; | ||||
| file: HyperParameterFileList; | |||||
| is_best?: boolean; | |||||
| }; | }; | ||||
| export type HyperParameterFileList = { | export type HyperParameterFileList = { | ||||
| name?: string; | |||||
| size?: string; | |||||
| name: string; | |||||
| size: string; | |||||
| url: string; | |||||
| isFile: boolean; | |||||
| children?: HyperParameterFileList[]; | |||||
| }; | }; | ||||