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) => {
const generatedId = React.useId();
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 (
<label className="inline-flex items-center gap-2 text-sm text-[var(--foreground)]">
<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}
/>
{input}
{label}
</label>
);

View File

@ -549,6 +549,282 @@ body {
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 {
color: var(--muted-foreground);
font-size: 11px;

View File

@ -51,9 +51,9 @@ describe('ConfigTab DOM behavior', () => {
await user.type(modeTextarea, 'disabled');
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();
const resetModeTextarea = screen.getByDisplayValue('enabled');
@ -63,7 +63,7 @@ describe('ConfigTab DOM behavior', () => {
await waitFor(() => {
expect(upsertDynamicConfigItemsMock).toHaveBeenCalledTimes(1);
expect(screen.getByText('SYNC COMPLETE')).toBeInTheDocument();
expect(screen.getByText('Saved')).toBeInTheDocument();
});
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 waitFor(() => {
expect(screen.getByText('CRITICAL ERROR')).toBeInTheDocument();
expect(screen.getByText('Update failed')).toBeInTheDocument();
expect(screen.getByText(/Sync failed: write failed/)).toBeInTheDocument();
});
}, 20000);
@ -109,7 +109,7 @@ describe('ConfigTab DOM behavior', () => {
render(<ConfigTab />);
expect(screen.getByText(/Access Denied/i)).toBeInTheDocument();
expect(screen.getByText(/Access denied/i)).toBeInTheDocument();
expect(fetchDynamicConfigItemsMock).not.toHaveBeenCalled();
});
});

View File

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

View File

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