Browse Source

Merge pull request '合并dev-zw' (#180) from dev-zw into dev

dev-credits
cp3hnu 10 months ago
parent
commit
03ab0657a4
86 changed files with 1495 additions and 1230 deletions
  1. +2
    -1
      react-ui/.eslintignore
  2. +7
    -1
      react-ui/.eslintrc.js
  3. +92
    -0
      react-ui/.storybook/mock/websocket.mock.js
  4. +5
    -0
      react-ui/.storybook/preview.tsx
  5. +1
    -0
      react-ui/package.json
  6. +2
    -2
      react-ui/src/app.tsx
  7. +14
    -14
      react-ui/src/components/CodeSelectorModal/index.tsx
  8. +11
    -14
      react-ui/src/components/IFramePage/index.tsx
  9. +14
    -13
      react-ui/src/components/ParameterSelect/index.tsx
  10. +1
    -1
      react-ui/src/components/ResourceSelect/index.tsx
  11. +52
    -52
      react-ui/src/components/ResourceSelectorModal/index.tsx
  12. +5
    -3
      react-ui/src/hooks/index.ts
  13. +22
    -20
      react-ui/src/hooks/resource.ts
  14. +0
    -25
      react-ui/src/hooks/sessionStorage.ts
  15. +18
    -17
      react-ui/src/pages/Authorize/index.tsx
  16. +50
    -50
      react-ui/src/pages/AutoML/Create/index.tsx
  17. +9
    -9
      react-ui/src/pages/AutoML/Info/index.tsx
  18. +1
    -3
      react-ui/src/pages/AutoML/Instance/index.tsx
  19. +27
    -27
      react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx
  20. +1
    -1
      react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx
  21. +7
    -7
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  22. +8
    -8
      react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx
  23. +7
    -7
      react-ui/src/pages/CodeConfig/List/index.tsx
  24. +49
    -52
      react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx
  25. +23
    -23
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  26. +24
    -21
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  27. +18
    -18
      react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx
  28. +7
    -7
      react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
  29. +19
    -19
      react-ui/src/pages/Experiment/Comparison/index.tsx
  30. +10
    -12
      react-ui/src/pages/Experiment/Info/index.jsx
  31. +3
    -0
      react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx
  32. +1
    -1
      react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
  33. +11
    -12
      react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx
  34. +14
    -14
      react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx
  35. +97
    -84
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  36. +33
    -27
      react-ui/src/pages/Experiment/components/LogList/index.tsx
  37. +20
    -20
      react-ui/src/pages/Experiment/index.jsx
  38. +27
    -27
      react-ui/src/pages/HyperParameter/Create/index.tsx
  39. +9
    -9
      react-ui/src/pages/HyperParameter/Info/index.tsx
  40. +6
    -6
      react-ui/src/pages/HyperParameter/Instance/index.tsx
  41. +2
    -2
      react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx
  42. +8
    -8
      react-ui/src/pages/HyperParameter/components/ExperimentResult/index.tsx
  43. +2
    -11
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  44. +1
    -1
      react-ui/src/pages/Mirror/Create/index.tsx
  45. +25
    -17
      react-ui/src/pages/Mirror/Info/index.tsx
  46. +18
    -18
      react-ui/src/pages/Mirror/List/index.tsx
  47. +68
    -65
      react-ui/src/pages/Model/components/MetricsChart/index.tsx
  48. +5
    -1
      react-ui/src/pages/Model/components/ModelEvolution/index.tsx
  49. +52
    -44
      react-ui/src/pages/Model/components/ModelMetrics/index.tsx
  50. +16
    -18
      react-ui/src/pages/ModelDeployment/CreateService/index.tsx
  51. +13
    -14
      react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx
  52. +48
    -43
      react-ui/src/pages/ModelDeployment/List/index.tsx
  53. +13
    -13
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
  54. +10
    -8
      react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx
  55. +18
    -19
      react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx
  56. +10
    -10
      react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx
  57. +1
    -10
      react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx
  58. +13
    -13
      react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx
  59. +1
    -1
      react-ui/src/pages/Monitor/Job/edit.tsx
  60. +1
    -1
      react-ui/src/pages/Monitor/JobLog/index.tsx
  61. +5
    -2
      react-ui/src/pages/Pipeline/Info/index.jsx
  62. +17
    -13
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
  63. +40
    -41
      react-ui/src/pages/Pipeline/components/ModelMenu/index.tsx
  64. +51
    -47
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  65. +7
    -8
      react-ui/src/pages/Pipeline/index.jsx
  66. +1
    -1
      react-ui/src/pages/System/Config/edit.tsx
  67. +1
    -1
      react-ui/src/pages/System/Dept/edit.tsx
  68. +1
    -1
      react-ui/src/pages/System/Dict/edit.tsx
  69. +1
    -1
      react-ui/src/pages/System/DictData/edit.tsx
  70. +1
    -1
      react-ui/src/pages/System/DictData/index.tsx
  71. +1
    -1
      react-ui/src/pages/System/Menu/edit.tsx
  72. +1
    -1
      react-ui/src/pages/System/Notice/edit.tsx
  73. +1
    -1
      react-ui/src/pages/System/Post/edit.tsx
  74. +1
    -1
      react-ui/src/pages/System/Role/components/DataScope.tsx
  75. +1
    -1
      react-ui/src/pages/System/Role/edit.tsx
  76. +26
    -20
      react-ui/src/pages/System/User/components/DeptTree.tsx
  77. +1
    -1
      react-ui/src/pages/System/User/edit.tsx
  78. +2
    -1
      react-ui/src/pages/Tool/Gen/edit.tsx
  79. +2
    -1
      react-ui/src/pages/User/Login/login.tsx
  80. +39
    -37
      react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx
  81. +91
    -87
      react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx
  82. +0
    -16
      react-ui/src/state/computingResourceStore.ts
  83. +53
    -0
      react-ui/src/stories/mockData.ts
  84. +90
    -0
      react-ui/src/stories/pages/LogList.stories.tsx
  85. +8
    -2
      react-ui/src/utils/index.ts
  86. +1
    -0
      react-ui/src/utils/table.tsx

+ 2
- 1
react-ui/.eslintignore View File

@@ -5,4 +5,5 @@
public
dist
.umi
mock
mock
/src/iconfont/

+ 7
- 1
react-ui/.eslintrc.js View File

@@ -1,10 +1,16 @@
module.exports = {
extends: [require.resolve('@umijs/lint/dist/config/eslint')],
extends: [
require.resolve('@umijs/lint/dist/config/eslint'),
'plugin:react/recommended',
"plugin:react-hooks/recommended"
],
globals: {
page: true,
REACT_APP_ENV: true,
},
rules: {
'@typescript-eslint/no-use-before-define': 'off',
'react/react-in-jsx-scope': 'off',
'react/display-name': 'off'
},
};

+ 92
- 0
react-ui/.storybook/mock/websocket.mock.js View File

@@ -0,0 +1,92 @@
export const createWebSocketMock = () => {
class WebSocketMock {
constructor(url) {
this.url = url;
this.readyState = WebSocket.OPEN;
this.listeners = {};
this.count = 0;

console.log("Mock WebSocket connected to:", url);

// 模拟服务器推送消息
this.intervalId = setInterval(() => {
this.count += 1;
if (this.count > 5) {
this.count = 0;
clearInterval(this.intervalId);
return;
}
this.sendMessage(JSON.stringify(logStreamData));
}, 3000);
}

sendMessage(data) {
if (this.listeners["message"]) {
this.listeners["message"].forEach((callback) => callback({ data }));
}
}

addEventListener(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}

removeEventListener(event, callback) {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event].filter((cb) => cb !== callback);
}
}

close() {
this.readyState = WebSocket.CLOSED;
console.log("Mock WebSocket closed");
}
}

return WebSocketMock;
};

export const logStreamData = {
streams: [
{
stream: {
workflows_argoproj_io_completed: 'false',
workflows_argoproj_io_workflow: 'workflow-p2ddj',
container: 'init',
filename:
'/var/log/pods/argo_workflow-p2ddj-git-clone-f33abcda-3988047653_e31cf6be-e013-4885-9eb6-ec84f83b9ba9/init/0.log',
job: 'argo/workflow-p2ddj-git-clone-f33abcda-3988047653',
namespace: 'argo',
pod: 'workflow-p2ddj-git-clone-f33abcda-3988047653',
stream: 'stderr',
},
values: [
[
'1742179591969785990',
'time="2025-03-17T02:46:31.969Z" level=info msg="Starting Workflow Executor" version=v3.5.10\n',
],
],
},
{
stream: {
filename:
'/var/log/pods/argo_workflow-p2ddj-git-clone-f33abcda-3988047653_e31cf6be-e013-4885-9eb6-ec84f83b9ba9/init/0.log',
job: 'argo/workflow-p2ddj-git-clone-f33abcda-3988047653',
namespace: 'argo',
pod: 'workflow-p2ddj-git-clone-f33abcda-3988047653',
stream: 'stderr',
workflows_argoproj_io_completed: 'false',
workflows_argoproj_io_workflow: 'workflow-p2ddj',
container: 'init',
},
values: [
[
'1742179591973414064',
'time="2025-03-17T02:46:31.973Z" level=info msg="Using executor retry strategy" Duration=1s Factor=1.6 Jitter=0.5 Steps=5\n',
],
],
},
],
};

+ 5
- 0
react-ui/.storybook/preview.tsx View File

@@ -5,6 +5,7 @@ import type { Preview } from '@storybook/react';
import { App, ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { createWebSocketMock } from './mock/websocket.mock';
import './storybook.css';

/*
@@ -14,6 +15,10 @@ import './storybook.css';
*/
initialize();

// 替换全局 WebSocket 为 Mock 版本
// @ts-ignore
global.WebSocket = createWebSocketMock();

const preview: Preview = {
parameters: {
controls: {


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

@@ -114,6 +114,7 @@
"@umijs/max": "^4.0.66",
"cross-env": "^7.0.3",
"eslint": "^8.39.0",
"eslint-plugin-react-hooks": "~5.2.0",
"eslint-plugin-storybook": "~0.11.2",
"express": "^4.18.2",
"gh-pages": "^5.0.0",


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

@@ -168,7 +168,7 @@ export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => {
}
};

export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => {
export const patchRoutes: RuntimeConfig['patchRoutes'] = () => {
//console.log('patchRoutes', e);
};

@@ -232,7 +232,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
rowSelectedBg: 'rgba(22, 100, 255, 0.05)',
// rowSelectedBg: 'rgba(22, 100, 255, 0.05)', 固定列时,横向滑动导致重叠
};
memo.theme.components.Tabs = {
titleFontSize: 16,


+ 14
- 14
react-ui/src/components/CodeSelectorModal/index.tsx View File

@@ -33,23 +33,23 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
const [inputText, setInputText] = useState<string | undefined>(undefined);

useEffect(() => {
// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText || undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

getDataList();
}, [pagination, searchText]);

// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText || undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

// 搜索
const handleSearch = (value: string) => {
setSearchText(value);


+ 11
- 14
react-ui/src/components/IFramePage/index.tsx View File

@@ -45,23 +45,20 @@ type IframePageProps = {
function IframePage({ type, className, style }: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);

useEffect(() => {
requestIframeUrl();
return () => {
if (type === IframePageType.DevEnv) {
SessionStorage.removeItem(SessionStorage.editorUrlKey);
const requestIframeUrl = async () => {
setLoading(true);
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
}
};
}, []);
const requestIframeUrl = async () => {
setLoading(true);
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
}
};

requestIframeUrl();
}, [type]);

const hideLoading = () => {
setLoading(false);


+ 14
- 13
react-ui/src/components/ParameterSelect/index.tsx View File

@@ -39,8 +39,20 @@ function ParameterSelect({
const valueText = typeof value === 'object' && value !== null ? value.value : value;

useEffect(() => {
// 获取下拉数据
const getSelectOptions = async () => {
if (!propsConfig) {
return;
}
const getOptions = propsConfig.getOptions;
const [res] = await to(getOptions());
if (res) {
setOptions(res);
}
};

getSelectOptions();
}, []);
}, [propsConfig]);

const handleChange = (text: string) => {
if (typeof value === 'object' && value !== null) {
@@ -53,18 +65,7 @@ function ParameterSelect({
}
};

// 获取下拉数据
const getSelectOptions = async () => {
if (!propsConfig) {
return;
}
const getOptions = propsConfig.getOptions;
const [res] = await to(getOptions());
if (res) {
setOptions(res);
}
};

// 只用于展示,FormInfo 组件带有 Tooltip
if (display) {
return (
<FormInfo


+ 1
- 1
react-ui/src/components/ResourceSelect/index.tsx View File

@@ -72,7 +72,7 @@ function ResourceSelect({
]) as ResourceSelectorResponse;
setSelectedResource(originResource);
}
}, [value]);
}, [value, type]);

const selectResource = () => {
const resource = selectedResource;


+ 52
- 52
react-ui/src/components/ResourceSelectorModal/index.tsx View File

@@ -91,16 +91,7 @@ function ResourceSelectorModal({
const treeRef = useRef<TreeRef>(null);
const config = selectorTypeConfig[type];

useEffect(() => {
setExpandedKeys([]);
setCheckedKeys([]);
setLoadedKeys([]);
setFiles([]);
setVersionPath('');
setSearchText('');
getTreeData();
}, [activeTab, type]);

// 搜索
const treeData = useMemo(
() =>
originTreeData.filter((v) =>
@@ -109,19 +100,45 @@ function ResourceSelectorModal({
[originTreeData, searchText],
);

// 获取数据集\模型\镜像列表
const getTreeData = async () => {
const isPublic = activeTab === CommonTabKeys.Private ? false : true;
const [res] = await to(config.getList(isPublic));
if (res) {
setOriginTreeData(res);
useEffect(() => {
// 获取数据集\模型\镜像列表
const getTreeData = async () => {
const isPublic = activeTab === CommonTabKeys.Private ? false : true;
const [res] = await to(config.getList(isPublic));
if (res) {
setOriginTreeData(res);

// 恢复上一次的 Expand 操作
restoreLastExpand();
} else {
setOriginTreeData([]);
}
};
// 恢复上一次的 Expand 操作
setFirstLoadList(true);
} else {
setOriginTreeData([]);
}
};

setExpandedKeys([]);
setCheckedKeys([]);
setLoadedKeys([]);
setFiles([]);
setVersionPath('');
setSearchText('');
getTreeData();
}, [activeTab, config]);

useEffect(() => {
// 恢复上一次的 Expand 操作
// 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys
// fisrtLoadList 标志位
const restoreLastExpand = () => {
if (firstLoadList && Array.isArray(defaultExpandedKeys) && defaultExpandedKeys.length > 0) {
setExpandedKeys(defaultExpandedKeys);
// 延时滑动到 defaultExpandedKeys,不然不会加载 defaultExpandedKeys,不然不会加载版本
setTimeout(() => {
treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' });
}, 100);
}
};
restoreLastExpand();
}, [firstLoadList, defaultExpandedKeys]);

// 获取数据集\模型\镜像版本列表
const getVersions = async (parentId: string, parentNode: any) => {
@@ -136,10 +153,10 @@ function ResourceSelectorModal({
setLoadedKeys((prev) => prev.concat(parentId));
}

// 恢复上一次的 Check 操作
// 恢复上一次的 Check 操作,需要延时以便 TreeData 更新完
setTimeout(() => {
restoreLastCheck(parentId, res);
}, 300);
}, 100);
} else {
setExpandedKeys([]);
return Promise.reject(error);
@@ -158,7 +175,7 @@ function ResourceSelectorModal({
}
};

// 动态加载 tree children
// 展开时,动态加载 tree children
const onLoadData = ({ key, children, ...rest }: TreeDataNode) => {
if (children) {
return Promise.resolve();
@@ -187,42 +204,25 @@ function ResourceSelectorModal({
}
};

// 恢复上一次的 Expand 操作
// 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys
// fisrtLoadList 标志位
const restoreLastExpand = () => {
if (!firstLoadList && defaultExpandedKeys.length > 0) {
setTimeout(() => {
setExpandedKeys(defaultExpandedKeys);
setFirstLoadList(true);
setTimeout(() => {
treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' });
}, 100);
}, 0);
}
};

// 恢复上一次的 Check 操作
// 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口
// fisrtLoadVersions 标志位
const restoreLastCheck = (parentId: string, versions: TreeDataNode[]) => {
if (!firstLoadVersions && defaultCheckedKeys.length > 0) {
if (!firstLoadVersions && Array.isArray(defaultCheckedKeys) && defaultCheckedKeys.length > 0) {
const last = defaultCheckedKeys[0] as string;
const { id } = getIdAndVersion(last);
// 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致
if (id === parentId) {
setCheckedKeys(defaultCheckedKeys);
const parentNode = versions.find((v) => v.key === last);
getFiles(last, parentNode);
setFirstLoadVersions(true);
setTimeout(() => {
setCheckedKeys(defaultCheckedKeys);
const parentNode = versions.find((v) => v.key === last);
getFiles(last, parentNode);
setFirstLoadVersions(true);
setTimeout(() => {
treeRef?.current?.scrollTo({
key: defaultCheckedKeys[0],
align: 'bottom',
});
}, 100);
}, 0);
treeRef?.current?.scrollTo({
key: defaultCheckedKeys[0],
align: 'bottom',
});
}, 100);
}
}
};


+ 5
- 3
react-ui/src/hooks/index.ts View File

@@ -105,6 +105,7 @@ export function useDomSize<T extends HTMLElement>(
return () => {
window.removeEventListener('resize', debounceFunc);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [domRef, ...deps]);

return [domRef, { width, height }] as const;
@@ -136,10 +137,10 @@ export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => {
* Executes the effect function when the specified condition is true.
*
* @param effect - The effect function to execute.
* @param deps - The dependencies for the effect.
* @param when - The condition to trigger the effect.
* @param deps - The dependencies for the effect.
*/
export const useEffectWhen = (effect: () => void, deps: React.DependencyList, when: boolean) => {
export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => {
const requestFns = useRef<(() => void)[]>([]);
useEffect(() => {
if (when) {
@@ -147,6 +148,7 @@ export const useEffectWhen = (effect: () => void, deps: React.DependencyList, wh
} else {
requestFns.current.splice(0, 1, effect);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);

useEffect(() => {
@@ -185,7 +187,7 @@ export const useCheck = <T>(list: T[]) => {
}
});
},
[selected, isSingleChecked],
[isSingleChecked],
);

return [


+ 22
- 20
react-ui/src/hooks/resource.ts View File

@@ -5,40 +5,39 @@
*/

import { getComputingResourceReq } from '@/services/pipeline';
import computingResourceState, { setComputingResource } from '@/state/computingResourceStore';
import { ComputingResource } from '@/types';
import { to } from '@/utils/promise';
import { type SelectProps } from 'antd';
import { useCallback, useEffect, useState } from 'react';
import { useSnapshot } from 'umi';

const computingResource: ComputingResource[] = [];

// 获取资源规格
export function useComputingResource() {
const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]);
const snap = useSnapshot(computingResourceState);

useEffect(() => {
if (snap.computingResource.length > 0) {
setResourceStandardList(snap.computingResource as ComputingResource[]);
// 获取资源规格列表数据
const getComputingResource = async () => {
const params = {
page: 0,
size: 1000,
resource_type: '',
};
const [res] = await to(getComputingResourceReq(params));
if (res && res.data && res.data.content) {
setResourceStandardList(res.data.content);
computingResource.splice(0, computingResource.length, ...res.data.content);
}
};

if (computingResource.length > 0) {
setResourceStandardList(computingResource);
} else {
getComputingResource();
}
}, []);

// 获取资源规格列表数据
const getComputingResource = useCallback(async () => {
const params = {
page: 0,
size: 1000,
resource_type: '',
};
const [res] = await to(getComputingResourceReq(params));
if (res && res.data && res.data.content) {
setResourceStandardList(res.data.content);
setComputingResource(res.data.content);
}
}, []);

// 过滤资源规格
const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] =
useCallback((input: string, option?: ComputingResource) => {
@@ -50,7 +49,10 @@ export function useComputingResource() {

// 根据 standard 获取 description
const getDescription = useCallback(
(standard: string) => {
(standard?: string) => {
if (!standard) {
return undefined;
}
return resourceStandardList.find((item) => item.standard === standard)?.description;
},
[resourceStandardList],


+ 0
- 25
react-ui/src/hooks/sessionStorage.ts View File

@@ -1,25 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-11-06 14:53:37
* @Description: SessionStorage hook
*/

import SessionStorage from '@/utils/sessionStorage';
import { useEffect, useState } from 'react';

// 读取缓存数据,组件卸载时清除缓存
export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) {
const [storage, setStorage] = useState<T>(initialValue);

useEffect(() => {
const res = SessionStorage.getItem(key, isObject);
if (res) {
setStorage(res);
}
return () => {
SessionStorage.removeItem(key);
};
}, []);

return [storage];
}

+ 18
- 17
react-ui/src/pages/Authorize/index.tsx View File

@@ -3,7 +3,7 @@ import { loginByOauth2Req } from '@/services/auth';
import { to } from '@/utils/promise';
import { history, useModel, useSearchParams } from '@umijs/max';
import { message } from 'antd';
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import { flushSync } from 'react-dom';
import styles from './index.less';

@@ -12,12 +12,21 @@ function Authorize() {
const [searchParams] = useSearchParams();
const code = searchParams.get('code');
const redirect = searchParams.get('redirect');
useEffect(() => {
loginByOauth2();
}, []);

const fetchUserInfo = useCallback(async () => {
const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) {
flushSync(() => {
setInitialState((s) => ({
...s,
currentUser: userInfo,
}));
});
}
}, [initialState, setInitialState]);

// 登录
const loginByOauth2 = async () => {
const loginByOauth2 = useCallback(async () => {
const params = {
code,
};
@@ -29,19 +38,11 @@ function Authorize() {
await fetchUserInfo();
history.push(redirect || '/');
}
};
}, [fetchUserInfo, redirect, code]);

const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) {
flushSync(() => {
setInitialState((s) => ({
...s,
currentUser: userInfo,
}));
});
}
};
useEffect(() => {
loginByOauth2();
}, [loginByOauth2]);

return <div className={styles.container}></div>;
}


+ 50
- 50
react-ui/src/pages/AutoML/Create/index.tsx View File

@@ -29,60 +29,60 @@ function CreateAutoML() {
const isCopy = pathname.includes('copy');

useEffect(() => {
// 获取服务详情
const getAutoMLInfo = async (id: number) => {
const [res] = await to(getAutoMLInfoReq({ id }));
if (res && res.data) {
const autoMLInfo: AutoMLData = res.data;
const {
include_classifier: include_classifier_str,
include_feature_preprocessor: include_feature_preprocessor_str,
include_regressor: include_regressor_str,
exclude_classifier: exclude_classifier_str,
exclude_feature_preprocessor: exclude_feature_preprocessor_str,
exclude_regressor: exclude_regressor_str,
metrics: metrics_str,
ml_name: ml_name_str,
...rest
} = autoMLInfo;
const include_classifier = include_classifier_str?.split(',').filter(Boolean);
const include_feature_preprocessor = include_feature_preprocessor_str
?.split(',')
.filter(Boolean);
const include_regressor = include_regressor_str?.split(',').filter(Boolean);
const exclude_classifier = exclude_classifier_str?.split(',').filter(Boolean);
const exclude_feature_preprocessor = exclude_feature_preprocessor_str
?.split(',')
.filter(Boolean);
const exclude_regressor = exclude_regressor_str?.split(',').filter(Boolean);
const metricsObj = safeInvoke(parseJsonText)(metrics_str) ?? {};
const metrics = Object.entries(metricsObj).map(([key, value]) => ({
name: key,
value,
}));
const ml_name = isCopy ? `${ml_name_str}-copy` : ml_name_str;

const formData = {
...rest,
include_classifier,
include_feature_preprocessor,
include_regressor,
exclude_classifier,
exclude_feature_preprocessor,
exclude_regressor,
metrics,
ml_name,
};

form.setFieldsValue(formData);
}
};

// 编辑,复制
if (id && !Number.isNaN(id)) {
getAutoMLInfo(id);
}
}, [id]);

// 获取服务详情
const getAutoMLInfo = async (id: number) => {
const [res] = await to(getAutoMLInfoReq({ id }));
if (res && res.data) {
const autoMLInfo: AutoMLData = res.data;
const {
include_classifier: include_classifier_str,
include_feature_preprocessor: include_feature_preprocessor_str,
include_regressor: include_regressor_str,
exclude_classifier: exclude_classifier_str,
exclude_feature_preprocessor: exclude_feature_preprocessor_str,
exclude_regressor: exclude_regressor_str,
metrics: metrics_str,
ml_name: ml_name_str,
...rest
} = autoMLInfo;
const include_classifier = include_classifier_str?.split(',').filter(Boolean);
const include_feature_preprocessor = include_feature_preprocessor_str
?.split(',')
.filter(Boolean);
const include_regressor = include_regressor_str?.split(',').filter(Boolean);
const exclude_classifier = exclude_classifier_str?.split(',').filter(Boolean);
const exclude_feature_preprocessor = exclude_feature_preprocessor_str
?.split(',')
.filter(Boolean);
const exclude_regressor = exclude_regressor_str?.split(',').filter(Boolean);
const metricsObj = safeInvoke(parseJsonText)(metrics_str) ?? {};
const metrics = Object.entries(metricsObj).map(([key, value]) => ({
name: key,
value,
}));
const ml_name = isCopy ? `${ml_name_str}-copy` : ml_name_str;

const formData = {
...rest,
include_classifier,
include_feature_preprocessor,
include_regressor,
exclude_classifier,
exclude_feature_preprocessor,
exclude_regressor,
metrics,
ml_name,
};

form.setFieldsValue(formData);
}
};
}, [id, form, isCopy]);

// 创建、更新、复制实验
const createExperiment = async (formData: FormData) => {


+ 9
- 9
react-ui/src/pages/AutoML/Info/index.tsx View File

@@ -19,18 +19,18 @@ function AutoMLInfo() {
const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined);

useEffect(() => {
// 获取自动机器学习详情
const getAutoMLInfo = async () => {
const [res] = await to(getAutoMLInfoReq({ id: autoMLId }));
if (res && res.data) {
setAutoMLInfo(res.data);
}
};

if (autoMLId) {
getAutoMLInfo();
}
}, []);

// 获取自动机器学习详情
const getAutoMLInfo = async () => {
const [res] = await to(getAutoMLInfoReq({ id: autoMLId }));
if (res && res.data) {
setAutoMLInfo(res.data);
}
};
}, [autoMLId]);

return (
<div className={styles['auto-ml-info']}>


+ 1
- 3
react-ui/src/pages/AutoML/Instance/index.tsx View File

@@ -39,6 +39,7 @@ function AutoMLInstance() {
return () => {
closeSSE();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// 获取实验实例详情
@@ -83,9 +84,6 @@ function AutoMLInstance() {

const setupSSE = (name: string, namespace: string) => {
const { origin } = location;
// if (process.env.NODE_ENV === 'development') {
// origin = 'http://172.20.32.197:31213';
// }
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,


+ 27
- 27
react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx View File

@@ -25,36 +25,36 @@ type TableData = {
function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) {
const [tableData, setTableData] = useState<TableData[]>([]);
useEffect(() => {
// 获取实验运行历史记录
const getHistoryFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
const data: any[] = res.data;
const list: TableData[] = data.map((item) => {
return {
id: item[0]?.[0],
accuracy: item[1]?.[5]?.accuracy,
duration: item[1]?.[5]?.duration,
train_loss: item[1]?.[5]?.train_loss,
status: item[1]?.[2]?.['__enum__']?.split('.')?.[1],
};
});
list.forEach((item) => {
if (!item.id) return;
const config = (res as any).configs?.[item.id];
item.feature = config?.['feature_preprocessor:__choice__'];
item.althorithm = isClassification
? config?.['classifier:__choice__']
: config?.['regressor:__choice__'];
});
setTableData(list);
}
};

if (fileUrl) {
getHistoryFile();
}
}, [fileUrl]);

// 获取实验运行历史记录
const getHistoryFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
const data: any[] = res.data;
const list: TableData[] = data.map((item) => {
return {
id: item[0]?.[0],
accuracy: item[1]?.[5]?.accuracy,
duration: item[1]?.[5]?.duration,
train_loss: item[1]?.[5]?.train_loss,
status: item[1]?.[2]?.['__enum__']?.split('.')?.[1],
};
});
list.forEach((item) => {
if (!item.id) return;
const config = (res as any).configs?.[item.id];
item.feature = config?.['feature_preprocessor:__choice__'];
item.althorithm = isClassification
? config?.['classifier:__choice__']
: config?.['regressor:__choice__'];
});
setTableData(list);
}
};
}, [fileUrl, isClassification]);

const columns: TableProps<TableData>['columns'] = [
{


+ 1
- 1
react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx View File

@@ -53,7 +53,7 @@ function ExperimentInstanceComponent({
if (allIntanceIds.length === 0) {
setSelectedIns([]);
}
}, [experimentInsList]);
}, [allIntanceIds, setSelectedIns]);

// 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => {


+ 7
- 7
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -28,7 +28,7 @@ import {
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import ExperimentInstance from '../ExperimentInstance';
import { ExperimentListType, experimentListConfig } from './config';
import styles from './index.less';
@@ -58,12 +58,8 @@ function ExperimentList({ type }: ExperimentListProps) {
);
const config = experimentListConfig[type];

useEffect(() => {
getAutoMLList();
}, [pagination, searchText]);

// 获取自主机器学习或超参数自动优化列表
const getAutoMLList = async () => {
const getAutoMLList = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
@@ -76,7 +72,11 @@ function ExperimentList({ type }: ExperimentListProps) {
setTableData(content);
setTotal(totalElements);
}
};
}, [pagination, searchText, config]);

useEffect(() => {
getAutoMLList();
}, [getAutoMLList]);

// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {


+ 8
- 8
react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx View File

@@ -22,19 +22,19 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp
}, [imageUrl]);

useEffect(() => {
// 获取实验运行历史记录
const getResultFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
setResult(res as any as string);
}
};

if (fileUrl) {
getResultFile();
}
}, [fileUrl]);

// 获取实验运行历史记录
const getResultFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
setResult(res as any as string);
}
};

return (
<div className={styles['experiment-result']}>
<InfoGroup title="实验结果" height={420} width="100%">


+ 7
- 7
react-ui/src/pages/CodeConfig/List/index.tsx View File

@@ -13,7 +13,7 @@ import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { App, Button, Input, Pagination, PaginationProps } from 'antd';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal';
import CodeConfigItem from '../components/CodeConfigItem';
import styles from './index.less';
@@ -50,12 +50,8 @@ function CodeConfigList() {
const [inputText, setInputText] = useState<string | undefined>(undefined);
const { message } = App.useApp();

useEffect(() => {
getDataList();
}, [pagination, searchText]);

// 获取数据请求
const getDataList = async () => {
const getDataList = useCallback(async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
@@ -69,7 +65,11 @@ function CodeConfigList() {
setDataList([]);
setTotal(0);
}
};
}, [pagination, searchText]);

useEffect(() => {
getDataList();
}, [getDataList]);

// 删除请求
const deleteRecord = async (id: number) => {


+ 49
- 52
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

@@ -18,7 +18,7 @@ import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { useParams, useSearchParams } from '@umijs/max';
import { App, Button, Flex, Select, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import AddVersionModal from '../AddVersionModal';
import ResourceIntro from '../ResourceIntro';
import ResourceVersion from '../ResourceVersion';
@@ -45,7 +45,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
// 模型演化传入的 tab
const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction;
// 模型演化传入的版本
let versionParam = searchParams.get('version');
const versionParam = searchParams.get('version');
const name = searchParams.get('name') || '';
const owner = searchParams.get('owner') || '';
const identifier = searchParams.get('identifier') || '';
@@ -57,63 +57,60 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
const typeName = config.name; // 数据集/模型
const { message } = App.useApp();

useEffect(() => {
getVersionList();
}, [resourceId, owner, identifier]);

useEffect(() => {
if (version) {
getResourceDetail({
id: resourceId,
owner,
name,
identifier,
version,
is_public: is_public,
});
}
}, [version]);

// 获取详情
const getResourceDetail = async (params: {
owner: string;
name: string;
id: number;
identifier: string;
version?: string;
is_public: boolean;
}) => {
const getResourceDetail = useCallback(async () => {
const params = {
id: resourceId,
owner,
name,
identifier,
version,
is_public,
};
const request = config.getInfo;
const [res] = await to(request(params));
if (res && res.data) {
setInfo(res.data);
}
};
}, [config, resourceId, owner, name, identifier, version, is_public]);

// 获取版本列表
const getVersionList = async () => {
const request = config.getVersions;
const [res] = await to(
request({
owner,
identifier,
}),
);
if (res && res.data && res.data.length > 0) {
setVersionList(res.data);
if (
versionParam &&
res.data.find((item: ResourceVersionData) => item.name === versionParam)
) {
setVersion(versionParam);
versionParam = null;
const getVersionList = useCallback(
async (refresh: boolean) => {
const request = config.getVersions;
const [res] = await to(
request({
owner,
identifier,
}),
);
if (res && res.data && res.data.length > 0) {
setVersionList(res.data);
if (
!refresh &&
versionParam &&
res.data.find((item: ResourceVersionData) => item.name === versionParam)
) {
setVersion(versionParam);
} else {
setVersion(res.data[0].name);
}
} else {
setVersion(res.data[0].name);
setVersion(undefined);
}
} else {
setVersion(undefined);
},
[config, owner, identifier, versionParam],
);

useEffect(() => {
if (version) {
getResourceDetail();
}
};
}, [version, getResourceDetail]);

useEffect(() => {
getVersionList(false);
}, [getVersionList]);

// 新建版本
const showModal = () => {
@@ -125,7 +122,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
identifier: info.identifier,
is_public: is_public,
onOk: () => {
getVersionList();
getVersionList(true);
close();
},
});
@@ -172,12 +169,12 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
const [res] = await to(request(params));
if (res) {
message.success('删除成功');
getVersionList();
getVersionList(true);
}
};

// 处理删除
const hanldeDelete = () => {
const handleDelete = () => {
modalConfirm({
title: '删除后,该版本将不可恢复',
content: '是否确认删除?',
@@ -268,7 +265,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
<Button
type="default"
style={{ marginLeft: 'auto', marginRight: 0 }}
onClick={hanldeDelete}
onClick={handleDelete}
icon={<KFIcon type="icon-shanchu" />}
disabled={!version}
danger


+ 23
- 23
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -8,7 +8,7 @@ import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { App, Button, Input, Pagination, PaginationProps } from 'antd';
import { pick } from 'lodash';
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { Ref, forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react';
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config';
import AddDatasetModal from '../AddDatasetModal';
import ResourceItem from '../ResourceItem';
@@ -58,9 +58,30 @@ function ResourceList(
const { message } = App.useApp();
const config = resourceConfig[resourceType];

// 获取数据请求
const getDataList = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
is_public: isPublic,
[config.typeParamKey]: dataType,
[config.tagParamKey]: dataTag,
name: searchText || undefined,
};
const request = config.getList;
const [res] = await to(request(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
} else {
setDataList([]);
setTotal(0);
}
}, [dataType, dataTag, pagination, searchText, isPublic, config]);

useEffect(() => {
getDataList();
}, [resourceType, dataType, dataTag, pagination, searchText, isPublic]);
}, [getDataList]);

useImperativeHandle(
ref,
@@ -81,27 +102,6 @@ function ResourceList(
[],
);

// 获取数据请求
const getDataList = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
is_public: isPublic,
[config.typeParamKey]: dataType,
[config.tagParamKey]: dataTag,
name: searchText || undefined,
};
const request = config.getList;
const [res] = await to(request(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
} else {
setDataList([]);
setTotal(0);
}
};

// 删除请求
const deleteRecord = async (params: { owner: string; identifier: string; repo_id?: number }) => {
const request = config.deleteRecord;


+ 24
- 21
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -3,7 +3,7 @@ import { useCacheState } from '@/hooks/pageCacheState';
import { getAssetIcon } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { Flex, Tabs, type TabsProps } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { CategoryData, ResourceType, resourceConfig } from '../../config';
import CategoryList from '../CategoryList';
import ResourceList, { ResourceListRef } from '../ResourceList';
@@ -23,9 +23,31 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
const dataListRef = useRef<ResourceListRef>(null);
const config = resourceConfig[resourceType];

// 获取分类
const getAssetIconList = useCallback(
async (name: string = '') => {
const params = {
name: name,
page: 0,
size: 10000,
};
const [res] = await to(getAssetIcon(params));
if (res && res.data && res.data.content) {
const { content } = res.data;
setTypeList(
content.filter((item: CategoryData) => Number(item.category_id) === config.typeValue),
);
setTagList(
content.filter((item: CategoryData) => Number(item.category_id) === config.tagValue),
);
}
},
[config],
);

useEffect(() => {
getAssetIconList();
}, []);
}, [getAssetIconList]);

// 分类搜索
const handleCategorySearch = (value: string) => {
@@ -42,25 +64,6 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
setActiveTag((prev) => (prev === record.name ? undefined : record.name));
};

// 获取分类
const getAssetIconList = async (name: string = '') => {
const params = {
name: name,
page: 0,
size: 10000,
};
const [res] = await to(getAssetIcon(params));
if (res && res.data && res.data.content) {
const { content } = res.data;
setTypeList(
content.filter((item: CategoryData) => Number(item.category_id) === config.typeValue),
);
setTagList(
content.filter((item: CategoryData) => Number(item.category_id) === config.tagValue),
);
}
};

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
dataListRef.current?.reset();


+ 18
- 18
react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx View File

@@ -127,28 +127,28 @@ function VersionCompareModal({
text: '版本描述',
},
],
[],
[resourceType],
);

useEffect(() => {
getServiceVersionCompare();
}, []);

// 获取对比数据
const getServiceVersionCompare = async () => {
const params = {
versions,
identifier,
is_public,
owner,
repo_id,
// 获取对比数据
const getServiceVersionCompare = async () => {
const params = {
versions,
identifier,
is_public,
owner,
repo_id,
};
const request = config.compareVersion;
const [res] = await to(request(params));
if (res && res.data) {
setCompareData(res.data);
}
};
const request = config.compareVersion;
const [res] = await to(request(params));
if (res && res.data) {
setCompareData(res.data);
}
};

getServiceVersionCompare();
}, [versions, identifier, is_public, owner, repo_id, config]);

// 获取值
function getValue<T extends DatasetData | ModelData>(


+ 7
- 7
react-ui/src/pages/DevelopmentEnvironment/List/index.tsx View File

@@ -29,7 +29,7 @@ import {
type TableProps,
} from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import CreateMirrorModal from '../components/CreateMirrorModal';
import EditorStatusCell from '../components/EditorStatusCell';
import styles from './index.less';
@@ -57,12 +57,8 @@ function EditorList() {
},
);

useEffect(() => {
getEditorList();
}, [pagination]);

// 获取编辑器列表
const getEditorList = async () => {
const getEditorList = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
@@ -73,7 +69,11 @@ function EditorList() {
setTableData(content);
setTotal(totalElements);
}
};
}, [pagination]);

useEffect(() => {
getEditorList();
}, [getEditorList]);

// 删除编辑器
const deleteEditor = async (id: number) => {


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

@@ -46,27 +46,27 @@ function ExperimentComparison() {
});

const { message } = App.useApp();
const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]);
const config = comparisonConfig[comparisonType];

useEffect(() => {
getComparisonData();
}, [experimentId]);

// 获取对比数据列表
const getComparisonData = async () => {
const request =
comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq;
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
// 获取对比数据列表
const getComparisonData = async () => {
const request =
comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq;
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
};
const [res] = await to(request(experimentId, params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};
const [res] = await to(request(experimentId, params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

getComparisonData();
}, [experimentId, pagination, comparisonType]);

// 获取对比 url
const getExpMetrics = async () => {
@@ -187,7 +187,7 @@ function ExperimentComparison() {
})),
},
];
}, [tableData]);
}, [tableData, config]);

return (
<div className={styles['experiment-comparison']}>


+ 10
- 12
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -21,7 +21,6 @@ function ExperimentText() {
const [experimentIns, setExperimentIns] = useState(undefined);
const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined);
const graphRef = useRef();
const timerRef = useRef();
const workflowRef = useRef();
const locationParams = useParams(); // 新版本获取路由参数接口
const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);
@@ -36,6 +35,16 @@ function ExperimentText() {
initGraph();
getWorkflow();

return () => {
if (evtSourceRef.current) {
evtSourceRef.current.close();
evtSourceRef.current = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
const changeSize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graphRef.current) return;
@@ -46,20 +55,9 @@ function ExperimentText() {
window.addEventListener('resize', changeSize);
return () => {
window.removeEventListener('resize', changeSize);
if (timerRef.current) {
clearTimeout(timerRef.current);
}
if (evtSourceRef.current) {
evtSourceRef.current.close();
evtSourceRef.current = null;
}
};
}, []);

useEffect(() => {
propsDrawerOpenRef.current = propsDrawerOpen;
}, [propsDrawerOpen]);

// 获取流水线模版
const getWorkflow = async () => {
const [res] = await to(getWorkflowById(locationParams.workflowId));


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

@@ -90,6 +90,9 @@ const ExperimentDrawer = ({
instanceNodeStatus,
workflowId,
instanceNodeStartTime,
experimentName,
experimentId,
pipelineId,
],
);



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

@@ -57,7 +57,7 @@ function ExperimentInstanceComponent({
if (allIntanceIds.length === 0) {
setSelectedIns([]);
}
}, [experimentInsList]);
}, [allIntanceIds, setSelectedIns]);

// 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => {


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

@@ -43,19 +43,18 @@ function ExperimentResult({
: undefined;

useEffect(() => {
// 获取实验结果
const getExperimentResult = async (params: any) => {
const [res] = await to(getNodeResult(params));
if (res && res.data && Array.isArray(res.data)) {
const data = res.data.filter((item: ExperimentResultData) => item.value.length > 0);
setExperimentResults(data);
} else {
setExperimentResults([]);
}
};
getExperimentResult({ id: `${experimentInsId}`, node_id: pipelineNodeId });
}, []);

// 获取实验结果
const getExperimentResult = async (params: any) => {
const [res] = await to(getNodeResult(params));
if (res && res.data && Array.isArray(res.data)) {
const data = res.data.filter((item: ExperimentResultData) => item.value.length > 0);
setExperimentResults(data);
} else {
setExperimentResults([]);
}
};
}, [experimentInsId, pipelineNodeId]);

// 下载
const download = (path: string) => {


+ 14
- 14
react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx View File

@@ -53,8 +53,21 @@ function ExportModelModal({
};

useEffect(() => {
// 获取数据集、模型列表
const requestResourceList = async () => {
const params = {
page: 0,
size: 1000,
is_public: false, // 个人
};
const [res] = await to(config.getList(params));
if (res && res.data) {
setResources(res.data.content || []);
}
};

requestResourceList();
}, []);
}, [config]);

// 获取选中的数据集、模型
const getSelectedResource = (id: number | undefined) => {
@@ -84,19 +97,6 @@ function ExportModelModal({
}
};

// 获取数据集、模型列表
const requestResourceList = async () => {
const params = {
page: 0,
size: 1000,
is_public: false, // 个人
};
const [res] = await to(config.getList(params));
if (res && res.data) {
setResources(res.data.content || []);
}
};

// 获取数据集、模型版本列表
const getRecourceVersions = async (id: number) => {
const resource = getSelectedResource(id);


+ 97
- 84
react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -37,20 +37,103 @@ function LogGroup({
const [completed, setCompleted] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false);
const preStatusRef = useRef<ExperimentStatus | undefined>(undefined);
const socketRef = useRef<WebSocket | undefined>(undefined);
const retryRef = useRef(2); // 等待 2 秒,重试 3 次
const elementRef = useRef<HTMLDivElement | null>(null);
const logElementRef = useRef<HTMLDivElement | null>(null);
// 如果是【运行中】状态,设置 hasRun 为 true,【运行中】或者从【运行中】切换到别的状态时,不显示【更多】按钮
const [hasRun, setHasRun] = useState(false);
if (status === ExperimentStatus.Running && !hasRun) {
setHasRun(true);
}

// 进入页面时,滚动到底部
useEffect(() => {
scrollToBottom(false);
}, []);

useEffect(() => {
// 建立 socket 连接
const setupSockect = () => {
let { host } = location;
if (process.env.NODE_ENV === 'development') {
host = '172.20.32.197: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);
// 有时候会出现连接失败,重试 3 次
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) => {
// console.log('message received.', 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;
};

if (status === ExperimentStatus.Running) {
setupSockect();
} else if (preStatusRef.current === ExperimentStatus.Running) {
setCompleted(true);
}
preStatusRef.current = status;
}, [status]);

return () => {
closeSocket();
};
}, [status, start_time, pod_name, isMouseDownRef, setLogList]);

// 鼠标拖到中不滚动到底部
useEffect(() => {
@@ -65,9 +148,8 @@ function LogGroup({
return () => {
document.removeEventListener('mousedown', mouseDown);
document.removeEventListener('mouseup', mouseUp);
closeSocket();
};
}, []);
}, [setIsMouseDown]);

// 请求日志
const requestExperimentPodsLog = async () => {
@@ -119,78 +201,7 @@ function LogGroup({
requestExperimentPodsLog();
};

// 建立 socket 连接
const setupSockect = () => {
let { host } = location;
if (process.env.NODE_ENV === 'development') {
host = '172.20.32.197: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);
// 有时候会出现连接失败,重试 3 次
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) => {
// console.log('message received.', 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;
};

// 关闭 socket
const closeSocket = () => {
if (socketRef.current) {
socketRef.current.close(1000, 'completed');
@@ -208,15 +219,17 @@ function LogGroup({
// };
// element.scrollTo(optons);
// }
elementRef?.current?.scrollIntoView({ block: 'end', behavior: smooth ? 'smooth' : 'instant' });
logElementRef?.current?.scrollIntoView({
block: 'end',
behavior: smooth ? 'smooth' : 'instant',
});
};

const showLog = (log_type === 'resource' && !collapse) || log_type === 'normal';
const logText = log_content + logList.map((v) => v.log_content).join('');
const showMoreBtn =
status !== ExperimentStatus.Running && showLog && !completed && logText !== '';
const showMoreBtn = !hasRun && !completed && showLog && logText !== '';
return (
<div className={styles['log-group']} ref={elementRef}>
<div className={styles['log-group']} ref={logElementRef}>
{log_type === 'resource' && (
<div className={styles['log-group__pod']} onClick={handleCollapse}>
<div className={styles['log-group__pod__name']}>{pod_name}</div>


+ 33
- 27
react-ui/src/pages/Experiment/components/LogList/index.tsx View File

@@ -3,7 +3,7 @@ import { getQueryByExperimentLog } from '@/services/experiment/index.js';
import { to } from '@/utils/promise';
import classNames from 'classnames';
import dayjs from 'dayjs';
import React, { useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import LogGroup from '../LogGroup';
import styles from './index.less';

@@ -15,13 +15,21 @@ export type ExperimentLog = {
};

type LogListProps = {
instanceName: string; // 实验实例 name
instanceNamespace: string; // 实验实例 namespace
pipelineNodeId: string; // 流水线节点 id
workflowId?: string; // 实验实例工作流 id
instanceNodeStartTime?: string; // 实验实例节点开始运行时间
/** 实验实例 name */
instanceName: string;
/** 实验实例 namespace */
instanceNamespace: string;
/** 流水线节点 id */
pipelineNodeId: string;
/** 实验实例工作流 id */
workflowId?: string;
/** 实验实例节点开始运行时间 */
instanceNodeStartTime?: string;
/** 实验实例节点运行状态 */
instanceNodeStatus?: ExperimentStatus;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

@@ -35,24 +43,11 @@ function LogList({
className,
style,
}: LogListProps) {
const [logList, setLogList] = useState<ExperimentLog[]>([]);
const preStatusRef = useRef<ExperimentStatus | undefined>(undefined);
const [logGroups, setLogGroups] = useState<ExperimentLog[]>([]);
const retryRef = useRef(3); // 等待 2 秒,重试 3 次

// 当实例节点运行状态不是 Pending,而上一个运行状态不存在或者是 Pending 时,获取实验日志
useEffect(() => {
if (
instanceNodeStatus &&
instanceNodeStatus !== ExperimentStatus.Pending &&
(!preStatusRef.current || preStatusRef.current === ExperimentStatus.Pending)
) {
getExperimentLog();
}
preStatusRef.current = instanceNodeStatus;
}, [instanceNodeStatus]);

// 获取实验日志
const getExperimentLog = async () => {
// 获取实验 Pods 组
const getExperimentLog = useCallback(async () => {
const start_time = dayjs(instanceNodeStartTime).valueOf() * 1.0e6;
const params = {
task_id: pipelineNodeId,
@@ -71,7 +66,7 @@ function LogList({
log_type,
},
];
setLogList(list);
setLogGroups(list);
} else if (log_type === 'resource') {
const list = pods.map((v: string) => ({
log_type,
@@ -79,7 +74,7 @@ function LogList({
log_content: '',
start_time,
}));
setLogList(list);
setLogGroups(list);
}
} else {
if (retryRef.current > 0) {
@@ -89,12 +84,23 @@ function LogList({
}, 2 * 1000);
}
}
};
}, [pipelineNodeId, workflowId, instanceName, instanceNamespace, instanceNodeStartTime]);

// 当实例节点运行状态不是 Pending,获取实验日志组
useEffect(() => {
if (
instanceNodeStatus &&
instanceNodeStatus !== ExperimentStatus.Pending &&
logGroups.length === 0
) {
getExperimentLog();
}
}, [getExperimentLog, logGroups, instanceNodeStatus]);

return (
<div className={classNames(styles['log-list'], className)} id="log-list" style={style}>
{logList.length > 0 ? (
logList.map((v) => <LogGroup key={v.pod_name} {...v} status={instanceNodeStatus} />)
{logGroups.length > 0 ? (
logGroups.map((v) => <LogGroup key={v.pod_name} {...v} status={instanceNodeStatus} />)
) : (
<div className={styles['log-list__empty']}>暂无日志</div>
)}


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

@@ -20,7 +20,7 @@ import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ComparisonType } from './Comparison/config';
import AddExperimentModal from './components/AddExperimentModal';
@@ -35,12 +35,6 @@ function Experiment() {
const navigate = useNavigate();
const [experimentList, setExperimentList] = useState([]);
const [workflowList, setWorkflowList] = useState([]);
const [queryFlow, setQueryFlow] = useState({
offset: 1,
page: 0,
size: 10000,
name: null,
});
const [experimentId, setExperimentId] = useState(null);
const [experimentInList, setExperimentInList] = useState([]);
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
@@ -61,18 +55,28 @@ function Experiment() {
const { message } = App.useApp();

useEffect(() => {
// 获取流水线列表
const getWorkflowList = async () => {
const queryFlow = {
offset: 1,
page: 0,
size: 10000,
name: null,
};
const [res] = await to(getWorkflow(queryFlow));
if (res && res.data && res.data.content) {
setWorkflowList(res.data.content);
}
};

getWorkflowList();
return () => {
clearExperimentInTimers();
};
}, []);

useEffect(() => {
getExperimentList();
}, [pagination, searchText]);

// 获取实验列表
const getExperimentList = async () => {
const getExperimentList = useCallback(async () => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
@@ -88,15 +92,11 @@ function Experiment() {

setTotal(res.data.totalElements);
}
};
}, [pagination, searchText]);

// 获取流水线列表
const getWorkflowList = async () => {
const [res] = await to(getWorkflow(queryFlow));
if (res && res.data && res.data.content) {
setWorkflowList(res.data.content);
}
};
useEffect(() => {
getExperimentList();
}, [getExperimentList]);

// 搜索
const onSearch = (value) => {


+ 27
- 27
react-ui/src/pages/HyperParameter/Create/index.tsx View File

@@ -26,37 +26,37 @@ function CreateHyperParameter() {
const isCopy = pathname.includes('copy');

useEffect(() => {
// 获取服务详情
const getHyperParameterInfo = async (id: number) => {
const [res] = await to(getRayInfoReq({ id }));
if (res && res.data) {
const info: HyperParameterData = res.data;
const { name: name_str, parameters, points_to_evaluate, ...rest } = info;
const name = isCopy ? `${name_str}-copy` : name_str;
if (parameters && Array.isArray(parameters)) {
parameters.forEach((item) => {
const paramName = getReqParamName(item.type);
item.range = item[paramName];
item[paramName] = undefined;
});
}

const formData = {
...rest,
name,
parameters,
points_to_evaluate: points_to_evaluate ?? [],
};

form.setFieldsValue(formData);
}
};

// 编辑,复制
if (id && !Number.isNaN(id)) {
getHyperParameterInfo(id);
}
}, [id]);

// 获取服务详情
const getHyperParameterInfo = async (id: number) => {
const [res] = await to(getRayInfoReq({ id }));
if (res && res.data) {
const info: HyperParameterData = res.data;
const { name: name_str, parameters, points_to_evaluate, ...rest } = info;
const name = isCopy ? `${name_str}-copy` : name_str;
if (parameters && Array.isArray(parameters)) {
parameters.forEach((item) => {
const paramName = getReqParamName(item.type);
item.range = item[paramName];
item[paramName] = undefined;
});
}

const formData = {
...rest,
name,
parameters,
points_to_evaluate: points_to_evaluate ?? [],
};

form.setFieldsValue(formData);
}
};
}, [id, form, isCopy]);

// 创建、更新、复制实验
const createExperiment = async (formData: FormData) => {


+ 9
- 9
react-ui/src/pages/HyperParameter/Info/index.tsx View File

@@ -8,7 +8,7 @@ import { getRayInfoReq } from '@/services/hyperParameter';
import { safeInvoke } from '@/utils/functional';
import { to } from '@/utils/promise';
import { useParams } from '@umijs/max';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import HyperParameterBasic from '../components/HyperParameterBasic';
import { HyperParameterData } from '../types';
import styles from './index.less';
@@ -20,19 +20,19 @@ function HyperparameterInfo() {
undefined,
);

useEffect(() => {
if (hyperparameterId) {
getHyperparameterInfo();
}
}, []);

// 获取自动机器学习详情
const getHyperparameterInfo = async () => {
const getHyperparameterInfo = useCallback(async () => {
const [res] = await to(getRayInfoReq({ id: hyperparameterId }));
if (res && res.data) {
setHyperparameterInfo(res.data);
}
};
}, [hyperparameterId]);

useEffect(() => {
if (hyperparameterId) {
getHyperparameterInfo();
}
}, [hyperparameterId, getHyperparameterInfo]);

return (
<div className={styles['hyper-parameter-info']}>


+ 6
- 6
react-ui/src/pages/HyperParameter/Instance/index.tsx View File

@@ -22,6 +22,8 @@ enum TabKeys {
History = 'history',
}

const NodePrefix = 'workflow';

function HyperParameterInstance() {
const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined);
const [instanceInfo, setInstanceInfo] = useState<HyperParameterInstanceData | undefined>(
@@ -41,6 +43,7 @@ function HyperParameterInstance() {
return () => {
closeSSE();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// 获取实验实例详情
@@ -83,7 +86,7 @@ function HyperParameterInstance() {
if (nodeStatusJson) {
setNodes(nodeStatusJson);
Object.keys(nodeStatusJson).some((key) => {
if (key.startsWith('workflow')) {
if (key.startsWith(NodePrefix)) {
const workflowStatus = nodeStatusJson[key];
setWorkflowStatus(workflowStatus);
return true;
@@ -101,9 +104,6 @@ function HyperParameterInstance() {

const setupSSE = (name: string, namespace: string) => {
const { origin } = location;
// if (process.env.NODE_ENV === 'development') {
// origin = 'http://172.20.32.197:31213';
// }
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
@@ -119,7 +119,7 @@ function HyperParameterInstance() {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith('workflow'),
node.displayName.startsWith(NodePrefix),
) as NodeStatus;

// 节点
@@ -192,7 +192,7 @@ function HyperParameterInstance() {
key: TabKeys.History,
label: '寻优列表',
icon: <KFIcon type="icon-Trialliebiao" />,
children: <ExperimentHistory trialList={instanceInfo?.trial_list} />,
children: <ExperimentHistory trialList={instanceInfo?.trial_list ?? []} />,
},
];



+ 2
- 2
react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx View File

@@ -32,10 +32,10 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
setTableData(trialList);
setLoading(false);
}, 500);
}, []);
}, [trialList]);

// 计算 column
const first: HyperParameterTrial | undefined = trialList[0];
const first: HyperParameterTrial | undefined = trialList ? trialList[0] : undefined;
const config: Record<string, any> = first?.config ?? {};
const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {};
const paramsNames = Object.keys(config);


+ 8
- 8
react-ui/src/pages/HyperParameter/components/ExperimentResult/index.tsx View File

@@ -12,19 +12,19 @@ function ExperimentResult({ fileUrl }: ExperimentResultProps) {
const [result, setResult] = useState<string | undefined>('');

useEffect(() => {
// 获取实验运行历史记录
const getResultFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
setResult(res as any as string);
}
};

if (fileUrl) {
getResultFile();
}
}, [fileUrl]);

// 获取实验运行历史记录
const getResultFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
setResult(res as any as string);
}
};

return (
<div className={styles['experiment-result']}>
<InfoGroup title="最佳实验结果" width="100%">


+ 2
- 11
react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx View File

@@ -43,15 +43,6 @@ function HyperParameterBasic({
}: HyperParameterBasicProps) {
const getResourceDescription = useComputingResource()[2];

// 格式化资源规格
const formatResource = (resource?: string) => {
if (!resource) {
return undefined;
}

return getResourceDescription(resource);
};

const basicDatas: BasicInfoData[] = useMemo(() => {
if (!info) {
return [];
@@ -146,10 +137,10 @@ function HyperParameterBasic({
{
label: '资源规格',
value: info.resource,
format: formatResource,
format: getResourceDescription,
},
];
}, [info]);
}, [info, getResourceDescription]);

const instanceDatas = useMemo(() => {
if (!runStatus) {


+ 1
- 1
react-ui/src/pages/Mirror/Create/index.tsx View File

@@ -65,7 +65,7 @@ function MirrorCreate() {
return () => {
SessionStorage.removeItem(SessionStorage.mirrorNameKey);
};
}, []);
}, [form]);

// 创建公网、本地镜像
const createPublicMirror = async (formData: FormData) => {


+ 25
- 17
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -16,6 +16,7 @@ import {
} from '@/services/mirror';
import themes from '@/styles/theme.less';
import { formatDate } from '@/utils/date';
import { safeInvoke } from '@/utils/functional';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
@@ -32,7 +33,7 @@ import {
type TablePaginationConfig,
type TableProps,
} from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import MirrorStatusCell from '../components/MirrorStatusCell';
import styles from './index.less';

@@ -70,31 +71,28 @@ function MirrorInfo() {
);
const { message } = App.useApp();
const isPublic = useMemo(() => mirrorInfo.image_type === 1, [mirrorInfo]);

useEffect(() => {
getMirrorInfo();
}, []);

useEffect(() => {
getMirrorVersionList();
}, [pagination]);
const mirrorId = safeInvoke(Number)(urlParams.id);

// 获取镜像详情
const getMirrorInfo = async () => {
const id = Number(urlParams.id);
const [res] = await to(getMirrorInfoReq(id));
const getMirrorInfo = useCallback(async () => {
if (!mirrorId) {
return;
}
const [res] = await to(getMirrorInfoReq(mirrorId));
if (res && res.data) {
setMirrorInfo(res.data);
}
};
}, [mirrorId]);

// 获取镜像版本列表
const getMirrorVersionList = async () => {
const id = Number(urlParams.id);
const getMirrorVersionList = useCallback(async () => {
if (!mirrorId) {
return;
}
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
image_id: id,
image_id: mirrorId,
};
const [res] = await to(getMirrorVersionListReq(params));
if (res && res.data) {
@@ -102,7 +100,17 @@ function MirrorInfo() {
setTableData(content);
setTotal(totalElements);
}
};
}, [mirrorId, pagination]);

// 获取镜像详情
useEffect(() => {
getMirrorInfo();
}, [getMirrorInfo]);

// 获取镜像版本列表
useEffect(() => {
getMirrorVersionList();
}, [getMirrorVersionList]);

// 删除镜像版本
const deleteMirrorVersion = async (id: number) => {


+ 18
- 18
react-ui/src/pages/Mirror/List/index.tsx View File

@@ -26,7 +26,7 @@ import {
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import styles from './index.less';

const mirrorTabItems = [
@@ -65,9 +65,25 @@ function MirrorList() {
);
const { message } = App.useApp();

// 获取镜像列表
const getMirrorList = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
name: searchText || undefined,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
};
const [res] = await to(getMirrorListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
}, [activeTab, pagination, searchText]);

useEffect(() => {
getMirrorList();
}, [activeTab, pagination, searchText]);
}, [getMirrorList]);

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
@@ -82,22 +98,6 @@ function MirrorList() {
setActiveTab(value);
};

// 获取镜像列表
const getMirrorList = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
name: searchText || undefined,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
};
const [res] = await to(getMirrorListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

// 删除镜像
const deleteMirror = async (id: number) => {
const [res] = await to(deleteMirrorReq(id));


+ 68
- 65
react-ui/src/pages/Model/components/MetricsChart/index.tsx View File

@@ -1,5 +1,5 @@
import * as echarts from 'echarts';
import { useEffect, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import styles from './index.less';
import './tooltip.css';

@@ -74,77 +74,80 @@ function MetricsChart({ name, chartData }: MetricsChartProps) {
};
});

const options: echarts.EChartsOption = {
backgroundColor: backgroundColor,
title: {
show: false,
},
tooltip: {
trigger: 'item',
padding: 10,
formatter: (params: any) => {
const { name: xTitle, data } = params;
return getTooltip('step', xTitle, name, data);
},
},
legend: {
bottom: 10,
icon: 'rect',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
textStyle: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
},
},
color: colors,
grid: {
left: '15',
right: '15',
top: '20',
bottom: '60',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: true,
offset: 10,
data: xAxisData,
axisLabel: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
},
axisTick: {
const options: echarts.EChartsOption = useMemo(
() => ({
backgroundColor: backgroundColor,
title: {
show: false,
},
axisLine: {
lineStyle: {
color: '#eaeaea',
width: 1,
tooltip: {
trigger: 'item',
padding: 10,
formatter: (params: any) => {
const { name: xTitle, data } = params;
return getTooltip('step', xTitle, name, data);
},
},
},
yAxis: {
type: 'value',
axisLabel: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
margin: 15,
legend: {
bottom: 10,
icon: 'rect',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
textStyle: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
},
},
axisLine: {
show: false,
color: colors,
grid: {
left: '15',
right: '15',
top: '20',
bottom: '60',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: true,
offset: 10,
data: xAxisData,
axisLabel: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
},
axisTick: {
show: false,
},
axisLine: {
lineStyle: {
color: '#eaeaea',
width: 1,
},
},
},
splitLine: {
lineStyle: {
color: '#e4e4e4',
width: 1,
type: 'dashed',
yAxis: {
type: 'value',
axisLabel: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
margin: 15,
},
axisLine: {
show: false,
},
splitLine: {
lineStyle: {
color: '#e4e4e4',
width: 1,
type: 'dashed',
},
},
},
},
series: seriesData,
};
series: seriesData,
}),
[name, seriesData, xAxisData],
);

useEffect(() => {
// 创建一个echarts实例,返回echarts实例
@@ -158,7 +161,7 @@ function MetricsChart({ name, chartData }: MetricsChartProps) {
// myChart.dispose() 销毁实例
chart.dispose();
};
}, []);
}, [options]);

return (
<div className={styles['metrics-chart']}>


+ 5
- 1
react-ui/src/pages/Model/components/ModelEvolution/index.tsx View File

@@ -56,6 +56,10 @@ function ModelEvolution({

useEffect(() => {
initGraph();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
const changeSize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graphRef.current) return;
@@ -77,8 +81,8 @@ function ModelEvolution({
clearGraphData();
}
},
[resourceId, version],
isActive,
[resourceId, version],
);

// 初始化图


+ 52
- 44
react-ui/src/pages/Model/components/ModelMetrics/index.tsx View File

@@ -60,60 +60,61 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
] = useCheck(allMetricsNames);

useEffect(() => {
if (selectedMetrics.length !== 0 && selectedRowKeys.length !== 0) {
getModelVersionsMetrics();
} else {
setChartData(undefined);
}
}, [selectedMetrics, selectedRowKeys, identifier, resourceId]);
// 获取模型版本列表,带有参数和指标数据
const getModelPageVersions = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
identifier: identifier,
owner: owner,
type: MetricsType.Train,
};
const [res] = await to(getModelPageVersionsReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

getModelPageVersions();
}, [pagination, identifier, owner]);

// 版本切换,自动勾选当前版本
useEffect(() => {
const curRow = tableData.find((item) => item.name === version);
if (
curRow &&
curRow.metrics_names &&
curRow.metrics_names.length > 0 &&
!selectedRowKeys.includes(version)
) {
setSelectedRowKeys([version, ...selectedRowKeys]);
if (curRow && curRow.metrics_names && curRow.metrics_names.length > 0) {
setSelectedRowKeys((prev) => {
if (!prev.includes(version)) {
return [version, ...prev];
}
return prev;
});
}
}, [tableData, version]);

useEffect(() => {
getModelPageVersions();
}, [pagination, identifier, owner]);

// 获取模型版本列表,带有参数和指标数据
const getModelPageVersions = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
identifier: identifier,
owner: owner,
type: MetricsType.Train,
// 获取模型版本指标的图表数据
const getModelVersionsMetrics = async () => {
const params = {
versions: selectedRowKeys,
metrics: selectedMetrics,
type: MetricsType.Train,
identifier: identifier,
repo_id: resourceId,
};
const [res] = await to(getModelVersionsMetricsReq(params));
if (res && res.data) {
setChartData(res.data);
}
};
const [res] = await to(getModelPageVersionsReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

const getModelVersionsMetrics = async () => {
const params = {
versions: selectedRowKeys,
metrics: selectedMetrics,
type: MetricsType.Train,
identifier: identifier,
repo_id: resourceId,
};
const [res] = await to(getModelVersionsMetricsReq(params));
if (res && res.data) {
setChartData(res.data);
if (selectedMetrics.length !== 0 && selectedRowKeys.length !== 0) {
getModelVersionsMetrics();
} else {
setChartData(undefined);
}
};
}, [selectedMetrics, selectedRowKeys, identifier, resourceId]);

// 分页切换
const handleTableChange: TableProps<TableData>['onChange'] = (
@@ -225,7 +226,14 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
})),
},
];
}, [tableData, selectedMetrics]);
}, [
tableData,
checkAllMetrics,
checkSingleMetrics,
isSingleMetricsChecked,
metricsChecked,
metricsIndeterminate,
]);

return (
<div className={styles['model-metrics']}>


+ 16
- 18
react-ui/src/pages/ModelDeployment/CreateService/index.tsx View File

@@ -11,8 +11,8 @@ import { createServiceReq, getServiceInfoReq, updateServiceReq } from '@/service
import { to } from '@/utils/promise';
import { useNavigate, useParams } from '@umijs/max';
import { App, Button, Col, Form, Input, Row, Select } from 'antd';
import { useEffect, useState } from 'react';
import { ServiceData, createServiceVersionMessage } from '../types';
import { useEffect } from 'react';
import { createServiceVersionMessage } from '../types';
import styles from './index.less';

// 表单数据
@@ -25,30 +25,28 @@ export type FormData = {
function CreateService() {
const navigate = useNavigate();
const [form] = Form.useForm();
const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined);
const { message } = App.useApp();
const params = useParams();
const serviceId = params.serviceId;

useEffect(() => {
// 获取服务详情
const getServiceInfo = async () => {
const [res] = await to(getServiceInfoReq(serviceId));
if (res && res.data) {
const { service_type, service_name, description } = res.data;
form.setFieldsValue({
service_type,
service_name,
description,
});
}
};

if (serviceId) {
getServiceInfo();
}
}, []);

// 获取服务详情
const getServiceInfo = async () => {
const [res] = await to(getServiceInfoReq(serviceId));
if (res && res.data) {
setServiceInfo(res.data);
const { service_type, service_name, description } = res.data;
form.setFieldsValue({
service_type,
service_name,
description,
});
}
};
}, [serviceId, form]);

// 创建、更新服务
const createService = async (formData: FormData) => {


+ 13
- 14
react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx View File

@@ -55,11 +55,11 @@ function CreateServiceVersion() {
const [operationType, setOperationType] = useState(ServiceOperationType.Create);
const [lastPage, setLastPage] = useState(CreateServiceVersionFrom.ServiceInfo);
const { message } = App.useApp();
// const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined);
const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined);
const params = useParams();
const serviceId = params.serviceId;

// 因为没有服务版本详情接口,需要从缓存中获取
useEffect(() => {
const res: ServiceVersionCache | undefined = SessionStorage.getItem(
SessionStorage.serviceVersionInfoKey,
@@ -98,22 +98,21 @@ function CreateServiceVersion() {
return () => {
SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey);
};
}, []);
}, [form]);

useEffect(() => {
getServiceInfo();
}, []);
// 获取服务详情,设置服务名称
const getServiceInfo = async () => {
const [res] = await to(getServiceInfoReq(serviceId));
if (res && res.data) {
form.setFieldsValue({
service_name: res.data.service_name,
});
}
};

// 获取服务详情
const getServiceInfo = async () => {
const [res] = await to(getServiceInfoReq(serviceId));
if (res && res.data) {
// setServiceInfo(res.data);
form.setFieldsValue({
service_name: res.data.service_name,
});
}
};
getServiceInfo();
}, [serviceId, form]);

// 创建版本
const createServiceVersion = async (formData: FormData) => {


+ 48
- 43
react-ui/src/pages/ModelDeployment/List/index.tsx View File

@@ -26,7 +26,7 @@ import {
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import {
CreateServiceVersionFrom,
ServiceData,
@@ -53,19 +53,8 @@ function ModelDeployment() {
},
);

useEffect(() => {
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, []);

useEffect(() => {
getServiceList();
}, [pagination, searchText, serviceType]);

// 获取模型部署服务列表
const getServiceList = async () => {
const getServiceList = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
@@ -78,7 +67,52 @@ function ModelDeployment() {
setTableData(content);
setTotal(totalElements);
}
};
}, [pagination, searchText, serviceType]);

// 去创建服务版本
const gotoCreateServiceVersion = useCallback(
(serviceId: number) => {
SessionStorage.setItem(
SessionStorage.serviceVersionInfoKey,
{
operationType: ServiceOperationType.Create,
lastPage: CreateServiceVersionFrom.CreateService,
},
true,
);

navigate(`serviceInfo/${serviceId}/createVersion`);
},
[navigate],
);

// 获取模型部署服务列表
useEffect(() => {
getServiceList();
}, [getServiceList]);

// 接收创建服务成功的消息
useEffect(() => {
const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data;
if (type === createServiceVersionMessage) {
modalConfirm({
title: '创建服务成功',
content: '是否创建服务版本?',
isDelete: false,
cancelText: '稍后创建',
onOk: () => {
gotoCreateServiceVersion(payload);
},
});
}
};

window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [gotoCreateServiceVersion]);

// 删除模型部署
const deleteService = async (record: ServiceData) => {
@@ -145,35 +179,6 @@ function ModelDeployment() {
navigate(`serviceInfo/${record.id}`);
};

const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data;
if (type === createServiceVersionMessage) {
modalConfirm({
title: '创建服务成功',
content: '是否创建服务版本?',
isDelete: false,
cancelText: '稍后创建',
onOk: () => {
gotoCreateServiceVersion(payload);
},
});
}
};

// 去创建服务版本
const gotoCreateServiceVersion = (serviceId: number) => {
SessionStorage.setItem(
SessionStorage.serviceVersionInfoKey,
{
operationType: ServiceOperationType.Create,
lastPage: CreateServiceVersionFrom.CreateService,
},
true,
);

navigate(`serviceInfo/${serviceId}/createVersion`);
};

// 分页切换
const handleTableChange: TableProps<ServiceData>['onChange'] = (
pagination,


+ 13
- 13
react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx View File

@@ -36,7 +36,7 @@ import {
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import ServiceRunStatusCell from '../components/ModelDeployStatusCell';
import VersionCompareModal from '../components/VersionCompareModal';
import {
@@ -89,24 +89,16 @@ function ServiceInfo() {
];
const getResourceDescription = useComputingResource()[2];

useEffect(() => {
getServiceInfo();
}, []);

useEffect(() => {
getServiceVersions();
}, [pagination, searchText, serviceStatus]);

// 获取服务详情
const getServiceInfo = async () => {
const getServiceInfo = useCallback(async () => {
const [res] = await to(getServiceInfoReq(serviceId));
if (res && res.data) {
setServiceInfo(res.data);
}
};
}, [serviceId]);

// 获取服务版本列表
const getServiceVersions = async () => {
const getServiceVersions = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
@@ -125,7 +117,15 @@ function ServiceInfo() {
setTableData(content);
setTotal(totalElements);
}
};
}, [pagination, serviceStatus, searchText, serviceId]);

useEffect(() => {
getServiceInfo();
}, [getServiceInfo]);

useEffect(() => {
getServiceVersions();
}, [getServiceVersions]);

// 删除模型部署
const deleteServiceVersion = async (record: ServiceVersionData) => {


+ 10
- 8
react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx View File

@@ -29,16 +29,18 @@ function ServiceVersionInfo() {
const id = params.id;

useEffect(() => {
getServiceVersionInfo();
}, []);
// 获取服务版本详情
const getServiceVersionInfo = async () => {
const [res] = await to(getServiceVersionInfoReq(id));
if (res && res.data) {
setVersionInfo(res.data);
}
};

// 获取服务版本详情
const getServiceVersionInfo = async () => {
const [res] = await to(getServiceVersionInfoReq(id));
if (res && res.data) {
setVersionInfo(res.data);
if (id) {
getServiceVersionInfo();
}
};
}, [id]);

const tabItems = [
{


+ 18
- 19
react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx View File

@@ -54,28 +54,27 @@ function ServerLog({ info }: ServerLogProps) {
];

useEffect(() => {
// 获取服务日志
const getModelDeploymentLog = async () => {
if (info && logTime && logTime.length === 2) {
const params = {
start_time: logTime[0],
end_time: logTime[1],
id: info.id,
};
const [res] = await to(getServiceVersionLogReq(params));
if (res && res.data) {
setLogData((prev) => [...prev, res.data]);
setHasMore(!!res.data.log_content);
// setTimeout(() => {
// scrollToBottom();
// }, 100);
}
}
};
getModelDeploymentLog();
}, [info, logTime]);

// 获取服务日志
const getModelDeploymentLog = async () => {
if (info && logTime && logTime.length === 2) {
const params = {
start_time: logTime[0],
end_time: logTime[1],
id: info.id,
};
const [res] = await to(getServiceVersionLogReq(params));
if (res && res.data) {
setLogData((prev) => [...prev, res.data]);
setHasMore(!!res.data.log_content);
// setTimeout(() => {
// scrollToBottom();
// }, 100);
}
}
};

// 搜索
const handleSearch = () => {
setLogData([]);


+ 10
- 10
react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx View File

@@ -10,20 +10,20 @@ type UserGuideProps = {

function UserGuide({ info }: UserGuideProps) {
const [docs, setDocs] = useState('');

useEffect(() => {
// 获取服务文档
const getModelDeploymentDocs = async () => {
if (info) {
const [res] = await to(getServiceVersionDocsReq(info.id));
if (res && res.data && res.data.docs) {
setDocs(JSON.stringify(res.data.docs, null, 2));
}
}
};
getModelDeploymentDocs();
}, [info]);

// 获取服务文档
const getModelDeploymentDocs = async () => {
if (info) {
const [res] = await to(getServiceVersionDocsReq(info.id));
if (res && res.data && res.data.docs) {
setDocs(JSON.stringify(res.data.docs, null, 2));
}
}
};

return <div className={styles['user-guide']}>{docs}</div>;
}



+ 1
- 10
react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx View File

@@ -38,15 +38,6 @@ const formatEnvText = (env?: Record<string, string>) => {
function VersionBasicInfo({ info }: BasicInfoProps) {
const getResourceDescription = useComputingResource()[2];

// 格式化资源规格
const formatResource = (resource?: string) => {
if (!resource) {
return undefined;
}

return getResourceDescription(resource);
};

const datas: BasicInfoData[] = [
{
label: '服务名称',
@@ -78,7 +69,7 @@ function VersionBasicInfo({ info }: BasicInfoProps) {
{
label: '资源规格',
value: info?.resource,
format: formatResource,
format: getResourceDescription,
},
{
label: '挂载路径',


+ 13
- 13
react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx View File

@@ -106,20 +106,20 @@ function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModa
);

useEffect(() => {
getServiceVersionCompare();
}, []);

// 获取对比数据
const getServiceVersionCompare = async () => {
const params = {
id1: version1,
id2: version2,
// 获取对比数据
const getServiceVersionCompare = async () => {
const params = {
id1: version1,
id2: version2,
};
const [res] = await to(getServiceVersionCompareReq(params));
if (res && res.data) {
setCompareData(res.data);
}
};
const [res] = await to(getServiceVersionCompareReq(params));
if (res && res.data) {
setCompareData(res.data);
}
};

getServiceVersionCompare();
}, [version1, version2]);

const {
version1: v1 = {} as ServiceVersionData,


+ 1
- 1
react-ui/src/pages/Monitor/Job/edit.tsx View File

@@ -55,7 +55,7 @@ const JobForm: React.FC<JobFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


+ 1
- 1
react-ui/src/pages/Monitor/JobLog/index.tsx View File

@@ -124,7 +124,7 @@ const JobLogTableList: React.FC = () => {
getDictValueEnum('sys_job_group').then((data) => {
setJobGroupOptions(data);
});
}, []);
}, [jobId]);

const columns: ProColumns<API.Monitor.JobLog>[] = [
{


+ 5
- 2
react-ui/src/pages/Pipeline/Info/index.jsx View File

@@ -32,7 +32,10 @@ const EditPipeline = () => {
useEffect(() => {
initGraph();
getFirstWorkflow(locationParams.id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
const changeSize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graphRef.current) return;
@@ -85,7 +88,7 @@ const EditPipeline = () => {
};

// 保存
const savePipeline = async (val) => {
const savePipeline = async (isBack) => {
const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields());
if (globalParamError) {
message.error('全局参数配置有误');
@@ -122,7 +125,7 @@ const EditPipeline = () => {
saveWorkflow(params).then((ret) => {
message.success('保存成功');
setTimeout(() => {
if (val) {
if (isBack) {
navigate({ pathname: `/pipeline/template` });
}
}, 500);


+ 17
- 13
react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx View File

@@ -19,19 +19,23 @@ const GlobalParamsDrawer = forwardRef(
({ open = false, onClose, globalParam = [] }: GlobalParamsDrawerProps, ref) => {
const [form] = Form.useForm();

useImperativeHandle(ref, () => ({
validateFields: async () => {
const [values, error] = await to(form.validateFields());
if (!error && values) {
return values;
} else {
return Promise.reject(error);
}
},
getFieldsValue: () => {
return form.getFieldsValue();
},
}));
useImperativeHandle(
ref,
() => ({
validateFields: async () => {
const [values, error] = await to(form.validateFields());
if (!error && values) {
return values;
} else {
return Promise.reject(error);
}
},
getFieldsValue: () => {
return form.getFieldsValue();
},
}),
[form],
);

// 处理参数类型变化
const handleTypeChange = (name: NamePath) => {


+ 40
- 41
react-ui/src/pages/Pipeline/components/ModelMenu/index.tsx View File

@@ -1,7 +1,7 @@
import { getComponentAll } from '@/services/pipeline/index.js';
import { PipelineNodeModel } from '@/types';
import { to } from '@/utils/promise';
import { Collapse, type CollapseProps } from 'antd';
import { Collapse } from 'antd';
import { useEffect, useState } from 'react';
import Styles from './index.less';

@@ -18,50 +18,20 @@ type ModelMenuProps = {
};
const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => {
const [modelMenusList, setModelMenusList] = useState<ModelMenuData[]>([]);
const [collapseItems, setCollapseItems] = useState<CollapseProps['items']>([]);

useEffect(() => {
// 获取所有组件
const getAllComponents = async () => {
const [res] = await to(getComponentAll());
if (res && res.data) {
const menus = res.data as ModelMenuData[];
setModelMenusList(menus);
}
};

getAllComponents();
}, []);

// 获取所有组件
const getAllComponents = async () => {
const [res] = await to(getComponentAll());
if (res && res.data) {
const menus = res.data as ModelMenuData[];
setModelMenusList(menus);
const items = menus.map((item) => {
return {
key: item.key,
label: item.name,
children: item.value.map((ele) => {
return (
<div
key={ele.id}
draggable="true"
onDragEnd={(e) => {
dragEnd(e, ele);
}}
className={Styles.collapseItem}
>
{ele.icon_path && (
<img
style={{ height: '16px', marginRight: '15px' }}
src={`/assets/images/${ele.icon_path}.png`}
draggable={false}
alt=""
/>
)}
{ele.component_label}
</div>
);
}),
};
});
setCollapseItems(items);
}
};

const dragEnd = (e: React.DragEvent<HTMLDivElement>, data: PipelineNodeModel) => {
onComponentDragEnd({
...data,
@@ -73,6 +43,35 @@ const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => {
};

const defaultActiveKey = modelMenusList.map((item) => item.key + '');
const items = modelMenusList.map((item) => {
return {
key: item.key,
label: item.name,
children: item.value.map((ele) => {
return (
<div
key={ele.id}
draggable="true"
onDragEnd={(e) => {
dragEnd(e, ele);
}}
className={Styles.collapseItem}
>
{ele.icon_path && (
<img
style={{ height: '16px', marginRight: '15px' }}
src={`/assets/images/${ele.icon_path}.png`}
draggable={false}
alt=""
/>
)}
{ele.component_label}
</div>
);
}),
};
});

return (
<div className={Styles.collapse}>
<div className={Styles.modelMenusTitle}>组件库</div>
@@ -82,7 +81,7 @@ const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => {
collapsible="header"
expandIconPosition="end"
defaultActiveKey={defaultActiveKey}
items={collapseItems}
items={items}
></Collapse>
) : null}
</div>


+ 51
- 47
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -78,55 +78,59 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
setOpen(false);
};

useImperativeHandle(ref, () => ({
showDrawer(
model: PipelineNodeModel,
params: PipelineGlobalParam[],
parentNodes: INode[],
validate: boolean = false,
) {
try {
const nodeData: PipelineNodeModelSerialize = {
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
};
// console.log('model', nodeData);
setStagingItem({
...nodeData,
});
form.resetFields();
form.setFieldsValue({
...nodeData,
});
if (validate) {
form.validateFields();
useImperativeHandle(
ref,
() => ({
showDrawer(
model: PipelineNodeModel,
params: PipelineGlobalParam[],
parentNodes: INode[],
validate: boolean = false,
) {
try {
const nodeData: PipelineNodeModelSerialize = {
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
};
// console.log('model', nodeData);
setStagingItem({
...nodeData,
});
form.resetFields();
form.setFieldsValue({
...nodeData,
});
if (validate) {
form.validateFields();
}
} catch (error) {
console.error('JSON.parse error: ', error);
}
} catch (error) {
console.error('JSON.parse error: ', error);
}
setOpen(true);
setOpen(true);

// 参数下拉菜单
setMenuItems(createMenuItems(params, parentNodes));
},
close: () => {
onClose();
},
validateFields: async () => {
if (!open) {
return;
}
const [values, error] = await to(form.validateFields());
if (!error && values) {
return values;
} else {
form.scrollToField((error as any)?.errorFields?.[0]?.name, { block: 'center' });
return Promise.reject(error);
}
},
}));
// 参数下拉菜单
setMenuItems(createMenuItems(params, parentNodes));
},
close: () => {
onClose();
},
validateFields: async () => {
if (!open) {
return;
}
const [values, error] = await to(form.validateFields());
if (!error && values) {
return values;
} else {
form.scrollToField((error as any)?.errorFields?.[0]?.name, { block: 'center' });
return Promise.reject(error);
}
},
}),
[form, open],
);

// ref 类型选择
const selectRefData = (


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

@@ -15,7 +15,7 @@ import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import styles from './index.less';

@@ -39,12 +39,8 @@ const Pipeline = () => {
);
const { message } = App.useApp();

useEffect(() => {
getList();
}, [pagination, searchText]);

// 获取流水线模板列表
const getList = () => {
const getList = useCallback(() => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
@@ -56,7 +52,11 @@ const Pipeline = () => {
setTotal(res.data.totalElements);
}
});
};
}, [pagination, searchText]);

useEffect(() => {
getList();
}, [getList]);

// 搜索
const onSearch = (value) => {
@@ -133,7 +133,6 @@ const Pipeline = () => {
current,
pageSize,
});
getList();
};

const columns = [


+ 1
- 1
react-ui/src/pages/System/Config/edit.tsx View File

@@ -42,7 +42,7 @@ const ConfigForm: React.FC<ConfigFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, configTypeOptions]);

const intl = useIntl();
const handleOk = () => {


+ 1
- 1
react-ui/src/pages/System/Dept/edit.tsx View File

@@ -53,7 +53,7 @@ const DeptForm: React.FC<DeptFormProps> = (props) => {
updateBy: props.values.updateBy,
updateTime: props.values.updateTime,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


+ 1
- 1
react-ui/src/pages/System/Dict/edit.tsx View File

@@ -41,7 +41,7 @@ const DictTypeForm: React.FC<DictTypeFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


+ 1
- 1
react-ui/src/pages/System/DictData/edit.tsx View File

@@ -47,7 +47,7 @@ const DictDataForm: React.FC<DataFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


+ 1
- 1
react-ui/src/pages/System/DictData/index.tsx View File

@@ -187,7 +187,7 @@ const DictDataTableList: React.FC = () => {
}
});
}
}, [dictId, dictType, params]);
}, [dictId, dictType, id]);

const columns: ProColumns<API.System.DictData>[] = [
{


+ 1
- 1
react-ui/src/pages/System/Menu/edit.tsx View File

@@ -68,7 +68,7 @@ const MenuForm: React.FC<MenuFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, statusOptions, visibleOptions]);

const intl = useIntl();
const handleOk = () => {


+ 1
- 1
react-ui/src/pages/System/Notice/edit.tsx View File

@@ -44,7 +44,7 @@ const NoticeForm: React.FC<NoticeFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


+ 1
- 1
react-ui/src/pages/System/Post/edit.tsx View File

@@ -42,7 +42,7 @@ const PostForm: React.FC<PostFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


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

@@ -48,7 +48,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
dataScope: props.values.dataScope,
});
setDataScopeType(props.values.dataScope);
}, [props.values]);
}, [props.values, deptCheckedKeys, form]);

const intl = useIntl();
const handleOk = () => {


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

@@ -52,7 +52,7 @@ const RoleForm: React.FC<RoleFormProps> = (props) => {
updateTime: props.values.updateTime,
remark: props.values.remark,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


+ 26
- 20
react-ui/src/pages/System/User/components/DeptTree.tsx View File

@@ -19,31 +19,37 @@ const DeptTree: React.FC<TreeProps> = (props) => {
const [treeData, setTreeData] = useState<any>([]);
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);

const fetchDeptList = async () => {
const hide = message.loading('正在查询');
try {
const res = await getDeptTree({});
const treeData = res.map((item: any) => ({ ...item, key: item.id }));
setTreeData(treeData);
props.onSelect(treeData[0]);
setExpandedKeys([treeData[0].key]);
setSelectedKeys([treeData[0].key]);
hide();
return true;
} catch (error) {
hide();
return false;
}
};
const { onSelect } = props;

useEffect(() => {
const fetchDeptList = async () => {
const hide = message.loading('正在查询');
try {
const res = await getDeptTree({});
const treeData = res.map((item: any) => ({ ...item, key: item.id }));
setTreeData(treeData);
setExpandedKeys([treeData[0].key]);
setSelectedKeys([treeData[0].key]);
hide();
return true;
} catch (error) {
hide();
return false;
}
};

fetchDeptList();
}, []);

const onSelect = (keys: React.Key[], info: any) => {
useEffect(() => {
if (treeData.length > 0) {
onSelect(treeData[0]);
}
}, [treeData, onSelect]);

const handleSelect = (keys: React.Key[], info: any) => {
setSelectedKeys(keys);
props.onSelect(info.node);
onSelect(info.node);
};

const onExpand = (keys: React.Key[]) => {
@@ -57,7 +63,7 @@ const DeptTree: React.FC<TreeProps> = (props) => {
onExpand={onExpand}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
onSelect={onSelect}
onSelect={handleSelect}
treeData={treeData}
/>
);


+ 1
- 1
react-ui/src/pages/System/User/edit.tsx View File

@@ -65,7 +65,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
gitLinkUsername: props.values.gitLinkUsername,
gitLinkPassword: props.values.gitLinkPassword,
});
}, [form, props]);
}, [form, props, statusOptions]);

const intl = useIntl();
const handleOk = () => {


+ 2
- 1
react-ui/src/pages/Tool/Gen/edit.tsx View File

@@ -114,6 +114,7 @@ const TableList: React.FC = () => {
};
useEffect(() => {
setStepComponent(getCurrentStepAndComponent(stepKey));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stepKey]);

useEffect(() => {
@@ -150,7 +151,7 @@ const TableList: React.FC = () => {
message.error(res.msg);
}
});
}, []);
}, [tableId]);

// const onFinish = (values: any) => {
// console.log('Success:', values);


+ 2
- 1
react-ui/src/pages/User/Login/login.tsx View File

@@ -49,7 +49,8 @@ const Login = () => {
} else {
form.setFieldsValue({ username: '', password: '', autoLogin: false });
}
}, []);
}, [form]);

const getCaptchaCode = async () => {
const [res] = await to(getCaptchaImg());
if (res) {


+ 39
- 37
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

@@ -7,46 +7,48 @@ import styles from './index.less';
function AssetsManagement() {
const [type, setType] = useState(CommonTabKeys.Public);
const [assetCounts, setAssetCounts] = useState<{ title: string; value: number }[]>([]);

useEffect(() => {
// 获取工作空间资产数量
const getWorkspacAssetCount = async () => {
const params = {
isPublic: type === CommonTabKeys.Public,
};
const [res] = await to(getWorkspaceAssetCountReq(params));
if (res && res.data) {
const { component, dataset, image, model, workflow } = res.data;
const items = [
{
title: '数据集',
value: dataset,
},
{
title: '模型',
value: model,
},
{
title: '镜像',
value: image,
},
{
title: '组件',
value: component,
},
// {
// title: '代码配置',
// value: 0,
// },
{
title: '流水线模版',
value: workflow,
},
];
setAssetCounts(items);
}
};

getWorkspacAssetCount();
}, [type]);
// 获取工作空间资产数量
const getWorkspacAssetCount = async () => {
const params = {
isPublic: type === CommonTabKeys.Public,
};
const [res] = await to(getWorkspaceAssetCountReq(params));
if (res && res.data) {
const { component, dataset, image, model, workflow } = res.data;
const items = [
{
title: '数据集',
value: dataset,
},
{
title: '模型',
value: model,
},
{
title: '镜像',
value: image,
},
{
title: '组件',
value: component,
},
// {
// title: '代码配置',
// value: 0,
// },
{
title: '流水线模版',
value: workflow,
},
];
setAssetCounts(items);
}
};

return (
<div className={styles['assets-management']}>


+ 91
- 87
react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx View File

@@ -1,6 +1,6 @@
import themes from '@/styles/theme.less';
import * as echarts from 'echarts';
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useMemo, useRef } from 'react';
import styles from './index.less';

const color1 = new echarts.graphic.LinearGradient(
@@ -94,102 +94,106 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
chartData.Running +
chartData.Succeeded +
chartData.Terminated;
const options: echarts.EChartsOption = {
title: {
show: true,
left: '29%',
top: 'center',
textAlign: 'center',
text: [`{a|${total}}`, '{b|实验状态}'].join('\n'),
textStyle: {
rich: {
a: {
color: themes['textColor'],
fontSize: 20,
fontWeight: 700,
lineHeight: 28,
},
b: {
color: themes['textColorSecondary'],
fontSize: 10,
fontWeight: 'normal',

const options: echarts.EChartsOption = useMemo(
() => ({
title: {
show: true,
left: '29%',
top: 'center',
textAlign: 'center',
text: [`{a|${total}}`, '{b|实验状态}'].join('\n'),
textStyle: {
rich: {
a: {
color: themes['textColor'],
fontSize: 20,
fontWeight: 700,
lineHeight: 28,
},
b: {
color: themes['textColorSecondary'],
fontSize: 10,
fontWeight: 'normal',
},
},
},
},
},
tooltip: {
trigger: 'item',
},
legend: {
top: 'center',
right: '5%',
orient: 'vertical',
icon: 'circle',
itemWidth: 6,
itemGap: 20,
height: 100,
},
color: [color1, color2, color3, color4, color5],
series: [
{
type: 'pie',
radius: ['70%', '80%'],
center: ['30%', '50%'],
avoidLabelOverlap: false,
padAngle: 3,
itemStyle: {
borderRadius: 3,
},
minAngle: 5,
label: {
show: false,
},
emphasis: {
tooltip: {
trigger: 'item',
},
legend: {
top: 'center',
right: '5%',
orient: 'vertical',
icon: 'circle',
itemWidth: 6,
itemGap: 20,
height: 100,
},
color: [color1, color2, color3, color4, color5],
series: [
{
type: 'pie',
radius: ['70%', '80%'],
center: ['30%', '50%'],
avoidLabelOverlap: false,
padAngle: 3,
itemStyle: {
borderRadius: 3,
},
minAngle: 5,
label: {
show: false,
},
emphasis: {
label: {
show: false,
},
},
labelLine: {
show: false,
},
data: [
{ value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' },
{ value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' },
{ value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' },
{ value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' },
{ value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' },
],
},
labelLine: {
show: false,
},
data: [
{ value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' },
{ value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' },
{ value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' },
{ value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' },
{ value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' },
],
},
{
type: 'pie',
radius: '60%',
center: ['30%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
},
tooltip: {
show: false,
},
emphasis: {
{
type: 'pie',
radius: '60%',
center: ['30%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
},
disabled: true,
},
animation: false,
labelLine: {
show: false,
},
data: [
{
value: 100,
itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 },
tooltip: {
show: false,
},
],
},
],
};
emphasis: {
label: {
show: false,
},
disabled: true,
},
animation: false,
labelLine: {
show: false,
},
data: [
{
value: 100,
itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 },
},
],
},
],
}),
[chartData, total],
);

useEffect(() => {
// 创建一个echarts实例,返回echarts实例
@@ -203,7 +207,7 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
// myChart.dispose() 销毁实例
chart.dispose();
};
}, []);
}, [options]);

return (
<div className={styles['experiment-chart']} style={style}>


+ 0
- 16
react-ui/src/state/computingResourceStore.ts View File

@@ -1,16 +0,0 @@
import { ComputingResource } from '@/types';
import { proxy } from 'umi';

type ComputingResourceStore = {
computingResource: ComputingResource[];
};

const state = proxy<ComputingResourceStore>({
computingResource: [],
});

export const setComputingResource = (computingResource: ComputingResource[]) => {
state.computingResource = computingResource;
};

export default state;

+ 53
- 0
react-ui/src/stories/mockData.ts View File

@@ -1,3 +1,4 @@
// 数据集列表
export const datasetListData = {
msg: '操作成功',
code: 200,
@@ -76,6 +77,7 @@ export const datasetListData = {
},
};

// 数据集版本列表
export const datasetVersionData = {
msg: '操作成功',
code: 200,
@@ -107,6 +109,7 @@ export const datasetVersionData = {
],
};

// 数据集详情
export const datasetDetailData = {
msg: '操作成功',
code: 200,
@@ -137,6 +140,7 @@ export const datasetDetailData = {
},
};

// 模型列表
export const modelListData = {
msg: '操作成功',
code: 200,
@@ -211,6 +215,7 @@ export const modelListData = {
},
};

// 模型版本列表
export const modelVersionData = {
msg: '操作成功',
code: 200,
@@ -234,6 +239,7 @@ export const modelVersionData = {
],
};

// 模型详情
export const modelDetailData = {
msg: '操作成功',
code: 200,
@@ -266,6 +272,7 @@ export const modelDetailData = {
},
};

// 镜像列表
export const mirrorListData = {
code: 200,
msg: '操作成功',
@@ -384,6 +391,7 @@ export const mirrorListData = {
},
};

// 镜像版本列表
export const mirrorVerionData = {
code: 200,
msg: '操作成功',
@@ -433,6 +441,7 @@ export const mirrorVerionData = {
},
};

// 代码配置列表
export const codeListData = {
code: 200,
msg: '操作成功',
@@ -547,6 +556,7 @@ export const codeListData = {
},
};

// 服务列表
export const serviceListData = {
code: 200,
msg: '操作成功',
@@ -620,6 +630,7 @@ export const serviceListData = {
},
};

// 计算资源列表
export const computeResourceData = {
code: 200,
msg: '操作成功',
@@ -793,3 +804,45 @@ export const computeResourceData = {
empty: false,
},
};

// 日志组
export const logGroupData = {
code: 200,
msg: '操作成功',
data: {
log_type: 'normal',
log_detail: {
pod_name: 'workflow-txpb5-git-clone-05955a53-2484323670',
log_content:
'[2025-03-06 14:02:23] time="2025-03-06T14:02:23.068Z" level=info msg="capturing logs" argo=true\n[2025-03-06 14:02:23] Cloning into \'/tmp/traincode\'...\n[2025-03-06 14:02:23] Cloning public repository without authentication.\n[2025-03-06 14:02:23] Repository cloned successfully.\n[2025-03-06 14:02:24] time="2025-03-06T14:02:24.069Z" level=info msg="sub-process exited" argo=true error="<nil>"\n',
start_time: '1741240944069759628',
},
pods: ['workflow-txpb5-git-clone-05955a53-2484323670'],
},
};

// 日志
export const logData = {
code: 200,
msg: '操作成功',
data: {
log_detail: {
pod_name: 'workflow-txpb5-git-clone-05955a53-2484323670',
log_content:
'[2025-03-06 14:02:24] time="2025-03-06T06:02:24.315Z" level=info msg="Main container completed" error="<nil>"\n[2025-03-06 14:02:24] time="2025-03-06T06:02:24.315Z" level=info msg="No Script output reference in workflow. Capturing script output ignored"\n[2025-03-06 14:02:24] time="2025-03-06T06:02:24.315Z" level=info msg="No output parameters"\n[2025-03-06 14:02:24] time="2025-03-06T06:02:24.315Z" level=info msg="No output artifacts"\n[2025-03-06 14:02:24] time="2025-03-06T06:02:24.315Z" level=info msg="S3 Save path: /tmp/argo/outputs/logs/main.log, key: workflow-txpb5/workflow-txpb5-git-clone-05955a53-2484323670/main.log"\n[2025-03-06 14:02:24] time="2025-03-06T06:02:24.315Z" level=info msg="Creating minio client using static credentials" endpoint="minio.argo.svc.cluster.local:9000"\n[2025-03-06 14:02:24] time="2025-03-06T06:02:24.315Z" level=info msg="Saving file to s3" bucket=my-bucket endpoint="minio.argo.svc.cluster.local:9000" key=workflow-txpb5/workflow-txpb5-git-clone-05955a53-2484323670/main.log path=/tmp/argo/outputs/logs/main.log\n[2025-03-06 14:02:25] time="2025-03-06T06:02:25.407Z" level=info msg="Save artifact" artifactName=main-logs duration=1.092064185s error="<nil>" key=workflow-txpb5/workflow-txpb5-git-clone-05955a53-2484323670/main.log\n[2025-03-06 14:02:25] time="2025-03-06T06:02:25.407Z" level=info msg="not deleting local artifact" localArtPath=/tmp/argo/outputs/logs/main.log\n[2025-03-06 14:02:25] time="2025-03-06T06:02:25.407Z" level=info msg="Successfully saved file: /tmp/argo/outputs/logs/main.log"\n[2025-03-06 14:02:25] time="2025-03-06T06:02:25.408Z" level=warning msg="failed to patch task result, falling back to legacy/insecure pod patch, see https://argo-workflows.readthedocs.io/en/release-3.5/workflow-rbac/" error="workflowtaskresults.argoproj.io is forbidden: User \\"system:serviceaccount:argo:argo\\" cannot create resource \\"workflowtaskresults\\" in API group \\"argoproj.io\\" in the namespace \\"argo\\""\n[2025-03-06 14:02:25] time="2025-03-06T06:02:25.418Z" level=info msg="Alloc=8553 TotalAlloc=14914 Sys=24421 NumGC=4 Goroutines=10"\n[2025-03-06 14:02:25] time="2025-03-06T06:02:25.419Z" level=warning msg="failed to patch task result, falling back to legacy/insecure pod patch, see https://argo-workflows.readthedocs.io/en/release-3.5/workflow-rbac/" error="workflowtaskresults.argoproj.io \\"workflow-txpb5-2484323670\\" is forbidden: User \\"system:serviceaccount:argo:argo\\" cannot patch resource \\"workflowtaskresults\\" in API group \\"argoproj.io\\" in the namespace \\"argo\\""\n[2025-03-06 14:02:25] time="2025-03-06T06:02:25.427Z" level=info msg="Deadline monitor stopped"\n',
start_time: '1741240945427542429',
},
},
};

export const logEmptyData = {
code: 200,
msg: '操作成功',
data: {
log_detail: {
pod_name: 'workflow-txpb5-git-clone-05955a53-2484323670',
log_content: '',
start_time: '1741240945427542429',
},
},
};

+ 90
- 0
react-ui/src/stories/pages/LogList.stories.tsx View File

@@ -0,0 +1,90 @@
import { ExperimentStatus } from '@/enums';
import LogList from '@/pages/Experiment/components/LogList';
import type { Meta, StoryObj } from '@storybook/react';
import { http, HttpResponse } from 'msw';
import { logData, logEmptyData, logGroupData } from '../mockData';

let count = 0;

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Pages/LogList 日志组',
component: LogList,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
// layout: 'centered',
msw: {
handlers: [
http.post('/api/mmp/experimentIns/realTimeLog', () => {
return HttpResponse.json(logGroupData);
}),
http.get('/api/mmp/experimentIns/pods/log', () => {
if (count > 0) {
count = 0;
return HttpResponse.json(logEmptyData);
}
count += 1;
return HttpResponse.json(logData);
}),
],
},
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
instanceNodeStatus: { control: 'select', options: Object.values(ExperimentStatus) },
},
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
// args: { onClick: fn() },
} satisfies Meta<typeof LogList>;

export default meta;
type Story = StoryObj<typeof meta>;

// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
/** 运行完成时 */
export const Succeeded: Story = {
args: {
instanceName: 'workflow-txpb5',
instanceNamespace: 'argo',
pipelineNodeId: 'git-clone-05955a53',
workflowId: 'workflow-txpb5-2484323670',
instanceNodeStartTime: '2025-03-06 14:02:14',
instanceNodeStatus: ExperimentStatus.Succeeded,
},
};

/** 无状态,空数据 */
export const NoStatus: Story = {
args: {
instanceName: 'workflow-txpb5',
instanceNamespace: 'argo',
pipelineNodeId: 'git-clone-05955a53',
workflowId: 'workflow-txpb5-2484323670',
instanceNodeStartTime: '2025-03-06 14:02:14',
},
};

/** Pending */
export const Pending: Story = {
args: {
instanceName: 'workflow-txpb5',
instanceNamespace: 'argo',
pipelineNodeId: 'git-clone-05955a53',
workflowId: 'workflow-txpb5-2484323670',
instanceNodeStartTime: '2025-03-06 14:02:14',
instanceNodeStatus: ExperimentStatus.Pending,
},
};

export const Running: Story = {
args: {
instanceName: 'workflow-txpb5',
instanceNamespace: 'argo',
pipelineNodeId: 'git-clone-05955a53',
workflowId: 'workflow-txpb5-2484323670',
instanceNodeStartTime: '2025-03-06 14:02:14',
instanceNodeStatus: ExperimentStatus.Running,
},
};

+ 8
- 2
react-ui/src/utils/index.ts View File

@@ -88,7 +88,10 @@ export function camelCaseToUnderscore(obj: Record<string, any>) {
}

// null to undefined
export function nullToUndefined(obj: Record<string, any>) {
export function nullToUndefined(obj: Record<string, any> | null) {
if (obj === null) {
return undefined;
}
if (!isPlainObject(obj)) {
return obj;
}
@@ -111,7 +114,10 @@ export function nullToUndefined(obj: Record<string, any>) {
}

// undefined to null
export function undefinedToNull(obj: Record<string, any>) {
export function undefinedToNull(obj?: Record<string, any>) {
if (obj === undefined) {
return null;
}
if (!isPlainObject(obj)) {
return obj;
}


+ 1
- 0
react-ui/src/utils/table.tsx View File

@@ -162,6 +162,7 @@ function renderText(text: any | undefined | null, ellipsis: boolean | 'auto') {
wordBreak: 'break-all',
display: 'inline-block',
maxWidth: '100%',
verticalAlign: 'middle',
}
: undefined
}


Loading…
Cancel
Save