refactor(ui): tokenize operational surfaces
This commit is contained in:
parent
373a72e823
commit
7375ad66f8
@ -1,13 +1,45 @@
|
||||
import React from 'react';
|
||||
import type { BotState } from '../hooks/useWebSocket';
|
||||
import { TrendingUp, Zap } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { BotState } from '../hooks/useWebSocket';
|
||||
import { TrendingUp, Zap } from 'lucide-react';
|
||||
|
||||
interface LivePulseTickerProps {
|
||||
botState: BotState;
|
||||
}
|
||||
|
||||
export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) => {
|
||||
const symbols = Object.keys(botState.symbols);
|
||||
export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) => {
|
||||
const tickerShellStyle: CSSProperties = {
|
||||
height: '40px',
|
||||
background: 'var(--bl-surface-overlay)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
borderTop: '1px solid var(--bl-border-subtle)',
|
||||
borderBottom: '1px solid var(--bl-border-subtle)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
position: 'sticky',
|
||||
top: '0',
|
||||
zIndex: 1000,
|
||||
padding: '0 24px'
|
||||
};
|
||||
const marketLabelStyle: CSSProperties = {
|
||||
fontSize: '10px',
|
||||
fontWeight: 900,
|
||||
color: 'var(--bl-text-faint)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '1px'
|
||||
};
|
||||
const aiShellStyle: CSSProperties = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
marginLeft: 'auto',
|
||||
background: 'var(--bl-success-muted)',
|
||||
padding: '0 16px',
|
||||
height: '100%',
|
||||
borderLeft: '1px solid var(--bl-success-border)',
|
||||
};
|
||||
const symbols = Object.keys(botState.symbols);
|
||||
const volatileSymbols = symbols
|
||||
.sort((a, b) => Math.abs(botState.symbols[b].change24h || 0) - Math.abs(botState.symbols[a].change24h || 0))
|
||||
.slice(0, 8);
|
||||
@ -18,71 +50,58 @@ export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) =>
|
||||
.slice(0, 3);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
height: '40px',
|
||||
background: 'rgba(10, 11, 13, 0.95)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
borderTop: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
borderBottom: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
position: 'sticky',
|
||||
top: '0',
|
||||
zIndex: 1000,
|
||||
padding: '0 24px'
|
||||
}}>
|
||||
{/* Market Ticker Section */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', borderRight: '1px solid rgba(255,255,255,0.1)', paddingRight: '16px', marginRight: '16px' }}>
|
||||
<TrendingUp size={14} className="text-[#00ff88]" />
|
||||
<span style={{ fontSize: '10px', fontWeight: 900, color: '#444', textTransform: 'uppercase', letterSpacing: '1px' }}>Market Pulse</span>
|
||||
</div>
|
||||
|
||||
<div className="ticker-scroll" style={{ display: 'flex', gap: '24px', flex: 1 }}>
|
||||
{volatileSymbols.map(s => {
|
||||
const change = botState.symbols[s].change24h || 0;
|
||||
return (
|
||||
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap' }}>
|
||||
<span style={{ fontSize: '11px', fontWeight: 800, color: '#aaa' }}>{s.split('/')[0]}</span>
|
||||
<span style={{
|
||||
fontSize: '11px',
|
||||
fontWeight: 900,
|
||||
color: change >= 0 ? '#00ff88' : '#ff3366',
|
||||
fontFamily: 'monospace'
|
||||
}}>
|
||||
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
<div style={tickerShellStyle}>
|
||||
{/* Market Ticker Section */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', borderRight: '1px solid var(--bl-border-soft)', paddingRight: '16px', marginRight: '16px' }}>
|
||||
<TrendingUp size={14} className="text-[var(--bl-success)]" />
|
||||
<span style={marketLabelStyle}>Market Pulse</span>
|
||||
</div>
|
||||
|
||||
<div className="ticker-scroll" style={{ display: 'flex', gap: '24px', flex: 1 }}>
|
||||
{volatileSymbols.map(s => {
|
||||
const change = botState.symbols[s].change24h || 0;
|
||||
return (
|
||||
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap' }}>
|
||||
<span style={{ fontSize: '11px', fontWeight: 800, color: 'var(--bl-text-quiet)' }}>{s.split('/')[0]}</span>
|
||||
<span style={{
|
||||
fontSize: '11px',
|
||||
fontWeight: 900,
|
||||
color: change >= 0 ? 'var(--bl-success)' : 'var(--bl-danger)',
|
||||
fontFamily: 'monospace'
|
||||
}}>
|
||||
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* AI Highlight Section */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', marginLeft: 'auto', background: 'rgba(0, 255, 136, 0.03)', padding: '0 16px', height: '100%', borderLeft: '1px solid rgba(0, 255, 136, 0.1)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||
<Zap size={14} className="text-[#00ff88]" fill="#00ff88" style={{ filter: 'drop-shadow(0 0 4px #00ff88)' }} />
|
||||
<span style={{ fontSize: '10px', fontWeight: 900, color: '#00ff88', textTransform: 'uppercase', letterSpacing: '1px' }}>AI Top Picks</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
{aiSetups.map(s => (
|
||||
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
<span style={{ fontSize: '11px', fontWeight: 800, color: '#fff' }}>{s.split('/')[0]}</span>
|
||||
<span style={{ fontSize: '10px', fontWeight: 900, color: '#00ff88', opacity: 0.6 }}>{botState.symbols[s].rules['AIAnalysisRule']?.metadata?.confidence}%</span>
|
||||
</div>
|
||||
))}
|
||||
{aiSetups.length === 0 && <span style={{ fontSize: '10px', fontWeight: 800, color: '#444' }}>SCANNING...</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
.ticker-scroll {
|
||||
animation: ticker-slide 30s linear infinite;
|
||||
}
|
||||
@keyframes ticker-slide {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-20%); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{/* AI Highlight Section */}
|
||||
<div style={aiShellStyle}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||
<Zap size={14} className="text-[var(--bl-success)]" fill="currentColor" style={{ filter: 'drop-shadow(0 0 4px color-mix(in oklab, var(--bl-success) 65%, transparent))' }} />
|
||||
<span style={{ fontSize: '10px', fontWeight: 900, color: 'var(--bl-success)', textTransform: 'uppercase', letterSpacing: '1px' }}>AI Top Picks</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
{aiSetups.map(s => (
|
||||
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
<span style={{ fontSize: '11px', fontWeight: 800, color: 'var(--foreground)' }}>{s.split('/')[0]}</span>
|
||||
<span style={{ fontSize: '10px', fontWeight: 900, color: 'var(--bl-success)', opacity: 0.6 }}>{botState.symbols[s].rules['AIAnalysisRule']?.metadata?.confidence}%</span>
|
||||
</div>
|
||||
))}
|
||||
{aiSetups.length === 0 && <span style={{ fontSize: '10px', fontWeight: 800, color: 'var(--bl-text-faint)' }}>SCANNING...</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
.ticker-scroll {
|
||||
animation: ticker-slide 30s linear infinite;
|
||||
}
|
||||
@keyframes ticker-slide {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-20%); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -75,13 +75,13 @@ export function Login() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <div className="error-message">{error}</div>}
|
||||
{message && <div className="success-message" style={{
|
||||
color: '#00ff88',
|
||||
background: 'rgba(0, 255, 136, 0.1)',
|
||||
padding: '10px',
|
||||
borderRadius: '6px',
|
||||
marginBottom: '20px',
|
||||
{error && <div className="error-message">{error}</div>}
|
||||
{message && <div className="success-message" style={{
|
||||
color: 'var(--bl-success)',
|
||||
background: 'var(--bl-success-muted)',
|
||||
padding: '10px',
|
||||
borderRadius: '6px',
|
||||
marginBottom: '20px',
|
||||
textAlign: 'center',
|
||||
fontSize: '0.9rem'
|
||||
}}>{message}</div>}
|
||||
@ -111,77 +111,77 @@ export function Login() {
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
.login-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: #0a0b0d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: #14151a;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1.5rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(90deg, #fff, #00ff88);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #929292;
|
||||
margin-bottom: 32px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.login-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: var(--bl-surface-strong);
|
||||
border: 1px solid var(--bl-border-soft);
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1.5rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(90deg, var(--foreground), var(--bl-success));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: var(--bl-text-quiet);
|
||||
margin-bottom: 32px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #ccc;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #00ff88;
|
||||
}
|
||||
|
||||
.auth-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #00ff88;
|
||||
color: #000;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: var(--bl-text-quiet);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--input);
|
||||
border: 1px solid var(--bl-border-soft);
|
||||
border-radius: 8px;
|
||||
color: var(--foreground);
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: var(--bl-success);
|
||||
}
|
||||
|
||||
.auth-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--bl-success);
|
||||
color: var(--primary-foreground);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
@ -191,14 +191,14 @@ export function Login() {
|
||||
.auth-button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff3366;
|
||||
background: rgba(255, 51, 102, 0.1);
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--bl-danger);
|
||||
background: var(--bl-danger-muted);
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
}
|
||||
@ -208,19 +208,19 @@ export function Login() {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #929292;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.link-button:hover {
|
||||
color: #fff;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
.link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--bl-text-quiet);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.link-button:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -74,24 +74,24 @@ function CenteredMessage({ title, body }: { title: string; body?: string }) {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: '#0a0b0d',
|
||||
color: '#f5f5f5',
|
||||
background: 'var(--background)',
|
||||
color: 'var(--foreground)',
|
||||
padding: '2rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '32rem',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
border: '1px solid var(--bl-border-subtle)',
|
||||
borderRadius: '1rem',
|
||||
padding: '2rem',
|
||||
background: 'rgba(18, 20, 24, 0.92)',
|
||||
boxShadow: '0 20px 60px rgba(0,0,0,0.35)',
|
||||
background: 'var(--bl-surface-overlay)',
|
||||
boxShadow: 'var(--card-shadow)',
|
||||
}}
|
||||
>
|
||||
<h1 style={{ margin: 0, fontSize: '1.5rem', marginBottom: '0.75rem' }}>{title}</h1>
|
||||
{body ? (
|
||||
<p style={{ margin: 0, color: 'rgba(255,255,255,0.72)', lineHeight: 1.6 }}>{body}</p>
|
||||
<p style={{ margin: 0, color: 'var(--bl-text-quiet)', lineHeight: 1.6 }}>{body}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -51,6 +51,16 @@
|
||||
--bl-danger-muted: color-mix(in oklab, var(--bl-danger) 12%, var(--background));
|
||||
--bl-info: var(--accent);
|
||||
--bl-info-muted: var(--accent-soft);
|
||||
--bl-info-strong: #3b82f6;
|
||||
--bl-attention: #06b6d4;
|
||||
--bl-emphasis: #a855f7;
|
||||
--bl-surface-strong: color-mix(in oklab, var(--card) 88%, var(--foreground));
|
||||
--bl-surface-overlay: color-mix(in oklab, var(--background) 90%, var(--foreground));
|
||||
--bl-surface-highlight: color-mix(in oklab, var(--card-elevated) 86%, var(--foreground));
|
||||
--bl-border-subtle: color-mix(in oklab, var(--border) 62%, transparent);
|
||||
--bl-border-soft: color-mix(in oklab, var(--border-strong) 50%, transparent);
|
||||
--bl-text-quiet: color-mix(in oklab, var(--muted-foreground) 82%, var(--background));
|
||||
--bl-text-faint: color-mix(in oklab, var(--muted-foreground) 58%, var(--background));
|
||||
--bl-focus-ring: var(--ring);
|
||||
--bl-focus-ring-muted: var(--ring-soft);
|
||||
--bl-radius-control: 0.5rem;
|
||||
|
||||
@ -34,12 +34,12 @@ const ruleDescriptions: { [key: string]: { desc: string; category: string; icon:
|
||||
};
|
||||
|
||||
const categoryColors: { [key: string]: string } = {
|
||||
'Trend': '#3b82f6',
|
||||
'Filter': '#a855f7',
|
||||
'Entry': '#f59e0b',
|
||||
'Momentum': '#06b6d4',
|
||||
'Risk': '#ef4444',
|
||||
'AI': '#10b981',
|
||||
'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) => {
|
||||
@ -147,8 +147,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
|| hasReconciliationBacklog
|
||||
);
|
||||
const systemBadgeLabel = systemCritical ? 'Critical' : systemDegraded ? 'Degraded' : 'Healthy';
|
||||
const systemBadgeDotClass = systemCritical ? 'bg-red-500' : systemDegraded ? 'bg-orange-500' : 'bg-[#00ff88]';
|
||||
const systemBadgeTextClass = systemCritical ? 'text-red-400' : systemDegraded ? 'text-orange-400' : 'text-[#00ff88]';
|
||||
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);
|
||||
@ -305,7 +310,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
{/* Health Summary Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{/* Trading Engine Card */}
|
||||
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5">
|
||||
<div className={`${adminPanelClass} rounded-2xl p-5`}>
|
||||
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Trading Engine</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${getStatusColor(tradingLoopStatus)}`} />
|
||||
@ -322,7 +327,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</div>
|
||||
|
||||
{/* Position Monitor Card */}
|
||||
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5">
|
||||
<div className={`${adminPanelClass} rounded-2xl p-5`}>
|
||||
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Position Monitor</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${getStatusColor(monitorLoopStatus)}`} />
|
||||
@ -339,7 +344,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</div>
|
||||
|
||||
{/* Reconciliation Card */}
|
||||
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5">
|
||||
<div className={`${adminPanelClass} rounded-2xl p-5`}>
|
||||
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Reconciliation</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${getStatusColor(reconciliationLoopStatus)}`} />
|
||||
@ -363,7 +368,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</div>
|
||||
|
||||
{/* Operational Events (Admin Error Panel) */}
|
||||
<section className="bg-[#12131a] border border-white/[0.04] rounded-2xl overflow-hidden">
|
||||
<section className={`${adminPanelClass} rounded-2xl overflow-hidden`}>
|
||||
<div className="px-5 py-4 border-b border-white/[0.04] flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<ShieldAlert size={14} className="text-orange-400" />
|
||||
@ -467,7 +472,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</section>
|
||||
|
||||
{/* Detailed Metrics */}
|
||||
<section className="bg-[#12131a] border border-white/[0.04] rounded-2xl p-5">
|
||||
<section className={`${adminPanelClass} rounded-2xl p-5`}>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Cpu size={14} className="text-blue-400" />
|
||||
<h3 className="text-xs font-bold text-zinc-300 uppercase tracking-wider">Performance Telemetry</h3>
|
||||
@ -552,7 +557,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
{Object.entries(import.meta.env)
|
||||
.filter(([key]) => key.startsWith('VITE_') || key === 'MODE')
|
||||
.map(([key, value]) => (
|
||||
<div key={key} className="flex items-center justify-between gap-4 px-4 py-3 bg-[#12131a] border border-white/[0.04] rounded-xl">
|
||||
<div key={key} className={`flex items-center justify-between gap-4 px-4 py-3 ${adminPanelClass} rounded-xl`}>
|
||||
<span className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider truncate">{key}</span>
|
||||
<span className="text-[10px] font-mono text-blue-400/80 bg-blue-500/[0.06] px-2.5 py-1 rounded-lg truncate max-w-[260px]">
|
||||
{String(value)}
|
||||
@ -571,7 +576,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
{botConfig ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{Object.entries(botConfig).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center justify-between gap-4 px-4 py-3 bg-[#12131a] border border-white/[0.04] rounded-xl">
|
||||
<div key={key} className={`flex items-center justify-between gap-4 px-4 py-3 ${adminPanelClass} rounded-xl`}>
|
||||
<span className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider truncate">{key}</span>
|
||||
<span className="text-[10px] font-mono text-orange-400/80 bg-orange-500/[0.06] px-2.5 py-1 rounded-lg truncate max-w-[260px]">
|
||||
{Array.isArray(value) ? value.join(', ') : String(value)}
|
||||
@ -580,7 +585,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-3 py-12 bg-[#12131a] border border-dashed border-red-500/10 rounded-xl">
|
||||
<div className={`flex items-center justify-center gap-3 py-12 ${adminPanelClass} border-dashed border-red-500/10 rounded-xl`}>
|
||||
<WifiOff size={18} className="text-red-500/40" />
|
||||
<p className="text-xs text-red-500/60 font-medium">Bot offline — unable to fetch config</p>
|
||||
</div>
|
||||
@ -596,7 +601,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</div>
|
||||
<span className="text-[9px] text-zinc-600 font-mono uppercase tracking-widest">Buffer: 100 entries</span>
|
||||
</div>
|
||||
<div className="bg-[#0c0d12] border border-white/[0.04] rounded-xl overflow-hidden shadow-2xl">
|
||||
<div className={`${adminPanelOverlayClass} rounded-xl overflow-hidden shadow-2xl`}>
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-white/[0.02] border-b border-white/[0.04]">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
|
||||
@ -631,7 +636,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{rules.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-20 bg-[#12131a] border border-dashed border-white/[0.06] rounded-xl">
|
||||
<div className={`flex flex-col items-center justify-center py-20 ${adminPanelClass} border-dashed border-white/[0.06] rounded-xl`}>
|
||||
<Hexagon size={28} className="text-zinc-700 mb-4" />
|
||||
<p className="text-sm font-semibold text-zinc-500">No Active Rules</p>
|
||||
<p className="text-[11px] text-zinc-600 mt-1 max-w-xs text-center">Connect to the bot service to load the pipeline.</p>
|
||||
@ -644,7 +649,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
{rules.length} rules execute sequentially per trading cycle
|
||||
</p>
|
||||
<span className="flex items-center gap-1.5 text-[10px] text-zinc-500">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#00ff88] animate-pulse" /> All active
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[var(--bl-success)] animate-pulse" /> All active
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -652,13 +657,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
<div className="space-y-2">
|
||||
{rules.map((rule, idx) => {
|
||||
const info = ruleDescriptions[rule] || { desc: 'Technical strategy rule.', category: 'System', icon: Hexagon };
|
||||
const color = categoryColors[info.category] || '#6b7280';
|
||||
const color = categoryColors[info.category] || 'var(--bl-text-quiet)';
|
||||
const RuleIcon = info.icon;
|
||||
const isLast = idx === rules.length - 1;
|
||||
|
||||
return (
|
||||
<React.Fragment key={rule}>
|
||||
<div className="group relative bg-[#12131a] border border-white/[0.04] rounded-xl hover:border-white/[0.08] transition-all duration-200">
|
||||
<div className={`group relative ${adminPanelHoverClass} rounded-xl transition-all duration-200`}>
|
||||
{/* Left accent */}
|
||||
<div
|
||||
className="absolute left-0 top-3 bottom-3 w-[3px] rounded-full opacity-50 group-hover:opacity-100 transition-opacity"
|
||||
@ -707,9 +712,9 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
|
||||
{/* Status */}
|
||||
<div className="shrink-0">
|
||||
<span className="flex items-center gap-1.5 px-2.5 py-1 bg-[#00ff88]/[0.06] border border-[#00ff88]/10 rounded-full">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#00ff88]" />
|
||||
<span className="text-[9px] font-bold text-[#00ff88]/80 uppercase tracking-wider">Active</span>
|
||||
<span className="flex items-center gap-1.5 px-2.5 py-1 bg-[var(--bl-success)]/10 border border-[var(--bl-success)]/10 rounded-full">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[var(--bl-success)]" />
|
||||
<span className="text-[9px] font-bold text-[var(--bl-success)]/80 uppercase tracking-wider">Active</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -831,7 +836,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#12131a] border border-white/[0.04] rounded-2xl overflow-hidden p-5 space-y-5">
|
||||
<div className={`${adminPanelClass} rounded-2xl overflow-hidden p-5 space-y-5`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-bold text-white">State Snapshots</p>
|
||||
@ -900,7 +905,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
|
||||
if (profile?.role !== 'admin') {
|
||||
return (
|
||||
<div className="p-8 text-center bg-[#12131a] border border-red-500/20 rounded-2xl">
|
||||
<div className={`p-8 text-center ${adminPanelClass} border-red-500/20 rounded-2xl`}>
|
||||
<XCircle className="mx-auto text-red-500 mb-4" size={48} />
|
||||
<h2 className="text-xl font-bold text-white mb-2">Access Denied</h2>
|
||||
<p className="text-zinc-500">You do not have administrative privileges to access this area.</p>
|
||||
@ -913,8 +918,8 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
{/* Header */}
|
||||
<header className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-[#00ff88]/10 border border-[#00ff88]/15 flex items-center justify-center">
|
||||
<ShieldCheck size={18} className="text-[#00ff88]" />
|
||||
<div className={`w-10 h-10 rounded-xl ${adminActiveAccentClass} flex items-center justify-center`}>
|
||||
<ShieldCheck size={18} className="text-[var(--bl-success)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-white tracking-tight">Admin Panel</h2>
|
||||
@ -923,7 +928,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-[#12131a] border border-white/[0.04] rounded-lg">
|
||||
<div className={`flex items-center gap-1.5 px-3 py-1.5 ${adminPanelClass} rounded-lg`}>
|
||||
{botConfig ? (
|
||||
<>
|
||||
<Wifi size={11} className="text-emerald-500" />
|
||||
@ -938,7 +943,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</div>
|
||||
|
||||
{/* System Status Badge */}
|
||||
<div className={`flex items-center gap-1.5 px-3 py-1.5 bg-[#12131a] border border-white/[0.04] rounded-lg`}>
|
||||
<div className={`flex items-center gap-1.5 px-3 py-1.5 ${adminPanelClass} rounded-lg`}>
|
||||
<div className={`w-1.5 h-1.5 rounded-full ${systemBadgeDotClass}`} />
|
||||
<span className={`text-[10px] font-bold uppercase tracking-widest ${systemBadgeTextClass}`}>
|
||||
System: {systemBadgeLabel}
|
||||
@ -948,7 +953,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
</header>
|
||||
|
||||
{/* Sub Navigation */}
|
||||
<nav className="flex gap-1 bg-[#0e0f14] border border-white/[0.04] rounded-xl p-1">
|
||||
<nav className={`flex gap-1 ${adminNavigationClass} rounded-xl p-1`}>
|
||||
{subTabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
const isActive = subTab === tab.id;
|
||||
@ -961,7 +966,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||
: 'text-zinc-500 hover:text-zinc-300 hover:bg-white/[0.02]'
|
||||
}`}
|
||||
>
|
||||
<Icon size={13} className={isActive ? 'text-[#00ff88]' : ''} />
|
||||
<Icon size={13} className={isActive ? 'text-[var(--bl-success)]' : ''} />
|
||||
<span>{tab.label}</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user