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:
@@ -27,6 +27,7 @@ interface SelectOption {
|
|||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
|
isCurrent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional per-tab entity filter (GPU instance on the GPU tab / volume on the
|
// 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()];
|
: [dayjs().add(-DefaultDateConfig.defaultRange, 'd'), dayjs()];
|
||||||
|
|
||||||
const userOptionRender = (option: any) => (
|
const renderTag = (tag: string) => (
|
||||||
<span className="flex-center gap-4">
|
<span className="text-tertiary" style={{ fontSize: 12, marginRight: 4 }}>
|
||||||
<AutoTooltip ghost>{option?.data?.label}</AutoTooltip>
|
[{tag}]
|
||||||
{option?.data?.deleted && (
|
|
||||||
<span
|
|
||||||
className="text-tertiary"
|
|
||||||
style={{ fontSize: 12, marginRight: 4 }}
|
|
||||||
>
|
|
||||||
[{intl.formatMessage({ id: 'usage.table.deleted' })}]
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
</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 (
|
return (
|
||||||
<div className={FilterBarCss.wrapper}>
|
<div className={FilterBarCss.wrapper}>
|
||||||
<div className={FilterBarCss.filters}>
|
<div className={FilterBarCss.filters}>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
queryResourceFilterMeta,
|
queryResourceFilterMeta,
|
||||||
@@ -8,6 +9,9 @@ export interface SelectOption {
|
|||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
deleted?: boolean;
|
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 {
|
export interface ResourceMetaOptions {
|
||||||
@@ -25,6 +29,26 @@ const EMPTY: ResourceMetaOptions = {
|
|||||||
const toOptions = (items: ResourceFilterOption[]): SelectOption[] =>
|
const toOptions = (items: ResourceFilterOption[]): SelectOption[] =>
|
||||||
items.map((i) => ({ value: i.id, label: i.label, deleted: i.deleted }));
|
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:
|
* Loads the resource tabs' filter dropdown sources in one call:
|
||||||
* - ``creators`` — "filter by user" (Tokens-tab equivalent of /usage/meta
|
* - ``creators`` — "filter by user" (Tokens-tab equivalent of /usage/meta
|
||||||
@@ -39,12 +63,14 @@ export default function useResourceMeta(
|
|||||||
scope: 'self' | 'all' = 'all'
|
scope: 'self' | 'all' = 'all'
|
||||||
): ResourceMetaOptions {
|
): ResourceMetaOptions {
|
||||||
const [meta, setMeta] = useState<ResourceMetaOptions>(EMPTY);
|
const [meta, setMeta] = useState<ResourceMetaOptions>(EMPTY);
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const currentUserId = initialState?.currentUser?.id;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryResourceFilterMeta(scope)
|
queryResourceFilterMeta(scope)
|
||||||
.then((res) =>
|
.then((res) =>
|
||||||
setMeta({
|
setMeta({
|
||||||
creators: toOptions(res.creators),
|
creators: toUserOptions(res.creators, currentUserId),
|
||||||
instances: toOptions(res.instances),
|
instances: toOptions(res.instances),
|
||||||
volumes: toOptions(res.volumes)
|
volumes: toOptions(res.volumes)
|
||||||
})
|
})
|
||||||
@@ -53,7 +79,7 @@ export default function useResourceMeta(
|
|||||||
// Network/auth errors surface via the global interceptor; leave the
|
// Network/auth errors surface via the global interceptor; leave the
|
||||||
// dropdowns empty rather than crashing the tab.
|
// dropdowns empty rather than crashing the tab.
|
||||||
});
|
});
|
||||||
}, [scope]);
|
}, [scope, currentUserId]);
|
||||||
|
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { useCoolAccents } from '@/hooks/use-cool-colors';
|
|||||||
import BarChart from '@/pages/_components/bar-chart';
|
import BarChart from '@/pages/_components/bar-chart';
|
||||||
import PieChart from '@/pages/_components/pie-chart';
|
import PieChart from '@/pages/_components/pie-chart';
|
||||||
import { formatLargeNumber } from '@/utils';
|
import { formatLargeNumber } from '@/utils';
|
||||||
|
import { useModel } from '@@/plugin-model';
|
||||||
import { CardWrapper } from '@gpustack/core-ui';
|
import { CardWrapper } from '@gpustack/core-ui';
|
||||||
import { useAccess, useIntl } from '@umijs/max';
|
import { useAccess, useIntl } from '@umijs/max';
|
||||||
import { Col, Row } from 'antd';
|
import { Col, Row } from 'antd';
|
||||||
@@ -210,6 +211,8 @@ const DomainSection: React.FC<{
|
|||||||
const SummaryTab: React.FC = () => {
|
const SummaryTab: React.FC = () => {
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const currentUserId = initialState?.currentUser?.id;
|
||||||
const t = (id: string) => intl.formatMessage({ id });
|
const t = (id: string) => intl.formatMessage({ id });
|
||||||
// One vivid primary per summary card (Tokens / Compute / Storage).
|
// One vivid primary per summary card (Tokens / Compute / Storage).
|
||||||
const coolColors = useCoolAccents()(3);
|
const coolColors = useCoolAccents()(3);
|
||||||
@@ -244,16 +247,31 @@ const SummaryTab: React.FC = () => {
|
|||||||
const userOptions = useMemo<SelectOption[]>(() => {
|
const userOptions = useMemo<SelectOption[]>(() => {
|
||||||
const map = new Map<number, SelectOption>();
|
const map = new Map<number, SelectOption>();
|
||||||
resourceUsers.forEach((u) =>
|
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) => {
|
(tokenMeta?.users || []).forEach((u) => {
|
||||||
const id = u.identity.current?.user_id;
|
const id = u.identity.current?.user_id;
|
||||||
if (id != null && !map.has(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());
|
// Sort the signed-in user first, tagged "[Current Account]" (matches the
|
||||||
}, [resourceUsers, tokenMeta]);
|
// 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
|
// 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.
|
// token meta so the trend's ``users`` filter carries the real identity.
|
||||||
|
|||||||
Reference in New Issue
Block a user