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