feat(usage): resource usage metering page

Add the Usage page with Summary / Tokens / GPU Instances / Storage / Resource
Events tabs over the new metering endpoints: per-resource breakdowns with
date / scope / user / resource filters, trend charts, server-side sortable
tables (GPU-Hours, Instance-Hours, GB-Days, GB-Hours), Excel export with an
in-dialog preview, and KPI cards with help tooltips explaining each metric.

MaaS-only users (no Kubernetes cluster and no resource events) get a
tokens-only view with the tab bar dropped; GPU Service / the full page unlock
for admins, cluster owners, or anyone who has run a resource. Instance-type
rows reuse the GPU Instances list styling, and deleted users / instances /
volumes are flagged in breakdowns and filters.
This commit is contained in:
michelia
2026-06-04 19:52:52 +08:00
committed by jialin
parent 50601b0aff
commit 62f34ffcaf
14 changed files with 459 additions and 45 deletions
+21 -5
View File
@@ -50,6 +50,14 @@ const ExportData: React.FC<{
} = props || {};
const intl = useIntl();
// Members are forced to self scope, where the backend forbids grouping by
// user (privacy) — including it 403s the export request. Drop the user
// dimension (and its column) when we can't group by it.
const canGroupByUser = initialScope !== 'self';
const exportGroupBy = canGroupByUser
? ['date', 'user', 'route', 'api_key']
: ['date', 'route', 'api_key'];
const [pageParams, setPageParams] = React.useState<{
page: number;
perPage: number;
@@ -84,7 +92,7 @@ const ExportData: React.FC<{
...pageParams,
granularity: 'day',
sort_by: '-date',
group_by: ['date', 'user', 'route', 'api_key'],
group_by: exportGroupBy,
filters: nextFilters,
scope: initialScope,
start_date: nextCommonFilters.start_date,
@@ -192,6 +200,14 @@ const ExportData: React.FC<{
}
];
// Hide the User column when we can't group by user (self scope) — it'd be
// empty otherwise.
const visibleColumns = canGroupByUser
? exportTableColumns
: exportTableColumns.filter(
(c) => !(Array.isArray(c.dataIndex) && c.dataIndex[0] === 'user')
);
const handleSubmit = () => {
exportJsonToExcel({
fileName: `usage_export_${commonFilters.start_date}_${commonFilters.end_date}.xlsx`,
@@ -211,7 +227,7 @@ const ExportData: React.FC<{
sheetName: 'usage',
fields: [
'date',
'user',
...(canGroupByUser ? ['user'] : []),
'route',
'api_key',
'input_tokens',
@@ -252,7 +268,7 @@ const ExportData: React.FC<{
page,
perPage: pageSize,
granularity: 'day',
group_by: ['date', 'user', 'model', 'api_key'],
group_by: exportGroupBy,
filters,
sort_by: '-date',
scope: initialScope,
@@ -271,7 +287,7 @@ const ExportData: React.FC<{
fetchExportData({
...INITIAL_PAGE_PARAMS,
granularity: 'day',
group_by: ['date', 'user', 'route', 'api_key'],
group_by: exportGroupBy,
filters,
sort_by: '-date',
scope: initialScope,
@@ -323,7 +339,7 @@ const ExportData: React.FC<{
></FilterBar>
</div>
<Table
columns={exportTableColumns}
columns={visibleColumns}
className={'scroll-table'}
tableLayout={'auto'}
style={{ width: '100%', marginTop: '16px', minHeight: 400 }}