@samyx/preview-stacks-ui 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-D8Xlp6c9.js +2 -0
- package/dist/assets/{ActionButton.vue_vue_type_script_setup_true_lang-C6PZurL0.js.map → ActionButton.vue_vue_type_script_setup_true_lang-D8Xlp6c9.js.map} +1 -1
- package/dist/assets/{AxesTab-BNAORqpJ.js → AxesTab-BWzsbFAx.js} +2 -2
- package/dist/assets/{AxesTab-BNAORqpJ.js.map → AxesTab-BWzsbFAx.js.map} +1 -1
- package/dist/assets/ConfigTab-ucr87rD6.js +2 -0
- package/dist/assets/{ConfigTab-8Ifz-gnA.js.map → ConfigTab-ucr87rD6.js.map} +1 -1
- package/dist/assets/{ConfigView-CPj8QeBo.js → ConfigView-BbRYGyvZ.js} +2 -2
- package/dist/assets/{ConfigView-CPj8QeBo.js.map → ConfigView-BbRYGyvZ.js.map} +1 -1
- package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-DDt6qe65.js +2 -0
- package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-DDt6qe65.js.map +1 -0
- package/dist/assets/DangerTab-BimKNJab.js +2 -0
- package/dist/assets/DangerTab-BimKNJab.js.map +1 -0
- package/dist/assets/DashboardView-DK0qDo-f.js +2 -0
- package/dist/assets/{DashboardView-CuDBbXnZ.js.map → DashboardView-DK0qDo-f.js.map} +1 -1
- package/dist/assets/DeploymentDetailView-BcImKApM.js +2 -0
- package/dist/assets/{DeploymentDetailView-qtlOQQ0z.js.map → DeploymentDetailView-BcImKApM.js.map} +1 -1
- package/dist/assets/DeploymentsView-Cx9BqQ8Q.js +2 -0
- package/dist/assets/DeploymentsView-Cx9BqQ8Q.js.map +1 -0
- package/dist/assets/DialogTrigger-nWkfUj3N.js +2 -0
- package/dist/assets/DialogTrigger-nWkfUj3N.js.map +1 -0
- package/dist/assets/{EquivalentCommand-DX5xFW-p.js → EquivalentCommand-D92xDU0_.js} +3 -3
- package/dist/assets/{EquivalentCommand-DX5xFW-p.js.map → EquivalentCommand-D92xDU0_.js.map} +1 -1
- package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-CUZs06O0.js → ErrorNote.vue_vue_type_script_setup_true_lang-DVbGiVTB.js} +2 -2
- package/dist/assets/{ErrorNote.vue_vue_type_script_setup_true_lang-CUZs06O0.js.map → ErrorNote.vue_vue_type_script_setup_true_lang-DVbGiVTB.js.map} +1 -1
- package/dist/assets/HelpModal.vue_vue_type_script_setup_true_lang-CSOdBT2E.js +2 -0
- package/dist/assets/HelpModal.vue_vue_type_script_setup_true_lang-CSOdBT2E.js.map +1 -0
- package/dist/assets/JobDetailView-B1sXXVJ5.js +2 -0
- package/dist/assets/JobDetailView-B1sXXVJ5.js.map +1 -0
- package/dist/assets/JobsView-CC7lre3Q.css +1 -0
- package/dist/assets/JobsView-DjP5lXHk.js +2 -0
- package/dist/assets/JobsView-DjP5lXHk.js.map +1 -0
- package/dist/assets/LogViewer.vue_vue_type_script_setup_true_lang-Bhy_5cyC.js +3 -0
- package/dist/assets/{LogViewer.vue_vue_type_script_setup_true_lang-Sg8LzIyX.js.map → LogViewer.vue_vue_type_script_setup_true_lang-Bhy_5cyC.js.map} +1 -1
- package/dist/assets/{LoginView-Gpnnyg1m.js → LoginView-CIkI9Qm_.js} +2 -2
- package/dist/assets/{LoginView-Gpnnyg1m.js.map → LoginView-CIkI9Qm_.js.map} +1 -1
- package/dist/assets/LogsTab-D8zhR7Eb.js +6 -0
- package/dist/assets/{LogsTab-CZ0fNgsO.js.map → LogsTab-D8zhR7Eb.js.map} +1 -1
- package/dist/assets/{NotFoundView-BE2kvYuP.js → NotFoundView-aUtqh2Qi.js} +2 -2
- package/dist/assets/{NotFoundView-BE2kvYuP.js.map → NotFoundView-aUtqh2Qi.js.map} +1 -1
- package/dist/assets/NotifiersView-aRHInxVx.js +2 -0
- package/dist/assets/{NotifiersView-Df_CM6Bj.js.map → NotifiersView-aRHInxVx.js.map} +1 -1
- package/dist/assets/OverviewTab-ChCj5LAj.js +2 -0
- package/dist/assets/{OverviewTab-BSeic8dR.js.map → OverviewTab-ChCj5LAj.js.map} +1 -1
- package/dist/assets/PublicLogsView-DIWJH7qL.js +4 -0
- package/dist/assets/{PublicLogsView-BDyvxkgj.js.map → PublicLogsView-DIWJH7qL.js.map} +1 -1
- package/dist/assets/{RefreshButton.vue_vue_type_script_setup_true_lang-ZUc7pgOx.js → RefreshButton.vue_vue_type_script_setup_true_lang-P7VwWcRa.js} +2 -2
- package/dist/assets/{RefreshButton.vue_vue_type_script_setup_true_lang-ZUc7pgOx.js.map → RefreshButton.vue_vue_type_script_setup_true_lang-P7VwWcRa.js.map} +1 -1
- package/dist/assets/RegistriesView-D2aCsYIu.js +2 -0
- package/dist/assets/{RegistriesView-DMzsLFcT.js.map → RegistriesView-D2aCsYIu.js.map} +1 -1
- package/dist/assets/RelativeTime.vue_vue_type_script_setup_true_lang-NGvUfuKC.js +2 -0
- package/dist/assets/RelativeTime.vue_vue_type_script_setup_true_lang-NGvUfuKC.js.map +1 -0
- package/dist/assets/{RequiresTab-DkuxJsEj.js → RequiresTab-3BhU5BHd.js} +2 -2
- package/dist/assets/{RequiresTab-DkuxJsEj.js.map → RequiresTab-3BhU5BHd.js.map} +1 -1
- package/dist/assets/RouteTarget.vue_vue_type_script_setup_true_lang-Caee0wEB.js +2 -0
- package/dist/assets/RouteTarget.vue_vue_type_script_setup_true_lang-Caee0wEB.js.map +1 -0
- package/dist/assets/RoutingView-T9afX8_M.js +9 -0
- package/dist/assets/RoutingView-T9afX8_M.js.map +1 -0
- package/dist/assets/RoutingView-tn0RQdqM.css +0 -0
- package/dist/assets/RuntimeTab-Bh2gGuhj.js +2 -0
- package/dist/assets/RuntimeTab-Bh2gGuhj.js.map +1 -0
- package/dist/assets/RuntimeTab-By55qdsQ.css +1 -0
- package/dist/assets/SelectMenu.vue_vue_type_script_setup_true_lang-BtHqT6G1.js +2 -0
- package/dist/assets/{SelectMenu.vue_vue_type_script_setup_true_lang-DKYlHroe.js.map → SelectMenu.vue_vue_type_script_setup_true_lang-BtHqT6G1.js.map} +1 -1
- package/dist/assets/{SettingsView-CgwOmLat.js → SettingsView-DIKjdBdy.js} +2 -2
- package/dist/assets/{SettingsView-CgwOmLat.js.map → SettingsView-DIKjdBdy.js.map} +1 -1
- package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js → SkeletonList.vue_vue_type_script_setup_true_lang-BmeRGmxf.js} +2 -2
- package/dist/assets/{SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js.map → SkeletonList.vue_vue_type_script_setup_true_lang-BmeRGmxf.js.map} +1 -1
- package/dist/assets/SpecDetailView-Bd4Hx-bI.js +2 -0
- package/dist/assets/{SpecDetailView-BlHaL1Y8.js.map → SpecDetailView-Bd4Hx-bI.js.map} +1 -1
- package/dist/assets/SpecsView-B9qQPzkG.js +2 -0
- package/dist/assets/{SpecsView-C-bS2Av2.js.map → SpecsView-B9qQPzkG.js.map} +1 -1
- package/dist/assets/{SsoMark.vue_vue_type_script_setup_true_lang-aKprkHCF.js → SsoMark.vue_vue_type_script_setup_true_lang-BBiCq_4w.js} +2 -2
- package/dist/assets/{SsoMark.vue_vue_type_script_setup_true_lang-aKprkHCF.js.map → SsoMark.vue_vue_type_script_setup_true_lang-BBiCq_4w.js.map} +1 -1
- package/dist/assets/{SsoView-BUz2EaVq.js → SsoView-CltdTdwt.js} +2 -2
- package/dist/assets/{SsoView-BUz2EaVq.js.map → SsoView-CltdTdwt.js.map} +1 -1
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-nD6Zdx0q.js +2 -0
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-nD6Zdx0q.js.map +1 -0
- package/dist/assets/{SubmitView-s5haxS9q.js → SubmitView-D-jg5Wxy.js} +5 -5
- package/dist/assets/{SubmitView-s5haxS9q.js.map → SubmitView-D-jg5Wxy.js.map} +1 -1
- package/dist/assets/SwarmView-CqFndUrV.js +2 -0
- package/dist/assets/{SwarmView-skNbhHws.js.map → SwarmView-CqFndUrV.js.map} +1 -1
- package/dist/assets/Teleport-I0XiM19H.js +4 -0
- package/dist/assets/{Teleport-CKfsIrp8.js.map → Teleport-I0XiM19H.js.map} +1 -1
- package/dist/assets/TerminalTab-Dleba0ru.js +3 -0
- package/dist/assets/{TerminalTab-CPuFkMzh.js.map → TerminalTab-Dleba0ru.js.map} +1 -1
- package/dist/assets/UsersView-Bd8B9pLD.css +1 -0
- package/dist/assets/UsersView-BrZbuuOg.js +2 -0
- package/dist/assets/UsersView-BrZbuuOg.js.map +1 -0
- package/dist/assets/VarEditor.vue_vue_type_script_setup_true_lang-uPHU_Ugp.js +2 -0
- package/dist/assets/VarEditor.vue_vue_type_script_setup_true_lang-uPHU_Ugp.js.map +1 -0
- package/dist/assets/VarIO-BVE8A4Ur.css +1 -0
- package/dist/assets/VarIO-BldCQ6Xx.js +15 -0
- package/dist/assets/VarIO-BldCQ6Xx.js.map +1 -0
- package/dist/assets/VariablesView-CQp0T7Q6.js +3 -0
- package/dist/assets/VariablesView-CQp0T7Q6.js.map +1 -0
- package/dist/assets/VariablesView-Ck3IyRCN.css +1 -0
- package/dist/assets/index-B1ONXQzZ.css +1 -0
- package/dist/assets/index-Cqe_7QZw.js +154 -0
- package/dist/assets/index-Cqe_7QZw.js.map +1 -0
- package/dist/assets/useDeployment-BDbkdxpo.js +2 -0
- package/dist/assets/{useDeployment-CV0kcdoc.js.map → useDeployment-BDbkdxpo.js.map} +1 -1
- package/dist/assets/useJobQueue-D5JkRJNX.js +2 -0
- package/dist/assets/useJobQueue-D5JkRJNX.js.map +1 -0
- package/dist/index.html +2 -2
- package/package.json +2 -1
- package/dist/assets/ActionButton.vue_vue_type_script_setup_true_lang-C6PZurL0.js +0 -2
- package/dist/assets/ConfigTab-8Ifz-gnA.js +0 -2
- package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-B0PJUns4.js +0 -2
- package/dist/assets/ConflictNote.vue_vue_type_script_setup_true_lang-B0PJUns4.js.map +0 -1
- package/dist/assets/DangerTab-BwXkb6Rr.js +0 -2
- package/dist/assets/DangerTab-BwXkb6Rr.js.map +0 -1
- package/dist/assets/DashboardView-CuDBbXnZ.js +0 -2
- package/dist/assets/DeploymentDetailView-qtlOQQ0z.js +0 -2
- package/dist/assets/DeploymentsView-CQwTTlTI.js +0 -2
- package/dist/assets/DeploymentsView-CQwTTlTI.js.map +0 -1
- package/dist/assets/HelpModal.vue_vue_type_script_setup_true_lang-ErbSSi6D.js +0 -2
- package/dist/assets/HelpModal.vue_vue_type_script_setup_true_lang-ErbSSi6D.js.map +0 -1
- package/dist/assets/JobDetailView-BgoEL7EB.js +0 -2
- package/dist/assets/JobDetailView-BgoEL7EB.js.map +0 -1
- package/dist/assets/JobsView-CVG0iuN4.js +0 -2
- package/dist/assets/JobsView-CVG0iuN4.js.map +0 -1
- package/dist/assets/LogViewer.vue_vue_type_script_setup_true_lang-Sg8LzIyX.js +0 -3
- package/dist/assets/LogsTab-CZ0fNgsO.js +0 -6
- package/dist/assets/NotifiersView-Df_CM6Bj.js +0 -2
- package/dist/assets/OverviewTab-BSeic8dR.js +0 -2
- package/dist/assets/PublicLogsView-BDyvxkgj.js +0 -4
- package/dist/assets/RegistriesView-DMzsLFcT.js +0 -2
- package/dist/assets/RelativeTime.vue_vue_type_script_setup_true_lang-D4NO-r5D.js +0 -2
- package/dist/assets/RelativeTime.vue_vue_type_script_setup_true_lang-D4NO-r5D.js.map +0 -1
- package/dist/assets/RoutingView-BEJ3iJ9c.js +0 -9
- package/dist/assets/RoutingView-BEJ3iJ9c.js.map +0 -1
- package/dist/assets/RuntimeTab-DY8ZYeng.css +0 -1
- package/dist/assets/RuntimeTab-bWJuLJcn.js +0 -2
- package/dist/assets/RuntimeTab-bWJuLJcn.js.map +0 -1
- package/dist/assets/SelectMenu.vue_vue_type_script_setup_true_lang-DKYlHroe.js +0 -2
- package/dist/assets/SpecDetailView-BlHaL1Y8.js +0 -2
- package/dist/assets/SpecsView-C-bS2Av2.js +0 -2
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-C-7EQXcg.js +0 -2
- package/dist/assets/StateBadge.vue_vue_type_script_setup_true_lang-C-7EQXcg.js.map +0 -1
- package/dist/assets/SwarmView-skNbhHws.js +0 -2
- package/dist/assets/Teleport-CKfsIrp8.js +0 -4
- package/dist/assets/TerminalTab-CPuFkMzh.js +0 -3
- package/dist/assets/UsersView-Be77zCYE.css +0 -1
- package/dist/assets/UsersView-CIfFPshj.js +0 -2
- package/dist/assets/UsersView-CIfFPshj.js.map +0 -1
- package/dist/assets/VarEditor.vue_vue_type_script_setup_true_lang-JwR7DqO1.js +0 -2
- package/dist/assets/VarEditor.vue_vue_type_script_setup_true_lang-JwR7DqO1.js.map +0 -1
- package/dist/assets/VariablesView-ByeOvDGJ.css +0 -1
- package/dist/assets/VariablesView-DatC33mT.js +0 -2
- package/dist/assets/VariablesView-DatC33mT.js.map +0 -1
- package/dist/assets/index-B_k9b7xG.css +0 -1
- package/dist/assets/index-T3JGe9ku.js +0 -154
- package/dist/assets/index-T3JGe9ku.js.map +0 -1
- package/dist/assets/useDeployment-CV0kcdoc.js +0 -2
- package/dist/assets/useFormat-DIlTY5Dl.js +0 -2
- package/dist/assets/useFormat-DIlTY5Dl.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"JobDetailView-BgoEL7EB.js","sources":["../../src/composables/useSteps.ts","../../src/components/StepList.vue","../../src/views/JobDetailView.vue"],"sourcesContent":["/**\n * Step classification — the single most important logic in this app.\n *\n * A job's outcome has four states that MUST NOT blur into one another. The whole product is the\n * distinction between the middle two:\n *\n * ok passed. A `down` step is ALWAYS ok and may still carry a \"non-fatal:\" note —\n * teardown is best-effort by design, so the step really did pass.\n * unverifiable the axis defines no `assert_gone`, so `verify` had nothing to check. NOT a pass.\n * A green here would be exactly the false confidence this tool exists to remove.\n * leaked `assert_gone` FAILED — the resource survived teardown. Nothing else will clean\n * it up. This is the signal the whole tool exists for, and the CLI's exit code 2.\n * failed any other non-zero step.\n *\n * Order matters: `unverifiable` is matched on the message FIRST, exactly as the CLI's own report\n * does. `skipped` alone is not enough — a dry run also skips.\n */\n\nimport type { StepResult } from '../api/types';\n\nexport type StepState = 'ok' | 'unverifiable' | 'leaked' | 'failed';\n\nexport function stepState(s: StepResult): StepState {\n if (s.message?.startsWith('unverifiable')) return 'unverifiable';\n if (!s.ok) return s.phase === 'assert_gone' ? 'leaked' : 'failed';\n return 'ok';\n}\n\nexport function stepMark(s: StepResult): string {\n return { ok: '✓', unverifiable: '?', leaked: '!', failed: '✗' }[stepState(s)];\n}\n\nexport function stepText(s: StepResult): string {\n if (s.message) return s.message;\n if (s.skipped) return 'skipped';\n return stepState(s) === 'ok' ? 'ok' : `exit ${s.code}`;\n}\n\n/** Only a failed `assert_gone` is a leak. Every other non-zero step is a plain failure. */\nexport function leakedAxes(steps: StepResult[]): string[] {\n return steps.filter((s) => s.phase === 'assert_gone' && !s.ok).map((s) => s.axis);\n}\n\nexport function countUnverifiable(steps: StepResult[]): number {\n return steps.filter((s) => stepState(s) === 'unverifiable').length;\n}\n","<script setup lang=\"ts\">\n/**\n * A job's step results.\n *\n * The legend is part of the component because the four states only mean anything together:\n * `unverifiable` is not a pass, and `leaked` is not the same problem as `failed`.\n */\nimport type { StepResult } from '../api/types';\nimport { stepMark, stepState, stepText } from '../composables/useSteps';\n\ndefineProps<{ steps: StepResult[] }>();\n</script>\n\n<template>\n <div>\n <ul class=\"steps stagger\">\n <li\n v-for=\"(s, i) in steps\"\n :key=\"`${s.phase}-${s.axis}-${i}`\"\n class=\"step\"\n :style=\"{ '--i': i }\"\n >\n <span class=\"mark\" :class=\"`s-${stepState(s)}`\" aria-hidden=\"true\">{{ stepMark(s) }}</span>\n <span>\n <span class=\"who\">{{ s.axis }}</span>\n <span class=\"dim\" style=\"font-size: var(--t-sm)\"> · {{ s.phase }}</span>\n <span class=\"badge\" :class=\"stepState(s)\" style=\"margin-left: var(--s2)\">\n {{ stepState(s) }}\n </span>\n <div class=\"msg\" :class=\"`s-${stepState(s)}`\">{{ stepText(s) }}</div>\n </span>\n </li>\n </ul>\n\n <p class=\"hint\">\n <span class=\"s-ok\">✓ ok</span> ·\n <span class=\"s-failed\">✗ failed</span> ·\n <span class=\"s-leaked\">! leaked — survived teardown</span> ·\n <span class=\"s-unverifiable\">? unverifiable — nothing to check, not a pass</span>.\n A <code>down</code> step marked ok may still carry a <code>non-fatal:</code> note: teardown is\n best-effort by design, so aborting halfway would leave more behind than continuing.\n </p>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One job: its live log while it runs, then the step table that says what actually happened.\n *\n * The log streams over `EventSource`. The server replays the whole buffered history first and then\n * sends live events, so attaching late still shows the beginning — no polling, no gap to reconcile.\n * A final `{done:true,state}` ends the stream; we close the source then and fetch the job once more\n * to pick up `outcome`, which only exists after the job finishes.\n */\nimport { computed, onBeforeUnmount, ref, watch } from 'vue';\nimport { api, problem } from '../api/client';\nimport type { Job, LogEvent } from '../api/types';\nimport { leakedAxes, countUnverifiable } from '../composables/useSteps';\nimport { actionLabel, stamp, took } from '../composables/useFormat';\nimport LogViewer from '../components/LogViewer.vue';\nimport type { LogRow } from '../components/log-viewer-types';\nimport StepList from '../components/StepList.vue';\nimport StateBadge from '../components/StateBadge.vue';\nimport ActionButton from '../components/ActionButton.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport { toast } from '../composables/useToasts';\n\nconst props = defineProps<{ jobId: string }>();\n\nconst job = ref<Job | null>(null);\nconst lines = ref<LogEvent[]>([]);\nconst error = ref('');\nconst loading = ref(true);\nconst streaming = ref(false);\n\n/** Job events → viewer rows. The date repeats for hundreds of lines, so it appears once per DAY as\n * a separator and each row carries only the clock; the full stamp survives in the tooltip. */\nconst logRows = computed<LogRow[]>(() => {\n const out: LogRow[] = [];\n let lastDay = '';\n for (const l of lines.value) {\n const d = new Date(l.at);\n const day = d.toLocaleDateString('sv');\n if (day !== lastDay) {\n out.push({ key: `day-${day}-${l.seq}`, separator: true, text: day });\n lastDay = day;\n }\n out.push({\n key: l.seq,\n gutter: d.toTimeString().slice(0, 8),\n gutterTitle: stamp(l.at),\n text: l.message,\n tone: l.level,\n });\n }\n return out;\n});\n\nlet source: EventSource | null = null;\n\nconst leaks = computed(() => (job.value?.outcome ? leakedAxes(job.value.outcome.steps) : []));\nconst unverifiable = computed(() =>\n job.value?.outcome ? countUnverifiable(job.value.outcome.steps) : 0,\n);\n\n/** Returns the job it loaded, so callers need not re-read the ref — see the note at the watcher. */\nasync function load(): Promise<Job | null> {\n const r = await api.get<{ job: Job }>(`/api/jobs/${encodeURIComponent(props.jobId)}`);\n loading.value = false;\n if (!r.ok) {\n error.value = problem(r, 'load job');\n return null;\n }\n error.value = '';\n job.value = r.body.job;\n // The job carries its buffered log too. Seed from it so a finished job renders instantly without\n // opening a stream that would only replay and close.\n if (r.body.job.log?.length && lines.value.length === 0) lines.value = [...r.body.job.log];\n return r.body.job;\n}\n\nfunction closeStream(): void {\n source?.close();\n source = null;\n streaming.value = false;\n}\n\nfunction stream(): void {\n closeStream();\n /*\n * The stream REPLAYS the whole buffer before going live, and `load()` has already seeded `lines`\n * from the same buffer — so every line the job had produced before the page opened was rendered\n * twice, with the count to match. Clearing here is the honest fix: the replay is complete by\n * definition, so the seed is only ever the placeholder that keeps a finished job from flashing\n * empty.\n */\n lines.value = [];\n source = new EventSource(api.url(`/api/jobs/${encodeURIComponent(props.jobId)}/stream`));\n streaming.value = true;\n\n source.onmessage = (ev) => {\n let payload: unknown;\n try {\n payload = JSON.parse(ev.data as string);\n } catch {\n return; // a frame we cannot parse is not worth tearing the stream down for\n }\n const done = payload as { done?: boolean };\n if (done.done) {\n closeStream();\n void load(); // `outcome` only exists once the job has ended\n return;\n }\n lines.value.push(payload as LogEvent);\n };\n\n // An error here is usually the job having ended between the fetch and the connect, or the API\n // becoming unreachable. Either way stop rather than let the browser reconnect forever.\n source.onerror = () => {\n closeStream();\n void load();\n };\n}\n\n\nwatch(\n () => props.jobId,\n async () => {\n lines.value = [];\n job.value = null;\n loading.value = true;\n // Use what `load` returned rather than re-reading the ref: control-flow analysis still has\n // `job.value` narrowed to the `null` assigned above, because it cannot see that an awaited\n // call reassigned it.\n const loaded = await load();\n if (loaded && loaded.state === 'running') stream();\n },\n { immediate: true },\n);\n\nconst cancelling = ref(false);\n\n/**\n * Stop the job.\n *\n * The stream is deliberately left open: the server writes the cancellation into the SAME log the\n * page is already watching, so the operator sees the run stop where it stopped instead of watching a\n * page that has gone quiet. `load()` afterwards picks up the final state and `cancelledBy`.\n */\nasync function cancel(): Promise<void> {\n cancelling.value = true;\n const r = await api.post<{ warning?: string }>(\n `/api/jobs/${encodeURIComponent(props.jobId)}/cancel`,\n );\n cancelling.value = false;\n if (!r.ok) {\n // 409 means it finished on its own between the click and the request — not an error worth an\n // alarm, just a stale page.\n toast(r.status === 409 ? 'info' : 'error', problem(r, 'stop this job'));\n void load();\n return;\n }\n toast('ok', r.body.warning ?? 'Stopped.');\n void load();\n}\n\nonBeforeUnmount(closeStream);\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>\n <RouterLink to=\"/jobs\" class=\"mute\">Jobs</RouterLink>\n <span class=\"mute\"> / </span>{{ job ? actionLabel(job.action) : '…' }}\n </h1>\n <div class=\"sub\">\n <template v-if=\"job\">\n <code>{{ job.stack }}</code> · started {{ stamp(job.startedAt) }}\n <template v-if=\"job.endedAt\"> · took {{ took(job.startedAt, job.endedAt) }}</template>\n </template>\n <template v-else>{{ jobId }}</template>\n </div>\n </div>\n <span class=\"grow\" />\n <StateBadge v-if=\"job\" :state=\"job.state\" />\n <!--\n Stopping is destructive in the way that matters here: it does not undo, so the confirmation\n says what will be left behind rather than asking \"are you sure\".\n -->\n <ActionButton\n v-if=\"job?.state === 'running'\"\n variant=\"danger\"\n :pending=\"cancelling\"\n confirm=\"Stop it? Nothing is undone.\"\n title=\"Kills the command in flight. Anything already created or destroyed stays that way.\"\n @run=\"cancel\"\n >\n Stop\n </ActionButton>\n </div>\n\n <!--\n Not a badge's worth of information. A cancelled job is the one state where the record is\n complete and the INFRASTRUCTURE is not, and the next step is a person's to take.\n -->\n <section v-if=\"job?.state === 'cancelled'\" class=\"panel banner-panel warn\">\n <b>Stopped by {{ job.cancelledBy ?? 'an operator' }} — nothing was undone.</b>\n <p>\n Whatever this job had already created or destroyed is still that way. Run\n <b>Verify</b> on the deployment to see what actually exists.\n </p>\n </section>\n\n <ErrorNote v-if=\"error\" :text=\"error\" />\n\n <!--\n Leaked is called out above everything else. It means a resource survived teardown and nothing\n else is going to clean it up — the one outcome that needs a human.\n -->\n <section v-if=\"leaks.length\" class=\"panel leaked-banner\">\n <strong>Leaked:</strong> {{ leaks.join(', ') }}\n <div class=\"mute\">\n These axes' <code>assert_gone</code> failed, so the resources are still present. They will\n not be retried — tear them down by hand, then re-run <code>verify</code>.\n </div>\n </section>\n\n <section v-if=\"job?.error\" class=\"panel\">\n <strong class=\"bad\">The job crashed</strong>\n <pre class=\"pre\">{{ job.error }}</pre>\n </section>\n\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"phead-title\">Log</h2>\n </div>\n\n <!-- Time-of-day gutter with a date separator when the day flips — the full 19-character\n timestamp per line is what used to wrap every entry onto five or six lines. -->\n <LogViewer\n :rows=\"logRows\"\n :live=\"streaming\"\n :empty-text=\"loading ? 'Loading…' : 'No output.'\"\n >\n <template #actions>\n <button v-if=\"!streaming && job?.state === 'running'\" class=\"ghost sm\" @click=\"stream\">\n Reconnect\n </button>\n </template>\n </LogViewer>\n </section>\n\n <section v-if=\"job?.outcome\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"phead-title\">Steps</h2>\n <span v-if=\"unverifiable\" class=\"mute\">\n {{ unverifiable }} unverifiable — no <code>assert_gone</code>, so nothing was checked\n </span>\n </div>\n <StepList :steps=\"job.outcome.steps\" />\n </section>\n </div>\n</template>\n"],"names":["stepState","s","_a","stepMark","stepText","leakedAxes","steps","countUnverifiable","_createElementBlock","_createElementVNode","_hoisted_1","_openBlock","_Fragment","_renderList","__props","i","_normalizeClass","_unref","_toDisplayString","_hoisted_2","_hoisted_3","props","job","ref","lines","error","loading","streaming","logRows","computed","out","lastDay","l","d","day","stamp","source","leaks","unverifiable","load","r","api","problem","closeStream","stream","ev","payload","watch","loaded","cancelling","cancel","toast","onBeforeUnmount","_createVNode","_component_RouterLink","_cache","_createTextVNode","actionLabel","_createBlock","StateBadge","ActionButton","_b","ErrorNote","_hoisted_4","_c","_hoisted_5","_hoisted_6","_hoisted_7","LogViewer","_d","_hoisted_8","_hoisted_9","_hoisted_10","StepList"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as y,c as o,a as t,f as r,e as l,w as _,_ as w,u as n,H as g,i as x,v as C,g as f,A as i,t as d,z as V,F as h,r as L,j as k,k as S,o as a,D as $,l as A}from"./index-T3JGe9ku.js";import{a as N,t as B}from"./useFormat-DIlTY5Dl.js";import{_ as R}from"./StateBadge.vue_vue_type_script_setup_true_lang-C-7EQXcg.js";import{_ as F}from"./SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js";import{_ as T}from"./RelativeTime.vue_vue_type_script_setup_true_lang-D4NO-r5D.js";import{_ as U}from"./SelectMenu.vue_vue_type_script_setup_true_lang-DKYlHroe.js";import{_ as D}from"./RefreshButton.vue_vue_type_script_setup_true_lang-ZUc7pgOx.js";import"./Teleport-CKfsIrp8.js";const E={class:"page-head"},J={class:"sub"},M={class:"panel"},j={class:"phead"},q={class:"searchbox"},z={key:0},I={key:0,class:"s-failed"},H={key:2,class:"cards"},O={class:"stagger"},G={"data-label":"state"},K={"data-label":"action"},P={class:"name","data-label":"stack"},Q={class:"dim nowrap","data-label":"started"},W={class:"dim nowrap","data-label":"took"},X={class:"dim","data-label":"steps"},Y={key:0},Z={colspan:"6",class:"mute"},dt=y({__name:"JobsView",setup(tt){const u=k(""),c=k("all"),p=S(()=>{const m=u.value.trim().toLowerCase();return i.jobs.filter(e=>c.value!=="all"&&e.state!==c.value?!1:m?e.stack.toLowerCase().includes(m)||e.action.includes(m):!0)});return(m,e)=>{const b=A("RouterLink");return a(),o("div",null,[t("div",E,[t("div",null,[e[4]||(e[4]=t("h1",null,"Jobs",-1)),t("div",J,[e[3]||(e[3]=r(" Most recent first, capped at the last 50 ",-1)),l(w,{label:"why old jobs disappear"},{default:_(()=>[...e[2]||(e[2]=[r(" The history is held in memory and a server restart clears it. A job record is the transcript of an attempt, not the truth about what exists — that truth lives in the containers themselves and in each axis's own probe. ",-1)])]),_:1})])]),e[5]||(e[5]=t("span",{class:"grow"},null,-1)),l(D,{run:n(g)},null,8,["run"])]),t("section",M,[t("div",j,[t("div",q,[e[6]||(e[6]=t("svg",{viewBox:"0 0 24 24",width:"15",height:"15","aria-hidden":"true"},[t("circle",{cx:"11",cy:"11",r:"7",fill:"none",stroke:"currentColor","stroke-width":"2"}),t("path",{d:"M16.5 16.5 21 21",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round"})],-1)),x(t("input",{id:"jq","onUpdate:modelValue":e[0]||(e[0]=s=>u.value=s),"data-search":"",type:"search","aria-label":"Search jobs",placeholder:"Search by stack or action",spellcheck:"false"},null,512),[[C,u.value]]),u.value?f("",!0):(a(),o("kbd",z,"/"))]),l(U,{id:"js",modelValue:c.value,"onUpdate:modelValue":e[1]||(e[1]=s=>c.value=s),label:"Filter by state",options:[{value:"all",label:"All states"},{value:"running",label:"Running"},{value:"ok",label:"Ok"},{value:"leaked",label:"Leaked"},{value:"failed",label:"Failed"}]},null,8,["modelValue"])]),n(i).jobsError?(a(),o("p",I,d(n(i).jobsError),1)):n(i).jobsLoaded?(a(),o("table",H,[e[7]||(e[7]=t("thead",null,[t("tr",null,[t("th",null,"State"),t("th",null,"Action"),t("th",null,"Stack"),t("th",null,"Started"),t("th",null,"Took"),t("th",null,"Steps")])],-1)),t("tbody",O,[(a(!0),o(h,null,L(p.value,(s,v)=>(a(),o("tr",{key:s.id,style:$({"--i":v})},[t("td",G,[l(b,{to:`/jobs/${encodeURIComponent(s.id)}`},{default:_(()=>[l(R,{state:s.state},null,8,["state"])]),_:2},1032,["to"])]),t("td",K,d(n(N)(s.action)),1),t("td",P,[l(b,{to:`/jobs/${encodeURIComponent(s.id)}`},{default:_(()=>[r(d(s.stack),1)]),_:2},1032,["to"])]),t("td",Q,[l(T,{at:s.startedAt},null,8,["at"])]),t("td",W,d(n(B)(s.startedAt,s.endedAt)),1),t("td",X,d(s.outcome?s.outcome.steps.length:"—"),1)],4))),128)),p.value.length?f("",!0):(a(),o("tr",Y,[t("td",Z,[n(i).jobs.length?(a(),o(h,{key:0},[r("Nothing matches this filter.")],64)):(a(),o(h,{key:1},[r("No jobs recorded.")],64))])]))])])):(a(),V(F,{key:1,rows:5}))])])}}});export{dt as default};
|
|
2
|
-
//# sourceMappingURL=JobsView-CVG0iuN4.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"JobsView-CVG0iuN4.js","sources":["../../src/views/JobsView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Job history. Polled by the shell already, so this view only filters what is in memory. */\nimport { computed, ref } from 'vue';\nimport { loadJobs, state } from '../composables/useControlPlane';\nimport { actionLabel, took } from '../composables/useFormat';\nimport StateBadge from '../components/StateBadge.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport RelativeTime from '../components/RelativeTime.vue';\nimport SelectMenu from '../components/SelectMenu.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport type { JobState } from '../api/types';\nimport RefreshButton from '../components/RefreshButton.vue';\n\nconst q = ref('');\nconst only = ref<'all' | JobState>('all');\n\nconst rows = computed(() => {\n const needle = q.value.trim().toLowerCase();\n return state.jobs.filter((j) => {\n if (only.value !== 'all' && j.state !== only.value) return false;\n if (!needle) return true;\n return j.stack.toLowerCase().includes(needle) || j.action.includes(needle);\n });\n});\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>Jobs</h1>\n <div class=\"sub\">\n Most recent first, capped at the last 50\n <InfoHint label=\"why old jobs disappear\">\n The history is held in memory and a server restart clears it. A job record is the\n transcript of an attempt, not the truth about what exists — that truth lives in the\n containers themselves and in each axis's own probe.\n </InfoHint>\n </div>\n </div>\n <span class=\"grow\" />\n <RefreshButton :run=\"loadJobs\" />\n </div>\n\n <section class=\"panel\">\n <!-- Same one-row toolbar as Deployments — this page had kept the old stacked-label layout,\n and two list pages with two different toolbars reads as two different products. -->\n <div class=\"phead\">\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=\"jq\"\n v-model=\"q\"\n data-search\n type=\"search\"\n aria-label=\"Search jobs\"\n placeholder=\"Search by stack or action\"\n spellcheck=\"false\"\n />\n <kbd v-if=\"!q\">/</kbd>\n </div>\n <SelectMenu\n id=\"js\"\n v-model=\"only\"\n label=\"Filter by state\"\n :options=\"[\n { value: 'all', label: 'All states' },\n { value: 'running', label: 'Running' },\n { value: 'ok', label: 'Ok' },\n { value: 'leaked', label: 'Leaked' },\n { value: 'failed', label: 'Failed' },\n ]\"\n />\n </div>\n\n <p v-if=\"state.jobsError\" class=\"s-failed\">{{ state.jobsError }}</p>\n <SkeletonList v-else-if=\"!state.jobsLoaded\" :rows=\"5\" />\n\n <table v-else class=\"cards\">\n <thead>\n <tr>\n <th>State</th>\n <th>Action</th>\n <th>Stack</th>\n <th>Started</th>\n <th>Took</th>\n <th>Steps</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(j, i) in rows\" :key=\"j.id\" :style=\"{ '--i': i }\">\n <td data-label=\"state\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(j.id)}`\">\n <StateBadge :state=\"j.state\" />\n </RouterLink>\n </td>\n <td data-label=\"action\">{{ actionLabel(j.action) }}</td>\n <td class=\"name\" data-label=\"stack\">\n <RouterLink :to=\"`/jobs/${encodeURIComponent(j.id)}`\">{{ j.stack }}</RouterLink>\n </td>\n <td class=\"dim nowrap\" data-label=\"started\"><RelativeTime :at=\"j.startedAt\" /></td>\n <td class=\"dim nowrap\" data-label=\"took\">{{ took(j.startedAt, j.endedAt) }}</td>\n <td class=\"dim\" data-label=\"steps\">{{ j.outcome ? j.outcome.steps.length : '—' }}</td>\n </tr>\n <tr v-if=\"!rows.length\">\n <td colspan=\"6\" class=\"mute\">\n <template v-if=\"state.jobs.length\">Nothing matches this filter.</template>\n <template v-else>No jobs recorded.</template>\n </td>\n </tr>\n </tbody>\n </table>\n </section>\n </div>\n</template>\n"],"names":["q","ref","only","rows","computed","needle","state","j","_createElementBlock","_createElementVNode","_hoisted_1","_cache","_hoisted_2","_createVNode","InfoHint","RefreshButton","_unref","loadJobs","_hoisted_3","_hoisted_4","_hoisted_5","$event","_openBlock","SelectMenu","_hoisted_7","_toDisplayString","_hoisted_8","_hoisted_9","_Fragment","_renderList","i","_hoisted_10","_component_RouterLink","StateBadge","_hoisted_11","_hoisted_12","_createTextVNode","_hoisted_13","RelativeTime","_hoisted_14","took","_hoisted_15","_hoisted_16","_hoisted_17","_createBlock","SkeletonList"],"mappings":"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"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{d as x,J as b,K as _,M as T,c as s,a,t as n,f as C,g as c,$ as B,F as m,r as N,j as y,a0 as S,O as g,k as V,o as l,E as j}from"./index-T3JGe9ku.js";const F={class:"lv"},H={class:"lv-bar"},E={class:"lv-count"},L={key:0,class:"badge running"},M=["disabled"],$={key:0,class:"lv-empty"},z={key:0,class:"lv-sep"},D=["title"],J=["title"],K={class:"lv-msg"},P=x({__name:"LogViewer",props:{rows:{},live:{type:Boolean},copyText:{},emptyText:{}},setup(i){const r=i,d=y(null),u=y(!0);function k(){const t=d.value;return!!t&&t.scrollHeight-t.scrollTop-t.clientHeight<24}function f(){u.value=k()}async function v(){await S();const t=d.value;t&&u.value&&(t.scrollTop=t.scrollHeight)}b(()=>r.rows.length,v),_(v);function h(){u.value=!0,v()}async function w(){const t=r.copyText??r.rows.filter(o=>!o.separator).map(o=>[o.time,o.gutter,o.text].filter(Boolean).join(" ")).join(`
|
|
2
|
-
`);try{await navigator.clipboard.writeText(t),g("ok","Copied.")}catch{g("error","Could not copy — your browser blocked clipboard access.")}}const p=V(()=>r.rows.filter(t=>!t.separator).length);return T(()=>{}),(t,o)=>(l(),s("div",F,[a("div",H,[a("span",E,n(p.value)+" line"+n(p.value===1?"":"s"),1),i.live?(l(),s("span",L,[...o[0]||(o[0]=[a("span",{class:"dot pulse"},null,-1),C("live",-1)])])):c("",!0),o[1]||(o[1]=a("span",{class:"grow"},null,-1)),i.live&&!u.value?(l(),s("button",{key:1,class:"ghost sm",onClick:h},"↓ Follow")):c("",!0),a("button",{class:"ghost sm",disabled:!p.value,onClick:w},"Copy",8,M),B(t.$slots,"actions")]),a("div",{ref_key:"box",ref:d,class:"lv-box",role:"log","aria-live":"polite",onScrollPassive:f},[i.rows.length?c("",!0):(l(),s("p",$,n(i.emptyText??"No output."),1)),(l(!0),s(m,null,N(i.rows,e=>(l(),s(m,{key:e.key},[e.separator?(l(),s("div",z,[a("span",null,n(e.text),1)])):(l(),s("div",{key:1,class:j(["lv-row",e.tone])},[e.time!==void 0?(l(),s("span",{key:0,class:"lv-time",title:e.timeTitle},n(e.time),9,D)):c("",!0),e.gutter!==void 0?(l(),s("span",{key:1,class:"lv-gutter",title:e.gutterTitle},n(e.gutter),9,J)):c("",!0),a("span",K,n(e.text),1)],2))],64))),128))],544)]))}});export{P as _};
|
|
3
|
-
//# sourceMappingURL=LogViewer.vue_vue_type_script_setup_true_lang-Sg8LzIyX.js.map
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import{d as ue,J as O,M as de,c as n,a as s,e as I,i as P,I as K,f as i,u as w,E as M,t as r,g as x,F as y,r as ve,v as me,w as Q,_ as Y,z as ce,j as c,k as A,p as W,P as H,C as pe,n as fe,m as he,o as a}from"./index-T3JGe9ku.js";import{d as u}from"./useDeployment-CV0kcdoc.js";import{s as ge}from"./useFormat-DIlTY5Dl.js";import{_ as ee}from"./SelectMenu.vue_vue_type_script_setup_true_lang-DKYlHroe.js";import{_ as ye}from"./LogViewer.vue_vue_type_script_setup_true_lang-Sg8LzIyX.js";import"./useVars-VN9gYdR6.js";import"./Teleport-CKfsIrp8.js";const be={class:"panel"},ke={class:"phead"},we={class:"field inline"},xe={class:"field inline"},Ce={class:"check"},_e=["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"},Te={key:1,class:"banner warn"},Ue={class:"logs-split"},Ve={class:"clist","aria-label":"Containers"},Ne={class:"clist-sub"},Ee=["onClick"],Ie={class:"clist-name"},Me={key:0,class:"badge warn"},Ae={class:"clist-sub mono"},qe={key:0,class:"hint"},Be={class:"logs-main"},je={class:"row",style:{"margin-bottom":"var(--s3)"}},ze={class:"field grow"},De={class:"check"},Oe={key:0,class:"log"},Pe={key:2,class:"hint"},We={key:3,class:"hint"},He={key:1,class:"mute"},J=5e3,tt=ue({__name:"LogsTab",setup(Je){const X=fe(),te=he(),S=c(200),R=c(""),$=c(!1),f=c(""),p=c([]),g=A(()=>p.value.find(t=>t.name===f.value)??null),q=A(()=>{var t;return((t=g.value)==null?void 0:t.service)??""});async function se(){const t=await W.get(`/api/deployments/${encodeURIComponent(u.id)}/runtime${H(u.vars)}`);t.ok&&(p.value=[...t.body.containers].sort((e,l)=>(e.service??e.name).localeCompare(l.service??l.name)))}const T=c(""),d=c(null),U=c(""),C=c(!1);O(()=>{var t;return[u.id,!!((t=u.detail)!=null&&t.compose)]},([t,e],l)=>{(!l||l[0]!==t)&&(p.value=[],f.value="",d.value=null),e&&p.value.length===0&&se()},{immediate:!0}),O([p,()=>X.query.container],([t,e])=>{const l=typeof e=="string"?e:"";!l||!t.length||f.value===l||t.some(m=>m.name===l)&&(f.value=l,B())},{immediate:!0});function Z(t){f.value!==t&&(f.value=t,te.replace({query:{...X.query,container:t||void 0}}),B())}async function B(){var m;if(!((m=u.detail)!=null&&m.compose))return;const t=u.id;C.value=!0,U.value="";const e=q.value,l=await W.get(`/api/deployments/${encodeURIComponent(t)}/logs${H(u.vars,{tail:S.value,...e?{service:e}:{},...R.value?{since:R.value}:{},...$.value?{timestamps:1}:{}})}`);if(C.value=!1,u.id===t&&!(l.ok&&(l.body.service??"")!==e)){if(!l.ok){d.value=null,U.value=pe(l,"load the logs");return}d.value={...l.body,ok:l.body.ok!==!1,text:l.body.text??""}}}const v=c(!1),h=c([]);let b=null;function F(){b==null||b.close(),b=null,v.value=!1}function G(){F(),h.value=[];const t=H(u.vars,{tail:S.value,...q.value?{service:q.value}:{},...$.value?{timestamps:1}:{}});b=new EventSource(W.url(`/api/deployments/${encodeURIComponent(u.id)}/logs/stream${t}`)),v.value=!0,b.onmessage=e=>{const l=JSON.parse(e.data);if(l.done){h.value.push(`— ${l.reason??"stream ended"} —`),F();return}l.line!==void 0&&(h.value.push(l.line),h.value.length>J&&h.value.splice(0,h.value.length-J))},b.onerror=()=>F()}function le(){v.value?F():G()}O([f,$,S],()=>{v.value&&G()}),de(F);const j=A(()=>{var m;const t=v.value||h.value.length?h.value.join(`
|
|
2
|
-
`):((m=d.value)==null?void 0:m.text)??"",e=T.value.trim().toLowerCase();if(!e)return t;const l=t.split(`
|
|
3
|
-
`).filter(k=>k.toLowerCase().includes(e));return l.length?l.join(`
|
|
4
|
-
`):`(no line matches "${T.value}")`}),z=c(!1),oe=/^(\S+)\s+\| /,ae=/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s/,ne=A(()=>{var k;const t=j.value;if(!t||t.startsWith("(no line matches"))return[];const e=new Map,l=((k=g.value)==null?void 0:k.name)??"",m=[];return t.split(`
|
|
5
|
-
`).forEach((_,o)=>{const V=oe.exec(_);if(!V){m.push({key:o,text:_});return}const L=V[1];if(l&&L!==l&&p.value.some(re=>re.name===L))return;let N=_.slice(V[0].length),E;const D=ae.exec(N);D&&(E=D[1],N=N.slice(D[0].length)),e.has(L)||e.set(L,e.size%6),m.push({key:o,gutter:L,text:N,tone:`svc-${e.get(L)}`,...E?{time:new Date(E).toTimeString().slice(0,8),timeTitle:E}:{}})}),m}),ie=t=>t.state==="running"&&t.health!=="unhealthy"?"s-ok":t.state==="exited"&&t.exitCode===0?"mute":"s-failed";return(t,e)=>{var l,m,k,_;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)),I(ee,{id:"tail","model-value":String(S.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=>S.value=Number(o))},null,8,["model-value"])]),s("div",xe,[e[7]||(e[7]=s("label",{for:"since"},"Since",-1)),I(ee,{id:"since",modelValue:R.value,"onUpdate:modelValue":e[1]||(e[1]=o=>R.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",Ce,[P(s("input",{"onUpdate:modelValue":e[2]||(e[2]=o=>$.value=o),type:"checkbox"},null,512),[[K,$.value]]),e[8]||(e[8]=i(" Timestamps ",-1))]),s("button",{class:M(v.value?"danger":""),disabled:!((l=w(u).detail)!=null&&l.compose),title:v.value?"Stop streaming":"Stream new lines as they arrive",onClick:le},r(v.value?"Stop following":"Follow"),11,_e),s("button",{class:"primary",disabled:C.value||v.value||!((m=w(u).detail)!=null&&m.compose),title:v.value?"Already streaming live.":void 0,onClick:B},[i(r(C.value?"Reading…":d.value?"Refresh":"Fetch")+" ",1),C.value?(a(),n("span",Se)):x("",!0)],8,Le)]),w(u).detail?w(u).detail.compose?(a(),n(y,{key:2},[U.value?(a(),n("div",Re,r(U.value),1)):d.value&&!d.value.ok?(a(),n("div",Te,[s("b",null,r(((k=w(u).detail)==null?void 0:k.orchestrator)==="swarm"?"docker service logs":"Compose")+" exited non-zero.",1),s("p",null,[(_=w(u).detail)!=null&&_.asleep?(a(),n(y,{key:0},[e[12]||(e[12]=i(" This deployment is ",-1)),e[13]||(e[13]=s("b",null,"asleep",-1)),e[14]||(e[14]=i(" — its containers are gone until something wakes it, and there is nothing to read logs from. ",-1))],64)):(a(),n(y,{key:1},[i(" 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. ")],64))])])):x("",!0),s("div",Ue,[s("aside",Ve,[s("button",{class:M(["clist-item",{on:f.value===""}]),onClick:e[3]||(e[3]=o=>Z(""))},[e[15]||(e[15]=s("span",{class:"clist-name"},"All services",-1)),s("span",Ne,r(p.value.length)+" container"+r(p.value.length===1?"":"s"),1)],2),(a(!0),n(y,null,ve(p.value,o=>(a(),n("button",{key:o.id,class:M(["clist-item",{on:f.value===o.name}]),onClick:V=>Z(o.name)},[s("span",Ie,[i(r(o.service??o.name)+" ",1),o.restartCount>0?(a(),n("span",Me,r(o.restartCount)+"×",1)):x("",!0)]),s("span",Ae,r(o.name),1),s("span",{class:M(["clist-state",ie(o)])},r(w(ge)(o.health??o.state)),3)],10,Ee))),128)),p.value.length?x("",!0):(a(),n("p",qe," No containers. Nothing has been brought up, or their profile is not selected. "))]),s("div",Be,[s("div",je,[s("div",ze,[e[16]||(e[16]=s("label",{for:"lf"},"Search in these lines",-1)),P(s("input",{id:"lf","onUpdate:modelValue":e[4]||(e[4]=o=>T.value=o),"data-search":"",type:"search",placeholder:"Substring",spellcheck:"false"},null,512),[[me,T.value]])]),s("label",De,[P(s("input",{"onUpdate:modelValue":e[5]||(e[5]=o=>z.value=o),type:"checkbox"},null,512),[[K,z.value]]),e[18]||(e[18]=i(" Raw output ",-1)),I(Y,{label:"when raw matters"},{default:Q(()=>[...e[17]||(e[17]=[i(" 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})])]),d.value||v.value||h.value.length?(a(),n(y,{key:0},[z.value?(a(),n("pre",Oe,r(j.value||"(no output)"),1)):(a(),ce(ye,{key:1,rows:ne.value,live:v.value,"copy-text":j.value,"empty-text":v.value?"Waiting for output…":"(no output)"},null,8,["rows","live","copy-text","empty-text"])),v.value?(a(),n("p",Pe," Following "+r(g.value?g.value.name:"the whole stack")+" — new lines appear as they are written, up to "+r(J.toLocaleString())+" kept on screen. ",1)):d.value?(a(),n("p",We,[i(r(d.value.lines??0)+" line"+r((d.value.lines??0)===1?"":"s")+" ",1),g.value?(a(),n(y,{key:0},[e[19]||(e[19]=i("from ",-1)),s("b",null,r(g.value.name),1)],64)):(a(),n(y,{key:1},[i("across the stack")],64)),d.value.since?(a(),n(y,{key:2},[i(", last "+r(d.value.since),1)],64)):x("",!0),i(", tail "+r(d.value.tail)+". Redacted on the host before sending: credentials inside URLs, ",1),e[21]||(e[21]=s("code",null,"NAME=value",-1)),e[22]||(e[22]=i(" pairs whose name reads as a secret, and this server's own token. ",-1)),I(Y,{label:"why this is not live"},{default:Q(()=>[...e[20]||(e[20]=[i(" Fetched on demand, never polled: an auto-refresh against a chatty stack would hammer the host that runs everyone else's previews. ",-1)])]),_:1})])):x("",!0)],64)):C.value?x("",!0):(a(),n("p",He,[e[23]||(e[23]=i(" Press ",-1)),e[24]||(e[24]=s("b",null,"Fetch",-1)),e[25]||(e[25]=i(" for the last lines, or ",-1)),e[26]||(e[26]=s("b",null,"Follow",-1)),i(" to stream them live"+r(g.value?` from ${g.value.name}`:"")+". ",1)]))])])],64)):(a(),n("p",Fe,[...e[11]||(e[11]=[i(" This spec has no ",-1),s("code",null,"compose:",-1),i(" 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-CZ0fNgsO.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as ie,J as le,c as l,a as t,f as n,e as f,w as m,_ as S,z as D,g as c,F as k,t as r,r as N,i as F,v as I,j as u,k as U,p as y,C as g,o as i,D as oe,u as j,E as M,O as x}from"./index-T3JGe9ku.js";import{s as W,c as ne}from"./useFormat-DIlTY5Dl.js";import{_ as P}from"./ActionButton.vue_vue_type_script_setup_true_lang-C6PZurL0.js";import{_ as J}from"./ErrorNote.vue_vue_type_script_setup_true_lang-CUZs06O0.js";import{_ as re}from"./SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js";import{_ as de}from"./RelativeTime.vue_vue_type_script_setup_true_lang-D4NO-r5D.js";import{_ as ue}from"./SelectMenu.vue_vue_type_script_setup_true_lang-DKYlHroe.js";import{_ as Q}from"./RefreshButton.vue_vue_type_script_setup_true_lang-ZUc7pgOx.js";import"./Teleport-CKfsIrp8.js";const ve={class:"page-head"},ce={class:"sub"},pe={key:1,class:"panel"},fe={key:0,class:"banner ok"},ye={class:"code"},he={class:"row"},me={class:"panel"},ke={key:1,class:"cards"},ge={class:"stagger"},be={class:"name","data-label":"name"},_e={class:"mute",style:{"font-size":"var(--t-sm)"}},we={"data-label":"events"},$e={key:0,class:"badge"},xe={key:1},Ce={"data-label":"last delivery"},Te={key:0,class:"mute"},Ee={key:2,class:"mute",style:{"font-size":"var(--t-sm)"}},Re={"data-label":""},Se={class:"row",style:{"justify-content":"flex-end"}},De={key:0,class:"badge off"},Ne=["onClick"],Ve=["onClick"],Ae=["onClick"],ze={colspan:"4"},Ue={class:"row",style:{"margin-bottom":"var(--s2)"}},je={key:0,class:"badge busy"},Pe={key:0,class:"kvlist"},qe={class:"k"},Be={class:"v"},He={class:"mute",style:{"font-size":"var(--t-sm)"}},Le={key:0,class:"s-failed",style:{"font-size":"var(--t-sm)"}},Oe={key:1,class:"mute"},Xe={key:2,class:"mute"},Fe={class:"panel"},Ie={key:0,class:"field",style:{"max-width":"320px"}},Me={class:"field",style:{"max-width":"380px"}},We=["for"],Je=["id","onUpdate:modelValue","placeholder"],Qe={class:"section",style:{margin:"var(--s5) 0 var(--s3)"}},Ye={class:"row"},Ge={class:"check"},Ke=["checked"],Ze={key:1,class:"check-grid"},et=["checked","onChange"],tt={class:"row",style:{"margin-top":"var(--s4)"}},st={key:3,class:"hint"},at={key:4,class:"hint"},yt=ie({__name:"NotifiersView",setup(it){const V=u([]),b=u(null),q=u(!1),h=u(""),B=u(!1),w=u(null),o=u({name:"",type:"webhook",events:[],config:{}}),$=u(!1),C=u(""),p=u(null),T=u([]),E=u(0),A=u(0),Y=U(()=>{var s;return(((s=b.value)==null?void 0:s.types)??[]).map(e=>({value:e.kind,label:e.label}))}),z=U(()=>{var s;return((s=b.value)==null?void 0:s.types.find(e=>e.kind===o.value.type))??null});le(()=>o.value.type,()=>{o.value.config={}});const G=U(()=>{var s;return!$.value&&!!o.value.name.trim()&&o.value.events.length>0&&(((s=z.value)==null?void 0:s.fields)??[]).every(e=>{var v;return!e.required||!!((v=o.value.config[e.key])!=null&&v.trim())})});async function _(){const[s,e]=await Promise.all([y.get("/api/notifiers"),y.get("/api/notifiers/meta")]);if(q.value=!0,s.status===404||e.status===404){B.value=!0;return}if(!s.ok){h.value=g(s,"load the notifiers");return}h.value="",V.value=s.body.notifiers??[],e.ok&&(b.value=e.body)}_();function H(s){const e=o.value.events.indexOf(s);e===-1?o.value.events.push(s):o.value.events.splice(e,1)}async function K(){$.value=!0,C.value="";const s=await y.post("/api/notifiers",{name:o.value.name.trim(),type:o.value.type,events:o.value.events,config:o.value.config});if($.value=!1,!s.ok){C.value=g(s,"register this notifier");return}w.value=s.body.secret?{name:s.body.notifier.name,secret:s.body.secret}:null,o.value={name:"",type:o.value.type,events:[],config:{}},_()}async function Z(s,e){const v=await y.patch(`/api/notifiers/${s.id}`,{enabled:e});v.ok||(h.value=g(v,"update this notifier")),_()}async function ee(s){const e=await y.del(`/api/notifiers/${s.id}`);if(!e.ok){h.value=g(e,"delete this notifier");return}p.value===s.id&&(p.value=null),x("ok",`Removed ${s.name}.`),_()}async function te(s){const e=await y.post(`/api/notifiers/${s.id}/test`,{});if(!e.ok){h.value=g(e,"send a test delivery");return}const v=e.body.result;v.ok?x("ok",`${s.name} accepted the test delivery.`):x("error",`${s.name}: ${v.error??`HTTP ${v.status}`}`),_()}async function se(s){if(p.value===s.id){p.value=null;return}const e=await y.get(`/api/notifiers/${s.id}/deliveries`);if(!e.ok){h.value=g(e,"load the delivery log");return}T.value=e.body.deliveries??[],E.value=e.body.queued??0,p.value=s.id}async function L(){const s=p.value;if(s===null)return;const e=await y.get(`/api/notifiers/${s}/deliveries`);e.ok&&(T.value=e.body.deliveries??[],E.value=e.body.queued??0)}async function ae(s){if(p.value===null)return;A.value=s.id;const e=await y.post(`/api/notifiers/${p.value}/deliveries/${s.id}/redeliver`);if(A.value=0,!e.ok){x("error",g(e,"redeliver this event"));return}x("ok",e.body.note??"Queued for redelivery."),await L()}return(s,e)=>{var v,O,X;return i(),l("div",null,[t("div",ve,[t("div",null,[e[6]||(e[6]=t("h1",null,"Notifiers",-1)),t("div",ce,[e[5]||(e[5]=n(" Where events on this host get sent ",-1)),f(S,{label:"what a notifier is"},{default:m(()=>[...e[4]||(e[4]=[n(" A registration that receives events — a teardown that leaked, a deployment created, a job that failed. Webhooks today; the server decides what other kinds exist, and this page renders whatever it offers. ",-1)])]),_:1})])]),e[7]||(e[7]=t("span",{class:"grow"},null,-1)),f(Q,{run:_})]),h.value?(i(),D(J,{key:0,text:h.value,title:"Could not load the notifiers."},null,8,["text"])):c("",!0),B.value?(i(),l("section",pe,[...e[8]||(e[8]=[t("div",{class:"banner plain"},[t("b",null,"This server has no notifiers."),t("p",null,"It is an older build of pstack. Upgrade the host to register webhooks.")],-1)])])):(i(),l(k,{key:2},[w.value?(i(),l("div",fe,[t("b",null,"Signing secret for “"+r(w.value.name)+"” — copy it now.",1),t("p",null,[e[10]||(e[10]=n(" This is the only time it is shown — the server keeps no way to show it again. Your receiver uses it to check that a delivery really came from here. ",-1)),f(S,{label:"how a receiver checks it"},{default:m(()=>[...e[9]||(e[9]=[n(" Each delivery carries a signature header computed from this secret and the exact body sent. Recompute it on your side and compare; a mismatch means the delivery was not sent by this host, or was altered on the way. ",-1)])]),_:1})]),t("pre",ye,r(w.value.secret),1),t("div",he,[t("button",{class:"btn",onClick:e[0]||(e[0]=a=>w.value=null)},"I have stored it")])])):c("",!0),t("section",me,[e[17]||(e[17]=t("div",{class:"phead"},[t("h2",{class:"section"},"Registered")],-1)),q.value?V.value.length?(i(),l("table",ke,[e[16]||(e[16]=t("thead",null,[t("tr",null,[t("th",null,"Name"),t("th",null,"Events"),t("th",null,"Last delivery"),t("th")])],-1)),t("tbody",ge,[(i(!0),l(k,null,N(V.value,(a,R)=>(i(),l(k,{key:a.id},[t("tr",{style:oe({"--i":R})},[t("td",be,[n(r(a.name)+" ",1),t("div",_e,r(j(W)(a.type))+" · "+r(String(a.config.url??a.config.webhookUrl??"")),1)]),t("td",we,[a.events.includes("*")?(i(),l("span",$e,"all events")):(i(),l("span",xe,r(a.events.join(", ")),1))]),t("td",Ce,[a.lastStatus?(i(),l("span",{key:1,class:M(a.lastStatus==="ok"?"s-ok":"s-failed")},r(a.lastStatus),3)):(i(),l("span",Te,"never")),a.lastAt?(i(),l("div",Ee,[f(de,{at:a.lastAt},null,8,["at"])])):c("",!0)]),t("td",Re,[t("div",Se,[a.enabled?c("",!0):(i(),l("span",De,"disabled")),t("button",{class:"ghost sm",onClick:d=>se(a)},r(p.value===a.id?"Hide":"Deliveries"),9,Ne),t("button",{class:"ghost sm",onClick:d=>te(a)},"Test",8,Ve),t("button",{class:"ghost sm",onClick:d=>Z(a,!a.enabled)},r(a.enabled?"Disable":"Enable"),9,Ae),f(P,{variant:"danger",onClick:d=>ee(a)},{default:m(()=>[...e[11]||(e[11]=[n("Remove",-1)])]),_:1},8,["onClick"])])])],4),p.value===a.id?(i(),l("tr",{key:`d${a.id}`},[t("td",ze,[t("div",Ue,[E.value>0?(i(),l("span",je,[e[12]||(e[12]=t("span",{class:"dot pulse"},null,-1)),n(r(E.value)+" queued ",1)])):c("",!0),e[13]||(e[13]=t("span",{class:"mute",style:{"font-size":"var(--t-xs)"}}," Events wait their turn per notifier — one delivery at a time, so a slow receiver cannot starve the others. ",-1)),e[14]||(e[14]=t("span",{class:"grow"},null,-1)),f(Q,{run:L})]),T.value.length?(i(),l("ul",Pe,[(i(!0),l(k,null,N(T.value,d=>(i(),l("li",{key:d.id},[t("span",qe,[t("span",{class:M(d.status==="ok"?"s-ok":"s-failed")},r(j(W)(d.status)),3)]),t("span",Be,[n(r(d.event)+" ",1),t("span",He,[n(" · "+r(j(ne)(d.createdAt))+" · "+r(d.attempts)+" attempt(s)",1),d.responseCode?(i(),l(k,{key:0},[n(" · HTTP "+r(d.responseCode),1)],64)):c("",!0)]),d.error?(i(),l("div",Le,r(d.error),1)):c("",!0)]),d.replayable?(i(),D(P,{key:0,class:"sm",variant:"ghost",pending:A.value===d.id,confirm:"Send it again?",title:"Sends this exact event again, with its original id so a receiver that already handled it can dedupe.",onRun:lt=>ae(d)},{default:m(()=>[...e[15]||(e[15]=[n(" Redeliver ",-1)])]),_:1},8,["pending","onRun"])):c("",!0)]))),128))])):(i(),l("p",Oe,"Nothing delivered yet."))])])):c("",!0)],64))),128))])])):(i(),l("p",Xe," Nothing registered. Events still happen — nobody is being told about them. ")):(i(),D(re,{key:0,rows:2}))]),t("section",Fe,[e[25]||(e[25]=t("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Add a notifier",-1)),b.value&&b.value.types.length>1?(i(),l("div",Ie,[e[18]||(e[18]=t("label",{for:"ty"},"Type",-1)),f(ue,{modelValue:o.value.type,"onUpdate:modelValue":e[1]||(e[1]=a=>o.value.type=a),id:"ty",label:"Notifier type",options:Y.value},null,8,["modelValue","options"])])):c("",!0),t("div",Me,[e[19]||(e[19]=t("label",{for:"nn"},"Name",-1)),F(t("input",{id:"nn","onUpdate:modelValue":e[2]||(e[2]=a=>o.value.name=a),type:"text",placeholder:"ops-slack-bridge",spellcheck:"false"},null,512),[[I,o.value.name,void 0,{trim:!0}]])]),(i(!0),l(k,null,N(((v=z.value)==null?void 0:v.fields)??[],a=>(i(),l("div",{key:a.key,class:"field",style:{"max-width":"380px"}},[t("label",{for:`cfg-${a.key}`},r(a.label),9,We),F(t("input",{id:`cfg-${a.key}`,"onUpdate:modelValue":R=>o.value.config[a.key]=R,type:"text",placeholder:a.placeholder,spellcheck:"false"},null,8,Je),[[I,o.value.config[a.key],void 0,{trim:!0}]])]))),128)),t("h2",Qe,[e[21]||(e[21]=n(" Events ",-1)),f(S,{label:"which events to pick"},{default:m(()=>[...e[20]||(e[20]=[t("b",null,"All events",-1),n(" also covers events added in later versions — a specific list does not, and the failure mode of that is silence nobody can explain. ",-1),t("code",null,"job.leaked",-1),n(" is the one worth alerting on: a teardown ran and something survived it. ",-1)])]),_:1})]),t("div",Ye,[t("label",Ge,[t("input",{type:"checkbox",checked:o.value.events.includes("*"),onChange:e[3]||(e[3]=a=>H("*"))},null,40,Ke),e[22]||(e[22]=n(" All events ",-1))])]),o.value.events.includes("*")?c("",!0):(i(),l("div",Ze,[(i(!0),l(k,null,N(((O=b.value)==null?void 0:O.events)??[],a=>(i(),l("label",{key:a,class:"check"},[t("input",{type:"checkbox",checked:o.value.events.includes(a),onChange:R=>H(a)},null,40,et),n(" "+r(a),1)]))),128))])),C.value?(i(),D(J,{key:2,text:C.value,title:"Could not register this notifier."},null,8,["text"])):c("",!0),t("div",tt,[f(P,{variant:"primary",pending:$.value,disabled:!G.value,onClick:K},{default:m(()=>[n(r($.value?"Registering…":"Register"),1)]),_:1},8,["pending","disabled"])]),((X=z.value)==null?void 0:X.signs)!==!1?(i(),l("p",st,[e[24]||(e[24]=n(" Deliveries are signed with a secret shown once at registration, retried twice on failure, and logged here. ",-1)),f(S,{label:"how a receiver verifies a delivery"},{default:m(()=>[...e[23]||(e[23]=[n(" Recompute ",-1),t("code",null,"HMAC-SHA256(secret, `${timestamp}.${rawBody}`)",-1),n(" and compare against ",-1),t("code",null,"X-Pstack-Signature",-1),n(", using the ",-1),t("em",null,"raw",-1),n(" body — re-serialising it changes the bytes. Reject anything whose ",-1),t("code",null,"X-Pstack-Timestamp",-1),n(" is more than five minutes old, and dedupe on ",-1),t("code",null,"X-Pstack-Delivery",-1),n(": delivery is at-least-once, and that id is stable across retries. ",-1)])]),_:1})])):(i(),l("p",at," The webhook URL is the credential — it is stored write-only and scrubbed from logs, and no signing secret is involved. Deliveries are retried twice on failure and logged here. "))])],64))])}}});export{yt as default};
|
|
2
|
-
//# sourceMappingURL=NotifiersView-Df_CM6Bj.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as o,r as w,i as x}from"./useDeployment-CV0kcdoc.js";import{s as T,c as v}from"./useFormat-DIlTY5Dl.js";import{_ as C}from"./SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js";import{d as A,c as n,a as s,u as t,z as y,w as p,g as r,f as a,e as k,_ as c,t as i,E as S,F as u,r as m,k as z,l as R,o as l}from"./index-T3JGe9ku.js";import"./useVars-VN9gYdR6.js";const q={class:"panel"},N={class:"phead"},$={key:1,class:"mute"},B={key:1,class:"mute"},U={key:2,class:"kvlist"},L={class:"k"},V={class:"v mono"},j={class:"v"},E={class:"mute",style:{"margin-left":"8px","font-size":"var(--t-sm)"}},I={key:0},O={class:"v"},P={class:"v mono"},D={key:1},F={class:"v"},H={key:0,class:"mute"},K={key:2},W={class:"k"},G={class:"v"},J={class:"mono"},M={class:"mute",style:{"font-size":"var(--t-sm)"}},Q={key:3},X={class:"k"},Y={class:"v"},Z={key:4},ee={class:"k"},se={class:"v"},te={key:0},oe={key:1,class:"mute"},le={key:2},ne={key:5},ae={class:"v"},ie={class:"mute"},de={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},re={key:1,class:"mute",style:{"font-size":"var(--t-sm)"}},pe={key:6},ue={class:"v"},me={key:0,class:"mute"},ye={class:"v"},ke={class:"v"},ce={class:"v"},he={class:"v"},Te=A({__name:"OverviewTab",setup(ve){const g=z(()=>x.value?"a host singleton previews borrow — no axes, and down is guarded":"one tenant, created and destroyed constantly, expected to leave nothing behind");return(fe,e)=>{var f,_,b;const h=R("RouterLink");return l(),n("section",q,[s("div",N,[e[2]||(e[2]=s("h2",{class:"section"},"Resolved spec",-1)),e[3]||(e[3]=s("span",{class:"grow"},null,-1)),t(o).detail?(l(),y(h,{key:0,to:`/submit?from=${encodeURIComponent(t(o).id)}`},{default:p(()=>[...e[0]||(e[0]=[a(" Duplicate ",-1)])]),_:1},8,["to"])):r("",!0),t(o).detail?(l(),n("span",$,"·")):r("",!0),t(o).detail?(l(),y(h,{key:2,to:`/submit/${encodeURIComponent(t(o).id)}`},{default:p(()=>[...e[1]||(e[1]=[a(" Edit spec & compose → ",-1)])]),_:1},8,["to"])):r("",!0)]),!t(o).detail&&!t(o).error?(l(),y(C,{key:0,rows:5})):t(o).detail?(l(),n("ul",U,[s("li",null,[s("span",L,[e[5]||(e[5]=a(" Stack ",-1)),k(c,{label:"the difference between id and stack"},{default:p(()=>[...e[4]||(e[4]=[a(" The ",-1),s("b",null,"id",-1),a(" is what you named this deployment. The ",-1),s("b",null,"stack",-1),a(" is what its spec resolved to once the variables were filled in — the compose project name, the hostname label, and ",-1),s("code",null,"$STACK",-1),a(" inside every hook. ",-1)])]),_:1})]),s("span",V,i(t(o).detail.stack||"—"),1)]),s("li",null,[e[6]||(e[6]=s("span",{class:"k"},"Kind",-1)),s("span",j,[s("span",{class:S(["badge",t(o).detail.kind])},i(t(T)(t(o).detail.kind)),3),s("span",E,i(g.value),1)])]),(f=t(w))!=null&&f.specName?(l(),n("li",I,[e[8]||(e[8]=s("span",{class:"k"},"Spec",-1)),s("span",O,[s("b",null,i(t(w).specName),1),e[7]||(e[7]=s("span",{class:"mute",style:{"font-size":"var(--t-sm)"}}," — stored, and shared with any other deployment using it ",-1))])])):r("",!0),s("li",null,[e[9]||(e[9]=s("span",{class:"k"},"Compose file",-1)),s("span",P,i(t(o).detail.compose?t(o).detail.compose.file:"none"),1)]),t(o).detail.compose?(l(),n("li",D,[e[10]||(e[10]=s("span",{class:"k"},"Profiles",-1)),s("span",F,[(l(!0),n(u,null,m(t(o).detail.compose.profiles,d=>(l(),n("span",{key:d,class:"badge",style:{"margin-right":"4px"}},i(d),1))),128)),t(o).detail.compose.profiles.length?r("",!0):(l(),n("span",H," none — a bare up starts every service "))])])):r("",!0),(b=(_=t(o).detail.compose)==null?void 0:_.subdomains)!=null&&b.length?(l(),n("li",K,[s("span",W,[e[15]||(e[15]=a(" Subdomains ",-1)),k(c,{label:"how wildcard subdomains work"},{default:p(()=>[e[14]||(e[14]=a(" Anything one label under these hosts reaches the same service the bare host does. An exact hostname you route yourself always wins over the wildcard. ",-1)),t(o).detail.compose.subdomains.some(d=>d.depth==="any")?(l(),n(u,{key:0},[e[11]||(e[11]=a(" A route marked ",-1)),e[12]||(e[12]=s("b",null,"any depth",-1)),e[13]||(e[13]=a(" works over HTTP only — no TLS certificate can cover more than one label. ",-1))],64)):r("",!0)]),_:1})]),s("span",G,[(l(!0),n(u,null,m(t(o).detail.compose.subdomains,d=>(l(),n("div",{key:d.profile},[s("span",J,"*."+i(d.host),1),s("span",M,[a(" → "+i(d.profile),1),d.depth==="any"?(l(),n(u,{key:0},[a(" · any depth, HTTP only")],64)):r("",!0)])]))),128))])])):r("",!0),t(o).detail.compose?(l(),n("li",Q,[s("span",X,[e[17]||(e[17]=a(" Orchestrator ",-1)),k(c,{label:"compose or swarm"},{default:p(()=>[...e[16]||(e[16]=[s("b",null,"compose",-1),a(" runs the project with docker compose on this host. ",-1),s("b",null,"swarm",-1),a(" deploys it as a swarm stack — the file you submitted is converted on every deploy (profiles resolved, ",-1),s("code",null,"restart",-1),a(" and limits moved under ",-1),s("code",null,"deploy",-1),a(", Traefik labels onto the service), and its tasks may land on any node of the swarm. ",-1)])]),_:1})]),s("span",Y,i(t(o).detail.orchestrator??"compose"),1)])):r("",!0),t(o).detail.sleep?(l(),n("li",Z,[s("span",ee,[e[19]||(e[19]=a(" Sleep policy ",-1)),k(c,{label:"what the scheduler does"},{default:p(()=>[...e[18]||(e[18]=[a(" The scheduler takes the compose project down — volumes and axes stay — when the policy says so, and the next request to any of its hostnames brings it back. ",-1),s("b",null,"idle",-1),a(" counts requests through Traefik; ",-1),s("b",null,"after",-1),a(" counts from the last deploy. ",-1)])]),_:1})]),s("span",se,[t(o).detail.sleep.idle?(l(),n("span",te,[e[20]||(e[20]=a("after ",-1)),s("b",null,i(t(o).detail.sleep.idle),1),e[21]||(e[21]=a(" without a request",-1))])):r("",!0),t(o).detail.sleep.idle&&t(o).detail.sleep.after?(l(),n("span",oe," · ")):r("",!0),t(o).detail.sleep.after?(l(),n("span",le,[s("b",null,i(t(o).detail.sleep.after),1),e[22]||(e[22]=a(" after the last deploy",-1))])):r("",!0)])])):r("",!0),t(o).detail.asleep?(l(),n("li",ne,[e[24]||(e[24]=s("span",{class:"k"},"Asleep",-1)),s("span",ae,[a(" since "+i(t(v)(t(o).detail.asleep.since))+" ",1),s("span",ie,"("+i(t(o).detail.asleep.reason)+")",1),t(o).detail.asleep.hosts.length||t(o).detail.asleep.rules.length?(l(),n("div",de,[e[23]||(e[23]=a(" wakes on a request to ",-1)),(l(!0),n(u,null,m(t(o).detail.asleep.hosts,d=>(l(),n("span",{key:d,class:"mono",style:{"margin-right":"8px"}},i(d),1))),128)),(l(!0),n(u,null,m(t(o).detail.asleep.rules,d=>(l(),n("span",{key:d,class:"mono",style:{"margin-right":"8px"}},"/"+i(d)+"/",1))),128))])):(l(),n("div",re," no hostnames were found on its containers — only Wake brings it back "))])])):r("",!0),t(o).detail.compose?(l(),n("li",pe,[e[25]||(e[25]=s("span",{class:"k"},"Overlays",-1)),s("span",ue,[(l(!0),n(u,null,m(t(o).detail.compose.overlays,d=>(l(),n("span",{key:d,class:"mono",style:{"margin-right":"8px"}},i(d),1))),128)),t(o).detail.compose.overlays.length?r("",!0):(l(),n("span",me,"none"))])])):r("",!0),s("li",null,[e[27]||(e[27]=s("span",{class:"k"},"Axes",-1)),s("span",ye,[a(i(t(o).detail.axes.length)+" ",1),t(o).detail.axes.length?(l(),y(h,{key:0,to:`/deployments/${encodeURIComponent(t(o).id)}/axes`},{default:p(()=>[...e[26]||(e[26]=[a(" view → ",-1)])]),_:1},8,["to"])):r("",!0)])]),s("li",null,[e[28]||(e[28]=s("span",{class:"k"},"Requires",-1)),s("span",ke,i(t(o).detail.requires.length),1)]),s("li",null,[e[29]||(e[29]=s("span",{class:"k"},"Created",-1)),s("span",ce,i(t(v)(t(o).detail.createdAt)),1)]),s("li",null,[e[30]||(e[30]=s("span",{class:"k"},"Updated",-1)),s("span",he,i(t(v)(t(o).detail.updatedAt)),1)])])):(l(),n("p",B,"Unavailable until the spec resolves."))])}}});export{Te as default};
|
|
2
|
-
//# sourceMappingURL=OverviewTab-BSeic8dR.js.map
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{d as ee,J as B,K as se,L as E,M as te,c as n,a as s,t as i,f as C,g as d,u as m,E as M,F as O,r as le,e as W,i as j,I as H,k as U,j as v,p as T,o,n as ne}from"./index-T3JGe9ku.js";import{c as V,s as P}from"./useFormat-DIlTY5Dl.js";import{_ as oe}from"./LogViewer.vue_vue_type_script_setup_true_lang-Sg8LzIyX.js";import{_ as ae}from"./SelectMenu.vue_vue_type_script_setup_true_lang-DKYlHroe.js";import{_ as ie}from"./_plugin-vue_export-helper-DlAUqK2U.js";import"./Teleport-CKfsIrp8.js";const re={class:"public-page"},ue={class:"page-head"},de={style:{"font-size":"var(--t-xl)"}},ve={class:"sub"},pe={key:0},ce={key:1,class:"mute"},me={key:0,class:"banner failed"},fe={key:0,class:"panel"},ke={key:0,class:"kvlist"},he={class:"v"},ye={class:"v"},ge={class:"v"},be={class:"v"},we={key:0},_e={class:"v"},xe={class:"mute"},Te={key:1,class:"mute"},Se={key:2,class:"banner warn",style:{"margin-top":"var(--s3)"}},$e={key:3,class:"cards",style:{"margin-top":"var(--s3)"}},Ce={key:0},Ue={"data-label":"service"},Ve={"data-label":"container",class:"mono"},Le={"data-label":"state"},Ne={"data-label":"health"},Ae={key:0,"data-label":"node"},Ie={key:4,class:"mute",style:{"margin-top":"var(--s3)"}},Pe={key:1,class:"panel"},Re={class:"phead"},ze={class:"row",style:{"flex-wrap":"wrap",gap:"var(--s3)"}},De={key:0,class:"field inline"},Fe={class:"check"},Be={class:"check"},Ee={key:0,class:"hint"},J=5e3,Me=ee({__name:"PublicLogsView",props:{id:{}},setup(R){const b=R,K=ne(),z=U(()=>String(K.query.token??"")),w=v([]),L=v(null),h=v(""),u=v(null),r=v(null),f=v(""),_=v(!1),y=v(!0),p=v([]),k=v("");let c=null;const D=U(()=>{var e;const t=new Set;for(const a of((e=r.value)==null?void 0:e.containers)??[])a.service&&t.add(a.service);return[...t]});function F(t,e){return t===401?"This link has expired or is not valid.":t===403?`This link does not allow ${e}.`:t===0?"The control plane is unreachable.":`Could not load ${e} (HTTP ${t}).`}function x(){c==null||c.close(),c=null}function X(){x(),p.value=[],k.value="";const t=new URLSearchParams({tail:"500"});f.value&&t.set("service",f.value),_.value&&t.set("timestamps","1"),c=new EventSource(T.url(`/api/deployments/${encodeURIComponent(b.id)}/logs/stream?${t}`)),c.onmessage=e=>{const a=JSON.parse(e.data);if(a.done){k.value=a.reason??"stream ended",x();return}a.line!==void 0&&(p.value.push(a.line),p.value.length>J&&p.value.splice(0,p.value.length-J))},c.onerror=()=>{k.value="the stream disconnected — reload to reconnect, or the link has expired",x()}}async function q(){x();const t=new URLSearchParams({tail:"500"});f.value&&t.set("service",f.value),_.value&&t.set("timestamps","1");const e=await T.get(`/api/deployments/${encodeURIComponent(b.id)}/logs?${t}`);if(!e.ok){k.value=F(e.status,"logs");return}p.value=(e.body.text??"").split(`
|
|
2
|
-
`).filter(Boolean),k.value=e.body.ok?"":"the log command exited non-zero — the stack may be asleep or not deployed"}function N(){y.value?X():q()}const Z=/^(\S+)\s+\| /,G=/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s/,Q=U(()=>{const t=new Map;return p.value.map((e,a)=>{const l=Z.exec(e);if(!l)return{key:a,text:e};const g=l[1];let S=e.slice(l[0].length),$;const I=G.exec(S);return I&&($=I[1],S=S.slice(I[0].length)),t.has(g)||t.set(g,t.size%6),{key:a,gutter:g,text:S,tone:`svc-${t.get(g)}`,...$?{time:new Date($).toTimeString().slice(0,8),timeTitle:$}:{}}})});async function Y(){if(!z.value){h.value="This link is incomplete — it carries no token.";return}const t=await T.get("/api/auth/me");if(!t.ok){h.value=F(t.status,"this page");return}if(!t.body.share){h.value="This is not a share link.";return}if(t.body.share.deployment!==b.id){h.value="This link belongs to a different deployment.";return}if(w.value=t.body.share.views,L.value=t.body.share.expiresAt,w.value.includes("details")){const[e,a]=await Promise.all([T.get(`/api/deployments/${encodeURIComponent(b.id)}`),T.get(`/api/deployments/${encodeURIComponent(b.id)}/runtime`)]);e.ok&&(u.value=e.body),a.ok&&(r.value=a.body)}w.value.includes("logs")&&N()}const A=U(()=>{var t,e;return(t=u.value)!=null&&t.asleep?{text:"asleep",cls:"asleep"}:r.value&&r.value.reachable===!1?{text:"unknown",cls:"unknown"}:(e=r.value)!=null&&e.containers.some(a=>a.state==="running")?{text:"running",cls:"ok"}:r.value?{text:"not running",cls:""}:null});return B([f,_],N),B(y,N),se(()=>{E(z.value),Y()}),te(()=>{x(),E("")}),(t,e)=>{var a;return o(),n("div",re,[s("div",ue,[s("div",null,[e[4]||(e[4]=s("div",{class:"mute",style:{"font-size":"var(--t-sm)"}},"shared deployment · read-only",-1)),s("h1",de,i(R.id),1),s("div",ve,[u.value?(o(),n("span",pe,[e[3]||(e[3]=C("stack ",-1)),s("b",null,i(u.value.stack),1)])):d("",!0),L.value?(o(),n("span",ce," · link expires "+i(m(V)(L.value)),1)):d("",!0)])]),e[5]||(e[5]=s("span",{class:"grow"},null,-1)),A.value?(o(),n("span",{key:0,class:M(["badge",A.value.cls])},i(A.value.text),3)):d("",!0)]),h.value?(o(),n("div",me,[s("b",null,i(h.value),1),e[6]||(e[6]=s("p",null,"Ask whoever shared it for a new link.",-1))])):(o(),n(O,{key:1},[w.value.includes("details")?(o(),n("section",fe,[e[17]||(e[17]=s("div",{class:"phead"},[s("h2",{class:"section"},"Details")],-1)),u.value?(o(),n("ul",ke,[s("li",null,[e[7]||(e[7]=s("span",{class:"k"},"Kind",-1)),s("span",he,i(m(P)(u.value.kind)),1)]),s("li",null,[e[8]||(e[8]=s("span",{class:"k"},"Orchestrator",-1)),s("span",ye,i(u.value.orchestrator??"compose"),1)]),s("li",null,[e[9]||(e[9]=s("span",{class:"k"},"Created",-1)),s("span",ge,i(m(V)(u.value.createdAt)),1)]),s("li",null,[e[10]||(e[10]=s("span",{class:"k"},"Updated",-1)),s("span",be,i(m(V)(u.value.updatedAt)),1)]),u.value.asleep?(o(),n("li",we,[e[11]||(e[11]=s("span",{class:"k"},"Asleep",-1)),s("span",_e,[C("since "+i(m(V)(u.value.asleep.since))+" ",1),s("span",xe,"("+i(u.value.asleep.reason)+")",1)])])):d("",!0)])):(o(),n("p",Te,"Details could not be loaded.")),r.value&&r.value.reachable===!1?(o(),n("div",Se,[...e[12]||(e[12]=[s("b",null,"Docker did not answer.",-1),s("p",null,"What is running could not be listed — not the same as nothing running.",-1)])])):(a=r.value)!=null&&a.containers.length?(o(),n("table",$e,[s("thead",null,[s("tr",null,[e[13]||(e[13]=s("th",null,"Service",-1)),e[14]||(e[14]=s("th",null,"Container",-1)),e[15]||(e[15]=s("th",null,"State",-1)),e[16]||(e[16]=s("th",null,"Health",-1)),r.value.containers.some(l=>l.node)?(o(),n("th",Ce,"Node")):d("",!0)])]),s("tbody",null,[(o(!0),n(O,null,le(r.value.containers,l=>(o(),n("tr",{key:l.id},[s("td",Ue,i(l.service??"—"),1),s("td",Ve,i(l.name),1),s("td",Le,[s("span",{class:M(l.state==="running"?"s-ok":"s-failed")},i(m(P)(l.state)),3)]),s("td",Ne,i(l.health?m(P)(l.health):"—"),1),r.value.containers.some(g=>g.node)?(o(),n("td",Ae,i(l.node??"—"),1)):d("",!0)]))),128))])])):r.value?(o(),n("p",Ie,"Nothing is running.")):d("",!0)])):d("",!0),w.value.includes("logs")?(o(),n("section",Pe,[s("div",Re,[e[21]||(e[21]=s("h2",{class:"section"},"Logs",-1)),e[22]||(e[22]=s("span",{class:"grow"},null,-1)),s("div",ze,[D.value.length?(o(),n("div",De,[e[18]||(e[18]=s("label",{for:"pub-service"},"Service",-1)),W(ae,{id:"pub-service",modelValue:f.value,"onUpdate:modelValue":e[0]||(e[0]=l=>f.value=l),label:"Which service",options:[{value:"",label:"All services"},...D.value.map(l=>({value:l,label:l}))]},null,8,["modelValue","options"])])):d("",!0),s("label",Fe,[j(s("input",{"onUpdate:modelValue":e[1]||(e[1]=l=>_.value=l),type:"checkbox"},null,512),[[H,_.value]]),e[19]||(e[19]=C(" Timestamps",-1))]),s("label",Be,[j(s("input",{"onUpdate:modelValue":e[2]||(e[2]=l=>y.value=l),type:"checkbox"},null,512),[[H,y.value]]),e[20]||(e[20]=C(" Follow",-1))])])]),W(oe,{rows:Q.value,live:y.value&&!!m(c),"copy-text":p.value.join(`
|
|
3
|
-
`),"empty-text":y.value?"Waiting for output…":"(no output)"},null,8,["rows","live","copy-text","empty-text"]),k.value?(o(),n("p",Ee,i(k.value),1)):d("",!0)])):d("",!0)],64)),e[23]||(e[23]=s("p",{class:"mute",style:{"font-size":"var(--t-sm)","margin-top":"var(--s4)"}},"pstack · this page can only read.",-1))])}}}),Xe=ie(Me,[["__scopeId","data-v-2b720b48"]]);export{Xe as default};
|
|
4
|
-
//# sourceMappingURL=PublicLogsView-BDyvxkgj.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{d as E,c as i,a as t,f as s,e as u,w as g,_ as N,z as x,g as d,t as p,F as H,r as O,i as _,v as C,u as U,V as S,j as a,k as P,p as $,C as V,o as n,O as A}from"./index-T3JGe9ku.js";import{_ as F}from"./ActionButton.vue_vue_type_script_setup_true_lang-C6PZurL0.js";import{_ as T}from"./ErrorNote.vue_vue_type_script_setup_true_lang-CUZs06O0.js";import{_ as z}from"./SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js";import{_ as L}from"./HelpModal.vue_vue_type_script_setup_true_lang-ErbSSi6D.js";import{_ as M}from"./RefreshButton.vue_vue_type_script_setup_true_lang-ZUc7pgOx.js";import"./Teleport-CKfsIrp8.js";const W={class:"page-head"},q={class:"sub"},G={key:1,class:"banner failed"},J={key:2,class:"banner warn"},K={class:"mono"},Q={class:"panel"},X={class:"phead"},Y={key:0,class:"mute break",style:{"font-size":"var(--t-sm)"}},Z={class:"mute"},ee={key:1,class:"kvlist"},te={class:"k break",style:{width:"220px"}},se={class:"v row"},oe={key:0,class:"badge warn"},ne={key:2,class:"mute"},le={class:"panel"},ae={class:"dim"},ie={class:"field",style:{"max-width":"380px"}},re={class:"field",style:{"max-width":"380px"}},de={class:"field",style:{"max-width":"380px"}},ue={class:"row",style:{"margin-top":"var(--s4)"}},pe={key:0,class:"mute"},ve={class:"hint"},fe={class:"mono"},_e=E({__name:"RegistriesView",setup(ce){const b=a([]),w=a([]),h=a(""),R=a(null),D=a(!1),v=a(""),r=a(""),f=a(""),c=a(""),m=a(!1),y=a(""),j=P(()=>!!S.token&&!m.value&&!!r.value&&!!f.value&&!!c.value);async function k(){const o=await $.get("/api/registries");if(D.value=!0,!o.ok){v.value=V(o,"load the registry credentials");return}v.value="",b.value=o.body.entries??[],w.value=o.body.helpers??[],h.value=o.body.dir??"",R.value=o.body.writable??!1}k();async function B(){m.value=!0,y.value="";const o=await $.put(`/api/registries/${encodeURIComponent(r.value)}`,{username:f.value,password:c.value});if(m.value=!1,!o.ok){y.value=V(o,"store this credential");return}c.value="";const e=o.body.registry;e&&e!==r.value?A("ok",`Stored for ${e} — Docker Hub's canonical key.`):A("ok",`Stored for ${e||r.value}. It applies to the next pull.`),r.value="",f.value="",k()}async function I(o){const e=await $.del(`/api/registries/${encodeURIComponent(o)}`);if(!e.ok){v.value=V(e,"forget this credential");return}A("ok",`Forgot ${o}.`),k()}return(o,e)=>(n(),i("div",null,[t("div",W,[t("div",null,[e[5]||(e[5]=t("h1",null,"Registries",-1)),t("div",q,[e[4]||(e[4]=s(" Credentials for pulling private images ",-1)),u(N,{label:"why a host login is not enough"},{default:g(()=>[...e[3]||(e[3]=[s(" A pull is authenticated by the docker ",-1),t("em",null,"client",-1),s(", not the daemon: it reads its own ",-1),t("code",null,"config.json",-1),s(" and hands the credential over. pstack's client runs inside the control container, so a ",-1),t("code",null,"docker login",-1),s(" on the host writes a file it cannot see — and the pull fails on a host that is logged in. ",-1)])]),_:1})])]),e[6]||(e[6]=t("span",{class:"grow"},null,-1)),u(M,{run:k})]),v.value?(n(),x(T,{key:0,text:v.value,title:"Could not load the registry credentials."},null,8,["text"])):d("",!0),R.value===!1?(n(),i("div",G,[e[9]||(e[9]=t("b",null,"Credentials cannot be saved here yet.",-1)),t("p",null,[e[8]||(e[8]=s(" Re-run setup on the host — it is safe to repeat. ",-1)),u(L,{title:"Why this happens"},{default:g(()=>[...e[7]||(e[7]=[t("p",null," Saving a credential needs a writable directory that the control stack only started providing in version 0.7.0. A host set up before then does not have it, so this page can list credentials but not add one. ",-1),t("p",null," Re-running setup is safe to repeat and recreates the control containers with the directory in place. Nothing already stored is lost. ",-1)])]),_:1})])])):d("",!0),w.value.length?(n(),i("div",J,[e[12]||(e[12]=t("b",null,"This file uses a credential helper, which will not work here.",-1)),t("p",null,[e[10]||(e[10]=s(" Found: ",-1)),t("span",K,p(w.value.join(", ")),1),e[11]||(e[11]=s(". A helper keeps the secret outside the file — in an OS keychain, usually — and the helper binary does not exist in this container, so pulls from private registries will fail. Add the credential below instead, which stores it in the file. ",-1))])])):d("",!0),t("section",Q,[t("div",X,[e[13]||(e[13]=t("h2",{class:"section"},"Stored",-1)),e[14]||(e[14]=t("span",{class:"grow"},null,-1)),h.value?(n(),i("span",Y,[t("span",Z,p(h.value),1)])):d("",!0)]),D.value?b.value.length?(n(),i("ul",ee,[(n(!0),i(H,null,O(b.value,l=>(n(),i("li",{key:l.registry},[t("span",te,[t("b",null,p(l.registry),1)]),t("span",se,[t("span",null,p(l.username??"unknown user"),1),l.viaHelper?(n(),i("span",oe,"via helper")):d("",!0),e[16]||(e[16]=t("span",{class:"grow"},null,-1)),u(F,{disabled:!U(S).token,variant:"danger",onClick:me=>I(l.registry)},{default:g(()=>[...e[15]||(e[15]=[s(" Forget ",-1)])]),_:1},8,["disabled","onClick"])])]))),128))])):(n(),i("p",ne," Nothing stored. Public images need no credential — this is only for a private registry. ")):(n(),x(z,{key:0,rows:2}))]),t("section",le,[e[27]||(e[27]=t("h2",{class:"section",style:{"margin-bottom":"var(--s3)"}},"Add a credential",-1)),t("p",ae,[e[18]||(e[18]=s(" Applies to the ",-1)),e[19]||(e[19]=t("b",null,"next pull",-1)),e[20]||(e[20]=s(" — nothing needs restarting. ",-1)),u(N,{label:"why no restart is needed"},{default:g(()=>[...e[17]||(e[17]=[s(" The docker client re-reads ",-1),t("code",null,"config.json",-1),s(" on every invocation, so there is no cache to bust. Storing one now is enough for a deploy started a second later. ",-1)])]),_:1})]),t("div",ie,[e[21]||(e[21]=t("label",{for:"host"},"Registry",-1)),_(t("input",{id:"host","onUpdate:modelValue":e[0]||(e[0]=l=>r.value=l),type:"text",placeholder:"ghcr.io",spellcheck:"false",autocomplete:"off"},null,512),[[C,r.value,void 0,{trim:!0}]]),e[22]||(e[22]=t("div",{class:"mute hint"}," Docker Hub is stored under its canonical key. ",-1))]),t("div",re,[e[23]||(e[23]=t("label",{for:"user"},"Username",-1)),_(t("input",{id:"user","onUpdate:modelValue":e[1]||(e[1]=l=>f.value=l),type:"text",spellcheck:"false",autocomplete:"off"},null,512),[[C,f.value,void 0,{trim:!0}]])]),t("div",de,[e[24]||(e[24]=t("label",{for:"pass"},"Password or token",-1)),_(t("input",{id:"pass","onUpdate:modelValue":e[2]||(e[2]=l=>c.value=l),type:"password",autocomplete:"off",spellcheck:"false"},null,512),[[C,c.value]]),e[25]||(e[25]=t("div",{class:"mute hint"}," Prefer a token scoped to reading packages. Stored on the host as reversible base64, exactly as Stored the same way a terminal login stores it — never sent back to this page. ",-1))]),y.value?(n(),x(T,{key:0,text:y.value,title:"Could not store this credential."},null,8,["text"])):d("",!0),t("div",ue,[u(F,{variant:"primary",pending:m.value,disabled:!j.value,onClick:B},{default:g(()=>[s(p(m.value?"Storing…":"Store"),1)]),_:1},8,["pending","disabled"]),U(S).token?d("",!0):(n(),i("span",pe,"Storing needs an access token."))]),t("p",ve,[e[26]||(e[26]=s(" Or from the host, which writes the same file: ",-1)),t("span",fe,"docker login --config "+p(h.value||"<DATA_DIR>/control/docker")+" <registry>",1)])])]))}});export{_e as default};
|
|
2
|
-
//# sourceMappingURL=RegistriesView-DMzsLFcT.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{c as i,b as l}from"./useFormat-DIlTY5Dl.js";import{d as c,K as m,a6 as r,o as u,c as d,u as f,t as p,j as v,k as w}from"./index-T3JGe9ku.js";const D=["datetime","title"],I=15e3,x=c({__name:"RelativeTime",props:{at:{}},setup(e){const o=e,n=v(Date.now());let t=null,a=0;m(()=>{a++,t||(t=setInterval(()=>n.value=Date.now(),I))}),r(()=>{a--,a===0&&t&&(clearInterval(t),t=null)});const s=w(()=>(n.value,l(o.at)));return(S,_)=>(u(),d("time",{datetime:e.at?new Date(e.at).toISOString():void 0,title:f(i)(e.at)},p(s.value),9,D))}});export{x as _};
|
|
2
|
-
//# sourceMappingURL=RelativeTime.vue_vue_type_script_setup_true_lang-D4NO-r5D.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RelativeTime.vue_vue_type_script_setup_true_lang-D4NO-r5D.js","sources":["../../src/components/RelativeTime.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A timestamp that stays true — `2m ago` becomes `3m ago` without a reload.\n *\n * WHY NOT JUST CALL `ago()`. It is already correct at first render and wrong a minute later, and a\n * control plane is a page people leave open while watching a deploy. A relative time that silently\n * stops advancing is worse than an absolute one: it looks live and is not.\n *\n * ONE TICKER FOR THE WHOLE PAGE. A per-instance `setInterval` on a hundred-row job list is a\n * hundred timers waking the tab independently. This is a single module-level interval that bumps\n * one shared ref; every instance recomputes from it, and the interval only exists while at least\n * one instance is mounted.\n *\n * THE EXACT TIME IS NEVER LOST — it moves into `title`. \"3m ago\" is what you scan; the ISO stamp is\n * what you need the moment you are correlating with a log somewhere else, and throwing it away to\n * gain readability would be a bad trade.\n */\nimport { computed, onMounted, onUnmounted, ref } from 'vue';\nimport { ago, stamp } from '../composables/useFormat';\n\nconst props = defineProps<{ at: number | undefined }>();\n\nconst now = ref(Date.now());\nlet timer: ReturnType<typeof setInterval> | null = null;\nlet mounted = 0;\n\n/**\n * 15s, not 1s. The coarsest unit this renders is a minute, so ticking every second would repaint\n * sixty times to change a label once; 15s bounds the staleness to a quarter of the smallest unit,\n * which nobody can perceive.\n */\nconst TICK_MS = 15_000;\n\nonMounted(() => {\n mounted++;\n if (!timer) timer = setInterval(() => (now.value = Date.now()), TICK_MS);\n});\nonUnmounted(() => {\n mounted--;\n if (mounted === 0 && timer) {\n clearInterval(timer);\n timer = null;\n }\n});\n\n// Reading `now` is what subscribes this instance to the ticker; `ago` itself calls Date.now().\nconst text = computed(() => (now.value, ago(props.at)));\n</script>\n\n<template>\n <time :datetime=\"at ? new Date(at).toISOString() : undefined\" :title=\"stamp(at)\">{{ text }}</time>\n</template>\n"],"names":["TICK_MS","props","__props","now","ref","timer","mounted","onMounted","onUnmounted","text","computed","ago","_createElementBlock","_unref","stamp","_hoisted_1"],"mappings":"iLA+BMA,EAAU,uDAXhB,MAAMC,EAAQC,EAERC,EAAMC,EAAI,KAAK,IAAA,CAAK,EAC1B,IAAIC,EAA+C,KAC/CC,EAAU,EASdC,EAAU,IAAM,CACdD,IACKD,IAAOA,EAAQ,YAAY,IAAOF,EAAI,MAAQ,KAAK,IAAA,EAAQH,CAAO,EACzE,CAAC,EACDQ,EAAY,IAAM,CAChBF,IACIA,IAAY,GAAKD,IACnB,cAAcA,CAAK,EACnBA,EAAQ,KAEZ,CAAC,EAGD,MAAMI,EAAOC,EAAS,KAAOP,EAAI,MAAOQ,EAAIV,EAAM,EAAE,EAAE,oBAIpDW,EAAkG,OAAA,CAA3F,SAAUV,KAAE,IAAO,KAAKA,EAAA,EAAE,EAAE,YAAA,EAAgB,OAAY,MAAOW,EAAAC,CAAA,EAAMZ,EAAA,EAAE,CAAA,IAAMO,EAAA,KAAI,EAAA,EAAAM,CAAA"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import{d as le,c as o,a as t,f as n,e as f,w as c,_ as G,g as v,z as g,t as u,F as w,r as E,u as x,V as A,i as z,v as j,j as r,k as q,p as _,C as L,l as ne,O as J,o as s,D as ae}from"./index-T3JGe9ku.js";import{c as ie}from"./useFormat-DIlTY5Dl.js";import{_ as K}from"./ActionButton.vue_vue_type_script_setup_true_lang-C6PZurL0.js";import{_ as Q}from"./ErrorNote.vue_vue_type_script_setup_true_lang-CUZs06O0.js";import{_ as X}from"./SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js";import{_ as re}from"./HelpModal.vue_vue_type_script_setup_true_lang-ErbSSi6D.js";import{_ as ue}from"./RefreshButton.vue_vue_type_script_setup_true_lang-ZUc7pgOx.js";import"./Teleport-CKfsIrp8.js";const de={class:"page-head"},ve={class:"sub"},pe={key:1,class:"banner info"},fe={class:"panel"},ce={class:"phead"},me={class:"mute",style:{"font-size":"var(--t-sm)"}},he={key:0,class:"banner warn"},ye={key:1,class:"cards"},ke={class:"stagger"},be={"data-label":"url"},ge=["href"],we={key:1,class:"mono mute"},_e={key:0,class:"mute"},Re={"data-label":"forwards to"},Te={key:0,class:"mono"},Ce={key:1,class:"s-failed"},$e={"data-label":"deployment"},xe={key:1,class:"mute"},Ae={class:"dim","data-label":"router"},Le={key:2,class:"mute"},Ne={class:"panel"},Se={class:"phead"},De={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},Ie={class:"mute"},Pe={key:0,class:"banner failed"},Ue={key:1,class:"banner info"},Ve={key:3,class:"kvlist"},Ee={class:"k"},ze=["onClick"],je={class:"v mute",style:{"font-size":"var(--t-sm)"}},Fe={key:4,class:"mute"},Be={key:2,class:"panel"},We={class:"phead"},Me={class:"section"},He={key:0,class:"badge warn"},Oe={key:0,class:"field",style:{"max-width":"340px"}},Ge={key:2,class:"banner plain"},qe={class:"field"},Je={class:"row",style:{"margin-top":"var(--s4)"}},Ke={key:0,class:"mute"},Qe={class:"field",style:{"max-width":"340px"}},Xe=["for"],Ye=["id","placeholder"],Ze={class:"row"},et=`http:
|
|
2
|
-
middlewares:
|
|
3
|
-
# An example — replace it. Middlewares go UNDER http; at the top level Traefik ignores them.
|
|
4
|
-
ip-allow:
|
|
5
|
-
ipAllowList:
|
|
6
|
-
sourceRange:
|
|
7
|
-
- 203.0.113.0/24
|
|
8
|
-
`,dt=le({__name:"RoutingView",setup(tt){const N=r([]),S=r(null),D=r([]),I=r(""),P=r(null),F=r(!1),R=r(""),i=r(null),d=r(""),m=r(""),T=r(!1),U=r(!1),k=r(!1),p=r(""),C=r(""),h=r(null),y=r(""),B=q(()=>i.value!==null&&d.value!==m.value),Y=q(()=>!!A.token&&!k.value&&d.value.trim().length>0&&(B.value||i.value===""));async function Z(){await Promise.all([$(),W()])}async function $(){const l=await _.get("/api/routing");if(F.value=!0,!l.ok){R.value=L(l,"load the routing files");return}R.value="",D.value=l.body.files??[],I.value=l.body.dir??"",P.value=l.body.writable??!1}async function W(){const l=await _.get("/api/routing/live");if(!l.ok){S.value=!1;return}S.value=l.body.reachable,N.value=l.body.routes??[]}$(),W();async function ee(l){i.value=l,U.value=!0,p.value="",T.value=!1,y.value="";const e=await _.getAuthed(`/api/routing/${encodeURIComponent(l)}`);if(U.value=!1,!e.ok){p.value=L(e,"open this file");return}if(e.body.sourceWithheld){T.value=!0,d.value="",m.value="";return}d.value=e.body.content??"",m.value=d.value}function te(){i.value="",C.value="",d.value=et,m.value="",T.value=!1,p.value=""}function M(){i.value=null,d.value="",m.value="",p.value=""}async function H(){const l=i.value===""?C.value.trim():i.value;if(!l){p.value="Give the file a name, ending in .yml";return}k.value=!0,p.value="";const e=await _.put(`/api/routing/${encodeURIComponent(l)}`,{content:d.value});if(k.value=!1,!e.ok){p.value=L(e,"save this file");return}e.body.previous&&(h.value={name:l,content:e.body.previous}),m.value=d.value,i.value=l,J("ok",`Saved ${l}. Traefik reloads within a second or two.`),$()}async function se(){const l=h.value;l&&(d.value=l.content,i.value=l.name,await H(),h.value=null)}async function oe(){const l=i.value;if(!l||y.value!==l)return;const e=await _.del(`/api/routing/${encodeURIComponent(l)}`);if(!e.ok){p.value=L(e,"delete this file");return}e.body.previous&&(h.value={name:l,content:e.body.previous}),J("ok",`Deleted ${l}.`),M(),$()}return(l,e)=>{const V=ne("RouterLink");return s(),o("div",null,[t("div",de,[t("div",null,[t("h1",null,[e[4]||(e[4]=n(" Routing ",-1)),f(re,{title:"How routing files work, and what can go wrong"},{default:c(()=>[...e[3]||(e[3]=[t("p",null,[t("b",null,"Traefik reads this directory as a whole."),n(" One file it cannot parse and it reports a problem for the "),t("em",null,"directory"),n(" — so the symptom is other, unrelated routes disappearing rather than an error about the file you just saved. ")],-1),t("p",null," Saves here are checked before they are written and replace the old file in one step, so a file that would break the directory never reaches Traefik in the first place. ",-1),t("p",null,[t("b",null,"This page cannot lock you out."),n(" It and the API are routed by their containers' own settings, not by these files — no save here can take away the page you would use to undo it. ")],-1),t("p",null," What belongs here: shared middleware (sign-in prompts, rate limits, address allow-lists), TLS options, the catch-all fallback, and routes to things running outside this host's previews. A single preview's own routes are not here — those travel with the deployment. ",-1)])]),_:1})]),t("div",ve,[e[6]||(e[6]=n(" Every route on this host, and the config files behind them ",-1)),f(G,{label:"what belongs in these files"},{default:c(()=>[...e[5]||(e[5]=[n(" Middleware (basic auth, rate limits, IP allow-lists), TLS options, the catch-all fallback router, and routes to anything running outside compose. Per-PR routers are not here — those are labels in each deployment's own compose file. ",-1)])]),_:1})])]),e[7]||(e[7]=t("span",{class:"grow"},null,-1)),P.value?(s(),o("button",{key:0,class:"btn primary",onClick:te},"New file")):v("",!0),e[8]||(e[8]=t("span",{class:"grow"},null,-1)),f(ue,{run:Z})]),R.value?(s(),g(Q,{key:0,text:R.value,title:"Could not load the routing files."},null,8,["text"])):v("",!0),h.value?(s(),o("div",pe,[t("b",null,"Replaced "+u(h.value.name)+".",1),t("p",null,[e[9]||(e[9]=n(" The previous contents are still here for this session. ",-1)),t("button",{class:"btn sm",onClick:se},"Put it back")])])):v("",!0),t("section",fe,[t("div",ce,[e[12]||(e[12]=t("h2",{class:"section"},"Live routes",-1)),e[13]||(e[13]=t("span",{class:"grow"},null,-1)),t("span",me,[e[11]||(e[11]=n(" from container labels ",-1)),f(G,{label:"why routes are not in the files below",align:"end"},{default:c(()=>[...e[10]||(e[10]=[n(" Traefik reads two providers. Per-PR routers are ",-1),t("b",null,"labels on containers",-1),n(", written by each deployment's own compose file — so deploying a stack changes this list and never the files. The files are the other provider: middleware, TLS options, the fallback router. ",-1)])]),_:1})])]),S.value===!1?(s(),o("div",he,[...e[14]||(e[14]=[t("b",null,"Docker did not answer.",-1),t("p",null,"Routes could not be listed — which is not the same as there being none.",-1)])])):N.value.length?(s(),o("table",ye,[e[15]||(e[15]=t("thead",null,[t("tr",null,[t("th",null,"URL"),t("th",null,"Forwards to"),t("th",null,"Deployment"),t("th",null,"Router")])],-1)),t("tbody",ke,[(s(!0),o(w,null,E(N.value,(a,O)=>(s(),o("tr",{key:`${a.container}-${a.router}`,style:ae({"--i":O})},[t("td",be,[(s(!0),o(w,null,E(a.hosts,b=>(s(),o("div",{key:b},[b.startsWith("(pattern)")?(s(),o("span",we,u(b),1)):(s(),o("a",{key:0,href:`https://${b}`,target:"_blank",rel:"noreferrer"},u(b),9,ge))]))),128)),a.hosts.length?v("",!0):(s(),o("span",_e,"no host in the rule"))]),t("td",Re,[a.target?(s(),o("span",Te,u(a.target),1)):(s(),o("span",Ce,u(a.port===null?"no port declared":"not on the ingress network"),1))]),t("td",$e,[a.project?(s(),g(V,{key:0,to:`/deployments/${encodeURIComponent(a.project)}/runtime`},{default:c(()=>[n(u(a.project),1)]),_:2},1032,["to"])):(s(),o("span",xe,"—"))]),t("td",Ae,u(a.router),1)],4))),128))])])):(s(),o("p",Le,[...e[16]||(e[16]=[n(" No container on this host declares a Traefik router. A deployment's routes come from labels in its own compose file — open a deployment's ",-1),t("b",null,"Containers & routes",-1),n(" tab to see what is missing. ",-1)])]))]),t("section",Ne,[t("div",Se,[e[17]||(e[17]=t("h2",{class:"section"},"Config files",-1)),e[18]||(e[18]=t("span",{class:"grow"},null,-1)),I.value?(s(),o("span",De,[t("span",Ie,u(I.value),1)])):v("",!0)]),P.value===!1?(s(),o("div",Pe,[...e[19]||(e[19]=[t("b",null,"Read-only: the API cannot write to this directory.",-1),t("p",null," The control stack mounts it into the API from 0.4.0 onward. Re-run setup again on the host to pick up the mount — it is safe to repeat, and it recreates the control containers. ",-1)])])):x(A).token?v("",!0):(s(),o("div",Ue,[e[23]||(e[23]=t("b",null,"Viewing needs a token too.",-1)),t("p",null,[e[21]||(e[21]=n(" These files hold basic-auth hashes and forward-auth URLs, so their contents are not served unauthenticated. ",-1)),f(V,{to:"/settings"},{default:c(()=>[...e[20]||(e[20]=[n("Add your token",-1)])]),_:1}),e[22]||(e[22]=n(". ",-1))])])),F.value?D.value.length?(s(),o("ul",Ve,[(s(!0),o(w,null,E(D.value,a=>(s(),o("li",{key:a.name},[t("span",Ee,[t("button",{class:"ghost sm",onClick:O=>ee(a.name)},u(a.name),9,ze)]),t("span",je,u(a.size)+" bytes · "+u(x(ie)(a.updatedAt)),1)]))),128))])):(s(),o("p",Fe," No files yet. Everything is routed by container labels, which is a perfectly good place to be. ")):(s(),g(X,{key:2,rows:3}))]),i.value!==null?(s(),o("section",Be,[t("div",We,[t("h2",Me,u(i.value===""?"New file":i.value),1),B.value?(s(),o("span",He,"unsaved")):v("",!0),e[24]||(e[24]=t("span",{class:"grow"},null,-1)),t("button",{class:"ghost sm",onClick:M},"Close")]),i.value===""?(s(),o("div",Oe,[e[25]||(e[25]=t("label",{for:"fname"},"File name",-1)),z(t("input",{id:"fname","onUpdate:modelValue":e[0]||(e[0]=a=>C.value=a),type:"text",placeholder:"middleware.yml",spellcheck:"false",autocomplete:"off"},null,512),[[j,C.value,void 0,{trim:!0}]]),e[26]||(e[26]=t("div",{class:"mute hint"},[n("Lower case, ending in "),t("code",null,".yml"),n(".")],-1))])):v("",!0),U.value?(s(),g(X,{key:1,rows:4})):T.value?(s(),o("div",Ge,[e[29]||(e[29]=t("b",null,"Hidden without an access token.",-1)),t("p",null,[f(V,{to:"/settings"},{default:c(()=>[...e[27]||(e[27]=[n("Add your token",-1)])]),_:1}),e[28]||(e[28]=n(" to read and edit this file. ",-1))])])):(s(),o(w,{key:3},[t("div",qe,[e[30]||(e[30]=t("label",{for:"content"},"Contents",-1)),z(t("textarea",{id:"content","onUpdate:modelValue":e[1]||(e[1]=a=>d.value=a),rows:"18",spellcheck:"false"},null,512),[[j,d.value]])]),p.value?(s(),g(Q,{key:0,text:p.value,title:"Traefik would not accept this."},null,8,["text"])):v("",!0),t("div",Je,[f(K,{variant:"primary",pending:k.value,disabled:!Y.value,onClick:H},{default:c(()=>[n(u(k.value?"Saving…":"Save"),1)]),_:1},8,["pending","disabled"]),x(A).token?v("",!0):(s(),o("span",Ke,"Saving needs a token."))]),i.value?(s(),o(w,{key:1},[e[34]||(e[34]=t("h2",{class:"section",style:{margin:"var(--s5) 0 var(--s3)"}},"Delete this file",-1)),e[35]||(e[35]=t("p",{class:"dim"}," Whatever it configures stops applying as soon as Traefik reloads. If it holds a router, those hostnames stop resolving to anything. ",-1)),t("div",Qe,[t("label",{for:`confirm-${i.value}`},[e[31]||(e[31]=n(" Type ",-1)),t("b",null,u(i.value),1),e[32]||(e[32]=n(" to confirm ",-1))],8,Xe),z(t("input",{id:`confirm-${i.value}`,"onUpdate:modelValue":e[2]||(e[2]=a=>y.value=a),type:"text",placeholder:i.value,spellcheck:"false",autocomplete:"off"},null,8,Ye),[[j,y.value,void 0,{trim:!0}]])]),t("div",Ze,[f(K,{variant:"danger",disabled:y.value!==i.value||!x(A).token,title:y.value!==i.value?"Type the file name to confirm.":void 0,onClick:oe},{default:c(()=>[...e[33]||(e[33]=[n(" Delete ",-1)])]),_:1},8,["disabled","title"])])],64)):v("",!0)],64))])):v("",!0)])}}});export{dt as default};
|
|
9
|
-
//# sourceMappingURL=RoutingView-BEJ3iJ9c.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RoutingView-BEJ3iJ9c.js","sources":["../../src/views/RoutingView.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Traefik's dynamic configuration — the file list, and an editor.\n *\n * WHAT LIVES HERE. Traefik reads config from two providers: labels on containers (every per-PR router)\n * and a watched *directory* of files. This page is the directory: middleware (basic auth, rate limits,\n * IP allow-lists), TLS options, the catch-all fallback router, routes to something running outside\n * compose. Several files rather than one is the point of a watched directory — one file per concern.\n *\n * THE WARNING IS NOT DECORATION. Traefik's documented behaviour is that an unparseable file produces\n * a parse error for the whole DIRECTORY, and the rest can be discarded with it — so a careless save\n * here breaks *other people's* routes, not the file being edited. The server validates before writing\n * and renames into place so the watcher never sees a partial file, and every rejection is rendered\n * verbatim because it names the actual problem.\n *\n * What it cannot break: `control.<domain>` and `api.<domain>` are docker labels on the pstack\n * container, not file config. This page cannot lock you out of the page you would use to undo a bad\n * save. That is stated in the UI, because an operator hesitating over a save deserves to know it.\n *\n * `previous` from a save is an in-session undo. There is deliberately no history on disk: the obvious\n * place to keep it is the one directory that must contain nothing but dynamic config.\n */\nimport { computed, ref } from 'vue';\nimport { api, problem } from '../api/client';\nimport type {\n LiveRoutesResponse,\n RoutingFile,\n RoutingListResponse,\n RoutingReadResponse,\n RoutingWriteResponse,\n RuntimeRoute,\n} from '../api/types';\nimport { settings } from '../composables/useSettings';\nimport { stamp } from '../composables/useFormat';\nimport { toast } from '../composables/useToasts';\nimport ActionButton from '../components/ActionButton.vue';\nimport ErrorNote from '../components/ErrorNote.vue';\nimport InfoHint from '../components/InfoHint.vue';\nimport SkeletonList from '../components/SkeletonList.vue';\nimport HelpModal from '../components/HelpModal.vue';\nimport RefreshButton from '../components/RefreshButton.vue';\n\n/**\n * The live routes, from container labels.\n *\n * This page used to show only the file provider, and the reported confusion was exactly right: a\n * deployment goes up, its hostname appears, and nothing here changes — because per-PR routers are\n * DOCKER labels and the files below are Traefik's *other* provider. Showing both is what makes the\n * page match what \"routing\" means to whoever opened it.\n */\nconst live = ref<RuntimeRoute[]>([]);\nconst liveReachable = ref<boolean | null>(null);\n\nconst files = ref<RoutingFile[]>([]);\nconst dir = ref('');\nconst writable = ref<boolean | null>(null);\nconst loaded = ref(false);\nconst listError = ref('');\n\n/** The open file. `null` name means a new one. */\nconst openName = ref<string | null>(null);\nconst draft = ref('');\nconst original = ref('');\nconst withheld = ref(false);\nconst loadingFile = ref(false);\nconst saving = ref(false);\nconst fileError = ref('');\nconst newName = ref('');\n/** Last replaced content, offered as an undo for the rest of the session. */\nconst undo = ref<{ name: string; content: string } | null>(null);\nconst confirmDelete = ref('');\n\nconst dirty = computed(() => openName.value !== null && draft.value !== original.value);\nconst canSave = computed(\n () => !!settings.token && !saving.value && draft.value.trim().length > 0 && (dirty.value || openName.value === ''),\n);\n\nconst STARTER = `http:\n middlewares:\n # An example — replace it. Middlewares go UNDER http; at the top level Traefik ignores them.\n ip-allow:\n ipAllowList:\n sourceRange:\n - 203.0.113.0/24\n`;\n\n/** Both halves: the file provider's directory AND the routes Traefik actually has. */\nasync function refreshAll(): Promise<void> {\n await Promise.all([loadList(), loadLive()]);\n}\n\nasync function loadList(): Promise<void> {\n const r = await api.get<RoutingListResponse>('/api/routing');\n loaded.value = true;\n if (!r.ok) {\n listError.value = problem(r, 'load the routing files');\n return;\n }\n listError.value = '';\n files.value = r.body.files ?? [];\n dir.value = r.body.dir ?? '';\n writable.value = r.body.writable ?? false;\n}\n\nasync function loadLive(): Promise<void> {\n const r = await api.get<LiveRoutesResponse>('/api/routing/live');\n if (!r.ok) {\n liveReachable.value = false;\n return;\n }\n liveReachable.value = r.body.reachable;\n live.value = r.body.routes ?? [];\n}\n\nvoid loadList();\nvoid loadLive();\n\nasync function open(name: string): Promise<void> {\n openName.value = name;\n loadingFile.value = true;\n fileError.value = '';\n withheld.value = false;\n confirmDelete.value = '';\n // Authenticated: dynamic config holds basic-auth hashes and forward-auth URLs.\n const r = await api.getAuthed<RoutingReadResponse>(`/api/routing/${encodeURIComponent(name)}`);\n loadingFile.value = false;\n if (!r.ok) {\n fileError.value = problem(r, 'open this file');\n return;\n }\n if (r.body.sourceWithheld) {\n withheld.value = true;\n draft.value = '';\n original.value = '';\n return;\n }\n draft.value = r.body.content ?? '';\n original.value = draft.value;\n}\n\nfunction startNew(): void {\n openName.value = '';\n newName.value = '';\n draft.value = STARTER;\n original.value = '';\n withheld.value = false;\n fileError.value = '';\n}\n\nfunction close(): void {\n openName.value = null;\n draft.value = '';\n original.value = '';\n fileError.value = '';\n}\n\nasync function save(): Promise<void> {\n const name = openName.value === '' ? newName.value.trim() : openName.value;\n if (!name) {\n fileError.value = 'Give the file a name, ending in .yml';\n return;\n }\n saving.value = true;\n fileError.value = '';\n const r = await api.put<RoutingWriteResponse>(`/api/routing/${encodeURIComponent(name)}`, {\n content: draft.value,\n });\n saving.value = false;\n if (!r.ok) {\n // Rendered verbatim: the server's message names the unknown section or the YAML error, which is\n // the whole diagnosis.\n fileError.value = problem(r, 'save this file');\n return;\n }\n if (r.body.previous) undo.value = { name, content: r.body.previous };\n original.value = draft.value;\n openName.value = name;\n toast('ok', `Saved ${name}. Traefik reloads within a second or two.`);\n void loadList();\n}\n\nasync function revert(): Promise<void> {\n const u = undo.value;\n if (!u) return;\n draft.value = u.content;\n openName.value = u.name;\n await save();\n undo.value = null;\n}\n\nasync function remove(): Promise<void> {\n const name = openName.value;\n if (!name || confirmDelete.value !== name) return;\n const r = await api.del<RoutingWriteResponse>(`/api/routing/${encodeURIComponent(name)}`);\n if (!r.ok) {\n fileError.value = problem(r, 'delete this file');\n return;\n }\n if (r.body.previous) undo.value = { name, content: r.body.previous };\n toast('ok', `Deleted ${name}.`);\n close();\n void loadList();\n}\n</script>\n\n<template>\n <div>\n <div class=\"page-head\">\n <div>\n <h1>\n Routing\n <HelpModal title=\"How routing files work, and what can go wrong\">\n <p>\n <b>Traefik reads this directory as a whole.</b> One file it cannot parse and it reports a\n problem for the <em>directory</em> — so the symptom is other, unrelated routes\n disappearing rather than an error about the file you just saved.\n </p>\n <p>\n Saves here are checked before they are written and replace the old file in one step, so a\n file that would break the directory never reaches Traefik in the first place.\n </p>\n <p>\n <b>This page cannot lock you out.</b> It and the API are routed by their containers'\n own settings, not by these files — no save here can take away the page you would use to\n undo it.\n </p>\n <p>\n What belongs here: shared middleware (sign-in prompts, rate limits, address\n allow-lists), TLS options, the catch-all fallback, and routes to things running outside\n this host's previews. A single preview's own routes are not here — those travel with the\n deployment.\n </p>\n </HelpModal>\n </h1>\n <div class=\"sub\">\n Every route on this host, and the config files behind them\n <InfoHint label=\"what belongs in these files\">\n Middleware (basic auth, rate limits, IP allow-lists), TLS options, the catch-all fallback\n router, and routes to anything running outside compose. Per-PR routers are not here —\n those are labels in each deployment's own compose file.\n </InfoHint>\n </div>\n </div>\n <span class=\"grow\" />\n <button v-if=\"writable\" class=\"btn primary\" @click=\"startNew\">New file</button>\n <span class=\"grow\" />\n <RefreshButton :run=\"refreshAll\" />\n </div>\n\n <ErrorNote v-if=\"listError\" :text=\"listError\" title=\"Could not load the routing files.\" />\n\n <div v-if=\"undo\" class=\"banner info\">\n <b>Replaced {{ undo.name }}.</b>\n <p>\n The previous contents are still here for this session.\n <button class=\"btn sm\" @click=\"revert\">Put it back</button>\n </p>\n </div>\n\n <!-- ============================ live routes ============================ -->\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Live routes</h2>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n from container labels\n <InfoHint label=\"why routes are not in the files below\" align=\"end\">\n Traefik reads two providers. Per-PR routers are <b>labels on containers</b>, written by each\n deployment's own compose file — so deploying a stack changes this list and never the files.\n The files are the other provider: middleware, TLS options, the fallback router.\n </InfoHint>\n </span>\n </div>\n\n <div v-if=\"liveReachable === false\" class=\"banner warn\">\n <b>Docker did not answer.</b>\n <p>Routes could not be listed — which is not the same as there being none.</p>\n </div>\n\n <table v-else-if=\"live.length\" class=\"cards\">\n <thead>\n <tr>\n <th>URL</th>\n <th>Forwards to</th>\n <th>Deployment</th>\n <th>Router</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(r, i) in live\" :key=\"`${r.container}-${r.router}`\" :style=\"{ '--i': i }\">\n <td data-label=\"url\">\n <div v-for=\"h in r.hosts\" :key=\"h\">\n <a v-if=\"!h.startsWith('(pattern)')\" :href=\"`https://${h}`\" target=\"_blank\" rel=\"noreferrer\">\n {{ h }}\n </a>\n <span v-else class=\"mono mute\">{{ h }}</span>\n </div>\n <span v-if=\"!r.hosts.length\" class=\"mute\">no host in the rule</span>\n </td>\n <td data-label=\"forwards to\">\n <span v-if=\"r.target\" class=\"mono\">{{ r.target }}</span>\n <span v-else class=\"s-failed\">\n {{ r.port === null ? 'no port declared' : 'not on the ingress network' }}\n </span>\n </td>\n <td data-label=\"deployment\">\n <RouterLink v-if=\"r.project\" :to=\"`/deployments/${encodeURIComponent(r.project)}/runtime`\">\n {{ r.project }}\n </RouterLink>\n <span v-else class=\"mute\">—</span>\n </td>\n <td class=\"dim\" data-label=\"router\">{{ r.router }}</td>\n </tr>\n </tbody>\n </table>\n\n <p v-else class=\"mute\">\n No container on this host declares a Traefik router. A deployment's routes come from labels in\n its own compose file — open a deployment's <b>Containers & routes</b> tab to see what is\n missing.\n </p>\n </section>\n\n <!-- ============================ config files ============================ -->\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Config files</h2>\n <span class=\"grow\" />\n <span v-if=\"dir\" class=\"mute\" style=\"font-size: var(--t-sm)\">\n <span class=\"mute\">{{ dir }}</span>\n </span>\n </div>\n\n <!--\n The blast radius, stated once and up front — and the reassurance alongside it, because \"this can\n break everything\" without \"you will still be able to fix it\" just makes people avoid the page.\n -->\n\n <div v-if=\"writable === false\" class=\"banner failed\">\n <b>Read-only: the API cannot write to this directory.</b>\n <p>\n The control stack mounts it into the API from 0.4.0 onward. Re-run\n setup again on the host to pick up the mount — it is safe to repeat, and it recreates\n the control containers.\n </p>\n </div>\n\n <div v-else-if=\"!settings.token\" class=\"banner info\">\n <b>Viewing needs a token too.</b>\n <p>\n These files hold basic-auth hashes and forward-auth URLs, so their contents are not served\n unauthenticated. <RouterLink to=\"/settings\">Add your token</RouterLink>.\n </p>\n </div>\n\n <SkeletonList v-if=\"!loaded\" :rows=\"3\" />\n <ul v-else-if=\"files.length\" class=\"kvlist\">\n <li v-for=\"f in files\" :key=\"f.name\">\n <span class=\"k\">\n <button class=\"ghost sm\" @click=\"open(f.name)\">{{ f.name }}</button>\n </span>\n <span class=\"v mute\" style=\"font-size: var(--t-sm)\">\n {{ f.size }} bytes · {{ stamp(f.updatedAt) }}\n </span>\n </li>\n </ul>\n <p v-else class=\"mute\">\n No files yet. Everything is routed by container labels, which is a perfectly good place to be.\n </p>\n </section>\n\n <section v-if=\"openName !== null\" class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">{{ openName === '' ? 'New file' : openName }}</h2>\n <span v-if=\"dirty\" class=\"badge warn\">unsaved</span>\n <span class=\"grow\" />\n <button class=\"ghost sm\" @click=\"close\">Close</button>\n </div>\n\n <div v-if=\"openName === ''\" class=\"field\" style=\"max-width: 340px\">\n <label for=\"fname\">File name</label>\n <input\n id=\"fname\"\n v-model.trim=\"newName\"\n type=\"text\"\n placeholder=\"middleware.yml\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n <div class=\"mute hint\">Lower case, ending in <code>.yml</code>.</div>\n </div>\n\n <SkeletonList v-if=\"loadingFile\" :rows=\"4\" />\n\n <div v-else-if=\"withheld\" class=\"banner plain\">\n <b>Hidden without an access token.</b>\n <p>\n <RouterLink to=\"/settings\">Add your token</RouterLink> to read and edit this file.\n </p>\n </div>\n\n <template v-else>\n <div class=\"field\">\n <label for=\"content\">Contents</label>\n <textarea id=\"content\" v-model=\"draft\" rows=\"18\" spellcheck=\"false\" />\n </div>\n\n <ErrorNote v-if=\"fileError\" :text=\"fileError\" title=\"Traefik would not accept this.\" />\n\n <div class=\"row\" style=\"margin-top: var(--s4)\">\n <ActionButton variant=\"primary\" :pending=\"saving\" :disabled=\"!canSave\" @click=\"save\">\n {{ saving ? 'Saving…' : 'Save' }}\n </ActionButton>\n <span v-if=\"!settings.token\" class=\"mute\">Saving needs a token.</span>\n </div>\n\n <template v-if=\"openName\">\n <h2 class=\"section\" style=\"margin: var(--s5) 0 var(--s3)\">Delete this file</h2>\n <p class=\"dim\">\n Whatever it configures stops applying as soon as Traefik reloads. If it holds a router,\n those hostnames stop resolving to anything.\n </p>\n <div class=\"field\" style=\"max-width: 340px\">\n <label :for=\"`confirm-${openName}`\">\n Type <b>{{ openName }}</b> to confirm\n </label>\n <input\n :id=\"`confirm-${openName}`\"\n v-model.trim=\"confirmDelete\"\n type=\"text\"\n :placeholder=\"openName\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n </div>\n <div class=\"row\">\n <ActionButton\n variant=\"danger\"\n :disabled=\"confirmDelete !== openName || !settings.token\"\n :title=\"confirmDelete !== openName ? 'Type the file name to confirm.' : undefined\"\n @click=\"remove\"\n >\n Delete\n </ActionButton>\n </div>\n </template>\n </template>\n </section>\n </div>\n</template>\n"],"names":["STARTER","live","ref","liveReachable","files","dir","writable","loaded","listError","openName","draft","original","withheld","loadingFile","saving","fileError","newName","undo","confirmDelete","dirty","computed","canSave","settings","refreshAll","loadList","loadLive","r","api","problem","open","name","startNew","close","save","toast","revert","u","remove","_createElementBlock","_createElementVNode","_hoisted_1","_createVNode","HelpModal","_cache","_hoisted_2","InfoHint","RefreshButton","_createBlock","ErrorNote","_openBlock","_hoisted_3","_toDisplayString","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_Fragment","_renderList","i","_hoisted_10","h","_hoisted_12","_hoisted_11","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_component_RouterLink","_createTextVNode","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_unref","_hoisted_26","_hoisted_27","f","_hoisted_28","$event","_hoisted_29","_hoisted_30","stamp","_hoisted_31","SkeletonList","_hoisted_32","_hoisted_33","_hoisted_34","_hoisted_35","_hoisted_36","_hoisted_37","_hoisted_38","_hoisted_39","ActionButton","_hoisted_40","_hoisted_41","_hoisted_44"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.task[data-v-e69ab573]{font-size:var(--t-sm);max-width:30ch}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{s as x}from"./useFormat-DIlTY5Dl.js";import{d as O,J as W,x as H,c as o,z as k,g as u,F as p,a as s,r as f,f as l,t as r,e as y,w as g,_ as U,j as R,k as b,l as M,o as n,D as z,E as T,u as S,p as L,C as A,O as D}from"./index-T3JGe9ku.js";import{d as c,v as j}from"./useDeployment-CV0kcdoc.js";import{_ as J}from"./SkeletonList.vue_vue_type_script_setup_true_lang-Bwex16vy.js";import{_ as I}from"./ActionButton.vue_vue_type_script_setup_true_lang-C6PZurL0.js";import{_ as Q}from"./ErrorNote.vue_vue_type_script_setup_true_lang-CUZs06O0.js";import{_ as q}from"./RefreshButton.vue_vue_type_script_setup_true_lang-ZUc7pgOx.js";import{_ as G}from"./_plugin-vue_export-helper-DlAUqK2U.js";import"./useVars-VN9gYdR6.js";const X={key:0,class:"banner warn"},Y={key:0,class:"banner ok"},Z={class:"panel"},ee={class:"phead"},te={class:"mute",style:{"font-size":"var(--t-sm)"}},se={key:0,class:"cards"},ne={class:"stagger"},oe={"data-label":"url"},ae=["href"],re={key:1,class:"mono mute"},le={key:0,class:"mute"},ie={"data-label":"forwards to"},de={key:0,class:"mono"},ue={key:1,class:"s-failed"},me={class:"mute",style:{"font-size":"var(--t-sm)"}},pe={"data-label":"router"},he={key:0,class:"mute",style:{"font-size":"var(--t-sm)"}},ve={"data-label":"tls"},fe={key:0,class:"badge ok"},ge={key:1,class:"badge off"},ke={key:1,class:"banner warn"},ye={class:"panel"},be={class:"phead"},ce={class:"mute",style:{"font-size":"var(--t-sm)"}},_e={key:0,class:"cards"},we={key:0},$e={class:"stagger"},Re={class:"name","data-label":"service"},Se=["title"],Ce={key:0,"data-label":"node"},Te={key:0,class:"badge",title:"runs on another swarm node — logs reach it through the manager; a shell and stop/start do not"},Ie={"data-label":"state"},Ne={"data-label":"ports"},Pe={class:"mono"},xe={key:0,class:"mute"},Ue={key:0,class:"mute"},ze={"data-label":"networks"},Le={key:0,class:"mute mono",style:{"font-size":"var(--t-sm)"}},Ae={class:"dim","data-label":"image"},De={class:"row-actions","data-label":""},je={key:0,class:"badge",title:"a one-shot job — it runs to completion; there is no long-lived container to start, stop, or open a shell in"},Be={key:1,class:"mute",style:{"font-size":"var(--t-sm)"},title:"runs on another swarm node — redeploy the stack, or act on the worker itself"},Ee=["href"],Ve={key:1,class:"mute"},Fe=O({__name:"RuntimeTab",setup(Ke){const i=R(null),_=R(""),w=R(!0);async function $(){const a=await L.get(`/api/deployments/${encodeURIComponent(c.id)}/runtime${j.value}`);if(w.value=!1,!a.ok){_.value=A(a,"inspect what is running");return}_.value="",i.value=a.body}W(()=>c.id,()=>{w.value=!0,$()},{immediate:!0}),H(()=>void $(),8e3);const B=b(()=>{var a;return((a=i.value)==null?void 0:a.findings.filter(e=>e.level==="error"))??[]}),E=b(()=>{var a;return((a=i.value)==null?void 0:a.findings.filter(e=>e.level==="warn"))??[]}),V=b(()=>{var a;return((a=i.value)==null?void 0:a.findings.filter(e=>e.level==="info"))??[]}),F=b(()=>{var a;return((a=i.value)==null?void 0:a.containers.filter(e=>e.state==="running").length)??0}),N=b(()=>{var a;return((a=i.value)==null?void 0:a.containers.some(e=>e.node))??!1});function K(a){const e=a.split(".");return e.length===3?`${e[1]}.${e[2]}`:a}const v=R("");async function C(a,e){v.value=`${e}:${a.name}`;const m=await L.post(`/api/deployments/${encodeURIComponent(c.id)}/containers/${encodeURIComponent(a.name)}/${e}${j.value}`);if(v.value="",!m.ok){D("error",A(m,`${e} ${a.name}`));return}D("ok",m.body.note??`${a.name}: ${e} done.`),$()}function P(a){var m;const e=(m=i.value)==null?void 0:m.routes.filter(t=>t.container===a.name||t.container===a.service).flatMap(t=>t.hosts).find(t=>!t.startsWith("(pattern)"));return e?`https://${e}`:null}return(a,e)=>{const m=M("RouterLink");return n(),o("div",null,[_.value?(n(),k(Q,{key:0,text:_.value,title:"Could not inspect this deployment."},null,8,["text"])):u("",!0),w.value&&!i.value?(n(),k(J,{key:1,rows:5})):i.value?(n(),o(p,{key:2},[i.value.reachable?(n(),o(p,{key:1},[(n(!0),o(p,null,f(B.value,(t,h)=>(n(),o("div",{key:`e${h}`,class:"banner failed"},[e[1]||(e[1]=s("b",null,"Blocking",-1)),s("p",null,r(t.message),1)]))),128)),(n(!0),o(p,null,f(E.value,(t,h)=>(n(),o("div",{key:`w${h}`,class:"banner warn"},[e[2]||(e[2]=s("b",null,"Worth checking",-1)),s("p",null,r(t.message),1)]))),128)),(n(!0),o(p,null,f(V.value,(t,h)=>(n(),o("div",{key:`i${h}`,class:"banner plain"},[s("p",null,r(t.message),1)]))),128)),!i.value.findings.length&&i.value.containers.length?(n(),o("div",Y,[...e[3]||(e[3]=[s("b",null,"Nothing looks wrong.",-1),s("p",null," Every container is attached to the ingress network and every router has a rule and a port. If a hostname still fails, the next suspects are DNS and the certificate. ",-1)])])):u("",!0),s("section",Z,[s("div",ee,[e[5]||(e[5]=s("h2",{class:"section"},"Routing",-1)),e[6]||(e[6]=s("span",{class:"grow"},null,-1)),s("span",te,[l(" TLS: "+r(i.value.challenge==="unknown"?"unknown":i.value.challenge)+" ",1),y(U,{label:"why the TLS mode matters here",align:"end"},{default:g(()=>[...e[4]||(e[4]=[l(" Under HTTP-01 every per-PR router needs ",-1),s("code",null,"tls.certresolver=le",-1),l(", because each hostname resolves its own certificate. Under DNS-01 that inverts — one always-on router holds the wildcard, and a per-PR certresolver makes each PR order its own and burn the weekly limit. Read from the running Traefik's own flags, not configured here. ",-1)])]),_:1})])]),i.value.routes.length?(n(),o("table",se,[s("thead",null,[s("tr",null,[e[9]||(e[9]=s("th",null,"URL",-1)),s("th",null,[e[8]||(e[8]=l(" Forwards to ",-1)),y(U,{label:"how a URL reaches a port"},{default:g(()=>[...e[7]||(e[7]=[l(" Traefik's docker provider resolves the container's IP on ",-1),s("code",null,"preview-ingress",-1),l(" and dials it on the port from ",-1),s("code",null,"loadbalancer.server.port",-1),l(" — it does ",-1),s("em",null,"not",-1),l(" use ",-1),s("code",null,"service:port",-1),l(" DNS. So the port here is the one inside the container, and publishing a host port is unnecessary. ",-1)])]),_:1})]),e[10]||(e[10]=s("th",null,"Router",-1)),e[11]||(e[11]=s("th",null,"TLS",-1))])]),s("tbody",ne,[(n(!0),o(p,null,f(i.value.routes,(t,h)=>(n(),o("tr",{key:t.router,style:z({"--i":h})},[s("td",oe,[(n(!0),o(p,null,f(t.hosts,d=>(n(),o("div",{key:d},[d.startsWith("(pattern)")?(n(),o("span",re,r(d),1)):(n(),o("a",{key:0,href:`https://${d}`,target:"_blank",rel:"noreferrer"},r(d),9,ae))]))),128)),t.hosts.length?u("",!0):(n(),o("span",le,"no host in the rule"))]),s("td",ie,[t.target?(n(),o("span",de,r(t.target),1)):(n(),o("span",ue,r(t.port===null?"no port declared":"not on the ingress network"),1)),s("div",me,r(t.container),1)]),s("td",pe,[l(r(t.router)+" ",1),t.priority?(n(),o("div",he," priority "+r(t.priority),1)):u("",!0)]),s("td",ve,[t.tls?(n(),o("span",fe,r(t.certresolver||"inherited"),1)):(n(),o("span",ge,"off"))])],4))),128))])])):(n(),o("div",ke,[e[17]||(e[17]=s("b",null,"No routes. Traefik has not been told about this deployment.",-1)),s("p",null,[e[13]||(e[13]=l(" Routes come from labels in ",-1)),e[14]||(e[14]=s("em",null,"your",-1)),e[15]||(e[15]=l(" compose file, not from pstack and not from the ",-1)),y(m,{to:"/routing"},{default:g(()=>[...e[12]||(e[12]=[l("Routing",-1)])]),_:1}),e[16]||(e[16]=l(" page (that is Traefik's file provider — middleware and TLS options, not per-PR routers). A reachable service needs four things: ",-1))]),e[18]||(e[18]=s("pre",{class:"code"},"labels:\n - traefik.enable=true # the host runs exposedbydefault=false\n - traefik.docker.network=preview-ingress # which network Traefik should dial\n - traefik.http.routers.<name>-${STACK}.rule=Host(`<name>-${STACK}.${PREVIEW_DOMAIN}`)\n - traefik.http.services.<name>-${STACK}.loadbalancer.server.port=80\nnetworks: [default, preview-ingress] # and preview-ingress: { external: true }",-1)),e[19]||(e[19]=s("p",{class:"mute"},[l(" Put "),s("code",null,"${STACK}"),l(" in the router and service names: Traefik's namespace is global across the host, so two deployments sharing a router name serve each other's containers. ")],-1))]))]),s("section",ye,[s("div",be,[e[20]||(e[20]=s("h2",{class:"section"},"Containers",-1)),e[21]||(e[21]=s("span",{class:"grow"},null,-1)),s("span",ce,r(F.value)+" of "+r(i.value.containers.length)+" running ",1),y(q,{run:$,busy:w.value},null,8,["busy"])]),i.value.containers.length?(n(),o("table",_e,[s("thead",null,[s("tr",null,[e[22]||(e[22]=s("th",null,"Service",-1)),N.value?(n(),o("th",we,"Node")):u("",!0),e[23]||(e[23]=s("th",null,"State",-1)),e[24]||(e[24]=s("th",null,"Ports",-1)),e[25]||(e[25]=s("th",null,"Networks",-1)),e[26]||(e[26]=s("th",null,"Image",-1)),e[27]||(e[27]=s("th",{"aria-label":"Actions"},null,-1))])]),s("tbody",$e,[(n(!0),o(p,null,f(i.value.containers,(t,h)=>(n(),o("tr",{key:t.id,style:z({"--i":h})},[s("td",Re,[l(r(t.service??t.name)+" ",1),s("div",{class:"mute task",title:t.name},r(K(t.name)),9,Se)]),N.value?(n(),o("td",Ce,[l(r(t.node??"—")+" ",1),t.remote?(n(),o("span",Te,"remote")):u("",!0)])):u("",!0),s("td",Ie,[s("span",{class:T(t.state==="running"?"s-ok":"s-failed")},r(S(x)(t.state)),3),t.health&&t.state==="running"?(n(),o("div",{key:0,class:T(["mute",t.health==="healthy"?"s-ok":"s-failed"]),style:{"font-size":"var(--t-sm)"}},r(S(x)(t.health)),3)):u("",!0)]),s("td",Ne,[(n(!0),o(p,null,f(t.ports,d=>(n(),o("div",{key:`${d.containerPort}/${d.protocol}`},[s("span",Pe,r(d.containerPort),1),d.hostPort?(n(),o("span",xe," ← host "+r(d.hostPort),1)):u("",!0)]))),128)),t.ports.length?u("",!0):(n(),o("span",Ue,"none exposed"))]),s("td",ze,[(n(!0),o(p,null,f(t.networks,d=>(n(),o("div",{key:d},[s("span",{class:T(d==="preview-ingress"?"s-ok":"")},r(d),3)]))),128)),t.ingressIp?(n(),o("div",Le,r(t.ingressIp),1)):u("",!0)]),s("td",Ae,r(t.image),1),s("td",De,[t.job?(n(),o("span",je,"one-shot")):t.remote?(n(),o("span",Be," on "+r(t.node),1)):t.state!=="running"?(n(),k(I,{key:2,class:"sm",variant:"ghost",pending:v.value===`start:${t.name}`,disabled:!!v.value,confirm:"Start it?",onRun:d=>C(t,"start")},{default:g(()=>[...e[28]||(e[28]=[l(" Start ",-1)])]),_:1},8,["pending","disabled","onRun"])):(n(),k(I,{key:3,class:"sm",variant:"ghost",pending:v.value===`stop:${t.name}`,disabled:!!v.value,confirm:"Stop it?",title:"Stops this container only. It stays stopped until something starts it.",onRun:d=>C(t,"stop")},{default:g(()=>[...e[29]||(e[29]=[l(" Stop ",-1)])]),_:1},8,["pending","disabled","onRun"])),!t.remote&&!t.job?(n(),k(I,{key:4,class:"sm",variant:"ghost",pending:v.value===`restart:${t.name}`,disabled:!!v.value,confirm:"Restart it?",onRun:d=>C(t,"restart")},{default:g(()=>[...e[30]||(e[30]=[l(" Restart ",-1)])]),_:1},8,["pending","disabled","onRun"])):u("",!0),y(m,{class:"btn sm ghost",to:`/deployments/${encodeURIComponent(S(c).id)}/logs?container=${encodeURIComponent(t.name)}`},{default:g(()=>[...e[31]||(e[31]=[l(" Logs ",-1)])]),_:1},8,["to"]),t.state==="running"&&!t.remote&&!t.job?(n(),k(m,{key:5,class:"btn sm ghost",to:`/deployments/${encodeURIComponent(S(c).id)}/terminal?container=${encodeURIComponent(t.name)}`},{default:g(()=>[...e[32]||(e[32]=[l(" Shell ",-1)])]),_:1},8,["to"])):u("",!0),t.state==="running"&&P(t)?(n(),o("a",{key:6,class:"btn sm ghost",href:P(t),target:"_blank",rel:"noreferrer"}," Open ",8,Ee)):u("",!0)])],4))),128))])])):(n(),o("p",Ve," No containers for this stack. If you expected some, check that their profile is one the spec selects — a service behind a profile that is not enabled is never started. "))])],64)):(n(),o("div",X,[...e[0]||(e[0]=[s("b",null,"Docker did not answer.",-1),s("p",null," Nothing below could be determined — this is not the same as nothing running. Check the daemon and the socket mount, then refresh. ",-1)])]))],64)):u("",!0)])}}}),Ye=G(Fe,[["__scopeId","data-v-e69ab573"]]);export{Ye as default};
|
|
2
|
-
//# sourceMappingURL=RuntimeTab-bWJuLJcn.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RuntimeTab-bWJuLJcn.js","sources":["../../src/views/tabs/RuntimeTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * What is actually running, and what Traefik was actually told about it.\n *\n * THE QUESTION THIS ANSWERS. \"The hostname does not work.\" Nothing in this UI could answer it: the\n * registry shows what was *submitted*, the logs show what the container *says*, and the reason a\n * request 404s is in neither — it is in the container's Traefik labels, which pstack never writes.\n *\n * The routing table is the direct answer to \"which URL maps to which port\". Traefik does **not** dial\n * `service_name:port`: its docker provider resolves the container's IP on the ingress network and\n * dials that, on the port from the `loadbalancer.server.port` label. That is why a service must be\n * attached to `preview-ingress`, why the port is the *container-internal* one, and why publishing a\n * host port is unnecessary. The `target` column shows the address Traefik assembled, so a missing\n * half is visible rather than inferred.\n *\n * Findings come first because they are the reason anyone opened this tab.\n */\nimport { computed, ref, watch } from 'vue';\nimport { sentence } from '../../composables/useFormat';\nimport { api, problem } from '../../api/client';\nimport type { RuntimeContainer, RuntimeResponse } from '../../api/types';\nimport { dep, varsQuery } from '../../composables/useDeployment';\nimport { usePolling } from '../../composables/usePolling';\nimport SkeletonList from '../../components/SkeletonList.vue';\nimport ActionButton from '../../components/ActionButton.vue';\nimport ErrorNote from '../../components/ErrorNote.vue';\nimport InfoHint from '../../components/InfoHint.vue';\nimport { toast } from '../../composables/useToasts';\nimport RefreshButton from '../../components/RefreshButton.vue';\n\nconst rt = ref<RuntimeResponse | null>(null);\nconst error = ref('');\nconst loading = ref(true);\n\nasync function load(): Promise<void> {\n const r = await api.get<RuntimeResponse>(\n `/api/deployments/${encodeURIComponent(dep.id)}/runtime${varsQuery.value}`,\n );\n loading.value = false;\n if (!r.ok) {\n error.value = problem(r, 'inspect what is running');\n return;\n }\n error.value = '';\n rt.value = r.body;\n}\n\nwatch(() => dep.id, () => { loading.value = true; void load(); }, { immediate: true });\n// Containers change state during a deploy; the poll pauses itself when the tab is hidden.\nusePolling(() => void load(), 8000);\n\nconst errors = computed(() => rt.value?.findings.filter((f) => f.level === 'error') ?? []);\nconst warns = computed(() => rt.value?.findings.filter((f) => f.level === 'warn') ?? []);\nconst infos = computed(() => rt.value?.findings.filter((f) => f.level === 'info') ?? []);\nconst running = computed(() => rt.value?.containers.filter((c) => c.state === 'running').length ?? 0);\n\n/**\n * Whether the node column exists at all — compose has no nodes to name.\n *\n * Hoisted rather than asked twice: the header and the cell were computing this separately, which is\n * how a header can end up describing a column the body did not render in the same position.\n */\nconst hasNode = computed(() => rt.value?.containers.some((c) => c.node) ?? false);\n\n/**\n * The part of a container's name worth reading in a table cell.\n *\n * Compose names a container `<stack>-<service>-<n>`. Swarm names a TASK\n * `<stack>_<service>.<slot>.<task id>` — long, and its first half repeats the service already\n * printed on the line above, in a column that under swarm has to share the row with the node and\n * the row actions. The tail is the half that identifies which task this is: the slot and the id\n * `docker service ps` prints. Compose names hold no dots and come back untouched.\n *\n * The full name stays in the cell's `title` rather than in the text. Truncating the text would have\n * cut the id — the one part worth copying — and nothing here needs the whole name by hand anyway:\n * Logs, Shell and the container actions all carry it themselves.\n */\nfunction taskOf(name: string): string {\n const p = name.split('.');\n return p.length === 3 ? `${p[1]}.${p[2]}` : name;\n}\n\n/**\n * One container action in flight, as `action:name`.\n *\n * A single ref rather than a per-row flag: two `docker restart`s racing on the same stack is not\n * something to make easy, and the readiness watch each one starts would fight the other.\n */\nconst busy = ref('');\n\nasync function act(c: RuntimeContainer, action: 'start' | 'stop' | 'restart'): Promise<void> {\n busy.value = `${action}:${c.name}`;\n const r = await api.post<{ note?: string }>(\n `/api/deployments/${encodeURIComponent(dep.id)}/containers/${encodeURIComponent(c.name)}/${action}${varsQuery.value}`,\n );\n busy.value = '';\n if (!r.ok) {\n // Docker's own words. \"Already started\" and \"no such container\" are different problems and it\n // says both better than this layer could.\n toast('error', problem(r, `${action} ${c.name}`));\n return;\n }\n toast('ok', r.body.note ?? `${c.name}: ${action} done.`);\n // Read the table back rather than assuming the new state: a container that exits again immediately\n // is exactly the case worth seeing, and the 8s poll would show it a beat later anyway.\n void load();\n}\n\n/**\n * The hostname Traefik actually routes to this container, or null.\n *\n * Built from the ROUTES rather than guessed from the container: a router's rule is the only thing\n * that decides what URL reaches it, and a link assembled from a service name and a domain would be\n * a plausible-looking 404. Pattern hosts (`HostRegexp`) are skipped for the same reason — there is\n * no single address to send someone to.\n */\nfunction urlFor(c: RuntimeContainer): string | null {\n const host = rt.value?.routes\n // Under swarm the labels live on the service, so a route names `app` where the container is a\n // task called `pr-1_app.1.<id>`. Matching the name alone left every swarm row without a link to\n // a router that does point at it. Both tasks of one service share the URL — Traefik balances.\n .filter((r) => r.container === c.name || r.container === c.service)\n .flatMap((r) => r.hosts)\n .find((h) => !h.startsWith('(pattern)'));\n return host ? `https://${host}` : null;\n}\n</script>\n\n<template>\n <div>\n <ErrorNote v-if=\"error\" :text=\"error\" title=\"Could not inspect this deployment.\" />\n <SkeletonList v-if=\"loading && !rt\" :rows=\"5\" />\n\n <template v-else-if=\"rt\">\n <!--\n `reachable: false` is not \"nothing is running\". The stack may be perfectly healthy while this\n process cannot reach the Docker socket, and reporting that as empty is how a UI tells someone\n their live preview is gone.\n -->\n <div v-if=\"!rt.reachable\" class=\"banner warn\">\n <b>Docker did not answer.</b>\n <p>\n Nothing below could be determined — this is not the same as nothing running. Check the\n daemon and the socket mount, then refresh.\n </p>\n </div>\n\n <template v-else>\n <!-- Findings first: this tab exists because something is not working. -->\n <div v-for=\"(f, i) in errors\" :key=\"`e${i}`\" class=\"banner failed\">\n <b>Blocking</b>\n <p>{{ f.message }}</p>\n </div>\n <div v-for=\"(f, i) in warns\" :key=\"`w${i}`\" class=\"banner warn\">\n <b>Worth checking</b>\n <p>{{ f.message }}</p>\n </div>\n <div v-for=\"(f, i) in infos\" :key=\"`i${i}`\" class=\"banner plain\">\n <p>{{ f.message }}</p>\n </div>\n <div v-if=\"!rt.findings.length && rt.containers.length\" class=\"banner ok\">\n <b>Nothing looks wrong.</b>\n <p>\n Every container is attached to the ingress network and every router has a rule and a port.\n If a hostname still fails, the next suspects are DNS and the certificate.\n </p>\n </div>\n\n <!-- ============================ routing ============================ -->\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Routing</h2>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n TLS: {{ rt.challenge === 'unknown' ? 'unknown' : rt.challenge }}\n <InfoHint label=\"why the TLS mode matters here\" align=\"end\">\n Under HTTP-01 every per-PR router needs <code>tls.certresolver=le</code>, because each\n hostname resolves its own certificate. Under DNS-01 that inverts — one always-on router\n holds the wildcard, and a per-PR certresolver makes each PR order its own and burn the\n weekly limit. Read from the running Traefik's own flags, not configured here.\n </InfoHint>\n </span>\n </div>\n\n <table v-if=\"rt.routes.length\" class=\"cards\">\n <thead>\n <tr>\n <th>URL</th>\n <th>\n Forwards to\n <InfoHint label=\"how a URL reaches a port\">\n Traefik's docker provider resolves the container's IP on\n <code>preview-ingress</code> and dials it on the port from\n <code>loadbalancer.server.port</code> — it does <em>not</em> use\n <code>service:port</code> DNS. So the port here is the one inside the container,\n and publishing a host port is unnecessary.\n </InfoHint>\n </th>\n <th>Router</th>\n <th>TLS</th>\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(r, i) in rt.routes\" :key=\"r.router\" :style=\"{ '--i': i }\">\n <td data-label=\"url\">\n <div v-for=\"h in r.hosts\" :key=\"h\">\n <a v-if=\"!h.startsWith('(pattern)')\" :href=\"`https://${h}`\" target=\"_blank\" rel=\"noreferrer\">\n {{ h }}\n </a>\n <span v-else class=\"mono mute\">{{ h }}</span>\n </div>\n <span v-if=\"!r.hosts.length\" class=\"mute\">no host in the rule</span>\n </td>\n <td data-label=\"forwards to\">\n <span v-if=\"r.target\" class=\"mono\">{{ r.target }}</span>\n <span v-else class=\"s-failed\">\n {{ r.port === null ? 'no port declared' : 'not on the ingress network' }}\n </span>\n <div class=\"mute\" style=\"font-size: var(--t-sm)\">{{ r.container }}</div>\n </td>\n <td data-label=\"router\">\n {{ r.router }}\n <div v-if=\"r.priority\" class=\"mute\" style=\"font-size: var(--t-sm)\">\n priority {{ r.priority }}\n </div>\n </td>\n <td data-label=\"tls\">\n <span v-if=\"r.tls\" class=\"badge ok\">{{ r.certresolver || 'inherited' }}</span>\n <span v-else class=\"badge off\">off</span>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!--\n The reported failure, stated where it will be looked for. pstack does not write these\n labels — the deployment's own compose file does — so the fix is in the user's file.\n -->\n <div v-else class=\"banner warn\">\n <b>No routes. Traefik has not been told about this deployment.</b>\n <p>\n Routes come from labels in <em>your</em> compose file, not from pstack and not from the\n <RouterLink to=\"/routing\">Routing</RouterLink> page (that is Traefik's file provider —\n middleware and TLS options, not per-PR routers). A reachable service needs four things:\n </p>\n <pre class=\"code\">labels:\n - traefik.enable=true # the host runs exposedbydefault=false\n - traefik.docker.network=preview-ingress # which network Traefik should dial\n - traefik.http.routers.<name>-${STACK}.rule=Host(`<name>-${STACK}.${PREVIEW_DOMAIN}`)\n - traefik.http.services.<name>-${STACK}.loadbalancer.server.port=80\nnetworks: [default, preview-ingress] # and preview-ingress: { external: true }</pre>\n <p class=\"mute\">\n Put <code>${STACK}</code> in the router and service names: Traefik's namespace is global\n across the host, so two deployments sharing a router name serve each other's containers.\n </p>\n </div>\n </section>\n\n <!-- ============================ containers ============================ -->\n <section class=\"panel\">\n <div class=\"phead\">\n <h2 class=\"section\">Containers</h2>\n <span class=\"grow\" />\n <span class=\"mute\" style=\"font-size: var(--t-sm)\">\n {{ running }} of {{ rt.containers.length }} running\n </span>\n <RefreshButton :run=\"load\" :busy=\"loading\" />\n </div>\n\n <table v-if=\"rt.containers.length\" class=\"cards\">\n <thead>\n <tr>\n <th>Service</th>\n <!-- Where, then how it is doing — the order the cells are in. They disagreed, so\n under swarm (the only time this column exists) every state read as a node. -->\n <th v-if=\"hasNode\">Node</th>\n <th>State</th>\n <th>Ports</th>\n <th>Networks</th>\n <th>Image</th>\n <th aria-label=\"Actions\" />\n </tr>\n </thead>\n <tbody class=\"stagger\">\n <tr v-for=\"(c, i) in rt.containers\" :key=\"c.id\" :style=\"{ '--i': i }\">\n <td class=\"name\" data-label=\"service\">\n {{ c.service ?? c.name }}\n <div class=\"mute task\" :title=\"c.name\">{{ taskOf(c.name) }}</div>\n </td>\n <td v-if=\"hasNode\" data-label=\"node\">\n {{ c.node ?? '—' }}\n <!-- A task on another node: listed from the manager, out of reach of exec/stop. -->\n <span v-if=\"c.remote\" class=\"badge\" title=\"runs on another swarm node — logs reach it through the manager; a shell and stop/start do not\">remote</span>\n </td>\n <td data-label=\"state\">\n <span :class=\"c.state === 'running' ? 's-ok' : 's-failed'\">{{ sentence(c.state) }}</span>\n <!--\n Health is only reported while it is RUNNING. Docker keeps the last probe result on\n a stopped container, so an exited one rendered \"Exited / Healthy\" — a stale reading\n presented beside the fact that contradicts it.\n -->\n <div\n v-if=\"c.health && c.state === 'running'\"\n class=\"mute\"\n style=\"font-size: var(--t-sm)\"\n :class=\"c.health === 'healthy' ? 's-ok' : 's-failed'\"\n >\n {{ sentence(c.health) }}\n </div>\n </td>\n <td data-label=\"ports\">\n <div v-for=\"p in c.ports\" :key=\"`${p.containerPort}/${p.protocol}`\">\n <span class=\"mono\">{{ p.containerPort }}</span>\n <span v-if=\"p.hostPort\" class=\"mute\"> ← host {{ p.hostPort }}</span>\n </div>\n <span v-if=\"!c.ports.length\" class=\"mute\">none exposed</span>\n </td>\n <td data-label=\"networks\">\n <div v-for=\"n in c.networks\" :key=\"n\">\n <span :class=\"n === 'preview-ingress' ? 's-ok' : ''\">{{ n }}</span>\n </div>\n <div v-if=\"c.ingressIp\" class=\"mute mono\" style=\"font-size: var(--t-sm)\">\n {{ c.ingressIp }}\n </div>\n </td>\n <td class=\"dim\" data-label=\"image\">{{ c.image }}</td>\n <!--\n The three things anyone wants from a container row, without hunting for the tab\n that does it. Open goes to the URL Traefik actually assembled for THIS container\n (see `urlFor`), so it is absent rather than wrong when no router points here.\n -->\n <td class=\"row-actions\" data-label=\"\">\n <!--\n One container, not the service and not the stack. Start appears only when it is\n not running and Stop only when it is, so the control on screen is the one that\n would do something. Both destructive ones confirm in place.\n -->\n <!-- None of these reach a task on another node; docker's verbs are node-local. -->\n <!-- And none of them reach a one-shot job's row, which stands for the SERVICE:\n there is no container behind its name, so the verbs could only fail. -->\n <span v-if=\"c.job\" class=\"badge\" title=\"a one-shot job — it runs to completion; there is no long-lived container to start, stop, or open a shell in\">one-shot</span>\n <span v-else-if=\"c.remote\" class=\"mute\" style=\"font-size: var(--t-sm)\" title=\"runs on another swarm node — redeploy the stack, or act on the worker itself\">\n on {{ c.node }}\n </span>\n <ActionButton\n v-else-if=\"c.state !== 'running'\"\n class=\"sm\"\n variant=\"ghost\"\n :pending=\"busy === `start:${c.name}`\"\n :disabled=\"!!busy\"\n confirm=\"Start it?\"\n @run=\"act(c, 'start')\"\n >\n Start\n </ActionButton>\n <ActionButton\n v-else\n class=\"sm\"\n variant=\"ghost\"\n :pending=\"busy === `stop:${c.name}`\"\n :disabled=\"!!busy\"\n confirm=\"Stop it?\"\n title=\"Stops this container only. It stays stopped until something starts it.\"\n @run=\"act(c, 'stop')\"\n >\n Stop\n </ActionButton>\n <ActionButton\n v-if=\"!c.remote && !c.job\"\n class=\"sm\"\n variant=\"ghost\"\n :pending=\"busy === `restart:${c.name}`\"\n :disabled=\"!!busy\"\n confirm=\"Restart it?\"\n @run=\"act(c, 'restart')\"\n >\n Restart\n </ActionButton>\n <RouterLink\n class=\"btn sm ghost\"\n :to=\"`/deployments/${encodeURIComponent(dep.id)}/logs?container=${encodeURIComponent(c.name)}`\"\n >\n Logs\n </RouterLink>\n <RouterLink\n v-if=\"c.state === 'running' && !c.remote && !c.job\"\n class=\"btn sm ghost\"\n :to=\"`/deployments/${encodeURIComponent(dep.id)}/terminal?container=${encodeURIComponent(c.name)}`\"\n >\n Shell\n </RouterLink>\n <!--\n Only while running: the router label survives a stopped container, so the URL\n still exists and answers 502. A link that is certain to fail is worse than no link.\n -->\n <a\n v-if=\"c.state === 'running' && urlFor(c)\"\n class=\"btn sm ghost\"\n :href=\"urlFor(c)!\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n Open\n </a>\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"mute\">\n No containers for this stack. If you expected some, check that their profile is one the\n spec selects — a service behind a profile that is not enabled is never started.\n </p>\n </section>\n </template>\n </template>\n </div>\n</template>\n\n<style scoped>\n/*\n * The second line of the service cell: the task, under the service it belongs to.\n *\n * The cap is a declared ceiling on what this line may bid for in the auto table layout, not a fix\n * for an overflow — `td.name` already breaks. Under swarm this column shares the row with the node\n * column and the row actions: `taskOf` makes the line's length independent of the stack and service\n * names, and this bounds what is left of it. Past the ceiling it wraps rather than widening the\n * column.\n */\n.task {\n font-size: var(--t-sm);\n max-width: 30ch;\n}\n</style>\n"],"names":["rt","ref","error","loading","load","r","api","dep","varsQuery","problem","watch","usePolling","errors","computed","_a","f","warns","infos","running","c","hasNode","taskOf","name","p","busy","act","action","toast","urlFor","host","h","_createElementBlock","_createBlock","ErrorNote","SkeletonList","_Fragment","_openBlock","_renderList","i","_cache","_createElementVNode","_toDisplayString","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_createVNode","InfoHint","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_10","_hoisted_9","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_component_RouterLink","_hoisted_22","_hoisted_23","_hoisted_24","RefreshButton","_hoisted_25","_hoisted_27","_hoisted_28","_createTextVNode","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_normalizeClass","_unref","sentence","_hoisted_33","_hoisted_34","_hoisted_35","_hoisted_36","_hoisted_37","n","_hoisted_38","_hoisted_39","_hoisted_40","_hoisted_41","_hoisted_42","ActionButton","$event","_hoisted_43","_hoisted_44","_hoisted_1"],"mappings":"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"}
|