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:
66
server/web/src/components/Charts/HashrateChart.tsx
Normal file
66
server/web/src/components/Charts/HashrateChart.tsx
Normal 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 === '%' ? '%' : ''}`;
|
||||
}
|
||||
50
server/web/src/components/HelpTip.css
Normal file
50
server/web/src/components/HelpTip.css
Normal 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;
|
||||
}
|
||||
24
server/web/src/components/HelpTip.tsx
Normal file
24
server/web/src/components/HelpTip.tsx
Normal 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>;
|
||||
}
|
||||
Reference in New Issue
Block a user