refactor(ui): polish bot config settings

This commit is contained in:
Saravana Achu Mac 2026-05-09 01:12:56 -07:00
parent 92e717509d
commit 944ae498d2
5 changed files with 355 additions and 81 deletions

View File

@ -301,15 +301,23 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
({ className, label, id, ...props }, ref) => { ({ className, label, id, ...props }, ref) => {
const generatedId = React.useId(); const generatedId = React.useId();
const checkboxId = id ?? (label ? `checkbox-${generatedId}` : undefined); const checkboxId = id ?? (label ? `checkbox-${generatedId}` : undefined);
const input = (
<input
ref={ref}
id={checkboxId}
type="checkbox"
className={cn('h-4 w-4 rounded border-[var(--border)] bg-[var(--card)] text-[var(--primary)] focus-visible:ring-2 focus-visible:ring-[var(--ring-soft)]', className)}
{...props}
/>
);
if (!label) {
return input;
}
return ( return (
<label className="inline-flex items-center gap-2 text-sm text-[var(--foreground)]"> <label className="inline-flex items-center gap-2 text-sm text-[var(--foreground)]">
<input {input}
ref={ref}
id={checkboxId}
type="checkbox"
className={cn('h-4 w-4 rounded border-[var(--border)] bg-[var(--card)] text-[var(--primary)] focus-visible:ring-2 focus-visible:ring-[var(--ring-soft)]', className)}
{...props}
/>
{label} {label}
</label> </label>
); );

View File

@ -549,6 +549,282 @@ body {
gap: 8px; gap: 8px;
} }
.settings-config-stack {
display: grid;
gap: 24px;
}
.settings-access-panel,
.settings-loading-state,
.settings-action-panel,
.settings-control-panel,
.settings-info-footer {
border: 1px solid var(--border);
border-radius: var(--bl-radius-card);
background: var(--card);
box-shadow: var(--card-shadow);
}
.settings-access-panel {
border-color: color-mix(in oklab, var(--bl-danger) 26%, var(--border));
background: color-mix(in oklab, var(--bl-danger) 8%, var(--card));
padding: 28px;
}
.settings-access-panel h2 {
margin: 8px 0 0;
color: var(--foreground);
font-size: 22px;
font-weight: 780;
line-height: 1.2;
}
.settings-access-panel p:not(.settings-panel-eyebrow) {
margin: 10px 0 0;
color: var(--muted-foreground);
font-size: 14px;
line-height: 1.6;
}
.settings-panel-eyebrow {
margin: 0;
color: var(--bl-danger);
font-size: 11px;
font-weight: 850;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.settings-loading-state {
min-height: 260px;
display: grid;
place-items: center;
align-content: center;
gap: 14px;
color: var(--muted-foreground);
text-align: center;
}
.settings-loading-state p {
margin: 0;
font-size: 13px;
font-weight: 700;
}
.settings-loading-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--border);
border-top-color: var(--accent);
border-radius: 999px;
animation: spin 0.8s linear infinite;
}
.settings-feedback {
display: flex;
align-items: center;
gap: 14px;
border: 1px solid var(--border);
border-radius: 16px;
padding: 16px;
}
.settings-feedback--success {
border-color: color-mix(in oklab, var(--bl-success) 28%, var(--border));
background: color-mix(in oklab, var(--bl-success) 10%, var(--card));
color: var(--bl-success);
}
.settings-feedback--error {
border-color: color-mix(in oklab, var(--bl-danger) 28%, var(--border));
background: color-mix(in oklab, var(--bl-danger) 10%, var(--card));
color: var(--bl-danger);
}
.settings-feedback-icon {
display: grid;
width: 40px;
height: 40px;
flex: 0 0 auto;
place-items: center;
border-radius: 999px;
background: color-mix(in oklab, currentColor 12%, transparent);
}
.settings-feedback span {
display: block;
color: var(--foreground);
font-size: 14px;
font-weight: 750;
}
.settings-feedback p {
margin: 4px 0 0;
color: var(--muted-foreground);
font-size: 13px;
line-height: 1.5;
}
.settings-action-panel {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 24px;
}
.settings-action-heading {
display: flex;
min-width: 0;
align-items: center;
gap: 16px;
}
.settings-action-icon,
.settings-info-icon {
display: grid;
width: 52px;
height: 52px;
flex: 0 0 auto;
place-items: center;
border: 1px solid color-mix(in oklab, var(--accent) 18%, var(--border));
border-radius: 16px;
background: color-mix(in oklab, var(--accent) 10%, var(--card));
color: var(--accent);
}
.settings-action-heading h2,
.settings-control-header h3,
.settings-info-content h5 {
margin: 0;
color: var(--foreground);
font-weight: 760;
line-height: 1.25;
}
.settings-action-heading h2 {
font-size: 20px;
}
.settings-action-heading p,
.settings-control-header p,
.settings-info-content p {
margin: 6px 0 0;
color: var(--muted-foreground);
font-size: 13px;
line-height: 1.55;
}
.settings-action-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 10px;
}
.settings-control-panel {
display: grid;
gap: 18px;
padding: 20px;
}
.settings-control-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.settings-control-header h3,
.settings-info-content h5 {
font-size: 14px;
}
.settings-toggle-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.settings-toggle-card {
display: flex;
align-items: flex-start;
gap: 12px;
min-width: 0;
border: 1px solid var(--border);
border-radius: 16px;
background: var(--card-elevated);
padding: 16px;
}
.settings-toggle-card > span {
min-width: 0;
}
.settings-toggle-card span span {
display: block;
color: var(--foreground);
font-size: 13px;
font-weight: 750;
}
.settings-toggle-card small {
display: block;
margin-top: 4px;
color: var(--muted-foreground);
font-size: 12px;
line-height: 1.45;
}
.settings-info-footer {
position: relative;
overflow: hidden;
padding: 24px;
}
.settings-info-watermark {
position: absolute;
top: -34px;
right: 18px;
opacity: 0.05;
pointer-events: none;
}
.settings-info-content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 18px;
}
.settings-info-content strong {
color: var(--foreground);
font-weight: 760;
}
@media (max-width: 760px) {
.settings-action-panel,
.settings-control-header,
.settings-info-content {
align-items: stretch;
flex-direction: column;
}
.settings-action-controls {
justify-content: stretch;
}
.settings-action-controls .product-button {
width: 100%;
}
.settings-toggle-grid {
grid-template-columns: 1fr;
}
}
.ux-field-label { .ux-field-label {
color: var(--muted-foreground); color: var(--muted-foreground);
font-size: 11px; font-size: 11px;

View File

@ -51,9 +51,9 @@ describe('ConfigTab DOM behavior', () => {
await user.type(modeTextarea, 'disabled'); await user.type(modeTextarea, 'disabled');
expect(screen.getByRole('button', { name: 'Commit Changes' })).toBeEnabled(); expect(screen.getByRole('button', { name: 'Commit Changes' })).toBeEnabled();
expect(screen.getByRole('button', { name: 'Abort Changes' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Reset changes' })).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Abort Changes' })); await user.click(screen.getByRole('button', { name: 'Reset changes' }));
expect(screen.getByDisplayValue('enabled')).toBeInTheDocument(); expect(screen.getByDisplayValue('enabled')).toBeInTheDocument();
const resetModeTextarea = screen.getByDisplayValue('enabled'); const resetModeTextarea = screen.getByDisplayValue('enabled');
@ -63,7 +63,7 @@ describe('ConfigTab DOM behavior', () => {
await waitFor(() => { await waitFor(() => {
expect(upsertDynamicConfigItemsMock).toHaveBeenCalledTimes(1); expect(upsertDynamicConfigItemsMock).toHaveBeenCalledTimes(1);
expect(screen.getByText('SYNC COMPLETE')).toBeInTheDocument(); expect(screen.getByText('Saved')).toBeInTheDocument();
}); });
expect(upsertDynamicConfigItemsMock.mock.calls[0][0]).toEqual( expect(upsertDynamicConfigItemsMock.mock.calls[0][0]).toEqual(
@ -99,7 +99,7 @@ describe('ConfigTab DOM behavior', () => {
await user.click(screen.getByRole('button', { name: 'Commit Changes' })); await user.click(screen.getByRole('button', { name: 'Commit Changes' }));
await waitFor(() => { await waitFor(() => {
expect(screen.getByText('CRITICAL ERROR')).toBeInTheDocument(); expect(screen.getByText('Update failed')).toBeInTheDocument();
expect(screen.getByText(/Sync failed: write failed/)).toBeInTheDocument(); expect(screen.getByText(/Sync failed: write failed/)).toBeInTheDocument();
}); });
}, 20000); }, 20000);
@ -109,7 +109,7 @@ describe('ConfigTab DOM behavior', () => {
render(<ConfigTab />); render(<ConfigTab />);
expect(screen.getByText(/Access Denied/i)).toBeInTheDocument(); expect(screen.getByText(/Access denied/i)).toBeInTheDocument();
expect(fetchDynamicConfigItemsMock).not.toHaveBeenCalled(); expect(fetchDynamicConfigItemsMock).not.toHaveBeenCalled();
}); });
}); });

View File

@ -4,6 +4,7 @@ import { clearBacktestRuntimeFlagCache } from '../backtest/flags';
import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi'; import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi';
import { useAuth } from '../components/AuthContext'; import { useAuth } from '../components/AuthContext';
import { BACKTEST_FLAG_KEYS } from '../../../shared/feature-flags.js'; import { BACKTEST_FLAG_KEYS } from '../../../shared/feature-flags.js';
import { Button, Checkbox, Textarea } from '../components/ui/Primitives';
interface ConfigItem { interface ConfigItem {
key: string; key: string;
@ -102,7 +103,7 @@ export const ConfigTab = () => {
await upsertDynamicConfigItems(buildConfigUpsertPayload(configs)); await upsertDynamicConfigItems(buildConfigUpsertPayload(configs));
clearBacktestRuntimeFlagCache(); clearBacktestRuntimeFlagCache();
setMessage({ type: 'success', text: 'Neural parameters synchronized successfully.' }); setMessage({ type: 'success', text: 'Runtime configuration updated successfully.' });
setOriginalConfigs(cloneConfigItems(configs)); setOriginalConfigs(cloneConfigItems(configs));
setTimeout(() => setMessage(null), 4000); setTimeout(() => setMessage(null), 4000);
} catch (err: any) { } catch (err: any) {
@ -142,99 +143,91 @@ export const ConfigTab = () => {
if (!isAdmin) { if (!isAdmin) {
return ( return (
<div className="bg-[var(--card-elevated)] border border-red-500/20 p-8 rounded-3xl"> <div className="settings-access-panel">
<div className="max-w-xl"> <p className="settings-panel-eyebrow">Restricted</p>
<p className="text-[11px] font-black uppercase tracking-[0.3em] text-red-400">Restricted</p> <h2>Access denied</h2>
<h2 className="text-2xl font-black text-white mt-3">Access Denied</h2> <p>Global bot configuration is limited to administrator accounts.</p>
<p className="text-sm text-zinc-400 mt-3">
Global bot configuration is limited to administrator accounts.
</p>
</div>
</div> </div>
); );
} }
if (loading) { if (loading) {
return ( return (
<div className="flex flex-col items-center justify-center h-64 text-gray-500 animate-pulse"> <div className="settings-loading-state" role="status">
<div className="w-10 h-10 border-4 border-[var(--bl-success-muted)] border-t-[var(--bl-success)] rounded-full animate-spin mb-4 shadow-xl" /> <div className="settings-loading-spinner" aria-hidden="true" />
<p className="text-xs font-black uppercase tracking-[0.3em]">Calibrating Infrastructure...</p> <p>Loading runtime configuration...</p>
</div> </div>
); );
} }
return ( return (
<div className="space-y-10 animate-in fade-in duration-700"> <div className="settings-config-stack animate-in fade-in duration-700">
{/* 1. NOTIFICATION PANEL */}
{message && ( {message && (
<div className={`p-6 rounded-2xl flex items-center gap-4 border-2 animate-in slide-in-from-top-4 duration-500 ${message.type === 'success' ? 'bg-green-500/10 border-green-500/20 text-green-400' : 'bg-red-500/10 border-red-500/20 text-red-400' <div className={`settings-feedback settings-feedback--${message.type} animate-in slide-in-from-top-4 duration-500`}>
}`}> <div className="settings-feedback-icon">
<div className={`p-2 rounded-full ${message.type === 'success' ? 'bg-green-400/20' : 'bg-red-400/20'}`}>
{message.type === 'success' ? <CheckCircle2 size={24} /> : <AlertCircle size={24} />} {message.type === 'success' ? <CheckCircle2 size={24} /> : <AlertCircle size={24} />}
</div> </div>
<div> <div>
<span className="text-sm font-black uppercase tracking-widest">{message.type === 'success' ? 'SYNC COMPLETE' : 'CRITICAL ERROR'}</span> <span>{message.type === 'success' ? 'Saved' : 'Update failed'}</span>
<p className="text-xs font-bold opacity-80 uppercase tracking-tight leading-none mt-1">{message.text}</p> <p>{message.text}</p>
</div> </div>
</div> </div>
)} )}
{/* 2. ACTION CONTROL CENTER */} <div className="settings-action-panel">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 bg-[var(--card-elevated)] border border-white/5 p-8 rounded-3xl relative overflow-hidden group shadow-2xl"> <div className="settings-action-heading">
<div className="relative z-10 flex items-center gap-6"> <div className="settings-action-icon">
<div className="w-14 h-14 bg-white/5 border border-white/10 rounded-2xl flex items-center justify-center text-[var(--bl-info-strong)] shadow-xl">
<Zap size={30} fill="currentColor" fillOpacity={0.1} /> <Zap size={30} fill="currentColor" fillOpacity={0.1} />
</div> </div>
<div> <div>
<h2 className="text-xl font-black text-white flex items-center gap-3 uppercase tracking-tight"> <h2>Bot Runtime Configuration</h2>
Global Operational Parameters <p>Core infrastructure settings, providers, and guarded credentials.</p>
</h2>
<p className="text-gray-500 text-[10px] font-bold uppercase tracking-[0.2em] opacity-60 mt-2">Core Infrastructure Configuration & Credentials</p>
</div> </div>
</div> </div>
<div className="flex items-center gap-4 relative z-10"> <div className="settings-action-controls">
{hasChanges && ( {hasChanges && (
<button <Button
type="button"
onClick={handleReset} onClick={handleReset}
className="px-4 py-2 text-[10px] font-black uppercase tracking-widest text-gray-500 hover:text-white transition-all underline underline-offset-8 decoration-white/10" variant="ghost"
size="sm"
> >
Abort Changes Reset changes
</button> </Button>
)} )}
<button <Button
type="button"
onClick={handleSaveAll} onClick={handleSaveAll}
disabled={!hasChanges || saving} disabled={!hasChanges || saving}
className={`flex items-center gap-3 px-10 py-4 rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all shadow-2xl ${hasChanges && !saving size="lg"
? 'bg-white text-black hover:bg-[var(--bl-info-strong)] transition-colors'
: 'bg-white/5 text-gray-600 cursor-not-allowed'
}`}
> >
{saving ? 'Syncing...' : 'Commit Changes'} {saving ? 'Syncing...' : 'Commit Changes'}
</button> </Button>
</div> </div>
</div> </div>
<div className="bg-[var(--card-elevated)] border border-white/5 p-6 rounded-3xl space-y-4"> <div className="settings-control-panel">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4"> <div className="settings-control-header">
<div> <div>
<h3 className="text-sm font-black text-white uppercase tracking-widest">Backtest Access Control</h3> <h3>Backtest Access Control</h3>
<p className="text-[10px] text-zinc-500 uppercase tracking-wider mt-1"> <p>
Runtime flags in <code>bot_config</code>. Applies automatically on dynamic config refresh. Runtime flags in <code>bot_config</code>. Applies automatically on dynamic config refresh.
</p> </p>
</div> </div>
<button <Button
type="button"
onClick={handleSaveBacktestFlags} onClick={handleSaveBacktestFlags}
disabled={savingBacktestFlags} disabled={savingBacktestFlags}
className={`px-5 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest ${savingBacktestFlags ? 'bg-white/5 text-zinc-600 cursor-not-allowed' : 'bg-[var(--bl-success)] text-black hover:opacity-90'}`} variant="outline"
size="sm"
> >
{savingBacktestFlags ? 'Saving...' : 'Save Backtest Flags'} {savingBacktestFlags ? 'Saving...' : 'Save Backtest Flags'}
</button> </Button>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="settings-toggle-grid">
<label className="flex items-start gap-3 rounded-2xl border border-white/10 bg-black/20 p-4"> <label className="settings-toggle-card">
<input <Checkbox
type="checkbox"
checked={backtestFlags.enableBacktest} checked={backtestFlags.enableBacktest}
onChange={(event) => setBacktestFlags((prev) => ({ onChange={(event) => setBacktestFlags((prev) => ({
...prev, ...prev,
@ -243,26 +236,24 @@ export const ConfigTab = () => {
}))} }))}
/> />
<span> <span>
<span className="block text-[11px] font-black text-white uppercase tracking-wide">Enable Backtest Globally</span> <span>Enable Backtest Globally</span>
<span className="block text-[10px] text-zinc-500 mt-1">Master switch for `/api/backtest/run`.</span> <small>Master switch for `/api/backtest/run`.</small>
</span> </span>
</label> </label>
<label className="flex items-start gap-3 rounded-2xl border border-white/10 bg-black/20 p-4"> <label className="settings-toggle-card">
<input <Checkbox
type="checkbox"
checked={backtestFlags.customerEnabled} checked={backtestFlags.customerEnabled}
disabled={!backtestFlags.enableBacktest} disabled={!backtestFlags.enableBacktest}
onChange={(event) => setBacktestFlags((prev) => ({ ...prev, customerEnabled: event.target.checked }))} onChange={(event) => setBacktestFlags((prev) => ({ ...prev, customerEnabled: event.target.checked }))}
/> />
<span> <span>
<span className="block text-[11px] font-black text-white uppercase tracking-wide">Enable Backtest For Customers</span> <span>Enable Backtest For Customers</span>
<span className="block text-[10px] text-zinc-500 mt-1">When off, only admin users can run backtests.</span> <small>When off, only admin users can run backtests.</small>
</span> </span>
</label> </label>
</div> </div>
</div> </div>
{/* 3. RESPONSIVE TABLE OF CONFIGS */}
<div className="table-container overflow-x-auto"> <div className="table-container overflow-x-auto">
<table className="pro-table w-full"> <table className="pro-table w-full">
<thead> <thead>
@ -295,7 +286,7 @@ export const ConfigTab = () => {
</td> </td>
<td className="px-6 py-6"> <td className="px-6 py-6">
<div className="relative group/field"> <div className="relative group/field">
<textarea <Textarea
value={config.value} value={config.value}
onChange={(e) => handleChange(config.key, e.target.value)} onChange={(e) => handleChange(config.key, e.target.value)}
className={`w-full bg-black/40 border border-white/5 rounded-2xl px-5 py-4 text-[12px] font-mono font-bold focus:outline-none transition-all min-h-[80px] hover:border-white/10 ${isSecret ? 'text-orange-400 focus:border-orange-500' : 'text-blue-400 focus:border-blue-500'}`} className={`w-full bg-black/40 border border-white/5 rounded-2xl px-5 py-4 text-[12px] font-mono font-bold focus:outline-none transition-all min-h-[80px] hover:border-white/10 ${isSecret ? 'text-orange-400 focus:border-orange-500' : 'text-blue-400 focus:border-blue-500'}`}
@ -321,20 +312,19 @@ export const ConfigTab = () => {
</table> </table>
</div> </div>
{/* 4. IMMUTABLE FOOTER INFO */} <footer className="settings-info-footer">
<footer className="relative p-12 bg-[var(--bl-surface-overlay)] border border-white/5 rounded-[3rem] overflow-hidden group shadow-2xl"> <div className="settings-info-watermark">
<div className="absolute top-0 right-0 p-12 opacity-[0.03] group-hover:opacity-10 transition-all duration-1000">
<ShieldAlert size={160} className="text-blue-400" /> <ShieldAlert size={160} className="text-blue-400" />
</div> </div>
<div className="relative z-10 flex flex-col lg:flex-row gap-10 items-center"> <div className="settings-info-content">
<div className="p-8 bg-blue-500/5 rounded-3xl text-blue-400 border border-blue-500/10 shadow-inner"> <div className="settings-info-icon">
<Info size={40} /> <Info size={40} />
</div> </div>
<div className="space-y-4 max-w-4xl text-center lg:text-left"> <div>
<h5 className="text-xs font-black text-white uppercase tracking-[0.5em] opacity-80">Synchronization Protocol Alpha</h5> <h5>Runtime Update Behavior</h5>
<p className="text-[11px] text-gray-600 font-bold leading-relaxed uppercase tracking-[0.15em]"> <p>
Updates to <span className="text-white/80 border-b border-blue-500/30 pb-0.5">SYMBOLS</span> and <span className="text-white/80 border-b border-blue-500/30 pb-0.5">CAPITAL</span> are hot-loaded within the active cycle. Updates to <strong>symbols</strong> and <strong>capital</strong> are hot-loaded within the active cycle.
Changes to <span className="text-white/80 border-b border-blue-500/30 pb-0.5">ENCRYPTED PROVIDERS</span> or <span className="text-white/80 border-b border-blue-500/30 pb-0.5">API CREDENTIALS</span> require a hard handshake reset of the neural execution daemon to re establishment secure connections. Changes to <strong>provider credentials</strong> or <strong>API keys</strong> may require a service restart before every worker uses the new value.
</p> </p>
</div> </div>
</div> </div>

View File

@ -140,7 +140,7 @@ describe('dashboard tabs smoke coverage', () => {
expect(adminHtml).toContain('AIAnalysis'); expect(adminHtml).toContain('AIAnalysis');
const configHtml = renderToStaticMarkup(React.createElement(ConfigTab)); const configHtml = renderToStaticMarkup(React.createElement(ConfigTab));
expect(configHtml).toContain('Calibrating Infrastructure'); expect(configHtml).toContain('Loading runtime configuration');
}); });
it('renders HistoryTab loading state shell', () => { it('renders HistoryTab loading state shell', () => {