fix: dashboard re-render

This commit is contained in:
jialin
2024-07-04 17:43:39 +08:00
parent ed3f9f7e96
commit e9d8828dcd
42 changed files with 240 additions and 1442 deletions
-94
View File
@@ -1,94 +0,0 @@
import { Area } from '@ant-design/plots';
import EmptyData from './empty-data';
interface LineChartProps {
title?: string;
height?: number;
data: any[];
color?: string;
xField?: string;
yField?: string;
locale?: string;
labelFormatter?: (v: any) => string;
slider?: any;
}
const LineChart: React.FC<LineChartProps> = (props) => {
const {
data,
title,
color,
xField,
locale,
yField,
slider,
height,
labelFormatter
} = props;
const config = {
height,
xField: xField || 'time',
yField: yField || 'value',
autoFit: true,
slider,
// shapeField: 'smooth',
axis: {
x: {
textStyle: {
autoRoate: true
},
labelFormatter
},
y: {
tick: false,
// size: 14,
// title: '%',
titlePosition: 'top',
titleFontSize: 12
}
},
style: {
fill: (params: any) => {
console.log('area param=========', params);
return color || 'rgba(84, 204, 152,0.8)';
},
stroke: (params: any) => {
console.log('area param========2=', params);
return color || 'rgba(84, 204, 152,0.8)';
}
},
title: {
title,
style: {
align: 'center',
titleFontSize: 14,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
},
legend: {
color: {
layout: { justifyContent: 'center' }
},
size: {
itemLabelFontSize: 14,
itemLabelFontWeight: 500
}
},
tooltip: {
title: 'time',
items: [{ channel: 'y' }]
}
};
return (
<>
{data.length === 0 ? (
<EmptyData height={height} title={title} />
) : (
<Area data={data} {...config} />
)}
</>
);
};
export default LineChart;
-113
View File
@@ -1,113 +0,0 @@
import { Column } from '@ant-design/plots';
import EmptyData from './empty-data';
interface BarChartProps {
data: any[];
xField: string;
yField: string;
title?: string;
height?: number;
group?: boolean;
colorField?: string;
seriesField?: string;
stack?: boolean;
legend?: any;
labelFormatter?: (v: any) => string;
}
const BarChart: React.FC<BarChartProps> = (props) => {
const {
data,
xField,
yField,
title,
height = 260,
group,
colorField,
seriesField,
stack,
legend = undefined,
labelFormatter
} = props;
const config = {
data,
xField,
yField,
colorField: colorField || 'name',
direction: 'vertical',
stack,
seriesField,
height,
group,
scale: {
x: {
type: 'band',
padding: 0.5
}
},
legend:
legend === 'undefined'
? {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
}
: legend,
axis: {
x: {
xAxis: true,
tick: false,
labelFormatter
},
y: {
tick: false,
labelAutoWrap: true
}
},
title: {
title,
style: {
align: 'center',
titleFontSize: 14,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
},
split: {
type: 'line',
line: {
color: 'red',
style: {
color: 'red',
lineDash: [4, 5]
}
}
},
style: {
fill: (params: any) => {
console.log('colbar=====', params);
return params.color || 'rgba(84, 204, 152,0.8)';
},
// radiusTopLeft: 12,
// radiusTopRight: 12,
align: 'center',
width: 20
}
};
return (
<>
{data.length > 0 ? (
<Column {...config} />
) : (
<EmptyData height={height} title={title} />
)}
</>
);
};
export default BarChart;
-8
View File
@@ -1,8 +0,0 @@
export const titleStyle = {
style: {
align: 'center',
titleFontSize: 14,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
};
-67
View File
@@ -1,67 +0,0 @@
import { Gauge } from '@ant-design/plots';
import React from 'react';
interface GaugeChartProps {
target: number;
total: number;
width?: number;
height?: number;
title?: string;
thresholds: number[];
rangColor: string[];
}
const GaugeChart: React.FC<GaugeChartProps> = (props) => {
const { target, total, width, height, thresholds, rangColor, title } = props;
const config = {
width,
height,
box: {
padding: [0, 0, 0, 0],
style: {
padding: [0, 0, 0, 0]
}
},
guide: {
arc: {
style: {
lineWidth: 30
}
}
},
autoFit: true,
data: {
target,
total,
name: 'score',
thresholds
},
legend: false,
scale: {
color: {
range: rangColor
}
},
startAngle: Math.PI,
endAngle: 0,
title: {
title,
size: 0,
style: {
align: 'center',
titleFontSize: 14,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
},
style: {
arcShape: 'round',
textContent: (target: number, total: number) => {
return `${(target / total) * 100}%`;
}
}
};
return <Gauge {...config} />;
};
export default GaugeChart;
-111
View File
@@ -1,111 +0,0 @@
import { Bar } from '@ant-design/plots';
import EmptyData from './empty-data';
interface BarChartProps {
data: any[];
xField: string;
yField: string;
title?: string;
height?: number;
group?: boolean;
colorField?: string;
seriesField?: string;
stack?: boolean;
legend?: any;
showYAxis?: boolean;
}
const BarChart: React.FC<BarChartProps> = (props) => {
const {
data,
xField,
yField,
title,
height,
group,
colorField,
seriesField,
stack,
showYAxis = true,
legend = undefined
} = props;
const config = {
data,
xField,
yField,
colorField: colorField || 'name',
direction: 'vertical',
seriesField,
height,
group,
stack,
legend:
legend === 'undefined'
? {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
}
: legend,
scale: {
x: {
type: 'band',
padding: 0.5
}
},
axis: {
x: {
xAxis: true,
tick: false
},
y: showYAxis
? {
tick: false
}
: null
},
title: {
title,
style: {
align: 'start',
titleFontSize: 14,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
},
split: {
type: 'line',
line: {
style: {
lineDash: [4, 5]
}
}
},
markBackground: {},
style: {
fill: (params: any) => {
return (
params.color ||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)'
);
},
// radiusTopLeft: 12,
// radiusTopRight: 12,
height: 20
}
};
return (
<>
{data.length === 0 ? (
<EmptyData height={height} title={title} />
) : (
<Bar {...config} />
)}
</>
);
};
export default BarChart;
-92
View File
@@ -1,92 +0,0 @@
import { Line } from '@ant-design/plots';
import EmptyData from './empty-data';
interface LineChartProps {
title?: string;
height?: number;
data: any[];
color?: string[];
xField?: string;
yField?: string;
locale?: string;
labelFormatter?: (v: any) => string;
slider?: any;
}
const LineChart: React.FC<LineChartProps> = (props) => {
const {
data,
title,
color,
xField,
locale,
yField,
slider,
height,
labelFormatter
} = props;
const config = {
title,
height,
xField: xField || 'time',
yField: yField || 'value',
// color: color || ['red', 'blue', 'green', 'yellow'],
colorField: 'type',
autoFit: true,
slider,
shapeField: 'smooth',
axis: {
x: {
textStyle: {
autoRoate: true
}
},
y: {
tick: false,
// size: 14,
// title: '%',
titlePosition: 'top',
titleFontSize: 12,
labelFormatter
}
},
// point: {
// shapeField: 'circle',
// sizeField: 2
// },
style: {
lineWidth: 1.5,
opacity: 0.7
// fill: (params: any) => {
// return params.color;
// }
// stroke: (params: any) => {
// console.log('line param=========', params.color);
// return params.color;
// }
},
legend: {
color: {
layout: { justifyContent: 'center' }
},
size: {
itemLabelFontSize: 14,
itemLabelFontWeight: 500
}
},
tooltip: {
title: 'time',
items: [{ channel: 'y' }]
}
};
return (
<>
{data.length === 0 ? (
<EmptyData height={height} title={title} />
) : (
<Line data={data} {...config} />
)}
</>
);
};
export default LineChart;
-79
View File
@@ -1,79 +0,0 @@
import { Liquid } from '@ant-design/plots';
import { useEffect, useState } from 'react';
interface LiquidChartProps {
percent: number;
color?: string;
title?: string;
height?: number;
width?: number;
thresholds?: number[];
rangColor?: string[];
}
const LiquidChart: React.FC<LiquidChartProps> = (props) => {
const {
percent = 0,
color,
title,
width,
height,
thresholds = [],
rangColor = []
} = props;
const primaryColor = 'rgba(84, 204, 152,0.8)';
const [fillColor, setFillColor] = useState<string>(primaryColor);
const calcColorByPercent = () => {
if (color) {
return color;
}
if (!rangColor.length) {
return primaryColor;
}
if (rangColor.length && thresholds.length) {
let index = 0;
for (let i = 0; i < thresholds.length; i++) {
if (percent <= thresholds[i]) {
index = i;
break;
}
}
return rangColor[index];
}
return primaryColor;
};
useEffect(() => {
const fc = calcColorByPercent();
setFillColor(fc);
}, [percent]);
const config = {
percent,
textAlign: 'center',
autoFit: true,
title: {
title,
size: 0,
style: {
align: 'center',
titleFontSize: 12,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
},
style: {
textAlign: 'center',
outlineBorder: 2,
waveLength: 128,
stroke: fillColor,
fill: fillColor
}
};
return <Liquid {...config} />;
};
export default LiquidChart;
-37
View File
@@ -1,37 +0,0 @@
import { Pie } from '@ant-design/plots';
import numeral from 'numeral';
interface PieChartProps {
data: { label: string; value: number }[];
height?: number;
radius?: number;
}
const PieChart: React.FC<PieChartProps> = (props) => {
const { data, height = 340, radius = 0.9 } = props;
return (
<Pie
height={height}
radius={radius}
angleField="value"
colorField="label"
data={data}
legend={{
color: {
position: 'bottom',
layout: {
justifyContent: 'center'
}
}
}}
label={{
position: 'outside',
text: (item: { label: number; value: number }) => {
return `${item.label}: ${numeral(item.value).format('0,0')}`;
}
}}
/>
);
};
export default PieChart;
+2 -2
View File
@@ -9,7 +9,7 @@ import {
yAxis
} from '@/components/echarts/config';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { memo, useEffect, useState } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
@@ -90,4 +90,4 @@ const BarChart: React.FC<ChartProps> = (props) => {
);
};
export default BarChart;
export default memo(BarChart);
-1
View File
@@ -37,7 +37,6 @@ export const legend = {
export const xAxis = {
type: 'category',
boundaryGap: true,
axisTick: {
show: true,
lineStyle: {
+2 -3
View File
@@ -8,7 +8,7 @@ import {
yAxis
} from '@/components/echarts/config';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { memo, useEffect, useState } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
@@ -87,7 +87,6 @@ const BarChart: React.FC<ChartProps> = (props) => {
},
xAxis: {
...options.xAxis,
boundaryGap: true,
type: 'value',
splitLine: {
show: false
@@ -113,4 +112,4 @@ const BarChart: React.FC<ChartProps> = (props) => {
);
};
export default BarChart;
export default memo(BarChart);
+2 -1
View File
@@ -1,3 +1,4 @@
import { isNotEmptyValue } from '@/utils/index';
import type { InputNumberProps } from 'antd';
import { Form, InputNumber } from 'antd';
import { useEffect, useRef, useState } from 'react';
@@ -24,7 +25,7 @@ const SealInputNumber: React.FC<InputNumberProps & SealFormItemProps> = (
}
useEffect(() => {
if (props.value) {
if (isNotEmptyValue(props.value)) {
setIsFocus(true);
}
}, [props.value]);
+6
View File
@@ -208,6 +208,12 @@ body {
height: 40px;
}
.ant-input-outlined,
.ant-input-affix-wrapper,
.ant-picker {
height: 40px;
}
.ant-input-outlined.ant-input-status-error:not(.ant-input-disabled) {
border: none;
box-shadow: none;
+10 -9
View File
@@ -1,5 +1,6 @@
import breakpoints from '@/config/breakpoints';
import { useEffect, useState } from 'react';
import _ from 'lodash';
import { useCallback, useEffect, useState } from 'react';
export default function useWindowResize() {
const [size, setSize] = useState<{ width: number; height: number }>({
@@ -11,7 +12,7 @@ export default function useWindowResize() {
const [isDesktop, setIsDesktop] = useState<boolean>(false);
const [currentPoint, setCurrentPoint] = useState<string>('');
const checkBreakpoint = (width: number) => {
const checkBreakpoint = useCallback((width: number) => {
if (width < breakpoints.sm) {
setIsMobile(true);
setIsTablet(false);
@@ -37,15 +38,15 @@ export default function useWindowResize() {
setIsTablet(false);
setIsDesktop(true);
setCurrentPoint('xl');
};
}, []);
const handleResize = _.throttle(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}, 200);
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
+1 -1
View File
@@ -5,7 +5,7 @@ import GpustackLogo from '@/assets/images/gpustack-logo.png';
import React from 'react';
const LogoIcon: React.FC = () => {
return <img src={GpustackLogo} alt="logo" style={{ height: 24 }} />;
return <img src={GpustackLogo} alt="logo" style={{ height: 16 }} />;
};
export default LogoIcon;
+1 -1
View File
@@ -15,7 +15,7 @@ export default {
'dashboard.vramutilization': 'VRAM Utilization',
'dashboard.gpuutilization': 'GPU Utilization',
'dashboard.usage': 'Usage',
'dashboard.apirequest': 'API Request',
'dashboard.apirequest': 'API Requests',
'dashboard.tokens': 'Token Usage',
'dashboard.topusers': 'Top Users',
'dashboard.activeModels': 'Active Models',
+1 -1
View File
@@ -25,5 +25,5 @@ export default {
'users.password.confirm': 'Confirm New Password',
'users.password.confirm.empty': 'Please confirm the new password.',
'users.password.confirm.error': 'The two passwords entered do not match.',
'users.login.title': 'Log in to GPUStack'
'users.login.title': 'Log in to {name}'
};
+1 -1
View File
@@ -24,5 +24,5 @@ export default {
'users.password.confirm': '确认新密码',
'users.password.confirm.empty': '请确认新密码',
'users.password.confirm.error': '两次输入的密码不一致',
'users.login.title': '登录 GPUStack'
'users.login.title': '登录 {name}'
};
+2 -1
View File
@@ -193,11 +193,12 @@ const Models: React.FC = () => {
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 300 }}
allowClear
onChange={handleNameChange}
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-primary)' }}
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
@@ -0,0 +1,39 @@
import { memo, useCallback, useEffect, useState } from 'react';
import { queryDashboardData } from '../apis';
import DashboardContext from '../config/dashboard-context';
import { DashboardProps } from '../config/types';
import ActiveTable from './active-table';
import Overview from './over-view';
import SystemLoad from './system-load';
import Usage from './usage';
const Dashboard: React.FC<{ setLoading: (loading: boolean) => void }> = ({
setLoading
}) => {
const [data, setData] = useState<DashboardProps>({} as DashboardProps);
const getDashboardData = useCallback(async () => {
try {
setLoading(true);
const res = await queryDashboardData();
setData(res);
setLoading(false);
} catch (error) {
setLoading(false);
setData({} as DashboardProps);
}
}, []);
useEffect(() => {
getDashboardData();
}, []);
return (
<DashboardContext.Provider value={{ ...data, fetchData: getDashboardData }}>
<Overview></Overview>
<SystemLoad></SystemLoad>
<Usage></Usage>
<ActiveTable></ActiveTable>
</DashboardContext.Provider>
);
};
export default memo(Dashboard);
+1 -1
View File
@@ -29,7 +29,7 @@ const renderCardItem = (data: {
const Overview: React.FC = () => {
const intl = useIntl();
const data = useContext(DashboardContext).resource_counts || {};
console.log('overview===');
const renderValue = (
value:
| number
@@ -2,7 +2,7 @@ import Chart from '@/components/echarts/chart';
import { getLocale, useIntl } from '@umijs/max';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useCallback, useContext, useEffect, useState } from 'react';
import { useContext } from 'react';
import { DashboardContext } from '../config/dashboard-context';
const chartColorMap = {
@@ -110,9 +110,11 @@ const option = {
};
const UtilizationOvertime: React.FC = () => {
console.log('systemload=====================');
const intl = useIntl();
const locale = getLocale();
const [dataOptions, setDataOptions] = useState<any>(option);
let dataOptions: any = {};
// const [dataOptions, setDataOptions] = useState<any>(option);
const data = useContext(DashboardContext)?.system_load?.history || {};
const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory'];
@@ -121,7 +123,7 @@ const UtilizationOvertime: React.FC = () => {
return `${value}%`;
};
const generateData = useCallback(() => {
const generateData = () => {
const legendData: string[] = [];
const xAxisData: string[] = [];
let list: { value: number; time: string; type: string }[] = [];
@@ -157,7 +159,7 @@ const UtilizationOvertime: React.FC = () => {
};
});
setDataOptions({
dataOptions = {
...option,
legend: {
...option.legend,
@@ -168,12 +170,12 @@ const UtilizationOvertime: React.FC = () => {
data: _.uniq(xAxisData)
},
series: list
});
}, [data, locale]);
useEffect(() => {
generateData();
}, [data, locale]);
};
};
generateData();
// useEffect(() => {
// generateData();
// }, [data, locale]);
return (
<>
@@ -0,0 +1,52 @@
import CardWrapper from '@/components/card-wrapper';
import BarChart from '@/components/echarts/bar-chart';
import LineChart from '@/components/echarts/line-chart';
import { useIntl } from '@umijs/max';
import { Col, Row } from 'antd';
import dayjs from 'dayjs';
import React from 'react';
interface RequestTokenInnerProps {
requestData: {
name: string;
color: string;
areaStyle: any;
data: { time: string; value: number }[];
}[];
tokenData: { time: string; value: number }[];
xAxisData: string[];
}
const RequestTokenInner: React.FC<RequestTokenInnerProps> = (props) => {
console.log('request token inner=====================');
const { requestData, tokenData, xAxisData } = props;
const intl = useIntl();
const labelFormatter = (v: any) => {
return dayjs(v).format('MM-DD');
};
return (
<CardWrapper style={{ width: '100%' }}>
<Row style={{ width: '100%' }}>
<Col span={12}>
<LineChart
title={intl.formatMessage({ id: 'dashboard.apirequest' })}
seriesData={requestData}
xAxisData={xAxisData}
height={360}
labelFormatter={labelFormatter}
></LineChart>
</Col>
<Col span={12}>
<BarChart
title={intl.formatMessage({ id: 'dashboard.tokens' })}
seriesData={tokenData}
xAxisData={xAxisData}
height={360}
labelFormatter={labelFormatter}
></BarChart>
</Col>
</Row>
</CardWrapper>
);
};
export default React.memo(RequestTokenInner);
@@ -0,0 +1,27 @@
import CardWrapper from '@/components/card-wrapper';
import HBar from '@/components/echarts/h-bar';
import { useIntl } from '@umijs/max';
import React from 'react';
interface TopUserProps {
userData: { name: string; value: number }[];
topUserList: string[];
}
const TopUser: React.FC<TopUserProps> = (props) => {
console.log('TopUser=====================');
const { userData, topUserList } = props;
const intl = useIntl();
return (
<CardWrapper>
<HBar
title={intl.formatMessage({ id: 'dashboard.topusers' })}
seriesData={userData}
xAxisData={topUserList}
height={360}
></HBar>
</CardWrapper>
);
};
export default React.memo(TopUser);
+36 -69
View File
@@ -1,7 +1,3 @@
import CardWrapper from '@/components/card-wrapper';
import BarChart from '@/components/echarts/bar-chart';
import HBar from '@/components/echarts/h-bar';
import LineChart from '@/components/echarts/line-chart';
import PageTools from '@/components/page-tools';
import breakpoints from '@/config/breakpoints';
import useWindowResize from '@/hooks/use-window-resize';
@@ -11,6 +7,8 @@ import dayjs from 'dayjs';
import _ from 'lodash';
import { useContext, useEffect, useState } from 'react';
import { DashboardContext } from '../config/dashboard-context';
import RequestTokenInner from './usage-inner/request-token-inner';
import TopUser from './usage-inner/top-user';
const baseColorMap = {
baseL2: 'rgba(13,171,219,0.8)',
@@ -38,28 +36,27 @@ const Usage = () => {
const intl = useIntl();
const { size } = useWindowResize();
const [paddingRight, setPaddingRight] = useState<string>('20px');
const [requestData, setRequestData] = useState<
{
name: string;
color: string;
areaStyle: any;
data: { time: string; value: number }[];
}[]
>([]);
console.log('usage========');
const currentDate = dayjs();
const currentMonthDays = getCurrentMonthDays();
const [tokenData, setTokenData] = useState<{ time: string; value: number }[]>(
[]
);
const [userData, setUserData] = useState<{ name: string; value: number }[]>(
[]
);
const [xAxisData, setXAxisData] = useState<string[]>(currentMonthDays);
const [topUserList, setTopUseList] = useState<string[]>([]);
let requestData: {
name: string;
color: string;
areaStyle: any;
data: { time: string; value: number }[];
}[] = [];
let tokenData: { time: string; value: number }[] = [];
let userData: { name: string; value: number }[] = [];
const xAxisData: string[] = currentMonthDays;
let topUserList: string[] = [];
const data = useContext(DashboardContext)?.model_usage || {};
const { model_usage, fetchData } = useContext(DashboardContext);
const data = model_usage || {};
const handleSelectDate = (date: any) => {};
const handleSelectDate = (date: any) => {
// fetchData?.();
};
const generateData = () => {
const requestList: {
@@ -68,30 +65,30 @@ const Usage = () => {
areaStyle: any;
data: { time: string; value: number }[];
} = {
name: 'API Request',
name: 'API requests',
areaStyle: {},
color: baseColorMap.base,
data: []
};
const completionData: any = {
name: 'completion_token',
name: 'Completion tokens',
color: baseColorMap.base,
data: []
};
const prompData: any = {
name: 'prompt_token',
name: 'Prompt tokens',
color: baseColorMap.baseR3,
data: []
};
const topUserPrompt: any = {
name: 'prompt_token',
name: 'Prompt tokens',
color: baseColorMap.baseR3,
data: []
};
const topUserCompletion: any = {
name: 'completion_token',
name: 'Completion tokens',
color: baseColorMap.base,
data: []
};
@@ -161,14 +158,10 @@ const Usage = () => {
});
});
setRequestData([requestList]);
setUserData([topUserCompletion, topUserPrompt]);
setTopUseList(users);
setTokenData([completionData, prompData]);
};
const labelFormatter = (v: any) => {
return dayjs(v).format('MM-DD');
requestData = [requestList];
userData = [topUserCompletion, topUserPrompt];
topUserList = users;
tokenData = [completionData, prompData];
};
useEffect(() => {
@@ -179,9 +172,7 @@ const Usage = () => {
}
}, [size.width]);
useEffect(() => {
generateData();
}, [data]);
generateData();
return (
<>
@@ -193,7 +184,7 @@ const Usage = () => {
onChange={handleSelectDate}
style={{ width: 300 }}
picker="month"
defaultValue={dayjs()}
defaultValue={currentDate}
/>
}
/>
@@ -206,38 +197,14 @@ const Usage = () => {
xl={16}
style={{ paddingRight: paddingRight }}
>
<CardWrapper style={{ width: '100%' }}>
<Row style={{ width: '100%' }}>
<Col span={12}>
<LineChart
title={intl.formatMessage({ id: 'dashboard.apirequest' })}
seriesData={requestData}
xAxisData={xAxisData}
height={360}
labelFormatter={labelFormatter}
></LineChart>
</Col>
<Col span={12}>
<BarChart
title={intl.formatMessage({ id: 'dashboard.tokens' })}
seriesData={tokenData}
xAxisData={xAxisData}
height={360}
labelFormatter={labelFormatter}
></BarChart>
</Col>
</Row>
</CardWrapper>
<RequestTokenInner
requestData={requestData}
xAxisData={xAxisData}
tokenData={tokenData}
></RequestTokenInner>
</Col>
<Col xs={24} sm={24} md={24} lg={24} xl={8}>
<CardWrapper>
<HBar
title={intl.formatMessage({ id: 'dashboard.topusers' })}
seriesData={userData}
xAxisData={topUserList}
height={360}
></HBar>
</CardWrapper>
<TopUser userData={userData} topUserList={topUserList}></TopUser>
</Col>
</Row>
</>
@@ -1,8 +1,8 @@
import { createContext } from 'react';
import { DashboardProps } from './types';
export const DashboardContext = createContext<DashboardProps>(
{} as DashboardProps
);
export const DashboardContext = createContext<
DashboardProps & { fetchData: () => Promise<void> }
>({} as DashboardProps & { fetchData: () => Promise<void> });
export default DashboardContext;
+4 -30
View File
@@ -1,44 +1,18 @@
import { PageContainer } from '@ant-design/pro-components';
import { Spin } from 'antd';
import { useEffect, useState } from 'react';
import { queryDashboardData } from './apis';
import ActiveTable from './components/active-table';
import Overview from './components/over-view';
import SystemLoad from './components/system-load';
import Usage from './components/usage';
import DashboardContext from './config/dashboard-context';
import { DashboardProps } from './config/types';
import { memo, useState } from 'react';
import DashboardInner from './components/dahboard-inner';
const Dashboard: React.FC = () => {
const [data, setData] = useState<DashboardProps>({} as DashboardProps);
const [loading, setLoading] = useState(false);
const getDashboardData = async () => {
try {
setLoading(true);
const res = await queryDashboardData();
setData(res);
setLoading(false);
} catch (error) {
setLoading(false);
setData({} as DashboardProps);
}
};
useEffect(() => {
getDashboardData();
}, []);
return (
<PageContainer ghost extra={[]}>
<Spin spinning={loading}>
<DashboardContext.Provider value={{ ...data }}>
<Overview></Overview>
<SystemLoad></SystemLoad>
<Usage></Usage>
<ActiveTable></ActiveTable>
</DashboardContext.Provider>
<DashboardInner setLoading={setLoading} />
</Spin>
</PageContainer>
);
};
export default Dashboard;
export default memo(Dashboard);
+5 -1
View File
@@ -146,6 +146,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
onChange={handleInputRepoChange}
onSearch={debounceSearch}
options={repoOptions}
disabled={action === PageAction.EDIT}
addAfter={
<span style={{ position: 'relative', top: '2px' }}>
<SearchOutlined></SearchOutlined>
@@ -176,6 +177,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
required
options={fileOptions}
onFocus={handleRepoOnBlur}
disabled={action === PageAction.EDIT}
></SealAutoComplete>
</Form.Item>
</>
@@ -229,6 +231,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
>
<SealAutoComplete
filterOption
disabled={action === PageAction.EDIT}
label={intl.formatMessage({ id: 'model.form.ollama.model' })}
required
options={ollamaModelOptions}
@@ -314,6 +317,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
]}
>
<SealSelect
disabled={action === PageAction.EDIT}
label={intl.formatMessage({
id: 'models.form.source'
})}
@@ -343,7 +347,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
id: 'models.form.replicas'
})}
required
min={1}
min={0}
></SealInput.Number>
</Form.Item>
<Form.Item<FormData> name="description">
+2 -1
View File
@@ -19,7 +19,8 @@ export const modelSourceMap = {
};
export const status: any = {
Running: StatusMaps.success
Running: StatusMaps.success,
Pending: StatusMaps.transitioning
};
export const ActionList = [
+7 -2
View File
@@ -334,9 +334,14 @@ const Models: React.FC = () => {
handleViewLogs(row);
}
};
useEffect(() => {
fetchData();
return () => {
axiosToken?.cancel?.();
};
}, [queryParams]);
useEffect(() => {
// fetchData();
createModelsChunkRequest();
return () => {
chunkRequedtRef.current?.current?.cancel?.();
@@ -430,7 +435,7 @@ const Models: React.FC = () => {
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-primary)' }}
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
+13 -13
View File
@@ -11,7 +11,6 @@ import { SelectLang, history, useIntl, useModel } from '@umijs/max';
import { Button, Checkbox, Form } from 'antd';
import CryptoJS from 'crypto-js';
import { useAtom } from 'jotai';
import { useEffect } from 'react';
import { flushSync } from 'react-dom';
import { login } from '../apis';
@@ -40,12 +39,21 @@ const renderWelCome = (intl: any) => {
style={{
display: 'flex',
marginBottom: 32,
fontSize: 24,
fontSize: 20,
justifyContent: 'center',
alignItems: 'center'
}}
>
<div>{intl?.formatMessage({ id: 'users.login.title' })}</div>
<div className="flex-center">
<span>
{intl?.formatMessage({ id: 'users.login.title' }, { name: '' })}
</span>
<img
src={LogoIcon}
alt="logo"
style={{ height: '24px', marginLeft: 10 }}
/>
</div>
</div>
);
};
@@ -130,9 +138,7 @@ const LoginForm = () => {
}
};
useEffect(() => {
callGetRememberMe();
}, []);
callGetRememberMe();
return (
<div>
@@ -141,15 +147,9 @@ const LoginForm = () => {
position: 'fixed',
right: 0,
top: 0,
left: 0,
width: '100%',
padding: '0 20px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
padding: '0 20px'
}}
>
<div>{renderLogo()}</div>
<SelectLang icon={<GlobalOutlined />} reload={false} />
</div>
<div>
@@ -47,7 +47,6 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
<Space size={20}>
<Button
disabled={disabled}
type="primary"
icon={<PlusOutlined />}
onClick={onNewMessage}
>
@@ -164,7 +164,7 @@ const MessageList: React.FC<MessageProps> = (props) => {
<span className="title">
{intl.formatMessage({ id: 'playground.system' })}
</span>
<Button type="primary" size="small">
<Button size="small">
{collapsed ? <EyeInvisibleOutlined /> : <EyeOutlined />}
</Button>
</div>
+2 -1
View File
@@ -88,11 +88,12 @@ const Models: React.FC = () => {
id: 'common.filter.name'
})}
style={{ width: 300 }}
allowClear
onChange={handleNameChange}
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-primary)' }}
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
+2 -1
View File
@@ -119,11 +119,12 @@ const Models: React.FC = () => {
id: 'common.filter.name'
})}
style={{ width: 300 }}
allowClear
onChange={handleNameChange}
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-primary)' }}
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
+2 -1
View File
@@ -213,11 +213,12 @@ const Models: React.FC = () => {
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 300 }}
allowClear
onChange={handleNameChange}
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-primary)' }}
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
+1 -7
View File
@@ -3,13 +3,7 @@ export const isNotEmptyValue = (value: any) => {
if (Array.isArray(value)) {
return value.length > 0;
}
return (
value !== null &&
value !== undefined &&
value !== '' &&
value !== false &&
value !== 0
);
return !!value || value === 0 || value === false;
};
export const handleBatchRequest = async (