@samyx/preview-stacks-ui 0.25.2 → 0.27.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 (122) hide show
  1. package/dist/assets/{ActionButton.vue_vue_type_script_setup_true_lang-D5wrEmB2.js → ActionButton.vue_vue_type_script_setup_true_lang-B0qf7_XI.js} +2 -2
  2. package/dist/assets/{ActionButton.vue_vue_type_script_setup_true_lang-D5wrEmB2.js.map → ActionButton.vue_vue_type_script_setup_true_lang-B0qf7_XI.js.map} +1 -1
  3. package/dist/assets/{AxesTab-DPb0krkI.js → AxesTab-BxVd_LDI.js} +2 -2
  4. package/dist/assets/{AxesTab-DPb0krkI.js.map → AxesTab-BxVd_LDI.js.map} +1 -1
  5. package/dist/assets/{ConfigTab-DLhjDiis.js → ConfigTab-DSHTlGAK.js} +2 -2
  6. package/dist/assets/{ConfigTab-DLhjDiis.js.map → ConfigTab-DSHTlGAK.js.map} +1 -1
  7. package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-R1IfGYYT.js +2 -0
  8. package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-R1IfGYYT.js.map +1 -0
  9. package/dist/assets/DangerTab-BZ5e4YGe.js +2 -0
  10. package/dist/assets/DangerTab-BZ5e4YGe.js.map +1 -0
  11. package/dist/assets/{DashboardView-DxZUkxRF.js → DashboardView-TYfDe8IH.js} +2 -2
  12. package/dist/assets/{DashboardView-DxZUkxRF.js.map → DashboardView-TYfDe8IH.js.map} +1 -1
  13. package/dist/assets/DeploymentDetailView-RSDMb2tC.js +2 -0
  14. package/dist/assets/DeploymentDetailView-RSDMb2tC.js.map +1 -0
  15. package/dist/assets/DeploymentsView-DYAPYBih.js +2 -0
  16. package/dist/assets/DeploymentsView-DYAPYBih.js.map +1 -0
  17. package/dist/assets/{EquivalentCommand-C45BzkNz.js → EquivalentCommand-GFG6rkn5.js} +3 -3
  18. package/dist/assets/{EquivalentCommand-C45BzkNz.js.map → EquivalentCommand-GFG6rkn5.js.map} +1 -1
  19. package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-B3hfwyPR.js → ErrorNote.vue_vue_type_script_setup_true_lang-DNaY6OBe.js} +2 -2
  20. package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-B3hfwyPR.js.map → ErrorNote.vue_vue_type_script_setup_true_lang-DNaY6OBe.js.map} +1 -1
  21. package/dist/assets/HelpModal.vue_vue_type_script_setup_true_lang-DEjnzS2A.js +2 -0
  22. package/dist/assets/{HelpModal.vue_vue_type_script_setup_true_lang-BSOuxyyP.js.map → HelpModal.vue_vue_type_script_setup_true_lang-DEjnzS2A.js.map} +1 -1
  23. package/dist/assets/JobDetailView-DvFGqRQj.js +2 -0
  24. package/dist/assets/{JobDetailView-HFOJln2b.js.map → JobDetailView-DvFGqRQj.js.map} +1 -1
  25. package/dist/assets/{JobsView-dWw_uNGE.js → JobsView-C-rxx15K.js} +2 -2
  26. package/dist/assets/{JobsView-dWw_uNGE.js.map → JobsView-C-rxx15K.js.map} +1 -1
  27. package/dist/assets/{LogViewer.vue_vue_type_script_setup_true_lang-5r84oNls.js → LogViewer.vue_vue_type_script_setup_true_lang-6CJlo0ox.js} +3 -3
  28. package/dist/assets/{LogViewer.vue_vue_type_script_setup_true_lang-5r84oNls.js.map → LogViewer.vue_vue_type_script_setup_true_lang-6CJlo0ox.js.map} +1 -1
  29. package/dist/assets/LoginView-CAXL0Xhr.js +2 -0
  30. package/dist/assets/LoginView-CAXL0Xhr.js.map +1 -0
  31. package/dist/assets/LoginView-Djedwklv.css +1 -0
  32. package/dist/assets/LogsTab-Bzz4JBQM.js +6 -0
  33. package/dist/assets/LogsTab-Bzz4JBQM.js.map +1 -0
  34. package/dist/assets/{NotFoundView-jZIc0wDm.js → NotFoundView-BRn9Jn4H.js} +2 -2
  35. package/dist/assets/{NotFoundView-jZIc0wDm.js.map → NotFoundView-BRn9Jn4H.js.map} +1 -1
  36. package/dist/assets/{NotifiersView-DJu-c-a6.js → NotifiersView-Bd13G-1b.js} +2 -2
  37. package/dist/assets/{NotifiersView-DJu-c-a6.js.map → NotifiersView-Bd13G-1b.js.map} +1 -1
  38. package/dist/assets/OverviewTab-Ae5-H5DV.js +2 -0
  39. package/dist/assets/OverviewTab-Ae5-H5DV.js.map +1 -0
  40. package/dist/assets/PublicLogsView-BAe90EbI.css +1 -0
  41. package/dist/assets/PublicLogsView-DvCE0GT1.js +4 -0
  42. package/dist/assets/PublicLogsView-DvCE0GT1.js.map +1 -0
  43. package/dist/assets/{RefreshButton.vue_vue_type_script_setup_true_lang-CXCY3Vlc.js → RefreshButton.vue_vue_type_script_setup_true_lang-DpeOKN8U.js} +2 -2
  44. package/dist/assets/{RefreshButton.vue_vue_type_script_setup_true_lang-CXCY3Vlc.js.map → RefreshButton.vue_vue_type_script_setup_true_lang-DpeOKN8U.js.map} +1 -1
  45. package/dist/assets/RegistriesView-ZRRfxVTX.js +2 -0
  46. package/dist/assets/{RegistriesView-BeiFx468.js.map → RegistriesView-ZRRfxVTX.js.map} +1 -1
  47. package/dist/assets/{RelativeTime.vue_vue_type_script_setup_true_lang-BGissyps.js → RelativeTime.vue_vue_type_script_setup_true_lang-Dm9XnXF7.js} +2 -2
  48. package/dist/assets/{RelativeTime.vue_vue_type_script_setup_true_lang-BGissyps.js.map → RelativeTime.vue_vue_type_script_setup_true_lang-Dm9XnXF7.js.map} +1 -1
  49. package/dist/assets/{RequiresTab-Bb3_lYqC.js → RequiresTab-CLfNJHOh.js} +2 -2
  50. package/dist/assets/{RequiresTab-Bb3_lYqC.js.map → RequiresTab-CLfNJHOh.js.map} +1 -1
  51. package/dist/assets/{RoutingView-y8L_ex2z.js → RoutingView-BCsr0bjm.js} +3 -3
  52. package/dist/assets/{RoutingView-y8L_ex2z.js.map → RoutingView-BCsr0bjm.js.map} +1 -1
  53. package/dist/assets/RuntimeTab-CeJS5wuv.js +2 -0
  54. package/dist/assets/RuntimeTab-CeJS5wuv.js.map +1 -0
  55. package/dist/assets/SelectMenu.vue_vue_type_script_setup_true_lang-BLfPhhAj.js +2 -0
  56. package/dist/assets/{SelectMenu.vue_vue_type_script_setup_true_lang-D8NGgBfa.js.map → SelectMenu.vue_vue_type_script_setup_true_lang-BLfPhhAj.js.map} +1 -1
  57. package/dist/assets/{SettingsView-C-xJVgVw.js → SettingsView-Bo-SQP0q.js} +2 -2
  58. package/dist/assets/{SettingsView-C-xJVgVw.js.map → SettingsView-Bo-SQP0q.js.map} +1 -1
  59. package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-qMyLRAMp.js → SkeletonList.vue_vue_type_script_setup_true_lang-DjfNq2KM.js} +2 -2
  60. package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-qMyLRAMp.js.map → SkeletonList.vue_vue_type_script_setup_true_lang-DjfNq2KM.js.map} +1 -1
  61. package/dist/assets/SpecDetailView-C68gpDhO.js +2 -0
  62. package/dist/assets/{SpecDetailView-CGQZS7NU.js.map → SpecDetailView-C68gpDhO.js.map} +1 -1
  63. package/dist/assets/{SpecsView-D9mKql2j.js → SpecsView-mr3zSd-0.js} +2 -2
  64. package/dist/assets/{SpecsView-D9mKql2j.js.map → SpecsView-mr3zSd-0.js.map} +1 -1
  65. package/dist/assets/SsoView-D2yf-noE.js +2 -0
  66. package/dist/assets/SsoView-D2yf-noE.js.map +1 -0
  67. package/dist/assets/{StateBadge.vue_vue_type_script_setup_true_lang-BgbZLzlL.js → StateBadge.vue_vue_type_script_setup_true_lang-BqldkWlT.js} +2 -2
  68. package/dist/assets/{StateBadge.vue_vue_type_script_setup_true_lang-BgbZLzlL.js.map → StateBadge.vue_vue_type_script_setup_true_lang-BqldkWlT.js.map} +1 -1
  69. package/dist/assets/{SubmitView-DuGtFMgF.js → SubmitView-Cdxo6Chq.js} +5 -5
  70. package/dist/assets/{SubmitView-DuGtFMgF.js.map → SubmitView-Cdxo6Chq.js.map} +1 -1
  71. package/dist/assets/SwarmView-DVKI5lNe.js +2 -0
  72. package/dist/assets/SwarmView-DVKI5lNe.js.map +1 -0
  73. package/dist/assets/Teleport-BUn4ayOo.js +4 -0
  74. package/dist/assets/{Teleport-DfE2iP8c.js.map → Teleport-BUn4ayOo.js.map} +1 -1
  75. package/dist/assets/TerminalTab-BRumgZMI.js +3 -0
  76. package/dist/assets/{TerminalTab-D5ncE4Cv.js.map → TerminalTab-BRumgZMI.js.map} +1 -1
  77. package/dist/assets/{UsersView-BxU2ho4N.js → UsersView-B7C2Bc3R.js} +2 -2
  78. package/dist/assets/{UsersView-BxU2ho4N.js.map → UsersView-B7C2Bc3R.js.map} +1 -1
  79. package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-SKwJUsNC.js → VarEditor.vue_vue_type_script_setup_true_lang-CyOlKVt5.js} +2 -2
  80. package/dist/assets/{VarEditor.vue_vue_type_script_setup_true_lang-SKwJUsNC.js.map → VarEditor.vue_vue_type_script_setup_true_lang-CyOlKVt5.js.map} +1 -1
  81. package/dist/assets/{VariablesView-BnW2I-92.js → VariablesView-UCBhNd_o.js} +2 -2
  82. package/dist/assets/{VariablesView-BnW2I-92.js.map → VariablesView-UCBhNd_o.js.map} +1 -1
  83. package/dist/assets/index-9Ra4jfL4.js +149 -0
  84. package/dist/assets/index-9Ra4jfL4.js.map +1 -0
  85. package/dist/assets/index-B_k9b7xG.css +1 -0
  86. package/dist/assets/useDeployment-DFOIiXrd.js +2 -0
  87. package/dist/assets/useDeployment-DFOIiXrd.js.map +1 -0
  88. package/dist/assets/useFormat-DIlTY5Dl.js +2 -0
  89. package/dist/assets/useFormat-DIlTY5Dl.js.map +1 -0
  90. package/dist/index.html +2 -2
  91. package/package.json +1 -1
  92. package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-HglPeTkY.js +0 -2
  93. package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-HglPeTkY.js.map +0 -1
  94. package/dist/assets/DangerTab-BLDIlbN5.js +0 -2
  95. package/dist/assets/DangerTab-BLDIlbN5.js.map +0 -1
  96. package/dist/assets/DeploymentDetailView-CEg8sA5I.js +0 -2
  97. package/dist/assets/DeploymentDetailView-CEg8sA5I.js.map +0 -1
  98. package/dist/assets/DeploymentsView-BfhwFiiQ.js +0 -2
  99. package/dist/assets/DeploymentsView-BfhwFiiQ.js.map +0 -1
  100. package/dist/assets/HelpModal.vue_vue_type_script_setup_true_lang-BSOuxyyP.js +0 -2
  101. package/dist/assets/JobDetailView-HFOJln2b.js +0 -2
  102. package/dist/assets/LoginView-0vIrAhlr.js +0 -2
  103. package/dist/assets/LoginView-0vIrAhlr.js.map +0 -1
  104. package/dist/assets/LoginView-BFIG7vGw.css +0 -1
  105. package/dist/assets/LogsTab-CFlgBsW3.js +0 -6
  106. package/dist/assets/LogsTab-CFlgBsW3.js.map +0 -1
  107. package/dist/assets/OverviewTab-DlJt0MIg.js +0 -2
  108. package/dist/assets/OverviewTab-DlJt0MIg.js.map +0 -1
  109. package/dist/assets/RegistriesView-BeiFx468.js +0 -2
  110. package/dist/assets/RuntimeTab-DWZ5eFho.js +0 -2
  111. package/dist/assets/RuntimeTab-DWZ5eFho.js.map +0 -1
  112. package/dist/assets/SelectMenu.vue_vue_type_script_setup_true_lang-D8NGgBfa.js +0 -2
  113. package/dist/assets/SpecDetailView-CGQZS7NU.js +0 -2
  114. package/dist/assets/Teleport-DfE2iP8c.js +0 -4
  115. package/dist/assets/TerminalTab-D5ncE4Cv.js +0 -3
  116. package/dist/assets/index-DVMQBHXF.js +0 -139
  117. package/dist/assets/index-DVMQBHXF.js.map +0 -1
  118. package/dist/assets/index-lyuEu6lW.css +0 -1
  119. package/dist/assets/useDeployment-DGzBsXE-.js +0 -2
  120. package/dist/assets/useDeployment-DGzBsXE-.js.map +0 -1
  121. package/dist/assets/useFormat-DVXTfIS-.js +0 -2
  122. package/dist/assets/useFormat-DVXTfIS-.js.map +0 -1
@@ -0,0 +1 @@
1
+ :root,:root[data-theme=dark]{color-scheme:dark;--bg: #0e1116;--bg-grad: radial-gradient(1100px 620px at 12% -12%, rgba(64, 106, 168, .22) 0%, transparent 70%), radial-gradient(900px 520px at 108% 8%, rgba(96, 76, 168, .13) 0%, transparent 68%);--panel: #171b22;--panel-2: #1e232c;--sunk: #11141a;--line: #2b323d;--line-soft: #21262f;--fg: #eef2f8;--fg-dim: #a4afbe;--fg-mute: #78838f;--accent: #4a9dff;--accent-soft: rgba(74, 157, 255, .18);--ok: #35d17e;--ok-soft: rgba(53, 209, 126, .17);--fail: #ff6159;--fail-soft: rgba(255, 97, 89, .17);--leak: #ffa62b;--leak-soft: rgba(255, 166, 43, .18);--neutral: #95a2b3;--neutral-soft: rgba(149, 162, 179, .16);--hairline: inset 0 1px 0 rgba(255, 255, 255, .055);--hairline-strong: inset 0 1px 0 rgba(255, 255, 255, .09);--elev-1: 0 1px 2px rgba(5, 8, 14, .5);--elev-2: 0 1px 2px rgba(5, 8, 14, .45), 0 6px 16px -4px rgba(5, 8, 14, .5);--elev-3: 0 1px 2px rgba(5, 8, 14, .5), 0 12px 32px -8px rgba(5, 8, 14, .62);--elev-4: 0 2px 4px rgba(5, 8, 14, .5), 0 24px 64px -12px rgba(5, 8, 14, .72);--shadow: var(--elev-2);--hover: rgba(255, 255, 255, .04);--hover-strong: rgba(255, 255, 255, .07);--press: rgba(0, 0, 0, .18);--skel: linear-gradient(90deg, #1c212a 0%, #262d38 50%, #1c212a 100%)}@media(prefers-color-scheme:light){:root:not([data-theme=dark]){color-scheme:light;--bg: #f5f6f8;--bg-grad: radial-gradient(1100px 620px at 12% -12%, rgba(31, 111, 235, .07) 0%, transparent 70%), radial-gradient(900px 520px at 108% 8%, rgba(120, 80, 220, .045) 0%, transparent 68%);--panel: #ffffff;--panel-2: #f4f6f9;--sunk: #eef1f5;--line: #dfe3e9;--line-soft: #eaedf1;--fg: #14181e;--fg-dim: #57616f;--fg-mute: #7d8794;--accent: #1f6feb;--accent-soft: rgba(31, 111, 235, .1);--ok: #1a7f37;--ok-soft: rgba(26, 127, 55, .1);--fail: #cf222e;--fail-soft: rgba(207, 34, 46, .1);--leak: #9a6700;--leak-soft: rgba(154, 103, 0, .12);--neutral: #656d76;--neutral-soft: rgba(101, 109, 118, .1);--hairline: inset 0 1px 0 rgba(255, 255, 255, .9);--hairline-strong: inset 0 1px 0 rgba(255, 255, 255, 1);--elev-1: 0 1px 1px rgba(16, 24, 40, .05);--elev-2: 0 1px 2px rgba(16, 24, 40, .06), 0 4px 12px -2px rgba(16, 24, 40, .07);--elev-3: 0 1px 2px rgba(16, 24, 40, .07), 0 10px 28px -6px rgba(16, 24, 40, .1);--elev-4: 0 2px 4px rgba(16, 24, 40, .07), 0 22px 56px -10px rgba(16, 24, 40, .14);--shadow: var(--elev-2);--hover: rgba(16, 24, 40, .035);--hover-strong: rgba(16, 24, 40, .06);--press: rgba(16, 24, 40, .07);--skel: linear-gradient(90deg, #e9edf2 0%, #f6f8fa 50%, #e9edf2 100%)}}:root[data-theme=light]{color-scheme:light;--bg: #f5f6f8;--bg-grad: radial-gradient(1100px 620px at 12% -12%, rgba(31, 111, 235, .07) 0%, transparent 70%), radial-gradient(900px 520px at 108% 8%, rgba(120, 80, 220, .045) 0%, transparent 68%);--panel: #ffffff;--panel-2: #f4f6f9;--sunk: #eef1f5;--line: #dfe3e9;--line-soft: #eaedf1;--fg: #14181e;--fg-dim: #57616f;--fg-mute: #7d8794;--accent: #1f6feb;--accent-soft: rgba(31, 111, 235, .1);--ok: #1a7f37;--ok-soft: rgba(26, 127, 55, .1);--fail: #cf222e;--fail-soft: rgba(207, 34, 46, .1);--leak: #9a6700;--leak-soft: rgba(154, 103, 0, .12);--neutral: #656d76;--neutral-soft: rgba(101, 109, 118, .1);--hairline: inset 0 1px 0 rgba(255, 255, 255, .9);--hairline-strong: inset 0 1px 0 rgba(255, 255, 255, 1);--elev-1: 0 1px 1px rgba(16, 24, 40, .05);--elev-2: 0 1px 2px rgba(16, 24, 40, .06), 0 4px 12px -2px rgba(16, 24, 40, .07);--elev-3: 0 1px 2px rgba(16, 24, 40, .07), 0 10px 28px -6px rgba(16, 24, 40, .1);--elev-4: 0 2px 4px rgba(16, 24, 40, .07), 0 22px 56px -10px rgba(16, 24, 40, .14);--shadow: var(--elev-2);--hover: rgba(16, 24, 40, .035);--hover-strong: rgba(16, 24, 40, .06);--press: rgba(16, 24, 40, .07);--skel: linear-gradient(90deg, #e9edf2 0%, #f6f8fa 50%, #e9edf2 100%)}:root{--t-xs: 12px;--t-sm: 13.5px;--t-md: 15px;--t-lg: 17px;--t-xl: 22px;--t-2xl: 28px;--t-3xl: 36px;--track-display: -.022em;--track-title: -.014em;--track-body: -.003em;--track-caps: .075em;--lh-tight: 1.15;--lh-body: 1.55;--sans: -apple-system, BlinkMacSystemFont, "Inter var", Inter, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--mono: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, Consolas, "Liberation Mono", monospace;--s1: 4px;--s2: 8px;--s3: 12px;--s4: 16px;--s5: 24px;--s6: 32px;--s7: 48px;--r0: 6px;--r1: 9px;--r2: 13px;--r3: 16px;--r4: 20px;--r-full: 999px;--control-h: 34px;--control-h-sm: 28px;--rail: 216px;--gap-shell: 12px;--measure: 1240px;--ring: 0 0 0 2px var(--bg), 0 0 0 4px color-mix(in srgb, var(--accent) 62%, transparent);--dur-fast: .11s;--dur: .17s;--dur-slow: .28s;--ease: cubic-bezier(.2, .7, .3, 1);--ease-spring: cubic-bezier(.22, 1.4, .36, 1);--stagger-step: 26ms}@media(prefers-reduced-motion:reduce){:root:not([data-motion=full]){--dur-fast: 0ms;--dur: 0ms;--dur-slow: 0ms;--stagger-step: 0ms}:root:not([data-motion=full]) *,:root:not([data-motion=full]) *:before,:root:not([data-motion=full]) *:after{animation-duration:1ms!important;animation-iteration-count:1!important;transition-duration:1ms!important;scroll-behavior:auto!important}:root:not([data-motion=full])::view-transition-old(root),:root:not([data-motion=full])::view-transition-new(root){animation:none!important}}:root[data-motion=none]{--dur-fast: 0ms;--dur: 0ms;--dur-slow: 0ms;--stagger-step: 0ms}:root[data-motion=none] *,:root[data-motion=none] *:before,:root[data-motion=none] *:after{animation-duration:1ms!important;animation-iteration-count:1!important;transition-duration:1ms!important;scroll-behavior:auto!important}:root[data-motion=none]::view-transition-old(root),:root[data-motion=none]::view-transition-new(root){animation:none!important}*,*:before,*:after{box-sizing:border-box}html,body,#app{height:100%}body{margin:0;background:var(--bg);background-image:var(--bg-grad);background-attachment:fixed;color:var(--fg);font:var(--t-md) / var(--lh-body) var(--sans);letter-spacing:var(--track-body);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-variant-numeric:tabular-nums;font-feature-settings:"cv05" 1;overflow-x:hidden}h1,h2,h3,p,ul,figure{margin:0}ul{padding:0;list-style:none}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}code,.mono{font-family:var(--mono);font-size:.92em}:focus-visible{outline:none;box-shadow:var(--ring);border-radius:var(--r1)}a:focus-visible{border-radius:var(--r0)}.skip{position:absolute;left:-9999px;top:0;z-index:100;padding:var(--s2) var(--s3);background:var(--panel);border:1px solid var(--line);border-radius:var(--r1)}.skip:focus{left:var(--s3);top:var(--s3)}.shell{display:grid;grid-template-columns:var(--rail) minmax(0,1fr);min-height:100%;padding:var(--gap-shell);gap:var(--gap-shell)}.shell.no-rail{grid-template-columns:minmax(0,1fr)}.shell.no-rail .view{max-width:none;min-height:100dvh;display:grid;place-items:center;padding:var(--s5)}.shell.public .view{display:block;max-width:1400px;margin:0 auto}.rail{border:1px solid var(--line);border-radius:var(--r4);background:color-mix(in srgb,var(--panel) 82%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);padding:var(--s4) var(--s3);position:sticky;top:var(--gap-shell);height:calc(100vh - var(--gap-shell) * 2);display:flex;flex-direction:column;gap:var(--s4);box-shadow:var(--shadow)}.brand{display:flex;align-items:center;gap:var(--s2);font-size:var(--t-lg);font-weight:650;letter-spacing:-.01em;color:var(--fg)}.brand:hover{text-decoration:none}.brand svg{color:var(--accent)}.rail nav{display:flex;flex-direction:column;gap:2px}.navlink{display:flex;align-items:center;gap:var(--s3);padding:var(--s2) var(--s3);border-radius:var(--r1);color:var(--fg-dim);font-weight:500;transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}.navlink:hover{background:var(--panel-2);color:var(--fg);text-decoration:none}.navlink.router-link-active{background:var(--accent-soft);color:var(--accent)}.navlink svg{flex:none}.navlink .count{margin-left:auto;font:600 var(--t-xs) / 1 var(--sans);font-variant-numeric:tabular-nums;color:var(--fg-mute);background:var(--panel-2);padding:3px 6px;border-radius:var(--r-full)}.rail .foot{margin-top:auto;font-size:var(--t-xs);color:var(--fg-mute);display:grid;gap:var(--s1);word-break:break-all}.rail .foot .foot-who{color:var(--fg-dim);font-weight:550;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.view{min-width:0;padding:var(--s3) var(--s4) var(--s7);max-width:none}@media(max-width:800px){.shell{grid-template-columns:minmax(0,1fr);padding:var(--gap-shell) var(--s2) 0}.rail{position:fixed;inset:auto var(--s2) calc(var(--s2) + env(safe-area-inset-bottom)) var(--s2);height:auto;flex-direction:row;align-items:center;gap:var(--s1);padding:var(--s2);background:color-mix(in srgb,var(--panel) 94%,transparent);z-index:20}.rail .brand,.rail .foot{display:none}.rail nav{flex-direction:row;flex:1;justify-content:space-around}.navlink{flex-direction:column;gap:2px;font-size:var(--t-xs);padding:var(--s1) var(--s2);min-width:56px;align-items:center}.navlink .count{display:none}.view{padding:var(--s4) var(--s4) calc(84px + env(safe-area-inset-bottom))}}.page-head{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;margin-bottom:var(--s5)}.page-head h1{font-size:var(--t-2xl);font-weight:640;letter-spacing:var(--track-display);line-height:var(--lh-tight)}.page-head .sub{color:var(--fg-dim);font-size:var(--t-sm);margin-top:2px}.grow{flex:1}h2.section{font-size:var(--t-xs);text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-dim);font-weight:620}h3{font-size:var(--t-md);font-weight:600}.dim{color:var(--fg-dim)}.mute{color:var(--fg-mute)}.nowrap{white-space:nowrap}.break{word-break:break-all}.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r3);padding:var(--s4) var(--s4) var(--s5);margin-bottom:var(--s4);min-width:0;container-type:inline-size;box-shadow:var(--hairline),var(--elev-2)}.panel.flush{padding:0;overflow:hidden}.panel>.phead{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;margin-bottom:var(--s3)}.panel.flush>.phead{padding:var(--s4) var(--s4) 0}.grid-2{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:var(--s4);align-items:start}.row{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}button,.btn{font:530 var(--t-sm) / 1 var(--sans);letter-spacing:var(--track-body);color:var(--fg);background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r1);padding:var(--s2) var(--s3);min-height:var(--control-h);cursor:pointer;display:inline-flex;align-items:center;gap:var(--s2);position:relative;overflow:hidden;box-shadow:var(--hairline),var(--elev-1);transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease),box-shadow var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease)}button:hover:not(:disabled),.btn:hover:not(:disabled){border-color:color-mix(in srgb,var(--fg-mute) 60%,var(--line));background:color-mix(in srgb,var(--hover) 100%,var(--panel-2));text-decoration:none}button:active:not(:disabled){transform:translateY(1px);box-shadow:inset 0 2px 4px var(--press)}button:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}button.primary{background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 88%,#fff) 0%,var(--accent) 100%);border-color:color-mix(in srgb,var(--accent) 75%,#000);color:#fff;box-shadow:var(--hairline-strong),var(--elev-2),0 0 color-mix(in srgb,var(--accent) 40%,transparent)}button.primary:hover:not(:disabled){background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 78%,#fff),color-mix(in srgb,var(--accent) 94%,#fff));border-color:color-mix(in srgb,var(--accent) 75%,#000);box-shadow:var(--hairline-strong),var(--elev-2),0 0 18px -2px color-mix(in srgb,var(--accent) 45%,transparent)}button.danger{background:var(--fail-soft);border-color:color-mix(in srgb,var(--fail) 45%,transparent);color:var(--fail)}button.danger:hover:not(:disabled){background:color-mix(in srgb,var(--fail) 26%,transparent);border-color:color-mix(in srgb,var(--fail) 62%,transparent)}button.ghost{background:transparent;box-shadow:none}button.ghost:hover:not(:disabled){background:var(--hover)}button.sm,.btn.sm{min-height:var(--control-h-sm);padding:var(--s1) var(--s2);font-size:var(--t-xs)}button.armed{background:var(--fail-soft);border-color:color-mix(in srgb,var(--fail) 70%,transparent);color:var(--fail);font-weight:600}button .progress{position:absolute;inset:auto 0 0 0;height:2px;background:currentColor;opacity:.65;animation:indeterminate 1.1s var(--ease) infinite;transform-origin:left}input[type=text],input[type=password],input[type=search],input[type=url],input[type=email],input[type=number],textarea,select{font:var(--t-sm) / 1.5 var(--sans);letter-spacing:var(--track-body);color:var(--fg);background:var(--sunk);border:1px solid var(--line);border-radius:var(--r1);padding:var(--s2) var(--s3);min-height:32px;width:100%;min-width:0;box-shadow:inset 0 1px 2px var(--press);transition:border-color var(--dur-fast) var(--ease),box-shadow var(--dur-fast) var(--ease)}input[type=text]:hover,input[type=password]:hover,input[type=search]:hover,input[type=url]:hover,input[type=email]:hover,input[type=number]:hover,textarea:hover,select:hover{border-color:color-mix(in srgb,var(--fg-mute) 55%,var(--line))}input[type=text]:focus,input[type=password]:focus,input[type=search]:focus,input[type=url]:focus,input[type=email]:focus,input[type=number]:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent);box-shadow:inset 0 1px 2px var(--press),0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent)}textarea{font-family:var(--mono);font-size:var(--t-sm);line-height:1.55;resize:vertical;white-space:pre;overflow-wrap:normal;overflow-x:auto}input::placeholder,textarea::placeholder{color:var(--fg-mute)}input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus{-webkit-box-shadow:0 0 0 1000px var(--sunk) inset;-webkit-text-fill-color:var(--fg);caret-color:var(--fg);transition:background-color 9999s ease-out}label{font-size:var(--t-sm);color:var(--fg-dim)}.field{display:grid;gap:var(--s2);min-width:0}.field+.field,.field+.hint,.hint+.field{margin-top:var(--s4)}.row>.field+.field,.row>.field+.hint,.row>.hint+.field,.phead>.field+.field,.phead>.field+.hint,.phead>.hint+.field{margin-top:0}.field.inline{display:flex;align-items:center;gap:var(--s2);margin-top:0}.field.inline>label{color:var(--fg-mute);font-size:var(--t-xs);white-space:nowrap}.phead>button,.phead>.btn,.phead>input,.phead>select,.phead>.check,.phead>.field>input,.phead>.field>select{min-height:var(--control-h)}.row.wrap>.field,.row>.field{align-self:stretch}.check{display:inline-flex;align-items:center;gap:var(--s2);cursor:pointer;color:var(--fg)}.check input{width:auto;min-height:0;accent-color:var(--accent)}.kv{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr) auto;gap:var(--s2);margin-bottom:var(--s2);align-items:center;max-width:640px}.kv.head{font-size:var(--t-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.06em}@media(max-width:560px){.kv{grid-template-columns:minmax(0,1fr) auto}.kv.head{display:none}}.badge{display:inline-flex;align-items:center;gap:5px;font:500 var(--t-xs) / 1 var(--sans);padding:4px 8px;border-radius:var(--r-full);background:var(--neutral-soft);color:var(--fg-dim);border:1px solid transparent;white-space:nowrap}.badge.ok{background:var(--ok-soft);color:var(--ok)}.badge.failed,.badge.fail{background:var(--fail-soft);color:var(--fail)}.badge.leaked,.badge.warn{background:var(--leak-soft);color:var(--leak)}.badge.running,.badge.busy,.badge.info{background:var(--accent-soft);color:var(--accent)}.badge.unknown{background:var(--neutral-soft);color:var(--neutral)}.badge.asleep,.badge.shared{background:var(--leak-soft);color:var(--leak)}.badge.isolated{background:var(--accent-soft);color:var(--accent)}.badge.off{opacity:.38}.badge .dot{width:6px;height:6px;border-radius:var(--r-full);background:currentColor}.badge.running .dot,.dot.pulse{animation:pulse 1.6s var(--ease) infinite}.banner{border:1px solid var(--line);border-left-width:3px;border-radius:var(--r2);padding:var(--s3) var(--s4);margin:var(--s3) 0;background:var(--panel-2);font-size:var(--t-sm)}.banner b{color:var(--fg)}.banner p{margin-top:var(--s2);color:var(--fg-dim)}.banner.ok{border-left-color:var(--ok);background:var(--ok-soft)}.banner.failed{border-left-color:var(--fail);background:var(--fail-soft)}.banner.leaked,.banner.warn{border-left-color:var(--leak);background:var(--leak-soft)}.banner.info{border-left-color:var(--accent);background:var(--accent-soft)}.banner.plain{border-left-color:var(--fg-mute)}.hint{font-size:var(--t-sm);color:var(--fg-mute);margin-top:var(--s3)}.hint code,.banner code{background:var(--sunk);padding:1px 5px;border-radius:var(--r0)}table{width:100%;border-collapse:collapse;font-size:var(--t-sm)}th{text-align:left;font:620 var(--t-xs) / 1 var(--sans);text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-mute);padding:var(--s2) var(--s3);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:1;background:var(--panel);white-space:nowrap}td{padding:var(--s2) var(--s3);border-bottom:1px solid var(--line-soft);vertical-align:middle}tbody tr{transition:background var(--dur-fast) var(--ease)}tbody tr:hover{background:var(--hover)}tbody tr:last-child td{border-bottom:none}td.name{font-weight:600;word-break:break-all}@container (max-width: 460px){table.cards,table.cards tbody,table.cards tr,table.cards td{display:block;width:100%}table.cards thead{display:none}table.cards tr{border:1px solid var(--line);border-radius:var(--r2);padding:var(--s2) var(--s3);margin-bottom:var(--s2);background:var(--panel-2)}table.cards td{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s3);border:0;padding:var(--s1) 0;text-align:right}table.cards td:before{content:attr(data-label);font:600 var(--t-xs) / 1.6 var(--sans);text-transform:uppercase;letter-spacing:.06em;color:var(--fg-mute);text-align:left;flex:none}table.cards td[colspan]{text-align:left}table.cards td[colspan]:before,table.cards td[data-label=""]:before{content:""}table.cards td[data-label=""]{justify-content:flex-end}}@container (min-width: 461px){.panel>table,.table-scroll{display:block;max-width:100%;overflow-x:auto}.panel>table>thead,.panel>table>tbody{display:table;width:100%;table-layout:auto}}.row-actions{text-align:end;white-space:nowrap}.row-actions>*+*{margin-inline-start:var(--s1)}.logs-split{display:grid;grid-template-columns:232px minmax(0,1fr);gap:var(--s4);align-items:start}@container (max-width: 720px){.logs-split{grid-template-columns:minmax(0,1fr)}.clist{max-height:220px}}.clist{display:flex;flex-direction:column;gap:2px;max-height:60vh;overflow-y:auto;padding-inline-end:var(--s1)}.clist-item{display:grid;gap:2px;text-align:start;justify-items:start;padding:var(--s2) var(--s3);min-height:0;background:transparent;border:1px solid transparent;box-shadow:none;width:100%}.clist-item:hover:not(:disabled){background:var(--hover);border-color:var(--line-soft)}.clist-item.on{background:var(--panel-2);border-color:var(--line);box-shadow:var(--hairline)}.clist-name{display:inline-flex;align-items:center;gap:var(--s2);font-weight:560}.clist-sub{font-size:var(--t-xs);color:var(--fg-mute);max-width:100%;overflow:hidden;text-overflow:ellipsis}.clist-state{font-size:var(--t-xs)}.logs-main{min-width:0}.lv-time{flex:none;color:var(--fg-mute);font-variant-numeric:tabular-nums;white-space:nowrap}.panel.banner-panel{border-color:color-mix(in srgb,var(--warn) 40%,var(--line));background:color-mix(in srgb,var(--warn) 8%,var(--panel))}.kvlist{display:grid;gap:1px;background:var(--line-soft);border-radius:var(--r2);overflow:hidden}.kvlist>li{display:flex;gap:var(--s3);padding:var(--s3);background:var(--panel);align-items:baseline;flex-wrap:wrap}.kvlist>li>.k{flex:none;width:128px;color:var(--fg-dim);font-size:var(--t-sm)}.kvlist>li>.v{flex:1;min-width:0;word-break:break-word}@media(max-width:480px){.kvlist>li>.k{width:100%}}pre.raw,pre.log{font-family:var(--mono);font-size:var(--t-sm);line-height:1.6;background:var(--sunk);border:1px solid var(--line);border-radius:var(--r2);padding:var(--s3);margin:var(--s3) 0 0;overflow:auto;white-space:pre}pre.raw{color:var(--fail);max-height:420px}pre.code{font-family:var(--mono);font-size:var(--t-sm);line-height:1.6;background:var(--sunk);border:1px solid var(--line);border-radius:var(--r2);padding:var(--s3);margin:0;max-height:520px;overflow:auto;white-space:pre;color:var(--fg-dim)}pre.log{max-height:460px;color:var(--fg-dim)}pre.log .t{color:var(--fg-mute)}pre.log .lv-error{color:var(--fail)}pre.log .lv-warn{color:var(--leak)}pre.log .lv-info{color:var(--fg)}pre.log mark{background:var(--leak-soft);color:var(--leak)}.masked{color:var(--fg-mute);letter-spacing:.12em}.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2)}@container (min-width: 680px){.stats{grid-template-columns:repeat(6,1fr)}}.stat{background:var(--panel-2);border:1px solid var(--line-soft);border-radius:var(--r2);padding:var(--s3);box-shadow:var(--hairline);min-width:0}.stat .v{font:600 var(--t-2xl) / 1.1 var(--sans);letter-spacing:var(--track-display);font-variant-numeric:tabular-nums}.stat[data-zero=true] .v{color:var(--fg-mute);font-weight:500}.stat .k{font-size:var(--t-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.04em;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.stat.unknown .v{color:var(--neutral)}.stat.busy .v{color:var(--accent)}.stat.leak .v{color:var(--leak)}.tabs{display:flex;gap:var(--s1);border-bottom:1px solid var(--line);margin-bottom:var(--s4);overflow-x:auto;scrollbar-width:none}.tabs::-webkit-scrollbar{display:none}.tabs a{padding:var(--s2) var(--s3);color:var(--fg-dim);font-size:var(--t-sm);font-weight:500;border-bottom:2px solid transparent;margin-bottom:-1px;white-space:nowrap;transition:color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}.tabs a:hover{color:var(--fg);text-decoration:none}.tabs a.router-link-active{color:var(--fg);border-bottom-color:var(--accent)}.steps{display:grid;gap:1px;background:var(--line-soft);border-radius:var(--r2);overflow:hidden}.step{display:grid;grid-template-columns:24px minmax(0,1fr);gap:var(--s3);padding:var(--s3);background:var(--panel);align-items:start}.step .mark{font:600 var(--t-lg) / 1.2 var(--sans);text-align:center}.step .who{font-weight:550;word-break:break-all}.step .msg{font-size:var(--t-sm);color:var(--fg-dim);word-break:break-word}.s-ok{color:var(--ok)}.s-failed{color:var(--fail)}.s-leaked{color:var(--leak)}.s-unverifiable{color:var(--neutral)}.skel{background:var(--skel);background-size:200% 100%;border-radius:var(--r1);height:14px;animation:shimmer 1.3s linear infinite}.skel+.skel{margin-top:var(--s2)}.skel.tall{height:44px}.skel.w60{width:60%}.skel.w40{width:40%}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.35;transform:scale(.8)}}@keyframes indeterminate{0%{transform:scaleX(0)}50%{transform:scaleX(.7)}to{transform:scaleX(0);transform-origin:right}}@keyframes rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}@keyframes land{0%{transform:scale(.985);opacity:.4}60%{transform:scale(1.004)}to{transform:none;opacity:1}}.stagger>*{animation:rise var(--dur) var(--ease) backwards;animation-delay:calc(var(--i, 0) * var(--stagger-step))}.land{animation:land var(--dur-slow) var(--ease)}.view-enter-active,.view-leave-active{transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease)}.view-enter-from{opacity:0;transform:translateY(6px)}.view-leave-to{opacity:0;transform:translateY(-4px)}.toasts{position:fixed;right:var(--s4);bottom:var(--s4);z-index:50;display:grid;gap:var(--s2);width:min(360px,calc(100vw - var(--s5)))}@media(max-width:800px){.toasts{bottom:calc(84px + env(safe-area-inset-bottom))}}.toast{display:flex;gap:var(--s3);align-items:flex-start;background:var(--panel);border:1px solid var(--line);border-radius:var(--r2);padding:var(--s3) var(--s3) var(--s3) var(--s2);box-shadow:var(--hairline),var(--elev-4);font-size:var(--t-sm);position:relative}.toast:before{content:"";flex:none;width:8px;height:8px;margin:6px 0 0 var(--s2);border-radius:var(--r-full);background:var(--neutral);box-shadow:0 0 0 3px var(--neutral-soft)}.toast.ok:before{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}.toast.error:before{background:var(--fail);box-shadow:0 0 0 3px var(--fail-soft)}.toast.warn:before{background:var(--leak);box-shadow:0 0 0 3px var(--leak-soft)}.toast.info:before{background:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}.toast .x{margin-left:auto;background:none;border:0;min-height:0;padding:2px 6px;color:var(--fg-mute)}.toast-enter-active,.toast-leave-active{transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease)}.toast-enter-from,.toast-leave-to{opacity:0;transform:translate(12px)}.toast-move{transition:transform var(--dur) var(--ease)}.scrim{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000008c;z-index:60;display:grid;place-items:center;padding:var(--s4)}.sheet{background:var(--panel);border:1px solid var(--line);border-radius:var(--r3);box-shadow:var(--shadow);padding:var(--s5);width:min(460px,100%);max-height:80vh;overflow:auto}.sheet dl{display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--s2) var(--s4);margin:var(--s4) 0 0;align-items:baseline}.sheet dt{text-align:right}.sheet dd{margin:0;color:var(--fg-dim);font-size:var(--t-sm)}kbd{font:500 var(--t-xs) / 1 var(--mono);background:var(--sunk);border:1px solid var(--line);border-bottom-width:2px;border-radius:var(--r0);padding:4px 6px;color:var(--fg)}.fade-enter-active,.fade-leave-active{transition:opacity var(--dur) var(--ease)}.fade-enter-from,.fade-leave-to{opacity:0}.hint-wrap{position:relative;display:inline-flex;vertical-align:middle;margin-inline-start:var(--s1)}.hint-btn{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;min-height:0;padding:0;border:0;border-radius:var(--r-full);background:none;color:var(--fg-mute);cursor:pointer;transition:color var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease)}.hint-btn:hover,.hint-btn.on{color:var(--accent);background:var(--accent-soft)}.hint-bub{position:absolute;z-index:40;width:max-content;max-width:min(300px,74vw);padding:var(--s2) var(--s3);background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r2);box-shadow:var(--shadow);color:var(--fg-dim);font:400 var(--t-sm) / 1.5 var(--sans);text-transform:none;letter-spacing:normal;white-space:normal;text-align:start;pointer-events:none}.hint-bub.below{top:calc(100% + 6px)}.hint-bub.above{bottom:calc(100% + 6px)}.hint-bub.to-start{inset-inline-start:-2px}.hint-bub.to-end{inset-inline-end:-2px}.hint-bub code{color:var(--fg)}.hint-enter-active,.hint-leave-active{transition:opacity var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease)}.hint-enter-from,.hint-leave-to{opacity:0}.hint-bub.below.hint-enter-from,.hint-bub.below.hint-leave-to{transform:translateY(-3px)}.hint-bub.above.hint-enter-from,.hint-bub.above.hint-leave-to{transform:translateY(3px)}.palette-scrim{align-items:flex-start;padding-top:12vh}.palette{width:min(620px,calc(100vw - var(--s5)));background:var(--panel);border:1px solid var(--line);border-radius:var(--r3);box-shadow:var(--hairline-strong),var(--elev-4);overflow:hidden;display:flex;flex-direction:column;max-height:min(60vh,560px)}.palette-q{display:flex;align-items:center;gap:var(--s3);padding:var(--s3) var(--s4);border-bottom:1px solid var(--line-soft);color:var(--fg-mute)}.palette-q input{flex:1;background:none;border:none;box-shadow:none;padding:0;min-height:26px;font-size:var(--t-lg);letter-spacing:var(--track-title);color:var(--fg)}.palette-q input:focus{outline:none;border:none;box-shadow:none}.palette-list{overflow-y:auto;padding:var(--s2);min-height:0}.palette-group{font-size:var(--t-xs);text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-mute);padding:var(--s3) var(--s2) var(--s1)}.palette-row{display:flex;align-items:center;gap:var(--s3);width:100%;text-align:left;background:none;border:none;box-shadow:none;border-radius:var(--r1);padding:var(--s2) var(--s3);min-height:34px;color:var(--fg)}.palette-row[data-on=true]{background:var(--accent-soft);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent) 32%,transparent)}.palette-label{font-weight:520}.palette-hint{margin-left:auto;color:var(--fg-mute);font-size:var(--t-xs);font-family:var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:45%}.palette-empty{color:var(--fg-mute);padding:var(--s5) var(--s3);text-align:center;font-size:var(--t-sm)}.palette-foot{display:flex;gap:var(--s4);align-items:center;padding:var(--s2) var(--s4);border-top:1px solid var(--line-soft);background:var(--panel-2);color:var(--fg-mute);font-size:var(--t-xs)}kbd{font:500 11px / 1 var(--mono);color:var(--fg-dim);background:var(--sunk);border:1px solid var(--line);border-radius:var(--r0);padding:3px 5px;margin-right:3px}.fade-enter-active .palette{animation:palette-in var(--dur-slow) var(--ease-spring)}@keyframes palette-in{0%{opacity:0;transform:translateY(-10px) scale(.98)}to{opacity:1;transform:none}}::view-transition-old(root),::view-transition-new(root){animation-duration:var(--dur);animation-timing-function:var(--ease)}::view-transition-old(root){animation-name:vt-out}::view-transition-new(root){animation-name:vt-in}@keyframes vt-out{to{opacity:0}}@keyframes vt-in{0%{opacity:0;transform:translateY(4px)}}.searchbox{display:flex;align-items:center;gap:var(--s2);flex:1;min-width:180px;padding-inline:var(--s3);background:var(--sunk);border:1px solid var(--line);border-radius:var(--r1);box-shadow:inset 0 1px 2px var(--press);color:var(--fg-mute);min-height:34px;transition:border-color var(--dur-fast) var(--ease),box-shadow var(--dur-fast) var(--ease)}.searchbox:focus-within{border-color:var(--accent);box-shadow:inset 0 1px 2px var(--press),0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent)}.searchbox input{border:none;background:none;box-shadow:none;padding:0;min-height:0}.searchbox input:focus{outline:none;border:none;box-shadow:none}.searchbox input::-webkit-search-cancel-button{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.compact{width:auto;min-height:34px;padding-inline-end:var(--s5)}.phead>.check{min-height:34px}.field>.selm-trigger,.selm-trigger{max-width:340px;width:100%}.selm-trigger{display:inline-flex;align-items:center;gap:var(--s2);justify-content:space-between;min-height:34px;padding:0 var(--s2) 0 var(--s3);background:var(--sunk);border:1px solid var(--line);border-radius:var(--r1);box-shadow:inset 0 1px 2px var(--press);font:var(--t-sm) / 1 var(--sans);color:var(--fg);cursor:pointer}.selm-trigger:hover:not(:disabled){border-color:color-mix(in srgb,var(--fg-mute) 55%,var(--line))}.selm-trigger[data-state=open]{border-color:var(--accent);box-shadow:inset 0 1px 2px var(--press),0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent)}.selm-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.selm-caret{flex:none;color:var(--fg-mute);transition:transform var(--dur-fast) var(--ease)}.selm-trigger[data-state=open] .selm-caret{transform:rotate(180deg)}.selm-list{z-index:61;padding:var(--s1);max-height:280px;overflow-y:auto;background:var(--panel);border:1px solid var(--line);border-radius:var(--r2);box-shadow:var(--hairline-strong),var(--elev-4);min-width:var(--reka-select-trigger-width);animation:palette-in var(--dur) var(--ease-spring)}.selm-opt{display:flex;align-items:center;gap:var(--s2);padding:var(--s2);min-height:30px;border-radius:calc(var(--r1) - 2px);font-size:var(--t-sm);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none}.selm-opt[data-highlighted]{background:var(--accent-soft);outline:none}.selm-opt[data-disabled]{opacity:.45;cursor:not-allowed}.selm-hint{color:var(--fg-mute);font-size:var(--t-xs);margin-inline-start:auto}.selm-tick{margin-inline-start:auto;color:var(--accent);display:inline-flex}.selm-hint+.selm-tick{margin-inline-start:var(--s2)}input[type=checkbox],input[type=radio]{-moz-appearance:none;appearance:none;-webkit-appearance:none;flex:none;width:17px;height:17px;margin:0;border:1px solid color-mix(in srgb,var(--fg-mute) 60%,var(--line));background:var(--sunk);box-shadow:inset 0 1px 2px var(--press);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}input[type=checkbox]{border-radius:var(--r0)}input[type=radio]{border-radius:var(--r-full)}input[type=checkbox]:hover:not(:disabled),input[type=radio]:hover:not(:disabled){border-color:var(--accent)}input[type=checkbox]:before,input[type=radio]:before{content:"";transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring)}input[type=checkbox]:before{width:11px;height:11px;background:#fff;clip-path:polygon(14% 47%,0 61%,40% 100%,100% 22%,85% 9%,39% 71%)}input[type=radio]:before{width:7px;height:7px;border-radius:var(--r-full);background:#fff}input[type=checkbox]:checked,input[type=radio]:checked{background:var(--accent);border-color:var(--accent)}input[type=checkbox]:checked:before,input[type=radio]:checked:before{transform:scale(1)}input[type=checkbox]:disabled,input[type=radio]:disabled{opacity:.45;cursor:not-allowed}.helpq{display:inline-grid;place-items:center;width:18px;height:18px;padding:0;min-height:0;border-radius:var(--r-full);border:1px solid var(--line);background:var(--panel-2);box-shadow:none;color:var(--fg-mute);cursor:pointer;vertical-align:middle}.helpq:hover{color:var(--accent);border-color:var(--accent);background:var(--accent-soft)}.help-scrim{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:70}.help-modal{position:fixed;z-index:71;top:50%;left:50%;transform:translate(-50%,-50%);width:min(620px,calc(100vw - var(--s5)));max-height:min(80vh,720px);display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:var(--r3);box-shadow:var(--hairline-strong),var(--elev-4);animation:help-in var(--dur-slow) var(--ease-spring)}@keyframes help-in{0%{opacity:0;transform:translate(-50%,-46%) scale(.97)}}.help-head{display:flex;align-items:center;gap:var(--s3);padding:var(--s4) var(--s4) var(--s3);border-bottom:1px solid var(--line-soft)}.help-title{font-size:var(--t-lg);font-weight:620;letter-spacing:var(--track-title);margin:0;flex:1}.help-body{padding:var(--s4);overflow-y:auto;color:var(--fg-dim);font-size:var(--t-sm);line-height:var(--lh-body)}.help-body>*+*{margin-top:var(--s3)}.help-body b,.help-body strong{color:var(--fg)}.check-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:var(--s2) var(--s4);margin-top:var(--s2)}.check-grid .check{min-height:26px}.settings-form .field input,.settings-form input[type=url],.settings-form input[type=password],.settings-form input[type=text]{max-width:560px}.settings-form .hint,.settings-form .mute.hint{max-width:640px}.lv{border:1px solid var(--line);border-radius:var(--r2);background:var(--sunk);overflow:hidden}.lv-bar{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);border-bottom:1px solid var(--line-soft);font-size:var(--t-xs);color:var(--fg-mute)}.lv-box{max-height:520px;overflow-y:auto;padding:var(--s2) 0 var(--s3);font:var(--t-sm) / 1.6 var(--mono);overscroll-behavior:contain}.lv-empty{color:var(--fg-mute);padding:var(--s4) var(--s3);font-family:var(--sans)}.lv-row{display:grid;grid-template-columns:auto minmax(0,1fr);gap:0 var(--s3);padding:1px var(--s3);content-visibility:auto;contain-intrinsic-block-size:auto 22px}.lv-row:hover{background:var(--hover)}.lv-gutter{white-space:nowrap;color:var(--fg-mute);-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums}.lv-msg{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--fg-dim)}.lv-row.error .lv-msg{color:var(--fail)}.lv-row.warn .lv-msg{color:var(--leak)}.lv-row.ok .lv-msg{color:var(--ok)}.lv-row.svc-0 .lv-gutter{color:#7aa2f7}.lv-row.svc-1 .lv-gutter{color:#9ece6a}.lv-row.svc-2 .lv-gutter{color:#e0af68}.lv-row.svc-3 .lv-gutter{color:#bb9af7}.lv-row.svc-4 .lv-gutter{color:#7dcfff}.lv-row.svc-5 .lv-gutter{color:#f7768e}.lv-sep{display:flex;align-items:center;gap:var(--s3);padding:var(--s2) var(--s3);color:var(--fg-mute);font-family:var(--sans);font-size:var(--t-xs)}.lv-sep:before,.lv-sep:after{content:"";flex:1;border-top:1px solid var(--line-soft)}.refresh-icon{display:inline-block;line-height:1}.refresh-icon.spin{animation:refresh-spin .9s linear infinite}@keyframes refresh-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.refresh-icon.spin{animation:none}}:root[data-motion=none] .refresh-icon.spin{animation:none}
@@ -0,0 +1,2 @@
1
+ import{W as l,j as i,n,C as d,P as p,X as u}from"./index-9Ra4jfL4.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-DFOIiXrd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDeployment-DFOIiXrd.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":"uHAkBO,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"}
@@ -0,0 +1,2 @@
1
+ function a(t){return t?new Date(t).toLocaleString("sv"):"—"}function u(t,n){if(!t)return"—";const r=Math.max(0,Math.round(((n??Date.now())-t)/1e3)),e=r<60?`${r}s`:`${Math.floor(r/60)}m${String(r%60).padStart(2,"0")}s`;return n?e:`${e}…`}function s(t){if(!t)return"—";const n=Math.max(0,Math.round((Date.now()-t)/1e3));if(n<45)return"just now";const r=Math.round(n/60);if(r<60)return`${r}m ago`;const e=Math.round(r/60);return e<24?`${e}h ago`:`${Math.round(e/24)}d ago`}const o={up:"Deploy",down:"Tear down",verify:"Verify",sleep:"Sleep",wake:"Wake"};function c(t){return t?o[t]??t:"—"}function i(t){if(!t)return"—";const n=t.replace(/[-_]+/g," ");return n.charAt(0).toUpperCase()+n.slice(1)}export{c as a,s as b,a as c,i as s,u as t};
2
+ //# sourceMappingURL=useFormat-DIlTY5Dl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFormat-DIlTY5Dl.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 sleep: 'Sleep',\n wake: 'Wake',\n};\n\nexport function actionLabel(action: string | undefined): string {\n if (!action) return '—';\n return ACTION_LABELS[action] ?? action;\n}\n\n/**\n * An API enum, rendered for a person: `isolated` → `Isolated`, `start-failed` → `Start failed`.\n *\n * ONE helper rather than capitalising at each render site, because the two halves have to stay\n * apart: the VALUE stays lowercase everywhere it is compared, stored, sent or put in a class name,\n * and only the rendered text changes. Capitalising in place — `state === 'Running'` — is how a badge\n * starts looking right and a comparison silently stops matching.\n *\n * Sentence case, not Title Case: `Start failed`, not `Start Failed`. Title Case on a status reads\n * like a proper noun, and these are descriptions.\n *\n * Identifiers are NOT sentences and must never come through here — a stack name, container name,\n * deployment id, file name, env var or hostname is a literal, and capitalising one produces a value\n * that does not exist.\n */\nexport function sentence(value: string | null | undefined): string {\n if (!value) return '—';\n const words = value.replace(/[-_]+/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n"],"names":["stamp","ms","took","startedAt","endedAt","s","text","ago","m","h","ACTION_LABELS","actionLabel","action","sentence","value","words"],"mappings":"AAOO,SAASA,EAAMC,EAAgC,CACpD,OAAOA,EAAK,IAAI,KAAKA,CAAE,EAAE,eAAe,IAAI,EAAI,GAClD,CAQO,SAASC,EAAKC,EAA+BC,EAA0B,CAC5E,GAAI,CAACD,EAAW,MAAO,IACvB,MAAME,EAAI,KAAK,IAAI,EAAG,KAAK,QAAQD,GAAW,KAAK,IAAA,GAASD,GAAa,GAAI,CAAC,EACxEG,EAAOD,EAAI,GAAK,GAAGA,CAAC,IAAM,GAAG,KAAK,MAAMA,EAAI,EAAE,CAAC,IAAI,OAAOA,EAAI,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,IACxF,OAAOD,EAAUE,EAAO,GAAGA,CAAI,GACjC,CAGO,SAASC,EAAIN,EAAgC,CAClD,GAAI,CAACA,EAAI,MAAO,IAChB,MAAMI,EAAI,KAAK,IAAI,EAAG,KAAK,OAAO,KAAK,IAAA,EAAQJ,GAAM,GAAI,CAAC,EAC1D,GAAII,EAAI,GAAI,MAAO,WACnB,MAAMG,EAAI,KAAK,MAAMH,EAAI,EAAE,EAC3B,GAAIG,EAAI,GAAI,MAAO,GAAGA,CAAC,QACvB,MAAMC,EAAI,KAAK,MAAMD,EAAI,EAAE,EAC3B,OAAIC,EAAI,GAAW,GAAGA,CAAC,QAChB,GAAG,KAAK,MAAMA,EAAI,EAAE,CAAC,OAC9B,CAUA,MAAMC,EAAwC,CAC5C,GAAI,SACJ,KAAM,YACN,OAAQ,SACR,MAAO,QACP,KAAM,MACR,EAEO,SAASC,EAAYC,EAAoC,CAC9D,OAAKA,EACEF,EAAcE,CAAM,GAAKA,EADZ,GAEtB,CAiBO,SAASC,EAASC,EAA0C,CACjE,GAAI,CAACA,EAAO,MAAO,IACnB,MAAMC,EAAQD,EAAM,QAAQ,SAAU,GAAG,EACzC,OAAOC,EAAM,OAAO,CAAC,EAAE,cAAgBA,EAAM,MAAM,CAAC,CACtD"}
package/dist/index.html CHANGED
@@ -27,8 +27,8 @@
27
27
  /* private mode: fall through to the OS preference */
28
28
  }
29
29
  </script>
30
- <script type="module" crossorigin src="/assets/index-DVMQBHXF.js"></script>
31
- <link rel="stylesheet" crossorigin href="/assets/index-lyuEu6lW.css">
30
+ <script type="module" crossorigin src="/assets/index-9Ra4jfL4.js"></script>
31
+ <link rel="stylesheet" crossorigin href="/assets/index-B_k9b7xG.css">
32
32
  </head>
33
33
  <body>
34
34
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samyx/preview-stacks-ui",
3
- "version": "0.25.2",
3
+ "version": "0.27.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{C as v,A as g,L as k,G as j,Q as C,B as $,r as u,R,y as x,S as I,b as L,d as S,c as r,E as T,a as d,t as b,f as i,e as N,w as U,j as B,o as l}from"./index-DVMQBHXF.js";import{d as a,v as D,i as E}from"./useDeployment-DGzBsXE-.js";import{a as f}from"./useFormat-DVXTfIS-.js";const h=u(""),c=u(""),w=u(null),m=u(""),A=v(()=>{var e;return((e=a.detail)==null?void 0:e.busy)===!0}),K=v(()=>{var e;return((e=x.health)==null?void 0:e.authEnforced)===!0&&!I.token&&!L.authed}),O=v(()=>{const e=a.vars.filter(o=>o.k).map(o=>o.k);return e.length?`Sending ${e.join(", ")} with every action.`:"Using the variables saved with this deployment."});function P(e,o=!1){if(!a.detail)return"The spec has not resolved — the server cannot compute the stack name.";if(A.value)return`A job is already in flight for ${a.detail.stack}.`;if(e==="down"&&E.value&&!o)return`Type the stack name "${a.detail.stack}" below to confirm a shared teardown.`}async function X(e,o={}){if(!a.detail)return;h.value=e,c.value="",w.value=null;const t=e==="down"?{verify:o.verify??!0,force:o.force??!1}:{},s=await g.post(`/api/deployments/${encodeURIComponent(a.id)}/${e}${D.value}`,t);if(h.value="",s.status===202){const n=s.body.job;if(n!=null&&n.id){k("info",`${f(e)} started on ${n.stack}`,{to:`/jobs/${encodeURIComponent(n.id)}`,toLabel:"Follow"}),j(),C.push(`/jobs/${encodeURIComponent(n.id)}`);return}c.value="The action was accepted, but the response carried nothing to follow.";return}if(s.status===409)return F(s.body);c.value=$(s,f(e).toLowerCase()),k("error",`${f(e)} was refused.`)}async function F(e){var p;const o=R(e);w.value=o,m.value="";const t=o.stack||((p=a.detail)==null?void 0:p.stack);if(o.kind!=="other"||!t)return;const n=((await g.get("/api/jobs")).body.jobs??[]).find(y=>y.state==="running"&&y.stack===t);n&&(m.value=n.id)}function Y(){h.value="",c.value="",w.value=null,m.value=""}const V={class:"raw",style:{color:"var(--fg-dim)","max-height":"200px"}},Q={key:0},z={key:1},G={key:2},J={key:3},M={key:4},Z=S({__name:"ConflictNote",props:{conflict:{},jobId:{}},setup(e){return(o,t)=>{const s=B("RouterLink");return l(),r("div",{class:T(["banner",e.conflict.kind==="shared"?"leaked":"info"]),role:"alert"},[t[5]||(t[5]=d("b",null,"Refused — nothing was started.",-1)),d("pre",V,b(e.conflict.text),1),e.conflict.kind==="shared"?(l(),r("p",Q,[...t[0]||(t[0]=[i(" Type the stack name in the confirmation field above and press ",-1),d("code",null,"down",-1),i(" again. ",-1)])])):e.conflict.kind==="containers"?(l(),r("p",z,[...t[1]||(t[1]=[i(" Run ",-1),d("code",null,"down",-1),i(" 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)])])):e.conflict.kind==="referenced"?(l(),r("p",G,[t[2]||(t[2]=i(" These deployments still use this spec: ",-1)),d("strong",null,b(e.conflict.deployments.join(", ")),1),t[3]||(t[3]=i(". Deleting it would leave them with no way to be torn down. ",-1))])):e.jobId?(l(),r("p",J,[N(s,{to:`/jobs/${encodeURIComponent(e.jobId)}`},{default:U(()=>[...t[4]||(t[4]=[i("Follow the running job →",-1)])]),_:1},8,["to"])])):(l(),r("p",M,"Nothing was retried. Wait for the current work to finish, then try again."))],2)}}});export{Z as _,X as a,A as b,c,w as d,m as e,F as o,h as p,Y as r,K as t,O as v,P as w};
2
- //# sourceMappingURL=ConflictNote.vue_vue_type_script_setup_true_lang-HglPeTkY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ConflictNote.vue_vue_type_script_setup_true_lang-HglPeTkY.js","sources":["../../src/composables/useDeploymentActions.ts","../../src/components/ConflictNote.vue"],"sourcesContent":["/**\n * Lifecycle actions for the open deployment — shared by the detail HEADER (Deploy, Verify) and the\n * Danger tab (Tear down, Forget).\n *\n * WHY THIS MOVED OUT OF THE DANGER TAB. Deploy is the primary action of the entire product, and it\n * lived behind a tab named \"Danger\" — three clicks deep, labelled as something to be afraid of.\n * Deploying and verifying a preview are routine and reversible; they belong in the header, visible\n * from every tab. What stays in Danger is what the name promises: teardown and forgetting.\n *\n * Module-level state, same pattern as `useDeployment`: exactly one deployment detail is open at a\n * time, and the header and the tab must see the SAME pending/conflict state — two components each\n * holding their own copy is how a button stays enabled while the other copy knows a job is running.\n */\nimport { computed, ref } from 'vue';\nimport { api, classifyConflict, problem, type Conflict } from '../api/client';\nimport type { ActionResponse, ConflictBody, JobsResponse } from '../api/types';\nimport { dep, isShared, varsQuery } from './useDeployment';\nimport { loadDeployments, state } from './useControlPlane';\nimport { settings } from './useSettings';\nimport { authState } from './useAuth';\nimport { toast } from './useToasts';\nimport { actionLabel } from './useFormat';\nimport { router } from '../router';\n\nexport type LifecycleAction = 'up' | 'verify' | 'down';\n\nexport const pending = ref<'' | LifecycleAction | 'forget'>('');\nexport const actionError = ref('');\nexport const conflict = ref<Conflict | null>(null);\nexport const conflictJobId = ref('');\n\nexport const busy = computed(() => dep.detail?.busy === true);\n/**\n * True only when NOTHING can authenticate the caller. This predates accounts and used to check the\n * token alone — so a signed-in operator with no token pasted was told \"every action below will be\n * refused\" while their actions were succeeding. A session is a credential; the warning must know so.\n */\nexport const tokenMissing = computed(\n () => state.health?.authEnforced === true && !settings.token && !authState.authed,\n);\n\n/**\n * The variables an action will send, as prose. Which variables are set decides which stack is\n * touched, so the NAMES are worth stating — the URL-encoded query string they become is not.\n */\nexport const varsSummary = computed(() => {\n const names = dep.vars.filter((v) => v.k).map((v) => v.k);\n // NOT \"no variables\": with none set here the server resolves the ones stored with the deployment,\n // and claiming otherwise would have an operator hunting for variables that are already in place.\n return names.length\n ? `Sending ${names.join(', ')} with every action.`\n : 'Using the variables saved with this deployment.';\n});\n\n/** A disabled control must say why. This is the text that answers it. */\nexport function whyDisabled(action: LifecycleAction, forceArmed = false): 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) {\n return `Type the stack name \"${dep.detail.stack}\" below to confirm a shared teardown.`;\n }\n return undefined;\n}\n\nexport async function act(\n action: LifecycleAction,\n opts: { verify?: boolean; force?: boolean } = {},\n): 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: opts.verify ?? true, force: opts.force ?? false }\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', `${actionLabel(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 = 'The action was accepted, but the response carried nothing to follow.';\n return;\n }\n if (r.status === 409) return onConflict(r.body);\n\n actionError.value = problem(r, actionLabel(action).toLowerCase());\n toast('error', `${actionLabel(action)} was refused.`);\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 */\nexport async function onConflict(body: ConflictBody): Promise<void> {\n const c = classifyConflict(body);\n conflict.value = c;\n conflictJobId.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\n/** Leaving the deployment must not carry a stale error onto the next one. */\nexport function resetActions(): void {\n pending.value = '';\n actionError.value = '';\n conflict.value = null;\n conflictJobId.value = '';\n}\n","<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>Refused — 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 use this spec: <strong>{{ conflict.deployments.join(', ') }}</strong>.\n Deleting it would leave them with no way to 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 current work to finish, then try again.</p>\n </div>\n</template>\n"],"names":["pending","ref","actionError","conflict","conflictJobId","busy","computed","_a","dep","tokenMissing","state","settings","authState","varsSummary","names","v","whyDisabled","action","forceArmed","isShared","act","opts","body","r","api","varsQuery","job","toast","actionLabel","loadDeployments","router","onConflict","problem","c","classifyConflict","stack","live","j","resetActions","_createElementBlock","_normalizeClass","__props","_cache","_createElementVNode","_hoisted_1","_toDisplayString","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_createVNode","_component_RouterLink","_openBlock"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{d as I,c as i,a as t,u as o,f as s,e as r,w as d,F as $,g as u,t as w,i as c,I as S,_ as x,v as B,x as V,r as C,C as D,j as F,o as a,k as j,A as L,L as q,G as E,B as M}from"./index-DVMQBHXF.js";import{d as n,i as k,v as G}from"./useDeployment-DGzBsXE-.js";import{t as J,v as O,a as Q,w as W,p as f,b as N,c as g,d as T,e as z,_ as H,o as K}from"./ConflictNote.vue_vue_type_script_setup_true_lang-HglPeTkY.js";import{_ as U}from"./ActionButton.vue_vue_type_script_setup_true_lang-D5wrEmB2.js";import{_ as P}from"./ErrorNote.vue_vue_type_script_setup_true_lang-B3hfwyPR.js";import"./useVars-VN9gYdR6.js";import"./useFormat-DVXTfIS-.js";const X={class:"panel"},Y={key:0,class:"mute"},Z={key:0,class:"banner warn"},_={class:"dim"},ee={class:"row",style:{"margin-top":"var(--s3)"}},te={class:"check"},oe={key:1,class:"hint"},se={key:2,class:"hint"},ne={key:3,class:"banner leaked"},ae={class:"field",style:{"margin-top":"var(--s3)","max-width":"380px"}},ie=["for"],le=["id","placeholder"],re={key:0,class:"s-failed"},de={class:"panel"},ue={key:0,class:"mute"},me={class:"dim"},fe={class:"row",style:{"margin-top":"var(--s3)"}},pe={class:"check"},xe=I({__name:"DangerTab",setup(ve){const A=j(),v=C(!0),y=C(""),p=C(!1),b=D(()=>{var l;return!!((l=n.detail)!=null&&l.stack)&&y.value===n.detail.stack});async function R(){if(!p.value)return;f.value="forget",g.value="",T.value=null;const l=await L.del(`/api/deployments/${encodeURIComponent(n.id)}${G.value}`);if(f.value="",l.ok){q("ok",`Forgot ${n.id}. Nothing was torn down.`),E(),A.push("/deployments");return}if(l.status===409)return K(l.body);g.value=M(l,"forget this deployment")}return(l,e)=>{const h=F("RouterLink");return a(),i("div",null,[t("section",X,[e[26]||(e[26]=t("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Tear down",-1)),o(n).detail?(a(),i($,{key:1},[o(J)?(a(),i("div",Z,[e[10]||(e[10]=t("b",null,"No access token set.",-1)),t("p",null,[e[8]||(e[8]=s(" Every action below will be refused. Add your token under ",-1)),r(h,{to:"/settings"},{default:d(()=>[...e[7]||(e[7]=[s("Settings",-1)])]),_:1}),e[9]||(e[9]=s(". ",-1))])])):u("",!0),t("p",_,[s(" Stops and removes this deployment's containers, networks and volumes. "+w(o(O))+" ",1),r(h,{to:`/deployments/${encodeURIComponent(o(n).id)}/config`},{default:d(()=>[...e[11]||(e[11]=[s("change",-1)])]),_:1},8,["to"])]),t("div",ee,[r(U,{variant:"danger",pending:o(f)==="down",disabled:!!o(f)||o(N)||o(k)&&!b.value,title:o(W)("down",b.value),onClick:e[0]||(e[0]=m=>o(Q)("down",{verify:v.value,force:o(k)&&b.value}))},{default:d(()=>[s(w(o(k)?"Tear down (force)":"Tear down"),1)]),_:1},8,["pending","disabled","title"]),t("label",te,[c(t("input",{"onUpdate:modelValue":e[1]||(e[1]=m=>v.value=m),type:"checkbox"},null,512),[[S,v.value]]),e[12]||(e[12]=s(" Check for leftovers afterwards ",-1))])]),o(N)?(a(),i("p",oe,[e[14]||(e[14]=s(" Something is already running on ",-1)),t("b",null,w(o(n).detail.stack),1),e[15]||(e[15]=s(". ",-1)),r(x,{label:"why actions wait"},{default:d(()=>[...e[13]||(e[13]=[s(" One action at a time per stack, and they are not queued. A teardown racing a deploy over the same database would corrupt it, so the second request is refused rather than delayed. ",-1)])]),_:1})])):u("",!0),v.value?u("",!0):(a(),i("p",se,[...e[16]||(e[16]=[s(" Nothing will check that teardown actually finished. A teardown that silently half-worked is the exact failure this tool exists to catch — run ",-1),t("b",null,"Verify",-1),s(" yourself afterwards. ",-1)])])),o(k)?(a(),i("div",ne,[e[25]||(e[25]=t("b",null,"Shared — read this before tearing down.",-1)),t("p",null,[e[18]||(e[18]=s(" Tearing this down ",-1)),e[19]||(e[19]=t("b",null,"deletes its stored data",-1)),e[20]||(e[20]=s(": the TLS certificates, the shared database or queue, admin credentials. Nothing here recreates them, so ",-1)),e[21]||(e[21]=t("b",null,"every preview on this host breaks",-1)),e[22]||(e[22]=s(" until someone rebuilds it by hand. The button is the same one that is routine on a single preview. ",-1)),r(x,{label:"what tearing down runs"},{default:d(()=>[...e[17]||(e[17]=[t("code",null,"docker compose down -v",-1),s(" — the ",-1),t("code",null,"-v",-1),s(" is what removes the volumes. ",-1)])]),_:1})]),t("div",ae,[t("label",{for:`confirm-${o(n).id}`},[e[23]||(e[23]=s(" Type the stack name ",-1)),t("b",null,w(o(n).detail.stack),1),e[24]||(e[24]=s(" to confirm ",-1))],8,ie),c(t("input",{id:`confirm-${o(n).id}`,"onUpdate:modelValue":e[2]||(e[2]=m=>y.value=m),type:"text",placeholder:o(n).detail.stack,spellcheck:"false",autocomplete:"off"},null,8,le),[[B,y.value,void 0,{trim:!0}]])]),y.value&&!b.value?(a(),i("p",re," That does not match, so tearing down stays disabled. ")):u("",!0)])):u("",!0),o(g)?(a(),V(P,{key:4,text:o(g),title:"The action was refused."},null,8,["text"])):u("",!0),o(T)?(a(),V(H,{key:5,conflict:o(T),"job-id":o(z)},null,8,["conflict","job-id"])):u("",!0)],64)):(a(),i("p",Y,[e[5]||(e[5]=s(" Unavailable until this deployment's variables are filled in — without them its stack has no name to act on. Set them under ",-1)),r(h,{to:`/deployments/${encodeURIComponent(o(n).id)}/config`},{default:d(()=>[...e[4]||(e[4]=[s(" Config & variables ",-1)])]),_:1},8,["to"]),e[6]||(e[6]=s("first. ",-1))]))]),t("section",de,[e[35]||(e[35]=t("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Forget this deployment",-1)),o(n).detail?(a(),i($,{key:1},[t("p",me,[e[28]||(e[28]=s(" This removes the ",-1)),e[29]||(e[29]=t("em",null,"record only",-1)),e[30]||(e[30]=s(" — ",-1)),e[31]||(e[31]=t("b",null,"it never tears anything down",-1)),e[32]||(e[32]=s(". Afterwards nothing here knows how to remove whatever this deployment created. ",-1)),r(x,{label:"when forgetting is refused"},{default:d(()=>[...e[27]||(e[27]=[s(" Refused while any container for this stack still exists — and refused just as firmly when docker cannot be reached, because “cannot confirm” is not evidence of absence. What is deleted is the stored spec, compose file and metadata. ",-1)])]),_:1})]),t("div",fe,[t("label",pe,[c(t("input",{"onUpdate:modelValue":e[3]||(e[3]=m=>p.value=m),type:"checkbox"},null,512),[[S,p.value]]),e[33]||(e[33]=s(" I have torn this down and want the record removed ",-1))]),r(U,{variant:"danger",pending:o(f)==="forget",disabled:!p.value||!!o(f),title:p.value?void 0:"Confirm you have torn this down first.",onClick:R},{default:d(()=>[...e[34]||(e[34]=[s(" Forget ",-1)])]),_:1},8,["pending","disabled","title"])])],64)):(a(),i("p",ue," Unavailable until the variables are filled in: this stack's containers are counted before the record can be forgotten, and that needs its name. "))])])}}});export{xe as default};
2
- //# sourceMappingURL=DangerTab-BLDIlbN5.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DangerTab-BLDIlbN5.js","sources":["../../src/views/tabs/DangerTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Teardown and forgetting — everything on this page destroys something, and NOTHING else is here.\n *\n * Deploy and Verify used to live on this tab too, which meant the product's primary action was\n * filed under \"Danger\". They moved to the detail header (`useDeploymentActions`); this tab now\n * carries exactly what its name promises.\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, problem } from '../../api/client';\nimport type { ConflictBody } from '../../api/types';\nimport { dep, isShared, varsQuery } from '../../composables/useDeployment';\nimport {\n act,\n actionError,\n busy,\n conflict,\n conflictJobId,\n onConflict,\n pending,\n tokenMissing,\n varsSummary,\n whyDisabled,\n} from '../../composables/useDeploymentActions';\nimport { loadDeployments } from '../../composables/useControlPlane';\nimport { toast } from '../../composables/useToasts';\nimport ActionButton from '../../components/ActionButton.vue';\nimport ConflictNote from '../../components/ConflictNote.vue';\nimport InfoHint from '../../components/InfoHint.vue';\nimport ErrorNote from '../../components/ErrorNote.vue';\n\nconst router = useRouter();\n\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\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, 'forget this deployment');\n}\n</script>\n\n<template>\n <div>\n <section class=\"panel\">\n <h2 class=\"section\" style=\"margin-bottom: var(--s3)\">Tear down</h2>\n\n <p v-if=\"!dep.detail\" class=\"mute\">\n Unavailable until this deployment's variables are filled in — without them its stack has no\n name to act on. Set them 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 access token set.</b>\n <p>\n Every action below will be refused. Add your token under\n <RouterLink to=\"/settings\">Settings</RouterLink>.\n </p>\n </div>\n\n <p class=\"dim\">\n Stops and removes this deployment's containers, networks and volumes.\n {{ varsSummary }}\n <RouterLink :to=\"`/deployments/${encodeURIComponent(dep.id)}/config`\">change</RouterLink>\n </p>\n\n <div class=\"row\" style=\"margin-top: var(--s3)\">\n <ActionButton\n variant=\"danger\"\n :pending=\"pending === 'down'\"\n :disabled=\"!!pending || busy || (isShared && !forceArmed)\"\n :title=\"whyDisabled('down', forceArmed)\"\n @click=\"act('down', { verify: downVerify, force: isShared && forceArmed })\"\n >\n {{ isShared ? 'Tear down (force)' : 'Tear down' }}\n </ActionButton>\n <label class=\"check\">\n <input v-model=\"downVerify\" type=\"checkbox\" />\n Check for leftovers afterwards\n </label>\n </div>\n\n <p v-if=\"busy\" class=\"hint\">\n Something is already running on <b>{{ dep.detail.stack }}</b>.\n <InfoHint label=\"why actions wait\">\n One action at a time per stack, and they are not queued. A teardown racing a deploy over\n the same database would corrupt it, so the second request is refused rather than delayed.\n </InfoHint>\n </p>\n <p v-if=\"!downVerify\" class=\"hint\">\n Nothing will check that teardown actually finished. A teardown that silently half-worked is\n the exact failure this tool exists to catch — run <b>Verify</b> 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 Tearing this down <b>deletes its stored data</b>: the TLS certificates, the shared\n database or queue, admin credentials. Nothing here recreates them, so\n <b>every preview on this host breaks</b> until someone rebuilds it by hand. The button is\n the same one that is routine on a single preview.\n <InfoHint label=\"what tearing down runs\">\n <code>docker compose down -v</code> — the <code>-v</code> is what removes the volumes.\n </InfoHint>\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>{{ dep.detail.stack }}</b> to confirm\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=\"s-failed\">\n That does not match, so tearing down 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 variables are filled in: this stack's containers are counted before the\n record can be forgotten, and that needs its name.\n </p>\n <template v-else>\n <p class=\"dim\">\n This removes the <em>record only</em> — <b>it never tears anything down</b>. Afterwards\n nothing here knows how to remove whatever this deployment created.\n <InfoHint label=\"when forgetting is refused\">\n Refused while any container for this stack still exists — and refused just as firmly when\n docker cannot be reached, because “cannot confirm” is not evidence of absence. What is\n deleted is the stored spec, compose file and metadata.\n </InfoHint>\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":["router","useRouter","downVerify","ref","forceTyped","forgetArmed","forceArmed","computed","_a","dep","forget","pending","actionError","conflict","r","api","varsQuery","toast","loadDeployments","onConflict","problem","_createElementBlock","_createElementVNode","_hoisted_1","_unref","_Fragment","tokenMissing","_openBlock","_hoisted_3","_cache","_createVNode","_component_RouterLink","_hoisted_4","_toDisplayString","varsSummary","_hoisted_5","ActionButton","isShared","whyDisabled","act","_hoisted_6","$event","busy","_hoisted_7","InfoHint","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_13","_createBlock","ErrorNote","ConflictNote","conflictJobId","_hoisted_2","_hoisted_14","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_15"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{d as I,J as _,c as i,a,e as l,w as r,t as p,u as t,f as o,E as L,g as d,x as y,F as N,s as S,_ as c,j as x,o as s,T as j,k as q,K as z}from"./index-DVMQBHXF.js";import{s as E}from"./useFormat-DVXTfIS-.js";import{d as n,l as U,i as F,o as J}from"./useDeployment-DGzBsXE-.js";import{a as C,w as D,p as f,b as R,c as $,d as h,e as K,_ as O,r as G}from"./ConflictNote.vue_vue_type_script_setup_true_lang-HglPeTkY.js";import{_ as V}from"./ActionButton.vue_vue_type_script_setup_true_lang-D5wrEmB2.js";import{_ as T}from"./ErrorNote.vue_vue_type_script_setup_true_lang-B3hfwyPR.js";import{_ as H}from"./RefreshButton.vue_vue_type_script_setup_true_lang-CXCY3Vlc.js";import"./useVars-VN9gYdR6.js";const M={class:"page-head"},P={class:"mute",style:{"font-size":"var(--t-sm)"}},Q={style:{"font-size":"var(--t-xl)"}},W={class:"sub"},X={key:0},Y={key:1},Z={key:2},ee={key:1,class:"badge busy"},te={key:2,class:"row",style:{"flex-wrap":"nowrap"}},se={class:"tabs","aria-label":"Deployment sections"},oe={key:3,class:"hint"},ne={key:4,class:"hint",style:{"margin-bottom":"var(--s4)"}},ye=I({__name:"DeploymentDetailView",props:{id:{}},setup(b){const v=b,g=q(),A=[{to:"overview",label:"Overview"},{to:"runtime",label:"Containers & routes"},{to:"config",label:"Config & variables"},{to:"axes",label:"Axes"},{to:"requires",label:"Requires"},{to:"logs",label:"Logs"},{to:"terminal",label:"Terminal"},{to:"danger",label:"Danger"}];return _(()=>v.id,m=>{J(m),G()},{immediate:!0}),_(()=>n.error,m=>{m&&g.currentRoute.value.name==="d.overview"&&g.replace(`/deployments/${encodeURIComponent(v.id)}/config`)}),(m,e)=>{var w;const k=x("RouterLink"),B=x("RouterView");return s(),i("div",null,[a("div",M,[a("div",null,[a("div",P,[l(k,{to:"/deployments"},{default:r(()=>[...e[2]||(e[2]=[o("← Deployments",-1)])]),_:1})]),a("h1",Q,p(b.id),1),a("div",W,[t(n).detail?(s(),i("span",X,[e[3]||(e[3]=o(" stack ",-1)),a("b",null,p(t(n).detail.stack),1)])):t(n).error?(s(),i("span",Y,"unresolved")):(s(),i("span",Z,"loading…"))])]),e[7]||(e[7]=a("span",{class:"grow"},null,-1)),t(n).detail?(s(),i("span",{key:0,class:L(["badge",t(n).detail.kind])},p(t(E)(t(n).detail.kind)),3)):d("",!0),((w=t(n).detail)==null?void 0:w.busy)===!0?(s(),i("span",ee,[...e[4]||(e[4]=[a("span",{class:"dot pulse"},null,-1),o("busy",-1)])])):d("",!0),t(n).detail?(s(),i("div",te,[l(H,{run:t(U),label:"Re-resolve",title:"Read this deployment again and re-run its variable substitution"},null,8,["run"]),l(V,{variant:"primary",pending:t(f)==="up",disabled:!!t(f)||t(R),title:t(D)("up"),onClick:e[0]||(e[0]=u=>t(C)("up"))},{default:r(()=>[...e[5]||(e[5]=[o(" Deploy ",-1)])]),_:1},8,["pending","disabled","title"]),l(V,{pending:t(f)==="verify",disabled:!!t(f)||t(R),title:t(D)("verify"),onClick:e[1]||(e[1]=u=>t(C)("verify"))},{default:r(()=>[...e[6]||(e[6]=[o(" Verify ",-1)])]),_:1},8,["pending","disabled","title"])])):d("",!0)]),t($)?(s(),y(T,{key:0,text:t($),title:"The action was refused."},null,8,["text"])):d("",!0),t(h)?(s(),y(O,{key:1,conflict:t(h),"job-id":t(K),style:{"margin-bottom":"var(--s4)"}},null,8,["conflict","job-id"])):d("",!0),a("nav",se,[(s(),i(N,null,S(A,u=>l(k,{key:u.to,to:`/deployments/${encodeURIComponent(b.id)}/${u.to}`},{default:r(()=>[o(p(u.label),1)]),_:2},1032,["to"])),64))]),t(n).error?(s(),y(T,{key:2,text:t(n).error,title:"Could not resolve this deployment."},null,8,["text"])):d("",!0),t(n).error?(s(),i("p",oe,[e[9]||(e[9]=o(" If that message names a variable, add it under ",-1)),e[10]||(e[10]=a("b",null,"Config & variables",-1)),e[11]||(e[11]=o(" and press ",-1)),e[12]||(e[12]=a("b",null,"Apply & reload",-1)),e[13]||(e[13]=o(". ",-1)),l(c,{label:"why the rest of the page is unavailable"},{default:r(()=>[...e[8]||(e[8]=[o(" Until the spec resolves there is no stack name, and every action on this deployment needs one. ",-1)])]),_:1})])):d("",!0),t(F)?(s(),i("p",ne,[e[15]||(e[15]=o(" Shared with every preview on this host. ",-1)),l(c,{label:"what a shared deployment is"},{default:r(()=>[...e[14]||(e[14]=[o(" A host singleton that previews borrow — a database, a queue, a registry mirror. It declares no axes, and tearing it down needs an explicit confirmation, because doing so destroys state every other deployment depends on. ",-1)])]),_:1})])):d("",!0),l(B,null,{default:r(({Component:u})=>[l(j,{name:"view",mode:"out-in"},{default:r(()=>[(s(),y(z(u)))]),_:2},1024)]),_:1})])}}});export{ye as default};
2
- //# sourceMappingURL=DeploymentDetailView-CEg8sA5I.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DeploymentDetailView-CEg8sA5I.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 { sentence } from '../composables/useFormat';\nimport { useRouter } from 'vue-router';\nimport { dep, isShared, loadDetail, openDeployment } from '../composables/useDeployment';\nimport {\n act,\n actionError,\n busy,\n conflict,\n conflictJobId,\n pending,\n resetActions,\n whyDisabled,\n} from '../composables/useDeploymentActions';\nimport ActionButton from '../components/ActionButton.vue';\nimport ConflictNote from '../components/ConflictNote.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport RefreshButton from '../components/RefreshButton.vue';\n\nconst props = defineProps<{ id: string }>();\nconst router = useRouter();\n\nconst TABS = [\n { to: 'overview', label: 'Overview' },\n // Second, not buried: \"the hostname does not work\" is the most common question, and this is the\n // only tab that can answer it.\n { to: 'runtime', label: 'Containers & routes' },\n { to: 'config', label: 'Config & variables' },\n { to: 'axes', label: 'Axes' },\n { to: 'requires', label: 'Requires' },\n { to: 'logs', label: 'Logs' },\n // After Logs: a shell is what you reach for when the logs did not answer it.\n { to: 'terminal', label: 'Terminal' },\n { to: 'danger', label: 'Danger' },\n] as const;\n\nwatch(\n () => props.id,\n (id) => {\n openDeployment(id);\n // A stale \"refused\" from the previous deployment must not greet the next one.\n resetActions();\n },\n { immediate: true },\n);\n\n// Land the operator on the editor when the spec will not resolve — but only from the tab that has\n// nothing to show. Sending them away from Logs or Danger mid-read would be its own annoyance.\nwatch(\n () => dep.error,\n (err) => {\n if (err && router.currentRoute.value.name === 'd.overview') {\n void router.replace(`/deployments/${encodeURIComponent(props.id)}/config`);\n }\n },\n);\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <div class=\"mute\" style=\"font-size: var(--t-sm)\">\n <RouterLink to=\"/deployments\">← Deployments</RouterLink>\n </div>\n <h1 style=\"font-size: var(--t-xl)\">{{ id }}</h1>\n <div class=\"sub\">\n <span v-if=\"dep.detail\">\n stack <b>{{ dep.detail.stack }}</b>\n </span>\n <span v-else-if=\"dep.error\">unresolved</span>\n <span v-else>loading…</span>\n </div>\n </div>\n <span class=\"grow\" />\n <span v-if=\"dep.detail\" class=\"badge\" :class=\"dep.detail.kind\">{{ sentence(dep.detail.kind) }}</span>\n <span v-if=\"dep.detail?.busy === true\" class=\"badge busy\"><span class=\"dot pulse\" />busy</span>\n <!--\n Deploy and Verify live HERE, on every tab — they are the product's routine actions, and they\n used to be reachable only through a tab named \"Danger\". Teardown stays there: the header\n carries what is safe to reach for, the Danger tab carries what deserves the walk.\n -->\n <!-- One flex unit, so on a narrow screen the pair wraps together instead of Verify being\n orphaned onto its own line under the title. -->\n <div v-if=\"dep.detail\" class=\"row\" style=\"flex-wrap: nowrap\">\n <RefreshButton :run=\"loadDetail\" label=\"Re-resolve\" title=\"Read this deployment again and re-run its variable substitution\" />\n <ActionButton\n variant=\"primary\"\n :pending=\"pending === 'up'\"\n :disabled=\"!!pending || busy\"\n :title=\"whyDisabled('up')\"\n @click=\"act('up')\"\n >\n Deploy\n </ActionButton>\n <ActionButton\n :pending=\"pending === 'verify'\"\n :disabled=\"!!pending || busy\"\n :title=\"whyDisabled('verify')\"\n @click=\"act('verify')\"\n >\n Verify\n </ActionButton>\n </div>\n </div>\n\n <ErrorNote v-if=\"actionError\" :text=\"actionError\" title=\"The action was refused.\" />\n <ConflictNote\n v-if=\"conflict\"\n :conflict=\"conflict\"\n :job-id=\"conflictJobId\"\n style=\"margin-bottom: var(--s4)\"\n />\n\n <nav class=\"tabs\" aria-label=\"Deployment sections\">\n <RouterLink\n v-for=\"t in TABS\"\n :key=\"t.to\"\n :to=\"`/deployments/${encodeURIComponent(id)}/${t.to}`\"\n >{{ t.label }}</RouterLink\n >\n </nav>\n\n <ErrorNote v-if=\"dep.error\" :text=\"dep.error\" title=\"Could not resolve this deployment.\">\n </ErrorNote>\n <p v-if=\"dep.error\" class=\"hint\">\n If that message names a variable, add it under <b>Config &amp; variables</b> and press\n <b>Apply &amp; reload</b>.\n <InfoHint label=\"why the rest of the page is unavailable\">\n Until the spec resolves there is no stack name, and every action on this deployment needs one.\n </InfoHint>\n </p>\n\n <p v-if=\"isShared\" class=\"hint\" style=\"margin-bottom: var(--s4)\">\n Shared with every preview on this host.\n <InfoHint label=\"what a shared deployment is\">\n A host singleton that previews borrow — a database, a queue, a registry mirror. It declares no\n axes, and tearing it down needs an explicit confirmation, because doing so destroys state\n every other deployment depends on.\n </InfoHint>\n </p>\n\n <RouterView v-slot=\"{ Component }\">\n <Transition name=\"view\" mode=\"out-in\">\n <component :is=\"Component\" />\n </Transition>\n </RouterView>\n </div>\n</template>\n"],"names":["props","__props","router","useRouter","TABS","watch","id","openDeployment","resetActions","dep","err","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","_component_RouterLink","_cache","_hoisted_3","_toDisplayString","_hoisted_4","_unref","_hoisted_5","_openBlock","sentence","_hoisted_8","_hoisted_9","RefreshButton","loadDetail","ActionButton","pending","busy","whyDisabled","act","actionError","_createBlock","ErrorNote","conflict","ConflictNote","conflictJobId","_hoisted_10","_Fragment","_renderList","t","_createTextVNode","_hoisted_11","InfoHint","isShared","_hoisted_12","_component_RouterView","_withCtx","Component","_Transition","_resolveDynamicComponent"],"mappings":"knCA6BA,MAAMA,EAAQC,EACRC,EAASC,EAAA,EAETC,EAAO,CACX,CAAE,GAAI,WAAY,MAAO,UAAA,EAGzB,CAAE,GAAI,UAAW,MAAO,qBAAA,EACxB,CAAE,GAAI,SAAU,MAAO,oBAAA,EACvB,CAAE,GAAI,OAAQ,MAAO,MAAA,EACrB,CAAE,GAAI,WAAY,MAAO,UAAA,EACzB,CAAE,GAAI,OAAQ,MAAO,MAAA,EAErB,CAAE,GAAI,WAAY,MAAO,UAAA,EACzB,CAAE,GAAI,SAAU,MAAO,QAAA,CAAS,EAGlC,OAAAC,EACE,IAAML,EAAM,GACXM,GAAO,CACNC,EAAeD,CAAE,EAEjBE,EAAA,CACF,EACA,CAAE,UAAW,EAAA,CAAK,EAKpBH,EACE,IAAMI,EAAI,MACTC,GAAQ,CACHA,GAAOR,EAAO,aAAa,MAAM,OAAS,cACvCA,EAAO,QAAQ,gBAAgB,mBAAmBF,EAAM,EAAE,CAAC,SAAS,CAE7E,CAAA,qEAKAW,EAwFM,MAAA,KAAA,CAvFJC,EA4CM,MA5CNC,EA4CM,CA3CJD,EAYM,MAAA,KAAA,CAXJA,EAEM,MAFNE,EAEM,CADJC,EAAwDC,EAAA,CAA5C,GAAG,gBAAc,WAAC,IAAa,CAAA,GAAAC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAAb,gBAAa,EAAA,CAAA,aAE7CL,EAAgD,KAAhDM,EAAgDC,EAAVlB,EAAA,EAAE,EAAA,CAAA,EACxCW,EAMM,MANNQ,EAMM,CALQC,EAAAZ,CAAA,EAAI,YAAhBE,EAEO,OAAAW,EAAA,eAFiB,UAChB,EAAA,GAAAV,EAA6B,IAAA,KAAAO,EAAvBE,EAAAZ,CAAA,EAAI,OAAO,KAAK,EAAA,CAAA,CAAA,IAEbY,EAAAZ,CAAA,EAAI,OAArBc,IAAAZ,EAA6C,SAAjB,YAAU,IACtCY,EAAA,EAAAZ,EAA4B,SAAf,UAAQ,EAAA,iBAGzBC,EAAqB,OAAA,CAAf,MAAM,MAAA,EAAM,KAAA,EAAA,GACNS,EAAAZ,CAAA,EAAI,YAAhBE,EAAqG,OAAA,OAA7E,SAAM,QAAgBU,KAAI,OAAO,IAAI,CAAA,CAAA,EAAKF,EAAAE,EAAAG,CAAA,EAASH,EAAAZ,CAAA,EAAI,OAAO,IAAI,CAAA,EAAA,CAAA,cAC9EY,EAAAA,EAAAZ,CAAA,EAAI,SAAJY,YAAAA,EAAY,QAAI,IAA5BE,IAAAZ,EAA+F,OAA/Fc,GAA+F,CAAA,GAAAR,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAArCL,EAA0B,OAAA,CAApB,MAAM,WAAA,EAAW,KAAA,EAAA,IAAG,OAAI,EAAA,CAAA,eAQ7ES,EAAAZ,CAAA,EAAI,QAAfc,IAAAZ,EAmBM,MAnBNe,GAmBM,CAlBJX,EAA8HY,EAAA,CAA9G,IAAKN,EAAAO,CAAA,EAAY,MAAM,aAAa,MAAM,iEAAA,kBAC1Db,EAQec,EAAA,CAPb,QAAQ,UACP,QAASR,EAAAS,CAAA,IAAO,KAChB,SAAQ,CAAA,CAAIT,EAAAS,CAAA,GAAWT,EAAAU,CAAA,EACvB,MAAOV,EAAAW,CAAA,EAAW,IAAA,EAClB,uBAAOX,EAAAY,CAAA,EAAG,IAAA,EAAA,aACZ,IAED,CAAA,GAAAhB,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAFC,WAED,EAAA,CAAA,4CACAF,EAOec,EAAA,CANZ,QAASR,EAAAS,CAAA,IAAO,SAChB,SAAQ,CAAA,CAAIT,EAAAS,CAAA,GAAWT,EAAAU,CAAA,EACvB,MAAOV,EAAAW,CAAA,EAAW,QAAA,EAClB,uBAAOX,EAAAY,CAAA,EAAG,QAAA,EAAA,aACZ,IAED,CAAA,GAAAhB,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAFC,WAED,EAAA,CAAA,0DAIaI,EAAAa,CAAA,OAAjBC,EAAoFC,EAAA,OAArD,KAAMf,EAAAa,CAAA,EAAa,MAAM,yBAAA,6BAEhDb,EAAAgB,CAAA,OADRF,EAKEG,EAAA,OAHC,SAAUjB,EAAAgB,CAAA,EACV,SAAQhB,EAAAkB,CAAA,EACT,MAAA,CAAA,gBAAA,WAAA,CAAA,0CAGF3B,EAOM,MAPN4B,GAOM,MANJ7B,EAKC8B,EAAA,KAAAC,EAJatC,EAALuC,GADT5B,EAKCC,EAAA,CAHE,IAAK2B,EAAE,GACP,mBAAoB,mBAAmB1C,IAAE,CAAA,IAAK0C,EAAE,EAAE,EAAA,aAClD,IAAa,CAAVC,EAAAzB,EAAAwB,EAAE,KAAK,EAAA,CAAA,CAAA,6BAIEtB,EAAAZ,CAAA,EAAI,WAArB0B,EACYC,EAAA,OADiB,KAAMf,EAAAZ,CAAA,EAAI,MAAO,MAAM,oCAAA,6BAE3CY,EAAAZ,CAAA,EAAI,OAAbc,IAAAZ,EAMI,IANJkC,GAMI,eAN6B,mDACgB,EAAA,GAAA5B,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAL,EAA6B,SAA1B,qBAAsB,EAAA,mBAAI,cAC5E,EAAA,GAAAK,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAAL,EAAyB,SAAtB,iBAAkB,EAAA,mBAAI,KACzB,EAAA,GAAAG,EAEW+B,EAAA,CAFD,MAAM,2CAAyC,WAAC,IAE1D,CAAA,GAAA7B,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAF0D,mGAE1D,EAAA,CAAA,uBAGOI,EAAA0B,CAAA,GAATxB,IAAAZ,EAOI,IAPJqC,GAOI,iBAP6D,4CAE/D,EAAA,GAAAjC,EAIW+B,EAAA,CAJD,MAAM,+BAA6B,WAAC,IAI9C,CAAA,GAAA7B,EAAA,EAAA,IAAAA,EAAA,EAAA,EAAA,GAJ8C,gOAI9C,EAAA,CAAA,uBAGFF,EAIakC,EAAA,KAAA,CAHX,QAAAC,EAAA,CAEa,CAHO,UAAAC,KAAS,CAC7BpC,EAEaqC,EAAA,CAFD,KAAK,OAAO,KAAK,QAAA,aAC3B,IAA6B,EAA7B7B,IAAAY,EAA6BkB,EAAbF,CAAS,CAAA,EAAA"}
@@ -1,2 +0,0 @@
1
- import{d as $,o as l,c as n,a as s,f as o,t as a,u as r,z as f,e as u,G as B,w as k,i as w,v as L,g,I as N,y as p,x as R,F as b,s as C,r as h,C as x,j as D,D as U,E as I}from"./index-DVMQBHXF.js";import{s as z}from"./useFormat-DVXTfIS-.js";import{_ as E}from"./SkeletonList.vue_vue_type_script_setup_true_lang-qMyLRAMp.js";import{_ as q}from"./RelativeTime.vue_vue_type_script_setup_true_lang-BGissyps.js";import{_ as A}from"./SelectMenu.vue_vue_type_script_setup_true_lang-D8NGgBfa.js";import{_ as F}from"./RefreshButton.vue_vue_type_script_setup_true_lang-CXCY3Vlc.js";import"./Teleport-DfE2iP8c.js";const M={key:0,class:"badge busy"},T={key:1,class:"badge ok"},j={key:2,class:"badge unknown",title:"the server could not determine this — it is not the same as idle"},G={key:3,class:"badge"},K=$({__name:"RunStateBadge",props:{busy:{type:[Boolean,null]},running:{type:[Boolean,null]}},setup(m){return(y,i)=>m.busy===!0?(l(),n("span",M,[...i[0]||(i[0]=[s("span",{class:"dot pulse"},null,-1),o("Busy",-1)])])):m.running===!0?(l(),n("span",T,[...i[1]||(i[1]=[s("span",{class:"dot"},null,-1),o("Running",-1)])])):m.busy===null||m.running===null?(l(),n("span",j,"Unknown")):(l(),n("span",G,"Idle"))}}),H={class:"page-head"},J={class:"sub"},O={class:"panel"},P={class:"phead"},Q={class:"searchbox"},W={key:0},X={class:"check"},Y={key:0,class:"s-failed"},Z={class:"cards"},ee={class:"stagger"},se={"data-label":"id"},te={key:0,class:"mute",style:{"font-size":"var(--t-xs)"}},le={"data-label":"kind"},ne={class:"name dim","data-label":"stack"},oe={key:0},ae={key:1,class:"badge warn",title:"the spec could not be resolved without variables"},ie={"data-label":"state"},re={class:"dim nowrap","data-label":"updated"},ue={key:0},de={colspan:"5",class:"mute"},pe={class:"mono"},ge=$({__name:"DeploymentsView",setup(m){const y=h(""),i=h("all"),c=h(!1),_=x(()=>{const d=y.value.trim().toLowerCase();return p.deployments.filter(e=>i.value!=="all"&&e.kind!==i.value||c.value&&!(e.running===!0||e.busy===!0)?!1:d?e.id.toLowerCase().includes(d)||(e.stack??"").toLowerCase().includes(d)||(e.specName??"").toLowerCase().includes(d):!0)}),V=x(()=>p.deployments.filter(d=>d.unresolved));return(d,e)=>{const v=D("RouterLink");return l(),n("div",null,[s("div",H,[s("div",null,[e[3]||(e[3]=s("h1",null,"Deployments",-1)),s("div",J,a(r(f).total)+" submitted · "+a(r(f).running)+" running · "+a(r(f).busy)+" busy ",1)]),e[5]||(e[5]=s("span",{class:"grow"},null,-1)),u(F,{run:r(B)},null,8,["run"]),u(v,{to:"/submit",class:"btn"},{default:k(()=>[...e[4]||(e[4]=[o("+ Submit",-1)])]),_:1})]),s("section",O,[s("div",P,[s("div",Q,[e[6]||(e[6]=s("svg",{viewBox:"0 0 24 24",width:"15",height:"15","aria-hidden":"true"},[s("circle",{cx:"11",cy:"11",r:"7",fill:"none",stroke:"currentColor","stroke-width":"2"}),s("path",{d:"M16.5 16.5 21 21",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round"})],-1)),w(s("input",{id:"q","onUpdate:modelValue":e[0]||(e[0]=t=>y.value=t),"data-search":"",type:"search","aria-label":"Search deployments",placeholder:"Search by id, stack or spec",spellcheck:"false"},null,512),[[L,y.value]]),y.value?g("",!0):(l(),n("kbd",W,"/"))]),u(A,{modelValue:i.value,"onUpdate:modelValue":e[1]||(e[1]=t=>i.value=t),label:"Filter by kind",options:[{value:"all",label:"All kinds"},{value:"isolated",label:"Isolated"},{value:"shared",label:"Shared"}]},null,8,["modelValue"]),s("label",X,[w(s("input",{"onUpdate:modelValue":e[2]||(e[2]=t=>c.value=t),type:"checkbox"},null,512),[[N,c.value]]),e[7]||(e[7]=o(" Running or busy only ",-1))])]),r(p).deploymentsError?(l(),n("p",Y,a(r(p).deploymentsError),1)):r(p).deploymentsLoaded?(l(),n(b,{key:2},[s("table",Z,[e[11]||(e[11]=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",ee,[(l(!0),n(b,null,C(_.value,(t,S)=>(l(),n("tr",{key:t.id,style:U({"--i":S})},[s("td",se,[u(v,{to:`/deployments/${encodeURIComponent(t.id)}`},{default:k(()=>[o(a(t.id),1)]),_:2},1032,["to"]),t.specName?(l(),n("div",te," spec: "+a(t.specName),1)):g("",!0)]),s("td",le,[s("span",{class:I(["badge",t.kind])},a(r(z)(t.kind)),3)]),s("td",ne,[t.stack?(l(),n("span",oe,a(t.stack),1)):(l(),n("span",ae," needs variables "))]),s("td",ie,[u(K,{busy:t.busy,running:t.running},null,8,["busy","running"])]),s("td",re,[u(q,{at:t.updatedAt},null,8,["at"])])],4))),128)),_.value.length?g("",!0):(l(),n("tr",ue,[s("td",de,[r(p).deployments.length?(l(),n(b,{key:0},[o(" Nothing matches this filter. ")],64)):(l(),n(b,{key:1},[e[9]||(e[9]=o(" Nothing submitted yet — ",-1)),u(v,{to:"/submit"},{default:k(()=>[...e[8]||(e[8]=[o("submit a spec",-1)])]),_:1}),e[10]||(e[10]=o(" to put a deployment in the registry. ",-1))],64))])]))])]),(l(!0),n(b,null,C(V.value,t=>(l(),n("div",{key:`u-${t.id}`,class:"banner warn"},[s("b",null,a(t.id),1),e[19]||(e[19]=o(" could not be resolved: ",-1)),s("span",pe,a(t.unresolved),1),s("p",null,[e[13]||(e[13]=o(" This listing resolves every spec with the ",-1)),e[14]||(e[14]=s("em",null,"same",-1)),e[15]||(e[15]=o(" (empty) request variables, so a spec with a ",-1)),e[16]||(e[16]=s("code",null,"${VAR}",-1)),e[17]||(e[17]=o(" the server does not already hold will always appear here. ",-1)),u(v,{to:`/deployments/${encodeURIComponent(t.id)}/config`},{default:k(()=>[...e[12]||(e[12]=[o(" Set its variables ",-1)])]),_:1},8,["to"]),e[18]||(e[18]=o(" and the detail view will resolve. ",-1))])]))),128))],64)):(l(),R(E,{key:1,rows:4,tall:""}))])])}}});export{ge as default};
2
- //# sourceMappingURL=DeploymentsView-BfhwFiiQ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DeploymentsView-BfhwFiiQ.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 { sentence } from '../composables/useFormat';\nimport { loadDeployments, state, summary } from '../composables/useControlPlane';\n\nimport RunStateBadge from '../components/RunStateBadge.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport RelativeTime from '../components/RelativeTime.vue';\nimport SelectMenu from '../components/SelectMenu.vue';\nimport RefreshButton from '../components/RefreshButton.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 <RefreshButton :run=\"loadDeployments\" />\n <RouterLink to=\"/submit\" class=\"btn\">+ Submit</RouterLink>\n </div>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <!--\n One row of equal-height controls. Each used to sit under its own stacked label, which cost\n a line of vertical space per control, put \"Search — press /\" on screen as a permanent\n instruction, and left the two fields 8px out of vertical alignment because a `select` and\n an `input` do not have the same intrinsic height. A placeholder says the same thing in the\n place you are already looking, and the select's own options say what it filters.\n -->\n <div class=\"searchbox\">\n <svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" />\n <path d=\"M16.5 16.5 21 21\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n <input\n id=\"q\"\n v-model=\"q\"\n data-search\n type=\"search\"\n aria-label=\"Search deployments\"\n placeholder=\"Search by id, stack or spec\"\n spellcheck=\"false\"\n />\n <kbd v-if=\"!q\">/</kbd>\n </div>\n <SelectMenu\n v-model=\"kind\"\n label=\"Filter by kind\"\n :options=\"[\n { value: 'all', label: 'All kinds' },\n { value: 'isolated', label: 'Isolated' },\n { value: 'shared', label: 'Shared' },\n ]\"\n />\n <label class=\"check\">\n <input v-model=\"onlyLive\" type=\"checkbox\" />\n Running or busy only\n </label>\n </div>\n\n <p v-if=\"state.deploymentsError\" class=\"s-failed\">{{ state.deploymentsError }}</p>\n <SkeletonList v-else-if=\"!state.deploymentsLoaded\" :rows=\"4\" tall />\n\n <template v-else>\n <table class=\"cards\">\n <thead>\n <tr>\n <th>ID</th>\n <th>Kind</th>\n <th>Stack</th>\n <th>State</th>\n <th>Updated</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(d, i) in rows\" :key=\"d.id\" :style=\"{ '--i': i }\">\n <td data-label=\"id\">\n <RouterLink :to=\"`/deployments/${encodeURIComponent(d.id)}`\">\n {{ d.id }}\n </RouterLink>\n <div v-if=\"d.specName\" class=\"mute\" style=\"font-size: var(--t-xs)\">\n spec: {{ d.specName }}\n </div>\n </td>\n <td data-label=\"kind\"><span class=\"badge\" :class=\"d.kind\">{{ sentence(d.kind) }}</span></td>\n <td class=\"name dim\" data-label=\"stack\">\n <span v-if=\"d.stack\">{{ d.stack }}</span>\n <!--\n No stack name means the spec could not be resolved with the variables this\n listing had. It is not an error state for the deployment — it is a missing input.\n -->\n <span v-else class=\"badge warn\" title=\"the spec could not be resolved without variables\">\n needs variables\n </span>\n </td>\n <td data-label=\"state\"><RunStateBadge :busy=\"d.busy\" :running=\"d.running\" /></td>\n <td class=\"dim nowrap\" data-label=\"updated\"><RelativeTime :at=\"d.updatedAt\" /></td>\n </tr>\n <tr v-if=\"!rows.length\">\n <td colspan=\"5\" class=\"mute\">\n <template v-if=\"state.deployments.length\">\n Nothing matches this filter.\n </template>\n <template v-else>\n Nothing submitted yet — <RouterLink to=\"/submit\">submit a spec</RouterLink> to\n put a deployment in the registry.\n </template>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- One message per unresolved row, verbatim: it names the missing variable. -->\n <div v-for=\"d in unresolvedRows\" :key=\"`u-${d.id}`\" class=\"banner warn\">\n <b>{{ d.id }}</b> could not be resolved:\n <span class=\"mono\">{{ d.unresolved }}</span>\n <p>\n This listing resolves every spec with the <em>same</em> (empty) request variables, so a\n spec with a <code>${VAR}</code> the server does not already hold will always appear\n here.\n <RouterLink :to=\"`/deployments/${encodeURIComponent(d.id)}/config`\">\n Set its variables\n </RouterLink>\n and the detail view will resolve.\n </p>\n </div>\n </template>\n </section>\n </div>\n</template>\n"],"names":["__props","_openBlock","_createElementBlock","_hoisted_1","_cache","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_4","q","ref","kind","onlyLive","rows","computed","needle","state","d","unresolvedRows","_toDisplayString","_unref","_createVNode","RefreshButton","loadDeployments","_component_RouterLink","_hoisted_5","$event","SelectMenu","_hoisted_7","_hoisted_8","_Fragment","_hoisted_9","_hoisted_10","_renderList","i","_hoisted_11","_createTextVNode","_hoisted_12","_hoisted_13","_normalizeClass","sentence","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","RunStateBadge","_hoisted_18","RelativeTime","_hoisted_19","_hoisted_20","_hoisted_21","_createBlock","SkeletonList"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{u as k,c as R,a as D,P as B,b as w,g as F,D as T,d as K,F as N,e as E,f as V,h as M,i as S,T as L}from"./Teleport-DfE2iP8c.js";import{d as g,W as H,X as v,u as e,r as b,o as c,x as _,w as u,Y as m,Z as A,e as O,h as j,g as z,$ as U,a0 as W,a as q,f as I,t as X}from"./index-DVMQBHXF.js";const[C,Y]=R("DialogRoot");var Z=g({inheritAttrs:!1,__name:"DialogRoot",props:{open:{type:Boolean,required:!1,default:void 0},defaultOpen:{type:Boolean,required:!1,default:!1},modal:{type:Boolean,required:!1,default:!0}},emits:["update:open"],setup(r,{emit:l}){const t=r,o=k(t,"open",l,{defaultValue:t.defaultOpen,passive:t.open===void 0}),d=b(),f=b(),{modal:i}=H(t);return Y({open:o,modal:i,openModal:()=>{o.value=!0},onOpenChange:n=>{o.value=n},onOpenToggle:()=>{o.value=!o.value},contentId:"",titleId:"",descriptionId:"",triggerElement:d,contentElement:f}),(n,s)=>v(n.$slots,"default",{open:e(o),close:()=>o.value=!1})}}),G=Z,J=g({__name:"DialogClose",props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:"button"}},setup(r){const l=r;D();const t=C();return(a,o)=>(c(),_(e(B),m(l,{type:a.as==="button"?"button":void 0,onClick:o[0]||(o[0]=d=>e(t).onOpenChange(!1))}),{default:u(()=>[v(a.$slots,"default")]),_:3},16,["type"]))}}),Q=J,x=g({__name:"DialogContentImpl",props:{forceMount:{type:Boolean,required:!1},trapFocus:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:["escapeKeyDown","pointerDownOutside","focusOutside","interactOutside","openAutoFocus","closeAutoFocus"],setup(r,{emit:l}){const t=r,a=l,o=C(),{forwardRef:d,currentElement:f}=D();return o.titleId||(o.titleId=w(void 0,"reka-dialog-title")),o.descriptionId||(o.descriptionId=w(void 0,"reka-dialog-description")),A(()=>{o.contentElement=f,F()!==document.body&&(o.triggerElement.value=F())}),(i,n)=>(c(),_(e(N),{"as-child":"",loop:"",trapped:t.trapFocus,onMountAutoFocus:n[5]||(n[5]=s=>a("openAutoFocus",s)),onUnmountAutoFocus:n[6]||(n[6]=s=>a("closeAutoFocus",s))},{default:u(()=>[O(e(T),m({id:e(o).contentId,ref:e(d),as:i.as,"as-child":i.asChild,"disable-outside-pointer-events":i.disableOutsidePointerEvents,role:"dialog","aria-describedby":e(o).descriptionId,"aria-labelledby":e(o).titleId,"data-state":e(K)(e(o).open.value)},i.$attrs,{onDismiss:n[0]||(n[0]=s=>e(o).onOpenChange(!1)),onEscapeKeyDown:n[1]||(n[1]=s=>a("escapeKeyDown",s)),onFocusOutside:n[2]||(n[2]=s=>a("focusOutside",s)),onInteractOutside:n[3]||(n[3]=s=>a("interactOutside",s)),onPointerDownOutside:n[4]||(n[4]=s=>a("pointerDownOutside",s))}),{default:u(()=>[v(i.$slots,"default")]),_:3},16,["id","as","as-child","disable-outside-pointer-events","aria-describedby","aria-labelledby","data-state"])]),_:3},8,["trapped"]))}}),$=x,ee=g({__name:"DialogContentModal",props:{forceMount:{type:Boolean,required:!1},trapFocus:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:["escapeKeyDown","pointerDownOutside","focusOutside","interactOutside","openAutoFocus","closeAutoFocus"],setup(r,{emit:l}){const t=r,a=l,o=C(),d=E(a),{forwardRef:f,currentElement:i}=D();return V(i),(n,s)=>(c(),_($,m({...t,...e(d)},{ref:e(f),"trap-focus":e(o).open.value,"disable-outside-pointer-events":t.disableOutsidePointerEvents,onCloseAutoFocus:s[0]||(s[0]=p=>{var y;p.defaultPrevented||(p.preventDefault(),(y=e(o).triggerElement.value)==null||y.focus())}),onPointerDownOutside:s[1]||(s[1]=p=>{const y=p.detail.originalEvent,P=y.button===0&&y.ctrlKey===!0;(y.button===2||P)&&p.preventDefault()}),onFocusOutside:s[2]||(s[2]=p=>{p.preventDefault()})}),{default:u(()=>[v(n.$slots,"default")]),_:3},16,["trap-focus","disable-outside-pointer-events"]))}}),te=ee,oe=g({__name:"DialogContentNonModal",props:{forceMount:{type:Boolean,required:!1},trapFocus:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:["escapeKeyDown","pointerDownOutside","focusOutside","interactOutside","openAutoFocus","closeAutoFocus"],setup(r,{emit:l}){const t=r,o=E(l);D();const d=C(),f=b(!1),i=b(!1);return(n,s)=>(c(),_($,m({...t,...e(o)},{"trap-focus":!1,"disable-outside-pointer-events":!1,onCloseAutoFocus:s[0]||(s[0]=p=>{var y;p.defaultPrevented||(f.value||(y=e(d).triggerElement.value)==null||y.focus(),p.preventDefault()),f.value=!1,i.value=!1}),onInteractOutside:s[1]||(s[1]=p=>{var h;p.defaultPrevented||(f.value=!0,p.detail.originalEvent.type==="pointerdown"&&(i.value=!0));const y=p.target;((h=e(d).triggerElement.value)==null?void 0:h.contains(y))&&p.preventDefault(),p.detail.originalEvent.type==="focusin"&&i.value&&p.preventDefault()})}),{default:u(()=>[v(n.$slots,"default")]),_:3},16))}}),ae=oe,se=g({__name:"DialogContent",props:{forceMount:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1,default:void 0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:["escapeKeyDown","pointerDownOutside","focusOutside","interactOutside","openAutoFocus","closeAutoFocus"],setup(r,{emit:l}){const t=r,a=l,o=C(),d=E(a),{forwardRef:f}=D();return(i,n)=>(c(),_(e(M),{present:i.forceMount||e(o).open.value},{default:u(()=>[e(o).modal.value?(c(),_(te,m({key:0,ref:e(f)},{...t,...e(d),...i.$attrs}),{default:u(()=>[v(i.$slots,"default")]),_:3},16)):(c(),_(ae,m({key:1,ref:e(f)},{...t,...e(d),...i.$attrs}),{default:u(()=>[v(i.$slots,"default")]),_:3},16))]),_:3},8,["present"]))}}),le=se,re=g({__name:"DialogOverlayImpl",props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(r){const l=C();return S(!0),D(),(t,a)=>(c(),_(e(B),{as:t.as,"as-child":t.asChild,"data-state":e(l).open.value?"open":"closed",style:{"pointer-events":"auto"},onPointerdown:a[0]||(a[0]=j(()=>{},["left","self","prevent"]))},{default:u(()=>[v(t.$slots,"default")]),_:3},8,["as","as-child","data-state"]))}}),ne=re,ue=g({__name:"DialogOverlay",props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(r){const l=C(),{forwardRef:t}=D();return(a,o)=>{var d;return(d=e(l))!=null&&d.modal.value?(c(),_(e(M),{key:0,present:a.forceMount||e(l).open.value},{default:u(()=>[O(ne,m(a.$attrs,{ref:e(t),as:a.as,"as-child":a.asChild}),{default:u(()=>[v(a.$slots,"default")]),_:3},16,["as","as-child"])]),_:3},8,["present"])):z("v-if",!0)}}}),ie=ue,de=g({__name:"DialogPortal",props:{to:{type:null,required:!1},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(r){const l=r;return(t,a)=>(c(),_(e(L),U(W(l)),{default:u(()=>[v(t.$slots,"default")]),_:3},16))}}),pe=de,fe=g({__name:"DialogTitle",props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:"h2"}},setup(r){const l=r,t=C();return D(),(a,o)=>(c(),_(e(B),m(l,{id:e(t).titleId}),{default:u(()=>[v(a.$slots,"default")]),_:3},16,["id"]))}}),ce=fe,_e=g({__name:"DialogTrigger",props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:"button"}},setup(r){const l=r,t=C(),{forwardRef:a,currentElement:o}=D();return t.contentId||(t.contentId=w(void 0,"reka-dialog-content")),A(()=>{t.triggerElement.value=o.value}),(d,f)=>(c(),_(e(B),m(l,{ref:e(a),type:d.as==="button"?"button":void 0,"aria-haspopup":"dialog","aria-expanded":e(t).open.value||!1,"aria-controls":e(t).open.value?e(t).contentId:void 0,"data-state":e(t).open.value?"open":"closed",onClick:e(t).onOpenToggle}),{default:u(()=>[v(d.$slots,"default")]),_:3},16,["type","aria-expanded","aria-controls","data-state","onClick"]))}}),ve=_e;const ge={class:"help-head"},ye={class:"help-body"},Ce=g({__name:"HelpModal",props:{title:{},label:{}},setup(r){return(l,t)=>(c(),_(e(G),null,{default:u(()=>[O(e(ve),{class:"helpq","aria-label":r.label??r.title},{default:u(()=>[...t[0]||(t[0]=[q("svg",{viewBox:"0 0 24 24",width:"13",height:"13","aria-hidden":"true",fill:"none",stroke:"currentColor","stroke-width":"2.2","stroke-linecap":"round"},[q("path",{d:"M9.2 9a2.9 2.9 0 1 1 3.8 2.8c-.7.3-1 .9-1 1.6v.4"}),q("circle",{cx:"12",cy:"17.6",r:"0.9",fill:"currentColor",stroke:"none"})],-1)])]),_:1},8,["aria-label"]),O(e(pe),null,{default:u(()=>[O(e(ie),{class:"scrim help-scrim"}),O(e(le),{class:"help-modal"},{default:u(()=>[q("div",ge,[O(e(ce),{class:"help-title"},{default:u(()=>[I(X(r.title),1)]),_:1}),O(e(Q),{class:"ghost sm","aria-label":"Close"},{default:u(()=>[...t[1]||(t[1]=[I("Close",-1)])]),_:1})]),q("div",ye,[v(l.$slots,"default")])]),_:3})]),_:3})]),_:3}))}});export{Ce as _};
2
- //# sourceMappingURL=HelpModal.vue_vue_type_script_setup_true_lang-BSOuxyyP.js.map
@@ -1,2 +0,0 @@
1
- import{d as q,c as d,a as t,F as j,s as W,a2 as K,o as l,D as O,E as S,u as v,t as u,J as G,M as H,e as C,w as L,f as i,g as c,x as I,r as b,C as R,j as P,A,B as J,L as z}from"./index-DVMQBHXF.js";import{a as Q,c as E,t as X}from"./useFormat-DVXTfIS-.js";import{_ as Y}from"./LogViewer.vue_vue_type_script_setup_true_lang-5r84oNls.js";import{_ as Z}from"./StateBadge.vue_vue_type_script_setup_true_lang-BgbZLzlL.js";import{_ as ee}from"./ActionButton.vue_vue_type_script_setup_true_lang-D5wrEmB2.js";import{_ as te}from"./ErrorNote.vue_vue_type_script_setup_true_lang-B3hfwyPR.js";function y(n){var r;return(r=n.message)!=null&&r.startsWith("unverifiable")?"unverifiable":n.ok?"ok":n.phase==="assert_gone"?"leaked":"failed"}function se(n){return{ok:"✓",unverifiable:"?",leaked:"!",failed:"✗"}[y(n)]}function ae(n){return n.message?n.message:n.skipped?"skipped":y(n)==="ok"?"ok":`exit ${n.code}`}function ne(n){return n.filter(r=>r.phase==="assert_gone"&&!r.ok).map(r=>r.axis)}function oe(n){return n.filter(r=>y(r)==="unverifiable").length}const le={class:"steps stagger"},re={class:"who"},ie={class:"dim",style:{"font-size":"var(--t-sm)"}},ue=q({__name:"StepList",props:{steps:{}},setup(n){return(r,s)=>(l(),d("div",null,[t("ul",le,[(l(!0),d(j,null,W(n.steps,(o,m)=>(l(),d("li",{key:`${o.phase}-${o.axis}-${m}`,class:"step",style:O({"--i":m})},[t("span",{class:S(["mark",`s-${v(y)(o)}`]),"aria-hidden":"true"},u(v(se)(o)),3),t("span",null,[t("span",re,u(o.axis),1),t("span",ie," · "+u(o.phase),1),t("span",{class:S(["badge",v(y)(o)]),style:{"margin-left":"var(--s2)"}},u(v(y)(o)),3),t("div",{class:S(["msg",`s-${v(y)(o)}`])},u(v(ae)(o)),3)])],4))),128))]),s[0]||(s[0]=K('<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))]))}}),de={class:"page-head"},ce={class:"sub"},pe={key:0,class:"panel banner-panel warn"},ve={key:2,class:"panel leaked-banner"},me={key:3,class:"panel"},fe={class:"pre"},ge={class:"panel"},ye={key:4,class:"panel"},be={class:"phead"},ke={key:0,class:"mute"},Se=q({__name:"JobDetailView",props:{jobId:{}},setup(n){const r=n,s=b(null),o=b([]),m=b(""),x=b(!0),_=b(!1),F=R(()=>{const a=[];let e="";for(const p of o.value){const h=new Date(p.at),g=h.toLocaleDateString("sv");g!==e&&(a.push({key:`day-${g}-${p.seq}`,separator:!0,text:g}),e=g),a.push({key:p.seq,gutter:h.toTimeString().slice(0,8),gutterTitle:E(p.at),text:p.message,tone:p.level})}return a});let f=null;const N=R(()=>{var a;return(a=s.value)!=null&&a.outcome?ne(s.value.outcome.steps):[]}),B=R(()=>{var a;return(a=s.value)!=null&&a.outcome?oe(s.value.outcome.steps):0});async function k(){var e;const a=await A.get(`/api/jobs/${encodeURIComponent(r.jobId)}`);return x.value=!1,a.ok?(m.value="",s.value=a.body.job,(e=a.body.job.log)!=null&&e.length&&o.value.length===0&&(o.value=[...a.body.job.log]),a.body.job):(m.value=J(a,"load job"),null)}function w(){f==null||f.close(),f=null,_.value=!1}function T(){w(),o.value=[],f=new EventSource(A.url(`/api/jobs/${encodeURIComponent(r.jobId)}/stream`)),_.value=!0,f.onmessage=a=>{let e;try{e=JSON.parse(a.data)}catch{return}if(e.done){w(),k();return}o.value.push(e)},f.onerror=()=>{w(),k()}}G(()=>r.jobId,async()=>{o.value=[],s.value=null,x.value=!0;const a=await k();a&&a.state==="running"&&T()},{immediate:!0});const $=b(!1);async function M(){$.value=!0;const a=await A.post(`/api/jobs/${encodeURIComponent(r.jobId)}/cancel`);if($.value=!1,!a.ok){z(a.status===409?"info":"error",J(a,"stop this job")),k();return}z("ok",a.body.warning??"Stopped."),k()}return H(w),(a,e)=>{var h,g,V,D;const p=P("RouterLink");return l(),d("div",null,[t("div",de,[t("div",null,[t("h1",null,[C(p,{to:"/jobs",class:"mute"},{default:L(()=>[...e[0]||(e[0]=[i("Jobs",-1)])]),_:1}),e[1]||(e[1]=t("span",{class:"mute"}," / ",-1)),i(u(s.value?v(Q)(s.value.action):"…"),1)]),t("div",ce,[s.value?(l(),d(j,{key:0},[t("code",null,u(s.value.stack),1),i(" · started "+u(v(E)(s.value.startedAt))+" ",1),s.value.endedAt?(l(),d(j,{key:0},[i(" · took "+u(v(X)(s.value.startedAt,s.value.endedAt)),1)],64)):c("",!0)],64)):(l(),d(j,{key:1},[i(u(n.jobId),1)],64))])]),e[3]||(e[3]=t("span",{class:"grow"},null,-1)),s.value?(l(),I(Z,{key:0,state:s.value.state},null,8,["state"])):c("",!0),((h=s.value)==null?void 0:h.state)==="running"?(l(),I(ee,{key:1,variant:"danger",pending:$.value,confirm:"Stop it? Nothing is undone.",title:"Kills the command in flight. Anything already created or destroyed stays that way.",onRun:M},{default:L(()=>[...e[2]||(e[2]=[i(" Stop ",-1)])]),_:1},8,["pending"])):c("",!0)]),((g=s.value)==null?void 0:g.state)==="cancelled"?(l(),d("section",pe,[t("b",null,"Stopped by "+u(s.value.cancelledBy??"an operator")+" — nothing was undone.",1),e[4]||(e[4]=t("p",null,[i(" Whatever this job had already created or destroyed is still that way. Run "),t("b",null,"Verify"),i(" on the deployment to see what actually exists. ")],-1))])):c("",!0),m.value?(l(),I(te,{key:1,text:m.value},null,8,["text"])):c("",!0),N.value.length?(l(),d("section",ve,[e[5]||(e[5]=t("strong",null,"Leaked:",-1)),i(" "+u(N.value.join(", "))+" ",1),e[6]||(e[6]=t("div",{class:"mute"},[i(" These axes' "),t("code",null,"assert_gone"),i(" failed, so the resources are still present. They will not be retried — tear them down by hand, then re-run "),t("code",null,"verify"),i(". ")],-1))])):c("",!0),(V=s.value)!=null&&V.error?(l(),d("section",me,[e[7]||(e[7]=t("strong",{class:"bad"},"The job crashed",-1)),t("pre",fe,u(s.value.error),1)])):c("",!0),t("section",ge,[e[8]||(e[8]=t("div",{class:"phead"},[t("h2",{class:"phead-title"},"Log")],-1)),C(Y,{rows:F.value,live:_.value,"empty-text":x.value?"Loading…":"No output."},{actions:L(()=>{var U;return[!_.value&&((U=s.value)==null?void 0:U.state)==="running"?(l(),d("button",{key:0,class:"ghost sm",onClick:T}," Reconnect ")):c("",!0)]}),_:1},8,["rows","live","empty-text"])]),(D=s.value)!=null&&D.outcome?(l(),d("section",ye,[t("div",be,[e[11]||(e[11]=t("h2",{class:"phead-title"},"Steps",-1)),B.value?(l(),d("span",ke,[i(u(B.value)+" unverifiable — no ",1),e[9]||(e[9]=t("code",null,"assert_gone",-1)),e[10]||(e[10]=i(", so nothing was checked ",-1))])):c("",!0)]),C(ue,{steps:s.value.outcome.steps},null,8,["steps"])])):c("",!0)])}}});export{Se as default};
2
- //# sourceMappingURL=JobDetailView-HFOJln2b.js.map
@@ -1,2 +0,0 @@
1
- import{d as x,c as v,a as t,u as S,b as V,F as C,e as n,f as s,w as l,_ as g,g as h,h as q,i as w,v as y,t as b,r as u,j as N,k as T,l as U,o as f,m as L,n as R}from"./index-DVMQBHXF.js";import{_ as B}from"./ActionButton.vue_vue_type_script_setup_true_lang-D5wrEmB2.js";import{E}from"./EquivalentCommand-C45BzkNz.js";import{_ as A}from"./_plugin-vue_export-helper-DlAUqK2U.js";const D={class:"login-wrap"},F={class:"login-card panel"},I={class:"banner plain",style:{"margin-top":"var(--s3)"}},M={class:"mute",style:{"margin-top":"var(--s3)"}},P={class:"field",style:{"margin-top":"var(--s4)"}},$={class:"field"},j={key:0,class:"banner failed",style:{"margin-top":"var(--s3)"}},z={class:"row",style:{"margin-top":"var(--s4)"}},O=x({__name:"LoginView",setup(W){const k=T(),d=U(),r=u(""),o=u(""),a=u(!1),i=u("");async function c(){a.value=!0,i.value="";const p=await L(r.value.trim(),o.value);if(a.value=!1,p){i.value=p,o.value="";return}await R();const e=typeof d.query.next=="string"&&d.query.next.startsWith("/")?d.query.next:"/";k.replace(e)}return(p,e)=>{const _=N("RouterLink");return f(),v("div",D,[t("div",F,[e[12]||(e[12]=t("h1",{style:{"font-size":"var(--t-xl)","margin-bottom":"var(--s2)"}},"Sign in",-1)),S(V).hasUsers===!1?(f(),v(C,{key:0},[e[6]||(e[6]=t("p",{class:"dim"}," No accounts exist on this server yet — create the first one, then sign in. ",-1)),t("div",I,[e[4]||(e[4]=t("b",null,"Create the first account",-1)),e[5]||(e[5]=t("p",null," This has to be done with the host token, from a terminal — a browser has no way to prove it is allowed to claim a fresh server. ",-1)),n(E,{what:"creating the first account",method:"POST",path:"/api/auth/bootstrap",body:{username:"you",password:"…"}}),t("p",M,[e[3]||(e[3]=s(" It can also be set up ahead of time when the host is first provisioned. ",-1)),n(g,{label:"how"},{default:l(()=>[...e[2]||(e[2]=[s(" Set the admin username and password in the environment before running the setup command on the host. They are honoured only while no account exists, so they cannot overwrite one later. ",-1)])]),_:1})])])],64)):h("",!0),t("form",{onSubmit:q(c,["prevent"])},[t("div",P,[e[7]||(e[7]=t("label",{for:"u"},"Username",-1)),w(t("input",{id:"u","onUpdate:modelValue":e[0]||(e[0]=m=>r.value=m),type:"text",autocomplete:"username",spellcheck:"false"},null,512),[[y,r.value]])]),t("div",$,[e[8]||(e[8]=t("label",{for:"p"},"Password",-1)),w(t("input",{id:"p","onUpdate:modelValue":e[1]||(e[1]=m=>o.value=m),type:"password",autocomplete:"current-password"},null,512),[[y,o.value]])]),i.value?(f(),v("div",j,[t("p",null,b(i.value),1)])):h("",!0),t("div",z,[n(B,{variant:"primary",pending:a.value,disabled:a.value||!r.value.trim()||!o.value,onClick:c},{default:l(()=>[s(b(a.value?"Signing in…":"Sign in"),1)]),_:1},8,["pending","disabled"]),e[11]||(e[11]=t("span",{class:"grow"},null,-1)),n(_,{to:"/settings",class:"mute"},{default:l(()=>[e[10]||(e[10]=s(" Use a token instead ",-1)),n(g,{label:"token access",align:"end"},{default:l(()=>[...e[9]||(e[9]=[s(" A bearer token in Settings — the machine token from ",-1),t("code",null,"pstack init",-1),s(", or a personal token from your account — authenticates every request without a session. ",-1)])]),_:1})]),_:1})])],32)])])}}}),Q=A(O,[["__scopeId","data-v-93108325"]]);export{Q as default};
2
- //# sourceMappingURL=LoginView-0vIrAhlr.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LoginView-0vIrAhlr.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 { ref } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\nimport { authState, checkAuth, login } from '../composables/useAuth';\nimport ActionButton from '../components/ActionButton.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport EquivalentCommand from '../components/EquivalentCommand.vue';\n\nconst router = useRouter();\nconst route = useRoute();\n\nconst username = ref('');\nconst password = ref('');\nconst pending = ref(false);\nconst error = ref('');\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 const dest = typeof route.query.next === 'string' && route.query.next.startsWith('/')\n ? route.query.next\n : '/';\n void router.replace(dest);\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 <p class=\"dim\">\n No accounts exist on this server yet — create the first one, then sign in.\n </p>\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>Create the first account</b>\n <p>\n This has to be done with the host token, from a terminal — a browser has no way to prove\n it is allowed to claim a fresh server.\n </p>\n <EquivalentCommand\n what=\"creating the first account\"\n method=\"POST\"\n path=\"/api/auth/bootstrap\"\n :body=\"{ username: 'you', password: '…' }\"\n />\n <p class=\"mute\" style=\"margin-top: var(--s3)\">\n It can also be set up ahead of time when the host is first provisioned.\n <InfoHint label=\"how\">\n Set the admin username and password in the environment before running the setup\n command on the host. They are honoured only while no account exists, so they cannot\n overwrite one later.\n </InfoHint>\n </p>\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\" 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\">\n Use a token instead\n <InfoHint label=\"token access\" align=\"end\">\n A bearer token in Settings — the machine token from <code>pstack init</code>, or a\n personal token from your account — authenticates every request without a session.\n </InfoHint>\n </RouterLink>\n </div>\n </form>\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</style>\n"],"names":["router","useRouter","route","useRoute","username","ref","password","pending","error","submit","failure","login","checkAuth","dest","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_unref","authState","_Fragment","_hoisted_3","_createVNode","EquivalentCommand","_hoisted_4","InfoHint","_hoisted_5","$event","_hoisted_6","_hoisted_7","_hoisted_8","ActionButton","_component_RouterLink"],"mappings":"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"}
@@ -1 +0,0 @@
1
- .login-wrap[data-v-93108325]{width:min(420px,100%)}.login-card[data-v-93108325]{width:100%}
@@ -1,6 +0,0 @@
1
- import{d as re,J as D,M as de,c as n,a as s,e as V,i as O,I as K,f as u,u as S,E as A,t as i,g as b,F as x,s as ve,v as ce,w as Q,_ as Y,x as me,r as m,C as E,A as W,N as H,B as pe,l as fe,k as he,o as a}from"./index-DVMQBHXF.js";import{d as c}from"./useDeployment-DGzBsXE-.js";import{s as ge}from"./useFormat-DVXTfIS-.js";import{_ as ee}from"./SelectMenu.vue_vue_type_script_setup_true_lang-D8NGgBfa.js";import{_ as ye}from"./LogViewer.vue_vue_type_script_setup_true_lang-5r84oNls.js";import"./useVars-VN9gYdR6.js";import"./Teleport-DfE2iP8c.js";const be={class:"panel"},ke={class:"phead"},we={class:"field inline"},xe={class:"field inline"},_e={class:"check"},Ce=["disabled","title"],Le=["disabled","title"],Se={key:0,class:"progress","aria-hidden":"true"},$e={key:0,class:"mute"},Fe={key:1,class:"mute"},Re={key:0,class:"banner failed"},Ne={key:1,class:"banner warn"},Te={class:"logs-split"},Ue={class:"clist","aria-label":"Containers"},Ve={class:"clist-sub"},Ae=["onClick"],Ee={class:"clist-name"},Ie={key:0,class:"badge warn"},Me={class:"clist-sub mono"},Be={key:0,class:"hint"},qe={class:"logs-main"},je={class:"row",style:{"margin-bottom":"var(--s3)"}},ze={class:"field grow"},De={class:"check"},Oe={key:0,class:"log"},We={key:2,class:"hint"},He={key:3,class:"hint"},Je={key:1,class:"mute"},J=5e3,tt=re({__name:"LogsTab",setup(Pe){const P=fe(),te=he(),_=m(200),$=m(""),C=m(!1),f=m(""),p=m([]),g=E(()=>p.value.find(t=>t.name===f.value)??null),I=E(()=>{var t;return((t=g.value)==null?void 0:t.service)??""});async function se(){const t=await W.get(`/api/deployments/${encodeURIComponent(c.id)}/runtime${H(c.vars)}`);t.ok&&(p.value=[...t.body.containers].sort((e,l)=>(e.service??e.name).localeCompare(l.service??l.name)))}const F=m(""),r=m(null),R=m(""),k=m(!1);D(()=>{var t;return[c.id,!!((t=c.detail)!=null&&t.compose)]},([t,e],l)=>{(!l||l[0]!==t)&&(p.value=[],f.value="",r.value=null),e&&p.value.length===0&&se()},{immediate:!0}),D([p,()=>P.query.container],([t,e])=>{const l=typeof e=="string"?e:"";!l||!t.length||f.value===l||t.some(v=>v.name===l)&&(f.value=l,M())},{immediate:!0});function X(t){f.value!==t&&(f.value=t,te.replace({query:{...P.query,container:t||void 0}}),M())}async function M(){var v;if(!((v=c.detail)!=null&&v.compose))return;const t=c.id;k.value=!0,R.value="";const e=I.value,l=await W.get(`/api/deployments/${encodeURIComponent(t)}/logs${H(c.vars,{tail:_.value,...e?{service:e}:{},...$.value?{since:$.value}:{},...C.value?{timestamps:1}:{}})}`);if(k.value=!1,c.id===t&&!(l.ok&&(l.body.service??"")!==e)){if(!l.ok){r.value=null,R.value=pe(l,"load the logs");return}r.value={...l.body,ok:l.body.ok!==!1,text:l.body.text??""}}}const d=m(!1),h=m([]);let y=null;function L(){y==null||y.close(),y=null,d.value=!1}function Z(){L(),h.value=[];const t=H(c.vars,{tail:_.value,...I.value?{service:I.value}:{},...C.value?{timestamps:1}:{}});y=new EventSource(W.url(`/api/deployments/${encodeURIComponent(c.id)}/logs/stream${t}`)),d.value=!0,y.onmessage=e=>{const l=JSON.parse(e.data);if(l.done){h.value.push(`— ${l.reason??"stream ended"} —`),L();return}l.line!==void 0&&(h.value.push(l.line),h.value.length>J&&h.value.splice(0,h.value.length-J))},y.onerror=()=>L()}function le(){d.value?L():Z()}D([f,C,_],()=>{d.value&&Z()}),de(L);const B=E(()=>{var v;const t=d.value||h.value.length?h.value.join(`
2
- `):((v=r.value)==null?void 0:v.text)??"",e=F.value.trim().toLowerCase();if(!e)return t;const l=t.split(`
3
- `).filter(o=>o.toLowerCase().includes(e));return l.length?l.join(`
4
- `):`(no line matches "${F.value}")`}),q=m(!1),oe=/^(\S+)\s+\| /,ae=/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s/,ne=E(()=>{var o;const t=B.value;if(!t||t.startsWith("(no line matches"))return[];const e=new Map,l=((o=g.value)==null?void 0:o.name)??"",v=[];return t.split(`
5
- `).forEach((N,G)=>{const j=oe.exec(N);if(!j){v.push({key:G,text:N});return}const w=j[1];if(l&&w!==l&&p.value.some(ue=>ue.name===w))return;let T=N.slice(j[0].length),U;const z=ae.exec(T);z&&(U=z[1],T=T.slice(z[0].length)),e.has(w)||e.set(w,e.size%6),v.push({key:G,gutter:w,text:T,tone:`svc-${e.get(w)}`,...U?{time:new Date(U).toTimeString().slice(0,8),timeTitle:U}:{}})}),v}),ie=t=>t.state==="running"&&t.health!=="unhealthy"?"s-ok":t.state==="exited"&&t.exitCode===0?"mute":"s-failed";return(t,e)=>{var l,v;return a(),n("section",be,[s("div",ke,[e[9]||(e[9]=s("h2",{class:"section"},"Compose logs",-1)),e[10]||(e[10]=s("span",{class:"grow"},null,-1)),s("div",we,[e[6]||(e[6]=s("label",{for:"tail"},"Lines",-1)),V(ee,{id:"tail","model-value":String(_.value),label:"How many lines",options:[{value:"50",label:"50"},{value:"200",label:"200"},{value:"500",label:"500"},{value:"1000",label:"1000"},{value:"2000",label:"2000"}],"onUpdate:modelValue":e[0]||(e[0]=o=>_.value=Number(o))},null,8,["model-value"])]),s("div",xe,[e[7]||(e[7]=s("label",{for:"since"},"Since",-1)),V(ee,{id:"since",modelValue:$.value,"onUpdate:modelValue":e[1]||(e[1]=o=>$.value=o),label:"How far back",options:[{value:"",label:"Anything kept"},{value:"5m",label:"Last 5 minutes"},{value:"30m",label:"Last 30 minutes"},{value:"1h",label:"Last hour"},{value:"24h",label:"Last day"}]},null,8,["modelValue"])]),s("label",_e,[O(s("input",{"onUpdate:modelValue":e[2]||(e[2]=o=>C.value=o),type:"checkbox"},null,512),[[K,C.value]]),e[8]||(e[8]=u(" Timestamps ",-1))]),s("button",{class:A(d.value?"danger":""),disabled:!((l=S(c).detail)!=null&&l.compose),title:d.value?"Stop streaming":"Stream new lines as they arrive",onClick:le},i(d.value?"Stop following":"Follow"),11,Ce),s("button",{class:"primary",disabled:k.value||d.value||!((v=S(c).detail)!=null&&v.compose),title:d.value?"Already streaming live.":void 0,onClick:M},[u(i(k.value?"Reading…":r.value?"Refresh":"Fetch")+" ",1),k.value?(a(),n("span",Se)):b("",!0)],8,Le)]),S(c).detail?S(c).detail.compose?(a(),n(x,{key:2},[R.value?(a(),n("div",Re,i(R.value),1)):r.value&&!r.value.ok?(a(),n("div",Ne,[...e[12]||(e[12]=[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)])])):b("",!0),s("div",Te,[s("aside",Ue,[s("button",{class:A(["clist-item",{on:f.value===""}]),onClick:e[3]||(e[3]=o=>X(""))},[e[13]||(e[13]=s("span",{class:"clist-name"},"All services",-1)),s("span",Ve,i(p.value.length)+" container"+i(p.value.length===1?"":"s"),1)],2),(a(!0),n(x,null,ve(p.value,o=>(a(),n("button",{key:o.id,class:A(["clist-item",{on:f.value===o.name}]),onClick:N=>X(o.name)},[s("span",Ee,[u(i(o.service??o.name)+" ",1),o.restartCount>0?(a(),n("span",Ie,i(o.restartCount)+"×",1)):b("",!0)]),s("span",Me,i(o.name),1),s("span",{class:A(["clist-state",ie(o)])},i(S(ge)(o.health??o.state)),3)],10,Ae))),128)),p.value.length?b("",!0):(a(),n("p",Be," No containers. Nothing has been brought up, or their profile is not selected. "))]),s("div",qe,[s("div",je,[s("div",ze,[e[14]||(e[14]=s("label",{for:"lf"},"Search in these lines",-1)),O(s("input",{id:"lf","onUpdate:modelValue":e[4]||(e[4]=o=>F.value=o),"data-search":"",type:"search",placeholder:"Substring",spellcheck:"false"},null,512),[[ce,F.value]])]),s("label",De,[O(s("input",{"onUpdate:modelValue":e[5]||(e[5]=o=>q.value=o),type:"checkbox"},null,512),[[K,q.value]]),e[16]||(e[16]=u(" Raw output ",-1)),V(Y,{label:"when raw matters"},{default:Q(()=>[...e[15]||(e[15]=[u(" The pretty view lifts each line's container prefix into a coloured column and its timestamp into the time gutter. Raw is the exact text the server sent — the form to paste into a bug report or compare with someone else's copy. ",-1)])]),_:1})])]),r.value||d.value||h.value.length?(a(),n(x,{key:0},[q.value?(a(),n("pre",Oe,i(B.value||"(no output)"),1)):(a(),me(ye,{key:1,rows:ne.value,live:d.value,"copy-text":B.value,"empty-text":d.value?"Waiting for output…":"(no output)"},null,8,["rows","live","copy-text","empty-text"])),d.value?(a(),n("p",We," Following "+i(g.value?g.value.name:"the whole stack")+" — new lines appear as they are written, up to "+i(J.toLocaleString())+" kept on screen. ",1)):r.value?(a(),n("p",He,[u(i(r.value.lines??0)+" line"+i((r.value.lines??0)===1?"":"s")+" ",1),g.value?(a(),n(x,{key:0},[e[17]||(e[17]=u("from ",-1)),s("b",null,i(g.value.name),1)],64)):(a(),n(x,{key:1},[u("across the stack")],64)),r.value.since?(a(),n(x,{key:2},[u(", last "+i(r.value.since),1)],64)):b("",!0),u(", tail "+i(r.value.tail)+". Redacted on the host before sending: credentials inside URLs, ",1),e[19]||(e[19]=s("code",null,"NAME=value",-1)),e[20]||(e[20]=u(" pairs whose name reads as a secret, and this server's own token. ",-1)),V(Y,{label:"why this is not live"},{default:Q(()=>[...e[18]||(e[18]=[u(" Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer the host that runs everyone else's previews. ",-1)])]),_:1})])):b("",!0)],64)):k.value?b("",!0):(a(),n("p",Je,[e[21]||(e[21]=u(" Press ",-1)),e[22]||(e[22]=s("b",null,"Fetch",-1)),e[23]||(e[23]=u(" for the last lines, or ",-1)),e[24]||(e[24]=s("b",null,"Follow",-1)),u(" to stream them live"+i(g.value?` from ${g.value.name}`:"")+". ",1)]))])])],64)):(a(),n("p",Fe,[...e[11]||(e[11]=[u(" This spec has no ",-1),s("code",null,"compose:",-1),u(" section, so there are no compose logs to read. Its axes may still have created resources — check those at their own source. ",-1)])])):(a(),n("p",$e,"Unavailable until the spec resolves."))])}}});export{tt as default};
6
- //# sourceMappingURL=LogsTab-CFlgBsW3.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LogsTab-CFlgBsW3.js","sources":["../../src/views/tabs/LogsTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Compose logs, fetched ON DEMAND.\n *\n * Never polled: an auto-refresh against a chatty stack would hammer the host through\n * `docker compose logs` — the same host that is running everyone else's previews. The tail is\n * clamped to 1–2000 by the server for the same reason.\n *\n * ── THE CONTAINER LIST IS A PANEL, NOT A DROPDOWN ────────────────────────────────────────────────\n *\n * A picker hides the fleet behind a click and shows one name at a time. Reading logs is a hunt —\n * \"which of these is the one that's broken\" — so the containers are a standing list beside the\n * output, each with its state, and one click switches the pane. It scrolls, because a stack with\n * twenty containers must not push the log off the screen.\n *\n * Selecting a CONTAINER narrows two ways at once: the server is asked for that container's service\n * (so the whole tail is spent on it), and rows whose prefix names a different replica are dropped\n * client-side. Selecting \"All services\" restores the interleaved view.\n *\n * ── WHY THE OPTIONS EXIST ────────────────────────────────────────────────────────────────────────\n *\n * `--timestamps` is what makes a line comparable to anything else — a deploy, a healthcheck flap,\n * another service. `--since` is what makes a long-lived container readable: a tail of 2000 on a\n * service that logs every request is 2000 lines of the last four minutes.\n */\nimport { computed, onBeforeUnmount, ref, watch } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\nimport { api, problem, query } from '../../api/client';\nimport type { LogsResponse, RuntimeContainer, RuntimeResponse } from '../../api/types';\nimport { dep } from '../../composables/useDeployment';\nimport { sentence } from '../../composables/useFormat';\nimport InfoHint from '../../components/InfoHint.vue';\nimport SelectMenu from '../../components/SelectMenu.vue';\nimport LogViewer from '../../components/LogViewer.vue';\nimport type { LogRow } from '../../components/log-viewer-types';\n\nconst route = useRoute();\nconst router = useRouter();\n\nconst tail = ref(200);\nconst since = ref('');\nconst timestamps = ref(false);\n/** '' means the whole stack; otherwise a CONTAINER name (its service is what the server is asked for). */\nconst selected = ref('');\nconst containers = ref<RuntimeContainer[]>([]);\n\nconst current = computed(() => containers.value.find((c) => c.name === selected.value) ?? null);\n/** What the server is asked to narrow to — compose narrows by service, not by container. */\nconst service = computed(() => current.value?.service ?? '');\n\nasync function loadContainers(): Promise<void> {\n const r = await api.get<RuntimeResponse>(\n `/api/deployments/${encodeURIComponent(dep.id)}/runtime${query(dep.vars)}`,\n );\n if (!r.ok) return; // the panel is a convenience; the whole-stack read still works without it\n containers.value = [...r.body.containers].sort((a, b) =>\n (a.service ?? a.name).localeCompare(b.service ?? b.name),\n );\n}\n\nconst filter = ref('');\nconst logs = ref<LogsResponse | null>(null);\nconst error = ref('');\nconst loading = ref(false);\n\nwatch(\n () => [dep.id, !!dep.detail?.compose] as const,\n ([id, hasCompose], prev) => {\n if (!prev || prev[0] !== id) {\n containers.value = [];\n selected.value = '';\n logs.value = null;\n }\n // Gated on the RESOLVED spec, not just the id: this component mounts before the parent has\n // resolved the deployment, so an id-only watcher saw `dep.detail === undefined` and the\n // container panel never appeared.\n if (hasCompose && containers.value.length === 0) void loadContainers();\n },\n { immediate: true },\n);\n\n/**\n * `?container=` deep link — how the Containers table jumps straight here.\n *\n * Applied once the container list has arrived, because the name has to resolve to a real container\n * to pick its service; and it fetches immediately, since arriving from \"show me this container's\n * logs\" and then being asked to press Fetch is a wasted click.\n */\nwatch(\n [containers, () => route.query.container],\n ([list, wanted]) => {\n const name = typeof wanted === 'string' ? wanted : '';\n if (!name || !list.length || selected.value === name) return;\n if (!list.some((c) => c.name === name)) return;\n selected.value = name;\n void fetchLogs();\n },\n { immediate: true },\n);\n\nfunction select(name: string): void {\n if (selected.value === name) return;\n selected.value = name;\n // Keep the URL honest so the pane can be linked to and survives a reload.\n void router.replace({ query: { ...route.query, container: name || undefined } });\n void fetchLogs();\n}\n\nasync function fetchLogs(): Promise<void> {\n if (!dep.detail?.compose) return;\n const id = dep.id;\n loading.value = true;\n error.value = '';\n const chosen = service.value;\n const r = await api.get<LogsResponse>(\n `/api/deployments/${encodeURIComponent(id)}/logs${query(dep.vars, {\n tail: tail.value,\n ...(chosen ? { service: chosen } : {}),\n ...(since.value ? { since: since.value } : {}),\n ...(timestamps.value ? { timestamps: 1 } : {}),\n })}`,\n );\n loading.value = false;\n if (dep.id !== id) return; // navigated away mid-flight\n // A slow response for the previously-selected service must not land in a pane now labelled with a\n // different one.\n if (r.ok && (r.body.service ?? '') !== chosen) return;\n if (!r.ok) {\n logs.value = null;\n error.value = problem(r, 'load the logs');\n return;\n }\n logs.value = { ...r.body, ok: r.body.ok !== false, text: r.body.text ?? '' };\n}\n\n/**\n * FOLLOW — one `compose logs --follow` on the server, streamed here over SSE.\n *\n * Not a poll. Re-fetching a full tail every few seconds would re-send everything each time and hit\n * the host that is running everyone else's previews; a followed process sends each line once and\n * costs nothing in between. That is why the \"never polled\" rule in this file survives a live tail.\n *\n * The buffer is CAPPED. A chatty service can emit thousands of lines a minute, and an unbounded\n * array behind a DOM list is how a tab left open overnight ends up unresponsive.\n */\nconst MAX_FOLLOW_LINES = 5_000;\nconst following = ref(false);\nconst liveLines = ref<string[]>([]);\nlet source: EventSource | null = null;\n\nfunction stopFollow(): void {\n source?.close();\n source = null;\n following.value = false;\n}\n\nfunction startFollow(): void {\n stopFollow();\n liveLines.value = [];\n const q = query(dep.vars, {\n tail: tail.value,\n ...(service.value ? { service: service.value } : {}),\n ...(timestamps.value ? { timestamps: 1 } : {}),\n });\n // EventSource cannot set headers, which is exactly why sessions are cookies — the request is\n // same-origin and carries the session automatically. A token-only browser session is the one case\n // this cannot serve, and the server answers 401 rather than streaming nothing.\n source = new EventSource(\n api.url(`/api/deployments/${encodeURIComponent(dep.id)}/logs/stream${q}`),\n );\n following.value = true;\n source.onmessage = (ev) => {\n const payload = JSON.parse(ev.data) as { line?: string; done?: boolean; reason?: string };\n if (payload.done) {\n // The stack stopped, or the follow hit its cap. Say which, rather than leaving a live-looking\n // pane that will never produce another line.\n liveLines.value.push(`— ${payload.reason ?? 'stream ended'} —`);\n stopFollow();\n return;\n }\n if (payload.line === undefined) return;\n liveLines.value.push(payload.line);\n if (liveLines.value.length > MAX_FOLLOW_LINES) {\n liveLines.value.splice(0, liveLines.value.length - MAX_FOLLOW_LINES);\n }\n };\n source.onerror = () => stopFollow();\n}\n\nfunction toggleFollow(): void {\n if (following.value) stopFollow();\n else startFollow();\n}\n\n// Switching container/options mid-follow must re-open the stream, or the pane keeps showing the\n// previous selection's lines under the new label.\nwatch([selected, timestamps, tail], () => {\n if (following.value) startFollow();\n});\nonBeforeUnmount(stopFollow);\n\n/** Search-in-logs: filter to matching lines rather than highlighting inside a wall of text. */\nconst shown = computed(() => {\n // While following, the live buffer IS the log — one source of truth for the pane, so the pretty\n // view, raw view, search and copy all agree about what is on screen.\n const text = following.value || liveLines.value.length ? liveLines.value.join('\\n') : (logs.value?.text ?? '');\n const needle = filter.value.trim().toLowerCase();\n if (!needle) return text;\n const lines = text.split('\\n').filter((l) => l.toLowerCase().includes(needle));\n return lines.length ? lines.join('\\n') : `(no line matches \"${filter.value}\")`;\n});\n\n/**\n * Raw is ALWAYS available, one click away. The pretty view re-arranges compose's output, and a\n * re-arranged log is the wrong thing to paste into a bug report or diff against a colleague's —\n * raw shows the exact bytes the server sent, filter and all.\n */\nconst raw = ref(false);\n\n/**\n * Compose prefixes every line with `container-name | `, and with `--timestamps` the timestamp\n * follows it. The pretty view lifts the prefix into a non-wrapping gutter with a stable per-service\n * colour and the timestamp into the viewer's own time column, so interleaved services stop reading\n * as one grey wall. A line with no prefix (a compose warning, a continuation) gets an empty gutter\n * rather than being guessed at.\n */\nconst PREFIX = /^(\\S+)\\s+\\| /;\n/** Docker's own stamp: RFC3339 with nanoseconds, always first after the prefix. */\nconst TS = /^(\\d{4}-\\d{2}-\\d{2}T[\\d:.]+Z?)\\s/;\n\nconst logRows = computed<LogRow[]>(() => {\n const text = shown.value;\n if (!text || text.startsWith('(no line matches')) return [];\n const hues = new Map<string, number>();\n const only = current.value?.name ?? '';\n const rows: LogRow[] = [];\n text.split('\\n').forEach((line, i) => {\n const m = PREFIX.exec(line);\n if (!m) {\n rows.push({ key: i, text: line });\n return;\n }\n const from = m[1]!;\n // A service with several replicas answers as one stream; keep only the chosen replica's lines.\n if (only && from !== only && containers.value.some((c) => c.name === from)) return;\n let rest = line.slice(m[0].length);\n let when: string | undefined;\n const t = TS.exec(rest);\n if (t) {\n when = t[1]!;\n rest = rest.slice(t[0].length);\n }\n if (!hues.has(from)) hues.set(from, hues.size % 6);\n rows.push({\n key: i,\n gutter: from,\n text: rest,\n tone: `svc-${hues.get(from)}`,\n ...(when ? { time: new Date(when).toTimeString().slice(0, 8), timeTitle: when } : {}),\n });\n });\n return rows;\n});\n\nconst stateTone = (c: RuntimeContainer): string =>\n c.state === 'running' && c.health !== 'unhealthy' ? 's-ok' : c.state === 'exited' && c.exitCode === 0 ? 'mute' : 's-failed';\n</script>\n\n<template>\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Compose logs</h2>\n <span class=\"grow\" />\n <div class=\"field inline\">\n <label for=\"tail\">Lines</label>\n <SelectMenu\n id=\"tail\"\n :model-value=\"String(tail)\"\n label=\"How many lines\"\n :options=\"[\n { value: '50', label: '50' },\n { value: '200', label: '200' },\n { value: '500', label: '500' },\n { value: '1000', label: '1000' },\n { value: '2000', label: '2000' },\n ]\"\n @update:model-value=\"(v) => (tail = Number(v))\"\n />\n </div>\n <div class=\"field inline\">\n <label for=\"since\">Since</label>\n <SelectMenu\n id=\"since\"\n v-model=\"since\"\n label=\"How far back\"\n :options=\"[\n { value: '', label: 'Anything kept' },\n { value: '5m', label: 'Last 5 minutes' },\n { value: '30m', label: 'Last 30 minutes' },\n { value: '1h', label: 'Last hour' },\n { value: '24h', label: 'Last day' },\n ]\"\n />\n </div>\n <label class=\"check\">\n <input v-model=\"timestamps\" type=\"checkbox\" />\n Timestamps\n </label>\n <button\n :class=\"following ? 'danger' : ''\"\n :disabled=\"!dep.detail?.compose\"\n :title=\"following ? 'Stop streaming' : 'Stream new lines as they arrive'\"\n @click=\"toggleFollow\"\n >\n {{ following ? 'Stop following' : 'Follow' }}\n </button>\n <button\n class=\"primary\"\n :disabled=\"loading || following || !dep.detail?.compose\"\n :title=\"following ? 'Already streaming live.' : undefined\"\n @click=\"fetchLogs\"\n >\n {{ loading ? 'Reading…' : logs ? 'Refresh' : 'Fetch' }}\n <span v-if=\"loading\" class=\"progress\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <p v-if=\"!dep.detail\" class=\"mute\">Unavailable until the spec resolves.</p>\n <!-- Guard on the field rather than calling and interpreting the failure after the fact. -->\n <p v-else-if=\"!dep.detail.compose\" class=\"mute\">\n This spec has no <code>compose:</code> section, so there are no compose logs to read. Its axes\n may still have created resources — check those at their own source.\n </p>\n\n <template v-else>\n <div v-if=\"error\" class=\"banner failed\">{{ error }}</div>\n\n <!--\n `ok: false` means compose exited non-zero. Usually \"no such project\" because the stack was\n never brought up — not a server fault, so it is a note rather than an error.\n -->\n <div v-else-if=\"logs && !logs.ok\" class=\"banner warn\">\n <b>Compose exited non-zero.</b>\n <p>\n Most often the project does not exist yet (nothing has been brought up), or a profile is\n missing. Any output it did produce is below.\n </p>\n </div>\n\n <div class=\"logs-split\">\n <!-- ============================ the container panel ============================ -->\n <aside class=\"clist\" aria-label=\"Containers\">\n <button class=\"clist-item\" :class=\"{ on: selected === '' }\" @click=\"select('')\">\n <span class=\"clist-name\">All services</span>\n <span class=\"clist-sub\">{{ containers.length }} container{{ containers.length === 1 ? '' : 's' }}</span>\n </button>\n <button\n v-for=\"c in containers\"\n :key=\"c.id\"\n class=\"clist-item\"\n :class=\"{ on: selected === c.name }\"\n @click=\"select(c.name)\"\n >\n <span class=\"clist-name\">\n {{ c.service ?? c.name }}\n <span v-if=\"c.restartCount > 0\" class=\"badge warn\">{{ c.restartCount }}×</span>\n </span>\n <span class=\"clist-sub mono\">{{ c.name }}</span>\n <span class=\"clist-state\" :class=\"stateTone(c)\">\n {{ sentence(c.health ?? c.state) }}\n </span>\n </button>\n <p v-if=\"!containers.length\" class=\"hint\">\n No containers. Nothing has been brought up, or their profile is not selected.\n </p>\n </aside>\n\n <!-- ============================ the output ============================ -->\n <div class=\"logs-main\">\n <div class=\"row\" style=\"margin-bottom: var(--s3)\">\n <div class=\"field grow\">\n <label for=\"lf\">Search in these lines</label>\n <input\n id=\"lf\"\n v-model=\"filter\"\n data-search\n type=\"search\"\n placeholder=\"Substring\"\n spellcheck=\"false\"\n />\n </div>\n <label class=\"check\">\n <input v-model=\"raw\" type=\"checkbox\" />\n Raw output\n <InfoHint label=\"when raw matters\">\n The pretty view lifts each line's container prefix into a coloured column and its\n timestamp into the time gutter. Raw is the exact text the server sent — the form to\n paste into a bug report or compare with someone else's copy.\n </InfoHint>\n </label>\n </div>\n\n <template v-if=\"logs || following || liveLines.length\">\n <pre v-if=\"raw\" class=\"log\">{{ shown || '(no output)' }}</pre>\n <LogViewer\n v-else\n :rows=\"logRows\"\n :live=\"following\"\n :copy-text=\"shown\"\n :empty-text=\"following ? 'Waiting for output…' : '(no output)'\"\n />\n <p v-if=\"following\" class=\"hint\">\n Following {{ current ? current.name : 'the whole stack' }} — new lines appear as they\n are written, up to {{ MAX_FOLLOW_LINES.toLocaleString() }} kept on screen.\n </p>\n <p v-else-if=\"logs\" class=\"hint\">\n {{ logs.lines ?? 0 }} line{{ (logs.lines ?? 0) === 1 ? '' : 's' }}\n <template v-if=\"current\">from <b>{{ current.name }}</b></template>\n <template v-else>across the stack</template>\n <template v-if=\"logs.since\">, last {{ logs.since }}</template>, tail {{ logs.tail }}.\n Redacted on the host before sending: credentials inside URLs,\n <code>NAME=value</code> pairs whose name reads as a secret, and this server's own token.\n <InfoHint label=\"why this is not live\">\n Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer\n the host that runs everyone else's previews.\n </InfoHint>\n </p>\n </template>\n <p v-else-if=\"!loading\" class=\"mute\">\n Press <b>Fetch</b> for the last lines, or <b>Follow</b> to stream them live{{\n current ? ` from ${current.name}` : ''\n }}.\n </p>\n </div>\n </div>\n </template>\n </section>\n</template>\n"],"names":["MAX_FOLLOW_LINES","route","useRoute","router","useRouter","tail","ref","since","timestamps","selected","containers","current","computed","c","service","_a","loadContainers","r","api","dep","query","a","b","filter","logs","error","loading","watch","id","hasCompose","prev","list","wanted","name","fetchLogs","select","chosen","problem","following","liveLines","source","stopFollow","startFollow","q","ev","payload","toggleFollow","onBeforeUnmount","shown","text","needle","lines","l","raw","PREFIX","TS","logRows","hues","only","rows","line","i","m","from","rest","when","t","stateTone","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_hoisted_3","_createVNode","SelectMenu","v","_hoisted_4","$event","_hoisted_5","_unref","_hoisted_6","_hoisted_8","_Fragment","_hoisted_11","_toDisplayString","_hoisted_12","_hoisted_13","_hoisted_14","_normalizeClass","_hoisted_15","_renderList","_hoisted_17","_createTextVNode","_hoisted_18","_hoisted_19","sentence","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","InfoHint","_hoisted_25","_createBlock","LogViewer","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_10","_hoisted_9"],"mappings":"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"}