feat(usage): add plugin injection points for org / user-group filters and org breakdown tab

- token & resource filter bars render a plugin slot (UsageFilterBar /
  ResourceUsageFilterBar) for the organization / user-group filters
- breakdown tabs read plugin-provided extra sub-tabs (breakdownExtraTabs /
  resourceBreakdownExtraTabs) so the enterprise plugin can inject the
  Organization sub-tab
- thread the org / user-group filter state through the token filter hook and
  the resource tabs / breakdown tables / summary / export dialog
- standardize the organization label on the principal name; resource export
  date formatted to date-only
This commit is contained in:
michelia
2026-07-14 14:35:23 +08:00
committed by jialin
parent d0b6498bda
commit efb63335e7
15 changed files with 725 additions and 91 deletions
@@ -1,3 +1,4 @@
import { getGPUStackPlugin } from '@/plugins';
import { useIntl } from '@umijs/max';
import { Tabs } from 'antd';
import React, { useMemo } from 'react';
@@ -6,6 +7,24 @@ import ApiKeysTable from '../tables/apikeys-table';
import ModelsTable from '../tables/models-table';
import UsersTable from '../tables/users-table';
// A breakdown sub-tab contributed by a plugin (e.g. the enterprise
// Organization tab). ``useVisible`` is a React hook the host calls
// unconditionally per descriptor (stable array length → hook-safe) so the
// plugin can gate visibility on its own runtime state (e.g. platform-wide
// "All" context).
export interface BreakdownExtraTab {
key: string;
labelId: string;
useVisible?: () => boolean;
Component: React.ComponentType<{
filters: BreakdownFilters;
dateRange: { start_date: string; end_date: string };
scope: string;
pageResetKey?: number;
refreshKey?: number;
}>;
}
const BreakdownTabs: React.FC<{
dateRange: {
start_date: string;
@@ -18,7 +37,37 @@ const BreakdownTabs: React.FC<{
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
const intl = useIntl();
const extraTabs: BreakdownExtraTab[] =
getGPUStackPlugin()?.usage?.breakdownExtraTabs ?? [];
// Call each descriptor's visibility hook here (outside useMemo) so React's
// rules of hooks hold; the plugin's array is stable, so call order is too.
const extraVisible = extraTabs.map((tab) =>
tab.useVisible ? tab.useVisible() : true
);
const items = useMemo(() => {
const extraItems = extraTabs
.map((tab, index) => ({ tab, visible: extraVisible[index] }))
.filter(({ visible }) => visible)
.map(({ tab }) => {
const Component = tab.Component;
return {
key: tab.key,
label: intl.formatMessage({ id: tab.labelId }),
forceRender: true,
children: (
<Component
key={tab.key}
filters={filters}
dateRange={dateRange}
scope={scope}
pageResetKey={pageResetKey}
refreshKey={refreshKey}
/>
)
};
});
return [
{
key: 'models',
@@ -65,13 +114,24 @@ const BreakdownTabs: React.FC<{
/>
)
}
].filter((item) => {
if (item.key === 'users') {
return scope === 'all';
}
return true;
});
}, [filters, dateRange, pageResetKey, refreshKey, scope]);
]
.filter((item) => {
if (item.key === 'users') {
return scope === 'all';
}
return true;
})
.concat(extraItems);
}, [
filters,
dateRange,
pageResetKey,
refreshKey,
scope,
extraTabs,
extraVisible,
intl
]);
return (
<div style={{ marginTop: 16 }}>