// drover-classic.jsx — Variant 1: Classic devtool. // Information-dense. Mono metrics. Plain rectangles, hairline borders. // Sober palette: neutral grays + one teal accent for primary action / success. const ClassicTheme = { // dark d: { bg: '#1c1d20', chrome: '#15161a', panel: '#22242a', panelAlt: '#1a1c20', border: '#34373d', borderSoft:'#2a2c32', text: '#dde0e6', dim: '#8a8f99', dimmer: '#5b6068', accent: '#3ea99f', // teal accentDim: '#2a7d76', danger: '#d96565', warn: '#d9a155', pass: '#5cba8b', skip: '#7c8088', inputBg: '#15161a', btnBg: '#2c2f36', btnBgH: '#373b43', primaryBg: '#3ea99f', primaryFg: '#0c1a18', }, l: { bg: '#f3f4f6', chrome: '#e8eaef', panel: '#ffffff', panelAlt: '#f8f9fb', border: '#d8dbe1', borderSoft:'#e6e8ec', text: '#1c1f24', dim: '#5c6168', dimmer: '#8a8f97', accent: '#2a7d76', accentDim: '#bdded9', danger: '#c0463f', warn: '#a8731e', pass: '#2f8c5a', skip: '#7c8088', inputBg: '#ffffff', btnBg: '#ffffff', btnBgH: '#f1f2f5', primaryBg: '#2a7d76', primaryFg: '#ffffff', }, }; function ClassicWindow({ mode = 'dark', initial }) { const t = ClassicTheme[mode === 'dark' ? 'd' : 'l']; 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","SF Mono",ui-monospace,Menlo,Consolas,monospace'; const fontUI = '"Inter","Segoe UI",system-ui,sans-serif'; const isActive = D.phase === 'active'; const allChecked = D.phase === 'checked' || D.phase === 'active'; const failed = D.lastSummary?.failed ?? 0; return (
{/* ─── title bar ─── */} {/* ─── content ─── */}
{/* Form */} SOCKS5 Proxy
D.update({ host: e.target.value })} placeholder="95.165.72.59 или example.com" onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={inputStyle(t, fontMono)} /> D.update({ port: e.target.value.replace(/\D/g,'') })} placeholder="12334" inputMode="numeric" onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={inputStyle(t, fontMono)} />
{ D.update({ auth: v }); if (v) setTimeout(() => document.getElementById('cls-login')?.focus(), 30); }}> Authentication
D.update({ login: e.target.value })} placeholder="user" onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={inputStyle(t, fontMono, !D.form.auth)} /> D.update({ password: e.target.value })} placeholder="••••••" onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={inputStyle(t, fontMono, !D.form.auth)} />
{D.phase === 'checking' ? 'Checking…' : 'Check connection'} {/* Status */}
Status {/* Action buttons */}
{isActive && }
{/* Logs collapsible */}
); } // ─── pieces ───────────────────────────────────────────────────────────────── function ClassicTitleBar({ t }) { const cellStyle = { width: 38, height: 28, display:'flex', alignItems:'center', justifyContent:'center', color: t.dim, cursor:'pointer', }; return (
Drover-Go v0.4.2
e.currentTarget.style.background = '#c0463f'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
); } function SectionLabel({ children, t }) { return
{children}
; } function Field({ children, label, t, style }) { return ( ); } function inputStyle(t, fontMono, disabled) { return { background: t.inputBg, color: disabled ? t.dimmer : t.text, border: `1px solid ${t.border}`, borderRadius: 3, padding: '7px 9px', fontFamily: fontMono, fontSize: 12, outline: 'none', width: '100%', boxSizing: 'border-box', transition: 'border-color .12s, box-shadow .12s', }; } function Checkbox({ checked, onChange, children, t }) { return ( ); } function PrimaryBtn({ t, onClick, disabled, children, style }) { return ( ); } // ─── status panel ────────────────────────────────────────────────────────── function ClassicStatus({ t, D, palette, fontMono }) { const idle = D.phase === 'idle'; if (idle) { return (
Ready to check
); } return (
{/* header */}
{D.phase === 'checking' ? <> Running diagnostics… {Object.keys(D.results).length}/{D.tests.length} : D.lastSummary?.failed === 0 ? All checks passed. Ready to start. : {D.lastSummary?.failed} of {D.tests.length} checks failed. Some features won't work.}
{/* tests */}
{D.tests.map((test, i) => { const r = D.results[test.id]; const state = r?.result || (D.running === test.id ? 'running' : 'pending'); const isLast = i === D.tests.length - 1; return (
{test.label} {r?.metric || (state === 'running' ? '...' : '')} {r?.result === 'failed' && ( )}
{r?.result === 'failed' && r.expanded && (
{r.error}
{r.hint}
)}
); })}
); } function iconBtnStyle(t) { return { width: 20, height: 20, padding: 0, border:'none', background:'transparent', cursor:'pointer', display:'inline-flex', alignItems:'center', justifyContent:'center', borderRadius: 2, }; } function smallBtn(t, fontMono) { return { display:'inline-flex', alignItems:'center', gap: 4, padding: '3px 7px', background: t.btnBg, border: `1px solid ${t.border}`, color: t.dim, borderRadius: 3, fontFamily: fontMono, fontSize: 10.5, cursor:'pointer', }; } // crude color mix for dark/light. expects hex (#rrggbb), bg can be hex too. amount=share-of-bg. function mode_mix(fg, bg, amt) { const a = hexToRgb(fg), b = hexToRgb(bg); return `rgb(${Math.round(a.r*(1-amt)+b.r*amt)},${Math.round(a.g*(1-amt)+b.g*amt)},${Math.round(a.b*(1-amt)+b.b*amt)})`; } function hexToRgb(h) { const v = h.replace('#',''); return { r: parseInt(v.slice(0,2),16), g: parseInt(v.slice(2,4),16), b: parseInt(v.slice(4,6),16) }; } // ─── start/stop ──────────────────────────────────────────────────────────── function ClassicStartBtn({ t, D, fontMono }) { const phase = D.phase; const summary = D.lastSummary; const allFailed = summary && summary.failed === D.tests.length; const checkedOk = phase === 'checked' && !allFailed; const active = phase === 'active'; const warning = active && (summary?.failed || 0) > 0; if (active) { return (
Active{warning ? ' · UDP fallback' : ''}
); } return ( Start proxying ); } function ClassicStopBtn({ t, D }) { const enabled = D.phase === 'active'; return ( ); } function ClassicLiveStats({ t, stats, fontMono }) { const cell = (icon, val) => (
{icon}{val}
); return (
{cell(, window.fmtBytes(stats.up))} {cell(, window.fmtBytes(stats.down))} {cell(TCP, stats.tcp)} {cell(UDP, stats.udp)} {cell(↑t, window.fmtUptime(stats.uptimeS))}
); } // ─── logs ────────────────────────────────────────────────────────────────── function ClassicLogs({ t, D, fontMono }) { return (
{D.logsOpen && (
el && (el.scrollTop = el.scrollHeight)}> {D.logs.map((l, i) => (
{window.fmtTime(l.t)} {' '} [{l.level}] {' '} {l.msg}
))}
)}
); } window.ClassicWindow = ClassicWindow;