Redesign dashboard with steampunk neon theme and live gauges.

Ambient background, 3D neon cards, brass HUD styling, glowing charts, gauge rings, and refreshed Command Deck, Fleet Roster, Forge, and Calibrate pages.
This commit is contained in:
drjones
2026-05-27 00:43:34 -07:00
parent 6db9a33a20
commit 9d223b8137
20 changed files with 1638 additions and 307 deletions

View File

@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { api } from '../api/client';
import type { Agent, HashrateSample } from '../types';
import HashrateChart from '../components/Charts/HashrateChart';
import NeonCard from '../components/NeonCard/NeonCard';
import './Pages.css';
export default function AgentsPage() {
@@ -28,29 +29,33 @@ export default function AgentsPage() {
};
return (
<div className="page fade-in">
<div className="page-header">
<h1>Agents</h1>
<span className="header-count">{agents.length} total</span>
</div>
<div className="page fade-in command-deck">
<header className="deck-hero">
<div className="deck-hero-text">
<p className="deck-eyebrow font-tech">FLEET REGISTRY</p>
<h1>Fleet Roster</h1>
<p className="page-subtitle">Inspect each node hashrate history, hardware, share ledger.</p>
</div>
<span className="header-count font-tech">{agents.length} NODES</span>
</header>
{loading ? (
<div className="card empty-state">
<p>Loading agents...</p>
</div>
<NeonCard accent="brass" className="empty-state">
<p>Scanning network...</p>
</NeonCard>
) : agents.length === 0 ? (
<div className="card empty-state">
<div className="empty-icon">🖥</div>
<NeonCard accent="brass" className="empty-state">
<div className="empty-icon"></div>
<h3>No agents registered</h3>
<p>Deploy a miner to a Windows machine and it will appear here automatically.</p>
</div>
</NeonCard>
) : (
<div className="agents-layout">
<div className="agents-list">
{agents.map((agent) => (
<div
key={agent.id}
className={`card agent-list-item ${selectedAgent?.id === agent.id ? 'selected' : ''}`}
className={`neon-card agent-list-item ${selectedAgent?.id === agent.id ? 'selected' : ''}`}
onClick={() => selectAgent(agent)}
>
<div className="agent-list-header">
@@ -76,8 +81,8 @@ export default function AgentsPage() {
</div>
{selectedAgent && (
<div className="agent-detail card">
<h2>{selectedAgent.name}</h2>
<NeonCard accent="cyan" className="agent-detail" hud>
<h2 className="font-display">{selectedAgent.name}</h2>
<div className="agent-detail-grid">
<div className="detail-item">
<span className="detail-label">Status</span>
@@ -153,11 +158,12 @@ export default function AgentsPage() {
{hashrateHistory.length > 0 && (
<div className="detail-section">
<h3>Hashrate History</h3>
<h3 className="font-tech">HASHRATE TELEMETRY</h3>
<HashrateChart
title=""
color="#22c55e"
color="#00f5ff"
unit="H/s"
height={240}
data={[...hashrateHistory].reverse().map((s) => ({
time: new Date(s.timestamp).toLocaleTimeString(),
value: s.hashrate,
@@ -165,25 +171,7 @@ export default function AgentsPage() {
/>
</div>
)}
{hashrateHistory.length > 0 && (
<div className="detail-section">
<h3>Raw Samples ({hashrateHistory.length})</h3>
<div className="hashrate-chart">
{hashrateHistory.reverse().map((sample, i) => (
<div
key={sample.id}
className="chart-bar"
style={{
height: `${Math.max(5, (sample.hashrate / Math.max(...hashrateHistory.map(s => s.hashrate))) * 100)}%`,
}}
title={`${formatHashrate(sample.hashrate)} at ${new Date(sample.timestamp).toLocaleTimeString()}`}
/>
))}
</div>
</div>
)}
</div>
</NeonCard>
)}
</div>
)}