diff --git a/src/pages/usage/components/resource-filter-bar.tsx b/src/pages/usage/components/resource-filter-bar.tsx index 3bc528bc..79db1f1c 100644 --- a/src/pages/usage/components/resource-filter-bar.tsx +++ b/src/pages/usage/components/resource-filter-bar.tsx @@ -27,6 +27,7 @@ interface SelectOption { value: number; label: string; deleted?: boolean; + isCurrent?: boolean; } // Optional per-tab entity filter (GPU instance on the GPU tab / volume on the @@ -137,20 +138,25 @@ const ResourceFilterBar: React.FC = (props) => { ) : [dayjs().add(-DefaultDateConfig.defaultRange, 'd'), dayjs()]; - const userOptionRender = (option: any) => ( - - {option?.data?.label} - {option?.data?.deleted && ( - - [{intl.formatMessage({ id: 'usage.table.deleted' })}] - - )} + const renderTag = (tag: string) => ( + + [{tag}] ); + const userOptionRender = (option: any) => { + const { data } = option; + return ( + + {data?.label} + {data?.isCurrent && + renderTag(intl.formatMessage({ id: 'usage.user.currentAccount' }))} + {data?.deleted && + renderTag(intl.formatMessage({ id: 'usage.table.deleted' }))} + + ); + }; + return (
diff --git a/src/pages/usage/hooks/use-resource-meta.ts b/src/pages/usage/hooks/use-resource-meta.ts index 5920158b..8572656a 100644 --- a/src/pages/usage/hooks/use-resource-meta.ts +++ b/src/pages/usage/hooks/use-resource-meta.ts @@ -1,3 +1,4 @@ +import { useModel } from '@@/plugin-model'; import { useEffect, useState } from 'react'; import { queryResourceFilterMeta, @@ -8,6 +9,9 @@ export interface SelectOption { value: number; label: string; deleted?: boolean; + // The signed-in user's own entry, sorted first and tagged "[Current Account]" + // in the filter dropdown (matches the Tokens tab). + isCurrent?: boolean; } export interface ResourceMetaOptions { @@ -25,6 +29,26 @@ const EMPTY: ResourceMetaOptions = { const toOptions = (items: ResourceFilterOption[]): SelectOption[] => items.map((i) => ({ value: i.id, label: i.label, deleted: i.deleted })); +// Tag the signed-in user's own entry and sort it first, matching the Tokens +// tab's "[Current Account]" treatment. +const toUserOptions = ( + items: ResourceFilterOption[], + currentUserId?: number +): SelectOption[] => { + const options = items.map((i) => ({ + value: i.id, + label: i.label, + deleted: i.deleted, + isCurrent: currentUserId != null && i.id === currentUserId + })); + if (currentUserId == null) return options; + return options.sort((a, b) => { + if (a.isCurrent) return -1; + if (b.isCurrent) return 1; + return 0; + }); +}; + /** * Loads the resource tabs' filter dropdown sources in one call: * - ``creators`` — "filter by user" (Tokens-tab equivalent of /usage/meta @@ -39,12 +63,14 @@ export default function useResourceMeta( scope: 'self' | 'all' = 'all' ): ResourceMetaOptions { const [meta, setMeta] = useState(EMPTY); + const { initialState } = useModel('@@initialState'); + const currentUserId = initialState?.currentUser?.id; useEffect(() => { queryResourceFilterMeta(scope) .then((res) => setMeta({ - creators: toOptions(res.creators), + creators: toUserOptions(res.creators, currentUserId), instances: toOptions(res.instances), volumes: toOptions(res.volumes) }) @@ -53,7 +79,7 @@ export default function useResourceMeta( // Network/auth errors surface via the global interceptor; leave the // dropdowns empty rather than crashing the tab. }); - }, [scope]); + }, [scope, currentUserId]); return meta; } diff --git a/src/pages/usage/summary-tab/index.tsx b/src/pages/usage/summary-tab/index.tsx index 93ca1c5d..71a01d84 100644 --- a/src/pages/usage/summary-tab/index.tsx +++ b/src/pages/usage/summary-tab/index.tsx @@ -25,6 +25,7 @@ import { useCoolAccents } from '@/hooks/use-cool-colors'; import BarChart from '@/pages/_components/bar-chart'; import PieChart from '@/pages/_components/pie-chart'; import { formatLargeNumber } from '@/utils'; +import { useModel } from '@@/plugin-model'; import { CardWrapper } from '@gpustack/core-ui'; import { useAccess, useIntl } from '@umijs/max'; import { Col, Row } from 'antd'; @@ -210,6 +211,8 @@ const DomainSection: React.FC<{ const SummaryTab: React.FC = () => { const access = useAccess(); const intl = useIntl(); + const { initialState } = useModel('@@initialState'); + const currentUserId = initialState?.currentUser?.id; const t = (id: string) => intl.formatMessage({ id }); // One vivid primary per summary card (Tokens / Compute / Storage). const coolColors = useCoolAccents()(3); @@ -244,16 +247,31 @@ const SummaryTab: React.FC = () => { const userOptions = useMemo(() => { const map = new Map(); resourceUsers.forEach((u) => - map.set(u.value, { value: u.value, label: u.label, deleted: u.deleted }) + map.set(u.value, { + value: u.value, + label: u.label, + deleted: u.deleted, + isCurrent: u.isCurrent + }) ); (tokenMeta?.users || []).forEach((u) => { const id = u.identity.current?.user_id; if (id != null && !map.has(id)) { - map.set(id, { value: id, label: u.label }); + map.set(id, { + value: id, + label: u.label, + isCurrent: currentUserId != null && id === currentUserId + }); } }); - return Array.from(map.values()); - }, [resourceUsers, tokenMeta]); + // Sort the signed-in user first, tagged "[Current Account]" (matches the + // Tokens tab), regardless of which source it came from. + return Array.from(map.values()).sort((a, b) => { + if (a.isCurrent) return -1; + if (b.isCurrent) return 1; + return 0; + }); + }, [resourceUsers, tokenMeta, currentUserId]); // user id → the identity object the token series filters by. Built from the // token meta so the trend's ``users`` filter carries the real identity.