fix: ui issues

This commit is contained in:
jialin
2024-07-03 15:27:35 +08:00
parent 316019586f
commit 81e9a3cc92
46 changed files with 1075 additions and 305 deletions
+10 -5
View File
@@ -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: {
+1
View File
@@ -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",
+21 -1
View File
@@ -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

+5
View File
@@ -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 -2
View File
@@ -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);
} }
+9 -2
View File
@@ -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,
+1
View File
@@ -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,
+6 -6
View File
@@ -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
+96
View File
@@ -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;
+58
View File
@@ -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;
+102
View File
@@ -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
}
};
+116
View File
@@ -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;
+56
View File
@@ -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;
+98
View File
@@ -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;
+17
View File
@@ -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 }[];
}
+16 -1
View File
@@ -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>
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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)';
+1
View File
@@ -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>
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
}
+48
View File
@@ -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
};
}
+2 -7
View File
@@ -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 -2
View File
@@ -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'
}; };
+3 -3
View File
@@ -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': '已分配显存 / 内存'
}; };
+9 -10
View File
@@ -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);
} }
} }
+1 -1
View File
@@ -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}
/>
</> </>
); );
}; };
+102 -84
View File
@@ -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>
+1 -1
View File
@@ -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);
+6 -5
View File
@@ -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
View File
@@ -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>
); );
+2 -2
View File
@@ -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}>
+2 -13
View File
@@ -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({
+5 -19
View File
@@ -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}