@samyx/preview-stacks-ui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-eyfGLbs3.js +2 -0
  4. package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-eyfGLbs3.js.map +1 -0
  5. package/dist/assets/AxesTab-BRrPVCft.js +3 -0
  6. package/dist/assets/AxesTab-BRrPVCft.js.map +1 -0
  7. package/dist/assets/ConfigTab-BOCoIOPW.js +2 -0
  8. package/dist/assets/ConfigTab-BOCoIOPW.js.map +1 -0
  9. package/dist/assets/DangerTab-dMsIXX7F.js +2 -0
  10. package/dist/assets/DangerTab-dMsIXX7F.js.map +1 -0
  11. package/dist/assets/DashboardView-D27l8yqd.js +2 -0
  12. package/dist/assets/DashboardView-D27l8yqd.js.map +1 -0
  13. package/dist/assets/DeploymentDetailView-BjELdE9f.js +2 -0
  14. package/dist/assets/DeploymentDetailView-BjELdE9f.js.map +1 -0
  15. package/dist/assets/DeploymentsView-02Cg08fs.js +2 -0
  16. package/dist/assets/DeploymentsView-02Cg08fs.js.map +1 -0
  17. package/dist/assets/ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js +2 -0
  18. package/dist/assets/ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js.map +1 -0
  19. package/dist/assets/JobDetailView-Ck89gkKn.js +2 -0
  20. package/dist/assets/JobDetailView-Ck89gkKn.js.map +1 -0
  21. package/dist/assets/JobsView-0OjmomhQ.js +2 -0
  22. package/dist/assets/JobsView-0OjmomhQ.js.map +1 -0
  23. package/dist/assets/LogsTab-Bb2E4F-n.js +4 -0
  24. package/dist/assets/LogsTab-Bb2E4F-n.js.map +1 -0
  25. package/dist/assets/NotFoundView-3kRJpFYC.js +2 -0
  26. package/dist/assets/NotFoundView-3kRJpFYC.js.map +1 -0
  27. package/dist/assets/OverviewTab-BrElTGDU.js +2 -0
  28. package/dist/assets/OverviewTab-BrElTGDU.js.map +1 -0
  29. package/dist/assets/RequiresTab-DB0yEAmP.js +2 -0
  30. package/dist/assets/RequiresTab-DB0yEAmP.js.map +1 -0
  31. package/dist/assets/SettingsView-CZvKTBtQ.js +2 -0
  32. package/dist/assets/SettingsView-CZvKTBtQ.js.map +1 -0
  33. package/dist/assets/SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js +2 -0
  34. package/dist/assets/SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js.map +1 -0
  35. package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js +2 -0
  36. package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js.map +1 -0
  37. package/dist/assets/SubmitView-dblBZxuc.js +45 -0
  38. package/dist/assets/SubmitView-dblBZxuc.js.map +1 -0
  39. package/dist/assets/VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js +2 -0
  40. package/dist/assets/VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js.map +1 -0
  41. package/dist/assets/index--8-Mcu3f.js +29 -0
  42. package/dist/assets/index--8-Mcu3f.js.map +1 -0
  43. package/dist/assets/index-C_dmDOA0.css +1 -0
  44. package/dist/assets/useDeployment-ByQUNe_X.js +2 -0
  45. package/dist/assets/useDeployment-ByQUNe_X.js.map +1 -0
  46. package/dist/assets/useFormat-CNPlQbIu.js +2 -0
  47. package/dist/assets/useFormat-CNPlQbIu.js.map +1 -0
  48. package/dist/assets/useVars-VN9gYdR6.js +2 -0
  49. package/dist/assets/useVars-VN9gYdR6.js.map +1 -0
  50. package/dist/index.html +33 -0
  51. package/nginx.conf +52 -0
  52. package/package.json +32 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JobsView-0OjmomhQ.js","sources":["../../src/views/JobsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Job history. Polled by the shell already, so this view only filters what is in memory. */\nimport { computed, ref } from 'vue';\nimport { state } from '../composables/useControlPlane';\nimport { stamp, took } from '../composables/useFormat';\nimport StateBadge from '../components/StateBadge.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport type { JobState } from '../api/types';\n\nconst q = ref('');\nconst only = ref<'all' | JobState>('all');\n\nconst rows = computed(() => {\n const needle = q.value.trim().toLowerCase();\n return state.jobs.filter((j) => {\n if (only.value !== 'all' && j.state !== only.value) return false;\n if (!needle) return true;\n return j.stack.toLowerCase().includes(needle) || j.action.includes(needle);\n });\n});\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Jobs</h1>\n <div class=\"sub\">Most recent first, capped at the last 50.</div>\n </div>\n </div>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <div class=\"field\" style=\"flex: 1; min-width: 180px\">\n <label for=\"jq\" class=\"mute\" style=\"font-size: var(--t-xs)\">Search — press /</label>\n <input id=\"jq\" v-model=\"q\" data-search type=\"search\" placeholder=\"stack or action\" spellcheck=\"false\" />\n </div>\n <div class=\"field\">\n <label for=\"js\" class=\"mute\" style=\"font-size: var(--t-xs)\">State</label>\n <select id=\"js\" v-model=\"only\">\n <option value=\"all\">all</option>\n <option value=\"running\">running</option>\n <option value=\"ok\">ok</option>\n <option value=\"leaked\">leaked</option>\n <option value=\"failed\">failed</option>\n </select>\n </div>\n </div>\n\n <p class=\"hint\" style=\"margin: 0 0 var(--s3)\">\n Held in memory. A server restart clears the history — a job record is the transcript of an\n attempt, not the truth about what exists. That truth lives in docker and in each axis's own\n probe.\n </p>\n\n <p v-if=\"state.jobsError\" class=\"s-failed\">{{ state.jobsError }}</p>\n <SkeletonList v-else-if=\"!state.jobsLoaded\" :rows=\"5\" />\n\n <table v-else class=\"cards\">\n <thead>\n <tr>\n <th>state</th>\n <th>action</th>\n <th>stack</th>\n <th>started</th>\n <th>took</th>\n <th>steps</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(j, i) in rows\" :key=\"j.id\" :style=\"{ '--i': i }\">\n <td data-label=\"state\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(j.id)}`\">\n <StateBadge :state=\"j.state\" />\n </RouterLink>\n </td>\n <td class=\"mono\" data-label=\"action\">{{ j.action }}</td>\n <td class=\"name\" data-label=\"stack\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(j.id)}`\">{{ j.stack }}</RouterLink>\n </td>\n <td class=\"mono dim nowrap\" data-label=\"started\">{{ stamp(j.startedAt) }}</td>\n <td class=\"mono dim nowrap\" data-label=\"took\">{{ took(j.startedAt, j.endedAt) }}</td>\n <td class=\"dim\" data-label=\"steps\">{{ j.outcome ? j.outcome.steps.length : '—' }}</td>\n </tr>\n <tr v-if=\"!rows.length\">\n <td colspan=\"6\" class=\"mute\">\n <template v-if=\"state.jobs.length\">Nothing matches this filter.</template>\n <template v-else>No jobs recorded.</template>\n </td>\n </tr>\n </tbody>\n </table>\n </section>\n </div>\n</template>\n"],"names":["q","ref","only","rows","computed","needle","state","j","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_hoisted_3","$event","_hoisted_4","_unref","_openBlock","_hoisted_5","_toDisplayString","_hoisted_6","_hoisted_7","_Fragment","_renderList","i","_hoisted_8","_createVNode","_component_RouterLink","StateBadge","_hoisted_9","_hoisted_10","_createTextVNode","_hoisted_11","_hoisted_12","took","_hoisted_13","_hoisted_14","_hoisted_15","_createBlock","SkeletonList"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import{d as w,c as t,a as s,x as g,z as _,b as h,e as l,t as y,f as p,F as C,y as F,j as v,k as L,G as T,p as U,m as V,o}from"./index--8-Mcu3f.js";import{d as i}from"./useDeployment-ByQUNe_X.js";import"./useVars-VN9gYdR6.js";const z={class:"panel"},M={class:"phead"},N={class:"field"},j=["disabled"],B={key:0,class:"progress","aria-hidden":"true"},E={key:0,class:"mute"},R={key:1,class:"mute"},S={key:0,class:"banner failed"},$={key:1,class:"banner warn"},A={key:2,class:"field",style:{"margin-top":"var(--s3)"}},D={key:3,class:"log"},G={key:4,class:"mute",style:{"margin-top":"var(--s3)"}},I={key:5,class:"hint"},K=w({__name:"LogsTab",setup(q){const c=v(200),m=v(""),a=v(null),f=v(""),r=v(!1);async function b(){var n;if(!((n=i.detail)!=null&&n.compose))return;const d=i.id;r.value=!0,f.value="";const e=await L.get(`/api/deployments/${encodeURIComponent(d)}/logs${T(i.vars,{tail:c.value})}`);if(r.value=!1,i.id===d){if(!e.ok){a.value=null,f.value=U(e,"GET logs");return}a.value={...e.body,ok:e.body.ok!==!1,text:e.body.text??""}}}const k=V(()=>{var u;const d=((u=a.value)==null?void 0:u.text)??"",e=m.value.trim().toLowerCase();if(!e)return d;const n=d.split(`
2
+ `).filter(x=>x.toLowerCase().includes(e));return n.length?n.join(`
3
+ `):`(no line matches "${m.value}")`});return(d,e)=>{var n;return o(),t("section",z,[s("div",M,[e[4]||(e[4]=s("h2",{class:"section"},"Compose logs",-1)),e[5]||(e[5]=s("span",{class:"grow"},null,-1)),s("div",N,[e[3]||(e[3]=s("label",{for:"tail",class:"mute",style:{"font-size":"var(--t-xs)"}},"tail",-1)),g(s("select",{id:"tail","onUpdate:modelValue":e[0]||(e[0]=u=>c.value=u)},[...e[2]||(e[2]=[s("option",{value:50},"50",-1),s("option",{value:200},"200",-1),s("option",{value:500},"500",-1),s("option",{value:1e3},"1000",-1),s("option",{value:2e3},"2000",-1)])],512),[[_,c.value,void 0,{number:!0}]])]),s("button",{class:"primary",style:{"align-self":"end"},disabled:r.value||!((n=h(i).detail)!=null&&n.compose),onClick:b},[l(y(r.value?"reading…":"Fetch")+" ",1),r.value?(o(),t("span",B)):p("",!0)],8,j)]),h(i).detail?h(i).detail.compose?(o(),t(C,{key:2},[e[12]||(e[12]=s("p",{class:"hint",style:{margin:"0 0 var(--s3)"}},[l(" Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer the host. "),s("code",null,"tail"),l(" is clamped to 1–2000 by the server. ")],-1)),f.value?(o(),t("div",S,y(f.value),1)):a.value&&!a.value.ok?(o(),t("div",$,[...e[7]||(e[7]=[s("b",null,"compose exited non-zero.",-1),s("p",null," Most often the project does not exist yet (nothing has been brought up), or a profile is missing. Any output it did produce is below. ",-1)])])):p("",!0),a.value?(o(),t("div",A,[e[8]||(e[8]=s("label",{for:"lf",class:"mute",style:{"font-size":"var(--t-xs)"}},"Search in these lines",-1)),g(s("input",{id:"lf","onUpdate:modelValue":e[1]||(e[1]=u=>m.value=u),"data-search":"",type:"search",placeholder:"substring",spellcheck:"false"},null,512),[[F,m.value]])])):p("",!0),a.value?(o(),t("pre",D,y(k.value||"(no output)"),1)):r.value?p("",!0):(o(),t("p",G,[e[9]||(e[9]=l(" Press ",-1)),e[10]||(e[10]=s("b",null,"Fetch",-1)),l(" to read the last "+y(c.value)+" lines. ",1)])),a.value?(o(),t("p",I,[...e[11]||(e[11]=[l(" Redacted on the host before sending: credentials inside URLs, ",-1),s("code",null,"NAME=value",-1),l(" pairs whose name reads as a secret, and this server's own token. ",-1)])])):p("",!0)],64)):(o(),t("p",R,[...e[6]||(e[6]=[l(" This spec has no ",-1),s("code",null,"compose:",-1),l(" section, so there are no compose logs to read. Its axes may still have created resources — check those at their own source. ",-1)])])):(o(),t("p",E,"Unavailable until the spec resolves."))])}}});export{K as default};
4
+ //# sourceMappingURL=LogsTab-Bb2E4F-n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogsTab-Bb2E4F-n.js","sources":["../../src/views/tabs/LogsTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Compose logs, fetched ON DEMAND.\n *\n * Never polled: an auto-refresh against a chatty stack would hammer the host through\n * `docker compose logs` — the same host that is running everyone else's previews. The tail is\n * clamped to 1–2000 by the server for the same reason.\n */\nimport { computed, ref } from 'vue';\nimport { api, problem, query } from '../../api/client';\nimport type { LogsResponse } from '../../api/types';\nimport { dep } from '../../composables/useDeployment';\n\nconst tail = ref(200);\nconst filter = ref('');\nconst logs = ref<LogsResponse | null>(null);\nconst error = ref('');\nconst loading = ref(false);\n\nasync function fetchLogs(): Promise<void> {\n if (!dep.detail?.compose) return;\n const id = dep.id;\n loading.value = true;\n error.value = '';\n const r = await api.get<LogsResponse>(\n `/api/deployments/${encodeURIComponent(id)}/logs${query(dep.vars, { tail: tail.value })}`,\n );\n loading.value = false;\n if (dep.id !== id) return; // navigated away mid-flight\n if (!r.ok) {\n logs.value = null;\n error.value = problem(r, 'GET logs');\n return;\n }\n logs.value = { ...r.body, ok: r.body.ok !== false, text: r.body.text ?? '' };\n}\n\n/** Search-in-logs: filter to matching lines rather than highlighting inside a wall of text. */\nconst shown = computed(() => {\n const text = logs.value?.text ?? '';\n const needle = filter.value.trim().toLowerCase();\n if (!needle) return text;\n const lines = text.split('\\n').filter((l) => l.toLowerCase().includes(needle));\n return lines.length ? lines.join('\\n') : `(no line matches \"${filter.value}\")`;\n});\n</script>\n\n<template>\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Compose logs</h2>\n <span class=\"grow\" />\n <div class=\"field\">\n <label for=\"tail\" class=\"mute\" style=\"font-size: var(--t-xs)\">tail</label>\n <select id=\"tail\" v-model.number=\"tail\">\n <option :value=\"50\">50</option>\n <option :value=\"200\">200</option>\n <option :value=\"500\">500</option>\n <option :value=\"1000\">1000</option>\n <option :value=\"2000\">2000</option>\n </select>\n </div>\n <button\n class=\"primary\"\n style=\"align-self: end\"\n :disabled=\"loading || !dep.detail?.compose\"\n @click=\"fetchLogs\"\n >\n {{ loading ? 'reading…' : 'Fetch' }}\n <span v-if=\"loading\" class=\"progress\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <p v-if=\"!dep.detail\" class=\"mute\">Unavailable until the spec resolves.</p>\n <!-- Guard on the field rather than calling and interpreting the failure after the fact. -->\n <p v-else-if=\"!dep.detail.compose\" class=\"mute\">\n This spec has no <code>compose:</code> section, so there are no compose logs to read. Its axes\n may still have created resources — check those at their own source.\n </p>\n\n <template v-else>\n <p class=\"hint\" style=\"margin: 0 0 var(--s3)\">\n Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer the\n host. <code>tail</code> is clamped to 1–2000 by the server.\n </p>\n\n <div v-if=\"error\" class=\"banner failed\">{{ error }}</div>\n\n <!--\n `ok: false` means compose exited non-zero. Usually \"no such project\" because the stack was\n never brought up — not a server fault, so it is a note rather than an error.\n -->\n <div v-else-if=\"logs && !logs.ok\" class=\"banner warn\">\n <b>compose exited non-zero.</b>\n <p>\n Most often the project does not exist yet (nothing has been brought up), or a profile is\n missing. Any output it did produce is below.\n </p>\n </div>\n\n <div v-if=\"logs\" class=\"field\" style=\"margin-top: var(--s3)\">\n <label for=\"lf\" class=\"mute\" style=\"font-size: var(--t-xs)\">Search in these lines</label>\n <input id=\"lf\" v-model=\"filter\" data-search type=\"search\" placeholder=\"substring\" spellcheck=\"false\" />\n </div>\n\n <pre v-if=\"logs\" class=\"log\">{{ shown || '(no output)' }}</pre>\n <p v-else-if=\"!loading\" class=\"mute\" style=\"margin-top: var(--s3)\">\n Press <b>Fetch</b> to read the last {{ tail }} lines.\n </p>\n\n <p v-if=\"logs\" class=\"hint\">\n Redacted on the host before sending: credentials inside URLs, <code>NAME=value</code> pairs\n whose name reads as a secret, and this server's own token.\n </p>\n </template>\n </section>\n</template>\n"],"names":["tail","ref","filter","logs","error","loading","fetchLogs","_a","dep","id","r","api","query","problem","shown","computed","text","needle","lines","l","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_hoisted_3","$event","_unref","_createTextVNode","_toDisplayString","_hoisted_5","_Fragment","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_7","_hoisted_6"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{d as i,c as d,a as o,e as s,t as r,b as u,h as n,w as l,R as p,n as m,o as f}from"./index--8-Mcu3f.js";const b={class:"page-head"},y={class:"sub"},g={class:"panel"},v={class:"row",style:{"margin-top":"12px",gap:"8px"}},x=i({__name:"NotFoundView",setup(w){const a=p();return(_,t)=>{const e=m("RouterLink");return f(),d("div",null,[o("div",b,[o("div",null,[t[2]||(t[2]=o("h1",null,"Not found",-1)),o("div",y,[t[0]||(t[0]=s("Nothing is routed at ",-1)),o("code",null,r(u(a).fullPath),1),t[1]||(t[1]=s(".",-1))])])]),o("section",g,[t[6]||(t[6]=o("p",{class:"mute"}," If you followed a link to a deployment that has since been forgotten, it will not resolve — the control plane only knows deployments that are still in its registry. ",-1)),o("div",v,[n(e,{class:"btn",to:"/"},{default:l(()=>[...t[3]||(t[3]=[s("Dashboard",-1)])]),_:1}),n(e,{class:"btn",to:"/deployments"},{default:l(()=>[...t[4]||(t[4]=[s("Deployments",-1)])]),_:1}),n(e,{class:"btn",to:"/jobs"},{default:l(()=>[...t[5]||(t[5]=[s("Jobs",-1)])]),_:1})])])])}}});export{x as default};
2
+ //# sourceMappingURL=NotFoundView-3kRJpFYC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotFoundView-3kRJpFYC.js","sources":["../../src/views/NotFoundView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Catch-all route.\n *\n * Exists so a mistyped or stale deep link renders something with a way out, rather than the blank\n * screen a SPA gives you by default when no route matches.\n */\nimport { useRoute } from 'vue-router';\n\nconst route = useRoute();\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Not found</h1>\n <div class=\"sub\">Nothing is routed at <code>{{ route.fullPath }}</code>.</div>\n </div>\n </div>\n\n <section class=\"panel\">\n <p class=\"mute\">\n If you followed a link to a deployment that has since been forgotten, it will not resolve —\n the control plane only knows deployments that are still in its registry.\n </p>\n <div class=\"row\" style=\"margin-top: 12px; gap: 8px\">\n <RouterLink class=\"btn\" to=\"/\">Dashboard</RouterLink>\n <RouterLink class=\"btn\" to=\"/deployments\">Deployments</RouterLink>\n <RouterLink class=\"btn\" to=\"/jobs\">Jobs</RouterLink>\n </div>\n </section>\n </div>\n</template>\n"],"names":["route","useRoute","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_hoisted_2","_toDisplayString","_unref","_hoisted_3","_hoisted_4","_createVNode","_component_RouterLink"],"mappings":"sQASA,MAAMA,EAAQC,EAAA,mDAIZC,EAmBM,MAAA,KAAA,CAlBJC,EAKM,MALNC,EAKM,CAJJD,EAGM,MAAA,KAAA,CAFJE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAAkB,UAAd,YAAS,EAAA,GACbA,EAA8E,MAA9EG,EAA8E,eAA7D,wBAAqB,EAAA,GAAAH,EAAiC,OAAA,KAAAI,EAAxBC,EAAAR,CAAA,EAAM,QAAQ,EAAA,CAAA,gBAAU,IAAC,EAAA,EAAA,OAI5EG,EAUU,UAVVM,EAUU,CATRJ,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAGI,IAAA,CAHD,MAAM,MAAA,EAAO,yKAGhB,EAAA,GACAA,EAIM,MAJNO,EAIM,CAHJC,EAAqDC,EAAA,CAAzC,MAAM,MAAM,GAAG,GAAA,aAAI,IAAS,CAAA,GAAAP,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAAT,YAAS,EAAA,CAAA,WACxCM,EAAkEC,EAAA,CAAtD,MAAM,MAAM,GAAG,cAAA,aAAe,IAAW,CAAA,GAAAP,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAAX,cAAW,EAAA,CAAA,WACrDM,EAAoDC,EAAA,CAAxC,MAAM,MAAM,GAAG,OAAA,aAAQ,IAAI,CAAA,GAAAP,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAAJ,OAAI,EAAA,CAAA"}
@@ -0,0 +1,2 @@
1
+ import{d as o,r as c,i as f}from"./useDeployment-ByQUNe_X.js";import{s as u}from"./useFormat-CNPlQbIu.js";import{_ as g}from"./SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js";import{d as h,c as n,a as s,b as t,g as k,F as p,t as l,v as x,e as i,f as r,r as v,w,m as b,n as C,o as a}from"./index--8-Mcu3f.js";import"./useVars-VN9gYdR6.js";const B={class:"panel"},N={key:1,class:"mute"},R={class:"kvlist"},z={class:"v mono"},A={class:"v"},L={class:"mute",style:{"margin-left":"8px","font-size":"var(--t-sm)"}},S={key:0},T={class:"v mono"},V={class:"v mono"},$={key:1},q={class:"v"},F={key:0,class:"mute"},U={key:2},j={class:"v mono"},D={key:0,class:"mute"},E={class:"v"},I={class:"v"},K={class:"v mono"},O={class:"v mono"},X=h({__name:"OverviewTab",setup(G){const y=b(()=>f.value?"a host singleton previews borrow — no axes, and down is guarded":"one tenant, created and destroyed constantly, expected to leave nothing behind");return(H,e)=>{var m;const _=C("RouterLink");return a(),n("section",B,[e[13]||(e[13]=s("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Resolved spec",-1)),!t(o).detail&&!t(o).error?(a(),k(g,{key:0,rows:5})):t(o).detail?(a(),n(p,{key:2},[s("ul",R,[s("li",null,[e[0]||(e[0]=s("span",{class:"k"},"stack",-1)),s("span",z,l(t(o).detail.stack||"—"),1)]),s("li",null,[e[1]||(e[1]=s("span",{class:"k"},"kind",-1)),s("span",A,[s("span",{class:x(["badge",t(o).detail.kind])},l(t(o).detail.kind),3),s("span",L,l(y.value),1)])]),(m=t(c))!=null&&m.specName?(a(),n("li",S,[e[3]||(e[3]=s("span",{class:"k"},"spec",-1)),s("span",T,[i(l(t(c).specName)+" ",1),e[2]||(e[2]=s("span",{class:"mute",style:{"font-size":"var(--t-sm)"}}," — a stored spec, shared with any other deployment that references it ",-1))])])):r("",!0),s("li",null,[e[4]||(e[4]=s("span",{class:"k"},"compose file",-1)),s("span",V,l(t(o).detail.compose?t(o).detail.compose.file:"— (no compose section)"),1)]),t(o).detail.compose?(a(),n("li",$,[e[5]||(e[5]=s("span",{class:"k"},"profiles",-1)),s("span",q,[(a(!0),n(p,null,v(t(o).detail.compose.profiles,d=>(a(),n("span",{key:d,class:"badge",style:{"margin-right":"4px"}},l(d),1))),128)),t(o).detail.compose.profiles.length?r("",!0):(a(),n("span",F," none — a bare up starts every service "))])])):r("",!0),t(o).detail.compose?(a(),n("li",U,[e[6]||(e[6]=s("span",{class:"k"},"overlays",-1)),s("span",j,[(a(!0),n(p,null,v(t(o).detail.compose.overlays,d=>(a(),n("span",{key:d,style:{"margin-right":"8px"}},l(d),1))),128)),t(o).detail.compose.overlays.length?r("",!0):(a(),n("span",D,"none"))])])):r("",!0),s("li",null,[e[8]||(e[8]=s("span",{class:"k"},"axes",-1)),s("span",E,[i(l(t(o).detail.axes.length)+" ",1),t(o).detail.axes.length?(a(),k(_,{key:0,to:`/deployments/${encodeURIComponent(t(o).id)}/axes`},{default:w(()=>[...e[7]||(e[7]=[i(" view → ",-1)])]),_:1},8,["to"])):r("",!0)])]),s("li",null,[e[9]||(e[9]=s("span",{class:"k"},"requires",-1)),s("span",I,l(t(o).detail.requires.length),1)]),s("li",null,[e[10]||(e[10]=s("span",{class:"k"},"created",-1)),s("span",K,l(t(u)(t(o).detail.createdAt)),1)]),s("li",null,[e[11]||(e[11]=s("span",{class:"k"},"updated",-1)),s("span",O,l(t(u)(t(o).detail.updatedAt)),1)])]),e[12]||(e[12]=s("p",{class:"hint"},[s("code",null,"id"),i(" is the registry id — a directory name, and what every route addresses. "),s("code",null,"stack"),i(" is what the spec resolved to: the compose project name, the hostname label, and "),s("code",null,"$STACK"),i(" inside every hook. ")],-1))],64)):(a(),n("p",N,"Unavailable until the spec resolves."))])}}});export{X as default};
2
+ //# sourceMappingURL=OverviewTab-BrElTGDU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverviewTab-BrElTGDU.js","sources":["../../src/views/tabs/OverviewTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** The resolved spec, summarised. Read-only — everything that acts lives on Danger. */\nimport { computed } from 'vue';\nimport { dep, isShared, row } from '../../composables/useDeployment';\nimport { stamp } from '../../composables/useFormat';\nimport SkeletonList from '../../components/SkeletonList.vue';\n\nconst kindBlurb = computed(() =>\n isShared.value\n ? 'a host singleton previews borrow — no axes, and down is guarded'\n : 'one tenant, created and destroyed constantly, expected to leave nothing behind',\n);\n</script>\n\n<template>\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">Resolved spec</h2>\n\n <SkeletonList v-if=\"!dep.detail && !dep.error\" :rows=\"5\" />\n <p v-else-if=\"!dep.detail\" class=\"mute\">Unavailable until the spec resolves.</p>\n\n <template v-else>\n <ul class=\"kvlist\">\n <li>\n <span class=\"k\">stack</span>\n <span class=\"v mono\">{{ dep.detail.stack || '—' }}</span>\n </li>\n <li>\n <span class=\"k\">kind</span>\n <span class=\"v\">\n <span class=\"badge\" :class=\"dep.detail.kind\">{{ dep.detail.kind }}</span>\n <span class=\"mute\" style=\"margin-left: 8px; font-size: var(--t-sm)\">{{ kindBlurb }}</span>\n </span>\n </li>\n <li v-if=\"row?.specName\">\n <span class=\"k\">spec</span>\n <span class=\"v mono\">\n {{ row.specName }}\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n — a stored spec, shared with any other deployment that references it\n </span>\n </span>\n </li>\n <li>\n <span class=\"k\">compose file</span>\n <span class=\"v mono\">\n {{ dep.detail.compose ? dep.detail.compose.file : '— (no compose section)' }}\n </span>\n </li>\n <li v-if=\"dep.detail.compose\">\n <span class=\"k\">profiles</span>\n <span class=\"v\">\n <span v-for=\"p in dep.detail.compose.profiles\" :key=\"p\" class=\"badge\" style=\"margin-right: 4px\">\n {{ p }}\n </span>\n <span v-if=\"!dep.detail.compose.profiles.length\" class=\"mute\">\n none — a bare up starts every service\n </span>\n </span>\n </li>\n <li v-if=\"dep.detail.compose\">\n <span class=\"k\">overlays</span>\n <span class=\"v mono\">\n <span v-for=\"o in dep.detail.compose.overlays\" :key=\"o\" style=\"margin-right: 8px\">{{ o }}</span>\n <span v-if=\"!dep.detail.compose.overlays.length\" class=\"mute\">none</span>\n </span>\n </li>\n <li>\n <span class=\"k\">axes</span>\n <span class=\"v\">\n {{ dep.detail.axes.length }}\n <RouterLink v-if=\"dep.detail.axes.length\" :to=\"`/deployments/${encodeURIComponent(dep.id)}/axes`\">\n view →\n </RouterLink>\n </span>\n </li>\n <li>\n <span class=\"k\">requires</span>\n <span class=\"v\">{{ dep.detail.requires.length }}</span>\n </li>\n <li><span class=\"k\">created</span><span class=\"v mono\">{{ stamp(dep.detail.createdAt) }}</span></li>\n <li><span class=\"k\">updated</span><span class=\"v mono\">{{ stamp(dep.detail.updatedAt) }}</span></li>\n </ul>\n\n <p class=\"hint\">\n <code>id</code> is the registry id — a directory name, and what every route addresses.\n <code>stack</code> is what the spec resolved to: the compose project name, the hostname\n label, and <code>$STACK</code> inside every hook.\n </p>\n </template>\n </section>\n</template>\n"],"names":["kindBlurb","computed","isShared","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_unref","dep","_createBlock","SkeletonList","_Fragment","_hoisted_3","_cache","_hoisted_4","_toDisplayString","_hoisted_5","_hoisted_6","row","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_renderList","p","_hoisted_12","_hoisted_13","_hoisted_14","o","_hoisted_15","_hoisted_16","_createTextVNode","_component_RouterLink","_hoisted_17","_hoisted_18","stamp","_hoisted_19","_hoisted_2"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{d as o}from"./useDeployment-ByQUNe_X.js";import{d as m,c as s,a as e,e as n,b as l,F as i,r as c,f as h,o as a,q as p,t as d}from"./index--8-Mcu3f.js";import"./useVars-VN9gYdR6.js";const y={class:"panel"},f={key:0,class:"mute"},_={class:"cards"},b={class:"stagger"},g={class:"name","data-label":"requirement"},k={class:"dim","data-label":"hint"},q={key:0},T=m({__name:"RequiresTab",setup(v){return(x,t)=>(a(),s("section",y,[t[3]||(t[3]=e("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},[n(" Requires "),e("span",{class:"mute"},"(checked before anything is created)")],-1)),l(o).detail?(a(),s(i,{key:1},[t[2]||(t[2]=e("p",{class:"hint",style:{margin:"0 0 var(--s3)"}},[n(" Preconditions run first, in order, and "),e("code",null,"up"),n(" stops at the first one that fails — by name, before a single resource exists. The "),e("code",null,"assert"),n(" command itself stays on the host; only the name and its authored hint are sent here. ")],-1)),e("table",_,[t[1]||(t[1]=e("thead",null,[e("tr",null,[e("th",null,"requirement"),e("th",null,"hint shown on failure")])],-1)),e("tbody",b,[(a(!0),s(i,null,c(l(o).detail.requires,(r,u)=>(a(),s("tr",{key:r.name,style:p({"--i":u})},[e("td",g,d(r.name),1),e("td",k,d(r.hint||"— (no hint authored)"),1)],4))),128)),l(o).detail.requires.length?h("",!0):(a(),s("tr",q,[...t[0]||(t[0]=[e("td",{colspan:"2",class:"mute"}," None declared — an isolated deployment that borrows shared infrastructure usually wants at least one. ",-1)])]))])])],64)):(a(),s("p",f,"Unavailable until the spec resolves."))]))}});export{T as default};
2
+ //# sourceMappingURL=RequiresTab-DB0yEAmP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequiresTab-DB0yEAmP.js","sources":["../../src/views/tabs/RequiresTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Preconditions: checked before anything is created, so a failure names a thing, not a stack trace. */\nimport { dep } from '../../composables/useDeployment';\n</script>\n\n<template>\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">\n Requires <span class=\"mute\">(checked before anything is created)</span>\n </h2>\n\n <p v-if=\"!dep.detail\" class=\"mute\">Unavailable until the spec resolves.</p>\n <template v-else>\n <p class=\"hint\" style=\"margin: 0 0 var(--s3)\">\n Preconditions run first, in order, and <code>up</code> stops at the first one that fails —\n by name, before a single resource exists. The <code>assert</code> command itself stays on\n the host; only the name and its authored hint are sent here.\n </p>\n\n <table class=\"cards\">\n <thead>\n <tr>\n <th>requirement</th>\n <th>hint shown on failure</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(r, i) in dep.detail.requires\" :key=\"r.name\" :style=\"{ '--i': i }\">\n <td class=\"name\" data-label=\"requirement\">{{ r.name }}</td>\n <td class=\"dim\" data-label=\"hint\">{{ r.hint || '— (no hint authored)' }}</td>\n </tr>\n <tr v-if=\"!dep.detail.requires.length\">\n <td colspan=\"2\" class=\"mute\">\n None declared — an isolated deployment that borrows shared infrastructure usually\n wants at least one.\n </td>\n </tr>\n </tbody>\n </table>\n </template>\n </section>\n</template>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_unref","dep","_Fragment","_hoisted_3","_hoisted_4","_renderList","i","_hoisted_5","_toDisplayString","_hoisted_6","_hoisted_7","_cache","_hoisted_2"],"mappings":"4ZAMEA,EAAA,EAAAC,EAkCU,UAlCVC,EAkCU,aAjCRC,EAEK,KAAA,CAFD,MAAM,UAAU,MAAA,CAAA,gBAAA,WAAA,CAAA,KAAiC,YAC1C,EAAAA,EAA8D,OAAA,CAAxD,MAAM,MAAA,EAAO,sCAAoC,CAAA,OAGxDC,EAAAC,CAAA,EAAI,YACdJ,EA2BWK,EAAA,CAAA,IAAA,GAAA,aA1BTH,EAII,IAAA,CAJD,MAAM,OAAO,MAAA,CAAA,OAAA,eAAA,CAAA,KAA8B,0CACL,EAAAA,EAAe,YAAT,IAAE,IAAO,qFACR,EAAAA,EAAmB,YAAb,QAAM,IAAO,wFAEnE,CAAA,OAEAA,EAmBQ,QAnBRI,EAmBQ,aAlBNJ,EAKQ,QAAA,KAAA,CAJNA,EAGK,KAAA,KAAA,CAFHA,EAAoB,UAAhB,aAAW,EACfA,EAA8B,UAA1B,uBAAqB,CAAA,SAG7BA,EAWQ,QAXRK,EAWQ,QAVNP,EAGKK,EAAA,KAAAG,EAHgBL,KAAI,OAAO,SAAQ,CAA5B,EAAGM,SAAfT,EAGK,KAAA,CAHsC,IAAK,EAAE,KAAO,eAAgBS,EAAC,CAAA,GACxEP,EAA2D,KAA3DQ,EAA2DC,EAAd,EAAE,IAAI,EAAA,CAAA,EACnDT,EAA6E,KAA7EU,EAA6ED,EAAxC,EAAE,MAAI,sBAAA,EAAA,CAAA,CAAA,aAElCR,EAAAC,CAAA,EAAI,OAAO,SAAS,qBAA/BJ,EAKK,KAAAa,EAAA,CAAA,GAAAC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAJHZ,EAGK,KAAA,CAHD,QAAQ,IAAI,MAAM,MAAA,EAAO,0GAG7B,EAAA,CAAA,qBAxBRF,EAA2E,IAA3Ee,EAAmC,sCAAoC"}
@@ -0,0 +1,2 @@
1
+ import{d as y,c as r,a as t,x as d,y as h,b as o,K as s,e as n,F as b,f as v,t as u,s as l,z as f,j as w,m as E,Q as x,H as T,o as p}from"./index--8-Mcu3f.js";const S={class:"panel"},c={class:"field"},B={class:"field"},C={class:"mute hint"},U={class:"row",style:{gap:"8px","margin-top":"4px"}},V=["disabled"],F={key:0,class:"mute"},A={key:1,class:"bad"},D={class:"panel"},L={class:"field"},N={class:"field"},P=y({__name:"SettingsView",setup(R){const a=w(!1),k=E(()=>{var m;return((m=l.health)==null?void 0:m.authEnforced)??null});async function g(){a.value=!0;try{await x(),T(l.healthError?"error":"ok",l.healthError||"Control plane reachable")}finally{a.value=!1}}return(m,e)=>(p(),r("div",null,[e[21]||(e[21]=t("div",{class:"page-head"},[t("div",null,[t("h1",null,"Settings"),t("div",{class:"sub"},"Stored in this browser only. Nothing here is sent to the server.")])],-1)),t("section",S,[e[14]||(e[14]=t("h2",{class:"phead-title"},"Connection",-1)),t("div",c,[e[4]||(e[4]=t("label",{for:"apiBase"},"API base URL",-1)),d(t("input",{id:"apiBase","onUpdate:modelValue":e[0]||(e[0]=i=>o(s).apiBase=i),type:"url",spellcheck:"false",placeholder:"(same origin)"},null,512),[[h,o(s).apiBase,void 0,{trim:!0}]]),e[5]||(e[5]=t("div",{class:"mute hint"},[n(" Leave empty for same-origin, which is how this UI is served in the control stack — nginx proxies "),t("code",null,"/api/"),n(" to the pstack container, so the browser never makes a cross-origin request and no CORS configuration exists to get wrong. Set an absolute URL only for a dev session against a tunnelled host. ")],-1))]),t("div",B,[e[13]||(e[13]=t("label",{for:"token"},"Bearer token",-1)),d(t("input",{id:"token","onUpdate:modelValue":e[1]||(e[1]=i=>o(s).token=i),type:"password",autocomplete:"off",spellcheck:"false",placeholder:"PSTACK_TOKEN"},null,512),[[h,o(s).token,void 0,{trim:!0}]]),t("div",C,[e[10]||(e[10]=n(" Attached to ",-1)),e[11]||(e[11]=t("strong",null,"POST, PUT and DELETE",-1)),e[12]||(e[12]=n(" only — the routes that change or destroy something. Reads are unauthenticated, so the dashboard works before you paste one. ",-1)),k.value===!1?(p(),r(b,{key:0},[e[6]||(e[6]=t("br",null,null,-1)),e[7]||(e[7]=n(" This server reports ",-1)),e[8]||(e[8]=t("code",null,"authEnforced: false",-1)),e[9]||(e[9]=n(": it is bound to loopback and accepts mutations without a token. A token here is harmless but unnecessary. ",-1))],64)):v("",!0)])]),t("div",U,[t("button",{class:"btn",disabled:a.value,onClick:g},u(a.value?"Checking…":"Test connection"),9,V),o(l).health?(p(),r("span",F," pstack "+u(o(l).health.version)+" · registry "+u(o(l).health.dataDir),1)):o(l).healthError?(p(),r("span",A,u(o(l).healthError),1)):v("",!0)])]),t("section",D,[e[20]||(e[20]=t("h2",{class:"phead-title"},"Appearance",-1)),t("div",L,[e[16]||(e[16]=t("label",{for:"theme"},"Theme",-1)),d(t("select",{id:"theme","onUpdate:modelValue":e[2]||(e[2]=i=>o(s).theme=i)},[...e[15]||(e[15]=[t("option",{value:"system"},"Follow the system",-1),t("option",{value:"dark"},"Dark",-1),t("option",{value:"light"},"Light",-1)])],512),[[f,o(s).theme]])]),t("div",N,[e[18]||(e[18]=t("label",{for:"motion"},"Motion",-1)),d(t("select",{id:"motion","onUpdate:modelValue":e[3]||(e[3]=i=>o(s).motion=i)},[...e[17]||(e[17]=[t("option",{value:"system"},"Follow the system",-1),t("option",{value:"full"},"Full animation",-1),t("option",{value:"none"},"Reduce motion",-1)])],512),[[f,o(s).motion]]),e[19]||(e[19]=t("div",{class:"mute hint"},[n(" “Follow the system” honours "),t("code",null,"prefers-reduced-motion"),n(". The explicit options override it in both directions, because an OS-level preference is not always the right answer for one app on one screen. ")],-1))])])]))}});export{P as default};
2
+ //# sourceMappingURL=SettingsView-CZvKTBtQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsView-CZvKTBtQ.js","sources":["../../src/views/SettingsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Settings: where the API is, how to authenticate to it, and how the app looks.\n *\n * Everything here is client-side and persisted to localStorage by `useSettings` — nothing is sent\n * to the server, and there is nothing to save. Bindings write through immediately.\n */\nimport { computed, ref } from 'vue';\nimport { settings } from '../composables/useSettings';\nimport { state, loadHealth } from '../composables/useControlPlane';\nimport { toast } from '../composables/useToasts';\n\nconst checking = ref(false);\n\n/**\n * The server tells us whether it enforces auth. When it does not, it is bound to loopback and a\n * token would be pointless — worth saying, so nobody hunts for one that was never issued.\n */\nconst authEnforced = computed(() => state.health?.authEnforced ?? null);\n\nasync function recheck(): Promise<void> {\n checking.value = true;\n try {\n await loadHealth();\n toast(state.healthError ? 'error' : 'ok', state.healthError || 'Control plane reachable');\n } finally {\n checking.value = false;\n }\n}\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Settings</h1>\n <div class=\"sub\">Stored in this browser only. Nothing here is sent to the server.</div>\n </div>\n </div>\n\n <section class=\"panel\">\n <h2 class=\"phead-title\">Connection</h2>\n\n <div class=\"field\">\n <label for=\"apiBase\">API base URL</label>\n <input\n id=\"apiBase\"\n v-model.trim=\"settings.apiBase\"\n type=\"url\"\n spellcheck=\"false\"\n placeholder=\"(same origin)\"\n />\n <div class=\"mute hint\">\n Leave empty for same-origin, which is how this UI is served in the control stack — nginx\n proxies <code>/api/</code> to the pstack container, so the browser never makes a\n cross-origin request and no CORS configuration exists to get wrong. Set an absolute URL\n only for a dev session against a tunnelled host.\n </div>\n </div>\n\n <div class=\"field\">\n <label for=\"token\">Bearer token</label>\n <input\n id=\"token\"\n v-model.trim=\"settings.token\"\n type=\"password\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n placeholder=\"PSTACK_TOKEN\"\n />\n <div class=\"mute hint\">\n Attached to <strong>POST, PUT and DELETE</strong> only — the routes that change or destroy\n something. Reads are unauthenticated, so the dashboard works before you paste one.\n <template v-if=\"authEnforced === false\">\n <br />\n This server reports <code>authEnforced: false</code>: it is bound to loopback and accepts\n mutations without a token. A token here is harmless but unnecessary.\n </template>\n </div>\n </div>\n\n <div class=\"row\" style=\"gap: 8px; margin-top: 4px\">\n <button class=\"btn\" :disabled=\"checking\" @click=\"recheck\">\n {{ checking ? 'Checking…' : 'Test connection' }}\n </button>\n <span v-if=\"state.health\" class=\"mute\">\n pstack {{ state.health.version }} · registry {{ state.health.dataDir }}\n </span>\n <span v-else-if=\"state.healthError\" class=\"bad\">{{ state.healthError }}</span>\n </div>\n </section>\n\n <section class=\"panel\">\n <h2 class=\"phead-title\">Appearance</h2>\n\n <div class=\"field\">\n <label for=\"theme\">Theme</label>\n <select id=\"theme\" v-model=\"settings.theme\">\n <option value=\"system\">Follow the system</option>\n <option value=\"dark\">Dark</option>\n <option value=\"light\">Light</option>\n </select>\n </div>\n\n <div class=\"field\">\n <label for=\"motion\">Motion</label>\n <select id=\"motion\" v-model=\"settings.motion\">\n <option value=\"system\">Follow the system</option>\n <option value=\"full\">Full animation</option>\n <option value=\"none\">Reduce motion</option>\n </select>\n <div class=\"mute hint\">\n “Follow the system” honours <code>prefers-reduced-motion</code>. The explicit options\n override it in both directions, because an OS-level preference is not always the right\n answer for one app on one screen.\n </div>\n </div>\n </section>\n </div>\n</template>\n"],"names":["checking","ref","authEnforced","computed","_a","state","recheck","loadHealth","toast","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_hoisted_2","$event","_unref","settings","_hoisted_3","_hoisted_4","_Fragment","_hoisted_5","_hoisted_6","_openBlock","_hoisted_7","_toDisplayString","_hoisted_8","_hoisted_9","_hoisted_10","_vModelSelect","_hoisted_11"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{d as s,o as e,c as a,F as o,r,q as n,v as i}from"./index--8-Mcu3f.js";const c={role:"status","aria-live":"polite","aria-label":"loading"},p=s({__name:"SkeletonList",props:{rows:{default:3},tall:{type:Boolean,default:!1}},setup(l){return(d,u)=>(e(),a("div",c,[(e(!0),a(o,null,r(l.rows,t=>(e(),a("div",{key:t,class:i(["skel",{tall:l.tall}]),style:n({width:`${100-t*7}%`})},null,6))),128))]))}});export{p as _};
2
+ //# sourceMappingURL=SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js","sources":["../../src/components/SkeletonList.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A loading placeholder shaped like the thing that is coming.\n *\n * Deliberately not a spinner on an empty page: a spinner says \"wait\" and nothing else, while a\n * skeleton says what will be there and stops the layout jumping when it arrives. It also makes an\n * empty result visibly different from a slow one, which on this page is the difference between\n * \"nothing is deployed\" and \"the API is not answering\".\n */\nwithDefaults(defineProps<{ rows?: number; tall?: boolean }>(), { rows: 3, tall: false });\n</script>\n\n<template>\n <div role=\"status\" aria-live=\"polite\" aria-label=\"loading\">\n <div v-for=\"n in rows\" :key=\"n\" class=\"skel\" :class=\"{ tall }\" :style=\"{ width: `${100 - n * 7}%` }\" />\n </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_Fragment","_renderList","__props","n","_normalizeClass"],"mappings":"2PAaEA,EAAA,EAAAC,EAEM,MAFNC,EAEM,QADJD,EAAuGE,EAAA,KAAAC,EAAtFC,EAAA,KAALC,QAAZL,EAAuG,MAAA,CAA/E,IAAKK,EAAG,MAAKC,EAAA,CAAC,OAAM,CAAA,KAAWF,EAAA,IAAA,CAAI,CAAA,EAAK,sBAAyBC,EAAC,CAAA,GAAA,CAAA,CAAA"}
@@ -0,0 +1,2 @@
1
+ import{d as t,c as s,v as n,a as o,e as r,t as l,o as c}from"./index--8-Mcu3f.js";const m=t({__name:"StateBadge",props:{state:{}},setup(e){return(p,a)=>(c(),s("span",{class:n(["badge",e.state])},[a[0]||(a[0]=o("span",{class:"dot"},null,-1)),r(" "+l(e.state),1)],2))}});export{m as _};
2
+ //# sourceMappingURL=StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js","sources":["../../src/components/StateBadge.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The four job states, and only the four. Kept in one component so the colour of `leaked` can\n * never drift from the colour of `failed` in one view and not another:\n *\n * ok green · failed red · leaked AMBER · running blue, pulsing\n *\n * `leaked` must be unmistakably distinct from `failed`. They are different problems with different\n * owners — \"teardown errored\" versus \"teardown ran and something survived\" — which is why the CLI\n * gives leaked its own exit code, and why this app never renders them in the same colour.\n */\nimport type { JobState } from '../api/types';\n\ndefineProps<{ state: JobState }>();\n</script>\n\n<template>\n <span class=\"badge\" :class=\"state\">\n <span class=\"dot\" />\n {{ state }}\n </span>\n</template>\n"],"names":["_createElementBlock","_normalizeClass","__props","_createElementVNode","_createTextVNode"],"mappings":"6JAiBEA,EAGO,OAAA,CAHD,MAAKC,EAAA,CAAC,QAAgBC,EAAA,KAAK,CAAA,CAAA,eAC/BC,EAAoB,OAAA,CAAd,MAAM,KAAA,EAAK,KAAA,EAAA,GAAGC,EAAA,MACjBF,EAAA,KAAK,EAAA,CAAA,CAAA"}
@@ -0,0 +1,45 @@
1
+ import{d as _,j as m,k as R,B as M,c as i,a as s,t as d,e as t,f as c,x as y,y as x,N as S,F as C,z as N,r as O,h as V,w as A,g as F,m as $,n as j,o as n,I as B,H as D,p as K,M as q}from"./index--8-Mcu3f.js";import{a as z,w as H,p as X,r as W}from"./useVars-VN9gYdR6.js";import{_ as G}from"./ActionButton.vue_vue_type_script_setup_true_lang-eyfGLbs3.js";import{_ as J}from"./ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js";import{_ as Q}from"./VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js";const Y={class:"page-head"},Z={class:"panel"},ee={key:0,class:"banner warn"},se={class:"mono"},oe={class:"row",style:{"margin-bottom":"var(--s4)"}},te={class:"field",style:{"max-width":"280px"}},ae={key:1,class:"row",style:{"margin-bottom":"var(--s4)"}},le={class:"check"},ne={class:"check"},ie={class:"field",style:{"max-width":"340px"}},re=["value"],de={key:0,class:"hint"},pe={key:0},ue={key:1,class:"mono"},me={key:2},ve={class:"field"},ce={class:"field",style:{"margin-top":"var(--s3)"}},ye={class:"row",style:{"margin-top":"var(--s5)"}},fe={key:0,class:"s-ok"},he={class:"mono"},ke=`version: 1
2
+ kind: isolated
3
+
4
+ # The stack identity: the compose project name, and $STACK inside every hook.
5
+ # \${PR} is interpolated ONCE, at resolve time, from the variables sent with the request.
6
+ stack: pr-\${PR}
7
+
8
+ env:
9
+ PREVIEW_DOMAIN: preview.example.com
10
+
11
+ compose:
12
+ file: compose.yml
13
+ # Every service sits behind a profile so a bare \`up\` starts nothing.
14
+ # \`down\` always enables ALL of them — otherwise the unselected profiles' network leaks.
15
+ profiles: [app]
16
+
17
+ requires:
18
+ - name: traefik-network
19
+ assert: docker network inspect traefik >/dev/null 2>&1
20
+ hint: run \`pstack init\` on the host first
21
+
22
+ axes:
23
+ # Hooks run from the deployment directory, which holds only spec.yml and compose.yml —
24
+ # so use inline shell or absolute paths, never ./hooks/foo.sh.
25
+ - name: scratch
26
+ up: mkdir -p /tmp/$STACK
27
+ assert_live: test -d /tmp/$STACK
28
+ down: rm -rf /tmp/$STACK
29
+ # assert_gone MUST FAIL CLOSED. A bare \`! <probe>\` reports "gone" whenever the probe fails
30
+ # for any reason — a missing CLI, an expired token — turning "I could not tell" into
31
+ # "it is gone", a false negative on the one thing this tool exists to catch.
32
+ assert_gone: |
33
+ test -d /tmp || exit 1
34
+ ! test -d /tmp/$STACK
35
+ `,be=`services:
36
+ app:
37
+ image: nginx:alpine
38
+ profiles: [app]
39
+ labels:
40
+ - traefik.enable=true
41
+ `,$e=_({__name:"SubmitView",props:{id:{}},setup(T){const w=T,a=m({id:w.id??"",spec:"",compose:""}),v=m([{k:"",v:""}]),p=m("inline"),f=m(""),h=m(!1),k=m(""),u=m(null),b=m([]);R.get("/api/specs").then(l=>{l.ok&&Array.isArray(l.body.specs)&&(b.value=l.body.specs)});const g=$(()=>b.value.find(l=>l.name===f.value)),P=$(()=>!!w.id);M(()=>w.id,l=>{var o;if(k.value="",u.value=null,!l)return;l!==a.value.id&&(a.value.spec="",a.value.compose=""),a.value.id=l;const e=(o=q(l))==null?void 0:o.vars,r=e?X(e):W(l);r.some(I=>I.k)&&(v.value=r)},{immediate:!0});const U=$(()=>!!a.value.id&&(p.value==="stored"?!!f.value:!!a.value.spec.trim())&&!h.value);async function E(){h.value=!0,k.value="",u.value=null;const l=a.value.id,e=z(v.value),r={vars:e,env:e};p.value==="stored"?r.specName=f.value:(r.spec=a.value.spec,a.value.compose.trim()&&(r.compose=a.value.compose));const o=await R.put(`/api/deployments/${encodeURIComponent(l)}`,r);if(h.value=!1,o.status===200||o.status===201){u.value={id:o.body.id??l,stack:o.body.stack??"?",created:o.status===201},H(l,v.value),B(),D("ok",`${o.status===201?"Created":"Replaced"} ${l} → ${u.value.stack}`,{to:`/deployments/${encodeURIComponent(l)}`,toLabel:"Open"});return}k.value=K(o,`PUT /api/deployments/${l}`)}function L(){p.value="inline",a.value.spec=ke,a.value.compose.trim()||(a.value.compose=be),a.value.id||(a.value.id="pr-123"),v.value.some(l=>l.k)||(v.value=[{k:"PR",v:"123"}])}return(l,e)=>{const r=j("RouterLink");return n(),i("div",null,[s("div",Y,[s("div",null,[s("h1",null,d(P.value?"Replace a deployment":"Submit a deployment"),1),e[7]||(e[7]=s("div",{class:"sub"},[s("code",null,"PUT /api/deployments/:id")],-1))]),e[8]||(e[8]=s("span",{class:"grow"},null,-1)),s("button",{class:"ghost",onClick:L},"Load example spec")]),s("section",Z,[e[24]||(e[24]=s("p",{class:"dim",style:{"font-size":"var(--t-sm)"}},[t(" The spec is parsed "),s("em",null,"before"),t(" anything touches disk, so a rejected submission leaves nothing behind — and on a replace, a good record is never destroyed over a typo. "),s("code",null,"kind"),t(" is read from the spec, never from this form. ")],-1)),P.value?(n(),i("div",ee,[s("b",null,[e[9]||(e[9]=t("Replacing ",-1)),s("span",se,d(T.id),1),e[10]||(e[10]=t(".",-1))]),e[11]||(e[11]=s("p",null,[t(" This form starts "),s("em",null,"empty"),t(": the API has no route that hands back a stored spec, so nothing here was loaded from disk. Paste the "),s("b",null,"complete"),t(" spec you want stored — "),s("code",null,"PUT"),t(" replaces the record outright, and any axis you leave out stops being tracked while whatever it created keeps running. ")],-1))])):c("",!0),s("div",oe,[s("div",te,[e[12]||(e[12]=s("label",{for:"did"},"deployment id",-1)),y(s("input",{id:"did","onUpdate:modelValue":e[0]||(e[0]=o=>a.value.id=o),type:"text",placeholder:"pr-123",spellcheck:"false",autocomplete:"off"},null,512),[[x,a.value.id,void 0,{trim:!0}]])]),e[13]||(e[13]=s("span",{class:"mono mute",style:{"align-self":"end","padding-bottom":"8px"}}," [a-z0-9][a-z0-9._-]{0,63} ",-1))]),b.value.length?(n(),i("div",ae,[s("label",le,[y(s("input",{"onUpdate:modelValue":e[1]||(e[1]=o=>p.value=o),type:"radio",value:"inline"},null,512),[[S,p.value]]),e[14]||(e[14]=t(" inline spec ",-1))]),s("label",ne,[y(s("input",{"onUpdate:modelValue":e[2]||(e[2]=o=>p.value=o),type:"radio",value:"stored"},null,512),[[S,p.value]]),e[15]||(e[15]=t(" reference a stored spec ",-1))])])):c("",!0),p.value==="stored"&&b.value.length?(n(),i(C,{key:2},[s("div",ie,[e[17]||(e[17]=s("label",{for:"sn"},"stored spec",-1)),y(s("select",{id:"sn","onUpdate:modelValue":e[3]||(e[3]=o=>f.value=o)},[e[16]||(e[16]=s("option",{value:""},"— choose —",-1)),(n(!0),i(C,null,O(b.value,o=>(n(),i("option",{key:o.name,value:o.name},d(o.name)+" ("+d(o.kind)+") ",9,re))),128))],512),[[N,f.value]])]),g.value?(n(),i("p",de,[g.value.description?(n(),i("span",pe,d(g.value.description)+" — ",1)):c("",!0),e[18]||(e[18]=t(" needs ",-1)),g.value.requiredVars.length?(n(),i("span",ue,d(g.value.requiredVars.join(", ")),1)):(n(),i("span",me,"no variables")),e[19]||(e[19]=t(". A variable the spec needs and is not given is a 400 that names it, never a stack every PR would collide on. ",-1))])):c("",!0)],64)):(n(),i(C,{key:3},[s("div",ve,[e[20]||(e[20]=s("label",{for:"spec"},"spec.yml",-1)),y(s("textarea",{id:"spec","onUpdate:modelValue":e[4]||(e[4]=o=>a.value.spec=o),rows:"18",spellcheck:"false",placeholder:`version: 1
42
+ kind: isolated
43
+ stack: pr-\${PR}
44
+ axes: []`},null,512),[[x,a.value.spec]])]),s("div",ce,[e[21]||(e[21]=s("label",{for:"compose"},[t("compose.yml "),s("span",{class:"mute"},"(optional)")],-1)),y(s("textarea",{id:"compose","onUpdate:modelValue":e[5]||(e[5]=o=>a.value.compose=o),rows:"8",spellcheck:"false",placeholder:"written next to spec.yml, so `compose: { file: compose.yml }` resolves"},null,512),[[x,a.value.compose]])]),e[22]||(e[22]=s("p",{class:"hint"},[t(" Only "),s("code",null,"spec.yml"),t(" and "),s("code",null,"compose.yml"),t(" ever live in a deployment directory, and hooks run from there — so a hook must be inline shell or an absolute path. "),s("code",null,"up: ./hooks/db.sh"),t(" cannot work here. ")],-1))],64)),e[25]||(e[25]=s("h2",{class:"section",style:{margin:"var(--s5) 0 var(--s3)"}},"Variables",-1)),V(Q,{modelValue:v.value,"onUpdate:modelValue":e[6]||(e[6]=o=>v.value=o)},null,8,["modelValue"]),e[26]||(e[26]=s("p",{class:"hint"},[t(" These validate the spec now, and are stored with the deployment on a server that supports it — which is what lets a later "),s("code",null,"down"),t(" resolve the same stack this "),s("code",null,"up"),t(" creates. They are also saved in this browser, so the actions on the deployment page send the same ones. ")],-1)),s("div",ye,[V(G,{variant:"primary",pending:h.value,disabled:!U.value,onClick:E},{default:A(()=>[t(d(h.value?"submitting…":"Submit"),1)]),_:1},8,["pending","disabled"]),u.value?(n(),i("span",fe,[t(d(u.value.created?"created":"replaced")+" — stack ",1),s("b",he,d(u.value.stack),1),V(r,{to:`/deployments/${encodeURIComponent(u.value.id)}`,style:{"margin-left":"8px"}},{default:A(()=>[...e[23]||(e[23]=[t(" open → ",-1)])]),_:1},8,["to"])])):c("",!0)]),k.value?(n(),F(J,{key:4,text:k.value,title:"The server rejected this submission."},null,8,["text"])):c("",!0)])])}}});export{$e as default};
45
+ //# sourceMappingURL=SubmitView-dblBZxuc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubmitView-dblBZxuc.js","sources":["../../src/views/SubmitView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Submit or replace a deployment.\n *\n * The server's 400 is the whole point of this screen. It carries the SpecError verbatim — the key\n * or variable that is wrong, often the corrected form, and the assert-lint warnings about an\n * `assert_gone` that fails open. That text is the tool's main teaching surface, so it is rendered\n * whole, with newlines intact, and never summarised.\n *\n * The spec is parsed BEFORE anything touches disk, so a rejected submission leaves nothing behind\n * and, on a replace, a good record is never destroyed over a typo.\n */\nimport { computed, ref, watch } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { SpecMeta, SpecsResponse, SubmitResponse, VarPair } from '../api/types';\nimport { loadDeployments, rowFor } from '../composables/useControlPlane';\nimport { pairsFromRecord, readVars, recordFromPairs, writeVars } from '../composables/useVars';\nimport { toast } from '../composables/useToasts';\nimport ActionButton from '../components/ActionButton.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport VarEditor from '../components/VarEditor.vue';\n\nconst props = defineProps<{ id?: string }>();\n\nconst form = ref({ id: props.id ?? '', spec: '', compose: '' });\nconst vars = ref<VarPair[]>([{ k: '', v: '' }]);\nconst source = ref<'inline' | 'stored'>('inline');\nconst specName = ref('');\n\nconst submitting = ref(false);\nconst error = ref('');\nconst result = ref<{ id: string; stack: string; created: boolean } | null>(null);\n\n/**\n * Stored specs, when this server has them. A build without `/api/specs` answers 404, which is not\n * an error here — the picker simply is not offered and inline stays the only shape.\n */\nconst specs = ref<SpecMeta[]>([]);\nvoid api.get<SpecsResponse>('/api/specs').then((r) => {\n if (r.ok && Array.isArray(r.body.specs)) specs.value = r.body.specs;\n});\n\nconst chosenSpec = computed(() => specs.value.find((s) => s.name === specName.value));\n\nconst replacing = computed(() => !!props.id);\n\n/**\n * Arriving to replace a DIFFERENT deployment must not carry the previous one's body across. There\n * is no route that returns a stored spec, so this form can never pre-fill the body — and a\n * leftover body is the dangerous case: it is perfectly valid YAML, so the PUT would succeed and\n * replace the wrong deployment's spec with nothing on the server able to catch it.\n */\nwatch(\n () => props.id,\n (id) => {\n error.value = '';\n result.value = null;\n if (!id) return;\n if (id !== form.value.id) {\n form.value.spec = '';\n form.value.compose = '';\n }\n form.value.id = id;\n // Seed the variables from what this deployment already uses: replacing a spec usually needs\n // the same ones that resolved it.\n const stored = rowFor(id)?.vars;\n const saved = stored ? pairsFromRecord(stored) : readVars(id);\n if (saved.some((e) => e.k)) vars.value = saved;\n },\n { immediate: true },\n);\n\nconst canSubmit = computed(\n () =>\n !!form.value.id &&\n (source.value === 'stored' ? !!specName.value : !!form.value.spec.trim()) &&\n !submitting.value,\n);\n\nasync function submit(): Promise<void> {\n submitting.value = true;\n error.value = '';\n result.value = null;\n\n const id = form.value.id;\n const record = recordFromPairs(vars.value);\n /**\n * `vars` are STORED with the deployment on servers that support it, which makes a later `down`\n * resolve the same stack `up` created with no query parameters at all. `env` validates this\n * submission on every server, new or old. Sending both is what keeps this form working against\n * either build — an older server ignores the key it does not know.\n */\n const body: Record<string, unknown> = { vars: record, env: record };\n if (source.value === 'stored') body.specName = specName.value;\n else {\n body.spec = form.value.spec;\n if (form.value.compose.trim()) body.compose = form.value.compose;\n }\n\n const r = await api.put<SubmitResponse>(`/api/deployments/${encodeURIComponent(id)}`, body);\n submitting.value = false;\n\n if (r.status === 200 || r.status === 201) {\n result.value = { id: r.body.id ?? id, stack: r.body.stack ?? '?', created: r.status === 201 };\n // Persist the variables that just validated this spec. On an older server this is the single\n // thing keeping a later `down` pointed at the stack the `up` created.\n writeVars(id, vars.value);\n void loadDeployments();\n toast('ok', `${r.status === 201 ? 'Created' : 'Replaced'} ${id} → ${result.value.stack}`, {\n to: `/deployments/${encodeURIComponent(id)}`,\n toLabel: 'Open',\n });\n return;\n }\n // 400 (SpecError), 409 (a job in flight over this stack), 401, 404 — all rendered verbatim.\n error.value = problem(r, `PUT /api/deployments/${id}`);\n}\n\nconst EXAMPLE_SPEC = `version: 1\nkind: isolated\n\n# The stack identity: the compose project name, and $STACK inside every hook.\n# \\${PR} is interpolated ONCE, at resolve time, from the variables sent with the request.\nstack: pr-\\${PR}\n\nenv:\n PREVIEW_DOMAIN: preview.example.com\n\ncompose:\n file: compose.yml\n # Every service sits behind a profile so a bare \\`up\\` starts nothing.\n # \\`down\\` always enables ALL of them — otherwise the unselected profiles' network leaks.\n profiles: [app]\n\nrequires:\n - name: traefik-network\n assert: docker network inspect traefik >/dev/null 2>&1\n hint: run \\`pstack init\\` on the host first\n\naxes:\n # Hooks run from the deployment directory, which holds only spec.yml and compose.yml —\n # so use inline shell or absolute paths, never ./hooks/foo.sh.\n - name: scratch\n up: mkdir -p /tmp/$STACK\n assert_live: test -d /tmp/$STACK\n down: rm -rf /tmp/$STACK\n # assert_gone MUST FAIL CLOSED. A bare \\`! <probe>\\` reports \"gone\" whenever the probe fails\n # for any reason — a missing CLI, an expired token — turning \"I could not tell\" into\n # \"it is gone\", a false negative on the one thing this tool exists to catch.\n assert_gone: |\n test -d /tmp || exit 1\n ! test -d /tmp/$STACK\n`;\n\nconst EXAMPLE_COMPOSE = `services:\n app:\n image: nginx:alpine\n profiles: [app]\n labels:\n - traefik.enable=true\n`;\n\nfunction loadExample(): void {\n source.value = 'inline';\n form.value.spec = EXAMPLE_SPEC;\n if (!form.value.compose.trim()) form.value.compose = EXAMPLE_COMPOSE;\n if (!form.value.id) form.value.id = 'pr-123';\n if (!vars.value.some((e) => e.k)) vars.value = [{ k: 'PR', v: '123' }];\n}\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>{{ replacing ? 'Replace a deployment' : 'Submit a deployment' }}</h1>\n <div class=\"sub\"><code>PUT /api/deployments/:id</code></div>\n </div>\n <span class=\"grow\" />\n <button class=\"ghost\" @click=\"loadExample\">Load example spec</button>\n </div>\n\n <section class=\"panel\">\n <p class=\"dim\" style=\"font-size: var(--t-sm)\">\n The spec is parsed <em>before</em> anything touches disk, so a rejected submission leaves\n nothing behind — and on a replace, a good record is never destroyed over a typo.\n <code>kind</code> is read from the spec, never from this form.\n </p>\n\n <!--\n A `PUT` REPLACES the whole record, and this form cannot pre-fill it: the API has no route\n that returns a stored spec. Say that out loud, because a half-remembered spec pasted here\n is still valid YAML — the server will accept it, and the axes the original declared simply\n stop existing as far as the control plane knows, while whatever they created keeps running.\n -->\n <div v-if=\"replacing\" class=\"banner warn\">\n <b>Replacing <span class=\"mono\">{{ id }}</span>.</b>\n <p>\n This form starts <em>empty</em>: the API has no route that hands back a stored spec, so\n nothing here was loaded from disk. Paste the <b>complete</b> spec you want stored —\n <code>PUT</code> replaces the record outright, and any axis you leave out stops being\n tracked while whatever it created keeps running.\n </p>\n </div>\n\n <div class=\"row\" style=\"margin-bottom: var(--s4)\">\n <div class=\"field\" style=\"max-width: 280px\">\n <label for=\"did\">deployment id</label>\n <input\n id=\"did\"\n v-model.trim=\"form.id\"\n type=\"text\"\n placeholder=\"pr-123\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n </div>\n <span class=\"mono mute\" style=\"align-self: end; padding-bottom: 8px\">\n [a-z0-9][a-z0-9._-]{0,63}\n </span>\n </div>\n\n <!-- Offered only when this server actually has stored specs. -->\n <div v-if=\"specs.length\" class=\"row\" style=\"margin-bottom: var(--s4)\">\n <label class=\"check\">\n <input v-model=\"source\" type=\"radio\" value=\"inline\" /> inline spec\n </label>\n <label class=\"check\">\n <input v-model=\"source\" type=\"radio\" value=\"stored\" /> reference a stored spec\n </label>\n </div>\n\n <template v-if=\"source === 'stored' && specs.length\">\n <div class=\"field\" style=\"max-width: 340px\">\n <label for=\"sn\">stored spec</label>\n <select id=\"sn\" v-model=\"specName\">\n <option value=\"\">— choose —</option>\n <option v-for=\"s in specs\" :key=\"s.name\" :value=\"s.name\">\n {{ s.name }} ({{ s.kind }})\n </option>\n </select>\n </div>\n <p v-if=\"chosenSpec\" class=\"hint\">\n <span v-if=\"chosenSpec.description\">{{ chosenSpec.description }} — </span>\n needs\n <span v-if=\"chosenSpec.requiredVars.length\" class=\"mono\">\n {{ chosenSpec.requiredVars.join(', ') }}\n </span>\n <span v-else>no variables</span>. A variable the spec needs and is not given is a 400 that\n names it, never a stack every PR would collide on.\n </p>\n </template>\n\n <template v-else>\n <div class=\"field\">\n <label for=\"spec\">spec.yml</label>\n <textarea\n id=\"spec\"\n v-model=\"form.spec\"\n rows=\"18\"\n spellcheck=\"false\"\n placeholder=\"version: 1&#10;kind: isolated&#10;stack: pr-${PR}&#10;axes: []\"\n />\n </div>\n\n <div class=\"field\" style=\"margin-top: var(--s3)\">\n <label for=\"compose\">compose.yml <span class=\"mute\">(optional)</span></label>\n <textarea\n id=\"compose\"\n v-model=\"form.compose\"\n rows=\"8\"\n spellcheck=\"false\"\n placeholder=\"written next to spec.yml, so `compose: { file: compose.yml }` resolves\"\n />\n </div>\n <p class=\"hint\">\n Only <code>spec.yml</code> and <code>compose.yml</code> ever live in a deployment\n directory, and hooks run from there — so a hook must be inline shell or an absolute path.\n <code>up: ./hooks/db.sh</code> cannot work here.\n </p>\n </template>\n\n <h2 class=\"section\" style=\"margin: var(--s5) 0 var(--s3)\">Variables</h2>\n <VarEditor v-model=\"vars\" />\n <p class=\"hint\">\n These validate the spec now, and are stored with the deployment on a server that supports it\n — which is what lets a later <code>down</code> resolve the same stack this <code>up</code>\n creates. They are also saved in this browser, so the actions on the deployment page send the\n same ones.\n </p>\n\n <div class=\"row\" style=\"margin-top: var(--s5)\">\n <ActionButton variant=\"primary\" :pending=\"submitting\" :disabled=\"!canSubmit\" @click=\"submit\">\n {{ submitting ? 'submitting…' : 'Submit' }}\n </ActionButton>\n <span v-if=\"result\" class=\"s-ok\">\n {{ result.created ? 'created' : 'replaced' }} — stack\n <b class=\"mono\">{{ result.stack }}</b>\n <RouterLink :to=\"`/deployments/${encodeURIComponent(result.id)}`\" style=\"margin-left: 8px\">\n open →\n </RouterLink>\n </span>\n </div>\n\n <!--\n The server's rejection text IS the documentation: it names the key, the variable or the\n assert that is wrong, and often prints the corrected form. Rendered whole, newlines and\n indentation intact. Never truncate it, never collapse it, never say \"invalid spec\".\n -->\n <ErrorNote v-if=\"error\" :text=\"error\" title=\"The server rejected this submission.\" />\n </section>\n </div>\n</template>\n"],"names":["EXAMPLE_SPEC","EXAMPLE_COMPOSE","props","__props","form","ref","vars","source","specName","submitting","error","result","specs","api","r","chosenSpec","computed","s","replacing","watch","id","stored","_a","rowFor","saved","pairsFromRecord","readVars","e","canSubmit","submit","record","recordFromPairs","body","writeVars","loadDeployments","toast","problem","loadExample","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_openBlock","_hoisted_3","_hoisted_4","_toDisplayString","_hoisted_5","_hoisted_6","_cache","$event","_hoisted_7","_hoisted_8","_hoisted_9","_Fragment","_hoisted_10","_renderList","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_16","_vModelText","_hoisted_17","_createVNode","VarEditor","_hoisted_18","ActionButton","_hoisted_19","_hoisted_20","_component_RouterLink","_createBlock","ErrorNote"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{d as k,c as r,a,F as f,r as h,o as c}from"./index--8-Mcu3f.js";const b=["value","aria-label","onInput"],V=["value","aria-label","onInput"],_=["aria-label","onClick"],x=k({__name:"VarEditor",props:{modelValue:{}},emits:["update:modelValue","change"],setup(d,{emit:p}){const s=d,t=p;function m(u,l){const o=s.modelValue.map((e,n)=>n===u?{...e,...l}:e);t("update:modelValue",o),t("change")}function i(){t("update:modelValue",[...s.modelValue,{k:"",v:""}]),t("change")}function v(u){const l=s.modelValue.filter((o,e)=>e!==u);t("update:modelValue",l.length?l:[{k:"",v:""}]),t("change")}return(u,l)=>(c(),r("div",null,[l[0]||(l[0]=a("div",{class:"kv head"},[a("span",null,"name"),a("span",null,"value"),a("span")],-1)),(c(!0),r(f,null,h(d.modelValue,(o,e)=>(c(),r("div",{key:e,class:"kv"},[a("input",{type:"text",value:o.k,"aria-label":`variable name ${e+1}`,placeholder:"PR",spellcheck:"false",autocomplete:"off",onInput:n=>m(e,{k:n.target.value.trim()})},null,40,b),a("input",{type:"text",value:o.v,"aria-label":`variable value ${e+1}`,placeholder:"7",spellcheck:"false",autocomplete:"off",onInput:n=>m(e,{v:n.target.value})},null,40,V),a("button",{class:"sm ghost","aria-label":`remove variable ${e+1}`,onClick:n=>v(e)},"−",8,_)]))),128)),a("button",{class:"sm",onClick:i},"+ variable")]))}});export{x as _};
2
+ //# sourceMappingURL=VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js","sources":["../../src/components/VarEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The free-form variable editor, shared by the Config tab and Submit.\n *\n * Free-form on purpose: the spec's `env:` block lists what it DECLARES, which is not the same as\n * what it NEEDS — `stack: pr-${PR}` consumes `PR` without declaring it anywhere. The authoritative\n * list of what is missing is the server's 400 text, not this form.\n */\nimport type { VarPair } from '../api/types';\n\nconst props = defineProps<{ modelValue: VarPair[] }>();\nconst emit = defineEmits<{ 'update:modelValue': [VarPair[]]; change: [] }>();\n\nfunction set(i: number, patch: Partial<VarPair>): void {\n const next = props.modelValue.map((e, n) => (n === i ? { ...e, ...patch } : e));\n emit('update:modelValue', next);\n emit('change');\n}\n\nfunction add(): void {\n emit('update:modelValue', [...props.modelValue, { k: '', v: '' }]);\n emit('change');\n}\n\nfunction remove(i: number): void {\n const next = props.modelValue.filter((_, n) => n !== i);\n // Never leave the editor with no rows: an empty list looks like a broken control.\n emit('update:modelValue', next.length ? next : [{ k: '', v: '' }]);\n emit('change');\n}\n</script>\n\n<template>\n <div>\n <div class=\"kv head\"><span>name</span><span>value</span><span /></div>\n <div v-for=\"(e, i) in modelValue\" :key=\"i\" class=\"kv\">\n <input\n type=\"text\"\n :value=\"e.k\"\n :aria-label=\"`variable name ${i + 1}`\"\n placeholder=\"PR\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n @input=\"set(i, { k: ($event.target as HTMLInputElement).value.trim() })\"\n />\n <input\n type=\"text\"\n :value=\"e.v\"\n :aria-label=\"`variable value ${i + 1}`\"\n placeholder=\"7\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n @input=\"set(i, { v: ($event.target as HTMLInputElement).value })\"\n />\n <button class=\"sm ghost\" :aria-label=\"`remove variable ${i + 1}`\" @click=\"remove(i)\">−</button>\n </div>\n <button class=\"sm\" @click=\"add\">+ variable</button>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","set","i","patch","next","add","remove","_","n","_createElementBlock","_createElementVNode","_openBlock","_Fragment","_renderList","e","$event","_hoisted_3"],"mappings":"kRAUA,MAAMA,EAAQC,EACRC,EAAOC,EAEb,SAASC,EAAIC,EAAWC,EAA+B,CACrD,MAAMC,EAAOP,EAAM,WAAW,IAAI,CAAC,EAAG,IAAO,IAAMK,EAAI,CAAE,GAAG,EAAG,GAAGC,CAAA,EAAU,CAAE,EAC9EJ,EAAK,oBAAqBK,CAAI,EAC9BL,EAAK,QAAQ,CACf,CAEA,SAASM,GAAY,CACnBN,EAAK,oBAAqB,CAAC,GAAGF,EAAM,WAAY,CAAE,EAAG,GAAI,EAAG,EAAA,CAAI,CAAC,EACjEE,EAAK,QAAQ,CACf,CAEA,SAASO,EAAOJ,EAAiB,CAC/B,MAAME,EAAOP,EAAM,WAAW,OAAO,CAACU,EAAGC,IAAMA,IAAMN,CAAC,EAEtDH,EAAK,oBAAqBK,EAAK,OAASA,EAAO,CAAC,CAAE,EAAG,GAAI,EAAG,EAAA,CAAI,CAAC,EACjEL,EAAK,QAAQ,CACf,mBAIEU,EAwBM,MAAA,KAAA,aAvBJC,EAAsE,MAAA,CAAjE,MAAM,WAAS,CAACA,EAAiB,YAAX,MAAI,EAAOA,EAAkB,YAAZ,OAAK,EAAOA,EAAQ,MAAA,CAAA,QAChEC,EAAA,EAAA,EAAAF,EAoBMG,EAAA,KAAAC,EApBgBf,EAAA,WAAU,CAAnBgB,EAAGZ,SAAhBO,EAoBM,MAAA,CApB6B,IAAKP,EAAG,MAAM,IAAA,GAC/CQ,EAQE,QAAA,CAPA,KAAK,OACJ,MAAOI,EAAE,EACT,8BAA6BZ,EAAC,CAAA,GAC/B,YAAY,KACZ,WAAW,QACX,aAAa,MACZ,QAAKa,GAAEd,EAAIC,EAAC,CAAA,EAAQa,EAAO,OAA4B,MAAM,MAAI,CAAA,CAAA,aAEpEL,EAQE,QAAA,CAPA,KAAK,OACJ,MAAOI,EAAE,EACT,+BAA8BZ,EAAC,CAAA,GAChC,YAAY,IACZ,WAAW,QACX,aAAa,MACZ,QAAKa,GAAEd,EAAIC,KAASa,EAAO,OAA4B,KAAA,CAAK,CAAA,aAE/DL,EAA+F,SAAA,CAAvF,MAAM,WAAY,gCAA+BR,EAAC,CAAA,GAAS,QAAKa,GAAET,EAAOJ,CAAC,CAAA,EAAG,IAAC,EAAAc,CAAA,CAAA,WAExFN,EAAmD,SAAA,CAA3C,MAAM,KAAM,QAAOL,CAAA,EAAK,YAAU,CAAA"}