@samyx/preview-stacks-ui 0.39.1 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/assets/{ActionButton-OewKsFrK.js → ActionButton-BaGfzZKv.js} +2 -2
  2. package/dist/assets/{ActionButton-OewKsFrK.js.map → ActionButton-BaGfzZKv.js.map} +1 -1
  3. package/dist/assets/{AxesTab-DSzgRhgG.js → AxesTab-8px21p5I.js} +2 -2
  4. package/dist/assets/{AxesTab-DSzgRhgG.js.map → AxesTab-8px21p5I.js.map} +1 -1
  5. package/dist/assets/{ConfigTab-DhtUF0Dh.js → ConfigTab-XMpNHvgd.js} +2 -2
  6. package/dist/assets/{ConfigTab-DhtUF0Dh.js.map → ConfigTab-XMpNHvgd.js.map} +1 -1
  7. package/dist/assets/{ConfigView-wXO4j8H7.js → ConfigView-Ed9sCukp.js} +2 -2
  8. package/dist/assets/{ConfigView-wXO4j8H7.js.map → ConfigView-Ed9sCukp.js.map} +1 -1
  9. package/dist/assets/{ConflictNote.vue_vue_type_script_setup_true_lang-B2fgJAmK.js → ConflictNote.vue_vue_type_script_setup_true_lang-B4a9DxS8.js} +2 -2
  10. package/dist/assets/{ConflictNote.vue_vue_type_script_setup_true_lang-B2fgJAmK.js.map → ConflictNote.vue_vue_type_script_setup_true_lang-B4a9DxS8.js.map} +1 -1
  11. package/dist/assets/ControlView-B-gr_xQ3.css +1 -0
  12. package/dist/assets/ControlView-C4R1joVF.js +2 -0
  13. package/dist/assets/ControlView-C4R1joVF.js.map +1 -0
  14. package/dist/assets/{DangerTab-D2dFrj4t.js → DangerTab-CMIMyWEv.js} +2 -2
  15. package/dist/assets/{DangerTab-D2dFrj4t.js.map → DangerTab-CMIMyWEv.js.map} +1 -1
  16. package/dist/assets/{DashboardView-FpEFhaCl.js → DashboardView-gTjtsiF-.js} +2 -2
  17. package/dist/assets/{DashboardView-FpEFhaCl.js.map → DashboardView-gTjtsiF-.js.map} +1 -1
  18. package/dist/assets/{DeploymentDetailView-B1dKUzvp.js → DeploymentDetailView-Dg-9p6_v.js} +2 -2
  19. package/dist/assets/{DeploymentDetailView-B1dKUzvp.js.map → DeploymentDetailView-Dg-9p6_v.js.map} +1 -1
  20. package/dist/assets/{DeploymentsView-_V93UC6F.js → DeploymentsView-CNzMaxE7.js} +2 -2
  21. package/dist/assets/{DeploymentsView-_V93UC6F.js.map → DeploymentsView-CNzMaxE7.js.map} +1 -1
  22. package/dist/assets/{EquivalentCommand-D-mkbReY.js → EquivalentCommand-CAhOGrAR.js} +2 -2
  23. package/dist/assets/{EquivalentCommand-D-mkbReY.js.map → EquivalentCommand-CAhOGrAR.js.map} +1 -1
  24. package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-FEOV8vdM.js → ErrorNote.vue_vue_type_script_setup_true_lang-DU01Dqor.js} +2 -2
  25. package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-FEOV8vdM.js.map → ErrorNote.vue_vue_type_script_setup_true_lang-DU01Dqor.js.map} +1 -1
  26. package/dist/assets/{JobDetailView-RD5MxdA7.js → JobDetailView-ChKP7xeI.js} +2 -2
  27. package/dist/assets/{JobDetailView-RD5MxdA7.js.map → JobDetailView-ChKP7xeI.js.map} +1 -1
  28. package/dist/assets/{JobsView-BJrH9ViP.js → JobsView-DndjCwKL.js} +2 -2
  29. package/dist/assets/{JobsView-BJrH9ViP.js.map → JobsView-DndjCwKL.js.map} +1 -1
  30. package/dist/assets/{LogViewer.vue_vue_type_script_setup_true_lang-BRSt9Exf.js → LogViewer.vue_vue_type_script_setup_true_lang-D2yEiI2n.js} +2 -2
  31. package/dist/assets/{LogViewer.vue_vue_type_script_setup_true_lang-BRSt9Exf.js.map → LogViewer.vue_vue_type_script_setup_true_lang-D2yEiI2n.js.map} +1 -1
  32. package/dist/assets/LoginView-B9aLjuvQ.css +1 -0
  33. package/dist/assets/LoginView-C2Yny6iz.js +2 -0
  34. package/dist/assets/LoginView-C2Yny6iz.js.map +1 -0
  35. package/dist/assets/{LogsTab--Q-KHeWj.js → LogsTab-B6R6j6Uv.js} +2 -2
  36. package/dist/assets/{LogsTab--Q-KHeWj.js.map → LogsTab-B6R6j6Uv.js.map} +1 -1
  37. package/dist/assets/{NotFoundView-1lI66Xt-.js → NotFoundView-CvHMcInC.js} +2 -2
  38. package/dist/assets/{NotFoundView-1lI66Xt-.js.map → NotFoundView-CvHMcInC.js.map} +1 -1
  39. package/dist/assets/{NotifiersView-BfWfRzg4.js → NotifiersView-9rwo6wXV.js} +2 -2
  40. package/dist/assets/{NotifiersView-BfWfRzg4.js.map → NotifiersView-9rwo6wXV.js.map} +1 -1
  41. package/dist/assets/{OverviewTab-P3htOnPs.js → OverviewTab-BO8-8pTB.js} +2 -2
  42. package/dist/assets/{OverviewTab-P3htOnPs.js.map → OverviewTab-BO8-8pTB.js.map} +1 -1
  43. package/dist/assets/{PublicLogsView-DcDQMivg.js → PublicLogsView-BA7ao_jC.js} +2 -2
  44. package/dist/assets/{PublicLogsView-DcDQMivg.js.map → PublicLogsView-BA7ao_jC.js.map} +1 -1
  45. package/dist/assets/{RefreshButton.vue_vue_type_script_setup_true_lang-CQvD4Out.js → RefreshButton.vue_vue_type_script_setup_true_lang-Dk-lSii3.js} +2 -2
  46. package/dist/assets/{RefreshButton.vue_vue_type_script_setup_true_lang-CQvD4Out.js.map → RefreshButton.vue_vue_type_script_setup_true_lang-Dk-lSii3.js.map} +1 -1
  47. package/dist/assets/{RegistriesView-9hPjVsOW.js → RegistriesView-y9nKbmQz.js} +2 -2
  48. package/dist/assets/{RegistriesView-9hPjVsOW.js.map → RegistriesView-y9nKbmQz.js.map} +1 -1
  49. package/dist/assets/{RelativeTime.vue_vue_type_script_setup_true_lang-BxIqRu_b.js → RelativeTime.vue_vue_type_script_setup_true_lang-B1_l0Vtf.js} +2 -2
  50. package/dist/assets/{RelativeTime.vue_vue_type_script_setup_true_lang-BxIqRu_b.js.map → RelativeTime.vue_vue_type_script_setup_true_lang-B1_l0Vtf.js.map} +1 -1
  51. package/dist/assets/{RequiresTab--C-3QPQB.js → RequiresTab-DJZagYBh.js} +2 -2
  52. package/dist/assets/{RequiresTab--C-3QPQB.js.map → RequiresTab-DJZagYBh.js.map} +1 -1
  53. package/dist/assets/{RouteTarget.vue_vue_type_script_setup_true_lang-D3FLq6Mt.js → RouteTarget.vue_vue_type_script_setup_true_lang-Ct40NR9a.js} +2 -2
  54. package/dist/assets/{RouteTarget.vue_vue_type_script_setup_true_lang-D3FLq6Mt.js.map → RouteTarget.vue_vue_type_script_setup_true_lang-Ct40NR9a.js.map} +1 -1
  55. package/dist/assets/{RoutingView-CC8xPrcT.js → RoutingView-DzE98-qV.js} +2 -2
  56. package/dist/assets/{RoutingView-CC8xPrcT.js.map → RoutingView-DzE98-qV.js.map} +1 -1
  57. package/dist/assets/{RuntimeTab-Do1xfTp8.js → RuntimeTab-Cp6CxBJG.js} +2 -2
  58. package/dist/assets/{RuntimeTab-Do1xfTp8.js.map → RuntimeTab-Cp6CxBJG.js.map} +1 -1
  59. package/dist/assets/{SelectMenu.vue_vue_type_script_setup_true_lang-CMLYr7A7.js → SelectMenu.vue_vue_type_script_setup_true_lang-PIaLSNsk.js} +2 -2
  60. package/dist/assets/{SelectMenu.vue_vue_type_script_setup_true_lang-CMLYr7A7.js.map → SelectMenu.vue_vue_type_script_setup_true_lang-PIaLSNsk.js.map} +1 -1
  61. package/dist/assets/{SettingsView-Dxbl6HZA.js → SettingsView-iWRluEj5.js} +2 -2
  62. package/dist/assets/{SettingsView-Dxbl6HZA.js.map → SettingsView-iWRluEj5.js.map} +1 -1
  63. package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-D4pkKZAr.js → SkeletonList.vue_vue_type_script_setup_true_lang-7pKEuaEM.js} +2 -2
  64. package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-D4pkKZAr.js.map → SkeletonList.vue_vue_type_script_setup_true_lang-7pKEuaEM.js.map} +1 -1
  65. package/dist/assets/{SpecDetailView-DyFAAMfr.js → SpecDetailView-Ca4WXLH1.js} +2 -2
  66. package/dist/assets/{SpecDetailView-DyFAAMfr.js.map → SpecDetailView-Ca4WXLH1.js.map} +1 -1
  67. package/dist/assets/{SpecsView-CfV1_GDx.js → SpecsView-DN6EIbcC.js} +2 -2
  68. package/dist/assets/{SpecsView-CfV1_GDx.js.map → SpecsView-DN6EIbcC.js.map} +1 -1
  69. package/dist/assets/{SsoMark.vue_vue_type_script_setup_true_lang-BU6ThRhq.js → SsoMark.vue_vue_type_script_setup_true_lang-rMP20d_u.js} +2 -2
  70. package/dist/assets/{SsoMark.vue_vue_type_script_setup_true_lang-BU6ThRhq.js.map → SsoMark.vue_vue_type_script_setup_true_lang-rMP20d_u.js.map} +1 -1
  71. package/dist/assets/{SsoView-CxFylJ9d.js → SsoView-wGpab5Q8.js} +2 -2
  72. package/dist/assets/{SsoView-CxFylJ9d.js.map → SsoView-wGpab5Q8.js.map} +1 -1
  73. package/dist/assets/{StateBadge.vue_vue_type_script_setup_true_lang-CfjzlWSc.js → StateBadge.vue_vue_type_script_setup_true_lang-aK93ZMhG.js} +2 -2
  74. package/dist/assets/{StateBadge.vue_vue_type_script_setup_true_lang-CfjzlWSc.js.map → StateBadge.vue_vue_type_script_setup_true_lang-aK93ZMhG.js.map} +1 -1
  75. package/dist/assets/{SubmitView-BcAcTlwC.js → SubmitView-BTK9zNDd.js} +2 -2
  76. package/dist/assets/{SubmitView-BcAcTlwC.js.map → SubmitView-BTK9zNDd.js.map} +1 -1
  77. package/dist/assets/SwarmView-DXLQlbD0.css +1 -0
  78. package/dist/assets/SwarmView-m2XvvAfR.js +2 -0
  79. package/dist/assets/SwarmView-m2XvvAfR.js.map +1 -0
  80. package/dist/assets/{TerminalTab-DF8cSjcg.js → TerminalTab-CFKfVx8D.js} +2 -2
  81. package/dist/assets/{TerminalTab-DF8cSjcg.js.map → TerminalTab-CFKfVx8D.js.map} +1 -1
  82. package/dist/assets/{UsersView-CbPtmnP6.js → UsersView-BnN_hvh2.js} +2 -2
  83. package/dist/assets/{UsersView-CbPtmnP6.js.map → UsersView-BnN_hvh2.js.map} +1 -1
  84. package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-BNQTEru_.js → VarEditor.vue_vue_type_script_setup_true_lang-Dg_ORvZN.js} +2 -2
  85. package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-BNQTEru_.js.map → VarEditor.vue_vue_type_script_setup_true_lang-Dg_ORvZN.js.map} +1 -1
  86. package/dist/assets/{VarIO-BFBaI8oU.js → VarIO-CNugbzeb.js} +2 -2
  87. package/dist/assets/{VarIO-BFBaI8oU.js.map → VarIO-CNugbzeb.js.map} +1 -1
  88. package/dist/assets/{VariablesView-BEcI8Bi0.js → VariablesView-qrqDNseI.js} +2 -2
  89. package/dist/assets/{VariablesView-BEcI8Bi0.js.map → VariablesView-qrqDNseI.js.map} +1 -1
  90. package/dist/assets/{index-D_Ks6aQi.js → index-DlhYSSKq.js} +25 -20
  91. package/dist/assets/index-DlhYSSKq.js.map +1 -0
  92. package/dist/assets/{useDeployment-DFuBcVnK.js → useDeployment-BJuUhf9K.js} +2 -2
  93. package/dist/assets/{useDeployment-DFuBcVnK.js.map → useDeployment-BJuUhf9K.js.map} +1 -1
  94. package/dist/index.html +1 -1
  95. package/package.json +1 -1
  96. package/dist/assets/ControlView-ninZ6BIV.js +0 -2
  97. package/dist/assets/ControlView-ninZ6BIV.js.map +0 -1
  98. package/dist/assets/LoginView-wAfA0Ozk.css +0 -1
  99. package/dist/assets/LoginView-zWd9FvKS.js +0 -2
  100. package/dist/assets/LoginView-zWd9FvKS.js.map +0 -1
  101. package/dist/assets/SwarmView-BHtmW4QF.js +0 -2
  102. package/dist/assets/SwarmView-BHtmW4QF.js.map +0 -1
  103. package/dist/assets/index-D_Ks6aQi.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{ad as l,j as i,p as n,C as d,a7 as p,ae as u}from"./index-D_Ks6aQi.js";import{w as c,r as y}from"./useVars-VN9gYdR6.js";const e=l({id:"",detail:null,error:"",loading:!1,vars:[]}),f=i(()=>p(e.vars)),k=i(()=>u(e.id)),h=i(()=>{var a;return((a=e.detail)==null?void 0:a.kind)==="shared"}),b=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 n.get(`/api/deployments/${encodeURIComponent(a)}${f.value}`);if(e.id!==a)return;if(e.loading=!1,!s.ok){e.detail=null,e.error=d(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,orchestrator:r.orchestrator??null,sleep:r.sleep??null,asleep:r.asleep??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(){c(e.id,e.vars)}function x(){m(),t()}export{x as a,e as d,h as i,t as l,g as o,m as p,k as r,b as u,f as v};
2
- //# sourceMappingURL=useDeployment-DFuBcVnK.js.map
1
+ import{ad as l,j as i,p as n,C as d,a7 as p,ae as u}from"./index-DlhYSSKq.js";import{w as c,r as y}from"./useVars-VN9gYdR6.js";const e=l({id:"",detail:null,error:"",loading:!1,vars:[]}),f=i(()=>p(e.vars)),k=i(()=>u(e.id)),h=i(()=>{var a;return((a=e.detail)==null?void 0:a.kind)==="shared"}),b=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 n.get(`/api/deployments/${encodeURIComponent(a)}${f.value}`);if(e.id!==a)return;if(e.loading=!1,!s.ok){e.detail=null,e.error=d(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,orchestrator:r.orchestrator??null,sleep:r.sleep??null,asleep:r.asleep??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(){c(e.id,e.vars)}function x(){m(),t()}export{x as a,e as d,h as i,t as l,g as o,m as p,k as r,b as u,f as v};
2
+ //# sourceMappingURL=useDeployment-BJuUhf9K.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDeployment-DFuBcVnK.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 orchestrator: b.orchestrator ?? null,\n sleep: b.sleep ?? null,\n asleep: b.asleep ?? 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":"+HAkBO,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,aAAcA,EAAE,cAAgB,KAChC,MAAOA,EAAE,OAAS,KAClB,OAAQA,EAAE,QAAU,KACpB,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-BJuUhf9K.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 orchestrator: b.orchestrator ?? null,\n sleep: b.sleep ?? null,\n asleep: b.asleep ?? 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":"+HAkBO,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,aAAcA,EAAE,cAAgB,KAChC,MAAOA,EAAE,OAAS,KAClB,OAAQA,EAAE,QAAU,KACpB,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"}
package/dist/index.html CHANGED
@@ -30,7 +30,7 @@
30
30
  /* private mode: fall through to the OS preference */
31
31
  }
32
32
  </script>
33
- <script type="module" crossorigin src="/assets/index-D_Ks6aQi.js"></script>
33
+ <script type="module" crossorigin src="/assets/index-DlhYSSKq.js"></script>
34
34
  <link rel="stylesheet" crossorigin href="/assets/index-D4djwlnX.css">
35
35
  </head>
36
36
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samyx/preview-stacks-ui",
3
- "version": "0.39.1",
3
+ "version": "0.40.0",
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,2 +0,0 @@
1
- import{d as te,x as K,c as o,a as s,E as h,t as n,f as r,e as A,z as j,n as u,F as $,r as Y,u as y,af as I,g as F,v as L,ag as le,h as x,R as oe,i as d,j as P,o as l,D as ne,M as re,H as ie,p as k,C as _,a6 as c}from"./index-D_Ks6aQi.js";import{A as S}from"./ActionButton-OewKsFrK.js";import{_ as de}from"./ErrorNote.vue_vue_type_script_setup_true_lang-FEOV8vdM.js";import{_ as ue}from"./RefreshButton.vue_vue_type_script_setup_true_lang-CQvD4Out.js";import{_ as ve}from"./SkeletonList.vue_vue_type_script_setup_true_lang-D4pkKZAr.js";import"./_plugin-vue_export-helper-DlAUqK2U.js";const ce={class:"page-head"},me={key:0,class:"vitals"},pe={key:0,class:"v-fail"},ke={key:1},ye={key:3},fe={key:2,class:"panel"},ge={key:0,class:"banner warn"},we={key:1,role:"table",class:"cards"},he={role:"rowgroup",class:"stagger"},_e={role:"cell",class:"name","data-label":"service"},be={key:0,class:"badge info",title:"the container answering this page"},Ce={role:"cell","data-label":"image",class:"mono mute"},De={role:"cell","data-label":"state"},Re={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},Ae={role:"cell","data-label":"restarts"},$e={key:0,class:"badge failed",title:"Killed by the kernel for exceeding its memory limit"},xe={role:"cell","data-label":"memory",class:"mono mute"},Se={role:"cell","data-label":"started",class:"mute"},Be={role:"cell","data-label":""},Ee={key:1,class:"mute",style:{"font-size":"var(--t-sm)"},title:"Restart this one from the host"},Me={key:2,class:"mute",style:{"font-size":"var(--t-sm)"},title:"No compose service label, so the restart API cannot address it."},Ne={key:2,class:"mute"},Ve={key:3,class:"panel"},Ie={class:"phead"},Pe={class:"mute",style:{"font-size":"var(--t-sm)"}},Te={class:"mono"},ze={class:"kvlist",style:{margin:"var(--s3) 0"}},Ke={class:"k mono"},je={class:"k mono"},Fe={class:"v"},Le={class:"mute"},Oe=["disabled","onClick"],Ge={key:0,class:"row",style:{gap:"var(--s3)","flex-wrap":"wrap"}},Ue={class:"field inline",style:{flex:"1 1 18rem"}},We={key:4,class:"panel"},He={class:"phead"},Je={class:"kvlist",style:{margin:"var(--s3) 0"}},Ye={class:"v mono"},qe={class:"v"},Qe={class:"v"},Xe={key:0,class:"badge warn"},Ze={key:0,class:"row"},es={class:"field"},ss={class:"field"},as={key:2,class:"mute"},ts={class:"row",style:{"margin-top":"var(--s4)","align-items":"center"}},ls={key:3,class:"mute",style:{"margin-top":"var(--s2)"}},ms=te({__name:"ControlView",setup(os){const m=d(null),b=d(""),O=d(!1);async function T(){const a=await k.get("/api/control/runtime");if(O.value=!0,!a.ok){b.value=_(a,"read the control stack");return}b.value="",m.value={...a.body,containers:a.body.containers??[]}}K(T,1e4);const B=d("");async function q(a){B.value=a;const e=await k.post("/api/control/restart",{service:a});if(B.value="",!e.ok){c("error",_(e,`restart ${a}`));return}c("ok",`Restarting ${e.body.container}.`),await T()}function Q(a){return a===null?"unlimited":a>=1024*1024*1024?`${(a/(1024*1024*1024)).toFixed(1)} GiB`:`${Math.round(a/(1024*1024))} MiB`}const v=d(null),f=d(""),C=d(!1);async function G(){const a=await k.get("/api/domains");a.ok&&(v.value={...a.body,domains:a.body.domains??[]})}K(G,3e4);async function U(a){C.value=!0;const e=await k.put("/api/domains",{domains:a});if(C.value=!1,!e.ok){c("error",_(e,"save the domains"));return}f.value="",c("ok","Domains saved."),await Promise.all([G(),E()])}const W=()=>{var a;return U([...((a=v.value)==null?void 0:a.domains)??[],f.value.trim()])},X=a=>{var e;return U((((e=v.value)==null?void 0:e.domains)??[]).filter(w=>w!==a))},i=d(null);async function E(){const a=await k.get("/api/tls");a.ok&&(i.value=a.body)}K(E,3e4);const H=P(()=>{var a;return(((a=m.value)==null?void 0:a.containers)??[]).reduce((e,w)=>e+w.restartCount,0)}),Z=P(()=>{var a;return(((a=m.value)==null?void 0:a.containers)??[]).some(e=>e.oomKilled)}),z=P(()=>v.value?v.value.domains.length+(v.value.primary?1:0):0),g=P(()=>{var a;return(a=i.value)!=null&&a.wildcard?Math.floor((i.value.wildcard.notAfter-Date.now())/864e5):null}),D=d(""),R=d(""),M=d(!1);async function ee(){M.value=!0;const a=await k.put("/api/tls/wildcard",{cert:D.value,key:R.value});if(M.value=!1,!a.ok){c("error",_(a,"store the wildcard"));return}D.value="",R.value="",c("ok","Stored — redeploy all stacks."),p.value=null,await E()}const N=d(!1);async function se(){N.value=!0;const a=await k.del("/api/tls/wildcard");if(N.value=!1,!a.ok){c("error",_(a,"remove the wildcard"));return}c("ok","Removed — redeploy all stacks."),p.value=null,await E()}const V=d(!1),p=d(null);async function ae(){V.value=!0;const a=await k.post("/api/tls/redeploy",{});if(V.value=!1,!a.ok){c("error",_(a,"redeploy the stacks"));return}p.value=a.body,c("ok",`Redeploying ${a.body.started.length}, skipped ${a.body.skipped.length}.`)}return(a,e)=>{var w;return l(),o("div",null,[s("div",ce,[s("div",null,[e[4]||(e[4]=s("h1",null,"Control stack",-1)),(w=m.value)!=null&&w.reachable?(l(),o("div",me,[s("span",{class:h(H.value?"v-warn":"")},n(H.value)+" restarts",3),Z.value?(l(),o("span",pe,"OOM")):r("",!0),i.value?(l(),o("span",ke,n(i.value.mode),1)):r("",!0),g.value!==null?(l(),o("span",{key:2,class:h(g.value<21?"v-fail":"")},n(g.value)+"d cert",3)):r("",!0),z.value?(l(),o("span",ye,n(z.value)+" domain"+n(z.value>1?"s":""),1)):r("",!0)])):r("",!0)]),e[5]||(e[5]=s("span",{class:"grow"},null,-1)),A(ue,{run:T})]),b.value?(l(),j(de,{key:0,text:b.value,title:"Could not read the control stack."},null,8,["text"])):r("",!0),!O.value&&!b.value?(l(),j(ve,{key:1,rows:3})):m.value?(l(),o("section",fe,[e[9]||(e[9]=s("div",{class:"phead"},[s("h2",{class:"section"},"Containers")],-1)),m.value.reachable?m.value.containers.length?(l(),o("table",we,[e[8]||(e[8]=s("thead",{role:"rowgroup"},[s("tr",{role:"row"},[s("th",{role:"columnheader"},"Service"),s("th",{role:"columnheader"},"Image"),s("th",{role:"columnheader"},"State"),s("th",{role:"columnheader"},"Restarts"),s("th",{role:"columnheader"},"Memory limit"),s("th",{role:"columnheader"},"Started"),s("th",{role:"columnheader"})])],-1)),s("tbody",he,[(l(!0),o($,null,Y(m.value.containers,(t,J)=>(l(),o("tr",{key:t.id,role:"row",style:ne({"--i":J})},[s("td",_e,[u(n(t.service||t.name)+" ",1),t.service==="pstack"?(l(),o("span",be,"This API")):r("",!0)]),s("td",Ce,n(t.image),1),s("td",De,[s("span",{class:h(t.state==="running"?"s-ok":"s-failed")},n(y(re)(t.state)),3),t.health?(l(),o("span",Re," · "+n(t.health),1)):r("",!0)]),s("td",Ae,[s("span",{class:h(t.restartCount>0?"badge warn":"mute")},n(t.restartCount),3),t.oomKilled?(l(),o("span",$e,"OOM")):r("",!0)]),s("td",xe,n(Q(t.memLimitBytes)),1),s("td",Se,n(t.startedAt?y(ie)(t.startedAt):"—"),1),s("td",Be,[t.service&&t.service!=="pstack"?(l(),j(S,{key:0,variant:"ghost",pending:B.value===t.service,disabled:B.value!=="",onClick:ns=>q(t.service)},{default:x(()=>[...e[7]||(e[7]=[u(" Restart ",-1)])]),_:1},8,["pending","disabled","onClick"])):t.service==="pstack"?(l(),o("span",Ee," host-only ")):(l(),o("span",Me," — "))])],4))),128))])])):(l(),o("p",Ne,"None listed — the project label changed, not the stack stopped.")):(l(),o("div",ge,[...e[6]||(e[6]=[s("b",null,"Docker isn't answering.",-1),u(" Nothing here is known.",-1)])]))])):r("",!0),v.value?(l(),o("section",Ve,[s("div",Ie,[e[11]||(e[11]=s("h2",{class:"section"},"Domains",-1)),e[12]||(e[12]=s("span",{class:"grow"},null,-1)),s("span",Pe,[e[10]||(e[10]=u("primary ",-1)),s("span",Te,n(v.value.primary||"—"),1)])]),s("ul",ze,[s("li",null,[s("span",Ke,n(v.value.primary||"not set"),1),e[13]||(e[13]=s("span",{class:"v mute"},"primary",-1))]),(l(!0),o($,null,Y(v.value.domains,t=>(l(),o("li",{key:t},[s("span",je,n(t),1),s("span",Fe,[s("span",Le,"control."+n(t)+" · api."+n(t)+" · wakes sleeping previews",1),y(I)("maintainer")?(l(),o("button",{key:0,class:"sm ghost",disabled:C.value,onClick:J=>X(t)},"Remove",8,Oe)):r("",!0)])]))),128))]),y(I)("maintainer")?(l(),o("div",Ge,[s("div",Ue,[e[14]||(e[14]=s("label",{for:"new-domain"},"Add a domain",-1)),F(s("input",{id:"new-domain","onUpdate:modelValue":e[0]||(e[0]=t=>f.value=t),class:"mono",placeholder:"preview.new-company.com",spellcheck:"false",onKeyup:e[1]||(e[1]=le(t=>f.value.trim()&&W(),["enter"]))},null,544),[[L,f.value]])]),A(S,{variant:"primary",pending:C.value,disabled:C.value||!f.value.trim(),onClick:W},{default:x(()=>[...e[15]||(e[15]=[u(" Add ",-1)])]),_:1},8,["pending","disabled"])])):r("",!0)])):r("",!0),i.value?(l(),o("section",We,[s("div",He,[e[16]||(e[16]=s("h2",{class:"section"},"Certificates",-1)),e[17]||(e[17]=s("span",{class:"grow"},null,-1)),s("span",{class:h(["badge",i.value.mode==="dns-persist-01"?"ok":"info"])},n(i.value.mode),3)]),i.value.wildcard?(l(),o($,{key:0},[s("ul",Je,[s("li",null,[e[18]||(e[18]=s("span",{class:"k"},"Covers",-1)),s("span",Ye,n(i.value.wildcard.domains.join(", ")),1)]),s("li",null,[e[19]||(e[19]=s("span",{class:"k"},"Valid until",-1)),s("span",qe,[u(n(y(oe)(i.value.wildcard.notAfter))+" ",1),s("span",{class:h(g.value!==null&&g.value<21?"badge failed":"mute")},n(g.value)+" days left",3)])]),s("li",null,[e[20]||(e[20]=s("span",{class:"k"},"Issuer",-1)),s("span",Qe,[u(n(i.value.wildcard.issuer||"unnamed")+" ",1),i.value.wildcard.selfSigned?(l(),o("span",Xe,"self-signed — browsers will warn")):r("",!0)])])]),y(I)("admin")?(l(),o("div",Ze,[A(S,{variant:"danger",pending:N.value,disabled:N.value,confirm:"Remove it? Deployed stacks lose TLS.",onRun:se},{default:x(()=>[...e[21]||(e[21]=[u(" Remove wildcard ",-1)])]),_:1},8,["pending","disabled"]),e[22]||(e[22]=s("span",{class:"mute"},"Stacks deployed under it serve no certificate until redeployed.",-1))])):r("",!0)],64)):r("",!0),y(I)("admin")?(l(),o($,{key:1},[e[25]||(e[25]=s("p",{class:"dim"},"Stored 0600. Never returned.",-1)),s("div",es,[e[23]||(e[23]=s("label",{for:"tls-cert"},"Certificate (PEM, leaf first, chain after)",-1)),F(s("textarea",{id:"tls-cert","onUpdate:modelValue":e[2]||(e[2]=t=>D.value=t),rows:"5",class:"mono",spellcheck:"false",placeholder:"-----BEGIN CERTIFICATE-----"},null,512),[[L,D.value]])]),s("div",ss,[e[24]||(e[24]=s("label",{for:"tls-key"},"Private key (PEM)",-1)),F(s("textarea",{id:"tls-key","onUpdate:modelValue":e[3]||(e[3]=t=>R.value=t),rows:"4",class:"mono",spellcheck:"false",placeholder:"-----BEGIN PRIVATE KEY-----"},null,512),[[L,R.value]])]),A(S,{variant:"primary",pending:M.value,disabled:M.value||!D.value||!R.value,onClick:ee},{default:x(()=>[u(n(i.value.wildcard?"Replace wildcard":"Store wildcard"),1)]),_:1},8,["pending","disabled"])],64)):i.value.wildcard?r("",!0):(l(),o("p",as,"Admin only.")),s("div",ts,[A(S,{pending:V.value,disabled:V.value,onClick:ae},{default:x(()=>[...e[26]||(e[26]=[u(" Redeploy all stacks ",-1)])]),_:1},8,["pending","disabled"]),e[27]||(e[27]=s("span",{class:"mute"},"Router labels are stamped at deploy time. Asleep stacks pick them up on wake.",-1))]),p.value?(l(),o("p",ls,[u(" Started "+n(p.value.started.length)+" · skipped "+n(p.value.skipped.length),1),p.value.skipped.length?(l(),o($,{key:0},[u(" ("+n(p.value.skipped.map(t=>`${t.id}: ${t.reason}`).join("; "))+")",1)],64)):r("",!0),e[28]||(e[28]=u(" — watch them under Jobs. ",-1))])):r("",!0)])):r("",!0)])}}});export{ms as default};
2
- //# sourceMappingURL=ControlView-ninZ6BIV.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ControlView-ninZ6BIV.js","sources":["../../src/views/ControlView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The control stack as an operator debugs it — traefik, pstack, the optional advanced UI.\n *\n * The dashboard's card answers \"is it up\"; this page answers \"why did it restart\". The two columns\n * that earn it are RESTARTS and OOM: a Traefik restart silently wipes its in-memory certificate\n * challenges, so TLS stops issuing while every state here still reads `running`. That incident was\n * once diagnosed from a default certificate's timestamp; now it is a red badge on this table.\n *\n * One action, one refusal: any control service can be restarted except `pstack` itself — the\n * server refuses its own container by name, whoever asks, and this page does not offer it.\n */\nimport { computed, ref } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { ControlRuntime, DomainsStatus, TlsRedeploy, TlsStatus } from '../api/types';\nimport { usePolling } from '../composables/usePolling';\nimport { can } from '../composables/useAuth';\nimport { ago, sentence, stamp } from '../composables/useFormat';\nimport { toast } from '../composables/useToasts';\nimport ActionButton from '../components/ActionButton.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport RefreshButton from '../components/RefreshButton.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\n\nconst view = ref<ControlRuntime | null>(null);\nconst error = ref('');\nconst loaded = ref(false);\n\nasync function load(): Promise<void> {\n const r = await api.get<ControlRuntime>('/api/control/runtime');\n loaded.value = true;\n if (!r.ok) {\n error.value = problem(r, 'read the control stack');\n return;\n }\n error.value = '';\n view.value = { ...r.body, containers: r.body.containers ?? [] };\n}\nusePolling(load, 10_000);\n\nconst restarting = ref('');\nasync function restart(service: string): Promise<void> {\n restarting.value = service;\n const r = await api.post<{ container: string }>('/api/control/restart', { service });\n restarting.value = '';\n if (!r.ok) {\n toast('error', problem(r, `restart ${service}`));\n return;\n }\n toast('ok', `Restarting ${r.body.container}.`);\n await load();\n}\n\n/** 268435456 → \"256 MiB\"; null → \"unlimited\" (the server normalizes docker's 0 to null). */\nfunction mem(bytes: number | null): string {\n if (bytes === null) return 'unlimited';\n if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`;\n return `${Math.round(bytes / (1024 * 1024))} MiB`;\n}\n\n// ── the hostnames this host answers on ────────────────────────────────────────────────────────────\nconst domains = ref<DomainsStatus | null>(null);\nconst newDomain = ref('');\nconst savingDomains = ref(false);\n\nasync function loadDomains(): Promise<void> {\n const r = await api.get<DomainsStatus>('/api/domains');\n if (r.ok) domains.value = { ...r.body, domains: r.body.domains ?? [] };\n}\nusePolling(loadDomains, 30_000);\n\n/** The list replaces what is stored, so add and remove are the same call. */\nasync function saveDomains(next: string[]): Promise<void> {\n savingDomains.value = true;\n const r = await api.put<DomainsStatus>('/api/domains', { domains: next });\n savingDomains.value = false;\n if (!r.ok) {\n toast('error', problem(r, 'save the domains'));\n return;\n }\n newDomain.value = '';\n toast('ok', 'Domains saved.');\n await Promise.all([loadDomains(), loadTls()]);\n}\n\nconst addDomain = () => saveDomains([...(domains.value?.domains ?? []), newDomain.value.trim()]);\nconst removeDomain = (d: string) => saveDomains((domains.value?.domains ?? []).filter((x) => x !== d));\n\n// ── the certificate mode ──────────────────────────────────────────────────────────────────────────\nconst tls = ref<TlsStatus | null>(null);\nasync function loadTls(): Promise<void> {\n const r = await api.get<TlsStatus>('/api/tls');\n if (r.ok) tls.value = r.body;\n}\nusePolling(loadTls, 30_000);\n\n// The two numbers that explain a control plane misbehaving, summed for the strip.\nconst restarts = computed(() => (view.value?.containers ?? []).reduce((n, c) => n + c.restartCount, 0));\nconst oomed = computed(() => (view.value?.containers ?? []).some((c) => c.oomKilled));\n// The primary is a hostname, not an entry in `domains` — but it is only there once init has set\n// one, so it is counted rather than assumed.\nconst domainCount = computed(() =>\n domains.value ? domains.value.domains.length + (domains.value.primary ? 1 : 0) : 0,\n);\n\nconst daysLeft = computed(() => {\n if (!tls.value?.wildcard) return null;\n return Math.floor((tls.value.wildcard.notAfter - Date.now()) / 86_400_000);\n});\n\nconst certDraft = ref('');\nconst keyDraft = ref('');\nconst storing = ref(false);\nasync function storeWildcard(): Promise<void> {\n storing.value = true;\n const r = await api.put<{ wildcard: TlsWildcardShape; note: string }>('/api/tls/wildcard', {\n cert: certDraft.value,\n key: keyDraft.value,\n });\n storing.value = false;\n if (!r.ok) {\n toast('error', problem(r, 'store the wildcard'));\n return;\n }\n certDraft.value = '';\n keyDraft.value = '';\n toast('ok', 'Stored — redeploy all stacks.');\n redeployed.value = null; // that summary described a run under the previous mode\n await loadTls();\n}\ntype TlsWildcardShape = NonNullable<TlsStatus['wildcard']>;\n\nconst removing = ref(false);\nasync function removeWildcard(): Promise<void> {\n removing.value = true;\n const r = await api.del<{ note: string }>('/api/tls/wildcard');\n removing.value = false;\n if (!r.ok) {\n toast('error', problem(r, 'remove the wildcard'));\n return;\n }\n toast('ok', 'Removed — redeploy all stacks.');\n redeployed.value = null;\n await loadTls();\n}\n\nconst redeploying = ref(false);\nconst redeployed = ref<TlsRedeploy | null>(null);\nasync function redeployAll(): Promise<void> {\n redeploying.value = true;\n const r = await api.post<TlsRedeploy>('/api/tls/redeploy', {});\n redeploying.value = false;\n if (!r.ok) {\n toast('error', problem(r, 'redeploy the stacks'));\n return;\n }\n redeployed.value = r.body;\n toast('ok', `Redeploying ${r.body.started.length}, skipped ${r.body.skipped.length}.`);\n}\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Control stack</h1>\n <!--\n The vitals, not a sentence about them. Restarts and OOM are here rather than buried in the\n table because they are the two numbers that explain a control plane misbehaving — a\n restarting Traefik loses in-flight certificate issuance while every container still reads\n `running`. Why that matters is docs/usage.md's job, not this page's.\n -->\n <div v-if=\"view?.reachable\" class=\"vitals\">\n <span :class=\"restarts ? 'v-warn' : ''\">{{ restarts }} restarts</span>\n <span v-if=\"oomed\" class=\"v-fail\">OOM</span>\n <span v-if=\"tls\">{{ tls.mode }}</span>\n <span v-if=\"daysLeft !== null\" :class=\"daysLeft < 21 ? 'v-fail' : ''\">{{ daysLeft }}d cert</span>\n <span v-if=\"domainCount\">{{ domainCount }} domain{{ domainCount > 1 ? 's' : '' }}</span>\n </div>\n </div>\n <span class=\"grow\" />\n <RefreshButton :run=\"load\" />\n </div>\n\n <ErrorNote v-if=\"error\" :text=\"error\" title=\"Could not read the control stack.\" />\n\n <SkeletonList v-if=\"!loaded && !error\" :rows=\"3\" />\n\n <section v-else-if=\"view\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Containers</h2>\n </div>\n\n <!-- Unknown is not \"empty\": a dead docker means NOTHING here is known. -->\n <!-- Unknown is not empty: a dead docker means nothing here is KNOWN. The distinction is the\n point (invariant 10); the sentence explaining it is not. -->\n <div v-if=\"!view.reachable\" class=\"banner warn\"><b>Docker isn't answering.</b> Nothing here is known.</div>\n <table v-else-if=\"view.containers.length\" role=\"table\" class=\"cards\">\n <thead role=\"rowgroup\">\n <tr role=\"row\">\n <th role=\"columnheader\">Service</th>\n <th role=\"columnheader\">Image</th>\n <th role=\"columnheader\">State</th>\n <th role=\"columnheader\">Restarts</th>\n <th role=\"columnheader\">Memory limit</th>\n <th role=\"columnheader\">Started</th>\n <th role=\"columnheader\"></th>\n </tr>\n </thead>\n <tbody role=\"rowgroup\" class=\"stagger\">\n <tr v-for=\"(c, i) in view.containers\" :key=\"c.id\" role=\"row\" :style=\"{ '--i': i }\">\n <td role=\"cell\" class=\"name\" data-label=\"service\">\n {{ c.service || c.name }}\n <span v-if=\"c.service === 'pstack'\" class=\"badge info\" title=\"the container answering this page\">This API</span>\n </td>\n <td role=\"cell\" data-label=\"image\" class=\"mono mute\">{{ c.image }}</td>\n <td role=\"cell\" data-label=\"state\">\n <span :class=\"c.state === 'running' ? 's-ok' : 's-failed'\">{{ sentence(c.state) }}</span>\n <span v-if=\"c.health\" class=\"mute\" style=\"font-size: var(--t-sm)\"> · {{ c.health }}</span>\n </td>\n <td role=\"cell\" data-label=\"restarts\">\n <span :class=\"c.restartCount > 0 ? 'badge warn' : 'mute'\">{{ c.restartCount }}</span>\n <span\n v-if=\"c.oomKilled\"\n class=\"badge failed\"\n title=\"Killed by the kernel for exceeding its memory limit\"\n >OOM</span\n >\n </td>\n <td role=\"cell\" data-label=\"memory\" class=\"mono mute\">{{ mem(c.memLimitBytes) }}</td>\n <td role=\"cell\" data-label=\"started\" class=\"mute\">{{ c.startedAt ? ago(c.startedAt) : '—' }}</td>\n <td role=\"cell\" data-label=\"\">\n <ActionButton\n v-if=\"c.service && c.service !== 'pstack'\"\n variant=\"ghost\"\n :pending=\"restarting === c.service\"\n :disabled=\"restarting !== ''\"\n @click=\"restart(c.service)\"\n >\n Restart\n </ActionButton>\n <span\n v-else-if=\"c.service === 'pstack'\"\n class=\"mute\"\n style=\"font-size: var(--t-sm)\"\n title=\"Restart this one from the host\"\n >\n host-only\n </span>\n <span\n v-else\n class=\"mute\"\n style=\"font-size: var(--t-sm)\"\n title=\"No compose service label, so the restart API cannot address it.\"\n >\n —\n </span>\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"mute\">None listed — the project label changed, not the stack stopped.</p>\n </section>\n\n <!-- ============================ the hostnames ============================ -->\n <section v-if=\"domains\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Domains</h2>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">primary <span class=\"mono\">{{ domains.primary || '—' }}</span></span>\n </div>\n\n <ul class=\"kvlist\" style=\"margin: var(--s3) 0\">\n <li>\n <span class=\"k mono\">{{ domains.primary || 'not set' }}</span>\n <span class=\"v mute\">primary</span>\n </li>\n <li v-for=\"d in domains.domains\" :key=\"d\">\n <span class=\"k mono\">{{ d }}</span>\n <span class=\"v\">\n <span class=\"mute\">control.{{ d }} · api.{{ d }} · wakes sleeping previews</span>\n <button v-if=\"can('maintainer')\" class=\"sm ghost\" :disabled=\"savingDomains\" @click=\"removeDomain(d)\">Remove</button>\n </span>\n </li>\n </ul>\n\n <div v-if=\"can('maintainer')\" class=\"row\" style=\"gap: var(--s3); flex-wrap: wrap\">\n <div class=\"field inline\" style=\"flex: 1 1 18rem\">\n <label for=\"new-domain\">Add a domain</label>\n <input\n id=\"new-domain\"\n v-model=\"newDomain\"\n class=\"mono\"\n placeholder=\"preview.new-company.com\"\n spellcheck=\"false\"\n @keyup.enter=\"newDomain.trim() && addDomain()\"\n />\n </div>\n <ActionButton variant=\"primary\" :pending=\"savingDomains\" :disabled=\"savingDomains || !newDomain.trim()\" @click=\"addDomain\">\n Add\n </ActionButton>\n </div>\n\n </section>\n\n <!-- ============================ the certificate mode ============================ -->\n <section v-if=\"tls\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Certificates</h2>\n <span class=\"grow\" />\n <span class=\"badge\" :class=\"tls.mode === 'dns-persist-01' ? 'ok' : 'info'\">{{ tls.mode }}</span>\n </div>\n\n <template v-if=\"tls.wildcard\">\n <ul class=\"kvlist\" style=\"margin: var(--s3) 0\">\n <li>\n <span class=\"k\">Covers</span>\n <span class=\"v mono\">{{ tls.wildcard.domains.join(', ') }}</span>\n </li>\n <li>\n <span class=\"k\">Valid until</span>\n <span class=\"v\">\n {{ stamp(tls.wildcard.notAfter) }}\n <span :class=\"daysLeft !== null && daysLeft < 21 ? 'badge failed' : 'mute'\"> {{ daysLeft }} days left</span>\n </span>\n </li>\n <li>\n <span class=\"k\">Issuer</span>\n <span class=\"v\">\n {{ tls.wildcard.issuer || 'unnamed' }}\n <span v-if=\"tls.wildcard.selfSigned\" class=\"badge warn\">self-signed — browsers will warn</span>\n </span>\n </li>\n </ul>\n <div v-if=\"can('admin')\" class=\"row\">\n <ActionButton\n variant=\"danger\"\n :pending=\"removing\"\n :disabled=\"removing\"\n confirm=\"Remove it? Deployed stacks lose TLS.\"\n @run=\"removeWildcard\"\n >\n Remove wildcard\n </ActionButton>\n <span class=\"mute\">Stacks deployed under it serve no certificate until redeployed.</span>\n </div>\n </template>\n\n <template v-if=\"can('admin')\">\n <p class=\"dim\">Stored 0600. Never returned.</p>\n <div class=\"field\">\n <label for=\"tls-cert\">Certificate (PEM, leaf first, chain after)</label>\n <textarea id=\"tls-cert\" v-model=\"certDraft\" rows=\"5\" class=\"mono\" spellcheck=\"false\" placeholder=\"-----BEGIN CERTIFICATE-----\"></textarea>\n </div>\n <div class=\"field\">\n <label for=\"tls-key\">Private key (PEM)</label>\n <textarea id=\"tls-key\" v-model=\"keyDraft\" rows=\"4\" class=\"mono\" spellcheck=\"false\" placeholder=\"-----BEGIN PRIVATE KEY-----\"></textarea>\n </div>\n <ActionButton variant=\"primary\" :pending=\"storing\" :disabled=\"storing || !certDraft || !keyDraft\" @click=\"storeWildcard\">\n {{ tls.wildcard ? 'Replace wildcard' : 'Store wildcard' }}\n </ActionButton>\n </template>\n <p v-else-if=\"!tls.wildcard\" class=\"mute\">Admin only.</p>\n\n <div class=\"row\" style=\"margin-top: var(--s4); align-items: center\">\n <ActionButton :pending=\"redeploying\" :disabled=\"redeploying\" @click=\"redeployAll\">\n Redeploy all stacks\n </ActionButton>\n <span class=\"mute\">Router labels are stamped at deploy time. Asleep stacks pick them up on wake.</span>\n </div>\n <p v-if=\"redeployed\" class=\"mute\" style=\"margin-top: var(--s2)\">\n Started {{ redeployed.started.length }} · skipped {{ redeployed.skipped.length\n }}<template v-if=\"redeployed.skipped.length\"> ({{ redeployed.skipped.map((x) => `${x.id}: ${x.reason}`).join('; ') }})</template>\n — watch them under Jobs.\n </p>\n </section>\n </div>\n</template>\n"],"names":["view","ref","error","loaded","load","r","api","problem","usePolling","restarting","restart","service","toast","mem","bytes","domains","newDomain","savingDomains","loadDomains","saveDomains","next","loadTls","addDomain","_a","removeDomain","d","x","tls","restarts","computed","n","c","oomed","domainCount","daysLeft","certDraft","keyDraft","storing","storeWildcard","redeployed","removing","removeWildcard","redeploying","redeployAll","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_openBlock","_hoisted_2","_toDisplayString","_hoisted_3","_hoisted_4","_hoisted_5","_createVNode","RefreshButton","_createBlock","ErrorNote","SkeletonList","_hoisted_6","_hoisted_8","_hoisted_9","_Fragment","i","_hoisted_10","_createTextVNode","_hoisted_11","_hoisted_12","_hoisted_13","_normalizeClass","_unref","sentence","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","ago","_hoisted_19","ActionButton","$event","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_7","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_renderList","_hoisted_29","_hoisted_30","_hoisted_31","can","_hoisted_32","_hoisted_33","_hoisted_34","_withKeys","_hoisted_35","_hoisted_36","_hoisted_37","_hoisted_38","_hoisted_39","stamp","_hoisted_40","_hoisted_41","_hoisted_42","_hoisted_43","_hoisted_44","_hoisted_45","_hoisted_46","_hoisted_47"],"mappings":"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"}
@@ -1 +0,0 @@
1
- .login-wrap[data-v-caf273ac]{width:min(420px,100%)}.login-card[data-v-caf273ac]{width:100%}.sso-list[data-v-caf273ac]{display:flex;flex-direction:column;gap:var(--s2)}.sso-btn[data-v-caf273ac]{width:100%;justify-content:center}.sso-btn svg[data-v-caf273ac]{flex:none}
@@ -1,2 +0,0 @@
1
- import{d as R,c as n,a as e,u as E,b as _,e as m,f as c,w as L,g as k,v as h,t as u,h as x,F as C,r as $,i as f,j as g,k as B,l as I,m as P,o as a,n as S,p as T,q as A,s as z}from"./index-D_Ks6aQi.js";import{A as D}from"./ActionButton-OewKsFrK.js";import{E as F}from"./EquivalentCommand-D-mkbReY.js";import{_ as M}from"./SsoMark.vue_vue_type_script_setup_true_lang-BU6ThRhq.js";import{_ as W}from"./_plugin-vue_export-helper-DlAUqK2U.js";const j={class:"login-wrap"},O={class:"login-card panel"},G={key:0,class:"banner plain",style:{"margin-top":"var(--s3)"}},H={class:"field",style:{"margin-top":"var(--s4)"}},J={class:"field"},K={key:0,role:"alert",class:"banner failed",style:{"margin-top":"var(--s3)"}},Q={class:"row",style:{"margin-top":"var(--s4)"}},X={key:0,class:"banner failed",style:{"margin-bottom":"var(--s3)"}},Y={class:"sso-list"},Z=["onClick"],ee=R({__name:"LoginView",setup(te){const V=I(),r=P(),d=f(""),i=f(""),l=f(!1),p=f(""),y=g(()=>typeof r.query.next=="string"&&r.query.next.startsWith("/")?r.query.next:"/"),b=g(()=>typeof r.query.sso_error=="string"?r.query.sso_error:""),v=g(()=>{var s;return((s=_.sso)==null?void 0:s.providers)??[]}),q=g(()=>{const s=v.value.length===1?v.value[0]:void 0;return s?`Signing in with ${s.label} did not work.`:"Signing in did not work."});function U(s){window.location.assign(T.url(`/api/auth/sso/start?provider=${encodeURIComponent(s)}&next=${encodeURIComponent(y.value)}`))}async function w(){l.value=!0,p.value="";const s=await A(d.value.trim(),i.value);if(l.value=!1,s){p.value=s,i.value="";return}await z(),V.replace(y.value)}return(s,t)=>{const N=B("RouterLink");return a(),n("div",j,[e("div",O,[t[9]||(t[9]=e("h1",{style:{"font-size":"var(--t-xl)","margin-bottom":"var(--s2)"}},"Sign in",-1)),E(_).hasUsers===!1?(a(),n("div",G,[t[2]||(t[2]=e("b",null,"No accounts yet — create the first one.",-1)),t[3]||(t[3]=e("p",{class:"mute"},"Needs the host token.",-1)),m(F,{what:"creating the first account",method:"POST",path:"/api/auth/bootstrap",body:{username:"you",password:"…"}})])):c("",!0),e("form",{onSubmit:L(w,["prevent"])},[e("div",H,[t[4]||(t[4]=e("label",{for:"u"},"Username",-1)),k(e("input",{id:"u","onUpdate:modelValue":t[0]||(t[0]=o=>d.value=o),type:"text",autocomplete:"username",spellcheck:"false"},null,512),[[h,d.value]])]),e("div",J,[t[5]||(t[5]=e("label",{for:"p"},"Password",-1)),k(e("input",{id:"p","onUpdate:modelValue":t[1]||(t[1]=o=>i.value=o),type:"password",autocomplete:"current-password"},null,512),[[h,i.value]])]),p.value?(a(),n("div",K,[e("p",null,u(p.value),1)])):c("",!0),e("div",Q,[m(D,{variant:"primary",pending:l.value,disabled:l.value||!d.value.trim()||!i.value,onClick:w},{default:x(()=>[S(u(l.value?"Signing in…":"Sign in"),1)]),_:1},8,["pending","disabled"]),t[7]||(t[7]=e("span",{class:"grow"},null,-1)),m(N,{to:"/settings",class:"mute"},{default:x(()=>[...t[6]||(t[6]=[S("Use a token instead",-1)])]),_:1})])],32),v.value.length?(a(),n(C,{key:1},[t[8]||(t[8]=e("div",{class:"row",style:{margin:"var(--s4) 0","align-items":"center",gap:"var(--s3)"}},[e("hr",{style:{flex:"1",border:"0","border-top":"1px solid var(--line)"}}),e("span",{class:"mute",style:{"font-size":"var(--t-sm)"}},"or"),e("hr",{style:{flex:"1",border:"0","border-top":"1px solid var(--line)"}})],-1)),b.value?(a(),n("div",X,[e("b",null,u(q.value),1),e("p",null,u(b.value),1)])):c("",!0),e("div",Y,[(a(!0),n(C,null,$(v.value,o=>(a(),n("button",{key:o.key,class:"sso-btn",onClick:se=>U(o.key)},[m(M,{preset:o.preset},null,8,["preset"]),e("span",null,"Continue with "+u(o.label),1)],8,Z))),128))])],64)):c("",!0)])])}}}),le=W(ee,[["__scopeId","data-v-caf273ac"]]);export{le as default};
2
- //# sourceMappingURL=LoginView-zWd9FvKS.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LoginView-zWd9FvKS.js","sources":["../../src/views/LoginView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Sign in — the one page reachable without a session.\n *\n * Two states it must distinguish, because they need opposite instructions:\n * - accounts exist → the form\n * - no accounts yet → bootstrap guidance. A dead login form with no way to create the\n * account it asks for is the worst version of this page.\n *\n * A bearer token in Settings also authenticates (CI-style access); the link stays visible for\n * operators who have the machine token and no account yet.\n */\nimport { computed, ref } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\nimport { authState, checkAuth, login } from '../composables/useAuth';\nimport { api } from '../api/client';\nimport ActionButton from '../components/ActionButton.vue';\nimport EquivalentCommand from '../components/EquivalentCommand.vue';\nimport SsoMark from '../components/SsoMark.vue';\n\nconst router = useRouter();\nconst route = useRoute();\n\nconst username = ref('');\nconst password = ref('');\nconst pending = ref(false);\nconst error = ref('');\n\n/** Where the guard wanted to go. Also what the SSO round trip carries and comes back to. */\nconst next = computed(() =>\n typeof route.query.next === 'string' && route.query.next.startsWith('/') ? route.query.next : '/',\n);\n\n/**\n * The provider failed or refused. It arrives in the query string because the callback is a REDIRECT\n * — there is no fetch to read a body from — and it is the provider's own words, so it renders as\n * text and never as markup.\n */\nconst ssoError = computed(() => (typeof route.query.sso_error === 'string' ? route.query.sso_error : ''));\n\n/** The enabled providers, one button each. Empty until /api/health has answered. */\nconst ssoProviders = computed(() => authState.sso?.providers ?? []);\n\n/**\n * The banner must name the provider that failed. With one provider that is known locally; with\n * several, the server prefixes its label onto the message itself, so the generic title is enough.\n */\nconst ssoErrorTitle = computed(() => {\n const only = ssoProviders.value.length === 1 ? ssoProviders.value[0] : undefined;\n return only ? `Signing in with ${only.label} did not work.` : 'Signing in did not work.';\n});\n\n/**\n * A full navigation, not a fetch: the whole point is to leave this origin for the provider's\n * consent screen. `api.url` so a dev session pointed at a tunnelled host still reaches its API.\n */\nfunction signInWithProvider(key: string): void {\n window.location.assign(\n api.url(`/api/auth/sso/start?provider=${encodeURIComponent(key)}&next=${encodeURIComponent(next.value)}`),\n );\n}\n\nasync function submit(): Promise<void> {\n pending.value = true;\n error.value = '';\n const failure = await login(username.value.trim(), password.value);\n pending.value = false;\n if (failure) {\n error.value = failure;\n password.value = '';\n return;\n }\n await checkAuth();\n // Back to wherever the guard bounced them from, or home.\n void router.replace(next.value);\n}\n</script>\n\n<template>\n <div class=\"login-wrap\">\n <div class=\"login-card panel\">\n <h1 style=\"font-size: var(--t-xl); margin-bottom: var(--s2)\">Sign in</h1>\n\n <template v-if=\"authState.hasUsers === false\">\n <!--\n A curl block used to sit here permanently, on the FIRST screen anyone sees. It is needed\n exactly once per host and by one person; behind the button it costs a click then and\n nothing on every later visit — and it is generated, so it cannot drift from the route.\n -->\n <div class=\"banner plain\" style=\"margin-top: var(--s3)\">\n <b>No accounts yet — create the first one.</b>\n <p class=\"mute\">Needs the host token.</p>\n <EquivalentCommand\n what=\"creating the first account\"\n method=\"POST\"\n path=\"/api/auth/bootstrap\"\n :body=\"{ username: 'you', password: '…' }\"\n />\n </div>\n </template>\n\n <form @submit.prevent=\"submit\">\n <div class=\"field\" style=\"margin-top: var(--s4)\">\n <label for=\"u\">Username</label>\n <input id=\"u\" v-model=\"username\" type=\"text\" autocomplete=\"username\" spellcheck=\"false\" />\n </div>\n <div class=\"field\">\n <label for=\"p\">Password</label>\n <input id=\"p\" v-model=\"password\" type=\"password\" autocomplete=\"current-password\" />\n </div>\n\n <div v-if=\"error\" role=\"alert\" class=\"banner failed\" style=\"margin-top: var(--s3)\">\n <p>{{ error }}</p>\n </div>\n\n <div class=\"row\" style=\"margin-top: var(--s4)\">\n <ActionButton\n variant=\"primary\"\n :pending=\"pending\"\n :disabled=\"pending || !username.trim() || !password\"\n @click=\"submit\"\n >\n {{ pending ? 'Signing in…' : 'Sign in' }}\n </ActionButton>\n <span class=\"grow\" />\n <RouterLink to=\"/settings\" class=\"mute\">Use a token instead</RouterLink>\n </div>\n </form>\n\n <!-- ── the providers, below the form: one full-width button each, so one configured\n provider and five lay out the same way. Below rather than above because the health\n probe answers after first paint — buttons appearing UNDER the form do not shove the\n username field out from beneath the pointer. -->\n <template v-if=\"ssoProviders.length\">\n <div class=\"row\" style=\"margin: var(--s4) 0; align-items: center; gap: var(--s3)\">\n <hr style=\"flex: 1; border: 0; border-top: 1px solid var(--line)\" />\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">or</span>\n <hr style=\"flex: 1; border: 0; border-top: 1px solid var(--line)\" />\n </div>\n <div v-if=\"ssoError\" class=\"banner failed\" style=\"margin-bottom: var(--s3)\">\n <b>{{ ssoErrorTitle }}</b>\n <!-- The provider's (or server's) own words — arrives via redirect query, rendered as\n text and never as markup. -->\n <p>{{ ssoError }}</p>\n </div>\n <div class=\"sso-list\">\n <button v-for=\"p in ssoProviders\" :key=\"p.key\" class=\"sso-btn\" @click=\"signInWithProvider(p.key)\">\n <SsoMark :preset=\"p.preset\" />\n <span>Continue with {{ p.label }}</span>\n </button>\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* The shell centres this now — see `.shell.no-rail .view`. A second centring context here would\n * fight it, and `min-height: 70vh` was what pinned the card toward the top of the window. */\n/* `100%`, not `92vw`: the shell already insets this by its own padding, so a viewport-relative\n * width added on top overflowed the right edge on a phone (measured 32px/0px at 390). */\n.login-wrap {\n width: min(420px, 100%);\n}\n.login-card {\n width: 100%;\n}\n\n/* One neutral button per provider: the brand mark carries the identity, the button stays the\n * app's own control — a row of provider-branded buttons is a ransom note. */\n.sso-list {\n display: flex;\n flex-direction: column;\n gap: var(--s2);\n}\n.sso-btn {\n width: 100%;\n justify-content: center;\n}\n.sso-btn svg {\n flex: none;\n}\n</style>\n"],"names":["router","useRouter","route","useRoute","username","ref","password","pending","error","next","computed","ssoError","ssoProviders","_a","authState","ssoErrorTitle","only","signInWithProvider","key","api","submit","failure","login","checkAuth","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_unref","_hoisted_3","_createVNode","EquivalentCommand","_hoisted_4","$event","_hoisted_5","_hoisted_6","_hoisted_7","ActionButton","_component_RouterLink","_Fragment","_hoisted_8","_hoisted_9","_renderList","p","SsoMark","_toDisplayString"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{d as U,x as E,Q as I,c as s,a as e,e as b,z as y,f as r,n as f,t as n,F as k,r as S,h as R,i as d,a6 as V,o as t,D as H,E as $,u as w,M as _,p as D,C as M}from"./index-D_Ks6aQi.js";import{A as O}from"./ActionButton-OewKsFrK.js";import{_ as A}from"./ErrorNote.vue_vue_type_script_setup_true_lang-FEOV8vdM.js";import{_ as P}from"./RefreshButton.vue_vue_type_script_setup_true_lang-CQvD4Out.js";import{_ as N}from"./SelectMenu.vue_vue_type_script_setup_true_lang-CMLYr7A7.js";import{_ as q}from"./SkeletonList.vue_vue_type_script_setup_true_lang-D4pkKZAr.js";import"./_plugin-vue_export-helper-DlAUqK2U.js";const J={class:"page-head"},L={key:2,class:"panel"},Q={class:"phead"},G={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},K={class:"mono"},W={key:0,class:"banner warn"},X={key:1,class:"banner plain"},Y={key:0,class:"banner warn"},Z={class:"mono"},ee={key:1,role:"table",class:"cards"},ae={role:"rowgroup",class:"stagger"},oe={role:"cell",class:"name","data-label":"hostname"},te={key:0,class:"badge info",title:"the node this control plane runs on"},se={role:"cell","data-label":"role"},le={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},ne={role:"cell","data-label":"status"},re={role:"cell","data-label":"availability"},ie={role:"cell","data-label":"engine",class:"mono"},de={role:"cell","data-label":"id",class:"mono mute"},ue={key:2,class:"mute"},me={key:3,class:"panel"},ve={class:"kvlist",style:{"margin-bottom":"var(--s4)"}},ce={class:"k mono"},pe={class:"v mute"},be={class:"row",style:{"flex-wrap":"wrap",gap:"var(--s3)"}},fe={class:"field inline"},he={key:0,class:"field inline"},ye={class:"mute",style:{"font-size":"var(--t-sm)"}},ke={key:1,class:"banner leaked",style:{"margin-top":"var(--s3)"}},we={class:"code",style:{"white-space":"pre-wrap","word-break":"break-all","max-height":"50vh",overflow:"auto"}},_e={class:"row"},De=U({__name:"SwarmView",setup(ge){const l=d(null),c=d(""),g=d(!1);async function x(){const i=await D.get("/api/swarm");if(g.value=!0,!i.ok){c.value=M(i,"read the swarm");return}c.value="",l.value={...i.body,nodes:i.body.nodes??[],ports:i.body.ports??[]}}E(x,1e4);const m=d("command"),h=d("ubuntu"),u=d(""),p=d(!1),v=d(""),C=[{value:"command",label:"docker swarm join …",blurb:"For a machine already running Docker."},{value:"script",label:"Shell script",blurb:"Installs Docker, then joins."},{value:"cloud-config",label:"cloud-config",blurb:"User-data for a fresh machine."},{value:"token",label:"Token only",blurb:"For your own tooling."}];async function z(){p.value=!0,v.value="",u.value="";const i=`format=${m.value}${m.value==="cloud-config"?`&distro=${encodeURIComponent(h.value)}`:""}`,a=await D.getText(`/api/swarm/join?${i}`);if(p.value=!1,a.status===403){v.value="The join token is admin-only.";return}if(a.status===409){v.value="This host is not a swarm manager.";return}if(!a.ok){v.value=a.text||`HTTP ${a.status}`;return}u.value=a.text}async function B(){try{await navigator.clipboard.writeText(u.value),V("ok","Copied.")}catch{V("error","Copy failed — select it and copy by hand.")}}return I(()=>{u.value=""}),(i,a)=>{var T,j;return t(),s("div",null,[e("div",J,[a[3]||(a[3]=e("div",null,[e("h1",null,"Swarm"),e("div",{class:"sub"},"The nodes previews run on")],-1)),a[4]||(a[4]=e("span",{class:"grow"},null,-1)),b(P,{run:x})]),c.value?(t(),y(A,{key:0,text:c.value,title:"Could not read the swarm."},null,8,["text"])):r("",!0),!g.value&&!c.value?(t(),y(q,{key:1,rows:4})):l.value?(t(),s("section",L,[e("div",Q,[a[6]||(a[6]=e("h2",{class:"section"},"Nodes",-1)),a[7]||(a[7]=e("span",{class:"grow"},null,-1)),l.value.active&&l.value.managerAddr?(t(),s("span",G,[a[5]||(a[5]=f(" manager ",-1)),e("span",K,n(l.value.managerAddr),1)])):r("",!0)]),l.value.reachable?l.value.active?(t(),s(k,{key:2},[l.value.error?(t(),s("div",Y,[a[10]||(a[10]=e("b",null,"Docker reported a problem.",-1)),e("p",Z,n(l.value.error),1)])):r("",!0),l.value.nodes.length?(t(),s("table",ee,[a[11]||(a[11]=e("thead",{role:"rowgroup"},[e("tr",{role:"row"},[e("th",{role:"columnheader"},"Hostname"),e("th",{role:"columnheader"},"Role"),e("th",{role:"columnheader"},"Status"),e("th",{role:"columnheader"},"Availability"),e("th",{role:"columnheader"},"Engine"),e("th",{role:"columnheader"},"Id")])],-1)),e("tbody",ae,[(t(!0),s(k,null,S(l.value.nodes,(o,F)=>(t(),s("tr",{key:o.id,role:"row",style:H({"--i":F})},[e("td",oe,[f(n(o.hostname||"—")+" ",1),o.self?(t(),s("span",te,"This node")):r("",!0)]),e("td",se,[e("span",{class:$(["badge",o.role==="manager"?"isolated":""])},n(w(_)(o.role)),3),o.managerStatus?(t(),s("span",le,n(o.managerStatus),1)):r("",!0)]),e("td",ne,[e("span",{class:$(o.status==="ready"?"s-ok":"s-failed")},n(w(_)(o.status)),3)]),e("td",re,n(w(_)(o.availability||"unknown")),1),e("td",ie,n(o.engineVersion||"—"),1),e("td",de,n(o.id.slice(0,12)),1)],4))),128))])])):(t(),s("p",ue,"Docker lists no nodes."))],64)):(t(),s("div",X,[a[9]||(a[9]=e("b",null,"Not a swarm manager.",-1)),e("p",null,n(l.value.note),1)])):(t(),s("div",W,[...a[8]||(a[8]=[e("b",null,"Docker did not answer — this is not the same as no swarm.",-1)])]))])):r("",!0),(T=l.value)!=null&&T.active?(t(),s("section",me,[a[17]||(a[17]=e("div",{class:"phead"},[e("h2",{class:"section"},"Add a worker")],-1)),a[18]||(a[18]=e("p",{class:"dim"},"Open these between the new machine and every other node.",-1)),e("ul",ve,[(t(!0),s(k,null,S(l.value.ports,o=>(t(),s("li",{key:o.port},[e("span",ce,n(o.port),1),e("span",pe,n(o.why),1)]))),128))]),e("div",be,[e("div",fe,[a[12]||(a[12]=e("label",{for:"join-format"},"Format",-1)),b(N,{id:"join-format","model-value":m.value,label:"Join format",options:C.map(o=>({value:o.value,label:o.label})),"onUpdate:modelValue":a[0]||(a[0]=o=>m.value=o)},null,8,["model-value","options"])]),m.value==="cloud-config"?(t(),s("div",he,[a[13]||(a[13]=e("label",{for:"join-distro"},"Distro",-1)),b(N,{id:"join-distro",modelValue:h.value,"onUpdate:modelValue":a[1]||(a[1]=o=>h.value=o),label:"Distro",options:["ubuntu","debian","fedora","suse","arch","alpine"].map(o=>({value:o,label:o}))},null,8,["modelValue","options"])])):r("",!0),b(O,{variant:"primary",pending:p.value,disabled:p.value,onClick:z},{default:R(()=>[...a[14]||(a[14]=[f(" Reveal ",-1)])]),_:1},8,["pending","disabled"]),e("span",ye,n((j=C.find(o=>o.value===m.value))==null?void 0:j.blurb),1)]),v.value?(t(),y(A,{key:0,text:v.value,title:"Nothing was revealed."},null,8,["text"])):r("",!0),u.value?(t(),s("div",ke,[a[15]||(a[15]=e("b",null,"Secret, shown once — whoever has it can add a node that runs any task here.",-1)),a[16]||(a[16]=e("p",null,[f(" If it leaks, rotate it on the manager: "),e("code",null,"docker swarm join-token --rotate worker")],-1)),e("pre",we,n(u.value),1),e("div",_e,[e("button",{onClick:B},"Copy"),e("button",{onClick:a[2]||(a[2]=o=>u.value="")},"Hide")])])):r("",!0)])):r("",!0)])}}});export{De as default};
2
- //# sourceMappingURL=SwarmView-BHtmW4QF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SwarmView-BHtmW4QF.js","sources":["../../src/views/SwarmView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The swarm this host manages: its nodes, and what a new worker needs to join.\n *\n * Read straight from docker on every refresh — there is no cached node list anywhere (a table of\n * what is running is exactly what AGENTS.md invariant 10 refuses). Three states are kept apart:\n * docker did not answer (nothing is known), this daemon is not a manager (previews run with\n * compose), and active (the node table).\n *\n * THE JOIN TOKEN IS A SECRET. Whoever holds it can add a node that runs any task on the cluster.\n * It is fetched on a click and never by the poll, shown once, held in component state only, and\n * cleared the moment this page is left.\n */\nimport { onBeforeUnmount, ref } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { SwarmInfo } from '../api/types';\nimport { usePolling } from '../composables/usePolling';\nimport { sentence } from '../composables/useFormat';\nimport { toast } from '../composables/useToasts';\nimport ActionButton from '../components/ActionButton.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport RefreshButton from '../components/RefreshButton.vue';\nimport SelectMenu from '../components/SelectMenu.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\n\nconst info = ref<SwarmInfo | null>(null);\nconst error = ref('');\nconst loaded = ref(false);\n\nasync function load(): Promise<void> {\n const r = await api.get<SwarmInfo>('/api/swarm');\n loaded.value = true;\n if (!r.ok) {\n error.value = problem(r, 'read the swarm');\n return;\n }\n error.value = '';\n info.value = {\n ...r.body,\n nodes: r.body.nodes ?? [],\n ports: r.body.ports ?? [],\n };\n}\nusePolling(load, 10_000);\n\n// ── joining ─────────────────────────────────────────────────────────────────────────────────────\ntype Format = 'token' | 'command' | 'script' | 'cloud-config';\nconst format = ref<Format>('command');\nconst distro = ref('ubuntu');\nconst revealed = ref('');\nconst revealing = ref(false);\nconst joinError = ref('');\n\nconst FORMATS: Array<{ value: Format; label: string; blurb: string }> = [\n { value: 'command', label: 'docker swarm join …', blurb: 'For a machine already running Docker.' },\n { value: 'script', label: 'Shell script', blurb: 'Installs Docker, then joins.' },\n { value: 'cloud-config', label: 'cloud-config', blurb: 'User-data for a fresh machine.' },\n { value: 'token', label: 'Token only', blurb: 'For your own tooling.' },\n];\n\nasync function reveal(): Promise<void> {\n revealing.value = true;\n joinError.value = '';\n revealed.value = '';\n const q = `format=${format.value}${format.value === 'cloud-config' ? `&distro=${encodeURIComponent(distro.value)}` : ''}`;\n const r = await api.getText(`/api/swarm/join?${q}`);\n revealing.value = false;\n if (r.status === 403) {\n joinError.value = 'The join token is admin-only.';\n return;\n }\n if (r.status === 409) {\n joinError.value = 'This host is not a swarm manager.';\n return;\n }\n if (!r.ok) {\n joinError.value = r.text || `HTTP ${r.status}`;\n return;\n }\n revealed.value = r.text;\n}\n\nasync function copy(): Promise<void> {\n try {\n await navigator.clipboard.writeText(revealed.value);\n toast('ok', 'Copied.');\n } catch {\n toast('error', 'Copy failed — select it and copy by hand.');\n }\n}\n\n// Leaving the page forgets the secret; coming back means asking for it again.\nonBeforeUnmount(() => {\n revealed.value = '';\n});\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <!-- How swarm mode works, what a worker can and cannot do, and where a worker's volumes\n live is docs/usage.md's job; this page shows the cluster as it is right now. -->\n <h1>Swarm</h1>\n <div class=\"sub\">The nodes previews run on</div>\n </div>\n <span class=\"grow\" />\n <RefreshButton :run=\"load\" />\n </div>\n\n <ErrorNote v-if=\"error\" :text=\"error\" title=\"Could not read the swarm.\" />\n\n <SkeletonList v-if=\"!loaded && !error\" :rows=\"4\" />\n\n <!-- ============================ the cluster ============================ -->\n <section v-else-if=\"info\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Nodes</h2>\n <span class=\"grow\" />\n <span v-if=\"info.active && info.managerAddr\" class=\"mute\" style=\"font-size: var(--t-sm)\">\n manager <span class=\"mono\">{{ info.managerAddr }}</span>\n </span>\n </div>\n\n <!-- Three states, never folded: unknown is not \"no nodes\", and not-a-manager is not an error. -->\n <div v-if=\"!info.reachable\" class=\"banner warn\">\n <b>Docker did not answer — this is not the same as no swarm.</b>\n </div>\n <div v-else-if=\"!info.active\" class=\"banner plain\">\n <b>Not a swarm manager.</b>\n <p>{{ info.note }}</p>\n </div>\n <template v-else>\n <div v-if=\"info.error\" class=\"banner warn\">\n <b>Docker reported a problem.</b>\n <p class=\"mono\">{{ info.error }}</p>\n </div>\n <table v-if=\"info.nodes.length\" role=\"table\" class=\"cards\">\n <thead role=\"rowgroup\">\n <tr role=\"row\">\n <th role=\"columnheader\">Hostname</th>\n <th role=\"columnheader\">Role</th>\n <th role=\"columnheader\">Status</th>\n <th role=\"columnheader\">Availability</th>\n <th role=\"columnheader\">Engine</th>\n <th role=\"columnheader\">Id</th>\n </tr>\n </thead>\n <tbody role=\"rowgroup\" class=\"stagger\">\n <tr v-for=\"(n, i) in info.nodes\" :key=\"n.id\" role=\"row\" :style=\"{ '--i': i }\">\n <td role=\"cell\" class=\"name\" data-label=\"hostname\">\n {{ n.hostname || '—' }}\n <span v-if=\"n.self\" class=\"badge info\" title=\"the node this control plane runs on\">This node</span>\n </td>\n <td role=\"cell\" data-label=\"role\">\n <span class=\"badge\" :class=\"n.role === 'manager' ? 'isolated' : ''\">{{ sentence(n.role) }}</span>\n <span v-if=\"n.managerStatus\" class=\"mute\" style=\"font-size: var(--t-sm)\"> {{ n.managerStatus }}</span>\n </td>\n <td role=\"cell\" data-label=\"status\">\n <span :class=\"n.status === 'ready' ? 's-ok' : 's-failed'\">{{ sentence(n.status) }}</span>\n </td>\n <td role=\"cell\" data-label=\"availability\">{{ sentence(n.availability || 'unknown') }}</td>\n <td role=\"cell\" data-label=\"engine\" class=\"mono\">{{ n.engineVersion || '—' }}</td>\n <td role=\"cell\" data-label=\"id\" class=\"mono mute\">{{ n.id.slice(0, 12) }}</td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"mute\">Docker lists no nodes.</p>\n </template>\n </section>\n\n <!-- ============================ add a worker ============================ -->\n <section v-if=\"info?.active\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Add a worker</h2>\n </div>\n\n <p class=\"dim\">Open these between the new machine and every other node.</p>\n <ul class=\"kvlist\" style=\"margin-bottom: var(--s4)\">\n <li v-for=\"p in info.ports\" :key=\"p.port\">\n <span class=\"k mono\">{{ p.port }}</span>\n <span class=\"v mute\">{{ p.why }}</span>\n </li>\n </ul>\n\n <div class=\"row\" style=\"flex-wrap: wrap; gap: var(--s3)\">\n <div class=\"field inline\">\n <label for=\"join-format\">Format</label>\n <SelectMenu\n id=\"join-format\"\n :model-value=\"format\"\n label=\"Join format\"\n :options=\"FORMATS.map((f) => ({ value: f.value, label: f.label }))\"\n @update:model-value=\"(v) => (format = v as Format)\"\n />\n </div>\n <div v-if=\"format === 'cloud-config'\" class=\"field inline\">\n <label for=\"join-distro\">Distro</label>\n <SelectMenu\n id=\"join-distro\"\n v-model=\"distro\"\n label=\"Distro\"\n :options=\"['ubuntu', 'debian', 'fedora', 'suse', 'arch', 'alpine'].map((d) => ({ value: d, label: d }))\"\n />\n </div>\n <ActionButton variant=\"primary\" :pending=\"revealing\" :disabled=\"revealing\" @click=\"reveal\">\n Reveal\n </ActionButton>\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">{{ FORMATS.find((f) => f.value === format)?.blurb }}</span>\n </div>\n\n <ErrorNote v-if=\"joinError\" :text=\"joinError\" title=\"Nothing was revealed.\" />\n\n <div v-if=\"revealed\" class=\"banner leaked\" style=\"margin-top: var(--s3)\">\n <b>Secret, shown once — whoever has it can add a node that runs any task here.</b>\n <p>\n If it leaks, rotate it on the manager:\n <code>docker swarm join-token --rotate worker</code>\n </p>\n <pre class=\"code\" style=\"white-space: pre-wrap; word-break: break-all; max-height: 50vh; overflow: auto\">{{ revealed }}</pre>\n <div class=\"row\">\n <button @click=\"copy\">Copy</button>\n <button @click=\"revealed = ''\">Hide</button>\n </div>\n </div>\n </section>\n </div>\n</template>\n"],"names":["info","ref","error","loaded","load","r","api","problem","usePolling","format","distro","revealed","revealing","joinError","FORMATS","reveal","q","copy","toast","onBeforeUnmount","_createElementBlock","_createElementVNode","_hoisted_1","_createVNode","RefreshButton","_createBlock","ErrorNote","SkeletonList","_openBlock","_hoisted_2","_hoisted_3","_cache","_hoisted_4","_hoisted_5","_toDisplayString","_Fragment","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","n","i","_hoisted_12","_hoisted_13","_hoisted_14","_normalizeClass","_unref","sentence","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_7","_hoisted_6","_a","_hoisted_21","_hoisted_22","_renderList","p","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","SelectMenu","f","v","_hoisted_27","$event","d","ActionButton","_hoisted_28","_b","_hoisted_29","_hoisted_30","_hoisted_31"],"mappings":"u2DAyBA,MAAMA,EAAOC,EAAsB,IAAI,EACjCC,EAAQD,EAAI,EAAE,EACdE,EAASF,EAAI,EAAK,EAExB,eAAeG,GAAsB,CACnC,MAAMC,EAAI,MAAMC,EAAI,IAAe,YAAY,EAE/C,GADAH,EAAO,MAAQ,GACX,CAACE,EAAE,GAAI,CACTH,EAAM,MAAQK,EAAQF,EAAG,gBAAgB,EACzC,MACF,CACAH,EAAM,MAAQ,GACdF,EAAK,MAAQ,CACX,GAAGK,EAAE,KACL,MAAOA,EAAE,KAAK,OAAS,CAAA,EACvB,MAAOA,EAAE,KAAK,OAAS,CAAA,CAAC,CAE5B,CACAG,EAAWJ,EAAM,GAAM,EAIvB,MAAMK,EAASR,EAAY,SAAS,EAC9BS,EAAST,EAAI,QAAQ,EACrBU,EAAWV,EAAI,EAAE,EACjBW,EAAYX,EAAI,EAAK,EACrBY,EAAYZ,EAAI,EAAE,EAElBa,EAAkE,CACtE,CAAE,MAAO,UAAW,MAAO,sBAAuB,MAAO,uCAAA,EACzD,CAAE,MAAO,SAAU,MAAO,eAAgB,MAAO,8BAAA,EACjD,CAAE,MAAO,eAAgB,MAAO,eAAgB,MAAO,gCAAA,EACvD,CAAE,MAAO,QAAS,MAAO,aAAc,MAAO,uBAAA,CAAwB,EAGxE,eAAeC,GAAwB,CACrCH,EAAU,MAAQ,GAClBC,EAAU,MAAQ,GAClBF,EAAS,MAAQ,GACjB,MAAMK,EAAI,UAAUP,EAAO,KAAK,GAAGA,EAAO,QAAU,eAAiB,WAAW,mBAAmBC,EAAO,KAAK,CAAC,GAAK,EAAE,GACjHL,EAAI,MAAMC,EAAI,QAAQ,mBAAmBU,CAAC,EAAE,EAElD,GADAJ,EAAU,MAAQ,GACdP,EAAE,SAAW,IAAK,CACpBQ,EAAU,MAAQ,gCAClB,MACF,CACA,GAAIR,EAAE,SAAW,IAAK,CACpBQ,EAAU,MAAQ,oCAClB,MACF,CACA,GAAI,CAACR,EAAE,GAAI,CACTQ,EAAU,MAAQR,EAAE,MAAQ,QAAQA,EAAE,MAAM,GAC5C,MACF,CACAM,EAAS,MAAQN,EAAE,IACrB,CAEA,eAAeY,GAAsB,CACnC,GAAI,CACF,MAAM,UAAU,UAAU,UAAUN,EAAS,KAAK,EAClDO,EAAM,KAAM,SAAS,CACvB,MAAQ,CACNA,EAAM,QAAS,2CAA2C,CAC5D,CACF,CAGA,OAAAC,EAAgB,IAAM,CACpBR,EAAS,MAAQ,EACnB,CAAC,6BAICS,EAgIM,MAAA,KAAA,CA/HJC,EASM,MATNC,EASM,aARJD,EAKM,MAAA,KAAA,CAFJA,EAAc,UAAV,OAAK,EACTA,EAAgD,MAAA,CAA3C,MAAM,KAAA,EAAM,2BAAyB,CAAA,mBAE5CA,EAAqB,OAAA,CAAf,MAAM,MAAA,EAAM,KAAA,EAAA,GAClBE,EAA6BC,EAAA,CAAb,IAAKpB,EAAI,CAAA,GAGVF,EAAA,WAAjBuB,EAA0EC,EAAA,OAAjD,KAAMxB,EAAA,MAAO,MAAM,2BAAA,6BAEvB,CAAAC,EAAA,QAAWD,EAAA,WAAhCuB,EAAmDE,EAAA,OAAX,KAAM,CAAA,IAG1B3B,EAAA,OAApB4B,IAAAR,EAsDU,UAtDVS,EAsDU,CArDRR,EAMM,MANNS,EAMM,CALJC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAV,EAA8B,KAAA,CAA1B,MAAM,SAAA,EAAU,QAAK,EAAA,eACzBA,EAAqB,OAAA,CAAf,MAAM,MAAA,EAAM,KAAA,EAAA,GACNrB,EAAA,MAAK,QAAUA,EAAA,MAAK,aAAhC4B,IAAAR,EAEO,OAFPY,EAEO,eAFkF,YAC/E,EAAA,GAAAX,EAAgD,OAAhDY,EAAgDC,EAA1BlC,EAAA,MAAK,WAAW,EAAA,CAAA,CAAA,eAKtCA,EAAA,MAAK,UAGAA,EAAA,MAAK,YAItBoB,EAoCWe,EAAA,CAAA,IAAA,GAAA,CAnCEnC,EAAA,MAAK,OAAhB4B,IAAAR,EAGM,MAHNgB,EAGM,CAFJL,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAV,EAAiC,SAA9B,6BAA0B,EAAA,GAC7BA,EAAoC,IAApCgB,EAAoCH,EAAjBlC,EAAA,MAAK,KAAK,EAAA,CAAA,CAAA,aAElBA,EAAA,MAAK,MAAM,QAAxB4B,IAAAR,EA6BQ,QA7BRkB,GA6BQ,eA5BNjB,EASQ,QAAA,CATD,KAAK,YAAU,CACpBA,EAOK,KAAA,CAPD,KAAK,OAAK,CACZA,EAAqC,KAAA,CAAjC,KAAK,cAAA,EAAe,UAAQ,EAChCA,EAAiC,KAAA,CAA7B,KAAK,cAAA,EAAe,MAAI,EAC5BA,EAAmC,KAAA,CAA/B,KAAK,cAAA,EAAe,QAAM,EAC9BA,EAAyC,KAAA,CAArC,KAAK,cAAA,EAAe,cAAY,EACpCA,EAAmC,KAAA,CAA/B,KAAK,cAAA,EAAe,QAAM,EAC9BA,EAA+B,KAAA,CAA3B,KAAK,cAAA,EAAe,IAAE,CAAA,SAG9BA,EAiBQ,QAjBRkB,GAiBQ,EAhBNX,EAAA,EAAA,EAAAR,EAeKe,SAfgBnC,EAAA,MAAK,MAAK,CAAnBwC,EAAGC,SAAfrB,EAeK,KAAA,CAf6B,IAAKoB,EAAE,GAAI,KAAK,MAAO,eAAgBC,EAAC,CAAA,GACxEpB,EAGK,KAHLqB,GAGK,KAFAF,EAAE,UAAQ,GAAA,EAAU,IACvB,CAAA,EAAYA,EAAE,UAAdpB,EAAmG,OAAnGuB,GAAmF,WAAS,cAE9FtB,EAGK,KAHLuB,GAGK,CAFHvB,EAAiG,OAAA,CAA3F,MAAKwB,EAAA,CAAC,QAAgBL,EAAE,OAAI,UAAA,WAAA,EAAA,CAAA,CAAA,IAAqCM,EAAAC,CAAA,EAASP,EAAE,IAAI,CAAA,EAAA,CAAA,EAC1EA,EAAE,eAAdZ,EAAA,EAAAR,EAAsG,OAAtG4B,GAAsGd,EAAzBM,EAAE,aAAa,EAAA,CAAA,cAE9FnB,EAEK,KAFL4B,GAEK,CADH5B,EAAyF,OAAA,CAAlF,MAAKwB,EAAEL,EAAE,SAAM,QAAA,OAAA,UAAA,CAAA,IAAuCM,EAAAC,CAAA,EAASP,EAAE,MAAM,CAAA,EAAA,CAAA,CAAA,GAEhFnB,EAA0F,KAA1F6B,GAA0FhB,EAA7CY,KAASN,EAAE,cAAY,SAAA,CAAA,EAAA,CAAA,EACpEnB,EAAkF,KAAlF8B,GAAkFjB,EAA9BM,EAAE,eAAa,GAAA,EAAA,CAAA,EACnEnB,EAA8E,KAA9E+B,GAA8ElB,EAAzBM,EAAE,GAAG,MAAK,EAAA,EAAA,CAAA,EAAA,CAAA,CAAA,uBAIrEpB,EAAiD,IAAjDiC,GAAuB,wBAAsB,EAAA,QAvC/CzB,IAAAR,EAGM,MAHNkC,EAGM,CAFJvB,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAV,EAA2B,SAAxB,uBAAoB,EAAA,GACvBA,EAAsB,IAAA,KAAAa,EAAhBlC,EAAA,MAAK,IAAI,EAAA,CAAA,CAAA,KALjB4B,EAAA,EAAAR,EAEM,MAFNmC,EAEM,CAAA,GAAAxB,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CADJV,EAAgE,SAA7D,4DAAyD,EAAA,CAAA,mBA8CjDmC,EAAAxD,EAAA,QAAA,MAAAwD,EAAM,QAArB5B,IAAAR,EAqDU,UArDVqC,GAqDU,eApDRpC,EAEM,MAAA,CAFD,MAAM,SAAO,CAChBA,EAAqC,KAAA,CAAjC,MAAM,SAAA,EAAU,cAAY,CAAA,OAGlCU,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAV,EAA2E,IAAA,CAAxE,MAAM,KAAA,EAAM,2DAAwD,EAAA,GACvEA,EAKK,KALLqC,GAKK,EAJH9B,EAAA,EAAA,EAAAR,EAGKe,EAAA,KAAAwB,EAHW3D,EAAA,MAAK,MAAV4D,QAAXxC,EAGK,KAAA,CAHwB,IAAKwC,EAAE,IAAA,GAClCvC,EAAwC,OAAxCwC,GAAwC3B,EAAhB0B,EAAE,IAAI,EAAA,CAAA,EAC9BvC,EAAuC,OAAvCyC,GAAuC5B,EAAf0B,EAAE,GAAG,EAAA,CAAA,CAAA,aAIjCvC,EAwBM,MAxBN0C,GAwBM,CAvBJ1C,EASM,MATN2C,GASM,CARJjC,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAV,EAAuC,QAAA,CAAhC,IAAI,aAAA,EAAc,SAAM,EAAA,GAC/BE,EAME0C,EAAA,CALA,GAAG,cACF,cAAaxD,EAAA,MACd,MAAM,cACL,QAASK,EAAQ,IAAKoD,IAAC,CAAA,MAAeA,EAAE,MAAK,MAASA,EAAE,KAAA,EAAK,EAC7D,sBAAkBnC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAGoC,GAAO1D,EAAA,MAAS0D,EAAA,sCAG/B1D,EAAA,QAAM,gBAAjBmB,IAAAR,EAQM,MARNgD,GAQM,CAPJrC,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAV,EAAuC,QAAA,CAAhC,IAAI,aAAA,EAAc,SAAM,EAAA,GAC/BE,EAKE0C,EAAA,CAJA,GAAG,yBACMvD,EAAA,2CAAAA,EAAM,MAAA2D,GACf,MAAM,SACL,4DAAkE,IAAKC,IAAC,CAAA,MAAeA,QAAUA,GAAC,CAAA,+CAGvG/C,EAEegD,EAAA,CAFD,QAAQ,UAAW,QAAS3D,EAAA,MAAY,SAAUA,EAAA,MAAY,QAAOG,CAAA,aAAQ,IAE3F,CAAA,GAAAgB,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAA,GAF2F,WAE3F,EAAA,CAAA,oCACAV,EAA6G,OAA7GmD,GAA6GtC,GAAxDuC,EAAA3D,EAAQ,KAAMoD,GAAMA,EAAE,QAAUzD,EAAA,KAAM,IAAtC,YAAAgE,EAAyC,KAAK,EAAA,CAAA,CAAA,GAGpF5D,EAAA,WAAjBY,EAA8EC,EAAA,OAAjD,KAAMb,EAAA,MAAW,MAAM,uBAAA,6BAEzCF,EAAA,OAAXiB,EAAA,EAAAR,EAWM,MAXNsD,GAWM,CAVJ3C,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAV,EAAkF,SAA/E,8EAA2E,EAAA,iBAC9EA,EAGI,IAAA,KAAA,GAHD,0CAED,EAAAA,EAAoD,YAA9C,yCAAuC,CAAA,OAE/CA,EAA6H,MAA7HsD,GAA6HzC,EAAjBvB,EAAA,KAAQ,EAAA,CAAA,EACpHU,EAGM,MAHNuD,GAGM,CAFJvD,EAAmC,SAAA,CAA1B,QAAOJ,CAAA,EAAM,MAAI,EAC1BI,EAA4C,SAAA,CAAnC,uBAAOV,EAAA,MAAQ,GAAA,EAAO,MAAI,CAAA"}