conductor

CI task system
git clone git://git.finwo.net/app/conductor
Log | Files | Refs | README | LICENSE

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 };