Browse Source

Merge remote-tracking branch 'origin/dev' into dev

pull/106/head
西大锐 1 year ago
parent
commit
cc03f5048c
23 changed files with 392 additions and 261 deletions
  1. +0
    -2
      react-ui/jest.config.ts
  2. +1
    -1
      react-ui/package.json
  3. +2
    -4
      react-ui/src/app.tsx
  4. +2
    -2
      react-ui/src/components/DictTag/index.tsx
  5. +1
    -1
      react-ui/src/global.tsx
  6. +1
    -1
      react-ui/src/pages/Experiment/Comparison/index.tsx
  7. +105
    -37
      react-ui/src/pages/Experiment/Info/index.jsx
  8. +0
    -146
      react-ui/src/pages/Experiment/Info/props.tsx
  9. +0
    -0
      react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less
  10. +131
    -0
      react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx
  11. +2
    -2
      react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx
  12. +87
    -18
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  13. +37
    -17
      react-ui/src/pages/Experiment/components/LogList/index.tsx
  14. +1
    -1
      react-ui/src/pages/Experiment/index.jsx
  15. +4
    -6
      react-ui/src/pages/Pipeline/editPipeline/index.jsx
  16. +4
    -4
      react-ui/src/pages/Pipeline/editPipeline/props.tsx
  17. +1
    -7
      react-ui/src/pages/Pipeline/index.jsx
  18. +1
    -1
      react-ui/src/pages/System/Operlog/detail.tsx
  19. +1
    -1
      react-ui/src/pages/System/Role/components/DataScope.tsx
  20. +0
    -2
      react-ui/src/pages/User/Login/index.tsx
  21. +9
    -6
      react-ui/src/requestConfig.ts
  22. +1
    -0
      react-ui/src/services/experiment/index.js
  23. +1
    -2
      react-ui/src/services/session.ts

+ 0
- 2
react-ui/jest.config.ts View File

@@ -6,8 +6,6 @@ export default async () => {
target: 'browser', target: 'browser',
}), }),
}); });

console.log();
return { return {
...config, ...config,
testEnvironmentOptions: { testEnvironmentOptions: {


+ 1
- 1
react-ui/package.json View File

@@ -32,7 +32,7 @@
"record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login", "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login",
"serve": "umi-serve", "serve": "umi-serve",
"start": "cross-env UMI_ENV=dev max dev", "start": "cross-env UMI_ENV=dev max dev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev",
"start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev", "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",


+ 2
- 4
react-ui/src/app.tsx View File

@@ -34,9 +34,7 @@ export async function getInitialState(): Promise<{
// console.log('getInitialState'); // console.log('getInitialState');
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
try { try {
const response = await getUserInfo({
skipErrorHandler: true,
});
const response = await getUserInfo();
return { return {
...response.user, ...response.user,
avatar: response.user.avatar || require('@/assets/img/avatar-default.png'), avatar: response.user.avatar || require('@/assets/img/avatar-default.png'),
@@ -45,7 +43,7 @@ export async function getInitialState(): Promise<{
roleNames: response.user.roles, roleNames: response.user.roles,
} as API.CurrentUser; } as API.CurrentUser;
} catch (error) { } catch (error) {
console.log(error);
console.error(error);
gotoLoginPage(); gotoLoginPage();
} }
return undefined; return undefined;


+ 2
- 2
react-ui/src/components/DictTag/index.tsx View File

@@ -63,7 +63,7 @@ const DictTag: React.FC<DictTagProps> = (props) => {
} }
if (props.options) { if (props.options) {
if (!Array.isArray(props.options)) { if (!Array.isArray(props.options)) {
console.log('DictTag options is no array!');
// console.log('DictTag options is no array!');
return ''; return '';
} }
for (const item of props.options) { for (const item of props.options) {
@@ -85,7 +85,7 @@ const DictTag: React.FC<DictTagProps> = (props) => {
} }
if (props.options) { if (props.options) {
if (!Array.isArray(props.options)) { if (!Array.isArray(props.options)) {
console.log('DictTag options is no array!');
// console.log('DictTag options is no array!');
return 'default'; return 'default';
} }
for (const item of props.options) { for (const item of props.options) {


+ 1
- 1
react-ui/src/global.tsx View File

@@ -15,7 +15,7 @@ const clearCache = () => {
caches.delete(key); caches.delete(key);
}); });
}) })
.catch((e) => console.log(e));
.catch((e) => console.error(e));
} }
}; };




+ 1
- 1
react-ui/src/pages/Experiment/Comparison/index.tsx View File

@@ -94,7 +94,7 @@ function ExperimentComparison() {
fixed: 'left', fixed: 'left',
selectedRowKeys, selectedRowKeys,
onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => { onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedRowKeys(selectedRowKeys); setSelectedRowKeys(selectedRowKeys);
}, },
}; };


+ 105
- 37
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -10,10 +10,10 @@ import G6, { Util } from '@antv/g6';
import { Button } from 'antd'; import { Button } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import ExperimentDrawer from '../components/ExperimentDrawer';
import ParamsModal from '../components/ViewParamsModal'; import ParamsModal from '../components/ViewParamsModal';
import { experimentStatusInfo } from '../status'; import { experimentStatusInfo } from '../status';
import styles from './index.less'; import styles from './index.less';
import ExperimentDrawer from './props';


let graph = null; let graph = null;


@@ -28,6 +28,7 @@ function ExperimentText() {
const [propsDrawerOpen, openPropsDrawer, closePropsDrawer, propsDrawerOpenRef] = const [propsDrawerOpen, openPropsDrawer, closePropsDrawer, propsDrawerOpenRef] =
useVisible(false); useVisible(false);
const navigate = useNavigate(); const navigate = useNavigate();
const evtSourceRef = useRef();
const width = 110; const width = 110;
const height = 36; const height = 36;


@@ -48,6 +49,10 @@ function ExperimentText() {
if (timerRef.current) { if (timerRef.current) {
clearTimeout(timerRef.current); clearTimeout(timerRef.current);
} }
if (evtSourceRef.current) {
evtSourceRef.current.close();
evtSourceRef.current = null;
}
}; };
}, []); }, []);


@@ -68,57 +73,39 @@ function ExperimentText() {
item.imgName = item.img.slice(0, item.img.length - 4); item.imgName = item.img.slice(0, item.img.length - 4);
}); });
workflowRef.current = dag; workflowRef.current = dag;
getExperimentInstance(true);
getExperimentInstance();
} catch (error) { } catch (error) {
// JSON.parse 错误 // JSON.parse 错误
console.log(error);
console.error('JSON.parse error: ', error);
} }
} }
}; };


// 获取实验实例 // 获取实验实例
const getExperimentInstance = async (first) => {
const getExperimentInstance = async () => {
const [res] = await to(getExperimentIns(locationParams.id)); const [res] = await to(getExperimentIns(locationParams.id));
if (res && res.data && workflowRef.current) { if (res && res.data && workflowRef.current) {
setExperimentIns(res.data); setExperimentIns(res.data);
const { status, nodes_status } = res.data;
const { status, nodes_status, argo_ins_ns, argo_ins_name } = res.data;
const workflowData = workflowRef.current; const workflowData = workflowRef.current;
const experimentStatusObjs = JSON.parse(nodes_status); const experimentStatusObjs = JSON.parse(nodes_status);
workflowData.nodes.forEach((item) => { workflowData.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs?.[item.id] ?? {};
const { finishedAt, startedAt, phase, id } = experimentNode;
item.experimentStartTime = startedAt;
item.experimentEndTime = finishedAt;
item.experimentStatus = phase;
item.workflowId = id;
item.img = phase ? `${item.imgName}-${phase}.png` : `${item.imgName}.png`;
const experimentNode = experimentStatusObjs?.[item.id];
updateWorkflowNode(item, experimentNode);
}); });


// 更新打开的抽屉数据
if (propsDrawerOpenRef.current && experimentNodeDataRef.current) {
const currentId = experimentNodeDataRef.current.id;
const node = workflowData.nodes.find((item) => item.id === currentId);
if (node) {
setExperimentNodeData(node);
}
}

getGraphData(workflowData, first);

// 运行中或者等待中,每5秒获取一次实验实例
if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
timerRef.current = setTimeout(() => {
getExperimentInstance(false);
}, 5 * 1000);
}
// 绘制图
getGraphData(workflowData, true);


if (first && status === ExperimentStatus.Pending) {
if (status === ExperimentStatus.Pending) {
// 如果状态是 Pending, 打开第一个节点
const node = workflowData.nodes[0]; const node = workflowData.nodes[0];
if (node) { if (node) {
setExperimentNodeData(node); setExperimentNodeData(node);
openPropsDrawer(); openPropsDrawer();
} }
} else if (first && status === ExperimentStatus.Running) {
} else if (status === ExperimentStatus.Running) {
// 如果状态是 Running,打开第一个运行中的节点,如果没有运行中的节点,则打开第一个节点
const node = const node =
workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ?? workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ??
workflowData.nodes[0]; workflowData.nodes[0];
@@ -127,9 +114,83 @@ function ExperimentText() {
openPropsDrawer(); openPropsDrawer();
} }
} }

// 运行中或者等待中,开启 SSE
if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
setupSSE(argo_ins_name, argo_ins_ns);
}
} }
}; };


const setupSSE = (name, namespace) => {
const { origin } = location;
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
{ withCredentials: false },
);
evtSource.onmessage = (event) => {
const data = event?.data;
if (!data) {
return;
}
try {
const dataJson = JSON.parse(data);
const statusData = dataJson?.result?.object?.status;
if (!statusData) {
return;
}
const { startedAt, finishedAt, phase, nodes = {} } = statusData;
setExperimentIns((prev) => ({
...prev,
finish_time: finishedAt,
status: phase,
}));

const workflowData = workflowRef.current;
workflowData.nodes.forEach((item) => {
const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
updateWorkflowNode(item, experimentNode);
});
getGraphData(workflowData, false);

// 更新打开的抽屉数据
if (propsDrawerOpenRef.current && experimentNodeDataRef.current) {
const currentId = experimentNodeDataRef.current.id;
const node = workflowData.nodes.find((item) => item.id === currentId);
if (node) {
setExperimentNodeData(node);
}
}
if (phase !== ExperimentStatus.Pending && phase !== ExperimentStatus.Running) {
evtSource.close();
evtSourceRef.current = null;
}
} catch (error) {
console.error('JSON.parse error: ', error);
}
};
evtSource.onerror = (error) => {
console.error('SSE error: ', error);
};

evtSourceRef.current = evtSource;
};

function updateWorkflowNode(workflowNode, statusNode) {
if (!statusNode) {
return;
}
const { finishedAt, startedAt, phase, id } = statusNode;
workflowNode.experimentStartTime = startedAt;
workflowNode.experimentEndTime = finishedAt;
workflowNode.experimentStatus = phase;
workflowNode.workflowId = id;
workflowNode.img = phase
? `${workflowNode.imgName}-${phase}.png`
: `${workflowNode.imgName}.png`;
}

// 根据数据,渲染图 // 根据数据,渲染图
const getGraphData = (data, first) => { const getGraphData = (data, first) => {
if (graph) { if (graph) {
@@ -149,7 +210,7 @@ function ExperimentText() {
} }
} else { } else {
setTimeout(() => { setTimeout(() => {
getGraphData(data);
getGraphData(data, first);
}, 500); }, 500);
} }
}; };
@@ -390,6 +451,12 @@ function ExperimentText() {
graph.on('node:mouseleave', (e) => { graph.on('node:mouseleave', (e) => {
graph.setItemState(e.item, 'hover', false); graph.setItemState(e.item, 'hover', false);
}); });
graph.on('canvas:click', (e) => {
closePropsDrawer();
setTimeout(() => {
setExperimentNodeData(null);
}, 200);
});
}; };


return ( return (
@@ -425,18 +492,19 @@ function ExperimentText() {
</Button> </Button>
</div> </div>
<div className={styles['pipeline-container__graph']} ref={graphRef}></div> <div className={styles['pipeline-container__graph']} ref={graphRef}></div>
{experimentNodeData ? (
{experimentIns && experimentNodeData ? (
<ExperimentDrawer <ExperimentDrawer
key={experimentNodeData.id}
open={propsDrawerOpen} open={propsDrawerOpen}
onClose={closePropsDrawer} onClose={closePropsDrawer}
instanceId={experimentIns?.id}
instanceName={experimentIns?.argo_ins_name}
instanceNamespace={experimentIns?.argo_ins_ns}
instanceId={experimentIns.id}
instanceName={experimentIns.argo_ins_name}
instanceNamespace={experimentIns.argo_ins_ns}
instanceNodeData={experimentNodeData} instanceNodeData={experimentNodeData}
workflowId={experimentNodeData.workflowId} workflowId={experimentNodeData.workflowId}
instanceNodeStatus={experimentNodeData.experimentStatus} instanceNodeStatus={experimentNodeData.experimentStatus}
instanceNodeStartTime={experimentNodeData.experimentStartTime} instanceNodeStartTime={experimentNodeData.experimentStartTime}
instanceNodeEndTime={experimentIns.experimentEndTime}
instanceNodeEndTime={experimentNodeData.experimentEndTime}
></ExperimentDrawer> ></ExperimentDrawer>
) : null} ) : null}
<ParamsModal <ParamsModal


+ 0
- 146
react-ui/src/pages/Experiment/Info/props.tsx View File

@@ -1,146 +0,0 @@
import { ExperimentStatus } from '@/enums';
import { PipelineNodeModelSerialize } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Tabs } from 'antd';
import { forwardRef, useImperativeHandle, useMemo } from 'react';
import ExperimentParameter from '../components/ExperimentParameter';
import ExperimentResult from '../components/ExperimentResult';
import LogList from '../components/LogList';
import { experimentStatusInfo } from '../status';
import styles from './props.less';

export type ExperimentLog = {
log_type: 'normal' | 'resource'; // 日志类型
pod_name?: string; // 分布式名称
log_content?: string; // 日志内容
start_time?: string; // 日志开始时间
};

type ExperimentDrawerProps = {
open: boolean;
onClose: () => void;
instanceId?: number; // 实验实例 id
instanceName?: string; // 实验实例 name
instanceNamespace?: string; // 实验实例 namespace
instanceNodeData: PipelineNodeModelSerialize; // 节点数据,在定时刷新实验实例状态中不会变化
workflowId?: string; // 实验实例工作流 id
instanceNodeStatus?: ExperimentStatus; // 在定时刷新实验实例状态中,变化一两次
instanceNodeStartTime?: string; // 在定时刷新实验实例状态中,变化一两次
instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化
};

const ExperimentDrawer = forwardRef(
(
{
open,
onClose,
instanceId,
instanceName,
instanceNamespace,
instanceNodeData,
workflowId,
instanceNodeStatus,
instanceNodeStartTime,
instanceNodeEndTime,
}: ExperimentDrawerProps,
ref,
) => {
useImperativeHandle(ref, () => ({}));

// 如果性能有问题,可以进一步拆解
const items = useMemo(
() => [
{
key: '1',
label: '日志详情',
children: (
<LogList
instanceName={instanceName}
instanceNamespace={instanceNamespace}
pipelineNodeId={instanceNodeData.id}
workflowId={workflowId}
instanceNodeStartTime={instanceNodeStartTime}
instanceNodeStatus={instanceNodeStatus}
></LogList>
),
icon: <ProfileOutlined />,
},
{
key: '2',
label: '配置参数',
icon: <DatabaseOutlined />,
children: <ExperimentParameter nodeData={instanceNodeData} />,
},
{
key: '3',
label: '输出结果',
children: (
<ExperimentResult
experimentInsId={instanceId}
pipelineNodeId={instanceNodeData.id}
></ExperimentResult>
),
icon: <ProfileOutlined />,
},
],
[
instanceNodeData,
instanceId,
instanceName,
instanceNamespace,
instanceNodeStatus,
workflowId,
instanceNodeStartTime,
],
);

return (
<Drawer
title="任务执行详情"
placement="right"
getContainer={false}
closeIcon={false}
onClose={onClose}
open={open}
width={520}
className={styles['experiment-drawer']}
destroyOnClose={true}
>
<div style={{ paddingTop: '15px' }}>
<div className={styles['experiment-drawer__info']}>
任务名称:{instanceNodeData.label}
</div>
<div className={styles['experiment-drawer__info']}>
执行状态:
{instanceNodeStatus ? (
<>
<div
className={styles['experiment-drawer__status-dot']}
style={{
backgroundColor: experimentStatusInfo[instanceNodeStatus]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[instanceNodeStatus]?.color }}>
{experimentStatusInfo[instanceNodeStatus]?.label}
</span>
</>
) : (
'--'
)}
</div>
<div className={styles['experiment-drawer__info']}>
启动时间:{formatDate(instanceNodeStartTime)}
</div>
<div className={styles['experiment-drawer__info']}>
耗时:
{elapsedTime(instanceNodeStartTime, instanceNodeEndTime)}
</div>
</div>
<Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} />
</Drawer>
);
},
);

export default ExperimentDrawer;

react-ui/src/pages/Experiment/Info/props.less → react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less View File


+ 131
- 0
react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx View File

@@ -0,0 +1,131 @@
import { ExperimentStatus } from '@/enums';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { PipelineNodeModelSerialize } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Tabs } from 'antd';
import { useMemo } from 'react';
import ExperimentParameter from '../ExperimentParameter';
import ExperimentResult from '../ExperimentResult';
import LogList from '../LogList';
import styles from './index.less';

type ExperimentDrawerProps = {
open: boolean;
onClose: () => void;
instanceId: number; // 实验实例 id
instanceName: string; // 实验实例 name
instanceNamespace: string; // 实验实例 namespace
instanceNodeData: PipelineNodeModelSerialize; // 节点数据,在定时刷新实验实例状态中不会变化
workflowId?: string; // 实验实例工作流 id
instanceNodeStatus?: ExperimentStatus; // 实例节点状态
instanceNodeStartTime?: string; // 开始时间
instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化
};

const ExperimentDrawer = ({
open,
onClose,
instanceId,
instanceName,
instanceNamespace,
instanceNodeData,
workflowId,
instanceNodeStatus,
instanceNodeStartTime,
instanceNodeEndTime,
}: ExperimentDrawerProps) => {
// 如果性能有问题,可以进一步拆解
const items = useMemo(
() => [
{
key: '1',
label: '日志详情',
children: (
<LogList
instanceName={instanceName}
instanceNamespace={instanceNamespace}
pipelineNodeId={instanceNodeData.id}
workflowId={workflowId}
instanceNodeStartTime={instanceNodeStartTime}
instanceNodeStatus={instanceNodeStatus}
></LogList>
),
icon: <ProfileOutlined />,
},
{
key: '2',
label: '配置参数',
icon: <DatabaseOutlined />,
children: <ExperimentParameter nodeData={instanceNodeData} />,
},
{
key: '3',
label: '输出结果',
children: (
<ExperimentResult
experimentInsId={instanceId}
pipelineNodeId={instanceNodeData.id}
></ExperimentResult>
),
icon: <ProfileOutlined />,
},
],
[
instanceNodeData,
instanceId,
instanceName,
instanceNamespace,
instanceNodeStatus,
workflowId,
instanceNodeStartTime,
],
);

return (
<Drawer
title="任务执行详情"
placement="right"
getContainer={false}
closeIcon={false}
onClose={onClose}
open={open}
width={520}
className={styles['experiment-drawer']}
destroyOnClose={true}
mask={false}
>
<div style={{ paddingTop: '15px' }}>
<div className={styles['experiment-drawer__info']}>任务名称:{instanceNodeData.label}</div>
<div className={styles['experiment-drawer__info']}>
执行状态:
{instanceNodeStatus ? (
<>
<div
className={styles['experiment-drawer__status-dot']}
style={{
backgroundColor: experimentStatusInfo[instanceNodeStatus]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[instanceNodeStatus]?.color }}>
{experimentStatusInfo[instanceNodeStatus]?.label}
</span>
</>
) : (
'--'
)}
</div>
<div className={styles['experiment-drawer__info']}>
启动时间:{formatDate(instanceNodeStartTime)}
</div>
<div className={styles['experiment-drawer__info']}>
耗时:
{elapsedTime(instanceNodeStartTime, instanceNodeEndTime)}
</div>
</div>
<Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} />
</Drawer>
);
};

export default ExperimentDrawer;

+ 2
- 2
react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx View File

@@ -8,8 +8,8 @@ import ExportModelModal from '../ExportModelModal';
import styles from './index.less'; import styles from './index.less';


type ExperimentResultProps = { type ExperimentResultProps = {
experimentInsId?: number; // 实验实例 id
pipelineNodeId?: string; // 流水线节点 id
experimentInsId: number; // 实验实例 id
pipelineNodeId: string; // 流水线节点 id
}; };


type ExperimentResultData = { type ExperimentResultData = {


+ 87
- 18
react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -6,12 +6,13 @@


import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { useStateRef } from '@/hooks'; import { useStateRef } from '@/hooks';
import { ExperimentLog } from '@/pages/Experiment/Info/props';
import { getExperimentPodsLog } from '@/services/experiment/index.js'; import { getExperimentPodsLog } from '@/services/experiment/index.js';
import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import { Button } from 'antd'; import { Button } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { ExperimentLog } from '../LogList';
import styles from './index.less'; import styles from './index.less';


export type LogGroupProps = ExperimentLog & { export type LogGroupProps = ExperimentLog & {
@@ -21,6 +22,7 @@ export type LogGroupProps = ExperimentLog & {
type Log = { type Log = {
start_time: string; // 日志开始时间 start_time: string; // 日志开始时间
log_content: string; // 日志内容 log_content: string; // 日志内容
pod_name: string; // pod名称
}; };


// 滚动到底部 // 滚动到底部
@@ -49,44 +51,33 @@ function LogGroup({
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false);
const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); const preStatusRef = useRef<ExperimentStatus | undefined>(undefined);
const socketRef = useRef<WebSocket | undefined>(undefined);
const retryRef = useRef(2);


useEffect(() => { useEffect(() => {
scrollToBottom(false); scrollToBottom(false);
let timerId: NodeJS.Timeout | undefined;
if (status === ExperimentStatus.Running) { if (status === ExperimentStatus.Running) {
timerId = setInterval(() => {
requestExperimentPodsLog();
}, 5 * 1000);
setupSockect();
} else if (preStatusRef.current === ExperimentStatus.Running) { } else if (preStatusRef.current === ExperimentStatus.Running) {
requestExperimentPodsLog();
setTimeout(() => {
requestExperimentPodsLog();
}, 5 * 1000);
setCompleted(true);
} }
preStatusRef.current = status; preStatusRef.current = status;
return () => {
if (timerId) {
clearInterval(timerId);
timerId = undefined;
}
};
}, [status]); }, [status]);


// 鼠标拖到中不滚动到底部
useEffect(() => { useEffect(() => {
const mouseDown = () => { const mouseDown = () => {
setIsMouseDown(true); setIsMouseDown(true);
}; };

const mouseUp = () => { const mouseUp = () => {
setIsMouseDown(false); setIsMouseDown(false);
}; };

document.addEventListener('mousedown', mouseDown); document.addEventListener('mousedown', mouseDown);
document.addEventListener('mouseup', mouseUp); document.addEventListener('mouseup', mouseUp);

return () => { return () => {
document.removeEventListener('mousedown', mouseDown); document.removeEventListener('mousedown', mouseDown);
document.removeEventListener('mouseup', mouseUp); document.removeEventListener('mouseup', mouseUp);
closeSocket();
}; };
}, []); }, []);


@@ -140,6 +131,84 @@ function LogGroup({
requestExperimentPodsLog(); requestExperimentPodsLog();
}; };


// 建立 socket 连接
const setupSockect = () => {
let { host } = location;

if (process.env.NODE_ENV === 'development') {
host = '172.20.32.181:31213';
}
const socket = new WebSocket(
`ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`,
);

socket.addEventListener('open', () => {
// console.log('WebSocket is open now.');
});

socket.addEventListener('close', (event) => {
// console.log('WebSocket is closed:', event);
if (event.code !== 1000 && retryRef.current > 0) {
retryRef.current -= 1;
setTimeout(() => {
setupSockect();
}, 2 * 1000);
}
});

socket.addEventListener('error', (event) => {
console.error('WebSocket error observed:', event);
});

socket.addEventListener('message', (event) => {
if (!event.data) {
return;
}
try {
const data = JSON.parse(event.data);
const streams = data.streams;
if (!streams || !Array.isArray(streams)) {
return;
}
let startTime = start_time;
const logContent = streams.reduce((result, item) => {
const values = item.values;
return (
result +
values.reduce((prev: string, cur: [string, string]) => {
const [time, value] = cur;
startTime = time;
const str = `[${dayjs(Number(time) / 1.0e6).format('YYYY-MM-DD HH:mm:ss')}] ${value}`;
return prev + str;
}, '')
);
}, '');
const logDetail: Log = {
start_time: startTime!,
log_content: logContent,
pod_name: pod_name,
};
setLogList((oldList) => oldList.concat(logDetail));
if (!isMouseDownRef.current && logContent) {
setTimeout(() => {
scrollToBottom();
}, 100);
}
} catch (error) {
console.error('JSON parse error: ', error);
}
});

socketRef.current = socket;
};

const closeSocket = () => {
if (socketRef.current) {
socketRef.current.close(1000, 'completed');
socketRef.current = undefined;
}
};

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 =


+ 37
- 17
react-ui/src/pages/Experiment/components/LogList/index.tsx View File

@@ -1,16 +1,22 @@
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { ExperimentLog } from '@/pages/Experiment/Info/props';
import { getQueryByExperimentLog } from '@/services/experiment/index.js'; import { getQueryByExperimentLog } from '@/services/experiment/index.js';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import LogGroup from '../LogGroup'; import LogGroup from '../LogGroup';
import styles from './index.less'; import styles from './index.less';


export type ExperimentLog = {
log_type: 'normal' | 'resource'; // 日志类型
pod_name?: string; // 分布式名称
log_content?: string; // 日志内容
start_time?: string; // 日志开始时间
};

type LogListProps = { type LogListProps = {
instanceName?: string; // 实验实例 name
instanceNamespace?: string; // 实验实例 namespace
pipelineNodeId?: string; // 流水线节点 id
instanceName: string; // 实验实例 name
instanceNamespace: string; // 实验实例 namespace
pipelineNodeId: string; // 流水线节点 id
workflowId?: string; // 实验实例工作流 id workflowId?: string; // 实验实例工作流 id
instanceNodeStartTime?: string; // 实验实例节点开始运行时间 instanceNodeStartTime?: string; // 实验实例节点开始运行时间
instanceNodeStatus?: ExperimentStatus; instanceNodeStatus?: ExperimentStatus;
@@ -25,23 +31,30 @@ function LogList({
instanceNodeStatus, instanceNodeStatus,
}: LogListProps) { }: LogListProps) {
const [logList, setLogList] = useState<ExperimentLog[]>([]); const [logList, setLogList] = useState<ExperimentLog[]>([]);
const preStatusRef = useRef<ExperimentStatus | undefined>(undefined);
const retryRef = useRef(3);


useEffect(() => { useEffect(() => {
if (workflowId) {
const start_time = dayjs(instanceNodeStartTime).valueOf() * 1.0e6;
const params = {
task_id: pipelineNodeId,
component_id: workflowId,
name: instanceName,
namespace: instanceNamespace,
start_time: start_time,
};
getExperimentLog(params, start_time);
if (
instanceNodeStatus &&
instanceNodeStatus !== ExperimentStatus.Pending &&
(!preStatusRef.current || preStatusRef.current === ExperimentStatus.Pending)
) {
getExperimentLog();
} }
}, [workflowId, instanceNodeStartTime]);
preStatusRef.current = instanceNodeStatus;
}, [instanceNodeStatus]);


// 获取实验日志 // 获取实验日志
const getExperimentLog = async (params: any, start_time: number) => {
const getExperimentLog = async () => {
const start_time = dayjs(instanceNodeStartTime).valueOf() * 1.0e6;
const params = {
task_id: pipelineNodeId,
component_id: workflowId,
name: instanceName,
namespace: instanceNamespace,
start_time: start_time,
};
const [res] = await to(getQueryByExperimentLog(params)); const [res] = await to(getQueryByExperimentLog(params));
if (res && res.data) { if (res && res.data) {
const { log_type, pods, log_detail } = res.data; const { log_type, pods, log_detail } = res.data;
@@ -62,6 +75,13 @@ function LogList({
})); }));
setLogList(list); setLogList(list);
} }
} else {
if (retryRef.current > 0) {
retryRef.current -= 1;
setTimeout(() => {
getExperimentLog();
}, 2 * 1000);
}
} }
}; };




+ 1
- 1
react-ui/src/pages/Experiment/index.jsx View File

@@ -127,7 +127,7 @@ function Experiment() {
} }
}); });
} catch (error) { } catch (error) {
console.log(error);
console.error('JSON parse error: ', error);
} }
} }
}; };


+ 4
- 6
react-ui/src/pages/Pipeline/editPipeline/index.jsx View File

@@ -18,7 +18,7 @@ import { findAllParentNodes } from './utils';
let graph = null; let graph = null;


const EditPipeline = () => { const EditPipeline = () => {
const navgite = useNavigate();
const navigate = useNavigate();
const locationParams = useParams(); //新版本获取路由参数接口 const locationParams = useParams(); //新版本获取路由参数接口
const graphRef = useRef(); const graphRef = useRef();
const paramsDrawerRef = useRef(); const paramsDrawerRef = useRef();
@@ -103,10 +103,8 @@ const EditPipeline = () => {


setTimeout(() => { setTimeout(() => {
const data = graph.save(); const data = graph.save();
console.log(data);
const errorNode = data.nodes.find((item) => {
return item.formError === true;
});
// console.log(data);
const errorNode = data.nodes.find((item) => item.formError === true);
if (errorNode) { if (errorNode) {
message.error(`【${errorNode.label}】节点必填项必须配置`); message.error(`【${errorNode.label}】节点必填项必须配置`);
const graphNode = graph.findById(errorNode.id); const graphNode = graph.findById(errorNode.id);
@@ -124,7 +122,7 @@ const EditPipeline = () => {
message.success('保存成功'); message.success('保存成功');
setTimeout(() => { setTimeout(() => {
if (val) { if (val) {
navgite({ pathname: `/pipeline/template` });
navigate({ pathname: `/pipeline/template` });
} }
}, 500); }, 500);
}); });


+ 4
- 4
react-ui/src/pages/Pipeline/editPipeline/props.tsx View File

@@ -46,7 +46,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const control_strategy = JSON.stringify(fields.control_strategy); const control_strategy = JSON.stringify(fields.control_strategy);
const in_parameters = JSON.stringify(fields.in_parameters); const in_parameters = JSON.stringify(fields.in_parameters);
const out_parameters = JSON.stringify(fields.out_parameters); const out_parameters = JSON.stringify(fields.out_parameters);
console.log('getFieldsValue', fields);
// console.log('getFieldsValue', fields);


const res = { const res = {
...stagingItem, ...stagingItem,
@@ -57,7 +57,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
formError: !!error, formError: !!error,
}; };


console.log('res', res);
// console.log('res', res);
onFormChange(res); onFormChange(res);
} }
}; };
@@ -79,7 +79,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
out_parameters: JSON.parse(model.out_parameters), out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy), control_strategy: JSON.parse(model.control_strategy),
}; };
console.log('model', nodeData);
// console.log('model', nodeData);
setStagingItem({ setStagingItem({
...nodeData, ...nodeData,
}); });
@@ -91,7 +91,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
form.validateFields(); form.validateFields();
} }
} catch (error) { } catch (error) {
console.log(error);
console.error('JSON.parse error: ', error);
} }
setOpen(true); setOpen(true);




+ 1
- 7
react-ui/src/pages/Pipeline/index.jsx View File

@@ -71,9 +71,6 @@ const Pipeline = () => {
}); });
} }
}; };
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const pageOption = useRef({ page: 1, size: 10 }); const pageOption = useRef({ page: 1, size: 10 });
const paginationProps = { const paginationProps = {
showSizeChanger: true, showSizeChanger: true,
@@ -86,7 +83,7 @@ const Pipeline = () => {
}; };
// 当前页面切换 // 当前页面切换
const paginationChange = async (current, size) => { const paginationChange = async (current, size) => {
console.log('page', current, size);
// console.log('page', current, size);
pageOption.current = { pageOption.current = {
page: current, page: current,
size: size, size: size,
@@ -176,7 +173,6 @@ const Pipeline = () => {
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
console.log(record);
cloneWorkflow(record.id).then((ret) => { cloneWorkflow(record.id).then((ret) => {
if (ret.code === 200) { if (ret.code === 200) {
message.success('复制成功'); message.success('复制成功');
@@ -214,7 +210,6 @@ const Pipeline = () => {
title: '删除后,该流水线将不可恢复', title: '删除后,该流水线将不可恢复',
content: '是否确认删除?', content: '是否确认删除?',
onOk: () => { onOk: () => {
console.log(record);
removeWorkflow(record.id).then((ret) => { removeWorkflow(record.id).then((ret) => {
if (ret.code === 200) { if (ret.code === 200) {
message.success('删除成功'); message.success('删除成功');
@@ -276,7 +271,6 @@ const Pipeline = () => {
remember: true, remember: true,
}} }}
onFinish={onFinish} onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off" autoComplete="off"
> >
<Form.Item <Form.Item


+ 1
- 1
react-ui/src/pages/System/Operlog/detail.tsx View File

@@ -21,7 +21,7 @@ const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => {


const intl = useIntl(); const intl = useIntl();
const handleOk = () => { const handleOk = () => {
console.log('handle ok');
// console.log('handle ok');
}; };
const handleCancel = () => { const handleCancel = () => {
props.onCancel(); props.onCancel();


+ 1
- 1
react-ui/src/pages/System/Role/components/DataScope.tsx View File

@@ -216,7 +216,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
checkedKeys={deptIds} checkedKeys={deptIds}
defaultCheckedKeys={deptCheckedKeys} defaultCheckedKeys={deptCheckedKeys}
onCheck={(checkedKeys: any, checkInfo: any) => { onCheck={(checkedKeys: any, checkInfo: any) => {
console.log(checkedKeys, checkInfo);
// console.log(checkedKeys, checkInfo);
if (checkStrictly) { if (checkStrictly) {
return setDeptIds(checkedKeys.checked); return setDeptIds(checkedKeys.checked);
} else { } else {


+ 0
- 2
react-ui/src/pages/User/Login/index.tsx View File

@@ -210,12 +210,10 @@ const Login: React.FC = () => {
setSessionToken(response.data?.access_token, response.data?.access_token, expireTime); setSessionToken(response.data?.access_token, response.data?.access_token, expireTime);
message.success(defaultLoginSuccessMessage); message.success(defaultLoginSuccessMessage);
await fetchUserInfo(); await fetchUserInfo();
console.log('login ok');
const urlParams = new URL(window.location.href).searchParams; const urlParams = new URL(window.location.href).searchParams;
history.push(urlParams.get('redirect') || '/'); history.push(urlParams.get('redirect') || '/');
return; return;
} else { } else {
console.log(response.msg);
clearSessionToken(); clearSessionToken();
// 如果失败去设置用户错误信息 // 如果失败去设置用户错误信息
setUserLoginState({ ...response, type }); setUserLoginState({ ...response, type });


+ 9
- 6
react-ui/src/requestConfig.ts View File

@@ -3,14 +3,17 @@
* @Date: 2024-03-25 13:52:54 * @Date: 2024-03-25 13:52:54
* @Description: 网络请求配置,详情请参考 https://umijs.org/docs/max/request * @Description: 网络请求配置,详情请参考 https://umijs.org/docs/max/request
*/ */
import type { AxiosRequestConfig, AxiosResponse, RequestConfig } from '@umijs/max';
import type { AxiosRequestConfig, AxiosResponse, RequestConfig, RequestOptions } from '@umijs/max';
import { message } from 'antd'; import { message } from 'antd';
import { clearSessionToken, getAccessToken } from './access'; import { clearSessionToken, getAccessToken } from './access';
import { setRemoteMenu } from './services/session'; import { setRemoteMenu } from './services/session';
import { gotoLoginPage } from './utils/ui'; import { gotoLoginPage } from './utils/ui';


// [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. // [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead.
const popupError = (error: string) => {
const popupError = (error: string, skipErrorHandler?: boolean = false) => {
if (skipErrorHandler) {
return;
}
// 直接调用 message.error 有时候不弹出来 // 直接调用 message.error 有时候不弹出来
setTimeout(() => { setTimeout(() => {
message.error(error); message.error(error);
@@ -39,8 +42,8 @@ export const requestConfig: RequestConfig = {
responseInterceptors: [ responseInterceptors: [
[ [
(response: AxiosResponse) => { (response: AxiosResponse) => {
const { status, data } = response || {};
// console.log(message, data);
const { status, data, config } = response || {};
const skipErrorHandler = (config as RequestOptions)?.skipErrorHandler;
if (status >= 200 && status < 300) { if (status >= 200 && status < 300) {
if (data && (data instanceof Blob || data.code === 200)) { if (data && (data instanceof Blob || data.code === 200)) {
return response; return response;
@@ -51,11 +54,11 @@ export const requestConfig: RequestConfig = {
popupError('请重新登录'); popupError('请重新登录');
return Promise.reject(response); return Promise.reject(response);
} else { } else {
popupError(data?.msg ?? '请求失败');
popupError(data?.msg ?? '请求失败', skipErrorHandler);
return Promise.reject(response); return Promise.reject(response);
} }
} else { } else {
popupError('请求失败');
popupError('请求失败', skipErrorHandler);
return Promise.reject(response); return Promise.reject(response);
} }
}, },


+ 1
- 0
react-ui/src/services/experiment/index.js View File

@@ -51,6 +51,7 @@ export function getQueryByExperimentLog(data) {
return request('/api/mmp/experimentIns/realTimeLog/', { return request('/api/mmp/experimentIns/realTimeLog/', {
method: 'POST', method: 'POST',
data, data,
skipErrorHandler: true,
}); });
} }
// 查询实例节点结果 // 查询实例节点结果


+ 1
- 2
react-ui/src/services/session.ts View File

@@ -148,8 +148,7 @@ export function getMatchMenuItem(
} else { } else {
const paths = path.split('/'); const paths = path.split('/');
if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') { if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') {
console.log(item);

// console.log(item);
items.push(item); items.push(item);
} }
} }


Loading…
Cancel
Save