Files
gpustack-ui/src/pages/usage/components/daily-usage.tsx
T
2026-04-22 13:16:48 +08:00

165 lines
4.6 KiB
TypeScript

import CardWrapper from '@/components/card-wrapper';
import MixLineBar from '@/components/echarts/mix-line-bar';
import BaseSelect from '@/components/seal-form/base/select';
import { baseColorMap } from '@/pages/dashboard/config';
import { useIntl } from '@umijs/max';
import { Segmented } from 'antd';
import dayjs from 'dayjs';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { granularities, groupByOptions, metricOptions } from '../config';
import { TimeSeriesData } from '../config/types';
const ControlsWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
.group {
display: flex;
gap: 8px;
align-items: center;
}
`;
const ControlLabel = styled.span`
font-size: 14px;
color: var(--ant-color-text-tertiary);
margin-right: 8px;
`;
interface DailyUsageProps {
timeSeriesData: TimeSeriesData | null;
metric: string;
groupBy: string | null;
granularity: string;
onMetricChange: (value: string) => void;
onGroupByChange: (value: string | null) => void;
onGranularityChange: (value: string) => void;
}
const labelFormatter = (v: any) => {
return dayjs(v).format('MM-DD');
};
const DailyUsage: React.FC<DailyUsageProps> = (props) => {
const intl = useIntl();
const {
timeSeriesData,
metric,
groupBy,
granularity,
onMetricChange,
onGroupByChange,
onGranularityChange
} = props;
const { chartData, xAxisData } = useMemo(() => {
if (!timeSeriesData?.series || timeSeriesData.series.length === 0) {
return { chartData: { line: [], bar: [] }, xAxisData: [] };
}
const series = timeSeriesData.series;
const xAxis = series[0]?.timeline?.map((item) => item.date) || [];
const colors = [
baseColorMap.base,
baseColorMap.baseR3,
baseColorMap.baseL1,
baseColorMap.baseR1,
baseColorMap.baseR2,
baseColorMap.baseL2
];
const chartSeries = series.map((item, index) => ({
name: item.label,
data: item.timeline.map((t) => ({ time: t.date, value: t.value })),
color: colors[index % colors.length]
}));
// API requests use line chart, tokens use bar chart
return {
chartData: { line: [], bar: chartSeries },
xAxisData: xAxis
};
}, [timeSeriesData, metric]);
const legendData = useMemo(() => {
if (!timeSeriesData?.series) return [];
return timeSeriesData.series.map((item) => ({
name: item.label,
icon: metric === 'api_requests' ? 'circle' : 'roundRect'
}));
}, [timeSeriesData, metric]);
const handleOnGroupByChange = (value: string) => {
onGroupByChange(value || null);
};
return (
<div>
<CardWrapper style={{ width: '100%', marginTop: 20 }}>
<ControlsWrapper>
<div className="group">
<BaseSelect
variant="borderless"
prefix={
<ControlLabel>
{intl.formatMessage({ id: 'usage.filter.metric' })}
</ControlLabel>
}
options={metricOptions.map((item) => ({
label: intl.formatMessage({ id: item.label }),
value: item.value
}))}
value={metric}
popupMatchSelectWidth={false}
onChange={onMetricChange}
style={{ width: 'max-content' }}
/>
<BaseSelect
allowClear
variant="borderless"
prefix={
<ControlLabel>
{intl.formatMessage({ id: 'usage.filter.groupBy' })}
</ControlLabel>
}
options={groupByOptions.map((item) => ({
label: intl.formatMessage({ id: item.label }),
value: item.value
}))}
value={groupBy}
popupMatchSelectWidth={false}
onChange={handleOnGroupByChange}
style={{ width: 'max-content' }}
/>
</div>
<Segmented
size="small"
options={granularities.map((item) => ({
label: intl.formatMessage({ id: item.label }),
value: item.value
}))}
value={granularity}
onChange={onGranularityChange}
></Segmented>
</ControlsWrapper>
<MixLineBar
chartData={chartData}
seriesData={[]}
xAxisData={xAxisData}
height={280}
smooth={false}
legendData={legendData}
labelFormatter={labelFormatter}
/>
</CardWrapper>
</div>
);
};
export default DailyUsage;