feat: new deploy queue with live job logs

This commit is contained in:
2024-03-11 02:02:56 +01:00
parent 3083dc5f18
commit ad49ce171d
14 changed files with 623 additions and 176 deletions
+26
View File
@@ -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
View File
@@ -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>
</>