Browse Source

feat: 完成模型部署服务日志

pull/62/head
cp3hnu 1 year ago
parent
commit
41a684806e
17 changed files with 442 additions and 230 deletions
  1. +19
    -0
      react-ui/src/components/LabelValue/index.less
  2. +20
    -0
      react-ui/src/components/LabelValue/index.tsx
  3. +1
    -1
      react-ui/src/components/ModalTitle/index.less
  4. +3
    -3
      react-ui/src/components/ModalTitle/index.tsx
  5. +1
    -1
      react-ui/src/iconfont/iconfont.js
  6. +16
    -12
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  7. +8
    -14
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  8. +3
    -2
      react-ui/src/pages/Model/components/ModelEvolution/index.tsx
  9. +20
    -27
      react-ui/src/pages/ModelDeployment/Info/index.less
  10. +27
    -169
      react-ui/src/pages/ModelDeployment/Info/index.tsx
  11. +91
    -0
      react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx
  12. +16
    -0
      react-ui/src/pages/ModelDeployment/components/ServerLog/index.less
  13. +147
    -0
      react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx
  14. +8
    -0
      react-ui/src/pages/ModelDeployment/components/UserGuide/index.less
  15. +32
    -0
      react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx
  16. +8
    -0
      react-ui/src/services/modelDeployment/index.ts
  17. +22
    -1
      react-ui/src/utils/ui.tsx

+ 19
- 0
react-ui/src/components/LabelValue/index.less View File

@@ -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;
}
}

+ 20
- 0
react-ui/src/components/LabelValue/index.tsx View File

@@ -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 (
<div className={classNames('kf-label-value', className)} style={style}>
<div className="kf-label-value__label">{label}</div>
<div className="kf-label-value__value">{value ?? '--'}</div>
</div>
);
}

export default LabelValue;

+ 1
- 1
react-ui/src/components/ModalTitle/index.less View File

@@ -1,4 +1,4 @@
.modal-title {
.kf-modal-title {
display: flex;
align-items: center;
color: @primary-color;


+ 3
- 3
react-ui/src/components/ModalTitle/index.tsx View File

@@ -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 (
<div className={classNames(styles['modal-title'], className)} style={style}>
{image && <img className={styles['modal-title__image']} src={image} alt="" />}
<div className={classNames('kf-modal-title', className)} style={style}>
{image && <img className={'kf-modal-title__image'} src={image} alt="" />}
{title}
</div>
);


+ 1
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


+ 16
- 12
react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx View File

@@ -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<ResourceData>({} 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<string | undefined>(undefined);
@@ -74,8 +75,9 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {

const items = [
{
key: TabKeys.Introduction,
key: ResourceInfoTabKeys.Introduction,
label: `${typeName}简介`,
icon: <KFIcon type="icon-moxingjianjie" />,
children: (
<>
<div className={styles['resource-intro__title']}>简介</div>
@@ -84,8 +86,9 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
),
},
{
key: TabKeys.Version,
key: ResourceInfoTabKeys.Version,
label: `${typeName}文件/版本`,
icon: <KFIcon type="icon-moxingwenjian" />,
children: (
<ResourceVersion
resourceType={resourceType}
@@ -94,7 +97,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
isPublic={info.available_range === 1}
versionList={versionList}
version={version}
isActive={activeTab === TabKeys.Version}
isActive={activeTab === ResourceInfoTabKeys.Version}
getVersionList={getVersionList}
onVersionChange={handleVersionChange}
></ResourceVersion>
@@ -104,15 +107,16 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {

if (resourceType === ResourceType.Model) {
items.push({
key: TabKeys.Evolution,
key: ResourceInfoTabKeys.Evolution,
label: `模型演化`,
icon: <KFIcon type="icon-moxingyanhua1" />,
children: (
<ModelEvolution
resourceId={resourceId}
resourceName={info.name}
versionList={versionList}
version={version}
isActive={activeTab === TabKeys.Evolution}
isActive={activeTab === ResourceInfoTabKeys.Evolution}
onVersionChange={handleVersionChange}
></ModelEvolution>
),


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

@@ -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({
)}
<div className={styles['log-group__more-button']}>
{showMoreBtn && (
<Button
type="text"
style={{ color: 'white' }}
icon={<DoubleRightOutlined rotate={90} />}
onClick={loadMore}
>
<Button type="text" style={{ color: 'white' }} onClick={loadMore}>
更多
<DoubleRightOutlined rotate={90} />
</Button>
)}
</div>


+ 3
- 2
react-ui/src/pages/Model/components/ModelEvolution/index.tsx View File

@@ -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:


+ 20
- 27
react-ui/src/pages/ModelDeployment/Info/index.less View File

@@ -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);
}
}

+ 27
- 169
react-ui/src/pages/ModelDeployment/Info/index.tsx View File

@@ -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: <KFIcon type="icon-yuce" />,
},
{
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
},
{
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
},
];

function ModelDeploymentInfo() {
const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict);
const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>(
@@ -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: <KFIcon type="icon-yuce" />,
},
{
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <UserGuide info={modelDeployementInfo}></UserGuide>,
},
{
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={modelDeployementInfo}></ServerLog>,
},
];

// 切换 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 (
<div className={styles['model-deployment-info']}>
<PageTitle title="服务详情"></PageTitle>
@@ -91,125 +64,10 @@ function ModelDeploymentInfo() {
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<div className={styles['model-deployment-info__basic']}>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>服务名称:</div>
<div className={styles['value']}>{modelDeployementInfo?.service_name ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>镜  像:</div>
<div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>状  态:</div>
<div className={styles['value']}>
{ModelDeploymentStatusCell(modelDeployementInfo?.status)}
</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>模  型:</div>
<div className={styles['value']}>
{modelDeployementInfo?.model?.show_value ?? '--'}
</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>创建人:</div>
<div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>挂载路径:</div>
<div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>API URL:</div>
<div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>副本数量:</div>
<div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>创建时间:</div>
<div className={styles['value']}>
{modelDeployementInfo?.create_time
? formatDate(modelDeployementInfo.create_time)
: '--'}
</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>更新时间:</div>
<div className={styles['value']}>
{modelDeployementInfo?.update_time
? formatDate(modelDeployementInfo.update_time)
: '--'}
</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>环境变量:</div>
<div className={styles['value']}>{formatEnvText()}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>资源规格:</div>
<div className={styles['value']}>
{modelDeployementInfo?.resource
? getResourceDescription(modelDeployementInfo.resource)
: '--'}
</div>
</div>
</Col>
</Row>
<Row gutter={40}>
<Col span={18}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>描  述:</div>
<div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div>
</div>
</Col>
</Row>
<BasicInfo info={modelDeployementInfo} />
<div className={styles['model-deployment-info__content__tabs']}>
<Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} />
</div>
<Tabs
activeKey={activeTab}
style={{ marginTop: '20px' }}
items={tabItems}
onChange={hanleTabChange}
/>
{activeTab === ModelDeploymentTabKey.Guide && (
<div className={styles['model-deployment-info__guide']}>{docs}</div>
)}
</div>
</div>
);


+ 91
- 0
react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx View File

@@ -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 (
<div>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<LabelValue label="服务名称:" value={info?.service_name}></LabelValue>
</Col>
<Col span={10}>
<LabelValue label="镜  像:" value={info?.image}></LabelValue>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<LabelValue
label="状  态:"
value={ModelDeploymentStatusCell(info?.status)}
></LabelValue>
</Col>
<Col span={10}>
<LabelValue label="模  型:" value={info?.model?.show_value}></LabelValue>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<LabelValue label="创建人:" value={info?.created_by}></LabelValue>
</Col>
<Col span={10}>
<LabelValue label="挂载路径:" value={info?.model_path}></LabelValue>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<LabelValue label="API URL:" value={info?.url}></LabelValue>
</Col>
<Col span={10}>
<LabelValue label="副本数量:" value={info?.replicas}></LabelValue>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<LabelValue label="创建时间:" value={formatDate(info?.create_time)}></LabelValue>
</Col>
<Col span={10}>
<LabelValue label="更新时间:" value={formatDate(info?.update_time)}></LabelValue>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<LabelValue label="环境变量:" value={formatEnvText()}></LabelValue>
</Col>
<Col span={10}>
<LabelValue
label="资源规格:"
value={info?.resource ? getResourceDescription(info.resource) : '--'}
></LabelValue>
</Col>
</Row>
<Row gutter={40}>
<Col span={18}>
<LabelValue label="描  述:" value={info?.description}></LabelValue>
</Col>
</Row>
</div>
);
}

export default BasicInfo;

+ 16
- 0
react-ui/src/pages/ModelDeployment/components/ServerLog/index.less View File

@@ -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;
}
}
}

+ 147
- 0
react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx View File

@@ -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<NonNullable<TimeRangePickerProps['value']>>([
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<LogData[]>([]);
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 (
<div className={styles['server-log']}>
<div>
<RangePicker
presets={rangePresets}
showTime
value={dateRange}
format="YYYY-MM-DD HH:mm:ss"
onChange={handleRangeChange}
allowClear={false}
disabledDate={disabledDate}
/>
<Button type="default" style={{ marginLeft: '20px' }} onClick={handleSearch}>
查询
</Button>
</div>
{logData.length > 0 && (
<div className={styles['server-log__data']} id="server-log">
<div>{logData.map((v) => v.log_content).join('') || '暂无日志'}</div>
{hasMore && (
<Button
type="text"
className={styles['server-log__data__more']}
style={{ color: 'white' }}
onClick={loadMoreLog}
>
更多
<DoubleRightOutlined rotate={90} />
</Button>
)}
</div>
)}
</div>
);
}

export default ServerLog;

+ 8
- 0
react-ui/src/pages/ModelDeployment/components/UserGuide/index.less View File

@@ -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);
}

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

@@ -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 <div className={styles['user-guide']}>{docs}</div>;
}

export default UserGuide;

+ 8
- 0
react-ui/src/services/modelDeployment/index.ts View File

@@ -67,3 +67,11 @@ export function getModelDeploymentDocsReq(data: any) {
data,
});
}

// 获取模型部署日志
export function getModelDeploymentLogReq(data: any) {
return request(`/api/v1/model/getAppLog`, {
method: 'POST',
data,
});
}

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

@@ -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);
}
};

Loading…
Cancel
Save