AkurAI Build
Menu

AkurAI-Build

public

Latest change 834088c92ff481a8a0cc0f5825c8fb9524396e7a - Build lean Git-native AkurAI CI/CD by Ólafur Búi Ólafsson

const $=id=>document.getElementById(id),state={selected:null};
const element=(tag,text,className)=>{const node=document.createElement(tag);if(text!==undefined)node.textContent=text;if(className)node.className=className;return node};
const status=value=>{const node=element("span",value,"pt-status");node.dataset.status=String(value||"unknown").replace(/[^a-z]/g,"");return node};
const when=value=>value?new Date(value*1000).toLocaleString():"—";
async function api(path,options={}){const token=$("token").value.trim();sessionStorage.setItem("akurai-token",token);const response=await fetch(path,{...options,headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json",...(options.headers||{})}});const body=await response.json().catch(()=>({ok:false,error:{message:response.statusText}}));if(!response.ok||!body.ok)throw new Error(body.error?.message||`HTTP ${response.status}`);return body.data}
async function load(){try{const data=await api("/api/state"),select=$("repo"),selected=select.value;$("health").textContent="online";select.replaceChildren(...data.repositories.map(repository=>{const option=element("option",repository.name);option.value=repository.name;return option}));if(selected)select.value=selected;$("m-projects").textContent=data.repositories.length;$("m-running").textContent=data.runs.filter(run=>["queued","running","waiting"].includes(run.status)).length;$("m-passed").textContent=data.runs.filter(run=>run.status==="succeeded").length;$("m-failed").textContent=data.runs.filter(run=>run.status==="failed").length;$("updated").textContent=`Updated ${new Date().toLocaleTimeString()}`;$("runs").replaceChildren(...data.runs.map(run=>{const row=element("tr");row.dataset.id=run.id;const stateCell=element("td");stateCell.append(status(run.status));row.append(element("td",`#${run.id} · ${run.repository}`),stateCell,element("td",run.git_ref),element("td",(run.commit_sha||"pending").slice(0,9),"pt-commit"),element("td",when(run.started_at||run.created_at)));row.onclick=()=>selectRun(run.id);return row}));if(state.selected)await selectRun(state.selected)}catch(error){$("health").textContent=error.message;$("detail").replaceChildren(element("p",error.message,"pt-notice pt-error"))}}
async function selectRun(id){state.selected=Number(id);const run=await api(`/api/runs/${id}`),content=document.createDocumentFragment(),title=element("div",undefined,"pt-run-title"),left=element("div");left.append(element("h3",`#${run.id} ${run.repository}`),element("div",`${run.git_ref} · ${(run.commit_sha||"pending").slice(0,12)}`,"pt-commit"));title.append(left,status(run.status));content.append(title);$("retry").hidden=false;$("retry").onclick=async()=>{await api(`/api/runs/${id}/retry`,{method:"POST",body:"{}"});await load()};if(run.status==="waiting")content.append(element("p","A protected environment is waiting for promotion.","pt-notice"));for(const job of run.jobs){const card=element("section",undefined,"pt-job"),head=element("div",undefined,"pt-job-head"),meta=element("div");meta.append(element("strong",job.name),element("div",[job.executor,job.image,job.platform].filter(Boolean).join(" · "),"pt-job-meta"));head.append(meta,status(job.status));card.append(head);if(job.logs)card.append(element("pre",job.logs));if(job.status==="waiting"){const wrap=element("div",undefined,"pt-panel-body"),button=element("button",`Promote ${job.environment||job.name}`,"pt-button pt-primary");button.onclick=async()=>{await api(`/api/runs/${run.id}/promote/${encodeURIComponent(job.environment)}`,{method:"POST",body:"{}"});await load()};wrap.append(button);card.append(wrap)}content.append(card)}if(run.artifacts.length){content.append(element("h3","Artifacts"));const list=element("div",undefined,"pt-artifacts");for(const artifact of run.artifacts){const row=element("div",undefined,"pt-artifact"),info=element("div"),button=element("button","Download","pt-button pt-small");info.append(element("div",artifact.name),element("div",`${artifact.bytes} bytes · ${artifact.sha256.slice(0,12)}`,"pt-job-meta"));button.onclick=()=>download(artifact.id,artifact.name);row.append(info,button);list.append(row)}content.append(list)}if(run.deployments.length){content.append(element("h3","Deployments"));for(const deployment of run.deployments){const row=element("div",undefined,"pt-artifact");row.append(element("span",deployment.environment),status(deployment.status));content.append(row)}}$("detail").replaceChildren(content)}
async function download(id,name){const response=await fetch(`/api/artifacts/${id}`,{headers:{Authorization:`Bearer ${$("token").value.trim()}`}});if(!response.ok){alert("Download failed");return}const link=document.createElement("a");link.href=URL.createObjectURL(await response.blob());link.download=name.split("/").pop();link.click();setTimeout(()=>URL.revokeObjectURL(link.href),1000)}
$("token").value=sessionStorage.getItem("akurai-token")||"";$("refresh").onclick=load;$("trigger").onclick=async()=>{try{const repository=$("repo").value;if(!repository)throw new Error("Register a repository first");const run=await api(`/api/repos/${encodeURIComponent(repository)}/runs`,{method:"POST",body:JSON.stringify({git_ref:$("ref").value})});state.selected=run.id;await load()}catch(error){alert(error.message)}};load();setInterval(load,2500);