| @@ -5,4 +5,5 @@ | |||||
| public | public | ||||
| dist | dist | ||||
| .umi | .umi | ||||
| mock | |||||
| mock | |||||
| /src/iconfont/ | |||||
| @@ -1,10 +1,16 @@ | |||||
| module.exports = { | 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: { | globals: { | ||||
| page: true, | page: true, | ||||
| REACT_APP_ENV: true, | REACT_APP_ENV: true, | ||||
| }, | }, | ||||
| rules: { | rules: { | ||||
| '@typescript-eslint/no-use-before-define': 'off', | '@typescript-eslint/no-use-before-define': 'off', | ||||
| 'react/react-in-jsx-scope': 'off', | |||||
| 'react/display-name': 'off' | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -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,6 +5,7 @@ import type { Preview } from '@storybook/react'; | |||||
| import { App, ConfigProvider } from 'antd'; | import { App, ConfigProvider } from 'antd'; | ||||
| import zhCN from 'antd/locale/zh_CN'; | import zhCN from 'antd/locale/zh_CN'; | ||||
| import { initialize, mswLoader } from 'msw-storybook-addon'; | import { initialize, mswLoader } from 'msw-storybook-addon'; | ||||
| import { createWebSocketMock } from './mock/websocket.mock'; | |||||
| import './storybook.css'; | import './storybook.css'; | ||||
| /* | /* | ||||
| @@ -14,6 +15,10 @@ import './storybook.css'; | |||||
| */ | */ | ||||
| initialize(); | initialize(); | ||||
| // 替换全局 WebSocket 为 Mock 版本 | |||||
| // @ts-ignore | |||||
| global.WebSocket = createWebSocketMock(); | |||||
| const preview: Preview = { | const preview: Preview = { | ||||
| parameters: { | parameters: { | ||||
| controls: { | controls: { | ||||
| @@ -114,6 +114,7 @@ | |||||
| "@umijs/max": "^4.0.66", | "@umijs/max": "^4.0.66", | ||||
| "cross-env": "^7.0.3", | "cross-env": "^7.0.3", | ||||
| "eslint": "^8.39.0", | "eslint": "^8.39.0", | ||||
| "eslint-plugin-react-hooks": "~5.2.0", | |||||
| "eslint-plugin-storybook": "~0.11.2", | "eslint-plugin-storybook": "~0.11.2", | ||||
| "express": "^4.18.2", | "express": "^4.18.2", | ||||
| "gh-pages": "^5.0.0", | "gh-pages": "^5.0.0", | ||||
| @@ -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); | //console.log('patchRoutes', e); | ||||
| }; | }; | ||||
| @@ -232,7 +232,7 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| memo.theme.components.Table = { | memo.theme.components.Table = { | ||||
| headerBg: 'rgba(242, 244, 247, 0.36)', | headerBg: 'rgba(242, 244, 247, 0.36)', | ||||
| headerBorderRadius: 4, | headerBorderRadius: 4, | ||||
| rowSelectedBg: 'rgba(22, 100, 255, 0.05)', | |||||
| // rowSelectedBg: 'rgba(22, 100, 255, 0.05)', 固定列时,横向滑动导致重叠 | |||||
| }; | }; | ||||
| memo.theme.components.Tabs = { | memo.theme.components.Tabs = { | ||||
| titleFontSize: 16, | titleFontSize: 16, | ||||
| @@ -33,23 +33,23 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | const [inputText, setInputText] = useState<string | undefined>(undefined); | ||||
| useEffect(() => { | 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(); | getDataList(); | ||||
| }, [pagination, searchText]); | }, [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) => { | const handleSearch = (value: string) => { | ||||
| setSearchText(value); | setSearchText(value); | ||||
| @@ -45,23 +45,20 @@ type IframePageProps = { | |||||
| function IframePage({ type, className, style }: IframePageProps) { | function IframePage({ type, className, style }: IframePageProps) { | ||||
| const [iframeUrl, setIframeUrl] = useState(''); | const [iframeUrl, setIframeUrl] = useState(''); | ||||
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| useEffect(() => { | 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 = () => { | const hideLoading = () => { | ||||
| setLoading(false); | setLoading(false); | ||||
| @@ -39,8 +39,20 @@ function ParameterSelect({ | |||||
| const valueText = typeof value === 'object' && value !== null ? value.value : value; | const valueText = typeof value === 'object' && value !== null ? value.value : value; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取下拉数据 | |||||
| const getSelectOptions = async () => { | |||||
| if (!propsConfig) { | |||||
| return; | |||||
| } | |||||
| const getOptions = propsConfig.getOptions; | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| } | |||||
| }; | |||||
| getSelectOptions(); | getSelectOptions(); | ||||
| }, []); | |||||
| }, [propsConfig]); | |||||
| const handleChange = (text: string) => { | const handleChange = (text: string) => { | ||||
| if (typeof value === 'object' && value !== null) { | 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) { | if (display) { | ||||
| return ( | return ( | ||||
| <FormInfo | <FormInfo | ||||
| @@ -72,7 +72,7 @@ function ResourceSelect({ | |||||
| ]) as ResourceSelectorResponse; | ]) as ResourceSelectorResponse; | ||||
| setSelectedResource(originResource); | setSelectedResource(originResource); | ||||
| } | } | ||||
| }, [value]); | |||||
| }, [value, type]); | |||||
| const selectResource = () => { | const selectResource = () => { | ||||
| const resource = selectedResource; | const resource = selectedResource; | ||||
| @@ -91,16 +91,7 @@ function ResourceSelectorModal({ | |||||
| const treeRef = useRef<TreeRef>(null); | const treeRef = useRef<TreeRef>(null); | ||||
| const config = selectorTypeConfig[type]; | const config = selectorTypeConfig[type]; | ||||
| useEffect(() => { | |||||
| setExpandedKeys([]); | |||||
| setCheckedKeys([]); | |||||
| setLoadedKeys([]); | |||||
| setFiles([]); | |||||
| setVersionPath(''); | |||||
| setSearchText(''); | |||||
| getTreeData(); | |||||
| }, [activeTab, type]); | |||||
| // 搜索 | |||||
| const treeData = useMemo( | const treeData = useMemo( | ||||
| () => | () => | ||||
| originTreeData.filter((v) => | originTreeData.filter((v) => | ||||
| @@ -109,19 +100,45 @@ function ResourceSelectorModal({ | |||||
| [originTreeData, searchText], | [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) => { | const getVersions = async (parentId: string, parentNode: any) => { | ||||
| @@ -136,10 +153,10 @@ function ResourceSelectorModal({ | |||||
| setLoadedKeys((prev) => prev.concat(parentId)); | setLoadedKeys((prev) => prev.concat(parentId)); | ||||
| } | } | ||||
| // 恢复上一次的 Check 操作 | |||||
| // 恢复上一次的 Check 操作,需要延时以便 TreeData 更新完 | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| restoreLastCheck(parentId, res); | restoreLastCheck(parentId, res); | ||||
| }, 300); | |||||
| }, 100); | |||||
| } else { | } else { | ||||
| setExpandedKeys([]); | setExpandedKeys([]); | ||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| @@ -158,7 +175,7 @@ function ResourceSelectorModal({ | |||||
| } | } | ||||
| }; | }; | ||||
| // 动态加载 tree children | |||||
| // 展开时,动态加载 tree children | |||||
| const onLoadData = ({ key, children, ...rest }: TreeDataNode) => { | const onLoadData = ({ key, children, ...rest }: TreeDataNode) => { | ||||
| if (children) { | if (children) { | ||||
| return Promise.resolve(); | 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 操作 | // 恢复上一次的 Check 操作 | ||||
| // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 | // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 | ||||
| // fisrtLoadVersions 标志位 | // fisrtLoadVersions 标志位 | ||||
| const restoreLastCheck = (parentId: string, versions: TreeDataNode[]) => { | 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 last = defaultCheckedKeys[0] as string; | ||||
| const { id } = getIdAndVersion(last); | const { id } = getIdAndVersion(last); | ||||
| // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 | // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 | ||||
| if (id === parentId) { | if (id === parentId) { | ||||
| setCheckedKeys(defaultCheckedKeys); | |||||
| const parentNode = versions.find((v) => v.key === last); | |||||
| getFiles(last, parentNode); | |||||
| setFirstLoadVersions(true); | |||||
| setTimeout(() => { | 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); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -105,6 +105,7 @@ export function useDomSize<T extends HTMLElement>( | |||||
| return () => { | return () => { | ||||
| window.removeEventListener('resize', debounceFunc); | window.removeEventListener('resize', debounceFunc); | ||||
| }; | }; | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [domRef, ...deps]); | }, [domRef, ...deps]); | ||||
| return [domRef, { width, height }] as const; | 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. | * Executes the effect function when the specified condition is true. | ||||
| * | * | ||||
| * @param effect - The effect function to execute. | * @param effect - The effect function to execute. | ||||
| * @param deps - The dependencies for the effect. | |||||
| * @param when - The condition to trigger 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)[]>([]); | const requestFns = useRef<(() => void)[]>([]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (when) { | if (when) { | ||||
| @@ -147,6 +148,7 @@ export const useEffectWhen = (effect: () => void, deps: React.DependencyList, wh | |||||
| } else { | } else { | ||||
| requestFns.current.splice(0, 1, effect); | requestFns.current.splice(0, 1, effect); | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, deps); | }, deps); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -185,7 +187,7 @@ export const useCheck = <T>(list: T[]) => { | |||||
| } | } | ||||
| }); | }); | ||||
| }, | }, | ||||
| [selected, isSingleChecked], | |||||
| [isSingleChecked], | |||||
| ); | ); | ||||
| return [ | return [ | ||||
| @@ -5,40 +5,39 @@ | |||||
| */ | */ | ||||
| import { getComputingResourceReq } from '@/services/pipeline'; | import { getComputingResourceReq } from '@/services/pipeline'; | ||||
| import computingResourceState, { setComputingResource } from '@/state/computingResourceStore'; | |||||
| import { ComputingResource } from '@/types'; | import { ComputingResource } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { type SelectProps } from 'antd'; | import { type SelectProps } from 'antd'; | ||||
| import { useCallback, useEffect, useState } from 'react'; | import { useCallback, useEffect, useState } from 'react'; | ||||
| import { useSnapshot } from 'umi'; | |||||
| const computingResource: ComputingResource[] = []; | |||||
| // 获取资源规格 | // 获取资源规格 | ||||
| export function useComputingResource() { | export function useComputingResource() { | ||||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | ||||
| const snap = useSnapshot(computingResourceState); | |||||
| useEffect(() => { | 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 { | } else { | ||||
| getComputingResource(); | 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'] = | const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = | ||||
| useCallback((input: string, option?: ComputingResource) => { | useCallback((input: string, option?: ComputingResource) => { | ||||
| @@ -50,7 +49,10 @@ export function useComputingResource() { | |||||
| // 根据 standard 获取 description | // 根据 standard 获取 description | ||||
| const getDescription = useCallback( | const getDescription = useCallback( | ||||
| (standard: string) => { | |||||
| (standard?: string) => { | |||||
| if (!standard) { | |||||
| return undefined; | |||||
| } | |||||
| return resourceStandardList.find((item) => item.standard === standard)?.description; | return resourceStandardList.find((item) => item.standard === standard)?.description; | ||||
| }, | }, | ||||
| [resourceStandardList], | [resourceStandardList], | ||||
| @@ -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]; | |||||
| } | |||||
| @@ -3,7 +3,7 @@ import { loginByOauth2Req } from '@/services/auth'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { history, useModel, useSearchParams } from '@umijs/max'; | import { history, useModel, useSearchParams } from '@umijs/max'; | ||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { useEffect } from 'react'; | |||||
| import { useCallback, useEffect } from 'react'; | |||||
| import { flushSync } from 'react-dom'; | import { flushSync } from 'react-dom'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -12,12 +12,21 @@ function Authorize() { | |||||
| const [searchParams] = useSearchParams(); | const [searchParams] = useSearchParams(); | ||||
| const code = searchParams.get('code'); | const code = searchParams.get('code'); | ||||
| const redirect = searchParams.get('redirect'); | 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 = { | const params = { | ||||
| code, | code, | ||||
| }; | }; | ||||
| @@ -29,19 +38,11 @@ function Authorize() { | |||||
| await fetchUserInfo(); | await fetchUserInfo(); | ||||
| history.push(redirect || '/'); | 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>; | return <div className={styles.container}></div>; | ||||
| } | } | ||||
| @@ -29,60 +29,60 @@ function CreateAutoML() { | |||||
| const isCopy = pathname.includes('copy'); | const isCopy = pathname.includes('copy'); | ||||
| useEffect(() => { | 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)) { | if (id && !Number.isNaN(id)) { | ||||
| getAutoMLInfo(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) => { | const createExperiment = async (formData: FormData) => { | ||||
| @@ -19,18 +19,18 @@ function AutoMLInfo() { | |||||
| const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取自动机器学习详情 | |||||
| const getAutoMLInfo = async () => { | |||||
| const [res] = await to(getAutoMLInfoReq({ id: autoMLId })); | |||||
| if (res && res.data) { | |||||
| setAutoMLInfo(res.data); | |||||
| } | |||||
| }; | |||||
| if (autoMLId) { | if (autoMLId) { | ||||
| getAutoMLInfo(); | getAutoMLInfo(); | ||||
| } | } | ||||
| }, []); | |||||
| // 获取自动机器学习详情 | |||||
| const getAutoMLInfo = async () => { | |||||
| const [res] = await to(getAutoMLInfoReq({ id: autoMLId })); | |||||
| if (res && res.data) { | |||||
| setAutoMLInfo(res.data); | |||||
| } | |||||
| }; | |||||
| }, [autoMLId]); | |||||
| return ( | return ( | ||||
| <div className={styles['auto-ml-info']}> | <div className={styles['auto-ml-info']}> | ||||
| @@ -39,6 +39,7 @@ function AutoMLInstance() { | |||||
| return () => { | return () => { | ||||
| closeSSE(); | closeSSE(); | ||||
| }; | }; | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []); | }, []); | ||||
| // 获取实验实例详情 | // 获取实验实例详情 | ||||
| @@ -83,9 +84,6 @@ function AutoMLInstance() { | |||||
| const setupSSE = (name: string, namespace: string) => { | const setupSSE = (name: string, namespace: string) => { | ||||
| const { origin } = location; | 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 params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | ||||
| const evtSource = new EventSource( | const evtSource = new EventSource( | ||||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | ||||
| @@ -25,36 +25,36 @@ type TableData = { | |||||
| function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { | function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { | ||||
| const [tableData, setTableData] = useState<TableData[]>([]); | const [tableData, setTableData] = useState<TableData[]>([]); | ||||
| useEffect(() => { | 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) { | if (fileUrl) { | ||||
| getHistoryFile(); | 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'] = [ | const columns: TableProps<TableData>['columns'] = [ | ||||
| { | { | ||||
| @@ -53,7 +53,7 @@ function ExperimentInstanceComponent({ | |||||
| if (allIntanceIds.length === 0) { | if (allIntanceIds.length === 0) { | ||||
| setSelectedIns([]); | setSelectedIns([]); | ||||
| } | } | ||||
| }, [experimentInsList]); | |||||
| }, [allIntanceIds, setSelectedIns]); | |||||
| // 删除实验实例确认 | // 删除实验实例确认 | ||||
| const handleRemove = (instance: ExperimentInstance) => { | const handleRemove = (instance: ExperimentInstance) => { | ||||
| @@ -28,7 +28,7 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import ExperimentInstance from '../ExperimentInstance'; | import ExperimentInstance from '../ExperimentInstance'; | ||||
| import { ExperimentListType, experimentListConfig } from './config'; | import { ExperimentListType, experimentListConfig } from './config'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -58,12 +58,8 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| ); | ); | ||||
| const config = experimentListConfig[type]; | const config = experimentListConfig[type]; | ||||
| useEffect(() => { | |||||
| getAutoMLList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取自主机器学习或超参数自动优化列表 | // 获取自主机器学习或超参数自动优化列表 | ||||
| const getAutoMLList = async () => { | |||||
| const getAutoMLList = useCallback(async () => { | |||||
| const params: Record<string, any> = { | const params: Record<string, any> = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -76,7 +72,11 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| setTableData(content); | setTableData(content); | ||||
| setTotal(totalElements); | setTotal(totalElements); | ||||
| } | } | ||||
| }; | |||||
| }, [pagination, searchText, config]); | |||||
| useEffect(() => { | |||||
| getAutoMLList(); | |||||
| }, [getAutoMLList]); | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch: SearchProps['onSearch'] = (value) => { | const onSearch: SearchProps['onSearch'] = (value) => { | ||||
| @@ -22,19 +22,19 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp | |||||
| }, [imageUrl]); | }, [imageUrl]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取实验运行历史记录 | |||||
| const getResultFile = async () => { | |||||
| const [res] = await to(getFileReq(fileUrl)); | |||||
| if (res) { | |||||
| setResult(res as any as string); | |||||
| } | |||||
| }; | |||||
| if (fileUrl) { | if (fileUrl) { | ||||
| getResultFile(); | getResultFile(); | ||||
| } | } | ||||
| }, [fileUrl]); | }, [fileUrl]); | ||||
| // 获取实验运行历史记录 | |||||
| const getResultFile = async () => { | |||||
| const [res] = await to(getFileReq(fileUrl)); | |||||
| if (res) { | |||||
| setResult(res as any as string); | |||||
| } | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-result']}> | <div className={styles['experiment-result']}> | ||||
| <InfoGroup title="实验结果" height={420} width="100%"> | <InfoGroup title="实验结果" height={420} width="100%"> | ||||
| @@ -13,7 +13,7 @@ import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | 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 AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | ||||
| import CodeConfigItem from '../components/CodeConfigItem'; | import CodeConfigItem from '../components/CodeConfigItem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -50,12 +50,8 @@ function CodeConfigList() { | |||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | const [inputText, setInputText] = useState<string | undefined>(undefined); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| useEffect(() => { | |||||
| getDataList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取数据请求 | // 获取数据请求 | ||||
| const getDataList = async () => { | |||||
| const getDataList = useCallback(async () => { | |||||
| const params = { | const params = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -69,7 +65,11 @@ function CodeConfigList() { | |||||
| setDataList([]); | setDataList([]); | ||||
| setTotal(0); | setTotal(0); | ||||
| } | } | ||||
| }; | |||||
| }, [pagination, searchText]); | |||||
| useEffect(() => { | |||||
| getDataList(); | |||||
| }, [getDataList]); | |||||
| // 删除请求 | // 删除请求 | ||||
| const deleteRecord = async (id: number) => { | const deleteRecord = async (id: number) => { | ||||
| @@ -18,7 +18,7 @@ import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useParams, useSearchParams } from '@umijs/max'; | import { useParams, useSearchParams } from '@umijs/max'; | ||||
| import { App, Button, Flex, Select, Tabs } from 'antd'; | import { App, Button, Flex, Select, Tabs } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import AddVersionModal from '../AddVersionModal'; | import AddVersionModal from '../AddVersionModal'; | ||||
| import ResourceIntro from '../ResourceIntro'; | import ResourceIntro from '../ResourceIntro'; | ||||
| import ResourceVersion from '../ResourceVersion'; | import ResourceVersion from '../ResourceVersion'; | ||||
| @@ -45,7 +45,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| // 模型演化传入的 tab | // 模型演化传入的 tab | ||||
| const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | ||||
| // 模型演化传入的版本 | // 模型演化传入的版本 | ||||
| let versionParam = searchParams.get('version'); | |||||
| const versionParam = searchParams.get('version'); | |||||
| const name = searchParams.get('name') || ''; | const name = searchParams.get('name') || ''; | ||||
| const owner = searchParams.get('owner') || ''; | const owner = searchParams.get('owner') || ''; | ||||
| const identifier = searchParams.get('identifier') || ''; | const identifier = searchParams.get('identifier') || ''; | ||||
| @@ -57,63 +57,60 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| const typeName = config.name; // 数据集/模型 | const typeName = config.name; // 数据集/模型 | ||||
| const { message } = App.useApp(); | 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 request = config.getInfo; | ||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setInfo(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 { | } 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 = () => { | const showModal = () => { | ||||
| @@ -125,7 +122,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| identifier: info.identifier, | identifier: info.identifier, | ||||
| is_public: is_public, | is_public: is_public, | ||||
| onOk: () => { | onOk: () => { | ||||
| getVersionList(); | |||||
| getVersionList(true); | |||||
| close(); | close(); | ||||
| }, | }, | ||||
| }); | }); | ||||
| @@ -172,12 +169,12 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| getVersionList(); | |||||
| getVersionList(true); | |||||
| } | } | ||||
| }; | }; | ||||
| // 处理删除 | // 处理删除 | ||||
| const hanldeDelete = () => { | |||||
| const handleDelete = () => { | |||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '删除后,该版本将不可恢复', | title: '删除后,该版本将不可恢复', | ||||
| content: '是否确认删除?', | content: '是否确认删除?', | ||||
| @@ -268,7 +265,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| style={{ marginLeft: 'auto', marginRight: 0 }} | style={{ marginLeft: 'auto', marginRight: 0 }} | ||||
| onClick={hanldeDelete} | |||||
| onClick={handleDelete} | |||||
| icon={<KFIcon type="icon-shanchu" />} | icon={<KFIcon type="icon-shanchu" />} | ||||
| disabled={!version} | disabled={!version} | ||||
| danger | danger | ||||
| @@ -8,7 +8,7 @@ import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | 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 { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | ||||
| import AddDatasetModal from '../AddDatasetModal'; | import AddDatasetModal from '../AddDatasetModal'; | ||||
| import ResourceItem from '../ResourceItem'; | import ResourceItem from '../ResourceItem'; | ||||
| @@ -58,9 +58,30 @@ function ResourceList( | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const config = resourceConfig[resourceType]; | 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(() => { | useEffect(() => { | ||||
| getDataList(); | getDataList(); | ||||
| }, [resourceType, dataType, dataTag, pagination, searchText, isPublic]); | |||||
| }, [getDataList]); | |||||
| useImperativeHandle( | useImperativeHandle( | ||||
| ref, | 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 deleteRecord = async (params: { owner: string; identifier: string; repo_id?: number }) => { | ||||
| const request = config.deleteRecord; | const request = config.deleteRecord; | ||||
| @@ -3,7 +3,7 @@ import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { getAssetIcon } from '@/services/dataset/index.js'; | import { getAssetIcon } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Flex, Tabs, type TabsProps } from 'antd'; | 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 { CategoryData, ResourceType, resourceConfig } from '../../config'; | ||||
| import CategoryList from '../CategoryList'; | import CategoryList from '../CategoryList'; | ||||
| import ResourceList, { ResourceListRef } from '../ResourceList'; | import ResourceList, { ResourceListRef } from '../ResourceList'; | ||||
| @@ -23,9 +23,31 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| const dataListRef = useRef<ResourceListRef>(null); | const dataListRef = useRef<ResourceListRef>(null); | ||||
| const config = resourceConfig[resourceType]; | 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(() => { | useEffect(() => { | ||||
| getAssetIconList(); | getAssetIconList(); | ||||
| }, []); | |||||
| }, [getAssetIconList]); | |||||
| // 分类搜索 | // 分类搜索 | ||||
| const handleCategorySearch = (value: string) => { | const handleCategorySearch = (value: string) => { | ||||
| @@ -42,25 +64,6 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| setActiveTag((prev) => (prev === record.name ? undefined : record.name)); | 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,重置数据 | // 切换 Tab,重置数据 | ||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | const hanleTabChange: TabsProps['onChange'] = (value) => { | ||||
| dataListRef.current?.reset(); | dataListRef.current?.reset(); | ||||
| @@ -127,28 +127,28 @@ function VersionCompareModal({ | |||||
| text: '版本描述', | text: '版本描述', | ||||
| }, | }, | ||||
| ], | ], | ||||
| [], | |||||
| [resourceType], | |||||
| ); | ); | ||||
| useEffect(() => { | 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>( | function getValue<T extends DatasetData | ModelData>( | ||||
| @@ -29,7 +29,7 @@ import { | |||||
| type TableProps, | type TableProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import CreateMirrorModal from '../components/CreateMirrorModal'; | import CreateMirrorModal from '../components/CreateMirrorModal'; | ||||
| import EditorStatusCell from '../components/EditorStatusCell'; | import EditorStatusCell from '../components/EditorStatusCell'; | ||||
| import styles from './index.less'; | 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> = { | const params: Record<string, any> = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -73,7 +69,11 @@ function EditorList() { | |||||
| setTableData(content); | setTableData(content); | ||||
| setTotal(totalElements); | setTotal(totalElements); | ||||
| } | } | ||||
| }; | |||||
| }, [pagination]); | |||||
| useEffect(() => { | |||||
| getEditorList(); | |||||
| }, [getEditorList]); | |||||
| // 删除编辑器 | // 删除编辑器 | ||||
| const deleteEditor = async (id: number) => { | const deleteEditor = async (id: number) => { | ||||
| @@ -46,27 +46,27 @@ function ExperimentComparison() { | |||||
| }); | }); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]); | |||||
| const config = comparisonConfig[comparisonType]; | |||||
| useEffect(() => { | 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 | // 获取对比 url | ||||
| const getExpMetrics = async () => { | const getExpMetrics = async () => { | ||||
| @@ -187,7 +187,7 @@ function ExperimentComparison() { | |||||
| })), | })), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, [tableData]); | |||||
| }, [tableData, config]); | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-comparison']}> | <div className={styles['experiment-comparison']}> | ||||
| @@ -21,7 +21,6 @@ function ExperimentText() { | |||||
| const [experimentIns, setExperimentIns] = useState(undefined); | const [experimentIns, setExperimentIns] = useState(undefined); | ||||
| const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined); | const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined); | ||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const timerRef = useRef(); | |||||
| const workflowRef = useRef(); | const workflowRef = useRef(); | ||||
| const locationParams = useParams(); // 新版本获取路由参数接口 | const locationParams = useParams(); // 新版本获取路由参数接口 | ||||
| const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); | ||||
| @@ -36,6 +35,16 @@ function ExperimentText() { | |||||
| initGraph(); | initGraph(); | ||||
| getWorkflow(); | getWorkflow(); | ||||
| return () => { | |||||
| if (evtSourceRef.current) { | |||||
| evtSourceRef.current.close(); | |||||
| evtSourceRef.current = null; | |||||
| } | |||||
| }; | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| const changeSize = () => { | const changeSize = () => { | ||||
| if (!graph || graph.get('destroyed')) return; | if (!graph || graph.get('destroyed')) return; | ||||
| if (!graphRef.current) return; | if (!graphRef.current) return; | ||||
| @@ -46,20 +55,9 @@ function ExperimentText() { | |||||
| window.addEventListener('resize', changeSize); | window.addEventListener('resize', changeSize); | ||||
| return () => { | return () => { | ||||
| window.removeEventListener('resize', changeSize); | 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 getWorkflow = async () => { | ||||
| const [res] = await to(getWorkflowById(locationParams.workflowId)); | const [res] = await to(getWorkflowById(locationParams.workflowId)); | ||||
| @@ -90,6 +90,9 @@ const ExperimentDrawer = ({ | |||||
| instanceNodeStatus, | instanceNodeStatus, | ||||
| workflowId, | workflowId, | ||||
| instanceNodeStartTime, | instanceNodeStartTime, | ||||
| experimentName, | |||||
| experimentId, | |||||
| pipelineId, | |||||
| ], | ], | ||||
| ); | ); | ||||
| @@ -57,7 +57,7 @@ function ExperimentInstanceComponent({ | |||||
| if (allIntanceIds.length === 0) { | if (allIntanceIds.length === 0) { | ||||
| setSelectedIns([]); | setSelectedIns([]); | ||||
| } | } | ||||
| }, [experimentInsList]); | |||||
| }, [allIntanceIds, setSelectedIns]); | |||||
| // 删除实验实例确认 | // 删除实验实例确认 | ||||
| const handleRemove = (instance: ExperimentInstance) => { | const handleRemove = (instance: ExperimentInstance) => { | ||||
| @@ -43,19 +43,18 @@ function ExperimentResult({ | |||||
| : undefined; | : undefined; | ||||
| useEffect(() => { | 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 }); | 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) => { | const download = (path: string) => { | ||||
| @@ -53,8 +53,21 @@ function ExportModelModal({ | |||||
| }; | }; | ||||
| useEffect(() => { | 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(); | requestResourceList(); | ||||
| }, []); | |||||
| }, [config]); | |||||
| // 获取选中的数据集、模型 | // 获取选中的数据集、模型 | ||||
| const getSelectedResource = (id: number | undefined) => { | 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 getRecourceVersions = async (id: number) => { | ||||
| const resource = getSelectedResource(id); | const resource = getSelectedResource(id); | ||||
| @@ -37,20 +37,103 @@ function LogGroup({ | |||||
| const [completed, setCompleted] = useState(false); | const [completed, setCompleted] = useState(false); | ||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
| const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | ||||
| const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | |||||
| const socketRef = useRef<WebSocket | undefined>(undefined); | const socketRef = useRef<WebSocket | undefined>(undefined); | ||||
| const retryRef = useRef(2); // 等待 2 秒,重试 3 次 | 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(() => { | useEffect(() => { | ||||
| scrollToBottom(false); | 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) { | if (status === ExperimentStatus.Running) { | ||||
| setupSockect(); | setupSockect(); | ||||
| } else if (preStatusRef.current === ExperimentStatus.Running) { | |||||
| setCompleted(true); | |||||
| } | } | ||||
| preStatusRef.current = status; | |||||
| }, [status]); | |||||
| return () => { | |||||
| closeSocket(); | |||||
| }; | |||||
| }, [status, start_time, pod_name, isMouseDownRef, setLogList]); | |||||
| // 鼠标拖到中不滚动到底部 | // 鼠标拖到中不滚动到底部 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -65,9 +148,8 @@ function LogGroup({ | |||||
| return () => { | return () => { | ||||
| document.removeEventListener('mousedown', mouseDown); | document.removeEventListener('mousedown', mouseDown); | ||||
| document.removeEventListener('mouseup', mouseUp); | document.removeEventListener('mouseup', mouseUp); | ||||
| closeSocket(); | |||||
| }; | }; | ||||
| }, []); | |||||
| }, [setIsMouseDown]); | |||||
| // 请求日志 | // 请求日志 | ||||
| const requestExperimentPodsLog = async () => { | const requestExperimentPodsLog = async () => { | ||||
| @@ -119,78 +201,7 @@ function LogGroup({ | |||||
| requestExperimentPodsLog(); | 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 = () => { | const closeSocket = () => { | ||||
| if (socketRef.current) { | if (socketRef.current) { | ||||
| socketRef.current.close(1000, 'completed'); | socketRef.current.close(1000, 'completed'); | ||||
| @@ -208,15 +219,17 @@ function LogGroup({ | |||||
| // }; | // }; | ||||
| // element.scrollTo(optons); | // 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 showLog = (log_type === 'resource' && !collapse) || log_type === 'normal'; | ||||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | const logText = log_content + logList.map((v) => v.log_content).join(''); | ||||
| const showMoreBtn = | |||||
| status !== ExperimentStatus.Running && showLog && !completed && logText !== ''; | |||||
| const showMoreBtn = !hasRun && !completed && showLog && logText !== ''; | |||||
| return ( | return ( | ||||
| <div className={styles['log-group']} ref={elementRef}> | |||||
| <div className={styles['log-group']} ref={logElementRef}> | |||||
| {log_type === 'resource' && ( | {log_type === 'resource' && ( | ||||
| <div className={styles['log-group__pod']} onClick={handleCollapse}> | <div className={styles['log-group__pod']} onClick={handleCollapse}> | ||||
| <div className={styles['log-group__pod__name']}>{pod_name}</div> | <div className={styles['log-group__pod__name']}>{pod_name}</div> | ||||
| @@ -3,7 +3,7 @@ import { getQueryByExperimentLog } from '@/services/experiment/index.js'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import React, { useEffect, useRef, useState } from 'react'; | |||||
| import React, { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| import LogGroup from '../LogGroup'; | import LogGroup from '../LogGroup'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -15,13 +15,21 @@ export type ExperimentLog = { | |||||
| }; | }; | ||||
| type LogListProps = { | 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; | instanceNodeStatus?: ExperimentStatus; | ||||
| /** 自定义类名 */ | |||||
| className?: string; | className?: string; | ||||
| /** 自定义样式 */ | |||||
| style?: React.CSSProperties; | style?: React.CSSProperties; | ||||
| }; | }; | ||||
| @@ -35,24 +43,11 @@ function LogList({ | |||||
| className, | className, | ||||
| style, | style, | ||||
| }: LogListProps) { | }: LogListProps) { | ||||
| const [logList, setLogList] = useState<ExperimentLog[]>([]); | |||||
| const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | |||||
| const [logGroups, setLogGroups] = useState<ExperimentLog[]>([]); | |||||
| const retryRef = useRef(3); // 等待 2 秒,重试 3 次 | 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 start_time = dayjs(instanceNodeStartTime).valueOf() * 1.0e6; | ||||
| const params = { | const params = { | ||||
| task_id: pipelineNodeId, | task_id: pipelineNodeId, | ||||
| @@ -71,7 +66,7 @@ function LogList({ | |||||
| log_type, | log_type, | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| setLogList(list); | |||||
| setLogGroups(list); | |||||
| } else if (log_type === 'resource') { | } else if (log_type === 'resource') { | ||||
| const list = pods.map((v: string) => ({ | const list = pods.map((v: string) => ({ | ||||
| log_type, | log_type, | ||||
| @@ -79,7 +74,7 @@ function LogList({ | |||||
| log_content: '', | log_content: '', | ||||
| start_time, | start_time, | ||||
| })); | })); | ||||
| setLogList(list); | |||||
| setLogGroups(list); | |||||
| } | } | ||||
| } else { | } else { | ||||
| if (retryRef.current > 0) { | if (retryRef.current > 0) { | ||||
| @@ -89,12 +84,23 @@ function LogList({ | |||||
| }, 2 * 1000); | }, 2 * 1000); | ||||
| } | } | ||||
| } | } | ||||
| }; | |||||
| }, [pipelineNodeId, workflowId, instanceName, instanceNamespace, instanceNodeStartTime]); | |||||
| // 当实例节点运行状态不是 Pending,获取实验日志组 | |||||
| useEffect(() => { | |||||
| if ( | |||||
| instanceNodeStatus && | |||||
| instanceNodeStatus !== ExperimentStatus.Pending && | |||||
| logGroups.length === 0 | |||||
| ) { | |||||
| getExperimentLog(); | |||||
| } | |||||
| }, [getExperimentLog, logGroups, instanceNodeStatus]); | |||||
| return ( | return ( | ||||
| <div className={classNames(styles['log-list'], className)} id="log-list" style={style}> | <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> | <div className={styles['log-list__empty']}>暂无日志</div> | ||||
| )} | )} | ||||
| @@ -20,7 +20,7 @@ import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd'; | import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import { ComparisonType } from './Comparison/config'; | import { ComparisonType } from './Comparison/config'; | ||||
| import AddExperimentModal from './components/AddExperimentModal'; | import AddExperimentModal from './components/AddExperimentModal'; | ||||
| @@ -35,12 +35,6 @@ function Experiment() { | |||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [experimentList, setExperimentList] = useState([]); | const [experimentList, setExperimentList] = useState([]); | ||||
| const [workflowList, setWorkflowList] = useState([]); | const [workflowList, setWorkflowList] = useState([]); | ||||
| const [queryFlow, setQueryFlow] = useState({ | |||||
| offset: 1, | |||||
| page: 0, | |||||
| size: 10000, | |||||
| name: null, | |||||
| }); | |||||
| const [experimentId, setExperimentId] = useState(null); | const [experimentId, setExperimentId] = useState(null); | ||||
| const [experimentInList, setExperimentInList] = useState([]); | const [experimentInList, setExperimentInList] = useState([]); | ||||
| const [expandedRowKeys, setExpandedRowKeys] = useState(null); | const [expandedRowKeys, setExpandedRowKeys] = useState(null); | ||||
| @@ -61,18 +55,28 @@ function Experiment() { | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| useEffect(() => { | 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(); | getWorkflowList(); | ||||
| return () => { | return () => { | ||||
| clearExperimentInTimers(); | clearExperimentInTimers(); | ||||
| }; | }; | ||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| getExperimentList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取实验列表 | // 获取实验列表 | ||||
| const getExperimentList = async () => { | |||||
| const getExperimentList = useCallback(async () => { | |||||
| const params = { | const params = { | ||||
| page: pagination.current - 1, | page: pagination.current - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -88,15 +92,11 @@ function Experiment() { | |||||
| setTotal(res.data.totalElements); | 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) => { | const onSearch = (value) => { | ||||
| @@ -26,37 +26,37 @@ function CreateHyperParameter() { | |||||
| const isCopy = pathname.includes('copy'); | const isCopy = pathname.includes('copy'); | ||||
| useEffect(() => { | 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)) { | if (id && !Number.isNaN(id)) { | ||||
| getHyperParameterInfo(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) => { | const createExperiment = async (formData: FormData) => { | ||||
| @@ -8,7 +8,7 @@ import { getRayInfoReq } from '@/services/hyperParameter'; | |||||
| import { safeInvoke } from '@/utils/functional'; | import { safeInvoke } from '@/utils/functional'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useParams } from '@umijs/max'; | import { useParams } from '@umijs/max'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import HyperParameterBasic from '../components/HyperParameterBasic'; | import HyperParameterBasic from '../components/HyperParameterBasic'; | ||||
| import { HyperParameterData } from '../types'; | import { HyperParameterData } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -20,19 +20,19 @@ function HyperparameterInfo() { | |||||
| undefined, | undefined, | ||||
| ); | ); | ||||
| useEffect(() => { | |||||
| if (hyperparameterId) { | |||||
| getHyperparameterInfo(); | |||||
| } | |||||
| }, []); | |||||
| // 获取自动机器学习详情 | // 获取自动机器学习详情 | ||||
| const getHyperparameterInfo = async () => { | |||||
| const getHyperparameterInfo = useCallback(async () => { | |||||
| const [res] = await to(getRayInfoReq({ id: hyperparameterId })); | const [res] = await to(getRayInfoReq({ id: hyperparameterId })); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setHyperparameterInfo(res.data); | setHyperparameterInfo(res.data); | ||||
| } | } | ||||
| }; | |||||
| }, [hyperparameterId]); | |||||
| useEffect(() => { | |||||
| if (hyperparameterId) { | |||||
| getHyperparameterInfo(); | |||||
| } | |||||
| }, [hyperparameterId, getHyperparameterInfo]); | |||||
| return ( | return ( | ||||
| <div className={styles['hyper-parameter-info']}> | <div className={styles['hyper-parameter-info']}> | ||||
| @@ -22,6 +22,8 @@ enum TabKeys { | |||||
| History = 'history', | History = 'history', | ||||
| } | } | ||||
| const NodePrefix = 'workflow'; | |||||
| function HyperParameterInstance() { | function HyperParameterInstance() { | ||||
| const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined); | const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined); | ||||
| const [instanceInfo, setInstanceInfo] = useState<HyperParameterInstanceData | undefined>( | const [instanceInfo, setInstanceInfo] = useState<HyperParameterInstanceData | undefined>( | ||||
| @@ -41,6 +43,7 @@ function HyperParameterInstance() { | |||||
| return () => { | return () => { | ||||
| closeSSE(); | closeSSE(); | ||||
| }; | }; | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []); | }, []); | ||||
| // 获取实验实例详情 | // 获取实验实例详情 | ||||
| @@ -83,7 +86,7 @@ function HyperParameterInstance() { | |||||
| if (nodeStatusJson) { | if (nodeStatusJson) { | ||||
| setNodes(nodeStatusJson); | setNodes(nodeStatusJson); | ||||
| Object.keys(nodeStatusJson).some((key) => { | Object.keys(nodeStatusJson).some((key) => { | ||||
| if (key.startsWith('workflow')) { | |||||
| if (key.startsWith(NodePrefix)) { | |||||
| const workflowStatus = nodeStatusJson[key]; | const workflowStatus = nodeStatusJson[key]; | ||||
| setWorkflowStatus(workflowStatus); | setWorkflowStatus(workflowStatus); | ||||
| return true; | return true; | ||||
| @@ -101,9 +104,6 @@ function HyperParameterInstance() { | |||||
| const setupSSE = (name: string, namespace: string) => { | const setupSSE = (name: string, namespace: string) => { | ||||
| const { origin } = location; | 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 params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | ||||
| const evtSource = new EventSource( | const evtSource = new EventSource( | ||||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | ||||
| @@ -119,7 +119,7 @@ function HyperParameterInstance() { | |||||
| const nodes = dataJson?.result?.object?.status?.nodes; | const nodes = dataJson?.result?.object?.status?.nodes; | ||||
| if (nodes) { | if (nodes) { | ||||
| const workflowStatus = Object.values(nodes).find((node: any) => | const workflowStatus = Object.values(nodes).find((node: any) => | ||||
| node.displayName.startsWith('workflow'), | |||||
| node.displayName.startsWith(NodePrefix), | |||||
| ) as NodeStatus; | ) as NodeStatus; | ||||
| // 节点 | // 节点 | ||||
| @@ -192,7 +192,7 @@ function HyperParameterInstance() { | |||||
| key: TabKeys.History, | key: TabKeys.History, | ||||
| label: '寻优列表', | label: '寻优列表', | ||||
| icon: <KFIcon type="icon-Trialliebiao" />, | icon: <KFIcon type="icon-Trialliebiao" />, | ||||
| children: <ExperimentHistory trialList={instanceInfo?.trial_list} />, | |||||
| children: <ExperimentHistory trialList={instanceInfo?.trial_list ?? []} />, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -32,10 +32,10 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| setTableData(trialList); | setTableData(trialList); | ||||
| setLoading(false); | setLoading(false); | ||||
| }, 500); | }, 500); | ||||
| }, []); | |||||
| }, [trialList]); | |||||
| // 计算 column | // 计算 column | ||||
| const first: HyperParameterTrial | undefined = trialList[0]; | |||||
| const first: HyperParameterTrial | undefined = trialList ? trialList[0] : undefined; | |||||
| const config: Record<string, any> = first?.config ?? {}; | const config: Record<string, any> = first?.config ?? {}; | ||||
| const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {}; | const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {}; | ||||
| const paramsNames = Object.keys(config); | const paramsNames = Object.keys(config); | ||||
| @@ -12,19 +12,19 @@ function ExperimentResult({ fileUrl }: ExperimentResultProps) { | |||||
| const [result, setResult] = useState<string | undefined>(''); | const [result, setResult] = useState<string | undefined>(''); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取实验运行历史记录 | |||||
| const getResultFile = async () => { | |||||
| const [res] = await to(getFileReq(fileUrl)); | |||||
| if (res) { | |||||
| setResult(res as any as string); | |||||
| } | |||||
| }; | |||||
| if (fileUrl) { | if (fileUrl) { | ||||
| getResultFile(); | getResultFile(); | ||||
| } | } | ||||
| }, [fileUrl]); | }, [fileUrl]); | ||||
| // 获取实验运行历史记录 | |||||
| const getResultFile = async () => { | |||||
| const [res] = await to(getFileReq(fileUrl)); | |||||
| if (res) { | |||||
| setResult(res as any as string); | |||||
| } | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-result']}> | <div className={styles['experiment-result']}> | ||||
| <InfoGroup title="最佳实验结果" width="100%"> | <InfoGroup title="最佳实验结果" width="100%"> | ||||
| @@ -43,15 +43,6 @@ function HyperParameterBasic({ | |||||
| }: HyperParameterBasicProps) { | }: HyperParameterBasicProps) { | ||||
| const getResourceDescription = useComputingResource()[2]; | const getResourceDescription = useComputingResource()[2]; | ||||
| // 格式化资源规格 | |||||
| const formatResource = (resource?: string) => { | |||||
| if (!resource) { | |||||
| return undefined; | |||||
| } | |||||
| return getResourceDescription(resource); | |||||
| }; | |||||
| const basicDatas: BasicInfoData[] = useMemo(() => { | const basicDatas: BasicInfoData[] = useMemo(() => { | ||||
| if (!info) { | if (!info) { | ||||
| return []; | return []; | ||||
| @@ -146,10 +137,10 @@ function HyperParameterBasic({ | |||||
| { | { | ||||
| label: '资源规格', | label: '资源规格', | ||||
| value: info.resource, | value: info.resource, | ||||
| format: formatResource, | |||||
| format: getResourceDescription, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, [info]); | |||||
| }, [info, getResourceDescription]); | |||||
| const instanceDatas = useMemo(() => { | const instanceDatas = useMemo(() => { | ||||
| if (!runStatus) { | if (!runStatus) { | ||||
| @@ -65,7 +65,7 @@ function MirrorCreate() { | |||||
| return () => { | return () => { | ||||
| SessionStorage.removeItem(SessionStorage.mirrorNameKey); | SessionStorage.removeItem(SessionStorage.mirrorNameKey); | ||||
| }; | }; | ||||
| }, []); | |||||
| }, [form]); | |||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| const createPublicMirror = async (formData: FormData) => { | const createPublicMirror = async (formData: FormData) => { | ||||
| @@ -16,6 +16,7 @@ import { | |||||
| } from '@/services/mirror'; | } from '@/services/mirror'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { safeInvoke } from '@/utils/functional'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| @@ -32,7 +33,7 @@ import { | |||||
| type TablePaginationConfig, | type TablePaginationConfig, | ||||
| type TableProps, | type TableProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { useEffect, useMemo, useState } from 'react'; | |||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||||
| import MirrorStatusCell from '../components/MirrorStatusCell'; | import MirrorStatusCell from '../components/MirrorStatusCell'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -70,31 +71,28 @@ function MirrorInfo() { | |||||
| ); | ); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const isPublic = useMemo(() => mirrorInfo.image_type === 1, [mirrorInfo]); | 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) { | if (res && res.data) { | ||||
| setMirrorInfo(res.data); | setMirrorInfo(res.data); | ||||
| } | } | ||||
| }; | |||||
| }, [mirrorId]); | |||||
| // 获取镜像版本列表 | // 获取镜像版本列表 | ||||
| const getMirrorVersionList = async () => { | |||||
| const id = Number(urlParams.id); | |||||
| const getMirrorVersionList = useCallback(async () => { | |||||
| if (!mirrorId) { | |||||
| return; | |||||
| } | |||||
| const params = { | const params = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| image_id: id, | |||||
| image_id: mirrorId, | |||||
| }; | }; | ||||
| const [res] = await to(getMirrorVersionListReq(params)); | const [res] = await to(getMirrorVersionListReq(params)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| @@ -102,7 +100,17 @@ function MirrorInfo() { | |||||
| setTableData(content); | setTableData(content); | ||||
| setTotal(totalElements); | setTotal(totalElements); | ||||
| } | } | ||||
| }; | |||||
| }, [mirrorId, pagination]); | |||||
| // 获取镜像详情 | |||||
| useEffect(() => { | |||||
| getMirrorInfo(); | |||||
| }, [getMirrorInfo]); | |||||
| // 获取镜像版本列表 | |||||
| useEffect(() => { | |||||
| getMirrorVersionList(); | |||||
| }, [getMirrorVersionList]); | |||||
| // 删除镜像版本 | // 删除镜像版本 | ||||
| const deleteMirrorVersion = async (id: number) => { | const deleteMirrorVersion = async (id: number) => { | ||||
| @@ -26,7 +26,7 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const mirrorTabItems = [ | const mirrorTabItems = [ | ||||
| @@ -65,9 +65,25 @@ function MirrorList() { | |||||
| ); | ); | ||||
| const { message } = App.useApp(); | 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(() => { | useEffect(() => { | ||||
| getMirrorList(); | getMirrorList(); | ||||
| }, [activeTab, pagination, searchText]); | |||||
| }, [getMirrorList]); | |||||
| // 切换 Tab,重置数据 | // 切换 Tab,重置数据 | ||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | const hanleTabChange: TabsProps['onChange'] = (value) => { | ||||
| @@ -82,22 +98,6 @@ function MirrorList() { | |||||
| setActiveTab(value); | 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 deleteMirror = async (id: number) => { | ||||
| const [res] = await to(deleteMirrorReq(id)); | const [res] = await to(deleteMirrorReq(id)); | ||||
| @@ -1,5 +1,5 @@ | |||||
| import * as echarts from 'echarts'; | import * as echarts from 'echarts'; | ||||
| import { useEffect, useRef } from 'react'; | |||||
| import { useEffect, useMemo, useRef } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import './tooltip.css'; | 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, | 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(() => { | useEffect(() => { | ||||
| // 创建一个echarts实例,返回echarts实例 | // 创建一个echarts实例,返回echarts实例 | ||||
| @@ -158,7 +161,7 @@ function MetricsChart({ name, chartData }: MetricsChartProps) { | |||||
| // myChart.dispose() 销毁实例 | // myChart.dispose() 销毁实例 | ||||
| chart.dispose(); | chart.dispose(); | ||||
| }; | }; | ||||
| }, []); | |||||
| }, [options]); | |||||
| return ( | return ( | ||||
| <div className={styles['metrics-chart']}> | <div className={styles['metrics-chart']}> | ||||
| @@ -56,6 +56,10 @@ function ModelEvolution({ | |||||
| useEffect(() => { | useEffect(() => { | ||||
| initGraph(); | initGraph(); | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| const changeSize = () => { | const changeSize = () => { | ||||
| if (!graph || graph.get('destroyed')) return; | if (!graph || graph.get('destroyed')) return; | ||||
| if (!graphRef.current) return; | if (!graphRef.current) return; | ||||
| @@ -77,8 +81,8 @@ function ModelEvolution({ | |||||
| clearGraphData(); | clearGraphData(); | ||||
| } | } | ||||
| }, | }, | ||||
| [resourceId, version], | |||||
| isActive, | isActive, | ||||
| [resourceId, version], | |||||
| ); | ); | ||||
| // 初始化图 | // 初始化图 | ||||
| @@ -60,60 +60,61 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| ] = useCheck(allMetricsNames); | ] = useCheck(allMetricsNames); | ||||
| useEffect(() => { | 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(() => { | useEffect(() => { | ||||
| const curRow = tableData.find((item) => item.name === version); | 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]); | }, [tableData, version]); | ||||
| useEffect(() => { | 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'] = ( | 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 ( | return ( | ||||
| <div className={styles['model-metrics']}> | <div className={styles['model-metrics']}> | ||||
| @@ -11,8 +11,8 @@ import { createServiceReq, getServiceInfoReq, updateServiceReq } from '@/service | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | 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'; | import styles from './index.less'; | ||||
| // 表单数据 | // 表单数据 | ||||
| @@ -25,30 +25,28 @@ export type FormData = { | |||||
| function CreateService() { | function CreateService() { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined); | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const params = useParams(); | const params = useParams(); | ||||
| const serviceId = params.serviceId; | const serviceId = params.serviceId; | ||||
| useEffect(() => { | 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) { | if (serviceId) { | ||||
| getServiceInfo(); | 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) => { | const createService = async (formData: FormData) => { | ||||
| @@ -55,11 +55,11 @@ function CreateServiceVersion() { | |||||
| const [operationType, setOperationType] = useState(ServiceOperationType.Create); | const [operationType, setOperationType] = useState(ServiceOperationType.Create); | ||||
| const [lastPage, setLastPage] = useState(CreateServiceVersionFrom.ServiceInfo); | const [lastPage, setLastPage] = useState(CreateServiceVersionFrom.ServiceInfo); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| // const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined); | |||||
| const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined); | const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined); | ||||
| const params = useParams(); | const params = useParams(); | ||||
| const serviceId = params.serviceId; | const serviceId = params.serviceId; | ||||
| // 因为没有服务版本详情接口,需要从缓存中获取 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const res: ServiceVersionCache | undefined = SessionStorage.getItem( | const res: ServiceVersionCache | undefined = SessionStorage.getItem( | ||||
| SessionStorage.serviceVersionInfoKey, | SessionStorage.serviceVersionInfoKey, | ||||
| @@ -98,22 +98,21 @@ function CreateServiceVersion() { | |||||
| return () => { | return () => { | ||||
| SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); | SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); | ||||
| }; | }; | ||||
| }, []); | |||||
| }, [form]); | |||||
| useEffect(() => { | 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) => { | const createServiceVersion = async (formData: FormData) => { | ||||
| @@ -26,7 +26,7 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import { | import { | ||||
| CreateServiceVersionFrom, | CreateServiceVersionFrom, | ||||
| ServiceData, | 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> = { | const params: Record<string, any> = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -78,7 +67,52 @@ function ModelDeployment() { | |||||
| setTableData(content); | setTableData(content); | ||||
| setTotal(totalElements); | 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) => { | const deleteService = async (record: ServiceData) => { | ||||
| @@ -145,35 +179,6 @@ function ModelDeployment() { | |||||
| navigate(`serviceInfo/${record.id}`); | 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'] = ( | const handleTableChange: TableProps<ServiceData>['onChange'] = ( | ||||
| pagination, | pagination, | ||||
| @@ -36,7 +36,7 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import ServiceRunStatusCell from '../components/ModelDeployStatusCell'; | import ServiceRunStatusCell from '../components/ModelDeployStatusCell'; | ||||
| import VersionCompareModal from '../components/VersionCompareModal'; | import VersionCompareModal from '../components/VersionCompareModal'; | ||||
| import { | import { | ||||
| @@ -89,24 +89,16 @@ function ServiceInfo() { | |||||
| ]; | ]; | ||||
| const getResourceDescription = useComputingResource()[2]; | const getResourceDescription = useComputingResource()[2]; | ||||
| useEffect(() => { | |||||
| getServiceInfo(); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| getServiceVersions(); | |||||
| }, [pagination, searchText, serviceStatus]); | |||||
| // 获取服务详情 | // 获取服务详情 | ||||
| const getServiceInfo = async () => { | |||||
| const getServiceInfo = useCallback(async () => { | |||||
| const [res] = await to(getServiceInfoReq(serviceId)); | const [res] = await to(getServiceInfoReq(serviceId)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setServiceInfo(res.data); | setServiceInfo(res.data); | ||||
| } | } | ||||
| }; | |||||
| }, [serviceId]); | |||||
| // 获取服务版本列表 | // 获取服务版本列表 | ||||
| const getServiceVersions = async () => { | |||||
| const getServiceVersions = useCallback(async () => { | |||||
| const params: Record<string, any> = { | const params: Record<string, any> = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -125,7 +117,15 @@ function ServiceInfo() { | |||||
| setTableData(content); | setTableData(content); | ||||
| setTotal(totalElements); | setTotal(totalElements); | ||||
| } | } | ||||
| }; | |||||
| }, [pagination, serviceStatus, searchText, serviceId]); | |||||
| useEffect(() => { | |||||
| getServiceInfo(); | |||||
| }, [getServiceInfo]); | |||||
| useEffect(() => { | |||||
| getServiceVersions(); | |||||
| }, [getServiceVersions]); | |||||
| // 删除模型部署 | // 删除模型部署 | ||||
| const deleteServiceVersion = async (record: ServiceVersionData) => { | const deleteServiceVersion = async (record: ServiceVersionData) => { | ||||
| @@ -29,16 +29,18 @@ function ServiceVersionInfo() { | |||||
| const id = params.id; | const id = params.id; | ||||
| useEffect(() => { | 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 = [ | const tabItems = [ | ||||
| { | { | ||||
| @@ -54,28 +54,27 @@ function ServerLog({ info }: ServerLogProps) { | |||||
| ]; | ]; | ||||
| useEffect(() => { | 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(); | getModelDeploymentLog(); | ||||
| }, [info, logTime]); | }, [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 = () => { | const handleSearch = () => { | ||||
| setLogData([]); | setLogData([]); | ||||
| @@ -10,20 +10,20 @@ type UserGuideProps = { | |||||
| function UserGuide({ info }: UserGuideProps) { | function UserGuide({ info }: UserGuideProps) { | ||||
| const [docs, setDocs] = useState(''); | const [docs, setDocs] = useState(''); | ||||
| useEffect(() => { | 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(); | getModelDeploymentDocs(); | ||||
| }, [info]); | }, [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>; | return <div className={styles['user-guide']}>{docs}</div>; | ||||
| } | } | ||||
| @@ -38,15 +38,6 @@ const formatEnvText = (env?: Record<string, string>) => { | |||||
| function VersionBasicInfo({ info }: BasicInfoProps) { | function VersionBasicInfo({ info }: BasicInfoProps) { | ||||
| const getResourceDescription = useComputingResource()[2]; | const getResourceDescription = useComputingResource()[2]; | ||||
| // 格式化资源规格 | |||||
| const formatResource = (resource?: string) => { | |||||
| if (!resource) { | |||||
| return undefined; | |||||
| } | |||||
| return getResourceDescription(resource); | |||||
| }; | |||||
| const datas: BasicInfoData[] = [ | const datas: BasicInfoData[] = [ | ||||
| { | { | ||||
| label: '服务名称', | label: '服务名称', | ||||
| @@ -78,7 +69,7 @@ function VersionBasicInfo({ info }: BasicInfoProps) { | |||||
| { | { | ||||
| label: '资源规格', | label: '资源规格', | ||||
| value: info?.resource, | value: info?.resource, | ||||
| format: formatResource, | |||||
| format: getResourceDescription, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '挂载路径', | label: '挂载路径', | ||||
| @@ -106,20 +106,20 @@ function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModa | |||||
| ); | ); | ||||
| useEffect(() => { | 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 { | const { | ||||
| version1: v1 = {} as ServiceVersionData, | version1: v1 = {} as ServiceVersionData, | ||||
| @@ -55,7 +55,7 @@ const JobForm: React.FC<JobFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -124,7 +124,7 @@ const JobLogTableList: React.FC = () => { | |||||
| getDictValueEnum('sys_job_group').then((data) => { | getDictValueEnum('sys_job_group').then((data) => { | ||||
| setJobGroupOptions(data); | setJobGroupOptions(data); | ||||
| }); | }); | ||||
| }, []); | |||||
| }, [jobId]); | |||||
| const columns: ProColumns<API.Monitor.JobLog>[] = [ | const columns: ProColumns<API.Monitor.JobLog>[] = [ | ||||
| { | { | ||||
| @@ -32,7 +32,10 @@ const EditPipeline = () => { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| initGraph(); | initGraph(); | ||||
| getFirstWorkflow(locationParams.id); | getFirstWorkflow(locationParams.id); | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| const changeSize = () => { | const changeSize = () => { | ||||
| if (!graph || graph.get('destroyed')) return; | if (!graph || graph.get('destroyed')) return; | ||||
| if (!graphRef.current) 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()); | const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); | ||||
| if (globalParamError) { | if (globalParamError) { | ||||
| message.error('全局参数配置有误'); | message.error('全局参数配置有误'); | ||||
| @@ -122,7 +125,7 @@ const EditPipeline = () => { | |||||
| saveWorkflow(params).then((ret) => { | saveWorkflow(params).then((ret) => { | ||||
| message.success('保存成功'); | message.success('保存成功'); | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| if (val) { | |||||
| if (isBack) { | |||||
| navigate({ pathname: `/pipeline/template` }); | navigate({ pathname: `/pipeline/template` }); | ||||
| } | } | ||||
| }, 500); | }, 500); | ||||
| @@ -19,19 +19,23 @@ const GlobalParamsDrawer = forwardRef( | |||||
| ({ open = false, onClose, globalParam = [] }: GlobalParamsDrawerProps, ref) => { | ({ open = false, onClose, globalParam = [] }: GlobalParamsDrawerProps, ref) => { | ||||
| const [form] = Form.useForm(); | 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) => { | const handleTypeChange = (name: NamePath) => { | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getComponentAll } from '@/services/pipeline/index.js'; | import { getComponentAll } from '@/services/pipeline/index.js'; | ||||
| import { PipelineNodeModel } from '@/types'; | import { PipelineNodeModel } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Collapse, type CollapseProps } from 'antd'; | |||||
| import { Collapse } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import Styles from './index.less'; | import Styles from './index.less'; | ||||
| @@ -18,50 +18,20 @@ type ModelMenuProps = { | |||||
| }; | }; | ||||
| const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => { | const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => { | ||||
| const [modelMenusList, setModelMenusList] = useState<ModelMenuData[]>([]); | const [modelMenusList, setModelMenusList] = useState<ModelMenuData[]>([]); | ||||
| const [collapseItems, setCollapseItems] = useState<CollapseProps['items']>([]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取所有组件 | |||||
| const getAllComponents = async () => { | |||||
| const [res] = await to(getComponentAll()); | |||||
| if (res && res.data) { | |||||
| const menus = res.data as ModelMenuData[]; | |||||
| setModelMenusList(menus); | |||||
| } | |||||
| }; | |||||
| getAllComponents(); | 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) => { | const dragEnd = (e: React.DragEvent<HTMLDivElement>, data: PipelineNodeModel) => { | ||||
| onComponentDragEnd({ | onComponentDragEnd({ | ||||
| ...data, | ...data, | ||||
| @@ -73,6 +43,35 @@ const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => { | |||||
| }; | }; | ||||
| const defaultActiveKey = modelMenusList.map((item) => item.key + ''); | 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 ( | return ( | ||||
| <div className={Styles.collapse}> | <div className={Styles.collapse}> | ||||
| <div className={Styles.modelMenusTitle}>组件库</div> | <div className={Styles.modelMenusTitle}>组件库</div> | ||||
| @@ -82,7 +81,7 @@ const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => { | |||||
| collapsible="header" | collapsible="header" | ||||
| expandIconPosition="end" | expandIconPosition="end" | ||||
| defaultActiveKey={defaultActiveKey} | defaultActiveKey={defaultActiveKey} | ||||
| items={collapseItems} | |||||
| items={items} | |||||
| ></Collapse> | ></Collapse> | ||||
| ) : null} | ) : null} | ||||
| </div> | </div> | ||||
| @@ -78,55 +78,59 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| setOpen(false); | 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 类型选择 | // ref 类型选择 | ||||
| const selectRefData = ( | const selectRefData = ( | ||||
| @@ -15,7 +15,7 @@ import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd'; | import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -39,12 +39,8 @@ const Pipeline = () => { | |||||
| ); | ); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| useEffect(() => { | |||||
| getList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取流水线模板列表 | // 获取流水线模板列表 | ||||
| const getList = () => { | |||||
| const getList = useCallback(() => { | |||||
| const params = { | const params = { | ||||
| page: pagination.current - 1, | page: pagination.current - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -56,7 +52,11 @@ const Pipeline = () => { | |||||
| setTotal(res.data.totalElements); | setTotal(res.data.totalElements); | ||||
| } | } | ||||
| }); | }); | ||||
| }; | |||||
| }, [pagination, searchText]); | |||||
| useEffect(() => { | |||||
| getList(); | |||||
| }, [getList]); | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch = (value) => { | const onSearch = (value) => { | ||||
| @@ -133,7 +133,6 @@ const Pipeline = () => { | |||||
| current, | current, | ||||
| pageSize, | pageSize, | ||||
| }); | }); | ||||
| getList(); | |||||
| }; | }; | ||||
| const columns = [ | const columns = [ | ||||
| @@ -42,7 +42,7 @@ const ConfigForm: React.FC<ConfigFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, configTypeOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -53,7 +53,7 @@ const DeptForm: React.FC<DeptFormProps> = (props) => { | |||||
| updateBy: props.values.updateBy, | updateBy: props.values.updateBy, | ||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -41,7 +41,7 @@ const DictTypeForm: React.FC<DictTypeFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -47,7 +47,7 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -187,7 +187,7 @@ const DictDataTableList: React.FC = () => { | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| }, [dictId, dictType, params]); | |||||
| }, [dictId, dictType, id]); | |||||
| const columns: ProColumns<API.System.DictData>[] = [ | const columns: ProColumns<API.System.DictData>[] = [ | ||||
| { | { | ||||
| @@ -68,7 +68,7 @@ const MenuForm: React.FC<MenuFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions, visibleOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -44,7 +44,7 @@ const NoticeForm: React.FC<NoticeFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -42,7 +42,7 @@ const PostForm: React.FC<PostFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -48,7 +48,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||||
| dataScope: props.values.dataScope, | dataScope: props.values.dataScope, | ||||
| }); | }); | ||||
| setDataScopeType(props.values.dataScope); | setDataScopeType(props.values.dataScope); | ||||
| }, [props.values]); | |||||
| }, [props.values, deptCheckedKeys, form]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -52,7 +52,7 @@ const RoleForm: React.FC<RoleFormProps> = (props) => { | |||||
| updateTime: props.values.updateTime, | updateTime: props.values.updateTime, | ||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -19,31 +19,37 @@ const DeptTree: React.FC<TreeProps> = (props) => { | |||||
| const [treeData, setTreeData] = useState<any>([]); | const [treeData, setTreeData] = useState<any>([]); | ||||
| const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]); | const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]); | ||||
| const [selectedKeys, setSelectedKeys] = 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(() => { | 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(); | 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); | setSelectedKeys(keys); | ||||
| props.onSelect(info.node); | |||||
| onSelect(info.node); | |||||
| }; | }; | ||||
| const onExpand = (keys: React.Key[]) => { | const onExpand = (keys: React.Key[]) => { | ||||
| @@ -57,7 +63,7 @@ const DeptTree: React.FC<TreeProps> = (props) => { | |||||
| onExpand={onExpand} | onExpand={onExpand} | ||||
| expandedKeys={expandedKeys} | expandedKeys={expandedKeys} | ||||
| selectedKeys={selectedKeys} | selectedKeys={selectedKeys} | ||||
| onSelect={onSelect} | |||||
| onSelect={handleSelect} | |||||
| treeData={treeData} | treeData={treeData} | ||||
| /> | /> | ||||
| ); | ); | ||||
| @@ -65,7 +65,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| gitLinkUsername: props.values.gitLinkUsername, | gitLinkUsername: props.values.gitLinkUsername, | ||||
| gitLinkPassword: props.values.gitLinkPassword, | gitLinkPassword: props.values.gitLinkPassword, | ||||
| }); | }); | ||||
| }, [form, props]); | |||||
| }, [form, props, statusOptions]); | |||||
| const intl = useIntl(); | const intl = useIntl(); | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -114,6 +114,7 @@ const TableList: React.FC = () => { | |||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| setStepComponent(getCurrentStepAndComponent(stepKey)); | setStepComponent(getCurrentStepAndComponent(stepKey)); | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [stepKey]); | }, [stepKey]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -150,7 +151,7 @@ const TableList: React.FC = () => { | |||||
| message.error(res.msg); | message.error(res.msg); | ||||
| } | } | ||||
| }); | }); | ||||
| }, []); | |||||
| }, [tableId]); | |||||
| // const onFinish = (values: any) => { | // const onFinish = (values: any) => { | ||||
| // console.log('Success:', values); | // console.log('Success:', values); | ||||
| @@ -49,7 +49,8 @@ const Login = () => { | |||||
| } else { | } else { | ||||
| form.setFieldsValue({ username: '', password: '', autoLogin: false }); | form.setFieldsValue({ username: '', password: '', autoLogin: false }); | ||||
| } | } | ||||
| }, []); | |||||
| }, [form]); | |||||
| const getCaptchaCode = async () => { | const getCaptchaCode = async () => { | ||||
| const [res] = await to(getCaptchaImg()); | const [res] = await to(getCaptchaImg()); | ||||
| if (res) { | if (res) { | ||||
| @@ -7,46 +7,48 @@ import styles from './index.less'; | |||||
| function AssetsManagement() { | function AssetsManagement() { | ||||
| const [type, setType] = useState(CommonTabKeys.Public); | const [type, setType] = useState(CommonTabKeys.Public); | ||||
| const [assetCounts, setAssetCounts] = useState<{ title: string; value: number }[]>([]); | const [assetCounts, setAssetCounts] = useState<{ title: string; value: number }[]>([]); | ||||
| useEffect(() => { | 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(); | getWorkspacAssetCount(); | ||||
| }, [type]); | }, [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 ( | return ( | ||||
| <div className={styles['assets-management']}> | <div className={styles['assets-management']}> | ||||
| @@ -1,6 +1,6 @@ | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import * as echarts from 'echarts'; | import * as echarts from 'echarts'; | ||||
| import React, { useEffect, useRef } from 'react'; | |||||
| import React, { useEffect, useMemo, useRef } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const color1 = new echarts.graphic.LinearGradient( | const color1 = new echarts.graphic.LinearGradient( | ||||
| @@ -94,102 +94,106 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) { | |||||
| chartData.Running + | chartData.Running + | ||||
| chartData.Succeeded + | chartData.Succeeded + | ||||
| chartData.Terminated; | 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: { | label: { | ||||
| show: false, | 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: { | label: { | ||||
| show: false, | 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(() => { | useEffect(() => { | ||||
| // 创建一个echarts实例,返回echarts实例 | // 创建一个echarts实例,返回echarts实例 | ||||
| @@ -203,7 +207,7 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) { | |||||
| // myChart.dispose() 销毁实例 | // myChart.dispose() 销毁实例 | ||||
| chart.dispose(); | chart.dispose(); | ||||
| }; | }; | ||||
| }, []); | |||||
| }, [options]); | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-chart']} style={style}> | <div className={styles['experiment-chart']} style={style}> | ||||
| @@ -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; | |||||
| @@ -1,3 +1,4 @@ | |||||
| // 数据集列表 | |||||
| export const datasetListData = { | export const datasetListData = { | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| code: 200, | code: 200, | ||||
| @@ -76,6 +77,7 @@ export const datasetListData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 数据集版本列表 | |||||
| export const datasetVersionData = { | export const datasetVersionData = { | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| code: 200, | code: 200, | ||||
| @@ -107,6 +109,7 @@ export const datasetVersionData = { | |||||
| ], | ], | ||||
| }; | }; | ||||
| // 数据集详情 | |||||
| export const datasetDetailData = { | export const datasetDetailData = { | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| code: 200, | code: 200, | ||||
| @@ -137,6 +140,7 @@ export const datasetDetailData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 模型列表 | |||||
| export const modelListData = { | export const modelListData = { | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| code: 200, | code: 200, | ||||
| @@ -211,6 +215,7 @@ export const modelListData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 模型版本列表 | |||||
| export const modelVersionData = { | export const modelVersionData = { | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| code: 200, | code: 200, | ||||
| @@ -234,6 +239,7 @@ export const modelVersionData = { | |||||
| ], | ], | ||||
| }; | }; | ||||
| // 模型详情 | |||||
| export const modelDetailData = { | export const modelDetailData = { | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| code: 200, | code: 200, | ||||
| @@ -266,6 +272,7 @@ export const modelDetailData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 镜像列表 | |||||
| export const mirrorListData = { | export const mirrorListData = { | ||||
| code: 200, | code: 200, | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| @@ -384,6 +391,7 @@ export const mirrorListData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 镜像版本列表 | |||||
| export const mirrorVerionData = { | export const mirrorVerionData = { | ||||
| code: 200, | code: 200, | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| @@ -433,6 +441,7 @@ export const mirrorVerionData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 代码配置列表 | |||||
| export const codeListData = { | export const codeListData = { | ||||
| code: 200, | code: 200, | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| @@ -547,6 +556,7 @@ export const codeListData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 服务列表 | |||||
| export const serviceListData = { | export const serviceListData = { | ||||
| code: 200, | code: 200, | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| @@ -620,6 +630,7 @@ export const serviceListData = { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 计算资源列表 | |||||
| export const computeResourceData = { | export const computeResourceData = { | ||||
| code: 200, | code: 200, | ||||
| msg: '操作成功', | msg: '操作成功', | ||||
| @@ -793,3 +804,45 @@ export const computeResourceData = { | |||||
| empty: false, | 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', | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| @@ -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, | |||||
| }, | |||||
| }; | |||||
| @@ -88,7 +88,10 @@ export function camelCaseToUnderscore(obj: Record<string, any>) { | |||||
| } | } | ||||
| // null to undefined | // 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)) { | if (!isPlainObject(obj)) { | ||||
| return obj; | return obj; | ||||
| } | } | ||||
| @@ -111,7 +114,10 @@ export function nullToUndefined(obj: Record<string, any>) { | |||||
| } | } | ||||
| // undefined to null | // 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)) { | if (!isPlainObject(obj)) { | ||||
| return obj; | return obj; | ||||
| } | } | ||||
| @@ -162,6 +162,7 @@ function renderText(text: any | undefined | null, ellipsis: boolean | 'auto') { | |||||
| wordBreak: 'break-all', | wordBreak: 'break-all', | ||||
| display: 'inline-block', | display: 'inline-block', | ||||
| maxWidth: '100%', | maxWidth: '100%', | ||||
| verticalAlign: 'middle', | |||||
| } | } | ||||
| : undefined | : undefined | ||||
| } | } | ||||