feat(usage): enhance dashboard with auto-refresh control and robust formatting (#942)

* style: format code and apply clippy lint fixes

* feat(usage): enhance dashboard with auto-refresh control and robust formatting

- Add configurable auto-refresh interval toggle (off/5s/10s/30s/60s) to usage dashboard
- Extract shared format utilities (fmtUsd, fmtInt, parseFiniteNumber, getLocaleFromLanguage)
- Refactor request log time filtering to rolling vs fixed mode with validation
- Use stable serializable query keys instead of filter objects
- Handle NaN/Infinity safely in number formatting across all usage components
- Use RFC 3339 date format in backend trend data
This commit is contained in:
Dex Miller
2026-02-06 22:00:33 +08:00
committed by GitHub
parent 14fa749ca9
commit 87b80c66b2
9 changed files with 401 additions and 130 deletions
+55 -25
View File
@@ -8,11 +8,28 @@ import { ProviderStatsTable } from "./ProviderStatsTable";
import { ModelStatsTable } from "./ModelStatsTable";
import type { TimeRange } from "@/types/usage";
import { motion } from "framer-motion";
import { BarChart3, ListFilter, Activity } from "lucide-react";
import { BarChart3, ListFilter, Activity, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useQueryClient } from "@tanstack/react-query";
import { usageKeys } from "@/lib/query/usage";
export function UsageDashboard() {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [timeRange, setTimeRange] = useState<TimeRange>("1d");
const [refreshIntervalMs, setRefreshIntervalMs] = useState(30000);
const refreshIntervalOptionsMs = [0, 5000, 10000, 30000, 60000] as const;
const changeRefreshInterval = () => {
const currentIndex = refreshIntervalOptionsMs.indexOf(
refreshIntervalMs as (typeof refreshIntervalOptionsMs)[number],
);
const safeIndex = currentIndex >= 0 ? currentIndex : 3; // default 30s
const nextIndex = (safeIndex + 1) % refreshIntervalOptionsMs.length;
const next = refreshIntervalOptionsMs[nextIndex];
setRefreshIntervalMs(next);
queryClient.invalidateQueries({ queryKey: usageKeys.all });
};
const days = timeRange === "1d" ? 1 : timeRange === "7d" ? 7 : 30;
@@ -34,32 +51,45 @@ export function UsageDashboard() {
onValueChange={(v) => setTimeRange(v as TimeRange)}
className="w-full sm:w-auto"
>
<TabsList className="flex w-full sm:w-auto bg-card/60 border border-border/50 backdrop-blur-sm shadow-sm h-10 p-1">
<TabsTrigger
value="1d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
<div className="flex w-full sm:w-auto items-center gap-1">
<Button
type="button"
variant="ghost"
size="sm"
className="h-10 px-2 text-xs text-muted-foreground"
title={t("common.refresh", "刷新")}
onClick={changeRefreshInterval}
>
{t("usage.today")}
</TabsTrigger>
<TabsTrigger
value="7d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.last7days")}
</TabsTrigger>
<TabsTrigger
value="30d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.last30days")}
</TabsTrigger>
</TabsList>
<RefreshCw className="mr-1 h-3.5 w-3.5" />
{refreshIntervalMs > 0 ? `${refreshIntervalMs / 1000}s` : "--"}
</Button>
<TabsList className="flex w-full sm:w-auto bg-card/60 border border-border/50 backdrop-blur-sm shadow-sm h-10 p-1">
<TabsTrigger
value="1d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.today")}
</TabsTrigger>
<TabsTrigger
value="7d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.last7days")}
</TabsTrigger>
<TabsTrigger
value="30d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.last30days")}
</TabsTrigger>
</TabsList>
</div>
</Tabs>
</div>
<UsageSummaryCards days={days} />
<UsageSummaryCards days={days} refreshIntervalMs={refreshIntervalMs} />
<UsageTrendChart days={days} />
<UsageTrendChart days={days} refreshIntervalMs={refreshIntervalMs} />
<div className="space-y-4">
<Tabs defaultValue="logs" className="w-full">
@@ -86,15 +116,15 @@ export function UsageDashboard() {
transition={{ delay: 0.2 }}
>
<TabsContent value="logs" className="mt-0">
<RequestLogTable />
<RequestLogTable refreshIntervalMs={refreshIntervalMs} />
</TabsContent>
<TabsContent value="providers" className="mt-0">
<ProviderStatsTable />
<ProviderStatsTable refreshIntervalMs={refreshIntervalMs} />
</TabsContent>
<TabsContent value="models" className="mt-0">
<ModelStatsTable />
<ModelStatsTable refreshIntervalMs={refreshIntervalMs} />
</TabsContent>
</motion.div>
</Tabs>