fix: table list action

This commit is contained in:
jialin
2024-07-01 15:35:48 +08:00
parent 018c8cd208
commit 0f9123ee4f
21 changed files with 316 additions and 169 deletions
+5
View File
@@ -1,3 +1,8 @@
import { atomWithStorage } from 'jotai/utils';
export const userAtom = atomWithStorage<any>('userInfo', null);
export const initialPasswordAtom = atomWithStorage<string>(
'initialPassword',
''
);
+9 -1
View File
@@ -25,7 +25,6 @@ const LineChart: React.FC<LineChartProps> = (props) => {
labelFormatter
} = props;
const config = {
title,
height,
xField: xField || 'time',
yField: yField || 'value',
@@ -50,6 +49,15 @@ const LineChart: React.FC<LineChartProps> = (props) => {
style: {
fill: 'rgba(84, 204, 152,0.8)'
},
title: {
title,
style: {
align: 'center',
titleFontSize: 14,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
},
legend: {
color: {
layout: { justifyContent: 'center' }
+6 -7
View File
@@ -85,15 +85,14 @@ const BarChart: React.FC<BarChartProps> = (props) => {
},
style: {
// fill: (params: any) => {
// return (
// params.color ||
// 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)'
// );
// },
fill: (params: any) => {
return (
params.color ||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)'
);
},
// radiusTopLeft: 12,
// radiusTopRight: 12,
fill: 'rgba(84, 204, 152,0.8)',
align: 'center',
width: 20
}
+8
View File
@@ -0,0 +1,8 @@
export const titleStyle = {
style: {
align: 'center',
titleFontSize: 14,
titleFill: 'rgba(0,0,0,0.88)',
titleFontWeight: 500
}
};
@@ -0,0 +1,61 @@
import { MoreOutlined } from '@ant-design/icons';
import { Button, Dropdown, Tooltip, type MenuProps } from 'antd';
import _ from 'lodash';
interface DropdownButtonsProps {
items: MenuProps['items'];
size?: 'small' | 'middle' | 'large';
onSelect: (val: any, item?: any) => void;
}
const DropdownButtons: React.FC<DropdownButtonsProps> = ({
items,
size = 'small',
onSelect
}) => {
const handleMenuClick = (item: any) => {
console.log('menu click', item.key);
const selectItem = _.find(items, { key: item.key });
onSelect(item.key, selectItem);
};
const handleButtonClick = (e: any) => {
const headItem = _.head(items);
onSelect(headItem.key, headItem);
};
if (!items?.length) {
return null;
}
return (
<>
{items?.length === 1 ? (
<Button
icon={_.get(items, '0.icon')}
size={size}
onClick={handleButtonClick}
></Button>
) : (
<Dropdown.Button
menu={{
items: _.tail(items),
onClick: handleMenuClick
}}
buttonsRender={([leftButton, rightButton]) => [
<Tooltip title={_.head(items)?.label} key="leftButton">
<Button
onClick={handleButtonClick}
size={size}
icon={_.head(items)?.icon}
></Button>
</Tooltip>,
<Button icon={<MoreOutlined />} size={size} key="menu"></Button>
]}
></Dropdown.Button>
)}
</>
);
};
export default DropdownButtons;
@@ -39,7 +39,6 @@ const SealInputNumber: React.FC<InputNumberProps & SealFormItemProps> = (
};
const handleChange = (e: any) => {
e.target.value = e.target.value?.trim?.();
props.onChange?.(e);
};
@@ -49,7 +48,6 @@ const SealInputNumber: React.FC<InputNumberProps & SealFormItemProps> = (
};
const handleOnBlur = (e: any) => {
e.target.value = e.target.value?.trim?.();
if (!inputRef.current?.value) {
setIsFocus(false);
props.onBlur?.(e);
+5 -4
View File
@@ -6,19 +6,20 @@ export default {
'dashboard.allocategpus': 'Allocated GPUs',
'dashboard.instances': 'Instances',
'dashboard.systemload': 'System Load',
'dashboard.memory': 'Memory',
'dashboard.memory': 'RAM',
'dashboard.disk': 'Storage',
'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': 'CPU Utilization',
'dashboard.memoryutilization': 'Memory Utilization',
'dashboard.memoryutilization': 'RAM Utilization',
'dashboard.diskutilization': 'Storage Utilization',
'dashboard.vramutilization': 'VRAM Utilization',
'dashboard.gpuutilization': 'GPU Utilization',
'dashboard.usage': 'Usage',
'dashboard.apirequest': 'API Request',
'dashboard.tokens': 'Tokens',
'dashboard.tokens': 'Token Usage',
'dashboard.topusers': 'Top Users',
'dashboard.activeModels': 'Active Models',
'dashboard.runninginstances': 'Running Instances',
'dashboard.activeModels.name': 'Model Name'
'dashboard.activeModels.name': 'Model Name',
'dashboard.allocatevram': 'Allocated VRAM'
};
+2 -2
View File
@@ -4,12 +4,12 @@ export default {
'resources.table.hostname': 'Hostname',
'resources.table.ip': 'IP',
'resources.table.cpu': 'CPU',
'resources.table.memory': 'Memory',
'resources.table.memory': 'RAM',
'resources.table.gpu': 'GPU',
'resources.table.disk': 'Storage',
'resources.table.vram': 'VRAM',
'resources.table.index': 'Index',
'resources.table.workername': 'Wroker Name',
'resources.table.workername': 'Worker Name',
'resources.table.vender': 'Vender',
'resources.table.temperature': 'Temperature',
'resources.table.core': 'Core',
+3 -2
View File
@@ -16,9 +16,10 @@ export default {
'dashboard.gpuutilization': 'GPU 利用率',
'dashboard.usage': '使用量',
'dashboard.apirequest': 'API 请求',
'dashboard.tokens': 'Tokens',
'dashboard.tokens': 'Token 使用量',
'dashboard.topusers': '用户排行',
'dashboard.activeModels': '活跃模型',
'dashboard.activeModels.name': '模型名称',
'dashboard.runninginstances': '运行实例'
'dashboard.runninginstances': '运行实例',
'dashboard.allocatevram': '已分配显存'
};
+13 -16
View File
@@ -1,8 +1,7 @@
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import { convertFileSize } from '@/utils';
import { useIntl } from '@umijs/max';
import { Col, Row, Table } from 'antd';
import _ from 'lodash';
import { useContext } from 'react';
import { DashboardContext } from '../config/dashboard-context';
@@ -77,21 +76,19 @@ const ActiveTable = () => {
dataIndex: 'name',
key: 'name'
},
// {
// title: intl.formatMessage({ id: 'dashboard.gpuutilization' }),
// dataIndex: 'gpu_utilization',
// key: 'gpu_utilization',
// render: (text: any, record: any) => (
// <ProgressBar percent={_.round(text, 0)}></ProgressBar>
// )
// },
{
title: intl.formatMessage({ id: 'dashboard.gpuutilization' }),
dataIndex: 'gpu_utilization',
key: 'gpu_utilization',
render: (text: any, record: any) => (
<ProgressBar percent={_.round(text, 0)}></ProgressBar>
)
},
{
title: intl.formatMessage({ id: 'dashboard.vramutilization' }),
dataIndex: 'gpu_memory_utilization',
title: intl.formatMessage({ id: 'dashboard.allocatevram' }),
dataIndex: 'allocate_gpu_memory_utilization',
key: 'gpu_memory_utilization',
render: (text: any, record: any) => (
<ProgressBar percent={_.round(text, 0)}></ProgressBar>
)
render: (text: any, record: any) => <span>{convertFileSize(text)}</span>
},
{
title: intl.formatMessage({ id: 'dashboard.runninginstances' }),
@@ -99,7 +96,7 @@ const ActiveTable = () => {
key: 'instance_count'
},
{
title: 'Tokens',
title: intl.formatMessage({ id: 'dashboard.tokens' }),
dataIndex: 'token_count',
key: 'token_count'
}
+3 -3
View File
@@ -57,9 +57,9 @@ const Overview: React.FC = () => {
<Col
xs={{ flex: '100%' }}
sm={{ flex: '50%' }}
md={{ flex: '30%' }}
lg={{ flex: '20%' }}
xl={{ flex: '20%' }}
md={{ flex: '50%' }}
lg={{ flex: '25%' }}
xl={{ flex: '25%' }}
key={config.key}
>
{renderCardItem({
@@ -67,7 +67,7 @@ const UtilizationOvertime: React.FC = () => {
const list: { value: number; time: string; type: string }[] = [];
_.each(typeList, (type: any) => {
const dataList = _.map(_.get(data, type, []), (item: any) => {
const value = _.get(item, 'value', 0);
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({
+72 -15
View File
@@ -92,6 +92,19 @@ const tokenUsage = TokensData.map((val, i) => {
};
});
const getCurrentMonthDays = () => {
const now = dayjs();
const firstDayOfMonth = now.startOf('month');
const dateRange = [];
let currentDate = firstDayOfMonth;
while (currentDate.isBefore(now) || currentDate.isSame(now, 'day')) {
dateRange.push(currentDate.format('YYYY-MM-DD'));
currentDate = currentDate.add(1, 'day');
}
return dateRange;
};
const Usage = () => {
const intl = useIntl();
const { size } = useWindowResize();
@@ -105,8 +118,11 @@ const Usage = () => {
const [userData, setUserData] = useState<{ name: string; value: number }[]>(
[]
);
const [dateRange, setDateRange] = useState<string[]>(getCurrentMonthDays());
const data = useContext(DashboardContext)?.model_usage || {};
console.log('dateRange', dateRange);
const handleSelectDate = (dateString: string) => {};
const generateData = () => {
@@ -126,26 +142,67 @@ const Usage = () => {
_.each(data.api_request_history, (item: any) => {
requestList.push({
time: dayjs(item.timestamp * 1000).format('YYYY-MM'),
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'),
name: 'completion_token',
color: 'rgba(84, 204, 152,0.8)',
value: item.value
_.each(dateRange, (date: string) => {
// tokens data
const item = _.find(data.completion_token_history, (item: any) => {
return dayjs(item.timestamp * 1000).format('YYYY-MM-DD') === date;
});
});
_.each(data.prompt_token_history, (item: any) => {
tokenList.push({
time: dayjs(item.timestamp * 1000).format('YYYY-MM'),
name: 'prompt_token',
color: 'rgba(0, 170, 173, 0.8)',
value: item.value
if (!item) {
tokenList.push({
time: date,
name: 'completion_token',
color: 'rgba(84, 204, 152,0.8)',
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
});
}
const promptItem = _.find(data.prompt_token_history, (item: any) => {
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
});
} 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
});
}
// 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({
time: date,
value: 0
});
} else {
requestList.push({
time: dayjs(requestItem.timestamp * 1000).format('YYYY-MM-DD'),
value: requestItem.value
});
}
});
_.each(data.top_users, (item: any) => {
@@ -190,7 +247,7 @@ const Usage = () => {
</span>
}
right={
<RangePicker
<DatePicker
onChange={handleSelectDate}
style={{ width: 300 }}
picker="month"
+6 -6
View File
@@ -11,12 +11,12 @@ export const overviewConfigs = [
backgroundColor: 'var(--color-white-1)'
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
},
{
key: 'allocatedGpus',
label: 'dashboard.allocategpus',
backgroundColor: 'var(--color-white-1)'
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
},
// {
// key: 'allocatedGpus',
// label: 'dashboard.allocategpus',
// backgroundColor: 'var(--color-white-1)'
// // backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
// },
{
key: 'model_count',
label: 'dashboard.models',
+19
View File
@@ -1,4 +1,5 @@
import { StatusMaps } from '@/config';
import { EditOutlined } from '@ant-design/icons';
export const ollamaModelOptions = [
{ label: 'llama3', value: 'llama3' },
@@ -20,3 +21,21 @@ export const modelSourceMap = {
export const status: any = {
Running: StatusMaps.success
};
export const ActionList = [
{
label: 'common.button.edit',
key: 'edit',
icon: EditOutlined
},
{
label: 'models.openinplayground',
key: 'chat',
icon: EditOutlined
},
{
label: 'common.button.delete',
key: 'delete',
icon: EditOutlined
}
];
+1
View File
@@ -31,6 +31,7 @@ export interface ModelInstanceListItem {
gpu_index: number;
pid: number;
port: number;
name: string;
state: string;
download_progress: number;
model_id: number;
+64 -79
View File
@@ -1,3 +1,4 @@
import DropdownButtons from '@/components/drop-down-buttons';
import PageTools from '@/components/page-tools';
import SealTable from '@/components/seal-table';
import RowChildren from '@/components/seal-table/components/row-children';
@@ -24,17 +25,7 @@ import {
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
import {
Button,
Col,
Input,
Modal,
Row,
Space,
Tag,
Tooltip,
message
} from 'antd';
import { Button, Col, Input, Modal, Row, Space, message } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
@@ -79,6 +70,38 @@ const Models: React.FC = () => {
);
const [currentInstanceUrl, setCurrentInstanceUrl] = useState<string>('');
const ActionList = [
{
label: intl.formatMessage({ id: 'common.button.edit' }),
key: 'edit',
icon: <EditOutlined />
},
{
label: intl.formatMessage({ id: 'models.openinplayground' }),
key: 'chat',
icon: <WechatWorkOutlined />
},
{
label: intl.formatMessage({ id: 'common.button.delete' }),
key: 'delete',
danger: true,
icon: <DeleteOutlined />
}
];
const childActionList = [
{
label: intl.formatMessage({ id: 'common.button.viewlog' }),
key: 'viewlog',
icon: <FieldTimeOutlined />
},
{
label: intl.formatMessage({ id: 'common.button.delete' }),
key: 'delete',
danger: true,
icon: <DeleteOutlined />
}
];
const chunkRequedtRef = useRef<any>();
const timer = useRef<any>();
let axiosToken = createAxiosToken();
@@ -370,6 +393,27 @@ const Models: React.FC = () => {
setTitle(intl.formatMessage({ id: 'models.title.edit' }));
};
const handleSelect = (val: any, row: ListItem) => {
if (val === 'edit') {
handleEdit(row);
}
if (val === 'chat') {
handleOpenPlayGround(row);
}
if (val === 'delete') {
handleDelete(row);
}
};
const handleChildSelect = (val: any, row: ModelInstanceListItem) => {
if (val === 'delete') {
handleDeleteInstace(row);
}
if (val === 'viewlog') {
handleViewLogs(row);
}
};
useEffect(() => {
// fetchData();
createModelsChunkRequest();
@@ -394,10 +438,7 @@ const Models: React.FC = () => {
>
<RowChildren key={`${item.id}_row`}>
<Row style={{ width: '100%' }} align="middle">
<Col span={6}>
<Tag>{item.gpu_index}</Tag>
{item.worker_ip}:{item.port}
</Col>
<Col span={6}>{item.name}</Col>
<Col span={4}>
<span>{item.huggingface_filename}</span>
</Col>
@@ -423,33 +464,10 @@ const Models: React.FC = () => {
</span>
</Col>
<Col span={5}>
{hoverChildIndex === `${item.id}-${index}` && (
<Space size={20}>
<Tooltip
title={intl.formatMessage({
id: 'common.button.delete'
})}
>
<Button
size="small"
danger
onClick={() => handleDeleteInstace(item)}
icon={<DeleteOutlined></DeleteOutlined>}
></Button>
</Tooltip>
<Tooltip
title={intl.formatMessage({
id: 'common.button.viewlog'
})}
>
<Button
size="small"
onClick={() => handleViewLogs(item)}
icon={<FieldTimeOutlined />}
></Button>
</Tooltip>
</Space>
)}
<DropdownButtons
items={childActionList}
onSelect={(val) => handleChildSelect(val, item)}
></DropdownButtons>
</Col>
</Row>
</RowChildren>
@@ -570,43 +588,10 @@ const Models: React.FC = () => {
key="operation"
render={(text, record) => {
return !record.transition ? (
<Space size={20}>
<Tooltip
title={intl.formatMessage({
id: 'common.button.edit'
})}
>
<Button
size="small"
type="primary"
onClick={() => handleEdit(record)}
icon={<EditOutlined></EditOutlined>}
></Button>
</Tooltip>
<Tooltip
title={intl.formatMessage({
id: 'models.openinplayground'
})}
>
<Button
size="small"
type="primary"
onClick={() => handleOpenPlayGround(record)}
icon={<WechatWorkOutlined />}
></Button>
</Tooltip>
<Tooltip
title={intl.formatMessage({ id: 'common.button.delete' })}
>
<Button
size="small"
type="primary"
danger
onClick={() => handleDelete(record)}
icon={<DeleteOutlined></DeleteOutlined>}
></Button>
</Tooltip>
</Space>
<DropdownButtons
items={ActionList}
onSelect={(val) => handleSelect(val, record)}
></DropdownButtons>
) : null;
}}
/>
+3 -1
View File
@@ -1,5 +1,5 @@
import LogoIcon from '@/assets/images/logo.png';
import { userAtom } from '@/atoms/user';
import { initialPasswordAtom, userAtom } from '@/atoms/user';
import SealInput from '@/components/seal-form/seal-input';
import { GlobalOutlined, LockOutlined, UserOutlined } from '@ant-design/icons';
import { SelectLang, history, useIntl, useModel } from '@umijs/max';
@@ -26,6 +26,7 @@ const renderLogo = () => {
};
const LoginForm = () => {
const [userInfo, setUserInfo] = useAtom(userAtom);
const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom);
const { initialState, setInitialState } = useModel('@@initialState');
const { globalState, setGlobalState } = useModel('global');
const intl = useIntl();
@@ -62,6 +63,7 @@ const LoginForm = () => {
userInfo
});
setUserInfo(userInfo);
setInitialPassword(values.password);
if (!userInfo?.require_password_change) {
gotoDefaultPage(userInfo);
}
+6 -4
View File
@@ -1,4 +1,4 @@
import { userAtom } from '@/atoms/user';
import { initialPasswordAtom, userAtom } from '@/atoms/user';
import SealInput from '@/components/seal-form/seal-input';
import { PasswordReg } from '@/config';
import { GlobalOutlined, LockOutlined } from '@ant-design/icons';
@@ -12,6 +12,7 @@ const PasswordForm: React.FC = () => {
const [form] = Form.useForm();
const [userInfo, setUserInfo] = useAtom(userAtom);
const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom);
const gotoDefaultPage = (userInfo: any) => {
const pathname =
userInfo && userInfo?.is_admin ? '/dashboard' : '/playground';
@@ -23,13 +24,14 @@ const PasswordForm: React.FC = () => {
try {
await updatePassword({
new_password: values.new_password,
current_password: values.current_password
current_password: initialPassword
});
await setUserInfo({
...userInfo,
require_password_change: false
});
setInitialPassword('');
gotoDefaultPage(userInfo);
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {
@@ -63,7 +65,7 @@ const PasswordForm: React.FC = () => {
{intl.formatMessage({ id: 'users.password.modify.description' })}
</span>
</h2>
{/*
<Form.Item
name="current_password"
rules={[
@@ -82,7 +84,7 @@ const PasswordForm: React.FC = () => {
prefix={<LockOutlined />}
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
/>
</Form.Item>
</Form.Item> */}
<Form.Item
name="new_password"
rules={[
+27 -24
View File
@@ -1,3 +1,4 @@
import DropdownButtons from '@/components/drop-down-buttons';
import PageTools from '@/components/page-tools';
import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types';
@@ -14,7 +15,7 @@ import {
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Button, Input, Modal, Space, Table, Tooltip, message } from 'antd';
import { Button, Input, Modal, Space, Table, message } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useEffect, useState } from 'react';
@@ -44,6 +45,19 @@ const Models: React.FC = () => {
query: ''
});
const ActionList = [
{
key: 'edit',
label: intl.formatMessage({ id: 'common.button.edit' }),
icon: <EditOutlined></EditOutlined>
},
{
key: 'delete',
danger: true,
label: intl.formatMessage({ id: 'common.button.delete' }),
icon: <DeleteOutlined></DeleteOutlined>
}
];
const fetchData = async () => {
setLoading(true);
try {
@@ -172,6 +186,14 @@ const Models: React.FC = () => {
setTitle(intl.formatMessage({ id: 'users.form.edit' }));
};
const handleSelect = (val: any, row: ListItem) => {
if (val === 'edit') {
handleEditUser(row);
} else if (val === 'delete') {
handleDelete(row);
}
};
useEffect(() => {
fetchData();
}, [queryParams]);
@@ -296,29 +318,10 @@ const Models: React.FC = () => {
width={200}
render={(text, record: ListItem) => {
return (
<Space size={20}>
<Tooltip
title={intl.formatMessage({ id: 'common.button.edit' })}
>
<Button
size="small"
type="primary"
onClick={() => handleEditUser(record)}
icon={<EditOutlined></EditOutlined>}
></Button>
</Tooltip>
<Tooltip
title={intl.formatMessage({ id: 'common.button.delete' })}
>
<Button
size="small"
type="primary"
danger
onClick={() => handleDelete(record)}
icon={<DeleteOutlined></DeleteOutlined>}
></Button>
</Tooltip>
</Space>
<DropdownButtons
items={ActionList}
onSelect={(val) => handleSelect(val, record)}
></DropdownButtons>
);
}}
/>
+2 -2
View File
@@ -20,9 +20,9 @@ export const handleBatchRequest = async (
};
export const convertFileSize = (sizeInBytes: number, prec?: number) => {
const precision = prec ?? 2;
const precision = prec ?? 1;
if (!sizeInBytes) {
return '0 B';
return '0';
}
if (sizeInBytes < 1024) {
return `${sizeInBytes.toFixed(precision)} B`;