projects.tsx (4095B)
1 // web/src/screen/projects.tsx - the project list, and creating one 2 3 import m from 'mithril'; 4 import { page, post, Page } from '../util/api'; 5 import { Empty, Failed, Loading, Pager, When, session } from '../component/layout'; 6 7 const LIMIT = 25; 8 9 type Project = { 10 id: string; name: string; repo_url: string; default_branch: string; 11 visibility: string; enabled: number; job_counter: number; created_at: number; 12 }; 13 14 export const Projects: m.Component = { 15 oninit(vnode: any) { 16 vnode.state.offset = 0; 17 vnode.state.rows = null as Page<Project> | null; 18 vnode.state.error = null; 19 vnode.state.creating = false; 20 vnode.state.created = null; 21 vnode.state.form = { id: '', name: '', repo_url: '', default_branch: 'main' }; 22 23 vnode.state.load = async () => { 24 try { 25 vnode.state.rows = await page<Project>('/projects', { 26 query: { offset: vnode.state.offset, limit: LIMIT }, 27 }); 28 vnode.state.error = null; 29 } catch (e) { 30 vnode.state.error = e; 31 } 32 m.redraw(); 33 }; 34 vnode.state.load(); 35 }, 36 37 view(vnode: any) { 38 const { rows, error, created } = vnode.state; 39 if (error && !rows) return <Failed error={error} />; 40 if (!rows) return <Loading />; 41 42 const create = async (e: Event) => { 43 e.preventDefault(); 44 try { 45 vnode.state.created = await post<any>('/projects', vnode.state.form); 46 vnode.state.creating = false; 47 vnode.state.form = { id: '', name: '', repo_url: '', default_branch: 'main' }; 48 await vnode.state.load(); 49 } catch (err) { 50 vnode.state.error = err; 51 } 52 }; 53 54 const field = (key: string, label: string, required = false) => ( 55 <label> 56 <span>{label}</span> 57 <input 58 value={vnode.state.form[key]} 59 required={required} 60 oninput={(e: any) => { vnode.state.form[key] = e.target.value; }} 61 /> 62 </label> 63 ); 64 65 return [ 66 <h1>projects</h1>, 67 error ? <Failed error={error} /> : null, 68 69 // Shown once, because it is never readable again. 70 created?.trigger_secret 71 ? ( 72 <div class="reveal"> 73 <strong>Trigger secret for {created.id}</strong> 74 <p class="muted">Copy it now. It cannot be shown again.</p> 75 <code class="mono">{created.trigger_secret}</code> 76 <button onclick={() => { vnode.state.created = null; }}>done</button> 77 </div> 78 ) 79 : null, 80 81 !session.user ? null : vnode.state.creating 82 ? ( 83 <form class="panel" onsubmit={create}> 84 {field('id', 'id')} 85 {field('name', 'name')} 86 {field('repo_url', 'repository url', true)} 87 {field('default_branch', 'default branch')} 88 <div class="actions"> 89 <button type="submit">create</button> 90 <button type="button" onclick={() => { vnode.state.creating = false; }}>cancel</button> 91 </div> 92 </form> 93 ) 94 : <div class="actions"><button onclick={() => { vnode.state.creating = true; }}>new project</button></div>, 95 96 rows.rows.length === 0 ? <Empty what="projects" /> : ( 97 <table> 98 <thead> 99 <tr><th>project</th><th>repository</th><th>branch</th><th>visibility</th><th>jobs</th><th>created</th></tr> 100 </thead> 101 <tbody> 102 {rows.rows.map((p) => ( 103 <tr key={p.id} class={p.enabled ? '' : 'disabled'}> 104 <td><m.route.Link href={`/projects/${p.id}`}>{p.name}</m.route.Link></td> 105 <td class="mono ellipsis">{p.repo_url}</td> 106 <td class="ref">{p.default_branch}</td> 107 <td>{p.visibility}</td> 108 <td>{p.job_counter}</td> 109 <td><When at={p.created_at} /></td> 110 </tr> 111 ))} 112 </tbody> 113 </table> 114 ), 115 116 <Pager 117 offset={rows.offset} 118 total={rows.total} 119 limit={LIMIT} 120 onmove={(offset: number) => { vnode.state.offset = offset; vnode.state.load(); }} 121 />, 122 ]; 123 }, 124 };