113616b039
Release / release (push) Successful in 3m19s
Stash the full claude.ai/design output (12 JSX variants — brutalist, classic, cli, compact, fluent-live, glass, hero-live, minimal, sketches, studio, wizard-live, workshop — plus shared hooks and a standalone HTML preview) for reference when we get to the Wails frontend in Phase 6/7. Source archive: C:\Users\root\Downloads\app(1).zip (~1MB). Not wired into any build target yet — current GUI is the temporary MessageBox stub. Pulling these in is the goal of the Wails phase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
264 lines
14 KiB
React
264 lines
14 KiB
React
// drover-cli.jsx — Cyber CLI: terminal aesthetic, phosphor green, ASCII feel.
|
||
// All-mono. No round corners. Subtle CRT scanline overlay.
|
||
|
||
const CLI = {
|
||
bg: '#0a0d0a', chrome: '#070a07', panel: '#0d110d', panel2: '#111511',
|
||
border: '#1f2a1f', borderSoft: '#162016',
|
||
text: '#bfe7c0', // phosphor green-white
|
||
dim: '#5a8a5e', dimmer: '#3a5a3d',
|
||
accent: '#5cf08a', accentDim: '#2a8a44',
|
||
danger: '#ff6b6b', warn: '#ffd166', pass: '#5cf08a', skip: '#6a8a6e',
|
||
inputBg: '#0a0e0a',
|
||
};
|
||
|
||
function CliWindow({ initial }) {
|
||
const t = CLI;
|
||
const D = window.useDrover(initial);
|
||
const palette = { pending: t.dimmer, running: t.accent, passed: t.pass, failed: t.danger, skipped: t.skip };
|
||
const fontMono = '"JetBrains Mono","IBM Plex Mono","Cascadia Mono",ui-monospace,monospace';
|
||
const isActive = D.phase === 'active';
|
||
|
||
return (
|
||
<div style={{
|
||
width: 480, height: 640, background: t.bg, color: t.text, display:'flex', flexDirection:'column',
|
||
fontFamily: fontMono, fontSize: 12, overflow:'hidden', position:'relative',
|
||
border:'1px solid #000', textShadow: `0 0 1px ${t.accent}30`,
|
||
}}>
|
||
{/* CRT scanlines */}
|
||
<div style={{
|
||
position:'absolute', inset:0, pointerEvents:'none', zIndex: 5, opacity: 0.18,
|
||
background: 'repeating-linear-gradient(to bottom, transparent 0, transparent 2px, rgba(0,0,0,.55) 3px, transparent 4px)',
|
||
}}/>
|
||
{/* vignette */}
|
||
<div style={{
|
||
position:'absolute', inset:0, pointerEvents:'none', zIndex: 4,
|
||
background: 'radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,.45) 100%)',
|
||
}}/>
|
||
|
||
{/* title bar — like a terminal tab */}
|
||
<div style={{
|
||
height: 28, background: t.chrome, borderBottom:`1px solid ${t.border}`,
|
||
display:'flex', alignItems:'center', position:'relative', zIndex: 6,
|
||
}}>
|
||
<div style={{ display:'flex', alignItems:'center', gap: 7, padding:'0 11px', flex:1 }}>
|
||
<span style={{ color: t.accent }}>$</span>
|
||
<span style={{ fontSize: 11.5, fontWeight: 600, letterSpacing: 0.6 }}>drover-go</span>
|
||
<span style={{ fontSize: 10.5, color: t.dim }}>— ~ — 80×24</span>
|
||
</div>
|
||
<div style={{ display:'flex' }}>
|
||
{['settings','min','close'].map(k => (
|
||
<span key={k} style={{
|
||
width: 32, height: 28, display:'flex', alignItems:'center', justifyContent:'center',
|
||
cursor:'pointer', color: t.dim, fontSize: 11,
|
||
}}>{k==='close'?'×':k==='min'?'−':'⚙'}</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ flex: 1, overflow:'auto', padding: '10px 14px', position:'relative', zIndex: 6 }}>
|
||
{/* prompt-style form */}
|
||
<div style={cliHead(t)}>┌── socks5 proxy ─────────────────────────────────────────</div>
|
||
<div style={{ marginLeft: 4, paddingLeft: 14, borderLeft:`1px solid ${t.borderSoft}`, paddingTop: 4, paddingBottom: 6 }}>
|
||
<CliRow t={t}>
|
||
<span style={{ color: t.dim }}>host:</span>
|
||
<CliInput t={t} value={D.form.host} onChange={v => D.update({ host: v })}
|
||
placeholder="95.165.72.59 / example.com" onSubmit={D.runCheck} style={{ flex: 1 }}/>
|
||
<span style={{ color: t.dim }}>port:</span>
|
||
<CliInput t={t} value={D.form.port} onChange={v => D.update({ port: v.replace(/\D/g,'') })}
|
||
placeholder="12334" onSubmit={D.runCheck} style={{ width: 70 }}/>
|
||
</CliRow>
|
||
<div style={{ marginTop: 6 }}>
|
||
<label style={{ cursor:'pointer', userSelect:'none' }}>
|
||
<span style={{ color: t.dim }}>auth:</span>{' '}
|
||
<span onClick={() => {
|
||
D.update({ auth: !D.form.auth });
|
||
if (!D.form.auth) setTimeout(()=>document.getElementById('cli-login')?.focus(),30);
|
||
}}
|
||
style={{
|
||
color: D.form.auth ? t.accent : t.dim, fontWeight: 600, cursor:'pointer',
|
||
borderBottom: `1px dotted ${t.accent}`,
|
||
}}>{D.form.auth ? '[ on ]' : '[ off ]'}</span>
|
||
</label>
|
||
</div>
|
||
{D.form.auth && (
|
||
<CliRow t={t} style={{ marginTop: 6 }}>
|
||
<span style={{ color: t.dim }}>user:</span>
|
||
<CliInput id="cli-login" t={t} value={D.form.login} onChange={v => D.update({ login: v })}
|
||
placeholder="login" onSubmit={D.runCheck} style={{ flex: 1 }}/>
|
||
<span style={{ color: t.dim }}>pass:</span>
|
||
<CliInput t={t} value={D.form.password} onChange={v => D.update({ password: v })} type="password"
|
||
placeholder="••••••" onSubmit={D.runCheck} style={{ flex: 1 }}/>
|
||
</CliRow>
|
||
)}
|
||
</div>
|
||
|
||
<button onClick={D.runCheck} disabled={D.phase==='checking'||isActive} style={{
|
||
marginTop: 8, width:'100%', padding:'7px 12px', background:'transparent',
|
||
border:`1px solid ${(D.phase==='checking'||isActive)?t.dim:t.accent}`,
|
||
color: (D.phase==='checking'||isActive)?t.dim:t.accent, fontFamily: fontMono,
|
||
fontWeight: 600, fontSize: 11.5, letterSpacing: 1, textAlign:'left',
|
||
cursor: D.phase==='checking'?'not-allowed':'pointer', borderRadius: 0,
|
||
textShadow: (D.phase==='checking'||isActive)?'none':`0 0 6px ${t.accent}80`,
|
||
}}>
|
||
{D.phase==='checking'
|
||
? <>{'>'} running…<span style={{ animation:'drv-blink 1s steps(2) infinite' }}>_</span></>
|
||
: <>{'> check_connection --strict'}</>}
|
||
</button>
|
||
|
||
{/* status */}
|
||
<div style={{ height: 12 }}/>
|
||
<div style={cliHead(t)}>┌── status ─────────────────────────────────────────────────</div>
|
||
{D.phase === 'idle'
|
||
? <div style={{ marginLeft: 4, paddingLeft: 14, borderLeft:`1px solid ${t.borderSoft}`, color: t.dim }}>
|
||
{'> '} ready to check<span style={{ animation: 'drv-blink 1s steps(2) infinite' }}>_</span>
|
||
</div>
|
||
: <div style={{ marginLeft: 4, paddingLeft: 14, borderLeft:`1px solid ${t.borderSoft}` }}>
|
||
{D.phase === 'checking'
|
||
? <div style={{ color: t.dim }}>{'> '} running diagnostics<span style={{ animation:'drv-blink 1s steps(2) infinite' }}>...</span></div>
|
||
: (D.lastSummary?.failed === 0
|
||
? <div style={{ color: t.accent, fontWeight: 600 }}>[ ✓ ] all checks passed. ready to start.</div>
|
||
: <div style={{ color: t.warn, fontWeight: 600 }}>[ ! ] {D.lastSummary?.failed}/{D.tests.length} failed. some features won't work.</div>
|
||
)}
|
||
<div style={{ marginTop: 6 }}>
|
||
{D.tests.map(test => {
|
||
const r = D.results[test.id];
|
||
const state = r?.result || (D.running === test.id ? 'running' : 'pending');
|
||
const sym = state==='passed'?'✓':state==='failed'?'✗':state==='skipped'?'~':state==='running'?'›':'·';
|
||
const c = state==='passed'?t.pass:state==='failed'?t.danger:state==='skipped'?t.skip:state==='running'?t.accent:t.dimmer;
|
||
return (
|
||
<div key={test.id}>
|
||
<div style={{ display:'flex', gap: 9, alignItems:'baseline', padding:'1px 0' }}>
|
||
<span style={{ color: c, width: 10 }}>{sym}</span>
|
||
<span style={{ color: state==='pending'?t.dimmer:t.text, flex: 1 }} title={test.desc}>{test.label}</span>
|
||
<span style={{ color: c, fontSize: 10.5 }}>{r?.metric || (state==='running'?'…':'')}</span>
|
||
{r?.result === 'failed' && (
|
||
<button onClick={() => D.toggleExpand(test.id)} style={{
|
||
background:'transparent', border:'none', color: t.dim, cursor:'pointer', padding: 0,
|
||
}}>{r.expanded?'[−]':'[+]'}</button>
|
||
)}
|
||
</div>
|
||
{r?.result === 'failed' && r.expanded && (
|
||
<div className="drv-fadein" style={{
|
||
margin:'2px 0 6px 19px', padding:'4px 8px', borderLeft:`2px solid ${t.danger}`,
|
||
background: 'rgba(255,107,107,0.06)', fontSize: 10.5,
|
||
}}>
|
||
<div style={{ color: t.danger, fontWeight: 600 }}>! {r.error}</div>
|
||
<div style={{ color: t.dim }}>{r.hint}</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
{/* actions */}
|
||
<div style={{ height: 12 }}/>
|
||
<div style={{ display:'flex', gap: 6 }}>
|
||
{(() => {
|
||
const allFailed = D.lastSummary && D.lastSummary.failed === D.tests.length;
|
||
const ok = D.phase === 'checked' && !allFailed;
|
||
const warning = isActive && (D.lastSummary?.failed||0) > 0;
|
||
if (isActive) {
|
||
const c = warning ? t.warn : t.accent;
|
||
return (
|
||
<div style={{
|
||
flex: 1, padding:'7px 12px', border:`1px solid ${c}`, color: c, fontWeight: 700,
|
||
background: `${c}14`, textShadow: `0 0 6px ${c}80`,
|
||
display:'flex', alignItems:'center', gap: 7,
|
||
}}>
|
||
<span className="drv-pulsedot" style={{ width: 7, height: 7, borderRadius: 3, background: c, boxShadow:`0 0 6px ${c}` }}/>
|
||
[ ACTIVE{warning ? ' / UDP-WARN' : ''} ]
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<button onClick={D.startProxy} disabled={!ok} style={{
|
||
flex: 1, padding:'7px 12px', border:`1px solid ${ok?t.accent:t.dim}`,
|
||
background: ok ? `${t.accent}14` : 'transparent',
|
||
color: ok ? t.accent : t.dim, fontFamily: fontMono, fontWeight: 600, fontSize: 11.5,
|
||
letterSpacing: 1, textAlign:'left', cursor: ok?'pointer':'not-allowed',
|
||
textShadow: ok ? `0 0 6px ${t.accent}80` : 'none', borderRadius: 0,
|
||
}}>{'>'} start_proxying</button>
|
||
);
|
||
})()}
|
||
<button onClick={D.stopProxy} disabled={!isActive} style={{
|
||
flex: 1, padding:'7px 12px', border:`1px solid ${isActive?t.dim:t.borderSoft}`,
|
||
background:'transparent', color: isActive ? t.text : t.dimmer, fontFamily: fontMono,
|
||
fontWeight: 600, fontSize: 11.5, letterSpacing: 1, textAlign:'left',
|
||
cursor: isActive?'pointer':'not-allowed', borderRadius: 0,
|
||
}}>{'■'} stop</button>
|
||
</div>
|
||
{isActive && (
|
||
<div style={{
|
||
marginTop: 8, padding:'5px 9px', border:`1px dashed ${t.borderSoft}`,
|
||
color: t.dim, fontSize: 10.5, display:'flex', justifyContent:'space-between',
|
||
}}>
|
||
<span>↑ {window.fmtBytes(D.stats.up).padEnd(10)}</span>
|
||
<span>↓ {window.fmtBytes(D.stats.down).padEnd(10)}</span>
|
||
<span>tcp={D.stats.tcp}</span>
|
||
<span>udp={D.stats.udp}</span>
|
||
<span>up={window.fmtUptime(D.stats.uptimeS)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* logs */}
|
||
<div style={{ borderTop:`1px solid ${t.border}`, background: t.chrome, flexShrink: 0, position:'relative', zIndex: 6 }}>
|
||
<button onClick={() => D.setLogsOpen(!D.logsOpen)} style={{
|
||
width:'100%', padding:'6px 14px', display:'flex', alignItems:'center', gap: 8,
|
||
background:'transparent', border:'none', color: t.dim, cursor:'pointer',
|
||
fontFamily: fontMono, fontSize: 11, letterSpacing: 0.6,
|
||
}}>
|
||
<span>{D.logsOpen?'▼':'▶'}</span>
|
||
<span style={{ fontWeight: 600 }}>tail -f drover.log</span>
|
||
<span style={{ marginLeft:'auto', color: t.dimmer }}>{D.logs.length} lines</span>
|
||
</button>
|
||
{D.logsOpen && (
|
||
<>
|
||
<div style={{ display:'flex', gap: 6, padding:'0 14px 6px' }}>
|
||
{[['copy', () => navigator.clipboard?.writeText(D.logs.map(x=>`[${x.level}] ${x.msg}`).join('\n'))],
|
||
['clear', D.clearLogs], ['file', null]].map(([l, fn]) => (
|
||
<button key={l} onClick={fn||undefined} style={{
|
||
background:'transparent', border:`1px solid ${t.borderSoft}`, color: t.dim,
|
||
padding:'2px 7px', fontSize: 10, fontFamily: fontMono, cursor:'pointer', borderRadius: 0,
|
||
}}>{`[${l}]`}</button>
|
||
))}
|
||
</div>
|
||
<div className="drv-log" ref={el => el && (el.scrollTop = el.scrollHeight)}
|
||
style={{ maxHeight: 110, overflowY:'auto', padding:'5px 14px',
|
||
fontFamily: fontMono, fontSize: 10, lineHeight: 1.55, color: t.dim }}>
|
||
{D.logs.map((l,i) => (
|
||
<div key={i}>
|
||
<span style={{ color: t.dimmer }}>{window.fmtTime(l.t)}</span>{' '}
|
||
<span style={{ color: l.level==='ERROR'?t.danger:l.level==='WARN'?t.warn:t.accent }}>{l.level}</span>{' '}
|
||
<span style={{ color: t.text }}>{l.msg}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function cliHead(t) { return { color: t.dim, fontSize: 10.5, marginBottom: 2, letterSpacing: 0.3 }; }
|
||
function CliRow({ children, style }) {
|
||
return <div style={{ display:'flex', alignItems:'center', gap: 7, ...style }}>{children}</div>;
|
||
}
|
||
function CliInput({ value, onChange, placeholder, type, onSubmit, style, t, id }) {
|
||
return <input id={id} value={value} type={type||'text'}
|
||
onChange={e => onChange(e.target.value)} placeholder={placeholder}
|
||
onKeyDown={e => e.key === 'Enter' && onSubmit?.()}
|
||
style={{
|
||
background: t.inputBg, color: t.accent, border:'none',
|
||
borderBottom:`1px solid ${t.borderSoft}`, borderRadius: 0,
|
||
padding:'3px 4px', fontFamily: 'inherit', fontSize: 12,
|
||
outline:'none', boxSizing:'border-box', textShadow:`0 0 4px ${t.accent}60`, ...style,
|
||
}}/>;
|
||
}
|
||
|
||
window.CliWindow = CliWindow;
|