Add master dashboard reporter and settings UI

- New reporter module that pushes firm stats (total accounts, accounts
  traded, in trade) to a configurable master dashboard every 30 seconds
- Add Instance Name and Dashboard URL fields to the settings page
- Register master_dashboard_url and instance_name in settings API
- Seed default (empty) values for new settings in db

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Senofy
2026-03-13 03:01:48 -05:00
co-authored by Claude Opus 4.6
parent 15ef2d7adc
commit f112118090
6 changed files with 159 additions and 3 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getSetting, setSetting } from '@/lib/db';
const VALID_KEYS = ['max_concurrent_accounts', 'tick_interval_seconds'] as const;
const VALID_KEYS = ['max_concurrent_accounts', 'tick_interval_seconds', 'master_dashboard_url', 'instance_name'] as const;
type SettingKey = typeof VALID_KEYS[number];
export async function GET() {
+48 -1
View File
@@ -17,12 +17,16 @@ interface ResolvedContract {
interface AppSettings {
max_concurrent_accounts: string | null;
master_dashboard_url: string | null;
instance_name: string | null;
}
export default function SettingsPage() {
const [instruments, setInstruments] = useState<Instrument[]>([]);
const [contracts, setContracts] = useState<Record<string, ResolvedContract | null>>({});
const [maxConcurrent, setMaxConcurrent] = useState<string>('5');
const [masterUrl, setMasterUrl] = useState<string>('');
const [instanceName, setInstanceName] = useState<string>('');
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(false);
@@ -37,6 +41,12 @@ export default function SettingsPage() {
if (s.max_concurrent_accounts != null) {
setMaxConcurrent(s.max_concurrent_accounts);
}
if (s.master_dashboard_url != null) {
setMasterUrl(s.master_dashboard_url);
}
if (s.instance_name != null) {
setInstanceName(s.instance_name);
}
});
// Load cached contracts; if cache is empty, auto-resolve
@@ -91,7 +101,7 @@ export default function SettingsPage() {
await fetch('/api/settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ max_concurrent_accounts: maxConcurrent }),
body: JSON.stringify({ max_concurrent_accounts: maxConcurrent, master_dashboard_url: masterUrl, instance_name: instanceName }),
});
setSaving(false);
setSaved(true);
@@ -148,6 +158,43 @@ export default function SettingsPage() {
</div>
</div>
{/* ── Master Dashboard ── */}
<h2 className="text-sm font-semibold uppercase tracking-wider text-slate-500 mb-3">
Master Dashboard
</h2>
<div className="bg-white border border-slate-200 rounded-xl shadow-sm mb-8">
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100">
<div>
<p className="text-sm font-medium text-slate-800">Instance Name</p>
<p className="text-xs text-slate-400 mt-0.5">
Identifier for this autotrader on the master dashboard
</p>
</div>
<input
type="text"
placeholder="e.g. VPS-1"
value={instanceName}
onChange={(e) => setInstanceName(e.target.value)}
className="w-48 rounded-lg border border-slate-200 bg-slate-50 px-3 py-1.5 text-sm font-mono text-slate-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex items-center justify-between px-4 py-3">
<div>
<p className="text-sm font-medium text-slate-800">Dashboard URL</p>
<p className="text-xs text-slate-400 mt-0.5">
Reports state to this URL every 30 seconds
</p>
</div>
<input
type="text"
placeholder="e.g. http://master-ip:4000"
value={masterUrl}
onChange={(e) => setMasterUrl(e.target.value)}
className="w-72 rounded-lg border border-slate-200 bg-slate-50 px-3 py-1.5 text-sm font-mono text-slate-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
{/* ── Instruments ── */}
<h2 className="text-sm font-semibold uppercase tracking-wider text-slate-500 mb-3">
Instruments