mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-21 18:55:36 +02:00
feat(capture): async polling in CaptureDialog + pollCaptureJob api helper
This commit is contained in:
parent
f76f5438f2
commit
9ac28459ed
2 changed files with 52 additions and 9 deletions
|
|
@ -64,9 +64,14 @@ export async function submitCapture(archiveId, locator) {
|
||||||
body: JSON.stringify({ locator }),
|
body: JSON.stringify({ locator }),
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const msg = await res.text();
|
const body = await res.json().catch(() => ({}));
|
||||||
throw new Error(msg || `HTTP ${res.status}`);
|
throw new Error(body.error || `HTTP ${res.status}`);
|
||||||
}
|
}
|
||||||
|
return res.json(); // { job_uid, status: "pending" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pollCaptureJob(archiveId, jobUid) {
|
||||||
|
return getJson(`/api/archives/${archiveId}/capture_jobs/${jobUid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Auth helpers ─────────────────────────────────────────────────────────────
|
// ── Auth helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
import { useRef, useEffect, useState } from 'react'
|
import { useRef, useEffect, useState } from 'react'
|
||||||
import { submitCapture } from '../api'
|
import { submitCapture, pollCaptureJob } from '../api'
|
||||||
|
|
||||||
export default function CaptureDialog({ open, archiveId, onClose, onCaptured }) {
|
export default function CaptureDialog({ open, archiveId, onClose, onCaptured }) {
|
||||||
const dialogRef = useRef(null)
|
const dialogRef = useRef(null)
|
||||||
const [locator, setLocator] = useState('')
|
const [locator, setLocator] = useState('')
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
|
const [jobStatus, setJobStatus] = useState(null) // null | 'running' | 'completed' | 'failed'
|
||||||
|
const pollRef = useRef(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dialog = dialogRef.current
|
const dialog = dialogRef.current
|
||||||
if (!dialog) return
|
if (!dialog) return
|
||||||
const handleClose = () => onClose()
|
const handleClose = () => {
|
||||||
|
clearInterval(pollRef.current)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
dialog.addEventListener('close', handleClose)
|
dialog.addEventListener('close', handleClose)
|
||||||
return () => dialog.removeEventListener('close', handleClose)
|
return () => dialog.removeEventListener('close', handleClose)
|
||||||
}, [onClose])
|
}, [onClose])
|
||||||
|
|
@ -21,6 +26,9 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
||||||
if (open) {
|
if (open) {
|
||||||
setLocator('')
|
setLocator('')
|
||||||
setError(null)
|
setError(null)
|
||||||
|
setJobStatus(null)
|
||||||
|
setBusy(false)
|
||||||
|
clearInterval(pollRef.current)
|
||||||
if (!dialog.open) dialog.showModal()
|
if (!dialog.open) dialog.showModal()
|
||||||
} else {
|
} else {
|
||||||
if (dialog.open) dialog.close()
|
if (dialog.open) dialog.close()
|
||||||
|
|
@ -31,17 +39,47 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
||||||
if (!locator.trim()) { setError('Enter a locator.'); return }
|
if (!locator.trim()) { setError('Enter a locator.'); return }
|
||||||
setBusy(true)
|
setBusy(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
|
setJobStatus(null)
|
||||||
try {
|
try {
|
||||||
await submitCapture(archiveId, locator.trim())
|
const job = await submitCapture(archiveId, locator.trim())
|
||||||
dialogRef.current?.close()
|
setJobStatus('running')
|
||||||
onCaptured()
|
pollRef.current = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const updated = await pollCaptureJob(archiveId, job.job_uid)
|
||||||
|
if (updated.status === 'completed') {
|
||||||
|
clearInterval(pollRef.current)
|
||||||
|
pollRef.current = null
|
||||||
|
setBusy(false)
|
||||||
|
setJobStatus('completed')
|
||||||
|
dialogRef.current?.close()
|
||||||
|
onCaptured()
|
||||||
|
} else if (updated.status === 'failed') {
|
||||||
|
clearInterval(pollRef.current)
|
||||||
|
pollRef.current = null
|
||||||
|
setBusy(false)
|
||||||
|
setJobStatus('failed')
|
||||||
|
setError(updated.error_text || 'Capture failed.')
|
||||||
|
}
|
||||||
|
// pending / running: keep polling
|
||||||
|
} catch (pollErr) {
|
||||||
|
clearInterval(pollRef.current)
|
||||||
|
pollRef.current = null
|
||||||
|
setBusy(false)
|
||||||
|
setError(pollErr.message)
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message)
|
setError(e.message)
|
||||||
} finally {
|
|
||||||
setBusy(false)
|
setBusy(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buttonLabel() {
|
||||||
|
if (!busy) return 'Capture'
|
||||||
|
if (jobStatus === 'running') return 'Running\u2026'
|
||||||
|
return 'Capturing\u2026'
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog ref={dialogRef} className="capture-dialog">
|
<dialog ref={dialogRef} className="capture-dialog">
|
||||||
<div className="capture-dialog-inner">
|
<div className="capture-dialog-inner">
|
||||||
|
|
@ -56,7 +94,7 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
||||||
<div className="capture-actions">
|
<div className="capture-actions">
|
||||||
<button type="button" className="capture-cancel" onClick={() => dialogRef.current?.close()}>Cancel</button>
|
<button type="button" className="capture-cancel" onClick={() => dialogRef.current?.close()}>Cancel</button>
|
||||||
<button type="button" className="capture-submit" onClick={handleSubmit} disabled={busy}>
|
<button type="button" className="capture-submit" onClick={handleSubmit} disabled={busy}>
|
||||||
{busy ? 'Capturing\u2026' : 'Capture'}
|
{buttonLabel()}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue