Updates tab (apt list/refresh/upgrade/changelog, reboot flag) on a new background tasks package with live WS streaming; header power button with hostname confirmation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
41c2f05535
commit
acb11193b9
@@ -0,0 +1,44 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { api, TaskInfo, wsURLPath } from './api'
|
||||
|
||||
// Follows one background task: backlog, then live chunks over a WebSocket, until it exits.
|
||||
export default function TaskOutput({ id, onDone }: { id: string; onDone?: (t: TaskInfo) => void }) {
|
||||
const [task, setTask] = useState<TaskInfo | null>(null)
|
||||
const [text, setText] = useState('')
|
||||
const box = useRef<HTMLPreElement>(null)
|
||||
const doneRef = useRef(onDone)
|
||||
doneRef.current = onDone
|
||||
|
||||
useEffect(() => {
|
||||
setText(''); setTask(null)
|
||||
let closed = false
|
||||
api.task(id).then(setTask).catch(() => { /* shown as unknown */ })
|
||||
const s = new WebSocket(wsURLPath(`/api/tasks/${id}/stream`))
|
||||
s.binaryType = 'arraybuffer'
|
||||
const dec = new TextDecoder()
|
||||
s.onmessage = (ev) => {
|
||||
if (ev.data instanceof ArrayBuffer) { setText((t) => t + dec.decode(ev.data, { stream: true })); return }
|
||||
try {
|
||||
const m = JSON.parse(ev.data)
|
||||
if (m.type === 'exit') api.task(id).then((t) => { setTask(t); doneRef.current?.(t) }).catch(() => { /* ignore */ })
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
return () => { closed = true; s.close(); void closed }
|
||||
}, [id])
|
||||
|
||||
useEffect(() => { if (box.current) box.current.scrollTop = box.current.scrollHeight }, [text])
|
||||
|
||||
const status = !task ? '' : task.running ? 'running…' : task.error ? `failed: ${task.error}` : task.exit_code === 0 ? 'done' : `exited ${task.exit_code}`
|
||||
return (
|
||||
<div className="task">
|
||||
<div className="logs-bar">
|
||||
<span className={'dot ' + (task?.running ? 'warn' : task && (task.exit_code !== 0 || task.error) ? 'bad' : 'ok')} />
|
||||
<span>{task?.title ?? '…'}</span>
|
||||
<span className="muted">· {status}</span>
|
||||
<span className="spacer" />
|
||||
{task?.running && <button className="btn" onClick={() => api.cancelTask(id)}>Cancel</button>}
|
||||
</div>
|
||||
<pre className="run-out task-out" ref={box}>{text || (task?.running ? '' : '(no output)')}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user