Add pool presets, operator bake-ins, and USB pack improvements

- Pool preset checkboxes with failover in Calibrate and Forge

- Tier 1/2 UX: setup banner, forge next worker, LAN defaults, blueprint prompt

- USB: auto-install forge tools, seed config.json, sync LAUNCH.bat
This commit is contained in:
AetherForge
2026-06-01 21:06:19 -07:00
parent 1e0482ebe1
commit 1413a9394f
29 changed files with 1078 additions and 314 deletions

View File

@@ -2,7 +2,15 @@ import { useState, useEffect, useRef } from 'react';
import { api } from '../api/client';
import { setStoredAuth, getStoredAuth, clearStoredAuth } from '../api/auth';
import type { ServerConfig } from '../types';
import {
DEFAULT_PRESET_IDS,
orderedPoolsFromSelection,
applyPoolsToForgeFields,
} from '../help/poolPresets';
import { looksLikeXMRWallet } from '../help/forgeValidation';
import { HelpTip, FieldHint } from '../components/HelpTip';
import PoolPresetPicker from '../components/PoolPresetPicker';
import type { BackupPool } from '../types';
import NeonCard from '../components/NeonCard/NeonCard';
import './Pages.css';
@@ -89,6 +97,17 @@ export default function SettingsPage() {
const handleSave = async () => {
if (!config) return;
const wallet = config.wallet.address?.trim() ?? '';
if (wallet && !looksLikeXMRWallet(wallet)) {
const strict = config.server?.strict_wallet_validation ?? false;
if (strict) {
setSaveMessage('Save blocked: wallet does not look like a valid Monero mainnet address.');
return;
}
if (!window.confirm('Wallet does not look like a standard Monero address. Save anyway?')) {
return;
}
}
setSaving(true);
setSaveMessage('');
try {
@@ -272,10 +291,26 @@ export default function SettingsPage() {
updateField('server.open_firewall_on_start', true);
updateField('server.obfuscate_default', false);
updateField('server.sign_enabled', false);
const pools = orderedPoolsFromSelection(
[...DEFAULT_PRESET_IDS],
config.pool.password || 'x'
);
const poolFields = applyPoolsToForgeFields(pools);
updateField('pool.host', poolFields.pool_host);
updateField('pool.port', poolFields.pool_port);
updateField('pool.use_tls', poolFields.pool_tls);
updateField(
'pool.backup_pools',
poolFields.backup_pools.map((bp) => ({
host: bp.host,
port: bp.port,
use_tls: bp.tls,
}))
);
if (!config.wallet.address?.trim()) {
setSaveMessage('Set your Monero wallet below, then Save Calibration.');
setSaveMessage('TLS pool presets + LAN URL applied — set your Monero wallet, then Save Calibration.');
} else {
setSaveMessage('Best defaults applied to the form — click Save Calibration to keep them.');
setSaveMessage('Best defaults applied (LAN URL + TLS pool presets) — click Save Calibration to keep them.');
}
}}
>
@@ -335,26 +370,29 @@ export default function SettingsPage() {
<NeonCard accent="cyan" className="settings-section">
<h2 className="font-display">Upstream Pool</h2>
<p className="section-desc">The control server connects here and relays work to your fleet (not per-miner in this tab).</p>
<div className="form-group">
<label htmlFor="cfg-pool-host" className="label">Pool Host <HelpTip field="pool_host" /></label>
<input id="cfg-pool-host" type="text" className="input" value={config.pool.host}
onChange={(e) => updateField('pool.host', e.target.value)} />
</div>
<div className="form-row">
<div className="form-group">
<label htmlFor="cfg-pool-port" className="label">Port</label>
<input id="cfg-pool-port" type="number" className="input" value={config.pool.port}
onChange={(e) => updateField('pool.port', parseInt(e.target.value) || 3333)} />
</div>
<div className="form-group checkbox-group" style={{ alignSelf: 'flex-end' }}>
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={config.pool.use_tls}
onChange={(e) => updateField('pool.use_tls', e.target.checked)} />
<span>Use TLS</span>
</label>
</div>
</div>
<p className="section-desc">The control server connects here and relays work to your fleet. Pick presets or add your own unreachable pools are skipped automatically.</p>
<PoolPresetPicker
host={config.pool.host}
port={config.pool.port}
tls={config.pool.use_tls}
pass={config.pool.password || 'x'}
backups={(config.pool.backup_pools ?? []).map(
(bp): BackupPool => ({ host: bp.host, port: bp.port, tls: bp.use_tls, pass: config.pool.password || 'x' })
)}
onChange={(next) => {
updateField('pool.host', next.pool_host);
updateField('pool.port', next.pool_port);
updateField('pool.use_tls', next.pool_tls);
updateField(
'pool.backup_pools',
(next.backup_pools ?? []).map((bp) => ({
host: bp.host,
port: bp.port,
use_tls: bp.tls,
}))
);
}}
/>
<div className="form-group">
<label htmlFor="cfg-pool-pass" className="label">Pool Password</label>
<input id="cfg-pool-pass" type="text" className="input" value={config.pool.password}