@@ -65,6 +65,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
// esbuildMinifyIIFE: true,
|
// esbuildMinifyIIFE: true,
|
||||||
|
favicons: ['/static/favicon.ico'],
|
||||||
jsMinifier: 'terser',
|
jsMinifier: 'terser',
|
||||||
cssMinifier: 'cssnano',
|
cssMinifier: 'cssnano',
|
||||||
presets: ['umi-presets-pro'],
|
presets: ['umi-presets-pro'],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 867 KiB |
|
After Width: | Height: | Size: 896 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
.cardWrapper {
|
||||||
|
border-radius: var(--border-radius-middle);
|
||||||
|
background-color: var(--color-white-1);
|
||||||
|
box-shadow: var(--box-shadow-base);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const CardWrapper = (props: any) => {
|
||||||
|
const { children, style } = props;
|
||||||
|
return (
|
||||||
|
<div className={styles.cardWrapper} style={{ ...style }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardWrapper;
|
||||||
@@ -6,34 +6,69 @@ interface BarChartProps {
|
|||||||
yField: string;
|
yField: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
group?: boolean;
|
||||||
|
colorField?: string;
|
||||||
|
seriesField?: string;
|
||||||
|
stack?: boolean;
|
||||||
|
legend?: any;
|
||||||
}
|
}
|
||||||
const BarChart: React.FC<BarChartProps> = (props) => {
|
const BarChart: React.FC<BarChartProps> = (props) => {
|
||||||
const { data, xField, yField, title, height = 260 } = props;
|
const {
|
||||||
|
data,
|
||||||
|
xField,
|
||||||
|
yField,
|
||||||
|
title,
|
||||||
|
height = 260,
|
||||||
|
group,
|
||||||
|
colorField,
|
||||||
|
seriesField,
|
||||||
|
stack,
|
||||||
|
legend = undefined
|
||||||
|
} = props;
|
||||||
const config = {
|
const config = {
|
||||||
data,
|
data,
|
||||||
xField,
|
xField,
|
||||||
yField,
|
yField,
|
||||||
// colorField: 'name',
|
colorField: colorField || 'name',
|
||||||
direction: 'vertical',
|
direction: 'vertical',
|
||||||
|
stack,
|
||||||
|
seriesField,
|
||||||
height,
|
height,
|
||||||
group: true,
|
group,
|
||||||
legend: {
|
scale: {
|
||||||
color: {
|
x: {
|
||||||
position: 'top',
|
type: 'band',
|
||||||
layout: {
|
padding: 0.5
|
||||||
justifyContent: 'center'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
legend:
|
||||||
|
legend === 'undefined'
|
||||||
|
? {
|
||||||
|
color: {
|
||||||
|
position: 'top',
|
||||||
|
layout: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: legend,
|
||||||
axis: {
|
axis: {
|
||||||
x: {
|
x: {
|
||||||
xAxis: true
|
xAxis: true,
|
||||||
|
tick: false
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
tick: false,
|
||||||
|
labelAutoWrap: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
title,
|
title,
|
||||||
style: {
|
style: {
|
||||||
align: 'center'
|
align: 'center',
|
||||||
|
titleFontSize: 14,
|
||||||
|
titleFill: 'rgba(0,0,0,0.88)',
|
||||||
|
titleFontWeight: 500
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
split: {
|
split: {
|
||||||
@@ -48,10 +83,16 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
style: {
|
style: {
|
||||||
fill: '#54cc98',
|
fill: (params: any) => {
|
||||||
radiusTopLeft: 8,
|
return (
|
||||||
radiusTopRight: 8,
|
params.color ||
|
||||||
width: 30
|
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
radiusTopLeft: 12,
|
||||||
|
radiusTopRight: 12,
|
||||||
|
align: 'center',
|
||||||
|
width: 20
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,19 @@ const GaugeChart: React.FC<GaugeChartProps> = (props) => {
|
|||||||
const config = {
|
const config = {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
box: {
|
||||||
|
padding: [0, 0, 0, 0],
|
||||||
|
style: {
|
||||||
|
padding: [0, 0, 0, 0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
guide: {
|
||||||
|
arc: {
|
||||||
|
style: {
|
||||||
|
lineWidth: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
autoFit: true,
|
autoFit: true,
|
||||||
data: {
|
data: {
|
||||||
target,
|
target,
|
||||||
@@ -34,9 +47,11 @@ const GaugeChart: React.FC<GaugeChartProps> = (props) => {
|
|||||||
title: {
|
title: {
|
||||||
title,
|
title,
|
||||||
size: 0,
|
size: 0,
|
||||||
titleFontSize: 14,
|
|
||||||
style: {
|
style: {
|
||||||
align: 'center'
|
align: 'center',
|
||||||
|
titleFontSize: 14,
|
||||||
|
titleFill: 'rgba(0,0,0,0.88)',
|
||||||
|
titleFontWeight: 500
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
|
|||||||
@@ -6,52 +6,89 @@ interface BarChartProps {
|
|||||||
yField: string;
|
yField: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
group?: boolean;
|
||||||
|
colorField?: string;
|
||||||
|
seriesField?: string;
|
||||||
|
stack?: boolean;
|
||||||
|
legend?: any;
|
||||||
}
|
}
|
||||||
const BarChart: React.FC<BarChartProps> = (props) => {
|
const BarChart: React.FC<BarChartProps> = (props) => {
|
||||||
const { data, xField, yField, title, height = 300 } = props;
|
const {
|
||||||
|
data,
|
||||||
|
xField,
|
||||||
|
yField,
|
||||||
|
title,
|
||||||
|
height,
|
||||||
|
group,
|
||||||
|
colorField,
|
||||||
|
seriesField,
|
||||||
|
stack,
|
||||||
|
legend = undefined
|
||||||
|
} = props;
|
||||||
const config = {
|
const config = {
|
||||||
data,
|
data,
|
||||||
xField,
|
xField,
|
||||||
yField,
|
yField,
|
||||||
// colorField: 'name',
|
colorField: colorField || 'name',
|
||||||
direction: 'vertical',
|
direction: 'vertical',
|
||||||
|
seriesField,
|
||||||
height,
|
height,
|
||||||
group: true,
|
group,
|
||||||
legend: {
|
stack,
|
||||||
color: {
|
legend:
|
||||||
position: 'top',
|
legend === 'undefined'
|
||||||
layout: {
|
? {
|
||||||
justifyContent: 'center'
|
color: {
|
||||||
}
|
position: 'top',
|
||||||
|
layout: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: legend,
|
||||||
|
scale: {
|
||||||
|
x: {
|
||||||
|
type: 'band',
|
||||||
|
padding: 0.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
axis: {
|
axis: {
|
||||||
x: {
|
x: {
|
||||||
xAxis: true
|
xAxis: true,
|
||||||
|
tick: false
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
tick: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
title,
|
title,
|
||||||
style: {
|
style: {
|
||||||
align: 'center'
|
align: 'center',
|
||||||
|
titleFontSize: 14,
|
||||||
|
titleFill: 'rgba(0,0,0,0.88)',
|
||||||
|
titleFontWeight: 500
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
split: {
|
split: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
line: {
|
line: {
|
||||||
color: 'red',
|
|
||||||
style: {
|
style: {
|
||||||
color: 'red',
|
|
||||||
lineDash: [4, 5]
|
lineDash: [4, 5]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
markBackground: {},
|
||||||
style: {
|
style: {
|
||||||
fill: '#54cc98',
|
fill: (params: any) => {
|
||||||
radiusTopLeft: 8,
|
return (
|
||||||
radiusTopRight: 8,
|
params.color ||
|
||||||
height: 30
|
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
radiusTopLeft: 12,
|
||||||
|
radiusTopRight: 12,
|
||||||
|
height: 20
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,18 @@ interface LineChartProps {
|
|||||||
color?: string[];
|
color?: string[];
|
||||||
xField?: string;
|
xField?: string;
|
||||||
yField?: string;
|
yField?: string;
|
||||||
slider?: boolean;
|
labelFormatter?: (v: any) => string;
|
||||||
|
slider?: any;
|
||||||
}
|
}
|
||||||
const LineChart: React.FC<LineChartProps> = (props) => {
|
const LineChart: React.FC<LineChartProps> = (props) => {
|
||||||
const { data, title, color, xField, yField, slider, height } = props;
|
const { data, title, color, xField, yField, slider, height, labelFormatter } =
|
||||||
|
props;
|
||||||
const config = {
|
const config = {
|
||||||
title,
|
title,
|
||||||
height,
|
height,
|
||||||
xField: xField || 'time',
|
xField: xField || 'time',
|
||||||
yField: yField || 'value',
|
yField: yField || 'value',
|
||||||
color: color || ['red', 'blue', 'green'],
|
// color: color || ['red', 'blue', 'green', 'yellow'],
|
||||||
colorField: 'type',
|
colorField: 'type',
|
||||||
autoFit: true,
|
autoFit: true,
|
||||||
slider,
|
slider,
|
||||||
@@ -26,21 +28,31 @@ const LineChart: React.FC<LineChartProps> = (props) => {
|
|||||||
textStyle: {
|
textStyle: {
|
||||||
autoRoate: true
|
autoRoate: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
tick: false,
|
||||||
|
// size: 14,
|
||||||
|
// title: '%',
|
||||||
|
titlePosition: 'top',
|
||||||
|
titleFontSize: 12,
|
||||||
|
labelFormatter
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
point: {
|
// point: {
|
||||||
shapeField: 'circle',
|
// shapeField: 'circle',
|
||||||
sizeField: 2
|
// sizeField: 2
|
||||||
},
|
// },
|
||||||
style: {
|
style: {
|
||||||
lineWidth: 1.5
|
lineWidth: 1.5,
|
||||||
|
opacity: 0.8
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
itemMarker: {
|
|
||||||
symbol: 'circle'
|
|
||||||
},
|
|
||||||
color: {
|
color: {
|
||||||
layout: { justifyContent: 'center' }
|
layout: { justifyContent: 'center' }
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
itemLabelFontSize: 14,
|
||||||
|
itemLabelFontWeight: 500
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { Liquid } from '@ant-design/plots';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
interface LiquidChartProps {
|
||||||
|
percent: number;
|
||||||
|
color?: string;
|
||||||
|
title?: string;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
thresholds?: number[];
|
||||||
|
rangColor?: string[];
|
||||||
|
}
|
||||||
|
const LiquidChart: React.FC<LiquidChartProps> = (props) => {
|
||||||
|
const {
|
||||||
|
percent = 0,
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
thresholds = [],
|
||||||
|
rangColor = []
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const primaryColor = 'rgba(84, 204, 152,0.8)';
|
||||||
|
const [fillColor, setFillColor] = useState<string>(primaryColor);
|
||||||
|
|
||||||
|
const calcColorByPercent = () => {
|
||||||
|
if (color) {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
if (!rangColor.length) {
|
||||||
|
return primaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangColor.length && thresholds.length) {
|
||||||
|
let index = 0;
|
||||||
|
for (let i = 0; i < thresholds.length; i++) {
|
||||||
|
if (percent <= thresholds[i]) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rangColor[index];
|
||||||
|
}
|
||||||
|
return primaryColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fc = calcColorByPercent();
|
||||||
|
setFillColor(fc);
|
||||||
|
}, [percent]);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
percent,
|
||||||
|
textAlign: 'center',
|
||||||
|
autoFit: true,
|
||||||
|
title: {
|
||||||
|
title,
|
||||||
|
size: 0,
|
||||||
|
style: {
|
||||||
|
align: 'center',
|
||||||
|
titleFontSize: 12,
|
||||||
|
titleFill: 'rgba(0,0,0,0.88)',
|
||||||
|
titleFontWeight: 500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
style: {
|
||||||
|
textAlign: 'center',
|
||||||
|
outlineBorder: 2,
|
||||||
|
waveLength: 128,
|
||||||
|
stroke: fillColor,
|
||||||
|
fill: fillColor
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return <Liquid {...config} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LiquidChart;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
.g-polygon {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-polygon-1 {
|
||||||
|
// 定位代码,容器高宽随意
|
||||||
|
background: #fe5;
|
||||||
|
clip-path: polygon(0 10%, 30% 0, 100% 40%, 70% 100%, 20% 90%);
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-polygon-2 {
|
||||||
|
// 定位代码,容器高宽随意
|
||||||
|
background: #e950d1;
|
||||||
|
clip-path: polygon(10% 0, 100% 70%, 100% 100%, 20% 90%);
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-polygon-3 {
|
||||||
|
// 定位代码,容器高宽随意
|
||||||
|
background: rgba(87, 80, 233);
|
||||||
|
clip-path: polygon(80% 0, 100% 70%, 100% 100%, 20% 90%);
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-bg::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
backdrop-filter: blur(150px);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import './index.less';
|
||||||
|
|
||||||
|
const GlassBg = () => {
|
||||||
|
return (
|
||||||
|
<div className="g-bg">
|
||||||
|
<div className="g-polygon g-polygon-1"></div>
|
||||||
|
<div className="g-polygon g-polygon-2"></div>
|
||||||
|
<div className="g-polygon g-polygon-3"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlassBg;
|
||||||
@@ -2,57 +2,69 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.header-row-wrapper {
|
.header-row-wrapper {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.header-row-prefix-wrapper {
|
.header-row-prefix-wrapper {
|
||||||
padding-left: var(--ant-table-cell-padding-inline);
|
padding-left: var(--ant-table-cell-padding-inline);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-box {
|
.row-box {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: var(--ant-table-header-border-radius);
|
border-radius: var(--ant-table-header-border-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expanded-row {
|
.expanded-row {
|
||||||
background-color: var(--color-white-1);
|
background-color: var(--color-white-1);
|
||||||
padding: 16px 16px;
|
padding: 16px;
|
||||||
border: 1px solid var(--color-fill-1);
|
border: 1px solid var(--color-fill-1);
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
border-radius: 0 0 var(--ant-table-header-border-radius)
|
border-radius: 0 0 var(--ant-table-header-border-radius)
|
||||||
var(--ant-table-header-border-radius);
|
var(--ant-table-header-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-wrapper {
|
.row-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: var(--color-fill-1);
|
background-color: var(--color-white-1);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--ant-table-row-hover-bg);
|
background-color: var(--ant-table-row-hover-bg);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-selected {
|
&-selected {
|
||||||
background-color: var(--ant-table-row-selected-bg);
|
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-selected-hover-bg);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-prefix-wrapper {
|
.row-prefix-wrapper {
|
||||||
padding-left: var(--ant-table-cell-padding-inline);
|
padding-left: var(--ant-table-cell-padding-inline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.seal-table-row {
|
.seal-table-row {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spin {
|
.spin {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
xs: 0,
|
||||||
|
sm: 576,
|
||||||
|
md: 768,
|
||||||
|
lg: 992,
|
||||||
|
xl: 1200,
|
||||||
|
xxl: 1600
|
||||||
|
};
|
||||||
@@ -4,13 +4,14 @@
|
|||||||
html {
|
html {
|
||||||
--ant-color-fill-tertiary: rgba(0, 0, 0, 4%);
|
--ant-color-fill-tertiary: rgba(0, 0, 0, 4%);
|
||||||
--color-fill-1: var(--ant-color-fill-tertiary);
|
--color-fill-1: var(--ant-color-fill-tertiary);
|
||||||
// --color-fill-1: #f3f6fa;
|
// --color-fill-1: #fff;
|
||||||
--color-fill-2: #fff;
|
--color-fill-2: #fff;
|
||||||
--color-fill-3: #f3f6fa;
|
--color-fill-3: #f3f6fa;
|
||||||
--menu-border-radius-base: 32px;
|
--menu-border-radius-base: 20px;
|
||||||
--border-radius-base: 16px;
|
--border-radius-base: 16px;
|
||||||
|
--border-radius-middle: 20px;
|
||||||
--border-radius-small: 8px;
|
--border-radius-small: 8px;
|
||||||
--color-white-1: #fff;
|
--color-white-1: rgba(255, 255, 255, 100%);
|
||||||
--font-weight-normal: 500;
|
--font-weight-normal: 500;
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
--color-text-1: var(--ant-color-text);
|
--color-text-1: var(--ant-color-text);
|
||||||
@@ -19,7 +20,7 @@ html {
|
|||||||
--font-size-large: 16px;
|
--font-size-large: 16px;
|
||||||
--font-size-middle: 14px;
|
--font-size-middle: 14px;
|
||||||
--ant-color-fill-secondary: rgba(0, 0, 0, 6%);
|
--ant-color-fill-secondary: rgba(0, 0, 0, 6%);
|
||||||
--table-td-radius: 24px;
|
--table-td-radius: 20px;
|
||||||
--checkbox-border-radius: 4px;
|
--checkbox-border-radius: 4px;
|
||||||
--ant-table-cell-padding-inline: 16px;
|
--ant-table-cell-padding-inline: 16px;
|
||||||
--ant-table-cell-padding-block: 16px;
|
--ant-table-cell-padding-block: 16px;
|
||||||
@@ -27,7 +28,7 @@ html {
|
|||||||
--ant-table-header-split-color: #f0f0f0;
|
--ant-table-header-split-color: #f0f0f0;
|
||||||
--ant-table-row-selected-bg: #f0fff6;
|
--ant-table-row-selected-bg: #f0fff6;
|
||||||
--ant-table-row-selected-hover-bg: #d8f2e4;
|
--ant-table-row-selected-hover-bg: #d8f2e4;
|
||||||
--ant-table-row-hover-bg: #e6e6e6;
|
--ant-table-row-hover-bg: rgba(230, 230, 230, 70%);
|
||||||
--color-chart-red: #ff7875;
|
--color-chart-red: #ff7875;
|
||||||
--color-chart-green: #54cc98;
|
--color-chart-green: #54cc98;
|
||||||
--color-chart-glod: #ffd666;
|
--color-chart-glod: #ffd666;
|
||||||
@@ -36,6 +37,7 @@ html {
|
|||||||
--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: #2fbf85;
|
--ant-input-active-border-color: #2fbf85;
|
||||||
--ant-input-hover-border-color: #54cc98;
|
--ant-input-hover-border-color: #54cc98;
|
||||||
|
--box-shadow-base: 0 4px 12px rgb(227, 232, 240);
|
||||||
|
|
||||||
.css-var-rf {
|
.css-var-rf {
|
||||||
--ant-font-size: var(--font-size-base);
|
--ant-font-size: var(--font-size-base);
|
||||||
@@ -49,7 +51,7 @@ html {
|
|||||||
&.ant-menu-css-var {
|
&.ant-menu-css-var {
|
||||||
--ant-menu-item-height: 46px;
|
--ant-menu-item-height: 46px;
|
||||||
--ant-menu-item-selected-bg: var(--color-white-1);
|
--ant-menu-item-selected-bg: var(--color-white-1);
|
||||||
--ant-menu-item-border-radius: 24px;
|
--ant-menu-item-border-radius: 16px;
|
||||||
--ant-menu-item-selected-color: var(--ant-color-primary);
|
--ant-menu-item-selected-color: var(--ant-color-primary);
|
||||||
--ant-menu-item-hover-bg: var(--color-white-1);
|
--ant-menu-item-hover-bg: var(--color-white-1);
|
||||||
--ant-menu-item-color: var(--color-text-1);
|
--ant-menu-item-color: var(--color-text-1);
|
||||||
@@ -67,6 +69,7 @@ html {
|
|||||||
--ant-border-radius-lg: 16px;
|
--ant-border-radius-lg: 16px;
|
||||||
--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-popover {
|
&.ant-popover {
|
||||||
--ant-popover-inner-padding: 26px;
|
--ant-popover-inner-padding: 26px;
|
||||||
@@ -95,7 +98,7 @@ html {
|
|||||||
.css-var-rh.ant-menu-css-var {
|
.css-var-rh.ant-menu-css-var {
|
||||||
--ant-menu-item-height: 46px;
|
--ant-menu-item-height: 46px;
|
||||||
--ant-menu-item-selected-bg: var(--color-white-1);
|
--ant-menu-item-selected-bg: var(--color-white-1);
|
||||||
--ant-menu-item-border-radius: 24px;
|
--ant-menu-item-border-radius: 20px;
|
||||||
--ant-menu-item-selected-color: var(--ant-color-primary);
|
--ant-menu-item-selected-color: var(--ant-color-primary);
|
||||||
--ant-menu-item-hover-bg: var(--color-white-1);
|
--ant-menu-item-hover-bg: var(--color-white-1);
|
||||||
--ant-menu-item-color: var(--color-text-1);
|
--ant-menu-item-color: var(--color-text-1);
|
||||||
@@ -161,7 +164,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr > td {
|
tr > td {
|
||||||
background-color: var(--color-fill-1);
|
// background-color: var(--color-fill-1);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
|
|
||||||
@@ -222,12 +225,43 @@ body {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============== new theme style start ===============
|
||||||
|
.ant-pro-layout {
|
||||||
|
background-color: var(--color-fill-1);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.ant-pro-sider .ant-layout-sider-children {
|
||||||
|
background-color: var(--color-white-1);
|
||||||
|
box-shadow: var(--box-shadow-base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-wrapper .ant-table {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-content table {
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody {
|
||||||
|
.ant-table-row {
|
||||||
|
border-radius: var(--table-td-radius);
|
||||||
|
background-color: var(--color-white-1);
|
||||||
|
box-shadow: var(--box-shadow-base);
|
||||||
|
|
||||||
|
> td {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== new theme style end =================
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== basic layout style start============
|
|
||||||
|
|
||||||
// ======== basic layout style end ============
|
|
||||||
|
|
||||||
// ======== menu style start ============
|
// ======== menu style start ============
|
||||||
.ant-pro-sider-collapsed-button {
|
.ant-pro-sider-collapsed-button {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -250,11 +284,19 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-layout-container {
|
.ant-pro-layout-container {
|
||||||
background-color: var(--color-fill-2);
|
// background-color: var(--color-fill-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider {
|
.ant-pro-sider {
|
||||||
.ant-menu {
|
.ant-menu {
|
||||||
|
.ant-menu-item {
|
||||||
|
border-radius: 16px;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--ant-menu-item-selected-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-menu-item:not(.ant-menu-item-selected) {
|
.ant-menu-item:not(.ant-menu-item-selected) {
|
||||||
color: var(--ant-color-text);
|
color: var(--ant-color-text);
|
||||||
}
|
}
|
||||||
@@ -266,6 +308,11 @@ body {
|
|||||||
.ant-menu-item.ant-menu-item-selected:hover {
|
.ant-menu-item.ant-menu-item-selected:hover {
|
||||||
color: var(--ant-color-primary);
|
color: var(--ant-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-menu-item.ant-menu-item-selected {
|
||||||
|
color: var(--ant-color-primary);
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,20 +322,35 @@ body {
|
|||||||
.monaco-editor {
|
.monaco-editor {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
// .background {
|
|
||||||
// position: fixed;
|
.background {
|
||||||
// top: 0;
|
position: fixed;
|
||||||
// left: 0;
|
top: 0;
|
||||||
// bottom: 0;
|
left: 0;
|
||||||
// right: 0;
|
bottom: 0;
|
||||||
// background: radial-gradient(
|
right: 0;
|
||||||
// circle,
|
// background: radial-gradient(
|
||||||
// rgba(47, 191, 133, 0.02),
|
// circle,
|
||||||
// rgba(55, 125, 184, 0.2)
|
// rgba(47, 191, 133, 0.02),
|
||||||
// );
|
// rgba(55, 125, 184, 0.2)
|
||||||
// filter: blur(100px);
|
// );
|
||||||
// height: 100%;
|
background: url('./assets/images/bg.png') center center no-repeat;
|
||||||
// }
|
background-size: cover;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
backdrop-filter: blur(100px);
|
||||||
|
filter: blur(100px);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-pro-page-container {
|
.ant-pro-page-container {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
@@ -308,7 +370,7 @@ body {
|
|||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
rgba(255, 255, 255, 4%) 25%,
|
rgba(255, 255, 255, 4%) 25%,
|
||||||
rgba(0, 0, 0, 10%) 37%,
|
rgba(0, 0, 0, 8%) 37%,
|
||||||
rgba(255, 255, 255, 4%) 63%
|
rgba(255, 255, 255, 4%) 63%
|
||||||
);
|
);
|
||||||
background-size: 400% 100%;
|
background-size: 400% 100%;
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import breakpoints from '@/config/breakpoints';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export default function useWindowResize() {
|
||||||
|
const [size, setSize] = useState<{ width: number; height: number }>({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight
|
||||||
|
});
|
||||||
|
const [isMobile, setIsMobile] = useState<boolean>(false);
|
||||||
|
const [isTablet, setIsTablet] = useState<boolean>(false);
|
||||||
|
const [isDesktop, setIsDesktop] = useState<boolean>(false);
|
||||||
|
const [currentPoint, setCurrentPoint] = useState<string>('');
|
||||||
|
|
||||||
|
const checkBreakpoint = (width: number) => {
|
||||||
|
if (width < breakpoints.sm) {
|
||||||
|
setIsMobile(true);
|
||||||
|
setIsTablet(false);
|
||||||
|
setIsDesktop(false);
|
||||||
|
setCurrentPoint('sm');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (width < breakpoints.md) {
|
||||||
|
setIsMobile(false);
|
||||||
|
setIsTablet(true);
|
||||||
|
setIsDesktop(false);
|
||||||
|
setCurrentPoint('md');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (width < breakpoints.lg) {
|
||||||
|
setIsMobile(false);
|
||||||
|
setIsTablet(false);
|
||||||
|
setIsDesktop(true);
|
||||||
|
setCurrentPoint('lg');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsMobile(false);
|
||||||
|
setIsTablet(false);
|
||||||
|
setIsDesktop(true);
|
||||||
|
setCurrentPoint('xl');
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setSize({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkBreakpoint(size.width);
|
||||||
|
}, [size.width]);
|
||||||
|
|
||||||
|
return { size, isMobile, isTablet, isDesktop, currentPoint };
|
||||||
|
}
|
||||||
@@ -144,7 +144,7 @@ export default (props: any) => {
|
|||||||
location={location}
|
location={location}
|
||||||
title={userConfig.title}
|
title={userConfig.title}
|
||||||
navTheme="light"
|
navTheme="light"
|
||||||
siderWidth={270}
|
siderWidth={220}
|
||||||
onMenuHeaderClick={(e) => {
|
onMenuHeaderClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
|
export const DASHBOARD_API = '/dashboard';
|
||||||
|
|
||||||
|
export async function queryDashboardData() {
|
||||||
|
return request(DASHBOARD_API);
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import ContentWrapper from '@/components/content-wrapper';
|
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
|
import ProgressBar from '@/components/progress-bar';
|
||||||
import { Col, Row, Table } from 'antd';
|
import { Col, Row, Table } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
|
|
||||||
const modelColumns = [
|
const modelColumns = [
|
||||||
{
|
{
|
||||||
@@ -9,28 +12,30 @@ const modelColumns = [
|
|||||||
key: 'name'
|
key: 'name'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Allocated GPUs',
|
title: 'GPU Utilization',
|
||||||
dataIndex: 'allocated',
|
dataIndex: 'gpu_utilization',
|
||||||
key: 'allocated',
|
key: 'gpu_utilization',
|
||||||
render: (text: any, record: any) => <span>{record.gpu.allocated}</span>
|
render: (text: any, record: any) => (
|
||||||
|
<ProgressBar percent={_.round(text, 2)}></ProgressBar>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'GPU Utilization',
|
title: 'VRAM Utilization',
|
||||||
dataIndex: 'utilization',
|
dataIndex: 'gpu_memory_utilization',
|
||||||
key: 'utilization',
|
key: 'gpu_memory_utilization',
|
||||||
render: (text: any, record: any) => <span>{record.gpu.utilization}</span>
|
render: (text: any, record: any) => (
|
||||||
|
<ProgressBar percent={_.round(text, 2)}></ProgressBar>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Running Instances',
|
title: 'Running Instances',
|
||||||
dataIndex: 'running',
|
dataIndex: 'instance_count',
|
||||||
key: 'running',
|
key: 'instance_count'
|
||||||
render: (text: any, record: any) => <span>{record.instances.running}</span>
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pending Instances',
|
title: 'Tokens',
|
||||||
dataIndex: 'pending',
|
dataIndex: 'token_count',
|
||||||
key: 'pending',
|
key: 'token_count'
|
||||||
render: (text: any, record: any) => <span>{record.instances.pending}</span>
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -59,39 +64,6 @@ const projectColumns = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const modelData = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'qwen2',
|
|
||||||
gpu: { allocated: 4, utilization: '50%' },
|
|
||||||
instances: { running: 1, pending: 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'llama3:70b',
|
|
||||||
gpu: { allocated: 3, utilization: '70%' },
|
|
||||||
instances: { running: 1, pending: 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: 'llama3',
|
|
||||||
gpu: { allocated: 5, utilization: '20%' },
|
|
||||||
instances: { running: 1, pending: 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: 'gemma',
|
|
||||||
gpu: { allocated: 1, utilization: '25%' },
|
|
||||||
instances: { running: 1, pending: 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: 'phi3',
|
|
||||||
gpu: { allocated: 2, utilization: '46%' },
|
|
||||||
instances: { running: 1, pending: 0 }
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const projectData = [
|
const projectData = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -130,11 +102,12 @@ const projectData = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
const ActiveTable = () => {
|
const ActiveTable = () => {
|
||||||
|
const data = useContext(DashboardContext).active_models || [];
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row gutter={[20, 0]}>
|
||||||
<Col span={12}>
|
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||||
<PageTools
|
<PageTools
|
||||||
style={{ margin: '32px 40px' }}
|
style={{ margin: '32px 8px' }}
|
||||||
left={
|
left={
|
||||||
<span
|
<span
|
||||||
style={{ fontSize: 'var(--font-size-large)', padding: '9px 0' }}
|
style={{ fontSize: 'var(--font-size-large)', padding: '9px 0' }}
|
||||||
@@ -144,35 +117,14 @@ const ActiveTable = () => {
|
|||||||
}
|
}
|
||||||
right={false}
|
right={false}
|
||||||
/>
|
/>
|
||||||
<ContentWrapper contentStyle={{ paddingRight: 0 }} title={false}>
|
<div>
|
||||||
<Table
|
<Table
|
||||||
columns={modelColumns}
|
columns={modelColumns}
|
||||||
dataSource={modelData}
|
dataSource={data}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
/>
|
/>
|
||||||
</ContentWrapper>
|
</div>
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<PageTools
|
|
||||||
style={{ margin: '32px 40px' }}
|
|
||||||
left={
|
|
||||||
<span
|
|
||||||
style={{ fontSize: 'var(--font-size-large)', padding: '9px 0' }}
|
|
||||||
>
|
|
||||||
Active Projects
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
right={false}
|
|
||||||
/>
|
|
||||||
<ContentWrapper title={false}>
|
|
||||||
<Table
|
|
||||||
columns={projectColumns}
|
|
||||||
dataSource={projectData}
|
|
||||||
pagination={false}
|
|
||||||
rowKey="id"
|
|
||||||
/>
|
|
||||||
</ContentWrapper>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import ContentWrapper from '@/components/content-wrapper';
|
|
||||||
import { Card, Col, Row, Space } from 'antd';
|
import { Card, Col, Row, Space } from 'antd';
|
||||||
import React from 'react';
|
import _ from 'lodash';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
import { overviewConfigs } from '../config';
|
import { overviewConfigs } from '../config';
|
||||||
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
import '../styles/index.less';
|
import '../styles/index.less';
|
||||||
import styles from './over-view.less';
|
import styles from './over-view.less';
|
||||||
|
|
||||||
@@ -24,23 +25,8 @@ const renderCardItem = (data: {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const Overview: React.FC = (props) => {
|
const Overview: React.FC = () => {
|
||||||
// const { data = {} } = props;
|
const data = useContext(DashboardContext).resource_counts || {};
|
||||||
const data = {
|
|
||||||
workers: 8,
|
|
||||||
models: {
|
|
||||||
healthy: 10,
|
|
||||||
warning: 2,
|
|
||||||
error: 1
|
|
||||||
},
|
|
||||||
gpus: 30,
|
|
||||||
allocatedGpus: 12,
|
|
||||||
instances: {
|
|
||||||
healthy: 32,
|
|
||||||
warning: 3,
|
|
||||||
error: 2
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderValue = (
|
const renderValue = (
|
||||||
value:
|
value:
|
||||||
@@ -63,19 +49,26 @@ const Overview: React.FC = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ContentWrapper contentStyle={{ paddingBlockStart: '32px' }} title={false}>
|
<div>
|
||||||
<Row gutter={[24, 20]} className={styles.row}>
|
<Row gutter={[24, 20]} className={styles.row}>
|
||||||
{overviewConfigs.map((config, index) => (
|
{overviewConfigs.map((config, index) => (
|
||||||
<Col span={5} key={config.key}>
|
<Col
|
||||||
|
xs={{ flex: '100%' }}
|
||||||
|
sm={{ flex: '50%' }}
|
||||||
|
md={{ flex: '30%' }}
|
||||||
|
lg={{ flex: '20%' }}
|
||||||
|
xl={{ flex: '20%' }}
|
||||||
|
key={config.key}
|
||||||
|
>
|
||||||
{renderCardItem({
|
{renderCardItem({
|
||||||
label: config.label,
|
label: config.label,
|
||||||
value: renderValue(data[config.key] || 0),
|
value: renderValue(_.get(data, config.key, 0)),
|
||||||
bgColor: config.backgroundColor
|
bgColor: config.backgroundColor
|
||||||
})}
|
})}
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
</ContentWrapper>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +1,68 @@
|
|||||||
import LineChart from '@/components/charts/line-chart';
|
import LineChart from '@/components/charts/line-chart';
|
||||||
import { generateFluctuatingData } from '@/utils';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
|
|
||||||
const mockData = {
|
const TypeKeyMap = {
|
||||||
GPU: generateFluctuatingData({
|
cpu: 'CPU',
|
||||||
total: 17,
|
memory: 'Memory',
|
||||||
max: 80,
|
gpu: 'GPU',
|
||||||
min: 20
|
gpu_memory: 'VRAM'
|
||||||
}),
|
|
||||||
CPU: generateFluctuatingData({
|
|
||||||
total: 17,
|
|
||||||
max: 70,
|
|
||||||
min: 10
|
|
||||||
}),
|
|
||||||
Memory: generateFluctuatingData({
|
|
||||||
total: 17,
|
|
||||||
max: 60,
|
|
||||||
min: 20
|
|
||||||
}),
|
|
||||||
VRAM: generateFluctuatingData({
|
|
||||||
total: 17,
|
|
||||||
max: 90,
|
|
||||||
min: 15
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const UtilizationOvertime: React.FC = () => {
|
const UtilizationOvertime: React.FC = () => {
|
||||||
const timeList = [
|
const data = useContext(DashboardContext)?.system_load?.history || {};
|
||||||
'08:00:00',
|
const [result, setResult] = useState<
|
||||||
'09:00:00',
|
{ time: string; value: number; type: string }[]
|
||||||
'10:00:00',
|
>([]);
|
||||||
'11:00:00',
|
|
||||||
'12:00:00',
|
const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory'];
|
||||||
'13:00:00',
|
const sliderConfig = {
|
||||||
'14:00:00',
|
y: false,
|
||||||
'15:00:00',
|
x: {
|
||||||
'16:00:00',
|
style: {
|
||||||
'17:00:00',
|
selectionFill: 'rgb(84, 204, 152)',
|
||||||
'18:00:00',
|
selectionFillOpacity: 0.1,
|
||||||
'19:00:00',
|
handleIconFill: 'rgb(84, 204, 152)',
|
||||||
'20:00:00',
|
handleIconFillOpacity: 0.15,
|
||||||
'21:00:00',
|
handleIconStrokeOpacity: 0,
|
||||||
'22:00:00',
|
sparklineType: 'line',
|
||||||
'23:00:00',
|
sparkline: true
|
||||||
'24:00:00'
|
},
|
||||||
];
|
sparkline: true
|
||||||
const typeList = ['GPU', 'CPU', 'Memory', 'VRAM'];
|
|
||||||
const generateData = () => {
|
|
||||||
const data = [];
|
|
||||||
for (let i = 0; i < timeList.length; i++) {
|
|
||||||
for (let j = 0; j < typeList.length; j++) {
|
|
||||||
data.push({
|
|
||||||
time: timeList[i],
|
|
||||||
type: typeList[j],
|
|
||||||
value: _.get(mockData, typeList[j])[i]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
const data = generateData();
|
|
||||||
|
const labelFormatter = (value: any) => {
|
||||||
|
return `${value}%`;
|
||||||
|
};
|
||||||
|
const generateData = () => {
|
||||||
|
const list: { value: number; time: string; type: string }[] = [];
|
||||||
|
_.each(typeList, (type: any) => {
|
||||||
|
const dataList = _.map(_.get(data, type, []), (item: any) => {
|
||||||
|
return {
|
||||||
|
value: _.round(item.value, 2) || 0,
|
||||||
|
time: dayjs(item.timestamp * 1000).format('HH:mm:ss'),
|
||||||
|
type: _.get(TypeKeyMap, type, '')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
list.push(...dataList);
|
||||||
|
});
|
||||||
|
setResult(list);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
generateData();
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LineChart height={400} data={data} />
|
<LineChart
|
||||||
|
data={result}
|
||||||
|
labelFormatter={labelFormatter}
|
||||||
|
slider={sliderConfig}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,43 @@
|
|||||||
import GaugeChart from '@/components/charts/gauge';
|
import CardWrapper from '@/components/card-wrapper';
|
||||||
|
import LiquidChart from '@/components/charts/liquid';
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import breakpoints from '@/config/breakpoints';
|
||||||
|
import useWindowResize from '@/hooks/use-window-resize';
|
||||||
import { Col, DatePicker, Row } from 'antd';
|
import { Col, DatePicker, Row } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
import ResourceUtilization from './resource-utilization';
|
import ResourceUtilization from './resource-utilization';
|
||||||
|
|
||||||
const SystemLoad = () => {
|
const SystemLoad = () => {
|
||||||
const handleSelectDate = (date: string) => {
|
const colors = [
|
||||||
console.log('dateString============', date);
|
'linear-gradient(90deg, rgba(84, 204, 152,.8) 0%, rgba(84, 204, 152,0.5) 50%, rgba(84, 204, 152,.8) 100%)',
|
||||||
};
|
'linear-gradient(90deg, rgba(255, 214, 102,.8) 0%, rgba(255, 214, 102,0.5) 50%, rgba(255, 214, 102,.8) 100%)',
|
||||||
|
'linear-gradient(90deg, rgba(255, 120, 117,.8) 0%, rgba(255, 120, 117,0.5) 50%, rgba(255, 120, 117,.8) 100%)'
|
||||||
|
];
|
||||||
|
const data = useContext(DashboardContext)?.system_load?.current || {};
|
||||||
|
const { size } = useWindowResize();
|
||||||
|
const [paddingRight, setPaddingRight] = useState<string>('20px');
|
||||||
|
const [smallChartHeight, setSmallChartHeight] = useState<number>(190);
|
||||||
|
const [largeChartHeight, setLargeChartHeight] = useState<number>(400);
|
||||||
|
const thresholds = [0.5, 0.7, 1];
|
||||||
|
const height = 400;
|
||||||
|
|
||||||
|
const handleSelectDate = (date: string) => {};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (size.width < breakpoints.xl) {
|
||||||
|
setPaddingRight('0');
|
||||||
|
} else {
|
||||||
|
setPaddingRight('20px');
|
||||||
|
}
|
||||||
|
}, [size.width]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer ghost title={false}>
|
<div>
|
||||||
<div className="system-load">
|
<div className="system-load">
|
||||||
<PageTools
|
<PageTools
|
||||||
marginBottom={10}
|
style={{ margin: '32px 8px' }}
|
||||||
marginTop={0}
|
|
||||||
left={
|
left={
|
||||||
<span style={{ fontSize: 'var(--font-size-large)' }}>
|
<span style={{ fontSize: 'var(--font-size-large)' }}>
|
||||||
System Load
|
System Load
|
||||||
@@ -24,51 +47,64 @@ const SystemLoad = () => {
|
|||||||
<DatePicker onChange={handleSelectDate} style={{ width: 300 }} />
|
<DatePicker onChange={handleSelectDate} style={{ width: 300 }} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ResourceUtilization />
|
<Row style={{ width: '100%' }} gutter={[0, 20]}>
|
||||||
<Row style={{ width: '100%', marginTop: '32px' }}>
|
<Col
|
||||||
<Col xs={24} sm={24} md={12} lg={6} xl={6}>
|
xs={24}
|
||||||
<GaugeChart
|
sm={24}
|
||||||
title="GPU Compute Utilization"
|
md={24}
|
||||||
total={100}
|
lg={24}
|
||||||
target={20}
|
xl={16}
|
||||||
// height={320}
|
style={{ paddingRight: paddingRight }}
|
||||||
thresholds={[50, 70, 100]}
|
>
|
||||||
rangColor={['#54cc98', '#ffd666', '#ff7875']}
|
<CardWrapper style={{ height: height, width: '100%' }}>
|
||||||
></GaugeChart>
|
<ResourceUtilization />
|
||||||
|
</CardWrapper>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={24} md={12} lg={6} xl={6}>
|
<Col xs={24} sm={24} md={24} lg={24} xl={8}>
|
||||||
<GaugeChart
|
<CardWrapper style={{ height: largeChartHeight, width: '100%' }}>
|
||||||
title="GPU Memory Utilization"
|
<Row style={{ height: largeChartHeight, width: '100%' }}>
|
||||||
total={100}
|
<Col span={12} style={{ height: smallChartHeight }}>
|
||||||
target={30}
|
<LiquidChart
|
||||||
// height={320}
|
title="GPU Compute Utilization"
|
||||||
thresholds={[50, 70, 100]}
|
percent={_.round(data.gpu?.utilization_rate || 0, 2) / 100}
|
||||||
rangColor={['#54cc98', '#ffd666', '#ff7875']}
|
thresholds={thresholds}
|
||||||
></GaugeChart>
|
rangColor={colors}
|
||||||
</Col>
|
></LiquidChart>
|
||||||
<Col xs={24} sm={24} md={12} lg={6} xl={6}>
|
</Col>
|
||||||
<GaugeChart
|
<Col span={12} style={{ height: smallChartHeight }}>
|
||||||
title="CPU Compute Utilization"
|
<LiquidChart
|
||||||
total={100}
|
title="GPU Memory Utilization"
|
||||||
target={40}
|
percent={
|
||||||
// height={320}
|
_.round(data.gpu_memory?.utilization_rate || 0, 2) / 100
|
||||||
thresholds={[50, 70, 100]}
|
}
|
||||||
rangColor={['#54cc98', '#ffd666', '#ff7875']}
|
thresholds={thresholds}
|
||||||
></GaugeChart>
|
rangColor={colors}
|
||||||
</Col>
|
></LiquidChart>
|
||||||
<Col xs={24} sm={24} md={12} lg={6} xl={6}>
|
</Col>
|
||||||
<GaugeChart
|
<Col span={12} style={{ height: smallChartHeight }}>
|
||||||
title="CPU Memory Utilization"
|
<LiquidChart
|
||||||
total={100}
|
title="CPU Compute Utilization"
|
||||||
target={70}
|
percent={_.round(data.cpu?.utilization_rate || 0, 2) / 100}
|
||||||
// height={320}
|
thresholds={thresholds}
|
||||||
thresholds={[50, 70, 100]}
|
rangColor={colors}
|
||||||
rangColor={['#54cc98', '#ffd666', '#ff7875']}
|
></LiquidChart>
|
||||||
></GaugeChart>
|
</Col>
|
||||||
|
<Col span={12} style={{ height: smallChartHeight }}>
|
||||||
|
<LiquidChart
|
||||||
|
title="CPU Memory Utilization"
|
||||||
|
percent={
|
||||||
|
_.round(data.memory?.utilization_rate || 0, 2) / 100
|
||||||
|
}
|
||||||
|
thresholds={thresholds}
|
||||||
|
rangColor={colors}
|
||||||
|
></LiquidChart>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</CardWrapper>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
import CardWrapper from '@/components/card-wrapper';
|
||||||
import ColumnBar from '@/components/charts/column-bar';
|
import ColumnBar from '@/components/charts/column-bar';
|
||||||
import HBar from '@/components/charts/h-bar';
|
import HBar from '@/components/charts/h-bar';
|
||||||
import ContentWrapper from '@/components/content-wrapper';
|
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
|
import breakpoints from '@/config/breakpoints';
|
||||||
|
import useWindowResize from '@/hooks/use-window-resize';
|
||||||
import { generateRandomArray } from '@/utils';
|
import { generateRandomArray } from '@/utils';
|
||||||
import { Col, DatePicker, Row } from 'antd';
|
import { Col, DatePicker, Row } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
const times = [
|
const times = [
|
||||||
'june 1',
|
'june 1',
|
||||||
'june 2',
|
'june 2',
|
||||||
@@ -86,64 +93,154 @@ const tokenUsage = TokensData.map((val, i) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Usage = () => {
|
const Usage = () => {
|
||||||
const handleSelectDate = (dateString: string) => {
|
const { size } = useWindowResize();
|
||||||
console.log('dateString============', dateString);
|
const [paddingRight, setPaddingRight] = useState<string>('20px');
|
||||||
|
const [requestData, setRequestData] = useState<
|
||||||
|
{ time: string; value: number }[]
|
||||||
|
>([]);
|
||||||
|
const [tokenData, setTokenData] = useState<{ time: string; value: number }[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [userData, setUserData] = useState<{ name: string; value: number }[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const data = useContext(DashboardContext)?.model_usage || {};
|
||||||
|
|
||||||
|
const handleSelectDate = (dateString: string) => {};
|
||||||
|
|
||||||
|
const generateData = () => {
|
||||||
|
const requestList: { time: string; value: number }[] = [];
|
||||||
|
const tokenList: {
|
||||||
|
time: string;
|
||||||
|
value: number;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}[] = [];
|
||||||
|
const userList: {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
type: string;
|
||||||
|
color: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
_.each(data.api_request_history, (item: any) => {
|
||||||
|
requestList.push({
|
||||||
|
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
|
||||||
|
value: item.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_.each(data.completion_token_history, (item: any) => {
|
||||||
|
tokenList.push({
|
||||||
|
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
|
||||||
|
name: 'completion_token',
|
||||||
|
color:
|
||||||
|
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)',
|
||||||
|
value: item.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_.each(data.prompt_token_history, (item: any) => {
|
||||||
|
tokenList.push({
|
||||||
|
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
|
||||||
|
name: 'prompt_token',
|
||||||
|
color:
|
||||||
|
'linear-gradient(90deg,rgba(0, 170, 173, 0.8) 0%,rgba(0, 109, 193, 0.7) 100%)',
|
||||||
|
value: item.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_.each(data.top_users, (item: any) => {
|
||||||
|
userList.push({
|
||||||
|
name: item.username,
|
||||||
|
type: 'completion_token',
|
||||||
|
color:
|
||||||
|
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)',
|
||||||
|
value: item.completion_token_count
|
||||||
|
});
|
||||||
|
userList.push({
|
||||||
|
name: item.username,
|
||||||
|
type: 'prompt_token',
|
||||||
|
color:
|
||||||
|
'linear-gradient(90deg,rgba(0, 170, 173, 0.8) 0%,rgba(0, 109, 193, 0.7) 100%)',
|
||||||
|
value: item.prompt_token_count
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setRequestData(requestList);
|
||||||
|
setTokenData(tokenList);
|
||||||
|
setUserData(userList);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (size.width < breakpoints.xl) {
|
||||||
|
setPaddingRight('0');
|
||||||
|
} else {
|
||||||
|
setPaddingRight('20px');
|
||||||
|
}
|
||||||
|
}, [size.width]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
generateData();
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTools
|
<PageTools
|
||||||
style={{ margin: '32px 40px' }}
|
style={{ margin: '32px 8px' }}
|
||||||
left={<span style={{ fontSize: 'var(--font-size-large)' }}>Usage</span>}
|
left={<span style={{ fontSize: 'var(--font-size-large)' }}>Usage</span>}
|
||||||
right={
|
right={
|
||||||
<DatePicker onChange={handleSelectDate} style={{ width: 300 }} />
|
<RangePicker onChange={handleSelectDate} style={{ width: 300 }} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Row style={{ width: '100%' }} gutter={[20, 0]}>
|
<Row style={{ width: '100%' }} gutter={[0, 20]}>
|
||||||
<Col span={12}>
|
<Col
|
||||||
<ContentWrapper title={false} contentStyle={{ paddingRight: 0 }}>
|
xs={24}
|
||||||
<ColumnBar
|
sm={24}
|
||||||
title="API Request"
|
md={24}
|
||||||
data={dataList}
|
lg={24}
|
||||||
xField="time"
|
xl={16}
|
||||||
yField="value"
|
style={{ paddingRight: paddingRight }}
|
||||||
height={360}
|
>
|
||||||
></ColumnBar>
|
<CardWrapper style={{ width: '100%' }}>
|
||||||
</ContentWrapper>
|
<Row style={{ width: '100%' }}>
|
||||||
|
<Col span={12}>
|
||||||
|
<ColumnBar
|
||||||
|
title="API Request"
|
||||||
|
data={requestData}
|
||||||
|
xField="time"
|
||||||
|
yField="value"
|
||||||
|
height={360}
|
||||||
|
></ColumnBar>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<ColumnBar
|
||||||
|
title="Tokens"
|
||||||
|
data={tokenData}
|
||||||
|
group={false}
|
||||||
|
colorField="name"
|
||||||
|
stack={true}
|
||||||
|
xField="time"
|
||||||
|
legend={false}
|
||||||
|
yField="value"
|
||||||
|
height={360}
|
||||||
|
></ColumnBar>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</CardWrapper>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col xs={24} sm={24} md={24} lg={24} xl={8}>
|
||||||
<ContentWrapper title={false} contentStyle={{ paddingLeft: 0 }}>
|
<CardWrapper>
|
||||||
<ColumnBar
|
|
||||||
title="Tokens"
|
|
||||||
data={tokenUsage}
|
|
||||||
xField="time"
|
|
||||||
yField="value"
|
|
||||||
height={360}
|
|
||||||
></ColumnBar>
|
|
||||||
</ContentWrapper>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{ width: '100%' }} gutter={[20, 0]}>
|
|
||||||
<Col span={12}>
|
|
||||||
<ContentWrapper title={false} contentStyle={{ paddingRight: 0 }}>
|
|
||||||
<HBar
|
<HBar
|
||||||
title="Top Users"
|
title="Top Users"
|
||||||
data={userDataList}
|
data={userData}
|
||||||
xField="time"
|
colorField="type"
|
||||||
|
stack={true}
|
||||||
|
legend={false}
|
||||||
|
xField="name"
|
||||||
yField="value"
|
yField="value"
|
||||||
height={400}
|
height={360}
|
||||||
></HBar>
|
></HBar>
|
||||||
</ContentWrapper>
|
</CardWrapper>
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<ContentWrapper title={false} contentStyle={{ paddingLeft: 0 }}>
|
|
||||||
<HBar
|
|
||||||
title="Top Projects"
|
|
||||||
data={projectDataList}
|
|
||||||
xField="time"
|
|
||||||
yField="value"
|
|
||||||
height={400}
|
|
||||||
></HBar>
|
|
||||||
</ContentWrapper>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import { DashboardProps } from './types';
|
||||||
|
|
||||||
|
export const DashboardContext = createContext<DashboardProps>(
|
||||||
|
{} as DashboardProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DashboardContext;
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
export const overviewConfigs = [
|
export const overviewConfigs = [
|
||||||
{
|
{
|
||||||
key: 'workers',
|
key: 'workworker_count',
|
||||||
label: 'Workers',
|
label: 'Workers',
|
||||||
backgroundColor:
|
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
|
||||||
'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)'
|
backgroundColor: 'var(--color-white-1)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'gpus',
|
key: 'gpu_count',
|
||||||
label: 'Total GPUs',
|
label: 'Total GPUs',
|
||||||
backgroundColor:
|
backgroundColor: 'var(--color-white-1)'
|
||||||
'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)'
|
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'allocatedGpus',
|
key: 'allocatedGpus',
|
||||||
label: 'Allocated GPUs',
|
label: 'Allocated GPUs',
|
||||||
backgroundColor:
|
backgroundColor: 'var(--color-white-1)'
|
||||||
'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)'
|
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'models',
|
key: 'model_count',
|
||||||
label: 'Models',
|
label: 'Models',
|
||||||
backgroundColor:
|
backgroundColor: 'var(--color-white-1)'
|
||||||
'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)'
|
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'instances',
|
key: 'model_instance_count',
|
||||||
label: 'Instances',
|
label: 'Instances',
|
||||||
backgroundColor:
|
backgroundColor: 'var(--color-white-1)'
|
||||||
'linear-gradient(180deg, rgb(0 139 188 / 20%) 0%, rgba(40,207,181,.2) 100%)'
|
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
export interface DashboardProps {
|
||||||
|
resource_counts: {
|
||||||
|
worker_count: number;
|
||||||
|
gpu_count: number;
|
||||||
|
model_count: number;
|
||||||
|
model_instance_count: number;
|
||||||
|
};
|
||||||
|
system_load: {
|
||||||
|
current: {
|
||||||
|
cpu: {
|
||||||
|
total: number;
|
||||||
|
used: number;
|
||||||
|
utilization_rate: number;
|
||||||
|
};
|
||||||
|
memory: {
|
||||||
|
total: number;
|
||||||
|
used: number;
|
||||||
|
utilization_rate: number;
|
||||||
|
};
|
||||||
|
gpu: {
|
||||||
|
total: number;
|
||||||
|
used: number;
|
||||||
|
utilization_rate: number;
|
||||||
|
};
|
||||||
|
gpu_memory: {
|
||||||
|
total: number;
|
||||||
|
used: number;
|
||||||
|
utilization_rate: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
history: {
|
||||||
|
cpu: {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}[];
|
||||||
|
memory: {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}[];
|
||||||
|
gpu: {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}[];
|
||||||
|
gpu_memory: {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
model_usage: {
|
||||||
|
api_request_history: any[];
|
||||||
|
completion_token_history: any[];
|
||||||
|
prompt_token_history: any[];
|
||||||
|
top_users: {
|
||||||
|
user_id: number;
|
||||||
|
username: string;
|
||||||
|
prompt_token_count: number;
|
||||||
|
completion_token_count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
active_models: any[];
|
||||||
|
}
|
||||||
@@ -1,20 +1,40 @@
|
|||||||
import DividerLine from '@/components/divider-line';
|
import { Spin } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { queryDashboardData } from './apis';
|
||||||
import ActiveTable from './components/active-table';
|
import ActiveTable from './components/active-table';
|
||||||
import Overview from './components/over-view';
|
import Overview from './components/over-view';
|
||||||
import SystemLoad from './components/system-load';
|
import SystemLoad from './components/system-load';
|
||||||
import Usage from './components/usage';
|
import Usage from './components/usage';
|
||||||
|
import DashboardContext from './config/dashboard-context';
|
||||||
|
import { DashboardProps } from './config/types';
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
|
const [data, setData] = useState<DashboardProps>({} as DashboardProps);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const getDashboardData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await queryDashboardData();
|
||||||
|
setData(res);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
setData({} as DashboardProps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
getDashboardData();
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<>
|
<Spin spinning={loading}>
|
||||||
<Overview></Overview>
|
<DashboardContext.Provider value={{ ...data }}>
|
||||||
<DividerLine></DividerLine>
|
<Overview></Overview>
|
||||||
<SystemLoad></SystemLoad>
|
<SystemLoad></SystemLoad>
|
||||||
<DividerLine></DividerLine>
|
<Usage></Usage>
|
||||||
<Usage></Usage>
|
<ActiveTable></ActiveTable>
|
||||||
<DividerLine></DividerLine>
|
</DashboardContext.Provider>
|
||||||
<ActiveTable></ActiveTable>
|
</Spin>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ type AddModalProps = {
|
|||||||
|
|
||||||
const sourceOptions = [
|
const sourceOptions = [
|
||||||
{ label: 'Huggingface', value: 'huggingface', key: 'huggingface' },
|
{ label: 'Huggingface', value: 'huggingface', key: 'huggingface' },
|
||||||
{ label: 'Ollama', value: 'ollama_library', key: 'ollama_library' },
|
{ label: 'Ollama Library', value: 'ollama_library', key: 'ollama_library' }
|
||||||
{ label: 'S3', value: 's3', key: 's3' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const AddModal: React.FC<AddModalProps> = (props) => {
|
const AddModal: React.FC<AddModalProps> = (props) => {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ const Models: React.FC = () => {
|
|||||||
clearInterval(timer.current);
|
clearInterval(timer.current);
|
||||||
timer.current = setInterval(() => {
|
timer.current = setInterval(() => {
|
||||||
fetchData(true);
|
fetchData(true);
|
||||||
}, 3000);
|
}, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShowSizeChange = (page: number, size: number) => {
|
const handleShowSizeChange = (page: number, size: number) => {
|
||||||
|
|||||||
@@ -18,11 +18,20 @@ interface ChatFooterProps {
|
|||||||
onView: () => void;
|
onView: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
feedback?: React.ReactNode;
|
feedback?: React.ReactNode;
|
||||||
|
hasTokenResult?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props;
|
const {
|
||||||
|
onSubmit,
|
||||||
|
onClear,
|
||||||
|
onNewMessage,
|
||||||
|
onView,
|
||||||
|
feedback,
|
||||||
|
disabled,
|
||||||
|
hasTokenResult
|
||||||
|
} = props;
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
HotKeys.SUBMIT.join(','),
|
HotKeys.SUBMIT.join(','),
|
||||||
() => {
|
() => {
|
||||||
@@ -34,7 +43,7 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="chat-footer">
|
<div className="chat-footer">
|
||||||
<Row style={{ width: '100%' }}>
|
<Row style={{ width: '100%' }}>
|
||||||
<Col span={8}>
|
<Col span={hasTokenResult ? 8 : 12}>
|
||||||
<Space size={20}>
|
<Space size={20}>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -53,8 +62,8 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>{feedback}</Col>
|
<Col span={hasTokenResult ? 8 : 0}>{feedback}</Col>
|
||||||
<Col span={8} style={{ textAlign: 'right' }}>
|
<Col span={hasTokenResult ? 8 : 12} style={{ textAlign: 'right' }}>
|
||||||
<Space size={20}>
|
<Space size={20}>
|
||||||
<Button
|
<Button
|
||||||
icon={<CodeOutlined></CodeOutlined>}
|
icon={<CodeOutlined></CodeOutlined>}
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ const MessageList: React.FC<MessageProps> = (props) => {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onView={handleView}
|
onView={handleView}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
hasTokenResult={!!tokenResult}
|
||||||
feedback={<ReferenceParams usage={tokenResult}></ReferenceParams>}
|
feedback={<ReferenceParams usage={tokenResult}></ReferenceParams>}
|
||||||
></ChatFooter>
|
></ChatFooter>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import CardWrapper from '@/components/card-wrapper';
|
||||||
import { useSearchParams } from '@umijs/max';
|
import { useSearchParams } from '@umijs/max';
|
||||||
import { Divider } from 'antd';
|
import { Divider } from 'antd';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -26,20 +27,25 @@ const Playground: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="play-ground">
|
<div style={{ padding: '32px 40px' }}>
|
||||||
<div className="chat">
|
<CardWrapper>
|
||||||
<GroundLeft parameters={params}></GroundLeft>
|
<div className="play-ground">
|
||||||
</div>
|
<div className="chat">
|
||||||
<div className="divider-line">
|
<GroundLeft parameters={params}></GroundLeft>
|
||||||
<Divider type="vertical" />
|
</div>
|
||||||
</div>
|
<div className="divider-line">
|
||||||
<div className="params">
|
<Divider type="vertical" />
|
||||||
<ParamsSettings
|
</div>
|
||||||
onClose={handleClosePopover}
|
|
||||||
setParams={setParams}
|
<div className="params">
|
||||||
selectedModel={selectModel}
|
<ParamsSettings
|
||||||
/>
|
onClose={handleClosePopover}
|
||||||
</div>
|
setParams={setParams}
|
||||||
|
selectedModel={selectModel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
.ground-left {
|
.ground-left {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100vh;
|
height: calc(100vh - 84px);
|
||||||
padding-bottom: 120px;
|
padding-bottom: 120px;
|
||||||
|
|
||||||
.message-list-wrap {
|
.message-list-wrap {
|
||||||
max-height: calc(100vh - 152px);
|
max-height: calc(100vh - 152px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.ant-pro-page-container-children-container {
|
||||||
|
padding-inline: 26px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ground-left-footer {
|
.ground-left-footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
.play-ground {
|
.play-ground {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
.chat {
|
.chat {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.params {
|
.params {
|
||||||
// width: 465px;
|
// width: 465px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider-line {
|
.divider-line {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
|
|
||||||
.ant-divider {
|
.ant-divider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
min-height: calc(100vh - 84px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@@ -42,14 +43,17 @@ const Profile: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
extra={[]}
|
extra={[]}
|
||||||
>
|
>
|
||||||
<Form
|
<CardWrapper
|
||||||
style={{ width: '524px' }}
|
style={{ padding: '32px', marginTop: '70px', width: 'max-content' }}
|
||||||
name="profileForm"
|
|
||||||
form={form}
|
|
||||||
onFinish={handleOnFinish}
|
|
||||||
onFinishFailed={handleOnFinishFailed}
|
|
||||||
>
|
>
|
||||||
{/* <Form.Item<ProfileProps>
|
<Form
|
||||||
|
style={{ width: '524px' }}
|
||||||
|
name="profileForm"
|
||||||
|
form={form}
|
||||||
|
onFinish={handleOnFinish}
|
||||||
|
onFinishFailed={handleOnFinishFailed}
|
||||||
|
>
|
||||||
|
{/* <Form.Item<ProfileProps>
|
||||||
name="username"
|
name="username"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@@ -69,50 +73,53 @@ 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
|
<SealInput.Password
|
||||||
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
|
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
|
||||||
required
|
required
|
||||||
style={{ width: INPUT_WIDTH.default }}
|
style={{ width: INPUT_WIDTH.default }}
|
||||||
></SealInput.Password>
|
></SealInput.Password>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item<ProfileProps>
|
<Form.Item<ProfileProps>
|
||||||
name="new_password"
|
name="new_password"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
pattern: PasswordReg,
|
pattern: PasswordReg,
|
||||||
message: intl.formatMessage({ id: 'users.form.rule.password' })
|
message: intl.formatMessage({
|
||||||
}
|
id: 'users.form.rule.password'
|
||||||
]}
|
})
|
||||||
>
|
}
|
||||||
<SealInput.Password
|
]}
|
||||||
label={intl.formatMessage({ id: 'users.form.newpassword' })}
|
>
|
||||||
required
|
<SealInput.Password
|
||||||
style={{ width: INPUT_WIDTH.default }}
|
label={intl.formatMessage({ id: 'users.form.newpassword' })}
|
||||||
></SealInput.Password>
|
required
|
||||||
</Form.Item>
|
style={{ width: INPUT_WIDTH.default }}
|
||||||
<FormButtons
|
></SealInput.Password>
|
||||||
htmlType="submit"
|
</Form.Item>
|
||||||
onCancel={handleCancel}
|
<FormButtons
|
||||||
showCancel={false}
|
htmlType="submit"
|
||||||
></FormButtons>
|
onCancel={handleCancel}
|
||||||
</Form>
|
showCancel={false}
|
||||||
|
></FormButtons>
|
||||||
|
</Form>
|
||||||
|
</CardWrapper>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -201,14 +201,7 @@ const Models: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
|
||||||
title={intl.formatMessage({ id: 'resources.table.disk' })}
|
|
||||||
dataIndex="storage"
|
|
||||||
key="storage"
|
|
||||||
render={(text, record: ListItem) => {
|
|
||||||
return <ProgressBar percent={0}></ProgressBar>;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'resources.table.vram' })}
|
title={intl.formatMessage({ id: 'resources.table.vram' })}
|
||||||
dataIndex="VRAM"
|
dataIndex="VRAM"
|
||||||
@@ -240,6 +233,14 @@ const Models: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Column
|
||||||
|
title={intl.formatMessage({ id: 'resources.table.disk' })}
|
||||||
|
dataIndex="storage"
|
||||||
|
key="storage"
|
||||||
|
render={(text, record: ListItem) => {
|
||||||
|
return <ProgressBar percent={0}></ProgressBar>;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Table>
|
</Table>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
export const isNotEmptyValue = (value: any) => {
|
export const isNotEmptyValue = (value: any) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.length > 0;
|
return value.length > 0;
|
||||||
|
|||||||