feat(usage): mark current account first in resource tab user filters

Apply the Tokens tab's current-user-first ordering and [Current Account] marker to the Summary/Storage/Instances tab user filters.
This commit is contained in:
jialin
2026-07-09 15:17:53 +08:00
committed by jialin
parent cd4b1b8629
commit c691df25c0
3 changed files with 67 additions and 17 deletions
@@ -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<ResourceFilterBarProps> = (props) => {
)
: [dayjs().add(-DefaultDateConfig.defaultRange, 'd'), dayjs()];
const userOptionRender = (option: any) => (
<span className="flex-center gap-4">
<AutoTooltip ghost>{option?.data?.label}</AutoTooltip>
{option?.data?.deleted && (
<span
className="text-tertiary"
style={{ fontSize: 12, marginRight: 4 }}
>
[{intl.formatMessage({ id: 'usage.table.deleted' })}]
</span>
)}
const renderTag = (tag: string) => (
<span className="text-tertiary" style={{ fontSize: 12, marginRight: 4 }}>
[{tag}]
</span>
);
const userOptionRender = (option: any) => {
const { data } = option;
return (
<span className="flex-center gap-4">
<AutoTooltip ghost>{data?.label}</AutoTooltip>
{data?.isCurrent &&
renderTag(intl.formatMessage({ id: 'usage.user.currentAccount' }))}
{data?.deleted &&
renderTag(intl.formatMessage({ id: 'usage.table.deleted' }))}
</span>
);
};
return (
<div className={FilterBarCss.wrapper}>
<div className={FilterBarCss.filters}>
+28 -2
View File
@@ -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<ResourceMetaOptions>(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;
}
+22 -4
View File
@@ -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<SelectOption[]>(() => {
const map = new Map<number, SelectOption>();
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.