Add fleet ops dashboard, Calibrate enforcement, and dead-code cleanup.

Ship live alerts, pool status, AI monitor, remote agent commands, build manager, and uninstall flow; wire Calibrate settings (WS ping, pool traffic log, retention limits) at runtime and exclude server/data from git.
This commit is contained in:
drjones
2026-05-27 09:16:04 -07:00
parent 9d223b8137
commit df81eb7744
75 changed files with 8891 additions and 966 deletions

View File

@@ -0,0 +1,59 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { api } from '../../api/client';
import './VisualComponents.css';
export default function SystemStatusBar() {
const [serverOk, setServerOk] = useState(true);
const [agentTotal, setAgentTotal] = useState(0);
const [agentOnline, setAgentOnline] = useState(0);
const [buildCount, setBuildCount] = useState(0);
useEffect(() => {
const poll = async () => {
try {
await api.healthCheck();
setServerOk(true);
} catch {
setServerOk(false);
}
try {
const agents = await api.listAgents();
setAgentTotal(agents.length);
setAgentOnline(agents.filter((a) => a.status === 'online').length);
} catch {
setAgentTotal(0);
setAgentOnline(0);
}
try {
const builds = await api.listBuilds();
setBuildCount(builds.length);
} catch {
setBuildCount(0);
}
};
poll();
const id = setInterval(poll, 15000);
return () => clearInterval(id);
}, []);
return (
<div className="system-status-bar">
<span className={`status-pill ${serverOk ? 'ok' : 'bad'}`}>
<span className="status-pill-dot" />
SERVER {serverOk ? 'UP' : 'DOWN'}
</span>
<span className={`status-pill ${agentOnline > 0 ? 'ok' : agentTotal > 0 ? 'warn' : ''}`}>
<span className="status-pill-dot" />
FLEET {agentOnline}/{agentTotal} ONLINE
</span>
<span className={`status-pill ${buildCount > 0 ? 'ok' : 'warn'}`}>
<span className="status-pill-dot" />
{buildCount} BUILD{buildCount === 1 ? '' : 'S'}
</span>
<Link to="/guide" className="status-pill" style={{ marginLeft: 'auto', textDecoration: 'none', color: 'var(--neon-cyan)' }}>
📖 GUIDE
</Link>
</div>
);
}

View File

@@ -0,0 +1,371 @@
/* Visual pipeline, activity, guide components */
.pipeline-flow {
display: flex;
align-items: stretch;
flex-wrap: wrap;
gap: 0.25rem;
padding: 1rem 0;
}
.pipeline-step-wrap {
display: flex;
align-items: center;
flex: 1;
min-width: 120px;
}
.pipeline-step {
flex: 1;
display: flex;
align-items: center;
gap: 0.65rem;
padding: 0.75rem 1rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.3);
transition: border-color 0.2s, box-shadow 0.2s;
}
.pipeline-step.pipeline-active {
border-color: var(--neon-cyan);
box-shadow: 0 0 20px rgba(0, 245, 255, 0.15);
}
.pipeline-icon {
font-size: 1.5rem;
width: 2.25rem;
height: 2.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(201, 162, 39, 0.15);
border: 1px solid rgba(201, 162, 39, 0.35);
}
.pipeline-text {
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.pipeline-text strong {
font-size: 0.85rem;
color: var(--text-primary);
}
.pipeline-text span {
font-size: 0.7rem;
color: var(--text-muted);
font-family: var(--font-tech);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.pipeline-connector {
width: 1.5rem;
height: 2px;
background: linear-gradient(90deg, var(--brass), var(--neon-cyan));
opacity: 0.5;
flex-shrink: 0;
}
.pipeline-compact .pipeline-step {
padding: 0.5rem 0.65rem;
}
.fleet-pipeline-status {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 0.5rem;
gap: 0.25rem;
}
.fleet-pipeline-item {
display: flex;
align-items: center;
flex: 1;
}
.fleet-pipeline-node {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35rem;
min-width: 4rem;
}
.fleet-pipeline-dot {
width: 14px;
height: 14px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s;
}
.fleet-pipeline-node.ok .fleet-pipeline-dot {
background: var(--neon-green);
border-color: var(--neon-green);
box-shadow: 0 0 12px rgba(74, 222, 128, 0.6);
animation: pulse-dot 2s ease-in-out infinite;
}
.fleet-pipeline-node.pending .fleet-pipeline-dot {
background: rgba(251, 191, 36, 0.2);
border-color: rgba(251, 191, 36, 0.5);
}
.fleet-pipeline-label {
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
}
.fleet-pipeline-node.ok .fleet-pipeline-label {
color: var(--neon-green);
}
.fleet-pipeline-line {
flex: 1;
height: 2px;
background: rgba(255, 255, 255, 0.08);
margin: 0 0.25rem;
margin-bottom: 1.25rem;
}
.fleet-pipeline-line.lit {
background: linear-gradient(90deg, var(--neon-green), rgba(74, 222, 128, 0.3));
}
@keyframes pulse-dot {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.15); opacity: 0.85; }
}
.activity-pulse {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.5rem 0;
}
.activity-blip {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
width: 3rem;
}
.activity-blip-core {
width: 10px;
height: 10px;
border-radius: 50%;
}
.activity-blip.ok .activity-blip-core {
background: var(--neon-green);
box-shadow: 0 0 8px var(--neon-green);
}
.activity-blip.bad .activity-blip-core {
background: #f87171;
box-shadow: 0 0 8px #f87171;
}
.activity-blip-label {
font-size: 0.55rem;
font-family: var(--font-tech);
color: var(--text-muted);
text-align: center;
line-height: 1.2;
}
.activity-empty {
color: var(--text-muted);
font-size: 0.8rem;
margin: 0;
}
.compare-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.compare-card h4 {
margin: 0 0 0.75rem;
font-size: 0.95rem;
}
.compare-card ul {
margin: 0 0 1rem;
padding-left: 1.1rem;
font-size: 0.85rem;
line-height: 1.6;
color: var(--text-secondary);
}
.roadmap-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 0.75rem;
}
.roadmap-card {
padding: 1rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.25);
}
.roadmap-card h4 {
margin: 0.35rem 0 0.5rem;
font-size: 0.9rem;
}
.roadmap-card p {
margin: 0;
font-size: 0.8rem;
color: var(--text-secondary);
line-height: 1.45;
}
.roadmap-priority {
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 0.15rem 0.4rem;
border-radius: 4px;
}
.roadmap-high .roadmap-priority {
color: #f87171;
border: 1px solid rgba(248, 113, 113, 0.4);
}
.roadmap-medium .roadmap-priority {
color: var(--neon-amber);
border: 1px solid rgba(251, 191, 36, 0.4);
}
.roadmap-low .roadmap-priority {
color: var(--neon-cyan);
border: 1px solid rgba(0, 245, 255, 0.35);
}
.guide-step-card {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 1rem;
align-items: start;
padding: 1rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(0, 0, 0, 0.2);
margin-bottom: 0.75rem;
}
.guide-step-num {
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-family: var(--font-tech);
font-size: 1.1rem;
border: 1px solid var(--brass);
color: var(--neon-amber);
background: rgba(201, 162, 39, 0.1);
}
.guide-step-body h4 {
margin: 0 0 0.35rem;
}
.guide-step-body p {
margin: 0 0 0.5rem;
font-size: 0.85rem;
color: var(--text-secondary);
}
.guide-tips {
margin: 0;
padding-left: 1rem;
font-size: 0.78rem;
color: var(--neon-cyan);
}
.trouble-card {
padding: 0.85rem 1rem;
border-left: 3px solid var(--neon-amber);
background: rgba(251, 191, 36, 0.06);
border-radius: 0 6px 6px 0;
margin-bottom: 0.5rem;
}
.trouble-card strong {
display: block;
margin-bottom: 0.25rem;
color: var(--neon-amber);
}
.trouble-card span {
font-size: 0.85rem;
color: var(--text-secondary);
}
.system-status-bar {
display: flex;
align-items: center;
gap: 1.25rem;
flex-wrap: wrap;
padding: 0.5rem 1.5rem;
background: rgba(8, 6, 4, 0.85);
border-bottom: 1px solid rgba(201, 162, 39, 0.2);
font-size: 0.75rem;
font-family: var(--font-tech);
letter-spacing: 0.04em;
}
.status-pill {
display: flex;
align-items: center;
gap: 0.4rem;
color: var(--text-muted);
}
.status-pill-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
}
.status-pill.ok .status-pill-dot {
background: var(--neon-green);
box-shadow: 0 0 6px var(--neon-green);
}
.status-pill.warn .status-pill-dot {
background: var(--neon-amber);
}
.status-pill.bad .status-pill-dot {
background: #f87171;
}
.status-pill.ok {
color: var(--neon-green);
}
@media (max-width: 640px) {
.compare-grid {
grid-template-columns: 1fr;
}
}

View File

@@ -0,0 +1,137 @@
import { Link } from 'react-router-dom';
import NeonCard from '../NeonCard/NeonCard';
import {
FORGE_VS_CALIBRATE,
PIPELINE_STEPS,
ROADMAP_FEATURES,
} from '../../help/cheatSheetContent';
import './VisualComponents.css';
interface PipelineFlowProps {
activeStep?: string;
compact?: boolean;
}
export function PipelineFlow({ activeStep, compact }: PipelineFlowProps) {
return (
<div className={`pipeline-flow ${compact ? 'pipeline-compact' : ''}`}>
{PIPELINE_STEPS.map((step, i) => (
<div key={step.id} className="pipeline-step-wrap">
<div className={`pipeline-step ${activeStep === step.id ? 'pipeline-active' : ''}`}>
<div className="pipeline-icon">{step.icon}</div>
<div className="pipeline-text">
<strong>{step.title}</strong>
<span>{step.subtitle}</span>
</div>
</div>
{i < PIPELINE_STEPS.length - 1 && <div className="pipeline-connector" aria-hidden />}
</div>
))}
</div>
);
}
interface FleetPipelineStatusProps {
hasBuilds: boolean;
agentCount: number;
onlineCount: number;
hasHashrate: boolean;
hasShares: boolean;
}
export function FleetPipelineStatus({
hasBuilds,
agentCount,
onlineCount,
hasHashrate,
hasShares,
}: FleetPipelineStatusProps) {
const steps = [
{ id: 'forge', label: 'Forged', ok: hasBuilds, hint: 'At least one build exists' },
{ id: 'deploy', label: 'Deployed', ok: agentCount > 0, hint: 'Agent registered on dashboard' },
{ id: 'connect', label: 'Online', ok: onlineCount > 0, hint: `${onlineCount} node(s) live` },
{ id: 'mine', label: 'Hashing', ok: hasHashrate, hint: 'Fleet hashrate > 0' },
{ id: 'shares', label: 'Shares', ok: hasShares, hint: 'Shares submitted to pool' },
];
return (
<div className="fleet-pipeline-status">
{steps.map((s, i) => (
<div key={s.id} className="fleet-pipeline-item">
<div className={`fleet-pipeline-node ${s.ok ? 'ok' : 'pending'}`} title={s.hint}>
<span className="fleet-pipeline-dot" />
<span className="fleet-pipeline-label font-tech">{s.label}</span>
</div>
{i < steps.length - 1 && <div className={`fleet-pipeline-line ${s.ok ? 'lit' : ''}`} />}
</div>
))}
</div>
);
}
interface ActivityPulseProps {
items: { id: string; label: string; ok: boolean; time?: string }[];
}
export function ActivityPulse({ items }: ActivityPulseProps) {
if (items.length === 0) {
return <p className="activity-empty font-tech">Awaiting fleet activity</p>;
}
return (
<div className="activity-pulse">
{items.slice(0, 12).map((item) => (
<div key={item.id} className={`activity-blip ${item.ok ? 'ok' : 'bad'}`} title={item.time || item.label}>
<span className="activity-blip-core" />
<span className="activity-blip-label">{item.label}</span>
</div>
))}
</div>
);
}
interface ForgeCalibrateCompareProps {
compact?: boolean;
}
export function ForgeCalibrateCompare({ compact }: ForgeCalibrateCompareProps) {
return (
<div className={`compare-grid ${compact ? 'compare-compact' : ''}`}>
<NeonCard accent="cyan" className="compare-card">
<h4>{FORGE_VS_CALIBRATE.forge.title}</h4>
<ul>
{FORGE_VS_CALIBRATE.forge.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
{!compact && (
<Link to="/forge" className="btn btn-outline btn-sm">Go to Forge</Link>
)}
</NeonCard>
<NeonCard accent="amber" className="compare-card">
<h4>{FORGE_VS_CALIBRATE.calibrate.title}</h4>
<ul>
{FORGE_VS_CALIBRATE.calibrate.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
{!compact && (
<Link to="/settings" className="btn btn-outline btn-sm">Go to Calibrate</Link>
)}
</NeonCard>
</div>
);
}
export function RoadmapGrid() {
return (
<div className="roadmap-grid">
{ROADMAP_FEATURES.map((f) => (
<div key={f.title} className={`roadmap-card roadmap-${f.priority}`}>
<span className={`roadmap-priority font-tech`}>{f.priority}</span>
<h4>{f.title}</h4>
<p>{f.desc}</p>
</div>
))}
</div>
);
}