jobs.tsx (2808B)
1 // web/src/screen/jobs.tsx - jobs, everywhere or within one project 2 3 import m from 'mithril'; 4 import { page, Page } from '../util/api'; 5 import { poll } from '../util/poll'; 6 import { settled, duration, shortSha } from '../util/format'; 7 import { Badge, Empty, Failed, Loading, Pager, When } from '../component/layout'; 8 9 const LIMIT = 25; 10 11 type Job = { 12 id: string; project_id: string; number: number; ref: string; head_sha: string; 13 trigger_type: string; actor: string | null; title: string | null; state: string; 14 created_at: number; started_at: number | null; finished_at: number | null; 15 }; 16 17 export const Jobs: m.Component = { 18 oninit(vnode: any) { 19 const project = m.route.param('project'); 20 vnode.state.offset = 0; 21 vnode.state.rows = null as Page<Job> | null; 22 vnode.state.error = null as unknown; 23 24 vnode.state.load = async () => { 25 try { 26 const path = project ? `/projects/${encodeURIComponent(project)}/jobs` : '/jobs'; 27 vnode.state.rows = await page<Job>(path, { query: { offset: vnode.state.offset, limit: LIMIT } }); 28 vnode.state.error = null; 29 } catch (e) { 30 vnode.state.error = e; 31 return false; 32 } 33 return vnode.state.rows.rows.some((j: Job) => !settled(j.state)); 34 }; 35 36 vnode.state.poller = poll(vnode.state.load, 4000); 37 vnode.state.poller.start(); 38 }, 39 40 onremove: (vnode: any) => vnode.state.poller.stop(), 41 42 view(vnode: any) { 43 const { rows, error } = vnode.state; 44 if (error) return <Failed error={error} />; 45 if (!rows) return <Loading />; 46 47 const move = (offset: number) => { 48 vnode.state.offset = offset; 49 vnode.state.poller.stop(); 50 vnode.state.poller.start(); 51 }; 52 53 return [ 54 <h1>jobs</h1>, 55 rows.rows.length === 0 ? <Empty what="jobs" /> : ( 56 <table> 57 <thead> 58 <tr> 59 <th>job</th><th>project</th><th>ref</th><th>commit</th> 60 <th>state</th><th>took</th><th>created</th> 61 </tr> 62 </thead> 63 <tbody> 64 {rows.rows.map((job) => ( 65 <tr key={job.id}> 66 <td><m.route.Link href={`/jobs/${job.id}`}>{`#${job.number}`}</m.route.Link></td> 67 <td><m.route.Link href={`/projects/${job.project_id}`}>{job.project_id}</m.route.Link></td> 68 <td class="ref">{job.ref?.replace(/^refs\/heads\//, '') ?? ''}</td> 69 <td class="mono">{shortSha(job.head_sha)}</td> 70 <td><Badge state={job.state} /></td> 71 <td>{duration(job.started_at, job.finished_at)}</td> 72 <td><When at={job.created_at} /></td> 73 </tr> 74 ))} 75 </tbody> 76 </table> 77 ), 78 <Pager offset={rows.offset} total={rows.total} limit={LIMIT} onmove={move} />, 79 ]; 80 }, 81 };