diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index ac8b8652..686c495d 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -11,6 +11,7 @@ import { getAccessToken } from './access'; import ErrorBoundary from './components/ErrorBoundary'; import './dayjsConfig'; import { removeAllPageCacheState } from './hooks/useCacheState'; +import { globalGetSeverTime } from './hooks/useServerTime'; import { getRemoteMenu, getRoutersInfo, @@ -29,6 +30,7 @@ export { requestConfig as request } from './requestConfig'; export async function getInitialState(): Promise { const fetchUserInfo = async () => { try { + globalGetSeverTime(); const response = await getUserInfo(); return { ...response.user, diff --git a/react-ui/src/components/RunDuration/index.tsx b/react-ui/src/components/RunDuration/index.tsx new file mode 100644 index 00000000..f430d058 --- /dev/null +++ b/react-ui/src/components/RunDuration/index.tsx @@ -0,0 +1,35 @@ +import { useServerTime } from '@/hooks/useServerTime'; +import { elapsedTime } from '@/utils/date'; +import React, { useEffect, useState } from 'react'; + +type RunDurationProps = { + createTime?: string; + finishTime?: string; + className?: string; + style?: React.CSSProperties; +}; +function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) { + const [now] = useServerTime(); + const [currentTime, setCurrentTime] = useState(now()); + + // 定时刷新耗时 + useEffect(() => { + if (finishTime) { + setCurrentTime(new Date(finishTime)); + } else { + const timer = setInterval(() => { + setCurrentTime(now()); + }, 1000); + return () => { + clearInterval(timer); + }; + } + }, [finishTime, now]); + return ( + + {elapsedTime(createTime, currentTime)} + + ); +} + +export default RunDuration; diff --git a/react-ui/src/hooks/useSSE.ts b/react-ui/src/hooks/useSSE.ts index 5e278675..9f364f68 100644 --- a/react-ui/src/hooks/useSSE.ts +++ b/react-ui/src/hooks/useSSE.ts @@ -1,11 +1,12 @@ import { parseJsonText } from '@/utils'; -import { useCallback, useRef } from 'react'; +import { useEffect } from 'react'; +import { ExperimentStatus } from '@/enums'; +import { NodeStatus } from '@/types'; -export const useSSE = (onMessage: (data: any) => void) => { - const evtSourceRef = useRef(null); - - const setupSSE = useCallback( - (name: string, namespace: string) => { +export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record) => void +export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => { + useEffect(() => { + if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { const { origin } = location; const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); const evtSource = new EventSource( @@ -18,11 +19,10 @@ export const useSSE = (onMessage: (data: any) => void) => { return; } const dataJson = parseJsonText(data); - if (dataJson) { - const nodes = dataJson?.result?.object?.status?.nodes; - if (nodes) { - onMessage(nodes); - } + const statusData = dataJson?.result?.object?.status; + if (statusData) { + const { finishedAt, phase, nodes } = statusData; + onMessage(experimentInsId, phase, finishedAt, nodes); } }; @@ -30,17 +30,10 @@ export const useSSE = (onMessage: (data: any) => void) => { console.error('SSE error: ', error); }; - evtSourceRef.current = evtSource; - }, - [onMessage], - ); - - const closeSSE = useCallback(() => { - if (evtSourceRef.current) { - evtSourceRef.current.close(); - evtSourceRef.current = null; + return () => { + evtSource.close(); + } } - }, []); - - return [setupSSE, closeSSE]; + + }, [experimentInsId, status, name, namespace, onMessage]); }; diff --git a/react-ui/src/hooks/useServerTime.ts b/react-ui/src/hooks/useServerTime.ts new file mode 100644 index 00000000..89b39775 --- /dev/null +++ b/react-ui/src/hooks/useServerTime.ts @@ -0,0 +1,54 @@ +/* + * @Author: 赵伟 + * @Date: 2024-10-10 08:51:41 + * @Description: 服务器时间 hook + */ + +import { getSeverTimeReq } from '@/services/experiment'; +import { to } from '@/utils/promise'; +import { useCallback, useEffect, useState } from 'react'; + +let globalTimeOffset: number | undefined = undefined; + +export const globalGetSeverTime = async () => { + const requestStartTime = Date.now() + const [res] = await to(getSeverTimeReq()); + const requestEndTime = Date.now() + const requestDuration = (requestEndTime - requestStartTime) / 2; + if (res && res.data) { + const serverDate = new Date(res.data); + const timeOffset = serverDate.getTime() + requestDuration - requestEndTime ; + globalTimeOffset = timeOffset; + return timeOffset + } +}; + +export const now = () => { + return new Date(Date.now() + (globalTimeOffset ?? 0)) +} + +/** 获取服务器时间 */ +export function useServerTime() { + const [timeOffset, setTimeOffset] = useState(globalTimeOffset ?? 0); + + useEffect(() => { + // 获取服务器时间 + const getSeverTime = async () => { + const [res] = await to(globalGetSeverTime()); + if (res) { + setTimeOffset(res) + } + }; + + if (!globalTimeOffset) { + getSeverTime(); + } + }, []); + + const now = useCallback(() => { + return new Date(Date.now() + timeOffset) + }, [timeOffset]) + + + return [now, timeOffset] as const; +} diff --git a/react-ui/src/pages/ActiveLearn/Instance/index.tsx b/react-ui/src/pages/ActiveLearn/Instance/index.tsx index 680f935c..5655e60d 100644 --- a/react-ui/src/pages/ActiveLearn/Instance/index.tsx +++ b/react-ui/src/pages/ActiveLearn/Instance/index.tsx @@ -106,13 +106,13 @@ function ActiveLearnInstance() { if (dataJson) { const nodes = dataJson?.result?.object?.status?.nodes; if (nodes) { + // 节点 + setNodes(nodes); + const workflowStatus = Object.values(nodes).find((node: any) => node.displayName.startsWith(NodePrefix), ) as NodeStatus; - // 节点 - setNodes(nodes); - // 设置工作流状态 if (workflowStatus) { setWorkflowStatus(workflowStatus); @@ -153,6 +153,7 @@ function ActiveLearnInstance() { className={styles['active-learn-instance__basic']} info={experimentInfo} runStatus={workflowStatus} + instanceStatus={instanceInfo?.status} isInstance /> ), diff --git a/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx index 892e6725..3c75eb61 100644 --- a/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx +++ b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx @@ -1,5 +1,5 @@ import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; -import { AutoMLTaskType, autoMLTaskTypeOptions } from '@/enums'; +import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums'; import { useComputingResource } from '@/hooks/useComputingResource'; import { classifierAlgorithms, @@ -9,9 +9,8 @@ import { regressorAlgorithms, } from '@/pages/ActiveLearn/components/CreateForm/utils'; import { ActiveLearnData } from '@/pages/ActiveLearn/types'; -import { experimentStatusInfo } from '@/pages/Experiment/status'; +import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic'; import { type NodeStatus } from '@/types'; -import { elapsedTime } from '@/utils/date'; import { formatBoolean, formatCodeConfig, @@ -21,7 +20,6 @@ import { formatMirror, formatModel, } from '@/utils/format'; -import { Flex } from 'antd'; import classNames from 'classnames'; import { useMemo } from 'react'; import styles from './index.less'; @@ -31,9 +29,16 @@ type BasicInfoProps = { className?: string; isInstance?: boolean; runStatus?: NodeStatus; + instanceStatus?: ExperimentStatus; }; -function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfoProps) { +function BasicInfo({ + info, + className, + runStatus, + instanceStatus, + isInstance = false, +}: BasicInfoProps) { const getResourceDescription = useComputingResource()[1]; const basicDatas: BasicInfoData[] = useMemo(() => { if (!info) { @@ -205,56 +210,13 @@ function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfo ]; }, [info, getResourceDescription]); - const instanceDatas = useMemo(() => { - if (!info || !runStatus) { - return []; - } - - return [ - { - label: '启动时间', - value: formatDate(info.create_time), - ellipsis: true, - }, - { - label: '执行时长', - value: elapsedTime(info.create_time, runStatus.finishedAt), - ellipsis: true, - }, - { - label: '状态', - value: ( - - -
- {experimentStatusInfo[runStatus?.phase]?.label} -
-
- ), - ellipsis: true, - }, - ]; - }, [runStatus, info]); - return (
{isInstance && runStatus && ( - )} {!isInstance && ( diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index 44489ea7..e0fb5e4d 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -110,13 +110,13 @@ function AutoMLInstance() { if (dataJson) { const nodes = dataJson?.result?.object?.status?.nodes; if (nodes) { + // 节点 + setNodes(nodes); + const workflowStatus = Object.values(nodes).find((node: any) => node.displayName.startsWith(NodePrefix), ) as NodeStatus; - // 节点 - setNodes(nodes); - if (workflowStatus) { setWorkflowStatus(workflowStatus); @@ -156,6 +156,7 @@ function AutoMLInstance() { className={styles['auto-ml-instance__basic']} info={autoMLInfo} runStatus={workflowStatus} + instanceStatus={instanceInfo?.status} isInstance /> ), diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index 98a1f4ba..08c95ed9 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -2,19 +2,18 @@ import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; import { AutoMLTaskType, AutoMLType, + ExperimentStatus, autoMLEnsembleClassOptions, autoMLTaskTypeOptions, } from '@/enums'; import { useComputingResource } from '@/hooks/useComputingResource'; import { AutoMLData } from '@/pages/AutoML/types'; -import { experimentStatusInfo } from '@/pages/Experiment/status'; import { type NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; -import { elapsedTime } from '@/utils/date'; import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format'; -import { Flex } from 'antd'; import classNames from 'classnames'; import { useMemo } from 'react'; +import ExperimentRunBasic from '../ExperimentRunBasic'; import styles from './index.less'; // 格式化优化方向 @@ -40,9 +39,16 @@ type AutoMLBasicProps = { className?: string; isInstance?: boolean; runStatus?: NodeStatus; + instanceStatus?: ExperimentStatus; }; -function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLBasicProps) { +function AutoMLBasic({ + info, + className, + runStatus, + instanceStatus, + isInstance = false, +}: AutoMLBasicProps) { const getResourceDescription = useComputingResource()[1]; const basicDatas: BasicInfoData[] = useMemo(() => { if (!info) { @@ -284,53 +290,13 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB ]; }, [info]); - const instanceDatas = useMemo(() => { - if (!info || !runStatus) { - return []; - } - - return [ - { - label: '启动时间', - value: formatDate(info.create_time), - }, - { - label: '执行时长', - value: elapsedTime(info.create_time, runStatus.finishedAt), - }, - { - label: '状态', - value: ( - - -
- {experimentStatusInfo[runStatus?.phase]?.label} -
-
- ), - }, - ]; - }, [runStatus, info]); - return (
{isInstance && runStatus && ( - )} {!isInstance && ( diff --git a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.less b/react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less similarity index 100% rename from react-ui/src/pages/AutoML/components/ExperimentInstance/index.less rename to react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less diff --git a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx similarity index 83% rename from react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx rename to react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx index 74cf1115..05d6d786 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx @@ -1,20 +1,19 @@ import KFIcon from '@/components/KFIcon'; import { ExperimentStatus } from '@/enums'; import { useCheck } from '@/hooks/useCheck'; -import { experimentStatusInfo } from '@/pages/Experiment/status'; import themes from '@/styles/theme.less'; import { type ExperimentInstance } from '@/types'; -import { elapsedTime, formatDate } from '@/utils/date'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; import { DoubleRightOutlined } from '@ant-design/icons'; -import { App, Button, Checkbox, ConfigProvider, Typography } from 'antd'; +import { App, Button, Checkbox, ConfigProvider } from 'antd'; import classNames from 'classnames'; import { useEffect, useMemo } from 'react'; import { ExperimentListType, experimentListConfig } from '../ExperimentList/config'; import styles from './index.less'; +import ExperimentInstanceComponent from './instance'; -type ExperimentInstanceProps = { +type ExperimentInstanceListProps = { type: ExperimentListType; experimentInsList?: ExperimentInstance[]; experimentInsTotal: number; @@ -24,7 +23,7 @@ type ExperimentInstanceProps = { onLoadMore?: () => void; }; -function ExperimentInstanceComponent({ +function ExperimentInstanceList({ type, experimentInsList, experimentInsTotal, @@ -32,7 +31,7 @@ function ExperimentInstanceComponent({ onRemove, onTerminate, onLoadMore, -}: ExperimentInstanceProps) { +}: ExperimentInstanceListProps) { const { message } = App.useApp(); const allIntanceIds = useMemo(() => { return experimentInsList?.map((item) => item.id) || []; @@ -171,28 +170,14 @@ function ExperimentInstanceComponent({ > {index + 1} -
- {elapsedTime(item.create_time, item.finish_time)} -
-
- - {formatDate(item.create_time)} - -
-
- - - {experimentStatusInfo[item.status as ExperimentStatus]?.label} - -
+
执行时长: - {elapsedTime(experimentIns?.create_time, experimentIns?.finish_time)} +
状态: diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx index bcb3af1e..11d0ff2e 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx @@ -1,10 +1,11 @@ +import RunDuration from '@/components/RunDuration'; import { ExperimentStatus } from '@/enums'; import { experimentStatusInfo } from '@/pages/Experiment/status'; import { PipelineNodeModelSerialize } from '@/types'; -import { elapsedTime, formatDate } from '@/utils/date'; +import { formatDate } from '@/utils/date'; import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; import { Drawer, Tabs, Typography } from 'antd'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import ExperimentParameter from '../ExperimentParameter'; import ExperimentResult from '../ExperimentResult'; import LogList from '../LogList'; @@ -41,22 +42,6 @@ const ExperimentDrawer = ({ instanceNodeStartTime, instanceNodeEndTime, }: ExperimentDrawerProps) => { - const [currentDate, setCurrentDate] = useState( - instanceNodeEndTime ? new Date(instanceNodeEndTime) : new Date(), - ); - - // 定时刷新耗时 - useEffect(() => { - if (!instanceNodeEndTime) { - const timer = setInterval(() => { - setCurrentDate(new Date()); - }, 1000); - return () => { - clearInterval(timer); - }; - } - }, [instanceNodeEndTime]); - // 如果性能有问题,可以进一步拆解 const items = useMemo( () => [ @@ -158,7 +143,7 @@ const ExperimentDrawer = ({
耗时: - {elapsedTime(instanceNodeStartTime, currentDate)} +
void; @@ -29,7 +28,7 @@ type ExperimentInstanceProps = { onLoadMore?: () => void; }; -function ExperimentInstanceComponent({ +function ExperimentInstanceList({ experimentInsList, experimentInsTotal, onClickInstance, @@ -37,7 +36,7 @@ function ExperimentInstanceComponent({ onRemove, onTerminate, onLoadMore, -}: ExperimentInstanceProps) { +}: ExperimentInstanceListProps) { const { message } = App.useApp(); const allIntanceIds = useMemo(() => { return experimentInsList?.map((item) => item.id) || []; @@ -185,28 +184,16 @@ function ExperimentInstanceComponent({ '--' )}
-
-
{elapsedTime(item.create_time, item.finish_time)}
-
- - {formatDate(item.create_time)} - -
-
-
- - - {experimentStatusInfo[item.status as ExperimentStatus]?.label} - -
+ + +