@samyx/preview-stacks-ui 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/assets/{ActionButton.vue_vue_type_script_setup_true_lang-eyfGLbs3.js → ActionButton.vue_vue_type_script_setup_true_lang-Dx6dvDgY.js} +2 -2
  2. package/dist/assets/{ActionButton.vue_vue_type_script_setup_true_lang-eyfGLbs3.js.map → ActionButton.vue_vue_type_script_setup_true_lang-Dx6dvDgY.js.map} +1 -1
  3. package/dist/assets/AxesTab-Bq4L7G1s.js +2 -0
  4. package/dist/assets/AxesTab-Bq4L7G1s.js.map +1 -0
  5. package/dist/assets/ConfigTab-Ltx3Zrdd.js +2 -0
  6. package/dist/assets/ConfigTab-Ltx3Zrdd.js.map +1 -0
  7. package/dist/assets/DangerTab-UVytlrHh.js +2 -0
  8. package/dist/assets/DangerTab-UVytlrHh.js.map +1 -0
  9. package/dist/assets/DashboardView-CWqm9Ut_.js +2 -0
  10. package/dist/assets/DashboardView-CWqm9Ut_.js.map +1 -0
  11. package/dist/assets/DeploymentDetailView-BoPv5KCl.js +2 -0
  12. package/dist/assets/DeploymentDetailView-BoPv5KCl.js.map +1 -0
  13. package/dist/assets/DeploymentsView-welgmlBc.js +2 -0
  14. package/dist/assets/DeploymentsView-welgmlBc.js.map +1 -0
  15. package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js → ErrorNote.vue_vue_type_script_setup_true_lang-BNRC57JZ.js} +2 -2
  16. package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js.map → ErrorNote.vue_vue_type_script_setup_true_lang-BNRC57JZ.js.map} +1 -1
  17. package/dist/assets/JobDetailView-C4HDkGMQ.js +2 -0
  18. package/dist/assets/JobDetailView-C4HDkGMQ.js.map +1 -0
  19. package/dist/assets/JobsView-C0kqyEse.js +2 -0
  20. package/dist/assets/JobsView-C0kqyEse.js.map +1 -0
  21. package/dist/assets/LogsTab-DMm5SmAq.js +4 -0
  22. package/dist/assets/LogsTab-DMm5SmAq.js.map +1 -0
  23. package/dist/assets/{NotFoundView-3kRJpFYC.js → NotFoundView-BXzLxfj8.js} +2 -2
  24. package/dist/assets/{NotFoundView-3kRJpFYC.js.map → NotFoundView-BXzLxfj8.js.map} +1 -1
  25. package/dist/assets/OverviewTab-GXUj8rfn.js +2 -0
  26. package/dist/assets/OverviewTab-GXUj8rfn.js.map +1 -0
  27. package/dist/assets/{RequiresTab-DB0yEAmP.js → RequiresTab-BBbppgfW.js} +2 -2
  28. package/dist/assets/{RequiresTab-DB0yEAmP.js.map → RequiresTab-BBbppgfW.js.map} +1 -1
  29. package/dist/assets/SettingsView-6NfKAhvR.js +2 -0
  30. package/dist/assets/SettingsView-6NfKAhvR.js.map +1 -0
  31. package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js → SkeletonList.vue_vue_type_script_setup_true_lang-CafU21a8.js} +2 -2
  32. package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js.map → SkeletonList.vue_vue_type_script_setup_true_lang-CafU21a8.js.map} +1 -1
  33. package/dist/assets/{StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js → StateBadge.vue_vue_type_script_setup_true_lang-CNfZduv9.js} +2 -2
  34. package/dist/assets/{StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js.map → StateBadge.vue_vue_type_script_setup_true_lang-CNfZduv9.js.map} +1 -1
  35. package/dist/assets/SubmitView-D_QtP8ym.js +45 -0
  36. package/dist/assets/SubmitView-D_QtP8ym.js.map +1 -0
  37. package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js → VarEditor.vue_vue_type_script_setup_true_lang-LB0r2ViA.js} +2 -2
  38. package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js.map → VarEditor.vue_vue_type_script_setup_true_lang-LB0r2ViA.js.map} +1 -1
  39. package/dist/assets/index-DfBxQ-9i.css +1 -0
  40. package/dist/assets/index-DmWBBqbv.js +29 -0
  41. package/dist/assets/index-DmWBBqbv.js.map +1 -0
  42. package/dist/assets/{useDeployment-ByQUNe_X.js → useDeployment-Dt6UHfpB.js} +2 -2
  43. package/dist/assets/{useDeployment-ByQUNe_X.js.map → useDeployment-Dt6UHfpB.js.map} +1 -1
  44. package/dist/assets/useFormat-CKJxQp_i.js +2 -0
  45. package/dist/assets/useFormat-CKJxQp_i.js.map +1 -0
  46. package/dist/index.html +2 -2
  47. package/package.json +1 -1
  48. package/dist/assets/AxesTab-BRrPVCft.js +0 -3
  49. package/dist/assets/AxesTab-BRrPVCft.js.map +0 -1
  50. package/dist/assets/ConfigTab-BOCoIOPW.js +0 -2
  51. package/dist/assets/ConfigTab-BOCoIOPW.js.map +0 -1
  52. package/dist/assets/DangerTab-dMsIXX7F.js +0 -2
  53. package/dist/assets/DangerTab-dMsIXX7F.js.map +0 -1
  54. package/dist/assets/DashboardView-D27l8yqd.js +0 -2
  55. package/dist/assets/DashboardView-D27l8yqd.js.map +0 -1
  56. package/dist/assets/DeploymentDetailView-BjELdE9f.js +0 -2
  57. package/dist/assets/DeploymentDetailView-BjELdE9f.js.map +0 -1
  58. package/dist/assets/DeploymentsView-02Cg08fs.js +0 -2
  59. package/dist/assets/DeploymentsView-02Cg08fs.js.map +0 -1
  60. package/dist/assets/JobDetailView-Ck89gkKn.js +0 -2
  61. package/dist/assets/JobDetailView-Ck89gkKn.js.map +0 -1
  62. package/dist/assets/JobsView-0OjmomhQ.js +0 -2
  63. package/dist/assets/JobsView-0OjmomhQ.js.map +0 -1
  64. package/dist/assets/LogsTab-Bb2E4F-n.js +0 -4
  65. package/dist/assets/LogsTab-Bb2E4F-n.js.map +0 -1
  66. package/dist/assets/OverviewTab-BrElTGDU.js +0 -2
  67. package/dist/assets/OverviewTab-BrElTGDU.js.map +0 -1
  68. package/dist/assets/SettingsView-CZvKTBtQ.js +0 -2
  69. package/dist/assets/SettingsView-CZvKTBtQ.js.map +0 -1
  70. package/dist/assets/SubmitView-dblBZxuc.js +0 -45
  71. package/dist/assets/SubmitView-dblBZxuc.js.map +0 -1
  72. package/dist/assets/index--8-Mcu3f.js +0 -29
  73. package/dist/assets/index--8-Mcu3f.js.map +0 -1
  74. package/dist/assets/index-C_dmDOA0.css +0 -1
  75. package/dist/assets/useFormat-CNPlQbIu.js +0 -2
  76. package/dist/assets/useFormat-CNPlQbIu.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{L as n,m as i,k as d,p as l,G as p,M as c}from"./index--8-Mcu3f.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-ByQUNe_X.js.map
1
+ import{L as n,m as i,k as d,p as l,G as p,M as c}from"./index-DmWBBqbv.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-Dt6UHfpB.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDeployment-ByQUNe_X.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"}
1
+ {"version":3,"file":"useDeployment-Dt6UHfpB.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 e(t){return t?new Date(t).toLocaleString("sv"):"—"}function u(t,o){if(!t)return"—";const n=Math.max(0,Math.round(((o??Date.now())-t)/1e3)),r=n<60?`${n}s`:`${Math.floor(n/60)}m${String(n%60).padStart(2,"0")}s`;return o?r:`${r}…`}function s(t){if(!t)return"—";const o=Math.max(0,Math.round((Date.now()-t)/1e3));if(o<45)return"just now";const n=Math.round(o/60);if(n<60)return`${n}m ago`;const r=Math.round(n/60);return r<24?`${r}h ago`:`${Math.round(r/24)}d ago`}const a={up:"Deploy",down:"Tear down",verify:"Verify"};function f(t){return t?a[t]??t:"—"}export{f as a,s as b,e as s,u as t};
2
+ //# sourceMappingURL=useFormat-CKJxQp_i.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFormat-CKJxQp_i.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"],"names":["stamp","ms","took","startedAt","endedAt","s","text","ago","m","h","ACTION_LABELS","actionLabel","action"],"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"}
package/dist/index.html CHANGED
@@ -24,8 +24,8 @@
24
24
  /* private mode: fall through to the OS preference */
25
25
  }
26
26
  </script>
27
- <script type="module" crossorigin src="/assets/index--8-Mcu3f.js"></script>
28
- <link rel="stylesheet" crossorigin href="/assets/index-C_dmDOA0.css">
27
+ <script type="module" crossorigin src="/assets/index-DmWBBqbv.js"></script>
28
+ <link rel="stylesheet" crossorigin href="/assets/index-DfBxQ-9i.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samyx/preview-stacks-ui",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "private": false,
5
5
  "description": "The advanced web UI for preview-stacks — an opt-in SPA that talks to the pstack HTTP API.",
6
6
  "type": "module",
@@ -1,3 +0,0 @@
1
- import{d,i as m,u as c}from"./useDeployment-ByQUNe_X.js";import{d as _,c as t,a as e,e as a,b as n,F as p,r as h,f as u,t as r,E as v,o,q as y,v as f}from"./index--8-Mcu3f.js";import"./useVars-VN9gYdR6.js";const k={class:"panel"},x={key:0,class:"mute"},w={class:"cards"},A={class:"stagger"},N={class:"name","data-label":"axis"},S={"data-label":"hooks"},C={"data-label":"provable"},V={key:0,class:"badge ok"},B={key:1,class:"badge warn",title:"verify cannot prove this axis was cleaned up — this is not a pass"},T={key:0},z={colspan:"3",class:"mute"},E={key:0},F={key:0,class:"banner warn"},H={class:"mono"},D=_({__name:"AxesTab",setup(I){const g=["up","assert_live","down","assert_gone"];return(L,s)=>(o(),t("section",k,[s[5]||(s[5]=e("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},[a(" Axes "),e("span",{class:"mute"},"(provisioned in order, destroyed in reverse)")],-1)),n(d).detail?(o(),t(p,{key:1},[s[4]||(s[4]=e("p",{class:"hint",style:{margin:"0 0 var(--s3)"}},[e("code",null,"up"),a(" provision · "),e("code",null,"assert_live"),a(" it exists · "),e("code",null,"down"),a(" destroy · "),e("code",null,"assert_gone"),a(" proven gone. Hook "),e("em",null,"bodies"),a(" are never sent to this page — a hook is a shell string that routinely carries a token inline. ")],-1)),e("table",w,[s[2]||(s[2]=e("thead",null,[e("tr",null,[e("th",null,"axis"),e("th",null,"hooks"),e("th",null,"teardown provable?")])],-1)),e("tbody",A,[(o(!0),t(p,null,h(n(d).detail.axes,(l,b)=>(o(),t("tr",{key:l.name,style:y({"--i":b})},[e("td",N,r(l.name),1),e("td",S,[(o(),t(p,null,h(g,i=>e("span",{key:i,class:f(["badge mono",{off:!l.hooks.includes(i)}]),style:{"margin-right":"4px"}},r(i),3)),64))]),e("td",C,[l.verifiable?(o(),t("span",V,"assert_gone")):(o(),t("span",B,"unverifiable"))])],4))),128)),n(d).detail.axes.length?u("",!0):(o(),t("tr",T,[e("td",z,[s[0]||(s[0]=a(" No axes",-1)),n(m)?(o(),t("span",E," — a shared singleton has nothing to isolate and nothing to prove gone")):u("",!0),s[1]||(s[1]=a(". ",-1))])]))])]),n(c).length?(o(),t("div",F,[e("b",null,r(n(c).length)+" axis/axes cannot be verified:",1),e("span",H,r(n(c).join(", ")),1),s[3]||(s[3]=v(`. <p> They define no <code>assert_gone</code>, so a green <code>verify</code> is <em>silence</em> about them, not proof they are gone. Add an <code>assert_gone</code> that <b>fails closed</b> — probe that the check itself can work, then assert absence: </p><pre class="raw" style="color:var(--fg-dim);">&lt;probe-is-usable&gt; || exit 1
2
- ! &lt;probe-for-this-resource&gt;</pre><p> A bare <code>! &lt;probe&gt;</code> exits 0 whenever the probe itself fails — a missing CLI, an expired token — which turns “I could not tell” into “it is gone”. </p>`,4))])):u("",!0)],64)):(o(),t("p",x,"Unavailable until the spec resolves."))]))}});export{D as default};
3
- //# sourceMappingURL=AxesTab-BRrPVCft.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AxesTab-BRrPVCft.js","sources":["../../src/views/tabs/AxesTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Axes: the hooks each declares, and whether teardown can be PROVEN.\n *\n * `verifiable: false` means the axis defines no `assert_gone`, so `verify` cannot prove it was\n * cleaned up. That is a THIRD state — neither pass nor fail — and it must never be coloured green:\n * a green verify on an unverifiable axis is exactly the false confidence this tool exists to\n * remove.\n */\nimport { dep, isShared, unverifiableAxes } from '../../composables/useDeployment';\nimport type { HookName } from '../../api/types';\n\n/** In the order `up` and `verify` actually use them. */\nconst HOOKS: HookName[] = ['up', 'assert_live', 'down', 'assert_gone'];\n</script>\n\n<template>\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">\n Axes <span class=\"mute\">(provisioned in order, destroyed in reverse)</span>\n </h2>\n\n <p v-if=\"!dep.detail\" class=\"mute\">Unavailable until the spec resolves.</p>\n <template v-else>\n <p class=\"hint\" style=\"margin: 0 0 var(--s3)\">\n <code>up</code> provision · <code>assert_live</code> it exists · <code>down</code> destroy ·\n <code>assert_gone</code> proven gone. Hook <em>bodies</em> are never sent to this page — a\n hook is a shell string that routinely carries a token inline.\n </p>\n\n <table class=\"cards\">\n <thead>\n <tr>\n <th>axis</th>\n <th>hooks</th>\n <th>teardown provable?</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(a, i) in dep.detail.axes\" :key=\"a.name\" :style=\"{ '--i': i }\">\n <td class=\"name\" data-label=\"axis\">{{ a.name }}</td>\n <td data-label=\"hooks\">\n <span\n v-for=\"h in HOOKS\"\n :key=\"h\"\n class=\"badge mono\"\n :class=\"{ off: !a.hooks.includes(h) }\"\n style=\"margin-right: 4px\"\n >{{ h }}</span\n >\n </td>\n <td data-label=\"provable\">\n <span v-if=\"a.verifiable\" class=\"badge ok\">assert_gone</span>\n <span\n v-else\n class=\"badge warn\"\n title=\"verify cannot prove this axis was cleaned up — this is not a pass\"\n >unverifiable</span\n >\n </td>\n </tr>\n <tr v-if=\"!dep.detail.axes.length\">\n <td colspan=\"3\" class=\"mute\">\n No axes<span v-if=\"isShared\">\n — a shared singleton has nothing to isolate and nothing to prove gone</span\n >.\n </td>\n </tr>\n </tbody>\n </table>\n\n <div v-if=\"unverifiableAxes.length\" class=\"banner warn\">\n <b>{{ unverifiableAxes.length }} axis/axes cannot be verified:</b>\n <span class=\"mono\">{{ unverifiableAxes.join(', ') }}</span\n >.\n <p>\n They define no <code>assert_gone</code>, so a green <code>verify</code> is\n <em>silence</em> about them, not proof they are gone. Add an <code>assert_gone</code> that\n <b>fails closed</b> — probe that the check itself can work, then assert absence:\n </p>\n <pre\n class=\"raw\"\n style=\"color: var(--fg-dim)\"\n >&lt;probe-is-usable&gt; || exit 1\n! &lt;probe-for-this-resource&gt;</pre>\n <p>\n A bare <code>! &lt;probe&gt;</code> exits 0 whenever the probe itself fails — a missing\n CLI, an expired token — which turns “I could not tell” into “it is gone”.\n </p>\n </div>\n </template>\n </section>\n</template>\n"],"names":["HOOKS","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_unref","dep","_Fragment","_hoisted_3","_hoisted_4","_renderList","a","i","_hoisted_5","_toDisplayString","_hoisted_6","h","_normalizeClass","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","isShared","unverifiableAxes","_hoisted_13","_hoisted_14","_hoisted_2"],"mappings":"8nBAaA,MAAMA,EAAoB,CAAC,KAAM,cAAe,OAAQ,aAAa,gBAInEC,EAAA,EAAAC,EA0EU,UA1EVC,EA0EU,aAzERC,EAEK,KAAA,CAFD,MAAM,UAAU,MAAA,CAAA,gBAAA,WAAA,CAAA,KAAiC,QAC9C,EAAAA,EAAsE,OAAA,CAAhE,MAAM,MAAA,EAAO,8CAA4C,CAAA,OAG5DC,EAAAC,CAAA,EAAI,YACdJ,EAmEWK,EAAA,CAAA,IAAA,GAAA,aAlETH,EAII,IAAA,CAJD,MAAM,OAAO,MAAA,CAAA,OAAA,eAAA,CAAA,GACdA,EAAe,YAAT,IAAE,IAAO,eAAa,EAAAA,EAAwB,YAAlB,aAAW,IAAO,eAAa,EAAAA,EAAiB,YAAX,MAAI,IAAO,aAClF,EAAAA,EAAwB,YAAlB,aAAW,IAAO,qBAAmB,EAAAA,EAAe,UAAX,QAAM,IAAK,iGAE5D,CAAA,OAEAA,EAuCQ,QAvCRI,EAuCQ,aAtCNJ,EAMQ,QAAA,KAAA,CALNA,EAIK,KAAA,KAAA,CAHHA,EAAa,UAAT,MAAI,EACRA,EAAc,UAAV,OAAK,EACTA,EAA2B,UAAvB,oBAAkB,CAAA,SAG1BA,EA8BQ,QA9BRK,EA8BQ,QA7BNP,EAqBKK,EAAA,KAAAG,EArBgBL,KAAI,OAAO,KAAI,CAAxBM,EAAGC,SAAfV,EAqBK,KAAA,CArBkC,IAAKS,EAAE,KAAO,eAAgBC,EAAC,CAAA,GACpER,EAAoD,KAApDS,EAAoDC,EAAdH,EAAE,IAAI,EAAA,CAAA,EAC5CP,EASK,KATLW,EASK,MARHb,EAOCK,EAAA,KAAAG,EANaV,EAALgB,GADTZ,EAOC,OAAA,CALE,IAAKY,EACN,MAAKC,EAAA,CAAC,aAAY,CAAA,IAAA,CACFN,EAAE,MAAM,SAASK,CAAC,CAAA,CAAA,CAAA,EAClC,MAAA,CAAA,eAAA,KAAA,CAAA,IACIA,CAAC,EAAA,CAAA,UAGTZ,EAQK,KARLc,EAQK,CAPSP,EAAE,gBAAdT,EAA6D,OAA7DiB,EAA2C,aAAW,QACtDjB,EAKC,OALDkB,EAIG,cAAY,EAAA,eAIRf,EAAAC,CAAA,EAAI,OAAO,KAAK,qBAA3BJ,EAMK,KAAAmB,EAAA,CALHjB,EAIK,KAJLkB,EAIK,eAJwB,WACpB,EAAA,GAAYjB,EAAAkB,CAAA,GAAZtB,EAAA,EAAAC,EAEN,SAF4B,wEAC0C,0BACtE,KACH,EAAA,EAAA,UAKKG,EAAAmB,CAAA,EAAiB,QAA5BvB,IAAAC,EAkBM,MAlBNuB,EAkBM,CAjBJrB,EAAkE,IAAA,KAAAU,EAA5DT,EAAAmB,CAAA,EAAiB,MAAM,EAAG,iCAA8B,CAAA,EAC9DpB,EACC,OADDsB,EACCZ,EADqBT,EAAAmB,CAAA,EAAiB,KAAI,IAAA,CAAA,EAAA,CAAA;8OAnD/CtB,EAA2E,IAA3EyB,EAAmC,sCAAoC"}
@@ -1,2 +0,0 @@
1
- import{p as f,d as i,a as y,v as k,r as _}from"./useDeployment-ByQUNe_X.js";import{c as C}from"./useVars-VN9gYdR6.js";import{_ as V}from"./VarEditor.vue_vue_type_script_setup_true_lang-DYi-LMaK.js";import{d as T,c as o,a as e,E as R,h as q,b as n,t as r,e as t,F as m,r as b,f as d,m as h,o as l,v as g}from"./index--8-Mcu3f.js";const N={class:"panel"},S={class:"row",style:{"margin-top":"var(--s3)"}},x={class:"mono mute"},B={key:0,class:"panel"},I={class:"kvlist",style:{"margin-top":"var(--s3)"}},L={class:"k mono"},j={class:"v mono"},P={key:0,class:"banner warn"},z={class:"mono"},A={class:"panel"},D={key:0,class:"mute"},E={class:"cards"},F={class:"name","data-label":"key"},U={"data-label":"visibility"},$={class:"dim mono","data-label":"length"},O={key:0,class:"badge warn",style:{"margin-left":"6px"}},Q={key:0},M=T({__name:"ConfigTab",setup(Y){const c=h(()=>{var u;return(u=_.value)==null?void 0:u.vars}),v=h(()=>Object.entries(c.value??{})),p=h(()=>C(i.vars,c.value));return(u,s)=>(l(),o("div",null,[e("section",N,[s[2]||(s[2]=R('<h2 class="section" style="margin-bottom:var(--s3);">Request variables</h2><div class="banner warn"><b>The same variables must accompany <code>down</code> as accompanied <code>up</code>.</b><p> These are sent as query parameters on <em>every</em> call that resolves this deployment, so <code>up</code> with <code>PR=7</code> and <code>down</code> without it target two different stacks — and the teardown silently misses everything the deploy created. They are kept in this browser (<code>localStorage</code>, keyed by deployment id) so they stay matched <em>here</em>; a different browser, a CI job, or the CLI must pass the same ones itself. </p></div>',2)),q(V,{modelValue:n(i).vars,"onUpdate:modelValue":s[0]||(s[0]=a=>n(i).vars=a),onChange:n(f)},null,8,["modelValue","onChange"]),e("div",S,[e("button",{class:"primary",onClick:s[1]||(s[1]=(...a)=>n(y)&&n(y)(...a))},"Apply & reload"),e("span",x,r(n(k)||"no variables"),1)]),s[3]||(s[3]=e("p",{class:"hint"},[t(" The "),e("code",null,"env"),t(" table below lists what the spec "),e("em",null,"declares"),t(", which is not the same as what it "),e("em",null,"needs"),t(": "),e("code",null,"stack: pr-${PR}"),t(" consumes "),e("code",null,"PR"),t(" without declaring it. So this editor is free-form, and the 400 message on a resolve failure is the authoritative list of what is missing. ")],-1))]),v.value.length?(l(),o("section",B,[s[7]||(s[7]=e("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Stored on the server",-1)),s[8]||(s[8]=e("p",{class:"dim",style:{"font-size":"var(--t-sm)"}},[t(" This deployment carries its own variables in the registry, so "),e("code",null,"up"),t(" and "),e("code",null,"down"),t(" resolve the same stack even with no query parameters at all. Request variables still win, so anything you set above "),e("em",null,"overrides"),t(" these. ")],-1)),e("ul",I,[(l(!0),o(m,null,b(v.value,([a,w])=>(l(),o("li",{key:a},[e("span",L,r(a),1),e("span",j,r(w),1)]))),128))]),p.value.length?(l(),o("div",P,[s[4]||(s[4]=e("b",null,"Your request variables disagree with the stored ones:",-1)),e("span",z,r(p.value.join(", ")),1),s[5]||(s[5]=t(". ",-1)),s[6]||(s[6]=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",A,[s[12]||(s[12]=e("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},[t(" Declared env "),e("span",{class:"mute"},"(after interpolation)")],-1)),n(i).detail?(l(),o(m,{key:1},[e("table",E,[s[10]||(s[10]=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),o(m,null,b(n(i).detail.env,a=>(l(),o("tr",{key:a.key},[e("td",F,r(a.key),1),e("td",{class:g(["name",{masked:a.visibility==="masked"}]),"data-label":"value"},r(a.value),3),e("td",U,[e("span",{class:g(["badge",a.visibility==="masked"?"warn":""])},r(a.visibility),3)]),e("td",$,[t(r(a.length)+" chars ",1),a.length===0?(l(),o("span",O,"never set")):d("",!0)])]))),128)),n(i).detail.env.length?d("",!0):(l(),o("tr",Q,[...s[9]||(s[9]=[e("td",{colspan:"4",class:"mute"},[t("This spec declares no "),e("code",null,"env:"),t(" block.")],-1)])]))])]),s[11]||(s[11]=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(),o("p",D,"Unavailable until the spec resolves."))])]))}});export{M as default};
2
- //# sourceMappingURL=ConfigTab-BOCoIOPW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ConfigTab-BOCoIOPW.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, varsQuery } from '../../composables/useDeployment';\nimport { conflictingVars } from '../../composables/useVars';\nimport VarEditor from '../../components/VarEditor.vue';\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 <div class=\"banner warn\">\n <b>The same variables must accompany <code>down</code> as accompanied <code>up</code>.</b>\n <p>\n These are sent as query parameters on <em>every</em> call that resolves this deployment,\n so <code>up</code> with <code>PR=7</code> and <code>down</code> without it target two\n different stacks — and the teardown silently misses everything the deploy created. They\n are kept in this browser (<code>localStorage</code>, keyed by deployment id) so they stay\n matched <em>here</em>; a different browser, a CI job, or the CLI must pass the same ones\n itself.\n </p>\n </div>\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 &amp; reload</button>\n <span class=\"mono mute\">{{ varsQuery || 'no variables' }}</span>\n </div>\n\n <p class=\"hint\">\n The <code>env</code> table below lists what the spec <em>declares</em>, which is not the\n same as what it <em>needs</em>: <code>stack: pr-${PR}</code> consumes <code>PR</code>\n without declaring it. So this editor is free-form, and the 400 message on a resolve failure\n is the authoritative list of what is missing.\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 This deployment carries its own variables in the registry, so <code>up</code> and\n <code>down</code> resolve the same stack even with no query parameters at all. Request\n variables still win, so anything you set above <em>overrides</em> 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 mono\">{{ k }}</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 <span class=\"mono\">{{ clashes.join(', ') }}</span\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 mono\" 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":["storedVars","computed","_a","row","storedList","clashes","conflictingVars","dep","_createElementBlock","_createElementVNode","_hoisted_1","_createVNode","VarEditor","_unref","_cache","$event","persistVars","_hoisted_2","applyVars","args","_hoisted_3","_toDisplayString","varsQuery","_openBlock","_hoisted_4","_hoisted_5","_Fragment","_renderList","k","v","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_12","e","_hoisted_13","_normalizeClass","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_11"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{d as H,c as d,v as W,a as t,t as c,e as o,h as p,w as m,n as J,o as i,b as r,F as B,f as y,x as U,A as O,y as G,g as K,E as X,j as b,m as A,k as N,H as D,I as M,p as z,J as Y,C as Z,s as _,K as ee}from"./index--8-Mcu3f.js";import{d as l,i as $,v as F}from"./useDeployment-ByQUNe_X.js";import{_ as j}from"./ActionButton.vue_vue_type_script_setup_true_lang-eyfGLbs3.js";import{_ as te}from"./ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js";import"./useVars-VN9gYdR6.js";const oe={class:"raw",style:{color:"var(--fg-dim)","max-height":"200px"}},ne={key:0},se={key:1},le={key:2},ie={class:"mono"},ae={key:3},de={key:4},re=H({__name:"ConflictNote",props:{conflict:{},jobId:{}},setup(v){return(T,n)=>{const f=J("RouterLink");return i(),d("div",{class:W(["banner",v.conflict.kind==="shared"?"leaked":"info"]),role:"alert"},[n[5]||(n[5]=t("b",null,"409 — refused, and nothing was started.",-1)),t("pre",oe,c(v.conflict.text),1),v.conflict.kind==="shared"?(i(),d("p",ne,[...n[0]||(n[0]=[o(" Type the stack name in the confirmation field above and press ",-1),t("code",null,"down",-1),o(" again. ",-1)])])):v.conflict.kind==="containers"?(i(),d("p",se,[...n[1]||(n[1]=[o(" Run ",-1),t("code",null,"down",-1),o(" first. Forgetting the record now would orphan those containers beyond the control plane's view: nothing left would know their stack name, their axes, or how to tear them down. ",-1)])])):v.conflict.kind==="referenced"?(i(),d("p",le,[n[2]||(n[2]=o(" These deployments still point at this spec: ",-1)),t("span",ie,c(v.conflict.deployments.join(", ")),1),n[3]||(n[3]=o(". Deleting it would leave them unresolvable — and a deployment that cannot be resolved can never be torn down. ",-1))])):v.jobId?(i(),d("p",ae,[p(f,{to:`/jobs/${encodeURIComponent(v.jobId)}`},{default:m(()=>[...n[4]||(n[4]=[o("Follow the running job →",-1)])]),_:1},8,["to"])])):(i(),d("p",de,"Nothing was retried. Wait for the lock to clear, then try again."))],2)}}}),ue={class:"panel"},ve={key:0,class:"mute"},fe={key:0,class:"banner warn"},pe={class:"row"},me={key:1,class:"hint"},ye={class:"mono"},be={class:"hint"},ce={class:"mono"},ke={class:"row",style:{"margin-top":"var(--s3)"}},we={class:"check"},ge={key:2,class:"hint"},he={key:3,class:"banner leaked"},$e={class:"field",style:{"margin-top":"var(--s3)","max-width":"380px"}},Ce=["for"],xe={class:"mono"},je=["id","placeholder"],Te={key:0,class:"mono"},Ie={class:"panel"},Re={key:0,class:"mute"},Ee={class:"row",style:{"margin-top":"var(--s3)"}},Ue={class:"check"},Se=H({__name:"DangerTab",setup(v){const T=Z(),n=b(""),f=b(""),k=b(null),I=b(""),C=b(!0),w=b(""),g=b(!1),x=A(()=>{var s;return!!((s=l.detail)!=null&&s.stack)&&w.value===l.detail.stack}),P=A(()=>{var s;return((s=_.health)==null?void 0:s.authEnforced)===!0&&!ee.token}),h=A(()=>{var s;return((s=l.detail)==null?void 0:s.busy)===!0});function R(s){if(!l.detail)return"The spec has not resolved — the server cannot compute the stack name.";if(h.value)return`A job is already in flight for ${l.detail.stack}.`;if(s==="down"&&$.value&&!x.value)return`Type the stack name "${l.detail.stack}" below to confirm a shared teardown.`}async function E(s){if(!l.detail)return;n.value=s,f.value="",k.value=null;const e=s==="down"?{verify:C.value,force:$.value&&x.value}:{},u=await N.post(`/api/deployments/${encodeURIComponent(l.id)}/${s}${F.value}`,e);if(n.value="",u.status===202){const a=u.body.job;if(a!=null&&a.id){D("info",`${s} started on ${a.stack}`,{to:`/jobs/${encodeURIComponent(a.id)}`,toLabel:"Follow"}),M(),T.push(`/jobs/${encodeURIComponent(a.id)}`);return}f.value="202 accepted, but the response carried no job id — nothing to follow.";return}if(u.status===409)return L(u.body);f.value=z(u,`POST ${s}`),D("error",`${s} was refused.`)}async function Q(){if(!g.value)return;n.value="forget",f.value="",k.value=null;const s=await N.del(`/api/deployments/${encodeURIComponent(l.id)}${F.value}`);if(n.value="",s.ok){D("ok",`Forgot ${l.id}. Nothing was torn down.`),M(),T.push("/deployments");return}if(s.status===409)return L(s.body);f.value=z(s,"DELETE deployment")}async function L(s){var V;const e=Y(s);k.value=e,I.value="",e.kind==="shared"&&(w.value="");const u=e.stack||((V=l.detail)==null?void 0:V.stack);if(e.kind!=="other"||!u)return;const S=((await N.get("/api/jobs")).body.jobs??[]).find(q=>q.state==="running"&&q.stack===u);S&&(I.value=S.id)}return(s,e)=>{const u=J("RouterLink");return i(),d("div",null,[t("section",ue,[e[41]||(e[41]=t("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Lifecycle",-1)),r(l).detail?(i(),d(B,{key:1},[P.value?(i(),d("div",fe,[e[16]||(e[16]=t("b",null,"No token set.",-1)),t("p",null,[e[10]||(e[10]=o(" Every action below is a mutating request and will be refused with ",-1)),e[11]||(e[11]=t("code",null,"401",-1)),e[12]||(e[12]=o(". Add ",-1)),e[13]||(e[13]=t("code",null,"PSTACK_TOKEN",-1)),e[14]||(e[14]=o(" under ",-1)),p(u,{to:"/settings"},{default:m(()=>[...e[9]||(e[9]=[o("Settings",-1)])]),_:1}),e[15]||(e[15]=o(". ",-1))])])):y("",!0),t("div",pe,[p(j,{variant:"primary",pending:n.value==="up",disabled:!!n.value||h.value,title:R("up"),onClick:e[0]||(e[0]=a=>E("up"))},{default:m(()=>[...e[17]||(e[17]=[o(" up ",-1)])]),_:1},8,["pending","disabled","title"]),p(j,{pending:n.value==="verify",disabled:!!n.value||h.value,title:R("verify"),onClick:e[1]||(e[1]=a=>E("verify"))},{default:m(()=>[...e[18]||(e[18]=[o(" verify ",-1)])]),_:1},8,["pending","disabled","title"]),p(j,{variant:"danger",pending:n.value==="down",disabled:!!n.value||h.value||r($)&&!x.value,title:R("down"),onClick:e[2]||(e[2]=a=>E("down"))},{default:m(()=>[o(c(r($)?"down (force)":"down"),1)]),_:1},8,["pending","disabled","title"]),e[20]||(e[20]=t("span",{class:"grow"},null,-1)),p(u,{to:`/submit/${encodeURIComponent(r(l).id)}`},{default:m(()=>[...e[19]||(e[19]=[o("replace spec →",-1)])]),_:1},8,["to"])]),h.value?(i(),d("p",me,[e[21]||(e[21]=o(" A job is in flight for ",-1)),t("b",ye,c(r(l).detail.stack),1),e[22]||(e[22]=o(". One job per stack, deliberately not queued — a ",-1)),e[23]||(e[23]=t("code",null,"down",-1)),e[24]||(e[24]=o(" racing an ",-1)),e[25]||(e[25]=t("code",null,"up",-1)),e[26]||(e[26]=o(" over the same database branch is corruption, not contention. ",-1))])):y("",!0),t("p",be,[e[28]||(e[28]=o(" Sent with ",-1)),t("span",ce,c(r(F)||"no request variables"),1),e[29]||(e[29]=o(". ",-1)),p(u,{to:`/deployments/${encodeURIComponent(r(l).id)}/config`},{default:m(()=>[...e[27]||(e[27]=[o("change",-1)])]),_:1},8,["to"])]),t("div",ke,[t("label",we,[U(t("input",{"onUpdate:modelValue":e[3]||(e[3]=a=>C.value=a),type:"checkbox"},null,512),[[O,C.value]]),e[30]||(e[30]=o(" run ",-1)),e[31]||(e[31]=t("code",null,"verify",-1)),e[32]||(e[32]=o(" after ",-1)),e[33]||(e[33]=t("code",null,"down",-1))])]),C.value?y("",!0):(i(),d("p",ge,[...e[34]||(e[34]=[o(" Teardown will not check for leaks. A teardown that silently half-worked is the exact failure this tool exists to catch — run ",-1),t("code",null,"verify",-1),o(" yourself afterwards. ",-1)])])),r($)?(i(),d("div",he,[e[39]||(e[39]=t("b",null,"SHARED — read this before tearing down.",-1)),e[40]||(e[40]=t("p",null,[t("code",null,"down"),o(" runs "),t("code",null,"docker compose down -v"),o(", and "),t("code",null,"-v"),o(" removes "),t("b",null,"volumes"),o(". On a shared deployment that destroys state every tenant depends on — the TLS certificate store, the shared database or queue, admin credentials. Nothing here recreates them: "),t("b",null,"every preview on this host breaks"),o(" until someone rebuilds the singleton by hand. It is the identical verb that is routine on a preview. ")],-1)),t("div",$e,[t("label",{for:`confirm-${r(l).id}`},[e[35]||(e[35]=o(" Type the stack name ",-1)),t("b",xe,c(r(l).detail.stack),1),e[36]||(e[36]=o(" to send ",-1)),e[37]||(e[37]=t("code",null,"force: true",-1))],8,Ce),U(t("input",{id:`confirm-${r(l).id}`,"onUpdate:modelValue":e[4]||(e[4]=a=>w.value=a),type:"text",placeholder:r(l).detail.stack,spellcheck:"false",autocomplete:"off"},null,8,je),[[G,w.value,void 0,{trim:!0}]])]),w.value&&!x.value?(i(),d("p",Te,[...e[38]||(e[38]=[o(" does not match — ",-1),t("code",null,"down",-1),o(" stays disabled ",-1)])])):y("",!0)])):y("",!0),f.value?(i(),K(te,{key:4,text:f.value,title:"The action was refused."},null,8,["text"])):y("",!0),k.value?(i(),K(re,{key:5,conflict:k.value,"job-id":I.value},null,8,["conflict","job-id"])):y("",!0)],64)):(i(),d("p",ve,[e[7]||(e[7]=o(" Actions are unavailable until the spec resolves — the server cannot act on a deployment whose stack name it cannot compute. Fix the variables under ",-1)),p(u,{to:`/deployments/${encodeURIComponent(r(l).id)}/config`},{default:m(()=>[...e[6]||(e[6]=[o(" Config & variables ",-1)])]),_:1},8,["to"]),e[8]||(e[8]=o("first. ",-1))]))]),t("section",Ie,[e[45]||(e[45]=t("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Forget this deployment",-1)),r(l).detail?(i(),d(B,{key:1},[e[44]||(e[44]=X('<p class="dim" style="font-size:var(--t-sm);"><code>DELETE</code> removes the <em>record</em> — the stored <code>spec.yml</code>, <code>compose.yml</code> and <code>meta.json</code>. <b>It never tears anything down.</b> The server refuses with <code>409</code> while any container for this stack still exists, and refuses just as firmly when docker did not answer: “cannot confirm” is not evidence of absence. </p><p class="hint"> After forgetting, this deployment disappears from the control plane. If a resource one of its axes created is still alive, nothing here knows how to remove it any more. </p>',2)),t("div",Ee,[t("label",Ue,[U(t("input",{"onUpdate:modelValue":e[5]||(e[5]=a=>g.value=a),type:"checkbox"},null,512),[[O,g.value]]),e[42]||(e[42]=o(" I have torn this down and want the record removed ",-1))]),p(j,{variant:"danger",pending:n.value==="forget",disabled:!g.value||!!n.value,title:g.value?void 0:"Confirm you have torn this down first.",onClick:Q},{default:m(()=>[...e[43]||(e[43]=[o(" Forget ",-1)])]),_:1},8,["pending","disabled","title"])])],64)):(i(),d("p",Re," Unavailable until the spec resolves — the server counts this stack's containers before it will forget the record, and it cannot name the stack without its variables. "))])])}}});export{Se as default};
2
- //# sourceMappingURL=DangerTab-dMsIXX7F.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DangerTab-dMsIXX7F.js","sources":["../../src/components/ConflictNote.vue","../../src/views/tabs/DangerTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A 409, explained.\n *\n * \"409\" on this API always means the same thing about consequences — **refused, and nothing was\n * started** — but four different things about the cause, and guessing wrong sends the operator\n * down the wrong path. The classification is done on payload SHAPE in `client.ts`, never on the\n * message text, and the server's own message is always printed verbatim underneath.\n *\n * Note what is deliberately NOT distinguished: \"a job is in flight\" and \"docker did not answer\"\n * are shape-identical, so this renders neither claim — it prints the message and offers a link to\n * a running job only when one demonstrably exists.\n */\nimport type { Conflict } from '../api/client';\n\ndefineProps<{ conflict: Conflict; jobId?: string }>();\n</script>\n\n<template>\n <div class=\"banner\" :class=\"conflict.kind === 'shared' ? 'leaked' : 'info'\" role=\"alert\">\n <b>409 — refused, and nothing was started.</b>\n <pre class=\"raw\" style=\"color: var(--fg-dim); max-height: 200px\">{{ conflict.text }}</pre>\n\n <p v-if=\"conflict.kind === 'shared'\">\n Type the stack name in the confirmation field above and press <code>down</code> again.\n </p>\n <p v-else-if=\"conflict.kind === 'containers'\">\n Run <code>down</code> first. Forgetting the record now would orphan those containers beyond\n the control plane's view: nothing left would know their stack name, their axes, or how to\n tear them down.\n </p>\n <p v-else-if=\"conflict.kind === 'referenced'\">\n These deployments still point at this spec:\n <span class=\"mono\">{{ conflict.deployments.join(', ') }}</span\n >. Deleting it would leave them unresolvable — and a deployment that cannot be resolved can\n never be torn down.\n </p>\n <p v-else-if=\"jobId\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(jobId)}`\">Follow the running job →</RouterLink>\n </p>\n <p v-else>Nothing was retried. Wait for the lock to clear, then try again.</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Lifecycle actions, and forgetting the record. Everything on this page destroys something.\n *\n * The two guards that matter:\n *\n * 1. TYPED CONFIRMATION FOR A SHARED `down`. A checkbox is one stray click; typing a name is a\n * decision. The typed string is the resolved STACK NAME — not the deployment id — because the\n * stack is the actual blast target, it is what the server's own 409 refusal names, and it is\n * what `compose down -v` will be pointed at. The server holds the authoritative guard\n * (`down()` refuses without `force`, and the API answers 409 before starting a job), so this\n * is defence in depth, not the only barrier.\n *\n * 2. FORGETTING IS NOT TEARING DOWN. `DELETE` removes the record and never touches a container.\n * The server refuses with 409 while any container exists — and refuses just as firmly when\n * docker did not answer, because \"cannot confirm\" is not evidence of absence.\n */\nimport { computed, ref } from 'vue';\nimport { useRouter } from 'vue-router';\nimport { api, classifyConflict, problem, type Conflict } from '../../api/client';\nimport type { ActionResponse, ConflictBody, JobsResponse } from '../../api/types';\nimport { dep, isShared, varsQuery } from '../../composables/useDeployment';\nimport { loadDeployments, state } from '../../composables/useControlPlane';\nimport { settings } from '../../composables/useSettings';\nimport { toast } from '../../composables/useToasts';\nimport ActionButton from '../../components/ActionButton.vue';\nimport ConflictNote from '../../components/ConflictNote.vue';\nimport ErrorNote from '../../components/ErrorNote.vue';\n\nconst router = useRouter();\n\nconst pending = ref<'' | 'up' | 'verify' | 'down' | 'forget'>('');\nconst actionError = ref('');\nconst conflict = ref<Conflict | null>(null);\nconst conflictJobId = ref('');\nconst downVerify = ref(true);\nconst forceTyped = ref('');\nconst forgetArmed = ref(false);\n\n/** Exact match against the resolved stack name. Nothing looser — no trim-insensitive, no prefix. */\nconst forceArmed = computed(\n () => !!dep.detail?.stack && forceTyped.value === dep.detail.stack,\n);\n\nconst tokenMissing = computed(() => state.health?.authEnforced === true && !settings.token);\nconst busy = computed(() => dep.detail?.busy === true);\n\n/** A disabled control must say why. This is the text that answers it. */\nfunction whyDisabled(action: 'up' | 'verify' | 'down'): string | undefined {\n if (!dep.detail) return 'The spec has not resolved — the server cannot compute the stack name.';\n if (busy.value) return `A job is already in flight for ${dep.detail.stack}.`;\n if (action === 'down' && isShared.value && !forceArmed.value) {\n return `Type the stack name \"${dep.detail.stack}\" below to confirm a shared teardown.`;\n }\n return undefined;\n}\n\nasync function act(action: 'up' | 'verify' | 'down'): Promise<void> {\n if (!dep.detail) return;\n pending.value = action;\n actionError.value = '';\n conflict.value = null;\n\n const body =\n action === 'down'\n ? // `force` is only ever true for a shared deployment whose stack name was typed out in full.\n { verify: downVerify.value, force: isShared.value && forceArmed.value }\n : {};\n\n const r = await api.post<ActionResponse & ConflictBody>(\n `/api/deployments/${encodeURIComponent(dep.id)}/${action}${varsQuery.value}`,\n body,\n );\n pending.value = '';\n\n if (r.status === 202) {\n const job = r.body.job;\n if (job?.id) {\n toast('info', `${action} started on ${job.stack}`, {\n to: `/jobs/${encodeURIComponent(job.id)}`,\n toLabel: 'Follow',\n });\n void loadDeployments();\n void router.push(`/jobs/${encodeURIComponent(job.id)}`);\n return;\n }\n actionError.value = '202 accepted, but the response carried no job id — nothing to follow.';\n return;\n }\n if (r.status === 409) return onConflict(r.body);\n\n actionError.value = problem(r, `POST ${action}`);\n toast('error', `${action} was refused.`);\n}\n\nasync function forget(): Promise<void> {\n if (!forgetArmed.value) return;\n pending.value = 'forget';\n actionError.value = '';\n conflict.value = null;\n\n const r = await api.del<ConflictBody>(\n `/api/deployments/${encodeURIComponent(dep.id)}${varsQuery.value}`,\n );\n pending.value = '';\n\n if (r.ok) {\n toast('ok', `Forgot ${dep.id}. Nothing was torn down.`);\n void loadDeployments();\n void router.push('/deployments');\n return;\n }\n if (r.status === 409) return onConflict(r.body);\n actionError.value = problem(r, 'DELETE deployment');\n}\n\n/**\n * Classify on payload SHAPE (in `client.ts`), never on message text — then look for a running job\n * ONLY to offer a link. \"A job is in flight\" and \"docker did not answer\" are shape-identical, so\n * neither is claimed; the server's message is printed verbatim either way. Never retry.\n */\nasync function onConflict(body: ConflictBody): Promise<void> {\n const c = classifyConflict(body);\n conflict.value = c;\n conflictJobId.value = '';\n if (c.kind === 'shared') forceTyped.value = '';\n\n const stack = c.stack || dep.detail?.stack;\n if (c.kind !== 'other' || !stack) return;\n const jl = await api.get<JobsResponse>('/api/jobs');\n const live = (jl.body.jobs ?? []).find((j) => j.state === 'running' && j.stack === stack);\n if (live) conflictJobId.value = live.id;\n}\n</script>\n\n<template>\n <div>\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">Lifecycle</h2>\n\n <p v-if=\"!dep.detail\" class=\"mute\">\n Actions are unavailable until the spec resolves — the server cannot act on a deployment\n whose stack name it cannot compute. Fix the variables under\n <RouterLink :to=\"`/deployments/${encodeURIComponent(dep.id)}/config`\">\n Config &amp; variables </RouterLink\n >first.\n </p>\n\n <template v-else>\n <div v-if=\"tokenMissing\" class=\"banner warn\">\n <b>No token set.</b>\n <p>\n Every action below is a mutating request and will be refused with <code>401</code>. Add\n <code>PSTACK_TOKEN</code> under <RouterLink to=\"/settings\">Settings</RouterLink>.\n </p>\n </div>\n\n <div class=\"row\">\n <ActionButton\n variant=\"primary\"\n :pending=\"pending === 'up'\"\n :disabled=\"!!pending || busy\"\n :title=\"whyDisabled('up')\"\n @click=\"act('up')\"\n >\n up\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 <ActionButton\n variant=\"danger\"\n :pending=\"pending === 'down'\"\n :disabled=\"!!pending || busy || (isShared && !forceArmed)\"\n :title=\"whyDisabled('down')\"\n @click=\"act('down')\"\n >\n {{ isShared ? 'down (force)' : 'down' }}\n </ActionButton>\n <span class=\"grow\" />\n <RouterLink :to=\"`/submit/${encodeURIComponent(dep.id)}`\">replace spec →</RouterLink>\n </div>\n\n <p v-if=\"busy\" class=\"hint\">\n A job is in flight for <b class=\"mono\">{{ dep.detail.stack }}</b\n >. One job per stack, deliberately not queued — a <code>down</code> racing an\n <code>up</code> over the same database branch is corruption, not contention.\n </p>\n <p class=\"hint\">\n Sent with <span class=\"mono\">{{ varsQuery || 'no request variables' }}</span\n >.\n <RouterLink :to=\"`/deployments/${encodeURIComponent(dep.id)}/config`\">change</RouterLink>\n </p>\n\n <div class=\"row\" style=\"margin-top: var(--s3)\">\n <label class=\"check\">\n <input v-model=\"downVerify\" type=\"checkbox\" />\n run <code>verify</code> after <code>down</code>\n </label>\n </div>\n <p v-if=\"!downVerify\" class=\"hint\">\n Teardown will not check for leaks. A teardown that silently half-worked is the exact\n failure this tool exists to catch — run <code>verify</code> yourself afterwards.\n </p>\n\n <!--\n Gate 1 of 2. The typed name is the point: a checkbox is one stray click, typing the stack\n name is a decision.\n -->\n <div v-if=\"isShared\" class=\"banner leaked\">\n <b>SHARED — read this before tearing down.</b>\n <p>\n <code>down</code> runs <code>docker compose down -v</code>, and <code>-v</code> removes\n <b>volumes</b>. On a shared deployment that destroys state every tenant depends on — the\n TLS certificate store, the shared database or queue, admin credentials. Nothing here\n recreates them: <b>every preview on this host breaks</b> until someone rebuilds the\n singleton by hand. It is the identical verb that is routine on a preview.\n </p>\n <div class=\"field\" style=\"margin-top: var(--s3); max-width: 380px\">\n <label :for=\"`confirm-${dep.id}`\">\n Type the stack name <b class=\"mono\">{{ dep.detail.stack }}</b> to send\n <code>force: true</code>\n </label>\n <input\n :id=\"`confirm-${dep.id}`\"\n v-model.trim=\"forceTyped\"\n type=\"text\"\n :placeholder=\"dep.detail.stack\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n </div>\n <p v-if=\"forceTyped && !forceArmed\" class=\"mono\">\n does not match — <code>down</code> stays disabled\n </p>\n </div>\n\n <ErrorNote v-if=\"actionError\" :text=\"actionError\" title=\"The action was refused.\" />\n <ConflictNote v-if=\"conflict\" :conflict=\"conflict\" :job-id=\"conflictJobId\" />\n </template>\n </section>\n\n <!-- ============================ forget ============================ -->\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">Forget this deployment</h2>\n\n <p v-if=\"!dep.detail\" class=\"mute\">\n Unavailable until the spec resolves — the server counts this stack's containers before it\n will forget the record, and it cannot name the stack without its variables.\n </p>\n <template v-else>\n <p class=\"dim\" style=\"font-size: var(--t-sm)\">\n <code>DELETE</code> removes the <em>record</em> — the stored <code>spec.yml</code>,\n <code>compose.yml</code> and <code>meta.json</code>. <b>It never tears anything down.</b>\n The server refuses with <code>409</code> while any container for this stack still exists,\n and refuses just as firmly when docker did not answer: “cannot confirm” is not evidence of\n absence.\n </p>\n <p class=\"hint\">\n After forgetting, this deployment disappears from the control plane. If a resource one of\n its axes created is still alive, nothing here knows how to remove it any more.\n </p>\n <div class=\"row\" style=\"margin-top: var(--s3)\">\n <label class=\"check\">\n <input v-model=\"forgetArmed\" type=\"checkbox\" />\n I have torn this down and want the record removed\n </label>\n <ActionButton\n variant=\"danger\"\n :pending=\"pending === 'forget'\"\n :disabled=\"!forgetArmed || !!pending\"\n :title=\"forgetArmed ? undefined : 'Confirm you have torn this down first.'\"\n @click=\"forget\"\n >\n Forget\n </ActionButton>\n </div>\n </template>\n </section>\n </div>\n</template>\n"],"names":["_createElementBlock","_normalizeClass","__props","_cache","_createElementVNode","_hoisted_1","_toDisplayString","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_createVNode","_component_RouterLink","_openBlock","router","useRouter","pending","ref","actionError","conflict","conflictJobId","downVerify","forceTyped","forgetArmed","forceArmed","computed","_a","dep","tokenMissing","state","settings","busy","whyDisabled","action","isShared","act","body","r","api","varsQuery","job","toast","loadDeployments","onConflict","problem","forget","c","classifyConflict","stack","live","j","_unref","_Fragment","ActionButton","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","$event","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_15","_hoisted_17","_createBlock","ErrorNote","ConflictNote","_hoisted_18","_hoisted_20","_hoisted_21","_hoisted_19"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{d as L,u as D,c as o,a as s,b as a,l as _,t as n,e as r,F as h,r as f,f as u,g as b,h as m,w as p,s as c,i as d,j as w,k as V,p as N,m as T,n as $,o as t,q as R,v as x}from"./index--8-Mcu3f.js";import{a as I,t as z}from"./useFormat-CNPlQbIu.js";import{_ as P}from"./StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js";import{_ as E}from"./SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js";const S={key:0,class:"banner leaked land"},q={class:"grid-2"},F={class:"panel"},J={class:"phead"},O={key:0,class:"badge mono"},U={key:0,class:"s-failed"},G={key:0,class:"banner warn"},K={key:1,class:"banner warn"},M={key:2,class:"banner failed"},Y={key:3,class:"cards"},H={class:"stagger"},Q={class:"name","data-label":"service"},W={class:"name dim","data-label":"image"},X={class:"banner plain"},Z={key:0,class:"mute"},ss={class:"panel"},es={class:"phead"},ts={key:0,class:"s-failed"},os={class:"stats"},ns={class:"stat"},ls={class:"v"},as={class:"stat"},rs={class:"v"},is={class:"stat"},ds={class:"v"},us={class:"stat"},cs={class:"v"},vs={class:"stat busy"},hs={class:"v"},ms={key:0,class:"stat unknown"},ps={class:"v"},ks={key:0,class:"hint"},ys={key:1,class:"hint"},bs={class:"panel"},gs={class:"phead"},_s={key:0,class:"s-failed"},fs={key:2,class:"kvlist stagger"},ws={class:"k"},Es={class:"v"},Cs={class:"mute",style:{"font-size":"var(--t-xs)"}},As={key:0,class:"mute"},Ds=L({__name:"DashboardView",setup(js){const i=w(null),k=w(""),C=w(!1);async function A(){const y=await V.get("/api/control");if(C.value=!0,!y.ok){k.value=N(y,"GET /api/control");return}k.value="";const e=y.body;i.value={project:e.project??"",reachable:e.reachable!==!1,parseError:!!e.parseError,services:Array.isArray(e.services)?e.services:[],managedBy:e.managedBy??"",actionable:e.actionable===!0,note:e.note??""}}D(()=>void A(),1e4);const j=T(()=>c.jobs.slice(0,8));return(y,e)=>{var B;const v=$("RouterLink");return t(),o("div",null,[e[28]||(e[28]=s("div",{class:"page-head"},[s("div",null,[s("h1",null,"Dashboard"),s("div",{class:"sub"},"One host, one Docker socket, one registry.")])],-1)),a(_).length?(t(),o("div",S,[s("b",null,n(a(_).length)+" job(s) ended LEAKED.",1),s("p",null,[e[0]||(e[0]=r(" Teardown ran and something survived it. ",-1)),e[1]||(e[1]=s("code",null,"compose down -v",-1)),e[2]||(e[2]=r(" removed containers, volumes and networks; whatever is left is outside compose's reach and nothing else will remove it. ",-1)),(t(!0),o(h,null,f(a(_).slice(0,3),l=>(t(),b(v,{key:l.id,to:`/jobs/${encodeURIComponent(l.id)}`,style:{"margin-left":"8px"}},{default:p(()=>[r(n(l.stack)+" →",1)]),_:2},1032,["to"]))),128))])])):u("",!0),s("div",q,[s("section",F,[s("div",J,[e[3]||(e[3]=s("h2",{class:"section"},"Control stack",-1)),(B=i.value)!=null&&B.project?(t(),o("span",O,n(i.value.project),1)):u("",!0),e[4]||(e[4]=s("span",{class:"grow"},null,-1)),s("button",{class:"sm ghost",onClick:A},"Refresh")]),k.value?(t(),o("p",U,n(k.value),1)):C.value?i.value?(t(),o(h,{key:2},[i.value.reachable?i.value.parseError?(t(),o("div",K,[...e[6]||(e[6]=[s("b",null,"docker answered, but the output could not be parsed.",-1),s("p",null,"Service state is unknown for this refresh; the stack itself may be fine.",-1)])])):i.value.services.length?u("",!0):(t(),o("div",M,[...e[7]||(e[7]=[s("b",null,"no containers in this project.",-1),s("p",null," docker answered clearly, so the control stack really is down — yet you are reading a page it serves. You are most likely reaching the API by some other route. ",-1)])])):(t(),o("div",G,[...e[5]||(e[5]=[s("b",null,"docker did not answer.",-1),s("p",null,[r(" This is "),s("em",null,"not"),r(" the same as “nothing is running” — the control stack may be perfectly healthy while this process cannot reach the socket. Check the daemon and the socket mount, then refresh. ")],-1)])])),i.value.services.length?(t(),o("table",Y,[e[8]||(e[8]=s("thead",null,[s("tr",null,[s("th",null,"service"),s("th",null,"state"),s("th",null,"health"),s("th",null,"image")])],-1)),s("tbody",H,[(t(!0),o(h,null,f(i.value.services,(l,g)=>(t(),o("tr",{key:l.name,style:R({"--i":g})},[s("td",Q,n(l.name),1),s("td",{"data-label":"state",class:x(l.state==="running"?"s-ok":"s-failed")},n(l.state||"—"),3),s("td",{"data-label":"health",class:x(l.health==="healthy"?"s-ok":l.health?"s-failed":"")},n(l.health||"—"),3),s("td",W,n(l.image||"—"),1)],4))),128))])])):u("",!0),s("div",X,[e[11]||(e[11]=s("b",null,"Read-only — there are no actions here.",-1)),s("p",null,n(i.value.note||"The control stack is not managed through this API: the process serving this request runs inside it."),1),i.value.managedBy?(t(),o("p",Z,[e[9]||(e[9]=r(" Managed by ",-1)),s("code",null,n(i.value.managedBy),1),e[10]||(e[10]=r(". ",-1))])):u("",!0)])],64)):u("",!0):(t(),b(E,{key:1,rows:3}))]),s("div",null,[s("section",ss,[s("div",es,[e[13]||(e[13]=s("h2",{class:"section"},"Deployments",-1)),e[14]||(e[14]=s("span",{class:"grow"},null,-1)),m(v,{to:"/deployments"},{default:p(()=>[...e[12]||(e[12]=[r("all →",-1)])]),_:1})]),a(c).deploymentsError?(t(),o("p",ts,n(a(c).deploymentsError),1)):a(c).deploymentsLoaded?(t(),o(h,{key:2},[s("div",os,[s("div",ns,[s("div",ls,n(a(d).total),1),e[15]||(e[15]=s("div",{class:"k"},"total",-1))]),s("div",as,[s("div",rs,n(a(d).isolated),1),e[16]||(e[16]=s("div",{class:"k"},"isolated",-1))]),s("div",is,[s("div",ds,n(a(d).shared),1),e[17]||(e[17]=s("div",{class:"k"},"shared",-1))]),s("div",us,[s("div",cs,n(a(d).running),1),e[18]||(e[18]=s("div",{class:"k"},"running",-1))]),s("div",vs,[s("div",hs,n(a(d).busy),1),e[19]||(e[19]=s("div",{class:"k"},"busy",-1))]),a(d).unknown?(t(),o("div",ms,[s("div",ps,n(a(d).unknown),1),e[20]||(e[20]=s("div",{class:"k"},"unknown",-1))])):u("",!0)]),a(d).unresolved?(t(),o("p",ks,[r(n(a(d).unresolved)+" deployment(s) could not be resolved in this listing — their spec needs ",1),e[22]||(e[22]=s("code",null,"${VAR}",-1)),e[23]||(e[23]=r(" values. ",-1)),m(v,{to:"/deployments"},{default:p(()=>[...e[21]||(e[21]=[r("Open one and set its variables.",-1)])]),_:1})])):a(d).unknown?(t(),o("p",ys,[...e[24]||(e[24]=[r(" “unknown” means the server could not determine ",-1),s("code",null,"busy",-1),r(" or ",-1),s("code",null,"running",-1),r(" for that row, ",-1),s("em",null,"not",-1),r(" that it is idle. ",-1)])])):u("",!0)],64)):(t(),b(E,{key:1,rows:2,tall:""}))]),s("section",bs,[s("div",gs,[e[26]||(e[26]=s("h2",{class:"section"},"Recent jobs",-1)),e[27]||(e[27]=s("span",{class:"grow"},null,-1)),m(v,{to:"/jobs"},{default:p(()=>[...e[25]||(e[25]=[r("all →",-1)])]),_:1})]),a(c).jobsError?(t(),o("p",_s,n(a(c).jobsError),1)):a(c).jobsLoaded?(t(),o("ul",fs,[(t(!0),o(h,null,f(j.value,(l,g)=>(t(),o("li",{key:l.id,style:R({"--i":g})},[s("span",ws,[m(P,{state:l.state},null,8,["state"])]),s("span",Es,[m(v,{to:`/jobs/${encodeURIComponent(l.id)}`,class:"mono"},{default:p(()=>[r(n(l.action)+" "+n(l.stack),1)]),_:2},1032,["to"]),s("span",Cs," · "+n(a(I)(l.startedAt))+" · "+n(a(z)(l.startedAt,l.endedAt)),1)])],4))),128)),j.value.length?u("",!0):(t(),o("li",As," No jobs yet — job history is held in memory, so a server restart clears it. A job record is the transcript of an attempt, not the truth about what exists. "))])):(t(),b(E,{key:1,rows:4}))])])])])}}});export{Ds as default};
2
- //# sourceMappingURL=DashboardView-D27l8yqd.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DashboardView-D27l8yqd.js","sources":["../../src/views/DashboardView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Dashboard: control-stack health, deployment counts, recent jobs, activity.\n *\n * The deployment and job lists are already polled by the shell, so this view only fetches the one\n * thing nobody else needs — the control stack.\n */\nimport { computed, ref } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { Control } from '../api/types';\nimport { leakedJobs, state, summary } from '../composables/useControlPlane';\nimport { usePolling } from '../composables/usePolling';\nimport { ago, took } from '../composables/useFormat';\nimport StateBadge from '../components/StateBadge.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\n\nconst control = ref<Control | null>(null);\nconst controlError = ref('');\nconst controlLoaded = ref(false);\n\nasync function loadControl(): Promise<void> {\n const r = await api.get<Control>('/api/control');\n controlLoaded.value = true;\n if (!r.ok) {\n controlError.value = problem(r, 'GET /api/control');\n return;\n }\n controlError.value = '';\n const b = r.body;\n control.value = {\n project: b.project ?? '',\n // `reachable !== false`, not `!!reachable`: an older build that omits the field should not be\n // reported as \"docker did not answer\".\n reachable: b.reachable !== false,\n parseError: !!b.parseError,\n services: Array.isArray(b.services) ? b.services : [],\n managedBy: b.managedBy ?? '',\n actionable: b.actionable === true,\n note: b.note ?? '',\n };\n}\n\nusePolling(() => void loadControl(), 10000);\n\nconst recentJobs = computed(() => state.jobs.slice(0, 8));\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Dashboard</h1>\n <div class=\"sub\">One host, one Docker socket, one registry.</div>\n </div>\n </div>\n\n <!--\n A leak is the single thing this whole tool exists to catch, so it outranks every other panel\n on the page. Nothing else will clean up what survived a teardown.\n -->\n <div v-if=\"leakedJobs.length\" class=\"banner leaked land\">\n <b>{{ leakedJobs.length }} job(s) ended LEAKED.</b>\n <p>\n Teardown ran and something survived it. <code>compose down -v</code> removed containers,\n volumes and networks; whatever is left is outside compose's reach and nothing else will\n remove it.\n <RouterLink\n v-for=\"j in leakedJobs.slice(0, 3)\"\n :key=\"j.id\"\n :to=\"`/jobs/${encodeURIComponent(j.id)}`\"\n style=\"margin-left: 8px\"\n >{{ j.stack }} →</RouterLink\n >\n </p>\n </div>\n\n <div class=\"grid-2\">\n <!-- ============================ control stack ============================ -->\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Control stack</h2>\n <span v-if=\"control?.project\" class=\"badge mono\">{{ control.project }}</span>\n <span class=\"grow\" />\n <button class=\"sm ghost\" @click=\"loadControl\">Refresh</button>\n </div>\n\n <p v-if=\"controlError\" class=\"s-failed\">{{ controlError }}</p>\n <SkeletonList v-else-if=\"!controlLoaded\" :rows=\"3\" />\n\n <template v-else-if=\"control\">\n <!--\n Three distinct failure shapes. Collapsing any of them into \"nothing is running\" would be\n a lie an operator acts on.\n -->\n <div v-if=\"!control.reachable\" class=\"banner warn\">\n <b>docker did not answer.</b>\n <p>\n This is <em>not</em> the same as “nothing is running” — the control stack may be\n perfectly healthy while this process cannot reach the socket. Check the daemon and the\n socket mount, then refresh.\n </p>\n </div>\n <div v-else-if=\"control.parseError\" class=\"banner warn\">\n <b>docker answered, but the output could not be parsed.</b>\n <p>Service state is unknown for this refresh; the stack itself may be fine.</p>\n </div>\n <div v-else-if=\"!control.services.length\" class=\"banner failed\">\n <b>no containers in this project.</b>\n <p>\n docker answered clearly, so the control stack really is down — yet you are reading a\n page it serves. You are most likely reaching the API by some other route.\n </p>\n </div>\n\n <table v-if=\"control.services.length\" class=\"cards\">\n <thead>\n <tr>\n <th>service</th>\n <th>state</th>\n <th>health</th>\n <th>image</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(s, i) in control.services\" :key=\"s.name\" :style=\"{ '--i': i }\">\n <td class=\"name\" data-label=\"service\">{{ s.name }}</td>\n <td data-label=\"state\" :class=\"s.state === 'running' ? 's-ok' : 's-failed'\">\n {{ s.state || '—' }}\n </td>\n <td\n data-label=\"health\"\n :class=\"s.health === 'healthy' ? 's-ok' : s.health ? 's-failed' : ''\"\n >\n {{ s.health || '—' }}\n </td>\n <td class=\"name dim\" data-label=\"image\">{{ s.image || '—' }}</td>\n </tr>\n </tbody>\n </table>\n\n <!--\n THIS SITS WHERE AN OPERATOR LOOKS FOR up/down/verify BUTTONS, because the answer to\n \"where are they\" is the important part. The API serving this page runs INSIDE this\n stack: `up` here would kill the process performing it, and a failed self-upgrade leaves\n the host with no control plane and no remote way in. The server states\n `actionable: false` for exactly this reason. Never add an action here, not even a safe\n one.\n -->\n <div class=\"banner plain\">\n <b>Read-only — there are no actions here.</b>\n <p>\n {{\n control.note ||\n 'The control stack is not managed through this API: the process serving this request runs inside it.'\n }}\n </p>\n <p v-if=\"control.managedBy\" class=\"mute\">\n Managed by <code>{{ control.managedBy }}</code\n >.\n </p>\n </div>\n </template>\n </section>\n\n <!-- ============================ deployments + jobs ============================ -->\n <div>\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Deployments</h2>\n <span class=\"grow\" />\n <RouterLink to=\"/deployments\">all →</RouterLink>\n </div>\n\n <p v-if=\"state.deploymentsError\" class=\"s-failed\">{{ state.deploymentsError }}</p>\n <SkeletonList v-else-if=\"!state.deploymentsLoaded\" :rows=\"2\" tall />\n <template v-else>\n <div class=\"stats\">\n <div class=\"stat\"><div class=\"v\">{{ summary.total }}</div><div class=\"k\">total</div></div>\n <div class=\"stat\"><div class=\"v\">{{ summary.isolated }}</div><div class=\"k\">isolated</div></div>\n <div class=\"stat\"><div class=\"v\">{{ summary.shared }}</div><div class=\"k\">shared</div></div>\n <div class=\"stat\"><div class=\"v\">{{ summary.running }}</div><div class=\"k\">running</div></div>\n <div class=\"stat busy\"><div class=\"v\">{{ summary.busy }}</div><div class=\"k\">busy</div></div>\n <!--\n The server sends `null`, never `false`, when it could not determine busy/running.\n An unknown must never be counted as a zero — that is how a live stack gets reported\n as torn down. It gets its own tile.\n -->\n <div v-if=\"summary.unknown\" class=\"stat unknown\">\n <div class=\"v\">{{ summary.unknown }}</div>\n <div class=\"k\">unknown</div>\n </div>\n </div>\n\n <p v-if=\"summary.unresolved\" class=\"hint\">\n {{ summary.unresolved }} deployment(s) could not be resolved in this listing — their\n spec needs <code>${VAR}</code> values.\n <RouterLink to=\"/deployments\">Open one and set its variables.</RouterLink>\n </p>\n <p v-else-if=\"summary.unknown\" class=\"hint\">\n “unknown” means the server could not determine <code>busy</code> or\n <code>running</code> for that row, <em>not</em> that it is idle.\n </p>\n </template>\n </section>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Recent jobs</h2>\n <span class=\"grow\" />\n <RouterLink to=\"/jobs\">all →</RouterLink>\n </div>\n\n <p v-if=\"state.jobsError\" class=\"s-failed\">{{ state.jobsError }}</p>\n <SkeletonList v-else-if=\"!state.jobsLoaded\" :rows=\"4\" />\n <ul v-else class=\"kvlist stagger\">\n <li v-for=\"(j, i) in recentJobs\" :key=\"j.id\" :style=\"{ '--i': i }\">\n <span class=\"k\"><StateBadge :state=\"j.state\" /></span>\n <span class=\"v\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(j.id)}`\" class=\"mono\">\n {{ j.action }} {{ j.stack }}\n </RouterLink>\n <span class=\"mute\" style=\"font-size: var(--t-xs)\">\n · {{ ago(j.startedAt) }} · {{ took(j.startedAt, j.endedAt) }}\n </span>\n </span>\n </li>\n <li v-if=\"!recentJobs.length\" class=\"mute\">\n No jobs yet — job history is held in memory, so a server restart clears it. A job\n record is the transcript of an attempt, not the truth about what exists.\n </li>\n </ul>\n </section>\n </div>\n </div>\n </div>\n</template>\n"],"names":["control","ref","controlError","controlLoaded","loadControl","r","api","problem","b","usePolling","recentJobs","computed","state","_createElementBlock","_createElementVNode","_unref","leakedJobs","_openBlock","_hoisted_1","_toDisplayString","_cache","_Fragment","_renderList","j","_createBlock","_component_RouterLink","_hoisted_2","_hoisted_3","_hoisted_4","_a","_hoisted_5","_hoisted_6","_hoisted_8","_hoisted_9","_hoisted_7","_hoisted_10","_hoisted_11","s","i","_hoisted_12","_normalizeClass","_hoisted_13","_hoisted_14","_hoisted_15","SkeletonList","_hoisted_16","_hoisted_17","_createVNode","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","summary","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_hoisted_33","_hoisted_34","_hoisted_35","_hoisted_36","_hoisted_37","_hoisted_38","StateBadge","_hoisted_39","_createTextVNode","_hoisted_40","ago","_hoisted_41"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{d as C,B as h,c as l,a as e,h as u,w as p,t as m,b as o,e as s,v as x,f as i,F as R,r as D,g as f,n as g,o as n,T as V,C as B,D as T}from"./index--8-Mcu3f.js";import{d as a,i as A,o as I}from"./useDeployment-ByQUNe_X.js";import{_ as L}from"./ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js";import"./useVars-VN9gYdR6.js";const N={class:"page-head"},$={class:"mute",style:{"font-size":"var(--t-sm)"}},q={class:"mono",style:{"font-size":"var(--t-xl)"}},z={class:"sub"},S={key:0},E={class:"mono"},F={key:1},U={key:2},O={key:1,class:"badge busy"},j={class:"tabs","aria-label":"Deployment sections"},G={key:1,class:"hint"},H={key:2,class:"banner warn"},Q=C({__name:"DeploymentDetailView",props:{id:{}},setup(c){const v=c,y=B(),k=[{to:"overview",label:"Overview"},{to:"config",label:"Config & variables"},{to:"axes",label:"Axes"},{to:"requires",label:"Requires"},{to:"logs",label:"Logs"},{to:"danger",label:"Danger"}];return h(()=>v.id,d=>I(d),{immediate:!0}),h(()=>a.error,d=>{d&&y.currentRoute.value.name==="d.overview"&&y.replace(`/deployments/${encodeURIComponent(v.id)}/config`)}),(d,t)=>{var _;const b=g("RouterLink"),w=g("RouterView");return n(),l("div",null,[e("div",N,[e("div",null,[e("div",$,[u(b,{to:"/deployments"},{default:p(()=>[...t[0]||(t[0]=[s("← deployments",-1)])]),_:1})]),e("h1",q,m(c.id),1),e("div",z,[o(a).detail?(n(),l("span",S,[t[1]||(t[1]=s(" stack ",-1)),e("span",E,m(o(a).detail.stack),1)])):o(a).error?(n(),l("span",F,"unresolved")):(n(),l("span",U,"loading…"))])]),t[3]||(t[3]=e("span",{class:"grow"},null,-1)),o(a).detail?(n(),l("span",{key:0,class:x(["badge",o(a).detail.kind])},m(o(a).detail.kind),3)):i("",!0),((_=o(a).detail)==null?void 0:_.busy)===!0?(n(),l("span",O,[...t[2]||(t[2]=[e("span",{class:"dot pulse"},null,-1),s("busy",-1)])])):i("",!0)]),e("nav",j,[(n(),l(R,null,D(k,r=>u(b,{key:r.to,to:`/deployments/${encodeURIComponent(c.id)}/${r.to}`},{default:p(()=>[s(m(r.label),1)]),_:2},1032,["to"])),64))]),o(a).error?(n(),f(L,{key:0,text:o(a).error,title:"Could not resolve this deployment."},null,8,["text"])):i("",!0),o(a).error?(n(),l("p",G,[...t[4]||(t[4]=[s(" If that message names a variable, add it under ",-1),e("b",null,"Config & variables",-1),s(" and press ",-1),e("b",null,"Apply & reload",-1),s(". Everything else on this page stays unavailable until the spec resolves — the server cannot tell us the stack name without it, and every action needs the stack name. ",-1)])])):i("",!0),o(A)?(n(),l("div",H,[...t[5]||(t[5]=[e("b",null,[s("This is a "),e("code",null,"kind: shared"),s(" deployment.")],-1),e("p",null,[s(" A host singleton that previews borrow — a database, a queue, a registry mirror. It declares no axes, and its "),e("code",null,"down"),s(" is refused without an explicit force, because "),e("code",null,"compose down -v"),s(" here destroys state every other deployment depends on. ")],-1)])])):i("",!0),u(w,null,{default:p(({Component:r})=>[u(V,{name:"view",mode:"out-in"},{default:p(()=>[(n(),f(T(r)))]),_:2},1024)]),_:1})])}}});export{Q as default};
2
- //# sourceMappingURL=DeploymentDetailView-BjELdE9f.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DeploymentDetailView-BjELdE9f.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 ErrorNote from '../components/ErrorNote.vue';\n\nconst props = defineProps<{ id: string }>();\nconst router = useRouter();\n\nconst TABS = [\n { to: 'overview', label: 'Overview' },\n { to: 'config', label: 'Config & variables' },\n { to: 'axes', label: 'Axes' },\n { to: 'requires', label: 'Requires' },\n { to: 'logs', label: 'Logs' },\n { to: 'danger', label: 'Danger' },\n] as const;\n\nwatch(() => props.id, (id) => openDeployment(id), { immediate: true });\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 class=\"mono\" style=\"font-size: var(--t-xl)\">{{ id }}</h1>\n <div class=\"sub\">\n <span v-if=\"dep.detail\">\n stack <span class=\"mono\">{{ dep.detail.stack }}</span>\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 </div>\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 &amp; variables</b> and press\n <b>Apply &amp; reload</b>. Everything else on this page stays unavailable until the spec\n resolves — the server cannot tell us the stack name without it, and every action needs the\n stack name.\n </p>\n\n <div v-if=\"isShared\" class=\"banner warn\">\n <b>This is a <code>kind: shared</code> deployment.</b>\n <p>\n A host singleton that previews borrow — a database, a queue, a registry mirror. It declares\n no axes, and its <code>down</code> is refused without an explicit force, because\n <code>compose down -v</code> here destroys state every other deployment depends on.\n </p>\n </div>\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","dep","err","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","_component_RouterLink","_cache","_hoisted_3","_toDisplayString","_hoisted_4","_unref","_hoisted_5","_hoisted_6","_openBlock","_hoisted_9","_hoisted_10","_Fragment","_renderList","t","_createTextVNode","_createBlock","ErrorNote","_hoisted_11","isShared","_hoisted_12","_component_RouterView","_withCtx","Component","_Transition","_resolveDynamicComponent"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{d as $,o as n,c as l,a as s,e as o,t as a,b as d,i as g,h as m,w as k,x as c,y as V,z as B,A as R,s as r,g as S,F as y,r as _,f as x,j as h,m as C,n as z,q,v as U}from"./index--8-Mcu3f.js";import{s as D}from"./useFormat-CNPlQbIu.js";import{_ as A}from"./SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js";const E={key:0,class:"badge busy"},M={key:1,class:"badge ok"},T={key:2,class:"badge unknown",title:"the server could not determine this — it is not the same as idle"},F={key:3,class:"badge"},I=$({__name:"RunStateBadge",props:{busy:{type:[Boolean,null]},running:{type:[Boolean,null]}},setup(p){return(v,i)=>p.busy===!0?(n(),l("span",E,[...i[0]||(i[0]=[s("span",{class:"dot pulse"},null,-1),o("busy",-1)])])):p.running===!0?(n(),l("span",M,[...i[1]||(i[1]=[s("span",{class:"dot"},null,-1),o("running",-1)])])):p.busy===null||p.running===null?(n(),l("span",T,"unknown")):(n(),l("span",F,"idle"))}}),j={class:"page-head"},K={class:"sub"},G={class:"panel"},H={class:"phead"},J={class:"field",style:{flex:"1","min-width":"180px"}},O={class:"field"},P={class:"check",style:{"align-self":"end","min-height":"32px"}},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"},se={class:"name dim","data-label":"stack"},te={key:0},ne={key:1,class:"badge warn",title:"the spec could not be resolved without variables"},le={"data-label":"state"},oe={class:"dim mono nowrap","data-label":"updated"},ae={key:0},ie={colspan:"5",class:"mute"},de={class:"mono"},ue={class:"mono"},ye=$({__name:"DeploymentsView",setup(p){const v=h(""),i=h("all"),f=h(!1),w=C(()=>{const u=v.value.trim().toLowerCase();return r.deployments.filter(e=>i.value!=="all"&&e.kind!==i.value||f.value&&!(e.running===!0||e.busy===!0)?!1:u?e.id.toLowerCase().includes(u)||(e.stack??"").toLowerCase().includes(u)||(e.specName??"").toLowerCase().includes(u):!0)}),L=C(()=>r.deployments.filter(u=>u.unresolved));return(u,e)=>{const b=z("RouterLink");return n(),l("div",null,[s("div",j,[s("div",null,[e[3]||(e[3]=s("h1",null,"Deployments",-1)),s("div",K,a(d(g).total)+" submitted · "+a(d(g).running)+" running · "+a(d(g).busy)+" busy ",1)]),e[5]||(e[5]=s("span",{class:"grow"},null,-1)),m(b,{to:"/submit",class:"btn"},{default:k(()=>[...e[4]||(e[4]=[o("+ Submit",-1)])]),_:1})]),s("section",G,[s("div",H,[s("div",J,[e[6]||(e[6]=s("label",{for:"q",class:"mute",style:{"font-size":"var(--t-xs)"}},"Search — press /",-1)),c(s("input",{id:"q","onUpdate:modelValue":e[0]||(e[0]=t=>v.value=t),"data-search":"",type:"search",placeholder:"id, stack or spec name",spellcheck:"false"},null,512),[[V,v.value]])]),s("div",O,[e[8]||(e[8]=s("label",{for:"kind",class:"mute",style:{"font-size":"var(--t-xs)"}},"Kind",-1)),c(s("select",{id:"kind","onUpdate:modelValue":e[1]||(e[1]=t=>i.value=t)},[...e[7]||(e[7]=[s("option",{value:"all"},"all",-1),s("option",{value:"isolated"},"isolated",-1),s("option",{value:"shared"},"shared",-1)])],512),[[B,i.value]])]),s("label",P,[c(s("input",{"onUpdate:modelValue":e[2]||(e[2]=t=>f.value=t),type:"checkbox"},null,512),[[R,f.value]]),e[9]||(e[9]=o(" running or busy only ",-1))])]),d(r).deploymentsError?(n(),l("p",Q,a(d(r).deploymentsError),1)):d(r).deploymentsLoaded?(n(),l(y,{key:2},[s("table",W,[e[13]||(e[13]=s("thead",null,[s("tr",null,[s("th",null,"id"),s("th",null,"kind"),s("th",null,"stack"),s("th",null,"state"),s("th",null,"updated")])],-1)),s("tbody",X,[(n(!0),l(y,null,_(w.value,(t,N)=>(n(),l("tr",{key:t.id,style:q({"--i":N})},[s("td",Y,[m(b,{to:`/deployments/${encodeURIComponent(t.id)}`,class:"mono"},{default:k(()=>[o(a(t.id),1)]),_:2},1032,["to"]),t.specName?(n(),l("div",Z," spec: "+a(t.specName),1)):x("",!0)]),s("td",ee,[s("span",{class:U(["badge",t.kind])},a(t.kind),3)]),s("td",se,[t.stack?(n(),l("span",te,a(t.stack),1)):(n(),l("span",ne," needs variables "))]),s("td",le,[m(I,{busy:t.busy,running:t.running},null,8,["busy","running"])]),s("td",oe,a(d(D)(t.updatedAt)),1)],4))),128)),w.value.length?x("",!0):(n(),l("tr",ae,[s("td",ie,[d(r).deployments.length?(n(),l(y,{key:0},[o(" Nothing matches this filter. ")],64)):(n(),l(y,{key:1},[e[11]||(e[11]=o(" Nothing submitted yet — ",-1)),m(b,{to:"/submit"},{default:k(()=>[...e[10]||(e[10]=[o("submit a spec",-1)])]),_:1}),e[12]||(e[12]=o(" to put a deployment in the registry. ",-1))],64))])]))])]),(n(!0),l(y,null,_(L.value,t=>(n(),l("div",{key:`u-${t.id}`,class:"banner warn"},[s("b",de,a(t.id),1),e[21]||(e[21]=o(" could not be resolved: ",-1)),s("span",ue,a(t.unresolved),1),s("p",null,[e[15]||(e[15]=o(" This listing resolves every spec with the ",-1)),e[16]||(e[16]=s("em",null,"same",-1)),e[17]||(e[17]=o(" (empty) request variables, so a spec with a ",-1)),e[18]||(e[18]=s("code",null,"${VAR}",-1)),e[19]||(e[19]=o(" the server does not already hold will always appear here. ",-1)),m(b,{to:`/deployments/${encodeURIComponent(t.id)}/config`},{default:k(()=>[...e[14]||(e[14]=[o(" Set its variables ",-1)])]),_:1},8,["to"]),e[20]||(e[20]=o(" and the detail view will resolve. ",-1))])]))),128))],64)):(n(),S(A,{key:1,rows:4,tall:""}))])])}}});export{ye as default};
2
- //# sourceMappingURL=DeploymentsView-02Cg08fs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DeploymentsView-02Cg08fs.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';\nimport { stamp } from '../composables/useFormat';\nimport RunStateBadge from '../components/RunStateBadge.vue';\nimport SkeletonList from '../components/SkeletonList.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 <div class=\"field\" style=\"flex: 1; min-width: 180px\">\n <label for=\"q\" class=\"mute\" style=\"font-size: var(--t-xs)\">Search — press /</label>\n <input\n id=\"q\"\n v-model=\"q\"\n data-search\n type=\"search\"\n placeholder=\"id, stack or spec name\"\n spellcheck=\"false\"\n />\n </div>\n <div class=\"field\">\n <label for=\"kind\" class=\"mute\" style=\"font-size: var(--t-xs)\">Kind</label>\n <select id=\"kind\" v-model=\"kind\">\n <option value=\"all\">all</option>\n <option value=\"isolated\">isolated</option>\n <option value=\"shared\">shared</option>\n </select>\n </div>\n <label class=\"check\" style=\"align-self: end; min-height: 32px\">\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)}`\" class=\"mono\">\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 mono nowrap\" data-label=\"updated\">{{ stamp(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 class=\"mono\">{{ 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","_hoisted_6","_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","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_createBlock","SkeletonList"],"mappings":"qnBAecA,EAAA,OAAI,IAAhBC,EAAA,EAAAC,EAAmF,OAAnFC,EAAmF,CAAA,GAAAC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAArCC,EAA0B,OAAA,CAApB,MAAM,WAAA,EAAW,KAAA,EAAA,IAAG,OAAI,EAAA,CAAA,MAC3DL,EAAA,UAAO,IAAxBC,IAAAC,EAAsF,OAAtFI,EAAsF,CAAA,GAAAF,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAAlCC,EAAoB,OAAA,CAAd,MAAM,KAAA,EAAK,KAAA,EAAA,IAAG,UAAO,EAAA,CAAA,MAElEL,EAAA,aAAiBA,EAAA,UAAO,UADrCE,EAKC,OALDK,EAIG,SAAO,QAEVL,EAAsC,OAAtCM,EAA2B,MAAI,mtBCVjC,MAAMC,EAAIC,EAAI,EAAE,EACVC,EAAOD,EAAmC,KAAK,EAC/CE,EAAWF,EAAI,EAAK,EAEpBG,EAAOC,EAAS,IAAM,CAC1B,MAAMC,EAASN,EAAE,MAAM,KAAA,EAAO,YAAA,EAC9B,OAAOO,EAAM,YAAY,OAAQC,GAC3BN,EAAK,QAAU,OAASM,EAAE,OAASN,EAAK,OAGxCC,EAAS,OAAS,EAAEK,EAAE,UAAY,IAAQA,EAAE,OAAS,IAAc,GAClEF,EAEHE,EAAE,GAAG,cAAc,SAASF,CAAM,IACjCE,EAAE,OAAS,IAAI,cAAc,SAASF,CAAM,IAC5CE,EAAE,UAAY,IAAI,YAAA,EAAc,SAASF,CAAM,EAJ9B,EAMrB,CACH,CAAC,EAEKG,EAAiBJ,EAAS,IAAME,EAAM,YAAY,OAAQC,GAAMA,EAAE,UAAU,CAAC,mDAIjFf,EA2GM,MAAA,KAAA,CA1GJG,EASM,MATNF,EASM,CARJE,EAKM,MAAA,KAAA,CAJJD,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAC,EAAoB,UAAhB,cAAW,EAAA,GACfA,EAEM,MAFNC,EAEMa,EADDC,KAAQ,KAAK,EAAG,gBAAaD,EAAGC,KAAQ,OAAO,EAAG,cAAWD,EAAGC,KAAQ,IAAI,EAAG,SACpF,CAAA,CAAA,eAEFf,EAAqB,OAAA,CAAf,MAAM,MAAA,EAAM,KAAA,EAAA,GAClBgB,EAA0DC,EAAA,CAA9C,GAAG,UAAU,MAAM,KAAA,aAAM,IAAQ,CAAA,GAAAlB,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAAR,WAAQ,EAAA,CAAA,aAG/CC,EA8FU,UA9FVE,EA8FU,CA7FRF,EAwBM,MAxBNG,EAwBM,CAvBJH,EAUM,MAVNkB,EAUM,aATJlB,EAAmF,QAAA,CAA5E,IAAI,IAAI,MAAM,OAAO,MAAA,CAAA,YAAA,aAAA,CAAA,EAA+B,mBAAgB,EAAA,KAC3EA,EAOE,QAAA,CANA,GAAG,yCACMI,EAAC,MAAAe,GACV,cAAA,GACA,KAAK,SACL,YAAY,yBACZ,WAAW,OAAA,gBAJFf,EAAA,KAAC,CAAA,KAOdJ,EAOM,MAPNoB,EAOM,aANJpB,EAA0E,QAAA,CAAnE,IAAI,OAAO,MAAM,OAAO,MAAA,CAAA,YAAA,aAAA,CAAA,EAA+B,OAAI,EAAA,KAClEA,EAIS,SAAA,CAJD,GAAG,4CAAgBM,EAAI,MAAAa,EAAA,mBAC7BnB,EAAgC,SAAA,CAAxB,MAAM,KAAA,EAAM,MAAG,EAAA,EACvBA,EAA0C,SAAA,CAAlC,MAAM,UAAA,EAAW,WAAQ,EAAA,EACjCA,EAAsC,SAAA,CAA9B,MAAM,QAAA,EAAS,SAAM,EAAA,CAAA,aAHJM,EAAA,KAAI,CAAA,KAMjCN,EAGQ,QAHRqB,EAGQ,GAFNrB,EAA4C,QAAA,sCAA5BO,EAAQ,MAAAY,GAAE,KAAK,UAAA,gBAAfZ,EAAA,KAAQ,CAAA,iBAAoB,yBAE9C,EAAA,EAAA,KAGOQ,EAAAJ,CAAA,EAAM,kBAAff,EAAA,EAAAC,EAAkF,IAAlFyB,EAAkFR,EAA7BC,EAAAJ,CAAA,EAAM,gBAAgB,EAAA,CAAA,GACjDI,EAAAJ,CAAA,EAAM,uBAEhCd,EA+DW0B,EAAA,CAAA,IAAA,GAAA,CA9DTvB,EA8CQ,QA9CRwB,EA8CQ,eA7CNxB,EAQQ,QAAA,KAAA,CAPNA,EAMK,KAAA,KAAA,CALHA,EAAW,UAAP,IAAE,EACNA,EAAa,UAAT,MAAI,EACRA,EAAc,UAAV,OAAK,EACTA,EAAc,UAAV,OAAK,EACTA,EAAgB,UAAZ,SAAO,CAAA,SAGfA,EAmCQ,QAnCRyB,EAmCQ,EAlCN7B,EAAA,EAAA,EAAAC,EAsBK0B,EAAA,KAAAG,EAtBgBlB,EAAA,MAAI,CAAbI,EAAGe,SAAf9B,EAsBK,KAAA,CAtBuB,IAAKe,EAAE,GAAK,eAAgBe,EAAC,CAAA,GACvD3B,EAOK,KAPL4B,EAOK,CANHZ,EAEaC,EAAA,CAFA,GAAE,gBAAkB,mBAAmBL,EAAE,EAAE,CAAA,GAAK,MAAM,MAAA,aACjE,IAAU,CAAPiB,EAAAf,EAAAF,EAAE,EAAE,EAAA,CAAA,CAAA,qBAEEA,EAAE,UAAbhB,EAAA,EAAAC,EAEM,MAFNiC,EAAmE,UAC3DhB,EAAGF,EAAE,QAAQ,EAAA,CAAA,cAGvBZ,EAAkF,KAAlF+B,GAAkF,CAA5D/B,EAAuD,OAAA,CAAjD,MAAKgC,EAAA,CAAC,QAAgBpB,EAAE,IAAI,CAAA,CAAA,EAAKE,EAAAF,EAAE,IAAI,EAAA,CAAA,CAAA,GACnEZ,EASK,KATLiC,GASK,CARSrB,EAAE,WAAdf,EAAyC,OAAAqC,GAAApB,EAAjBF,EAAE,KAAK,EAAA,CAAA,QAK/Bf,EAEO,OAFPsC,GAAyF,mBAEzF,EAAA,GAEFnC,EAAiF,KAAjFoC,GAAiF,CAA1DpB,EAAqDqB,EAAA,CAArC,KAAMzB,EAAE,KAAO,QAASA,EAAE,OAAA,+BACjEZ,EAA8E,KAA9EsC,GAA8ExB,EAA1BC,KAAMH,EAAE,SAAS,CAAA,EAAA,CAAA,CAAA,aAE5DJ,EAAA,MAAK,qBAAhBX,EAUK,KAAA0C,GAAA,CATHvC,EAQK,KARLwC,GAQK,CAPazB,EAAAJ,CAAA,EAAM,YAAY,YAAlCd,EAEW0B,EAAA,CAAA,IAAA,CAAA,EAAA,GAF+B,gCAE1C,CAAA,YACA1B,EAGW0B,EAAA,CAAA,IAAA,GAAA,iBAHM,4BACS,EAAA,GAAAP,EAAmDC,EAAA,CAAvC,GAAG,WAAS,WAAC,IAAa,CAAA,GAAAlB,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAA,GAAb,gBAAa,EAAA,CAAA,2BAAa,yCAE7E,EAAA,EAAA,uBAORF,EAYM0B,EAAA,KAAAG,EAZWb,EAAA,MAALD,QAAZf,EAYM,MAAA,CAZ4B,IAAG,KAAOe,EAAE,EAAE,GAAI,MAAM,aAAA,GACxDZ,EAA8B,IAA9ByC,GAA8B3B,EAAXF,EAAE,EAAE,EAAA,CAAA,kBAAO,2BAC9B,EAAA,GAAAZ,EAA4C,OAA5C0C,GAA4C5B,EAAtBF,EAAE,UAAU,EAAA,CAAA,EAClCZ,EAQI,IAAA,KAAA,iBARD,8CACyC,EAAA,GAAAD,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAC,EAAa,UAAT,OAAI,EAAA,mBAAK,gDAC3C,EAAA,GAAAD,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAC,EAAmB,YAAb,SAAM,EAAA,mBAAO,8DAE/B,EAAA,GAAAgB,EAEaC,EAAA,CAFA,GAAE,gBAAkB,mBAAmBL,EAAE,EAAE,CAAA,SAAA,aAAY,IAEpE,CAAA,GAAAb,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAA,GAFoE,sBAEpE,EAAA,CAAA,oCAAa,sCAEf,EAAA,EAAA,wBA/DJ4C,EAAoEC,EAAA,OAAhB,KAAM,EAAG,KAAA,EAAA"}
@@ -1,2 +0,0 @@
1
- import{d as z,c as r,a as e,F as g,r as D,E as J,o as n,q as M,v as _,b as p,t as u,B as H,P as O,h as B,w as P,e as d,f as c,g as E,x as W,A as G,j as f,m as R,k as U,n as K,p as Q}from"./index--8-Mcu3f.js";import{s as q,t as X}from"./useFormat-CNPlQbIu.js";import{_ as Y}from"./StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js";import{_ as Z}from"./ErrorNote.vue_vue_type_script_setup_true_lang-vu8RudtR.js";function k(o){var i;return(i=o.message)!=null&&i.startsWith("unverifiable")?"unverifiable":o.ok?"ok":o.phase==="assert_gone"?"leaked":"failed"}function ee(o){return{ok:"✓",unverifiable:"?",leaked:"!",failed:"✗"}[k(o)]}function se(o){return o.message?o.message:o.skipped?"skipped":k(o)==="ok"?"ok":`exit ${o.code}`}function te(o){return o.filter(i=>i.phase==="assert_gone"&&!i.ok).map(i=>i.axis)}function ae(o){return o.filter(i=>k(i)==="unverifiable").length}const oe={class:"steps stagger"},ne={class:"who"},le={class:"dim",style:{"font-size":"var(--t-sm)"}},re=z({__name:"StepList",props:{steps:{}},setup(o){return(i,t)=>(n(),r("div",null,[e("ul",oe,[(n(!0),r(g,null,D(o.steps,(l,v)=>(n(),r("li",{key:`${l.phase}-${l.axis}-${v}`,class:"step",style:M({"--i":v})},[e("span",{class:_(["mark",`s-${p(k)(l)}`]),"aria-hidden":"true"},u(p(ee)(l)),3),e("span",null,[e("span",ne,u(l.axis),1),e("span",le," · "+u(l.phase),1),e("span",{class:_(["badge",p(k)(l)]),style:{"margin-left":"var(--s2)"}},u(p(k)(l)),3),e("div",{class:_(["msg",`s-${p(k)(l)}`])},u(p(se)(l)),3)])],4))),128))]),t[0]||(t[0]=J('<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))]))}}),ue={class:"page-head"},ie={class:"sub"},de={key:1,class:"panel leaked-banner"},ce={key:2,class:"panel"},pe={class:"pre"},ve={class:"panel"},me={class:"phead"},fe={class:"row",style:{gap:"8px"}},ke={key:0,class:"mute pulse"},be={class:"row",style:{gap:"6px"}},ge={key:0,class:"mute"},he={key:1,class:"mute"},ye={class:"mute"},_e={key:3,class:"panel"},we={class:"phead"},xe={key:0,class:"mute"},Ae=z({__name:"JobDetailView",props:{jobId:{}},setup(o){const i=o,t=f(null),l=f([]),v=f(""),w=f(!0),h=f(!1),x=f(!0),$=f(null);let m=null;const C=R(()=>{var a;return(a=t.value)!=null&&a.outcome?te(t.value.outcome.steps):[]}),S=R(()=>{var a;return(a=t.value)!=null&&a.outcome?ae(t.value.outcome.steps):0});async function j(){var s;const a=await U.get(`/api/jobs/${encodeURIComponent(i.jobId)}`);return w.value=!1,a.ok?(v.value="",t.value=a.body.job,(s=a.body.job.log)!=null&&s.length&&l.value.length===0&&(l.value=[...a.body.job.log]),a.body.job):(v.value=Q(a,"load job"),null)}function y(){m==null||m.close(),m=null,h.value=!1}function A(){y(),m=new EventSource(U.url(`/api/jobs/${encodeURIComponent(i.jobId)}/stream`)),h.value=!0,m.onmessage=a=>{let s;try{s=JSON.parse(a.data)}catch{return}if(s.done){y(),j();return}l.value.push(s),x.value&&queueMicrotask(F)},m.onerror=()=>{y(),j()}}function F(){const a=$.value;a&&(a.scrollTop=a.scrollHeight)}return H(()=>i.jobId,async()=>{l.value=[],t.value=null,w.value=!0;const a=await j();a&&a.state==="running"&&A()},{immediate:!0}),O(y),(a,s)=>{var L,N,T,V;const I=K("RouterLink");return n(),r("div",null,[e("div",ue,[e("div",null,[e("h1",null,[B(I,{to:"/jobs",class:"mute"},{default:P(()=>[...s[1]||(s[1]=[d("Jobs",-1)])]),_:1}),s[2]||(s[2]=e("span",{class:"mute"}," / ",-1)),d(u(((L=t.value)==null?void 0:L.action)??"…"),1)]),e("div",ie,[t.value?(n(),r(g,{key:0},[e("code",null,u(t.value.stack),1),d(" · started "+u(p(q)(t.value.startedAt))+" ",1),t.value.endedAt?(n(),r(g,{key:0},[d(" · took "+u(p(X)(t.value.startedAt,t.value.endedAt)),1)],64)):c("",!0)],64)):(n(),r(g,{key:1},[d(u(o.jobId),1)],64))])]),t.value?(n(),E(Y,{key:0,state:t.value.state},null,8,["state"])):c("",!0)]),v.value?(n(),E(Z,{key:0,text:v.value},null,8,["text"])):c("",!0),C.value.length?(n(),r("section",de,[s[3]||(s[3]=e("strong",null,"Leaked:",-1)),d(" "+u(C.value.join(", "))+" ",1),s[4]||(s[4]=e("div",{class:"mute"},[d(" These axes' "),e("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 "),e("code",null,"verify"),d(". ")],-1))])):c("",!0),(N=t.value)!=null&&N.error?(n(),r("section",ce,[s[5]||(s[5]=e("strong",{class:"bad"},"The job crashed",-1)),e("pre",pe,u(t.value.error),1)])):c("",!0),e("section",ve,[e("div",me,[s[7]||(s[7]=e("h2",{class:"phead-title"},"Log",-1)),e("div",fe,[h.value?(n(),r("span",ke,"streaming…")):c("",!0),e("label",be,[W(e("input",{"onUpdate:modelValue":s[0]||(s[0]=b=>x.value=b),type:"checkbox"},null,512),[[G,x.value]]),s[6]||(s[6]=e("span",{class:"mute"},"Follow",-1))]),!h.value&&((T=t.value)==null?void 0:T.state)==="running"?(n(),r("button",{key:1,class:"btn",onClick:A}," Reconnect ")):c("",!0)])]),e("div",{ref_key:"logEl",ref:$,class:"logbox",role:"log","aria-live":"polite"},[w.value?(n(),r("div",ge,"Loading…")):l.value.length?c("",!0):(n(),r("div",he,"No output.")),(n(!0),r(g,null,D(l.value,b=>(n(),r("div",{key:b.seq,class:_(["logline",b.level])},[e("span",ye,u(p(q)(b.at)),1),d(" "+u(b.message),1)],2))),128))],512)]),(V=t.value)!=null&&V.outcome?(n(),r("section",_e,[e("div",we,[s[10]||(s[10]=e("h2",{class:"phead-title"},"Steps",-1)),S.value?(n(),r("span",xe,[d(u(S.value)+" unverifiable — no ",1),s[8]||(s[8]=e("code",null,"assert_gone",-1)),s[9]||(s[9]=d(", so nothing was checked ",-1))])):c("",!0)]),B(re,{steps:t.value.outcome.steps},null,8,["steps"])])):c("",!0)])}}});export{Ae as default};
2
- //# sourceMappingURL=JobDetailView-Ck89gkKn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"JobDetailView-Ck89gkKn.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 { stamp, took } from '../composables/useFormat';\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);\nconst follow = ref(true);\nconst logEl = ref<HTMLElement | null>(null);\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 if (follow.value) queueMicrotask(scrollToEnd);\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\nfunction scrollToEnd(): void {\n const el = logEl.value;\n if (el) el.scrollTop = el.scrollHeight;\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?.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 class=\"row\" style=\"gap: 8px\">\n <span v-if=\"streaming\" class=\"mute pulse\">streaming…</span>\n <label class=\"row\" style=\"gap: 6px\">\n <input v-model=\"follow\" type=\"checkbox\" />\n <span class=\"mute\">Follow</span>\n </label>\n <button\n v-if=\"!streaming && job?.state === 'running'\"\n class=\"btn\"\n @click=\"stream\"\n >\n Reconnect\n </button>\n </div>\n </div>\n\n <div ref=\"logEl\" class=\"logbox\" role=\"log\" aria-live=\"polite\">\n <div v-if=\"loading\" class=\"mute\">Loading…</div>\n <div v-else-if=\"!lines.length\" class=\"mute\">No output.</div>\n <div v-for=\"l in lines\" :key=\"l.seq\" class=\"logline\" :class=\"l.level\">\n <span class=\"mute\">{{ stamp(l.at) }}</span> {{ l.message }}\n </div>\n </div>\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","follow","logEl","source","leaks","computed","unverifiable","load","r","api","problem","closeStream","stream","ev","payload","scrollToEnd","el","watch","loaded","onBeforeUnmount","_createVNode","_component_RouterLink","_cache","_createTextVNode","stamp","_createBlock","StateBadge","ErrorNote","_b","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","$event","_c","_hoisted_11","_hoisted_12","l","_hoisted_13","_d","_hoisted_14","_hoisted_15","_hoisted_16","StepList"],"mappings":"oaAsBO,SAASA,EAAUC,EAA0B,OAClD,OAAIC,EAAAD,EAAE,UAAF,MAAAC,EAAW,WAAW,gBAAwB,eAC7CD,EAAE,GACA,KADWA,EAAE,QAAU,cAAgB,SAAW,QAE3D,CAEO,SAASE,GAASF,EAAuB,CAC9C,MAAO,CAAE,GAAI,IAAK,aAAc,IAAK,OAAQ,IAAK,OAAQ,GAAA,EAAMD,EAAUC,CAAC,CAAC,CAC9E,CAEO,SAASG,GAASH,EAAuB,CAC9C,OAAIA,EAAE,QAAgBA,EAAE,QACpBA,EAAE,QAAgB,UACfD,EAAUC,CAAC,IAAM,KAAO,KAAO,QAAQA,EAAE,IAAI,EACtD,CAGO,SAASI,GAAWC,EAA+B,CACxD,OAAOA,EAAM,OAAQL,GAAMA,EAAE,QAAU,eAAiB,CAACA,EAAE,EAAE,EAAE,IAAKA,GAAMA,EAAE,IAAI,CAClF,CAEO,SAASM,GAAkBD,EAA6B,CAC7D,OAAOA,EAAM,OAAQL,GAAMD,EAAUC,CAAC,IAAM,cAAc,EAAE,MAC9D,0KC/BEO,EA4BM,MAAA,KAAA,CA3BJC,EAiBK,KAjBLC,GAiBK,EAhBHC,EAAA,EAAA,EAAAH,EAeKI,EAAA,KAAAC,EAdcC,EAAA,MAAK,CAAdb,EAAGc,SADbP,EAeK,KAAA,CAbF,IAAG,GAAKP,EAAE,KAAK,IAAIA,EAAE,IAAI,IAAIc,CAAC,GAC/B,MAAM,OACL,eAAgBA,EAAC,CAAA,GAElBN,EAA2F,OAAA,CAArF,MAAKO,EAAA,CAAC,OAAM,KAAcC,EAAAjB,CAAA,EAAUC,CAAC,CAAA,EAAA,CAAA,EAAK,cAAY,MAAA,EAAUiB,EAAAD,EAAAd,EAAA,EAASF,CAAC,CAAA,EAAA,CAAA,EAChFQ,EAOO,OAAA,KAAA,CANLA,EAAqC,OAArCU,GAAqCD,EAAhBjB,EAAE,IAAI,EAAA,CAAA,EAC3BQ,EAAwE,OAAxEW,GAAiD,MAAGF,EAAGjB,EAAE,KAAK,EAAA,CAAA,EAC9DQ,EAEO,OAAA,CAFD,MAAKO,EAAA,CAAC,QAAgBC,EAAAjB,CAAA,EAAUC,CAAC,CAAA,CAAA,EAAG,MAAA,CAAA,cAAA,WAAA,CAAA,EACrCiB,EAAAD,EAAAjB,CAAA,EAAUC,CAAC,CAAA,EAAA,CAAA,EAEhBQ,EAAqE,MAAA,CAAhE,MAAKO,EAAA,CAAC,MAAK,KAAcC,EAAAjB,CAAA,EAAUC,CAAC,CAAA,EAAA,CAAA,CAAA,EAAQiB,EAAAD,EAAAb,EAAA,EAASH,CAAC,CAAA,EAAA,CAAA,CAAA,64BCXrE,MAAMoB,EAAQP,EAERQ,EAAMC,EAAgB,IAAI,EAC1BC,EAAQD,EAAgB,EAAE,EAC1BE,EAAQF,EAAI,EAAE,EACdG,EAAUH,EAAI,EAAI,EAClBI,EAAYJ,EAAI,EAAK,EACrBK,EAASL,EAAI,EAAI,EACjBM,EAAQN,EAAwB,IAAI,EAE1C,IAAIO,EAA6B,KAEjC,MAAMC,EAAQC,EAAS,WAAO,OAAA9B,EAAAoB,EAAI,QAAJ,MAAApB,EAAW,QAAUG,GAAWiB,EAAI,MAAM,QAAQ,KAAK,EAAI,CAAA,EAAG,EACtFW,EAAeD,EAAS,WAC5B,OAAA9B,EAAAoB,EAAI,QAAJ,MAAApB,EAAW,QAAUK,GAAkBe,EAAI,MAAM,QAAQ,KAAK,EAAI,EAAA,EAIpE,eAAeY,GAA4B,OACzC,MAAMC,EAAI,MAAMC,EAAI,IAAkB,aAAa,mBAAmBf,EAAM,KAAK,CAAC,EAAE,EAEpF,OADAK,EAAQ,MAAQ,GACXS,EAAE,IAIPV,EAAM,MAAQ,GACdH,EAAI,MAAQa,EAAE,KAAK,KAGfjC,EAAAiC,EAAE,KAAK,IAAI,MAAX,MAAAjC,EAAgB,QAAUsB,EAAM,MAAM,SAAW,IAAGA,EAAM,MAAQ,CAAC,GAAGW,EAAE,KAAK,IAAI,GAAG,GACjFA,EAAE,KAAK,MARZV,EAAM,MAAQY,EAAQF,EAAG,UAAU,EAC5B,KAQX,CAEA,SAASG,GAAoB,CAC3BR,GAAA,MAAAA,EAAQ,QACRA,EAAS,KACTH,EAAU,MAAQ,EACpB,CAEA,SAASY,GAAe,CACtBD,EAAA,EAGAR,EAAS,IAAI,YAAYM,EAAI,IAAI,aAAa,mBAAmBf,EAAM,KAAK,CAAC,SAAS,CAAC,EACvFM,EAAU,MAAQ,GAElBG,EAAO,UAAaU,GAAO,CACzB,IAAIC,EACJ,GAAI,CACFA,EAAU,KAAK,MAAMD,EAAG,IAAc,CACxC,MAAQ,CACN,MACF,CAEA,GADaC,EACJ,KAAM,CACbH,EAAA,EACKJ,EAAA,EACL,MACF,CACAV,EAAM,MAAM,KAAKiB,CAAmB,EAChCb,EAAO,OAAO,eAAec,CAAW,CAC9C,EAIAZ,EAAO,QAAU,IAAM,CACrBQ,EAAA,EACKJ,EAAA,CACP,CACF,CAEA,SAASQ,GAAoB,CAC3B,MAAMC,EAAKd,EAAM,MACbc,IAAIA,EAAG,UAAYA,EAAG,aAC5B,CAEA,OAAAC,EACE,IAAMvB,EAAM,MACZ,SAAY,CACVG,EAAM,MAAQ,CAAA,EACdF,EAAI,MAAQ,KACZI,EAAQ,MAAQ,GAIhB,MAAMmB,EAAS,MAAMX,EAAA,EACjBW,GAAUA,EAAO,QAAU,WAAWN,EAAA,CAC5C,EACA,CAAE,UAAW,EAAA,CAAK,EAGpBO,EAAgBR,CAAW,yDAIzB9B,EA0EM,MAAA,KAAA,CAzEJC,EAeM,MAfNC,GAeM,CAdJD,EAYM,MAAA,KAAA,CAXJA,EAGK,KAAA,KAAA,CAFHsC,EAAqDC,EAAA,CAAzC,GAAG,QAAQ,MAAM,MAAA,aAAO,IAAI,CAAA,GAAAC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAAJ,OAAI,EAAA,CAAA,WACxCA,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAxC,EAA6B,OAAA,CAAvB,MAAM,MAAA,EAAO,MAAG,EAAA,GAAUyC,EAAAhC,IAAAhB,EAAAoB,EAAA,QAAA,YAAApB,EAAK,SAAM,GAAA,EAAA,CAAA,CAAA,GAE7CO,EAMM,MANNU,GAMM,CALYG,EAAA,WAAhBd,EAGWI,EAAA,CAAA,IAAA,GAAA,CAFTH,EAA4B,OAAA,KAAAS,EAAnBI,EAAA,MAAI,KAAK,EAAA,CAAA,EAAU4B,EAAA,gBAAcjC,EAAAkC,CAAA,EAAM7B,QAAI,SAAS,GAAI,IACjE,CAAA,EAAgBA,EAAA,MAAI,aAApBd,EAAsFI,EAAA,CAAA,IAAA,GAAA,GAAzD,WAAQM,EAAGD,KAAKK,EAAA,MAAI,UAAWA,EAAA,MAAI,OAAO,CAAA,EAAA,CAAA,CAAA,2BAEzEd,EAAuCI,EAAA,CAAA,IAAA,GAAA,KAAnBE,EAAA,KAAK,EAAA,CAAA,CAAA,WAGXQ,EAAA,WAAlB8B,EAA4CC,EAAA,OAApB,MAAO/B,EAAA,MAAI,KAAA,gCAGpBG,EAAA,WAAjB2B,EAAwCE,EAAA,OAAf,KAAM7B,EAAA,KAAA,6BAMhBM,EAAA,MAAM,QAArBpB,IAAAH,EAMU,UANVY,GAMU,CALR6B,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAxC,EAAwB,cAAhB,UAAO,EAAA,GAASyC,EAAA,IAAChC,EAAGa,EAAA,MAAM,YAAa,IAC/C,CAAA,cAAAtB,EAGM,MAAA,CAHD,MAAM,QAAM,GAAC,eACJ,EAAAA,EAAwB,YAAlB,aAAW,IAAO,8GACiB,EAAAA,EAAmB,YAAb,QAAM,IAAO,IAC1E,CAAA,oBAGa8C,EAAAjC,EAAA,QAAA,MAAAiC,EAAK,OAApB5C,IAAAH,EAGU,UAHVgD,GAGU,CAFRP,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAxC,EAA4C,SAAA,CAApC,MAAM,KAAA,EAAM,kBAAe,EAAA,GACnCA,EAAsC,MAAtCgD,GAAsCvC,EAAlBI,EAAA,MAAI,KAAK,EAAA,CAAA,CAAA,aAG/Bb,EA0BU,UA1BViD,GA0BU,CAzBRjD,EAgBM,MAhBNkD,GAgBM,CAfJV,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAxC,EAAgC,KAAA,CAA5B,MAAM,aAAA,EAAc,MAAG,EAAA,GAC3BA,EAaM,MAbNmD,GAaM,CAZQjC,EAAA,WAAZnB,EAA2D,OAA3DqD,GAA0C,YAAU,YACpDpD,EAGQ,QAHRqD,GAGQ,GAFNrD,EAA0C,QAAA,sCAA1BmB,EAAM,MAAAmC,GAAE,KAAK,UAAA,gBAAbnC,EAAA,KAAM,CAAA,GACtBqB,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAxC,EAAgC,OAAA,CAA1B,MAAM,QAAO,SAAM,EAAA,EAAA,IAGlBkB,EAAA,SAAaqC,EAAA1C,EAAA,QAAA,YAAA0C,EAAK,SAAK,eADhCxD,EAMS,SAAA,OAJP,MAAM,MACL,QAAO+B,CAAA,EACT,aAED,gBAIJ9B,EAMM,MAAA,SANG,QAAJ,IAAIoB,EAAQ,MAAM,SAAS,KAAK,MAAM,YAAU,QAAA,GACxCH,EAAA,WAAXlB,EAA+C,MAA/CyD,GAAiC,UAAQ,GACxBzC,EAAA,MAAM,qBAAvBhB,EAA4D,MAA5D0D,GAA4C,YAAU,UACtD1D,EAEMI,EAAA,KAAAC,EAFWW,EAAA,MAAL2C,QAAZ3D,EAEM,MAAA,CAFmB,IAAK2D,EAAE,IAAK,MAAKnD,EAAA,CAAC,UAAkBmD,EAAE,KAAK,CAAA,CAAA,GAClE1D,EAA2C,OAA3C2D,GAA2ClD,EAArBD,KAAMkD,EAAE,EAAE,CAAA,EAAA,CAAA,IAAW,IAACjD,EAAGiD,EAAE,OAAO,EAAA,CAAA,CAAA,sBAK/CE,EAAA/C,EAAA,QAAA,MAAA+C,EAAK,SAApB1D,IAAAH,EAQU,UARV8D,GAQU,CAPR7D,EAKM,MALN8D,GAKM,CAJJtB,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAxC,EAAkC,KAAA,CAA9B,MAAM,aAAA,EAAc,QAAK,EAAA,GACjBwB,EAAA,OAAZtB,EAAA,EAAAH,EAEO,OAFPgE,GAEO,CADFtB,EAAAhC,EAAAe,EAAA,KAAY,EAAG,sBAAmB,CAAA,EAAAgB,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAxC,EAAwB,YAAlB,cAAW,EAAA,iBAAO,4BAC/D,EAAA,EAAA,eAEFsC,EAAuC0B,GAAA,CAA5B,MAAOnD,EAAA,MAAI,QAAQ,KAAA"}
@@ -1,2 +0,0 @@
1
- import{d as y,c as a,a as t,x as _,y as g,z as w,E as x,b as l,s as r,t as n,g as C,F as u,r as V,e as p,f as N,j as v,m as L,o,q as S,h as m,w as k,n as q}from"./index--8-Mcu3f.js";import{s as z,t as A}from"./useFormat-CNPlQbIu.js";import{_ as B}from"./StateBadge.vue_vue_type_script_setup_true_lang-BJu6FAcJ.js";import{_ as E}from"./SkeletonList.vue_vue_type_script_setup_true_lang-ECJq9map.js";const R={class:"panel"},U={class:"phead"},$={class:"field",style:{flex:"1","min-width":"180px"}},M={class:"field"},T={key:0,class:"s-failed"},j={key:2,class:"cards"},D={class:"stagger"},F={"data-label":"state"},I={class:"mono","data-label":"action"},J={class:"name","data-label":"stack"},H={class:"mono dim nowrap","data-label":"started"},G={class:"mono dim nowrap","data-label":"took"},K={class:"dim","data-label":"steps"},O={key:0},P={colspan:"6",class:"mute"},tt=y({__name:"JobsView",setup(Q){const c=v(""),i=v("all"),h=L(()=>{const d=c.value.trim().toLowerCase();return r.jobs.filter(e=>i.value!=="all"&&e.state!==i.value?!1:d?e.stack.toLowerCase().includes(d)||e.action.includes(d):!0)});return(d,e)=>{const f=q("RouterLink");return o(),a("div",null,[e[7]||(e[7]=t("div",{class:"page-head"},[t("div",null,[t("h1",null,"Jobs"),t("div",{class:"sub"},"Most recent first, capped at the last 50.")])],-1)),t("section",R,[t("div",U,[t("div",$,[e[2]||(e[2]=t("label",{for:"jq",class:"mute",style:{"font-size":"var(--t-xs)"}},"Search — press /",-1)),_(t("input",{id:"jq","onUpdate:modelValue":e[0]||(e[0]=s=>c.value=s),"data-search":"",type:"search",placeholder:"stack or action",spellcheck:"false"},null,512),[[g,c.value]])]),t("div",M,[e[4]||(e[4]=t("label",{for:"js",class:"mute",style:{"font-size":"var(--t-xs)"}},"State",-1)),_(t("select",{id:"js","onUpdate:modelValue":e[1]||(e[1]=s=>i.value=s)},[...e[3]||(e[3]=[x('<option value="all">all</option><option value="running">running</option><option value="ok">ok</option><option value="leaked">leaked</option><option value="failed">failed</option>',5)])],512),[[w,i.value]])])]),e[6]||(e[6]=t("p",{class:"hint",style:{margin:"0 0 var(--s3)"}}," Held in memory. A server restart clears the history — a job record is the transcript of an attempt, not the truth about what exists. That truth lives in docker and in each axis's own probe. ",-1)),l(r).jobsError?(o(),a("p",T,n(l(r).jobsError),1)):l(r).jobsLoaded?(o(),a("table",j,[e[5]||(e[5]=t("thead",null,[t("tr",null,[t("th",null,"state"),t("th",null,"action"),t("th",null,"stack"),t("th",null,"started"),t("th",null,"took"),t("th",null,"steps")])],-1)),t("tbody",D,[(o(!0),a(u,null,V(h.value,(s,b)=>(o(),a("tr",{key:s.id,style:S({"--i":b})},[t("td",F,[m(f,{to:`/jobs/${encodeURIComponent(s.id)}`},{default:k(()=>[m(B,{state:s.state},null,8,["state"])]),_:2},1032,["to"])]),t("td",I,n(s.action),1),t("td",J,[m(f,{to:`/jobs/${encodeURIComponent(s.id)}`},{default:k(()=>[p(n(s.stack),1)]),_:2},1032,["to"])]),t("td",H,n(l(z)(s.startedAt)),1),t("td",G,n(l(A)(s.startedAt,s.endedAt)),1),t("td",K,n(s.outcome?s.outcome.steps.length:"—"),1)],4))),128)),h.value.length?N("",!0):(o(),a("tr",O,[t("td",P,[l(r).jobs.length?(o(),a(u,{key:0},[p("Nothing matches this filter.")],64)):(o(),a(u,{key:1},[p("No jobs recorded.")],64))])]))])])):(o(),C(E,{key:1,rows:5}))])])}}});export{tt as default};
2
- //# sourceMappingURL=JobsView-0OjmomhQ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"JobsView-0OjmomhQ.js","sources":["../../src/views/JobsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Job history. Polled by the shell already, so this view only filters what is in memory. */\nimport { computed, ref } from 'vue';\nimport { state } from '../composables/useControlPlane';\nimport { stamp, took } from '../composables/useFormat';\nimport StateBadge from '../components/StateBadge.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport type { JobState } from '../api/types';\n\nconst q = ref('');\nconst only = ref<'all' | JobState>('all');\n\nconst rows = computed(() => {\n const needle = q.value.trim().toLowerCase();\n return state.jobs.filter((j) => {\n if (only.value !== 'all' && j.state !== only.value) return false;\n if (!needle) return true;\n return j.stack.toLowerCase().includes(needle) || j.action.includes(needle);\n });\n});\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Jobs</h1>\n <div class=\"sub\">Most recent first, capped at the last 50.</div>\n </div>\n </div>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <div class=\"field\" style=\"flex: 1; min-width: 180px\">\n <label for=\"jq\" class=\"mute\" style=\"font-size: var(--t-xs)\">Search — press /</label>\n <input id=\"jq\" v-model=\"q\" data-search type=\"search\" placeholder=\"stack or action\" spellcheck=\"false\" />\n </div>\n <div class=\"field\">\n <label for=\"js\" class=\"mute\" style=\"font-size: var(--t-xs)\">State</label>\n <select id=\"js\" v-model=\"only\">\n <option value=\"all\">all</option>\n <option value=\"running\">running</option>\n <option value=\"ok\">ok</option>\n <option value=\"leaked\">leaked</option>\n <option value=\"failed\">failed</option>\n </select>\n </div>\n </div>\n\n <p class=\"hint\" style=\"margin: 0 0 var(--s3)\">\n Held in memory. A server restart clears the history — a job record is the transcript of an\n attempt, not the truth about what exists. That truth lives in docker and in each axis's own\n probe.\n </p>\n\n <p v-if=\"state.jobsError\" class=\"s-failed\">{{ state.jobsError }}</p>\n <SkeletonList v-else-if=\"!state.jobsLoaded\" :rows=\"5\" />\n\n <table v-else class=\"cards\">\n <thead>\n <tr>\n <th>state</th>\n <th>action</th>\n <th>stack</th>\n <th>started</th>\n <th>took</th>\n <th>steps</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(j, i) in rows\" :key=\"j.id\" :style=\"{ '--i': i }\">\n <td data-label=\"state\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(j.id)}`\">\n <StateBadge :state=\"j.state\" />\n </RouterLink>\n </td>\n <td class=\"mono\" data-label=\"action\">{{ j.action }}</td>\n <td class=\"name\" data-label=\"stack\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(j.id)}`\">{{ j.stack }}</RouterLink>\n </td>\n <td class=\"mono dim nowrap\" data-label=\"started\">{{ stamp(j.startedAt) }}</td>\n <td class=\"mono dim nowrap\" data-label=\"took\">{{ took(j.startedAt, j.endedAt) }}</td>\n <td class=\"dim\" data-label=\"steps\">{{ j.outcome ? j.outcome.steps.length : '—' }}</td>\n </tr>\n <tr v-if=\"!rows.length\">\n <td colspan=\"6\" class=\"mute\">\n <template v-if=\"state.jobs.length\">Nothing matches this filter.</template>\n <template v-else>No jobs recorded.</template>\n </td>\n </tr>\n </tbody>\n </table>\n </section>\n </div>\n</template>\n"],"names":["q","ref","only","rows","computed","needle","state","j","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_hoisted_3","$event","_hoisted_4","_unref","_openBlock","_hoisted_5","_toDisplayString","_hoisted_6","_hoisted_7","_Fragment","_renderList","i","_hoisted_8","_createVNode","_component_RouterLink","StateBadge","_hoisted_9","_hoisted_10","_createTextVNode","_hoisted_11","_hoisted_12","took","_hoisted_13","_hoisted_14","_hoisted_15","_createBlock","SkeletonList"],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- import{d as w,c as t,a as s,x as g,z as _,b as h,e as l,t as y,f as p,F as C,y as F,j as v,k as L,G as T,p as U,m as V,o}from"./index--8-Mcu3f.js";import{d as i}from"./useDeployment-ByQUNe_X.js";import"./useVars-VN9gYdR6.js";const z={class:"panel"},M={class:"phead"},N={class:"field"},j=["disabled"],B={key:0,class:"progress","aria-hidden":"true"},E={key:0,class:"mute"},R={key:1,class:"mute"},S={key:0,class:"banner failed"},$={key:1,class:"banner warn"},A={key:2,class:"field",style:{"margin-top":"var(--s3)"}},D={key:3,class:"log"},G={key:4,class:"mute",style:{"margin-top":"var(--s3)"}},I={key:5,class:"hint"},K=w({__name:"LogsTab",setup(q){const c=v(200),m=v(""),a=v(null),f=v(""),r=v(!1);async function b(){var n;if(!((n=i.detail)!=null&&n.compose))return;const d=i.id;r.value=!0,f.value="";const e=await L.get(`/api/deployments/${encodeURIComponent(d)}/logs${T(i.vars,{tail:c.value})}`);if(r.value=!1,i.id===d){if(!e.ok){a.value=null,f.value=U(e,"GET logs");return}a.value={...e.body,ok:e.body.ok!==!1,text:e.body.text??""}}}const k=V(()=>{var u;const d=((u=a.value)==null?void 0:u.text)??"",e=m.value.trim().toLowerCase();if(!e)return d;const n=d.split(`
2
- `).filter(x=>x.toLowerCase().includes(e));return n.length?n.join(`
3
- `):`(no line matches "${m.value}")`});return(d,e)=>{var n;return o(),t("section",z,[s("div",M,[e[4]||(e[4]=s("h2",{class:"section"},"Compose logs",-1)),e[5]||(e[5]=s("span",{class:"grow"},null,-1)),s("div",N,[e[3]||(e[3]=s("label",{for:"tail",class:"mute",style:{"font-size":"var(--t-xs)"}},"tail",-1)),g(s("select",{id:"tail","onUpdate:modelValue":e[0]||(e[0]=u=>c.value=u)},[...e[2]||(e[2]=[s("option",{value:50},"50",-1),s("option",{value:200},"200",-1),s("option",{value:500},"500",-1),s("option",{value:1e3},"1000",-1),s("option",{value:2e3},"2000",-1)])],512),[[_,c.value,void 0,{number:!0}]])]),s("button",{class:"primary",style:{"align-self":"end"},disabled:r.value||!((n=h(i).detail)!=null&&n.compose),onClick:b},[l(y(r.value?"reading…":"Fetch")+" ",1),r.value?(o(),t("span",B)):p("",!0)],8,j)]),h(i).detail?h(i).detail.compose?(o(),t(C,{key:2},[e[12]||(e[12]=s("p",{class:"hint",style:{margin:"0 0 var(--s3)"}},[l(" Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer the host. "),s("code",null,"tail"),l(" is clamped to 1–2000 by the server. ")],-1)),f.value?(o(),t("div",S,y(f.value),1)):a.value&&!a.value.ok?(o(),t("div",$,[...e[7]||(e[7]=[s("b",null,"compose exited non-zero.",-1),s("p",null," Most often the project does not exist yet (nothing has been brought up), or a profile is missing. Any output it did produce is below. ",-1)])])):p("",!0),a.value?(o(),t("div",A,[e[8]||(e[8]=s("label",{for:"lf",class:"mute",style:{"font-size":"var(--t-xs)"}},"Search in these lines",-1)),g(s("input",{id:"lf","onUpdate:modelValue":e[1]||(e[1]=u=>m.value=u),"data-search":"",type:"search",placeholder:"substring",spellcheck:"false"},null,512),[[F,m.value]])])):p("",!0),a.value?(o(),t("pre",D,y(k.value||"(no output)"),1)):r.value?p("",!0):(o(),t("p",G,[e[9]||(e[9]=l(" Press ",-1)),e[10]||(e[10]=s("b",null,"Fetch",-1)),l(" to read the last "+y(c.value)+" lines. ",1)])),a.value?(o(),t("p",I,[...e[11]||(e[11]=[l(" Redacted on the host before sending: credentials inside URLs, ",-1),s("code",null,"NAME=value",-1),l(" pairs whose name reads as a secret, and this server's own token. ",-1)])])):p("",!0)],64)):(o(),t("p",R,[...e[6]||(e[6]=[l(" This spec has no ",-1),s("code",null,"compose:",-1),l(" section, so there are no compose logs to read. Its axes may still have created resources — check those at their own source. ",-1)])])):(o(),t("p",E,"Unavailable until the spec resolves."))])}}});export{K as default};
4
- //# sourceMappingURL=LogsTab-Bb2E4F-n.js.map