fix: ui issues
This commit is contained in:
+10
-5
@@ -8,7 +8,8 @@ export default {
|
||||
},
|
||||
Table: {
|
||||
cellFontSize: 12,
|
||||
rowSelectedHoverBg: '#e6f6ff'
|
||||
rowSelectedHoverBg: 'rgba(230, 230, 230, 0.88)',
|
||||
rowSelectedBg: 'rgb(244, 245, 244)'
|
||||
},
|
||||
Menu: {
|
||||
iconSize: 16,
|
||||
@@ -20,10 +21,14 @@ export default {
|
||||
},
|
||||
Slider: {
|
||||
handleSize: 8,
|
||||
handleSizeHover: 10
|
||||
// handleColor: '#007BFF'
|
||||
// trackBg: '#007BFF',
|
||||
// trackHoverBg: '#007BFF'
|
||||
handleSizeHover: 8,
|
||||
trackBg: 'rgba(0,0,0,0.15)',
|
||||
handleColor: 'rgba(0,0,0,0.2)',
|
||||
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: {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"classnames": "^2.5.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "^5.5.1",
|
||||
"has-ansi": "^5.0.1",
|
||||
"jotai": "^2.8.4",
|
||||
"localforage": "^1.10.0",
|
||||
|
||||
Generated
+21
-1
@@ -47,6 +47,9 @@ dependencies:
|
||||
dayjs:
|
||||
specifier: ^1.11.11
|
||||
version: 1.11.11
|
||||
echarts:
|
||||
specifier: ^5.5.1
|
||||
version: 5.5.1
|
||||
has-ansi:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
@@ -8238,6 +8241,13 @@ packages:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
dev: false
|
||||
@@ -16286,7 +16296,11 @@ packages:
|
||||
dev: false
|
||||
|
||||
/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
|
||||
|
||||
/tslib@2.6.2:
|
||||
@@ -17187,6 +17201,12 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
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:
|
||||
resolution: {integrity: sha512-G5NiNLKx2+QhhvZi2yV1jjVXY50otktxkseX2hG2N/eixohOUk0AY8ZpbAxNqS9oJS/NxItCsowupy2tsXxAMw==, tarball: https://registry.npmjs.org/zscroller/-/zscroller-0.4.8.tgz}
|
||||
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;
|
||||
}
|
||||
|
||||
.flex-gap-2 {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.opct-7 {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
.cardWrapper {
|
||||
border-radius: var(--border-radius-small);
|
||||
background-color: var(--color-white-1);
|
||||
// box-shadow: var(--box-shadow-base);
|
||||
box-shadow: none;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border: 1px solid var(--color-border-1);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ interface LineChartProps {
|
||||
title?: string;
|
||||
height?: number;
|
||||
data: any[];
|
||||
color?: string[];
|
||||
color?: string;
|
||||
xField?: string;
|
||||
yField?: string;
|
||||
locale?: string;
|
||||
@@ -47,7 +47,14 @@ const LineChart: React.FC<LineChartProps> = (props) => {
|
||||
}
|
||||
},
|
||||
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,
|
||||
|
||||
@@ -89,6 +89,7 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
||||
|
||||
style: {
|
||||
fill: (params: any) => {
|
||||
console.log('colbar=====', params);
|
||||
return params.color || 'rgba(84, 204, 152,0.8)';
|
||||
},
|
||||
// radiusTopLeft: 12,
|
||||
|
||||
@@ -55,19 +55,19 @@ const LineChart: React.FC<LineChartProps> = (props) => {
|
||||
// },
|
||||
style: {
|
||||
lineWidth: 1.5,
|
||||
opacity: 0.8
|
||||
opacity: 0.7
|
||||
// fill: (params: any) => {
|
||||
// return params.color;
|
||||
// }
|
||||
// stroke: (params: any) => {
|
||||
// return (
|
||||
// params.color ||
|
||||
// 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
|
||||
// );
|
||||
// console.log('line param=========', params.color);
|
||||
// return params.color;
|
||||
// }
|
||||
},
|
||||
legend: {
|
||||
color: {
|
||||
layout: { justifyContent: 'center' }
|
||||
},
|
||||
|
||||
size: {
|
||||
itemLabelFontSize: 14,
|
||||
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 useContainerScroll from '@/hooks/use-container-scorll';
|
||||
import Convert from 'ansi-to-html';
|
||||
import classNames from 'classnames';
|
||||
import hasAnsi from 'has-ansi';
|
||||
@@ -17,9 +18,18 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
||||
const [logsContent, setLogsContent] = useState(content);
|
||||
const { setChunkRequest } = useSetChunkRequest();
|
||||
const chunkRequedtRef = useRef<any>(null);
|
||||
const scroller = useRef<any>(null);
|
||||
const { updateScrollerPosition, handleContentWheel } = useContainerScroll(
|
||||
scroller,
|
||||
{ toBottom: true }
|
||||
);
|
||||
|
||||
const convert = new Convert();
|
||||
|
||||
useEffect(() => {
|
||||
updateScrollerPosition();
|
||||
}, [logsContent]);
|
||||
|
||||
const updateContent = (newVal: string) => {
|
||||
if (hasAnsi(newVal)) {
|
||||
const htmlStr = `${convert.toHtml(newVal)}`;
|
||||
@@ -51,7 +61,12 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
||||
|
||||
return (
|
||||
<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="text">{logsContent}</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ const PageTools: React.FC<PageToolsProps> = (props) => {
|
||||
left,
|
||||
right,
|
||||
marginBottom = 0,
|
||||
marginTop = 60,
|
||||
marginTop = 30,
|
||||
style: pageStyle
|
||||
} = props;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const RenderProgress = memo(
|
||||
return 'var(--ant-color-primary)';
|
||||
}
|
||||
if (percent <= 50) {
|
||||
return 'var(--ant-color-primary)';
|
||||
return 'var(--color-progress-green)';
|
||||
}
|
||||
if (percent <= 80) {
|
||||
return 'var(--ant-color-warning)';
|
||||
|
||||
@@ -104,6 +104,7 @@ const SealTable: React.FC<SealTableProps> = (props) => {
|
||||
title={title}
|
||||
style={headerStyle}
|
||||
firstCell={i === 0}
|
||||
align={align}
|
||||
lastCell={i === props.children.length - 1}
|
||||
></TableHeader>
|
||||
</Col>
|
||||
|
||||
@@ -48,11 +48,10 @@
|
||||
}
|
||||
|
||||
&-selected {
|
||||
background-color: var(--ant-table-row-selected-bg);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ant-table-row-selected-hover-bg);
|
||||
background-color: var(--ant-table-row-hover-bg);
|
||||
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)`
|
||||
},
|
||||
success: {
|
||||
text: `var(--ant-color-primary)`,
|
||||
text: `var(--color-progress-green)`,
|
||||
bg: `var(--ant-green-1)`
|
||||
},
|
||||
inactive: {
|
||||
|
||||
+37
-1
@@ -40,6 +40,8 @@ html {
|
||||
--color-chart-green: rgb(84, 204, 152, 80%);
|
||||
--color-chart-glod: rgba(250, 173, 20, 80%);
|
||||
--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 ============
|
||||
--ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%);
|
||||
--ant-input-active-border-color: #007bff;
|
||||
@@ -77,7 +79,7 @@ html {
|
||||
--ant-color-text: #000;
|
||||
--ant-color-error: #ff4d4f;
|
||||
--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-inner-padding: 26px;
|
||||
@@ -131,6 +133,10 @@ 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 {
|
||||
display: none;
|
||||
}
|
||||
@@ -406,3 +412,33 @@ body {
|
||||
);
|
||||
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
|
||||
// This file is generated by Umi automatically
|
||||
// DO NOT CHANGE IT MANUALLY!
|
||||
import GpustackLogo from '@/assets/images/gpustack-logo.png';
|
||||
import React from 'react';
|
||||
|
||||
const LogoIcon: React.FC = () => {
|
||||
return (
|
||||
<img
|
||||
src="https://www.seal.io/Public/Uploads/uploadfile/images/20230825/a1logo1244.svg"
|
||||
alt="logo"
|
||||
style={{ height: 24 }}
|
||||
/>
|
||||
);
|
||||
return <img src={GpustackLogo} alt="logo" style={{ height: 24 }} />;
|
||||
};
|
||||
|
||||
export default LogoIcon;
|
||||
|
||||
@@ -2,7 +2,7 @@ export default {
|
||||
'dashboard.title': 'Dashboard',
|
||||
'dashboard.workers': 'Workers',
|
||||
'dashboard.models': 'Models',
|
||||
'dashboard.totalgpus': 'Total GPUs',
|
||||
'dashboard.totalgpus': 'GPUs',
|
||||
'dashboard.allocategpus': 'Allocated GPUs',
|
||||
'dashboard.instances': 'Instances',
|
||||
'dashboard.systemload': 'System Load',
|
||||
@@ -21,5 +21,5 @@ export default {
|
||||
'dashboard.activeModels': 'Active Models',
|
||||
'dashboard.runninginstances': 'Running Instances',
|
||||
'dashboard.activeModels.name': 'Model Name',
|
||||
'dashboard.allocatevram': 'Allocated VRAM'
|
||||
'dashboard.allocatevram': 'Allocated VRAM / RAM'
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export default {
|
||||
'dashboard.title': '概览',
|
||||
'dashboard.workers': '节点',
|
||||
'dashboard.workers': 'Workers',
|
||||
'dashboard.models': '模型',
|
||||
'dashboard.totalgpus': '总 GPU 数量',
|
||||
'dashboard.totalgpus': 'GPUs',
|
||||
'dashboard.allocategpus': '已分配 GPU 数量',
|
||||
'dashboard.instances': '实例',
|
||||
'dashboard.systemload': '系统负载',
|
||||
@@ -21,5 +21,5 @@ export default {
|
||||
'dashboard.activeModels': '活跃模型',
|
||||
'dashboard.activeModels.name': '模型名称',
|
||||
'dashboard.runninginstances': '运行实例',
|
||||
'dashboard.allocatevram': '已分配显存'
|
||||
'dashboard.allocatevram': '已分配显存 / 内存'
|
||||
};
|
||||
|
||||
@@ -240,7 +240,7 @@ const Models: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Column
|
||||
title={intl.formatMessage({ id: 'apikeys.table.name' })}
|
||||
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||
dataIndex="name"
|
||||
key="name"
|
||||
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
|
||||
title={intl.formatMessage({ id: 'common.table.createTime' })}
|
||||
dataIndex="created_at"
|
||||
@@ -261,14 +269,6 @@ const Models: React.FC = () => {
|
||||
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
|
||||
title={intl.formatMessage({ id: 'common.table.operation' })}
|
||||
key="operation"
|
||||
@@ -281,7 +281,6 @@ const Models: React.FC = () => {
|
||||
<Button
|
||||
onClick={() => handleDelete(record)}
|
||||
size="small"
|
||||
type="primary"
|
||||
danger
|
||||
icon={<DeleteOutlined></DeleteOutlined>}
|
||||
></Button>
|
||||
|
||||
@@ -72,7 +72,7 @@ const ActiveTable = () => {
|
||||
const data = useContext(DashboardContext).active_models || [];
|
||||
const modelColumns = [
|
||||
{
|
||||
title: intl.formatMessage({ id: 'dashboard.activeModels.name' }),
|
||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
@@ -86,9 +86,16 @@ const ActiveTable = () => {
|
||||
// },
|
||||
{
|
||||
title: intl.formatMessage({ id: 'dashboard.allocatevram' }),
|
||||
dataIndex: 'allocate_gpu_memory_utilization',
|
||||
key: 'gpu_memory_utilization',
|
||||
render: (text: any, record: any) => <span>{convertFileSize(text)}</span>
|
||||
dataIndex: 'resource_claim.memory',
|
||||
key: 'gpu_memory',
|
||||
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' }),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
:local(.card-body) {
|
||||
box-shadow: none !important;
|
||||
|
||||
:global(.ant-card-body) {
|
||||
height: 110px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
// box-shadow: var(--box-shadow-base);
|
||||
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;
|
||||
return (
|
||||
<Card
|
||||
bordered={true}
|
||||
bordered={false}
|
||||
style={{ background: bgColor }}
|
||||
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 dayjs from 'dayjs';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { DashboardContext } from '../config/dashboard-context';
|
||||
|
||||
const chartColorMap = {
|
||||
tickLineColor: 'rgba(217,217,217,0.5)',
|
||||
axislabelColor: 'rgba(0, 0, 0, 0.4)'
|
||||
};
|
||||
|
||||
const TypeKeyMap = {
|
||||
cpu: {
|
||||
label: 'CPU',
|
||||
type: 'CPU',
|
||||
intl: false,
|
||||
color:
|
||||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
|
||||
color: 'rgba(250, 173, 20,.8)'
|
||||
},
|
||||
memory: {
|
||||
label: 'dashboard.memory',
|
||||
type: 'Memory',
|
||||
intl: true,
|
||||
color:
|
||||
'linear-gradient(90deg,rgba(249, 248, 113,.8) 0%,rgba(255, 199, 92,0.7) 100%)'
|
||||
color: 'rgba(114, 46, 209,.8)'
|
||||
},
|
||||
gpu: {
|
||||
label: 'GPU',
|
||||
type: 'GPU',
|
||||
intl: false,
|
||||
color: 'rgba(84, 204, 152,0.8)'
|
||||
color: 'rgba(84, 204, 152,.8)'
|
||||
},
|
||||
gpu_memory: {
|
||||
label: 'dashboard.vram',
|
||||
type: 'VRAM',
|
||||
intl: true,
|
||||
color:
|
||||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
|
||||
color: 'rgba(255, 107, 179, 80%)'
|
||||
}
|
||||
};
|
||||
|
||||
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 intl = useIntl();
|
||||
const locale = getLocale();
|
||||
const [dataOptions, setDataOptions] = useState<any>(option);
|
||||
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 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) => {
|
||||
return `${value}%`;
|
||||
};
|
||||
|
||||
const generateData = useCallback(() => {
|
||||
const list: { value: number; time: string; type: string }[] = [];
|
||||
_.each(typeList, (type: any) => {
|
||||
const dataList = _.map(_.get(data, type, []), (item: any) => {
|
||||
const value = _.round(_.get(item, 'value', 0), 1);
|
||||
const time = dayjs(item.timestamp * 1000).format('HH:mm:ss');
|
||||
const itemtype = _.get(TypeKeyMap, [type, 'intl'], false)
|
||||
? intl.formatMessage({
|
||||
id: _.get(TypeKeyMap, [type, 'label'], '')
|
||||
})
|
||||
: _.get(TypeKeyMap, [type, 'label'], '');
|
||||
return {
|
||||
value,
|
||||
time,
|
||||
type: itemtype,
|
||||
color: 'red'
|
||||
};
|
||||
});
|
||||
list.push(...dataList);
|
||||
const legendData: string[] = [];
|
||||
const xAxisData: string[] = [];
|
||||
let list: { value: number; time: string; type: string }[] = [];
|
||||
|
||||
list = _.map(typeList, (item: string) => {
|
||||
const itemConfig = _.get(TypeKeyMap, item, {});
|
||||
console.log('itemConfig', itemConfig);
|
||||
const name = itemConfig.intl
|
||||
? intl.formatMessage({ id: itemConfig.label })
|
||||
: itemConfig.label;
|
||||
legendData.push(name);
|
||||
const itemDataList = _.get(data, item, []);
|
||||
return {
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
itemStyle: {
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -92,13 +177,7 @@ const UtilizationOvertime: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LineChart
|
||||
data={result}
|
||||
locale={locale}
|
||||
height={390}
|
||||
labelFormatter={labelFormatter}
|
||||
slider={sliderConfig}
|
||||
/>
|
||||
<Chart height={390} options={dataOptions} width="100%"></Chart>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import CardWrapper from '@/components/card-wrapper';
|
||||
import AreaChart from '@/components/charts/area';
|
||||
import ColumnBar from '@/components/charts/column-bar';
|
||||
import HBar from '@/components/charts/h-bar';
|
||||
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';
|
||||
@@ -13,7 +13,14 @@ import _ from 'lodash';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
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 = [
|
||||
'june 1',
|
||||
'june 2',
|
||||
@@ -94,15 +101,15 @@ const tokenUsage = TokensData.map((val, i) => {
|
||||
|
||||
const getCurrentMonthDays = () => {
|
||||
const now = dayjs();
|
||||
const firstDayOfMonth = now.startOf('month');
|
||||
const dateRange = [];
|
||||
let currentDate = firstDayOfMonth;
|
||||
const daysInMonth = now.daysInMonth();
|
||||
const year = dayjs().year();
|
||||
const month = dayjs().month() + 1;
|
||||
|
||||
while (currentDate.isBefore(now) || currentDate.isSame(now, 'day')) {
|
||||
dateRange.push(currentDate.format('YYYY-MM-DD'));
|
||||
currentDate = currentDate.add(1, 'day');
|
||||
const dates = [];
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
dates.push(dayjs(`${year}-${month}-${day}`).format('YYYY-MM-DD'));
|
||||
}
|
||||
return dateRange;
|
||||
return dates;
|
||||
};
|
||||
|
||||
const Usage = () => {
|
||||
@@ -110,42 +117,64 @@ const Usage = () => {
|
||||
const { size } = useWindowResize();
|
||||
const [paddingRight, setPaddingRight] = useState<string>('20px');
|
||||
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 [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 || {};
|
||||
|
||||
console.log('dateRange', dateRange);
|
||||
const handleSelectDate = (dateString: string) => {};
|
||||
|
||||
const generateData = () => {
|
||||
const requestList: { time: string; value: number }[] = [];
|
||||
const tokenList: {
|
||||
time: string;
|
||||
value: number;
|
||||
const requestList: {
|
||||
name: string;
|
||||
color: string;
|
||||
}[] = [];
|
||||
const userList: {
|
||||
name: string;
|
||||
value: number;
|
||||
type: string;
|
||||
color: string;
|
||||
}[] = [];
|
||||
areaStyle: any;
|
||||
data: { time: string; value: number }[];
|
||||
} = {
|
||||
name: 'API Request',
|
||||
areaStyle: {},
|
||||
color: baseColorMap.base,
|
||||
data: []
|
||||
};
|
||||
|
||||
_.each(data.api_request_history, (item: any) => {
|
||||
requestList.push({
|
||||
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
|
||||
value: item.value
|
||||
});
|
||||
});
|
||||
const completionData: any = {
|
||||
name: 'completion_token',
|
||||
color: baseColorMap.base,
|
||||
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) => {
|
||||
// tokens data
|
||||
@@ -153,18 +182,14 @@ const Usage = () => {
|
||||
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
||||
});
|
||||
if (!item) {
|
||||
tokenList.push({
|
||||
time: date,
|
||||
name: 'completion_token',
|
||||
color: 'rgba(84, 204, 152,0.8)',
|
||||
completionData.data.push({
|
||||
titme: date,
|
||||
value: 0
|
||||
});
|
||||
} else {
|
||||
tokenList.push({
|
||||
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
|
||||
name: 'completion_token',
|
||||
color: 'rgba(84, 204, 152,0.8)',
|
||||
value: item.value
|
||||
completionData.data.push({
|
||||
value: item.value,
|
||||
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD')
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,57 +197,58 @@ const Usage = () => {
|
||||
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
||||
});
|
||||
if (!promptItem) {
|
||||
tokenList.push({
|
||||
time: date,
|
||||
name: 'prompt_token',
|
||||
color: 'rgba(0, 170, 173, 0.8)',
|
||||
value: 0
|
||||
prompData.data.push({
|
||||
value: 0,
|
||||
time: date
|
||||
});
|
||||
} else {
|
||||
tokenList.push({
|
||||
time: dayjs(promptItem.timestamp * 1000).format('YYYY-MM-DD'),
|
||||
name: 'prompt_token',
|
||||
color: 'rgba(0, 170, 173, 0.8)',
|
||||
value: promptItem.value
|
||||
prompData.data.push({
|
||||
value: promptItem.value,
|
||||
time: dayjs(promptItem.timestamp * 1000).format('YYYY-MM-DD')
|
||||
});
|
||||
}
|
||||
|
||||
// api request data
|
||||
// ============== api request data =================
|
||||
const requestItem = _.find(data.api_request_history, (item: any) => {
|
||||
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
|
||||
});
|
||||
|
||||
if (!requestItem) {
|
||||
requestList.push({
|
||||
requestList.data.push({
|
||||
time: date,
|
||||
value: 0
|
||||
});
|
||||
} else {
|
||||
requestList.push({
|
||||
requestList.data.push({
|
||||
time: dayjs(requestItem.timestamp * 1000).format('YYYY-MM-DD'),
|
||||
value: requestItem.value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ========== top users ============
|
||||
|
||||
_.each(data.top_users, (item: any) => {
|
||||
userList.push({
|
||||
users.push(item.username);
|
||||
topUserPrompt.data.push({
|
||||
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
|
||||
});
|
||||
topUserCompletion.data.push({
|
||||
name: item.username,
|
||||
value: item.completion_token_count
|
||||
});
|
||||
});
|
||||
|
||||
setRequestData(requestList);
|
||||
setTokenData(tokenList);
|
||||
setUserData(userList);
|
||||
console.log('usage=============', {
|
||||
requestList,
|
||||
xAxisData
|
||||
});
|
||||
|
||||
setRequestData([requestList]);
|
||||
setUserData([topUserCompletion, topUserPrompt]);
|
||||
setTopUseList(users);
|
||||
setTokenData([completionData, prompData]);
|
||||
};
|
||||
|
||||
const labelFormatter = (v: any) => {
|
||||
@@ -270,28 +296,25 @@ const Usage = () => {
|
||||
<CardWrapper style={{ width: '100%' }}>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col span={12}>
|
||||
<AreaChart
|
||||
<LineChart
|
||||
title={intl.formatMessage({ id: 'dashboard.apirequest' })}
|
||||
data={requestData}
|
||||
seriesData={requestData}
|
||||
xAxisData={xAxisData}
|
||||
xField="time"
|
||||
yField="value"
|
||||
height={360}
|
||||
color={baseColorMap.baseR3}
|
||||
labelFormatter={labelFormatter}
|
||||
></AreaChart>
|
||||
></LineChart>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<ColumnBar
|
||||
<BarChart
|
||||
title={intl.formatMessage({ id: 'dashboard.tokens' })}
|
||||
data={tokenData}
|
||||
group={false}
|
||||
colorField="name"
|
||||
stack={true}
|
||||
xField="time"
|
||||
legend={false}
|
||||
yField="value"
|
||||
seriesData={tokenData}
|
||||
xAxisData={xAxisData}
|
||||
height={360}
|
||||
labelFormatter={labelFormatter}
|
||||
></ColumnBar>
|
||||
></BarChart>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardWrapper>
|
||||
@@ -300,13 +323,8 @@ const Usage = () => {
|
||||
<CardWrapper>
|
||||
<HBar
|
||||
title={intl.formatMessage({ id: 'dashboard.topusers' })}
|
||||
data={userData}
|
||||
stack={true}
|
||||
legend={false}
|
||||
showYAxis={false}
|
||||
colorField="type"
|
||||
xField="name"
|
||||
yField="value"
|
||||
seriesData={userData}
|
||||
xAxisData={topUserList}
|
||||
height={360}
|
||||
></HBar>
|
||||
</CardWrapper>
|
||||
|
||||
@@ -100,7 +100,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
const handleOnSearchRepo = async (text: string) => {
|
||||
try {
|
||||
const params = {
|
||||
q: `${text} gguf`,
|
||||
q: `${text}`,
|
||||
type: 'model'
|
||||
};
|
||||
const res = await callHuggingfaceQuickSearch(params);
|
||||
|
||||
@@ -162,9 +162,6 @@ const Models: React.FC = () => {
|
||||
_.each(list, (data: any) => {
|
||||
updateChunkedList(data);
|
||||
});
|
||||
if (!dataSource.length) {
|
||||
handleFilter();
|
||||
}
|
||||
};
|
||||
|
||||
const createModelsChunkRequest = () => {
|
||||
@@ -367,7 +364,10 @@ const Models: React.FC = () => {
|
||||
</Col>
|
||||
|
||||
<Col span={4}>
|
||||
<span style={{ paddingLeft: '22px' }}>
|
||||
<span
|
||||
style={{ paddingLeft: '68px' }}
|
||||
className="flex justify-center"
|
||||
>
|
||||
{item.state && (
|
||||
<StatusTag
|
||||
download={
|
||||
@@ -478,7 +478,7 @@ const Models: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<SealColumn
|
||||
title={intl.formatMessage({ id: 'models.table.name' })}
|
||||
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||
dataIndex="name"
|
||||
key="name"
|
||||
width={400}
|
||||
@@ -494,6 +494,7 @@ const Models: React.FC = () => {
|
||||
title={intl.formatMessage({ id: 'models.form.replicas' })}
|
||||
dataIndex="replicas"
|
||||
key="replicas"
|
||||
align="center"
|
||||
span={4}
|
||||
/>
|
||||
<SealColumn
|
||||
|
||||
@@ -39,13 +39,6 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
||||
},
|
||||
{ enabled: !disabled }
|
||||
);
|
||||
useHotkeys(
|
||||
HotKeys.CREATE.join(','),
|
||||
(e) => {
|
||||
onNewMessage();
|
||||
},
|
||||
{ enabled: !disabled }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="chat-footer">
|
||||
@@ -59,7 +52,6 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
||||
onClick={onNewMessage}
|
||||
>
|
||||
{intl.formatMessage({ id: 'playground.newMessage' })}
|
||||
<span className="m-l-5 opct-7">CTRL+SHIFT+ N</span>
|
||||
</Button>
|
||||
<Button
|
||||
icon={<DeleteOutlined></DeleteOutlined>}
|
||||
|
||||
@@ -126,8 +126,13 @@ const MessageList: React.FC<MessageProps> = (props) => {
|
||||
}
|
||||
};
|
||||
const handleClear = () => {
|
||||
const headItem = _.get(messageList, '0');
|
||||
setMessageList(headItem ? [headItem] : []);
|
||||
setMessageList([
|
||||
{
|
||||
role: Roles.User,
|
||||
content: '',
|
||||
uid: messageId.current + 1
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
const handleView = () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { INPUT_WIDTH } from '@/constants';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Form, InputNumber, Slider } from 'antd';
|
||||
import { Form, InputNumber, Slider } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { queryModelsList } from '../apis';
|
||||
@@ -149,9 +149,6 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
||||
</Form.Item>
|
||||
<h3 className="m-b-20 m-l-10 flex-between flex-center">
|
||||
<span>{intl.formatMessage({ id: 'playground.parameters' })}</span>
|
||||
<Button size="small" onClick={handleResetParams}>
|
||||
{intl.formatMessage({ id: 'common.button.reset' })}
|
||||
</Button>
|
||||
</h3>
|
||||
<Form.Item<ParamsSettingsFormProps>
|
||||
name="temperature"
|
||||
|
||||
+84
-63
@@ -1,4 +1,3 @@
|
||||
import CardWrapper from '@/components/card-wrapper';
|
||||
import FormButtons from '@/components/form-buttons';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import { PasswordReg } from '@/config';
|
||||
@@ -18,11 +17,12 @@ const Profile: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const intl = useIntl();
|
||||
|
||||
const newpasswordValue = Form.useWatch('new_password', form);
|
||||
|
||||
const handleOnFinish = async (values: any) => {
|
||||
try {
|
||||
await updatePassword(values);
|
||||
await updatePassword({
|
||||
new_password: values.new_password,
|
||||
current_password: values.current_password
|
||||
});
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
} catch (error) {}
|
||||
};
|
||||
@@ -43,20 +43,14 @@ const Profile: React.FC = () => {
|
||||
}}
|
||||
extra={[]}
|
||||
>
|
||||
<CardWrapper
|
||||
style={{
|
||||
padding: 'var(--layout-content-inlinepadding)',
|
||||
width: 'max-content'
|
||||
}}
|
||||
<Form
|
||||
style={{ width: '524px' }}
|
||||
name="profileForm"
|
||||
form={form}
|
||||
onFinish={handleOnFinish}
|
||||
onFinishFailed={handleOnFinishFailed}
|
||||
>
|
||||
<Form
|
||||
style={{ width: '524px' }}
|
||||
name="profileForm"
|
||||
form={form}
|
||||
onFinish={handleOnFinish}
|
||||
onFinishFailed={handleOnFinishFailed}
|
||||
>
|
||||
{/* <Form.Item<ProfileProps>
|
||||
{/* <Form.Item<ProfileProps>
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
@@ -76,53 +70,80 @@ const Profile: React.FC = () => {
|
||||
style={{ width: INPUT_WIDTH.default }}
|
||||
></SealInput.Input>
|
||||
</Form.Item> */}
|
||||
<Form.Item<ProfileProps>
|
||||
name="current_password"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{ id: 'common.form.rule.input' },
|
||||
{
|
||||
name: intl.formatMessage({
|
||||
id: 'users.form.currentpassword'
|
||||
})
|
||||
}
|
||||
)
|
||||
<Form.Item<ProfileProps>
|
||||
name="current_password"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{ id: 'common.form.rule.input' },
|
||||
{
|
||||
name: intl.formatMessage({
|
||||
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' })}
|
||||
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>
|
||||
<FormButtons
|
||||
htmlType="submit"
|
||||
onCancel={handleCancel}
|
||||
showCancel={false}
|
||||
></FormButtons>
|
||||
</Form>
|
||||
</CardWrapper>
|
||||
})
|
||||
]}
|
||||
>
|
||||
<SealInput.Password
|
||||
label={intl.formatMessage({ id: 'users.password.confirm' })}
|
||||
/>
|
||||
</Form.Item>
|
||||
<FormButtons
|
||||
htmlType="submit"
|
||||
onCancel={handleCancel}
|
||||
showCancel={false}
|
||||
></FormButtons>
|
||||
</Form>
|
||||
</PageContainer>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@@ -146,7 +146,7 @@ const Models: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Column
|
||||
title={intl.formatMessage({ id: 'resources.table.hostname' })}
|
||||
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||
dataIndex="hostname"
|
||||
key="hostname"
|
||||
/>
|
||||
@@ -211,7 +211,7 @@ const Models: React.FC = () => {
|
||||
key="GPU"
|
||||
render={(text, record: ListItem) => {
|
||||
return (
|
||||
<span className="flex-column">
|
||||
<span className="flex-column flex-gap-2">
|
||||
{record?.status?.gpu_devices.map((item, index) => {
|
||||
return (
|
||||
<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 { PageAction, PasswordReg } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import {
|
||||
SyncOutlined,
|
||||
UserOutlined,
|
||||
UserSwitchOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { UserOutlined, UserSwitchOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form, Modal, Select } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
@@ -32,14 +28,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const intl = useIntl();
|
||||
const suffix = (
|
||||
<SyncOutlined
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: '#1677ff'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const initFormValue = () => {
|
||||
if (action === PageAction.EDIT && open) {
|
||||
form.setFieldsValue({
|
||||
|
||||
@@ -113,7 +113,6 @@ const Models: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleModalOk = async (data: FormData) => {
|
||||
console.log('handleModalOk');
|
||||
const params = {
|
||||
...data,
|
||||
is_admin: data.is_admin === 'admin'
|
||||
@@ -131,7 +130,7 @@ const Models: React.FC = () => {
|
||||
}
|
||||
fetchData();
|
||||
setOpenAddModal(false);
|
||||
message.success('');
|
||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||
} catch (error) {
|
||||
setOpenAddModal(false);
|
||||
}
|
||||
@@ -261,22 +260,9 @@ const Models: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Column
|
||||
title={intl.formatMessage({ id: 'users.table.username' })}
|
||||
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||
dataIndex="username"
|
||||
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
|
||||
title={intl.formatMessage({ id: 'users.table.role' })}
|
||||
@@ -301,9 +287,9 @@ const Models: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
<Column
|
||||
title={intl.formatMessage({ id: 'common.table.updateTime' })}
|
||||
dataIndex="updated_at"
|
||||
key="updateTime"
|
||||
title={intl.formatMessage({ id: 'common.table.createTime' })}
|
||||
dataIndex="created_at"
|
||||
key="createTime"
|
||||
defaultSortOrder="descend"
|
||||
sortOrder={sortOrder}
|
||||
showSorterTooltip={false}
|
||||
|
||||
Reference in New Issue
Block a user