feat(usage): add resource-usage API client, meta hook, and shared utils

The data layer the resource tabs build on:
- apis/resource.ts: adapter over the unified metered_usage read API
  (resource/gpu-instances/storage/summary/events breakdowns), flattening
  the server's generic shape into the per-tab item shape.
- hooks/use-resource-meta.ts: loads creators/instances/volumes filter
  options for the current scope.
- utils/time-buckets.ts: day/week/month/hour bucket keys + range fill.
- utils/export-breakdown.ts: derive Excel columns from antd table specs.
This commit is contained in:
michelia
2026-06-03 17:10:51 +08:00
committed by michela feng
parent 234e42ccfa
commit 2407416e33
4 changed files with 635 additions and 0 deletions
@@ -0,0 +1,58 @@
import { useEffect, useState } from 'react';
import {
queryResourceFilterMeta,
ResourceFilterOption
} from '../apis/resource';
export interface SelectOption {
value: number;
label: string;
}
export interface ResourceMetaOptions {
creators: SelectOption[];
instances: SelectOption[];
volumes: SelectOption[];
}
const EMPTY: ResourceMetaOptions = {
creators: [],
instances: [],
volumes: []
};
const toOptions = (items: ResourceFilterOption[]): SelectOption[] =>
items.map((i) => ({ value: i.id, label: i.label }));
/**
* Loads the resource tabs' filter dropdown sources in one call:
* - ``creators`` — "filter by user" (Tokens-tab equivalent of /usage/meta
* users); only shown to managers, but cheap to always load.
* - ``instances`` — "filter by GPU instance" (GPU Instances tab)
* - ``volumes`` — "filter by volume" (Storage tab)
*
* Scope-aware: managers get the org-wide lists, others only their own
* resources. Refetched when ``scope`` changes.
*/
export default function useResourceMeta(
scope: 'self' | 'all' = 'all'
): ResourceMetaOptions {
const [meta, setMeta] = useState<ResourceMetaOptions>(EMPTY);
useEffect(() => {
queryResourceFilterMeta(scope)
.then((res) =>
setMeta({
creators: toOptions(res.creators),
instances: toOptions(res.instances),
volumes: toOptions(res.volumes)
})
)
.catch(() => {
// Network/auth errors surface via the global interceptor; leave the
// dropdowns empty rather than crashing the tab.
});
}, [scope]);
return meta;
}