Close eight open SDK tasks: the pidfile, the log, cards, names and a live curve
Each was a loose end recorded under `### SDK` in the notepad. `serve` takes its own pidfile down on SIGTERM. uvicorn restores the handler it found and re-raises the signal it stopped on, so the default handler ended the process without unwinding and the `finally` never ran — which is what a stop sends, and what left `serve.pid` behind. `serve.log` is cut back past 5 MB by the engine rather than by the screen that started it, so an adopted engine is bounded too. Gated on its own stdout being an appended regular file, which is what makes the cut safe: the kernel then puts the next write at the new end. Cards are counted from `/dev/nvidia[0-9]*`, so `FLOW_GPUS`/`--gpus` of 0 means "work it out" the way `FLOW_CPUS` always has. The engine counts, not the accountant — a remote worker builds one of those from its own inventory, and detecting there would hand it the engine host's cards. The worker counts last: what a batch job says it was granted still wins. `GET /runs/metrics/names` is the distinct over a selection that `--list` and the terminal's metric picker were approximating by reading the newest run that had measured anything, which missed a name only an older run ever wrote. `MetricSink` announces each batch it has written (`run_metric`, carrying the names). Not a per-point event: one covers up to 500 points or two seconds of them, and the rows stay the record. The terminal comparison fills in as the first readings land instead of staying blank until reopened, and the browser refetches the run and any comparison rather than the list behind them. `retry --group` pages the list route by `before` instead of stopping at 500. The terminal dashboard takes the terminal's colours (`ansi-dark`), and the web UI can re-pair from Settings without disconnecting first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PRQ9bmTvCbqCwXo9mxZzzV
This commit is contained in:
@@ -109,6 +109,13 @@ type FlowEvent =
|
||||
group?: string
|
||||
ts?: number
|
||||
}
|
||||
| {
|
||||
type: "run_metric"
|
||||
flow: string
|
||||
run: string
|
||||
names: string[]
|
||||
ts?: number
|
||||
}
|
||||
|
||||
function socketUrl(): string {
|
||||
const base = String(OpenAPI.BASE || window.location.origin)
|
||||
@@ -328,6 +335,14 @@ function connect() {
|
||||
// live value, so nothing here goes through the live store.
|
||||
client?.invalidateQueries({ queryKey: runKeys.all })
|
||||
break
|
||||
case "run_metric":
|
||||
// A batch of readings was written. Only what draws them is refetched —
|
||||
// the run's own detail (its metric names and curve hang below that
|
||||
// key) and any comparison — so a training run reporting every second
|
||||
// does not re-read the list behind it.
|
||||
client?.invalidateQueries({ queryKey: runKeys.detail(message.run) })
|
||||
client?.invalidateQueries({ queryKey: runKeys.compares })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ export const runKeys = {
|
||||
detail: (id: string) => ["runs", "detail", id] as const,
|
||||
metrics: (id: string, name: string) =>
|
||||
["runs", "detail", id, "metrics", name] as const,
|
||||
compares: ["runs", "compare"] as const,
|
||||
compare: (ids: string[], metric: string, x: string) =>
|
||||
["runs", "compare", ids.join(","), metric, x] as const,
|
||||
}
|
||||
|
||||
@@ -74,7 +74,11 @@ export function RemoteAccess() {
|
||||
}),
|
||||
onSuccess: () => {
|
||||
setCode("")
|
||||
showSuccessToast("Connected to the portal")
|
||||
showSuccessToast(
|
||||
status?.enrolled
|
||||
? "Replaced the connection"
|
||||
: "Connected to the portal",
|
||||
)
|
||||
invalidate()
|
||||
},
|
||||
onError: handleError.bind(showErrorToast),
|
||||
@@ -104,6 +108,54 @@ export function RemoteAccess() {
|
||||
|
||||
if (!status) return null
|
||||
|
||||
// The same fields either way. A code is redeemed before anything local is
|
||||
// written, so one the portal refuses leaves a working connection working —
|
||||
// which is what makes re-pairing safe to offer beside a live one, rather
|
||||
// than only after a Disconnect.
|
||||
const form = (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="portal-url">Portal</Label>
|
||||
<Input
|
||||
id="portal-url"
|
||||
value={portalUrl}
|
||||
onChange={(event) => setPortalUrl(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="claim-code">Code</Label>
|
||||
<Input
|
||||
id="claim-code"
|
||||
value={code}
|
||||
placeholder="XXXX-XXXX"
|
||||
className="font-mono tracking-widest"
|
||||
data-testid="claim-code"
|
||||
onChange={(event) => setCode(event.target.value.toUpperCase())}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Get a code at fluksio.com → Instances → Add instance.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant={status.enrolled ? "outline" : "brand"}
|
||||
className="rounded-full"
|
||||
disabled={!code.trim() || !portalUrl.trim() || connect.isPending}
|
||||
onClick={() => connect.mutate()}
|
||||
data-testid="connect-portal"
|
||||
>
|
||||
{connect.isPending
|
||||
? status.enrolled
|
||||
? "Re-pairing…"
|
||||
: "Connecting…"
|
||||
: status.enrolled
|
||||
? "Re-pair"
|
||||
: "Connect"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -198,43 +250,28 @@ export function RemoteAccess() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<h3 className="font-medium">Connect with a new code</h3>
|
||||
<InfoTip label="Connect with a new code">
|
||||
Moving to another portal account clears who was mapped to
|
||||
whom here; the local users themselves stay.
|
||||
</InfoTip>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Replaces this connection. Panels and remote users pair again
|
||||
against the new one.
|
||||
</p>
|
||||
</div>
|
||||
{form}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="portal-url">Portal</Label>
|
||||
<Input
|
||||
id="portal-url"
|
||||
value={portalUrl}
|
||||
onChange={(event) => setPortalUrl(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="claim-code">Code</Label>
|
||||
<Input
|
||||
id="claim-code"
|
||||
value={code}
|
||||
placeholder="XXXX-XXXX"
|
||||
className="font-mono tracking-widest"
|
||||
onChange={(event) => setCode(event.target.value.toUpperCase())}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Get a code at fluksio.com → Instances → Add instance.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant="brand"
|
||||
className="rounded-full"
|
||||
disabled={
|
||||
!code.trim() || !portalUrl.trim() || connect.isPending
|
||||
}
|
||||
onClick={() => connect.mutate()}
|
||||
>
|
||||
{connect.isPending ? "Connecting…" : "Connect"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
form
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user