fix: ui issues
This commit is contained in:
+10
-5
@@ -8,7 +8,8 @@ export default {
|
|||||||
},
|
},
|
||||||
Table: {
|
Table: {
|
||||||
cellFontSize: 12,
|
cellFontSize: 12,
|
||||||
rowSelectedHoverBg: '#e6f6ff'
|
rowSelectedHoverBg: 'rgba(230, 230, 230, 0.88)',
|
||||||
|
rowSelectedBg: 'rgb(244, 245, 244)'
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
@@ -20,10 +21,14 @@ export default {
|
|||||||
},
|
},
|
||||||
Slider: {
|
Slider: {
|
||||||
handleSize: 8,
|
handleSize: 8,
|
||||||
handleSizeHover: 10
|
handleSizeHover: 8,
|
||||||
// handleColor: '#007BFF'
|
trackBg: 'rgba(0,0,0,0.15)',
|
||||||
// trackBg: '#007BFF',
|
handleColor: 'rgba(0,0,0,0.2)',
|
||||||
// trackHoverBg: '#007BFF'
|
trackHoverBg: 'rgba(0,0,0,0.25)',
|
||||||
|
handleActiveColor: 'rgba(0,0,0,0.3)',
|
||||||
|
dotActiveBorderColor: 'rgba(0,0,0,0.25)',
|
||||||
|
handleActiveOutlineColor: 'rgba(0,0,0,0.25)',
|
||||||
|
dotBorderColor: 'rgba(0,0,0,0.25)'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"echarts": "^5.5.1",
|
||||||
"has-ansi": "^5.0.1",
|
"has-ansi": "^5.0.1",
|
||||||
"jotai": "^2.8.4",
|
"jotai": "^2.8.4",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
|
|||||||
Generated
+21
-1
@@ -47,6 +47,9 @@ dependencies:
|
|||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.11
|
specifier: ^1.11.11
|
||||||
version: 1.11.11
|
version: 1.11.11
|
||||||
|
echarts:
|
||||||
|
specifier: ^5.5.1
|
||||||
|
version: 5.5.1
|
||||||
has-ansi:
|
has-ansi:
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
@@ -8238,6 +8241,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/echarts@5.5.1:
|
||||||
|
resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==, tarball: https://registry.npmjs.org/echarts/-/echarts-5.5.1.tgz}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.3.0
|
||||||
|
zrender: 5.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ee-first@1.1.1:
|
/ee-first@1.1.1:
|
||||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -16286,7 +16296,11 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tslib@1.14.1:
|
/tslib@1.14.1:
|
||||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==, tarball: https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/tslib@2.3.0:
|
||||||
|
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tslib@2.6.2:
|
/tslib@2.6.2:
|
||||||
@@ -17187,6 +17201,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
/zrender@5.6.0:
|
||||||
|
resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==, tarball: https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/zscroller@0.4.8:
|
/zscroller@0.4.8:
|
||||||
resolution: {integrity: sha512-G5NiNLKx2+QhhvZi2yV1jjVXY50otktxkseX2hG2N/eixohOUk0AY8ZpbAxNqS9oJS/NxItCsowupy2tsXxAMw==, tarball: https://registry.npmjs.org/zscroller/-/zscroller-0.4.8.tgz}
|
resolution: {integrity: sha512-G5NiNLKx2+QhhvZi2yV1jjVXY50otktxkseX2hG2N/eixohOUk0AY8ZpbAxNqS9oJS/NxItCsowupy2tsXxAMw==, tarball: https://registry.npmjs.org/zscroller/-/zscroller-0.4.8.tgz}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 896 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 140 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -45,6 +45,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-gap-2 {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.opct-7 {
|
.opct-7 {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
.cardWrapper {
|
.cardWrapper {
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
background-color: var(--color-white-1);
|
background-color: var(--color-white-1);
|
||||||
// box-shadow: var(--box-shadow-base);
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--color-border-1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ interface LineChartProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
height?: number;
|
height?: number;
|
||||||
data: any[];
|
data: any[];
|
||||||
color?: string[];
|
color?: string;
|
||||||
xField?: string;
|
xField?: string;
|
||||||
yField?: string;
|
yField?: string;
|
||||||
locale?: string;
|
locale?: string;
|
||||||
@@ -47,7 +47,14 @@ const LineChart: React.FC<LineChartProps> = (props) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
fill: 'rgba(84, 204, 152,0.8)'
|
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: {
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
|
|
||||||
style: {
|
style: {
|
||||||
fill: (params: any) => {
|
fill: (params: any) => {
|
||||||
|
console.log('colbar=====', params);
|
||||||
return params.color || 'rgba(84, 204, 152,0.8)';
|
return params.color || 'rgba(84, 204, 152,0.8)';
|
||||||
},
|
},
|
||||||
// radiusTopLeft: 12,
|
// radiusTopLeft: 12,
|
||||||
|
|||||||
@@ -55,19 +55,19 @@ const LineChart: React.FC<LineChartProps> = (props) => {
|
|||||||
// },
|
// },
|
||||||
style: {
|
style: {
|
||||||
lineWidth: 1.5,
|
lineWidth: 1.5,
|
||||||
opacity: 0.8
|
opacity: 0.7
|
||||||
|
// fill: (params: any) => {
|
||||||
|
// return params.color;
|
||||||
|
// }
|
||||||
// stroke: (params: any) => {
|
// stroke: (params: any) => {
|
||||||
// return (
|
// console.log('line param=========', params.color);
|
||||||
// params.color ||
|
// return params.color;
|
||||||
// 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
|
|
||||||
// );
|
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
color: {
|
color: {
|
||||||
layout: { justifyContent: 'center' }
|
layout: { justifyContent: 'center' }
|
||||||
},
|
},
|
||||||
|
|
||||||
size: {
|
size: {
|
||||||
itemLabelFontSize: 14,
|
itemLabelFontSize: 14,
|
||||||
itemLabelFontWeight: 500
|
itemLabelFontWeight: 500
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import Chart from '@/components/echarts/chart';
|
||||||
|
import {
|
||||||
|
barItemConfig,
|
||||||
|
grid,
|
||||||
|
legend,
|
||||||
|
tooltip,
|
||||||
|
xAxis,
|
||||||
|
yAxis
|
||||||
|
} from '@/components/echarts/config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { ChartProps } from './types';
|
||||||
|
|
||||||
|
const BarChart: React.FC<ChartProps> = (props) => {
|
||||||
|
const {
|
||||||
|
seriesData,
|
||||||
|
xAxisData,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
labelFormatter,
|
||||||
|
legendData,
|
||||||
|
title
|
||||||
|
} = props;
|
||||||
|
const [dataOptions, setDataOptions] = useState({});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
title: {
|
||||||
|
text: ''
|
||||||
|
},
|
||||||
|
grid,
|
||||||
|
tooltip: {
|
||||||
|
...tooltip
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'shadow'
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...xAxis,
|
||||||
|
axisLabel: {
|
||||||
|
...xAxis.axisLabel,
|
||||||
|
formatter: labelFormatter
|
||||||
|
},
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
yAxis,
|
||||||
|
legend: {
|
||||||
|
...legend,
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = _.map(seriesData, (item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...barItemConfig,
|
||||||
|
stack: 'total',
|
||||||
|
itemStyle: {
|
||||||
|
color: item.color
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const optionsConfig = {
|
||||||
|
...options,
|
||||||
|
title: {
|
||||||
|
show: true,
|
||||||
|
left: 'center',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
text: title
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
...options.yAxis
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...options.xAxis,
|
||||||
|
data: xAxisData
|
||||||
|
},
|
||||||
|
series: data
|
||||||
|
};
|
||||||
|
console.log('optionsConfig=========', optionsConfig);
|
||||||
|
setDataOptions(optionsConfig);
|
||||||
|
}, [seriesData, xAxisData, title]);
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
height={height}
|
||||||
|
options={dataOptions}
|
||||||
|
width={width || '100%'}
|
||||||
|
></Chart>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BarChart;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { throttle } from 'echarts/core';
|
||||||
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import echarts, { ECOption } from '.';
|
||||||
|
|
||||||
|
const Chart: React.FC<{
|
||||||
|
options: ECOption;
|
||||||
|
height: number | string;
|
||||||
|
width: number | string;
|
||||||
|
}> = ({ options, width, height }) => {
|
||||||
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
const chart = useRef<echarts.EChartsType>();
|
||||||
|
|
||||||
|
const init = useCallback(() => {
|
||||||
|
if (container.current) {
|
||||||
|
chart.current?.clear();
|
||||||
|
chart.current = echarts.init(container.current);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resize = useCallback(() => {
|
||||||
|
chart.current?.resize();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setOption = useCallback((options: ECOption) => {
|
||||||
|
chart.current?.clear();
|
||||||
|
chart.current?.setOption(options, {
|
||||||
|
notMerge: true,
|
||||||
|
lazyUpdate: true
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
return () => {
|
||||||
|
chart.current?.dispose();
|
||||||
|
};
|
||||||
|
}, [init]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
resize();
|
||||||
|
setOption(options);
|
||||||
|
}, [options]);
|
||||||
|
|
||||||
|
// resize on window resize
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = throttle(() => {
|
||||||
|
resize();
|
||||||
|
}, 100);
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, [resize, chart]);
|
||||||
|
|
||||||
|
return <div ref={container} style={{ width: width, height }}></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chart;
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
const chartColorMap = {
|
||||||
|
tickLineColor: 'rgba(217,217,217,0.5)',
|
||||||
|
axislabelColor: 'rgba(0, 0, 0, 0.4)'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tooltip = {
|
||||||
|
trigger: 'axis',
|
||||||
|
// axisPointer: {
|
||||||
|
// type: 'shadow'
|
||||||
|
// }
|
||||||
|
formatter(params: any) {
|
||||||
|
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
|
||||||
|
params.forEach((item: any) => {
|
||||||
|
result += `<span class="tooltip-item">
|
||||||
|
<span class="tooltip-item-name">
|
||||||
|
<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${item.color};"></span>
|
||||||
|
<span class="tooltip-title">${item.seriesName}</span>:
|
||||||
|
</span>
|
||||||
|
<span class="tooltip-value">${item.data.value}</span>
|
||||||
|
</span>`;
|
||||||
|
});
|
||||||
|
return `<div class="tooltip-wrapper">${result}</div>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const grid = {
|
||||||
|
left: 0,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20,
|
||||||
|
containLabel: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export const legend = {
|
||||||
|
itemWidth: 8,
|
||||||
|
itemHeight: 8
|
||||||
|
};
|
||||||
|
|
||||||
|
export const xAxis = {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: chartColorMap.tickLineColor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: chartColorMap.axislabelColor,
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const yAxis = {
|
||||||
|
// max: 100,
|
||||||
|
// min: 0,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: chartColorMap.axislabelColor,
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: 'value'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const title = {
|
||||||
|
show: true,
|
||||||
|
left: 'center',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
text: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const barItemConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
barMaxWidth: 20,
|
||||||
|
barMinWidth: 8,
|
||||||
|
// stack: 'total',
|
||||||
|
barGap: '30%',
|
||||||
|
barCategoryGap: '50%'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lineItemConfig = {
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol: false,
|
||||||
|
itemStyle: {},
|
||||||
|
lineStyle: {
|
||||||
|
width: 1.5,
|
||||||
|
opacity: 0.7
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import Chart from '@/components/echarts/chart';
|
||||||
|
import {
|
||||||
|
grid,
|
||||||
|
legend,
|
||||||
|
title as titleConfig,
|
||||||
|
tooltip,
|
||||||
|
xAxis,
|
||||||
|
yAxis
|
||||||
|
} from '@/components/echarts/config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { ChartProps } from './types';
|
||||||
|
|
||||||
|
const BarChart: React.FC<ChartProps> = (props) => {
|
||||||
|
const {
|
||||||
|
seriesData,
|
||||||
|
xAxisData,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
labelFormatter,
|
||||||
|
legendData,
|
||||||
|
title
|
||||||
|
} = props;
|
||||||
|
const [dataOptions, setDataOptions] = useState({});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
title: titleConfig,
|
||||||
|
grid,
|
||||||
|
tooltip: {
|
||||||
|
...tooltip
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...xAxis,
|
||||||
|
axisLabel: {
|
||||||
|
...xAxis.axisLabel,
|
||||||
|
formatter: labelFormatter
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis,
|
||||||
|
legend: {
|
||||||
|
...legend,
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = _.map(seriesData, (item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 20,
|
||||||
|
stack: 'Ad',
|
||||||
|
barGap: '30%',
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: item.color
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
const optionsConfig = {
|
||||||
|
...options,
|
||||||
|
title: {
|
||||||
|
...options.title,
|
||||||
|
left: 'start',
|
||||||
|
text: title
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
...options.yAxis,
|
||||||
|
inverse: true,
|
||||||
|
type: 'category',
|
||||||
|
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...options.xAxis,
|
||||||
|
boundaryGap: true,
|
||||||
|
type: 'value',
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: data
|
||||||
|
};
|
||||||
|
console.log('optionsConfig========h=', optionsConfig);
|
||||||
|
setDataOptions(optionsConfig);
|
||||||
|
}, [seriesData, xAxisData, title]);
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
height={height}
|
||||||
|
options={dataOptions}
|
||||||
|
width={width || '100%'}
|
||||||
|
></Chart>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BarChart;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import type {
|
||||||
|
// 系列类型的定义后缀都为 SeriesOption
|
||||||
|
BarSeriesOption,
|
||||||
|
LineSeriesOption
|
||||||
|
} from 'echarts/charts';
|
||||||
|
import { BarChart, LineChart } from 'echarts/charts';
|
||||||
|
import type {
|
||||||
|
DatasetComponentOption,
|
||||||
|
GridComponentOption,
|
||||||
|
// 组件类型的定义后缀都为 ComponentOption
|
||||||
|
TitleComponentOption,
|
||||||
|
TooltipComponentOption
|
||||||
|
} from 'echarts/components';
|
||||||
|
import {
|
||||||
|
// 数据集组件
|
||||||
|
DatasetComponent,
|
||||||
|
GridComponent,
|
||||||
|
LegendComponent,
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
// 内置数据转换器组件 (filter, sort)
|
||||||
|
TransformComponent
|
||||||
|
} from 'echarts/components';
|
||||||
|
import type { ComposeOption } from 'echarts/core';
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
|
||||||
|
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
|
||||||
|
type ECOption = ComposeOption<
|
||||||
|
| BarSeriesOption
|
||||||
|
| LineSeriesOption
|
||||||
|
| TitleComponentOption
|
||||||
|
| TooltipComponentOption
|
||||||
|
| GridComponentOption
|
||||||
|
| DatasetComponentOption
|
||||||
|
>;
|
||||||
|
|
||||||
|
// 注册必须的组件
|
||||||
|
echarts.use([
|
||||||
|
LegendComponent,
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
DatasetComponent,
|
||||||
|
TransformComponent,
|
||||||
|
BarChart,
|
||||||
|
LineChart,
|
||||||
|
LabelLayout,
|
||||||
|
UniversalTransition,
|
||||||
|
CanvasRenderer
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type { ECOption };
|
||||||
|
|
||||||
|
export default echarts;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import Chart from '@/components/echarts/chart';
|
||||||
|
import {
|
||||||
|
grid,
|
||||||
|
legend,
|
||||||
|
lineItemConfig,
|
||||||
|
tooltip,
|
||||||
|
xAxis,
|
||||||
|
yAxis
|
||||||
|
} from '@/components/echarts/config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { ChartProps } from './types';
|
||||||
|
|
||||||
|
const LineChart: React.FC<ChartProps> = (props) => {
|
||||||
|
const {
|
||||||
|
seriesData,
|
||||||
|
xAxisData,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
labelFormatter,
|
||||||
|
legendData,
|
||||||
|
smooth,
|
||||||
|
title
|
||||||
|
} = props;
|
||||||
|
const [dataOptions, setDataOptions] = useState({});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
title: {
|
||||||
|
text: ''
|
||||||
|
},
|
||||||
|
grid,
|
||||||
|
tooltip: {
|
||||||
|
...tooltip
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...xAxis,
|
||||||
|
axisLabel: {
|
||||||
|
...xAxis.axisLabel,
|
||||||
|
formatter: labelFormatter
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis,
|
||||||
|
legend: {
|
||||||
|
...legend,
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = _.map(seriesData, (item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...lineItemConfig,
|
||||||
|
smooth: smooth,
|
||||||
|
itemStyle: {
|
||||||
|
...lineItemConfig.itemStyle,
|
||||||
|
color: item.color
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
...lineItemConfig.lineStyle,
|
||||||
|
color: item.color
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const optionsConfig = {
|
||||||
|
...options,
|
||||||
|
title: {
|
||||||
|
show: true,
|
||||||
|
left: 'center',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
text: title
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
...options.yAxis
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...options.xAxis,
|
||||||
|
data: xAxisData
|
||||||
|
},
|
||||||
|
series: data
|
||||||
|
};
|
||||||
|
console.log('optionsConfig========line=', optionsConfig);
|
||||||
|
setDataOptions(optionsConfig);
|
||||||
|
}, [seriesData, xAxisData, title]);
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
height={height}
|
||||||
|
options={dataOptions}
|
||||||
|
width={width || '100%'}
|
||||||
|
></Chart>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LineChart;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export interface ChartProps {
|
||||||
|
seriesData: any[];
|
||||||
|
xAxisData: string[];
|
||||||
|
legendData?: string[];
|
||||||
|
labelFormatter?: (val?: any) => string;
|
||||||
|
height: string | number;
|
||||||
|
width?: string | number;
|
||||||
|
title?: string;
|
||||||
|
smooth?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AreaChartItemProps {
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
areaStyle: any;
|
||||||
|
data: { time: string; value: number }[];
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
||||||
|
import useContainerScroll from '@/hooks/use-container-scorll';
|
||||||
import Convert from 'ansi-to-html';
|
import Convert from 'ansi-to-html';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import hasAnsi from 'has-ansi';
|
import hasAnsi from 'has-ansi';
|
||||||
@@ -17,9 +18,18 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
|||||||
const [logsContent, setLogsContent] = useState(content);
|
const [logsContent, setLogsContent] = useState(content);
|
||||||
const { setChunkRequest } = useSetChunkRequest();
|
const { setChunkRequest } = useSetChunkRequest();
|
||||||
const chunkRequedtRef = useRef<any>(null);
|
const chunkRequedtRef = useRef<any>(null);
|
||||||
|
const scroller = useRef<any>(null);
|
||||||
|
const { updateScrollerPosition, handleContentWheel } = useContainerScroll(
|
||||||
|
scroller,
|
||||||
|
{ toBottom: true }
|
||||||
|
);
|
||||||
|
|
||||||
const convert = new Convert();
|
const convert = new Convert();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateScrollerPosition();
|
||||||
|
}, [logsContent]);
|
||||||
|
|
||||||
const updateContent = (newVal: string) => {
|
const updateContent = (newVal: string) => {
|
||||||
if (hasAnsi(newVal)) {
|
if (hasAnsi(newVal)) {
|
||||||
const htmlStr = `${convert.toHtml(newVal)}`;
|
const htmlStr = `${convert.toHtml(newVal)}`;
|
||||||
@@ -51,7 +61,12 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="logs-viewer-wrap-w2">
|
<div className="logs-viewer-wrap-w2">
|
||||||
<div className="wrap" style={{ height: height }}>
|
<div
|
||||||
|
className="wrap"
|
||||||
|
style={{ height: height }}
|
||||||
|
ref={scroller}
|
||||||
|
onWheel={handleContentWheel}
|
||||||
|
>
|
||||||
<div className={classNames('content', { 'line-break': nowrap })}>
|
<div className={classNames('content', { 'line-break': nowrap })}>
|
||||||
<div className="text">{logsContent}</div>
|
<div className="text">{logsContent}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const PageTools: React.FC<PageToolsProps> = (props) => {
|
|||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
marginBottom = 0,
|
marginBottom = 0,
|
||||||
marginTop = 60,
|
marginTop = 30,
|
||||||
style: pageStyle
|
style: pageStyle
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const RenderProgress = memo(
|
|||||||
return 'var(--ant-color-primary)';
|
return 'var(--ant-color-primary)';
|
||||||
}
|
}
|
||||||
if (percent <= 50) {
|
if (percent <= 50) {
|
||||||
return 'var(--ant-color-primary)';
|
return 'var(--color-progress-green)';
|
||||||
}
|
}
|
||||||
if (percent <= 80) {
|
if (percent <= 80) {
|
||||||
return 'var(--ant-color-warning)';
|
return 'var(--ant-color-warning)';
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ const SealTable: React.FC<SealTableProps> = (props) => {
|
|||||||
title={title}
|
title={title}
|
||||||
style={headerStyle}
|
style={headerStyle}
|
||||||
firstCell={i === 0}
|
firstCell={i === 0}
|
||||||
|
align={align}
|
||||||
lastCell={i === props.children.length - 1}
|
lastCell={i === props.children.length - 1}
|
||||||
></TableHeader>
|
></TableHeader>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -48,11 +48,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-selected {
|
&-selected {
|
||||||
background-color: var(--ant-table-row-selected-bg);
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--ant-table-row-selected-hover-bg);
|
background-color: var(--ant-table-row-hover-bg);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ export const StatusColorMap: Record<StatusType, { text: string; bg: string }> =
|
|||||||
bg: `var(--ant-blue-1)`
|
bg: `var(--ant-blue-1)`
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
text: `var(--ant-color-primary)`,
|
text: `var(--color-progress-green)`,
|
||||||
bg: `var(--ant-green-1)`
|
bg: `var(--ant-green-1)`
|
||||||
},
|
},
|
||||||
inactive: {
|
inactive: {
|
||||||
|
|||||||
+37
-1
@@ -40,6 +40,8 @@ html {
|
|||||||
--color-chart-green: rgb(84, 204, 152, 80%);
|
--color-chart-green: rgb(84, 204, 152, 80%);
|
||||||
--color-chart-glod: rgba(250, 173, 20, 80%);
|
--color-chart-glod: rgba(250, 173, 20, 80%);
|
||||||
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
|
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
|
||||||
|
--color-progress-green: rgba(84, 204, 152, 100%);
|
||||||
|
--color-border-1: rgba(217, 217, 217, 100%);
|
||||||
// ======== input ============
|
// ======== input ============
|
||||||
--ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%);
|
--ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%);
|
||||||
--ant-input-active-border-color: #007bff;
|
--ant-input-active-border-color: #007bff;
|
||||||
@@ -77,7 +79,7 @@ html {
|
|||||||
--ant-color-text: #000;
|
--ant-color-text: #000;
|
||||||
--ant-color-error: #ff4d4f;
|
--ant-color-error: #ff4d4f;
|
||||||
--ant-color-bg-mask: rgba(0, 0, 0, 35%);
|
--ant-color-bg-mask: rgba(0, 0, 0, 35%);
|
||||||
--ant-color-border: rgb(217 217 217 / 50%);
|
--ant-color-border: rgb(217 217 217 / 100%);
|
||||||
|
|
||||||
&.ant-popover {
|
&.ant-popover {
|
||||||
--ant-popover-inner-padding: 26px;
|
--ant-popover-inner-padding: 26px;
|
||||||
@@ -131,6 +133,10 @@ body * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
// .ant-slider:hover .ant-slider-handle::after {
|
||||||
|
// box-shadow: 0 0 0 var(--ant-slider-handle-line-width)
|
||||||
|
// var(--ant-slider-handle-color);
|
||||||
|
// }
|
||||||
.ant-modal-confirm-title {
|
.ant-modal-confirm-title {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -406,3 +412,33 @@ body {
|
|||||||
);
|
);
|
||||||
background-size: 400% 100%;
|
background-size: 400% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
background-color: rgba(255, 255, 255, 80%);
|
||||||
|
min-width: 100px;
|
||||||
|
|
||||||
|
.tooltip-x-name {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-item {
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.tooltip-item-title {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-value {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
export default function useContainerScroll(
|
||||||
|
container: any,
|
||||||
|
options?: { toBottom?: boolean }
|
||||||
|
) {
|
||||||
|
const isWheeled = useRef(false);
|
||||||
|
const scroller = useRef(container);
|
||||||
|
const optionsRef = useRef(options);
|
||||||
|
const toBottomFlag = useRef(options?.toBottom);
|
||||||
|
const handleContentWheel = (e: any) => {
|
||||||
|
isWheeled.current = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollerRun = () => {
|
||||||
|
const scrollerContainer = scroller.current?.current || {};
|
||||||
|
const { scrollHeight, clientHeight, scrollTop } = scrollerContainer;
|
||||||
|
if (
|
||||||
|
optionsRef.current?.toBottom &&
|
||||||
|
toBottomFlag.current &&
|
||||||
|
scrollHeight > clientHeight + scrollTop
|
||||||
|
) {
|
||||||
|
scroller.current.current.scrollTop = scrollHeight;
|
||||||
|
toBottomFlag.current = false;
|
||||||
|
isWheeled.current = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!isWheeled.current &&
|
||||||
|
scrollHeight > clientHeight + scrollTop &&
|
||||||
|
scroller.current?.current
|
||||||
|
) {
|
||||||
|
scroller.current.current.scrollTop += 5;
|
||||||
|
window.requestAnimationFrame(scrollerRun);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateScrollerPosition = () => {
|
||||||
|
if (!isWheeled.current) {
|
||||||
|
window.requestAnimationFrame(scrollerRun);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleContentWheel,
|
||||||
|
updateScrollerPosition,
|
||||||
|
scroller
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// This file is generated by Umi automatically
|
// This file is generated by Umi automatically
|
||||||
// DO NOT CHANGE IT MANUALLY!
|
// DO NOT CHANGE IT MANUALLY!
|
||||||
|
import GpustackLogo from '@/assets/images/gpustack-logo.png';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const LogoIcon: React.FC = () => {
|
const LogoIcon: React.FC = () => {
|
||||||
return (
|
return <img src={GpustackLogo} alt="logo" style={{ height: 24 }} />;
|
||||||
<img
|
|
||||||
src="https://www.seal.io/Public/Uploads/uploadfile/images/20230825/a1logo1244.svg"
|
|
||||||
alt="logo"
|
|
||||||
style={{ height: 24 }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LogoIcon;
|
export default LogoIcon;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export default {
|
|||||||
'dashboard.title': 'Dashboard',
|
'dashboard.title': 'Dashboard',
|
||||||
'dashboard.workers': 'Workers',
|
'dashboard.workers': 'Workers',
|
||||||
'dashboard.models': 'Models',
|
'dashboard.models': 'Models',
|
||||||
'dashboard.totalgpus': 'Total GPUs',
|
'dashboard.totalgpus': 'GPUs',
|
||||||
'dashboard.allocategpus': 'Allocated GPUs',
|
'dashboard.allocategpus': 'Allocated GPUs',
|
||||||
'dashboard.instances': 'Instances',
|
'dashboard.instances': 'Instances',
|
||||||
'dashboard.systemload': 'System Load',
|
'dashboard.systemload': 'System Load',
|
||||||
@@ -21,5 +21,5 @@ export default {
|
|||||||
'dashboard.activeModels': 'Active Models',
|
'dashboard.activeModels': 'Active Models',
|
||||||
'dashboard.runninginstances': 'Running Instances',
|
'dashboard.runninginstances': 'Running Instances',
|
||||||
'dashboard.activeModels.name': 'Model Name',
|
'dashboard.activeModels.name': 'Model Name',
|
||||||
'dashboard.allocatevram': 'Allocated VRAM'
|
'dashboard.allocatevram': 'Allocated VRAM / RAM'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export default {
|
export default {
|
||||||
'dashboard.title': '概览',
|
'dashboard.title': '概览',
|
||||||
'dashboard.workers': '节点',
|
'dashboard.workers': 'Workers',
|
||||||
'dashboard.models': '模型',
|
'dashboard.models': '模型',
|
||||||
'dashboard.totalgpus': '总 GPU 数量',
|
'dashboard.totalgpus': 'GPUs',
|
||||||
'dashboard.allocategpus': '已分配 GPU 数量',
|
'dashboard.allocategpus': '已分配 GPU 数量',
|
||||||
'dashboard.instances': '实例',
|
'dashboard.instances': '实例',
|
||||||
'dashboard.systemload': '系统负载',
|
'dashboard.systemload': '系统负载',
|
||||||
@@ -21,5 +21,5 @@ export default {
|
|||||||
'dashboard.activeModels': '活跃模型',
|
'dashboard.activeModels': '活跃模型',
|
||||||
'dashboard.activeModels.name': '模型名称',
|
'dashboard.activeModels.name': '模型名称',
|
||||||
'dashboard.runninginstances': '运行实例',
|
'dashboard.runninginstances': '运行实例',
|
||||||
'dashboard.allocatevram': '已分配显存'
|
'dashboard.allocatevram': '已分配显存 / 内存'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ const Models: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'apikeys.table.name' })}
|
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
dataIndex="name"
|
dataIndex="name"
|
||||||
key="name"
|
key="name"
|
||||||
width={400}
|
width={400}
|
||||||
@@ -249,6 +249,14 @@ const Models: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Column
|
||||||
|
title={intl.formatMessage({ id: 'apikeys.form.expiretime' })}
|
||||||
|
dataIndex="expires_at"
|
||||||
|
key="expiration"
|
||||||
|
render={(text, record) => {
|
||||||
|
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'common.table.createTime' })}
|
title={intl.formatMessage({ id: 'common.table.createTime' })}
|
||||||
dataIndex="created_at"
|
dataIndex="created_at"
|
||||||
@@ -261,14 +269,6 @@ const Models: React.FC = () => {
|
|||||||
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
|
||||||
title={intl.formatMessage({ id: 'apikeys.form.expiretime' })}
|
|
||||||
dataIndex="expires_at"
|
|
||||||
key="expiration"
|
|
||||||
render={(text, record) => {
|
|
||||||
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'common.table.operation' })}
|
title={intl.formatMessage({ id: 'common.table.operation' })}
|
||||||
key="operation"
|
key="operation"
|
||||||
@@ -281,7 +281,6 @@ const Models: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => handleDelete(record)}
|
onClick={() => handleDelete(record)}
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
|
||||||
danger
|
danger
|
||||||
icon={<DeleteOutlined></DeleteOutlined>}
|
icon={<DeleteOutlined></DeleteOutlined>}
|
||||||
></Button>
|
></Button>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const ActiveTable = () => {
|
|||||||
const data = useContext(DashboardContext).active_models || [];
|
const data = useContext(DashboardContext).active_models || [];
|
||||||
const modelColumns = [
|
const modelColumns = [
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.activeModels.name' }),
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name'
|
key: 'name'
|
||||||
},
|
},
|
||||||
@@ -86,9 +86,16 @@ const ActiveTable = () => {
|
|||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.allocatevram' }),
|
title: intl.formatMessage({ id: 'dashboard.allocatevram' }),
|
||||||
dataIndex: 'allocate_gpu_memory_utilization',
|
dataIndex: 'resource_claim.memory',
|
||||||
key: 'gpu_memory_utilization',
|
key: 'gpu_memory',
|
||||||
render: (text: any, record: any) => <span>{convertFileSize(text)}</span>
|
render: (text: any, record: any) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{convertFileSize(record.resource_claim?.gpu_memory || 0)} /{' '}
|
||||||
|
{convertFileSize(record.resource_claim?.memory || 0)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.runninginstances' }),
|
title: intl.formatMessage({ id: 'dashboard.runninginstances' }),
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
:local(.card-body) {
|
:local(.card-body) {
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
:global(.ant-card-body) {
|
:global(.ant-card-body) {
|
||||||
height: 110px;
|
height: 110px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
// box-shadow: var(--box-shadow-base);
|
|
||||||
border-radius: var(--ant-border-radius-lg);
|
border-radius: var(--ant-border-radius-lg);
|
||||||
// border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--color-border-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const renderCardItem = (data: {
|
|||||||
const { label, value, bgColor } = data;
|
const { label, value, bgColor } = data;
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
bordered={true}
|
bordered={false}
|
||||||
style={{ background: bgColor }}
|
style={{ background: bgColor }}
|
||||||
className={styles['card-body']}
|
className={styles['card-body']}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,89 +1,174 @@
|
|||||||
import LineChart from '@/components/charts/line-chart';
|
import Chart from '@/components/echarts/chart';
|
||||||
import { getLocale, useIntl } from '@umijs/max';
|
import { getLocale, useIntl } from '@umijs/max';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { DashboardContext } from '../config/dashboard-context';
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
|
|
||||||
|
const chartColorMap = {
|
||||||
|
tickLineColor: 'rgba(217,217,217,0.5)',
|
||||||
|
axislabelColor: 'rgba(0, 0, 0, 0.4)'
|
||||||
|
};
|
||||||
|
|
||||||
const TypeKeyMap = {
|
const TypeKeyMap = {
|
||||||
cpu: {
|
cpu: {
|
||||||
label: 'CPU',
|
label: 'CPU',
|
||||||
type: 'CPU',
|
type: 'CPU',
|
||||||
intl: false,
|
intl: false,
|
||||||
color:
|
color: 'rgba(250, 173, 20,.8)'
|
||||||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
|
|
||||||
},
|
},
|
||||||
memory: {
|
memory: {
|
||||||
label: 'dashboard.memory',
|
label: 'dashboard.memory',
|
||||||
type: 'Memory',
|
type: 'Memory',
|
||||||
intl: true,
|
intl: true,
|
||||||
color:
|
color: 'rgba(114, 46, 209,.8)'
|
||||||
'linear-gradient(90deg,rgba(249, 248, 113,.8) 0%,rgba(255, 199, 92,0.7) 100%)'
|
|
||||||
},
|
},
|
||||||
gpu: {
|
gpu: {
|
||||||
label: 'GPU',
|
label: 'GPU',
|
||||||
type: 'GPU',
|
type: 'GPU',
|
||||||
intl: false,
|
intl: false,
|
||||||
color: 'rgba(84, 204, 152,0.8)'
|
color: 'rgba(84, 204, 152,.8)'
|
||||||
},
|
},
|
||||||
gpu_memory: {
|
gpu_memory: {
|
||||||
label: 'dashboard.vram',
|
label: 'dashboard.vram',
|
||||||
type: 'VRAM',
|
type: 'VRAM',
|
||||||
intl: true,
|
intl: true,
|
||||||
color:
|
color: 'rgba(255, 107, 179, 80%)'
|
||||||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: ''
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
itemWidth: 8,
|
||||||
|
itemHeight: 8,
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 0,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter(params: any) {
|
||||||
|
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
|
||||||
|
params.forEach((item: any) => {
|
||||||
|
result += `<span class="tooltip-item">
|
||||||
|
<span class="tooltip-item-name">
|
||||||
|
<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${item.color};"></span>
|
||||||
|
<span class="tooltip-title">${item.seriesName}</span>:
|
||||||
|
</span>
|
||||||
|
<span class="tooltip-value">${item.data.value}</span>
|
||||||
|
</span>`;
|
||||||
|
});
|
||||||
|
return `<div class="tooltip-wrapper">${result}</div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: true,
|
||||||
|
axisTick: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: chartColorMap.tickLineColor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: chartColorMap.axislabelColor,
|
||||||
|
// fontFamily: 'unset',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
max: 100,
|
||||||
|
min: 0,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: chartColorMap.axislabelColor,
|
||||||
|
// fontFamily: 'unset',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
const UtilizationOvertime: React.FC = () => {
|
const UtilizationOvertime: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
|
const [dataOptions, setDataOptions] = useState<any>(option);
|
||||||
const data = useContext(DashboardContext)?.system_load?.history || {};
|
const data = useContext(DashboardContext)?.system_load?.history || {};
|
||||||
const [result, setResult] = useState<
|
|
||||||
{ time: string; value: number; type: string }[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory'];
|
const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory'];
|
||||||
const sliderConfig = {
|
|
||||||
y: false,
|
|
||||||
x: {
|
|
||||||
style: {
|
|
||||||
selectionFill: 'rgb(84, 204, 152)',
|
|
||||||
selectionFillOpacity: 0.1,
|
|
||||||
handleIconFill: 'rgb(84, 204, 152)',
|
|
||||||
handleIconFillOpacity: 0.15,
|
|
||||||
handleIconStrokeOpacity: 0,
|
|
||||||
sparklineType: 'line',
|
|
||||||
sparkline: true
|
|
||||||
},
|
|
||||||
sparkline: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const labelFormatter = (value: any) => {
|
const labelFormatter = (value: any) => {
|
||||||
return `${value}%`;
|
return `${value}%`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateData = useCallback(() => {
|
const generateData = useCallback(() => {
|
||||||
const list: { value: number; time: string; type: string }[] = [];
|
const legendData: string[] = [];
|
||||||
_.each(typeList, (type: any) => {
|
const xAxisData: string[] = [];
|
||||||
const dataList = _.map(_.get(data, type, []), (item: any) => {
|
let list: { value: number; time: string; type: string }[] = [];
|
||||||
const value = _.round(_.get(item, 'value', 0), 1);
|
|
||||||
const time = dayjs(item.timestamp * 1000).format('HH:mm:ss');
|
list = _.map(typeList, (item: string) => {
|
||||||
const itemtype = _.get(TypeKeyMap, [type, 'intl'], false)
|
const itemConfig = _.get(TypeKeyMap, item, {});
|
||||||
? intl.formatMessage({
|
console.log('itemConfig', itemConfig);
|
||||||
id: _.get(TypeKeyMap, [type, 'label'], '')
|
const name = itemConfig.intl
|
||||||
})
|
? intl.formatMessage({ id: itemConfig.label })
|
||||||
: _.get(TypeKeyMap, [type, 'label'], '');
|
: itemConfig.label;
|
||||||
return {
|
legendData.push(name);
|
||||||
value,
|
const itemDataList = _.get(data, item, []);
|
||||||
time,
|
return {
|
||||||
type: itemtype,
|
type: 'line',
|
||||||
color: 'red'
|
smooth: true,
|
||||||
};
|
showSymbol: false,
|
||||||
});
|
itemStyle: {
|
||||||
list.push(...dataList);
|
color: itemConfig.color
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: 1.5,
|
||||||
|
opacity: 0.7,
|
||||||
|
color: itemConfig.color
|
||||||
|
},
|
||||||
|
name: name,
|
||||||
|
data: _.map(itemDataList, (item: any) => {
|
||||||
|
xAxisData.push(dayjs(item.timestamp * 1000).format('HH:mm:ss'));
|
||||||
|
return {
|
||||||
|
time: dayjs(item.timestamp * 1000).format('HH:mm:ss'),
|
||||||
|
value: _.round(_.get(item, 'value', 0), 1)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setDataOptions({
|
||||||
|
...option,
|
||||||
|
legend: {
|
||||||
|
...option.legend,
|
||||||
|
data: legendData
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...option.xAxis,
|
||||||
|
data: _.uniq(xAxisData)
|
||||||
|
},
|
||||||
|
series: list
|
||||||
});
|
});
|
||||||
setResult(list);
|
|
||||||
}, [data, locale]);
|
}, [data, locale]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -92,13 +177,7 @@ const UtilizationOvertime: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LineChart
|
<Chart height={390} options={dataOptions} width="100%"></Chart>
|
||||||
data={result}
|
|
||||||
locale={locale}
|
|
||||||
height={390}
|
|
||||||
labelFormatter={labelFormatter}
|
|
||||||
slider={sliderConfig}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import CardWrapper from '@/components/card-wrapper';
|
import CardWrapper from '@/components/card-wrapper';
|
||||||
import AreaChart from '@/components/charts/area';
|
import BarChart from '@/components/echarts/bar-chart';
|
||||||
import ColumnBar from '@/components/charts/column-bar';
|
import HBar from '@/components/echarts/h-bar';
|
||||||
import HBar from '@/components/charts/h-bar';
|
import LineChart from '@/components/echarts/line-chart';
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
import breakpoints from '@/config/breakpoints';
|
import breakpoints from '@/config/breakpoints';
|
||||||
import useWindowResize from '@/hooks/use-window-resize';
|
import useWindowResize from '@/hooks/use-window-resize';
|
||||||
@@ -13,7 +13,14 @@ import _ from 'lodash';
|
|||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { DashboardContext } from '../config/dashboard-context';
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const baseColorMap = {
|
||||||
|
baseL2: 'rgba(13,171,219,0.8)',
|
||||||
|
baseL1: 'rgba(0,34,255,0.8)',
|
||||||
|
base: 'rgba(0,123,255,0.8)',
|
||||||
|
baseR1: 'rgba(0,255,233,0.8)',
|
||||||
|
baseR2: 'rgba(48,0,255,0.8)',
|
||||||
|
baseR3: 'rgba(85,167,255,0.8)'
|
||||||
|
};
|
||||||
const times = [
|
const times = [
|
||||||
'june 1',
|
'june 1',
|
||||||
'june 2',
|
'june 2',
|
||||||
@@ -94,15 +101,15 @@ const tokenUsage = TokensData.map((val, i) => {
|
|||||||
|
|
||||||
const getCurrentMonthDays = () => {
|
const getCurrentMonthDays = () => {
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
const firstDayOfMonth = now.startOf('month');
|
const daysInMonth = now.daysInMonth();
|
||||||
const dateRange = [];
|
const year = dayjs().year();
|
||||||
let currentDate = firstDayOfMonth;
|
const month = dayjs().month() + 1;
|
||||||
|
|
||||||
while (currentDate.isBefore(now) || currentDate.isSame(now, 'day')) {
|
const dates = [];
|
||||||
dateRange.push(currentDate.format('YYYY-MM-DD'));
|
for (let day = 1; day <= daysInMonth; day++) {
|
||||||
currentDate = currentDate.add(1, 'day');
|
dates.push(dayjs(`${year}-${month}-${day}`).format('YYYY-MM-DD'));
|
||||||
}
|
}
|
||||||
return dateRange;
|
return dates;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Usage = () => {
|
const Usage = () => {
|
||||||
@@ -110,42 +117,64 @@ const Usage = () => {
|
|||||||
const { size } = useWindowResize();
|
const { size } = useWindowResize();
|
||||||
const [paddingRight, setPaddingRight] = useState<string>('20px');
|
const [paddingRight, setPaddingRight] = useState<string>('20px');
|
||||||
const [requestData, setRequestData] = useState<
|
const [requestData, setRequestData] = useState<
|
||||||
{ time: string; value: number }[]
|
{
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
areaStyle: any;
|
||||||
|
data: { time: string; value: number }[];
|
||||||
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
|
const currentMonthDays = getCurrentMonthDays();
|
||||||
const [tokenData, setTokenData] = useState<{ time: string; value: number }[]>(
|
const [tokenData, setTokenData] = useState<{ time: string; value: number }[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const [userData, setUserData] = useState<{ name: string; value: number }[]>(
|
const [userData, setUserData] = useState<{ name: string; value: number }[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const [dateRange, setDateRange] = useState<string[]>(getCurrentMonthDays());
|
const [xAxisData, setXAxisData] = useState<string[]>(currentMonthDays);
|
||||||
|
const [dateRange, setDateRange] = useState<string[]>(currentMonthDays);
|
||||||
|
const [topUserList, setTopUseList] = useState<string[]>([]);
|
||||||
|
|
||||||
const data = useContext(DashboardContext)?.model_usage || {};
|
const data = useContext(DashboardContext)?.model_usage || {};
|
||||||
|
|
||||||
console.log('dateRange', dateRange);
|
|
||||||
const handleSelectDate = (dateString: string) => {};
|
const handleSelectDate = (dateString: string) => {};
|
||||||
|
|
||||||
const generateData = () => {
|
const generateData = () => {
|
||||||
const requestList: { time: string; value: number }[] = [];
|
const requestList: {
|
||||||
const tokenList: {
|
|
||||||
time: string;
|
|
||||||
value: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
color: string;
|
color: string;
|
||||||
}[] = [];
|
areaStyle: any;
|
||||||
const userList: {
|
data: { time: string; value: number }[];
|
||||||
name: string;
|
} = {
|
||||||
value: number;
|
name: 'API Request',
|
||||||
type: string;
|
areaStyle: {},
|
||||||
color: string;
|
color: baseColorMap.base,
|
||||||
}[] = [];
|
data: []
|
||||||
|
};
|
||||||
|
|
||||||
_.each(data.api_request_history, (item: any) => {
|
const completionData: any = {
|
||||||
requestList.push({
|
name: 'completion_token',
|
||||||
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
|
color: baseColorMap.base,
|
||||||
value: item.value
|
data: []
|
||||||
});
|
};
|
||||||
});
|
const prompData: any = {
|
||||||
|
name: 'prompt_token',
|
||||||
|
color: baseColorMap.baseR3,
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const topUserPrompt: any = {
|
||||||
|
name: 'prompt_token',
|
||||||
|
color: baseColorMap.baseR3,
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
const topUserCompletion: any = {
|
||||||
|
name: 'completion_token',
|
||||||
|
color: baseColorMap.base,
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
const users: string[] = [];
|
||||||
|
|
||||||
_.each(dateRange, (date: string) => {
|
_.each(dateRange, (date: string) => {
|
||||||
// tokens data
|
// tokens data
|
||||||
@@ -153,18 +182,14 @@ const Usage = () => {
|
|||||||
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
||||||
});
|
});
|
||||||
if (!item) {
|
if (!item) {
|
||||||
tokenList.push({
|
completionData.data.push({
|
||||||
time: date,
|
titme: date,
|
||||||
name: 'completion_token',
|
|
||||||
color: 'rgba(84, 204, 152,0.8)',
|
|
||||||
value: 0
|
value: 0
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
tokenList.push({
|
completionData.data.push({
|
||||||
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
|
value: item.value,
|
||||||
name: 'completion_token',
|
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD')
|
||||||
color: 'rgba(84, 204, 152,0.8)',
|
|
||||||
value: item.value
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,57 +197,58 @@ const Usage = () => {
|
|||||||
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
||||||
});
|
});
|
||||||
if (!promptItem) {
|
if (!promptItem) {
|
||||||
tokenList.push({
|
prompData.data.push({
|
||||||
time: date,
|
value: 0,
|
||||||
name: 'prompt_token',
|
time: date
|
||||||
color: 'rgba(0, 170, 173, 0.8)',
|
|
||||||
value: 0
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
tokenList.push({
|
prompData.data.push({
|
||||||
time: dayjs(promptItem.timestamp * 1000).format('YYYY-MM-DD'),
|
value: promptItem.value,
|
||||||
name: 'prompt_token',
|
time: dayjs(promptItem.timestamp * 1000).format('YYYY-MM-DD')
|
||||||
color: 'rgba(0, 170, 173, 0.8)',
|
|
||||||
value: promptItem.value
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// api request data
|
// ============== api request data =================
|
||||||
const requestItem = _.find(data.api_request_history, (item: any) => {
|
const requestItem = _.find(data.api_request_history, (item: any) => {
|
||||||
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!requestItem) {
|
if (!requestItem) {
|
||||||
requestList.push({
|
requestList.data.push({
|
||||||
time: date,
|
time: date,
|
||||||
value: 0
|
value: 0
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
requestList.push({
|
requestList.data.push({
|
||||||
time: dayjs(requestItem.timestamp * 1000).format('YYYY-MM-DD'),
|
time: dayjs(requestItem.timestamp * 1000).format('YYYY-MM-DD'),
|
||||||
value: requestItem.value
|
value: requestItem.value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========== top users ============
|
||||||
|
|
||||||
_.each(data.top_users, (item: any) => {
|
_.each(data.top_users, (item: any) => {
|
||||||
userList.push({
|
users.push(item.username);
|
||||||
|
topUserPrompt.data.push({
|
||||||
name: item.username,
|
name: item.username,
|
||||||
type: 'completion_token',
|
|
||||||
color: 'rgba(84, 204, 152,0.8)',
|
|
||||||
value: item.completion_token_count
|
|
||||||
});
|
|
||||||
userList.push({
|
|
||||||
name: item.username,
|
|
||||||
type: 'prompt_token',
|
|
||||||
color: 'rgba(0, 170, 173, 0.8)',
|
|
||||||
value: item.prompt_token_count
|
value: item.prompt_token_count
|
||||||
});
|
});
|
||||||
|
topUserCompletion.data.push({
|
||||||
|
name: item.username,
|
||||||
|
value: item.completion_token_count
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
setRequestData(requestList);
|
console.log('usage=============', {
|
||||||
setTokenData(tokenList);
|
requestList,
|
||||||
setUserData(userList);
|
xAxisData
|
||||||
|
});
|
||||||
|
|
||||||
|
setRequestData([requestList]);
|
||||||
|
setUserData([topUserCompletion, topUserPrompt]);
|
||||||
|
setTopUseList(users);
|
||||||
|
setTokenData([completionData, prompData]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const labelFormatter = (v: any) => {
|
const labelFormatter = (v: any) => {
|
||||||
@@ -270,28 +296,25 @@ const Usage = () => {
|
|||||||
<CardWrapper style={{ width: '100%' }}>
|
<CardWrapper style={{ width: '100%' }}>
|
||||||
<Row style={{ width: '100%' }}>
|
<Row style={{ width: '100%' }}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<AreaChart
|
<LineChart
|
||||||
title={intl.formatMessage({ id: 'dashboard.apirequest' })}
|
title={intl.formatMessage({ id: 'dashboard.apirequest' })}
|
||||||
data={requestData}
|
seriesData={requestData}
|
||||||
|
xAxisData={xAxisData}
|
||||||
xField="time"
|
xField="time"
|
||||||
yField="value"
|
yField="value"
|
||||||
height={360}
|
height={360}
|
||||||
|
color={baseColorMap.baseR3}
|
||||||
labelFormatter={labelFormatter}
|
labelFormatter={labelFormatter}
|
||||||
></AreaChart>
|
></LineChart>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<ColumnBar
|
<BarChart
|
||||||
title={intl.formatMessage({ id: 'dashboard.tokens' })}
|
title={intl.formatMessage({ id: 'dashboard.tokens' })}
|
||||||
data={tokenData}
|
seriesData={tokenData}
|
||||||
group={false}
|
xAxisData={xAxisData}
|
||||||
colorField="name"
|
|
||||||
stack={true}
|
|
||||||
xField="time"
|
|
||||||
legend={false}
|
|
||||||
yField="value"
|
|
||||||
height={360}
|
height={360}
|
||||||
labelFormatter={labelFormatter}
|
labelFormatter={labelFormatter}
|
||||||
></ColumnBar>
|
></BarChart>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
@@ -300,13 +323,8 @@ const Usage = () => {
|
|||||||
<CardWrapper>
|
<CardWrapper>
|
||||||
<HBar
|
<HBar
|
||||||
title={intl.formatMessage({ id: 'dashboard.topusers' })}
|
title={intl.formatMessage({ id: 'dashboard.topusers' })}
|
||||||
data={userData}
|
seriesData={userData}
|
||||||
stack={true}
|
xAxisData={topUserList}
|
||||||
legend={false}
|
|
||||||
showYAxis={false}
|
|
||||||
colorField="type"
|
|
||||||
xField="name"
|
|
||||||
yField="value"
|
|
||||||
height={360}
|
height={360}
|
||||||
></HBar>
|
></HBar>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
const handleOnSearchRepo = async (text: string) => {
|
const handleOnSearchRepo = async (text: string) => {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
q: `${text} gguf`,
|
q: `${text}`,
|
||||||
type: 'model'
|
type: 'model'
|
||||||
};
|
};
|
||||||
const res = await callHuggingfaceQuickSearch(params);
|
const res = await callHuggingfaceQuickSearch(params);
|
||||||
|
|||||||
@@ -162,9 +162,6 @@ const Models: React.FC = () => {
|
|||||||
_.each(list, (data: any) => {
|
_.each(list, (data: any) => {
|
||||||
updateChunkedList(data);
|
updateChunkedList(data);
|
||||||
});
|
});
|
||||||
if (!dataSource.length) {
|
|
||||||
handleFilter();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createModelsChunkRequest = () => {
|
const createModelsChunkRequest = () => {
|
||||||
@@ -367,7 +364,10 @@ const Models: React.FC = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<span style={{ paddingLeft: '22px' }}>
|
<span
|
||||||
|
style={{ paddingLeft: '68px' }}
|
||||||
|
className="flex justify-center"
|
||||||
|
>
|
||||||
{item.state && (
|
{item.state && (
|
||||||
<StatusTag
|
<StatusTag
|
||||||
download={
|
download={
|
||||||
@@ -478,7 +478,7 @@ const Models: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SealColumn
|
<SealColumn
|
||||||
title={intl.formatMessage({ id: 'models.table.name' })}
|
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
dataIndex="name"
|
dataIndex="name"
|
||||||
key="name"
|
key="name"
|
||||||
width={400}
|
width={400}
|
||||||
@@ -494,6 +494,7 @@ const Models: React.FC = () => {
|
|||||||
title={intl.formatMessage({ id: 'models.form.replicas' })}
|
title={intl.formatMessage({ id: 'models.form.replicas' })}
|
||||||
dataIndex="replicas"
|
dataIndex="replicas"
|
||||||
key="replicas"
|
key="replicas"
|
||||||
|
align="center"
|
||||||
span={4}
|
span={4}
|
||||||
/>
|
/>
|
||||||
<SealColumn
|
<SealColumn
|
||||||
|
|||||||
@@ -39,13 +39,6 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
|||||||
},
|
},
|
||||||
{ enabled: !disabled }
|
{ enabled: !disabled }
|
||||||
);
|
);
|
||||||
useHotkeys(
|
|
||||||
HotKeys.CREATE.join(','),
|
|
||||||
(e) => {
|
|
||||||
onNewMessage();
|
|
||||||
},
|
|
||||||
{ enabled: !disabled }
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-footer">
|
<div className="chat-footer">
|
||||||
@@ -59,7 +52,6 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
|||||||
onClick={onNewMessage}
|
onClick={onNewMessage}
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: 'playground.newMessage' })}
|
{intl.formatMessage({ id: 'playground.newMessage' })}
|
||||||
<span className="m-l-5 opct-7">CTRL+SHIFT+ N</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
icon={<DeleteOutlined></DeleteOutlined>}
|
icon={<DeleteOutlined></DeleteOutlined>}
|
||||||
|
|||||||
@@ -126,8 +126,13 @@ const MessageList: React.FC<MessageProps> = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
const headItem = _.get(messageList, '0');
|
setMessageList([
|
||||||
setMessageList(headItem ? [headItem] : []);
|
{
|
||||||
|
role: Roles.User,
|
||||||
|
content: '',
|
||||||
|
uid: messageId.current + 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleView = () => {
|
const handleView = () => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import SealInput from '@/components/seal-form/seal-input';
|
|||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import { INPUT_WIDTH } from '@/constants';
|
import { INPUT_WIDTH } from '@/constants';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Form, InputNumber, Slider } from 'antd';
|
import { Form, InputNumber, Slider } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { queryModelsList } from '../apis';
|
import { queryModelsList } from '../apis';
|
||||||
@@ -149,9 +149,6 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<h3 className="m-b-20 m-l-10 flex-between flex-center">
|
<h3 className="m-b-20 m-l-10 flex-between flex-center">
|
||||||
<span>{intl.formatMessage({ id: 'playground.parameters' })}</span>
|
<span>{intl.formatMessage({ id: 'playground.parameters' })}</span>
|
||||||
<Button size="small" onClick={handleResetParams}>
|
|
||||||
{intl.formatMessage({ id: 'common.button.reset' })}
|
|
||||||
</Button>
|
|
||||||
</h3>
|
</h3>
|
||||||
<Form.Item<ParamsSettingsFormProps>
|
<Form.Item<ParamsSettingsFormProps>
|
||||||
name="temperature"
|
name="temperature"
|
||||||
|
|||||||
+84
-63
@@ -1,4 +1,3 @@
|
|||||||
import CardWrapper from '@/components/card-wrapper';
|
|
||||||
import FormButtons from '@/components/form-buttons';
|
import FormButtons from '@/components/form-buttons';
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import { PasswordReg } from '@/config';
|
import { PasswordReg } from '@/config';
|
||||||
@@ -18,11 +17,12 @@ const Profile: React.FC = () => {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const newpasswordValue = Form.useWatch('new_password', form);
|
|
||||||
|
|
||||||
const handleOnFinish = async (values: any) => {
|
const handleOnFinish = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
await updatePassword(values);
|
await updatePassword({
|
||||||
|
new_password: values.new_password,
|
||||||
|
current_password: values.current_password
|
||||||
|
});
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
@@ -43,20 +43,14 @@ const Profile: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
extra={[]}
|
extra={[]}
|
||||||
>
|
>
|
||||||
<CardWrapper
|
<Form
|
||||||
style={{
|
style={{ width: '524px' }}
|
||||||
padding: 'var(--layout-content-inlinepadding)',
|
name="profileForm"
|
||||||
width: 'max-content'
|
form={form}
|
||||||
}}
|
onFinish={handleOnFinish}
|
||||||
|
onFinishFailed={handleOnFinishFailed}
|
||||||
>
|
>
|
||||||
<Form
|
{/* <Form.Item<ProfileProps>
|
||||||
style={{ width: '524px' }}
|
|
||||||
name="profileForm"
|
|
||||||
form={form}
|
|
||||||
onFinish={handleOnFinish}
|
|
||||||
onFinishFailed={handleOnFinishFailed}
|
|
||||||
>
|
|
||||||
{/* <Form.Item<ProfileProps>
|
|
||||||
name="username"
|
name="username"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@@ -76,53 +70,80 @@ const Profile: React.FC = () => {
|
|||||||
style={{ width: INPUT_WIDTH.default }}
|
style={{ width: INPUT_WIDTH.default }}
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Form.Item> */}
|
</Form.Item> */}
|
||||||
<Form.Item<ProfileProps>
|
<Form.Item<ProfileProps>
|
||||||
name="current_password"
|
name="current_password"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: intl.formatMessage(
|
message: intl.formatMessage(
|
||||||
{ id: 'common.form.rule.input' },
|
{ id: 'common.form.rule.input' },
|
||||||
{
|
{
|
||||||
name: intl.formatMessage({
|
name: intl.formatMessage({
|
||||||
id: 'users.form.currentpassword'
|
id: 'users.form.currentpassword'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Password
|
||||||
|
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
|
||||||
|
required
|
||||||
|
style={{ width: INPUT_WIDTH.default }}
|
||||||
|
></SealInput.Password>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<ProfileProps>
|
||||||
|
name="new_password"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
pattern: PasswordReg,
|
||||||
|
message: intl.formatMessage({
|
||||||
|
id: 'users.form.rule.password'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Password
|
||||||
|
label={intl.formatMessage({ id: 'users.form.newpassword' })}
|
||||||
|
required
|
||||||
|
style={{ width: INPUT_WIDTH.default }}
|
||||||
|
></SealInput.Password>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="confirm_password"
|
||||||
|
dependencies={['new_password']}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage({
|
||||||
|
id: 'users.password.confirm.empty'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value || getFieldValue('new_password') === value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(
|
||||||
|
intl.formatMessage({ id: 'users.password.confirm.error' })
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
]}
|
})
|
||||||
>
|
]}
|
||||||
<SealInput.Password
|
>
|
||||||
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
|
<SealInput.Password
|
||||||
required
|
label={intl.formatMessage({ id: 'users.password.confirm' })}
|
||||||
style={{ width: INPUT_WIDTH.default }}
|
/>
|
||||||
></SealInput.Password>
|
</Form.Item>
|
||||||
</Form.Item>
|
<FormButtons
|
||||||
<Form.Item<ProfileProps>
|
htmlType="submit"
|
||||||
name="new_password"
|
onCancel={handleCancel}
|
||||||
rules={[
|
showCancel={false}
|
||||||
{
|
></FormButtons>
|
||||||
required: true,
|
</Form>
|
||||||
pattern: PasswordReg,
|
|
||||||
message: intl.formatMessage({
|
|
||||||
id: 'users.form.rule.password'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealInput.Password
|
|
||||||
label={intl.formatMessage({ id: 'users.form.newpassword' })}
|
|
||||||
required
|
|
||||||
style={{ width: INPUT_WIDTH.default }}
|
|
||||||
></SealInput.Password>
|
|
||||||
</Form.Item>
|
|
||||||
<FormButtons
|
|
||||||
htmlType="submit"
|
|
||||||
onCancel={handleCancel}
|
|
||||||
showCancel={false}
|
|
||||||
></FormButtons>
|
|
||||||
</Form>
|
|
||||||
</CardWrapper>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ const Models: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'resources.table.hostname' })}
|
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
dataIndex="hostname"
|
dataIndex="hostname"
|
||||||
key="hostname"
|
key="hostname"
|
||||||
/>
|
/>
|
||||||
@@ -211,7 +211,7 @@ const Models: React.FC = () => {
|
|||||||
key="GPU"
|
key="GPU"
|
||||||
render={(text, record: ListItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<span className="flex-column">
|
<span className="flex-column flex-gap-2">
|
||||||
{record?.status?.gpu_devices.map((item, index) => {
|
{record?.status?.gpu_devices.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<span className="flex-center" key={index}>
|
<span className="flex-center" key={index}>
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ import SealInput from '@/components/seal-form/seal-input';
|
|||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import { PageAction, PasswordReg } from '@/config';
|
import { PageAction, PasswordReg } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import {
|
import { UserOutlined, UserSwitchOutlined } from '@ant-design/icons';
|
||||||
SyncOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
UserSwitchOutlined
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form, Modal, Select } from 'antd';
|
import { Form, Modal, Select } from 'antd';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@@ -32,14 +28,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const suffix = (
|
|
||||||
<SyncOutlined
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
color: '#1677ff'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const initFormValue = () => {
|
const initFormValue = () => {
|
||||||
if (action === PageAction.EDIT && open) {
|
if (action === PageAction.EDIT && open) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ const Models: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleModalOk = async (data: FormData) => {
|
const handleModalOk = async (data: FormData) => {
|
||||||
console.log('handleModalOk');
|
|
||||||
const params = {
|
const params = {
|
||||||
...data,
|
...data,
|
||||||
is_admin: data.is_admin === 'admin'
|
is_admin: data.is_admin === 'admin'
|
||||||
@@ -131,7 +130,7 @@ const Models: React.FC = () => {
|
|||||||
}
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
setOpenAddModal(false);
|
setOpenAddModal(false);
|
||||||
message.success('');
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setOpenAddModal(false);
|
setOpenAddModal(false);
|
||||||
}
|
}
|
||||||
@@ -261,22 +260,9 @@ const Models: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'users.table.username' })}
|
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
dataIndex="username"
|
dataIndex="username"
|
||||||
key="name"
|
key="name"
|
||||||
width={200}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title={intl.formatMessage({ id: 'common.table.createTime' })}
|
|
||||||
dataIndex="created_at"
|
|
||||||
key="createTime"
|
|
||||||
defaultSortOrder="descend"
|
|
||||||
sortOrder={sortOrder}
|
|
||||||
showSorterTooltip={false}
|
|
||||||
sorter={true}
|
|
||||||
render={(text, record) => {
|
|
||||||
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'users.table.role' })}
|
title={intl.formatMessage({ id: 'users.table.role' })}
|
||||||
@@ -301,9 +287,9 @@ const Models: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'common.table.updateTime' })}
|
title={intl.formatMessage({ id: 'common.table.createTime' })}
|
||||||
dataIndex="updated_at"
|
dataIndex="created_at"
|
||||||
key="updateTime"
|
key="createTime"
|
||||||
defaultSortOrder="descend"
|
defaultSortOrder="descend"
|
||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
|
|||||||
Reference in New Issue
Block a user