Fix dashboard black screen, add login gate, and expand README.
Pin React Three Fiber to React 18, remove PWA caching, add SessionGate and error boundaries, and document movie fusion, auth, outputs, and troubleshooting.
This commit is contained in:
39
server/web/src/components/ErrorBoundary.tsx
Normal file
39
server/web/src/components/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends Component<Props, State> {
|
||||
state: State = { error: null };
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||
console.error('UI error:', error, info);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
this.props.fallback ?? (
|
||||
<div className="error-boundary-fallback card">
|
||||
<h3>Something failed to render</h3>
|
||||
<p className="form-hint">{this.state.error.message}</p>
|
||||
<button type="button" className="btn btn-primary" onClick={() => this.setState({ error: null })}>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
margin-left: 260px;
|
||||
margin-left: 0;
|
||||
padding: 2rem 2.5rem;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
@@ -243,7 +243,7 @@
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 72px;
|
||||
margin-left: 0;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
73
server/web/src/components/SessionGate.tsx
Normal file
73
server/web/src/components/SessionGate.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useEffect, useState, type ReactNode } from 'react';
|
||||
import { getStoredAuth, setStoredAuth } from '../api/auth';
|
||||
|
||||
export default function SessionGate({ children }: { children: ReactNode }) {
|
||||
const [ready, setReady] = useState(false);
|
||||
const [authed, setAuthed] = useState(!!getStoredAuth());
|
||||
const [user, setUser] = useState('drjones');
|
||||
const [pass, setPass] = useState('');
|
||||
const [err, setErr] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const token = getStoredAuth();
|
||||
if (!token) {
|
||||
setReady(true);
|
||||
return;
|
||||
}
|
||||
fetch('/api/v1/config', { headers: { Authorization: `Basic ${token}` } })
|
||||
.then((r) => {
|
||||
setAuthed(r.ok);
|
||||
setReady(true);
|
||||
})
|
||||
.catch(() => setReady(true));
|
||||
}, []);
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setErr('');
|
||||
const token = btoa(`${user}:${pass}`);
|
||||
const res = await fetch('/api/v1/config', { headers: { Authorization: `Basic ${token}` } });
|
||||
if (!res.ok) {
|
||||
setErr('Login failed — check username and password.');
|
||||
return;
|
||||
}
|
||||
setStoredAuth(user, pass);
|
||||
setAuthed(true);
|
||||
};
|
||||
|
||||
if (!ready) {
|
||||
return (
|
||||
<div className="session-gate">
|
||||
<p className="font-tech">Starting AetherForge…</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!authed) {
|
||||
return (
|
||||
<div className="session-gate">
|
||||
<form className="session-gate-card card" onSubmit={handleLogin}>
|
||||
<h1 className="font-display">AetherForge</h1>
|
||||
<p className="form-hint">Sign in to open the command deck.</p>
|
||||
<label className="label">Username</label>
|
||||
<input className="input" value={user} onChange={(e) => setUser(e.target.value)} autoComplete="username" />
|
||||
<label className="label">Password</label>
|
||||
<input
|
||||
className="input"
|
||||
type="password"
|
||||
value={pass}
|
||||
onChange={(e) => setPass(e.target.value)}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
{err && <p className="form-hint" style={{ color: 'var(--accent-red)' }}>{err}</p>}
|
||||
<button type="submit" className="btn btn-primary btn-lg">
|
||||
Enter Command Deck
|
||||
</button>
|
||||
<p className="form-hint">Default: drjones / czapiewski (change under Calibrate → Users)</p>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Canvas, useFrame } from '@react-three/fiber';
|
||||
import { OrbitControls, Stars, Line, Sphere, Text } from '@react-three/drei';
|
||||
import { OrbitControls, Stars, Line, Sphere } from '@react-three/drei';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { Agent } from '../../../types';
|
||||
@@ -48,12 +48,6 @@ function AgentNode({ agent, position, serverPos }: { agent: Agent, position: [nu
|
||||
<Sphere ref={pulseRef} args={[0.3, 16, 16]}>
|
||||
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={isOnline ? (isHashing ? 2 : 1) : 0.2} wireframe />
|
||||
</Sphere>
|
||||
<Text position={[0, -0.6, 0]} fontSize={0.2} color="white" anchorX="center" anchorY="middle">
|
||||
{agent.name}
|
||||
</Text>
|
||||
<Text position={[0, -0.85, 0]} fontSize={0.15} color={color} anchorX="center" anchorY="middle">
|
||||
{isOnline ? `${(agent.hashrate_15m).toFixed(0)} H/s` : 'OFFLINE'}
|
||||
</Text>
|
||||
{/* Connection Line */}
|
||||
<Line points={[[0,0,0], [serverPos[0] - position[0], serverPos[1] - position[1], serverPos[2] - position[2]]]} color={isOnline ? '#004455' : '#330000'} lineWidth={1} transparent opacity={0.4} />
|
||||
|
||||
@@ -101,9 +95,6 @@ export default function FleetTopologyMap({ agents }: { agents: Agent[] }) {
|
||||
<Sphere args={[0.3, 16, 16]}>
|
||||
<meshStandardMaterial color="#ffffff" emissive="#ffffff" emissiveIntensity={2} />
|
||||
</Sphere>
|
||||
<Text position={[0, -1.2, 0]} fontSize={0.35} color="#ffb020" anchorX="center" anchorY="middle">
|
||||
MOTHERSHIP
|
||||
</Text>
|
||||
</group>
|
||||
|
||||
{/* Agent Nodes */}
|
||||
|
||||
Reference in New Issue
Block a user