diff --git a/app/admin/page.tsx b/app/admin/page.tsx
index dd3534b..93ca8f6 100644
--- a/app/admin/page.tsx
+++ b/app/admin/page.tsx
@@ -4,6 +4,7 @@ import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';
import { getCachedBunnyStorageStats, getCachedTotalStorage } from '@/lib/admin-stats';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { RefreshR2StatsButton } from '@/components/admin/refresh-r2-stats-button';
import { Users, Folder, Video, MessageSquare, Mic, HardDrive, Image as ImageIcon, Film, MessageSquareQuote, Star } from 'lucide-react';
export const metadata: Metadata = {
@@ -79,6 +80,7 @@ export default async function AdminDashboardPage() {
Dashboard Overview
+
diff --git a/app/api/admin/stats/refresh-r2/route.ts b/app/api/admin/stats/refresh-r2/route.ts
new file mode 100644
index 0000000..0374557
--- /dev/null
+++ b/app/api/admin/stats/refresh-r2/route.ts
@@ -0,0 +1,22 @@
+import { auth } from '@/lib/auth';
+import { apiErrors, successResponse } from '@/lib/api-response';
+import { refreshR2StorageSnapshot } from '@/lib/admin-stats';
+
+export async function POST() {
+ try {
+ const session = await auth();
+ if (!session?.user?.isAdmin) {
+ return apiErrors.forbidden('Admin access required');
+ }
+
+ const refreshedAt = await refreshR2StorageSnapshot();
+
+ return successResponse({
+ ok: true,
+ refreshedAt,
+ });
+ } catch (error) {
+ console.error('Error refreshing R2 admin stats cache:', error);
+ return apiErrors.internalError('Failed to refresh R2 stats');
+ }
+}
diff --git a/components/admin/refresh-r2-stats-button.tsx b/components/admin/refresh-r2-stats-button.tsx
new file mode 100644
index 0000000..18295c1
--- /dev/null
+++ b/components/admin/refresh-r2-stats-button.tsx
@@ -0,0 +1,50 @@
+'use client';
+
+import { useState } from 'react';
+import { useRouter } from 'next/navigation';
+import { Loader2, RefreshCw } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+
+export function RefreshR2StatsButton() {
+ const router = useRouter();
+ const [isRefreshing, setIsRefreshing] = useState(false);
+ const [error, setError] = useState(null);
+
+ const handleRefresh = async () => {
+ setIsRefreshing(true);
+ setError(null);
+
+ try {
+ const response = await fetch('/api/admin/stats/refresh-r2', {
+ method: 'POST',
+ });
+
+ const payload = await response.json().catch(() => ({}));
+ if (!response.ok) {
+ const message = (payload as { error?: string }).error || 'Failed to refresh R2 stats';
+ setError(message);
+ return;
+ }
+
+ router.refresh();
+ } catch {
+ setError('Failed to refresh R2 stats');
+ } finally {
+ setIsRefreshing(false);
+ }
+ };
+
+ return (
+
+
+ {error ?
{error}
: null}
+
+ );
+}
diff --git a/lib/admin-stats.ts b/lib/admin-stats.ts
index d8bf9e3..acb4e00 100644
--- a/lib/admin-stats.ts
+++ b/lib/admin-stats.ts
@@ -6,6 +6,17 @@ import { ListObjectsV2Command, type ListObjectsV2CommandInput } from '@aws-sdk/c
const BUNNY_API_BASE = 'https://video.bunnycdn.com';
const STORAGE_CACHE_SECONDS = 600;
+interface R2StorageSnapshot {
+ fileSizes: Map;
+ totalBytes: number;
+ refreshedAt: string;
+}
+
+const globalForAdminStats = globalThis as unknown as {
+ adminR2StorageSnapshot?: R2StorageSnapshot;
+ adminR2StorageSnapshotPromise?: Promise;
+};
+
interface BunnyStorageStats {
totalBytes: number;
byVideoId: Record;
@@ -75,6 +86,46 @@ async function listAllR2FileSizes(): Promise