garm/webapp/assets/_app/immutable/chunks/CwqI2jFH.js
Gabriel Adrian Samfira eec158b32c Add SPA UI for GARM
This change adds a single page application front-end to GARM. It uses
a generated REST client, built from the swagger definitions, the websocket
interface for live updates of entities and eager loading of everything
except runners, as users may have many runners and we don't want to load
hundreds of runners in memory.

Proper pagination should be implemented in the API, in future commits,
to avoid loading lots of elements for no reason.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-16 09:09:13 +00:00

1 line
17 KiB
JavaScript

import"./DsnmJJEf.js";import{i as Dr}from"./B3Pzt0F_.js";import{p as Lr,E as qr,m as s,o as Gr,f as m,k as r,j as o,g as e,r as a,t as v,e as M,c as b,v as T,b as Jr,z as gr,x as W,u as p,s as d,D as Ie,d as Fr}from"./D8EpLgQ1.js";import{p as vr,i as z}from"./5WA7h8uK.js";import{e as Ae,i as $e}from"./u94nIB4-.js";import{s as Oe,r as h,b as Q,g as C,c as Nr}from"./CiE1LlKV.js";import{b as E,a as Vr}from"./C6k1Q4We.js";import{p as Kr}from"./D4Caz1gY.js";import{M as Ur}from"./qB7B8uiS.js";import{J as Wr}from"./DZblzgqm.js";var Qr=m('<div class="rounded-md bg-red-50 dark:bg-red-900 p-4"><p class="text-sm font-medium text-red-800 dark:text-red-200"> </p></div>'),Xr=m('<div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-10 rounded"></div>'),Yr=m("<option><!></option>"),Zr=m('<select id="entity" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"><option> </option><!></select>'),et=m('<div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-10 rounded"></div>'),rt=m("<option> </option>"),tt=m('<select id="provider" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"><option>Select a provider</option><!></select>'),at=m('<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"> <button type="button" class="ml-1 h-4 w-4 rounded-full hover:bg-blue-200 dark:hover:bg-blue-800 flex items-center justify-center cursor-pointer"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg></button></span>'),ot=m('<div class="flex flex-wrap gap-2"></div>'),dt=m('<div class="space-y-4"><h3 class="text-lg font-medium text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">Entity & Provider Configuration</h3> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"><div><label for="entity" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <span class="text-red-500">*</span></label> <!></div> <div><label for="provider" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Provider <span class="text-red-500">*</span></label> <!></div></div></div> <div class="space-y-4"><h3 class="text-lg font-medium text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">Image & OS Configuration</h3> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"><div><label for="image" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Image <span class="text-red-500">*</span></label> <input id="image" type="text" required placeholder="e.g., ubuntu:22.04" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div> <div><label for="flavor" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Flavor <span class="text-red-500">*</span></label> <input id="flavor" type="text" required placeholder="e.g., default" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div> <div><label for="osType" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">OS Type</label> <select id="osType" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"><option>Linux</option><option>Windows</option></select></div> <div><label for="osArch" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Architecture</label> <select id="osArch" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"><option>AMD64</option><option>ARM64</option></select></div></div></div> <div class="space-y-4"><h3 class="text-lg font-medium text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">Runner Limits & Timing</h3> <div class="grid grid-cols-1 md:grid-cols-3 gap-4"><div><label for="minIdleRunners" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Min Idle Runners</label> <input id="minIdleRunners" type="number" min="0" placeholder="0" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div> <div><label for="maxRunners" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Max Runners</label> <input id="maxRunners" type="number" min="1" placeholder="10" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div> <div><label for="bootstrapTimeout" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Bootstrap Timeout (min)</label> <input id="bootstrapTimeout" type="number" min="1" placeholder="20" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div></div></div> <div class="space-y-4"><h3 class="text-lg font-medium text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">Advanced Settings</h3> <div class="grid grid-cols-1 md:grid-cols-3 gap-4"><div><label for="runnerPrefix" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Runner Prefix</label> <input id="runnerPrefix" type="text" placeholder="garm" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div> <div><label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Priority</label> <input id="priority" type="number" min="1" placeholder="100" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div> <div><label for="githubRunnerGroup" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">GitHub Runner Group (optional)</label> <input id="githubRunnerGroup" type="text" placeholder="Default group" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/></div></div> <div><label for="tag-input" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Tags</label> <div class="space-y-2"><div class="flex"><input id="tag-input" type="text" placeholder="Enter a tag" class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-l-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"/> <button type="button" class="px-3 py-2 bg-blue-600 text-white rounded-r-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer">Add</button></div> <!></div></div> <fieldset><legend class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Extra Specs (JSON)</legend> <!></fieldset> <div class="flex items-center"><input id="enabled" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 dark:border-gray-600 rounded"/> <label for="enabled" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">Enable pool immediately</label></div></div>',1),it=m('<div class="flex items-center"><div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div> Creating...</div>'),st=m('<div class="max-w-6xl w-full max-h-[90vh] overflow-y-auto"><div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700"><h2 class="text-xl font-semibold text-gray-900 dark:text-white">Create New Pool</h2></div> <form class="p-6 space-y-6"><!> <fieldset><legend class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Entity Level <span class="text-red-500">*</span></legend> <div class="grid grid-cols-3 gap-4"><button type="button"><svg class="w-8 h-8 mb-2 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z"></path></svg> <span class="text-sm font-medium text-gray-900 dark:text-white">Repository</span></button> <button type="button"><svg class="w-8 h-8 mb-2 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg> <span class="text-sm font-medium text-gray-900 dark:text-white">Organization</span></button> <button type="button"><svg class="w-8 h-8 mb-2 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg> <span class="text-sm font-medium text-gray-900 dark:text-white">Enterprise</span></button></div></fieldset> <!> <div class="flex justify-end space-x-3 pt-6 border-t border-gray-200 dark:border-gray-700"><button type="button" class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer">Cancel</button> <button type="submit" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"><!></button></div></form></div>');function yt(pr,X){Lr(X,!1);const Y=qr();let He=vr(X,"initialEntityType",8,""),mr=vr(X,"initialEntityId",8,""),G=s(!1),P=s(""),n=s(He()),S=s([]),Z=s([]),ee=s(!1),re=s(!1),k=s(mr()),j=s(""),B=s(""),D=s(""),te=s(void 0),ae=s(void 0),oe=s(void 0),de=s(100),ie=s("garm"),J=s("linux"),F=s("amd64"),se=s(""),le=s(!0),_=s([]),I=s(""),L=s("{}");async function fr(){try{d(re,!0),d(Z,await C.listProviders())}catch(l){d(P,l instanceof Error?l.message:"Failed to load providers")}finally{d(re,!1)}}async function Be(){if(e(n))try{switch(d(ee,!0),d(S,[]),e(n)){case"repository":d(S,await C.listRepositories());break;case"organization":d(S,await C.listOrganizations());break;case"enterprise":d(S,await C.listEnterprises());break}}catch(l){d(P,l instanceof Error?l.message:`Failed to load ${e(n)}s`)}finally{d(ee,!1)}}function ne(l){e(n)!==l&&(d(n,l),d(k,""),Be())}function De(){e(I).trim()&&!e(_).includes(e(I).trim())&&(d(_,[...e(_),e(I).trim()]),d(I,""))}function yr(l){d(_,e(_).filter((A,w)=>w!==l))}function xr(l){l.key==="Enter"&&(l.preventDefault(),De())}async function hr(){if(!e(n)||!e(k)||!e(j)||!e(B)||!e(D)){d(P,"Please fill in all required fields");return}try{d(G,!0),d(P,"");let l={};if(e(L).trim())try{l=JSON.parse(e(L))}catch{throw new Error("Invalid JSON in extra specs")}const A={provider_name:e(j),image:e(B),flavor:e(D),max_runners:e(te)||10,min_idle_runners:e(ae)||0,runner_bootstrap_timeout:e(oe)||20,priority:e(de),runner_prefix:e(ie),os_type:e(J),os_arch:e(F),"github-runner-group":e(se)||void 0,enabled:e(le),tags:e(_),extra_specs:e(L).trim()?l:void 0};let w;switch(e(n)){case"repository":w=await C.createRepositoryPool(e(k),A);break;case"organization":w=await C.createOrganizationPool(e(k),A);break;case"enterprise":w=await C.createEnterprisePool(e(k),A);break;default:throw new Error("Invalid entity level")}Y("submit",A)}catch(l){d(P,l instanceof Error?l.message:"Failed to create pool")}finally{d(G,!1)}}Gr(()=>{fr(),He()&&Be()}),Dr(),Ur(pr,{$$events:{close:()=>Y("close")},children:(l,A)=>{var w=st(),N=r(o(w),2),Le=o(N);{var kr=c=>{var y=Qr(),$=o(y),V=o($,!0);a($),a(y),v(()=>T(V,e(P))),b(c,y)};z(Le,c=>{e(P)&&c(kr)})}var ue=r(Le,2),qe=r(o(ue),2),be=o(qe),ce=r(be,2),Ge=r(ce,2);a(qe),a(ue);var Je=r(ue,2);{var _r=c=>{var y=dt(),$=Jr(y),V=r(o($),2),ve=o(V),pe=o(ve),Pr=o(pe);gr(),a(pe);var Rr=r(pe,2);{var Tr=t=>{var u=Xr();b(t,u)},zr=t=>{var u=Zr();v(()=>{e(k),W(()=>{e(n),e(S)})});var f=o(u),O=o(f);a(f),f.value=f.__value="";var R=r(f);Ae(R,1,()=>e(S),$e,(g,i)=>{var x=Yr(),U=o(x);{var Hr=H=>{var q=Ie();v(()=>T(q,`${e(i),p(()=>e(i).owner)??""}/${e(i),p(()=>e(i).name)??""} (${e(i),p(()=>e(i).endpoint?.name)??""})`)),b(H,q)},Br=H=>{var q=Ie();v(()=>T(q,`${e(i),p(()=>e(i).name)??""} (${e(i),p(()=>e(i).endpoint?.name)??""})`)),b(H,q)};z(U,H=>{e(n)==="repository"?H(Hr):H(Br,!1)})}a(x);var cr={};v(()=>{cr!==(cr=(e(i),p(()=>e(i).id)))&&(x.value=(x.__value=(e(i),p(()=>e(i).id)))??"")}),b(g,x)}),a(u),v(()=>T(O,`Select a ${e(n)??""}`)),Q(u,()=>e(k),g=>d(k,g)),b(t,u)};z(Rr,t=>{e(ee)?t(Tr):t(zr,!1)})}a(ve);var Ve=r(ve,2),Cr=r(o(Ve),2);{var Sr=t=>{var u=et();b(t,u)},jr=t=>{var u=tt();v(()=>{e(j),W(()=>{e(Z)})});var f=o(u);f.value=f.__value="";var O=r(f);Ae(O,1,()=>e(Z),$e,(R,g)=>{var i=rt(),x=o(i,!0);a(i);var U={};v(()=>{T(x,(e(g),p(()=>e(g).name))),U!==(U=(e(g),p(()=>e(g).name)))&&(i.value=(i.__value=(e(g),p(()=>e(g).name)))??"")}),b(R,i)}),a(u),Q(u,()=>e(j),R=>d(j,R)),b(t,u)};z(Cr,t=>{e(re)?t(Sr):t(jr,!1)})}a(Ve),a(V),a($);var me=r($,2),Ke=r(o(me),2),fe=o(Ke),Ue=r(o(fe),2);h(Ue),a(fe);var ye=r(fe,2),We=r(o(ye),2);h(We),a(ye);var xe=r(ye,2),he=r(o(xe),2);v(()=>{e(J),W(()=>{})});var ke=o(he);ke.value=ke.__value="linux";var Qe=r(ke);Qe.value=Qe.__value="windows",a(he),a(xe);var Xe=r(xe,2),_e=r(o(Xe),2);v(()=>{e(F),W(()=>{})});var we=o(_e);we.value=we.__value="amd64";var Ye=r(we);Ye.value=Ye.__value="arm64",a(_e),a(Xe),a(Ke),a(me);var Ee=r(me,2),Ze=r(o(Ee),2),Me=o(Ze),er=r(o(Me),2);h(er),a(Me);var Pe=r(Me,2),rr=r(o(Pe),2);h(rr),a(Pe);var tr=r(Pe,2),ar=r(o(tr),2);h(ar),a(tr),a(Ze),a(Ee);var or=r(Ee,2),Re=r(o(or),2),Te=o(Re),dr=r(o(Te),2);h(dr),a(Te);var ze=r(Te,2),ir=r(o(ze),2);h(ir),a(ze);var sr=r(ze,2),lr=r(o(sr),2);h(lr),a(sr),a(Re);var Ce=r(Re,2),nr=r(o(Ce),2),Se=o(nr),K=o(Se);h(K);var Ir=r(K,2);a(Se);var Ar=r(Se,2);{var $r=t=>{var u=ot();Ae(u,5,()=>e(_),$e,(f,O,R)=>{var g=at(),i=o(g),x=r(i);a(g),v(()=>{T(i,`${e(O)??""} `),Nr(x,"aria-label",`Remove tag ${e(O)}`)}),M("click",x,()=>yr(R)),b(f,g)}),a(u),b(t,u)};z(Ar,t=>{e(_),p(()=>e(_).length>0)&&t($r)})}a(nr),a(Ce);var je=r(Ce,2),Or=r(o(je),2);Wr(Or,{rows:4,placeholder:"{}",get value(){return e(L)},set value(t){d(L,t)},$$legacy:!0}),a(je);var ur=r(je,2),br=o(ur);h(br),gr(2),a(ur),a(or),v(t=>T(Pr,`${t??""} `),[()=>(e(n),p(()=>e(n).charAt(0).toUpperCase()+e(n).slice(1)))]),E(Ue,()=>e(B),t=>d(B,t)),E(We,()=>e(D),t=>d(D,t)),Q(he,()=>e(J),t=>d(J,t)),Q(_e,()=>e(F),t=>d(F,t)),E(er,()=>e(ae),t=>d(ae,t)),E(rr,()=>e(te),t=>d(te,t)),E(ar,()=>e(oe),t=>d(oe,t)),E(dr,()=>e(ie),t=>d(ie,t)),E(ir,()=>e(de),t=>d(de,t)),E(lr,()=>e(se),t=>d(se,t)),E(K,()=>e(I),t=>d(I,t)),M("keydown",K,xr),M("click",Ir,De),Vr(br,()=>e(le),t=>d(le,t)),b(c,y)};z(Je,c=>{e(n)&&c(_r)})}var Fe=r(Je,2),Ne=o(Fe),ge=r(Ne,2),wr=o(ge);{var Er=c=>{var y=it();b(c,y)},Mr=c=>{var y=Ie("Create Pool");b(c,y)};z(wr,c=>{e(G)?c(Er):c(Mr,!1)})}a(ge),a(Fe),a(N),a(w),v(()=>{Oe(be,1,`flex flex-col items-center justify-center p-4 border-2 rounded-lg transition-colors cursor-pointer ${e(n)==="repository"?"border-blue-500 bg-blue-50 dark:bg-blue-900":"border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"}`),Oe(ce,1,`flex flex-col items-center justify-center p-4 border-2 rounded-lg transition-colors cursor-pointer ${e(n)==="organization"?"border-blue-500 bg-blue-50 dark:bg-blue-900":"border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"}`),Oe(Ge,1,`flex flex-col items-center justify-center p-4 border-2 rounded-lg transition-colors cursor-pointer ${e(n)==="enterprise"?"border-blue-500 bg-blue-50 dark:bg-blue-900":"border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"}`),ge.disabled=e(G)||!e(n)||!e(k)||!e(j)||!e(B)||!e(D)}),M("click",be,()=>ne("repository")),M("click",ce,()=>ne("organization")),M("click",Ge,()=>ne("enterprise")),M("click",Ne,()=>Y("close")),M("submit",N,Kr(hr)),b(l,w)},$$slots:{default:!0}}),Fr()}export{yt as C};