refactor(usagesummary): fetch data
This commit is contained in:
@@ -18,6 +18,7 @@ export function useQueryDataList<
|
|||||||
Response = Array<ListItem>
|
Response = Array<ListItem>
|
||||||
>(option: {
|
>(option: {
|
||||||
key: string;
|
key: string;
|
||||||
|
manual?: boolean;
|
||||||
responseType?: 'array' | 'object';
|
responseType?: 'array' | 'object';
|
||||||
fetchList: (
|
fetchList: (
|
||||||
params: Params,
|
params: Params,
|
||||||
@@ -38,6 +39,7 @@ export function useQueryDataList<
|
|||||||
fetchList,
|
fetchList,
|
||||||
getLabel,
|
getLabel,
|
||||||
getValue,
|
getValue,
|
||||||
|
manual = true,
|
||||||
responseType = 'array',
|
responseType = 'array',
|
||||||
errorMsg
|
errorMsg
|
||||||
} = option;
|
} = option;
|
||||||
@@ -70,7 +72,7 @@ export function useQueryDataList<
|
|||||||
return responseType === 'array' ? res.items || [] : res;
|
return responseType === 'array' ? res.items || [] : res;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
manual: true,
|
manual: manual,
|
||||||
debounceWait: option.debounceWait || 300,
|
debounceWait: option.debounceWait || 300,
|
||||||
onSuccess: () => {},
|
onSuccess: () => {},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -105,16 +107,18 @@ export function useQueryDataList<
|
|||||||
export function useQueryData<Detail, Params = any>(option: {
|
export function useQueryData<Detail, Params = any>(option: {
|
||||||
key: string;
|
key: string;
|
||||||
delay?: number;
|
delay?: number;
|
||||||
|
manual?: boolean;
|
||||||
fetchDetail: (params: Params, options?: any) => Promise<Detail>;
|
fetchDetail: (params: Params, options?: any) => Promise<Detail>;
|
||||||
getData?: (response: Detail, params?: any) => any;
|
getData?: (response: Detail, params?: any) => any;
|
||||||
errorMsg?: string;
|
errorMsg?: string;
|
||||||
}): {
|
}): {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
detailData: Detail;
|
detailData: Detail;
|
||||||
|
manual?: boolean;
|
||||||
cancelRequest: () => void;
|
cancelRequest: () => void;
|
||||||
fetchData: (params: Params, extra?: any) => Promise<Detail>;
|
fetchData: (params: Params, extra?: any) => Promise<Detail>;
|
||||||
} {
|
} {
|
||||||
const { key, fetchDetail, getData, errorMsg, delay } = option;
|
const { key, fetchDetail, getData, errorMsg, delay, manual = true } = option;
|
||||||
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||||
const [detailData, setDetailData] = useState<Detail>({} as Detail);
|
const [detailData, setDetailData] = useState<Detail>({} as Detail);
|
||||||
|
|
||||||
@@ -142,7 +146,7 @@ export function useQueryData<Detail, Params = any>(option: {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
manual: true,
|
manual: manual,
|
||||||
onSuccess: () => {},
|
onSuccess: () => {},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
message.error(
|
message.error(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import useCoolColors from '@/hooks/use-cool-colors';
|
import useCoolColors from '@/hooks/use-cool-colors';
|
||||||
import { Chart } from '@gpustack/core-ui';
|
import { Chart } from '@gpustack/core-ui';
|
||||||
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
||||||
import { Empty, theme } from 'antd';
|
import { Empty, Spin, theme } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef } from 'react';
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ export interface BarChartProps {
|
|||||||
xAxisData: string[];
|
xAxisData: string[];
|
||||||
height: number | string;
|
height: number | string;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
|
loading?: boolean;
|
||||||
legendData?: { name: string; icon?: string }[];
|
legendData?: { name: string; icon?: string }[];
|
||||||
labelFormatter?: (val: any, index?: number) => string;
|
labelFormatter?: (val: any, index?: number) => string;
|
||||||
tooltipValueFormatter?: (val: any) => string;
|
tooltipValueFormatter?: (val: any) => string;
|
||||||
@@ -38,6 +39,7 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
legendData,
|
legendData,
|
||||||
|
loading,
|
||||||
labelFormatter,
|
labelFormatter,
|
||||||
tooltipValueFormatter,
|
tooltipValueFormatter,
|
||||||
title,
|
title,
|
||||||
@@ -277,18 +279,40 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
{loading ? (
|
||||||
|
<Spin size="middle" />
|
||||||
|
) : (
|
||||||
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Chart
|
<div style={{ width, height, position: 'relative' }}>
|
||||||
ref={chartRef as any}
|
<Chart
|
||||||
options={options as any}
|
ref={chartRef as any}
|
||||||
height={height}
|
options={options as any}
|
||||||
width={width || '100%'}
|
height={height}
|
||||||
/>
|
width={width || '100%'}
|
||||||
|
/>
|
||||||
|
{loading && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'var(--ant-color-bg-container)',
|
||||||
|
opacity: 0.6,
|
||||||
|
pointerEvents: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin size="middle" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,17 +27,10 @@ import PieChart from '@/pages/_components/pie-chart';
|
|||||||
import { formatLargeNumber } from '@/utils';
|
import { formatLargeNumber } from '@/utils';
|
||||||
import { CardWrapper } from '@gpustack/core-ui';
|
import { CardWrapper } from '@gpustack/core-ui';
|
||||||
import { useAccess, useIntl } from '@umijs/max';
|
import { useAccess, useIntl } from '@umijs/max';
|
||||||
import { Col, Empty, Row } from 'antd';
|
import { useRequest } from 'ahooks';
|
||||||
|
import { Col, Row } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { queryUsageTimeSeriesData } from '../apis';
|
|
||||||
import {
|
|
||||||
queryResourceBreakdown,
|
|
||||||
queryStorageBreakdown,
|
|
||||||
queryUsageSummary,
|
|
||||||
ResourceBreakdownResponse,
|
|
||||||
UsageSummaryResponse
|
|
||||||
} from '../apis/resource';
|
|
||||||
import ResourceFilterBar from '../components/resource-filter-bar';
|
import ResourceFilterBar from '../components/resource-filter-bar';
|
||||||
import useResourceMeta from '../hooks/use-resource-meta';
|
import useResourceMeta from '../hooks/use-resource-meta';
|
||||||
import {
|
import {
|
||||||
@@ -45,9 +38,19 @@ import {
|
|||||||
generateBucketRange,
|
generateBucketRange,
|
||||||
Granularity
|
Granularity
|
||||||
} from '../utils/time-buckets';
|
} from '../utils/time-buckets';
|
||||||
|
import useQueryResourceBreakdown from './services/use-query-resource-breakdown';
|
||||||
|
import useQueryStorageBreakdown from './services/use-query-storage-breakdown';
|
||||||
|
import useQueryTimeSeriesData from './services/use-query-timeseries-data';
|
||||||
|
import useQueryUsageSummary from './services/use-query-usage-summary';
|
||||||
|
|
||||||
type Scope = 'self' | 'all';
|
type Scope = 'self' | 'all';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
selectedUsers: number[];
|
||||||
|
};
|
||||||
|
|
||||||
// Round to at most 2 decimals everywhere (avoid 1.60999999… in the donut center).
|
// Round to at most 2 decimals everywhere (avoid 1.60999999… in the donut center).
|
||||||
const round2 = (n?: number) => Math.round((Number(n) || 0) * 100) / 100;
|
const round2 = (n?: number) => Math.round((Number(n) || 0) * 100) / 100;
|
||||||
const fmt = (n?: number) => formatLargeNumber(round2(n));
|
const fmt = (n?: number) => formatLargeNumber(round2(n));
|
||||||
@@ -96,6 +99,8 @@ const Stat: React.FC<{ value: React.ReactNode; label: string }> = ({
|
|||||||
// row, then a donut (left, legend hugging it) beside a trend (right) that
|
// row, then a donut (left, legend hugging it) beside a trend (right) that
|
||||||
// carries its own chart title — no wasteful caption rows.
|
// carries its own chart title — no wasteful caption rows.
|
||||||
const DomainSection: React.FC<{
|
const DomainSection: React.FC<{
|
||||||
|
pieLoading?: boolean;
|
||||||
|
barLoading?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
accent: string;
|
accent: string;
|
||||||
headline: React.ReactNode;
|
headline: React.ReactNode;
|
||||||
@@ -107,6 +112,8 @@ const DomainSection: React.FC<{
|
|||||||
trendColor: string;
|
trendColor: string;
|
||||||
trendGran: Granularity;
|
trendGran: Granularity;
|
||||||
}> = ({
|
}> = ({
|
||||||
|
pieLoading,
|
||||||
|
barLoading,
|
||||||
title,
|
title,
|
||||||
accent,
|
accent,
|
||||||
headline,
|
headline,
|
||||||
@@ -118,16 +125,14 @@ const DomainSection: React.FC<{
|
|||||||
trendColor,
|
trendColor,
|
||||||
trendGran
|
trendGran
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
|
||||||
const donutTotal = round2(donutData.reduce((s, d) => s + (d.value || 0), 0));
|
const donutTotal = round2(donutData.reduce((s, d) => s + (d.value || 0), 0));
|
||||||
const trendEmpty = trendData.every((v) => !v);
|
|
||||||
const empty = (
|
const seriesData = useMemo(() => {
|
||||||
<Empty
|
return [{ name: trendTitle, data: trendData, color: trendColor }].filter(
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
(s) => s.data.some((v) => !!v)
|
||||||
description={intl.formatMessage({ id: 'usage.common.noData' })}
|
);
|
||||||
style={{ margin: '32px 0' }}
|
}, [trendData, trendColor]);
|
||||||
/>
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<CardWrapper style={{ paddingBlock: 16 }}>
|
<CardWrapper style={{ paddingBlock: 16 }}>
|
||||||
<div
|
<div
|
||||||
@@ -166,37 +171,29 @@ const DomainSection: React.FC<{
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: 340, maxWidth: '100%', flexShrink: 0 }}>
|
<div style={{ width: 340, maxWidth: '100%', flexShrink: 0 }}>
|
||||||
{donutTotal <= 0 ? (
|
<PieChart
|
||||||
empty
|
loading={pieLoading}
|
||||||
) : (
|
data={donutData}
|
||||||
<PieChart
|
height={180}
|
||||||
data={donutData}
|
total={donutTotal}
|
||||||
height={180}
|
totalLabel={donutTotalLabel}
|
||||||
total={donutTotal}
|
/>
|
||||||
totalLabel={donutTotalLabel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, minWidth: 260 }}>
|
<div style={{ flex: 1, minWidth: 260 }}>
|
||||||
{trendEmpty ? (
|
<BarChart
|
||||||
empty
|
loading={barLoading}
|
||||||
) : (
|
seriesData={seriesData}
|
||||||
<BarChart
|
xAxisData={trendXAxis}
|
||||||
seriesData={[
|
height={180}
|
||||||
{ name: trendTitle, data: trendData, color: trendColor }
|
grid={{
|
||||||
]}
|
bottom: 0
|
||||||
xAxisData={trendXAxis}
|
}}
|
||||||
height={180}
|
title={trendTitle}
|
||||||
grid={{
|
labelFormatter={trendLabel(trendGran)}
|
||||||
bottom: 0
|
tooltipValueFormatter={(v) =>
|
||||||
}}
|
formatLargeNumber(round2(Number(v))) as string
|
||||||
title={trendTitle}
|
}
|
||||||
labelFormatter={trendLabel(trendGran)}
|
/>
|
||||||
tooltipValueFormatter={(v) =>
|
|
||||||
formatLargeNumber(round2(Number(v))) as string
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
@@ -216,95 +213,106 @@ const SummaryTab: React.FC = () => {
|
|||||||
// Summary trends are fixed to a daily granularity (no granularity control).
|
// Summary trends are fixed to a daily granularity (no granularity control).
|
||||||
const granularity: Granularity = 'day';
|
const granularity: Granularity = 'day';
|
||||||
|
|
||||||
const [dateRange, setDateRange] = useState<[dayjs.Dayjs, dayjs.Dayjs]>([
|
// Date range + user filter live together: every fetch keys off all three, so
|
||||||
dayjs().subtract(29, 'day'),
|
// a single object keeps them in sync and trims the dependency arrays.
|
||||||
dayjs()
|
const [queryParams, setQueryParams] = useState<{
|
||||||
]);
|
start: string;
|
||||||
const [selectedUsers, setSelectedUsers] = useState<number[]>([]);
|
end: string;
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
selectedUsers: number[];
|
||||||
|
}>({
|
||||||
|
start: dayjs().subtract(29, 'day').format('YYYY-MM-DD'),
|
||||||
|
end: dayjs().format('YYYY-MM-DD'),
|
||||||
|
selectedUsers: []
|
||||||
|
});
|
||||||
|
const { start, end, selectedUsers } = queryParams;
|
||||||
const { creators: userOptions } = useResourceMeta(scope);
|
const { creators: userOptions } = useResourceMeta(scope);
|
||||||
|
|
||||||
const [summary, setSummary] = useState<UsageSummaryResponse | null>(null);
|
const {
|
||||||
const [tokenSeries, setTokenSeries] = useState<any[]>([]);
|
detailData: summary,
|
||||||
const [computeByDate, setComputeByDate] =
|
loading: summaryLoading,
|
||||||
useState<ResourceBreakdownResponse | null>(null);
|
fetchData: fetchSummary
|
||||||
const [storageByDate, setStorageByDate] =
|
} = useQueryUsageSummary();
|
||||||
useState<ResourceBreakdownResponse | null>(null);
|
const {
|
||||||
const [storageByType, setStorageByType] =
|
detailData: tokenSeriesData,
|
||||||
useState<ResourceBreakdownResponse | null>(null);
|
loading: tokenSeriesLoading,
|
||||||
|
fetchData: fetchTokenSeries
|
||||||
|
} = useQueryTimeSeriesData();
|
||||||
|
const {
|
||||||
|
detailData: computeByDate,
|
||||||
|
loading: computeLoading,
|
||||||
|
fetchData: fetchComputeBreakdown
|
||||||
|
} = useQueryResourceBreakdown();
|
||||||
|
const {
|
||||||
|
detailData: storageByDate,
|
||||||
|
loading: storageByDateLoading,
|
||||||
|
fetchData: fetchStorageByDate
|
||||||
|
} = useQueryStorageBreakdown({ key: 'storageByDate' });
|
||||||
|
const {
|
||||||
|
detailData: storageByType,
|
||||||
|
loading: storageByTypeLoading,
|
||||||
|
fetchData: fetchStorageByType
|
||||||
|
} = useQueryStorageBreakdown({ key: 'storageByType' });
|
||||||
|
|
||||||
const start = dateRange[0].format('YYYY-MM-DD');
|
const fetchAll = async (params?: Partial<QueryParams>) => {
|
||||||
const end = dateRange[1].format('YYYY-MM-DD');
|
const currentParams = {
|
||||||
// Token usage (model_usages) is a daily rollup; the Summary trends are fixed
|
...queryParams,
|
||||||
// to ``day`` anyway, so the token trend shares the same granularity.
|
...params
|
||||||
const tokenGran: Granularity = granularity;
|
};
|
||||||
|
|
||||||
// "filter by user" — restricts every resource fetch to these creator ids.
|
const commonParams = {
|
||||||
const creatorFilter = selectedUsers.length
|
start_date: currentParams.start,
|
||||||
? { creator_ids: selectedUsers }
|
end_date: currentParams.end,
|
||||||
: undefined;
|
scope
|
||||||
|
};
|
||||||
// Date+scope scoped fetches (granularity-independent).
|
const paginationParams = {
|
||||||
useEffect(() => {
|
...commonParams,
|
||||||
queryUsageSummary({
|
|
||||||
start_date: start,
|
|
||||||
end_date: end,
|
|
||||||
scope,
|
|
||||||
creator_ids: selectedUsers.length ? selectedUsers : undefined
|
|
||||||
})
|
|
||||||
.then(setSummary)
|
|
||||||
.catch(() => {});
|
|
||||||
queryStorageBreakdown({
|
|
||||||
start_date: start,
|
|
||||||
end_date: end,
|
|
||||||
scope,
|
|
||||||
group_by: ['type'],
|
|
||||||
filters: creatorFilter,
|
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 100
|
perPage: 100
|
||||||
})
|
};
|
||||||
.then(setStorageByType)
|
|
||||||
.catch(() => {});
|
|
||||||
}, [start, end, scope, selectedUsers, refreshKey]);
|
|
||||||
|
|
||||||
// Trend fetches (depend on granularity too).
|
// "filter by user" — restricts every resource fetch to these creator ids.
|
||||||
useEffect(() => {
|
const creatorFilter = currentParams.selectedUsers.length
|
||||||
queryUsageTimeSeriesData({
|
? { creator_ids: currentParams.selectedUsers }
|
||||||
start_date: start,
|
: undefined;
|
||||||
end_date: end,
|
|
||||||
scope,
|
await Promise.all([
|
||||||
metric: 'total_tokens',
|
fetchSummary({
|
||||||
group_by: ['date'],
|
...commonParams,
|
||||||
granularity: tokenGran,
|
creator_ids: currentParams.selectedUsers.length
|
||||||
filters: {}
|
? currentParams.selectedUsers
|
||||||
})
|
: undefined
|
||||||
.then((res) => setTokenSeries(res.items || []))
|
}),
|
||||||
.catch(() => {});
|
|
||||||
queryResourceBreakdown({
|
fetchStorageByType({
|
||||||
start_date: start,
|
...paginationParams,
|
||||||
end_date: end,
|
group_by: ['type'],
|
||||||
scope,
|
filters: creatorFilter
|
||||||
group_by: ['date'],
|
}),
|
||||||
granularity,
|
|
||||||
filters: creatorFilter,
|
fetchTokenSeries({
|
||||||
page: 1,
|
...commonParams,
|
||||||
perPage: 100
|
metric: 'total_tokens',
|
||||||
})
|
group_by: ['date'],
|
||||||
.then(setComputeByDate)
|
granularity,
|
||||||
.catch(() => {});
|
filters: {}
|
||||||
queryStorageBreakdown({
|
}),
|
||||||
start_date: start,
|
|
||||||
end_date: end,
|
fetchComputeBreakdown({
|
||||||
scope,
|
...paginationParams,
|
||||||
group_by: ['date'],
|
group_by: ['date'],
|
||||||
granularity,
|
granularity,
|
||||||
filters: creatorFilter,
|
filters: creatorFilter
|
||||||
page: 1,
|
}),
|
||||||
perPage: 100
|
|
||||||
})
|
fetchStorageByDate({
|
||||||
.then(setStorageByDate)
|
...paginationParams,
|
||||||
.catch(() => {});
|
group_by: ['date'],
|
||||||
}, [start, end, scope, granularity, tokenGran, selectedUsers, refreshKey]);
|
granularity,
|
||||||
|
filters: creatorFilter
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
setQueryParams(currentParams);
|
||||||
|
};
|
||||||
|
|
||||||
// --- derived: donuts ---
|
// --- derived: donuts ---
|
||||||
const tokenDonut = useMemo(
|
const tokenDonut = useMemo(
|
||||||
@@ -337,16 +345,17 @@ const SummaryTab: React.FC = () => {
|
|||||||
const tokenTrend = useMemo(
|
const tokenTrend = useMemo(
|
||||||
() =>
|
() =>
|
||||||
buildTrend(
|
buildTrend(
|
||||||
tokenSeries.map((it) => ({
|
(tokenSeriesData?.items || []).map((it: any) => ({
|
||||||
date: it?.date?.value,
|
date: it?.date?.value,
|
||||||
value: Number(it?.total_tokens ?? 0)
|
value: Number(it?.total_tokens ?? 0)
|
||||||
})),
|
})),
|
||||||
tokenGran,
|
granularity,
|
||||||
start,
|
start,
|
||||||
end
|
end
|
||||||
),
|
),
|
||||||
[tokenSeries, tokenGran, start, end]
|
[tokenSeriesData, granularity, start, end]
|
||||||
);
|
);
|
||||||
|
|
||||||
const computeTrend = useMemo(
|
const computeTrend = useMemo(
|
||||||
() =>
|
() =>
|
||||||
buildTrend(
|
buildTrend(
|
||||||
@@ -360,6 +369,7 @@ const SummaryTab: React.FC = () => {
|
|||||||
),
|
),
|
||||||
[computeByDate, granularity, start, end]
|
[computeByDate, granularity, start, end]
|
||||||
);
|
);
|
||||||
|
|
||||||
const storageTrend = useMemo(
|
const storageTrend = useMemo(
|
||||||
() =>
|
() =>
|
||||||
buildTrend(
|
buildTrend(
|
||||||
@@ -377,16 +387,33 @@ const SummaryTab: React.FC = () => {
|
|||||||
const computeSum = computeByDate?.summary;
|
const computeSum = computeByDate?.summary;
|
||||||
const storageSum = storageByDate?.summary;
|
const storageSum = storageByDate?.summary;
|
||||||
|
|
||||||
|
const handleDateRangeChange = (dates: [dayjs.Dayjs, dayjs.Dayjs]) => {
|
||||||
|
fetchAll({
|
||||||
|
start: dates[0].format('YYYY-MM-DD'),
|
||||||
|
end: dates[1].format('YYYY-MM-DD')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUserFilterChange = (users: number[]) => {
|
||||||
|
fetchAll({ selectedUsers: users });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRefresh = () => {
|
||||||
|
fetchAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
useRequest(fetchAll);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ResourceFilterBar
|
<ResourceFilterBar
|
||||||
value={dateRange}
|
value={[dayjs(start), dayjs(end)]}
|
||||||
onChange={(dates) => setDateRange(dates)}
|
onChange={handleDateRangeChange}
|
||||||
canManageUsers={canManageUsers}
|
canManageUsers={canManageUsers}
|
||||||
userOptions={userOptions}
|
userOptions={userOptions}
|
||||||
selectedUsers={selectedUsers}
|
selectedUsers={selectedUsers}
|
||||||
onUsersChange={setSelectedUsers}
|
onUsersChange={handleUserFilterChange}
|
||||||
onRefresh={() => setRefreshKey((k) => k + 1)}
|
onRefresh={onRefresh}
|
||||||
/>
|
/>
|
||||||
<div style={{ height: 24 }} />
|
<div style={{ height: 24 }} />
|
||||||
|
|
||||||
@@ -400,8 +427,10 @@ const SummaryTab: React.FC = () => {
|
|||||||
trendTitle={t('usage.summary.tokensOverTime')}
|
trendTitle={t('usage.summary.tokensOverTime')}
|
||||||
trendXAxis={tokenTrend.xAxis}
|
trendXAxis={tokenTrend.xAxis}
|
||||||
trendData={tokenTrend.data}
|
trendData={tokenTrend.data}
|
||||||
|
pieLoading={summaryLoading}
|
||||||
|
barLoading={tokenSeriesLoading}
|
||||||
trendColor={coolColors[0]}
|
trendColor={coolColors[0]}
|
||||||
trendGran={tokenGran}
|
trendGran={granularity}
|
||||||
headline={
|
headline={
|
||||||
<>
|
<>
|
||||||
<Stat
|
<Stat
|
||||||
@@ -436,6 +465,8 @@ const SummaryTab: React.FC = () => {
|
|||||||
trendData={computeTrend.data}
|
trendData={computeTrend.data}
|
||||||
trendColor={coolColors[1]}
|
trendColor={coolColors[1]}
|
||||||
trendGran={granularity}
|
trendGran={granularity}
|
||||||
|
pieLoading={summaryLoading}
|
||||||
|
barLoading={computeLoading}
|
||||||
headline={
|
headline={
|
||||||
<>
|
<>
|
||||||
<Stat
|
<Stat
|
||||||
@@ -466,6 +497,8 @@ const SummaryTab: React.FC = () => {
|
|||||||
trendData={storageTrend.data}
|
trendData={storageTrend.data}
|
||||||
trendColor={coolColors[3]}
|
trendColor={coolColors[3]}
|
||||||
trendGran={granularity}
|
trendGran={granularity}
|
||||||
|
pieLoading={storageByTypeLoading}
|
||||||
|
barLoading={storageByDateLoading}
|
||||||
headline={
|
headline={
|
||||||
<>
|
<>
|
||||||
<Stat
|
<Stat
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { useQueryData } from '@/hooks/use-query-data-list';
|
||||||
|
import {
|
||||||
|
queryResourceBreakdown,
|
||||||
|
ResourceBreakdownResponse
|
||||||
|
} from '../../apis/resource';
|
||||||
|
|
||||||
|
export default function useQueryResourceBreakdown(option?: { key?: string }) {
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } =
|
||||||
|
useQueryData<ResourceBreakdownResponse>({
|
||||||
|
fetchDetail: queryResourceBreakdown,
|
||||||
|
key: option?.key || 'resourceBreakdown'
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { useQueryData } from '@/hooks/use-query-data-list';
|
||||||
|
import {
|
||||||
|
queryStorageBreakdown,
|
||||||
|
ResourceBreakdownResponse
|
||||||
|
} from '../../apis/resource';
|
||||||
|
|
||||||
|
export default function useQueryStorageBreakdown(option?: { key?: string }) {
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } =
|
||||||
|
useQueryData<ResourceBreakdownResponse>({
|
||||||
|
fetchDetail: queryStorageBreakdown,
|
||||||
|
key: option?.key || 'storageBreakdown'
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useQueryData } from '@/hooks/use-query-data-list';
|
||||||
|
import { queryUsageTimeSeriesData } from '../../apis';
|
||||||
|
import { UsageBreakdownResponse } from '../../config/types';
|
||||||
|
|
||||||
|
export default function useQueryTimeSeriesData(option?: { key?: string }) {
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } =
|
||||||
|
useQueryData<UsageBreakdownResponse>({
|
||||||
|
fetchDetail: queryUsageTimeSeriesData,
|
||||||
|
key: option?.key || 'tokenTimeSeries'
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { useQueryData } from '@/hooks/use-query-data-list';
|
||||||
|
import { queryUsageSummary, UsageSummaryResponse } from '../../apis/resource';
|
||||||
|
|
||||||
|
export default function useQueryUsageSummary(option?: { key?: string }) {
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } =
|
||||||
|
useQueryData<UsageSummaryResponse>({
|
||||||
|
fetchDetail: queryUsageSummary,
|
||||||
|
key: option?.key || 'usageSummary'
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user