@samyx/preview-stacks-ui 0.19.0 → 0.22.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.
- package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-D06apM-6.js +2 -0
- package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-D06apM-6.js.map +1 -0
- package/dist/assets/{AxesTab-COaSNzrG.js → AxesTab-CCB3wdSX.js} +2 -2
- package/dist/assets/{AxesTab-COaSNzrG.js.map → AxesTab-CCB3wdSX.js.map} +1 -1
- package/dist/assets/ConfigTab-Cyqzeq3C.js +2 -0
- package/dist/assets/ConfigTab-Cyqzeq3C.js.map +1 -0
- package/dist/assets/{ConflictNote.vue_vue_type_script_setup_true_lang-DrzDcr4X.js → ConflictNote.vue_vue_type_script_setup_true_lang-CtwJE2eJ.js} +2 -2
- package/dist/assets/{ConflictNote.vue_vue_type_script_setup_true_lang-DrzDcr4X.js.map → ConflictNote.vue_vue_type_script_setup_true_lang-CtwJE2eJ.js.map} +1 -1
- package/dist/assets/{DangerTab-DsSJqxzj.js → DangerTab-Cdr9luUy.js} +2 -2
- package/dist/assets/{DangerTab-DsSJqxzj.js.map → DangerTab-Cdr9luUy.js.map} +1 -1
- package/dist/assets/{DashboardView-0BLBDth-.js → DashboardView-S4eiNX8X.js} +2 -2
- package/dist/assets/{DashboardView-0BLBDth-.js.map → DashboardView-S4eiNX8X.js.map} +1 -1
- package/dist/assets/DeploymentDetailView-CD4RVr06.js +2 -0
- package/dist/assets/DeploymentDetailView-CD4RVr06.js.map +1 -0
- package/dist/assets/DeploymentsView-DIX7IA8g.js +2 -0
- package/dist/assets/DeploymentsView-DIX7IA8g.js.map +1 -0
- package/dist/assets/{EquivalentCommand-DyCAI1Cy.js → EquivalentCommand-NHzDrg5d.js} +2 -2
- package/dist/assets/{EquivalentCommand-DyCAI1Cy.js.map → EquivalentCommand-NHzDrg5d.js.map} +1 -1
- package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js → ErrorNote.vue_vue_type_script_setup_true_lang-BFuRxw1l.js} +2 -2
- package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js.map → ErrorNote.vue_vue_type_script_setup_true_lang-BFuRxw1l.js.map} +1 -1
- package/dist/assets/{HelpModal.vue_vue_type_script_setup_true_lang-CACw-lfW.js → HelpModal.vue_vue_type_script_setup_true_lang-DwwXY7EX.js} +2 -2
- package/dist/assets/{HelpModal.vue_vue_type_script_setup_true_lang-CACw-lfW.js.map → HelpModal.vue_vue_type_script_setup_true_lang-DwwXY7EX.js.map} +1 -1
- package/dist/assets/JobDetailView-BsCnnkPz.js +2 -0
- package/dist/assets/JobDetailView-BsCnnkPz.js.map +1 -0
- package/dist/assets/{JobsView-UUwzBG2c.js → JobsView-Bd_XHRkd.js} +2 -2
- package/dist/assets/{JobsView-UUwzBG2c.js.map → JobsView-Bd_XHRkd.js.map} +1 -1
- package/dist/assets/LogViewer.vue_vue_type_script_setup_true_lang-7Ggcxz1R.js +3 -0
- package/dist/assets/LogViewer.vue_vue_type_script_setup_true_lang-7Ggcxz1R.js.map +1 -0
- package/dist/assets/{LoginView-cKuF7PhJ.js → LoginView-Bj7uMFvv.js} +2 -2
- package/dist/assets/{LoginView-cKuF7PhJ.js.map → LoginView-Bj7uMFvv.js.map} +1 -1
- package/dist/assets/LogsTab-DRp76oC1.js +6 -0
- package/dist/assets/LogsTab-DRp76oC1.js.map +1 -0
- package/dist/assets/{NotFoundView-Bpcd3Ptj.js → NotFoundView-D-A8YFJw.js} +2 -2
- package/dist/assets/{NotFoundView-Bpcd3Ptj.js.map → NotFoundView-D-A8YFJw.js.map} +1 -1
- package/dist/assets/NotifiersView-CChDRHSy.js +2 -0
- package/dist/assets/NotifiersView-CChDRHSy.js.map +1 -0
- package/dist/assets/OverviewTab-jE_kOZI7.js +2 -0
- package/dist/assets/OverviewTab-jE_kOZI7.js.map +1 -0
- package/dist/assets/{RegistriesView-CfblUxVo.js → RegistriesView-D3Nh0dbq.js} +2 -2
- package/dist/assets/{RegistriesView-CfblUxVo.js.map → RegistriesView-D3Nh0dbq.js.map} +1 -1
- package/dist/assets/RelativeTime.vue_vue_type_script_setup_true_lang-mVrRpkV_.js +2 -0
- package/dist/assets/{RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js.map → RelativeTime.vue_vue_type_script_setup_true_lang-mVrRpkV_.js.map} +1 -1
- package/dist/assets/{RequiresTab-DKFeob92.js → RequiresTab-BgGIZ1Tn.js} +2 -2
- package/dist/assets/{RequiresTab-DKFeob92.js.map → RequiresTab-BgGIZ1Tn.js.map} +1 -1
- package/dist/assets/{RoutingView-7SWVlOx8.js → RoutingView-D2StZawf.js} +3 -3
- package/dist/assets/{RoutingView-7SWVlOx8.js.map → RoutingView-D2StZawf.js.map} +1 -1
- package/dist/assets/RuntimeTab-B0D_MxFQ.js +2 -0
- package/dist/assets/RuntimeTab-B0D_MxFQ.js.map +1 -0
- package/dist/assets/{SelectMenu.vue_vue_type_script_setup_true_lang-B-DTK242.js → SelectMenu.vue_vue_type_script_setup_true_lang-qGkx2MwW.js} +2 -2
- package/dist/assets/{SelectMenu.vue_vue_type_script_setup_true_lang-B-DTK242.js.map → SelectMenu.vue_vue_type_script_setup_true_lang-qGkx2MwW.js.map} +1 -1
- package/dist/assets/SettingsView-Cu1Zkufg.js +2 -0
- package/dist/assets/SettingsView-Cu1Zkufg.js.map +1 -0
- package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js → SkeletonList.vue_vue_type_script_setup_true_lang-BYHXRCNR.js} +2 -2
- package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js.map → SkeletonList.vue_vue_type_script_setup_true_lang-BYHXRCNR.js.map} +1 -1
- package/dist/assets/SpecDetailView-DIi21wo1.js +2 -0
- package/dist/assets/SpecDetailView-DIi21wo1.js.map +1 -0
- package/dist/assets/SpecsView-B49LI4Ut.js +2 -0
- package/dist/assets/SpecsView-B49LI4Ut.js.map +1 -0
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-DANvveJ6.js +2 -0
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-DANvveJ6.js.map +1 -0
- package/dist/assets/{SubmitView-6cqvVYlz.js → SubmitView-ZYGApCXE.js} +3 -3
- package/dist/assets/{SubmitView-6cqvVYlz.js.map → SubmitView-ZYGApCXE.js.map} +1 -1
- package/dist/assets/{Teleport-SesEhfqH.js → Teleport-d5PoAxFM.js} +2 -2
- package/dist/assets/{Teleport-SesEhfqH.js.map → Teleport-d5PoAxFM.js.map} +1 -1
- package/dist/assets/TerminalTab-DAZpn3jK.js +3 -0
- package/dist/assets/TerminalTab-DAZpn3jK.js.map +1 -0
- package/dist/assets/TerminalTab-DbjRludT.css +1 -0
- package/dist/assets/UsersView-BfQKf8Sf.js +2 -0
- package/dist/assets/UsersView-BfQKf8Sf.js.map +1 -0
- package/dist/assets/UsersView-DpZ_4vlY.css +1 -0
- package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-UEG5poOe.js → VarEditor.vue_vue_type_script_setup_true_lang-D--Rd-Mv.js} +2 -2
- package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-UEG5poOe.js.map → VarEditor.vue_vue_type_script_setup_true_lang-D--Rd-Mv.js.map} +1 -1
- package/dist/assets/VariablesView-Bej9ZZmS.js +2 -0
- package/dist/assets/VariablesView-Bej9ZZmS.js.map +1 -0
- package/dist/assets/VariablesView-Cnpkn8AM.css +1 -0
- package/dist/assets/index-Cyc_WOgY.css +1 -0
- package/dist/assets/{index-jLPJ82ve.js → index-DVCVnKbf.js} +28 -28
- package/dist/assets/index-DVCVnKbf.js.map +1 -0
- package/dist/assets/{useDeployment-Bgo0U4-I.js → useDeployment-752fTjLE.js} +2 -2
- package/dist/assets/{useDeployment-Bgo0U4-I.js.map → useDeployment-752fTjLE.js.map} +1 -1
- package/dist/assets/useFormat-DVXTfIS-.js +2 -0
- package/dist/assets/{useFormat-CKJxQp_i.js.map → useFormat-DVXTfIS-.js.map} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js +0 -2
- package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js.map +0 -1
- package/dist/assets/ConfigTab-XGvUbd66.js +0 -2
- package/dist/assets/ConfigTab-XGvUbd66.js.map +0 -1
- package/dist/assets/DeploymentDetailView-dJjqob1P.js +0 -2
- package/dist/assets/DeploymentDetailView-dJjqob1P.js.map +0 -1
- package/dist/assets/DeploymentsView-BM9LVpU-.js +0 -2
- package/dist/assets/DeploymentsView-BM9LVpU-.js.map +0 -1
- package/dist/assets/JobDetailView-DsYfLQUd.js +0 -2
- package/dist/assets/JobDetailView-DsYfLQUd.js.map +0 -1
- package/dist/assets/LogViewer.vue_vue_type_script_setup_true_lang-DYfYw8_a.js +0 -3
- package/dist/assets/LogViewer.vue_vue_type_script_setup_true_lang-DYfYw8_a.js.map +0 -1
- package/dist/assets/LogsTab-QEuDGrG2.js +0 -5
- package/dist/assets/LogsTab-QEuDGrG2.js.map +0 -1
- package/dist/assets/NotifiersView-B1xAKTwU.js +0 -2
- package/dist/assets/NotifiersView-B1xAKTwU.js.map +0 -1
- package/dist/assets/OverviewTab-B2aCu4e0.js +0 -2
- package/dist/assets/OverviewTab-B2aCu4e0.js.map +0 -1
- package/dist/assets/RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js +0 -2
- package/dist/assets/RuntimeTab-CiSAV3D0.js +0 -2
- package/dist/assets/RuntimeTab-CiSAV3D0.js.map +0 -1
- package/dist/assets/SettingsView-BEha1Bt3.js +0 -2
- package/dist/assets/SettingsView-BEha1Bt3.js.map +0 -1
- package/dist/assets/SpecDetailView-l86eWf2v.js +0 -2
- package/dist/assets/SpecDetailView-l86eWf2v.js.map +0 -1
- package/dist/assets/SpecsView-BfWq69K5.js +0 -2
- package/dist/assets/SpecsView-BfWq69K5.js.map +0 -1
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-DMzCGltO.js +0 -2
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-DMzCGltO.js.map +0 -1
- package/dist/assets/TerminalTab-4vbfGB2S.css +0 -1
- package/dist/assets/TerminalTab-gDAKYgTX.js +0 -3
- package/dist/assets/TerminalTab-gDAKYgTX.js.map +0 -1
- package/dist/assets/UsersView-Bvs30U8o.js +0 -2
- package/dist/assets/UsersView-Bvs30U8o.js.map +0 -1
- package/dist/assets/UsersView-DZhhpsnX.css +0 -1
- package/dist/assets/VariablesView-Xn79UX3b.js +0 -2
- package/dist/assets/VariablesView-Xn79UX3b.js.map +0 -1
- package/dist/assets/VariablesView-m1RjJR-x.css +0 -1
- package/dist/assets/index-WMJXAtiA.css +0 -1
- package/dist/assets/index-jLPJ82ve.js.map +0 -1
- package/dist/assets/useFormat-CKJxQp_i.js +0 -2
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"OverviewTab-B2aCu4e0.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';\nimport InfoHint from '../../components/InfoHint.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 <div class=\"phead\">\n <h2 class=\"section\">Resolved spec</h2>\n <span class=\"grow\" />\n <RouterLink v-if=\"dep.detail\" :to=\"`/submit?from=${encodeURIComponent(dep.id)}`\">\n Duplicate\n </RouterLink>\n <span v-if=\"dep.detail\" class=\"mute\">·</span>\n <RouterLink v-if=\"dep.detail\" :to=\"`/submit/${encodeURIComponent(dep.id)}`\">\n Edit spec & compose →\n </RouterLink>\n </div>\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\">\n stack\n <InfoHint label=\"the difference between id and stack\">\n The <b>id</b> is what you named this deployment. The <b>stack</b> is what its spec\n resolved to once the variables were filled in — the compose project name, the hostname\n label, and <code>$STACK</code> inside every hook.\n </InfoHint>\n </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\">\n <b>{{ row.specName }}</b>\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n — stored, and shared with any other deployment using 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 : 'none' }}\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?.subdomains?.length\">\n <span class=\"k\">\n subdomains\n <InfoHint label=\"how wildcard subdomains work\">\n Anything one label under these hosts reaches the same service the bare host does.\n An exact hostname you route yourself always wins over the wildcard.\n <template v-if=\"dep.detail.compose.subdomains.some((s) => s.depth === 'any')\">\n A route marked <b>any depth</b> works over HTTP only — no TLS certificate can cover\n more than one label.\n </template>\n </InfoHint>\n </span>\n <span class=\"v\">\n <div v-for=\"s in dep.detail.compose.subdomains\" :key=\"s.profile\">\n <span class=\"mono\">*.{{ s.host }}</span>\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n → {{ s.profile }}<template v-if=\"s.depth === 'any'\"> · any depth, HTTP only</template>\n </span>\n </div>\n </span>\n </li>\n <li v-if=\"dep.detail.compose\">\n <span class=\"k\">overlays</span>\n <span class=\"v\">\n <span v-for=\"o in dep.detail.compose.overlays\" :key=\"o\" class=\"mono\" 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\">{{ stamp(dep.detail.createdAt) }}</span></li>\n <li><span class=\"k\">updated</span><span class=\"v\">{{ stamp(dep.detail.updatedAt) }}</span></li>\n </ul>\n </template>\n </section>\n</template>\n"],"names":["kindBlurb","computed","isShared","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_unref","dep","_createBlock","_component_RouterLink","_hoisted_3","SkeletonList","_hoisted_5","_hoisted_6","_createVNode","InfoHint","_hoisted_7","_toDisplayString","_hoisted_8","_hoisted_9","row","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_Fragment","_renderList","p","_hoisted_15","_hoisted_16","_hoisted_17","s","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","o","_hoisted_23","_hoisted_24","_createTextVNode","_hoisted_25","_hoisted_26","stamp","_hoisted_27","_hoisted_4"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{s as i,b as l}from"./useFormat-CKJxQp_i.js";import{d as r,Y as m,a4 as c,o as u,c as d,u as f,t as p,r as v,C as w}from"./index-jLPJ82ve.js";const D=["datetime","title"],I=15e3,C=r({__name:"RelativeTime",props:{at:{}},setup(e){const o=e,n=v(Date.now());let t=null,a=0;m(()=>{a++,t||(t=setInterval(()=>n.value=Date.now(),I))}),c(()=>{a--,a===0&&t&&(clearInterval(t),t=null)});const s=w(()=>(n.value,l(o.at)));return(S,_)=>(u(),d("time",{datetime:e.at?new Date(e.at).toISOString():void 0,title:f(i)(e.at)},p(s.value),9,D))}});export{C as _};
|
|
2
|
-
//# sourceMappingURL=RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as z,H as I,p as A,c as n,x as w,g as p,F as u,a as e,s as m,f as a,t as r,e as k,w as g,_ as T,r as y,C as c,j as D,o as s,D as $,E as _,A as L,B}from"./index-jLPJ82ve.js";import{v as E,d as C}from"./useDeployment-Bgo0U4-I.js";import{_ as V}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{_ as K}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";import"./useVars-VN9gYdR6.js";const U={key:0,class:"banner warn"},H={key:0,class:"banner ok"},W={class:"panel"},F={class:"phead"},j={class:"mute",style:{"font-size":"var(--t-sm)"}},M={key:0,class:"cards"},O={class:"stagger"},Q={"data-label":"url"},q=["href"],G={key:1,class:"mono mute"},J={key:0,class:"mute"},X={"data-label":"forwards to"},Y={key:0,class:"mono"},Z={key:1,class:"s-failed"},ee={class:"mute",style:{"font-size":"var(--t-sm)"}},te={"data-label":"router"},se={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},ne={"data-label":"tls"},oe={key:0,class:"badge ok"},re={key:1,class:"badge off"},le={key:1,class:"banner warn"},ae={class:"panel"},ie={class:"phead"},de={class:"mute",style:{"font-size":"var(--t-sm)"}},ue={key:0,class:"cards"},he={class:"stagger"},pe={class:"name","data-label":"service"},me={class:"mute",style:{"font-size":"var(--t-sm)"}},ve={"data-label":"state"},ce={"data-label":"ports"},fe={class:"mono"},ke={key:0,class:"mute"},ge={key:0,class:"mute"},ye={"data-label":"networks"},_e={key:0,class:"mute mono",style:{"font-size":"var(--t-sm)"}},be={class:"dim","data-label":"image"},we={key:1,class:"mute"},xe=z({__name:"RuntimeTab",setup(Te){const l=y(null),v=y(""),f=y(!0);async function b(){const i=await L.get(`/api/deployments/${encodeURIComponent(C.id)}/runtime${E.value}`);if(f.value=!1,!i.ok){v.value=B(i,"inspect what is running");return}v.value="",l.value=i.body}I(()=>C.id,()=>{f.value=!0,b()},{immediate:!0}),A(()=>void b(),8e3);const R=c(()=>{var i;return((i=l.value)==null?void 0:i.findings.filter(t=>t.level==="error"))??[]}),P=c(()=>{var i;return((i=l.value)==null?void 0:i.findings.filter(t=>t.level==="warn"))??[]}),S=c(()=>{var i;return((i=l.value)==null?void 0:i.findings.filter(t=>t.level==="info"))??[]}),x=c(()=>{var i;return((i=l.value)==null?void 0:i.containers.filter(t=>t.state==="running").length)??0});return(i,t)=>{const N=D("RouterLink");return s(),n("div",null,[v.value?(s(),w(K,{key:0,text:v.value,title:"Could not inspect this deployment."},null,8,["text"])):p("",!0),f.value&&!l.value?(s(),w(V,{key:1,rows:5})):l.value?(s(),n(u,{key:2},[l.value.reachable?(s(),n(u,{key:1},[(s(!0),n(u,null,m(R.value,(o,h)=>(s(),n("div",{key:`e${h}`,class:"banner failed"},[t[1]||(t[1]=e("b",null,"Blocking",-1)),e("p",null,r(o.message),1)]))),128)),(s(!0),n(u,null,m(P.value,(o,h)=>(s(),n("div",{key:`w${h}`,class:"banner warn"},[t[2]||(t[2]=e("b",null,"Worth checking",-1)),e("p",null,r(o.message),1)]))),128)),(s(!0),n(u,null,m(S.value,(o,h)=>(s(),n("div",{key:`i${h}`,class:"banner plain"},[e("p",null,r(o.message),1)]))),128)),!l.value.findings.length&&l.value.containers.length?(s(),n("div",H,[...t[3]||(t[3]=[e("b",null,"Nothing looks wrong.",-1),e("p",null," Every container is attached to the ingress network and every router has a rule and a port. If a hostname still fails, the next suspects are DNS and the certificate. ",-1)])])):p("",!0),e("section",W,[e("div",F,[t[5]||(t[5]=e("h2",{class:"section"},"Routing",-1)),t[6]||(t[6]=e("span",{class:"grow"},null,-1)),e("span",j,[a(" TLS: "+r(l.value.challenge==="unknown"?"unknown":l.value.challenge)+" ",1),k(T,{label:"why the TLS mode matters here",align:"end"},{default:g(()=>[...t[4]||(t[4]=[a(" Under HTTP-01 every per-PR router needs ",-1),e("code",null,"tls.certresolver=le",-1),a(", because each hostname resolves its own certificate. Under DNS-01 that inverts — one always-on router holds the wildcard, and a per-PR certresolver makes each PR order its own and burn the weekly limit. Read from the running Traefik's own flags, not configured here. ",-1)])]),_:1})])]),l.value.routes.length?(s(),n("table",M,[e("thead",null,[e("tr",null,[t[9]||(t[9]=e("th",null,"url",-1)),e("th",null,[t[8]||(t[8]=a(" forwards to ",-1)),k(T,{label:"how a URL reaches a port"},{default:g(()=>[...t[7]||(t[7]=[a(" Traefik's docker provider resolves the container's IP on ",-1),e("code",null,"preview-ingress",-1),a(" and dials it on the port from ",-1),e("code",null,"loadbalancer.server.port",-1),a(" — it does ",-1),e("em",null,"not",-1),a(" use ",-1),e("code",null,"service:port",-1),a(" DNS. So the port here is the one inside the container, and publishing a host port is unnecessary. ",-1)])]),_:1})]),t[10]||(t[10]=e("th",null,"router",-1)),t[11]||(t[11]=e("th",null,"tls",-1))])]),e("tbody",O,[(s(!0),n(u,null,m(l.value.routes,(o,h)=>(s(),n("tr",{key:o.router,style:$({"--i":h})},[e("td",Q,[(s(!0),n(u,null,m(o.hosts,d=>(s(),n("div",{key:d},[d.startsWith("(pattern)")?(s(),n("span",G,r(d),1)):(s(),n("a",{key:0,href:`https://${d}`,target:"_blank",rel:"noreferrer"},r(d),9,q))]))),128)),o.hosts.length?p("",!0):(s(),n("span",J,"no host in the rule"))]),e("td",X,[o.target?(s(),n("span",Y,r(o.target),1)):(s(),n("span",Z,r(o.port===null?"no port declared":"not on the ingress network"),1)),e("div",ee,r(o.container),1)]),e("td",te,[a(r(o.router)+" ",1),o.priority?(s(),n("div",se," priority "+r(o.priority),1)):p("",!0)]),e("td",ne,[o.tls?(s(),n("span",oe,r(o.certresolver||"inherited"),1)):(s(),n("span",re,"off"))])],4))),128))])])):(s(),n("div",le,[t[17]||(t[17]=e("b",null,"No routes. Traefik has not been told about this deployment.",-1)),e("p",null,[t[13]||(t[13]=a(" Routes come from labels in ",-1)),t[14]||(t[14]=e("em",null,"your",-1)),t[15]||(t[15]=a(" compose file, not from pstack and not from the ",-1)),k(N,{to:"/routing"},{default:g(()=>[...t[12]||(t[12]=[a("Routing",-1)])]),_:1}),t[16]||(t[16]=a(" page (that is Traefik's file provider — middleware and TLS options, not per-PR routers). A reachable service needs four things: ",-1))]),t[18]||(t[18]=e("pre",{class:"code"},"labels:\n - traefik.enable=true # the host runs exposedbydefault=false\n - traefik.docker.network=preview-ingress # which network Traefik should dial\n - traefik.http.routers.<name>-${STACK}.rule=Host(`<name>-${STACK}.${PREVIEW_DOMAIN}`)\n - traefik.http.services.<name>-${STACK}.loadbalancer.server.port=80\nnetworks: [default, preview-ingress] # and preview-ingress: { external: true }",-1)),t[19]||(t[19]=e("p",{class:"mute"},[a(" Put "),e("code",null,"${STACK}"),a(" in the router and service names: Traefik's namespace is global across the host, so two deployments sharing a router name serve each other's containers. ")],-1))]))]),e("section",ae,[e("div",ie,[t[20]||(t[20]=e("h2",{class:"section"},"Containers",-1)),t[21]||(t[21]=e("span",{class:"grow"},null,-1)),e("span",de,r(x.value)+" of "+r(l.value.containers.length)+" running ",1)]),l.value.containers.length?(s(),n("table",ue,[t[22]||(t[22]=e("thead",null,[e("tr",null,[e("th",null,"service"),e("th",null,"state"),e("th",null,"ports"),e("th",null,"networks"),e("th",null,"image")])],-1)),e("tbody",he,[(s(!0),n(u,null,m(l.value.containers,(o,h)=>(s(),n("tr",{key:o.id,style:$({"--i":h})},[e("td",pe,[a(r(o.service??o.name)+" ",1),e("div",me,r(o.name),1)]),e("td",ve,[e("span",{class:_(o.state==="running"?"s-ok":"s-failed")},r(o.state),3),o.health?(s(),n("div",{key:0,class:_(["mute",o.health==="healthy"?"s-ok":"s-failed"]),style:{"font-size":"var(--t-sm)"}},r(o.health),3)):p("",!0)]),e("td",ce,[(s(!0),n(u,null,m(o.ports,d=>(s(),n("div",{key:`${d.containerPort}/${d.protocol}`},[e("span",fe,r(d.containerPort),1),d.hostPort?(s(),n("span",ke," ← host "+r(d.hostPort),1)):p("",!0)]))),128)),o.ports.length?p("",!0):(s(),n("span",ge,"none exposed"))]),e("td",ye,[(s(!0),n(u,null,m(o.networks,d=>(s(),n("div",{key:d},[e("span",{class:_(d==="preview-ingress"?"s-ok":"")},r(d),3)]))),128)),o.ingressIp?(s(),n("div",_e,r(o.ingressIp),1)):p("",!0)]),e("td",be,r(o.image),1)],4))),128))])])):(s(),n("p",we," No containers for this stack. If you expected some, check that their profile is one the spec selects — a service behind a profile that is not enabled is never started. "))])],64)):(s(),n("div",U,[...t[0]||(t[0]=[e("b",null,"Docker did not answer.",-1),e("p",null," Nothing below could be determined — this is not the same as nothing running. Check the daemon and the socket mount, then refresh. ",-1)])]))],64)):p("",!0)])}}});export{xe as default};
|
|
2
|
-
//# sourceMappingURL=RuntimeTab-CiSAV3D0.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RuntimeTab-CiSAV3D0.js","sources":["../../src/views/tabs/RuntimeTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * What is actually running, and what Traefik was actually told about it.\n *\n * THE QUESTION THIS ANSWERS. \"The hostname does not work.\" Nothing in this UI could answer it: the\n * registry shows what was *submitted*, the logs show what the container *says*, and the reason a\n * request 404s is in neither — it is in the container's Traefik labels, which pstack never writes.\n *\n * The routing table is the direct answer to \"which URL maps to which port\". Traefik does **not** dial\n * `service_name:port`: its docker provider resolves the container's IP on the ingress network and\n * dials that, on the port from the `loadbalancer.server.port` label. That is why a service must be\n * attached to `preview-ingress`, why the port is the *container-internal* one, and why publishing a\n * host port is unnecessary. The `target` column shows the address Traefik assembled, so a missing\n * half is visible rather than inferred.\n *\n * Findings come first because they are the reason anyone opened this tab.\n */\nimport { computed, ref, watch } from 'vue';\nimport { api, problem } from '../../api/client';\nimport type { RuntimeResponse } from '../../api/types';\nimport { dep, varsQuery } from '../../composables/useDeployment';\nimport { usePolling } from '../../composables/usePolling';\nimport SkeletonList from '../../components/SkeletonList.vue';\nimport ErrorNote from '../../components/ErrorNote.vue';\nimport InfoHint from '../../components/InfoHint.vue';\n\nconst rt = ref<RuntimeResponse | null>(null);\nconst error = ref('');\nconst loading = ref(true);\n\nasync function load(): Promise<void> {\n const r = await api.get<RuntimeResponse>(\n `/api/deployments/${encodeURIComponent(dep.id)}/runtime${varsQuery.value}`,\n );\n loading.value = false;\n if (!r.ok) {\n error.value = problem(r, 'inspect what is running');\n return;\n }\n error.value = '';\n rt.value = r.body;\n}\n\nwatch(() => dep.id, () => { loading.value = true; void load(); }, { immediate: true });\n// Containers change state during a deploy; the poll pauses itself when the tab is hidden.\nusePolling(() => void load(), 8000);\n\nconst errors = computed(() => rt.value?.findings.filter((f) => f.level === 'error') ?? []);\nconst warns = computed(() => rt.value?.findings.filter((f) => f.level === 'warn') ?? []);\nconst infos = computed(() => rt.value?.findings.filter((f) => f.level === 'info') ?? []);\nconst running = computed(() => rt.value?.containers.filter((c) => c.state === 'running').length ?? 0);\n</script>\n\n<template>\n <div>\n <ErrorNote v-if=\"error\" :text=\"error\" title=\"Could not inspect this deployment.\" />\n <SkeletonList v-if=\"loading && !rt\" :rows=\"5\" />\n\n <template v-else-if=\"rt\">\n <!--\n `reachable: false` is not \"nothing is running\". The stack may be perfectly healthy while this\n process cannot reach the Docker socket, and reporting that as empty is how a UI tells someone\n their live preview is gone.\n -->\n <div v-if=\"!rt.reachable\" class=\"banner warn\">\n <b>Docker did not answer.</b>\n <p>\n Nothing below could be determined — this is not the same as nothing running. Check the\n daemon and the socket mount, then refresh.\n </p>\n </div>\n\n <template v-else>\n <!-- Findings first: this tab exists because something is not working. -->\n <div v-for=\"(f, i) in errors\" :key=\"`e${i}`\" class=\"banner failed\">\n <b>Blocking</b>\n <p>{{ f.message }}</p>\n </div>\n <div v-for=\"(f, i) in warns\" :key=\"`w${i}`\" class=\"banner warn\">\n <b>Worth checking</b>\n <p>{{ f.message }}</p>\n </div>\n <div v-for=\"(f, i) in infos\" :key=\"`i${i}`\" class=\"banner plain\">\n <p>{{ f.message }}</p>\n </div>\n <div v-if=\"!rt.findings.length && rt.containers.length\" class=\"banner ok\">\n <b>Nothing looks wrong.</b>\n <p>\n Every container is attached to the ingress network and every router has a rule and a port.\n If a hostname still fails, the next suspects are DNS and the certificate.\n </p>\n </div>\n\n <!-- ============================ routing ============================ -->\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Routing</h2>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n TLS: {{ rt.challenge === 'unknown' ? 'unknown' : rt.challenge }}\n <InfoHint label=\"why the TLS mode matters here\" align=\"end\">\n Under HTTP-01 every per-PR router needs <code>tls.certresolver=le</code>, because each\n hostname resolves its own certificate. Under DNS-01 that inverts — one always-on router\n holds the wildcard, and a per-PR certresolver makes each PR order its own and burn the\n weekly limit. Read from the running Traefik's own flags, not configured here.\n </InfoHint>\n </span>\n </div>\n\n <table v-if=\"rt.routes.length\" class=\"cards\">\n <thead>\n <tr>\n <th>url</th>\n <th>\n forwards to\n <InfoHint label=\"how a URL reaches a port\">\n Traefik's docker provider resolves the container's IP on\n <code>preview-ingress</code> and dials it on the port from\n <code>loadbalancer.server.port</code> — it does <em>not</em> use\n <code>service:port</code> DNS. So the port here is the one inside the container,\n and publishing a host port is unnecessary.\n </InfoHint>\n </th>\n <th>router</th>\n <th>tls</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(r, i) in rt.routes\" :key=\"r.router\" :style=\"{ '--i': i }\">\n <td data-label=\"url\">\n <div v-for=\"h in r.hosts\" :key=\"h\">\n <a v-if=\"!h.startsWith('(pattern)')\" :href=\"`https://${h}`\" target=\"_blank\" rel=\"noreferrer\">\n {{ h }}\n </a>\n <span v-else class=\"mono mute\">{{ h }}</span>\n </div>\n <span v-if=\"!r.hosts.length\" class=\"mute\">no host in the rule</span>\n </td>\n <td data-label=\"forwards to\">\n <span v-if=\"r.target\" class=\"mono\">{{ r.target }}</span>\n <span v-else class=\"s-failed\">\n {{ r.port === null ? 'no port declared' : 'not on the ingress network' }}\n </span>\n <div class=\"mute\" style=\"font-size: var(--t-sm)\">{{ r.container }}</div>\n </td>\n <td data-label=\"router\">\n {{ r.router }}\n <div v-if=\"r.priority\" class=\"mute\" style=\"font-size: var(--t-sm)\">\n priority {{ r.priority }}\n </div>\n </td>\n <td data-label=\"tls\">\n <span v-if=\"r.tls\" class=\"badge ok\">{{ r.certresolver || 'inherited' }}</span>\n <span v-else class=\"badge off\">off</span>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!--\n The reported failure, stated where it will be looked for. pstack does not write these\n labels — the deployment's own compose file does — so the fix is in the user's file.\n -->\n <div v-else class=\"banner warn\">\n <b>No routes. Traefik has not been told about this deployment.</b>\n <p>\n Routes come from labels in <em>your</em> compose file, not from pstack and not from the\n <RouterLink to=\"/routing\">Routing</RouterLink> page (that is Traefik's file provider —\n middleware and TLS options, not per-PR routers). A reachable service needs four things:\n </p>\n <pre class=\"code\">labels:\n - traefik.enable=true # the host runs exposedbydefault=false\n - traefik.docker.network=preview-ingress # which network Traefik should dial\n - traefik.http.routers.<name>-${STACK}.rule=Host(`<name>-${STACK}.${PREVIEW_DOMAIN}`)\n - traefik.http.services.<name>-${STACK}.loadbalancer.server.port=80\nnetworks: [default, preview-ingress] # and preview-ingress: { external: true }</pre>\n <p class=\"mute\">\n Put <code>${STACK}</code> in the router and service names: Traefik's namespace is global\n across the host, so two deployments sharing a router name serve each other's containers.\n </p>\n </div>\n </section>\n\n <!-- ============================ containers ============================ -->\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Containers</h2>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n {{ running }} of {{ rt.containers.length }} running\n </span>\n </div>\n\n <table v-if=\"rt.containers.length\" class=\"cards\">\n <thead>\n <tr>\n <th>service</th>\n <th>state</th>\n <th>ports</th>\n <th>networks</th>\n <th>image</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(c, i) in rt.containers\" :key=\"c.id\" :style=\"{ '--i': i }\">\n <td class=\"name\" data-label=\"service\">\n {{ c.service ?? c.name }}\n <div class=\"mute\" style=\"font-size: var(--t-sm)\">{{ c.name }}</div>\n </td>\n <td data-label=\"state\">\n <span :class=\"c.state === 'running' ? 's-ok' : 's-failed'\">{{ c.state }}</span>\n <div\n v-if=\"c.health\"\n class=\"mute\"\n style=\"font-size: var(--t-sm)\"\n :class=\"c.health === 'healthy' ? 's-ok' : 's-failed'\"\n >\n {{ c.health }}\n </div>\n </td>\n <td data-label=\"ports\">\n <div v-for=\"p in c.ports\" :key=\"`${p.containerPort}/${p.protocol}`\">\n <span class=\"mono\">{{ p.containerPort }}</span>\n <span v-if=\"p.hostPort\" class=\"mute\"> ← host {{ p.hostPort }}</span>\n </div>\n <span v-if=\"!c.ports.length\" class=\"mute\">none exposed</span>\n </td>\n <td data-label=\"networks\">\n <div v-for=\"n in c.networks\" :key=\"n\">\n <span :class=\"n === 'preview-ingress' ? 's-ok' : ''\">{{ n }}</span>\n </div>\n <div v-if=\"c.ingressIp\" class=\"mute mono\" style=\"font-size: var(--t-sm)\">\n {{ c.ingressIp }}\n </div>\n </td>\n <td class=\"dim\" data-label=\"image\">{{ c.image }}</td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"mute\">\n No containers for this stack. If you expected some, check that their profile is one the\n spec selects — a service behind a profile that is not enabled is never started.\n </p>\n </section>\n </template>\n </template>\n </div>\n</template>\n"],"names":["rt","ref","error","loading","load","r","api","dep","varsQuery","problem","watch","usePolling","errors","computed","_a","f","warns","infos","running","c","_createElementBlock","_createBlock","ErrorNote","SkeletonList","_Fragment","_openBlock","_renderList","i","_cache","_createElementVNode","_toDisplayString","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_createVNode","InfoHint","_hoisted_6","_hoisted_7","_hoisted_8","h","_hoisted_10","_hoisted_9","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_component_RouterLink","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_createTextVNode","_hoisted_28","_hoisted_29","_normalizeClass","_hoisted_30","p","_hoisted_31","_hoisted_32","_hoisted_33","_hoisted_34","n","_hoisted_35","_hoisted_36","_hoisted_37","_hoisted_1"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as w,c as r,a as t,i as p,v as f,u as s,R as o,f as n,e as d,w as v,_ as c,F as V,g as b,t as u,y as a,r as C,C as E,a5 as x,N as B,o as m}from"./index-jLPJ82ve.js";import{_ as k}from"./SelectMenu.vue_vue_type_script_setup_true_lang-B-DTK242.js";import"./Teleport-SesEhfqH.js";const N={class:"panel settings-form"},T={class:"field"},U={class:"mute hint"},A={class:"field"},S={class:"mute hint"},_={class:"row",style:{gap:"8px","margin-top":"4px"}},D=["disabled"],F={key:0,class:"mute"},L={key:1,class:"bad"},R={class:"panel"},M={class:"field"},q={class:"field"},H=w({__name:"SettingsView",setup(I){const i=C(!1),y=E(()=>{var h;return((h=a.health)==null?void 0:h.authEnforced)??null});async function g(){i.value=!0;try{await x(),B(a.healthError?"error":"ok",a.healthError||"Control plane reachable")}finally{i.value=!1}}return(h,e)=>(m(),r("div",null,[e[16]||(e[16]=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",N,[e[11]||(e[11]=t("h2",{class:"phead-title"},"Connection",-1)),t("div",T,[e[6]||(e[6]=t("label",{for:"apiBase"},"API base URL",-1)),p(t("input",{id:"apiBase","onUpdate:modelValue":e[0]||(e[0]=l=>s(o).apiBase=l),type:"url",spellcheck:"false",placeholder:"(same origin)"},null,512),[[f,s(o).apiBase,void 0,{trim:!0}]]),t("div",U,[e[5]||(e[5]=n(" Leave it empty unless you are pointing this page at another host. ",-1)),d(c,{label:"about the API base URL"},{default:v(()=>[...e[4]||(e[4]=[n(" Empty means same-origin, which is how the control stack serves this page — the browser never makes a cross-origin request, so there is no CORS to configure. Set an absolute URL only for a development session against a remote host. ",-1)])]),_:1})])]),t("div",A,[e[8]||(e[8]=t("label",{for:"token"},"Access token",-1)),p(t("input",{id:"token","onUpdate:modelValue":e[1]||(e[1]=l=>s(o).token=l),type:"password",autocomplete:"off",spellcheck:"false",placeholder:"paste the token from pstack init"},null,512),[[f,s(o).token,void 0,{trim:!0}]]),t("div",S,[e[7]||(e[7]=n(" Authenticates every request — signing in with an account does the same job, so set this only for token-based access (the machine token, or a personal token from your account). ",-1)),y.value===!1?(m(),r(V,{key:0},[n(" This server is not asking for one — it only accepts connections from this machine, so a token here is harmless but unnecessary. ")],64)):b("",!0)])]),t("div",_,[t("button",{class:"btn",disabled:i.value,onClick:g},u(i.value?"Checking…":"Test connection"),9,D),s(a).health?(m(),r("span",F,[n(" Connected to pstack "+u(s(a).health.version)+" ",1),d(c,{label:"where data is stored"},{default:v(()=>[e[9]||(e[9]=n(" Deployment records and stored specs live in ",-1)),t("code",null,u(s(a).health.dataDir),1),e[10]||(e[10]=n(" on the host. ",-1))]),_:1})])):s(a).healthError?(m(),r("span",L,u(s(a).healthError),1)):b("",!0)])]),t("section",R,[e[15]||(e[15]=t("h2",{class:"phead-title"},"Appearance",-1)),t("div",M,[e[12]||(e[12]=t("label",{for:"theme"},"Theme",-1)),d(k,{modelValue:s(o).theme,"onUpdate:modelValue":e[2]||(e[2]=l=>s(o).theme=l),id:"theme",label:"Theme",options:[{value:"system",label:"Follow the system"},{value:"dark",label:"Dark"},{value:"light",label:"Light"}]},null,8,["modelValue"])]),t("div",q,[e[13]||(e[13]=t("label",{for:"motion"},"Motion",-1)),d(k,{modelValue:s(o).motion,"onUpdate:modelValue":e[3]||(e[3]=l=>s(o).motion=l),id:"motion",label:"Motion",options:[{value:"system",label:"Follow the system"},{value:"full",label:"Always animate"},{value:"none",label:"No animation"}]},null,8,["modelValue"]),e[14]||(e[14]=t("div",{class:"mute hint"}," “Follow the system” uses your device's reduce-motion setting. The other two override it, in either direction. ",-1))])])]))}});export{H as default};
|
|
2
|
-
//# sourceMappingURL=SettingsView-BEha1Bt3.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsView-BEha1Bt3.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';\nimport InfoHint from '../components/InfoHint.vue';\nimport SelectMenu from '../components/SelectMenu.vue';\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 <!-- `settings-form` caps the field widths: a URL input stretched to the panel's full 1500px\n reads as a text editor, not a form — see the rule in app.css. -->\n <section class=\"panel settings-form\">\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 it empty unless you are pointing this page at another host.\n <InfoHint label=\"about the API base URL\">\n Empty means same-origin, which is how the control stack serves this page — the browser\n never makes a cross-origin request, so there is no CORS to configure. Set an absolute\n URL only for a development session against a remote host.\n </InfoHint>\n </div>\n </div>\n\n <div class=\"field\">\n <label for=\"token\">Access token</label>\n <input\n id=\"token\"\n v-model.trim=\"settings.token\"\n type=\"password\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n placeholder=\"paste the token from pstack init\"\n />\n <div class=\"mute hint\">\n Authenticates every request — signing in with an account does the same job, so set this\n only for token-based access (the machine token, or a personal token from your account).\n <template v-if=\"authEnforced === false\">\n This server is not asking for one — it only accepts connections from this machine, so a\n 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 Connected to pstack {{ state.health.version }}\n <InfoHint label=\"where data is stored\">\n Deployment records and stored specs live in\n <code>{{ state.health.dataDir }}</code> on the host.\n </InfoHint>\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 <SelectMenu\n v-model=\"settings.theme\" id=\"theme\"\n label=\"Theme\"\n :options=\"[\n { value: 'system', label: 'Follow the system' },\n { value: 'dark', label: 'Dark' },\n { value: 'light', label: 'Light' },\n ]\"\n />\n </div>\n\n <div class=\"field\">\n <label for=\"motion\">Motion</label>\n <SelectMenu\n v-model=\"settings.motion\" id=\"motion\"\n label=\"Motion\"\n :options=\"[\n { value: 'system', label: 'Follow the system' },\n { value: 'full', label: 'Always animate' },\n { value: 'none', label: 'No animation' },\n ]\"\n />\n <div class=\"mute hint\">\n “Follow the system” uses your device's reduce-motion setting. The other two override it,\n in either direction.\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","_createVNode","InfoHint","_hoisted_4","_hoisted_5","_Fragment","_hoisted_6","_hoisted_7","_openBlock","_hoisted_8","_createTextVNode","_toDisplayString","_hoisted_9","_hoisted_10","_hoisted_11","SelectMenu","_hoisted_12"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as R,H as C,y as V,O as $,c as n,a as s,e as c,w as d,t as o,E as D,g as m,x as h,F as b,f as l,_ as A,s as I,u as N,r as v,C as L,j as z,o as t,R as E,A as S,B as j}from"./index-jLPJ82ve.js";import{s as B}from"./useFormat-CKJxQp_i.js";import{_ as q}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{_ as F}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";const H={class:"page-head"},T={class:"mute",style:{"font-size":"var(--t-sm)"}},U={style:{"font-size":"var(--t-xl)"}},O={class:"sub"},P={key:0},W={key:1},G={key:2},J={key:3,class:"mute"},K={key:1,class:"panel"},M={class:"banner failed"},Q={class:"panel"},X={key:1,class:"kvlist"},Y={class:"k"},Z={class:"v"},ee={key:0},se={key:1,class:"mute"},te={class:"v"},ne={key:1,class:"mute"},ae={class:"v"},oe={class:"v"},le={key:0,class:"panel"},ie={key:0,class:"banner plain"},de={key:1,class:"code"},re={key:2,class:"mute"},ve=R({__name:"SpecDetailView",props:{name:{}},setup(y){const f=y,a=v(null),r=v(!0),u=v(""),p=v(!1);async function g(){r.value=!0,p.value=!1;const i=await S.getAuthed(`/api/specs/${encodeURIComponent(f.name)}`);if(r.value=!1,i.status===404){p.value=!0;return}if(!i.ok){u.value=j(i,"load this spec");return}u.value="",a.value=i.body}C(()=>f.name,g,{immediate:!0}),C(()=>E.token,g),V.deploymentsLoaded||$();const w=L(()=>V.deployments.filter(i=>i.specName===f.name));return(i,e)=>{var x;const k=z("RouterLink");return t(),n("div",null,[s("div",H,[s("div",null,[s("div",T,[c(k,{to:"/specs"},{default:d(()=>[...e[0]||(e[0]=[l("← specs",-1)])]),_:1})]),s("h1",U,o(y.name),1),s("div",O,[(x=a.value)!=null&&x.description?(t(),n("span",P,o(a.value.description),1)):p.value?(t(),n("span",W,"not found")):r.value?(t(),n("span",G,"loading…")):(t(),n("span",J,"no description"))])]),e[1]||(e[1]=s("span",{class:"grow"},null,-1)),a.value?(t(),n("span",{key:0,class:D(["badge",a.value.kind])},o(a.value.kind),3)):m("",!0)]),u.value?(t(),h(F,{key:0,text:u.value,title:"Could not load this spec."},null,8,["text"])):m("",!0),p.value?(t(),n("section",K,[s("div",M,[s("b",null,"No spec named “"+o(y.name)+"”.",1),e[2]||(e[2]=s("p",null,"It may have been deleted, or this is an older link.",-1))])])):(t(),n(b,{key:2},[s("section",Q,[r.value?(t(),h(q,{key:0,rows:4})):a.value?(t(),n("ul",X,[s("li",null,[s("span",Y,[e[4]||(e[4]=l(" needs ",-1)),c(A,{label:"what needs means"},{default:d(()=>[...e[3]||(e[3]=[l(" Variables the spec uses but does not set. Every deployment referencing it supplies these, which is how one spec serves many stacks — and a missing one is refused by name rather than filled in blank. ",-1)])]),_:1})]),s("span",Z,[a.value.requiredVars.length?(t(),n("b",ee,o(a.value.requiredVars.join(", ")),1)):(t(),n("span",se,"nothing — every value is fixed by the spec"))])]),s("li",null,[e[6]||(e[6]=s("span",{class:"k"},"used by",-1)),s("span",te,[w.value.length?(t(),n(b,{key:0},[(t(!0),n(b,null,I(w.value,_=>(t(),h(k,{key:_.id,to:`/deployments/${encodeURIComponent(_.id)}`,style:{"margin-right":"10px"}},{default:d(()=>[l(o(_.id),1)]),_:2},1032,["to"]))),128)),c(A,{label:"why this matters"},{default:d(()=>[...e[5]||(e[5]=[l(" Deleting a spec while a deployment still references it is refused: that deployment could no longer be resolved, and one that cannot be resolved can never be torn down. ",-1)])]),_:1})],64)):(t(),n("span",ne,"no deployments reference it"))])]),s("li",null,[e[7]||(e[7]=s("span",{class:"k"},"created",-1)),s("span",ae,o(N(B)(a.value.createdAt)),1)]),s("li",null,[e[8]||(e[8]=s("span",{class:"k"},"updated",-1)),s("span",oe,o(N(B)(a.value.updatedAt)),1)])])):m("",!0)]),a.value?(t(),n("section",le,[e[13]||(e[13]=s("div",{class:"phead"},[s("h2",{class:"section"},"Source")],-1)),a.value.sourceWithheld?(t(),n("div",ie,[e[12]||(e[12]=s("b",null,"Hidden without an access token.",-1)),s("p",null,[e[10]||(e[10]=l(" A spec's hooks are shell commands, and they often carry a password or an API token inline — so the server does not send the file to an unauthenticated reader. ",-1)),c(k,{to:"/settings"},{default:d(()=>[...e[9]||(e[9]=[l("Add your token",-1)])]),_:1}),e[11]||(e[11]=l(" to see it. ",-1))])])):a.value.source?(t(),n("pre",de,o(a.value.source),1)):(t(),n("p",re,"The server returned no source for this spec."))])):m("",!0)],64))])}}});export{ve as default};
|
|
2
|
-
//# sourceMappingURL=SpecDetailView-l86eWf2v.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SpecDetailView-l86eWf2v.js","sources":["../../src/views/SpecDetailView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One stored spec: what it needs, who uses it, and its source.\n *\n * THE SOURCE IS A RESTRICTED READ. Everything else on this API can be read without a token, but a\n * spec's hook bodies are shell strings that routinely carry a credential inline, so the server sends\n * `source` only to an authenticated request and sets `sourceWithheld` otherwise. That is why this\n * one view uses `getAuthed`, and why \"no token\" renders an explanation rather than an empty editor —\n * a blank box would read as an empty spec, which is a different and much more alarming fact.\n */\nimport { computed, ref, watch } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { SpecDetail } from '../api/types';\nimport { state, loadDeployments } from '../composables/useControlPlane';\nimport { settings } from '../composables/useSettings';\nimport { stamp } from '../composables/useFormat';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\n\nconst props = defineProps<{ name: string }>();\n\nconst spec = ref<SpecDetail | null>(null);\nconst loading = ref(true);\nconst error = ref('');\nconst missing = ref(false);\n\nasync function load(): Promise<void> {\n loading.value = true;\n missing.value = false;\n const r = await api.getAuthed<SpecDetail>(`/api/specs/${encodeURIComponent(props.name)}`);\n loading.value = false;\n if (r.status === 404) {\n missing.value = true;\n return;\n }\n if (!r.ok) {\n error.value = problem(r, 'load this spec');\n return;\n }\n error.value = '';\n spec.value = r.body;\n}\n\nwatch(() => props.name, load, { immediate: true });\n// Re-fetch when a token is pasted: the same URL returns strictly more with one.\nwatch(() => settings.token, load);\n\nif (!state.deploymentsLoaded) void loadDeployments();\n\n/** Deployments that would become unresolvable if this spec were deleted. */\nconst users = computed(() => state.deployments.filter((d) => d.specName === props.name));\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <div class=\"mute\" style=\"font-size: var(--t-sm)\">\n <RouterLink to=\"/specs\">← specs</RouterLink>\n </div>\n <h1 style=\"font-size: var(--t-xl)\">{{ name }}</h1>\n <div class=\"sub\">\n <span v-if=\"spec?.description\">{{ spec.description }}</span>\n <span v-else-if=\"missing\">not found</span>\n <span v-else-if=\"loading\">loading…</span>\n <span v-else class=\"mute\">no description</span>\n </div>\n </div>\n <span class=\"grow\" />\n <span v-if=\"spec\" class=\"badge\" :class=\"spec.kind\">{{ spec.kind }}</span>\n </div>\n\n <ErrorNote v-if=\"error\" :text=\"error\" title=\"Could not load this spec.\" />\n\n <section v-if=\"missing\" class=\"panel\">\n <div class=\"banner failed\">\n <b>No spec named “{{ name }}”.</b>\n <p>It may have been deleted, or this is an older link.</p>\n </div>\n </section>\n\n <template v-else>\n <section class=\"panel\">\n <SkeletonList v-if=\"loading\" :rows=\"4\" />\n <ul v-else-if=\"spec\" class=\"kvlist\">\n <li>\n <span class=\"k\">\n needs\n <InfoHint label=\"what needs means\">\n Variables the spec uses but does not set. Every deployment referencing it supplies\n these, which is how one spec serves many stacks — and a missing one is refused by\n name rather than filled in blank.\n </InfoHint>\n </span>\n <span class=\"v\">\n <b v-if=\"spec.requiredVars.length\">{{ spec.requiredVars.join(', ') }}</b>\n <span v-else class=\"mute\">nothing — every value is fixed by the spec</span>\n </span>\n </li>\n <li>\n <span class=\"k\">used by</span>\n <span class=\"v\">\n <template v-if=\"users.length\">\n <RouterLink\n v-for=\"d in users\"\n :key=\"d.id\"\n :to=\"`/deployments/${encodeURIComponent(d.id)}`\"\n style=\"margin-right: 10px\"\n >{{ d.id }}</RouterLink\n >\n <InfoHint label=\"why this matters\">\n Deleting a spec while a deployment still references it is refused: that deployment\n could no longer be resolved, and one that cannot be resolved can never be torn\n down.\n </InfoHint>\n </template>\n <span v-else class=\"mute\">no deployments reference it</span>\n </span>\n </li>\n <li><span class=\"k\">created</span><span class=\"v\">{{ stamp(spec.createdAt) }}</span></li>\n <li><span class=\"k\">updated</span><span class=\"v\">{{ stamp(spec.updatedAt) }}</span></li>\n </ul>\n </section>\n\n <section v-if=\"spec\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Source</h2>\n </div>\n\n <!--\n Withheld, not empty. The server never sent the source, so there is nothing here to reveal —\n the same reasoning as the masked variables: an affordance that promises plaintext the page\n does not have would be a lie.\n -->\n <div v-if=\"spec.sourceWithheld\" class=\"banner plain\">\n <b>Hidden without an access token.</b>\n <p>\n A spec's hooks are shell commands, and they often carry a password or an API token\n inline — so the server does not send the file to an unauthenticated reader.\n <RouterLink to=\"/settings\">Add your token</RouterLink> to see it.\n </p>\n </div>\n <pre v-else-if=\"spec.source\" class=\"code\">{{ spec.source }}</pre>\n <p v-else class=\"mute\">The server returned no source for this spec.</p>\n </section>\n </template>\n </div>\n</template>\n"],"names":["props","__props","spec","ref","loading","error","missing","load","r","api","problem","watch","settings","state","loadDeployments","users","computed","d","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","_component_RouterLink","_cache","_hoisted_3","_toDisplayString","_hoisted_4","_a","_hoisted_5","_openBlock","_hoisted_8","_normalizeClass","_createBlock","ErrorNote","_hoisted_9","_hoisted_10","_Fragment","_hoisted_11","SkeletonList","_hoisted_12","_hoisted_13","InfoHint","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_renderList","_createTextVNode","_hoisted_18","_hoisted_19","_unref","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as q,y as k,O as L,c as a,a as t,f as l,e as i,w as y,_ as g,x as C,g as v,F as x,i as T,v as B,s as E,t as d,r as p,A as U,B as $,C as N,j as D,o as s,D as z,E as A}from"./index-jLPJ82ve.js";import{_ as R}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{_ as j}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";import{_ as F}from"./RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js";import{E as I}from"./EquivalentCommand-DyCAI1Cy.js";import"./useFormat-CKJxQp_i.js";import"./_plugin-vue_export-helper-DlAUqK2U.js";const M={class:"page-head"},P={class:"sub"},O={key:1,class:"panel"},W={key:2,class:"panel"},G={class:"field",style:{"max-width":"320px","margin-bottom":"var(--s4)"}},H={class:"cards","data-testid":"specs.list"},J={class:"stagger"},K={class:"name","data-label":"name"},Q={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},X={"data-label":"kind"},Y={"data-label":"needs"},Z={key:0},ee={key:1,class:"mute"},te={"data-label":"used by"},se={key:0},ne={key:1,class:"mute"},oe={class:"dim nowrap","data-label":"updated"},ae={key:0},le={colspan:"5",class:"mute"},ie={key:2,class:"banner plain"},fe=q({__name:"SpecsView",setup(de){const u=p([]),f=p(!1),m=p(""),h=p(!1),c=p("");async function V(){const o=await U.get("/api/specs");if(f.value=!0,o.status===404){h.value=!0;return}if(!o.ok){m.value=$(o,"load specs");return}m.value="",u.value=o.body.specs??[]}V(),k.deploymentsLoaded||L();const b=N(()=>{const o=new Map;for(const e of k.deployments){if(!e.specName)continue;const r=o.get(e.specName)??[];r.push(e.id),o.set(e.specName,r)}return o}),_=N(()=>{const o=c.value.trim().toLowerCase();return o?u.value.filter(e=>e.name.toLowerCase().includes(o)||(e.description??"").toLowerCase().includes(o)):u.value});return(o,e)=>{const r=D("RouterLink");return s(),a("div",null,[t("div",M,[t("div",null,[e[3]||(e[3]=t("h1",null,"Specs",-1)),t("div",P,[e[2]||(e[2]=l(" Stored once, used by any number of deployments ",-1)),i(g,{label:"why store a spec"},{default:y(()=>[...e[1]||(e[1]=[l(" Without this, every deployment carries its own copy — 50 open pull requests meant 50 identical files, and fixing a teardown step meant re-submitting it 50 times. ",-1)])]),_:1})])])]),m.value?(s(),C(j,{key:0,text:m.value,title:"Could not load the specs."},null,8,["text"])):v("",!0),h.value?(s(),a("section",O,[...e[4]||(e[4]=[t("div",{class:"banner plain"},[t("b",null,"This server has no stored specs."),t("p",null," It is an older build of pstack — every deployment carries its own copy of its spec, which still works. Upgrade the host to store specs and reference them by name. ")],-1)])])):(s(),a("section",W,[f.value?u.value.length?(s(),a(x,{key:1},[t("div",G,[e[5]||(e[5]=t("label",{for:"q"},"Search",-1)),T(t("input",{id:"q","onUpdate:modelValue":e[0]||(e[0]=n=>c.value=n),type:"search",placeholder:"name or description"},null,512),[[B,c.value]])]),t("table",H,[t("thead",null,[t("tr",null,[e[8]||(e[8]=t("th",null,"name",-1)),e[9]||(e[9]=t("th",null,"kind",-1)),t("th",null,[e[7]||(e[7]=l(" needs ",-1)),i(g,{label:"what needs means"},{default:y(()=>[...e[6]||(e[6]=[l(" Variables the spec uses but does not set. Every deployment referencing it must supply these, which is how one spec serves many stacks. ",-1)])]),_:1})]),e[10]||(e[10]=t("th",null,"used by",-1)),e[11]||(e[11]=t("th",null,"updated",-1))])]),t("tbody",J,[(s(!0),a(x,null,E(_.value,(n,S)=>{var w;return s(),a("tr",{key:n.name,style:z({"--i":S})},[t("td",K,[i(r,{to:`/specs/${encodeURIComponent(n.name)}`},{default:y(()=>[l(d(n.name),1)]),_:2},1032,["to"]),n.description?(s(),a("div",Q,d(n.description),1)):v("",!0)]),t("td",X,[t("span",{class:A(["badge",n.kind])},d(n.kind),3)]),t("td",Y,[n.requiredVars.length?(s(),a("span",Z,d(n.requiredVars.join(", ")),1)):(s(),a("span",ee,"nothing"))]),t("td",te,[(w=b.value.get(n.name))!=null&&w.length?(s(),a("span",se,d(b.value.get(n.name).length)+" deployment(s) ",1)):(s(),a("span",ne,"nothing yet"))]),t("td",oe,[i(F,{at:n.updatedAt},null,8,["at"])])],4)}),128)),_.value.length?v("",!0):(s(),a("tr",ae,[t("td",le,"No spec matches “"+d(c.value)+"”.",1)]))])])],64)):(s(),a("div",ie,[e[15]||(e[15]=t("b",null,"No specs stored yet.",-1)),t("p",null,[e[13]||(e[13]=l(" A deployment can carry its own spec inline — see ",-1)),i(r,{to:"/submit"},{default:y(()=>[...e[12]||(e[12]=[l("Submit",-1)])]),_:1}),e[14]||(e[14]=l(" — and that is all you need for a one-off. Storing one here pays off when several deployments share it. ",-1))]),e[16]||(e[16]=t("p",{class:"mute"},"This page cannot store one yet — the command below does.",-1)),i(I,{what:"storing a spec",method:"PUT",path:"/api/specs/my-spec",body:{spec:"…the contents of your preview.yml…"},cli:"pstack validate -f preview.yml # then PUT it with the curl above"})])):(s(),C(R,{key:0,rows:3}))]))])}}});export{fe as default};
|
|
2
|
-
//# sourceMappingURL=SpecsView-BfWq69K5.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SpecsView-BfWq69K5.js","sources":["../../src/views/SpecsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Stored specs: what is on this host, and who is using each one.\n *\n * The \"used by\" count is computed from the deployments already in shared state rather than asked\n * for: every row carries `specName`, so the join is free. It matters because deleting a spec that\n * deployments still reference is refused — a deployment that cannot resolve its spec can never be\n * torn down — so knowing the count before you try is the difference between a plan and a 409.\n *\n * A server built before named specs answers 404 here. That is a capability difference, not an\n * error, so it gets its own explanation instead of a red banner.\n */\nimport { computed, ref } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { SpecMeta, SpecsResponse } from '../api/types';\nimport { state, loadDeployments } from '../composables/useControlPlane';\n\nimport SkeletonList from '../components/SkeletonList.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport RelativeTime from '../components/RelativeTime.vue';\nimport EquivalentCommand from '../components/EquivalentCommand.vue';\n\nconst specs = ref<SpecMeta[]>([]);\nconst loaded = ref(false);\nconst error = ref('');\nconst unsupported = ref(false);\nconst needle = ref('');\n\nasync function load(): Promise<void> {\n const r = await api.get<SpecsResponse>('/api/specs');\n loaded.value = true;\n if (r.status === 404) {\n unsupported.value = true;\n return;\n }\n if (!r.ok) {\n error.value = problem(r, 'load specs');\n return;\n }\n error.value = '';\n specs.value = r.body.specs ?? [];\n}\n\nvoid load();\n// The deployment list is what the \"used by\" column joins against; a deep link straight here may\n// arrive before the shell's poll has filled it.\nif (!state.deploymentsLoaded) void loadDeployments();\n\n/** Deployment ids referencing each spec, by spec name. */\nconst users = computed(() => {\n const m = new Map<string, string[]>();\n for (const d of state.deployments) {\n if (!d.specName) continue;\n const list = m.get(d.specName) ?? [];\n list.push(d.id);\n m.set(d.specName, list);\n }\n return m;\n});\n\nconst shown = computed(() => {\n const q = needle.value.trim().toLowerCase();\n if (!q) return specs.value;\n return specs.value.filter(\n (s) => s.name.toLowerCase().includes(q) || (s.description ?? '').toLowerCase().includes(q),\n );\n});\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Specs</h1>\n <div class=\"sub\">\n Stored once, used by any number of deployments\n <InfoHint label=\"why store a spec\">\n Without this, every deployment carries its own copy — 50 open pull requests meant 50\n identical files, and fixing a teardown step meant re-submitting it 50 times.\n </InfoHint>\n </div>\n </div>\n </div>\n\n <ErrorNote v-if=\"error\" :text=\"error\" title=\"Could not load the specs.\" />\n\n <section v-if=\"unsupported\" class=\"panel\">\n <div class=\"banner plain\">\n <b>This server has no stored specs.</b>\n <p>\n It is an older build of pstack — every deployment carries its own copy of its spec, which\n still works. Upgrade the host to store specs and reference them by name.\n </p>\n </div>\n </section>\n\n <section v-else class=\"panel\">\n <SkeletonList v-if=\"!loaded\" :rows=\"3\" />\n\n <template v-else-if=\"specs.length\">\n <div class=\"field\" style=\"max-width: 320px; margin-bottom: var(--s4)\">\n <label for=\"q\">Search</label>\n <input id=\"q\" v-model=\"needle\" type=\"search\" placeholder=\"name or description\" />\n </div>\n\n <table class=\"cards\" data-testid=\"specs.list\">\n <thead>\n <tr>\n <th>name</th>\n <th>kind</th>\n <th>\n needs\n <InfoHint label=\"what needs means\">\n Variables the spec uses but does not set. Every deployment referencing it must\n supply these, which is how one spec serves many stacks.\n </InfoHint>\n </th>\n <th>used by</th>\n <th>updated</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(s, i) in shown\" :key=\"s.name\" :style=\"{ '--i': i }\">\n <td class=\"name\" data-label=\"name\">\n <RouterLink :to=\"`/specs/${encodeURIComponent(s.name)}`\">{{ s.name }}</RouterLink>\n <div v-if=\"s.description\" class=\"mute\" style=\"font-size: var(--t-sm)\">\n {{ s.description }}\n </div>\n </td>\n <td data-label=\"kind\">\n <span class=\"badge\" :class=\"s.kind\">{{ s.kind }}</span>\n </td>\n <td data-label=\"needs\">\n <span v-if=\"s.requiredVars.length\">{{ s.requiredVars.join(', ') }}</span>\n <span v-else class=\"mute\">nothing</span>\n </td>\n <td data-label=\"used by\">\n <span v-if=\"users.get(s.name)?.length\">\n {{ users.get(s.name)!.length }} deployment(s)\n </span>\n <span v-else class=\"mute\">nothing yet</span>\n </td>\n <td class=\"dim nowrap\" data-label=\"updated\"><RelativeTime :at=\"s.updatedAt\" /></td>\n </tr>\n <tr v-if=\"!shown.length\">\n <td colspan=\"5\" class=\"mute\">No spec matches “{{ needle }}”.</td>\n </tr>\n </tbody>\n </table>\n </template>\n\n <!--\n Honest empty state. This page cannot create a spec yet, so pointing at \"New spec\" would be a\n dead end — it names the way that does work instead.\n -->\n <div v-else class=\"banner plain\">\n <b>No specs stored yet.</b>\n <p>\n A deployment can carry its own spec inline — see\n <RouterLink to=\"/submit\">Submit</RouterLink> — and that is all you need for a one-off.\n Storing one here pays off when several deployments share it.\n </p>\n <p class=\"mute\">This page cannot store one yet — the command below does.</p>\n <EquivalentCommand\n what=\"storing a spec\"\n method=\"PUT\"\n path=\"/api/specs/my-spec\"\n :body=\"{ spec: '…the contents of your preview.yml…' }\"\n cli=\"pstack validate -f preview.yml # then PUT it with the curl above\"\n />\n </div>\n </section>\n </div>\n</template>\n"],"names":["specs","ref","loaded","error","unsupported","needle","load","r","api","problem","state","loadDeployments","users","computed","m","d","list","shown","q","s","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_hoisted_2","_createVNode","InfoHint","_createBlock","ErrorNote","_openBlock","_hoisted_3","_hoisted_4","_Fragment","_hoisted_5","$event","_hoisted_6","_hoisted_7","_renderList","i","_hoisted_8","_component_RouterLink","_createTextVNode","_toDisplayString","_hoisted_9","_hoisted_10","_normalizeClass","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_a","_hoisted_15","_hoisted_16","_hoisted_17","RelativeTime","_hoisted_18","_hoisted_19","_hoisted_20","EquivalentCommand","SkeletonList"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as t,c as s,E as n,a as o,f as r,t as l,o as c}from"./index-jLPJ82ve.js";const m=t({__name:"StateBadge",props:{state:{}},setup(a){return(p,e)=>(c(),s("span",{class:n(["badge",a.state])},[e[0]||(e[0]=o("span",{class:"dot"},null,-1)),r(" "+l(a.state),1)],2))}});export{m as _};
|
|
2
|
-
//# sourceMappingURL=StateBadge.vue_vue_type_script_setup_true_lang-DMzCGltO.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StateBadge.vue_vue_type_script_setup_true_lang-DMzCGltO.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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.terminal-host[data-v-3855f639]{min-height:26rem;border:1px solid var(--border);border-radius:var(--r3);background:#0b0e14;padding:var(--s2);overflow:hidden}.terminal-host[data-state=idle][data-v-3855f639]{display:none}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/xterm-D6Mng-DB.js","assets/_commonjsHelpers-CqkleIqs.js","assets/addon-fit-CevVMrUA.js","assets/xterm-DYP7pi_n.css"])))=>i.map(i=>d[i]);
|
|
2
|
-
import{d as q,H as L,K as F,c as v,a as t,f as a,e as C,w as M,_ as W,x as K,g,t as B,r as d,C as T,A as Y,B as j,L as E,M as V,o as c,l as G}from"./index-jLPJ82ve.js";import{_ as J}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";import{_ as N}from"./SelectMenu.vue_vue_type_script_setup_true_lang-B-DTK242.js";import{_ as Q}from"./_plugin-vue_export-helper-DlAUqK2U.js";import"./Teleport-SesEhfqH.js";const X={class:"stack"},Z={class:"banner warn"},ee={class:"row wrap",style:{gap:"var(--s3)","align-items":"end"}},ne={class:"field"},oe={class:"field"},te=["disabled"],le={key:2,class:"badge ok"},se={key:1,class:"hint"},ae={key:2,class:"hint"},ie=["data-state"],re=q({__name:"TerminalTab",setup(ue){const O=G(),_=T(()=>String(O.params.id)),P=["sh","bash","ash","zsh","fish"],S=d(null),m=d(""),u=d(""),h=d("sh"),n=d("idle"),f=d(""),p=d(null),w=V(null),U=V(null),y=V(null);let i=null;const I=T(()=>k.value.map(o=>({value:o.name,label:o.name,hint:o.service??void 0}))),k=T(()=>{var o;return((o=S.value)==null?void 0:o.containers.filter(e=>e.state==="running"))??[]});async function R(){var e;const o=await Y.get(`/api/deployments/${encodeURIComponent(_.value)}/runtime`);if(!o.ok){m.value=j(o,"list this deployment's containers");return}m.value="",S.value=o.body,u.value||(u.value=((e=k.value[0])==null?void 0:e.name)??"")}R();async function D(){var A;if(n.value==="connecting"||n.value==="open")return;f.value="",n.value="connecting";const[{Terminal:o},{FitAddon:e}]=await Promise.all([E(()=>import("./xterm-D6Mng-DB.js").then(s=>s.x),__vite__mapDeps([0,1])),E(()=>import("./addon-fit-CevVMrUA.js").then(s=>s.a),__vite__mapDeps([2,1]))]);await E(()=>Promise.resolve({}),__vite__mapDeps([3])),(A=w.value)==null||A.dispose();const l=new o({convertEol:!0,fontSize:13,fontFamily:"var(--font-mono, ui-monospace, monospace)",cursorBlink:!0,theme:{background:"#0b0e14"}}),b=new e;l.loadAddon(b),p.value&&(p.value.innerHTML="",l.open(p.value),b.fit()),w.value=l,U.value=b,i==null||i.disconnect(),i=new ResizeObserver(()=>{try{b.fit()}catch{}}),p.value&&i.observe(p.value);const z=location.protocol==="https:"?"wss":"ws",H=new URLSearchParams({container:u.value,shell:h.value}),r=new WebSocket(`${z}://${location.host}/api/deployments/${encodeURIComponent(_.value)}/terminal?${H}`);r.binaryType="arraybuffer",y.value=r,r.onopen=()=>{n.value="open",l.focus()},r.onmessage=s=>l.write(typeof s.data=="string"?s.data:new Uint8Array(s.data)),r.onclose=s=>{n.value="closed",f.value=s.reason||(s.code===1006?"the connection dropped (the upgrade may have been refused)":`code ${s.code}`)},r.onerror=()=>{n.value==="connecting"&&(f.value="could not open the connection")},l.onData(s=>{r.readyState===WebSocket.OPEN&&r.send(s)})}function x(){var o;(o=y.value)==null||o.close(1e3,"closed by the operator")}function $(){var o,e;i==null||i.disconnect(),i=null,(o=y.value)==null||o.close(),y.value=null,(e=w.value)==null||e.dispose(),w.value=null}return L([u,h],()=>{(n.value==="open"||n.value==="connecting")&&x()}),L(_,()=>{$(),n.value="idle",R()}),F($),(o,e)=>(c(),v("section",X,[t("div",Z,[e[3]||(e[3]=t("b",null,"No TTY.",-1)),e[4]||(e[4]=a(" The shell runs without a terminal device, so there is no prompt, no ",-1)),e[5]||(e[5]=t("code",null,"Ctrl-C",-1)),e[6]||(e[6]=a(", and no full-screen programs (",-1)),e[7]||(e[7]=t("code",null,"top",-1)),e[8]||(e[8]=a(", ",-1)),e[9]||(e[9]=t("code",null,"vim",-1)),e[10]||(e[10]=a("). Type a command and press Enter. ",-1)),C(W,{label:"why"},{default:M(()=>[...e[2]||(e[2]=[a(" A pseudo-terminal needs a wrapper (",-1),t("code",null,"script",-1),a(") whose flags differ across distributions, and it was never verified on a real host — so pstack ships the half it can stand behind. Everything an operator usually opens a shell for (",-1),t("code",null,"ls",-1),a(", ",-1),t("code",null,"cat",-1),a(", ",-1),t("code",null,"env",-1),a(", ",-1),t("code",null,"psql",-1),a(", a migration) works. ",-1)])]),_:1})]),m.value?(c(),K(J,{key:0,text:m.value,title:"Could not list this deployment's containers."},null,8,["text"])):g("",!0),t("div",ee,[t("label",ne,[e[11]||(e[11]=t("span",null,"Container",-1)),C(N,{modelValue:u.value,"onUpdate:modelValue":e[0]||(e[0]=l=>u.value=l),label:"Container",disabled:n.value==="open"||n.value==="connecting",options:I.value},null,8,["modelValue","disabled","options"])]),t("label",oe,[e[12]||(e[12]=t("span",null,"Shell",-1)),C(N,{modelValue:h.value,"onUpdate:modelValue":e[1]||(e[1]=l=>h.value=l),label:"Shell",disabled:n.value==="open"||n.value==="connecting",options:P.map(l=>({value:l,label:l}))},null,8,["modelValue","disabled","options"])]),n.value!=="open"?(c(),v("button",{key:0,class:"primary",disabled:!u.value||n.value==="connecting",onClick:D},B(n.value==="connecting"?"Connecting…":"Open shell"),9,te)):(c(),v("button",{key:1,onClick:x},"Close shell")),e[14]||(e[14]=t("span",{class:"grow"},null,-1)),n.value==="open"?(c(),v("span",le,[...e[13]||(e[13]=[t("span",{class:"dot pulse"},null,-1),a("connected",-1)])])):g("",!0)]),!k.value.length&&!m.value?(c(),v("p",se," Nothing is running for this deployment, so there is nothing to open a shell in. ")):g("",!0),n.value==="closed"&&f.value?(c(),v("p",ae,"Session ended — "+B(f.value)+".",1)):g("",!0),t("div",{ref_key:"host",ref:p,class:"terminal-host","data-state":n.value},null,8,ie),e[15]||(e[15]=t("p",{class:"hint"}," Every session is recorded — who, which container, when — and the record is written when the shell opens, so a session that dies with the process still leaves one. ",-1))]))}}),fe=Q(re,[["__scopeId","data-v-3855f639"]]);export{fe as default};
|
|
3
|
-
//# sourceMappingURL=TerminalTab-gDAKYgTX.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"mappings":"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","names":["route","useRoute","id","computed","SHELLS","rt","ref","loadError","container","shell","state","closeReason","host","term","shallowRef","fit","socket","observer","containerOptions","running","c","_a","loadContainers","r","api","problem","connect","Terminal","FitAddon","__vitePreload","n","t","f","proto","qs","ws","e","d","disconnect","teardown","_b","watch","onBeforeUnmount","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_createVNode","InfoHint","_createBlock","ErrorNote","_hoisted_3","_hoisted_4","SelectMenu","$event","_hoisted_5","s","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_toDisplayString"],"ignoreList":[],"sources":["../../src/views/tabs/TerminalTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A shell inside one of this deployment's containers.\n *\n * THE SERVER DECIDES WHICH CONTAINER. This picker is filled from `/runtime`, i.e. the containers the\n * deployment actually owns, and the server checks the name again on upgrade — a hand-typed\n * `?container=pstack-control` is a 404 there, not here. The picker is a convenience, never the\n * boundary.\n *\n * THE COOKIE IS THE CREDENTIAL. A browser cannot set an `Authorization` header on a WebSocket, which\n * is exactly why sessions are cookies (0.10.0) — the handshake is a same-origin GET and carries the\n * session automatically. There is no token in this URL and there must never be one: a URL lands in\n * proxy logs and browser history.\n *\n * NO PTY. The server runs `docker exec -i`, not `-it`, so there is no prompt, no ^C and no `top`.\n * That is stated on the page rather than left for the operator to discover as \"the terminal is\n * broken\" — see the banner below and the comment at the head of `terminal.ts`.\n */\nimport { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue';\nimport { useRoute } from 'vue-router';\nimport { api, problem } from '../../api/client';\nimport type { RuntimeResponse } from '../../api/types';\nimport ErrorNote from '../../components/ErrorNote.vue';\nimport InfoHint from '../../components/InfoHint.vue';\nimport SelectMenu from '../../components/SelectMenu.vue';\n\nconst route = useRoute();\nconst id = computed(() => String(route.params.id));\n\nconst SHELLS = ['sh', 'bash', 'ash', 'zsh', 'fish'];\n\nconst rt = ref<RuntimeResponse | null>(null);\nconst loadError = ref('');\nconst container = ref('');\nconst shell = ref('sh');\nconst state = ref<'idle' | 'connecting' | 'open' | 'closed'>('idle');\nconst closeReason = ref('');\n\nconst host = ref<HTMLDivElement | null>(null);\n/** shallowRef: xterm's Terminal is a large mutable object and must not be made reactive. */\nconst term = shallowRef<import('@xterm/xterm').Terminal | null>(null);\nconst fit = shallowRef<import('@xterm/addon-fit').FitAddon | null>(null);\nconst socket = shallowRef<WebSocket | null>(null);\nlet observer: ResizeObserver | null = null;\n\nconst containerOptions = computed(() =>\n running.value.map((c) => ({ value: c.name, label: c.name, hint: c.service ?? undefined })),\n);\n\nconst running = computed(() => rt.value?.containers.filter((c) => c.state === 'running') ?? []);\n\nasync function loadContainers() {\n const r = await api.get<RuntimeResponse>(\n `/api/deployments/${encodeURIComponent(id.value)}/runtime`,\n );\n if (!r.ok) {\n loadError.value = problem(r, \"list this deployment's containers\");\n return;\n }\n loadError.value = '';\n rt.value = r.body;\n if (!container.value) container.value = running.value[0]?.name ?? '';\n}\nvoid loadContainers();\n\nasync function connect() {\n if (state.value === 'connecting' || state.value === 'open') return;\n closeReason.value = '';\n state.value = 'connecting';\n\n // Loaded on demand, not in the app shell: xterm is ~250KB and only this tab needs it.\n const [{ Terminal }, { FitAddon }] = await Promise.all([\n import('@xterm/xterm'),\n import('@xterm/addon-fit'),\n ]);\n await import('@xterm/xterm/css/xterm.css');\n\n term.value?.dispose();\n const t = new Terminal({\n // The OUTPUT half of the line discipline the missing pty would have provided. A real terminal\n // converts NL to CR-NL on the way out (ONLCR); without it every `\\n` moves DOWN a row without\n // returning the carriage, and `ls` renders as a staircase marching off the right edge. The input\n // half — CR to NL — is done server-side; see `crToNl` in api.ts.\n convertEol: true,\n fontSize: 13,\n fontFamily: 'var(--font-mono, ui-monospace, monospace)',\n cursorBlink: true,\n theme: { background: '#0b0e14' },\n });\n const f = new FitAddon();\n t.loadAddon(f);\n if (host.value) {\n host.value.innerHTML = '';\n t.open(host.value);\n f.fit();\n }\n term.value = t;\n fit.value = f;\n\n observer?.disconnect();\n observer = new ResizeObserver(() => {\n try {\n f.fit();\n } catch {\n /* the element is detached mid-transition */\n }\n });\n if (host.value) observer.observe(host.value);\n\n const proto = location.protocol === 'https:' ? 'wss' : 'ws';\n const qs = new URLSearchParams({ container: container.value, shell: shell.value });\n const ws = new WebSocket(\n `${proto}://${location.host}/api/deployments/${encodeURIComponent(id.value)}/terminal?${qs}`,\n );\n ws.binaryType = 'arraybuffer';\n socket.value = ws;\n\n ws.onopen = () => {\n state.value = 'open';\n t.focus();\n };\n // Bytes, not text: decoding per frame would corrupt a multi-byte character split across two\n // reads. xterm takes a Uint8Array and handles the boundary itself.\n ws.onmessage = (e) =>\n t.write(typeof e.data === 'string' ? e.data : new Uint8Array(e.data as ArrayBuffer));\n ws.onclose = (e) => {\n state.value = 'closed';\n // A close CODE alone tells an operator nothing. 1006 in particular is the browser's \"it just\n // went away\", which here almost always means the upgrade was refused.\n closeReason.value =\n e.reason || (e.code === 1006 ? 'the connection dropped (the upgrade may have been refused)' : `code ${e.code}`);\n };\n ws.onerror = () => {\n if (state.value === 'connecting') closeReason.value = 'could not open the connection';\n };\n t.onData((d) => {\n if (ws.readyState === WebSocket.OPEN) ws.send(d);\n });\n}\n\nfunction disconnect() {\n socket.value?.close(1000, 'closed by the operator');\n}\n\nfunction teardown() {\n observer?.disconnect();\n observer = null;\n socket.value?.close();\n socket.value = null;\n term.value?.dispose();\n term.value = null;\n}\n\n// Switching container or shell must not leave the old shell running against a dead view.\nwatch([container, shell], () => {\n if (state.value === 'open' || state.value === 'connecting') disconnect();\n});\nwatch(id, () => {\n teardown();\n state.value = 'idle';\n void loadContainers();\n});\nonBeforeUnmount(teardown);\n</script>\n\n<template>\n <section class=\"stack\">\n <div class=\"banner warn\">\n <b>No TTY.</b> The shell runs without a terminal device, so there is no prompt, no\n <code>Ctrl-C</code>, and no full-screen programs (<code>top</code>, <code>vim</code>). Type a\n command and press Enter.\n <InfoHint label=\"why\">\n A pseudo-terminal needs a wrapper (<code>script</code>) whose flags differ across\n distributions, and it was never verified on a real host — so pstack ships the half it can\n stand behind. Everything an operator usually opens a shell for (<code>ls</code>,\n <code>cat</code>, <code>env</code>, <code>psql</code>, a migration) works.\n </InfoHint>\n </div>\n\n <ErrorNote v-if=\"loadError\" :text=\"loadError\" title=\"Could not list this deployment's containers.\" />\n\n <div class=\"row wrap\" style=\"gap: var(--s3); align-items: end\">\n <label class=\"field\">\n <span>Container</span>\n <SelectMenu v-model=\"container\" label=\"Container\" :disabled=\"state === 'open' || state === 'connecting'\" :options=\"containerOptions\" />\n </label>\n <label class=\"field\">\n <span>Shell</span>\n <SelectMenu v-model=\"shell\" label=\"Shell\" :disabled=\"state === 'open' || state === 'connecting'\" :options=\"SHELLS.map((s) => ({ value: s, label: s }))\" />\n </label>\n <button\n v-if=\"state !== 'open'\"\n class=\"primary\"\n :disabled=\"!container || state === 'connecting'\"\n @click=\"connect\"\n >\n {{ state === 'connecting' ? 'Connecting…' : 'Open shell' }}\n </button>\n <button v-else @click=\"disconnect\">Close shell</button>\n <span class=\"grow\" />\n <span v-if=\"state === 'open'\" class=\"badge ok\"><span class=\"dot pulse\" />connected</span>\n </div>\n\n <p v-if=\"!running.length && !loadError\" class=\"hint\">\n Nothing is running for this deployment, so there is nothing to open a shell in.\n </p>\n\n <p v-if=\"state === 'closed' && closeReason\" class=\"hint\">Session ended — {{ closeReason }}.</p>\n\n <div ref=\"host\" class=\"terminal-host\" :data-state=\"state\" />\n\n <p class=\"hint\">\n Every session is recorded — who, which container, when — and the record is written when the\n shell opens, so a session that dies with the process still leaves one.\n </p>\n </section>\n</template>\n\n<style scoped>\n.terminal-host {\n min-height: 26rem;\n border: 1px solid var(--border);\n border-radius: var(--r3);\n background: #0b0e14;\n padding: var(--s2);\n overflow: hidden;\n}\n.terminal-host[data-state='idle'] {\n display: none;\n}\n</style>\n"],"file":"assets/TerminalTab-gDAKYgTX.js"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as z,c as n,a as e,f as y,e as c,w as b,_ as S,x as C,g as _,t as i,F as R,s as I,h as P,i as A,v as T,u as U,b as g,T as G,r as d,C as H,A as m,B as f,o as a,N as x}from"./index-jLPJ82ve.js";import{_ as B}from"./ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js";import{E as D}from"./EquivalentCommand-DyCAI1Cy.js";import{_ as J}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";import{_ as E}from"./RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js";import{_ as F}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{_ as K}from"./_plugin-vue_export-helper-DlAUqK2U.js";import"./useFormat-CKJxQp_i.js";const Q={class:"page-head"},X={class:"sub"},Z={key:1,class:"banner ok"},ee={class:"code"},se={class:"row"},te={class:"grid-2"},oe={class:"panel"},ae={class:"phead"},ne={class:"mute",style:{"font-size":"var(--t-xs)"}},le={key:1,class:"cards"},re={class:"name","data-label":"user"},ie={key:0,class:"badge info"},de={class:"dim nowrap","data-label":"added"},ue={class:"right nowrap","data-label":""},ce=["onClick"],pe={key:2,class:"hint"},ve={class:"row wrap two-up"},me={class:"field grow"},he={class:"field grow"},we={class:"row"},ke=["disabled"],fe={class:"panel"},ye={class:"phead"},be={key:1,class:"cards"},_e={class:"name","data-label":"name"},ge={class:"dim nowrap","data-label":"last used"},$e={key:1,class:"mute"},Ce={class:"right nowrap","data-label":""},Ae={key:2,class:"hint"},Te={class:"field"},Ue={class:"row"},xe=["disabled"],Ve={class:"sheet",role:"dialog","aria-modal":"true","aria-label":"Change password"},Ne={class:"hint"},Pe={class:"field"},Se={class:"row"},Re=["disabled"],Ie=z({__name:"UsersView",setup(Be){const $=d([]),V=d([]),N=d(!1),r=d(""),l=d({username:"",password:""}),p=d(""),u=d(null),h=d(""),w=d(null),L=H(()=>l.value.username.trim().length>0&&l.value.password.length>=8);async function k(){const[o,s]=await Promise.all([m.get("/api/users"),m.get("/api/tokens")]);if(N.value=!0,!o.ok){r.value=f(o,"load the accounts");return}r.value="",$.value=o.body.users??[],V.value=s.ok?s.body.tokens??[]:[]}k();async function M(){const o=await m.post("/api/users",{username:l.value.username.trim(),password:l.value.password});if(!o.ok){r.value=f(o,"create this account");return}x("ok",`Created ${o.body.user.username}.`),l.value={username:"",password:""},k()}async function O(o){const s=await m.del(`/api/users/${o.id}`);if(!s.ok){r.value=f(s,`delete ${o.username}`);return}x("ok",`Deleted ${o.username}.`),k()}async function W(){var t;const o=u.value;if(!o)return;const s=await m.put(`/api/users/${o.id}/password`,{password:h.value});if(!s.ok){r.value=f(s,`change the password for ${o.username}`);return}u.value=null,h.value="",x("ok",o.id===((t=g.user)==null?void 0:t.id)?"Password changed. You have been signed out everywhere — sign in again.":`Password changed. ${o.username} has been signed out everywhere.`),k()}async function Y(){const o=await m.post("/api/tokens",{name:p.value.trim()});if(!o.ok){r.value=f(o,"create this token");return}w.value={what:`Personal token “${p.value.trim()}”`,value:o.body.token,note:"Send it as a bearer token. The server stores only a hash, so this is the only time it is shown."},p.value="",k()}async function j(o){const s=await m.del(`/api/tokens/${o.id}`);if(!s.ok){r.value=f(s,`revoke ${o.name}`);return}x("ok",`Revoked ${o.name}.`),k()}return(o,s)=>(a(),n("div",null,[e("div",Q,[e("div",null,[s[9]||(s[9]=e("h1",null,"Users & access",-1)),e("div",X,[s[8]||(s[8]=y(" Who can sign in, and the tokens they use from scripts ",-1)),c(S,{label:"how access works here"},{default:b(()=>[...s[7]||(s[7]=[y(" Three kinds of caller. The host token from the environment is the root credential CI holds. An account signs in with a password and gets a browser session. A personal token belongs to one account and is what a script uses — it can do anything that account can. ",-1)])]),_:1})])])]),r.value?(a(),C(J,{key:0,text:r.value,title:"Something went wrong."},null,8,["text"])):_("",!0),w.value?(a(),n("div",Z,[e("b",null,i(w.value.what)+" — copy it now.",1),e("p",null,i(w.value.note),1),e("pre",ee,i(w.value.value),1),e("div",se,[e("button",{onClick:s[0]||(s[0]=t=>w.value=null)},"I have stored it")])])):_("",!0),e("div",te,[e("section",oe,[e("div",ae,[s[10]||(s[10]=e("h2",{class:"section"},"Accounts",-1)),s[11]||(s[11]=e("span",{class:"grow"},null,-1)),e("span",ne,i($.value.length),1)]),N.value?$.value.length?(a(),n("table",le,[s[13]||(s[13]=e("thead",null,[e("tr",null,[e("th",null,"User"),e("th",null,"Added"),e("th",{"aria-label":"Actions"})])],-1)),e("tbody",null,[(a(!0),n(R,null,I($.value,t=>{var v;return a(),n("tr",{key:t.id},[e("td",re,[e("b",null,i(t.username),1),t.id===((v=U(g).user)==null?void 0:v.id)?(a(),n("span",ie,"you")):_("",!0)]),e("td",de,[c(E,{at:t.createdAt},null,8,["at"])]),e("td",ue,[e("button",{class:"ghost sm",onClick:q=>{u.value=t,h.value=""}},"Change password",8,ce),c(B,{class:"danger sm",confirm:`Delete ${t.username}? Their sessions and tokens go too.`,onRun:q=>O(t)},{default:b(()=>[...s[12]||(s[12]=[y("Delete",-1)])]),_:1},8,["confirm","onRun"])])])}),128))])])):(a(),n("p",pe,"No accounts yet.")):(a(),C(F,{key:0,rows:3})),e("form",{class:"stack-form",onSubmit:P(M,["prevent"])},[s[17]||(s[17]=e("h3",null,"Add an account",-1)),e("div",ve,[e("label",me,[s[14]||(s[14]=e("span",null,"Username",-1)),A(e("input",{"onUpdate:modelValue":s[1]||(s[1]=t=>l.value.username=t),type:"text",autocomplete:"off"},null,512),[[T,l.value.username]])]),e("label",he,[s[15]||(s[15]=e("span",null,"Password",-1)),A(e("input",{"onUpdate:modelValue":s[2]||(s[2]=t=>l.value.password=t),type:"password",autocomplete:"new-password"},null,512),[[T,l.value.password]])])]),s[18]||(s[18]=e("p",{class:"hint"},"At least 8 characters.",-1)),e("div",we,[e("button",{class:"primary",type:"submit",disabled:!L.value},"Add account",8,ke),s[16]||(s[16]=e("span",{class:"grow"},null,-1))]),c(D,{what:"this account creation",method:"POST",path:"/api/users",body:{username:l.value.username||"alice",password:"…"}},null,8,["body"])],32)]),e("section",fe,[e("div",ye,[s[20]||(s[20]=e("h2",{class:"section"},"Your API tokens",-1)),c(S,{label:"when to use one"},{default:b(()=>[...s[19]||(s[19]=[y(" A script or a CI job signing in as you. It carries whatever you can do, so give each one a name you will recognise later and revoke the ones you stop using. ",-1)])]),_:1})]),N.value?V.value.length?(a(),n("table",be,[s[22]||(s[22]=e("thead",null,[e("tr",null,[e("th",null,"Name"),e("th",null,"Last used"),e("th",{"aria-label":"Actions"})])],-1)),e("tbody",null,[(a(!0),n(R,null,I(V.value,t=>(a(),n("tr",{key:t.id},[e("td",_e,[e("b",null,i(t.name),1)]),e("td",ge,[t.lastUsedAt?(a(),C(E,{key:0,at:t.lastUsedAt},null,8,["at"])):(a(),n("span",$e,"never"))]),e("td",Ce,[c(B,{class:"danger sm",confirm:`Revoke ${t.name}? Anything using it stops working.`,onRun:v=>j(t)},{default:b(()=>[...s[21]||(s[21]=[y("Revoke",-1)])]),_:1},8,["confirm","onRun"])])]))),128))])])):(a(),n("p",Ae,i(U(g).root?"The host token is not an account, so it has no personal tokens.":"No tokens yet."),1)):(a(),C(F,{key:0,rows:2})),U(g).root?_("",!0):(a(),n("form",{key:3,class:"stack-form",onSubmit:P(Y,["prevent"])},[s[24]||(s[24]=e("h3",null,"New token",-1)),e("label",Te,[s[23]||(s[23]=e("span",null,"What is it for",-1)),A(e("input",{"onUpdate:modelValue":s[3]||(s[3]=t=>p.value=t),type:"text",placeholder:"ci-deploy",autocomplete:"off"},null,512),[[T,p.value]])]),e("div",Ue,[e("button",{class:"primary",type:"submit",disabled:!p.value.trim()},"Create token",8,xe)]),c(D,{what:"this token creation",method:"POST",path:"/api/tokens",body:{name:p.value||"ci-deploy"}},null,8,["body"])],32))])]),c(G,{name:"fade"},{default:b(()=>{var t;return[u.value?(a(),n("div",{key:0,class:"scrim",onClick:s[6]||(s[6]=P(v=>u.value=null,["self"]))},[e("div",Ve,[e("h3",null,"Change password for "+i(u.value.username),1),e("p",Ne," This signs "+i(u.value.id===((t=U(g).user)==null?void 0:t.id)?"you":"them")+" out everywhere — every session and personal token is revoked. ",1),e("label",Pe,[s[25]||(s[25]=e("span",null,"New password",-1)),A(e("input",{"onUpdate:modelValue":s[4]||(s[4]=v=>h.value=v),type:"password",autocomplete:"new-password"},null,512),[[T,h.value]])]),e("div",Se,[e("button",{class:"primary",disabled:h.value.length<8,onClick:W}," Change password ",8,Re),e("button",{class:"ghost",onClick:s[5]||(s[5]=v=>u.value=null)},"Cancel")])])])):_("",!0)]}),_:1})]))}}),je=K(Ie,[["__scopeId","data-v-09c4a1c4"]]);export{je as default};
|
|
2
|
-
//# sourceMappingURL=UsersView-Bvs30U8o.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UsersView-Bvs30U8o.js","sources":["../../src/views/UsersView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Accounts and personal API tokens.\n *\n * Accounts landed in 0.10.0 with no UI at all, so the only way to add a second operator was a curl\n * call — which is exactly the thing a control plane exists to spare people.\n *\n * TWO AUDIENCES, TWO SECTIONS, and the split is deliberate. **Accounts** are about other people and\n * are shared state; **tokens** are yours alone (the API scopes them to the caller and cannot show\n * anyone else's). Mixing them into one table would imply an admin can manage another person's\n * tokens, which is not true and would be a bad idea if it were.\n *\n * SECRETS ARE SHOWN ONCE, and the page says so in the same breath as showing them. Same discipline\n * as a notifier signing secret and a registry credential: there is no read path on the server, so\n * this is not a policy that could be relaxed later — the value genuinely stops existing.\n */\nimport { computed, ref } from 'vue';\nimport { api, problem } from '../api/client';\nimport { authState } from '../composables/useAuth';\nimport { toast } from '../composables/useToasts';\nimport ActionButton from '../components/ActionButton.vue';\nimport EquivalentCommand from '../components/EquivalentCommand.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport RelativeTime from '../components/RelativeTime.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\n\ntype User = { id: number; username: string; role: string; createdAt: number };\ntype Token = { id: number; name: string; createdAt: number; lastUsedAt: number | null };\n\nconst users = ref<User[]>([]);\nconst tokens = ref<Token[]>([]);\nconst loaded = ref(false);\nconst listError = ref('');\n\nconst newUser = ref({ username: '', password: '' });\nconst newToken = ref('');\nconst pwFor = ref<User | null>(null);\nconst pwValue = ref('');\n\n/** Held only until dismissed — there is no way to show either of these again. */\nconst revealed = ref<{ what: string; value: string; note: string } | null>(null);\n\nconst canCreateUser = computed(\n () => newUser.value.username.trim().length > 0 && newUser.value.password.length >= 8,\n);\n\nasync function load(): Promise<void> {\n const [u, t] = await Promise.all([\n api.get<{ users: User[] }>('/api/users'),\n api.get<{ tokens: Token[] }>('/api/tokens'),\n ]);\n loaded.value = true;\n if (!u.ok) {\n listError.value = problem(u, 'load the accounts');\n return;\n }\n listError.value = '';\n users.value = u.body.users ?? [];\n // Root (PSTACK_TOKEN) is not a user, so it has no personal tokens — an error here is expected.\n tokens.value = t.ok ? (t.body.tokens ?? []) : [];\n}\nvoid load();\n\nasync function createUser(): Promise<void> {\n const r = await api.post<{ user: User }>('/api/users', {\n username: newUser.value.username.trim(),\n password: newUser.value.password,\n });\n if (!r.ok) {\n listError.value = problem(r, 'create this account');\n return;\n }\n toast('ok', `Created ${r.body.user.username}.`);\n newUser.value = { username: '', password: '' };\n void load();\n}\n\nasync function removeUser(u: User): Promise<void> {\n const r = await api.del(`/api/users/${u.id}`);\n // The server refuses to delete the last account. That is a rule worth surfacing verbatim rather\n // than as \"could not delete\" — it tells the operator what to do instead.\n if (!r.ok) {\n listError.value = problem(r, `delete ${u.username}`);\n return;\n }\n toast('ok', `Deleted ${u.username}.`);\n void load();\n}\n\nasync function changePassword(): Promise<void> {\n const u = pwFor.value;\n if (!u) return;\n const r = await api.put(`/api/users/${u.id}/password`, { password: pwValue.value });\n if (!r.ok) {\n listError.value = problem(r, `change the password for ${u.username}`);\n return;\n }\n pwFor.value = null;\n pwValue.value = '';\n // Not a footnote: the change signs that user out everywhere, and if it was YOU, the next request\n // is a 401. Saying so here is the difference between an expected step and an apparent bug.\n toast(\n 'ok',\n u.id === authState.user?.id\n ? `Password changed. You have been signed out everywhere — sign in again.`\n : `Password changed. ${u.username} has been signed out everywhere.`,\n );\n void load();\n}\n\nasync function createToken(): Promise<void> {\n const r = await api.post<{ token: string; id: number }>('/api/tokens', {\n name: newToken.value.trim(),\n });\n if (!r.ok) {\n listError.value = problem(r, 'create this token');\n return;\n }\n revealed.value = {\n what: `Personal token “${newToken.value.trim()}”`,\n value: r.body.token,\n note: 'Send it as a bearer token. The server stores only a hash, so this is the only time it is shown.',\n };\n newToken.value = '';\n void load();\n}\n\nasync function removeToken(t: Token): Promise<void> {\n const r = await api.del(`/api/tokens/${t.id}`);\n if (!r.ok) {\n listError.value = problem(r, `revoke ${t.name}`);\n return;\n }\n toast('ok', `Revoked ${t.name}.`);\n void load();\n}\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Users & access</h1>\n <div class=\"sub\">\n Who can sign in, and the tokens they use from scripts\n <InfoHint label=\"how access works here\">\n Three kinds of caller. The host token from the environment is the root credential CI\n holds. An account signs in with a password and gets a browser session. A personal token\n belongs to one account and is what a script uses — it can do anything that account can.\n </InfoHint>\n </div>\n </div>\n </div>\n\n <ErrorNote v-if=\"listError\" :text=\"listError\" title=\"Something went wrong.\" />\n\n <div v-if=\"revealed\" class=\"banner ok\">\n <b>{{ revealed.what }} — copy it now.</b>\n <p>{{ revealed.note }}</p>\n <pre class=\"code\">{{ revealed.value }}</pre>\n <div class=\"row\">\n <button @click=\"revealed = null\">I have stored it</button>\n </div>\n </div>\n\n <div class=\"grid-2\">\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Accounts</h2>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-xs)\">{{ users.length }}</span>\n </div>\n\n <SkeletonList v-if=\"!loaded\" :rows=\"3\" />\n <!-- `cards` + `data-label` per cell: below 640px the same markup renders as cards. Without\n it the row's two buttons pushed the table past the viewport and got clipped. -->\n <table v-else-if=\"users.length\" class=\"cards\">\n <thead>\n <tr>\n <th>User</th>\n <th>Added</th>\n <th aria-label=\"Actions\" />\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"u in users\" :key=\"u.id\">\n <td class=\"name\" data-label=\"user\">\n <b>{{ u.username }}</b>\n <span v-if=\"u.id === authState.user?.id\" class=\"badge info\">you</span>\n </td>\n <td class=\"dim nowrap\" data-label=\"added\"><RelativeTime :at=\"u.createdAt\" /></td>\n <td class=\"right nowrap\" data-label=\"\">\n <button class=\"ghost sm\" @click=\"pwFor = u; pwValue = ''\">Change password</button>\n <ActionButton\n class=\"danger sm\"\n :confirm=\"`Delete ${u.username}? Their sessions and tokens go too.`\"\n @run=\"removeUser(u)\"\n >Delete</ActionButton\n >\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"hint\">No accounts yet.</p>\n\n <form class=\"stack-form\" @submit.prevent=\"createUser\">\n <h3>Add an account</h3>\n <div class=\"row wrap two-up\">\n <label class=\"field grow\">\n <span>Username</span>\n <input v-model=\"newUser.username\" type=\"text\" autocomplete=\"off\" />\n </label>\n <label class=\"field grow\">\n <span>Password</span>\n <input v-model=\"newUser.password\" type=\"password\" autocomplete=\"new-password\" />\n </label>\n </div>\n <p class=\"hint\">At least 8 characters.</p>\n <div class=\"row\">\n <button class=\"primary\" type=\"submit\" :disabled=\"!canCreateUser\">Add account</button>\n <span class=\"grow\" />\n </div>\n <EquivalentCommand\n what=\"this account creation\"\n method=\"POST\"\n path=\"/api/users\"\n :body=\"{ username: newUser.username || 'alice', password: '…' }\"\n />\n </form>\n </section>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Your API tokens</h2>\n <InfoHint label=\"when to use one\">\n A script or a CI job signing in as you. It carries whatever you can do, so give each one\n a name you will recognise later and revoke the ones you stop using.\n </InfoHint>\n </div>\n\n <SkeletonList v-if=\"!loaded\" :rows=\"2\" />\n <table v-else-if=\"tokens.length\" class=\"cards\">\n <thead>\n <tr>\n <th>Name</th>\n <th>Last used</th>\n <th aria-label=\"Actions\" />\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"t in tokens\" :key=\"t.id\">\n <td class=\"name\" data-label=\"name\"><b>{{ t.name }}</b></td>\n <td class=\"dim nowrap\" data-label=\"last used\">\n <RelativeTime v-if=\"t.lastUsedAt\" :at=\"t.lastUsedAt\" />\n <span v-else class=\"mute\">never</span>\n </td>\n <td class=\"right nowrap\" data-label=\"\">\n <ActionButton\n class=\"danger sm\"\n :confirm=\"`Revoke ${t.name}? Anything using it stops working.`\"\n @run=\"removeToken(t)\"\n >Revoke</ActionButton\n >\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"hint\">\n {{ authState.root ? 'The host token is not an account, so it has no personal tokens.' : 'No tokens yet.' }}\n </p>\n\n <form v-if=\"!authState.root\" class=\"stack-form\" @submit.prevent=\"createToken\">\n <h3>New token</h3>\n <label class=\"field\">\n <span>What is it for</span>\n <input v-model=\"newToken\" type=\"text\" placeholder=\"ci-deploy\" autocomplete=\"off\" />\n </label>\n <div class=\"row\">\n <button class=\"primary\" type=\"submit\" :disabled=\"!newToken.trim()\">Create token</button>\n </div>\n <EquivalentCommand\n what=\"this token creation\"\n method=\"POST\"\n path=\"/api/tokens\"\n :body=\"{ name: newToken || 'ci-deploy' }\"\n />\n </form>\n </section>\n </div>\n\n <!-- Password change: a dialog, because it is a distinct decision about one row. -->\n <Transition name=\"fade\">\n <div v-if=\"pwFor\" class=\"scrim\" @click.self=\"pwFor = null\">\n <div class=\"sheet\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Change password\">\n <h3>Change password for {{ pwFor.username }}</h3>\n <p class=\"hint\">\n This signs {{ pwFor.id === authState.user?.id ? 'you' : 'them' }} out everywhere —\n every session and personal token is revoked.\n </p>\n <label class=\"field\">\n <span>New password</span>\n <input v-model=\"pwValue\" type=\"password\" autocomplete=\"new-password\" />\n </label>\n <div class=\"row\">\n <button class=\"primary\" :disabled=\"pwValue.length < 8\" @click=\"changePassword\">\n Change password\n </button>\n <button class=\"ghost\" @click=\"pwFor = null\">Cancel</button>\n </div>\n </div>\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.stack-form {\n margin-top: var(--s5);\n padding-top: var(--s4);\n border-top: 1px solid var(--line-soft);\n display: flex;\n flex-direction: column;\n gap: var(--s3);\n}\n.stack-form h3 {\n font-size: var(--t-sm);\n color: var(--fg-dim);\n}\ntd.right {\n text-align: end;\n}\n/* Two fields share a line where there is room and stack where there is not — at 390px a pair of\n * half-width inputs is two inputs nobody can type into. */\n.two-up > .field {\n flex: 1 1 200px;\n}\ntd.right button {\n margin-inline-start: var(--s2);\n}\n</style>\n"],"names":["users","ref","tokens","loaded","listError","newUser","newToken","pwFor","pwValue","revealed","canCreateUser","computed","load","u","t","api","problem","createUser","r","toast","removeUser","changePassword","_a","authState","createToken","removeToken","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_hoisted_2","_createVNode","InfoHint","_createBlock","ErrorNote","_openBlock","_hoisted_3","_toDisplayString","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_Fragment","_renderList","_hoisted_11","_unref","_hoisted_12","_hoisted_13","RelativeTime","_hoisted_14","$event","_hoisted_15","ActionButton","_hoisted_16","SkeletonList","_hoisted_17","_hoisted_18","_vModelText","_hoisted_19","_hoisted_20","_hoisted_21","EquivalentCommand","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_Transition","_hoisted_33","_hoisted_34","_hoisted_35","_hoisted_36","_hoisted_37"],"mappings":"qqDA8BA,MAAMA,EAAQC,EAAY,EAAE,EACtBC,EAASD,EAAa,EAAE,EACxBE,EAASF,EAAI,EAAK,EAClBG,EAAYH,EAAI,EAAE,EAElBI,EAAUJ,EAAI,CAAE,SAAU,GAAI,SAAU,GAAI,EAC5CK,EAAWL,EAAI,EAAE,EACjBM,EAAQN,EAAiB,IAAI,EAC7BO,EAAUP,EAAI,EAAE,EAGhBQ,EAAWR,EAA0D,IAAI,EAEzES,EAAgBC,EACpB,IAAMN,EAAQ,MAAM,SAAS,KAAA,EAAO,OAAS,GAAKA,EAAQ,MAAM,SAAS,QAAU,CAAA,EAGrF,eAAeO,GAAsB,CACnC,KAAM,CAACC,EAAGC,CAAC,EAAI,MAAM,QAAQ,IAAI,CAC/BC,EAAI,IAAuB,YAAY,EACvCA,EAAI,IAAyB,aAAa,CAAA,CAC3C,EAED,GADAZ,EAAO,MAAQ,GACX,CAACU,EAAE,GAAI,CACTT,EAAU,MAAQY,EAAQH,EAAG,mBAAmB,EAChD,MACF,CACAT,EAAU,MAAQ,GAClBJ,EAAM,MAAQa,EAAE,KAAK,OAAS,CAAA,EAE9BX,EAAO,MAAQY,EAAE,GAAMA,EAAE,KAAK,QAAU,CAAA,EAAM,CAAA,CAChD,CACKF,EAAA,EAEL,eAAeK,GAA4B,CACzC,MAAMC,EAAI,MAAMH,EAAI,KAAqB,aAAc,CACrD,SAAUV,EAAQ,MAAM,SAAS,KAAA,EACjC,SAAUA,EAAQ,MAAM,QAAA,CACzB,EACD,GAAI,CAACa,EAAE,GAAI,CACTd,EAAU,MAAQY,EAAQE,EAAG,qBAAqB,EAClD,MACF,CACAC,EAAM,KAAM,WAAWD,EAAE,KAAK,KAAK,QAAQ,GAAG,EAC9Cb,EAAQ,MAAQ,CAAE,SAAU,GAAI,SAAU,EAAA,EACrCO,EAAA,CACP,CAEA,eAAeQ,EAAWP,EAAwB,CAChD,MAAMK,EAAI,MAAMH,EAAI,IAAI,cAAcF,EAAE,EAAE,EAAE,EAG5C,GAAI,CAACK,EAAE,GAAI,CACTd,EAAU,MAAQY,EAAQE,EAAG,UAAUL,EAAE,QAAQ,EAAE,EACnD,MACF,CACAM,EAAM,KAAM,WAAWN,EAAE,QAAQ,GAAG,EAC/BD,EAAA,CACP,CAEA,eAAeS,GAAgC,OAC7C,MAAMR,EAAIN,EAAM,MAChB,GAAI,CAACM,EAAG,OACR,MAAMK,EAAI,MAAMH,EAAI,IAAI,cAAcF,EAAE,EAAE,YAAa,CAAE,SAAUL,EAAQ,KAAA,CAAO,EAClF,GAAI,CAACU,EAAE,GAAI,CACTd,EAAU,MAAQY,EAAQE,EAAG,2BAA2BL,EAAE,QAAQ,EAAE,EACpE,MACF,CACAN,EAAM,MAAQ,KACdC,EAAQ,MAAQ,GAGhBW,EACE,KACAN,EAAE,OAAOS,EAAAC,EAAU,OAAV,YAAAD,EAAgB,IACrB,yEACA,qBAAqBT,EAAE,QAAQ,kCAAA,EAEhCD,EAAA,CACP,CAEA,eAAeY,GAA6B,CAC1C,MAAMN,EAAI,MAAMH,EAAI,KAAoC,cAAe,CACrE,KAAMT,EAAS,MAAM,KAAA,CAAK,CAC3B,EACD,GAAI,CAACY,EAAE,GAAI,CACTd,EAAU,MAAQY,EAAQE,EAAG,mBAAmB,EAChD,MACF,CACAT,EAAS,MAAQ,CACf,KAAM,mBAAmBH,EAAS,MAAM,MAAM,IAC9C,MAAOY,EAAE,KAAK,MACd,KAAM,iGAAA,EAERZ,EAAS,MAAQ,GACZM,EAAA,CACP,CAEA,eAAea,EAAYX,EAAyB,CAClD,MAAMI,EAAI,MAAMH,EAAI,IAAI,eAAeD,EAAE,EAAE,EAAE,EAC7C,GAAI,CAACI,EAAE,GAAI,CACTd,EAAU,MAAQY,EAAQE,EAAG,UAAUJ,EAAE,IAAI,EAAE,EAC/C,MACF,CACAK,EAAM,KAAM,WAAWL,EAAE,IAAI,GAAG,EAC3BF,EAAA,CACP,mBAIEc,EA6KM,MAAA,KAAA,CA5KJC,EAYM,MAZNC,EAYM,CAXJD,EAUM,MAAA,KAAA,CATJE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAA2B,UAAvB,iBAAkB,EAAA,GACtBA,EAOM,MAPNG,EAOM,eAPW,0DAEf,EAAA,GAAAC,EAIWC,EAAA,CAJD,MAAM,yBAAuB,WAAC,IAIxC,CAAA,GAAAH,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAJwC,yQAIxC,EAAA,CAAA,iBAKWzB,EAAA,WAAjB6B,EAA8EC,EAAA,OAAjD,KAAM9B,EAAA,MAAW,MAAM,uBAAA,6BAEzCK,EAAA,OAAX0B,EAAA,EAAAT,EAOM,MAPNU,EAOM,CANJT,EAAyC,IAAA,KAAAU,EAAnC5B,EAAA,MAAS,IAAI,EAAG,kBAAe,CAAA,EACrCkB,EAA0B,IAAA,KAAAU,EAApB5B,EAAA,MAAS,IAAI,EAAA,CAAA,EACnBkB,EAA4C,MAA5CW,GAA4CD,EAAvB5B,EAAA,MAAS,KAAK,EAAA,CAAA,EACnCkB,EAEM,MAFNY,GAEM,CADJZ,EAA0D,SAAA,CAAjD,uBAAOlB,EAAA,MAAQ,KAAA,EAAS,kBAAgB,CAAA,eAIrDkB,EA2HM,MA3HNa,GA2HM,CA1HJb,EA+DU,UA/DVc,GA+DU,CA9DRd,EAIM,MAJNe,GAIM,CAHJb,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAAiC,KAAA,CAA7B,MAAM,SAAA,EAAU,WAAQ,EAAA,iBAC5BA,EAAqB,OAAA,CAAf,MAAM,MAAA,EAAM,KAAA,EAAA,GAClBA,EAA2E,OAA3EgB,GAA2EN,EAAtBrC,EAAA,MAAM,MAAM,EAAA,CAAA,CAAA,GAG9CG,EAAA,MAGHH,EAAA,MAAM,QAAxBmC,EAAA,EAAAT,EA0BQ,QA1BRkB,GA0BQ,eAzBNjB,EAMQ,QAAA,KAAA,CALNA,EAIK,KAAA,KAAA,CAHHA,EAAa,UAAT,MAAI,EACRA,EAAc,UAAV,OAAK,EACTA,EAA2B,KAAA,CAAvB,aAAW,UAAS,CAAA,SAG5BA,EAiBQ,QAAA,KAAA,QAhBND,EAeKmB,EAAA,KAAAC,EAfW9C,EAAA,MAALa,GAAC,kBAAZa,EAeK,KAAA,CAfmB,IAAKb,EAAE,EAAA,GAC7Bc,EAGK,KAHLoB,GAGK,CAFHpB,EAAuB,IAAA,KAAAU,EAAjBxB,EAAE,QAAQ,EAAA,CAAA,EACJA,EAAE,OAAOmC,EAAAA,KAAU,OAAVA,YAAAA,EAAgB,SAArCtB,EAAsE,OAAtEuB,GAA4D,KAAG,cAEjEtB,EAAiF,KAAjFuB,GAAiF,CAAvCnB,EAAkCoB,EAAA,CAAnB,GAAItC,EAAE,SAAA,mBAC/Dc,EAQK,KARLyB,GAQK,CAPHzB,EAAkF,SAAA,CAA1E,MAAM,WAAY,QAAK0B,GAAA,CAAE9C,EAAA,MAAQM,EAAGL,EAAA,MAAO,EAAA,CAAA,EAAO,kBAAe,EAAA8C,EAAA,EACzEvB,EAKCwB,EAAA,CAJC,MAAM,YACL,QAAO,UAAY1C,EAAE,QAAQ,sCAC7B,MAAGwC,GAAEjC,EAAWP,CAAC,CAAA,aACjB,IAAM,CAAA,GAAAgB,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAA,GAAN,SAAM,EAAA,CAAA,uDAMjBH,EAA2C,IAA3C8B,GAAuB,kBAAgB,QA9BvCvB,EAAyCwB,EAAA,OAAX,KAAM,CAAA,IAgCpC9B,EAuBO,OAAA,CAvBD,MAAM,aAAc,WAAgBV,EAAU,CAAA,SAAA,CAAA,CAAA,GAClDY,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAAuB,UAAnB,iBAAc,EAAA,GAClBA,EASM,MATN+B,GASM,CARJ/B,EAGQ,QAHRgC,GAGQ,CAFN9B,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAAqB,YAAf,WAAQ,EAAA,KACdA,EAAmE,QAAA,CAAnD,sBAAAE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAwB,GAAAhD,EAAA,MAAQ,SAAQgD,GAAE,KAAK,OAAO,aAAa,KAAA,aAA3C,CAAAO,EAAAvD,EAAA,MAAQ,QAAQ,CAAA,KAElCsB,EAGQ,QAHRkC,GAGQ,CAFNhC,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAAqB,YAAf,WAAQ,EAAA,KACdA,EAAgF,QAAA,CAAhE,sBAAAE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAwB,GAAAhD,EAAA,MAAQ,SAAQgD,GAAE,KAAK,WAAW,aAAa,cAAA,aAA/C,CAAAO,EAAAvD,EAAA,MAAQ,QAAQ,CAAA,OAGpCwB,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAA0C,IAAA,CAAvC,MAAM,MAAA,EAAO,yBAAsB,EAAA,GACtCA,EAGM,MAHNmC,GAGM,CAFJnC,EAAqF,SAAA,CAA7E,MAAM,UAAU,KAAK,SAAU,UAAWjB,EAAA,KAAA,EAAe,cAAW,EAAAqD,EAAA,gBAC5EpC,EAAqB,OAAA,CAAf,MAAM,QAAM,KAAA,EAAA,EAAA,GAEpBI,EAKEiC,EAAA,CAJA,KAAK,wBACL,OAAO,OACP,KAAK,aACJ,KAAI,CAAA,SAAc3D,EAAA,MAAQ,UAAQ,QAAA,SAAA,GAAA,CAAA,0BAKzCsB,EAwDU,UAxDVsC,GAwDU,CAvDRtC,EAMM,MANNuC,GAMM,CALJrC,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAAwC,KAAA,CAApC,MAAM,SAAA,EAAU,kBAAe,EAAA,GACnCI,EAGWC,EAAA,CAHD,MAAM,mBAAiB,WAAC,IAGlC,CAAA,GAAAH,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAA,GAHkC,iKAGlC,EAAA,CAAA,aAGmB1B,EAAA,MACHD,EAAA,MAAO,QAAzBiC,EAAA,EAAAT,EAyBQ,QAzBRyC,GAyBQ,eAxBNxC,EAMQ,QAAA,KAAA,CALNA,EAIK,KAAA,KAAA,CAHHA,EAAa,UAAT,MAAI,EACRA,EAAkB,UAAd,WAAS,EACbA,EAA2B,KAAA,CAAvB,aAAW,UAAS,CAAA,SAG5BA,EAgBQ,QAAA,KAAA,QAfND,EAcKmB,EAAA,KAAAC,EAdW5C,EAAA,MAAL,QAAXwB,EAcK,KAAA,CAdoB,IAAK,EAAE,EAAA,GAC9BC,EAA2D,KAA3DyC,GAA2D,CAAxBzC,EAAmB,IAAA,KAAAU,EAAb,EAAE,IAAI,EAAA,CAAA,CAAA,GAC/CV,EAGK,KAHL0C,GAGK,CAFiB,EAAE,gBAAtBpC,EAAuDkB,EAAA,OAApB,GAAI,EAAE,UAAA,uBACzCzB,EAAsC,OAAtC4C,GAA0B,OAAK,EAAA,GAEjC3C,EAOK,KAPL4C,GAOK,CANHxC,EAKCwB,EAAA,CAJC,MAAM,YACL,QAAO,UAAY,EAAE,IAAI,qCACzB,MAAGF,GAAE5B,EAAY,CAAC,CAAA,aAClB,IAAM,CAAA,GAAAI,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAA,GAAN,SAAM,EAAA,CAAA,mDAMjBM,EAAA,EAAAT,EAEI,IAFJ8C,GAEInC,EADCW,EAAAzB,CAAA,EAAU,KAAI,kEAAA,gBAAA,EAAA,CAAA,QA5BnBU,EAAyCwB,EAAA,OAAX,KAAM,CAAA,IA+BvBT,EAAAzB,CAAA,EAAU,mBAAvBG,EAeO,OAAA,OAfsB,MAAM,aAAc,WAAgBF,EAAW,CAAA,SAAA,CAAA,CAAA,GAC1EK,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAAkB,UAAd,YAAS,EAAA,GACbA,EAGQ,QAHR8C,GAGQ,CAFN5C,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAA2B,YAArB,iBAAc,EAAA,KACpBA,EAAmF,QAAA,sCAAnErB,EAAQ,MAAA+C,GAAE,KAAK,OAAO,YAAY,YAAY,aAAa,KAAA,gBAA3D/C,EAAA,KAAQ,CAAA,KAE1BqB,EAEM,MAFN+C,GAEM,CADJ/C,EAAwF,SAAA,CAAhF,MAAM,UAAU,KAAK,SAAU,SAAQ,CAAGrB,EAAA,MAAS,KAAA,CAAI,EAAI,eAAY,EAAAqE,EAAA,CAAA,GAEjF5C,EAKEiC,EAAA,CAJA,KAAK,sBACL,OAAO,OACP,KAAK,cACJ,WAAc1D,EAAA,OAAQ,WAAA,CAAA,6BAO/ByB,EAoBa6C,EAAA,CApBD,KAAK,QAAM,WACrB,IAAA,OAkBM,OAlBKrE,EAAA,WAAXmB,EAkBM,MAAA,OAlBY,MAAM,QAAS,yBAAYnB,EAAA,MAAK,KAAA,CAAA,MAAA,CAAA,EAAA,GAChDoB,EAgBM,MAhBNkD,GAgBM,CAfJlD,EAAiD,KAAA,KAA7C,uBAAoBU,EAAG9B,EAAA,MAAM,QAAQ,EAAA,CAAA,EACzCoB,EAGI,IAHJmD,GAAgB,iBACAvE,EAAA,MAAM,OAAOyC,EAAAA,KAAU,OAAVA,YAAAA,EAAgB,kBAAsB,kEAEnE,CAAA,EACArB,EAGQ,QAHRoD,GAGQ,CAFNlD,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAF,EAAyB,YAAnB,eAAY,EAAA,KAClBA,EAAuE,QAAA,sCAAvDnB,EAAO,MAAA6C,GAAE,KAAK,WAAW,aAAa,cAAA,gBAAtC7C,EAAA,KAAO,CAAA,KAEzBmB,EAKM,MALNqD,GAKM,CAJJrD,EAES,SAAA,CAFD,MAAM,UAAW,SAAUnB,EAAA,MAAQ,OAAM,EAAO,QAAOa,CAAA,EAAgB,oBAE/E,EAAA4D,EAAA,EACAtD,EAA2D,SAAA,CAAnD,MAAM,QAAS,uBAAOpB,EAAA,MAAK,KAAA,EAAS,QAAM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.stack-form[data-v-09c4a1c4]{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--line-soft);display:flex;flex-direction:column;gap:var(--s3)}.stack-form h3[data-v-09c4a1c4]{font-size:var(--t-sm);color:var(--fg-dim)}td.right[data-v-09c4a1c4]{text-align:end}.two-up>.field[data-v-09c4a1c4]{flex:1 1 200px}td.right button[data-v-09c4a1c4]{margin-inline-start:var(--s2)}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as Z,c as r,a as e,f as o,e as d,w as m,_ as A,x as N,g as f,F as x,t as u,s as z,h as j,i as E,v as G,a3 as H,G as O,r as v,C as V,A as C,B as S,o as n,N as I}from"./index-jLPJ82ve.js";import{_ as P}from"./ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js";import{E as J}from"./EquivalentCommand-DyCAI1Cy.js";import{_ as K}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";import{_ as q}from"./RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js";import{_ as F}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{_ as Q}from"./_plugin-vue_export-helper-DlAUqK2U.js";import"./useFormat-CKJxQp_i.js";const X={class:"page-head"},Y={class:"sub"},ee={key:1,class:"panel"},te={class:"grid-2"},ae={class:"panel"},se={class:"phead"},le={class:"mute",style:{"font-size":"var(--t-xs)"}},ne={key:1,class:"cards"},oe={class:"name","data-label":"name"},re={class:"break","data-label":"value"},ie={class:"dim nowrap","data-label":"updated"},ue={class:"right nowrap","data-label":""},de=["onClick"],ce={key:2,class:"hint"},ve={class:"panel"},pe={class:"phead"},me={class:"mute",style:{"font-size":"var(--t-xs)"}},fe={key:1,class:"cards"},he={class:"name","data-label":"name"},be={class:"dim nowrap","data-label":"updated"},_e={class:"right nowrap","data-label":""},ge=["onClick"],ye={key:2,class:"hint"},ke={class:"panel"},we={class:"section",style:{"margin-bottom":"var(--s3)"}},$e={class:"row wrap two-up"},Ae={class:"field grow"},Ne=["disabled"],xe={class:"field grow"},Ee=["type","placeholder"],Ve={key:0,class:"s-failed",style:{"margin-top":"var(--s2)"}},Ce={class:"row",style:{"margin-top":"var(--s3)"}},Se={class:"check"},Me=["disabled"],De={key:1,class:"hint"},Re={class:"row",style:{"margin-top":"var(--s4)"}},Ue=["disabled"],Be={key:1,class:"mute",style:{"font-size":"var(--t-sm)"}},Te=Z({__name:"VariablesView",setup(ze){const h=v([]),b=v(!1),c=v(""),M=v(!1),_=V(()=>h.value.filter(s=>!s.secret)),g=V(()=>h.value.filter(s=>s.secret)),a=v({name:"",value:"",secret:!1}),y=v(!1),i=v(null),D=/^[A-Za-z_][A-Za-z0-9_]*$/,L=V(()=>!y.value&&D.test(a.value.name)&&a.value.value.length>0);async function k(){const s=await C.get("/api/host-vars");if(b.value=!0,s.status===404){M.value=!0;return}if(!s.ok){c.value=S(s,"load the variables");return}c.value="",h.value=s.body.entries??[]}k();function R(s){i.value=s,a.value={name:s.name,value:s.value??"",secret:s.secret}}function w(){i.value=null,a.value={name:"",value:"",secret:!1}}async function W(){y.value=!0;const s=await C.put(`/api/host-vars/${a.value.name}`,{value:a.value.value,secret:a.value.secret});if(y.value=!1,!s.ok){c.value=S(s,`store ${a.value.name}`);return}c.value="",I("ok",`Stored ${a.value.name}.`),w(),k()}async function U(s){var p;const t=await C.del(`/api/host-vars/${s.name}`);if(!t.ok){c.value=S(t,`delete ${s.name}`);return}I("ok",`Deleted ${s.name}. Specs referencing it will fail to resolve until it exists again.`),((p=i.value)==null?void 0:p.name)===s.name&&w(),k()}return(s,t)=>{var p,B,T;return n(),r("div",null,[e("div",X,[e("div",null,[t[5]||(t[5]=e("h1",null,"Variables & secrets",-1)),e("div",Y,[t[4]||(t[4]=o(" Host-level values every spec can reference ",-1)),d(A,{label:"how a spec uses these"},{default:m(()=>[...t[3]||(t[3]=[o(" Write ",-1),e("code",null,"${vars.NAME}",-1),o(" or ",-1),e("code",null,"${secrets.NAME}",-1),o(" anywhere a spec interpolates — the ",-1),e("code",null,"env:",-1),o(" block, hooks, compose settings. The spelling is deliberate: plain ",-1),e("code",null,"${NAME}",-1),o(' keeps meaning "whatever the request supplied", so the two can never collide. ',-1)])]),_:1})])])]),c.value?(n(),N(K,{key:0,text:c.value,title:"Something went wrong."},null,8,["text"])):f("",!0),M.value?(n(),r("section",ee,[...t[6]||(t[6]=[e("div",{class:"banner plain"},[e("b",null,"This server has no variable store."),e("p",null,"It is an older build of pstack. Upgrade the host to use host-level variables.")],-1)])])):(n(),r(x,{key:2},[e("div",te,[e("section",ae,[e("div",se,[t[8]||(t[8]=e("h2",{class:"section"},"Variables",-1)),d(A,{label:"what belongs here"},{default:m(()=>[...t[7]||(t[7]=[o(" Configuration that is fine to read back: a region, an image tag, the log level a preview should run at. Referenced as ",-1),e("code",null,"${vars.NAME}",-1),o(". ",-1)])]),_:1}),t[9]||(t[9]=e("span",{class:"grow"},null,-1)),e("span",le,u(_.value.length),1)]),b.value?_.value.length?(n(),r("table",ne,[t[11]||(t[11]=e("thead",null,[e("tr",null,[e("th",null,"Name"),e("th",null,"Value"),e("th",null,"Updated"),e("th",{"aria-label":"Actions"})])],-1)),e("tbody",null,[(n(!0),r(x,null,z(_.value,l=>(n(),r("tr",{key:l.name},[e("td",oe,[e("code",null,u(l.name),1)]),e("td",re,u(l.value),1),e("td",ie,[d(q,{at:l.updatedAt},null,8,["at"])]),e("td",ue,[e("button",{class:"ghost sm",onClick:$=>R(l)},"Edit",8,de),d(P,{class:"danger sm",confirm:`Delete ${l.name}? Specs referencing it stop resolving.`,onRun:$=>U(l)},{default:m(()=>[...t[10]||(t[10]=[o(" Delete ",-1)])]),_:1},8,["confirm","onRun"])])]))),128))])])):(n(),r("p",ce,"No variables yet.")):(n(),N(F,{key:0,rows:2}))]),e("section",ve,[e("div",pe,[t[13]||(t[13]=e("h2",{class:"section"},"Secrets",-1)),d(A,{label:"what makes a secret different"},{default:m(()=>[...t[12]||(t[12]=[o(" The value is write-only: no page and no API route ever returns it, and it is scrubbed from job and container logs by content. Referenced as ",-1),e("code",null,"${secrets.NAME}",-1),o(" — refused in ",-1),e("code",null,"stack:",-1),o(", because a stack name appears in container names and hostnames that cannot be redacted. ",-1)])]),_:1}),t[14]||(t[14]=e("span",{class:"grow"},null,-1)),e("span",me,u(g.value.length),1)]),b.value?g.value.length?(n(),r("table",fe,[t[16]||(t[16]=e("thead",null,[e("tr",null,[e("th",null,"Name"),e("th",null,"Updated"),e("th",{"aria-label":"Actions"})])],-1)),e("tbody",null,[(n(!0),r(x,null,z(g.value,l=>(n(),r("tr",{key:l.name},[e("td",he,[e("code",null,u(l.name),1)]),e("td",be,[d(q,{at:l.updatedAt},null,8,["at"])]),e("td",_e,[e("button",{class:"ghost sm",onClick:$=>R(l)},"Replace",8,ge),d(P,{class:"danger sm",confirm:`Delete ${l.name}? Specs referencing it stop resolving.`,onRun:$=>U(l)},{default:m(()=>[...t[15]||(t[15]=[o(" Delete ",-1)])]),_:1},8,["confirm","onRun"])])]))),128))])])):(n(),r("p",ye,"No secrets yet.")):(n(),N(F,{key:0,rows:2}))])]),e("section",ke,[e("h2",we,u(i.value?i.value.secret?`Replace the value of ${i.value.name}`:`Edit ${i.value.name}`:"Add a variable or secret"),1),e("form",{onSubmit:j(W,["prevent"])},[e("div",$e,[e("label",Ae,[t[17]||(t[17]=e("span",null,"Name",-1)),E(e("input",{"onUpdate:modelValue":t[0]||(t[0]=l=>a.value.name=l),type:"text",placeholder:"DB_PASSWORD",spellcheck:"false",autocomplete:"off",disabled:!!i.value},null,8,Ne),[[G,a.value.name,void 0,{trim:!0}]])]),e("label",xe,[t[18]||(t[18]=e("span",null,"Value",-1)),E(e("input",{"onUpdate:modelValue":t[1]||(t[1]=l=>a.value.value=l),type:a.value.secret?"password":"text",placeholder:(p=i.value)!=null&&p.secret?"enter the new value":"eu-central",spellcheck:"false",autocomplete:"off"},null,8,Ee),[[H,a.value.value]])])]),a.value.name&&!D.test(a.value.name)?(n(),r("p",Ve," Letters, digits and _ only, not starting with a digit — it has to be referenceable in a spec. ")):f("",!0),e("div",Ce,[e("label",Se,[E(e("input",{"onUpdate:modelValue":t[2]||(t[2]=l=>a.value.secret=l),type:"checkbox",disabled:((B=i.value)==null?void 0:B.secret)===!0},null,8,Me),[[O,a.value.secret]]),t[19]||(t[19]=o(" Secret — the value can never be read back ",-1))])]),(T=i.value)!=null&&T.secret?(n(),r("p",De," A secret cannot be turned back into a readable variable — that would reveal a value stored write-only. Delete it and create a variable instead, re-entering the value. ")):f("",!0),e("div",Re,[e("button",{class:"primary",type:"submit",disabled:!L.value},u(i.value?"Save":"Add"),9,Ue),i.value?(n(),r("button",{key:0,type:"button",class:"ghost",onClick:w},"Cancel")):f("",!0),i.value?f("",!0):(n(),r("span",Be,[t[20]||(t[20]=o(" Reference it as ",-1)),e("code",null,"$"+u("{")+u(a.value.secret?"secrets":"vars")+"."+u(a.value.name||"NAME")+u("}"),1),t[21]||(t[21]=o(" in any spec ",-1))]))]),d(J,{what:"storing this value",method:"PUT",path:`/api/host-vars/${a.value.name||"NAME"}`,body:{value:a.value.secret?"…":a.value.value||"…",secret:a.value.secret}},null,8,["path","body"])],32)])],64))])}}}),Ge=Q(Te,[["__scopeId","data-v-70159fb9"]]);export{Ge as default};
|
|
2
|
-
//# sourceMappingURL=VariablesView-Xn79UX3b.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"VariablesView-Xn79UX3b.js","sources":["../../src/views/VariablesView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Host variables & secrets — the GitHub model, scoped to this host.\n *\n * TWO SECTIONS BECAUSE THEY ARE TWO CONTRACTS. A variable's value is configuration anyone may read\n * back; a secret's value goes IN through this form and never comes back OUT — the list shows its\n * name and when it changed, nothing else, because the server keeps no route that returns it. The\n * form says so at the moment of storing, not in documentation nobody reads.\n *\n * Specs reference these as `${vars.NAME}` and `${secrets.NAME}` — spelled differently from plain\n * `${NAME}` on purpose, so a spec that reads host state says so on its face.\n */\nimport { computed, ref } from 'vue';\nimport { api, problem } from '../api/client';\nimport { toast } from '../composables/useToasts';\nimport ActionButton from '../components/ActionButton.vue';\nimport EquivalentCommand from '../components/EquivalentCommand.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport RelativeTime from '../components/RelativeTime.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\n\ntype Entry = { name: string; value: string | null; secret: boolean; updatedAt: number };\n\nconst entries = ref<Entry[]>([]);\nconst loaded = ref(false);\nconst listError = ref('');\nconst unsupported = ref(false);\n\nconst vars = computed(() => entries.value.filter((e) => !e.secret));\nconst secrets = computed(() => entries.value.filter((e) => e.secret));\n\nconst form = ref({ name: '', value: '', secret: false });\nconst saving = ref(false);\n/** Editing state: the row being replaced. For a secret this means re-entering the value. */\nconst editing = ref<Entry | null>(null);\n\nconst NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst canSave = computed(\n () => !saving.value && NAME.test(form.value.name) && form.value.value.length > 0,\n);\n\nasync function load(): Promise<void> {\n const r = await api.get<{ entries: Entry[] }>('/api/host-vars');\n loaded.value = true;\n if (r.status === 404) {\n unsupported.value = true;\n return;\n }\n if (!r.ok) {\n listError.value = problem(r, 'load the variables');\n return;\n }\n listError.value = '';\n entries.value = r.body.entries ?? [];\n}\nvoid load();\n\nfunction startEdit(e: Entry): void {\n editing.value = e;\n form.value = { name: e.name, value: e.value ?? '', secret: e.secret };\n}\n\nfunction cancelEdit(): void {\n editing.value = null;\n form.value = { name: '', value: '', secret: false };\n}\n\nasync function save(): Promise<void> {\n saving.value = true;\n const r = await api.put(`/api/host-vars/${form.value.name}`, {\n value: form.value.value,\n secret: form.value.secret,\n });\n saving.value = false;\n if (!r.ok) {\n listError.value = problem(r, `store ${form.value.name}`);\n return;\n }\n listError.value = '';\n toast('ok', `Stored ${form.value.name}.`);\n cancelEdit();\n void load();\n}\n\nasync function remove(e: Entry): Promise<void> {\n const r = await api.del(`/api/host-vars/${e.name}`);\n if (!r.ok) {\n listError.value = problem(r, `delete ${e.name}`);\n return;\n }\n toast('ok', `Deleted ${e.name}. Specs referencing it will fail to resolve until it exists again.`);\n if (editing.value?.name === e.name) cancelEdit();\n void load();\n}\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Variables & secrets</h1>\n <div class=\"sub\">\n Host-level values every spec can reference\n <InfoHint label=\"how a spec uses these\">\n Write <code>${vars.NAME}</code> or <code>${secrets.NAME}</code> anywhere a spec\n interpolates — the <code>env:</code> block, hooks, compose settings. The spelling is\n deliberate: plain <code>${NAME}</code> keeps meaning \"whatever the request supplied\", so\n the two can never collide.\n </InfoHint>\n </div>\n </div>\n </div>\n\n <ErrorNote v-if=\"listError\" :text=\"listError\" title=\"Something went wrong.\" />\n\n <section v-if=\"unsupported\" class=\"panel\">\n <div class=\"banner plain\">\n <b>This server has no variable store.</b>\n <p>It is an older build of pstack. Upgrade the host to use host-level variables.</p>\n </div>\n </section>\n\n <template v-else>\n <div class=\"grid-2\">\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Variables</h2>\n <InfoHint label=\"what belongs here\">\n Configuration that is fine to read back: a region, an image tag, the log level a\n preview should run at. Referenced as <code>${vars.NAME}</code>.\n </InfoHint>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-xs)\">{{ vars.length }}</span>\n </div>\n\n <SkeletonList v-if=\"!loaded\" :rows=\"2\" />\n <table v-else-if=\"vars.length\" class=\"cards\">\n <thead>\n <tr>\n <th>Name</th>\n <th>Value</th>\n <th>Updated</th>\n <th aria-label=\"Actions\" />\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"e in vars\" :key=\"e.name\">\n <td class=\"name\" data-label=\"name\"><code>{{ e.name }}</code></td>\n <td class=\"break\" data-label=\"value\">{{ e.value }}</td>\n <td class=\"dim nowrap\" data-label=\"updated\"><RelativeTime :at=\"e.updatedAt\" /></td>\n <td class=\"right nowrap\" data-label=\"\">\n <button class=\"ghost sm\" @click=\"startEdit(e)\">Edit</button>\n <ActionButton class=\"danger sm\" :confirm=\"`Delete ${e.name}? Specs referencing it stop resolving.`\" @run=\"remove(e)\">\n Delete\n </ActionButton>\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"hint\">No variables yet.</p>\n </section>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Secrets</h2>\n <InfoHint label=\"what makes a secret different\">\n The value is write-only: no page and no API route ever returns it, and it is scrubbed\n from job and container logs by content. Referenced as <code>${secrets.NAME}</code> —\n refused in <code>stack:</code>, because a stack name appears in container names and\n hostnames that cannot be redacted.\n </InfoHint>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-xs)\">{{ secrets.length }}</span>\n </div>\n\n <SkeletonList v-if=\"!loaded\" :rows=\"2\" />\n <table v-else-if=\"secrets.length\" class=\"cards\">\n <thead>\n <tr>\n <th>Name</th>\n <th>Updated</th>\n <th aria-label=\"Actions\" />\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"e in secrets\" :key=\"e.name\">\n <td class=\"name\" data-label=\"name\"><code>{{ e.name }}</code></td>\n <td class=\"dim nowrap\" data-label=\"updated\"><RelativeTime :at=\"e.updatedAt\" /></td>\n <td class=\"right nowrap\" data-label=\"\">\n <button class=\"ghost sm\" @click=\"startEdit(e)\">Replace</button>\n <ActionButton class=\"danger sm\" :confirm=\"`Delete ${e.name}? Specs referencing it stop resolving.`\" @run=\"remove(e)\">\n Delete\n </ActionButton>\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"hint\">No secrets yet.</p>\n </section>\n </div>\n\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">\n {{ editing ? (editing.secret ? `Replace the value of ${editing.name}` : `Edit ${editing.name}`) : 'Add a variable or secret' }}\n </h2>\n\n <form @submit.prevent=\"save\">\n <div class=\"row wrap two-up\">\n <label class=\"field grow\">\n <span>Name</span>\n <input\n v-model.trim=\"form.name\"\n type=\"text\"\n placeholder=\"DB_PASSWORD\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n :disabled=\"!!editing\"\n />\n </label>\n <label class=\"field grow\">\n <span>Value</span>\n <input\n v-model=\"form.value\"\n :type=\"form.secret ? 'password' : 'text'\"\n :placeholder=\"editing?.secret ? 'enter the new value' : 'eu-central'\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n </label>\n </div>\n <p v-if=\"form.name && !NAME.test(form.name)\" class=\"s-failed\" style=\"margin-top: var(--s2)\">\n Letters, digits and _ only, not starting with a digit — it has to be referenceable in a spec.\n </p>\n\n <div class=\"row\" style=\"margin-top: var(--s3)\">\n <label class=\"check\">\n <input v-model=\"form.secret\" type=\"checkbox\" :disabled=\"editing?.secret === true\" />\n Secret — the value can never be read back\n </label>\n </div>\n <p v-if=\"editing?.secret\" class=\"hint\">\n A secret cannot be turned back into a readable variable — that would reveal a value\n stored write-only. Delete it and create a variable instead, re-entering the value.\n </p>\n\n <div class=\"row\" style=\"margin-top: var(--s4)\">\n <button class=\"primary\" type=\"submit\" :disabled=\"!canSave\">\n {{ editing ? 'Save' : 'Add' }}\n </button>\n <button v-if=\"editing\" type=\"button\" class=\"ghost\" @click=\"cancelEdit\">Cancel</button>\n <span v-if=\"!editing\" class=\"mute\" style=\"font-size: var(--t-sm)\">\n Reference it as <code>${{ '{' }}{{ form.secret ? 'secrets' : 'vars' }}.{{ form.name || 'NAME' }}{{ '}' }}</code> in any spec\n </span>\n </div>\n <EquivalentCommand\n what=\"storing this value\"\n method=\"PUT\"\n :path=\"`/api/host-vars/${form.name || 'NAME'}`\"\n :body=\"{ value: form.secret ? '…' : form.value || '…', secret: form.secret }\"\n />\n </form>\n </section>\n </template>\n </div>\n</template>\n\n<style scoped>\ntd.right {\n text-align: end;\n}\ntd.right button {\n margin-inline-start: var(--s2);\n}\n.two-up > .field {\n flex: 1 1 200px;\n}\n</style>\n"],"names":["entries","ref","loaded","listError","unsupported","vars","computed","e","secrets","form","saving","editing","NAME","canSave","load","r","api","problem","startEdit","cancelEdit","save","toast","remove","_a","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_hoisted_2","_createVNode","InfoHint","_createBlock","ErrorNote","_openBlock","_hoisted_3","_Fragment","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_toDisplayString","_hoisted_8","_renderList","_hoisted_9","_hoisted_10","_hoisted_11","RelativeTime","_hoisted_12","$event","_hoisted_13","ActionButton","_hoisted_14","SkeletonList","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_29","_vModelDynamic","_hoisted_31","_hoisted_32","_hoisted_33","_b","_vModelCheckbox","_c","_hoisted_35","_hoisted_36","_hoisted_37","_hoisted_38","EquivalentCommand"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
td.right[data-v-70159fb9]{text-align:end}td.right button[data-v-70159fb9]{margin-inline-start:var(--s2)}.two-up>.field[data-v-70159fb9]{flex:1 1 200px}
|