import React from 'react'; import { ConfigTab } from './ConfigTab'; import { ReconciliationAuditPanel } from './ReconciliationAuditPanel'; import { ShieldCheck, Activity, Cpu, Hexagon, Settings2, Bug, Wifi, WifiOff, TrendingUp, Clock, Crosshair, Zap, Target, ShieldAlert, BrainCircuit, ChevronRight, Pause, Play, AlertTriangle, Database, RefreshCcw, Heart, Info, XCircle } from 'lucide-react'; import type { BotState } from '../hooks/useWebSocket'; import type { Socket } from 'socket.io-client'; import { useAuth } from '../components/AuthContext'; import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi'; import { getPlatformAccessToken } from '../lib/authSession'; import { createRequestId } from '../../../shared/request-id.js'; import { tradingRuntime } from '../lib/runtime'; interface AdminTabProps { botState: BotState; socket: Socket | null; } const ruleDescriptions: { [key: string]: { desc: string; category: string; icon: typeof Hexagon } } = { 'TrendBiasRule': { desc: 'Analyzes 4H and 1H trends to determine the primary market bias using EMA crossovers.', category: 'Trend', icon: TrendingUp }, 'SessionRule': { desc: 'Restricts trading to major market sessions — London and New York overlap.', category: 'Filter', icon: Clock }, 'ZoneRule': { desc: 'Identifies key Support & Resistance zones for high-precision entries.', category: 'Entry', icon: Crosshair }, 'MomentumRule': { desc: 'Analyzes RSI divergence and EMA crossovers for short-term momentum.', category: 'Momentum', icon: Zap }, 'EntryTriggerRule': { desc: 'Final confirmation logic for executing market orders based on pattern detection.', category: 'Entry', icon: Target }, 'RiskManagementRule': { desc: 'Calculates dynamic stop-loss, take-profit targets, and position sizing via ATR.', category: 'Risk', icon: ShieldAlert }, 'AIAnalysisRule': { desc: 'Leverages LLM models (Perplexity / OpenAI) for real-time sentiment validation.', category: 'AI', icon: BrainCircuit } }; const categoryColors: { [key: string]: string } = { 'Trend': 'var(--bl-info-strong)', 'Filter': 'var(--bl-emphasis)', 'Entry': 'var(--bl-warning)', 'Momentum': 'var(--bl-attention)', 'Risk': 'var(--bl-danger)', 'AI': 'var(--bl-success)', }; export const AdminTab = ({ botState, socket }: AdminTabProps) => { const { profile } = useAuth(); const [subTab, setSubTab] = React.useState<'rules' | 'config' | 'debug' | 'health' | 'reconciliation'>('rules'); const [debugLogs, setDebugLogs] = React.useState([]); const logEndRef = React.useRef(null); React.useEffect(() => { if (!socket) return; const handler = (log: any) => { setDebugLogs(prev => [...prev, log].slice(-100)); }; socket.on('debug_log', handler); return () => { socket.off('debug_log', handler); }; }, [socket]); React.useEffect(() => { logEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [debugLogs]); const rules = botState.settings.enabledRules || []; const [botConfig, setBotConfig] = React.useState | null>(null); const [isControlLoading, setIsControlLoading] = React.useState(false); const [controlError, setControlError] = React.useState(null); // DB Sync Controls const [dbSyncEnabled, setDbSyncEnabled] = React.useState(true); const [dbSyncInterval, setDbSyncInterval] = React.useState(300000); const [isDbSyncLoading, setIsDbSyncLoading] = React.useState(false); const observabilityHealth = (botState.health || {}) as any; const tradingControl = botState.health?.tradingControl; const isPaused = tradingControl?.mode === 'PAUSED'; const operationalEvents = (botState.operationalEvents ?? []).filter((event): event is NonNullable => Boolean(event)); const formatDuration = (ms?: number) => { if (!ms || !Number.isFinite(ms)) return 'Idle'; if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`; return `${Math.max(ms, 0).toFixed(0)}ms`; }; const resolveLoopStatus = ( lastRun: number | undefined, isHealthy: boolean, intervalMs: number ): 'Idle' | 'Active' | 'Degraded' | 'Failing' => { if (!lastRun) return 'Idle'; const safeIntervalMs = Math.max(1, Number(intervalMs) || 60000); const staleThresholdMs = Math.max(safeIntervalMs * 2, 120000); const isStale = (Date.now() - lastRun) > staleThresholdMs; if (!isHealthy) { return isStale ? 'Degraded' : 'Failing'; } return isStale ? 'Degraded' : 'Active'; }; const formatTimeAgo = (lastRun?: number) => { if (!lastRun) return 'Waiting for samples...'; const seconds = Math.floor((Date.now() - lastRun) / 1000); if (seconds <= 0) return 'Just now'; if (seconds < 60) return `${seconds}s ago`; return `${Math.floor(seconds / 60)}m ${seconds % 60}s ago`; }; const getStatusColor = (status: string) => { if (status === 'Failing') return 'bg-red-500'; if (status === 'Idle') return 'bg-zinc-600'; if (status === 'Degraded') return 'bg-orange-500 animate-pulse'; return 'bg-emerald-500 animate-pulse'; }; const getStatusTextColor = (status: string) => { if (status === 'Failing') return 'text-red-400'; if (status === 'Idle') return 'text-zinc-500'; if (status === 'Degraded') return 'text-orange-400'; return 'text-white'; }; const tradingLoopStatus = resolveLoopStatus( observabilityHealth.tradingLoopLastRun, Boolean(observabilityHealth.tradingLoopHealthy), Number(botConfig?.POLLING_INTERVAL || 30000) ); const monitorLoopStatus = resolveLoopStatus( observabilityHealth.monitorLoopLastRun, Boolean(observabilityHealth.monitorLoopHealthy), Number(botConfig?.MONITOR_INTERVAL_MS || 60000) ); const reconciliationLoopStatus = resolveLoopStatus( observabilityHealth.reconciliationLoopLastRun, Boolean(observabilityHealth.reconciliationLoopHealthy), Number(botConfig?.MONITOR_INTERVAL_MS || 60000) ); const reconciliationMismatchCount = Number(observabilityHealth.reconciliationMismatchCount || 0); const reconciliationNoGoTrades = Number(observabilityHealth.reconciliationNoGoTrades || 0); const reconciliationIntegrityWatchdogTriggered = Boolean(observabilityHealth.reconciliationIntegrityWatchdogTriggered); const hasReconciliationBacklog = reconciliationMismatchCount > 0 || reconciliationNoGoTrades > 0 || reconciliationIntegrityWatchdogTriggered; const systemCritical = tradingLoopStatus === 'Failing' || monitorLoopStatus === 'Failing'; const systemDegraded = !systemCritical && ( tradingLoopStatus === 'Degraded' || monitorLoopStatus === 'Degraded' || reconciliationLoopStatus === 'Degraded' || hasReconciliationBacklog ); const systemBadgeLabel = systemCritical ? 'Critical' : systemDegraded ? 'Degraded' : 'Healthy'; const systemBadgeDotClass = systemCritical ? 'bg-red-500' : systemDegraded ? 'bg-orange-500' : 'bg-[var(--bl-success)]'; const systemBadgeTextClass = systemCritical ? 'text-red-400' : systemDegraded ? 'text-orange-400' : 'text-[var(--bl-success)]'; const adminPanelClass = 'bg-[var(--bl-surface-strong)] border border-[var(--bl-border-subtle)]'; const adminPanelOverlayClass = 'bg-[var(--bl-surface-overlay)] border border-[var(--bl-border-subtle)]'; const adminPanelHoverClass = 'bg-[var(--bl-surface-strong)] border border-[var(--bl-border-subtle)] hover:border-[var(--bl-border-soft)]'; const adminNavigationClass = 'bg-[var(--bl-surface-overlay)] border border-[var(--bl-border-subtle)]'; const adminActiveAccentClass = 'bg-[var(--bl-success)]/10 border border-[var(--bl-success)]/15'; const handlePauseTrading = async () => { setIsControlLoading(true); setControlError(null); try { const apiUrl = tradingRuntime.tradingApiUrl; const accessToken = await getPlatformAccessToken(); const res = await fetch(`${apiUrl}/internal/trading/pause`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'x-request-id': createRequestId('web-admin') }, body: JSON.stringify({ reason: 'Admin pause from dashboard' }) }); if (!res.ok) { const errorData = await res.json().catch(() => ({ error: 'Unknown error' })); throw new Error(errorData.error || `HTTP ${res.status}`); } } catch (err: any) { setControlError(err.message || 'Failed to pause trading'); } finally { setIsControlLoading(false); } }; const handleResumeTrading = async () => { setIsControlLoading(true); setControlError(null); try { const apiUrl = tradingRuntime.tradingApiUrl; const accessToken = await getPlatformAccessToken(); const res = await fetch(`${apiUrl}/internal/trading/resume`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'x-request-id': createRequestId('web-admin') }, body: JSON.stringify({ reason: 'Admin resume from dashboard' }) }); if (!res.ok) { const errorData = await res.json().catch(() => ({ error: 'Unknown error' })); throw new Error(errorData.error || `HTTP ${res.status}`); } } catch (err: any) { setControlError(err.message || 'Failed to resume trading'); } finally { setIsControlLoading(false); } }; const handleClearEvents = async () => { setIsControlLoading(true); setControlError(null); try { const apiUrl = tradingRuntime.tradingApiUrl; const accessToken = await getPlatformAccessToken(); const res = await fetch(`${apiUrl}/api/events`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'x-request-id': createRequestId('web-admin') } }); if (!res.ok) { const errorData = await res.json().catch(() => ({ error: 'Unknown error' })); throw new Error(errorData.error || `HTTP ${res.status}`); } } catch (err: any) { setControlError(err.message || 'Failed to clear events'); } finally { setIsControlLoading(false); } }; React.useEffect(() => { if (profile?.role !== 'admin') return; const fetchConfig = async () => { try { const apiUrl = tradingRuntime.tradingApiUrl; const accessToken = await getPlatformAccessToken().catch(() => null); if (!accessToken) return; const res = await fetch(`${apiUrl}/api/config`, { headers: { 'Authorization': `Bearer ${accessToken}`, 'x-request-id': createRequestId('web-admin') } }); if (res.ok) { const data = await res.json(); setBotConfig(data); } } catch (err) { console.error("Failed to fetch bot config:", err); } }; const fetchDbSyncSettings = async () => { try { const data = await fetchDynamicConfigItems(); if (data) { data.forEach((item: { key: string; value: string; }) => { if (item.key === 'ENABLE_DB_SNAPSHOTS') { setDbSyncEnabled(item.value === 'true'); } else if (item.key === 'DB_SNAPSHOT_INTERVAL_MS') { setDbSyncInterval(parseInt(item.value) || 300000); } }); } } catch (err) { console.error("Failed to fetch DB sync settings:", err); } }; fetchConfig(); fetchDbSyncSettings(); }, [profile?.role]); const handleUpdateDbSync = async () => { setIsDbSyncLoading(true); try { const updates = [ { key: 'ENABLE_DB_SNAPSHOTS', value: String(dbSyncEnabled), description: 'Enable/Disable bot state snapshots to database' }, { key: 'DB_SNAPSHOT_INTERVAL_MS', value: String(dbSyncInterval), description: 'Minimum interval between database snapshots in ms' } ]; await upsertDynamicConfigItems(updates); } catch (err: any) { setControlError(`DB Sync Update Failed: ${err.message}`); } finally { setIsDbSyncLoading(false); } }; const subTabs = [ { id: 'rules' as const, label: 'Strategy Pipeline', icon: Hexagon }, { id: 'config' as const, label: 'Global Config', icon: Settings2 }, { id: 'health' as const, label: 'System Health', icon: Heart }, { id: 'reconciliation' as const, label: 'Recon Audit', icon: ShieldAlert }, { id: 'debug' as const, label: 'Debug', icon: Bug }, ]; const renderSubContent = () => { switch (subTab) { case 'config': return ; case 'health': return (
{/* Health Summary Grid */}
{/* Trading Engine Card */}

Trading Engine

{tradingLoopStatus}

{formatTimeAgo(observabilityHealth.tradingLoopLastRun)} {tradingLoopStatus === 'Degraded' && ( Stale )}

{/* Position Monitor Card */}

Position Monitor

{monitorLoopStatus}

{formatTimeAgo(observabilityHealth.monitorLoopLastRun)} {monitorLoopStatus === 'Degraded' && ( Stale )}

{/* Reconciliation Card */}

Reconciliation

{reconciliationLoopStatus}
{formatTimeAgo(observabilityHealth.reconciliationLoopLastRun)} {reconciliationLoopStatus === 'Degraded' && ( Stale )}

Mismatches: {reconciliationMismatchCount} | NO_GO: {reconciliationNoGoTrades}

{reconciliationIntegrityWatchdogTriggered && (

Integrity watchdog active

)}
{/* Operational Events (Admin Error Panel) */}

Operational Events

Buffer: {operationalEvents.length || 0} events
{/* 24h Severity Summary Bar */} {operationalEvents.length > 0 && (
Errors: {operationalEvents.filter(e => e.severity === 'ERROR' && (Date.now() - e.timestamp < 86400000)).length}
Warnings: {operationalEvents.filter(e => e.severity === 'WARN' && (Date.now() - e.timestamp < 86400000)).length}
Info: {operationalEvents.filter(e => e.severity === 'INFO' && (Date.now() - e.timestamp < 86400000)).length}
Buffer Distribution
)}
{(operationalEvents.length === 0) ? (

No actionable issues detected

) : (
{operationalEvents.map((event) => (
{event.severity === 'ERROR' ? ( ) : event.severity === 'WARN' ? ( ) : ( )}
{event.type} {event.symbol && ( {event.symbol} )} {event.profileId && ( profile: {event.profileId.slice(-8)} )}

{event.message}

{new Date(event.timestamp).toLocaleTimeString()}
))}
)}
{/* Detailed Metrics */}

Performance Telemetry

Avg Execution Loop

{tradingLoopStatus}

{formatDuration((observabilityHealth as any)?.tradingLoopDurationMs)}

Reconciliation Loop

{reconciliationLoopStatus}

{formatDuration((observabilityHealth as any)?.reconciliationLoopDurationMs)}

Exchange Latency (p95)

{(observabilityHealth as any)?.exchangeLatencyP95 ? 'Active' : 'Idle'}

{(observabilityHealth as any)?.exchangeLatencyP95 ? `${((observabilityHealth as any).exchangeLatencyP95).toFixed(1)}ms` : 'Idle'}

Uptime

Active

{formatDuration(botState.uptime)}

Entry Lock Contention

{(observabilityHealth as any)?.entryLockContentionCount ?? 0} hits

Recon Lock Contention

{(observabilityHealth as any)?.reconciliationLockContentionCount ?? 0} hits

Order Failures (24h)

{botState.orderFailures?.length ?? 0} events

); case 'reconciliation': return ; case 'debug': return (
{/* Dashboard Environment */}

Dashboard Environment

{Object.entries(import.meta.env) .filter(([key]) => key.startsWith('VITE_') || key === 'MODE') .map(([key, value]) => (
{key} {String(value)}
))}
{/* Bot Config */}

Bot Configuration

{botConfig ? (
{Object.entries(botConfig).map(([key, value]) => (
{key} {Array.isArray(value) ? value.join(', ') : String(value)}
))}
) : (

Bot offline — unable to fetch config

)}
{/* Live Debug Logs */}

Live Debug Stream

Buffer: 100 entries
bytelyst-stdout
{debugLogs.length === 0 ? (

Awaiting debug events...

) : (
{debugLogs.map((log, idx) => (
[{new Date().toLocaleTimeString([], { hour12: false })}] {typeof log === 'string' ? log : JSON.stringify(log)}
))}
)}
); default: // rules return (
{rules.length === 0 ? (

No Active Rules

Connect to the bot service to load the pipeline.

) : ( <> {/* Pipeline header */}

{rules.length} rules execute sequentially per trading cycle

All active
{/* Rule cards */}
{rules.map((rule, idx) => { const info = ruleDescriptions[rule] || { desc: 'Technical strategy rule.', category: 'System', icon: Hexagon }; const color = categoryColors[info.category] || 'var(--bl-text-quiet)'; const RuleIcon = info.icon; const isLast = idx === rules.length - 1; return (
{/* Left accent */}
{/* Step number */} {idx + 1} {/* Icon */}
{/* Text */}

{rule.replace('Rule', '')}

{info.category}

{info.desc}

{/* Status */}
Active
{/* Connector arrow between rules */} {!isLast && (
)} ); })}
{/* Pipeline footer note */}

Rules execute in order — each must pass before the next is evaluated

{/* Trading Control Panel */}

Trading Control

{/* Status Banner */}
{isPaused ? ( ) : ( )}

AUTO-TRADING: {isPaused ? 'PAUSED' : 'RUNNING'}

{isPaused ? 'No new positions will be opened. Existing positions are still managed.' : 'Bot is actively monitoring and executing trades based on strategy rules.'}

{tradingControl && (

Last Changed

{new Date(tradingControl.lastChangedAt).toLocaleString()}

by {tradingControl.lastChangedBy}

)}
{/* Control Buttons */}
{/* Error Display */} {controlError && (

{controlError}

)} {/* Safety Notice */}

Safety Note: Pausing trading blocks new entries only. Existing positions will continue to be monitored for exits, stop-losses, and take-profits.

{/* Database Synchronization Control */}

Database Synchronization

Neural Persistence

State Snapshots

Persistent sync of bot state to Supabase

Sync Interval

Minimum time between DB writes ({Math.round(dbSyncInterval / 60000)}m)

setDbSyncInterval(parseInt(e.target.value))} className="w-32 accent-cyan-500" /> {(dbSyncInterval / 60000).toFixed(0)}m

Increasing the interval reduces database IOPS and prevents service throttling under high volatility.

)}
); } }; if (profile?.role !== 'admin') { return (

Access Denied

You do not have administrative privileges to access this area.

); } return (
{/* Header */}

Admin Panel

System configuration & rule pipeline

{botConfig ? ( <> Bot Connected ) : ( <> Bot Offline )}
{/* System Status Badge */}
System: {systemBadgeLabel}
{/* Sub Navigation */} {/* Content */}
{renderSubContent()}
); };