style(usage): unify name column header + DeletedTag across breakdown tables

This commit is contained in:
jialin
2026-07-09 15:17:53 +08:00
committed by jialin
parent 7d9f68da2d
commit e79d49f2a8
7 changed files with 98 additions and 69 deletions
+13 -5
View File
@@ -64,6 +64,10 @@ export interface ResourceBreakdownItem extends ResourceBreakdownSummary {
volume_name?: string; volume_name?: string;
user_id?: number; user_id?: number;
user_name?: string; user_name?: string;
// The grouped entity (instance / volume / user) no longer exists. The name
// fields keep the clean (stale) name; the tables show a DeletedTag off this
// flag plus the id, matching the Tokens tab.
deleted?: boolean;
// Grouped-trend rows carry the sub-group label (sku / instance / user / …) // Grouped-trend rows carry the sub-group label (sku / instance / user / …)
// alongside ``date`` so the chart can pivot one series per group. // alongside ``date`` so the chart can pivot one series per group.
group?: string; group?: string;
@@ -277,12 +281,16 @@ function flattenItem(
}; };
if (it.date) flat.date = it.date; if (it.date) flat.date = it.date;
const id = it.id ?? undefined; const id = it.id ?? undefined;
// Deleted entities get a "(Deleted)" suffix, matching the Token breakdown. const deleted = !!it.deleted;
const rawKey = it.key ?? undefined; const rawKey = it.key ?? undefined;
const key = it.deleted && rawKey != null ? `${rawKey} (Deleted)` : rawKey; // The chart series legend keeps the "(Deleted)" suffix (a legend can't render
// a tag); the tables show a DeletedTag off ``flat.deleted`` + the id and so
// use the clean name.
const key = deleted && rawKey != null ? `${rawKey} (Deleted)` : rawKey;
// Generic group label — for a compound (date + dim) trend row the key is the // Generic group label — for a compound (date + dim) trend row the key is the
// sub-group value (the switch below targets single-dimension table rows). // sub-group value (the switch below targets single-dimension table rows).
if (rawKey != null) flat.group = key; if (rawKey != null) flat.group = key;
flat.deleted = deleted;
switch (groupBy) { switch (groupBy) {
case 'resource_type': case 'resource_type':
flat.resource_type = key; flat.resource_type = key;
@@ -292,15 +300,15 @@ function flattenItem(
flat.gpu_type = key; flat.gpu_type = key;
break; break;
case 'instance': case 'instance':
flat.instance_name = key; flat.instance_name = rawKey;
flat.instance_id = id; flat.instance_id = id;
break; break;
case 'volume': case 'volume':
flat.volume_name = key; flat.volume_name = rawKey;
flat.volume_id = id; flat.volume_id = id;
break; break;
case 'user': case 'user':
flat.user_name = key; flat.user_name = rawKey;
flat.user_id = id; flat.user_id = id;
break; break;
default: default:
+1 -1
View File
@@ -32,7 +32,7 @@ const DeletedTag: React.FC<DeletedTagProps> = ({ id }) => {
}} }}
> >
{label} {label}
{id != null && ( {!!id && (
<span className="text-tertiary"> <span className="text-tertiary">
<span style={{ margin: '0 2px' }}>·</span>#{id} <span style={{ margin: '0 2px' }}>·</span>#{id}
</span> </span>
+11 -20
View File
@@ -1,15 +1,11 @@
// columns.ts // columns.ts
import { AutoTooltip } from '@gpustack/core-ui'; import { AutoTooltip } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Tag } from 'antd';
import { useMemo } from 'react'; import { useMemo } from 'react';
import DeletedTag from '../components/deleted-tag';
import { BreakdownItem as ListItem } from '../config/types'; import { BreakdownItem as ListItem } from '../config/types';
const useModelsColumns = (): Array<{ const useModelsColumns = () => {
title: string;
dataIndex: string | string[];
key: string;
}> => {
const intl = useIntl(); const intl = useIntl();
return useMemo(() => { return useMemo(() => {
@@ -19,23 +15,18 @@ const useModelsColumns = (): Array<{
dataIndex: ['api_key', 'identity', 'value', 'api_key_name'], dataIndex: ['api_key', 'identity', 'value', 'api_key_name'],
key: 'api_key_name', key: 'api_key_name',
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
<span className="flex items-center"> <span className="flex items-center gap-8">
<AutoTooltip ghost title={<span>{text}</span>}> <AutoTooltip ghost title={<span>{text}</span>}>
<span className="text-primary">{text}</span> <span
className={
record.api_key?.deleted ? 'text-tertiary' : 'text-primary'
}
>
{text}
</span>
</AutoTooltip> </AutoTooltip>
{record.api_key?.deleted && ( {record.api_key?.deleted && (
<Tag <DeletedTag id={record.api_key?.identity?.current?.api_key_id} />
style={{
marginLeft: 8,
borderRadius: 12,
color: 'var(--ant-color-text-tertiary)',
borderColor: 'var(--ant-color-split)',
backgroundColor: 'transparent'
}}
variant="outlined"
>
{intl.formatMessage({ id: 'usage.table.deleted' })}
</Tag>
)} )}
</span> </span>
) )
+10 -15
View File
@@ -2,8 +2,8 @@
import { getGPUStackPlugin } from '@/plugins'; import { getGPUStackPlugin } from '@/plugins';
import { AutoTooltip } from '@gpustack/core-ui'; import { AutoTooltip } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Tag } from 'antd';
import { useMemo } from 'react'; import { useMemo } from 'react';
import DeletedTag from '../components/deleted-tag';
import { BreakdownItem as ListItem } from '../config/types'; import { BreakdownItem as ListItem } from '../config/types';
// Plugin slot: enterprise plugins can contribute extra columns to the // Plugin slot: enterprise plugins can contribute extra columns to the
@@ -51,27 +51,22 @@ const useModelsColumns = (): ColumnDef[] => {
dataIndex: ['route', 'label'], dataIndex: ['route', 'label'],
key: 'route_name', key: 'route_name',
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
<span className="flex items-center"> <span className="flex items-center gap-8">
<AutoTooltip <AutoTooltip
ghost ghost
style={{ maxWidth: 400 }} style={{ maxWidth: 400 }}
title={<span>{text}</span>} title={<span>{text}</span>}
> >
<span className="text-primary">{text}</span> <span
className={
record.route?.deleted ? 'text-tertiary' : 'text-primary'
}
>
{text}
</span>
</AutoTooltip> </AutoTooltip>
{record.route?.deleted && ( {record.route?.deleted && (
<Tag <DeletedTag id={record.route?.identity?.current?.route_id} />
style={{
marginLeft: 8,
borderRadius: 12,
color: 'var(--ant-color-text-tertiary)',
borderColor: 'var(--ant-color-split)',
backgroundColor: 'transparent'
}}
variant="outlined"
>
{intl.formatMessage({ id: 'usage.table.deleted' })}
</Tag>
)} )}
</span> </span>
) )
+11 -20
View File
@@ -1,19 +1,15 @@
// columns.ts // columns.ts
import { AutoTooltip } from '@gpustack/core-ui'; import { AutoTooltip } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Tag } from 'antd';
import { useMemo } from 'react'; import { useMemo } from 'react';
import DeletedTag from '../components/deleted-tag';
import { BreakdownItem as ListItem } from '../config/types'; import { BreakdownItem as ListItem } from '../config/types';
interface ColumnsHookProps { interface ColumnsHookProps {
sortOrder: string[]; sortOrder: string[];
} }
const useModelsColumns = (): Array<{ const useModelsColumns = () => {
title: string;
dataIndex: string | string[];
key: string;
}> => {
const intl = useIntl(); const intl = useIntl();
return useMemo(() => { return useMemo(() => {
@@ -23,27 +19,22 @@ const useModelsColumns = (): Array<{
dataIndex: ['user', 'identity', 'value', 'user_name'], dataIndex: ['user', 'identity', 'value', 'user_name'],
key: 'user_name', key: 'user_name',
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
<span className="flex items-center"> <span className="flex items-center gap-8">
<AutoTooltip <AutoTooltip
ghost ghost
style={{ maxWidth: 400 }} style={{ maxWidth: 400 }}
title={<span>{text}</span>} title={<span>{text}</span>}
> >
<span className="text-primary">{text}</span> <span
className={
record.user?.deleted ? 'text-tertiary' : 'text-primary'
}
>
{text}
</span>
</AutoTooltip> </AutoTooltip>
{record.user?.deleted && ( {record.user?.deleted && (
<Tag <DeletedTag id={record.user?.identity?.current?.user_id} />
style={{
marginLeft: 8,
borderRadius: 12,
color: 'var(--ant-color-text-tertiary)',
borderColor: 'var(--ant-color-split)',
backgroundColor: 'transparent'
}}
variant="outlined"
>
{intl.formatMessage({ id: 'usage.table.deleted' })}
</Tag>
)} )}
</span> </span>
) )
@@ -2,9 +2,11 @@ import {
buildInstanceTypeRecordFromMiB, buildInstanceTypeRecordFromMiB,
renderInstanceType renderInstanceType
} from '@/pages/gpu-service/instances/utils/render-instance-type'; } from '@/pages/gpu-service/instances/utils/render-instance-type';
import { AutoTooltip } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ResourceBreakdownItem } from '../../apis/resource'; import { ResourceBreakdownItem } from '../../apis/resource';
import DeletedTag from '../../components/deleted-tag';
import { instanceTypeSeriesLabel } from '../../utils/format-instance-type'; import { instanceTypeSeriesLabel } from '../../utils/format-instance-type';
import { parseRollup } from '../../utils/time-buckets'; import { parseRollup } from '../../utils/time-buckets';
@@ -108,6 +110,22 @@ const useInstancesColumns = (groupKey: GroupKey) => {
key: 'last_active', key: 'last_active',
render: (v?: string) => (v ? parseRollup(v).format('YYYY-MM-DD') : '-') render: (v?: string) => (v ? parseRollup(v).format('YYYY-MM-DD') : '-')
}; };
// Name cell with a DeletedTag when the entity no longer exists — mirrors the
// Tokens tab. The id keeps two deleted rows sharing a stale name distinct.
const renderName = (text: string, id?: number, deleted?: boolean) => (
<span className="flex items-center gap-8">
<AutoTooltip
ghost
style={{
maxWidth: 400,
...(deleted ? { color: 'var(--ant-color-text-tertiary)' } : null)
}}
>
{text || '-'}
</AutoTooltip>
{deleted && <DeletedTag id={id ?? null} />}
</span>
);
if (groupKey === 'gpu_type') { if (groupKey === 'gpu_type') {
return [ return [
instanceTypeColType, instanceTypeColType,
@@ -123,9 +141,11 @@ const useInstancesColumns = (groupKey: GroupKey) => {
if (groupKey === 'instance') { if (groupKey === 'instance') {
return [ return [
{ {
title: intl.formatMessage({ id: 'usage.table.instance' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'instance_name', dataIndex: 'instance_name',
key: 'instance_name' key: 'instance_name',
render: (text: string, row: ResourceBreakdownItem) =>
renderName(text, row.instance_id, row.deleted)
}, },
instanceTypeColInstance, instanceTypeColInstance,
...baseValueCols, ...baseValueCols,
@@ -135,9 +155,11 @@ const useInstancesColumns = (groupKey: GroupKey) => {
// user tab // user tab
return [ return [
{ {
title: intl.formatMessage({ id: 'usage.table.user' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'user_name', dataIndex: 'user_name',
key: 'user_name' key: 'user_name',
render: (text: string, row: ResourceBreakdownItem) =>
renderName(text, row.user_id, row.deleted)
}, },
...baseValueCols, ...baseValueCols,
lastActiveCol lastActiveCol
@@ -1,6 +1,8 @@
import { AutoTooltip } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ResourceBreakdownItem } from '../../apis/resource'; import { ResourceBreakdownItem } from '../../apis/resource';
import DeletedTag from '../../components/deleted-tag';
import { parseRollup } from '../../utils/time-buckets'; import { parseRollup } from '../../utils/time-buckets';
type GroupKey = 'volume' | 'user'; type GroupKey = 'volume' | 'user';
@@ -39,12 +41,30 @@ const useStorageColumns = (groupKey: GroupKey) => {
key: 'last_active', key: 'last_active',
render: (v?: string) => (v ? parseRollup(v).format('YYYY-MM-DD') : '-') render: (v?: string) => (v ? parseRollup(v).format('YYYY-MM-DD') : '-')
}; };
// Name cell with a DeletedTag when the entity no longer exists — mirrors the
// Tokens tab. The id keeps two deleted rows sharing a stale name distinct.
const renderName = (text: string, id?: number, deleted?: boolean) => (
<span className="flex items-center gap-8">
<AutoTooltip
ghost
style={{
maxWidth: 400,
...(deleted ? { color: 'var(--ant-color-text-tertiary)' } : null)
}}
>
{text || '-'}
</AutoTooltip>
{deleted && <DeletedTag id={id ?? null} />}
</span>
);
if (groupKey === 'volume') { if (groupKey === 'volume') {
return [ return [
{ {
title: intl.formatMessage({ id: 'usage.tabs.storage' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'volume_name', dataIndex: 'volume_name',
key: 'volume_name' key: 'volume_name',
render: (text: string, row: ResourceBreakdownItem) =>
renderName(text, row.volume_id, row.deleted)
}, },
{ {
title: intl.formatMessage({ id: 'usage.table.type' }), title: intl.formatMessage({ id: 'usage.table.type' }),
@@ -65,9 +85,11 @@ const useStorageColumns = (groupKey: GroupKey) => {
} }
return [ return [
{ {
title: intl.formatMessage({ id: 'usage.table.user' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'user_name', dataIndex: 'user_name',
key: 'user_name' key: 'user_name',
render: (text: string, row: ResourceBreakdownItem) =>
renderName(text, row.user_id, row.deleted)
}, },
...valueCols, ...valueCols,
{ {