Implement API health check badge in NavBar, enhance PlatformLineChart with metric filtering, and refactor PlatformSummary for improved layout and styling. Update dashboard to prioritize Platform Summary tab.

This commit is contained in:
2025-11-08 04:29:50 +07:00
parent 7b8d435521
commit 83d13bde74
4 changed files with 502 additions and 364 deletions

View File

@@ -9,6 +9,12 @@ import Logo from '../../../assets/img/logo.png'
import {Loader} from '../../atoms' import {Loader} from '../../atoms'
import useCookie from '../../../hooks/useCookie' import useCookie from '../../../hooks/useCookie'
interface HealthCheckResponse {
status: string
totalDuration?: string
entries?: Record<string, any>
}
const navigation = [ const navigation = [
{ href: '/bots', name: 'Strategies' }, { href: '/bots', name: 'Strategies' },
{ href: '/backtest', name: 'Backtests' }, { href: '/backtest', name: 'Backtests' },
@@ -18,13 +24,83 @@ const navigation = [
{ href: '/admin', name: 'Admin' }, { href: '/admin', name: 'Admin' },
] ]
// API Health Check Badge Component
const ApiHealthBadge = () => {
const { apiUrl } = useApiUrlStore()
const { data: healthData, isLoading, error } = useQuery<HealthCheckResponse>({
queryKey: ['apiHealth', apiUrl],
queryFn: async () => {
try {
const response = await fetch(`${apiUrl}/health`)
if (!response.ok) throw new Error('Health check failed')
const data = await response.json()
// Handle .NET health check response format
// The status might be in the root or we need to check entries
return data
} catch (error) {
console.error('Health check error:', error)
return { status: 'Unhealthy' }
}
},
refetchInterval: 30000, // Refetch every 30 seconds
retry: 1,
staleTime: 20000,
})
const getHealthStatus = () => {
if (isLoading) return { color: 'badge-ghost', label: '...', status: 'Checking...' }
if (error) return { color: 'badge-error', label: '●', status: 'Health Check Failed' }
// Check status field (could be "Healthy", "Degraded", "Unhealthy")
const status = healthData?.status || 'Unknown'
const statusLower = status.toLowerCase()
// Also check entries if status is not directly available
let overallStatus = statusLower
if (healthData?.entries) {
const entries = Object.values(healthData.entries)
const hasUnhealthy = entries.some((entry: any) => entry?.status?.toLowerCase() === 'unhealthy')
const hasDegraded = entries.some((entry: any) => entry?.status?.toLowerCase() === 'degraded')
if (hasUnhealthy) {
overallStatus = 'unhealthy'
} else if (hasDegraded) {
overallStatus = 'degraded'
} else if (entries.length > 0) {
overallStatus = 'healthy'
}
}
switch (overallStatus) {
case 'healthy':
return { color: 'badge-success', label: '●', status: 'API Status: Healthy' }
case 'degraded':
return { color: 'badge-warning', label: '●', status: 'API Status: Degraded' }
case 'unhealthy':
return { color: 'badge-error', label: '●', status: 'API Status: Unhealthy' }
default:
return { color: 'badge-ghost', label: '?', status: 'API Status: Unknown' }
}
}
const healthStatus = getHealthStatus()
return (
<div className="tooltip tooltip-bottom" data-tip={healthStatus.status}>
<div className={`badge ${healthStatus.color} badge-xs cursor-help p-0.5`}>
<span className="text-[8px] leading-none">{healthStatus.label}</span>
</div>
</div>
)
}
// Global Loader Component // Global Loader Component
const GlobalLoader = () => { const GlobalLoader = () => {
const isFetching = useIsFetching() const isFetching = useIsFetching()
return isFetching ? ( return isFetching ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Loader size="xs"></Loader> <Loader size="xs"></Loader>
<span className="text-xs text-base-content/70 hidden lg:inline">Loading...</span>
</div> </div>
) : null ) : null
} }
@@ -279,7 +355,7 @@ export default function NavBar() {
{/* Navbar */} {/* Navbar */}
<div className="navbar bg-base-300 shadow-lg border-b border-base-content/10 w-full relative z-50"> <div className="navbar bg-base-300 shadow-lg border-b border-base-content/10 w-full relative z-50">
{/* Navbar Start - Mobile Menu Button and Logo */} {/* Navbar Start - Mobile Menu Button and Logo */}
<div className="navbar-start"> <div className="navbar-start flex items-center gap-2">
<button <button
className="btn btn-ghost lg:hidden" className="btn btn-ghost lg:hidden"
onClick={toggleSidebar} onClick={toggleSidebar}
@@ -298,6 +374,7 @@ export default function NavBar() {
<Link className="btn btn-ghost text-xl" to="/"> <Link className="btn btn-ghost text-xl" to="/">
<img src={Logo} className="h-8 w-8 object-contain" alt="logo" /> <img src={Logo} className="h-8 w-8 object-contain" alt="logo" />
</Link> </Link>
<ApiHealthBadge />
</div> </div>
{/* Navbar Center - Desktop Navigation */} {/* Navbar Center - Desktop Navigation */}
@@ -326,7 +403,6 @@ export default function NavBar() {
<div className="navbar-end gap-2"> <div className="navbar-end gap-2">
<GlobalLoader /> <GlobalLoader />
<div className="hidden md:flex items-center gap-2"> <div className="hidden md:flex items-center gap-2">
<span className="text-xs opacity-70 hidden xl:inline">Environment:</span>
<EnvironmentDropdown isInSidebar={false} /> <EnvironmentDropdown isInSidebar={false} />
</div> </div>
{/* Show environment badge on mobile */} {/* Show environment badge on mobile */}
@@ -394,9 +470,6 @@ export default function NavBar() {
{authenticated && ( {authenticated && (
<li> <li>
<div className="px-4 py-2"> <div className="px-4 py-2">
<div className="flex items-center justify-between mb-2">
<span className="text-sm opacity-70">Environment:</span>
</div>
<EnvironmentDropdown isInSidebar={true} /> <EnvironmentDropdown isInSidebar={true} />
</div> </div>
</li> </li>

View File

@@ -14,12 +14,15 @@ interface CurrentPlatformData {
lastUpdated?: Date | string lastUpdated?: Date | string
} }
type MetricType = 'agents' | 'strategies' | 'volume' | 'pnl' | 'openInterest' | 'positionCount'
interface PlatformLineChartProps { interface PlatformLineChartProps {
dailySnapshots: DailySnapshot[] dailySnapshots: DailySnapshot[]
currentData?: CurrentPlatformData currentData?: CurrentPlatformData
title?: string title?: string
height?: number height?: number
width?: number width?: number
metrics?: MetricType[] // Filter which metrics to display
} }
function PlatformLineChart({ function PlatformLineChart({
@@ -27,7 +30,8 @@ function PlatformLineChart({
currentData, currentData,
title = "Platform Metrics Over Time", title = "Platform Metrics Over Time",
height = 400, height = 400,
width = 800 width = 800,
metrics = ['agents', 'strategies', 'volume', 'pnl', 'openInterest', 'positionCount'] // Default: show all
}: PlatformLineChartProps) { }: PlatformLineChartProps) {
const theme = useTheme().themeProperty() const theme = useTheme().themeProperty()
@@ -78,13 +82,15 @@ function PlatformLineChart({
return `$${value.toFixed(0)}` return `$${value.toFixed(0)}`
} }
const plotData = [ const allPlotData = [
{ {
key: 'agents' as MetricType,
type: 'scatter' as const, type: 'scatter' as const,
mode: 'lines+markers' as const, mode: 'lines+markers' as const,
name: 'Total Agents', name: 'Total Agents',
x: dates, x: dates,
y: agents, y: agents,
yaxis: 'y' as const,
line: { line: {
color: theme.primary, color: theme.primary,
width: 2 width: 2
@@ -98,11 +104,13 @@ function PlatformLineChart({
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>' (currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
}, },
{ {
key: 'strategies' as MetricType,
type: 'scatter' as const, type: 'scatter' as const,
mode: 'lines+markers' as const, mode: 'lines+markers' as const,
name: 'Active Strategies', name: 'Active Strategies',
x: dates, x: dates,
y: strategies, y: strategies,
yaxis: 'y' as const,
line: { line: {
color: theme.success, color: theme.success,
width: 2 width: 2
@@ -116,12 +124,13 @@ function PlatformLineChart({
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>' (currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
}, },
{ {
key: 'volume' as MetricType,
type: 'scatter' as const, type: 'scatter' as const,
mode: 'lines+markers' as const, mode: 'lines+markers' as const,
name: 'Total Volume', name: 'Total Volume',
x: dates, x: dates,
y: volume, y: volume,
yaxis: 'y2', yaxis: 'y2' as const,
line: { line: {
color: theme.info, color: theme.info,
width: 2 width: 2
@@ -135,12 +144,13 @@ function PlatformLineChart({
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>' (currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
}, },
{ {
key: 'pnl' as MetricType,
type: 'scatter' as const, type: 'scatter' as const,
mode: 'lines+markers' as const, mode: 'lines+markers' as const,
name: 'Platform PnL', name: 'Platform PnL',
x: dates, x: dates,
y: pnl, y: pnl,
yaxis: 'y2', yaxis: 'y2' as const,
line: { line: {
color: pnl.some(p => p >= 0) ? theme.success : theme.error, color: pnl.some(p => p >= 0) ? theme.success : theme.error,
width: 2 width: 2
@@ -154,12 +164,13 @@ function PlatformLineChart({
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>' (currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
}, },
{ {
key: 'openInterest' as MetricType,
type: 'scatter' as const, type: 'scatter' as const,
mode: 'lines+markers' as const, mode: 'lines+markers' as const,
name: 'Open Interest', name: 'Open Interest',
x: dates, x: dates,
y: openInterest, y: openInterest,
yaxis: 'y2', yaxis: 'y2' as const,
line: { line: {
color: theme.warning, color: theme.warning,
width: 2 width: 2
@@ -173,11 +184,13 @@ function PlatformLineChart({
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>' (currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
}, },
{ {
key: 'positionCount' as MetricType,
type: 'scatter' as const, type: 'scatter' as const,
mode: 'lines+markers' as const, mode: 'lines+markers' as const,
name: 'Position Count', name: 'Position Count',
x: dates, x: dates,
y: positionCount, y: positionCount,
yaxis: 'y' as const,
line: { line: {
color: theme.secondary, color: theme.secondary,
width: 2 width: 2
@@ -192,6 +205,11 @@ function PlatformLineChart({
} }
] ]
// Filter plot data based on metrics prop and remove key property
const plotData = allPlotData
.filter(item => metrics.includes(item.key))
.map(({ key, ...rest }) => rest)
const layout = { const layout = {
title: { title: {
text: title, text: title,
@@ -214,7 +232,7 @@ function PlatformLineChart({
}, },
yaxis: { yaxis: {
title: { title: {
text: 'Count', text: plotData.some(d => d.yaxis === 'y') ? 'Count' : '',
font: { font: {
color: theme['base-content'] || '#ffffff' color: theme['base-content'] || '#ffffff'
} }
@@ -223,11 +241,12 @@ function PlatformLineChart({
gridcolor: theme.neutral || '#333333', gridcolor: theme.neutral || '#333333',
showgrid: true, showgrid: true,
zeroline: false, zeroline: false,
side: 'left' as const side: 'left' as const,
visible: plotData.some(d => d.yaxis === 'y')
}, },
yaxis2: { yaxis2: {
title: { title: {
text: 'Amount ($)', text: plotData.some(d => d.yaxis === 'y2') ? 'Amount ($)' : '',
font: { font: {
color: theme['base-content'] || '#ffffff' color: theme['base-content'] || '#ffffff'
} }
@@ -238,7 +257,8 @@ function PlatformLineChart({
zeroline: false, zeroline: false,
side: 'right' as const, side: 'right' as const,
overlaying: 'y' as const, overlaying: 'y' as const,
tickformat: '$,.0f' tickformat: '$,.0f',
visible: plotData.some(d => d.yaxis === 'y2')
}, },
legend: { legend: {
font: { font: {

View File

@@ -13,43 +13,44 @@ import PlatformSummary from './platformSummary'
const tabs: ITabsType = [ const tabs: ITabsType = [
{ {
Component: Monitoring, Component: PlatformSummary,
index: 1, index: 1,
label: 'Platform Summary',
},
{
Component: Monitoring,
index: 2,
label: 'Monitoring', label: 'Monitoring',
}, },
{ {
Component: Analytics, Component: Analytics,
index: 2, index: 3,
label: 'Analytics', label: 'Analytics',
}, },
{ {
Component: BestAgents, Component: BestAgents,
index: 3, index: 4,
label: 'Best Agents', label: 'Best Agents',
}, },
{ {
Component: AgentSearch, Component: AgentSearch,
index: 4, index: 5,
label: 'Agent Search', label: 'Agent Search',
}, },
{ {
Component: AgentIndex, Component: AgentIndex,
index: 5, index: 6,
label: 'Agent Index', label: 'Agent Index',
}, },
{ {
Component: AgentStrategy, Component: AgentStrategy,
index: 6,
label: 'Agent Strategy',
},
{
Component: PlatformSummary,
index: 7, index: 7,
label: 'Platform Summary', label: 'Agent Strategy',
}, },
] ]
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
// Platform Summary is now the first tab (index 1)
const [selectedTab, setSelectedTab] = useState<number>(tabs[0].index) const [selectedTab, setSelectedTab] = useState<number>(tabs[0].index)
useEffect(() => {}, []) useEffect(() => {}, [])

View File

@@ -77,19 +77,19 @@ function PlatformSummary({index}: { index: number }) {
const formatChangeIndicator = (change: number) => { const formatChangeIndicator = (change: number) => {
if (change > 0) { if (change > 0) {
return ( return (
<span className="text-green-500"> <span className="text-success">
+{change} Today +{change} Today
</span> </span>
) )
} else if (change < 0) { } else if (change < 0) {
return ( return (
<span className="text-red-500"> <span className="text-error">
{change} Today {change} Today
</span> </span>
) )
} }
return ( return (
<span className="text-gray-500"> <span className="text-base-content/60">
No change No change
</span> </span>
) )
@@ -130,37 +130,20 @@ function PlatformSummary({index}: { index: number }) {
return ( return (
<div className="p-6 bg-base-100 min-h-screen"> <div className="p-6 bg-base-100 min-h-screen">
{/* Subtle refetching indicator */} {/* Subtle refetching indicator */}
{isFetching && data && (
<div className="fixed top-4 right-4 z-50">
<div className="bg-blue-500 text-white px-3 py-1 rounded-full text-sm flex items-center gap-2">
<div className="loading loading-spinner loading-xs"></div>
Updating...
</div>
</div>
)}
{/* Header */}
<div className="mb-8">
<h1 className="text-4xl font-bold text-white mb-2">
{formatNumber(platformData?.totalActiveStrategies || 0)} Strategies Deployed
</h1>
<div className="text-lg">
{platformData && formatChangeIndicator(changesToday.strategiesChange)}
</div>
</div>
{/* Main Stats Grid */} {/* Main Stats Grid */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{/* Total Volume Traded */} {/* Total Volume Traded */}
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Volume Traded</h3> <div className="card-body">
<div className="text-3xl font-bold text-white mb-1"> <h3 className="card-title text-base-content/70">Total Volume Traded</h3>
<div className="text-3xl font-bold text-base-content mb-1">
{formatCurrency(platformData?.totalPlatformVolume || 0)} {formatCurrency(platformData?.totalPlatformVolume || 0)}
</div> </div>
<div <div
className={`text-sm ${changesToday.volumeChange >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm ${changesToday.volumeChange >= 0 ? 'text-success' : 'text-error'}`}>
{changesToday.volumeChange >= 0 ? '+' : ''}{formatCurrency(changesToday.volumeChange)} Today {changesToday.volumeChange >= 0 ? '+' : ''}{formatCurrency(changesToday.volumeChange)} Today
<span className="ml-2 text-gray-400"> <span className="ml-2 text-base-content/60">
({formatPercentageChange(platformData?.totalPlatformVolume || 0, changesToday.volumeChange)}) ({formatPercentageChange(platformData?.totalPlatformVolume || 0, changesToday.volumeChange)})
</span> </span>
</div> </div>
@@ -178,7 +161,7 @@ function PlatformSummary({index}: { index: number }) {
return ( return (
<div <div
key={`historical-${index}`} key={`historical-${index}`}
className="flex-1 bg-green-500 rounded-sm opacity-60 hover:opacity-80 transition-opacity" className="flex-1 bg-success rounded-sm opacity-60 hover:opacity-80 transition-opacity"
style={{ height: `${Math.max(height, 4)}%` }} style={{ height: `${Math.max(height, 4)}%` }}
title={`${new Date(snapshot.date || 0).toLocaleDateString()}: ${formatCurrency(snapshot.totalVolume || 0)}`} title={`${new Date(snapshot.date || 0).toLocaleDateString()}: ${formatCurrency(snapshot.totalVolume || 0)}`}
/> />
@@ -186,7 +169,7 @@ function PlatformSummary({index}: { index: number }) {
})} })}
{/* Current data bar */} {/* Current data bar */}
<div <div
className="flex-1 bg-blue-500 rounded-sm opacity-80 hover:opacity-100 transition-opacity border-2 border-blue-300" className="flex-1 bg-primary rounded-sm opacity-80 hover:opacity-100 transition-opacity border-2 border-primary/50"
style={{ style={{
height: `${(() => { height: `${(() => {
const maxVolume = Math.max(...platformData.dailySnapshots?.map(s => s.totalVolume || 0) || [0], platformData?.totalPlatformVolume || 0) const maxVolume = Math.max(...platformData.dailySnapshots?.map(s => s.totalVolume || 0) || [0], platformData?.totalPlatformVolume || 0)
@@ -198,193 +181,230 @@ function PlatformSummary({index}: { index: number }) {
/> />
</> </>
) : ( ) : (
<div className="w-full h-8 bg-green-500 rounded opacity-20"></div> <div className="w-full h-8 bg-success rounded opacity-20"></div>
)} )}
</div> </div>
</div> </div>
</div>
{/* Top 3 Most Profitable */} {/* Top 3 Most Profitable */}
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<div className="card-body">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<span className="text-2xl">🤑</span> <span className="text-2xl">🤑</span>
<h3 className="text-lg font-semibold text-gray-400">Top 3 Most Profitable</h3> <h3 className="card-title text-base-content/70">Top 3 Most Profitable</h3>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => ( {topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
<div key={index} className="flex items-center justify-between"> <div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center"> <div className="avatar placeholder">
<span className="text-xs font-bold text-white"> <div className="bg-primary text-primary-content rounded-full w-8">
<span className="text-xs font-bold">
{strategy.strategyName?.charAt(0) || 'S'} {strategy.strategyName?.charAt(0) || 'S'}
</span> </span>
</div> </div>
</div>
<div> <div>
<div className="text-sm text-white font-medium"> <div className="text-sm text-base-content font-medium">
{strategy.strategyName || '[Strategy Name]'} {strategy.strategyName || '[Strategy Name]'}
</div> </div>
<div className="text-xs text-gray-400">📧</div> <div className="text-xs text-base-content/60">📧</div>
</div> </div>
</div> </div>
<div <div
className={`text-sm font-bold ${strategy.netPnL && strategy.netPnL >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm font-bold ${strategy.netPnL && strategy.netPnL >= 0 ? 'text-success' : 'text-error'}`}>
{strategy.netPnL && strategy.netPnL >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)} {strategy.netPnL && strategy.netPnL >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
</div> </div>
</div> </div>
)) || ( )) || (
<div className="text-gray-500 text-sm">No profitable strategies found</div> <div className="text-base-content/60 text-sm">No profitable strategies found</div>
)} )}
</div> </div>
</div> </div>
</div>
{/* Top 3 Rising (by ROI) */} {/* Top 3 Rising (by ROI) */}
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<div className="card-body">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<span className="text-2xl">📈</span> <span className="text-2xl">📈</span>
<h3 className="text-lg font-semibold text-gray-400">Top 3 by ROI</h3> <h3 className="card-title text-base-content/70">Top 3 by ROI</h3>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => ( {topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
<div key={index} className="flex items-center justify-between"> <div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center"> <div className="avatar placeholder">
<span className="text-xs font-bold text-white"> <div className="bg-success text-success-content rounded-full w-8">
<span className="text-xs font-bold">
{strategy.strategyName?.charAt(0) || 'S'} {strategy.strategyName?.charAt(0) || 'S'}
</span> </span>
</div> </div>
</div>
<div> <div>
<div className="text-sm text-white font-medium"> <div className="text-sm text-base-content font-medium">
{strategy.strategyName || '[Strategy Name]'} {strategy.strategyName || '[Strategy Name]'}
</div> </div>
<div className="text-xs text-gray-400"> <div className="text-xs text-base-content/60">
Vol: {formatCurrency(strategy.volume || 0)} Vol: {formatCurrency(strategy.volume || 0)}
</div> </div>
</div> </div>
</div> </div>
<div className="text-right"> <div className="text-right">
<div <div
className={`text-sm font-bold ${(strategy.roi || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm font-bold ${(strategy.roi || 0) >= 0 ? 'text-success' : 'text-error'}`}>
{(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}% {(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}%
</div> </div>
<div <div
className={`text-xs ${(strategy.netPnL || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}> className={`text-xs ${(strategy.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
{(strategy.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)} {(strategy.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
</div> </div>
</div> </div>
</div> </div>
)) || ( )) || (
<div className="text-gray-500 text-sm">No ROI data available</div> <div className="text-base-content/60 text-sm">No ROI data available</div>
)} )}
</div> </div>
</div> </div>
</div>
{/* Top 3 Agents by PnL */} {/* Top 3 Agents by PnL */}
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<div className="card-body">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<span className="text-2xl">👥</span> <span className="text-2xl">👥</span>
<h3 className="text-lg font-semibold text-gray-400">Top 3 Agents by PnL</h3> <h3 className="card-title text-base-content/70">Top 3 Agents by PnL</h3>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{topAgentsByPnL?.slice(0, 3).map((agent, index) => ( {topAgentsByPnL?.slice(0, 3).map((agent, index) => (
<div key={index} className="flex items-center justify-between"> <div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center"> <div className="avatar placeholder">
<span className="text-xs font-bold text-white"> <div className="bg-secondary text-secondary-content rounded-full w-8">
<span className="text-xs font-bold">
{agent.agentName?.charAt(0) || 'A'} {agent.agentName?.charAt(0) || 'A'}
</span> </span>
</div> </div>
</div>
<div> <div>
<div className="text-sm text-white font-medium"> <div className="text-sm text-base-content font-medium">
{agent.agentName || '[Agent Name]'} {agent.agentName || '[Agent Name]'}
</div> </div>
<div className="text-xs text-gray-400"> <div className="text-xs text-base-content/60">
{agent.activeStrategiesCount || 0} strategies {agent.activeStrategiesCount || 0} strategies
</div> </div>
</div> </div>
</div> </div>
<div className="text-right"> <div className="text-right">
<div <div
className={`text-sm font-bold ${(agent.netPnL || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm font-bold ${(agent.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
{(agent.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(agent.netPnL || 0)} {(agent.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(agent.netPnL || 0)}
</div> </div>
<div className="text-xs text-gray-400"> <div className="text-xs text-base-content/60">
{(agent.totalROI || 0).toFixed(2)}% ROI {(agent.totalROI || 0).toFixed(2)}% ROI
</div> </div>
</div> </div>
</div> </div>
)) || ( )) || (
<div className="text-gray-500 text-sm">No agent data available</div> <div className="text-base-content/60 text-sm">No agent data available</div>
)} )}
</div> </div>
</div> </div>
</div> </div>
</div>
{/* Platform Summary Stats */} {/* Platform Summary Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-6 mb-8"> <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-6 mb-8">
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Agents</h3> <div className="card-body">
<div className="text-3xl font-bold text-white mb-1"> <h3 className="card-title text-base-content/70 text-base">Total Agents</h3>
<div className="text-3xl font-bold text-base-content mb-1">
{formatNumber(platformData?.totalAgents || 0)} {formatNumber(platformData?.totalAgents || 0)}
</div> </div>
<div <div
className={`text-sm ${changesToday.agentsChange >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm ${changesToday.agentsChange >= 0 ? 'text-success' : 'text-error'}`}>
{formatChangeIndicator(changesToday.agentsChange)} {formatChangeIndicator(changesToday.agentsChange)}
</div> </div>
</div> </div>
</div>
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Active Strategies</h3> <div className="card-body">
<div className="text-3xl font-bold text-white mb-1"> <h3 className="card-title text-base-content/70 text-base">Strategies Deployed</h3>
<div className="text-3xl font-bold text-base-content mb-1">
{formatNumber(platformData?.totalActiveStrategies || 0)} {formatNumber(platformData?.totalActiveStrategies || 0)}
</div> </div>
<div <div
className={`text-sm ${changesToday.strategiesChange >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm ${changesToday.strategiesChange >= 0 ? 'text-success' : 'text-error'}`}>
{formatChangeIndicator(changesToday.strategiesChange)} {formatChangeIndicator(changesToday.strategiesChange)}
</div> </div>
</div> </div>
</div>
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Platform PnL</h3> <div className="card-body">
<h3 className="card-title text-base-content/70 text-base">Active Strategies</h3>
<div className="text-3xl font-bold text-base-content mb-1">
{formatNumber(platformData?.totalActiveStrategies || 0)}
</div>
<div <div
className={`text-3xl font-bold ${(platformData?.totalPlatformPnL || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm ${changesToday.strategiesChange >= 0 ? 'text-success' : 'text-error'}`}>
{formatChangeIndicator(changesToday.strategiesChange)}
</div>
</div>
</div>
<div className="card bg-base-200">
<div className="card-body">
<h3 className="card-title text-base-content/70 text-base">Total Platform PnL</h3>
<div
className={`text-3xl font-bold ${(platformData?.totalPlatformPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
{(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)} {(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)}
</div> </div>
<div className="text-sm text-gray-400 mb-1"> <div className="text-sm text-base-content/60 mb-1">
Fees: {formatCurrency(platformData?.totalPlatformFees || 0)} Fees: {formatCurrency(platformData?.totalPlatformFees || 0)}
</div> </div>
<div <div
className={`text-sm ${changesToday.pnLChange >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm ${changesToday.pnLChange >= 0 ? 'text-success' : 'text-error'}`}>
{changesToday.pnLChange >= 0 ? '+' : ''}{formatCurrency(changesToday.pnLChange)} Today {changesToday.pnLChange >= 0 ? '+' : ''}{formatCurrency(changesToday.pnLChange)} Today
</div> </div>
</div> </div>
</div>
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Open Interest</h3> <div className="card-body">
<div className="text-3xl font-bold text-white mb-1"> <h3 className="card-title text-base-content/70 text-base">Open Interest</h3>
<div className="text-3xl font-bold text-base-content mb-1">
{formatCurrency(platformData?.openInterest || 0)} {formatCurrency(platformData?.openInterest || 0)}
</div> </div>
<div <div
className={`text-sm ${changesToday.openInterestChange >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm ${changesToday.openInterestChange >= 0 ? 'text-success' : 'text-error'}`}>
{changesToday.openInterestChange >= 0 ? '+' : ''}{formatCurrency(changesToday.openInterestChange)} Today {changesToday.openInterestChange >= 0 ? '+' : ''}{formatCurrency(changesToday.openInterestChange)} Today
</div> </div>
</div> </div>
</div>
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Positions</h3> <div className="card-body">
<div className="text-3xl font-bold text-white mb-1"> <h3 className="card-title text-base-content/70 text-base">Total Positions</h3>
<div className="text-3xl font-bold text-base-content mb-1">
{formatNumber(platformData?.totalPositionCount || 0)} {formatNumber(platformData?.totalPositionCount || 0)}
</div> </div>
<div <div
className={`text-sm ${changesToday.positionCountChange >= 0 ? 'text-green-500' : 'text-red-500'}`}> className={`text-sm ${changesToday.positionCountChange >= 0 ? 'text-success' : 'text-error'}`}>
{formatChangeIndicator(changesToday.positionCountChange)} {formatChangeIndicator(changesToday.positionCountChange)}
</div> </div>
</div> </div>
</div>
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Long Positions</h3> <div className="card-body">
<div className="text-3xl font-bold text-green-400 mb-1"> <h3 className="card-title text-base-content/70 text-base">Long Positions</h3>
<div className="text-3xl font-bold text-success mb-1">
{formatNumber(platformData?.positionCountByDirection?.Long || 0)} {formatNumber(platformData?.positionCountByDirection?.Long || 0)}
</div> </div>
<div className="text-sm text-gray-400"> <div className="text-sm text-base-content/60">
{(() => { {(() => {
const longCount = platformData?.positionCountByDirection?.Long || 0; const longCount = platformData?.positionCountByDirection?.Long || 0;
const shortCount = platformData?.positionCountByDirection?.Short || 0; const shortCount = platformData?.positionCountByDirection?.Short || 0;
@@ -394,13 +414,15 @@ function PlatformSummary({index}: { index: number }) {
})()} of total positions })()} of total positions
</div> </div>
</div> </div>
</div>
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Short Positions</h3> <div className="card-body">
<div className="text-3xl font-bold text-red-400 mb-1"> <h3 className="card-title text-base-content/70 text-base">Short Positions</h3>
<div className="text-3xl font-bold text-error mb-1">
{formatNumber(platformData?.positionCountByDirection?.Short || 0)} {formatNumber(platformData?.positionCountByDirection?.Short || 0)}
</div> </div>
<div className="text-sm text-gray-400"> <div className="text-sm text-base-content/60">
{(() => { {(() => {
const longCount = platformData?.positionCountByDirection?.Long || 0; const longCount = platformData?.positionCountByDirection?.Long || 0;
const shortCount = platformData?.positionCountByDirection?.Short || 0; const shortCount = platformData?.positionCountByDirection?.Short || 0;
@@ -411,35 +433,50 @@ function PlatformSummary({index}: { index: number }) {
</div> </div>
</div> </div>
</div> </div>
</div>
{/* Position Metrics */} {/* Position Metrics */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
</div> </div>
{/* Platform Metrics Chart */} {/* Platform Metrics Charts */}
<div className="mb-8"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Trading Metrics Chart */}
<PlatformLineChart <PlatformLineChart
dailySnapshots={platformData?.dailySnapshots || []} dailySnapshots={platformData?.dailySnapshots || []}
currentData={{ currentData={{
totalAgents: platformData?.totalAgents,
totalActiveStrategies: platformData?.totalActiveStrategies,
totalPlatformVolume: platformData?.totalPlatformVolume, totalPlatformVolume: platformData?.totalPlatformVolume,
totalPlatformPnL: platformData?.totalPlatformPnL, totalPlatformPnL: platformData?.totalPlatformPnL,
openInterest: platformData?.openInterest, openInterest: platformData?.openInterest,
totalPositionCount: platformData?.totalPositionCount, totalPositionCount: platformData?.totalPositionCount,
lastUpdated: platformData?.lastUpdated lastUpdated: platformData?.lastUpdated
}} }}
title="Platform Metrics Over Time" title="Trading Metrics"
height={400} height={400}
metrics={['volume', 'pnl', 'openInterest', 'positionCount']}
/>
{/* User-Centric Metrics Chart */}
<PlatformLineChart
dailySnapshots={platformData?.dailySnapshots || []}
currentData={{
totalAgents: platformData?.totalAgents,
totalActiveStrategies: platformData?.totalActiveStrategies,
lastUpdated: platformData?.lastUpdated
}}
title="User-Centric Metrics"
height={400}
metrics={['agents', 'strategies']}
/> />
</div> </div>
{/* Volume and Positions by Asset */} {/* Volume and Positions by Asset */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Volume by Asset */} {/* Volume by Asset */}
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-4">Volume by Asset</h3> <div className="card-body">
<h3 className="card-title text-base-content/70">Volume by Asset</h3>
<div className="space-y-3 max-h-80 overflow-y-auto"> <div className="space-y-3 max-h-80 overflow-y-auto">
{platformData?.volumeByAsset && Object.keys(platformData.volumeByAsset).length > 0 ? ( {platformData?.volumeByAsset && Object.keys(platformData.volumeByAsset).length > 0 ? (
Object.entries(platformData.volumeByAsset) Object.entries(platformData.volumeByAsset)
@@ -448,30 +485,33 @@ function PlatformSummary({index}: { index: number }) {
.map(([asset, volume]) => ( .map(([asset, volume]) => (
<div key={asset} className="flex items-center justify-between"> <div key={asset} className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div className="avatar placeholder">
className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center"> <div className="bg-primary text-primary-content rounded-full w-8">
<span className="text-xs font-bold text-white"> <span className="text-xs font-bold">
{asset.substring(0, 2)} {asset.substring(0, 2)}
</span> </span>
</div> </div>
<span className="text-white font-medium">{asset}</span> </div>
<span className="text-base-content font-medium">{asset}</span>
</div> </div>
<div className="text-right"> <div className="text-right">
<div className="text-white font-semibold"> <div className="text-base-content font-semibold">
{formatCurrency(volume)} {formatCurrency(volume)}
</div> </div>
</div> </div>
</div> </div>
)) ))
) : ( ) : (
<div className="text-gray-500 text-sm">No volume data available</div> <div className="text-base-content/60 text-sm">No volume data available</div>
)} )}
</div> </div>
</div> </div>
</div>
{/* Positions by Asset */} {/* Positions by Asset */}
<div className="bg-base-200 rounded-lg p-6"> <div className="card bg-base-200">
<h3 className="text-lg font-semibold text-gray-400 mb-4">Positions by Asset</h3> <div className="card-body">
<h3 className="card-title text-base-content/70">Positions by Asset</h3>
<div className="space-y-3 max-h-80 overflow-y-auto"> <div className="space-y-3 max-h-80 overflow-y-auto">
{platformData?.positionCountByAsset && Object.keys(platformData.positionCountByAsset).length > 0 ? ( {platformData?.positionCountByAsset && Object.keys(platformData.positionCountByAsset).length > 0 ? (
Object.entries(platformData.positionCountByAsset) Object.entries(platformData.positionCountByAsset)
@@ -480,19 +520,20 @@ function PlatformSummary({index}: { index: number }) {
.map(([asset, count]) => ( .map(([asset, count]) => (
<div key={asset} className="flex items-center justify-between"> <div key={asset} className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div className="avatar placeholder">
className="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center"> <div className="bg-secondary text-secondary-content rounded-full w-8">
<span className="text-xs font-bold text-white"> <span className="text-xs font-bold">
{asset.substring(0, 2)} {asset.substring(0, 2)}
</span> </span>
</div> </div>
<span className="text-white font-medium">{asset}</span> </div>
<span className="text-base-content font-medium">{asset}</span>
</div> </div>
<div className="text-right"> <div className="text-right">
<div className="text-white font-semibold"> <div className="text-base-content font-semibold">
{formatNumber(count)} positions {formatNumber(count)} positions
</div> </div>
<div className="text-xs text-gray-400"> <div className="text-xs text-base-content/60">
{platformData?.totalPositionCount ? {platformData?.totalPositionCount ?
(count / platformData.totalPositionCount * 100).toFixed(1) : 0}% of (count / platformData.totalPositionCount * 100).toFixed(1) : 0}% of
total total
@@ -501,20 +542,22 @@ function PlatformSummary({index}: { index: number }) {
</div> </div>
)) ))
) : ( ) : (
<div className="text-gray-500 text-sm">No position data available</div> <div className="text-base-content/60 text-sm">No position data available</div>
)} )}
</div> </div>
</div> </div>
</div> </div>
</div>
{/* Data Freshness Indicator */} {/* Data Freshness Indicator */}
<div className="bg-base-200 rounded-lg p-4"> <div className="card bg-base-200">
<div className="flex items-center justify-between text-sm text-gray-400"> <div className="card-body">
<div className="flex items-center justify-between text-sm text-base-content/60">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span>Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'}</span> <span>Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'}</span>
{isFetching && ( {isFetching && (
<div className="flex items-center gap-1 text-blue-400"> <div className="flex items-center gap-1 text-primary">
<div className="loading loading-spinner loading-xs"></div> <div className="loading loading-spinner loading-xs"></div>
<span>Refreshing...</span> <span>Refreshing...</span>
</div> </div>
@@ -545,6 +588,7 @@ function PlatformSummary({index}: { index: number }) {
</div> </div>
</div> </div>
</div> </div>
</div>
) )
} }