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:
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Export a resource-breakdown table to Excel — the GPU Instances / Storage
|
||||
* tabs' counterpart to the Tokens tab export.
|
||||
*
|
||||
* Columns are derived from the same antd column specs the table renders, so the
|
||||
* export always matches what's on screen (whichever group_by tab is active).
|
||||
* Raw values are written (not the table's formatted render output) so numbers
|
||||
* stay sortable / calculable in the spreadsheet.
|
||||
*/
|
||||
import { exportJsonToExcel } from '@gpustack/core-ui/excel';
|
||||
|
||||
export interface ExportColumn {
|
||||
title: string;
|
||||
dataIndex: string;
|
||||
}
|
||||
|
||||
// Keep only real data columns (drop index / render-only columns), and only
|
||||
// those whose title is a plain string so the header is meaningful.
|
||||
export const toExportColumns = (columns: any[]): ExportColumn[] =>
|
||||
(columns || [])
|
||||
.filter(
|
||||
(c) => typeof c?.dataIndex === 'string' && typeof c?.title === 'string'
|
||||
)
|
||||
.map((c) => ({
|
||||
title: c.title as string,
|
||||
dataIndex: c.dataIndex as string
|
||||
}));
|
||||
|
||||
export const exportBreakdownRows = (
|
||||
rows: any[],
|
||||
columns: ExportColumn[],
|
||||
fileName: string,
|
||||
sheetName = 'usage'
|
||||
): void => {
|
||||
const fields = columns.map((c) => c.dataIndex);
|
||||
const fieldLabels = Object.fromEntries(
|
||||
columns.map((c) => [c.dataIndex, c.title])
|
||||
);
|
||||
const jsonData = (rows || []).map((r) => {
|
||||
const o: Record<string, any> = {};
|
||||
columns.forEach((c) => {
|
||||
o[c.dataIndex] = r?.[c.dataIndex] ?? '';
|
||||
});
|
||||
return o;
|
||||
});
|
||||
exportJsonToExcel({
|
||||
fileName,
|
||||
sheets: [{ jsonData, sheetName, fields, fieldLabels, formatMap: {} }]
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Time-bucket helpers shared by the resource-usage tabs' charts.
|
||||
*
|
||||
* The backend returns a per-bucket value keyed by ``bucket_start`` (hourly) or
|
||||
* a date_trunc'd date (day/week/month). The chart x-axis must use the SAME key
|
||||
* format so series line up. ``bucketKey`` normalizes any returned value to that
|
||||
* format via dayjs; ``generateBucketRange`` produces a contiguous axis.
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export type Granularity = 'hour' | 'day' | 'week' | 'month';
|
||||
|
||||
// Cap the hourly axis so a wide date range doesn't render hundreds of bars.
|
||||
const HOUR_MAX_DAYS = 7;
|
||||
|
||||
export const bucketKey = (value: any, granularity: Granularity): string => {
|
||||
const d = dayjs(value);
|
||||
if (granularity === 'hour') return d.format('YYYY-MM-DD HH:00');
|
||||
if (granularity === 'month') return d.format('YYYY-MM');
|
||||
return d.format('YYYY-MM-DD'); // day / week (week-start date as returned)
|
||||
};
|
||||
|
||||
export const generateBucketRange = (
|
||||
start: string,
|
||||
end: string,
|
||||
granularity: Granularity
|
||||
): string[] => {
|
||||
if (!start || !end) return [];
|
||||
const endDay = dayjs(end);
|
||||
let cursor = dayjs(start);
|
||||
// Hour view: clamp to the last HOUR_MAX_DAYS to keep the axis readable.
|
||||
if (granularity === 'hour') {
|
||||
const clampStart = endDay.subtract(HOUR_MAX_DAYS, 'day');
|
||||
if (cursor.isBefore(clampStart)) cursor = clampStart;
|
||||
}
|
||||
const step = granularity === 'hour' ? 'hour' : granularity;
|
||||
const out: string[] = [];
|
||||
const last =
|
||||
granularity === 'hour' ? endDay.endOf('day') : endDay.startOf('day');
|
||||
while (cursor.isBefore(last) || cursor.isSame(last)) {
|
||||
out.push(bucketKey(cursor, granularity));
|
||||
cursor = cursor.add(1, step as dayjs.ManipulateType);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
Reference in New Issue
Block a user