| @@ -1,12 +1,11 @@ | |||||
| import FullScreenFrame from '@/components/FullScreenFrame'; | import FullScreenFrame from '@/components/FullScreenFrame'; | ||||
| import KFSpin from '@/components/KFSpin'; | |||||
| import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment'; | import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment'; | ||||
| import Loading from '@/utils/loading'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import { FloatButton } from 'antd'; | import { FloatButton } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { createPortal } from 'react-dom'; | |||||
| import './index.less'; | import './index.less'; | ||||
| export enum IframePageType { | export enum IframePageType { | ||||
| @@ -66,16 +65,18 @@ type IframePageProps = { | |||||
| /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | ||||
| function IframePage({ type, openInTab = false, className, style }: IframePageProps) { | function IframePage({ type, openInTab = false, className, style }: IframePageProps) { | ||||
| const [iframeUrl, setIframeUrl] = useState(''); | const [iframeUrl, setIframeUrl] = useState(''); | ||||
| const [loading, setLoading] = useState(false); | |||||
| // const [loading, setLoading] = useState(false); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const requestIframeUrl = async () => { | const requestIframeUrl = async () => { | ||||
| setLoading(true); | |||||
| //setLoading(true); | |||||
| Loading.show(); | |||||
| const [res] = await to(getRequestAPI(type)()); | const [res] = await to(getRequestAPI(type)()); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setIframeUrl(res.data); | setIframeUrl(res.data); | ||||
| } else { | } else { | ||||
| setLoading(false); | |||||
| Loading.hide(); | |||||
| // setLoading(false); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -83,12 +84,13 @@ function IframePage({ type, openInTab = false, className, style }: IframePagePro | |||||
| }, [type]); | }, [type]); | ||||
| const hideLoading = () => { | const hideLoading = () => { | ||||
| setLoading(false); | |||||
| // setLoading(false); | |||||
| Loading.hide(); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={classNames('kf-iframe-page', className)} style={style}> | <div className={classNames('kf-iframe-page', className)} style={style}> | ||||
| {loading && createPortal(<KFSpin size="large" />, document.body)} | |||||
| {/* {loading && createPortal(<KFSpin size="large" />, document.body)} */} | |||||
| <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> | <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> | ||||
| {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} | {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} | ||||
| </div> | </div> | ||||
| @@ -33,7 +33,7 @@ export enum TensorBoardStatus { | |||||
| Unknown = 'Unknown', // 未知 | Unknown = 'Unknown', // 未知 | ||||
| Pending = 'Pending', // 启动中 | Pending = 'Pending', // 启动中 | ||||
| Running = 'Running', // 运行中 | Running = 'Running', // 运行中 | ||||
| Terminated = 'Terminated', // 未启动或者已终止 | |||||
| Terminated = 'Terminated', // 未启动 | |||||
| Failed = 'Failed', // 失败 | Failed = 'Failed', // 失败 | ||||
| } | } | ||||
| @@ -0,0 +1,8 @@ | |||||
| .experiment-visual { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| &__empty { | |||||
| height: 100%; | |||||
| } | |||||
| } | |||||
| @@ -5,10 +5,15 @@ | |||||
| */ | */ | ||||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | import IframePage, { IframePageType } from '@/components/IFramePage'; | ||||
| import { runTensorBoardReq } from '@/services/experiment/index.js'; | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFSpin from '@/components/KFSpin'; | |||||
| import { TensorBoardStatus } from '@/enums'; | |||||
| import { getTensorBoardStatusReq, runTensorBoardReq } from '@/services/experiment/index.js'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { LoadingOutlined } from '@ant-design/icons'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type TensorBoardProps = { | type TensorBoardProps = { | ||||
| namespace?: string; | namespace?: string; | ||||
| @@ -16,28 +21,75 @@ type TensorBoardProps = { | |||||
| }; | }; | ||||
| function ExperimentVisualResult({ namespace, path }: TensorBoardProps) { | function ExperimentVisualResult({ namespace, path }: TensorBoardProps) { | ||||
| const [tensorboardUrl, setTensorboardUrl] = useState(''); | |||||
| useEffect(() => { | |||||
| // 运行 TensorBoard | |||||
| const runTensorBoard = async () => { | |||||
| const params = { | |||||
| namespace: namespace, | |||||
| path: path, | |||||
| }; | |||||
| const [res] = await to(runTensorBoardReq(params)); | |||||
| if (res && res.data) { | |||||
| const url = res.data; | |||||
| SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url); | |||||
| setTensorboardUrl(url); | |||||
| const [tensorboardUrl, setTensorboardUrl] = useState<string | undefined | null>(undefined); | |||||
| const [status, setStatus] = useState<TensorBoardStatus | undefined>(undefined); | |||||
| // 获取 TensorBoard 状态 | |||||
| const getTensorBoardStatus = useCallback(async () => { | |||||
| const params = { | |||||
| namespace: namespace, | |||||
| path: path, | |||||
| }; | |||||
| const [res] = await to(getTensorBoardStatusReq(params)); | |||||
| if (res && res.data) { | |||||
| const status = res.data.status as TensorBoardStatus | undefined; | |||||
| setStatus(res.data.status); | |||||
| if (!status || status === TensorBoardStatus.Pending) { | |||||
| setTimeout(() => { | |||||
| getTensorBoardStatus(); | |||||
| }, 5000); | |||||
| } | } | ||||
| } | |||||
| }, [namespace, path]); | |||||
| // 运行 TensorBoard | |||||
| const runTensorBoard = useCallback(async () => { | |||||
| const params = { | |||||
| namespace: namespace, | |||||
| path: path, | |||||
| }; | }; | ||||
| const [res] = await to(runTensorBoardReq(params)); | |||||
| if (res && res.data) { | |||||
| const url = res.data; | |||||
| SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url); | |||||
| setTensorboardUrl(url); | |||||
| getTensorBoardStatus(); | |||||
| } else { | |||||
| setTensorboardUrl(null); | |||||
| } | |||||
| }, [namespace, path, getTensorBoardStatus]); | |||||
| useEffect(() => { | |||||
| if (namespace && path) { | if (namespace && path) { | ||||
| runTensorBoard(); | runTensorBoard(); | ||||
| } | } | ||||
| }, [namespace, path]); | |||||
| }, [namespace, path, runTensorBoard]); | |||||
| return <>{tensorboardUrl && <IframePage type={IframePageType.TensorBoard}></IframePage>}</>; | |||||
| if (tensorboardUrl === null || status === TensorBoardStatus.Failed) { | |||||
| return ( | |||||
| <div className={styles['experiment-visual']}> | |||||
| <KFEmpty | |||||
| className={styles['experiment-visual__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="运行可视化失败" | |||||
| buttonTitle="重新运行" | |||||
| onButtonClick={runTensorBoard} | |||||
| /> | |||||
| </div> | |||||
| ); | |||||
| } else if (status === TensorBoardStatus.Pending) { | |||||
| return ( | |||||
| <div className={styles['experiment-visual']}> | |||||
| <KFSpin indicator={<LoadingOutlined spin />} size="large" /> | |||||
| </div> | |||||
| ); | |||||
| } else if (status === TensorBoardStatus.Running) { | |||||
| return ( | |||||
| <div className={styles['experiment-visual']}> | |||||
| <IframePage type={IframePageType.TensorBoard}></IframePage> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| } | } | ||||
| export default ExperimentVisualResult; | export default ExperimentVisualResult; | ||||
| @@ -16,6 +16,7 @@ import styles from './index.less'; | |||||
| export type ResourceListRef = { | export type ResourceListRef = { | ||||
| reset: () => void; | reset: () => void; | ||||
| resetPage: () => void; | |||||
| }; | }; | ||||
| type ResourceListProps = { | type ResourceListProps = { | ||||
| @@ -97,6 +98,12 @@ function ResourceList( | |||||
| setDataList(undefined); | setDataList(undefined); | ||||
| setTotal(0); | setTotal(0); | ||||
| }, | }, | ||||
| resetPage: () => { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| }, | |||||
| }; | }; | ||||
| }, | }, | ||||
| [], | [], | ||||
| @@ -56,11 +56,13 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| // 选择类型 | // 选择类型 | ||||
| const chooseType = (record: CategoryData) => { | const chooseType = (record: CategoryData) => { | ||||
| dataListRef.current?.resetPage(); | |||||
| setActiveType((prev) => (prev === record.name ? undefined : record.name)); | setActiveType((prev) => (prev === record.name ? undefined : record.name)); | ||||
| }; | }; | ||||
| // 选择 Tag | // 选择 Tag | ||||
| const chooseTag = (record: CategoryData) => { | const chooseTag = (record: CategoryData) => { | ||||
| dataListRef.current?.resetPage(); | |||||
| setActiveTag((prev) => (prev === record.name ? undefined : record.name)); | setActiveTag((prev) => (prev === record.name ? undefined : record.name)); | ||||
| }; | }; | ||||
| @@ -9,6 +9,14 @@ | |||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| &__name-row { | |||||
| :global { | |||||
| .ant-form-item-row { | |||||
| flex-wrap: nowrap; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__type { | &__type { | ||||
| color: @text-color; | color: @text-color; | ||||
| font-size: @font-size-input-lg; | font-size: @font-size-input-lg; | ||||
| @@ -123,8 +123,6 @@ function MirrorCreate() { | |||||
| return true; | return true; | ||||
| }; | }; | ||||
| const descTitle = isAddVersion ? '版本描述' : '镜像描述'; | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-create']}> | <div className={styles['mirror-create']}> | ||||
| <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> | <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> | ||||
| @@ -161,6 +159,7 @@ function MirrorCreate() { | |||||
| message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', | ||||
| }, | }, | ||||
| ]} | ]} | ||||
| className={styles['mirror-create__content__name-row']} | |||||
| > | > | ||||
| <Input | <Input | ||||
| placeholder="请输入镜像名称" | placeholder="请输入镜像名称" | ||||
| @@ -193,21 +192,45 @@ function MirrorCreate() { | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| {!isAddVersion && ( | |||||
| <Row gutter={10}> | |||||
| <Col span={20}> | |||||
| <Form.Item | |||||
| label="镜像描述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| placeholder="请输入镜像描述" | |||||
| maxLength={128} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| )} | |||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={20}> | <Col span={20}> | ||||
| <Form.Item | <Form.Item | ||||
| label={descTitle} | |||||
| name="description" | |||||
| label="版本描述" | |||||
| name="version_description" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: `请输入${descTitle}`, | |||||
| message: '请输入版本描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| placeholder={`请输入${descTitle}`} | |||||
| placeholder="请输入版本描述" | |||||
| maxLength={128} | maxLength={128} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| @@ -211,7 +211,7 @@ function MirrorInfo() { | |||||
| hidden: isPublic, | hidden: isPublic, | ||||
| render: (_: any, record: MirrorVersionData) => ( | render: (_: any, record: MirrorVersionData) => ( | ||||
| <div> | <div> | ||||
| {!isPublic && ( | |||||
| {!isPublic && record.status && record.status !== MirrorVersionStatus.Building && ( | |||||
| <ConfigProvider | <ConfigProvider | ||||
| theme={{ | theme={{ | ||||
| token: { | token: { | ||||