internal/gui: wire real checker.Run + CancelCheck binding + RawHex display
- RunCheck now drives internal/checker.Run instead of the fake 7-step sleep loop. Streams checker.Result events as "check:result" with Duration converted to milliseconds; emits "check:done" summary on channel close. Re-running while a check is in flight cancels the previous run and waits for its goroutine to drain so two emitter goroutines never race on event order. - New CancelCheck method (no-op when nothing is running) is bound through wailsjs/go/gui/App.js and surfaced in useDrover as cancelCheck() with a "check cancelled by user" log line. - Classic.jsx: while phase==='checking', the Check button collapses to a disabled "Checking…" pill side-by-side with a Cancel button (uses Stop's secondary visual weight, t.danger on hover). The expanded failure row now renders r.rawHex (truncated to 64 chars) on its own mono line and the copy button includes raw=<hex> when present. - CheckResult struct gains RawHex (json:"rawHex") and Attempt fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -121,9 +121,18 @@ export function ClassicWindow({ mode = 'dark', initial, onToggleMode }) {
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<PrimaryBtn t={t} onClick={D.runCheck} disabled={D.phase === 'checking' || isActive}>
|
||||
{D.phase === 'checking' ? 'Checking…' : 'Check connection'}
|
||||
</PrimaryBtn>
|
||||
{D.phase === 'checking' ? (
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<PrimaryBtn t={t} onClick={D.runCheck} disabled style={{ flex: 1 }}>
|
||||
Checking…
|
||||
</PrimaryBtn>
|
||||
<ClassicCancelBtn t={t} onClick={D.cancelCheck} />
|
||||
</div>
|
||||
) : (
|
||||
<PrimaryBtn t={t} onClick={D.runCheck} disabled={isActive}>
|
||||
Check connection
|
||||
</PrimaryBtn>
|
||||
)}
|
||||
|
||||
{/* Status */}
|
||||
<div style={{ height: 18 }} />
|
||||
@@ -316,8 +325,19 @@ function ClassicStatus({ t, D, palette, fontMono }) {
|
||||
}}>
|
||||
<div style={{ color: t.danger, fontWeight: 600, marginBottom: 2 }}>{r.error}</div>
|
||||
<div style={{ color: t.dim }}>{r.hint}</div>
|
||||
{r.rawHex && (
|
||||
<div style={{
|
||||
fontFamily: fontMono, fontSize: 10.5, color: t.dimmer,
|
||||
marginTop: 4, padding: '4px 6px',
|
||||
background: t.panelAlt, borderRadius: 2,
|
||||
overflowX: 'auto', whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{r.rawHex.length > 64 ? r.rawHex.slice(0, 64) + '…' : r.rawHex}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display:'flex', gap: 6, marginTop: 6 }}>
|
||||
<button onClick={() => navigator.clipboard?.writeText(`[${test.label}] ${r.error} — ${r.metric}`)}
|
||||
<button onClick={() => navigator.clipboard?.writeText(
|
||||
`[${test.label}] ${r.error} — ${r.metric}` + (r.rawHex ? ` — raw=${r.rawHex}` : ''))}
|
||||
style={smallBtn(t, fontMono)}>
|
||||
<IconCopy color={t.dim}/> copy
|
||||
</button>
|
||||
@@ -388,6 +408,22 @@ function ClassicStartBtn({ t, D, fontMono }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ClassicCancelBtn({ t, onClick }) {
|
||||
const [hover, setHover] = React.useState(false);
|
||||
return (
|
||||
<button onClick={onClick}
|
||||
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
||||
style={{
|
||||
width: 92, padding: '9px 12px', borderRadius: 3, fontWeight: 600, fontSize: 12.5,
|
||||
background: t.btnBg, color: hover ? t.danger : t.text,
|
||||
border: `1px solid ${hover ? t.danger : t.border}`, cursor: 'pointer',
|
||||
transition: 'color .12s, border-color .12s',
|
||||
}}>
|
||||
Cancel
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function ClassicStopBtn({ t, D }) {
|
||||
const enabled = D.phase === 'active';
|
||||
return (
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// UI components don't need to be rewritten — only their imports.
|
||||
|
||||
import * as React from 'react'
|
||||
import { RunCheck, StartEngine, StopEngine, GetStatus } from '../../wailsjs/go/gui/App'
|
||||
import { RunCheck, CancelCheck, StartEngine, StopEngine, GetStatus } from '../../wailsjs/go/gui/App'
|
||||
import { EventsOn, EventsOff } from '../../wailsjs/runtime/runtime'
|
||||
|
||||
// ─── Test catalog ──────────────────────────────────────────────────────────
|
||||
@@ -117,10 +117,12 @@ export function useDrover(initial = {}) {
|
||||
setResults(prev => ({
|
||||
...prev,
|
||||
[r.id]: {
|
||||
result: r.status,
|
||||
metric: r.metric,
|
||||
error: r.error,
|
||||
hint: r.hint,
|
||||
result: r.status,
|
||||
metric: r.metric,
|
||||
error: r.error,
|
||||
hint: r.hint,
|
||||
rawHex: r.rawHex,
|
||||
attempt: r.attempt,
|
||||
expanded: r.status === 'failed',
|
||||
},
|
||||
}));
|
||||
@@ -162,6 +164,11 @@ export function useDrover(initial = {}) {
|
||||
// The rest is event-driven (check:result, check:done) — see useEffect above.
|
||||
}
|
||||
|
||||
function cancelCheck() {
|
||||
CancelCheck();
|
||||
pushLog('WARN', 'check cancelled by user');
|
||||
}
|
||||
|
||||
async function startProxy() {
|
||||
if (phase !== 'checked') return;
|
||||
if (lastSummary?.failed === tests.length) return;
|
||||
@@ -196,7 +203,7 @@ export function useDrover(initial = {}) {
|
||||
stats,
|
||||
logs, logsOpen, setLogsOpen, pushLog, clearLogs: () => setLogs([]),
|
||||
lastSummary,
|
||||
runCheck, startProxy, stopProxy,
|
||||
runCheck, cancelCheck, startProxy, stopProxy,
|
||||
toggleExpand,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user