fix: dashboard re-render
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,8 +0,0 @@
|
||||
export const titleStyle = {
|
||||
style: {
|
||||
align: 'center',
|
||||
titleFontSize: 14,
|
||||
titleFill: 'rgba(0,0,0,0.88)',
|
||||
titleFontWeight: 500
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -37,7 +37,6 @@ export const legend = {
|
||||
|
||||
export const xAxis = {
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisTick: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}'
|
||||
};
|
||||
|
||||
@@ -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}'
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -19,7 +19,8 @@ export const modelSourceMap = {
|
||||
};
|
||||
|
||||
export const status: any = {
|
||||
Running: StatusMaps.success
|
||||
Running: StatusMaps.success,
|
||||
Pending: StatusMaps.transitioning
|
||||
};
|
||||
|
||||
export const ActionList = [
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user