From 23a761226acb73874e34849f3896fe84929f854b Mon Sep 17 00:00:00 2001 From: jialin Date: Sat, 20 Dec 2025 12:20:06 +0800 Subject: [PATCH] feat: table list sorter --- .../seal-table/components/header.tsx | 8 +- .../seal-table/components/table-header.tsx | 38 +++++++-- src/components/seal-table/index.tsx | 14 +++- src/components/seal-table/types.ts | 32 +++++-- src/components/seal-table/use-sorter.ts | 84 +++++++++++++++++++ src/config/settings.ts | 7 ++ src/hooks/use-table-fetch.ts | 29 ++++--- src/hooks/use-table-sort.ts | 29 +++++++ src/pages/api-keys/hooks/use-keys-columns.tsx | 21 ++--- src/pages/api-keys/index.tsx | 8 +- src/pages/cluster-management/clusters.tsx | 14 +++- src/pages/cluster-management/credentials.tsx | 5 +- .../hooks/use-cluster-columns.tsx | 10 +++ .../hooks/use-credential-columns.tsx | 12 +-- src/pages/llmodels/components/table-list.tsx | 23 +++-- .../llmodels/hooks/use-models-columns.tsx | 39 ++++----- src/pages/llmodels/index.tsx | 26 +++++- src/pages/resources/components/gpus.tsx | 3 +- .../resources/components/model-files.tsx | 7 +- src/pages/resources/components/workers.tsx | 8 +- .../resources/hooks/use-files-columns.tsx | 28 ++++--- src/pages/resources/hooks/use-gpu-columns.tsx | 43 ++++++---- .../resources/hooks/use-worker-columns.tsx | 29 ++++--- src/pages/users/hooks/use-users-columns.tsx | 30 ++++--- src/pages/users/index.tsx | 8 +- 25 files changed, 417 insertions(+), 138 deletions(-) create mode 100644 src/components/seal-table/use-sorter.ts diff --git a/src/components/seal-table/components/header.tsx b/src/components/seal-table/components/header.tsx index 6bbd0d13..b4cd81a9 100644 --- a/src/components/seal-table/components/header.tsx +++ b/src/components/seal-table/components/header.tsx @@ -1,15 +1,16 @@ import { Col, Row } from 'antd'; import React from 'react'; -import { SealColumnProps } from '../types'; +import { OnSortFn, SealColumnProps } from '../types'; import TableHeader from './table-header'; interface HeaderProps { columns: SealColumnProps[]; - onSort?: (dataIndex: string, order: any) => void; + sortDirections?: ('ascend' | 'descend' | null)[]; + onSort?: OnSortFn; } const Header: React.FC = (props) => { - const { onSort } = props; + const { onSort, sortDirections } = props; return ( @@ -32,6 +33,7 @@ const Header: React.FC = (props) => { sorter={sorter} dataIndex={dataIndex} sortOrder={sortOrder} + sortDirections={sortDirections} width={width} defaultSortOrder={defaultSortOrder} title={title} diff --git a/src/components/seal-table/components/table-header.tsx b/src/components/seal-table/components/table-header.tsx index 93a82242..03287337 100644 --- a/src/components/seal-table/components/table-header.tsx +++ b/src/components/seal-table/components/table-header.tsx @@ -14,19 +14,39 @@ const TableHeader: React.FC = (props) => { firstCell, lastCell, sortOrder, + sortDirections = ['ascend', 'descend', null], + defaultSortOrder, onSort, - sorter, + sorter = false, width, dataIndex } = props; - const handleOnSort = () => { - if (sortOrder === 'ascend') { - onSort?.(dataIndex, 'descend'); - } else { - onSort?.(dataIndex, 'ascend'); - } + const [currentSortOrder, setCurrentSortOrder] = React.useState< + 'ascend' | 'descend' | null + >(sortOrder || defaultSortOrder || null); + + const getNextSortOrder = (currentOrder: 'ascend' | 'descend' | null) => { + const index = sortDirections.indexOf(currentOrder); + const nextIndex = (index + 1) % sortDirections.length; + return sortDirections[nextIndex]; }; + + const handleOnSort = () => { + setCurrentSortOrder((prev) => { + const next = getNextSortOrder(prev); + onSort?.( + { + columnKey: dataIndex, + field: dataIndex, + order: next + }, + sorter + ); + return next; + }); + }; + return (
= (props) => { diff --git a/src/components/seal-table/index.tsx b/src/components/seal-table/index.tsx index 474bddb1..36fd73b9 100644 --- a/src/components/seal-table/index.tsx +++ b/src/components/seal-table/index.tsx @@ -7,6 +7,7 @@ import HeaderPrefix from './components/header-prefix'; import TableBody from './components/table-body'; import './styles/index.less'; import { SealColumnProps, SealTableProps } from './types'; +import useSorter from './use-sorter'; const Wrapper = styled.div<{ $token: any }>` --ant-table-cell-padding-inline: ${(props) => @@ -32,7 +33,7 @@ const SealTable: React.FC = ( childParentKey, onExpand, onExpandAll, - onSort, + onTableSort, onCell, expandedRowKeys, loading, @@ -43,10 +44,15 @@ const SealTable: React.FC = ( rowSelection, pagination, empty, + sortDirections, renderChildren, loadChildren, loadChildrenAPI } = props; + const { handleOnTableSort } = useSorter({ + onTableSort, + columns + }); const { token } = theme.useToken(); const parsedColumns = useMemo(() => { if (columns) return columns; @@ -156,7 +162,11 @@ const SealTable: React.FC = ( disabled={!props.dataSource?.length} hasColumns={parsedColumns.length > 0} > -
+
void; + export interface CellContentProps { dataIndex: string; render?: (text: any, record: any) => React.ReactNode; @@ -20,7 +29,7 @@ export interface SealColumnProps { span: number; align?: 'left' | 'center' | 'right'; headerStyle?: React.CSSProperties; - sorter?: boolean; + sorter?: boolean | { multiple?: number }; defaultSortOrder?: 'ascend' | 'descend'; editable?: | boolean @@ -34,17 +43,23 @@ export interface SealColumnProps { } export interface TableHeaderProps { - sorter?: boolean; - defaultSortOrder?: 'ascend' | 'descend'; + sorter?: boolean | { multiple?: number }; + sortDirections?: ('ascend' | 'descend' | null)[]; + defaultSortOrder?: 'ascend' | 'descend' | null; sortOrder?: 'ascend' | 'descend' | null; dataIndex: string; - onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void; + onSort?: OnSortFn; title: React.ReactNode; style?: React.CSSProperties; firstCell?: boolean; lastCell?: boolean; align?: 'left' | 'center' | 'right'; width?: number | string; + sortedDataIndexList?: Array<{ + columnKey: string; + field: string; + order: 'ascend' | 'descend' | null; + }>; } export interface RowSelectionProps { @@ -54,7 +69,14 @@ export interface RowSelectionProps { removeSelectedKeys: (rowKeys: React.Key[]) => void; onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => void; } + +export type TableOrder = { + columnKey?: string; + field?: string; + order: 'ascend' | 'descend' | null; +}; export interface SealTableProps { + sortDirections?: ('ascend' | 'descend' | null)[]; columns?: SealColumnProps[]; childParentKey?: string; expandedRowKeys?: React.Key[]; @@ -68,7 +90,7 @@ export interface SealTableProps { loading?: boolean; loadend?: boolean; onCell?: (record: any, dataIndex: string) => void; - onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void; + onTableSort?: (order: TableOrder | Array) => void; onExpand?: (expanded: boolean, record: any, rowKey: any) => void; onExpandAll?: (expanded: boolean) => void; renderChildren?: ( diff --git a/src/components/seal-table/use-sorter.ts b/src/components/seal-table/use-sorter.ts new file mode 100644 index 00000000..86b21dac --- /dev/null +++ b/src/components/seal-table/use-sorter.ts @@ -0,0 +1,84 @@ +import { isBoolean } from 'lodash'; +import { useRef } from 'react'; +import { SealColumnProps, TableOrder } from './types'; + +const initSorterList = (columns: SealColumnProps[]) => { + const list = columns.filter((col) => col.defaultSortOrder); + if (list.length === 0) { + return []; + } + + return list.map((col) => ({ + columnKey: col.key || col.dataIndex, + field: col.dataIndex, + order: col.defaultSortOrder || null + })); +}; + +export default function useSorter(options: { + onTableSort?: (TableOrder: TableOrder | Array) => void; + columns?: SealColumnProps[]; +}) { + const { onTableSort, columns } = options; + const sorterListRef = useRef>( + initSorterList(columns || []) + ); + + const handleOnTableSort = ( + order: TableOrder, + sorter: boolean | { multiple?: number } + ) => { + let currentOrder: TableOrder = { ...order }; + + if (order.order === null) { + currentOrder = { + columnKey: undefined, + field: undefined, + order: null + }; + } + // single column sort + if (isBoolean(sorter)) { + sorterListRef.current = currentOrder; + + onTableSort?.(sorterListRef.current); + return; + } + + // multi column sort + if (sorter && typeof sorter === 'object' && sorter.multiple) { + if (!Array.isArray(sorterListRef.current)) { + if ( + sorterListRef.current.columnKey === currentOrder.columnKey || + sorterListRef.current.field === currentOrder.field + ) { + // remove the sorter if order is null + sorterListRef.current = { + ...currentOrder + }; + } else { + sorterListRef.current = [sorterListRef.current, { ...currentOrder }]; + } + } else if (Array.isArray(sorterListRef.current)) { + const existingIndex = sorterListRef.current.findIndex( + (item) => + item.columnKey === order.columnKey || item.field === order.field + ); + + if (existingIndex !== -1) { + sorterListRef.current.splice(existingIndex, 1); + sorterListRef.current.push({ ...currentOrder }); + } else { + sorterListRef.current.push({ ...currentOrder }); + } + } + } + + onTableSort?.(sorterListRef.current); + }; + + return { + sorterListRef, + handleOnTableSort + }; +} diff --git a/src/config/settings.ts b/src/config/settings.ts index e5814673..da93e20f 100644 --- a/src/config/settings.ts +++ b/src/config/settings.ts @@ -9,3 +9,10 @@ export const DEFAULT_ENTER_PAGE = { export const GPUSTACK_API_BASE_URL = 'v2'; export const OPENAI_COMPATIBLE = 'v1'; + +type SortDirection = 'ascend' | 'descend' | null; +export const TABLE_SORT_DIRECTIONS: SortDirection[] = [ + 'ascend', + 'descend', + null +]; diff --git a/src/hooks/use-table-fetch.ts b/src/hooks/use-table-fetch.ts index 6ab059cf..4f7384b2 100644 --- a/src/hooks/use-table-fetch.ts +++ b/src/hooks/use-table-fetch.ts @@ -1,3 +1,4 @@ +import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import useSetChunkRequest from '@/hooks/use-chunk-request'; import useTableRowSelection from '@/hooks/use-table-row-selection'; import useUpdateChunkedList from '@/hooks/use-update-chunk-list'; @@ -5,6 +6,7 @@ import { handleBatchRequest } from '@/utils'; import _ from 'lodash'; import qs from 'query-string'; import { useEffect, useRef, useState } from 'react'; +import { useTableMultiSort } from './use-table-sort'; type EventsType = 'CREATE' | 'UPDATE' | 'DELETE' | 'INSERT'; @@ -51,13 +53,7 @@ export default function useTableFetch( const chunkRequedtRef = useRef(null); const modalRef = useRef(null); const rowSelection = useTableRowSelection(); - const [sortOrder, setSortOrder] = useState<{ - order?: 'ascend' | 'descend' | null; - columnKey?: string; - }>({ - order: null, - columnKey: undefined - }); + const { sortOrder, handleMultiSortChange } = useTableMultiSort(); // for skeleton loading const [extraStatus, setExtraStatus] = useState>({ @@ -81,6 +77,7 @@ export default function useTableFetch( page: 1, perPage: 10, search: '', + sort_by: '', ...defaultQueryParams }); @@ -106,7 +103,6 @@ export default function useTableFetch( const updateHandler = (list: any) => { _.each(list, (data: any) => { - console.log('list================:', list); updateChunkedList(data); }); }; @@ -238,9 +234,19 @@ export default function useTableFetch( }; const handleTableChange = (pagination: any, filters: any, sorter: any) => { - setSortOrder({ - order: sorter.order, - columnKey: sorter.columnKey || sorter.field + const sortKeys = handleMultiSortChange(sorter); + setQueryParams((pre: any) => { + return { + ...pre, + sort_by: sortKeys.join(',') + }; + }); + fetchData({ + query: { + ...queryParams, + page: 1, + sort_by: sortKeys.join(',') + } }); }; @@ -337,6 +343,7 @@ export default function useTableFetch( queryParams, modalRef, extraStatus, + TABLE_SORT_DIRECTIONS, setQueryParams, handleDelete, handleDeleteBatch, diff --git a/src/hooks/use-table-sort.ts b/src/hooks/use-table-sort.ts index f802ee54..04abe925 100644 --- a/src/hooks/use-table-sort.ts +++ b/src/hooks/use-table-sort.ts @@ -23,3 +23,32 @@ export default function useTableSort({ setSortOrder: handleSortChange }; } + +type orderType = { + columnKey?: string; + field?: string; + order: SortOrder; +}; + +export function useTableMultiSort() { + const [sortOrder, setSortOrder] = useState([]); + + const handleMultiSortChange = (order: orderType | orderType[]) => { + const sortOrders = Array.isArray(order) ? order : [order]; + const sortOrderMap: string[] = []; + sortOrders.forEach((item) => { + const key = item.columnKey || item.field; + if (key) { + sortOrderMap.push(item.order === 'descend' ? `-${key}` : key); + } + }); + + setSortOrder(sortOrderMap); + return sortOrderMap; + }; + + return { + sortOrder, + handleMultiSortChange + }; +} diff --git a/src/pages/api-keys/hooks/use-keys-columns.tsx b/src/pages/api-keys/hooks/use-keys-columns.tsx index 0c229a95..4aecd379 100644 --- a/src/pages/api-keys/hooks/use-keys-columns.tsx +++ b/src/pages/api-keys/hooks/use-keys-columns.tsx @@ -10,10 +10,7 @@ import { ListItem } from '../config/types'; interface ColumnsHookProps { handleSelect: (val: string, record: ListItem) => void; - sortOrder: { - order?: 'ascend' | 'descend' | null; - columnKey?: string; - }; + sortOrder: string; } const actionList: Global.ActionItem[] = [ @@ -42,6 +39,9 @@ const useModelsColumns = ({ title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', key: 'name', + sorter: { + multiple: 1 + }, render: (text: string, record: ListItem) => ( {text} @@ -52,6 +52,9 @@ const useModelsColumns = ({ title: intl.formatMessage({ id: 'apikeys.form.expiretime' }), dataIndex: 'expires_at', key: 'expires_at', + sorter: { + multiple: 2 + }, render: (text: string, record: ListItem) => ( {text @@ -93,11 +96,9 @@ const useModelsColumns = ({ dataIndex: 'created_at', key: 'created_at', defaultSortOrder: 'descend', - sortOrder: - sortOrder.order && sortOrder.columnKey === 'created_at' - ? sortOrder.order - : null, - sorter: false, + sorter: { + multiple: 3 + }, ellipsis: { showTitle: false }, @@ -120,7 +121,7 @@ const useModelsColumns = ({ ) } ]; - }, [sortOrder, intl, handleSelect]); + }, [intl, handleSelect]); }; export default useModelsColumns; diff --git a/src/pages/api-keys/index.tsx b/src/pages/api-keys/index.tsx index e35f67c4..bd778a89 100644 --- a/src/pages/api-keys/index.tsx +++ b/src/pages/api-keys/index.tsx @@ -17,6 +17,7 @@ import useKeysColumns from './hooks/use-keys-columns'; const APIKeys: React.FC = () => { const { + TABLE_SORT_DIRECTIONS, dataSource, rowSelection, queryParams, @@ -32,7 +33,10 @@ const APIKeys: React.FC = () => { } = useTableFetch({ fetchAPI: queryApisKeysList, deleteAPI: deleteApisKey, - contentForDelete: 'apikeys.table.apikeys' + contentForDelete: 'apikeys.table.apikeys', + defaultQueryParams: { + sort_by: '-created_at' + } }); const intl = useIntl(); @@ -139,7 +143,7 @@ const APIKeys: React.FC = () => { dataSource={dataSource.dataList} rowSelection={rowSelection} loading={dataSource.loading} - sortDirections={['ascend', 'descend', 'ascend']} + sortDirections={TABLE_SORT_DIRECTIONS} rowKey="id" onChange={handleTableChange} pagination={{ diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index 1099d324..f358f07d 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -4,7 +4,9 @@ import IconFont from '@/components/icon-font'; import { FilterBar } from '@/components/page-tools'; import SealTable from '@/components/seal-table'; import TableContext from '@/components/seal-table/table-context'; +import { TableOrder } from '@/components/seal-table/types'; import { PageAction } from '@/config'; +import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import type { PageActionType } from '@/config/types'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useTableFetch from '@/hooks/use-table-fetch'; @@ -54,6 +56,7 @@ const Clusters: React.FC = () => { rowSelection, queryParams, modalRef, + handleTableChange, handleDelete, handleDeleteBatch, fetchData, @@ -65,7 +68,10 @@ const Clusters: React.FC = () => { deleteAPI: deleteCluster, watch: true, API: CLUSTERS_API, - contentForDelete: 'menu.clusterManagement.clusters' + contentForDelete: 'menu.clusterManagement.clusters', + defaultQueryParams: { + sort_by: '-created_at' + } }); const { watchDataList: allWorkerPoolList } = useWatchList(WORKER_POOLS_API); const [expandAtom] = useAtom(expandKeysAtom); @@ -227,6 +233,10 @@ const Clusters: React.FC = () => { } ); + const handleOnSortChange = (order: TableOrder | Array) => { + handleTableChange({}, {}, order); + }; + const setDisableExpand = (row: ClusterListItem) => { return ( row.provider !== ProviderValueMap.DigitalOcean || @@ -314,10 +324,12 @@ const Clusters: React.FC = () => { { } = useTableFetch({ fetchAPI: queryCredentialList, deleteAPI: deleteCredential, - contentForDelete: 'menu.clusterManagement.credentials' + contentForDelete: 'menu.clusterManagement.credentials', + defaultQueryParams: { + sort_by: '-created_at' + } }); const [isFromCluster, setIsFromCluster] = useAtom(fromClusterCreationAtom); const intl = useIntl(); diff --git a/src/pages/cluster-management/hooks/use-cluster-columns.tsx b/src/pages/cluster-management/hooks/use-cluster-columns.tsx index 87f3768b..d46fea3c 100644 --- a/src/pages/cluster-management/hooks/use-cluster-columns.tsx +++ b/src/pages/cluster-management/hooks/use-cluster-columns.tsx @@ -33,6 +33,9 @@ const useClusterColumns = ( { title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', + sorter: { + multiple: 1 + }, span: 3, render: (text: string, record: ClusterListItem) => ( {text} @@ -41,6 +44,9 @@ const useClusterColumns = ( { title: intl.formatMessage({ id: 'clusters.table.provider' }), dataIndex: 'provider', + sorter: { + multiple: 2 + }, span: 4, render: (value: string) => ( @@ -86,6 +92,10 @@ const useClusterColumns = ( { title: intl.formatMessage({ id: 'common.table.createTime' }), dataIndex: 'created_at', + defaultSortOrder: 'descend', + sorter: { + multiple: 5 + }, span: 4, render: (value: string) => ( diff --git a/src/pages/cluster-management/hooks/use-credential-columns.tsx b/src/pages/cluster-management/hooks/use-credential-columns.tsx index 7e8294bd..d1ea6d51 100644 --- a/src/pages/cluster-management/hooks/use-credential-columns.tsx +++ b/src/pages/cluster-management/hooks/use-credential-columns.tsx @@ -23,6 +23,9 @@ const useCredentialColumns = ( { title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', + sorter: { + multiple: 1 + }, render: (text: string) => ( {text} @@ -32,6 +35,7 @@ const useCredentialColumns = ( { title: intl.formatMessage({ id: 'clusters.table.provider' }), dataIndex: 'provider', + sorter: false, render: (value: string) => {ProviderLabelMap[value]} }, { @@ -39,11 +43,9 @@ const useCredentialColumns = ( dataIndex: 'created_at', showSorterTooltip: false, defaultSortOrder: 'descend', - sortOrder: - sortOrder.order && sortOrder.columnKey === 'created_at' - ? sortOrder.order - : null, - sorter: false, + sorter: { + multiple: 3 + }, ellipsis: { showTitle: false }, diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index 79a3c78b..f358e148 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -6,7 +6,9 @@ import { PageSize } from '@/components/logs-viewer/config'; import PageTools from '@/components/page-tools'; import BaseSelect from '@/components/seal-form/base/select'; import SealTable from '@/components/seal-table'; +import { TableOrder } from '@/components/seal-table/types'; import { PageAction } from '@/config'; +import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import { PageActionType } from '@/config/types'; import useBodyScroll from '@/hooks/use-body-scroll'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; @@ -75,6 +77,8 @@ interface ModelsProps { handleOnToggleExpandAll: () => void; onStop?: (ids: number[]) => void; onStart?: () => void; + onTableSort?: (order: TableOrder | Array) => void; + sortOrder: string[]; queryParams: { page: number; perPage: number; @@ -114,6 +118,8 @@ const Models: React.FC = ({ handleClusterChange, onStop, onStart, + onTableSort, + sortOrder, deleteIds, dataSource, queryParams, @@ -148,13 +154,6 @@ const Models: React.FC = ({ removeExpandedRowKey, expandedRowKeys } = useExpandedRowKeys(expandAtom); - const [sortOrder, setSortOrder] = useState<{ - columnKey: string; - order: 'ascend' | 'descend' | null; - }>({ - order: null, - columnKey: '' - }); const [apiAccessInfo, setAPIAccessInfo] = useState({ show: false, @@ -219,11 +218,8 @@ const Models: React.FC = ({ currentData.current = data; }; - const handleOnSort = (dataIndex: string, order: any) => { - setSortOrder({ - columnKey: dataIndex, - order: order - }); + const handleOnSort = (order: TableOrder | Array) => { + onTableSort?.(order); }; const handleOnCell = useMemoizedFn(async (record: any, extra: any) => { @@ -710,6 +706,7 @@ const Models: React.FC = ({ = ({ rowKey="id" childParentKey="model_id" expandable={true} - onSort={handleOnSort} + onTableSort={handleOnSort} onCell={handleOnCell} pollingChildren={false} watchChildren={true} diff --git a/src/pages/llmodels/hooks/use-models-columns.tsx b/src/pages/llmodels/hooks/use-models-columns.tsx index a33b1115..95d628e4 100644 --- a/src/pages/llmodels/hooks/use-models-columns.tsx +++ b/src/pages/llmodels/hooks/use-models-columns.tsx @@ -6,7 +6,6 @@ import { OPENAI_COMPATIBLE } from '@/config/settings'; import { QuestionCircleOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Tooltip } from 'antd'; -import type { SortOrder } from 'antd/es/table/interface'; import dayjs from 'dayjs'; import _ from 'lodash'; import { useMemo } from 'react'; @@ -34,10 +33,7 @@ const setModelActionList = (record: any) => { interface ModelsColumnsHookProps { handleSelect: (val: string, record: ListItem) => void; - sortOrder: { - columnKey: string; - order: SortOrder; - }; + sortOrder: string[]; clusterList: Global.BaseOption< number, { provider: string; state: string | number } @@ -57,11 +53,9 @@ const useModelsColumns = ({ title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', key: 'name', - sortOrder: - sortOrder.order && sortOrder.columnKey === 'name' - ? sortOrder.order - : null, - sorter: false, + sorter: { + multiple: 1 + }, span: 5, render: (text: string, record: ListItem) => ( @@ -74,8 +68,11 @@ const useModelsColumns = ({ }, { title: intl.formatMessage({ id: 'clusters.title' }), - dataIndex: 'cluster', - key: 'cluster', + dataIndex: 'cluster_id', + key: 'cluster_id', + sorter: { + multiple: 2 + }, span: 3, render: (text: string, record: ListItem) => ( @@ -90,6 +87,9 @@ const useModelsColumns = ({ title: intl.formatMessage({ id: 'models.form.source' }), dataIndex: 'source', key: 'source', + sorter: { + multiple: 3 + }, span: 5, render: (text: string, record: ListItem) => ( @@ -111,9 +111,12 @@ const useModelsColumns = ({ ), - dataIndex: 'replicas', - key: 'replicas', + dataIndex: 'ready_replicas', + key: 'ready_replicas', align: 'center', + sorter: { + multiple: 4 + }, span: 4, editable: { valueType: 'number', @@ -130,11 +133,9 @@ const useModelsColumns = ({ dataIndex: 'created_at', key: 'created_at', defaultSortOrder: 'descend', - sortOrder: - sortOrder.order && sortOrder.columnKey === 'created_at' - ? sortOrder.order - : null, - sorter: false, + sorter: { + multiple: 5 + }, span: 4, render: (text: number) => ( diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index b19c4068..ea34a03b 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -1,5 +1,7 @@ import TableContext from '@/components/seal-table/table-context'; +import { TableOrder } from '@/components/seal-table/types'; import useSetChunkRequest from '@/hooks/use-chunk-request'; +import { useTableMultiSort } from '@/hooks/use-table-sort'; import useUpdateChunkedList from '@/hooks/use-update-chunk-list'; import { useMemoizedFn } from 'ahooks'; import _ from 'lodash'; @@ -15,6 +17,7 @@ import TableList from './components/table-list'; import { ListItem } from './config/types'; const Models: React.FC = () => { + const { sortOrder, handleMultiSortChange } = useTableMultiSort(); const { setChunkRequest, createAxiosToken } = useSetChunkRequest(); const { setChunkRequest: setModelInstanceChunkRequest } = useSetChunkRequest(); @@ -43,7 +46,8 @@ const Models: React.FC = () => { perPage: 10, search: '', cluster_id: 0, - categories: [] + categories: [], + sort_by: '-created_at' }); const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({ @@ -96,6 +100,7 @@ const Models: React.FC = () => { perPage: number; search: string; categories: any[]; + sort_by: string; }; }) => { const { loadingVal, query } = params || {}; @@ -294,6 +299,23 @@ const Models: React.FC = () => { }); }; + const handleOnSortChange = (order: TableOrder | Array) => { + const sortKeys = handleMultiSortChange(order); + setQueryParams((pre: any) => { + return { + ...pre, + sort_by: sortKeys.join(',') + }; + }); + fetchData({ + query: { + ...queryParams, + page: 1, + sort_by: sortKeys.join(',') + } + }); + }; + useEffect(() => { let timer: any = null; // fetch data first time @@ -397,6 +419,8 @@ const Models: React.FC = () => { onCancelViewLogs={handleOnCancelViewLogs} onStop={handleSearchBySilent} onStart={handleSearchBySilent} + onTableSort={handleOnSortChange} + sortOrder={sortOrder} queryParams={queryParams} loading={dataSource.loading} loadend={dataSource.loadend} diff --git a/src/pages/resources/components/gpus.tsx b/src/pages/resources/components/gpus.tsx index e5ed21eb..353a3108 100644 --- a/src/pages/resources/components/gpus.tsx +++ b/src/pages/resources/components/gpus.tsx @@ -1,5 +1,6 @@ import IconFont from '@/components/icon-font'; import { FilterBar } from '@/components/page-tools'; +import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import useTableFetch from '@/hooks/use-table-fetch'; import NoResult from '@/pages/_components/no-result'; import PageBox from '@/pages/_components/page-box'; @@ -102,7 +103,7 @@ const GPUList: React.FC = () => { { deleteAPI: deleteModelFile, API: MODEL_FILES_API, watch: true, - contentForDelete: 'resources.modelfiles.modelfile' + contentForDelete: 'resources.modelfiles.modelfile', + defaultQueryParams: { + sort_by: '-created_at' + } }); const intl = useIntl(); const { showSuccess } = useAppUtils(); @@ -316,6 +320,7 @@ const ModelFiles = () => {
{ events: ['UPDATE', 'DELETE', 'INSERT'], contentForDelete: 'resources.worker', watch: true, - API: WORKERS_API + API: WORKERS_API, + defaultQueryParams: { + sort_by: '-created_at' + } }); const { TerminalPanel, terminals, handleAddTerminal } = useTerminalTabs(); const { MaintenanceModal, handleStopMaintenance, setOpenStatus } = @@ -270,7 +274,7 @@ const Workers: React.FC = () => {
void; workersList: Global.BaseOption[]; - sortOrder: { - order?: 'ascend' | 'descend' | null; - columnKey?: string; - }; + sortOrder: string[]; }): ColumnsType => { const { workersList, sortOrder, handleSelect } = props; const intl = useIntl(); @@ -268,6 +265,9 @@ const useFilesColumns = (props: { { title: intl.formatMessage({ id: 'models.form.source' }), dataIndex: 'source', + sorter: { + multiple: 1 + }, ellipsis: { showTitle: false }, @@ -285,7 +285,10 @@ const useFilesColumns = (props: { }, { title: intl.formatMessage({ id: 'resources.worker' }), - dataIndex: 'worker_name', + dataIndex: 'worker_id', + sorter: { + multiple: 2 + }, width: '18%', ellipsis: { showTitle: false @@ -309,6 +312,9 @@ const useFilesColumns = (props: { { title: intl.formatMessage({ id: 'resources.modelfiles.form.path' }), dataIndex: 'resolved_paths', + sorter: { + multiple: 3 + }, width: '20%', ellipsis: { showTitle: false @@ -336,15 +342,15 @@ const useFilesColumns = (props: { { title: intl.formatMessage({ id: 'common.table.createTime' }), dataIndex: 'created_at', - sorter: false, + defaultSortOrder: 'descend', + key: 'created_at', + sorter: { + multiple: 4 + }, width: 180, ellipsis: { showTitle: false }, - sortOrder: - sortOrder.order && sortOrder.columnKey === 'created_at' - ? sortOrder.order - : null, render: (text: number) => ( {dayjs(text).format('YYYY-MM-DD HH:mm:ss')} @@ -363,7 +369,7 @@ const useFilesColumns = (props: { ) } ]; - }, [intl, sortOrder, workersList, handleSelect]); + }, [intl, workersList, handleSelect]); }; export default useFilesColumns; diff --git a/src/pages/resources/hooks/use-gpu-columns.tsx b/src/pages/resources/hooks/use-gpu-columns.tsx index a331905e..213245d9 100644 --- a/src/pages/resources/hooks/use-gpu-columns.tsx +++ b/src/pages/resources/hooks/use-gpu-columns.tsx @@ -39,10 +39,7 @@ const useGPUColumns = (props: { loadend: boolean; firstLoad: boolean; clusterList: Global.BaseOption[]; - sortOrder: { - order?: 'ascend' | 'descend' | null; - columnKey?: string; - }; + sortOrder: string[]; }): ColumnsType => { const { clusterList, loadend, firstLoad, sortOrder } = props; const intl = useIntl(); @@ -53,11 +50,9 @@ const useGPUColumns = (props: { title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', width: 240, - sorter: false, - sortOrder: - sortOrder.order && sortOrder.columnKey === 'name' - ? sortOrder.order - : null, + sorter: { + multiple: 1 + }, render: (text: string, record: GPUDeviceItem) => ( {text} @@ -67,11 +62,17 @@ const useGPUColumns = (props: { { title: intl.formatMessage({ id: 'resources.table.index' }), dataIndex: 'index', + sorter: { + multiple: 2 + }, render: (text: string, record: GPUDeviceItem) => {text} }, { title: intl.formatMessage({ id: 'clusters.title' }), dataIndex: 'cluster_id', + sorter: { + multiple: 3 + }, ellipsis: { showTitle: false }, @@ -84,6 +85,9 @@ const useGPUColumns = (props: { { title: intl.formatMessage({ id: 'resources.worker' }), dataIndex: 'worker_name', + sorter: { + multiple: 4 + }, ellipsis: { showTitle: false }, @@ -93,7 +97,10 @@ const useGPUColumns = (props: { }, { title: intl.formatMessage({ id: 'resources.table.vender' }), - dataIndex: 'vendor' + dataIndex: 'vendor', + sorter: { + multiple: 5 + } }, { title: `${intl.formatMessage({ id: 'resources.table.temperature' })} (°C)`, @@ -104,8 +111,11 @@ const useGPUColumns = (props: { }, { title: `${intl.formatMessage({ id: 'resources.table.utilization' })}`, - dataIndex: 'gpuUtil', - key: 'gpuUtil', + dataIndex: 'core.utilization_rate', + key: 'core.utilization_rate', + sorter: { + multiple: 6 + }, render: (text: number, record: GPUDeviceItem) => { return ( <> @@ -122,8 +132,11 @@ const useGPUColumns = (props: { }, { title: intl.formatMessage({ id: 'resources.table.vramutilization' }), - dataIndex: 'VRAM', - key: 'VRAM', + dataIndex: 'memory.utilization_rate', + key: 'memory.utilization_rate', + sorter: { + multiple: 7 + }, render: (text: number, record: GPUDeviceItem, index: number) => { return ( void; }): ColumnsType => { const intl = useIntl(); @@ -277,11 +274,9 @@ const useWorkerColumns = ({ title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', width: 100, - sorter: false, - sortOrder: - sortOrder.order && sortOrder.columnKey === 'name' - ? sortOrder.order - : null, + sorter: { + multiple: 1 + }, render: (text: string) => ( {text} @@ -306,6 +301,9 @@ const useWorkerColumns = ({ { title: intl.formatMessage({ id: 'common.table.status' }), dataIndex: 'state', + sorter: { + multiple: 2 + }, render: (_, record) => ( ( {renderIP(text, record)} @@ -329,7 +330,10 @@ const useWorkerColumns = ({ }, { title: 'CPU', - dataIndex: 'cpu', + dataIndex: 'status.cpu.utilization_rate', + sorter: { + multiple: 4 + }, render: (text: string, record) => statusAvailable(record) ? ( statusAvailable(record) ? ( ( {text} @@ -85,8 +82,11 @@ const useUsersColumns = ({ }, { title: intl.formatMessage({ id: 'users.table.role' }), - dataIndex: 'role', - key: 'role', + dataIndex: 'is_admin', + key: 'is_admin', + sorter: { + multiple: 6 + }, ellipsis: { showTitle: false }, @@ -125,6 +125,9 @@ const useUsersColumns = ({ title: intl.formatMessage({ id: 'users.form.source' }), dataIndex: 'source', key: 'source', + sorter: { + multiple: 3 + }, ellipsis: { showTitle: false }, @@ -138,6 +141,9 @@ const useUsersColumns = ({ title: intl.formatMessage({ id: 'users.table.status' }), dataIndex: 'is_active', key: 'is_active', + sorter: { + multiple: 4 + }, ellipsis: { showTitle: false }, @@ -185,11 +191,9 @@ const useUsersColumns = ({ key: 'created_at', defaultSortOrder: 'descend', showSorterTooltip: false, - sortOrder: - sortOrder.order && sortOrder.columnKey === 'created_at' - ? sortOrder.order - : null, - sorter: false, + sorter: { + multiple: 5 + }, ellipsis: { showTitle: false }, diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 9df9faab..114df130 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -2,6 +2,7 @@ import DeleteModal from '@/components/delete-modal'; import IconFont from '@/components/icon-font'; import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; +import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import type { PageActionType } from '@/config/types'; import useTableFetch from '@/hooks/use-table-fetch'; import { useIntl, useModel } from '@umijs/max'; @@ -38,7 +39,10 @@ const Users: React.FC = () => { } = useTableFetch({ fetchAPI: queryUsersList, deleteAPI: deleteUser, - contentForDelete: 'users.table.user' + contentForDelete: 'users.table.user', + defaultQueryParams: { + sort_by: '-created_at' + } }); const { initialState } = useModel('@@initialState') || {}; @@ -188,7 +192,7 @@ const Users: React.FC = () => { dataSource={dataList} rowSelection={rowSelection} loading={dataSource.loading} - sortDirections={['ascend', 'descend', 'ascend']} + sortDirections={TABLE_SORT_DIRECTIONS} rowKey="id" onChange={handleTableChange} pagination={{