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: {
cellFontSize: 12,
rowSelectedHoverBg: '#e6f6ff'
rowSelectedHoverBg: 'rgba(230, 230, 230, 0.88)',
rowSelectedBg: 'rgb(244, 245, 244)'
},
Menu: {
iconSize: 16,
@@ -20,10 +21,14 @@ export default {
},
Slider: {
handleSize: 8,
handleSizeHover: 10
// handleColor: '#007BFF'
// trackBg: '#007BFF',
// trackHoverBg: '#007BFF'
handleSizeHover: 8,
trackBg: 'rgba(0,0,0,0.15)',
handleColor: 'rgba(0,0,0,0.2)',
trackHoverBg: 'rgba(0,0,0,0.25)',
handleActiveColor: 'rgba(0,0,0,0.3)',
dotActiveBorderColor: 'rgba(0,0,0,0.25)',
handleActiveOutlineColor: 'rgba(0,0,0,0.25)',
dotBorderColor: 'rgba(0,0,0,0.25)'
}
},
token: {
+1
View File
@@ -25,6 +25,7 @@
"classnames": "^2.5.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.11",
"echarts": "^5.5.1",
"has-ansi": "^5.0.1",
"jotai": "^2.8.4",
"localforage": "^1.10.0",
+21 -1
View File
@@ -47,6 +47,9 @@ dependencies:
dayjs:
specifier: ^1.11.11
version: 1.11.11
echarts:
specifier: ^5.5.1
version: 5.5.1
has-ansi:
specifier: ^5.0.1
version: 5.0.1
@@ -8238,6 +8241,13 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: false
/echarts@5.5.1:
resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==, tarball: https://registry.npmjs.org/echarts/-/echarts-5.5.1.tgz}
dependencies:
tslib: 2.3.0
zrender: 5.6.0
dev: false
/ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
@@ -16286,7 +16296,11 @@ packages:
dev: false
/tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==, tarball: https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz}
dev: false
/tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz}
dev: false
/tslib@2.6.2:
@@ -17187,6 +17201,12 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
/zrender@5.6.0:
resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==, tarball: https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz}
dependencies:
tslib: 2.3.0
dev: false
/zscroller@0.4.8:
resolution: {integrity: sha512-G5NiNLKx2+QhhvZi2yV1jjVXY50otktxkseX2hG2N/eixohOUk0AY8ZpbAxNqS9oJS/NxItCsowupy2tsXxAMw==, tarball: https://registry.npmjs.org/zscroller/-/zscroller-0.4.8.tgz}
dependencies:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+5
View File
@@ -45,6 +45,11 @@
align-items: center;
}
.flex-gap-2 {
display: flex;
gap: 2px;
}
.opct-7 {
opacity: 0.7;
}
+1 -2
View File
@@ -1,8 +1,7 @@
.cardWrapper {
border-radius: var(--border-radius-small);
background-color: var(--color-white-1);
// box-shadow: var(--box-shadow-base);
box-shadow: none;
padding: 10px;
border: 1px solid var(--ant-color-border);
border: 1px solid var(--color-border-1);
}
+9 -2
View File
@@ -5,7 +5,7 @@ interface LineChartProps {
title?: string;
height?: number;
data: any[];
color?: string[];
color?: string;
xField?: string;
yField?: string;
locale?: string;
@@ -47,7 +47,14 @@ const LineChart: React.FC<LineChartProps> = (props) => {
}
},
style: {
fill: 'rgba(84, 204, 152,0.8)'
fill: (params: any) => {
console.log('area param=========', params);
return color || 'rgba(84, 204, 152,0.8)';
},
stroke: (params: any) => {
console.log('area param========2=', params);
return color || 'rgba(84, 204, 152,0.8)';
}
},
title: {
title,
+1
View File
@@ -89,6 +89,7 @@ const BarChart: React.FC<BarChartProps> = (props) => {
style: {
fill: (params: any) => {
console.log('colbar=====', params);
return params.color || 'rgba(84, 204, 152,0.8)';
},
// radiusTopLeft: 12,
+6 -6
View File
@@ -55,19 +55,19 @@ const LineChart: React.FC<LineChartProps> = (props) => {
// },
style: {
lineWidth: 1.5,
opacity: 0.8
opacity: 0.7
// fill: (params: any) => {
// return params.color;
// }
// stroke: (params: any) => {
// return (
// params.color ||
// 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
// );
// console.log('line param=========', params.color);
// return params.color;
// }
},
legend: {
color: {
layout: { justifyContent: 'center' }
},
size: {
itemLabelFontSize: 14,
itemLabelFontWeight: 500
+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 useContainerScroll from '@/hooks/use-container-scorll';
import Convert from 'ansi-to-html';
import classNames from 'classnames';
import hasAnsi from 'has-ansi';
@@ -17,9 +18,18 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
const [logsContent, setLogsContent] = useState(content);
const { setChunkRequest } = useSetChunkRequest();
const chunkRequedtRef = useRef<any>(null);
const scroller = useRef<any>(null);
const { updateScrollerPosition, handleContentWheel } = useContainerScroll(
scroller,
{ toBottom: true }
);
const convert = new Convert();
useEffect(() => {
updateScrollerPosition();
}, [logsContent]);
const updateContent = (newVal: string) => {
if (hasAnsi(newVal)) {
const htmlStr = `${convert.toHtml(newVal)}`;
@@ -51,7 +61,12 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
return (
<div className="logs-viewer-wrap-w2">
<div className="wrap" style={{ height: height }}>
<div
className="wrap"
style={{ height: height }}
ref={scroller}
onWheel={handleContentWheel}
>
<div className={classNames('content', { 'line-break': nowrap })}>
<div className="text">{logsContent}</div>
</div>
+1 -1
View File
@@ -14,7 +14,7 @@ const PageTools: React.FC<PageToolsProps> = (props) => {
left,
right,
marginBottom = 0,
marginTop = 60,
marginTop = 30,
style: pageStyle
} = props;
+1 -1
View File
@@ -15,7 +15,7 @@ const RenderProgress = memo(
return 'var(--ant-color-primary)';
}
if (percent <= 50) {
return 'var(--ant-color-primary)';
return 'var(--color-progress-green)';
}
if (percent <= 80) {
return 'var(--ant-color-warning)';
+1
View File
@@ -104,6 +104,7 @@ const SealTable: React.FC<SealTableProps> = (props) => {
title={title}
style={headerStyle}
firstCell={i === 0}
align={align}
lastCell={i === props.children.length - 1}
></TableHeader>
</Col>
+1 -2
View File
@@ -48,11 +48,10 @@
}
&-selected {
background-color: var(--ant-table-row-selected-bg);
transition: all 0.2s ease;
&:hover {
background-color: var(--ant-table-row-selected-hover-bg);
background-color: var(--ant-table-row-hover-bg);
transition: all 0.2s ease;
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ export const StatusColorMap: Record<StatusType, { text: string; bg: string }> =
bg: `var(--ant-blue-1)`
},
success: {
text: `var(--ant-color-primary)`,
text: `var(--color-progress-green)`,
bg: `var(--ant-green-1)`
},
inactive: {
+37 -1
View File
@@ -40,6 +40,8 @@ html {
--color-chart-green: rgb(84, 204, 152, 80%);
--color-chart-glod: rgba(250, 173, 20, 80%);
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
--color-progress-green: rgba(84, 204, 152, 100%);
--color-border-1: rgba(217, 217, 217, 100%);
// ======== input ============
--ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%);
--ant-input-active-border-color: #007bff;
@@ -77,7 +79,7 @@ html {
--ant-color-text: #000;
--ant-color-error: #ff4d4f;
--ant-color-bg-mask: rgba(0, 0, 0, 35%);
--ant-color-border: rgb(217 217 217 / 50%);
--ant-color-border: rgb(217 217 217 / 100%);
&.ant-popover {
--ant-popover-inner-padding: 26px;
@@ -131,6 +133,10 @@ body * {
}
body {
// .ant-slider:hover .ant-slider-handle::after {
// box-shadow: 0 0 0 var(--ant-slider-handle-line-width)
// var(--ant-slider-handle-color);
// }
.ant-modal-confirm-title {
display: none;
}
@@ -406,3 +412,33 @@ body {
);
background-size: 400% 100%;
}
.tooltip-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 11px;
background-color: rgba(255, 255, 255, 80%);
min-width: 100px;
.tooltip-x-name {
font-size: var(--font-size-base);
color: var(--ant-color-text-tertiary);
}
.tooltip-item {
color: var(--ant-color-text-secondary);
display: flex;
justify-content: space-between;
align-items: center;
.tooltip-item-title {
margin-right: 2px;
}
.tooltip-value {
margin-left: 10px;
color: var(--ant-color-text);
}
}
}
+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
// This file is generated by Umi automatically
// DO NOT CHANGE IT MANUALLY!
import GpustackLogo from '@/assets/images/gpustack-logo.png';
import React from 'react';
const LogoIcon: React.FC = () => {
return (
<img
src="https://www.seal.io/Public/Uploads/uploadfile/images/20230825/a1logo1244.svg"
alt="logo"
style={{ height: 24 }}
/>
);
return <img src={GpustackLogo} alt="logo" style={{ height: 24 }} />;
};
export default LogoIcon;
+2 -2
View File
@@ -2,7 +2,7 @@ export default {
'dashboard.title': 'Dashboard',
'dashboard.workers': 'Workers',
'dashboard.models': 'Models',
'dashboard.totalgpus': 'Total GPUs',
'dashboard.totalgpus': 'GPUs',
'dashboard.allocategpus': 'Allocated GPUs',
'dashboard.instances': 'Instances',
'dashboard.systemload': 'System Load',
@@ -21,5 +21,5 @@ export default {
'dashboard.activeModels': 'Active Models',
'dashboard.runninginstances': 'Running Instances',
'dashboard.activeModels.name': 'Model Name',
'dashboard.allocatevram': 'Allocated VRAM'
'dashboard.allocatevram': 'Allocated VRAM / RAM'
};
+3 -3
View File
@@ -1,8 +1,8 @@
export default {
'dashboard.title': '概览',
'dashboard.workers': '节点',
'dashboard.workers': 'Workers',
'dashboard.models': '模型',
'dashboard.totalgpus': 'GPU 数量',
'dashboard.totalgpus': 'GPUs',
'dashboard.allocategpus': '已分配 GPU 数量',
'dashboard.instances': '实例',
'dashboard.systemload': '系统负载',
@@ -21,5 +21,5 @@ export default {
'dashboard.activeModels': '活跃模型',
'dashboard.activeModels.name': '模型名称',
'dashboard.runninginstances': '运行实例',
'dashboard.allocatevram': '已分配显存'
'dashboard.allocatevram': '已分配显存 / 内存'
};
+9 -10
View File
@@ -240,7 +240,7 @@ const Models: React.FC = () => {
}}
>
<Column
title={intl.formatMessage({ id: 'apikeys.table.name' })}
title={intl.formatMessage({ id: 'common.table.name' })}
dataIndex="name"
key="name"
width={400}
@@ -249,6 +249,14 @@ const Models: React.FC = () => {
}}
/>
<Column
title={intl.formatMessage({ id: 'apikeys.form.expiretime' })}
dataIndex="expires_at"
key="expiration"
render={(text, record) => {
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
}}
/>
<Column
title={intl.formatMessage({ id: 'common.table.createTime' })}
dataIndex="created_at"
@@ -261,14 +269,6 @@ const Models: React.FC = () => {
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
}}
/>
<Column
title={intl.formatMessage({ id: 'apikeys.form.expiretime' })}
dataIndex="expires_at"
key="expiration"
render={(text, record) => {
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
}}
/>
<Column
title={intl.formatMessage({ id: 'common.table.operation' })}
key="operation"
@@ -281,7 +281,6 @@ const Models: React.FC = () => {
<Button
onClick={() => handleDelete(record)}
size="small"
type="primary"
danger
icon={<DeleteOutlined></DeleteOutlined>}
></Button>
@@ -72,7 +72,7 @@ const ActiveTable = () => {
const data = useContext(DashboardContext).active_models || [];
const modelColumns = [
{
title: intl.formatMessage({ id: 'dashboard.activeModels.name' }),
title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'name',
key: 'name'
},
@@ -86,9 +86,16 @@ const ActiveTable = () => {
// },
{
title: intl.formatMessage({ id: 'dashboard.allocatevram' }),
dataIndex: 'allocate_gpu_memory_utilization',
key: 'gpu_memory_utilization',
render: (text: any, record: any) => <span>{convertFileSize(text)}</span>
dataIndex: 'resource_claim.memory',
key: 'gpu_memory',
render: (text: any, record: any) => {
return (
<span>
{convertFileSize(record.resource_claim?.gpu_memory || 0)} /{' '}
{convertFileSize(record.resource_claim?.memory || 0)}
</span>
);
}
},
{
title: intl.formatMessage({ id: 'dashboard.runninginstances' }),
@@ -1,11 +1,12 @@
:local(.card-body) {
box-shadow: none !important;
:global(.ant-card-body) {
height: 110px;
display: flex;
justify-content: space-around;
// box-shadow: var(--box-shadow-base);
border-radius: var(--ant-border-radius-lg);
// border: 1px solid var(--ant-color-border);
border: 1px solid var(--color-border-1);
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ const renderCardItem = (data: {
const { label, value, bgColor } = data;
return (
<Card
bordered={true}
bordered={false}
style={{ background: bgColor }}
className={styles['card-body']}
>
@@ -1,89 +1,174 @@
import LineChart from '@/components/charts/line-chart';
import Chart from '@/components/echarts/chart';
import { getLocale, useIntl } from '@umijs/max';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useCallback, useContext, useEffect, useState } from 'react';
import { DashboardContext } from '../config/dashboard-context';
const chartColorMap = {
tickLineColor: 'rgba(217,217,217,0.5)',
axislabelColor: 'rgba(0, 0, 0, 0.4)'
};
const TypeKeyMap = {
cpu: {
label: 'CPU',
type: 'CPU',
intl: false,
color:
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
color: 'rgba(250, 173, 20,.8)'
},
memory: {
label: 'dashboard.memory',
type: 'Memory',
intl: true,
color:
'linear-gradient(90deg,rgba(249, 248, 113,.8) 0%,rgba(255, 199, 92,0.7) 100%)'
color: 'rgba(114, 46, 209,.8)'
},
gpu: {
label: 'GPU',
type: 'GPU',
intl: false,
color: 'rgba(84, 204, 152,0.8)'
color: 'rgba(84, 204, 152,.8)'
},
gpu_memory: {
label: 'dashboard.vram',
type: 'VRAM',
intl: true,
color:
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgba(0, 168, 143,.7) 100%)'
color: 'rgba(255, 107, 179, 80%)'
}
};
const option = {
title: {
text: ''
},
legend: {
itemWidth: 8,
itemHeight: 8,
data: []
},
grid: {
left: 0,
right: 20,
bottom: 20,
containLabel: true
},
tooltip: {
trigger: 'axis',
formatter(params: any) {
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
params.forEach((item: any) => {
result += `<span class="tooltip-item">
<span class="tooltip-item-name">
<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${item.color};"></span>
<span class="tooltip-title">${item.seriesName}</span>:
</span>
<span class="tooltip-value">${item.data.value}</span>
</span>`;
});
return `<div class="tooltip-wrapper">${result}</div>`;
}
},
xAxis: {
type: 'category',
boundaryGap: true,
axisTick: {
show: true,
lineStyle: {
color: chartColorMap.tickLineColor
}
},
axisLabel: {
color: chartColorMap.axislabelColor,
// fontFamily: 'unset',
fontSize: 12
},
axisLine: {
show: false
},
data: []
},
yAxis: {
max: 100,
min: 0,
splitLine: {
show: true,
lineStyle: {
type: 'dashed'
}
},
axisLabel: {
color: chartColorMap.axislabelColor,
// fontFamily: 'unset',
fontSize: 12
},
axisTick: {
show: false
},
type: 'value'
},
series: []
};
const UtilizationOvertime: React.FC = () => {
const intl = useIntl();
const locale = getLocale();
const [dataOptions, setDataOptions] = useState<any>(option);
const data = useContext(DashboardContext)?.system_load?.history || {};
const [result, setResult] = useState<
{ time: string; value: number; type: string }[]
>([]);
const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory'];
const sliderConfig = {
y: false,
x: {
style: {
selectionFill: 'rgb(84, 204, 152)',
selectionFillOpacity: 0.1,
handleIconFill: 'rgb(84, 204, 152)',
handleIconFillOpacity: 0.15,
handleIconStrokeOpacity: 0,
sparklineType: 'line',
sparkline: true
},
sparkline: true
}
};
const labelFormatter = (value: any) => {
return `${value}%`;
};
const generateData = useCallback(() => {
const list: { value: number; time: string; type: string }[] = [];
_.each(typeList, (type: any) => {
const dataList = _.map(_.get(data, type, []), (item: any) => {
const value = _.round(_.get(item, 'value', 0), 1);
const time = dayjs(item.timestamp * 1000).format('HH:mm:ss');
const itemtype = _.get(TypeKeyMap, [type, 'intl'], false)
? intl.formatMessage({
id: _.get(TypeKeyMap, [type, 'label'], '')
})
: _.get(TypeKeyMap, [type, 'label'], '');
return {
value,
time,
type: itemtype,
color: 'red'
};
});
list.push(...dataList);
const legendData: string[] = [];
const xAxisData: string[] = [];
let list: { value: number; time: string; type: string }[] = [];
list = _.map(typeList, (item: string) => {
const itemConfig = _.get(TypeKeyMap, item, {});
console.log('itemConfig', itemConfig);
const name = itemConfig.intl
? intl.formatMessage({ id: itemConfig.label })
: itemConfig.label;
legendData.push(name);
const itemDataList = _.get(data, item, []);
return {
type: 'line',
smooth: true,
showSymbol: false,
itemStyle: {
color: itemConfig.color
},
lineStyle: {
width: 1.5,
opacity: 0.7,
color: itemConfig.color
},
name: name,
data: _.map(itemDataList, (item: any) => {
xAxisData.push(dayjs(item.timestamp * 1000).format('HH:mm:ss'));
return {
time: dayjs(item.timestamp * 1000).format('HH:mm:ss'),
value: _.round(_.get(item, 'value', 0), 1)
};
})
};
});
setDataOptions({
...option,
legend: {
...option.legend,
data: legendData
},
xAxis: {
...option.xAxis,
data: _.uniq(xAxisData)
},
series: list
});
setResult(list);
}, [data, locale]);
useEffect(() => {
@@ -92,13 +177,7 @@ const UtilizationOvertime: React.FC = () => {
return (
<>
<LineChart
data={result}
locale={locale}
height={390}
labelFormatter={labelFormatter}
slider={sliderConfig}
/>
<Chart height={390} options={dataOptions} width="100%"></Chart>
</>
);
};
+102 -84
View File
@@ -1,7 +1,7 @@
import CardWrapper from '@/components/card-wrapper';
import AreaChart from '@/components/charts/area';
import ColumnBar from '@/components/charts/column-bar';
import HBar from '@/components/charts/h-bar';
import BarChart from '@/components/echarts/bar-chart';
import HBar from '@/components/echarts/h-bar';
import LineChart from '@/components/echarts/line-chart';
import PageTools from '@/components/page-tools';
import breakpoints from '@/config/breakpoints';
import useWindowResize from '@/hooks/use-window-resize';
@@ -13,7 +13,14 @@ import _ from 'lodash';
import { useContext, useEffect, useState } from 'react';
import { DashboardContext } from '../config/dashboard-context';
const { RangePicker } = DatePicker;
const baseColorMap = {
baseL2: 'rgba(13,171,219,0.8)',
baseL1: 'rgba(0,34,255,0.8)',
base: 'rgba(0,123,255,0.8)',
baseR1: 'rgba(0,255,233,0.8)',
baseR2: 'rgba(48,0,255,0.8)',
baseR3: 'rgba(85,167,255,0.8)'
};
const times = [
'june 1',
'june 2',
@@ -94,15 +101,15 @@ const tokenUsage = TokensData.map((val, i) => {
const getCurrentMonthDays = () => {
const now = dayjs();
const firstDayOfMonth = now.startOf('month');
const dateRange = [];
let currentDate = firstDayOfMonth;
const daysInMonth = now.daysInMonth();
const year = dayjs().year();
const month = dayjs().month() + 1;
while (currentDate.isBefore(now) || currentDate.isSame(now, 'day')) {
dateRange.push(currentDate.format('YYYY-MM-DD'));
currentDate = currentDate.add(1, 'day');
const dates = [];
for (let day = 1; day <= daysInMonth; day++) {
dates.push(dayjs(`${year}-${month}-${day}`).format('YYYY-MM-DD'));
}
return dateRange;
return dates;
};
const Usage = () => {
@@ -110,42 +117,64 @@ const Usage = () => {
const { size } = useWindowResize();
const [paddingRight, setPaddingRight] = useState<string>('20px');
const [requestData, setRequestData] = useState<
{ time: string; value: number }[]
{
name: string;
color: string;
areaStyle: any;
data: { time: string; value: number }[];
}[]
>([]);
const currentMonthDays = getCurrentMonthDays();
const [tokenData, setTokenData] = useState<{ time: string; value: number }[]>(
[]
);
const [userData, setUserData] = useState<{ name: string; value: number }[]>(
[]
);
const [dateRange, setDateRange] = useState<string[]>(getCurrentMonthDays());
const [xAxisData, setXAxisData] = useState<string[]>(currentMonthDays);
const [dateRange, setDateRange] = useState<string[]>(currentMonthDays);
const [topUserList, setTopUseList] = useState<string[]>([]);
const data = useContext(DashboardContext)?.model_usage || {};
console.log('dateRange', dateRange);
const handleSelectDate = (dateString: string) => {};
const generateData = () => {
const requestList: { time: string; value: number }[] = [];
const tokenList: {
time: string;
value: number;
const requestList: {
name: string;
color: string;
}[] = [];
const userList: {
name: string;
value: number;
type: string;
color: string;
}[] = [];
areaStyle: any;
data: { time: string; value: number }[];
} = {
name: 'API Request',
areaStyle: {},
color: baseColorMap.base,
data: []
};
_.each(data.api_request_history, (item: any) => {
requestList.push({
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
value: item.value
});
});
const completionData: any = {
name: 'completion_token',
color: baseColorMap.base,
data: []
};
const prompData: any = {
name: 'prompt_token',
color: baseColorMap.baseR3,
data: []
};
const topUserPrompt: any = {
name: 'prompt_token',
color: baseColorMap.baseR3,
data: []
};
const topUserCompletion: any = {
name: 'completion_token',
color: baseColorMap.base,
data: []
};
const users: string[] = [];
_.each(dateRange, (date: string) => {
// tokens data
@@ -153,18 +182,14 @@ const Usage = () => {
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
});
if (!item) {
tokenList.push({
time: date,
name: 'completion_token',
color: 'rgba(84, 204, 152,0.8)',
completionData.data.push({
titme: date,
value: 0
});
} else {
tokenList.push({
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
name: 'completion_token',
color: 'rgba(84, 204, 152,0.8)',
value: item.value
completionData.data.push({
value: item.value,
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD')
});
}
@@ -172,57 +197,58 @@ const Usage = () => {
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
});
if (!promptItem) {
tokenList.push({
time: date,
name: 'prompt_token',
color: 'rgba(0, 170, 173, 0.8)',
value: 0
prompData.data.push({
value: 0,
time: date
});
} else {
tokenList.push({
time: dayjs(promptItem.timestamp * 1000).format('YYYY-MM-DD'),
name: 'prompt_token',
color: 'rgba(0, 170, 173, 0.8)',
value: promptItem.value
prompData.data.push({
value: promptItem.value,
time: dayjs(promptItem.timestamp * 1000).format('YYYY-MM-DD')
});
}
// api request data
// ============== api request data =================
const requestItem = _.find(data.api_request_history, (item: any) => {
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
});
if (!requestItem) {
requestList.push({
requestList.data.push({
time: date,
value: 0
});
} else {
requestList.push({
requestList.data.push({
time: dayjs(requestItem.timestamp * 1000).format('YYYY-MM-DD'),
value: requestItem.value
});
}
});
// ========== top users ============
_.each(data.top_users, (item: any) => {
userList.push({
users.push(item.username);
topUserPrompt.data.push({
name: item.username,
type: 'completion_token',
color: 'rgba(84, 204, 152,0.8)',
value: item.completion_token_count
});
userList.push({
name: item.username,
type: 'prompt_token',
color: 'rgba(0, 170, 173, 0.8)',
value: item.prompt_token_count
});
topUserCompletion.data.push({
name: item.username,
value: item.completion_token_count
});
});
setRequestData(requestList);
setTokenData(tokenList);
setUserData(userList);
console.log('usage=============', {
requestList,
xAxisData
});
setRequestData([requestList]);
setUserData([topUserCompletion, topUserPrompt]);
setTopUseList(users);
setTokenData([completionData, prompData]);
};
const labelFormatter = (v: any) => {
@@ -270,28 +296,25 @@ const Usage = () => {
<CardWrapper style={{ width: '100%' }}>
<Row style={{ width: '100%' }}>
<Col span={12}>
<AreaChart
<LineChart
title={intl.formatMessage({ id: 'dashboard.apirequest' })}
data={requestData}
seriesData={requestData}
xAxisData={xAxisData}
xField="time"
yField="value"
height={360}
color={baseColorMap.baseR3}
labelFormatter={labelFormatter}
></AreaChart>
></LineChart>
</Col>
<Col span={12}>
<ColumnBar
<BarChart
title={intl.formatMessage({ id: 'dashboard.tokens' })}
data={tokenData}
group={false}
colorField="name"
stack={true}
xField="time"
legend={false}
yField="value"
seriesData={tokenData}
xAxisData={xAxisData}
height={360}
labelFormatter={labelFormatter}
></ColumnBar>
></BarChart>
</Col>
</Row>
</CardWrapper>
@@ -300,13 +323,8 @@ const Usage = () => {
<CardWrapper>
<HBar
title={intl.formatMessage({ id: 'dashboard.topusers' })}
data={userData}
stack={true}
legend={false}
showYAxis={false}
colorField="type"
xField="name"
yField="value"
seriesData={userData}
xAxisData={topUserList}
height={360}
></HBar>
</CardWrapper>
+1 -1
View File
@@ -100,7 +100,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
const handleOnSearchRepo = async (text: string) => {
try {
const params = {
q: `${text} gguf`,
q: `${text}`,
type: 'model'
};
const res = await callHuggingfaceQuickSearch(params);
+6 -5
View File
@@ -162,9 +162,6 @@ const Models: React.FC = () => {
_.each(list, (data: any) => {
updateChunkedList(data);
});
if (!dataSource.length) {
handleFilter();
}
};
const createModelsChunkRequest = () => {
@@ -367,7 +364,10 @@ const Models: React.FC = () => {
</Col>
<Col span={4}>
<span style={{ paddingLeft: '22px' }}>
<span
style={{ paddingLeft: '68px' }}
className="flex justify-center"
>
{item.state && (
<StatusTag
download={
@@ -478,7 +478,7 @@ const Models: React.FC = () => {
}}
>
<SealColumn
title={intl.formatMessage({ id: 'models.table.name' })}
title={intl.formatMessage({ id: 'common.table.name' })}
dataIndex="name"
key="name"
width={400}
@@ -494,6 +494,7 @@ const Models: React.FC = () => {
title={intl.formatMessage({ id: 'models.form.replicas' })}
dataIndex="replicas"
key="replicas"
align="center"
span={4}
/>
<SealColumn
@@ -39,13 +39,6 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
},
{ enabled: !disabled }
);
useHotkeys(
HotKeys.CREATE.join(','),
(e) => {
onNewMessage();
},
{ enabled: !disabled }
);
return (
<div className="chat-footer">
@@ -59,7 +52,6 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
onClick={onNewMessage}
>
{intl.formatMessage({ id: 'playground.newMessage' })}
<span className="m-l-5 opct-7">CTRL+SHIFT+ N</span>
</Button>
<Button
icon={<DeleteOutlined></DeleteOutlined>}
@@ -126,8 +126,13 @@ const MessageList: React.FC<MessageProps> = (props) => {
}
};
const handleClear = () => {
const headItem = _.get(messageList, '0');
setMessageList(headItem ? [headItem] : []);
setMessageList([
{
role: Roles.User,
content: '',
uid: messageId.current + 1
}
]);
};
const handleView = () => {
@@ -3,7 +3,7 @@ import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import { INPUT_WIDTH } from '@/constants';
import { useIntl } from '@umijs/max';
import { Button, Form, InputNumber, Slider } from 'antd';
import { Form, InputNumber, Slider } from 'antd';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { queryModelsList } from '../apis';
@@ -149,9 +149,6 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
</Form.Item>
<h3 className="m-b-20 m-l-10 flex-between flex-center">
<span>{intl.formatMessage({ id: 'playground.parameters' })}</span>
<Button size="small" onClick={handleResetParams}>
{intl.formatMessage({ id: 'common.button.reset' })}
</Button>
</h3>
<Form.Item<ParamsSettingsFormProps>
name="temperature"
+84 -63
View File
@@ -1,4 +1,3 @@
import CardWrapper from '@/components/card-wrapper';
import FormButtons from '@/components/form-buttons';
import SealInput from '@/components/seal-form/seal-input';
import { PasswordReg } from '@/config';
@@ -18,11 +17,12 @@ const Profile: React.FC = () => {
const [form] = Form.useForm();
const intl = useIntl();
const newpasswordValue = Form.useWatch('new_password', form);
const handleOnFinish = async (values: any) => {
try {
await updatePassword(values);
await updatePassword({
new_password: values.new_password,
current_password: values.current_password
});
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {}
};
@@ -43,20 +43,14 @@ const Profile: React.FC = () => {
}}
extra={[]}
>
<CardWrapper
style={{
padding: 'var(--layout-content-inlinepadding)',
width: 'max-content'
}}
<Form
style={{ width: '524px' }}
name="profileForm"
form={form}
onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed}
>
<Form
style={{ width: '524px' }}
name="profileForm"
form={form}
onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed}
>
{/* <Form.Item<ProfileProps>
{/* <Form.Item<ProfileProps>
name="username"
rules={[
{
@@ -76,53 +70,80 @@ const Profile: React.FC = () => {
style={{ width: INPUT_WIDTH.default }}
></SealInput.Input>
</Form.Item> */}
<Form.Item<ProfileProps>
name="current_password"
rules={[
{
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({
id: 'users.form.currentpassword'
})
}
)
<Form.Item<ProfileProps>
name="current_password"
rules={[
{
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({
id: 'users.form.currentpassword'
})
}
)
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item<ProfileProps>
name="new_password"
rules={[
{
required: true,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.newpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item
name="confirm_password"
dependencies={['new_password']}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'users.password.confirm.empty'
})
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('new_password') === value) {
return Promise.resolve();
}
return Promise.reject(
new Error(
intl.formatMessage({ id: 'users.password.confirm.error' })
)
);
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item<ProfileProps>
name="new_password"
rules={[
{
required: true,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
}
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.form.newpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<FormButtons
htmlType="submit"
onCancel={handleCancel}
showCancel={false}
></FormButtons>
</Form>
</CardWrapper>
})
]}
>
<SealInput.Password
label={intl.formatMessage({ id: 'users.password.confirm' })}
/>
</Form.Item>
<FormButtons
htmlType="submit"
onCancel={handleCancel}
showCancel={false}
></FormButtons>
</Form>
</PageContainer>
</StrictMode>
);
+2 -2
View File
@@ -146,7 +146,7 @@ const Models: React.FC = () => {
}}
>
<Column
title={intl.formatMessage({ id: 'resources.table.hostname' })}
title={intl.formatMessage({ id: 'common.table.name' })}
dataIndex="hostname"
key="hostname"
/>
@@ -211,7 +211,7 @@ const Models: React.FC = () => {
key="GPU"
render={(text, record: ListItem) => {
return (
<span className="flex-column">
<span className="flex-column flex-gap-2">
{record?.status?.gpu_devices.map((item, index) => {
return (
<span className="flex-center" key={index}>
+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 { PageAction, PasswordReg } from '@/config';
import { PageActionType } from '@/config/types';
import {
SyncOutlined,
UserOutlined,
UserSwitchOutlined
} from '@ant-design/icons';
import { UserOutlined, UserSwitchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Form, Modal, Select } from 'antd';
import { useEffect } from 'react';
@@ -32,14 +28,7 @@ const AddModal: React.FC<AddModalProps> = ({
}) => {
const [form] = Form.useForm();
const intl = useIntl();
const suffix = (
<SyncOutlined
style={{
fontSize: 16,
color: '#1677ff'
}}
/>
);
const initFormValue = () => {
if (action === PageAction.EDIT && open) {
form.setFieldsValue({
+5 -19
View File
@@ -113,7 +113,6 @@ const Models: React.FC = () => {
};
const handleModalOk = async (data: FormData) => {
console.log('handleModalOk');
const params = {
...data,
is_admin: data.is_admin === 'admin'
@@ -131,7 +130,7 @@ const Models: React.FC = () => {
}
fetchData();
setOpenAddModal(false);
message.success('');
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {
setOpenAddModal(false);
}
@@ -261,22 +260,9 @@ const Models: React.FC = () => {
}}
>
<Column
title={intl.formatMessage({ id: 'users.table.username' })}
title={intl.formatMessage({ id: 'common.table.name' })}
dataIndex="username"
key="name"
width={200}
/>
<Column
title={intl.formatMessage({ id: 'common.table.createTime' })}
dataIndex="created_at"
key="createTime"
defaultSortOrder="descend"
sortOrder={sortOrder}
showSorterTooltip={false}
sorter={true}
render={(text, record) => {
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
}}
/>
<Column
title={intl.formatMessage({ id: 'users.table.role' })}
@@ -301,9 +287,9 @@ const Models: React.FC = () => {
}}
/>
<Column
title={intl.formatMessage({ id: 'common.table.updateTime' })}
dataIndex="updated_at"
key="updateTime"
title={intl.formatMessage({ id: 'common.table.createTime' })}
dataIndex="created_at"
key="createTime"
defaultSortOrder="descend"
sortOrder={sortOrder}
showSorterTooltip={false}