refactor(ui): tokenize operational surfaces
This commit is contained in:
parent
373a72e823
commit
7375ad66f8
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import type { BotState } from '../hooks/useWebSocket';
|
import type { BotState } from '../hooks/useWebSocket';
|
||||||
import { TrendingUp, Zap } from 'lucide-react';
|
import { TrendingUp, Zap } from 'lucide-react';
|
||||||
|
|
||||||
@ -7,6 +8,37 @@ interface LivePulseTickerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) => {
|
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 symbols = Object.keys(botState.symbols);
|
||||||
const volatileSymbols = symbols
|
const volatileSymbols = symbols
|
||||||
.sort((a, b) => Math.abs(botState.symbols[b].change24h || 0) - Math.abs(botState.symbols[a].change24h || 0))
|
.sort((a, b) => Math.abs(botState.symbols[b].change24h || 0) - Math.abs(botState.symbols[a].change24h || 0))
|
||||||
@ -18,24 +50,11 @@ export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) =>
|
|||||||
.slice(0, 3);
|
.slice(0, 3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={tickerShellStyle}>
|
||||||
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 */}
|
{/* Market Ticker Section */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', borderRight: '1px solid rgba(255,255,255,0.1)', paddingRight: '16px', marginRight: '16px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', borderRight: '1px solid var(--bl-border-soft)', paddingRight: '16px', marginRight: '16px' }}>
|
||||||
<TrendingUp size={14} className="text-[#00ff88]" />
|
<TrendingUp size={14} className="text-[var(--bl-success)]" />
|
||||||
<span style={{ fontSize: '10px', fontWeight: 900, color: '#444', textTransform: 'uppercase', letterSpacing: '1px' }}>Market Pulse</span>
|
<span style={marketLabelStyle}>Market Pulse</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ticker-scroll" style={{ display: 'flex', gap: '24px', flex: 1 }}>
|
<div className="ticker-scroll" style={{ display: 'flex', gap: '24px', flex: 1 }}>
|
||||||
@ -43,11 +62,11 @@ export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) =>
|
|||||||
const change = botState.symbols[s].change24h || 0;
|
const change = botState.symbols[s].change24h || 0;
|
||||||
return (
|
return (
|
||||||
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap' }}>
|
<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: 800, color: 'var(--bl-text-quiet)' }}>{s.split('/')[0]}</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
color: change >= 0 ? '#00ff88' : '#ff3366',
|
color: change >= 0 ? 'var(--bl-success)' : 'var(--bl-danger)',
|
||||||
fontFamily: 'monospace'
|
fontFamily: 'monospace'
|
||||||
}}>
|
}}>
|
||||||
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
|
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
|
||||||
@ -58,19 +77,19 @@ export const LivePulseTicker: React.FC<LivePulseTickerProps> = ({ botState }) =>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* AI Highlight Section */}
|
{/* 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={aiShellStyle}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
<Zap size={14} className="text-[#00ff88]" fill="#00ff88" style={{ filter: 'drop-shadow(0 0 4px #00ff88)' }} />
|
<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: '#00ff88', textTransform: 'uppercase', letterSpacing: '1px' }}>AI Top Picks</span>
|
<span style={{ fontSize: '10px', fontWeight: 900, color: 'var(--bl-success)', textTransform: 'uppercase', letterSpacing: '1px' }}>AI Top Picks</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '12px' }}>
|
<div style={{ display: 'flex', gap: '12px' }}>
|
||||||
{aiSetups.map(s => (
|
{aiSetups.map(s => (
|
||||||
<div key={s} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
<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: '11px', fontWeight: 800, color: 'var(--foreground)' }}>{s.split('/')[0]}</span>
|
||||||
<span style={{ fontSize: '10px', fontWeight: 900, color: '#00ff88', opacity: 0.6 }}>{botState.symbols[s].rules['AIAnalysisRule']?.metadata?.confidence}%</span>
|
<span style={{ fontSize: '10px', fontWeight: 900, color: 'var(--bl-success)', opacity: 0.6 }}>{botState.symbols[s].rules['AIAnalysisRule']?.metadata?.confidence}%</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{aiSetups.length === 0 && <span style={{ fontSize: '10px', fontWeight: 800, color: '#444' }}>SCANNING...</span>}
|
{aiSetups.length === 0 && <span style={{ fontSize: '10px', fontWeight: 800, color: 'var(--bl-text-faint)' }}>SCANNING...</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -77,8 +77,8 @@ export function Login() {
|
|||||||
|
|
||||||
{error && <div className="error-message">{error}</div>}
|
{error && <div className="error-message">{error}</div>}
|
||||||
{message && <div className="success-message" style={{
|
{message && <div className="success-message" style={{
|
||||||
color: '#00ff88',
|
color: 'var(--bl-success)',
|
||||||
background: 'rgba(0, 255, 136, 0.1)',
|
background: 'var(--bl-success-muted)',
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
marginBottom: '20px',
|
marginBottom: '20px',
|
||||||
@ -116,25 +116,25 @@ export function Login() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #0a0b0d;
|
background: var(--background);
|
||||||
color: #fff;
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-card {
|
||||||
background: #14151a;
|
background: var(--bl-surface-strong);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid var(--bl-border-soft);
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
box-shadow: var(--card-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: linear-gradient(90deg, #fff, #00ff88);
|
background: linear-gradient(90deg, var(--foreground), var(--bl-success));
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
@ -142,7 +142,7 @@ export function Login() {
|
|||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #929292;
|
color: var(--bl-text-quiet);
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
@ -154,31 +154,31 @@ export function Login() {
|
|||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: #ccc;
|
color: var(--bl-text-quiet);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: var(--input);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid var(--bl-border-soft);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: #fff;
|
color: var(--foreground);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #00ff88;
|
border-color: var(--bl-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-button {
|
.auth-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #00ff88;
|
background: var(--bl-success);
|
||||||
color: #000;
|
color: var(--primary-foreground);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -194,8 +194,8 @@ export function Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
color: #ff3366;
|
color: var(--bl-danger);
|
||||||
background: rgba(255, 51, 102, 0.1);
|
background: var(--bl-danger-muted);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -211,14 +211,14 @@ export function Login() {
|
|||||||
.link-button {
|
.link-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: #929292;
|
color: var(--bl-text-quiet);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-button:hover {
|
.link-button:hover {
|
||||||
color: #fff;
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -74,24 +74,24 @@ function CenteredMessage({ title, body }: { title: string; body?: string }) {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
background: '#0a0b0d',
|
background: 'var(--background)',
|
||||||
color: '#f5f5f5',
|
color: 'var(--foreground)',
|
||||||
padding: '2rem',
|
padding: '2rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '32rem',
|
maxWidth: '32rem',
|
||||||
border: '1px solid rgba(255,255,255,0.08)',
|
border: '1px solid var(--bl-border-subtle)',
|
||||||
borderRadius: '1rem',
|
borderRadius: '1rem',
|
||||||
padding: '2rem',
|
padding: '2rem',
|
||||||
background: 'rgba(18, 20, 24, 0.92)',
|
background: 'var(--bl-surface-overlay)',
|
||||||
boxShadow: '0 20px 60px rgba(0,0,0,0.35)',
|
boxShadow: 'var(--card-shadow)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1 style={{ margin: 0, fontSize: '1.5rem', marginBottom: '0.75rem' }}>{title}</h1>
|
<h1 style={{ margin: 0, fontSize: '1.5rem', marginBottom: '0.75rem' }}>{title}</h1>
|
||||||
{body ? (
|
{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}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -51,6 +51,16 @@
|
|||||||
--bl-danger-muted: color-mix(in oklab, var(--bl-danger) 12%, var(--background));
|
--bl-danger-muted: color-mix(in oklab, var(--bl-danger) 12%, var(--background));
|
||||||
--bl-info: var(--accent);
|
--bl-info: var(--accent);
|
||||||
--bl-info-muted: var(--accent-soft);
|
--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: var(--ring);
|
||||||
--bl-focus-ring-muted: var(--ring-soft);
|
--bl-focus-ring-muted: var(--ring-soft);
|
||||||
--bl-radius-control: 0.5rem;
|
--bl-radius-control: 0.5rem;
|
||||||
|
|||||||
@ -34,12 +34,12 @@ const ruleDescriptions: { [key: string]: { desc: string; category: string; icon:
|
|||||||
};
|
};
|
||||||
|
|
||||||
const categoryColors: { [key: string]: string } = {
|
const categoryColors: { [key: string]: string } = {
|
||||||
'Trend': '#3b82f6',
|
'Trend': 'var(--bl-info-strong)',
|
||||||
'Filter': '#a855f7',
|
'Filter': 'var(--bl-emphasis)',
|
||||||
'Entry': '#f59e0b',
|
'Entry': 'var(--bl-warning)',
|
||||||
'Momentum': '#06b6d4',
|
'Momentum': 'var(--bl-attention)',
|
||||||
'Risk': '#ef4444',
|
'Risk': 'var(--bl-danger)',
|
||||||
'AI': '#10b981',
|
'AI': 'var(--bl-success)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
||||||
@ -147,8 +147,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
|| hasReconciliationBacklog
|
|| hasReconciliationBacklog
|
||||||
);
|
);
|
||||||
const systemBadgeLabel = systemCritical ? 'Critical' : systemDegraded ? 'Degraded' : 'Healthy';
|
const systemBadgeLabel = systemCritical ? 'Critical' : systemDegraded ? 'Degraded' : 'Healthy';
|
||||||
const systemBadgeDotClass = systemCritical ? 'bg-red-500' : systemDegraded ? 'bg-orange-500' : 'bg-[#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-[#00ff88]';
|
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 () => {
|
const handlePauseTrading = async () => {
|
||||||
setIsControlLoading(true);
|
setIsControlLoading(true);
|
||||||
@ -305,7 +310,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
{/* Health Summary Grid */}
|
{/* Health Summary Grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
{/* Trading Engine Card */}
|
{/* 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>
|
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Trading Engine</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className={`w-2 h-2 rounded-full ${getStatusColor(tradingLoopStatus)}`} />
|
<div className={`w-2 h-2 rounded-full ${getStatusColor(tradingLoopStatus)}`} />
|
||||||
@ -322,7 +327,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Position Monitor Card */}
|
{/* 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>
|
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Position Monitor</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className={`w-2 h-2 rounded-full ${getStatusColor(monitorLoopStatus)}`} />
|
<div className={`w-2 h-2 rounded-full ${getStatusColor(monitorLoopStatus)}`} />
|
||||||
@ -339,7 +344,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Reconciliation Card */}
|
{/* 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>
|
<p className="text-[9px] text-zinc-500 uppercase tracking-widest mb-1">Reconciliation</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className={`w-2 h-2 rounded-full ${getStatusColor(reconciliationLoopStatus)}`} />
|
<div className={`w-2 h-2 rounded-full ${getStatusColor(reconciliationLoopStatus)}`} />
|
||||||
@ -363,7 +368,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Operational Events (Admin Error Panel) */}
|
{/* 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="px-5 py-4 border-b border-white/[0.04] flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<ShieldAlert size={14} className="text-orange-400" />
|
<ShieldAlert size={14} className="text-orange-400" />
|
||||||
@ -467,7 +472,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Detailed Metrics */}
|
{/* 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">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<Cpu size={14} className="text-blue-400" />
|
<Cpu size={14} className="text-blue-400" />
|
||||||
<h3 className="text-xs font-bold text-zinc-300 uppercase tracking-wider">Performance Telemetry</h3>
|
<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)
|
{Object.entries(import.meta.env)
|
||||||
.filter(([key]) => key.startsWith('VITE_') || key === 'MODE')
|
.filter(([key]) => key.startsWith('VITE_') || key === 'MODE')
|
||||||
.map(([key, value]) => (
|
.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-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]">
|
<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)}
|
{String(value)}
|
||||||
@ -571,7 +576,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
{botConfig ? (
|
{botConfig ? (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
{Object.entries(botConfig).map(([key, value]) => (
|
{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-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]">
|
<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)}
|
{Array.isArray(value) ? value.join(', ') : String(value)}
|
||||||
@ -580,7 +585,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</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" />
|
<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>
|
<p className="text-xs text-red-500/60 font-medium">Bot offline — unable to fetch config</p>
|
||||||
</div>
|
</div>
|
||||||
@ -596,7 +601,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<span className="text-[9px] text-zinc-600 font-mono uppercase tracking-widest">Buffer: 100 entries</span>
|
<span className="text-[9px] text-zinc-600 font-mono uppercase tracking-widest">Buffer: 100 entries</span>
|
||||||
</div>
|
</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 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="flex gap-1.5">
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-red-500/20" />
|
<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 (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{rules.length === 0 ? (
|
{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" />
|
<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-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>
|
<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
|
{rules.length} rules execute sequentially per trading cycle
|
||||||
</p>
|
</p>
|
||||||
<span className="flex items-center gap-1.5 text-[10px] text-zinc-500">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -652,13 +657,13 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{rules.map((rule, idx) => {
|
{rules.map((rule, idx) => {
|
||||||
const info = ruleDescriptions[rule] || { desc: 'Technical strategy rule.', category: 'System', icon: Hexagon };
|
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 RuleIcon = info.icon;
|
||||||
const isLast = idx === rules.length - 1;
|
const isLast = idx === rules.length - 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={rule}>
|
<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 */}
|
{/* Left accent */}
|
||||||
<div
|
<div
|
||||||
className="absolute left-0 top-3 bottom-3 w-[3px] rounded-full opacity-50 group-hover:opacity-100 transition-opacity"
|
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 */}
|
{/* Status */}
|
||||||
<div className="shrink-0">
|
<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="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-[#00ff88]" />
|
<span className="w-1.5 h-1.5 rounded-full bg-[var(--bl-success)]" />
|
||||||
<span className="text-[9px] font-bold text-[#00ff88]/80 uppercase tracking-wider">Active</span>
|
<span className="text-[9px] font-bold text-[var(--bl-success)]/80 uppercase tracking-wider">Active</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -831,7 +836,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</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="flex items-center justify-between">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs font-bold text-white">State Snapshots</p>
|
<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') {
|
if (profile?.role !== 'admin') {
|
||||||
return (
|
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} />
|
<XCircle className="mx-auto text-red-500 mb-4" size={48} />
|
||||||
<h2 className="text-xl font-bold text-white mb-2">Access Denied</h2>
|
<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>
|
<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 */}
|
||||||
<header className="flex items-center justify-between">
|
<header className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<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">
|
<div className={`w-10 h-10 rounded-xl ${adminActiveAccentClass} flex items-center justify-center`}>
|
||||||
<ShieldCheck size={18} className="text-[#00ff88]" />
|
<ShieldCheck size={18} className="text-[var(--bl-success)]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold text-white tracking-tight">Admin Panel</h2>
|
<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>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<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 ? (
|
{botConfig ? (
|
||||||
<>
|
<>
|
||||||
<Wifi size={11} className="text-emerald-500" />
|
<Wifi size={11} className="text-emerald-500" />
|
||||||
@ -938,7 +943,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* System Status Badge */}
|
{/* 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}`} />
|
<div className={`w-1.5 h-1.5 rounded-full ${systemBadgeDotClass}`} />
|
||||||
<span className={`text-[10px] font-bold uppercase tracking-widest ${systemBadgeTextClass}`}>
|
<span className={`text-[10px] font-bold uppercase tracking-widest ${systemBadgeTextClass}`}>
|
||||||
System: {systemBadgeLabel}
|
System: {systemBadgeLabel}
|
||||||
@ -948,7 +953,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Sub Navigation */}
|
{/* 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) => {
|
{subTabs.map((tab) => {
|
||||||
const Icon = tab.icon;
|
const Icon = tab.icon;
|
||||||
const isActive = subTab === tab.id;
|
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]'
|
: '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>
|
<span>{tab.label}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user