diff --git a/ui/app/balances/page.tsx b/ui/app/balances/page.tsx new file mode 100644 index 00000000..b5213287 --- /dev/null +++ b/ui/app/balances/page.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useCurrencyStore } from '@/lib/stores/currency'; +import { useQuery } from '@tanstack/react-query'; +import { AppSidebar } from '@/components/app-sidebar'; +import { SiteHeader } from '@/components/site-header'; +import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; +import { DetailedWalletBalance } from '@/components/detailed-wallet-balance'; +import { TemporaryBalances } from '@/components/temporary-balances'; +import { fetchBtcUsdPrice, btcToSatsRate } from '@/lib/exchange-rate'; + +export default function BalancesPage() { + const { displayUnit } = useCurrencyStore(); + + const { data: btcUsdPrice } = useQuery({ + queryKey: ['btc-usd-price'], + queryFn: fetchBtcUsdPrice, + refetchInterval: 120_000, + staleTime: 60_000, + }); + + const usdPerSat = btcUsdPrice ? btcToSatsRate(btcUsdPrice) : null; + + return ( + + + + +
+
+
+

+ Balances +

+

+ Monitor and manage wallet balances +

+
+ {/* Global currency toggle is now in SiteHeader */} +
+ +
+
+ +
+
+ +
+
+
+
+
+ ); +} diff --git a/ui/app/page.tsx b/ui/app/page.tsx index da8ffba3..628e104d 100644 --- a/ui/app/page.tsx +++ b/ui/app/page.tsx @@ -1,19 +1,34 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { AppSidebar } from '@/components/app-sidebar'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; -import { DetailedWalletBalance } from '@/components/detailed-wallet-balance'; -import { TemporaryBalances } from '@/components/temporary-balances'; -import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; -import type { DisplayUnit } from '@/lib/types/units'; +import { UsageMetricsChart } from '@/components/usage-metrics-chart'; +import { UsageSummaryCards } from '@/components/usage-summary-cards'; +import { ErrorDetailsTable } from '@/components/error-details-table'; +import { RevenueByModelTable } from '@/components/revenue-by-model-table'; +import { DashboardBalanceSummary } from '@/components/dashboard-balance-summary'; +import { AdminService } from '@/lib/api/services/admin'; +import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { RefreshCw } from 'lucide-react'; +import { useCurrencyStore } from '@/lib/stores/currency'; import { fetchBtcUsdPrice, btcToSatsRate } from '@/lib/exchange-rate'; -export default function Page() { - const [displayUnit, setDisplayUnit] = useState('sat'); - +export default function DashboardPage() { + const [timeRange, setTimeRange] = useState('24'); + const [interval, setInterval] = useState('15'); + const { displayUnit } = useCurrencyStore(); + const { data: btcUsdPrice } = useQuery({ queryKey: ['btc-usd-price'], queryFn: fetchBtcUsdPrice, @@ -23,63 +38,282 @@ export default function Page() { const usdPerSat = btcUsdPrice ? btcToSatsRate(btcUsdPrice) : null; - useEffect(() => { - if (displayUnit === 'usd' && usdPerSat === null) { - setDisplayUnit('sat'); - } - }, [displayUnit, usdPerSat]); + const { + data: metricsData, + isLoading: metricsLoading, + refetch: refetchMetrics, + } = useQuery({ + queryKey: ['usage-metrics', interval, timeRange], + queryFn: () => + AdminService.getUsageMetrics(parseInt(interval), parseInt(timeRange)), + refetchInterval: 60_000, + staleTime: 30_000, + }); + + const { + data: summaryData, + isLoading: summaryLoading, + refetch: refetchSummary, + } = useQuery({ + queryKey: ['usage-summary', timeRange], + queryFn: () => AdminService.getUsageSummary(parseInt(timeRange)), + refetchInterval: 60_000, + staleTime: 30_000, + }); + + const { + data: errorData, + isLoading: errorLoading, + refetch: refetchErrors, + } = useQuery({ + queryKey: ['usage-errors', timeRange], + queryFn: () => AdminService.getErrorDetails(parseInt(timeRange), 100), + refetchInterval: 60_000, + staleTime: 30_000, + }); + + const { + data: revenueByModelData, + isLoading: revenueByModelLoading, + refetch: refetchRevenueByModel, + } = useQuery({ + queryKey: ['revenue-by-model', timeRange], + queryFn: () => AdminService.getRevenueByModel(parseInt(timeRange), 20), + refetchInterval: 60_000, + staleTime: 30_000, + }); + + const handleRefresh = () => { + refetchMetrics(); + refetchSummary(); + refetchErrors(); + refetchRevenueByModel(); + }; return ( -
-
+
+
+

Dashboard

+ +
+ +
-

- Admin Dashboard -

-

- Monitor and manage wallet balances +

+ Usage Analytics +

+

+ Monitor requests, errors, and revenue over the last {timeRange} hours

-
- { - if (value) { - setDisplayUnit(value as DisplayUnit); - } - }} - variant='outline' - size='sm' - > - mSAT - sat - - USD - - +
+ + +
-
-
- +
+ {summaryLoading ? ( +
Loading summary...
+ ) : summaryData ? ( + + ) : null} + +
+ {metricsLoading ? ( +
+ Loading metrics... +
+ ) : metricsData && metricsData.metrics.length > 0 ? ( + <> +
+ ({ + ...m, + revenue_sats: m.revenue_msats / 1000, + refunds_sats: m.refunds_msats / 1000, + net_revenue_sats: + (m.revenue_msats - m.refunds_msats) / 1000, + })) as Array & { timestamp: string }>} + title='Revenue Over Time (sats)' + dataKeys={[ + { + key: 'revenue_sats', + name: 'Revenue', + color: '#10b981', + }, + { + key: 'net_revenue_sats', + name: 'Net Revenue', + color: '#059669', + }, + { + key: 'refunds_sats', + name: 'Refunds', + color: '#ef4444', + }, + ]} + /> +
+ & { timestamp: string }>} + title='Request Volume' + dataKeys={[ + { + key: 'total_requests', + name: 'Total Requests', + color: '#3b82f6', + }, + { + key: 'successful_chat_completions', + name: 'Successful', + color: '#22c55e', + }, + { + key: 'failed_requests', + name: 'Failed', + color: '#f43f5e', + }, + ]} + /> + & { timestamp: string }>} + title='Error Tracking' + dataKeys={[ + { + key: 'errors', + name: 'Errors', + color: '#f97316', + }, + { + key: 'warnings', + name: 'Warnings', + color: '#eab308', + }, + { + key: 'upstream_errors', + name: 'Upstream Errors', + color: '#dc2626', + }, + ]} + /> + & { timestamp: string }>} + title='Payment Activity' + dataKeys={[ + { + key: 'payment_processed', + name: 'Payments Processed', + color: '#8b5cf6', + }, + ]} + /> +
+ {summaryData && summaryData.unique_models.length > 0 && ( + + + Active Models + + +
+ {summaryData.unique_models.map((model) => ( + + {model} + + ))} +
+
+
+ )} + {summaryData && + summaryData.error_types && + Object.keys(summaryData.error_types).length > 0 && ( + + + Error Types Distribution + + +
+ {Object.entries(summaryData.error_types) + .sort(([, a], [, b]) => b - a) + .map(([type, count]) => ( +
+ {type} + + {count} + +
+ ))} +
+
+
+ )} +
+ + ) : ( + + + No Data Available + + +

+ No metrics data found for the selected time range. This + could be because no requests have been logged yet or the + log files are not available. +

+
+
+ )}
-
- Loading revenue by model...
+ ) : revenueByModelData && revenueByModelData.models.length > 0 ? ( + -
+ ) : null} + + {errorLoading ? ( +
Loading errors...
+ ) : errorData ? ( + + ) : null}
diff --git a/ui/app/usage/page.tsx b/ui/app/usage/page.tsx deleted file mode 100644 index 69673470..00000000 --- a/ui/app/usage/page.tsx +++ /dev/null @@ -1,302 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { AppSidebar } from '@/components/app-sidebar'; -import { SiteHeader } from '@/components/site-header'; -import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; -import { UsageMetricsChart } from '@/components/usage-metrics-chart'; -import { UsageSummaryCards } from '@/components/usage-summary-cards'; -import { ErrorDetailsTable } from '@/components/error-details-table'; -import { RevenueByModelTable } from '@/components/revenue-by-model-table'; -import { AdminService } from '@/lib/api/services/admin'; -import { Button } from '@/components/ui/button'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { RefreshCw } from 'lucide-react'; - -export default function UsagePage() { - const [timeRange, setTimeRange] = useState('24'); - const [interval, setInterval] = useState('15'); - - const { - data: metricsData, - isLoading: metricsLoading, - refetch: refetchMetrics, - } = useQuery({ - queryKey: ['usage-metrics', interval, timeRange], - queryFn: () => - AdminService.getUsageMetrics(parseInt(interval), parseInt(timeRange)), - refetchInterval: 60_000, - staleTime: 30_000, - }); - - const { - data: summaryData, - isLoading: summaryLoading, - refetch: refetchSummary, - } = useQuery({ - queryKey: ['usage-summary', timeRange], - queryFn: () => AdminService.getUsageSummary(parseInt(timeRange)), - refetchInterval: 60_000, - staleTime: 30_000, - }); - - const { - data: errorData, - isLoading: errorLoading, - refetch: refetchErrors, - } = useQuery({ - queryKey: ['usage-errors', timeRange], - queryFn: () => AdminService.getErrorDetails(parseInt(timeRange), 100), - refetchInterval: 60_000, - staleTime: 30_000, - }); - - const { - data: revenueByModelData, - isLoading: revenueByModelLoading, - refetch: refetchRevenueByModel, - } = useQuery({ - queryKey: ['revenue-by-model', timeRange], - queryFn: () => AdminService.getRevenueByModel(parseInt(timeRange), 20), - refetchInterval: 60_000, - staleTime: 30_000, - }); - - const handleRefresh = () => { - refetchMetrics(); - refetchSummary(); - refetchErrors(); - refetchRevenueByModel(); - }; - - return ( - - - - -
-
-
-

- Usage Tracking -

-

- Monitor system usage, requests, and errors over time -

-
-
- - - -
-
- -
- {summaryLoading ? ( -
Loading summary...
- ) : summaryData ? ( - - ) : null} - -
- {metricsLoading ? ( -
- Loading metrics... -
- ) : metricsData && metricsData.metrics.length > 0 ? ( - <> - & { timestamp: string }>} - title='Request Volume' - dataKeys={[ - { - key: 'total_requests', - name: 'Total Requests', - color: '#3b82f6', - }, - { - key: 'successful_chat_completions', - name: 'Successful', - color: '#10b981', - }, - { - key: 'failed_requests', - name: 'Failed', - color: '#ef4444', - }, - ]} - /> - ({ - ...m, - revenue_sats: m.revenue_msats / 1000, - refunds_sats: m.refunds_msats / 1000, - net_revenue_sats: - (m.revenue_msats - m.refunds_msats) / 1000, - })) as Array & { timestamp: string }>} - title='Revenue Over Time (sats)' - dataKeys={[ - { - key: 'revenue_sats', - name: 'Revenue', - color: '#10b981', - }, - { - key: 'refunds_sats', - name: 'Refunds', - color: '#ef4444', - }, - { - key: 'net_revenue_sats', - name: 'Net Revenue', - color: '#059669', - }, - ]} - /> - & { timestamp: string }>} - title='Error Tracking' - dataKeys={[ - { - key: 'errors', - name: 'Errors', - color: '#f97316', - }, - { - key: 'warnings', - name: 'Warnings', - color: '#eab308', - }, - { - key: 'upstream_errors', - name: 'Upstream Errors', - color: '#ef4444', - }, - ]} - /> - & { timestamp: string }>} - title='Payment Activity' - dataKeys={[ - { - key: 'payment_processed', - name: 'Payments Processed', - color: '#6366f1', - }, - ]} - /> - - ) : ( - - - No Data Available - - -

- No metrics data found for the selected time range. This - could be because no requests have been logged yet or the - log files are not available. -

-
-
- )} -
- - {revenueByModelLoading ? ( -
Loading revenue by model...
- ) : revenueByModelData && revenueByModelData.models.length > 0 ? ( - - ) : null} - - {errorLoading ? ( -
Loading errors...
- ) : errorData ? ( - - ) : null} - - {summaryData && summaryData.unique_models.length > 0 && ( - - - Active Models - - -
- {summaryData.unique_models.map((model) => ( - - {model} - - ))} -
-
-
- )} - - {summaryData && - summaryData.error_types && - Object.keys(summaryData.error_types).length > 0 && ( - - - Error Types Distribution - - -
- {Object.entries(summaryData.error_types) - .sort(([, a], [, b]) => b - a) - .map(([type, count]) => ( -
- {type} - - {count} - -
- ))} -
-
-
- )} -
-
-
-
- ); -}