feat: new deploy queue with live job logs
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { useEffect, useState } from "preact/hooks"
|
||||
|
||||
export const JobLogs = ({}) => {
|
||||
const [logLines, setLogLines] = useState([])
|
||||
|
||||
useEffect(async () => {
|
||||
const res = await fetch(location.href + '/logs?format=raw')
|
||||
const rawLogs = (await res.text()).trim()
|
||||
|
||||
if (rawLogs.length > 0) setLogLines(rawLogs.split('\n'))
|
||||
|
||||
// Setup SSE
|
||||
const es = new EventSource(location.href + '/logs?format=sse')
|
||||
es.addEventListener('message', ({ data }) => {
|
||||
const event = JSON.parse(data)
|
||||
setLogLines(lines => [
|
||||
...lines,
|
||||
event.content,
|
||||
])
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<pre><code>{logLines.join('\n')}</code></pre>
|
||||
)
|
||||
}
|
||||
+27
-57
@@ -6,8 +6,8 @@ import { durationToString } from './lib/utils'
|
||||
/**
|
||||
* @param {import('@/jobs.ts').QueuedJob} props
|
||||
*/
|
||||
export const QueuedJob = ({ uuid, name, submitter, submittedAt }) => (
|
||||
<div class="job queued" title={uuid}>
|
||||
export const QueuedJob = ({ uuid, status, name, submitter, submittedAt }) => (
|
||||
<div class={'job ' + status} title={uuid} onClick={() => (location.href = `/jobs/${uuid}`)}>
|
||||
<div class="name">{name}</div>
|
||||
<div class="footer">
|
||||
<div class="submitted-at">{new Date(submittedAt).toLocaleString()}</div>
|
||||
@@ -18,18 +18,9 @@ export const QueuedJob = ({ uuid, name, submitter, submittedAt }) => (
|
||||
/**
|
||||
* @param {import('@/jobs.ts').CompletedJob} props
|
||||
*/
|
||||
export const CompletedJob = ({
|
||||
uuid,
|
||||
name,
|
||||
submitter,
|
||||
submittedAt,
|
||||
startedAt,
|
||||
completedAt,
|
||||
successful,
|
||||
error,
|
||||
}) => {
|
||||
export const CompletedJob = ({ uuid, name, submittedAt, startedAt, completedAt }) => {
|
||||
return (
|
||||
<div class="job completed" title={uuid}>
|
||||
<div class="job completed" title={uuid} onClick={() => (location.href = `/jobs/${uuid}`)}>
|
||||
<div class="name">{name}</div>
|
||||
<div class="footer">
|
||||
<div class="submitted-at">{new Date(submittedAt).toLocaleString()}</div>
|
||||
@@ -40,54 +31,33 @@ export const CompletedJob = ({
|
||||
}
|
||||
|
||||
export const JobsPage = ({}) => {
|
||||
const [jobStore, setJobStore] = useState({
|
||||
queuedJobs: {},
|
||||
completedJobs: {},
|
||||
})
|
||||
const [jobStore, setJobStore] = useState({})
|
||||
|
||||
useEffect(async () => {
|
||||
const res = await fetch('/api/jobs')
|
||||
const { queuedJobs, completedJobs } = await res.json()
|
||||
// const { searchParams } = new URL(location.href)
|
||||
|
||||
const result = {
|
||||
queuedJobs: {},
|
||||
completedJobs: {},
|
||||
}
|
||||
// const limit = searchParams.has('limit') ? parseInt(searchParams.get('limit')) : 10
|
||||
// const offset = (searchParams.has('page') ? parseInt(searchParams.get('page')) : 1) - 1
|
||||
|
||||
for (const item of queuedJobs) {
|
||||
result.queuedJobs[item.uuid] = item
|
||||
}
|
||||
const res = await fetch(location.href + '?format=json')
|
||||
const jobs = await res.json()
|
||||
|
||||
for (const item of completedJobs) {
|
||||
result.completedJobs[item.uuid] = item
|
||||
const result = {}
|
||||
|
||||
for (const item of jobs) {
|
||||
result[item.uuid] = item
|
||||
}
|
||||
|
||||
setJobStore(result)
|
||||
|
||||
// Setup SSE
|
||||
const es = new EventSource('/api/sse')
|
||||
const es = new EventSource(location.href + '?format=sse')
|
||||
es.addEventListener('message', ({ data }) => {
|
||||
const event = JSON.parse(data)
|
||||
if (event.type === 'added') {
|
||||
setJobStore(s => ({
|
||||
...s,
|
||||
queuedJobs: {
|
||||
...s.queuedJobs,
|
||||
[event.job.uuid]: event.job,
|
||||
},
|
||||
}))
|
||||
}
|
||||
if (event.type === 'completed') {
|
||||
setJobStore(s => ({
|
||||
queuedJobs: {
|
||||
..._.omit(s.queuedJobs, event.job.uuid),
|
||||
},
|
||||
completedJobs: {
|
||||
...s.completedJobs,
|
||||
[event.job.uuid]: event.job,
|
||||
},
|
||||
}))
|
||||
}
|
||||
setJobStore(s => ({
|
||||
...s,
|
||||
[event.job.uuid]: event.job,
|
||||
}))
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -95,18 +65,18 @@ export const JobsPage = ({}) => {
|
||||
<>
|
||||
<h2>Queued Jobs</h2>
|
||||
<div class="list">
|
||||
{Object.values(jobStore.queuedJobs)
|
||||
.toReversed()
|
||||
.map(queuedJob => (
|
||||
<QueuedJob {...queuedJob} />
|
||||
{Object.values(jobStore)
|
||||
.filter(job => job.status !== 'completed')
|
||||
.map(job => (
|
||||
<QueuedJob {...job} />
|
||||
))}
|
||||
</div>
|
||||
<h2>Completed Jobs</h2>
|
||||
<div class="list">
|
||||
{Object.values(jobStore.completedJobs)
|
||||
.toReversed()
|
||||
.map(completedJob => (
|
||||
<CompletedJob {...completedJob} />
|
||||
{Object.values(jobStore)
|
||||
.filter(job => job.status === 'completed')
|
||||
.map(job => (
|
||||
<CompletedJob {...job} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user