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

@@ -0,0 +1,66 @@
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
export interface ChartPoint {
time: string;
value: number;
label?: string;
}
interface HashrateChartProps {
data: ChartPoint[];
title?: string;
color?: string;
unit?: string;
}
export default function HashrateChart({ data, title, color = '#06b6d4', unit = 'H/s' }: HashrateChartProps) {
if (data.length === 0) {
return (
<div className="chart-empty card">
<p>{title || 'Chart'} waiting for data...</p>
</div>
);
}
return (
<div className="chart-wrap">
{title && <h3 className="chart-title">{title}</h3>}
<ResponsiveContainer width="100%" height={260}>
<AreaChart data={data}>
<defs>
<linearGradient id={`grad-${color}`} x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color} stopOpacity={0.35} />
<stop offset="95%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="#243049" />
<XAxis dataKey="time" stroke="#64748b" fontSize={11} tickLine={false} />
<YAxis stroke="#64748b" fontSize={11} tickLine={false} tickFormatter={(v) => formatShort(v, unit)} />
<Tooltip
contentStyle={{ background: '#111827', border: '1px solid #2a3a5c', borderRadius: 8 }}
labelStyle={{ color: '#94a3b8' }}
formatter={(value: number) => [formatShort(value, unit), title || 'Value']}
/>
<Area type="monotone" dataKey="value" stroke={color} fill={`url(#grad-${color})`} strokeWidth={2} />
</AreaChart>
</ResponsiveContainer>
</div>
);
}
function formatShort(v: number, unit: string): string {
if (unit === 'H/s') {
if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
if (v >= 1_000) return `${(v / 1_000).toFixed(1)}K`;
return `${v.toFixed(0)}`;
}
return `${v.toFixed(1)}${unit === '%' ? '%' : ''}`;
}

View File

@@ -0,0 +1,50 @@
.help-tip {
display: inline-flex;
align-items: center;
gap: 0.35rem;
margin-left: 0.35rem;
cursor: help;
}
.help-tip-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1rem;
border-radius: 999px;
background: rgba(6, 182, 212, 0.15);
color: var(--accent-cyan);
font-size: 0.6875rem;
font-weight: 700;
}
.help-tip-label {
font-size: 0.75rem;
color: var(--text-muted);
}
.cheat-sheet {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.cheat-sheet-item {
padding: 0.75rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
}
.cheat-sheet-item h4 {
font-size: 0.8125rem;
color: var(--accent-cyan);
margin-bottom: 0.25rem;
}
.cheat-sheet-item p {
font-size: 0.75rem;
color: var(--text-secondary);
line-height: 1.5;
}

View File

@@ -0,0 +1,24 @@
import { FIELD_HELP } from '../help/settingHelp';
import '../components/HelpTip.css';
interface HelpTipProps {
field: string;
label?: string;
}
export function HelpTip({ field, label }: HelpTipProps) {
const text = FIELD_HELP[field];
if (!text) return null;
return (
<span className="help-tip" title={text} aria-label={text}>
<span className="help-tip-icon">?</span>
{label && <span className="help-tip-label">{label}</span>}
</span>
);
}
export function FieldHint({ field }: { field: string }) {
const text = FIELD_HELP[field];
if (!text) return null;
return <span className="form-hint">{text}</span>;
}