From 41a684806eb7c28adf2ffac5c4d50984cd8e2dcf Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 12 Jun 2024 09:19:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=9C=8D=E5=8A=A1=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/components/LabelValue/index.less | 19 ++ react-ui/src/components/LabelValue/index.tsx | 20 ++ react-ui/src/components/ModalTitle/index.less | 2 +- react-ui/src/components/ModalTitle/index.tsx | 6 +- react-ui/src/iconfont/iconfont.js | 2 +- .../components/ResourceIntro/index.tsx | 28 +-- .../Experiment/components/LogGroup/index.tsx | 22 +- .../Model/components/ModelEvolution/index.tsx | 5 +- .../src/pages/ModelDeployment/Info/index.less | 47 ++--- .../src/pages/ModelDeployment/Info/index.tsx | 196 +++--------------- .../components/BasicInfo/index.tsx | 91 ++++++++ .../components/ServerLog/index.less | 16 ++ .../components/ServerLog/index.tsx | 147 +++++++++++++ .../components/UserGuide/index.less | 8 + .../components/UserGuide/index.tsx | 32 +++ .../src/services/modelDeployment/index.ts | 8 + react-ui/src/utils/ui.tsx | 23 +- 17 files changed, 442 insertions(+), 230 deletions(-) create mode 100644 react-ui/src/components/LabelValue/index.less create mode 100644 react-ui/src/components/LabelValue/index.tsx create mode 100644 react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx create mode 100644 react-ui/src/pages/ModelDeployment/components/ServerLog/index.less create mode 100644 react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx create mode 100644 react-ui/src/pages/ModelDeployment/components/UserGuide/index.less create mode 100644 react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx diff --git a/react-ui/src/components/LabelValue/index.less b/react-ui/src/components/LabelValue/index.less new file mode 100644 index 00000000..5f1b9b0c --- /dev/null +++ b/react-ui/src/components/LabelValue/index.less @@ -0,0 +1,19 @@ +.kf-label-value { + display: flex; + align-items: flex-start; + font-size: 16px; + line-height: 1.6; + + &__label { + flex: none; + width: 80px; + color: @text-color-secondary; + } + + &__value { + flex: 1; + color: @text-color; + white-space: pre-line; + word-break: break-all; + } +} diff --git a/react-ui/src/components/LabelValue/index.tsx b/react-ui/src/components/LabelValue/index.tsx new file mode 100644 index 00000000..22b9b3eb --- /dev/null +++ b/react-ui/src/components/LabelValue/index.tsx @@ -0,0 +1,20 @@ +import classNames from 'classnames'; +import './index.less'; + +type labelValueProps = { + label: string; + value?: any; + className?: string; + style?: React.CSSProperties; +}; + +function LabelValue({ label, value, className, style }: labelValueProps) { + return ( +
+
{label}
+
{value ?? '--'}
+
+ ); +} + +export default LabelValue; diff --git a/react-ui/src/components/ModalTitle/index.less b/react-ui/src/components/ModalTitle/index.less index 20ebc539..60e2cbde 100644 --- a/react-ui/src/components/ModalTitle/index.less +++ b/react-ui/src/components/ModalTitle/index.less @@ -1,4 +1,4 @@ -.modal-title { +.kf-modal-title { display: flex; align-items: center; color: @primary-color; diff --git a/react-ui/src/components/ModalTitle/index.tsx b/react-ui/src/components/ModalTitle/index.tsx index 11825486..adc83227 100644 --- a/react-ui/src/components/ModalTitle/index.tsx +++ b/react-ui/src/components/ModalTitle/index.tsx @@ -6,7 +6,7 @@ import classNames from 'classnames'; import React from 'react'; -import styles from './index.less'; +import './index.less'; type ModalTitleProps = { title: React.ReactNode; @@ -17,8 +17,8 @@ type ModalTitleProps = { function ModalTitle({ title, image, style, className }: ModalTitleProps) { return ( -
- {image && } +
+ {image && } {title}
); diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index e135846d..1ec213e7 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],h=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(h&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}}(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],h=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(h&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}}(window); \ No newline at end of file diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx index c057a64a..caa5a539 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx @@ -1,3 +1,4 @@ +import KFIcon from '@/components/KFIcon'; import ModelEvolution from '@/pages/Model/components/ModelEvolution'; import { to } from '@/utils/promise'; import { useParams, useSearchParams } from '@umijs/max'; @@ -7,21 +8,21 @@ import { ResourceData, ResourceType, resourceConfig } from '../../config'; import ResourceVersion from '../ResourceVersion'; import styles from './index.less'; +export enum ResourceInfoTabKeys { + Introduction = 'introduction', + Version = 'version', + Evolution = 'evolution', +} + type ResourceIntroProps = { resourceType: ResourceType; }; -enum TabKeys { - Introduction = '1', - Version = '2', - Evolution = '3', -} - const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { const [info, setInfo] = useState({} as ResourceData); const locationParams = useParams(); const [searchParams] = useSearchParams(); - const defaultTab = searchParams.get('tab') || '1'; + const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; let versionParam = searchParams.get('version'); const [versionList, setVersionList] = useState([]); const [version, setVersion] = useState(undefined); @@ -74,8 +75,9 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { const items = [ { - key: TabKeys.Introduction, + key: ResourceInfoTabKeys.Introduction, label: `${typeName}简介`, + icon: , children: ( <>
简介
@@ -84,8 +86,9 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { ), }, { - key: TabKeys.Version, + key: ResourceInfoTabKeys.Version, label: `${typeName}文件/版本`, + icon: , children: ( { isPublic={info.available_range === 1} versionList={versionList} version={version} - isActive={activeTab === TabKeys.Version} + isActive={activeTab === ResourceInfoTabKeys.Version} getVersionList={getVersionList} onVersionChange={handleVersionChange} > @@ -104,15 +107,16 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { if (resourceType === ResourceType.Model) { items.push({ - key: TabKeys.Evolution, + key: ResourceInfoTabKeys.Evolution, label: `模型演化`, + icon: , children: ( ), diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index 244b9c78..a3da3044 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -27,14 +27,12 @@ type Log = { const scrollToBottom = (smooth: boolean = true) => { const element = document.getElementsByClassName('ant-tabs-content-holder')?.[0]; if (element) { - if (smooth) { - element.scrollTo({ - top: element.scrollHeight, - behavior: 'smooth', - }); - } else { - element.scrollTo({ top: element.scrollHeight }); - } + const optons: ScrollToOptions = { + top: element.scrollHeight, + behavior: smooth ? 'smooth' : 'instant', + }; + + element.scrollTo(optons); } }; @@ -153,13 +151,9 @@ function LogGroup({ )}
{showMoreBtn && ( - )}
diff --git a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx index fbb711d4..9fb1a3ce 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx @@ -5,6 +5,7 @@ import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; import G6, { G6GraphEvent, Graph, Item } from '@antv/g6'; // @ts-ignore +import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; import { Flex, Select } from 'antd'; import { useEffect, useRef, useState } from 'react'; import GraphLegand from '../GraphLegand'; @@ -178,7 +179,7 @@ function ModelEvolution({ case NodeType.children: case NodeType.parent: { const { current_model_id, version } = model as ModelDepsData; - url = `${origin}/dataset/model/${current_model_id}?tab=3&version=${version}`; + url = `${origin}/dataset/model/${current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${version}`; break; } case NodeType.project: { @@ -189,7 +190,7 @@ function ModelEvolution({ case NodeType.trainDataset: case NodeType.testDataset: { const { dataset_id, dataset_version } = model as TrainDataset; - url = `${origin}/dataset/dataset/${dataset_id}?tab=2&version=${dataset_version}`; + url = `${origin}/dataset/dataset/${dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${dataset_version}`; break; } default: diff --git a/react-ui/src/pages/ModelDeployment/Info/index.less b/react-ui/src/pages/ModelDeployment/Info/index.less index aaeb8056..4dcf90d9 100644 --- a/react-ui/src/pages/ModelDeployment/Info/index.less +++ b/react-ui/src/pages/ModelDeployment/Info/index.less @@ -9,37 +9,30 @@ padding: 30px 30px 0; background-color: white; border-radius: 10px; - } - &__basic { - &__item { - display: flex; - align-items: flex-start; - font-size: 16px; - line-height: 1.6; + &__tabs { + flex: 1; + min-height: 0; + margin-top: 20px; + padding-bottom: 10px; - .label { - flex: none; - width: 80px; - color: @text-color-secondary; - } + :global { + .ant-tabs { + height: 100%; + + .ant-tabs-nav { + margin-bottom: 10px; + } + + .ant-tabs-content { + height: 100%; - .value { - flex: 1; - color: @text-color; - white-space: pre-line; - word-break: break-all; + .ant-tabs-tabpane { + height: 100%; + } + } + } } } } - - &__guide { - flex: 1; - margin-top: 10px; - padding: 10px; - overflow-y: auto; - color: white; - white-space: pre-wrap; - background-color: rgba(0, 0, 0, 0.85); - } } diff --git a/react-ui/src/pages/ModelDeployment/Info/index.tsx b/react-ui/src/pages/ModelDeployment/Info/index.tsx index c6025188..a548e93a 100644 --- a/react-ui/src/pages/ModelDeployment/Info/index.tsx +++ b/react-ui/src/pages/ModelDeployment/Info/index.tsx @@ -6,16 +6,13 @@ import KFIcon from '@/components/KFIcon'; import PageTitle from '@/components/PageTitle'; import SubAreaTitle from '@/components/SubAreaTitle'; -import { useComputingResource } from '@/hooks/resource'; import { useSessionStorage } from '@/hooks/sessionStorage'; -import { getModelDeploymentDocsReq } from '@/services/modelDeployment'; -import { formatDate } from '@/utils/date'; -import { to } from '@/utils/promise'; import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; -import { Col, Row, Tabs, type TabsProps } from 'antd'; -import { pick } from 'lodash'; -import { useEffect, useState } from 'react'; -import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; +import { Tabs, type TabsProps } from 'antd'; +import { useState } from 'react'; +import BasicInfo from '../components/BasicInfo'; +import ServerLog from '../components/ServerLog'; +import UserGuide from '../components/UserGuide'; import { ModelDeploymentData } from '../types'; import styles from './index.less'; @@ -25,24 +22,6 @@ export enum ModelDeploymentTabKey { Log = 'Log', } -const tabItems = [ - { - key: ModelDeploymentTabKey.Predict, - label: '预测', - icon: , - }, - { - key: ModelDeploymentTabKey.Guide, - label: '调用指南', - icon: , - }, - { - key: ModelDeploymentTabKey.Log, - label: '服务日志', - icon: , - }, -]; - function ModelDeploymentInfo() { const [activeTab, setActiveTab] = useState(ModelDeploymentTabKey.Predict); const [modelDeployementInfo] = useSessionStorage( @@ -50,38 +29,32 @@ function ModelDeploymentInfo() { true, undefined, ); - const getResourceDescription = useComputingResource()[2]; - const [docs, setDocs] = useState(''); - - useEffect(() => { - getModelDeploymentDocs(); - }, [modelDeployementInfo]); - // 获取模型部署文档 - const getModelDeploymentDocs = async () => { - const params = pick(modelDeployementInfo, ['service_id', 'service_ins_id']); - const [res] = await to(getModelDeploymentDocsReq(params)); - if (res && res.data && res.data.docs) { - setDocs(JSON.stringify(res.data.docs, null, 2)); - } - }; + const tabItems = [ + { + key: ModelDeploymentTabKey.Predict, + label: '预测', + icon: , + }, + { + key: ModelDeploymentTabKey.Guide, + label: '调用指南', + icon: , + children: , + }, + { + key: ModelDeploymentTabKey.Log, + label: '服务日志', + icon: , + children: , + }, + ]; // 切换 Tab,重置数据 const hanleTabChange: TabsProps['onChange'] = (value) => { setActiveTab(value); }; - // 格式化环境变量 - const formatEnvText = () => { - if (!modelDeployementInfo?.env) { - return '--'; - } - const env = modelDeployementInfo.env; - return Object.entries(env) - .map(([key, value]) => `${key}: ${value}`) - .join('\n'); - }; - return (
@@ -91,125 +64,10 @@ function ModelDeploymentInfo() { image={require('@/assets/img/mirror-basic.png')} style={{ marginBottom: '26px' }} > -
- - -
-
服务名称:
-
{modelDeployementInfo?.service_name ?? '--'}
-
- - -
-
镜  像:
-
{modelDeployementInfo?.image ?? '--'}
-
- -
- - -
-
状  态:
-
- {ModelDeploymentStatusCell(modelDeployementInfo?.status)} -
-
- - -
-
模  型:
-
- {modelDeployementInfo?.model?.show_value ?? '--'} -
-
- -
- - -
-
创建人:
-
{modelDeployementInfo?.created_by ?? '--'}
-
- - -
-
挂载路径:
-
{modelDeployementInfo?.model_path ?? '--'}
-
- -
- - -
-
API URL:
-
{modelDeployementInfo?.url ?? '--'}
-
- - -
-
副本数量:
-
{modelDeployementInfo?.replicas ?? '--'}
-
- -
- - -
-
创建时间:
-
- {modelDeployementInfo?.create_time - ? formatDate(modelDeployementInfo.create_time) - : '--'} -
-
- - -
-
更新时间:
-
- {modelDeployementInfo?.update_time - ? formatDate(modelDeployementInfo.update_time) - : '--'} -
-
- -
- - -
-
环境变量:
-
{formatEnvText()}
-
- - -
-
资源规格:
-
- {modelDeployementInfo?.resource - ? getResourceDescription(modelDeployementInfo.resource) - : '--'} -
-
- -
- - -
-
描  述:
-
{modelDeployementInfo?.description ?? '--'}
-
- -
+ +
+
- - {activeTab === ModelDeploymentTabKey.Guide && ( -
{docs}
- )}
); diff --git a/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx new file mode 100644 index 00000000..73beba6f --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx @@ -0,0 +1,91 @@ +import LabelValue from '@/components/LabelValue'; +import { useComputingResource } from '@/hooks/resource'; +import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; +import { formatDate } from '@/utils/date'; +import { Col, Row } from 'antd'; +import ModelDeploymentStatusCell from '../ModelDeployStatusCell'; + +type BasicInfoProps = { + info?: ModelDeploymentData; +}; + +function BasicInfo({ info }: BasicInfoProps) { + const getResourceDescription = useComputingResource()[2]; + + // 格式化环境变量 + const formatEnvText = () => { + if (!info?.env) { + return '--'; + } + const env = info.env; + return Object.entries(env) + .map(([key, value]) => `${key}: ${value}`) + .join('\n'); + }; + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} + +export default BasicInfo; diff --git a/react-ui/src/pages/ModelDeployment/components/ServerLog/index.less b/react-ui/src/pages/ModelDeployment/components/ServerLog/index.less new file mode 100644 index 00000000..401686ba --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/ServerLog/index.less @@ -0,0 +1,16 @@ +.server-log { + height: 100%; + &__data { + height: calc(100% - 42px); + margin-top: 10px; + padding: 10px; + overflow-y: auto; + color: white; + white-space: pre-wrap; + background-color: rgba(0, 0, 0, 0.85); + + &__more { + padding: 10px 0; + } + } +} diff --git a/react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx b/react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx new file mode 100644 index 00000000..6f9cfe51 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx @@ -0,0 +1,147 @@ +import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; +import { getModelDeploymentLogReq } from '@/services/modelDeployment'; +import { to } from '@/utils/promise'; +import { DoubleRightOutlined } from '@ant-design/icons'; +import { Button, DatePicker, type TimeRangePickerProps } from 'antd'; +import dayjs from 'dayjs'; +import { pick } from 'lodash'; +import { useEffect, useState } from 'react'; +import styles from './index.less'; +const { RangePicker } = DatePicker; + +// 滚动到底部 +// const scrollToBottom = (smooth: boolean = true) => { +// const element = document.getElementById('server-log'); +// if (element) { +// const optons: ScrollToOptions = { +// top: element.scrollHeight, +// behavior: smooth ? 'smooth' : 'instant', +// }; +// element.scrollTo(optons); +// } +// }; + +type LogData = { + log_content: string; + end_time: string; + start_time: string; +}; + +type ServerLogProps = { + info?: ModelDeploymentData; +}; + +function ServerLog({ info }: ServerLogProps) { + const [dateRange, setDateRange] = useState>([ + dayjs().add(-1, 'h'), + dayjs(), + ]); + const [logTime, setLogTime] = useState<[string, string]>([ + `${dateRange[0]!.valueOf() * Math.pow(10, 6)}`, + `${dateRange[1]!.valueOf() * Math.pow(10, 6)}`, + ]); + const [logData, setLogData] = useState([]); + const [hasMore, setHasMore] = useState(false); + + const rangePresets: TimeRangePickerProps['presets'] = [ + { label: '最近 1 小时', value: [dayjs().add(-1, 'h'), dayjs()] }, + { label: '最近 2 小时', value: [dayjs().add(-2, 'h'), dayjs()] }, + { label: '最近 3 小时', value: [dayjs().add(-3, 'h'), dayjs()] }, + { label: '最近 1 天', value: [dayjs().add(-1, 'd'), dayjs()] }, + { label: '最近 2 天', value: [dayjs().add(-2, 'd'), dayjs()] }, + { label: '最近 7 天', value: [dayjs().add(-7, 'd'), dayjs()] }, + { label: '最近 14 天', value: [dayjs().add(-14, 'd'), dayjs()] }, + { label: '最近 30 天', value: [dayjs().add(-30, 'd'), dayjs()] }, + ]; + + useEffect(() => { + getModelDeploymentLog(); + }, [info, logTime]); + + // 获取模型部署日志 + const getModelDeploymentLog = async () => { + if (info && logTime && logTime.length === 2) { + const params = { + start_time: logTime[0], + end_time: logTime[1], + ...pick(info, ['service_id', 'service_ins_id']), + }; + const [res] = await to(getModelDeploymentLogReq(params)); + if (res && res.data) { + setLogData((prev) => [...prev, res.data]); + setHasMore(!!res.data.log_content); + // setTimeout(() => { + // scrollToBottom(); + // }, 100); + } + } + }; + + // 搜索 + const handleSearch = () => { + setLogData([]); + setHasMore(false); + setLogTime([ + `${dateRange[0]!.valueOf() * Math.pow(10, 6)}`, + `${dateRange[1]!.valueOf() * Math.pow(10, 6)}`, + ]); + }; + + // 加载更多日志 + const loadMoreLog = () => { + const lastLog = logData[logData.length - 1]; + setLogTime([lastLog.start_time, lastLog.end_time]); + }; + + // 禁止选择今天之后和之前31天的日期 + const disabledDate: TimeRangePickerProps['disabledDate'] = (currentDate) => { + return ( + Date.now() - currentDate.valueOf() < 0 || + Date.now() - currentDate.valueOf() > 31 * 24 * 60 * 60 * 1000 + ); + }; + + // 处理日期变化 + const handleRangeChange: TimeRangePickerProps['onChange'] = (dates) => { + if (dates) { + setDateRange(dates); + } + }; + + return ( +
+
+ + +
+ {logData.length > 0 && ( +
+
{logData.map((v) => v.log_content).join('') || '暂无日志'}
+ {hasMore && ( + + )} +
+ )} +
+ ); +} + +export default ServerLog; diff --git a/react-ui/src/pages/ModelDeployment/components/UserGuide/index.less b/react-ui/src/pages/ModelDeployment/components/UserGuide/index.less new file mode 100644 index 00000000..2ab1f679 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/UserGuide/index.less @@ -0,0 +1,8 @@ +.user-guide { + height: 100%; + padding: 10px; + overflow-y: auto; + color: white; + white-space: pre-wrap; + background-color: rgba(0, 0, 0, 0.85); +} diff --git a/react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx b/react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx new file mode 100644 index 00000000..995f4e40 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx @@ -0,0 +1,32 @@ +import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; +import { getModelDeploymentDocsReq } from '@/services/modelDeployment'; +import { to } from '@/utils/promise'; +import { pick } from 'lodash'; +import { useEffect, useState } from 'react'; +import styles from './index.less'; + +type UserGuideProps = { + info?: ModelDeploymentData; +}; + +function UserGuide({ info }: UserGuideProps) { + const [docs, setDocs] = useState(''); + useEffect(() => { + getModelDeploymentDocs(); + }, [info]); + + // 获取模型部署文档 + const getModelDeploymentDocs = async () => { + if (info) { + const params = pick(info, ['service_id', 'service_ins_id']); + const [res] = await to(getModelDeploymentDocsReq(params)); + if (res && res.data && res.data.docs) { + setDocs(JSON.stringify(res.data.docs, null, 2)); + } + } + }; + + return
{docs}
; +} + +export default UserGuide; diff --git a/react-ui/src/services/modelDeployment/index.ts b/react-ui/src/services/modelDeployment/index.ts index 5004b357..4492318d 100644 --- a/react-ui/src/services/modelDeployment/index.ts +++ b/react-ui/src/services/modelDeployment/index.ts @@ -67,3 +67,11 @@ export function getModelDeploymentDocsReq(data: any) { data, }); } + +// 获取模型部署日志 +export function getModelDeploymentLogReq(data: any) { + return request(`/api/v1/model/getAppLog`, { + method: 'POST', + data, + }); +} diff --git a/react-ui/src/utils/ui.tsx b/react-ui/src/utils/ui.tsx index b970556e..85d5234a 100644 --- a/react-ui/src/utils/ui.tsx +++ b/react-ui/src/utils/ui.tsx @@ -71,7 +71,13 @@ export const gotoLoginPage = (toHome: boolean = true) => { } }; -// 上传文件校验 +/** + * 验证文件上传 + * + * @param {UploadFile[]} files - The array of uploaded files. + * @param {boolean} [required=true] - Flag indicating if files are required. + * @return {boolean} Returns true if all files are valid, false otherwise. + */ export const validateUploadFiles = (files: UploadFile[], required: boolean = true): boolean => { if (required && files.length === 0) { message.error('请上传文件'); @@ -95,3 +101,18 @@ export const validateUploadFiles = (files: UploadFile[], required: boolean = tru }); return !hasError; }; + +/** + * 滚动到底部 + * + * @param {boolean} smooth - Determines if the scroll should be smooth + */ +export const scrollToBottom = (element: HTMLElement | null, smooth: boolean = true) => { + if (element) { + const optons: ScrollToOptions = { + top: element.scrollHeight, + behavior: smooth ? 'smooth' : 'instant', + }; + element.scrollTo(optons); + } +};