From 0f9123ee4f60a69060ac900688e97c2460896e4b Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 1 Jul 2024 15:35:01 +0800 Subject: [PATCH] fix: table list action --- src/atoms/user.ts | 5 + src/components/charts/area.tsx | 10 +- src/components/charts/column-bar.tsx | 13 +- src/components/charts/config/index.ts | 8 + src/components/drop-down-buttons/index.tsx | 61 ++++++++ src/components/seal-form/input-number.tsx | 2 - src/locales/en-US/dashboard.ts | 9 +- src/locales/en-US/resources.ts | 4 +- src/locales/zh-CN/dashboard.ts | 5 +- .../dashboard/components/active-table.tsx | 29 ++-- src/pages/dashboard/components/over-view.tsx | 6 +- .../components/resource-utilization.tsx | 2 +- src/pages/dashboard/components/usage.tsx | 87 +++++++++-- src/pages/dashboard/config/index.ts | 12 +- src/pages/llmodels/config/index.ts | 19 +++ src/pages/llmodels/config/types.ts | 1 + src/pages/llmodels/index.tsx | 143 ++++++++---------- src/pages/login/components/login-form.tsx | 4 +- src/pages/login/components/password-form.tsx | 10 +- src/pages/users/index.tsx | 51 ++++--- src/utils/index.ts | 4 +- 21 files changed, 316 insertions(+), 169 deletions(-) create mode 100644 src/components/charts/config/index.ts create mode 100644 src/components/drop-down-buttons/index.tsx diff --git a/src/atoms/user.ts b/src/atoms/user.ts index 0aaedc4e..97166f54 100644 --- a/src/atoms/user.ts +++ b/src/atoms/user.ts @@ -1,3 +1,8 @@ import { atomWithStorage } from 'jotai/utils'; export const userAtom = atomWithStorage('userInfo', null); + +export const initialPasswordAtom = atomWithStorage( + 'initialPassword', + '' +); diff --git a/src/components/charts/area.tsx b/src/components/charts/area.tsx index cfa940bd..672e0066 100644 --- a/src/components/charts/area.tsx +++ b/src/components/charts/area.tsx @@ -25,7 +25,6 @@ const LineChart: React.FC = (props) => { labelFormatter } = props; const config = { - title, height, xField: xField || 'time', yField: yField || 'value', @@ -50,6 +49,15 @@ const LineChart: React.FC = (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' } diff --git a/src/components/charts/column-bar.tsx b/src/components/charts/column-bar.tsx index b85b9068..064ea55c 100644 --- a/src/components/charts/column-bar.tsx +++ b/src/components/charts/column-bar.tsx @@ -85,15 +85,14 @@ const BarChart: React.FC = (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 } diff --git a/src/components/charts/config/index.ts b/src/components/charts/config/index.ts new file mode 100644 index 00000000..fbd5fd7c --- /dev/null +++ b/src/components/charts/config/index.ts @@ -0,0 +1,8 @@ +export const titleStyle = { + style: { + align: 'center', + titleFontSize: 14, + titleFill: 'rgba(0,0,0,0.88)', + titleFontWeight: 500 + } +}; diff --git a/src/components/drop-down-buttons/index.tsx b/src/components/drop-down-buttons/index.tsx new file mode 100644 index 00000000..d07e0bb9 --- /dev/null +++ b/src/components/drop-down-buttons/index.tsx @@ -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 = ({ + 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 ? ( + + ) : ( + [ + + + , + + ]} + > + )} + + ); +}; + +export default DropdownButtons; diff --git a/src/components/seal-form/input-number.tsx b/src/components/seal-form/input-number.tsx index defed6bd..8a7c95f5 100644 --- a/src/components/seal-form/input-number.tsx +++ b/src/components/seal-form/input-number.tsx @@ -39,7 +39,6 @@ const SealInputNumber: React.FC = ( }; const handleChange = (e: any) => { - e.target.value = e.target.value?.trim?.(); props.onChange?.(e); }; @@ -49,7 +48,6 @@ const SealInputNumber: React.FC = ( }; const handleOnBlur = (e: any) => { - e.target.value = e.target.value?.trim?.(); if (!inputRef.current?.value) { setIsFocus(false); props.onBlur?.(e); diff --git a/src/locales/en-US/dashboard.ts b/src/locales/en-US/dashboard.ts index bb317cdd..ecd78080 100644 --- a/src/locales/en-US/dashboard.ts +++ b/src/locales/en-US/dashboard.ts @@ -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' }; diff --git a/src/locales/en-US/resources.ts b/src/locales/en-US/resources.ts index 7cc6894c..43b624e0 100644 --- a/src/locales/en-US/resources.ts +++ b/src/locales/en-US/resources.ts @@ -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', diff --git a/src/locales/zh-CN/dashboard.ts b/src/locales/zh-CN/dashboard.ts index c7e62a87..e4b1c011 100644 --- a/src/locales/zh-CN/dashboard.ts +++ b/src/locales/zh-CN/dashboard.ts @@ -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': '已分配显存' }; diff --git a/src/pages/dashboard/components/active-table.tsx b/src/pages/dashboard/components/active-table.tsx index 71ea39cc..fa418e99 100644 --- a/src/pages/dashboard/components/active-table.tsx +++ b/src/pages/dashboard/components/active-table.tsx @@ -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) => ( + // + // ) + // }, { - title: intl.formatMessage({ id: 'dashboard.gpuutilization' }), - dataIndex: 'gpu_utilization', - key: 'gpu_utilization', - render: (text: any, record: any) => ( - - ) - }, - { - 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) => ( - - ) + render: (text: any, record: any) => {convertFileSize(text)} }, { 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' } diff --git a/src/pages/dashboard/components/over-view.tsx b/src/pages/dashboard/components/over-view.tsx index 10289e26..ac4a01d4 100644 --- a/src/pages/dashboard/components/over-view.tsx +++ b/src/pages/dashboard/components/over-view.tsx @@ -57,9 +57,9 @@ const Overview: React.FC = () => { {renderCardItem({ diff --git a/src/pages/dashboard/components/resource-utilization.tsx b/src/pages/dashboard/components/resource-utilization.tsx index 345f5bfb..5295e5f1 100644 --- a/src/pages/dashboard/components/resource-utilization.tsx +++ b/src/pages/dashboard/components/resource-utilization.tsx @@ -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({ diff --git a/src/pages/dashboard/components/usage.tsx b/src/pages/dashboard/components/usage.tsx index aaebd7fd..4db15b9c 100644 --- a/src/pages/dashboard/components/usage.tsx +++ b/src/pages/dashboard/components/usage.tsx @@ -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(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 = () => { } right={ - { ); const [currentInstanceUrl, setCurrentInstanceUrl] = useState(''); + const ActionList = [ + { + label: intl.formatMessage({ id: 'common.button.edit' }), + key: 'edit', + icon: + }, + { + label: intl.formatMessage({ id: 'models.openinplayground' }), + key: 'chat', + icon: + }, + { + label: intl.formatMessage({ id: 'common.button.delete' }), + key: 'delete', + danger: true, + icon: + } + ]; + + const childActionList = [ + { + label: intl.formatMessage({ id: 'common.button.viewlog' }), + key: 'viewlog', + icon: + }, + { + label: intl.formatMessage({ id: 'common.button.delete' }), + key: 'delete', + danger: true, + icon: + } + ]; const chunkRequedtRef = useRef(); const timer = useRef(); 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 = () => { > - - {item.gpu_index} - {item.worker_ip}:{item.port} - + {item.name} {item.huggingface_filename} @@ -423,33 +464,10 @@ const Models: React.FC = () => { - {hoverChildIndex === `${item.id}-${index}` && ( - - - - - - - - - )} + handleChildSelect(val, item)} + > @@ -570,43 +588,10 @@ const Models: React.FC = () => { key="operation" render={(text, record) => { return !record.transition ? ( - - - - - - - - - - - + handleSelect(val, record)} + > ) : null; }} /> diff --git a/src/pages/login/components/login-form.tsx b/src/pages/login/components/login-form.tsx index d9fbea9d..d2abf2a3 100644 --- a/src/pages/login/components/login-form.tsx +++ b/src/pages/login/components/login-form.tsx @@ -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); } diff --git a/src/pages/login/components/password-form.tsx b/src/pages/login/components/password-form.tsx index bb35768f..4c6b2f7c 100644 --- a/src/pages/login/components/password-form.tsx +++ b/src/pages/login/components/password-form.tsx @@ -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' })} - + {/* { prefix={} label={intl.formatMessage({ id: 'users.form.currentpassword' })} /> - + */} { query: '' }); + const ActionList = [ + { + key: 'edit', + label: intl.formatMessage({ id: 'common.button.edit' }), + icon: + }, + { + key: 'delete', + danger: true, + label: intl.formatMessage({ id: 'common.button.delete' }), + icon: + } + ]; 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 ( - - - - - - - - + handleSelect(val, record)} + > ); }} /> diff --git a/src/utils/index.ts b/src/utils/index.ts index 06d8effd..63c571b3 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -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`;