@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,2 +1,2 @@
|
|
|
1
|
-
import{S as n,C as i,A as d,B as l,
|
|
2
|
-
//# sourceMappingURL=useDeployment-
|
|
1
|
+
import{S as n,C as i,A as d,B as l,K as p,U as c}from"./index-DVCVnKbf.js";import{w as u,r as y}from"./useVars-VN9gYdR6.js";const e=n({id:"",detail:null,error:"",loading:!1,vars:[]}),f=i(()=>p(e.vars)),k=i(()=>c(e.id)),b=i(()=>{var a;return((a=e.detail)==null?void 0:a.kind)==="shared"}),h=i(()=>{var a;return(((a=e.detail)==null?void 0:a.axes)??[]).filter(s=>!s.verifiable).map(s=>s.name)});async function t(){const a=e.id;e.loading=!0;const s=await d.get(`/api/deployments/${encodeURIComponent(a)}${f.value}`);if(e.id!==a)return;if(e.loading=!1,!s.ok){e.detail=null,e.error=l(s,`GET /api/deployments/${a}`);return}e.error="";const r=s.body;e.detail={id:r.id??a,kind:r.kind==="shared"?"shared":"isolated",createdAt:r.createdAt,updatedAt:r.updatedAt,stack:typeof r.stack=="string"?r.stack:"",busy:r.busy??null,compose:r.compose?{file:r.compose.file??"?",profiles:r.compose.profiles??[],overlays:r.compose.overlays??[]}:null,requires:Array.isArray(r.requires)?r.requires:[],axes:(Array.isArray(r.axes)?r.axes:[]).map(o=>({name:o.name,hooks:Array.isArray(o.hooks)?o.hooks:[],verifiable:o.verifiable??(Array.isArray(o.hooks)&&o.hooks.includes("assert_gone"))})),env:Array.isArray(r.env)?r.env:[]}}function g(a){e.id===a&&(e.detail||e.error)||(e.id=a,e.detail=null,e.error="",e.vars=y(a),t())}function m(){u(e.id,e.vars)}function x(){m(),t()}export{x as a,e as d,b as i,g as o,m as p,k as r,h as u,f as v};
|
|
2
|
+
//# sourceMappingURL=useDeployment-752fTjLE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDeployment-
|
|
1
|
+
{"version":3,"file":"useDeployment-752fTjLE.js","sources":["../../src/composables/useDeployment.ts"],"sourcesContent":["/**\n * The deployment currently open in the detail view.\n *\n * A module singleton rather than provide/inject, because the six tabs are CHILD ROUTES: they are\n * siblings of each other and are mounted and unmounted independently, so passing the loaded\n * deployment down as a prop would mean re-fetching it on every tab change. Switching tabs must not\n * re-fetch or blank the panel — only opening a DIFFERENT deployment does.\n *\n * `vars` here are REQUEST-TIME variables, sent as `?query` on every route that resolves this\n * deployment. See `useVars.ts` for why they matter and what they can get wrong.\n */\n\nimport { computed, reactive } from 'vue';\nimport { api, problem, query } from '../api/client';\nimport type { Deployment, VarPair } from '../api/types';\nimport { readVars, writeVars } from './useVars';\nimport { rowFor } from './useControlPlane';\n\nexport const dep = reactive({\n id: '',\n detail: null as Deployment | null,\n /** The resolve failure, verbatim. It is a 400 that NAMES the missing variable. */\n error: '',\n loading: false,\n vars: [] as VarPair[],\n});\n\nexport const varsQuery = computed(() => query(dep.vars));\n\n/** The list row, which is the only place the server exposes stored `vars` and `specName` today. */\nexport const row = computed(() => rowFor(dep.id));\n\nexport const isShared = computed(() => dep.detail?.kind === 'shared');\n\nexport const unverifiableAxes = computed(() =>\n (dep.detail?.axes ?? []).filter((a) => !a.verifiable).map((a) => a.name),\n);\n\nexport async function loadDetail(): Promise<void> {\n const id = dep.id;\n dep.loading = true;\n const r = await api.get<Deployment>(`/api/deployments/${encodeURIComponent(id)}${varsQuery.value}`);\n // Navigated to another deployment while this was in flight: drop the answer rather than render\n // one deployment's spec under another's id.\n if (dep.id !== id) return;\n dep.loading = false;\n\n if (!r.ok) {\n dep.detail = null;\n dep.error = problem(r, `GET /api/deployments/${id}`);\n return;\n }\n dep.error = '';\n const b = r.body;\n dep.detail = {\n id: b.id ?? id,\n kind: b.kind === 'shared' ? 'shared' : 'isolated',\n createdAt: b.createdAt,\n updatedAt: b.updatedAt,\n stack: typeof b.stack === 'string' ? b.stack : '',\n busy: b.busy ?? null,\n compose: b.compose\n ? {\n file: b.compose.file ?? '?',\n profiles: b.compose.profiles ?? [],\n overlays: b.compose.overlays ?? [],\n }\n : null,\n requires: Array.isArray(b.requires) ? b.requires : [],\n axes: (Array.isArray(b.axes) ? b.axes : []).map((a) => ({\n name: a.name,\n hooks: Array.isArray(a.hooks) ? a.hooks : [],\n // Derive it if an older build omits the field. Defaulting to `false` would claim an axis is\n // unverifiable when it may define a perfectly good `assert_gone`.\n verifiable: a.verifiable ?? (Array.isArray(a.hooks) && a.hooks.includes('assert_gone')),\n })),\n env: Array.isArray(b.env) ? b.env : [],\n };\n}\n\n/** Point the singleton at a deployment. Resets everything a stale id could leak into the new one. */\nexport function openDeployment(id: string): void {\n if (dep.id === id && (dep.detail || dep.error)) return; // a tab change, not a navigation\n dep.id = id;\n dep.detail = null;\n dep.error = '';\n dep.vars = readVars(id);\n void loadDetail();\n}\n\nexport function persistVars(): void {\n writeVars(dep.id, dep.vars);\n}\n\nexport function applyVars(): void {\n persistVars();\n void loadDetail();\n}\n"],"names":["dep","reactive","varsQuery","computed","query","row","rowFor","isShared","_a","unverifiableAxes","a","loadDetail","id","r","api","problem","b","openDeployment","readVars","persistVars","writeVars","applyVars"],"mappings":"4HAkBO,MAAMA,EAAMC,EAAS,CAC1B,GAAI,GACJ,OAAQ,KAER,MAAO,GACP,QAAS,GACT,KAAM,CAAA,CACR,CAAC,EAEYC,EAAYC,EAAS,IAAMC,EAAMJ,EAAI,IAAI,CAAC,EAG1CK,EAAMF,EAAS,IAAMG,EAAON,EAAI,EAAE,CAAC,EAEnCO,EAAWJ,EAAS,IAAA,OAAM,QAAAK,EAAAR,EAAI,SAAJ,YAAAQ,EAAY,QAAS,SAAQ,EAEvDC,EAAmBN,EAAS,IAAA,OACtC,SAAAK,EAAAR,EAAI,SAAJ,YAAAQ,EAAY,OAAQ,CAAA,GAAI,OAAQE,GAAM,CAACA,EAAE,UAAU,EAAE,IAAKA,GAAMA,EAAE,IAAI,EACzE,EAEA,eAAsBC,GAA4B,CAChD,MAAMC,EAAKZ,EAAI,GACfA,EAAI,QAAU,GACd,MAAMa,EAAI,MAAMC,EAAI,IAAgB,oBAAoB,mBAAmBF,CAAE,CAAC,GAAGV,EAAU,KAAK,EAAE,EAGlG,GAAIF,EAAI,KAAOY,EAAI,OAGnB,GAFAZ,EAAI,QAAU,GAEV,CAACa,EAAE,GAAI,CACTb,EAAI,OAAS,KACbA,EAAI,MAAQe,EAAQF,EAAG,wBAAwBD,CAAE,EAAE,EACnD,MACF,CACAZ,EAAI,MAAQ,GACZ,MAAMgB,EAAIH,EAAE,KACZb,EAAI,OAAS,CACX,GAAIgB,EAAE,IAAMJ,EACZ,KAAMI,EAAE,OAAS,SAAW,SAAW,WACvC,UAAWA,EAAE,UACb,UAAWA,EAAE,UACb,MAAO,OAAOA,EAAE,OAAU,SAAWA,EAAE,MAAQ,GAC/C,KAAMA,EAAE,MAAQ,KAChB,QAASA,EAAE,QACP,CACE,KAAMA,EAAE,QAAQ,MAAQ,IACxB,SAAUA,EAAE,QAAQ,UAAY,CAAA,EAChC,SAAUA,EAAE,QAAQ,UAAY,CAAA,CAAC,EAEnC,KACJ,SAAU,MAAM,QAAQA,EAAE,QAAQ,EAAIA,EAAE,SAAW,CAAA,EACnD,MAAO,MAAM,QAAQA,EAAE,IAAI,EAAIA,EAAE,KAAO,CAAA,GAAI,IAAKN,IAAO,CACtD,KAAMA,EAAE,KACR,MAAO,MAAM,QAAQA,EAAE,KAAK,EAAIA,EAAE,MAAQ,CAAA,EAG1C,WAAYA,EAAE,aAAe,MAAM,QAAQA,EAAE,KAAK,GAAKA,EAAE,MAAM,SAAS,aAAa,EAAA,EACrF,EACF,IAAK,MAAM,QAAQM,EAAE,GAAG,EAAIA,EAAE,IAAM,CAAA,CAAC,CAEzC,CAGO,SAASC,EAAeL,EAAkB,CAC3CZ,EAAI,KAAOY,IAAOZ,EAAI,QAAUA,EAAI,SACxCA,EAAI,GAAKY,EACTZ,EAAI,OAAS,KACbA,EAAI,MAAQ,GACZA,EAAI,KAAOkB,EAASN,CAAE,EACjBD,EAAA,EACP,CAEO,SAASQ,GAAoB,CAClCC,EAAUpB,EAAI,GAAIA,EAAI,IAAI,CAC5B,CAEO,SAASqB,GAAkB,CAChCF,EAAA,EACKR,EAAA,CACP"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function a(t){return t?new Date(t).toLocaleString("sv"):"—"}function u(t,n){if(!t)return"—";const r=Math.max(0,Math.round(((n??Date.now())-t)/1e3)),o=r<60?`${r}s`:`${Math.floor(r/60)}m${String(r%60).padStart(2,"0")}s`;return n?o:`${o}…`}function s(t){if(!t)return"—";const n=Math.max(0,Math.round((Date.now()-t)/1e3));if(n<45)return"just now";const r=Math.round(n/60);if(r<60)return`${r}m ago`;const o=Math.round(r/60);return o<24?`${o}h ago`:`${Math.round(o/24)}d ago`}const e={up:"Deploy",down:"Tear down",verify:"Verify"};function c(t){return t?e[t]??t:"—"}function i(t){if(!t)return"—";const n=t.replace(/[-_]+/g," ");return n.charAt(0).toUpperCase()+n.slice(1)}export{c as a,s as b,a as c,i as s,u as t};
|
|
2
|
+
//# sourceMappingURL=useFormat-DVXTfIS-.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFormat-
|
|
1
|
+
{"version":3,"file":"useFormat-DVXTfIS-.js","sources":["../../src/composables/useFormat.ts"],"sourcesContent":["/**\n * Formatting shared across views. All times are the VIEWER's local time, with no zone marker,\n * because everything on the page — log lines, job stamps, deployment timestamps — comes from the\n * same clock and mixing a rendered zone into one of them is how two timestamps stop comparing.\n */\n\n/** ISO 8601 shaped and sortable: `2026-07-28 05:17:00`. `sv` is the locale that renders it. */\nexport function stamp(ms: number | undefined): string {\n return ms ? new Date(ms).toLocaleString('sv') : '—';\n}\n\n/** Just the clock, for a log line where the date is the same for every row. */\nexport function hhmmss(ms: number | undefined): string {\n return ms ? new Date(ms).toTimeString().slice(0, 8) : '--:--:--';\n}\n\n/** `12s` / `3m07s`. A running job gets an ellipsis so a stale number cannot read as a final one. */\nexport function took(startedAt: number | undefined, endedAt?: number): string {\n if (!startedAt) return '—';\n const s = Math.max(0, Math.round(((endedAt ?? Date.now()) - startedAt) / 1000));\n const text = s < 60 ? `${s}s` : `${Math.floor(s / 60)}m${String(s % 60).padStart(2, '0')}s`;\n return endedAt ? text : `${text}…`;\n}\n\n/** `2 minutes ago`, for an activity feed where the exact second is noise. */\nexport function ago(ms: number | undefined): string {\n if (!ms) return '—';\n const s = Math.max(0, Math.round((Date.now() - ms) / 1000));\n if (s < 45) return 'just now';\n const m = Math.round(s / 60);\n if (m < 60) return `${m}m ago`;\n const h = Math.round(m / 60);\n if (h < 24) return `${h}h ago`;\n return `${Math.round(h / 24)}d ago`;\n}\n\n/**\n * The user-facing name of an action.\n *\n * The API's verbs (`up`, `down`, `verify`) are the CLI's and the spec's vocabulary, and they stay in\n * both. But a BUTTON should say what it does, so the same mapping is used everywhere a person reads\n * an action — buttons, the job list, a job's own heading — rather than only on the buttons, which\n * would leave someone clicking \"Tear down\" and then reading about a job called `down`.\n */\nconst ACTION_LABELS: Record<string, string> = {\n up: 'Deploy',\n down: 'Tear down',\n verify: 'Verify',\n};\n\nexport function actionLabel(action: string | undefined): string {\n if (!action) return '—';\n return ACTION_LABELS[action] ?? action;\n}\n\n/**\n * An API enum, rendered for a person: `isolated` → `Isolated`, `start-failed` → `Start failed`.\n *\n * ONE helper rather than capitalising at each render site, because the two halves have to stay\n * apart: the VALUE stays lowercase everywhere it is compared, stored, sent or put in a class name,\n * and only the rendered text changes. Capitalising in place — `state === 'Running'` — is how a badge\n * starts looking right and a comparison silently stops matching.\n *\n * Sentence case, not Title Case: `Start failed`, not `Start Failed`. Title Case on a status reads\n * like a proper noun, and these are descriptions.\n *\n * Identifiers are NOT sentences and must never come through here — a stack name, container name,\n * deployment id, file name, env var or hostname is a literal, and capitalising one produces a value\n * that does not exist.\n */\nexport function sentence(value: string | null | undefined): string {\n if (!value) return '—';\n const words = value.replace(/[-_]+/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n"],"names":["stamp","ms","took","startedAt","endedAt","s","text","ago","m","h","ACTION_LABELS","actionLabel","action","sentence","value","words"],"mappings":"AAOO,SAASA,EAAMC,EAAgC,CACpD,OAAOA,EAAK,IAAI,KAAKA,CAAE,EAAE,eAAe,IAAI,EAAI,GAClD,CAQO,SAASC,EAAKC,EAA+BC,EAA0B,CAC5E,GAAI,CAACD,EAAW,MAAO,IACvB,MAAME,EAAI,KAAK,IAAI,EAAG,KAAK,QAAQD,GAAW,KAAK,IAAA,GAASD,GAAa,GAAI,CAAC,EACxEG,EAAOD,EAAI,GAAK,GAAGA,CAAC,IAAM,GAAG,KAAK,MAAMA,EAAI,EAAE,CAAC,IAAI,OAAOA,EAAI,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,IACxF,OAAOD,EAAUE,EAAO,GAAGA,CAAI,GACjC,CAGO,SAASC,EAAIN,EAAgC,CAClD,GAAI,CAACA,EAAI,MAAO,IAChB,MAAMI,EAAI,KAAK,IAAI,EAAG,KAAK,OAAO,KAAK,IAAA,EAAQJ,GAAM,GAAI,CAAC,EAC1D,GAAII,EAAI,GAAI,MAAO,WACnB,MAAMG,EAAI,KAAK,MAAMH,EAAI,EAAE,EAC3B,GAAIG,EAAI,GAAI,MAAO,GAAGA,CAAC,QACvB,MAAMC,EAAI,KAAK,MAAMD,EAAI,EAAE,EAC3B,OAAIC,EAAI,GAAW,GAAGA,CAAC,QAChB,GAAG,KAAK,MAAMA,EAAI,EAAE,CAAC,OAC9B,CAUA,MAAMC,EAAwC,CAC5C,GAAI,SACJ,KAAM,YACN,OAAQ,QACV,EAEO,SAASC,EAAYC,EAAoC,CAC9D,OAAKA,EACEF,EAAcE,CAAM,GAAKA,EADZ,GAEtB,CAiBO,SAASC,EAASC,EAA0C,CACjE,GAAI,CAACA,EAAO,MAAO,IACnB,MAAMC,EAAQD,EAAM,QAAQ,SAAU,GAAG,EACzC,OAAOC,EAAM,OAAO,CAAC,EAAE,cAAgBA,EAAM,MAAM,CAAC,CACtD"}
|
package/dist/index.html
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
/* private mode: fall through to the OS preference */
|
|
28
28
|
}
|
|
29
29
|
</script>
|
|
30
|
-
<script type="module" crossorigin src="/assets/index-
|
|
31
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
30
|
+
<script type="module" crossorigin src="/assets/index-DVCVnKbf.js"></script>
|
|
31
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Cyc_WOgY.css">
|
|
32
32
|
</head>
|
|
33
33
|
<body>
|
|
34
34
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as s,c as t,E as i,W as o,g as d,o as a}from"./index-jLPJ82ve.js";const l=["disabled","title","aria-busy"],r={key:0,class:"progress","aria-hidden":"true"},m=s({__name:"ActionButton",props:{pending:{type:Boolean},disabled:{type:Boolean},variant:{},title:{}},setup(e){return(n,c)=>(a(),t("button",{class:i(e.variant),disabled:e.pending||e.disabled,title:e.title,"aria-busy":e.pending?"true":void 0},[o(n.$slots,"default"),e.pending?(a(),t("span",r)):d("",!0)],10,l))}});export{m as _};
|
|
2
|
-
//# sourceMappingURL=ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js","sources":["../../src/components/ActionButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A button that disables itself the moment it is pressed and shows inline progress until the\n * request settles.\n *\n * Optimistic only about the DISABLED state, never about the outcome: every action here starts a\n * job that destroys or creates infrastructure, so the button says \"in flight\", not \"done\". The\n * result is reported by the caller once the server has actually answered.\n *\n * The progress bar sits inside the button rather than replacing the label, so the button keeps its\n * width and the page does not reflow under the pointer mid-click.\n */\ndefineProps<{\n pending?: boolean;\n disabled?: boolean;\n variant?: 'primary' | 'danger' | 'ghost';\n /** Shown as a native tooltip; use it to say WHY a disabled control is disabled. */\n title?: string;\n}>();\n</script>\n\n<template>\n <button\n :class=\"variant\"\n :disabled=\"pending || disabled\"\n :title=\"title\"\n :aria-busy=\"pending ? 'true' : undefined\"\n >\n <slot />\n <span v-if=\"pending\" class=\"progress\" aria-hidden=\"true\" />\n </button>\n</template>\n"],"names":["_createElementBlock","__props","_renderSlot","_ctx","_openBlock","_hoisted_2"],"mappings":"qSAsBEA,EAQS,SAAA,CAPN,QAAOC,EAAA,OAAO,EACd,SAAUA,EAAA,SAAWA,EAAA,SACrB,MAAOA,EAAA,MACP,YAAWA,EAAA,QAAO,OAAY,MAAA,GAE/BC,EAAQC,EAAA,OAAA,SAAA,EACIF,EAAA,SAAZG,EAAA,EAAAJ,EAA2D,OAA3DK,CAA2D"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{p as C,d as r,a as c,r as V}from"./useDeployment-Bgo0U4-I.js";import{d as x,c as n,a as e,f as t,e as v,w,u as o,t as i,_ as T,F as p,s as f,g as d,C as u,o as l,E as k}from"./index-jLPJ82ve.js";import{c as A}from"./useVars-VN9gYdR6.js";import{_ as N}from"./VarEditor.vue_vue_type_script_setup_true_lang-UEG5poOe.js";import{_ as $}from"./HelpModal.vue_vue_type_script_setup_true_lang-CACw-lfW.js";import"./Teleport-SesEhfqH.js";const j={class:"panel"},B={class:"hint"},I={class:"row",style:{"margin-top":"var(--s3)"}},z={class:"mute",style:{"font-size":"var(--t-xs)"}},D={class:"hint"},L={key:0,class:"panel"},R={class:"kvlist",style:{"margin-top":"var(--s3)"}},q={class:"k"},E={class:"v mono"},F={key:0,class:"banner warn"},O={class:"panel"},S={key:0,class:"mute"},U={class:"cards"},K={class:"name","data-label":"key"},W={"data-label":"visibility"},Y={class:"dim","data-label":"length"},G={key:0,class:"badge warn",style:{"margin-left":"6px"}},H={key:0},se=x({__name:"ConfigTab",setup(J){const h=u(()=>Object.keys(r.vars??{}).length),y=u(()=>{var m;return(m=V.value)==null?void 0:m.vars}),b=u(()=>Object.entries(y.value??{})),g=u(()=>A(r.vars,y.value));return(m,s)=>(l(),n("div",null,[e("section",j,[s[6]||(s[6]=e("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Request variables",-1)),e("p",B,[s[3]||(s[3]=t(" Remembered in this browser. Another browser, a CI job or the command line supplies its own. ",-1)),v($,{title:"Why the same variables have to be used for both"},{default:w(()=>[...s[2]||(s[2]=[e("p",null,[t(" These variables are part of how this deployment's name is worked out. Deploy with one value and tear down with another and the two commands are pointing at "),e("b",null,"different things"),t(" — the teardown finishes successfully having removed nothing, and the original is still running. ")],-1),e("p",null,[t(" They are kept per deployment in this browser only, which is what keeps them matched "),e("em",null,"here"),t(". Nothing carries them to anywhere else. ")],-1)])]),_:1})]),v(N,{modelValue:o(r).vars,"onUpdate:modelValue":s[0]||(s[0]=a=>o(r).vars=a),onChange:o(C)},null,8,["modelValue","onChange"]),e("div",I,[e("button",{class:"primary",onClick:s[1]||(s[1]=(...a)=>o(c)&&o(c)(...a))},"Apply & reload"),e("span",z,i(h.value?`${h.value} variable${h.value===1?"":"s"}`:"no variables"),1)]),e("p",D,[s[5]||(s[5]=t(" Add any variable you need — the list below is what the spec declares, not everything it uses. ",-1)),v(T,{label:"why the two differ"},{default:w(()=>[...s[4]||(s[4]=[t(" A spec can consume a variable without declaring it, for instance by building its stack name out of one. If a variable is missing, the error you get when the spec fails to resolve names it exactly — that message is the authoritative list. ",-1)])]),_:1})])]),b.value.length?(l(),n("section",L,[s[10]||(s[10]=e("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Stored on the server",-1)),s[11]||(s[11]=e("p",{class:"dim",style:{"font-size":"var(--t-sm)"}}," Kept with the deployment on the server, so deploying and tearing down agree without anyone passing anything. Anything set above overrides these. ",-1)),e("ul",R,[(l(!0),n(p,null,f(b.value,([a,_])=>(l(),n("li",{key:a},[e("span",q,[e("b",null,i(a),1)]),e("span",E,i(_),1)]))),128))]),g.value.length?(l(),n("div",F,[s[7]||(s[7]=e("b",null,"Your request variables disagree with the stored ones:",-1)),e("b",null,i(g.value.join(", ")),1),s[8]||(s[8]=t(". ",-1)),s[9]||(s[9]=e("p",null,[t(" Both resolve, so nothing will error — they simply resolve to "),e("em",null,"different stacks"),t(". That is the dangerous shape: an action sent from this page would target one stack while the CLI, CI, or another browser targets the other. Clear the override above unless you mean it. ")],-1))])):d("",!0)])):d("",!0),e("section",O,[s[15]||(s[15]=e("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},[t(" Declared env "),e("span",{class:"mute"},"(after interpolation)")],-1)),o(r).detail?(l(),n(p,{key:1},[e("table",U,[s[13]||(s[13]=e("thead",null,[e("tr",null,[e("th",null,"key"),e("th",null,"value"),e("th",null,"visibility"),e("th",null,"length")])],-1)),e("tbody",null,[(l(!0),n(p,null,f(o(r).detail.env,a=>(l(),n("tr",{key:a.key},[e("td",K,i(a.key),1),e("td",{class:k(["name",{masked:a.visibility==="masked"}]),"data-label":"value"},i(a.value),3),e("td",W,[e("span",{class:k(["badge",a.visibility==="masked"?"warn":""])},i(a.visibility),3)]),e("td",Y,[t(i(a.length)+" chars ",1),a.length===0?(l(),n("span",G,"never set")):d("",!0)])]))),128)),o(r).detail.env.length?d("",!0):(l(),n("tr",H,[...s[12]||(s[12]=[e("td",{colspan:"4",class:"mute"},[t("This spec declares no "),e("code",null,"env:"),t(" block.")],-1)])]))])]),s[14]||(s[14]=e("p",{class:"hint"},[t(" A "),e("b",null,"masked"),t(" value was redacted on the host because its name reads as a secret. The real value was never sent to this page, so there is "),e("b",null,"nothing to reveal"),t(" — only its length is shown. Read it on the host if you need it. ")],-1))],64)):(l(),n("p",S,"Unavailable until the spec resolves."))])]))}});export{se as default};
|
|
2
|
-
//# sourceMappingURL=ConfigTab-XGvUbd66.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigTab-XGvUbd66.js","sources":["../../src/views/tabs/ConfigTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Variables (what we SEND) and declared env (what the spec DECLARES). Two different things, on\n * purpose, and the tab explains the difference rather than merging them into one table.\n */\nimport { computed } from 'vue';\nimport { applyVars, dep, persistVars, row } from '../../composables/useDeployment';\nimport InfoHint from '../../components/InfoHint.vue';\n\nimport { conflictingVars } from '../../composables/useVars';\nimport VarEditor from '../../components/VarEditor.vue';\nimport HelpModal from '../../components/HelpModal.vue';\n\n/** A count, not the raw query string — `?PR=7®ION=eu` beside a button is machine talk. */\nconst varCount = computed(() => Object.keys(dep.vars ?? {}).length);\n\nconst storedVars = computed(() => row.value?.vars);\nconst storedList = computed(() => Object.entries(storedVars.value ?? {}));\nconst clashes = computed(() => conflictingVars(dep.vars, storedVars.value));\n</script>\n\n<template>\n <div>\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">Request variables</h2>\n\n <!--\n THE safety rule on this page. A spec interpolates `${VAR}` exactly once, at resolve time,\n and every route that resolves a deployment layers the request's query parameters over the\n server's environment. So `up` sent with PR=7 and a later `down` sent without it resolve to\n two DIFFERENT stacks, and the teardown quietly misses everything the deploy created.\n\n Keeping the pairs in this browser is what holds them matched here. It is a convenience, not\n a guarantee — a different browser, a CI job or the CLI must pass the same ones itself —\n which is why this warning stays visible rather than being dismissible.\n -->\n <p class=\"hint\">\n Remembered in this browser. Another browser, a CI job or the command line supplies its own.\n <HelpModal title=\"Why the same variables have to be used for both\">\n <p>\n These variables are part of how this deployment's name is worked out. Deploy with one\n value and tear down with another and the two commands are pointing at\n <b>different things</b> — the teardown finishes successfully having removed nothing, and\n the original is still running.\n </p>\n <p>\n They are kept per deployment in this browser only, which is what keeps them matched\n <em>here</em>. Nothing carries them to anywhere else.\n </p>\n </HelpModal>\n </p>\n\n <VarEditor v-model=\"dep.vars\" @change=\"persistVars\" />\n\n <div class=\"row\" style=\"margin-top: var(--s3)\">\n <button class=\"primary\" @click=\"applyVars\">Apply & reload</button>\n <span class=\"mute\" style=\"font-size: var(--t-xs)\">\n {{ varCount ? `${varCount} variable${varCount === 1 ? '' : 's'}` : 'no variables' }}\n </span>\n </div>\n\n <p class=\"hint\">\n Add any variable you need — the list below is what the spec declares, not everything it\n uses.\n <InfoHint label=\"why the two differ\">\n A spec can consume a variable without declaring it, for instance by building its stack\n name out of one. If a variable is missing, the error you get when the spec fails to\n resolve names it exactly — that message is the authoritative list.\n </InfoHint>\n </p>\n </section>\n\n <!--\n Newer servers store variables WITH the deployment, which makes `down` self-describing — the\n strictly better arrangement. It does not make the warning above obsolete: a request variable\n still OVERRIDES a stored one, so a different value typed here retargets the stack.\n -->\n <section v-if=\"storedList.length\" class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">Stored on the server</h2>\n <p class=\"dim\" style=\"font-size: var(--t-sm)\">\n Kept with the deployment on the server, so deploying and tearing down agree without anyone\n passing anything. Anything set above overrides these.\n </p>\n <ul class=\"kvlist\" style=\"margin-top: var(--s3)\">\n <li v-for=\"[k, v] in storedList\" :key=\"k\">\n <span class=\"k\"><b>{{ k }}</b></span>\n <span class=\"v mono\">{{ v }}</span>\n </li>\n </ul>\n\n <div v-if=\"clashes.length\" class=\"banner warn\">\n <b>Your request variables disagree with the stored ones:</b>\n <b>{{ clashes.join(', ') }}</b\n >.\n <p>\n Both resolve, so nothing will error — they simply resolve to <em>different stacks</em>.\n That is the dangerous shape: an action sent from this page would target one stack while\n the CLI, CI, or another browser targets the other. Clear the override above unless you\n mean it.\n </p>\n </div>\n </section>\n\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">\n Declared env <span class=\"mute\">(after interpolation)</span>\n </h2>\n\n <p v-if=\"!dep.detail\" class=\"mute\">Unavailable until the spec resolves.</p>\n <template v-else>\n <table class=\"cards\">\n <thead>\n <tr>\n <th>key</th>\n <th>value</th>\n <th>visibility</th>\n <th>length</th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"e in dep.detail.env\" :key=\"e.key\">\n <td class=\"name\" data-label=\"key\">{{ e.key }}</td>\n <td class=\"name\" :class=\"{ masked: e.visibility === 'masked' }\" data-label=\"value\">\n {{ e.value }}\n </td>\n <td data-label=\"visibility\">\n <span class=\"badge\" :class=\"e.visibility === 'masked' ? 'warn' : ''\">\n {{ e.visibility }}\n </span>\n </td>\n <!--\n `length` answers \"did it get set at all?\" without the value. length 0 on a masked\n var is the interesting case: declared, never set — usually a hook about to fail on\n an empty credential.\n -->\n <td class=\"dim\" data-label=\"length\">\n {{ e.length }} chars\n <span v-if=\"e.length === 0\" class=\"badge warn\" style=\"margin-left: 6px\">never set</span>\n </td>\n </tr>\n <tr v-if=\"!dep.detail.env.length\">\n <td colspan=\"4\" class=\"mute\">This spec declares no <code>env:</code> block.</td>\n </tr>\n </tbody>\n </table>\n\n <!--\n Masked is NOT \"hidden pending a click\". The plaintext was never in the response, so there\n is nothing here to reveal — a reveal control would be a lie, and building one would mean\n sending secrets into a browser tab, and from there into screenshots and support tickets.\n Deny-by-default by name, decided on the host.\n -->\n <p class=\"hint\">\n A <b>masked</b> value was redacted on the host because its name reads as a secret. The\n real value was never sent to this page, so there is <b>nothing to reveal</b> — only its\n length is shown. Read it on the host if you need it.\n </p>\n </template>\n </section>\n </div>\n</template>\n"],"names":["varCount","computed","dep","storedVars","_a","row","storedList","clashes","conflictingVars","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","HelpModal","_cache","VarEditor","_unref","$event","persistVars","_hoisted_3","applyVars","args","_hoisted_4","_toDisplayString","_hoisted_5","InfoHint","_openBlock","_hoisted_6","_hoisted_7","_Fragment","_renderList","k","v","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_13","e","_hoisted_14","_normalizeClass","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_12"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as B,H as c,c as i,a,e as l,w as r,t as m,u as t,f as o,E as L,g as d,x as y,F as N,s as S,_,j as x,o as s,T as j,k as q,I as z}from"./index-jLPJ82ve.js";import{d as n,i as E,o as U}from"./useDeployment-Bgo0U4-I.js";import{a as C,w as D,p as f,b as R,c as $,d as V,e as F,_ as H,r as J}from"./ConflictNote.vue_vue_type_script_setup_true_lang-DrzDcr4X.js";import{_ as h}from"./ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js";import{_ as I}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";import"./useVars-VN9gYdR6.js";import"./useFormat-CKJxQp_i.js";const O={class:"page-head"},G={class:"mute",style:{"font-size":"var(--t-sm)"}},K={style:{"font-size":"var(--t-xl)"}},M={class:"sub"},P={key:0},Q={key:1},W={key:2},X={key:1,class:"badge busy"},Y={key:2,class:"row",style:{"flex-wrap":"nowrap"}},Z={class:"tabs","aria-label":"Deployment sections"},ee={key:3,class:"hint"},te={key:4,class:"hint",style:{"margin-bottom":"var(--s4)"}},de=B({__name:"DeploymentDetailView",props:{id:{}},setup(b){const v=b,g=q(),T=[{to:"overview",label:"Overview"},{to:"runtime",label:"Containers & routes"},{to:"config",label:"Config & variables"},{to:"axes",label:"Axes"},{to:"requires",label:"Requires"},{to:"logs",label:"Logs"},{to:"terminal",label:"Terminal"},{to:"danger",label:"Danger"}];return c(()=>v.id,p=>{U(p),J()},{immediate:!0}),c(()=>n.error,p=>{p&&g.currentRoute.value.name==="d.overview"&&g.replace(`/deployments/${encodeURIComponent(v.id)}/config`)}),(p,e)=>{var w;const k=x("RouterLink"),A=x("RouterView");return s(),i("div",null,[a("div",O,[a("div",null,[a("div",G,[l(k,{to:"/deployments"},{default:r(()=>[...e[2]||(e[2]=[o("← deployments",-1)])]),_:1})]),a("h1",K,m(b.id),1),a("div",M,[t(n).detail?(s(),i("span",P,[e[3]||(e[3]=o(" stack ",-1)),a("b",null,m(t(n).detail.stack),1)])):t(n).error?(s(),i("span",Q,"unresolved")):(s(),i("span",W,"loading…"))])]),e[7]||(e[7]=a("span",{class:"grow"},null,-1)),t(n).detail?(s(),i("span",{key:0,class:L(["badge",t(n).detail.kind])},m(t(n).detail.kind),3)):d("",!0),((w=t(n).detail)==null?void 0:w.busy)===!0?(s(),i("span",X,[...e[4]||(e[4]=[a("span",{class:"dot pulse"},null,-1),o("busy",-1)])])):d("",!0),t(n).detail?(s(),i("div",Y,[l(h,{variant:"primary",pending:t(f)==="up",disabled:!!t(f)||t(R),title:t(D)("up"),onClick:e[0]||(e[0]=u=>t(C)("up"))},{default:r(()=>[...e[5]||(e[5]=[o(" Deploy ",-1)])]),_:1},8,["pending","disabled","title"]),l(h,{pending:t(f)==="verify",disabled:!!t(f)||t(R),title:t(D)("verify"),onClick:e[1]||(e[1]=u=>t(C)("verify"))},{default:r(()=>[...e[6]||(e[6]=[o(" Verify ",-1)])]),_:1},8,["pending","disabled","title"])])):d("",!0)]),t($)?(s(),y(I,{key:0,text:t($),title:"The action was refused."},null,8,["text"])):d("",!0),t(V)?(s(),y(H,{key:1,conflict:t(V),"job-id":t(F),style:{"margin-bottom":"var(--s4)"}},null,8,["conflict","job-id"])):d("",!0),a("nav",Z,[(s(),i(N,null,S(T,u=>l(k,{key:u.to,to:`/deployments/${encodeURIComponent(b.id)}/${u.to}`},{default:r(()=>[o(m(u.label),1)]),_:2},1032,["to"])),64))]),t(n).error?(s(),y(I,{key:2,text:t(n).error,title:"Could not resolve this deployment."},null,8,["text"])):d("",!0),t(n).error?(s(),i("p",ee,[e[9]||(e[9]=o(" If that message names a variable, add it under ",-1)),e[10]||(e[10]=a("b",null,"Config & variables",-1)),e[11]||(e[11]=o(" and press ",-1)),e[12]||(e[12]=a("b",null,"Apply & reload",-1)),e[13]||(e[13]=o(". ",-1)),l(_,{label:"why the rest of the page is unavailable"},{default:r(()=>[...e[8]||(e[8]=[o(" Until the spec resolves there is no stack name, and every action on this deployment needs one. ",-1)])]),_:1})])):d("",!0),t(E)?(s(),i("p",te,[e[15]||(e[15]=o(" Shared with every preview on this host. ",-1)),l(_,{label:"what a shared deployment is"},{default:r(()=>[...e[14]||(e[14]=[o(" A host singleton that previews borrow — a database, a queue, a registry mirror. It declares no axes, and tearing it down needs an explicit confirmation, because doing so destroys state every other deployment depends on. ",-1)])]),_:1})])):d("",!0),l(A,null,{default:r(({Component:u})=>[l(j,{name:"view",mode:"out-in"},{default:r(()=>[(s(),y(z(u)))]),_:2},1024)]),_:1})])}}});export{de as default};
|
|
2
|
-
//# sourceMappingURL=DeploymentDetailView-dJjqob1P.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DeploymentDetailView-dJjqob1P.js","sources":["../../src/views/DeploymentDetailView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The detail shell: breadcrumb, tabs, and the resolve-failure banner that gates the rest.\n *\n * A deployment whose spec will not resolve is not a dead end — the fix (its variables) lives on\n * the Config tab, so this sends the operator there rather than leaving them in front of an error\n * with no control in reach. Everything else stays unavailable until it resolves, because the\n * server cannot name the stack without the variables, and every action needs the stack name.\n */\nimport { watch } from 'vue';\nimport { useRouter } from 'vue-router';\nimport { dep, isShared, openDeployment } from '../composables/useDeployment';\nimport {\n act,\n actionError,\n busy,\n conflict,\n conflictJobId,\n pending,\n resetActions,\n whyDisabled,\n} from '../composables/useDeploymentActions';\nimport ActionButton from '../components/ActionButton.vue';\nimport ConflictNote from '../components/ConflictNote.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\n\nconst props = defineProps<{ id: string }>();\nconst router = useRouter();\n\nconst TABS = [\n { to: 'overview', label: 'Overview' },\n // Second, not buried: \"the hostname does not work\" is the most common question, and this is the\n // only tab that can answer it.\n { to: 'runtime', label: 'Containers & routes' },\n { to: 'config', label: 'Config & variables' },\n { to: 'axes', label: 'Axes' },\n { to: 'requires', label: 'Requires' },\n { to: 'logs', label: 'Logs' },\n // After Logs: a shell is what you reach for when the logs did not answer it.\n { to: 'terminal', label: 'Terminal' },\n { to: 'danger', label: 'Danger' },\n] as const;\n\nwatch(\n () => props.id,\n (id) => {\n openDeployment(id);\n // A stale \"refused\" from the previous deployment must not greet the next one.\n resetActions();\n },\n { immediate: true },\n);\n\n// Land the operator on the editor when the spec will not resolve — but only from the tab that has\n// nothing to show. Sending them away from Logs or Danger mid-read would be its own annoyance.\nwatch(\n () => dep.error,\n (err) => {\n if (err && router.currentRoute.value.name === 'd.overview') {\n void router.replace(`/deployments/${encodeURIComponent(props.id)}/config`);\n }\n },\n);\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=\"/deployments\">← deployments</RouterLink>\n </div>\n <h1 style=\"font-size: var(--t-xl)\">{{ id }}</h1>\n <div class=\"sub\">\n <span v-if=\"dep.detail\">\n stack <b>{{ dep.detail.stack }}</b>\n </span>\n <span v-else-if=\"dep.error\">unresolved</span>\n <span v-else>loading…</span>\n </div>\n </div>\n <span class=\"grow\" />\n <span v-if=\"dep.detail\" class=\"badge\" :class=\"dep.detail.kind\">{{ dep.detail.kind }}</span>\n <span v-if=\"dep.detail?.busy === true\" class=\"badge busy\"><span class=\"dot pulse\" />busy</span>\n <!--\n Deploy and Verify live HERE, on every tab — they are the product's routine actions, and they\n used to be reachable only through a tab named \"Danger\". Teardown stays there: the header\n carries what is safe to reach for, the Danger tab carries what deserves the walk.\n -->\n <!-- One flex unit, so on a narrow screen the pair wraps together instead of Verify being\n orphaned onto its own line under the title. -->\n <div v-if=\"dep.detail\" class=\"row\" style=\"flex-wrap: nowrap\">\n <ActionButton\n variant=\"primary\"\n :pending=\"pending === 'up'\"\n :disabled=\"!!pending || busy\"\n :title=\"whyDisabled('up')\"\n @click=\"act('up')\"\n >\n Deploy\n </ActionButton>\n <ActionButton\n :pending=\"pending === 'verify'\"\n :disabled=\"!!pending || busy\"\n :title=\"whyDisabled('verify')\"\n @click=\"act('verify')\"\n >\n Verify\n </ActionButton>\n </div>\n </div>\n\n <ErrorNote v-if=\"actionError\" :text=\"actionError\" title=\"The action was refused.\" />\n <ConflictNote\n v-if=\"conflict\"\n :conflict=\"conflict\"\n :job-id=\"conflictJobId\"\n style=\"margin-bottom: var(--s4)\"\n />\n\n <nav class=\"tabs\" aria-label=\"Deployment sections\">\n <RouterLink\n v-for=\"t in TABS\"\n :key=\"t.to\"\n :to=\"`/deployments/${encodeURIComponent(id)}/${t.to}`\"\n >{{ t.label }}</RouterLink\n >\n </nav>\n\n <ErrorNote v-if=\"dep.error\" :text=\"dep.error\" title=\"Could not resolve this deployment.\">\n </ErrorNote>\n <p v-if=\"dep.error\" class=\"hint\">\n If that message names a variable, add it under <b>Config & variables</b> and press\n <b>Apply & reload</b>.\n <InfoHint label=\"why the rest of the page is unavailable\">\n Until the spec resolves there is no stack name, and every action on this deployment needs one.\n </InfoHint>\n </p>\n\n <p v-if=\"isShared\" class=\"hint\" style=\"margin-bottom: var(--s4)\">\n Shared with every preview on this host.\n <InfoHint label=\"what a shared deployment is\">\n A host singleton that previews borrow — a database, a queue, a registry mirror. It declares no\n axes, and tearing it down needs an explicit confirmation, because doing so destroys state\n every other deployment depends on.\n </InfoHint>\n </p>\n\n <RouterView v-slot=\"{ Component }\">\n <Transition name=\"view\" mode=\"out-in\">\n <component :is=\"Component\" />\n </Transition>\n </RouterView>\n </div>\n</template>\n"],"names":["props","__props","router","useRouter","TABS","watch","id","openDeployment","resetActions","dep","err","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","_component_RouterLink","_cache","_hoisted_3","_toDisplayString","_hoisted_4","_unref","_hoisted_5","_openBlock","_hoisted_8","_hoisted_9","ActionButton","pending","busy","whyDisabled","act","actionError","_createBlock","ErrorNote","conflict","ConflictNote","conflictJobId","_hoisted_10","_Fragment","_renderList","t","_createTextVNode","_hoisted_11","InfoHint","isShared","_hoisted_12","_component_RouterView","_withCtx","Component","_Transition","_resolveDynamicComponent"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as $,o as l,c as n,a as t,f as o,t as a,u as d,z as g,e as u,w as k,i as w,v as N,g as h,G as B,y as p,x as R,F as b,s as C,r as f,C as x,j as S,D,E as U}from"./index-jLPJ82ve.js";import{_ as z}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{_ as E}from"./RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js";import{_ as q}from"./SelectMenu.vue_vue_type_script_setup_true_lang-B-DTK242.js";import"./useFormat-CKJxQp_i.js";import"./Teleport-SesEhfqH.js";const A={key:0,class:"badge busy"},F={key:1,class:"badge ok"},I={key:2,class:"badge unknown",title:"the server could not determine this — it is not the same as idle"},M={key:3,class:"badge"},T=$({__name:"RunStateBadge",props:{busy:{type:[Boolean,null]},running:{type:[Boolean,null]}},setup(m){return(y,i)=>m.busy===!0?(l(),n("span",A,[...i[0]||(i[0]=[t("span",{class:"dot pulse"},null,-1),o("busy",-1)])])):m.running===!0?(l(),n("span",F,[...i[1]||(i[1]=[t("span",{class:"dot"},null,-1),o("running",-1)])])):m.busy===null||m.running===null?(l(),n("span",I,"unknown")):(l(),n("span",M,"idle"))}}),j={class:"page-head"},G={class:"sub"},H={class:"panel"},J={class:"phead"},K={class:"searchbox"},O={key:0},P={class:"check"},Q={key:0,class:"s-failed"},W={class:"cards"},X={class:"stagger"},Y={"data-label":"id"},Z={key:0,class:"mute",style:{"font-size":"var(--t-xs)"}},ee={"data-label":"kind"},te={class:"name dim","data-label":"stack"},se={key:0},le={key:1,class:"badge warn",title:"the spec could not be resolved without variables"},ne={"data-label":"state"},oe={class:"dim nowrap","data-label":"updated"},ae={key:0},ie={colspan:"5",class:"mute"},re={class:"mono"},ve=$({__name:"DeploymentsView",setup(m){const y=f(""),i=f("all"),c=f(!1),_=x(()=>{const r=y.value.trim().toLowerCase();return p.deployments.filter(e=>i.value!=="all"&&e.kind!==i.value||c.value&&!(e.running===!0||e.busy===!0)?!1:r?e.id.toLowerCase().includes(r)||(e.stack??"").toLowerCase().includes(r)||(e.specName??"").toLowerCase().includes(r):!0)}),V=x(()=>p.deployments.filter(r=>r.unresolved));return(r,e)=>{const v=S("RouterLink");return l(),n("div",null,[t("div",j,[t("div",null,[e[3]||(e[3]=t("h1",null,"Deployments",-1)),t("div",G,a(d(g).total)+" submitted · "+a(d(g).running)+" running · "+a(d(g).busy)+" busy ",1)]),e[5]||(e[5]=t("span",{class:"grow"},null,-1)),u(v,{to:"/submit",class:"btn"},{default:k(()=>[...e[4]||(e[4]=[o("+ Submit",-1)])]),_:1})]),t("section",H,[t("div",J,[t("div",K,[e[6]||(e[6]=t("svg",{viewBox:"0 0 24 24",width:"15",height:"15","aria-hidden":"true"},[t("circle",{cx:"11",cy:"11",r:"7",fill:"none",stroke:"currentColor","stroke-width":"2"}),t("path",{d:"M16.5 16.5 21 21",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round"})],-1)),w(t("input",{id:"q","onUpdate:modelValue":e[0]||(e[0]=s=>y.value=s),"data-search":"",type:"search","aria-label":"Search deployments",placeholder:"Search by id, stack or spec",spellcheck:"false"},null,512),[[N,y.value]]),y.value?h("",!0):(l(),n("kbd",O,"/"))]),u(q,{modelValue:i.value,"onUpdate:modelValue":e[1]||(e[1]=s=>i.value=s),label:"Filter by kind",options:[{value:"all",label:"All kinds"},{value:"isolated",label:"Isolated"},{value:"shared",label:"Shared"}]},null,8,["modelValue"]),t("label",P,[w(t("input",{"onUpdate:modelValue":e[2]||(e[2]=s=>c.value=s),type:"checkbox"},null,512),[[B,c.value]]),e[7]||(e[7]=o(" Running or busy only ",-1))])]),d(p).deploymentsError?(l(),n("p",Q,a(d(p).deploymentsError),1)):d(p).deploymentsLoaded?(l(),n(b,{key:2},[t("table",W,[e[11]||(e[11]=t("thead",null,[t("tr",null,[t("th",null,"id"),t("th",null,"kind"),t("th",null,"stack"),t("th",null,"state"),t("th",null,"updated")])],-1)),t("tbody",X,[(l(!0),n(b,null,C(_.value,(s,L)=>(l(),n("tr",{key:s.id,style:D({"--i":L})},[t("td",Y,[u(v,{to:`/deployments/${encodeURIComponent(s.id)}`},{default:k(()=>[o(a(s.id),1)]),_:2},1032,["to"]),s.specName?(l(),n("div",Z," spec: "+a(s.specName),1)):h("",!0)]),t("td",ee,[t("span",{class:U(["badge",s.kind])},a(s.kind),3)]),t("td",te,[s.stack?(l(),n("span",se,a(s.stack),1)):(l(),n("span",le," needs variables "))]),t("td",ne,[u(T,{busy:s.busy,running:s.running},null,8,["busy","running"])]),t("td",oe,[u(E,{at:s.updatedAt},null,8,["at"])])],4))),128)),_.value.length?h("",!0):(l(),n("tr",ae,[t("td",ie,[d(p).deployments.length?(l(),n(b,{key:0},[o(" Nothing matches this filter. ")],64)):(l(),n(b,{key:1},[e[9]||(e[9]=o(" Nothing submitted yet — ",-1)),u(v,{to:"/submit"},{default:k(()=>[...e[8]||(e[8]=[o("submit a spec",-1)])]),_:1}),e[10]||(e[10]=o(" to put a deployment in the registry. ",-1))],64))])]))])]),(l(!0),n(b,null,C(V.value,s=>(l(),n("div",{key:`u-${s.id}`,class:"banner warn"},[t("b",null,a(s.id),1),e[19]||(e[19]=o(" could not be resolved: ",-1)),t("span",re,a(s.unresolved),1),t("p",null,[e[13]||(e[13]=o(" This listing resolves every spec with the ",-1)),e[14]||(e[14]=t("em",null,"same",-1)),e[15]||(e[15]=o(" (empty) request variables, so a spec with a ",-1)),e[16]||(e[16]=t("code",null,"${VAR}",-1)),e[17]||(e[17]=o(" the server does not already hold will always appear here. ",-1)),u(v,{to:`/deployments/${encodeURIComponent(s.id)}/config`},{default:k(()=>[...e[12]||(e[12]=[o(" Set its variables ",-1)])]),_:1},8,["to"]),e[18]||(e[18]=o(" and the detail view will resolve. ",-1))])]))),128))],64)):(l(),R(z,{key:1,rows:4,tall:""}))])])}}});export{ve as default};
|
|
2
|
-
//# sourceMappingURL=DeploymentsView-BM9LVpU-.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DeploymentsView-BM9LVpU-.js","sources":["../../src/components/RunStateBadge.vue","../../src/views/DeploymentsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A deployment's live state, from the two TRI-STATE fields the server sends.\n *\n * `busy` and `running` are `boolean | null`, and `null` means \"the server could not determine\n * this\": an unresolved spec has no stack name to look up, and a docker that did not answer is not\n * the same fact as \"nothing is running\". Rendering either as \"idle\" would be a guess presented as\n * a fact — that is how a live stack gets reported as torn down and someone forgets it.\n *\n * So unknown says unknown, and it gets its own colour.\n */\ndefineProps<{ busy: boolean | null; running: boolean | null }>();\n</script>\n\n<template>\n <span v-if=\"busy === true\" class=\"badge busy\"><span class=\"dot pulse\" />busy</span>\n <span v-else-if=\"running === true\" class=\"badge ok\"><span class=\"dot\" />running</span>\n <span\n v-else-if=\"busy === null || running === null\"\n class=\"badge unknown\"\n title=\"the server could not determine this — it is not the same as idle\"\n >unknown</span\n >\n <span v-else class=\"badge\">idle</span>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The deployment list: searchable, filterable, and honest about what could not be resolved.\n *\n * Filtering is client-side because the API has no query parameters for it and the list is one\n * host's worth of previews — a few dozen rows, already in memory from the shell's poll.\n */\nimport { computed, ref } from 'vue';\nimport { state, summary } from '../composables/useControlPlane';\n\nimport RunStateBadge from '../components/RunStateBadge.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport RelativeTime from '../components/RelativeTime.vue';\nimport SelectMenu from '../components/SelectMenu.vue';\n\nconst q = ref('');\nconst kind = ref<'all' | 'isolated' | 'shared'>('all');\nconst onlyLive = ref(false);\n\nconst rows = computed(() => {\n const needle = q.value.trim().toLowerCase();\n return state.deployments.filter((d) => {\n if (kind.value !== 'all' && d.kind !== kind.value) return false;\n // \"Live\" means DEMONSTRABLY live. An unknown is not evidence of either state, so it is not\n // silently included here — the filter says what it filters.\n if (onlyLive.value && !(d.running === true || d.busy === true)) return false;\n if (!needle) return true;\n return (\n d.id.toLowerCase().includes(needle) ||\n (d.stack ?? '').toLowerCase().includes(needle) ||\n (d.specName ?? '').toLowerCase().includes(needle)\n );\n });\n});\n\nconst unresolvedRows = computed(() => state.deployments.filter((d) => d.unresolved));\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Deployments</h1>\n <div class=\"sub\">\n {{ summary.total }} submitted · {{ summary.running }} running · {{ summary.busy }} busy\n </div>\n </div>\n <span class=\"grow\" />\n <RouterLink to=\"/submit\" class=\"btn\">+ Submit</RouterLink>\n </div>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <!--\n One row of equal-height controls. Each used to sit under its own stacked label, which cost\n a line of vertical space per control, put \"Search — press /\" on screen as a permanent\n instruction, and left the two fields 8px out of vertical alignment because a `select` and\n an `input` do not have the same intrinsic height. A placeholder says the same thing in the\n place you are already looking, and the select's own options say what it filters.\n -->\n <div class=\"searchbox\">\n <svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" />\n <path d=\"M16.5 16.5 21 21\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n <input\n id=\"q\"\n v-model=\"q\"\n data-search\n type=\"search\"\n aria-label=\"Search deployments\"\n placeholder=\"Search by id, stack or spec\"\n spellcheck=\"false\"\n />\n <kbd v-if=\"!q\">/</kbd>\n </div>\n <SelectMenu\n v-model=\"kind\"\n label=\"Filter by kind\"\n :options=\"[\n { value: 'all', label: 'All kinds' },\n { value: 'isolated', label: 'Isolated' },\n { value: 'shared', label: 'Shared' },\n ]\"\n />\n <label class=\"check\">\n <input v-model=\"onlyLive\" type=\"checkbox\" />\n Running or busy only\n </label>\n </div>\n\n <p v-if=\"state.deploymentsError\" class=\"s-failed\">{{ state.deploymentsError }}</p>\n <SkeletonList v-else-if=\"!state.deploymentsLoaded\" :rows=\"4\" tall />\n\n <template v-else>\n <table class=\"cards\">\n <thead>\n <tr>\n <th>id</th>\n <th>kind</th>\n <th>stack</th>\n <th>state</th>\n <th>updated</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(d, i) in rows\" :key=\"d.id\" :style=\"{ '--i': i }\">\n <td data-label=\"id\">\n <RouterLink :to=\"`/deployments/${encodeURIComponent(d.id)}`\">\n {{ d.id }}\n </RouterLink>\n <div v-if=\"d.specName\" class=\"mute\" style=\"font-size: var(--t-xs)\">\n spec: {{ d.specName }}\n </div>\n </td>\n <td data-label=\"kind\"><span class=\"badge\" :class=\"d.kind\">{{ d.kind }}</span></td>\n <td class=\"name dim\" data-label=\"stack\">\n <span v-if=\"d.stack\">{{ d.stack }}</span>\n <!--\n No stack name means the spec could not be resolved with the variables this\n listing had. It is not an error state for the deployment — it is a missing input.\n -->\n <span v-else class=\"badge warn\" title=\"the spec could not be resolved without variables\">\n needs variables\n </span>\n </td>\n <td data-label=\"state\"><RunStateBadge :busy=\"d.busy\" :running=\"d.running\" /></td>\n <td class=\"dim nowrap\" data-label=\"updated\"><RelativeTime :at=\"d.updatedAt\" /></td>\n </tr>\n <tr v-if=\"!rows.length\">\n <td colspan=\"5\" class=\"mute\">\n <template v-if=\"state.deployments.length\">\n Nothing matches this filter.\n </template>\n <template v-else>\n Nothing submitted yet — <RouterLink to=\"/submit\">submit a spec</RouterLink> to\n put a deployment in the registry.\n </template>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- One message per unresolved row, verbatim: it names the missing variable. -->\n <div v-for=\"d in unresolvedRows\" :key=\"`u-${d.id}`\" class=\"banner warn\">\n <b>{{ d.id }}</b> could not be resolved:\n <span class=\"mono\">{{ d.unresolved }}</span>\n <p>\n This listing resolves every spec with the <em>same</em> (empty) request variables, so a\n spec with a <code>${VAR}</code> the server does not already hold will always appear\n here.\n <RouterLink :to=\"`/deployments/${encodeURIComponent(d.id)}/config`\">\n Set its variables\n </RouterLink>\n and the detail view will resolve.\n </p>\n </div>\n </template>\n </section>\n </div>\n</template>\n"],"names":["__props","_openBlock","_createElementBlock","_hoisted_1","_cache","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_4","q","ref","kind","onlyLive","rows","computed","needle","state","d","unresolvedRows","_toDisplayString","_unref","_createVNode","_component_RouterLink","_hoisted_5","$event","SelectMenu","_hoisted_7","_hoisted_8","_Fragment","_hoisted_9","_hoisted_10","_renderList","i","_hoisted_11","_createTextVNode","_hoisted_12","_hoisted_13","_normalizeClass","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","RunStateBadge","_hoisted_18","RelativeTime","_hoisted_19","_hoisted_20","_hoisted_21","_createBlock","SkeletonList"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as V,c as i,a as t,F as w,s as z,a1 as E,o as r,D as J,E as $,u as p,t as u,H as q,K as F,e as S,w as T,f as d,g as v,x as B,r as y,C,j as H,A as D,B as K}from"./index-jLPJ82ve.js";import{a as M,s as R,t as O}from"./useFormat-CKJxQp_i.js";import{_ as W}from"./LogViewer.vue_vue_type_script_setup_true_lang-DYfYw8_a.js";import{_ as G}from"./StateBadge.vue_vue_type_script_setup_true_lang-DMzCGltO.js";import{_ as P}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";function g(o){var l;return(l=o.message)!=null&&l.startsWith("unverifiable")?"unverifiable":o.ok?"ok":o.phase==="assert_gone"?"leaked":"failed"}function Q(o){return{ok:"✓",unverifiable:"?",leaked:"!",failed:"✗"}[g(o)]}function X(o){return o.message?o.message:o.skipped?"skipped":g(o)==="ok"?"ok":`exit ${o.code}`}function Y(o){return o.filter(l=>l.phase==="assert_gone"&&!l.ok).map(l=>l.axis)}function Z(o){return o.filter(l=>g(l)==="unverifiable").length}const ee={class:"steps stagger"},te={class:"who"},se={class:"dim",style:{"font-size":"var(--t-sm)"}},ae=V({__name:"StepList",props:{steps:{}},setup(o){return(l,s)=>(r(),i("div",null,[t("ul",ee,[(r(!0),i(w,null,z(o.steps,(n,m)=>(r(),i("li",{key:`${n.phase}-${n.axis}-${m}`,class:"step",style:J({"--i":m})},[t("span",{class:$(["mark",`s-${p(g)(n)}`]),"aria-hidden":"true"},u(p(Q)(n)),3),t("span",null,[t("span",te,u(n.axis),1),t("span",se," · "+u(n.phase),1),t("span",{class:$(["badge",p(g)(n)]),style:{"margin-left":"var(--s2)"}},u(p(g)(n)),3),t("div",{class:$(["msg",`s-${p(g)(n)}`])},u(p(X)(n)),3)])],4))),128))]),s[0]||(s[0]=E('<p class="hint"><span class="s-ok">✓ ok</span> · <span class="s-failed">✗ failed</span> · <span class="s-leaked">! leaked — survived teardown</span> · <span class="s-unverifiable">? unverifiable — nothing to check, not a pass</span>. A <code>down</code> step marked ok may still carry a <code>non-fatal:</code> note: teardown is best-effort by design, so aborting halfway would leave more behind than continuing. </p>',1))]))}}),oe={class:"page-head"},ne={class:"sub"},le={key:1,class:"panel leaked-banner"},re={key:2,class:"panel"},ue={class:"pre"},ie={class:"panel"},de={key:3,class:"panel"},ce={class:"phead"},pe={key:0,class:"mute"},be=V({__name:"JobDetailView",props:{jobId:{}},setup(o){const l=o,s=y(null),n=y([]),m=y(""),x=y(!0),h=y(!1),U=C(()=>{const a=[];let e="";for(const c of n.value){const b=new Date(c.at),k=b.toLocaleDateString("sv");k!==e&&(a.push({key:`day-${k}-${c.seq}`,separator:!0,text:k}),e=k),a.push({key:c.seq,gutter:b.toTimeString().slice(0,8),gutterTitle:R(c.at),text:c.message,tone:c.level})}return a});let f=null;const L=C(()=>{var a;return(a=s.value)!=null&&a.outcome?Y(s.value.outcome.steps):[]}),A=C(()=>{var a;return(a=s.value)!=null&&a.outcome?Z(s.value.outcome.steps):0});async function j(){var e;const a=await D.get(`/api/jobs/${encodeURIComponent(l.jobId)}`);return x.value=!1,a.ok?(m.value="",s.value=a.body.job,(e=a.body.job.log)!=null&&e.length&&n.value.length===0&&(n.value=[...a.body.job.log]),a.body.job):(m.value=K(a,"load job"),null)}function _(){f==null||f.close(),f=null,h.value=!1}function I(){_(),f=new EventSource(D.url(`/api/jobs/${encodeURIComponent(l.jobId)}/stream`)),h.value=!0,f.onmessage=a=>{let e;try{e=JSON.parse(a.data)}catch{return}if(e.done){_(),j();return}n.value.push(e)},f.onerror=()=>{_(),j()}}return q(()=>l.jobId,async()=>{n.value=[],s.value=null,x.value=!0;const a=await j();a&&a.state==="running"&&I()},{immediate:!0}),F(_),(a,e)=>{var b,k;const c=H("RouterLink");return r(),i("div",null,[t("div",oe,[t("div",null,[t("h1",null,[S(c,{to:"/jobs",class:"mute"},{default:T(()=>[...e[0]||(e[0]=[d("Jobs",-1)])]),_:1}),e[1]||(e[1]=t("span",{class:"mute"}," / ",-1)),d(u(s.value?p(M)(s.value.action):"…"),1)]),t("div",ne,[s.value?(r(),i(w,{key:0},[t("code",null,u(s.value.stack),1),d(" · started "+u(p(R)(s.value.startedAt))+" ",1),s.value.endedAt?(r(),i(w,{key:0},[d(" · took "+u(p(O)(s.value.startedAt,s.value.endedAt)),1)],64)):v("",!0)],64)):(r(),i(w,{key:1},[d(u(o.jobId),1)],64))])]),s.value?(r(),B(G,{key:0,state:s.value.state},null,8,["state"])):v("",!0)]),m.value?(r(),B(P,{key:0,text:m.value},null,8,["text"])):v("",!0),L.value.length?(r(),i("section",le,[e[2]||(e[2]=t("strong",null,"Leaked:",-1)),d(" "+u(L.value.join(", "))+" ",1),e[3]||(e[3]=t("div",{class:"mute"},[d(" These axes' "),t("code",null,"assert_gone"),d(" failed, so the resources are still present. They will not be retried — tear them down by hand, then re-run "),t("code",null,"verify"),d(". ")],-1))])):v("",!0),(b=s.value)!=null&&b.error?(r(),i("section",re,[e[4]||(e[4]=t("strong",{class:"bad"},"The job crashed",-1)),t("pre",ue,u(s.value.error),1)])):v("",!0),t("section",ie,[e[5]||(e[5]=t("div",{class:"phead"},[t("h2",{class:"phead-title"},"Log")],-1)),S(W,{rows:U.value,live:h.value,"empty-text":x.value?"Loading…":"No output."},{actions:T(()=>{var N;return[!h.value&&((N=s.value)==null?void 0:N.state)==="running"?(r(),i("button",{key:0,class:"ghost sm",onClick:I}," Reconnect ")):v("",!0)]}),_:1},8,["rows","live","empty-text"])]),(k=s.value)!=null&&k.outcome?(r(),i("section",de,[t("div",ce,[e[8]||(e[8]=t("h2",{class:"phead-title"},"Steps",-1)),A.value?(r(),i("span",pe,[d(u(A.value)+" unverifiable — no ",1),e[6]||(e[6]=t("code",null,"assert_gone",-1)),e[7]||(e[7]=d(", so nothing was checked ",-1))])):v("",!0)]),S(ae,{steps:s.value.outcome.steps},null,8,["steps"])])):v("",!0)])}}});export{be as default};
|
|
2
|
-
//# sourceMappingURL=JobDetailView-DsYfLQUd.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"JobDetailView-DsYfLQUd.js","sources":["../../src/composables/useSteps.ts","../../src/components/StepList.vue","../../src/views/JobDetailView.vue"],"sourcesContent":["/**\n * Step classification — the single most important logic in this app.\n *\n * A job's outcome has four states that MUST NOT blur into one another. The whole product is the\n * distinction between the middle two:\n *\n * ok passed. A `down` step is ALWAYS ok and may still carry a \"non-fatal:\" note —\n * teardown is best-effort by design, so the step really did pass.\n * unverifiable the axis defines no `assert_gone`, so `verify` had nothing to check. NOT a pass.\n * A green here would be exactly the false confidence this tool exists to remove.\n * leaked `assert_gone` FAILED — the resource survived teardown. Nothing else will clean\n * it up. This is the signal the whole tool exists for, and the CLI's exit code 2.\n * failed any other non-zero step.\n *\n * Order matters: `unverifiable` is matched on the message FIRST, exactly as the CLI's own report\n * does. `skipped` alone is not enough — a dry run also skips.\n */\n\nimport type { StepResult } from '../api/types';\n\nexport type StepState = 'ok' | 'unverifiable' | 'leaked' | 'failed';\n\nexport function stepState(s: StepResult): StepState {\n if (s.message?.startsWith('unverifiable')) return 'unverifiable';\n if (!s.ok) return s.phase === 'assert_gone' ? 'leaked' : 'failed';\n return 'ok';\n}\n\nexport function stepMark(s: StepResult): string {\n return { ok: '✓', unverifiable: '?', leaked: '!', failed: '✗' }[stepState(s)];\n}\n\nexport function stepText(s: StepResult): string {\n if (s.message) return s.message;\n if (s.skipped) return 'skipped';\n return stepState(s) === 'ok' ? 'ok' : `exit ${s.code}`;\n}\n\n/** Only a failed `assert_gone` is a leak. Every other non-zero step is a plain failure. */\nexport function leakedAxes(steps: StepResult[]): string[] {\n return steps.filter((s) => s.phase === 'assert_gone' && !s.ok).map((s) => s.axis);\n}\n\nexport function countUnverifiable(steps: StepResult[]): number {\n return steps.filter((s) => stepState(s) === 'unverifiable').length;\n}\n","<script setup lang=\"ts\">\n/**\n * A job's step results.\n *\n * The legend is part of the component because the four states only mean anything together:\n * `unverifiable` is not a pass, and `leaked` is not the same problem as `failed`.\n */\nimport type { StepResult } from '../api/types';\nimport { stepMark, stepState, stepText } from '../composables/useSteps';\n\ndefineProps<{ steps: StepResult[] }>();\n</script>\n\n<template>\n <div>\n <ul class=\"steps stagger\">\n <li\n v-for=\"(s, i) in steps\"\n :key=\"`${s.phase}-${s.axis}-${i}`\"\n class=\"step\"\n :style=\"{ '--i': i }\"\n >\n <span class=\"mark\" :class=\"`s-${stepState(s)}`\" aria-hidden=\"true\">{{ stepMark(s) }}</span>\n <span>\n <span class=\"who\">{{ s.axis }}</span>\n <span class=\"dim\" style=\"font-size: var(--t-sm)\"> · {{ s.phase }}</span>\n <span class=\"badge\" :class=\"stepState(s)\" style=\"margin-left: var(--s2)\">\n {{ stepState(s) }}\n </span>\n <div class=\"msg\" :class=\"`s-${stepState(s)}`\">{{ stepText(s) }}</div>\n </span>\n </li>\n </ul>\n\n <p class=\"hint\">\n <span class=\"s-ok\">✓ ok</span> ·\n <span class=\"s-failed\">✗ failed</span> ·\n <span class=\"s-leaked\">! leaked — survived teardown</span> ·\n <span class=\"s-unverifiable\">? unverifiable — nothing to check, not a pass</span>.\n A <code>down</code> step marked ok may still carry a <code>non-fatal:</code> note: teardown is\n best-effort by design, so aborting halfway would leave more behind than continuing.\n </p>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One job: its live log while it runs, then the step table that says what actually happened.\n *\n * The log streams over `EventSource`. The server replays the whole buffered history first and then\n * sends live events, so attaching late still shows the beginning — no polling, no gap to reconcile.\n * A final `{done:true,state}` ends the stream; we close the source then and fetch the job once more\n * to pick up `outcome`, which only exists after the job finishes.\n */\nimport { computed, onBeforeUnmount, ref, watch } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { Job, LogEvent } from '../api/types';\nimport { leakedAxes, countUnverifiable } from '../composables/useSteps';\nimport { actionLabel, stamp, took } from '../composables/useFormat';\nimport LogViewer from '../components/LogViewer.vue';\nimport type { LogRow } from '../components/log-viewer-types';\nimport StepList from '../components/StepList.vue';\nimport StateBadge from '../components/StateBadge.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\n\nconst props = defineProps<{ jobId: string }>();\n\nconst job = ref<Job | null>(null);\nconst lines = ref<LogEvent[]>([]);\nconst error = ref('');\nconst loading = ref(true);\nconst streaming = ref(false);\n\n/** Job events → viewer rows. The date repeats for hundreds of lines, so it appears once per DAY as\n * a separator and each row carries only the clock; the full stamp survives in the tooltip. */\nconst logRows = computed<LogRow[]>(() => {\n const out: LogRow[] = [];\n let lastDay = '';\n for (const l of lines.value) {\n const d = new Date(l.at);\n const day = d.toLocaleDateString('sv');\n if (day !== lastDay) {\n out.push({ key: `day-${day}-${l.seq}`, separator: true, text: day });\n lastDay = day;\n }\n out.push({\n key: l.seq,\n gutter: d.toTimeString().slice(0, 8),\n gutterTitle: stamp(l.at),\n text: l.message,\n tone: l.level,\n });\n }\n return out;\n});\n\nlet source: EventSource | null = null;\n\nconst leaks = computed(() => (job.value?.outcome ? leakedAxes(job.value.outcome.steps) : []));\nconst unverifiable = computed(() =>\n job.value?.outcome ? countUnverifiable(job.value.outcome.steps) : 0,\n);\n\n/** Returns the job it loaded, so callers need not re-read the ref — see the note at the watcher. */\nasync function load(): Promise<Job | null> {\n const r = await api.get<{ job: Job }>(`/api/jobs/${encodeURIComponent(props.jobId)}`);\n loading.value = false;\n if (!r.ok) {\n error.value = problem(r, 'load job');\n return null;\n }\n error.value = '';\n job.value = r.body.job;\n // The job carries its buffered log too. Seed from it so a finished job renders instantly without\n // opening a stream that would only replay and close.\n if (r.body.job.log?.length && lines.value.length === 0) lines.value = [...r.body.job.log];\n return r.body.job;\n}\n\nfunction closeStream(): void {\n source?.close();\n source = null;\n streaming.value = false;\n}\n\nfunction stream(): void {\n closeStream();\n // No auth header: EventSource cannot send one. Reads are unauthenticated by design, which is\n // exactly why the log is a GET.\n source = new EventSource(api.url(`/api/jobs/${encodeURIComponent(props.jobId)}/stream`));\n streaming.value = true;\n\n source.onmessage = (ev) => {\n let payload: unknown;\n try {\n payload = JSON.parse(ev.data as string);\n } catch {\n return; // a frame we cannot parse is not worth tearing the stream down for\n }\n const done = payload as { done?: boolean };\n if (done.done) {\n closeStream();\n void load(); // `outcome` only exists once the job has ended\n return;\n }\n lines.value.push(payload as LogEvent);\n };\n\n // An error here is usually the job having ended between the fetch and the connect, or the API\n // becoming unreachable. Either way stop rather than let the browser reconnect forever.\n source.onerror = () => {\n closeStream();\n void load();\n };\n}\n\n\nwatch(\n () => props.jobId,\n async () => {\n lines.value = [];\n job.value = null;\n loading.value = true;\n // Use what `load` returned rather than re-reading the ref: control-flow analysis still has\n // `job.value` narrowed to the `null` assigned above, because it cannot see that an awaited\n // call reassigned it.\n const loaded = await load();\n if (loaded && loaded.state === 'running') stream();\n },\n { immediate: true },\n);\n\nonBeforeUnmount(closeStream);\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>\n <RouterLink to=\"/jobs\" class=\"mute\">Jobs</RouterLink>\n <span class=\"mute\"> / </span>{{ job ? actionLabel(job.action) : '…' }}\n </h1>\n <div class=\"sub\">\n <template v-if=\"job\">\n <code>{{ job.stack }}</code> · started {{ stamp(job.startedAt) }}\n <template v-if=\"job.endedAt\"> · took {{ took(job.startedAt, job.endedAt) }}</template>\n </template>\n <template v-else>{{ jobId }}</template>\n </div>\n </div>\n <StateBadge v-if=\"job\" :state=\"job.state\" />\n </div>\n\n <ErrorNote v-if=\"error\" :text=\"error\" />\n\n <!--\n Leaked is called out above everything else. It means a resource survived teardown and nothing\n else is going to clean it up — the one outcome that needs a human.\n -->\n <section v-if=\"leaks.length\" class=\"panel leaked-banner\">\n <strong>Leaked:</strong> {{ leaks.join(', ') }}\n <div class=\"mute\">\n These axes' <code>assert_gone</code> failed, so the resources are still present. They will\n not be retried — tear them down by hand, then re-run <code>verify</code>.\n </div>\n </section>\n\n <section v-if=\"job?.error\" class=\"panel\">\n <strong class=\"bad\">The job crashed</strong>\n <pre class=\"pre\">{{ job.error }}</pre>\n </section>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"phead-title\">Log</h2>\n </div>\n\n <!-- Time-of-day gutter with a date separator when the day flips — the full 19-character\n timestamp per line is what used to wrap every entry onto five or six lines. -->\n <LogViewer\n :rows=\"logRows\"\n :live=\"streaming\"\n :empty-text=\"loading ? 'Loading…' : 'No output.'\"\n >\n <template #actions>\n <button v-if=\"!streaming && job?.state === 'running'\" class=\"ghost sm\" @click=\"stream\">\n Reconnect\n </button>\n </template>\n </LogViewer>\n </section>\n\n <section v-if=\"job?.outcome\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"phead-title\">Steps</h2>\n <span v-if=\"unverifiable\" class=\"mute\">\n {{ unverifiable }} unverifiable — no <code>assert_gone</code>, so nothing was checked\n </span>\n </div>\n <StepList :steps=\"job.outcome.steps\" />\n </section>\n </div>\n</template>\n"],"names":["stepState","s","_a","stepMark","stepText","leakedAxes","steps","countUnverifiable","_createElementBlock","_createElementVNode","_hoisted_1","_openBlock","_Fragment","_renderList","__props","i","_normalizeClass","_unref","_toDisplayString","_hoisted_2","_hoisted_3","props","job","ref","lines","error","loading","streaming","logRows","computed","out","lastDay","l","d","day","stamp","source","leaks","unverifiable","load","r","api","problem","closeStream","stream","ev","payload","watch","loaded","onBeforeUnmount","_createVNode","_component_RouterLink","_cache","_createTextVNode","actionLabel","_createBlock","StateBadge","ErrorNote","_hoisted_4","_hoisted_5","_hoisted_6","LogViewer","_b","_hoisted_7","_hoisted_8","_hoisted_9","StepList"],"mappings":"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"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{d as x,H as b,Y as _,K as T,c as s,a,t as c,f as C,g as u,W as B,F as g,s as N,r as y,a2 as H,N as m,C as S,o,E as V}from"./index-jLPJ82ve.js";const F={class:"lv"},$={class:"lv-bar"},E={class:"lv-count"},L={key:0,class:"badge running"},j=["disabled"],z={key:0,class:"lv-empty"},D={key:0,class:"lv-sep"},K=["title"],M={class:"lv-msg"},U=x({__name:"LogViewer",props:{rows:{},live:{type:Boolean},copyText:{},emptyText:{}},setup(n){const r=n,d=y(null),i=y(!0);function f(){const t=d.value;return!!t&&t.scrollHeight-t.scrollTop-t.clientHeight<24}function k(){i.value=f()}async function v(){await H();const t=d.value;t&&i.value&&(t.scrollTop=t.scrollHeight)}b(()=>r.rows.length,v),_(v);function h(){i.value=!0,v()}async function w(){const t=r.copyText??r.rows.filter(e=>!e.separator).map(e=>e.gutter?`${e.gutter} ${e.text}`:e.text).join(`
|
|
2
|
-
`);try{await navigator.clipboard.writeText(t),m("ok","Copied.")}catch{m("error","Could not copy — your browser blocked clipboard access.")}}const p=S(()=>r.rows.filter(t=>!t.separator).length);return T(()=>{}),(t,e)=>(o(),s("div",F,[a("div",$,[a("span",E,c(p.value)+" line"+c(p.value===1?"":"s"),1),n.live?(o(),s("span",L,[...e[0]||(e[0]=[a("span",{class:"dot pulse"},null,-1),C("live",-1)])])):u("",!0),e[1]||(e[1]=a("span",{class:"grow"},null,-1)),n.live&&!i.value?(o(),s("button",{key:1,class:"ghost sm",onClick:h},"↓ Follow")):u("",!0),a("button",{class:"ghost sm",disabled:!p.value,onClick:w},"Copy",8,j),B(t.$slots,"actions")]),a("div",{ref_key:"box",ref:d,class:"lv-box",role:"log","aria-live":"polite",onScrollPassive:k},[n.rows.length?u("",!0):(o(),s("p",z,c(n.emptyText??"No output."),1)),(o(!0),s(g,null,N(n.rows,l=>(o(),s(g,{key:l.key},[l.separator?(o(),s("div",D,[a("span",null,c(l.text),1)])):(o(),s("div",{key:1,class:V(["lv-row",l.tone])},[l.gutter!==void 0?(o(),s("span",{key:0,class:"lv-gutter",title:l.gutterTitle},c(l.gutter),9,K)):u("",!0),a("span",M,c(l.text),1)],2))],64))),128))],544)]))}});export{U as _};
|
|
3
|
-
//# sourceMappingURL=LogViewer.vue_vue_type_script_setup_true_lang-DYfYw8_a.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LogViewer.vue_vue_type_script_setup_true_lang-DYfYw8_a.js","sources":["../../src/components/LogViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The log surface — used by the job page (structured events) and the container-logs tab (raw text\n * that the parent parses into rows).\n *\n * WHY NOT A LIBRARY. There is no widely-adopted Vue log viewer to reach for (the React ones —\n * react-lazylog and friends — have no Vue counterpart with real adoption), and xterm — already a\n * dependency for the terminal — renders to canvas, so log text would stop being selectable,\n * copyable and findable with ⌘F. For LOGS those are the whole point. What a log view actually\n * needs is small: a gutter that never wraps, a message that wraps with a hanging indent, stickiness\n * at the bottom, and cheap rendering for thousands of rows (`content-visibility: auto` — the\n * browser skips layout for off-screen rows, no virtualisation library required).\n *\n * THE GUTTER IS TIME-OF-DAY, NOT A FULL TIMESTAMP. `2026-08-04 03:47:45` per line is 19 characters\n * of the same date repeated hundreds of times — on a narrow panel it wrapped every entry onto five\n * or six lines. Within one log the date almost never changes, so each row carries `03:47:45` and a\n * date SEPARATOR row appears only when the day actually flips. The full stamp survives in the\n * row's tooltip.\n *\n * FOLLOW IS A STATE THE USER CONTROLS BY SCROLLING. Pinned to the bottom, new lines keep it there;\n * one scroll up releases it (reading must never be yanked away); the \"Follow\" chip puts it back.\n */\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { toast } from '../composables/useToasts';\nimport type { LogRow } from './log-viewer-types';\n\nconst props = defineProps<{\n rows: LogRow[];\n /** Renders the live pulse and keeps follow meaningful; a finished log needs neither. */\n live?: boolean;\n /** What Copy puts on the clipboard — the parent knows the faithful raw form, this view does not. */\n copyText?: string;\n emptyText?: string;\n}>();\n\nconst box = ref<HTMLElement | null>(null);\nconst follow = ref(true);\n\nfunction atBottom(): boolean {\n const el = box.value;\n return !!el && el.scrollHeight - el.scrollTop - el.clientHeight < 24;\n}\n\nfunction onScroll(): void {\n // The USER moved — follow reflects where they left the viewport, with no flag juggling for\n // programmatic scrolls: after scrollTo(bottom), atBottom() is simply true.\n follow.value = atBottom();\n}\n\nasync function stick(): Promise<void> {\n await nextTick();\n const el = box.value;\n if (el && follow.value) el.scrollTop = el.scrollHeight;\n}\n\nwatch(() => props.rows.length, stick);\nonMounted(stick);\n\nfunction resume(): void {\n follow.value = true;\n void stick();\n}\n\nasync function copy(): Promise<void> {\n const text = props.copyText ?? props.rows.filter((r) => !r.separator).map((r) => (r.gutter ? `${r.gutter} ${r.text}` : r.text)).join('\\n');\n try {\n await navigator.clipboard.writeText(text);\n toast('ok', 'Copied.');\n } catch {\n toast('error', 'Could not copy — your browser blocked clipboard access.');\n }\n}\n\nconst count = computed(() => props.rows.filter((r) => !r.separator).length);\n\n// A page-level listener would steal browser find; scoping ⌘F to the box when it has focus would be\n// surprising. The box is plain DOM precisely so the browser's own find works — nothing to do here.\nonBeforeUnmount(() => {});\n</script>\n\n<template>\n <div class=\"lv\">\n <div class=\"lv-bar\">\n <span class=\"lv-count\">{{ count }} line{{ count === 1 ? '' : 's' }}</span>\n <span v-if=\"live\" class=\"badge running\"><span class=\"dot pulse\" />live</span>\n <span class=\"grow\" />\n <button v-if=\"live && !follow\" class=\"ghost sm\" @click=\"resume\">↓ Follow</button>\n <button class=\"ghost sm\" :disabled=\"!count\" @click=\"copy\">Copy</button>\n <slot name=\"actions\" />\n </div>\n\n <div ref=\"box\" class=\"lv-box\" role=\"log\" aria-live=\"polite\" @scroll.passive=\"onScroll\">\n <p v-if=\"!rows.length\" class=\"lv-empty\">{{ emptyText ?? 'No output.' }}</p>\n <template v-for=\"r in rows\" :key=\"r.key\">\n <div v-if=\"r.separator\" class=\"lv-sep\"><span>{{ r.text }}</span></div>\n <div v-else class=\"lv-row\" :class=\"r.tone\">\n <span v-if=\"r.gutter !== undefined\" class=\"lv-gutter\" :title=\"r.gutterTitle\">{{ r.gutter }}</span>\n <span class=\"lv-msg\">{{ r.text }}</span>\n </div>\n </template>\n </div>\n </div>\n</template>\n"],"names":["props","__props","box","ref","follow","atBottom","el","onScroll","stick","nextTick","watch","onMounted","resume","copy","text","r","toast","count","computed","onBeforeUnmount","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_toDisplayString","_hoisted_4","_cache","_hoisted_5","_renderSlot","_ctx","_hoisted_6","_Fragment","_renderList","_hoisted_7","_normalizeClass","_hoisted_8","_hoisted_9"],"mappings":"mbA0BA,MAAMA,EAAQC,EASRC,EAAMC,EAAwB,IAAI,EAClCC,EAASD,EAAI,EAAI,EAEvB,SAASE,GAAoB,CAC3B,MAAMC,EAAKJ,EAAI,MACf,MAAO,CAAC,CAACI,GAAMA,EAAG,aAAeA,EAAG,UAAYA,EAAG,aAAe,EACpE,CAEA,SAASC,GAAiB,CAGxBH,EAAO,MAAQC,EAAA,CACjB,CAEA,eAAeG,GAAuB,CACpC,MAAMC,EAAA,EACN,MAAMH,EAAKJ,EAAI,MACXI,GAAMF,EAAO,QAAOE,EAAG,UAAYA,EAAG,aAC5C,CAEAI,EAAM,IAAMV,EAAM,KAAK,OAAQQ,CAAK,EACpCG,EAAUH,CAAK,EAEf,SAASI,GAAe,CACtBR,EAAO,MAAQ,GACVI,EAAA,CACP,CAEA,eAAeK,GAAsB,CACnC,MAAMC,EAAOd,EAAM,UAAYA,EAAM,KAAK,OAAQe,GAAM,CAACA,EAAE,SAAS,EAAE,IAAKA,GAAOA,EAAE,OAAS,GAAGA,EAAE,MAAM,KAAKA,EAAE,IAAI,GAAKA,EAAE,IAAK,EAAE,KAAK;AAAA,CAAI,EAC1I,GAAI,CACF,MAAM,UAAU,UAAU,UAAUD,CAAI,EACxCE,EAAM,KAAM,SAAS,CACvB,MAAQ,CACNA,EAAM,QAAS,yDAAyD,CAC1E,CACF,CAEA,MAAMC,EAAQC,EAAS,IAAMlB,EAAM,KAAK,OAAQe,GAAM,CAACA,EAAE,SAAS,EAAE,MAAM,EAI1E,OAAAI,EAAgB,IAAM,CAAC,CAAC,UAItBC,EAAA,EAAAC,EAoBM,MApBNC,EAoBM,CAnBJC,EAOM,MAPNC,EAOM,CANJD,EAA0E,OAA1EE,EAA0EC,EAAhDT,OAAK,EAAG,UAAQA,EAAA,QAAK,EAAA,GAAA,GAAA,EAAA,CAAA,EACnChB,EAAA,MAAZmB,IAAAC,EAA6E,OAA7EM,EAA6E,CAAA,GAAAC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAArCL,EAA0B,OAAA,CAApB,MAAM,WAAA,EAAW,KAAA,EAAA,IAAG,OAAI,EAAA,CAAA,2BACtEA,EAAqB,OAAA,CAAf,MAAM,MAAA,EAAM,KAAA,EAAA,GACJtB,EAAA,OAASG,EAAA,WAAvBiB,EAAiF,SAAA,OAAlD,MAAM,WAAY,QAAOT,CAAA,EAAQ,UAAQ,YACxEW,EAAuE,SAAA,CAA/D,MAAM,WAAY,UAAWN,EAAA,MAAQ,QAAOJ,CAAA,EAAM,OAAI,EAAAgB,CAAA,EAC9DC,EAAuBC,EAAA,OAAA,SAAA,CAAA,GAGzBR,EASM,MAAA,SATG,MAAJ,IAAIrB,EAAM,MAAM,SAAS,KAAK,MAAM,YAAU,yBAA0BK,CAAA,GACjEN,EAAA,KAAK,qBAAfoB,EAA2E,IAA3EW,EAA2EN,EAAhCzB,EAAA,WAAS,YAAA,EAAA,CAAA,UACpDoB,EAMWY,EAAA,KAAAC,EANWjC,EAAA,KAALc,aAAiB,IAAAA,EAAE,GAAA,GACvBA,EAAE,WAAbK,EAAA,EAAAC,EAAsE,MAAtEc,EAAsE,CAA/BZ,EAAyB,OAAA,KAAAG,EAAhBX,EAAE,IAAI,EAAA,CAAA,CAAA,SACtDM,EAGM,MAAA,OAHM,MAAKe,EAAA,CAAC,SAAiBrB,EAAE,IAAI,CAAA,CAAA,GAC3BA,EAAE,SAAW,YAAzBM,EAAkG,OAAA,OAA9D,MAAM,YAAa,MAAON,EAAE,WAAA,EAAgBW,EAAAX,EAAE,MAAM,EAAA,EAAAsB,CAAA,YACxFd,EAAwC,OAAxCe,EAAwCZ,EAAhBX,EAAE,IAAI,EAAA,CAAA,CAAA"}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{d as I,H as E,c as n,a as l,e as k,g as v,u as C,f as r,t as m,F as $,i as R,v as j,w as S,_ as U,x as D,G as H,r as d,C as V,A as N,J as F,B as O,o as a}from"./index-jLPJ82ve.js";import{d as u}from"./useDeployment-Bgo0U4-I.js";import{_ as T}from"./SelectMenu.vue_vue_type_script_setup_true_lang-B-DTK242.js";import{_ as P}from"./LogViewer.vue_vue_type_script_setup_true_lang-DYfYw8_a.js";import"./useVars-VN9gYdR6.js";import"./Teleport-SesEhfqH.js";const q={class:"panel"},G={class:"phead"},J={key:0,class:"field"},W={class:"field"},X=["disabled"],K={key:0,class:"progress","aria-hidden":"true"},Q={key:0,class:"mute"},Y={key:1,class:"mute"},Z={key:0,class:"banner failed"},ee={key:1,class:"banner warn"},te={key:2,class:"field",style:{"margin-top":"var(--s3)"}},se={key:3,class:"hint",style:{"margin-top":"var(--s3)"}},le={key:0,class:"log"},oe={class:"row",style:{"margin-top":"var(--s2)"}},ae={class:"check"},ne={key:5,class:"mute",style:{"margin-top":"var(--s3)"}},ie={key:6,class:"hint"},fe=I({__name:"LogsTab",setup(re){const h=d(200),p=d(""),f=d([]);async function z(){const t=await N.get(`/api/deployments/${encodeURIComponent(u.id)}/runtime${F(u.vars)}`);t.ok&&(f.value=[...new Set(t.body.containers.map(e=>e.service).filter(e=>!!e))].sort())}const B=V(()=>[{value:"",label:"All services"},...f.value.map(t=>({value:t,label:t}))]),b=d(""),i=d(null),g=d(""),c=d(!1);E(()=>{var t;return[u.id,!!((t=u.detail)!=null&&t.compose)]},([t,e],s)=>{(!s||s[0]!==t)&&(f.value=[],p.value="",i.value=null),e&&f.value.length===0&&z()},{immediate:!0});async function L(){var o;if(!((o=u.detail)!=null&&o.compose))return;const t=u.id;c.value=!0,g.value="";const e=p.value,s=await N.get(`/api/deployments/${encodeURIComponent(t)}/logs${F(u.vars,{tail:h.value,...e?{service:e}:{}})}`);if(c.value=!1,u.id===t&&!(s.ok&&(s.body.service??"")!==e)){if(!s.ok){i.value=null,g.value=O(s,"load the logs");return}i.value={...s.body,ok:s.body.ok!==!1,text:s.body.text??""}}}const x=V(()=>{var o;const t=((o=i.value)==null?void 0:o.text)??"",e=b.value.trim().toLowerCase();if(!e)return t;const s=t.split(`
|
|
2
|
-
`).filter(y=>y.toLowerCase().includes(e));return s.length?s.join(`
|
|
3
|
-
`):`(no line matches "${b.value}")`}),_=d(!1),M=/^(\S+)\s+\| /,A=V(()=>{const t=x.value;if(!t||t.startsWith("(no line matches"))return[];const e=new Map;return t.split(`
|
|
4
|
-
`).map((s,o)=>{const y=M.exec(s);if(!y)return{key:o,text:s};const w=y[1];return e.has(w)||e.set(w,e.size%6),{key:o,gutter:w,text:s.slice(y[0].length),tone:`svc-${e.get(w)}`}})});return(t,e)=>{var s;return a(),n("section",q,[l("div",G,[e[6]||(e[6]=l("h2",{class:"section"},"Compose logs",-1)),e[7]||(e[7]=l("span",{class:"grow"},null,-1)),f.value.length>1?(a(),n("div",J,[e[4]||(e[4]=l("label",{for:"svc",class:"mute",style:{"font-size":"var(--t-xs)"}},"service",-1)),k(T,{modelValue:p.value,"onUpdate:modelValue":e[0]||(e[0]=o=>p.value=o),id:"svc",label:"Service",options:B.value},null,8,["modelValue","options"])])):v("",!0),l("div",W,[e[5]||(e[5]=l("label",{for:"tail",class:"mute",style:{"font-size":"var(--t-xs)"}},"tail",-1)),k(T,{"model-value":String(h.value),label:"How many lines",options:[{value:"50",label:"50"},{value:"200",label:"200"},{value:"500",label:"500"},{value:"1000",label:"1000"},{value:"2000",label:"2000"}],"onUpdate:modelValue":e[1]||(e[1]=o=>h.value=Number(o))},null,8,["model-value"])]),l("button",{class:"primary",style:{"align-self":"end"},disabled:c.value||!((s=C(u).detail)!=null&&s.compose),onClick:L},[r(m(c.value?"reading…":"Fetch")+" ",1),c.value?(a(),n("span",K)):v("",!0)],8,X)]),C(u).detail?C(u).detail.compose?(a(),n($,{key:2},[e[19]||(e[19]=l("p",{class:"hint",style:{margin:"0 0 var(--s3)"}}," Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer the host. The server caps how many lines it will return. ",-1)),g.value?(a(),n("div",Z,m(g.value),1)):i.value&&!i.value.ok?(a(),n("div",ee,[...e[9]||(e[9]=[l("b",null,"compose exited non-zero.",-1),l("p",null," Most often the project does not exist yet (nothing has been brought up), or a profile is missing. Any output it did produce is below. ",-1)])])):v("",!0),i.value?(a(),n("div",te,[e[10]||(e[10]=l("label",{for:"lf",class:"mute",style:{"font-size":"var(--t-xs)"}},"Search in these lines",-1)),R(l("input",{id:"lf","onUpdate:modelValue":e[2]||(e[2]=o=>b.value=o),"data-search":"",type:"search",placeholder:"substring",spellcheck:"false"},null,512),[[j,b.value]])])):v("",!0),i.value&&i.value.service?(a(),n("div",se,[e[12]||(e[12]=r(" Showing ",-1)),l("b",null,m(i.value.service),1),e[13]||(e[13]=r(" only. ",-1)),k(U,{label:"why narrowing helps"},{default:S(()=>[...e[11]||(e[11]=[r(" On a stack with a chatty sidecar the interleaved output is unreadable, and the lines you want are often already past the tail. Narrowing spends the whole tail on one service. ",-1)])]),_:1})])):v("",!0),i.value?(a(),n($,{key:4},[_.value?(a(),n("pre",le,m(x.value||"(no output)"),1)):(a(),D(P,{key:1,rows:A.value,"copy-text":x.value,"empty-text":"(no output)"},null,8,["rows","copy-text"])),l("div",oe,[l("label",ae,[R(l("input",{"onUpdate:modelValue":e[3]||(e[3]=o=>_.value=o),type:"checkbox"},null,512),[[H,_.value]]),e[15]||(e[15]=r(" Raw output ",-1)),k(U,{label:"when raw matters"},{default:S(()=>[...e[14]||(e[14]=[r(" The pretty view lifts each line's service prefix into a coloured column. Raw is the exact text the server sent — the form to paste into a bug report or compare with someone else's copy. ",-1)])]),_:1})])])],64)):c.value?v("",!0):(a(),n("p",ne,[e[16]||(e[16]=r(" Press ",-1)),e[17]||(e[17]=l("b",null,"Fetch",-1)),r(" to read the last "+m(h.value)+" lines"+m(p.value?` of ${p.value}`:"")+". ",1)])),i.value?(a(),n("p",ie,[...e[18]||(e[18]=[r(" Redacted on the host before sending: credentials inside URLs, ",-1),l("code",null,"NAME=value",-1),r(" pairs whose name reads as a secret, and this server's own token. ",-1)])])):v("",!0)],64)):(a(),n("p",Y,[...e[8]||(e[8]=[r(" This spec has no ",-1),l("code",null,"compose:",-1),r(" section, so there are no compose logs to read. Its axes may still have created resources — check those at their own source. ",-1)])])):(a(),n("p",Q,"Unavailable until the spec resolves."))])}}});export{fe as default};
|
|
5
|
-
//# sourceMappingURL=LogsTab-QEuDGrG2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LogsTab-QEuDGrG2.js","sources":["../../src/views/tabs/LogsTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Compose logs, fetched ON DEMAND.\n *\n * Never polled: an auto-refresh against a chatty stack would hammer the host through\n * `docker compose logs` — the same host that is running everyone else's previews. The tail is\n * clamped to 1–2000 by the server for the same reason.\n */\nimport { computed, ref, watch } from 'vue';\nimport { api, problem, query } from '../../api/client';\nimport type { LogsResponse, RuntimeResponse } from '../../api/types';\nimport { dep } from '../../composables/useDeployment';\nimport InfoHint from '../../components/InfoHint.vue';\nimport SelectMenu from '../../components/SelectMenu.vue';\nimport LogViewer from '../../components/LogViewer.vue';\nimport type { LogRow } from '../../components/log-viewer-types';\n\nconst tail = ref(200);\n/** '' means the whole stack. */\nconst service = ref('');\n/**\n * The services to choose from, taken from what is actually RUNNING rather than from the compose file.\n * A service the spec declares but never started has no logs, and offering it would produce an\n * empty pane that reads like a bug.\n */\nconst services = ref<string[]>([]);\n\nasync function loadServices(): Promise<void> {\n const r = await api.get<RuntimeResponse>(\n `/api/deployments/${encodeURIComponent(dep.id)}/runtime${query(dep.vars)}`,\n );\n if (!r.ok) return; // the picker is a convenience; without it the whole-stack read still works\n services.value = [\n ...new Set(r.body.containers.map((c) => c.service).filter((s): s is string => !!s)),\n ].sort();\n}\n\n/** Empty value = the whole stack, which is a real choice here rather than a placeholder. */\nconst serviceOptions = computed(() => [\n { value: '', label: 'All services' },\n ...services.value.map((s) => ({ value: s, label: s })),\n]);\n\nconst filter = ref('');\nconst logs = ref<LogsResponse | null>(null);\nconst error = ref('');\nconst loading = ref(false);\n\nwatch(\n () => [dep.id, !!dep.detail?.compose] as const,\n ([id, hasCompose], prev) => {\n if (!prev || prev[0] !== id) {\n services.value = [];\n service.value = '';\n logs.value = null;\n }\n // Gated on the RESOLVED spec, not just the id: this component mounts before the parent has\n // resolved the deployment, so an id-only watcher saw `dep.detail === undefined` and the service\n // picker never appeared.\n if (hasCompose && services.value.length === 0) void loadServices();\n },\n { immediate: true },\n);\n\nasync function fetchLogs(): Promise<void> {\n if (!dep.detail?.compose) return;\n const id = dep.id;\n loading.value = true;\n error.value = '';\n const chosen = service.value;\n const r = await api.get<LogsResponse>(\n `/api/deployments/${encodeURIComponent(id)}/logs${query(dep.vars, {\n tail: tail.value,\n ...(chosen ? { service: chosen } : {}),\n })}`,\n );\n loading.value = false;\n if (dep.id !== id) return; // navigated away mid-flight\n // A slow response for the previously-selected service must not land in a pane now labelled with a\n // different one.\n if (r.ok && (r.body.service ?? '') !== chosen) return;\n if (!r.ok) {\n logs.value = null;\n error.value = problem(r, 'load the logs');\n return;\n }\n logs.value = { ...r.body, ok: r.body.ok !== false, text: r.body.text ?? '' };\n}\n\n/** Search-in-logs: filter to matching lines rather than highlighting inside a wall of text. */\nconst shown = computed(() => {\n const text = logs.value?.text ?? '';\n const needle = filter.value.trim().toLowerCase();\n if (!needle) return text;\n const lines = text.split('\\n').filter((l) => l.toLowerCase().includes(needle));\n return lines.length ? lines.join('\\n') : `(no line matches \"${filter.value}\")`;\n});\n\n/**\n * Raw is ALWAYS available, one click away. The pretty view re-arranges compose's output, and a\n * re-arranged log is the wrong thing to paste into a bug report or diff against a colleague's —\n * raw shows the exact bytes the server sent, filter and all.\n */\nconst raw = ref(false);\n\n/**\n * Compose prefixes every line with `container-name | `. The pretty view lifts that prefix into a\n * non-wrapping gutter with a stable per-service colour, so interleaved services stop reading as one\n * grey wall. A line with no prefix (a compose warning, a continuation) gets an empty gutter rather\n * than being guessed at.\n */\nconst PREFIX = /^(\\S+)\\s+\\| /;\nconst logRows = computed<LogRow[]>(() => {\n const text = shown.value;\n if (!text || text.startsWith('(no line matches')) return [];\n const hues = new Map<string, number>();\n return text.split('\\n').map((line, i) => {\n const m = PREFIX.exec(line);\n if (!m) return { key: i, text: line };\n const svc = m[1]!;\n if (!hues.has(svc)) hues.set(svc, hues.size % 6);\n return {\n key: i,\n gutter: svc,\n text: line.slice(m[0].length),\n tone: `svc-${hues.get(svc)}`,\n };\n });\n});\n</script>\n\n<template>\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Compose logs</h2>\n <span class=\"grow\" />\n <div v-if=\"services.length > 1\" class=\"field\">\n <label for=\"svc\" class=\"mute\" style=\"font-size: var(--t-xs)\">service</label>\n <SelectMenu v-model=\"service\" id=\"svc\" label=\"Service\" :options=\"serviceOptions\" />\n </div>\n <div class=\"field\">\n <label for=\"tail\" class=\"mute\" style=\"font-size: var(--t-xs)\">tail</label>\n <SelectMenu :model-value=\"String(tail)\" label=\"How many lines\" :options=\"[{ value: '50', label: '50' }, { value: '200', label: '200' }, { value: '500', label: '500' }, { value: '1000', label: '1000' }, { value: '2000', label: '2000' }]\" @update:model-value=\"(v) => (tail = Number(v))\" />\n </div>\n <button\n class=\"primary\"\n style=\"align-self: end\"\n :disabled=\"loading || !dep.detail?.compose\"\n @click=\"fetchLogs\"\n >\n {{ loading ? 'reading…' : 'Fetch' }}\n <span v-if=\"loading\" class=\"progress\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <p v-if=\"!dep.detail\" class=\"mute\">Unavailable until the spec resolves.</p>\n <!-- Guard on the field rather than calling and interpreting the failure after the fact. -->\n <p v-else-if=\"!dep.detail.compose\" class=\"mute\">\n This spec has no <code>compose:</code> section, so there are no compose logs to read. Its axes\n may still have created resources — check those at their own source.\n </p>\n\n <template v-else>\n <p class=\"hint\" style=\"margin: 0 0 var(--s3)\">\n Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer the\n host. The server caps how many lines it will return.\n </p>\n\n <div v-if=\"error\" class=\"banner failed\">{{ error }}</div>\n\n <!--\n `ok: false` means compose exited non-zero. Usually \"no such project\" because the stack was\n never brought up — not a server fault, so it is a note rather than an error.\n -->\n <div v-else-if=\"logs && !logs.ok\" class=\"banner warn\">\n <b>compose exited non-zero.</b>\n <p>\n Most often the project does not exist yet (nothing has been brought up), or a profile is\n missing. Any output it did produce is below.\n </p>\n </div>\n\n <div v-if=\"logs\" class=\"field\" style=\"margin-top: var(--s3)\">\n <label for=\"lf\" class=\"mute\" style=\"font-size: var(--t-xs)\">Search in these lines</label>\n <input id=\"lf\" v-model=\"filter\" data-search type=\"search\" placeholder=\"substring\" spellcheck=\"false\" />\n </div>\n\n <div v-if=\"logs && logs.service\" class=\"hint\" style=\"margin-top: var(--s3)\">\n Showing <b>{{ logs.service }}</b> only.\n <InfoHint label=\"why narrowing helps\">\n On a stack with a chatty sidecar the interleaved output is unreadable, and the lines you want\n are often already past the tail. Narrowing spends the whole tail on one service.\n </InfoHint>\n </div>\n\n <template v-if=\"logs\">\n <pre v-if=\"raw\" class=\"log\">{{ shown || '(no output)' }}</pre>\n <LogViewer v-else :rows=\"logRows\" :copy-text=\"shown\" empty-text=\"(no output)\" />\n <div class=\"row\" style=\"margin-top: var(--s2)\">\n <label class=\"check\">\n <input v-model=\"raw\" type=\"checkbox\" />\n Raw output\n <InfoHint label=\"when raw matters\">\n The pretty view lifts each line's service prefix into a coloured column. Raw is the\n exact text the server sent — the form to paste into a bug report or compare with\n someone else's copy.\n </InfoHint>\n </label>\n </div>\n </template>\n <p v-else-if=\"!loading\" class=\"mute\" style=\"margin-top: var(--s3)\">\n Press <b>Fetch</b> to read the last {{ tail }} lines{{ service ? ` of ${service}` : '' }}.\n </p>\n\n <p v-if=\"logs\" class=\"hint\">\n Redacted on the host before sending: credentials inside URLs, <code>NAME=value</code> pairs\n whose name reads as a secret, and this server's own token.\n </p>\n </template>\n </section>\n</template>\n"],"names":["tail","ref","service","services","loadServices","r","api","dep","query","c","s","serviceOptions","computed","filter","logs","error","loading","watch","_a","id","hasCompose","prev","fetchLogs","chosen","problem","shown","text","needle","lines","l","raw","PREFIX","logRows","hues","line","i","m","svc","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_hoisted_3","_createVNode","SelectMenu","$event","_hoisted_4","v","_unref","_createTextVNode","_toDisplayString","_hoisted_6","_Fragment","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","InfoHint","_hoisted_13","_createBlock","LogViewer","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_8","_hoisted_7"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as J,H as K,c as o,a as t,f as n,e as h,w as g,_ as T,x as E,g as c,F as f,t as r,s as N,i as j,v as U,r as v,C as S,A as y,B as _,o as l,D as Q,E as X,u as Z,N as D}from"./index-jLPJ82ve.js";import{s as ee}from"./useFormat-CKJxQp_i.js";import{_ as F}from"./ActionButton.vue_vue_type_script_setup_true_lang-C0iLjBml.js";import{_ as I}from"./ErrorNote.vue_vue_type_script_setup_true_lang-DjEWoD0O.js";import{_ as te}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{_ as se}from"./RelativeTime.vue_vue_type_script_setup_true_lang-Dx90hw4F.js";import{_ as ae}from"./SelectMenu.vue_vue_type_script_setup_true_lang-B-DTK242.js";import"./Teleport-SesEhfqH.js";const le={class:"page-head"},oe={class:"sub"},ie={key:1,class:"panel"},ne={key:0,class:"banner ok"},re={class:"code"},de={class:"row"},ue={class:"panel"},ve={key:1,class:"cards"},ce={class:"stagger"},pe={class:"name","data-label":"name"},he={class:"mute",style:{"font-size":"var(--t-sm)"}},me={"data-label":"events"},fe={key:0,class:"badge"},ye={key:1},ke={"data-label":"last delivery"},ge={key:0,class:"mute"},_e={key:2,class:"mute",style:{"font-size":"var(--t-sm)"}},be={"data-label":""},we={class:"row",style:{"justify-content":"flex-end"}},$e={key:0,class:"badge off"},xe=["onClick"],Ce=["onClick"],Te=["onClick"],Ne={colspan:"4"},Ve={key:0,class:"kvlist"},Ae={class:"k"},Ee={class:"v"},Se={class:"mute",style:{"font-size":"var(--t-sm)"}},De={key:0,class:"s-failed",style:{"font-size":"var(--t-sm)"}},Re={key:1,class:"mute"},ze={key:2,class:"mute"},Be={class:"panel"},He={key:0,class:"field",style:{"max-width":"320px"}},Pe={class:"field",style:{"max-width":"380px"}},je=["for"],Ue=["id","onUpdate:modelValue","placeholder"],Xe={class:"section",style:{margin:"var(--s5) 0 var(--s3)"}},Fe={class:"row"},Ie={class:"check"},Me=["checked"],Oe={key:1,class:"check-grid"},We=["checked","onChange"],qe={class:"row",style:{"margin-top":"var(--s4)"}},Le={class:"hint"},at=J({__name:"NotifiersView",setup(Ye){const V=v([]),k=v(null),R=v(!1),p=v(""),z=v(!1),b=v(null),i=v({name:"",type:"webhook",events:[],config:{}}),w=v(!1),x=v(""),m=v(null),A=v([]),M=S(()=>{var a;return(((a=k.value)==null?void 0:a.types)??[]).map(e=>({value:e.kind,label:e.label}))}),B=S(()=>{var a;return((a=k.value)==null?void 0:a.types.find(e=>e.kind===i.value.type))??null});K(()=>i.value.type,()=>{i.value.config={}});const O=S(()=>{var a;return!w.value&&!!i.value.name.trim()&&i.value.events.length>0&&(((a=B.value)==null?void 0:a.fields)??[]).every(e=>{var u;return!e.required||!!((u=i.value.config[e.key])!=null&&u.trim())})});async function $(){const[a,e]=await Promise.all([y.get("/api/notifiers"),y.get("/api/notifiers/meta")]);if(R.value=!0,a.status===404||e.status===404){z.value=!0;return}if(!a.ok){p.value=_(a,"load the notifiers");return}p.value="",V.value=a.body.notifiers??[],e.ok&&(k.value=e.body)}$();function H(a){const e=i.value.events.indexOf(a);e===-1?i.value.events.push(a):i.value.events.splice(e,1)}async function W(){w.value=!0,x.value="";const a=await y.post("/api/notifiers",{name:i.value.name.trim(),type:i.value.type,events:i.value.events,config:i.value.config});if(w.value=!1,!a.ok){x.value=_(a,"register this notifier");return}b.value=a.body.secret?{name:a.body.notifier.name,secret:a.body.secret}:null,i.value={name:"",type:i.value.type,events:[],config:{}},$()}async function q(a,e){const u=await y.patch(`/api/notifiers/${a.id}`,{enabled:e});u.ok||(p.value=_(u,"update this notifier")),$()}async function L(a){const e=await y.del(`/api/notifiers/${a.id}`);if(!e.ok){p.value=_(e,"delete this notifier");return}m.value===a.id&&(m.value=null),D("ok",`Removed ${a.name}.`),$()}async function Y(a){const e=await y.post(`/api/notifiers/${a.id}/test`,{});if(!e.ok){p.value=_(e,"send a test delivery");return}const u=e.body.result;u.ok?D("ok",`${a.name} accepted the test delivery.`):D("error",`${a.name}: ${u.error??`HTTP ${u.status}`}`),$()}async function G(a){if(m.value===a.id){m.value=null;return}const e=await y.get(`/api/notifiers/${a.id}/deliveries`);if(!e.ok){p.value=_(e,"load the delivery log");return}A.value=e.body.deliveries??[],m.value=a.id}return(a,e)=>{var u,P;return l(),o("div",null,[t("div",le,[t("div",null,[e[6]||(e[6]=t("h1",null,"Notifiers",-1)),t("div",oe,[e[5]||(e[5]=n(" Where events on this host get sent ",-1)),h(T,{label:"what a notifier is"},{default:g(()=>[...e[4]||(e[4]=[n(" A registration that receives events — a teardown that leaked, a deployment created, a job that failed. Webhooks today; the server decides what other kinds exist, and this page renders whatever it offers. ",-1)])]),_:1})])])]),p.value?(l(),E(I,{key:0,text:p.value,title:"Could not load the notifiers."},null,8,["text"])):c("",!0),z.value?(l(),o("section",ie,[...e[7]||(e[7]=[t("div",{class:"banner plain"},[t("b",null,"This server has no notifiers."),t("p",null,"It is an older build of pstack. Upgrade the host to register webhooks.")],-1)])])):(l(),o(f,{key:2},[b.value?(l(),o("div",ne,[t("b",null,"Signing secret for “"+r(b.value.name)+"” — copy it now.",1),t("p",null,[e[9]||(e[9]=n(" This is the only time it is shown — the server keeps no way to show it again. Your receiver uses it to check that a delivery really came from here. ",-1)),h(T,{label:"how a receiver checks it"},{default:g(()=>[...e[8]||(e[8]=[n(" Each delivery carries a signature header computed from this secret and the exact body sent. Recompute it on your side and compare; a mismatch means the delivery was not sent by this host, or was altered on the way. ",-1)])]),_:1})]),t("pre",re,r(b.value.secret),1),t("div",de,[t("button",{class:"btn",onClick:e[0]||(e[0]=s=>b.value=null)},"I have stored it")])])):c("",!0),t("section",ue,[e[12]||(e[12]=t("div",{class:"phead"},[t("h2",{class:"section"},"Registered")],-1)),R.value?V.value.length?(l(),o("table",ve,[e[11]||(e[11]=t("thead",null,[t("tr",null,[t("th",null,"name"),t("th",null,"events"),t("th",null,"last delivery"),t("th")])],-1)),t("tbody",ce,[(l(!0),o(f,null,N(V.value,(s,C)=>(l(),o(f,{key:s.id},[t("tr",{style:Q({"--i":C})},[t("td",pe,[n(r(s.name)+" ",1),t("div",he,r(s.type)+" · "+r(String(s.config.url??"")),1)]),t("td",me,[s.events.includes("*")?(l(),o("span",fe,"all events")):(l(),o("span",ye,r(s.events.join(", ")),1))]),t("td",ke,[s.lastStatus?(l(),o("span",{key:1,class:X(s.lastStatus==="ok"?"s-ok":"s-failed")},r(s.lastStatus),3)):(l(),o("span",ge,"never")),s.lastAt?(l(),o("div",_e,[h(se,{at:s.lastAt},null,8,["at"])])):c("",!0)]),t("td",be,[t("div",we,[s.enabled?c("",!0):(l(),o("span",$e,"disabled")),t("button",{class:"ghost sm",onClick:d=>G(s)},r(m.value===s.id?"Hide":"Deliveries"),9,xe),t("button",{class:"ghost sm",onClick:d=>Y(s)},"Test",8,Ce),t("button",{class:"ghost sm",onClick:d=>q(s,!s.enabled)},r(s.enabled?"Disable":"Enable"),9,Te),h(F,{variant:"danger",onClick:d=>L(s)},{default:g(()=>[...e[10]||(e[10]=[n("Remove",-1)])]),_:1},8,["onClick"])])])],4),m.value===s.id?(l(),o("tr",{key:`d${s.id}`},[t("td",Ne,[A.value.length?(l(),o("ul",Ve,[(l(!0),o(f,null,N(A.value,d=>(l(),o("li",{key:d.id},[t("span",Ae,[t("span",{class:X(d.status==="ok"?"s-ok":"s-failed")},r(d.status),3)]),t("span",Ee,[n(r(d.event)+" ",1),t("span",Se,[n(" · "+r(Z(ee)(d.createdAt))+" · "+r(d.attempts)+" attempt(s)",1),d.responseCode?(l(),o(f,{key:0},[n(" · HTTP "+r(d.responseCode),1)],64)):c("",!0)]),d.error?(l(),o("div",De,r(d.error),1)):c("",!0)])]))),128))])):(l(),o("p",Re,"Nothing delivered yet."))])])):c("",!0)],64))),128))])])):(l(),o("p",ze," Nothing registered. Events still happen — nobody is being told about them. ")):(l(),E(te,{key:0,rows:2}))]),t("section",Be,[e[20]||(e[20]=t("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Add a notifier",-1)),k.value&&k.value.types.length>1?(l(),o("div",He,[e[13]||(e[13]=t("label",{for:"ty"},"Type",-1)),h(ae,{modelValue:i.value.type,"onUpdate:modelValue":e[1]||(e[1]=s=>i.value.type=s),id:"ty",label:"Notifier type",options:M.value},null,8,["modelValue","options"])])):c("",!0),t("div",Pe,[e[14]||(e[14]=t("label",{for:"nn"},"Name",-1)),j(t("input",{id:"nn","onUpdate:modelValue":e[2]||(e[2]=s=>i.value.name=s),type:"text",placeholder:"ops-slack-bridge",spellcheck:"false"},null,512),[[U,i.value.name,void 0,{trim:!0}]])]),(l(!0),o(f,null,N(((u=B.value)==null?void 0:u.fields)??[],s=>(l(),o("div",{key:s.key,class:"field",style:{"max-width":"380px"}},[t("label",{for:`cfg-${s.key}`},r(s.label),9,je),j(t("input",{id:`cfg-${s.key}`,"onUpdate:modelValue":C=>i.value.config[s.key]=C,type:"text",placeholder:s.placeholder,spellcheck:"false"},null,8,Ue),[[U,i.value.config[s.key],void 0,{trim:!0}]])]))),128)),t("h2",Xe,[e[16]||(e[16]=n(" Events ",-1)),h(T,{label:"which events to pick"},{default:g(()=>[...e[15]||(e[15]=[t("b",null,"All events",-1),n(" also covers events added in later versions — a specific list does not, and the failure mode of that is silence nobody can explain. ",-1),t("code",null,"job.leaked",-1),n(" is the one worth alerting on: a teardown ran and something survived it. ",-1)])]),_:1})]),t("div",Fe,[t("label",Ie,[t("input",{type:"checkbox",checked:i.value.events.includes("*"),onChange:e[3]||(e[3]=s=>H("*"))},null,40,Me),e[17]||(e[17]=n(" All events ",-1))])]),i.value.events.includes("*")?c("",!0):(l(),o("div",Oe,[(l(!0),o(f,null,N(((P=k.value)==null?void 0:P.events)??[],s=>(l(),o("label",{key:s,class:"check"},[t("input",{type:"checkbox",checked:i.value.events.includes(s),onChange:C=>H(s)},null,40,We),n(" "+r(s),1)]))),128))])),x.value?(l(),E(I,{key:2,text:x.value,title:"Could not register this notifier."},null,8,["text"])):c("",!0),t("div",qe,[h(F,{variant:"primary",pending:w.value,disabled:!O.value,onClick:W},{default:g(()=>[n(r(w.value?"Registering…":"Register"),1)]),_:1},8,["pending","disabled"])]),t("p",Le,[e[19]||(e[19]=n(" Deliveries from a signing type are signed with a secret shown once at registration, retried twice on failure, and logged here. ",-1)),h(T,{label:"how a receiver verifies a delivery"},{default:g(()=>[...e[18]||(e[18]=[n(" Recompute ",-1),t("code",null,"HMAC-SHA256(secret, `${timestamp}.${rawBody}`)",-1),n(" and compare against ",-1),t("code",null,"X-Pstack-Signature",-1),n(", using the ",-1),t("em",null,"raw",-1),n(" body — re-serialising it changes the bytes. Reject anything whose ",-1),t("code",null,"X-Pstack-Timestamp",-1),n(" is more than five minutes old, and dedupe on ",-1),t("code",null,"X-Pstack-Delivery",-1),n(": delivery is at-least-once, and that id is stable across retries. ",-1)])]),_:1})])])],64))])}}});export{at as default};
|
|
2
|
-
//# sourceMappingURL=NotifiersView-B1xAKTwU.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NotifiersView-B1xAKTwU.js","sources":["../../src/views/NotifiersView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Notifiers — where events on this host get sent.\n *\n * THE FORM IS SERVER-DRIVEN. The event list and the per-type fields come from `/api/notifiers/meta`,\n * so adding a Slack or Discord type on the server puts it in this picker with its own form and no\n * change here. That is the composability seam reaching the UI; hard-coding the fields would have\n * quietly made the seam a lie.\n *\n * THE SIGNING SECRET IS SHOWN ONCE. Same discipline as a personal token and a registry credential:\n * there is no read path on the server, so this page cannot offer a reveal — it can only show the\n * value at the moment of creation and say plainly that this is the only time.\n */\nimport { computed, ref, watch } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { DeliveryRow, NotifierMeta, NotifierRow } from '../api/types';\nimport { stamp } from '../composables/useFormat';\nimport { toast } from '../composables/useToasts';\nimport ActionButton from '../components/ActionButton.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport RelativeTime from '../components/RelativeTime.vue';\nimport SelectMenu from '../components/SelectMenu.vue';\n\nconst notifiers = ref<NotifierRow[]>([]);\nconst meta = ref<NotifierMeta | null>(null);\nconst loaded = ref(false);\nconst listError = ref('');\nconst unsupported = ref(false);\n\n/** The freshly-minted secret, held only until the operator dismisses it. */\nconst revealed = ref<{ name: string; secret: string } | null>(null);\n\nconst form = ref({ name: '', type: 'webhook', events: [] as string[], config: {} as Record<string, string> });\nconst saving = ref(false);\nconst formError = ref('');\n\nconst openDeliveries = ref<number | null>(null);\nconst deliveries = ref<DeliveryRow[]>([]);\n\n/** Server-driven, so a new notifier type appears here with no change to this file. */\nconst typeOptions = computed(() => (meta.value?.types ?? []).map((x) => ({ value: x.kind, label: x.label })));\n\nconst chosenType = computed(() => meta.value?.types.find((t) => t.kind === form.value.type) ?? null);\n\n/**\n * `config` is one bag shared by every type, so switching the picker leaves the previous type's\n * fields behind — fill a webhook URL, switch to Slack, and the POST carries a stray `url` alongside\n * `webhookUrl`. Nothing downstream rejects it: `config` is stored whole and each type validates only\n * its OWN fields, so the dead value is persisted forever and shows up in the detail view. Clearing on\n * change costs one line; unknown-key rejection on the server would cost a migration path for every\n * future type.\n */\nwatch(\n () => form.value.type,\n () => {\n form.value.config = {};\n },\n);\nconst canSave = computed(\n () =>\n !saving.value &&\n !!form.value.name.trim() &&\n form.value.events.length > 0 &&\n (chosenType.value?.fields ?? []).every((f) => !f.required || !!form.value.config[f.key]?.trim()),\n);\n\nasync function load(): Promise<void> {\n const [list, m] = await Promise.all([\n api.get<{ notifiers: NotifierRow[] }>('/api/notifiers'),\n api.get<NotifierMeta>('/api/notifiers/meta'),\n ]);\n loaded.value = true;\n // A server built before this feature answers 404 — a capability difference, not an error.\n if (list.status === 404 || m.status === 404) {\n unsupported.value = true;\n return;\n }\n if (!list.ok) {\n listError.value = problem(list, 'load the notifiers');\n return;\n }\n listError.value = '';\n notifiers.value = list.body.notifiers ?? [];\n if (m.ok) meta.value = m.body;\n}\n\nvoid load();\n\nfunction toggleEvent(name: string): void {\n const at = form.value.events.indexOf(name);\n if (at === -1) form.value.events.push(name);\n else form.value.events.splice(at, 1);\n}\n\nasync function create(): Promise<void> {\n saving.value = true;\n formError.value = '';\n const r = await api.post<{ notifier: NotifierRow; secret: string | null }>('/api/notifiers', {\n name: form.value.name.trim(),\n type: form.value.type,\n events: form.value.events,\n config: form.value.config,\n });\n saving.value = false;\n if (!r.ok) {\n formError.value = problem(r, 'register this notifier');\n return;\n }\n // `null` for a type whose config already carries its credential — a Slack notifier must not be\n // handed 48 hex characters and told its receiver needs them.\n revealed.value = r.body.secret ? { name: r.body.notifier.name, secret: r.body.secret } : null;\n form.value = { name: '', type: form.value.type, events: [], config: {} };\n void load();\n}\n\nasync function setEnabled(n: NotifierRow, enabled: boolean): Promise<void> {\n const r = await api.patch<{ notifier: NotifierRow }>(`/api/notifiers/${n.id}`, { enabled });\n if (!r.ok) listError.value = problem(r, 'update this notifier');\n void load();\n}\n\nasync function remove(n: NotifierRow): Promise<void> {\n const r = await api.del(`/api/notifiers/${n.id}`);\n if (!r.ok) {\n listError.value = problem(r, 'delete this notifier');\n return;\n }\n if (openDeliveries.value === n.id) openDeliveries.value = null;\n toast('ok', `Removed ${n.name}.`);\n void load();\n}\n\nasync function test(n: NotifierRow): Promise<void> {\n const r = await api.post<{ result: { ok: boolean; status?: number; error?: string } }>(\n `/api/notifiers/${n.id}/test`,\n {},\n );\n if (!r.ok) {\n listError.value = problem(r, 'send a test delivery');\n return;\n }\n const res = r.body.result;\n // The endpoint's own answer, verbatim — \"it failed\" without the reason is the thing this page\n // exists to avoid.\n if (res.ok) toast('ok', `${n.name} accepted the test delivery.`);\n else toast('error', `${n.name}: ${res.error ?? `HTTP ${res.status}`}`);\n void load();\n}\n\nasync function showDeliveries(n: NotifierRow): Promise<void> {\n if (openDeliveries.value === n.id) {\n openDeliveries.value = null;\n return;\n }\n const r = await api.get<{ deliveries: DeliveryRow[] }>(`/api/notifiers/${n.id}/deliveries`);\n if (!r.ok) {\n listError.value = problem(r, 'load the delivery log');\n return;\n }\n deliveries.value = r.body.deliveries ?? [];\n openDeliveries.value = n.id;\n}\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Notifiers</h1>\n <div class=\"sub\">\n Where events on this host get sent\n <InfoHint label=\"what a notifier is\">\n A registration that receives events — a teardown that leaked, a deployment created, a job\n that failed. Webhooks today; the server decides what other kinds exist, and this page\n renders whatever it offers.\n </InfoHint>\n </div>\n </div>\n </div>\n\n <ErrorNote v-if=\"listError\" :text=\"listError\" title=\"Could not load the notifiers.\" />\n\n <section v-if=\"unsupported\" class=\"panel\">\n <div class=\"banner plain\">\n <b>This server has no notifiers.</b>\n <p>It is an older build of pstack. Upgrade the host to register webhooks.</p>\n </div>\n </section>\n\n <template v-else>\n <!--\n Shown once, and the page says so. There is no read path on the server, so this is not a\n design choice that could be softened later — the value genuinely does not exist anywhere\n retrievable after this moment.\n -->\n <!-- Only a signing type ever produces one; see `signs` in notify.ts. -->\n <div v-if=\"revealed\" class=\"banner ok\">\n <b>Signing secret for “{{ revealed.name }}” — copy it now.</b>\n <p>\n This is the only time it is shown — the server keeps no way to show it again. Your\n receiver uses it to check that a delivery really came from here.\n <InfoHint label=\"how a receiver checks it\">\n Each delivery carries a signature header computed from this secret and the exact body\n sent. Recompute it on your side and compare; a mismatch means the delivery was not sent\n by this host, or was altered on the way.\n </InfoHint>\n </p>\n <pre class=\"code\">{{ revealed.secret }}</pre>\n <div class=\"row\">\n <button class=\"btn\" @click=\"revealed = null\">I have stored it</button>\n </div>\n </div>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Registered</h2>\n </div>\n\n <SkeletonList v-if=\"!loaded\" :rows=\"2\" />\n <table v-else-if=\"notifiers.length\" class=\"cards\">\n <thead>\n <tr>\n <th>name</th>\n <th>events</th>\n <th>last delivery</th>\n <th></th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <template v-for=\"(n, i) in notifiers\" :key=\"n.id\">\n <tr :style=\"{ '--i': i }\">\n <td class=\"name\" data-label=\"name\">\n {{ n.name }}\n <div class=\"mute\" style=\"font-size: var(--t-sm)\">\n {{ n.type }} · {{ String(n.config.url ?? '') }}\n </div>\n </td>\n <td data-label=\"events\">\n <span v-if=\"n.events.includes('*')\" class=\"badge\">all events</span>\n <span v-else>{{ n.events.join(', ') }}</span>\n </td>\n <td data-label=\"last delivery\">\n <span v-if=\"!n.lastStatus\" class=\"mute\">never</span>\n <span v-else :class=\"n.lastStatus === 'ok' ? 's-ok' : 's-failed'\">\n {{ n.lastStatus }}\n </span>\n <div v-if=\"n.lastAt\" class=\"mute\" style=\"font-size: var(--t-sm)\">\n <RelativeTime :at=\"n.lastAt\" />\n </div>\n </td>\n <td data-label=\"\">\n <div class=\"row\" style=\"justify-content: flex-end\">\n <span v-if=\"!n.enabled\" class=\"badge off\">disabled</span>\n <button class=\"ghost sm\" @click=\"showDeliveries(n)\">\n {{ openDeliveries === n.id ? 'Hide' : 'Deliveries' }}\n </button>\n <button class=\"ghost sm\" @click=\"test(n)\">Test</button>\n <button class=\"ghost sm\" @click=\"setEnabled(n, !n.enabled)\">\n {{ n.enabled ? 'Disable' : 'Enable' }}\n </button>\n <ActionButton variant=\"danger\" @click=\"remove(n)\">Remove</ActionButton>\n </div>\n </td>\n </tr>\n <tr v-if=\"openDeliveries === n.id\" :key=\"`d${n.id}`\">\n <td colspan=\"4\">\n <ul v-if=\"deliveries.length\" class=\"kvlist\">\n <li v-for=\"d in deliveries\" :key=\"d.id\">\n <span class=\"k\">\n <span :class=\"d.status === 'ok' ? 's-ok' : 's-failed'\">{{ d.status }}</span>\n </span>\n <span class=\"v\">\n {{ d.event }}\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n · {{ stamp(d.createdAt) }} · {{ d.attempts }} attempt(s)<template\n v-if=\"d.responseCode\"\n >\n · HTTP {{ d.responseCode }}</template\n >\n </span>\n <div v-if=\"d.error\" class=\"s-failed\" style=\"font-size: var(--t-sm)\">\n {{ d.error }}\n </div>\n </span>\n </li>\n </ul>\n <p v-else class=\"mute\">Nothing delivered yet.</p>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n <p v-else class=\"mute\">\n Nothing registered. Events still happen — nobody is being told about them.\n </p>\n </section>\n\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">Add a notifier</h2>\n\n <div v-if=\"meta && meta.types.length > 1\" class=\"field\" style=\"max-width: 320px\">\n <label for=\"ty\">Type</label>\n <SelectMenu v-model=\"form.type\" id=\"ty\" label=\"Notifier type\" :options=\"typeOptions\" />\n </div>\n\n <div class=\"field\" style=\"max-width: 380px\">\n <label for=\"nn\">Name</label>\n <input id=\"nn\" v-model.trim=\"form.name\" type=\"text\" placeholder=\"ops-slack-bridge\" spellcheck=\"false\" />\n </div>\n\n <!-- Rendered from the server's field list — see the header. -->\n <div v-for=\"f in chosenType?.fields ?? []\" :key=\"f.key\" class=\"field\" style=\"max-width: 380px\">\n <label :for=\"`cfg-${f.key}`\">{{ f.label }}</label>\n <input\n :id=\"`cfg-${f.key}`\"\n v-model.trim=\"form.config[f.key]\"\n type=\"text\"\n :placeholder=\"f.placeholder\"\n spellcheck=\"false\"\n />\n </div>\n\n <h2 class=\"section\" style=\"margin: var(--s5) 0 var(--s3)\">\n Events\n <InfoHint label=\"which events to pick\">\n <b>All events</b> also covers events added in later versions — a specific list does not,\n and the failure mode of that is silence nobody can explain. <code>job.leaked</code> is the\n one worth alerting on: a teardown ran and something survived it.\n </InfoHint>\n </h2>\n <div class=\"row\">\n <label class=\"check\">\n <input\n type=\"checkbox\"\n :checked=\"form.events.includes('*')\"\n @change=\"toggleEvent('*')\"\n />\n All events\n </label>\n </div>\n <!-- A grid, not a wrapping row: eleven items of differing width wrapped into ragged lines\n with one stranded on its own, which reads as a layout accident. -->\n <div v-if=\"!form.events.includes('*')\" class=\"check-grid\">\n <label v-for=\"e in meta?.events ?? []\" :key=\"e\" class=\"check\">\n <input type=\"checkbox\" :checked=\"form.events.includes(e)\" @change=\"toggleEvent(e)\" />\n {{ e }}\n </label>\n </div>\n\n <ErrorNote v-if=\"formError\" :text=\"formError\" title=\"Could not register this notifier.\" />\n\n <div class=\"row\" style=\"margin-top: var(--s4)\">\n <ActionButton variant=\"primary\" :pending=\"saving\" :disabled=\"!canSave\" @click=\"create\">\n {{ saving ? 'Registering…' : 'Register' }}\n </ActionButton>\n </div>\n\n <p class=\"hint\">\n Deliveries from a signing type are signed with a secret shown once at registration, retried twice on failure,\n and logged here.\n <InfoHint label=\"how a receiver verifies a delivery\">\n Recompute <code>HMAC-SHA256(secret, `${timestamp}.${rawBody}`)</code> and compare against\n <code>X-Pstack-Signature</code>, using the <em>raw</em> body — re-serialising it changes\n the bytes. Reject anything whose <code>X-Pstack-Timestamp</code> is more than five minutes\n old, and dedupe on <code>X-Pstack-Delivery</code>: delivery is at-least-once, and that id\n is stable across retries.\n </InfoHint>\n </p>\n </section>\n </template>\n </div>\n</template>\n"],"names":["notifiers","ref","meta","loaded","listError","unsupported","revealed","form","saving","formError","openDeliveries","deliveries","typeOptions","computed","_a","x","chosenType","t","watch","canSave","f","load","list","m","api","problem","toggleEvent","name","at","create","r","setEnabled","n","enabled","remove","toast","test","res","showDeliveries","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_hoisted_2","_createVNode","InfoHint","_createBlock","ErrorNote","_openBlock","_hoisted_3","_Fragment","_hoisted_4","_toDisplayString","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_renderList","i","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_normalizeClass","_hoisted_16","_hoisted_17","RelativeTime","_hoisted_18","_hoisted_19","_hoisted_20","$event","_hoisted_21","_hoisted_22","_hoisted_23","ActionButton","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_createTextVNode","_unref","stamp","_hoisted_29","_hoisted_30","_hoisted_31","SkeletonList","_hoisted_32","_hoisted_33","SelectMenu","_hoisted_34","_hoisted_35","_hoisted_37","_hoisted_38","_hoisted_39","_hoisted_41","_b","e","_hoisted_43","_hoisted_44"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as o,r as _,i as x}from"./useDeployment-Bgo0U4-I.js";import{s as h}from"./useFormat-CKJxQp_i.js";import{_ as C}from"./SkeletonList.vue_vue_type_script_setup_true_lang-DiCGtn2a.js";import{d as T,c as a,a as e,u as t,x as m,w as p,g as r,f as l,e as b,_ as g,t as i,E as A,F as u,s as k,C as N,j as R,o as n}from"./index-jLPJ82ve.js";import"./useVars-VN9gYdR6.js";const $={class:"panel"},B={class:"phead"},z={key:1,class:"mute"},L={key:1,class:"mute"},S={key:2,class:"kvlist"},U={class:"k"},V={class:"v mono"},E={class:"v"},I={class:"mute",style:{"margin-left":"8px","font-size":"var(--t-sm)"}},j={key:0},q={class:"v"},D={class:"v mono"},F={key:1},H={class:"v"},P={key:0,class:"mute"},K={key:2},O={class:"k"},G={class:"v"},J={class:"mono"},M={class:"mute",style:{"font-size":"var(--t-sm)"}},Q={key:3},W={class:"v"},X={key:0,class:"mute"},Y={class:"v"},Z={class:"v"},ss={class:"v"},es={class:"v"},rs=T({__name:"OverviewTab",setup(ts){const w=N(()=>x.value?"a host singleton previews borrow — no axes, and down is guarded":"one tenant, created and destroyed constantly, expected to leave nothing behind");return(os,s)=>{var y,v,f;const c=R("RouterLink");return n(),a("section",$,[e("div",B,[s[2]||(s[2]=e("h2",{class:"section"},"Resolved spec",-1)),s[3]||(s[3]=e("span",{class:"grow"},null,-1)),t(o).detail?(n(),m(c,{key:0,to:`/submit?from=${encodeURIComponent(t(o).id)}`},{default:p(()=>[...s[0]||(s[0]=[l(" Duplicate ",-1)])]),_:1},8,["to"])):r("",!0),t(o).detail?(n(),a("span",z,"·")):r("",!0),t(o).detail?(n(),m(c,{key:2,to:`/submit/${encodeURIComponent(t(o).id)}`},{default:p(()=>[...s[1]||(s[1]=[l(" Edit spec & compose → ",-1)])]),_:1},8,["to"])):r("",!0)]),!t(o).detail&&!t(o).error?(n(),m(C,{key:0,rows:5})):t(o).detail?(n(),a("ul",S,[e("li",null,[e("span",U,[s[5]||(s[5]=l(" stack ",-1)),b(g,{label:"the difference between id and stack"},{default:p(()=>[...s[4]||(s[4]=[l(" The ",-1),e("b",null,"id",-1),l(" is what you named this deployment. The ",-1),e("b",null,"stack",-1),l(" is what its spec resolved to once the variables were filled in — the compose project name, the hostname label, and ",-1),e("code",null,"$STACK",-1),l(" inside every hook. ",-1)])]),_:1})]),e("span",V,i(t(o).detail.stack||"—"),1)]),e("li",null,[s[6]||(s[6]=e("span",{class:"k"},"kind",-1)),e("span",E,[e("span",{class:A(["badge",t(o).detail.kind])},i(t(o).detail.kind),3),e("span",I,i(w.value),1)])]),(y=t(_))!=null&&y.specName?(n(),a("li",j,[s[8]||(s[8]=e("span",{class:"k"},"spec",-1)),e("span",q,[e("b",null,i(t(_).specName),1),s[7]||(s[7]=e("span",{class:"mute",style:{"font-size":"var(--t-sm)"}}," — stored, and shared with any other deployment using it ",-1))])])):r("",!0),e("li",null,[s[9]||(s[9]=e("span",{class:"k"},"compose file",-1)),e("span",D,i(t(o).detail.compose?t(o).detail.compose.file:"none"),1)]),t(o).detail.compose?(n(),a("li",F,[s[10]||(s[10]=e("span",{class:"k"},"profiles",-1)),e("span",H,[(n(!0),a(u,null,k(t(o).detail.compose.profiles,d=>(n(),a("span",{key:d,class:"badge",style:{"margin-right":"4px"}},i(d),1))),128)),t(o).detail.compose.profiles.length?r("",!0):(n(),a("span",P," none — a bare up starts every service "))])])):r("",!0),(f=(v=t(o).detail.compose)==null?void 0:v.subdomains)!=null&&f.length?(n(),a("li",K,[e("span",O,[s[15]||(s[15]=l(" subdomains ",-1)),b(g,{label:"how wildcard subdomains work"},{default:p(()=>[s[14]||(s[14]=l(" Anything one label under these hosts reaches the same service the bare host does. An exact hostname you route yourself always wins over the wildcard. ",-1)),t(o).detail.compose.subdomains.some(d=>d.depth==="any")?(n(),a(u,{key:0},[s[11]||(s[11]=l(" A route marked ",-1)),s[12]||(s[12]=e("b",null,"any depth",-1)),s[13]||(s[13]=l(" works over HTTP only — no TLS certificate can cover more than one label. ",-1))],64)):r("",!0)]),_:1})]),e("span",G,[(n(!0),a(u,null,k(t(o).detail.compose.subdomains,d=>(n(),a("div",{key:d.profile},[e("span",J,"*."+i(d.host),1),e("span",M,[l(" → "+i(d.profile),1),d.depth==="any"?(n(),a(u,{key:0},[l(" · any depth, HTTP only")],64)):r("",!0)])]))),128))])])):r("",!0),t(o).detail.compose?(n(),a("li",Q,[s[16]||(s[16]=e("span",{class:"k"},"overlays",-1)),e("span",W,[(n(!0),a(u,null,k(t(o).detail.compose.overlays,d=>(n(),a("span",{key:d,class:"mono",style:{"margin-right":"8px"}},i(d),1))),128)),t(o).detail.compose.overlays.length?r("",!0):(n(),a("span",X,"none"))])])):r("",!0),e("li",null,[s[18]||(s[18]=e("span",{class:"k"},"axes",-1)),e("span",Y,[l(i(t(o).detail.axes.length)+" ",1),t(o).detail.axes.length?(n(),m(c,{key:0,to:`/deployments/${encodeURIComponent(t(o).id)}/axes`},{default:p(()=>[...s[17]||(s[17]=[l(" view → ",-1)])]),_:1},8,["to"])):r("",!0)])]),e("li",null,[s[19]||(s[19]=e("span",{class:"k"},"requires",-1)),e("span",Z,i(t(o).detail.requires.length),1)]),e("li",null,[s[20]||(s[20]=e("span",{class:"k"},"created",-1)),e("span",ss,i(t(h)(t(o).detail.createdAt)),1)]),e("li",null,[s[21]||(s[21]=e("span",{class:"k"},"updated",-1)),e("span",es,i(t(h)(t(o).detail.updatedAt)),1)])])):(n(),a("p",L,"Unavailable until the spec resolves."))])}}});export{rs as default};
|
|
2
|
-
//# sourceMappingURL=OverviewTab-B2aCu4e0.js.map
|