okengine 0.2.2 → 0.2.4
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/README.md +30 -4
- package/docs/spec/example.md +12 -3
- package/docs/spec/four-applications.md +10 -3
- package/package.json +9 -40
- package/src/cli/dev-app-runner.ts +45 -6
- package/src/cli/dev.ts +204 -23
- package/src/cli/docker-cli.test.ts +10 -8
- package/src/cli/docker.ts +18 -7
- package/src/cli/doctor.ts +3 -18
- package/src/cli/hero-meta.test.ts +85 -0
- package/src/cli/hero-meta.ts +252 -0
- package/src/cli/index.ts +4 -1
- package/src/cli/json-out.test.ts +1 -1
- package/src/cli/load-config.images.test.ts +126 -0
- package/src/cli/load-config.ts +145 -2
- package/src/cli/ports.test.ts +51 -0
- package/src/cli/ports.ts +81 -0
- package/src/cli/registry.help.test.ts +18 -0
- package/src/cli/registry.ts +0 -4
- package/src/cli/safe-defaults.test.ts +3 -3
- package/src/config/define-config.test.ts +61 -0
- package/src/config/index.ts +89 -6
- package/src/config/resolve-driver.test.ts +30 -0
- package/src/console/server/app.ts +2 -1
- package/src/console/server/flows.ts +4 -1
- package/src/console/server/index.ts +5 -0
- package/src/console/server/operator-db.test.ts +213 -0
- package/src/console/server/operator-db.ts +470 -0
- package/src/console/server/plugin.ts +20 -0
- package/src/console/server/serve.ts +47 -1
- package/src/console/server/state.ts +25 -2
- package/src/console/ui/dist/assets/{index-B71Yl_SS.js → index-Dy4jht9P.js} +3 -3
- package/src/console/ui/dist/assets/{panel-overview-Bd48d9km.js → panel-overview-Dt_AeXgd.js} +1 -1
- package/src/console/ui/dist/assets/{panel-runs-BwsWqKeB.js → panel-runs-DGstFHeq.js} +1 -1
- package/src/console/ui/dist/assets/{panel-signals-9najbZY2.js → panel-signals-DzEa2Fnt.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-OHkP2pDp.js → panel-store-BJkbNgxx.js} +1 -1
- package/src/console/ui/dist/assets/{panel-traces-tn2JoY8U.js → panel-traces-BaRVO3gM.js} +1 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +3 -0
- package/src/console/ui/dist/favicon.svg +7 -0
- package/src/console/ui/dist/index.html +3 -2
- package/src/console/ui/shell/App.tsx +44 -4
- package/src/console/ui/shell/components/oke-logo.tsx +40 -0
- package/src/console/ui/shell/index.html +1 -0
- package/src/console/ui/shell/layout/Shell.tsx +2 -3
- package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +8 -0
- package/src/console/ui/shell/public/favicon.svg +7 -0
- package/src/console/ui/shell/setup/Wizard.tsx +5 -2
- package/src/docker/compose.ts +45 -14
- package/src/docker/derive.ts +32 -10
- package/src/docker/docker.test.ts +28 -4
- package/src/docker/dockerfile.integration.test.ts +3 -1
- package/src/docker/index.ts +10 -0
- package/src/docker/stack-id.test.ts +86 -0
- package/src/docker/stack-id.ts +108 -0
- package/src/docker/stack.integration.test.ts +17 -8
- package/src/docker/types.ts +20 -1
- package/src/kernel/app.ts +39 -8
- package/src/kernel/boot-bind/store.test.ts +60 -0
- package/src/kernel/boot-bind/store.ts +142 -12
- package/src/kernel/boot.ts +28 -4
- package/src/mcp/server.ts +99 -57
- package/src/runtime/dev-request-log.test.ts +33 -0
- package/src/runtime/dev-request-log.ts +130 -0
- package/src/term.test.ts +98 -5
- package/src/term.ts +287 -6
- package/src/console/ui/dist/assets/style-Cnl7WLya.css +0 -3
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-outline-style:solid;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-800:oklch(47.3% .137 46.201);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{inset-block:0}.top-0{top:0}.right-\[min\(28\%\,20rem\)\]{right:min(28%,20rem)}.left-\[min\(28\%\,20rem\)\]{left:min(28%,20rem)}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.col-span-2{grid-column:span 2/span 2}.mx-auto{margin-inline:auto}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-auto{margin-top:auto}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mr-auto{margin-right:auto}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-12{height:calc(var(--spacing) * 12)}.h-16{height:calc(var(--spacing) * 16)}.h-40{height:calc(var(--spacing) * 40)}.h-64{height:calc(var(--spacing) * 64)}.h-\[calc\(100vh-3rem\)\]{height:calc(100vh - 3rem)}.h-auto{height:auto}.h-full{height:100%}.max-h-48{max-height:calc(var(--spacing) * 48)}.min-h-0{min-height:0}.min-h-2{min-height:calc(var(--spacing) * 2)}.min-h-8{min-height:calc(var(--spacing) * 8)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-24{min-height:calc(var(--spacing) * 24)}.w-2{width:calc(var(--spacing) * 2)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-16{width:calc(var(--spacing) * 16)}.w-24{width:calc(var(--spacing) * 24)}.w-56{width:calc(var(--spacing) * 56)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-80{width:calc(var(--spacing) * 80)}.w-auto{width:auto}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-prose{max-width:65ch}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-2{min-width:calc(var(--spacing) * 2)}.min-w-8{min-width:calc(var(--spacing) * 8)}.min-w-\[4px\]{min-width:4px}.min-w-\[8rem\]{min-width:8rem}.min-w-\[12rem\]{min-width:12rem}.min-w-\[16rem\]{min-width:16rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.basis-full{flex-basis:100%}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[minmax\(0\,8rem\)_1fr\]{grid-template-columns:minmax(0,8rem) 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0{gap:0}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-px{gap:1px}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-1{row-gap:var(--spacing)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-\[var\(--oke-line\)\]>:not(:last-child)){border-color:var(--oke-line)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:2147483647px}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-\[var\(--oke-accent\)\]{border-color:var(--oke-accent)}.border-\[var\(--oke-external\)\]{border-color:var(--oke-external)}.border-\[var\(--oke-line\)\],.border-\[var\(--oke-line\)\]\/50{border-color:var(--oke-line)}@supports (color:color-mix(in lab, red, red)){.border-\[var\(--oke-line\)\]\/50{border-color:color-mix(in oklab, var(--oke-line) 50%, transparent)}}.border-\[var\(--oke-muted\)\]{border-color:var(--oke-muted)}.bg-\[color-mix\(in_oklab\,var\(--oke-accent\)_55\%\,transparent\)\]{background-color:var(--oke-accent)}@supports (color:color-mix(in lab, red, red)){.bg-\[color-mix\(in_oklab\,var\(--oke-accent\)_55\%\,transparent\)\]{background-color:color-mix(in oklab,var(--oke-accent) 55%,transparent)}}.bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_3\%\,transparent\)\]{background-color:var(--oke-fg)}@supports (color:color-mix(in lab, red, red)){.bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_3\%\,transparent\)\]{background-color:color-mix(in oklab,var(--oke-fg) 3%,transparent)}}.bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_6\%\,transparent\)\]{background-color:var(--oke-fg)}@supports (color:color-mix(in lab, red, red)){.bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_6\%\,transparent\)\]{background-color:color-mix(in oklab,var(--oke-fg) 6%,transparent)}}.bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_35\%\,transparent\)\]{background-color:var(--oke-fg)}@supports (color:color-mix(in lab, red, red)){.bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_35\%\,transparent\)\]{background-color:color-mix(in oklab,var(--oke-fg) 35%,transparent)}}.bg-\[var\(--oke-accent\)\],.bg-\[var\(--oke-accent\)\]\/15{background-color:var(--oke-accent)}@supports (color:color-mix(in lab, red, red)){.bg-\[var\(--oke-accent\)\]\/15{background-color:color-mix(in oklab, var(--oke-accent) 15%, transparent)}}.bg-\[var\(--oke-bg\)\]{background-color:var(--oke-bg)}.bg-\[var\(--oke-danger\)\],.bg-\[var\(--oke-danger\)\]\/10{background-color:var(--oke-danger)}@supports (color:color-mix(in lab, red, red)){.bg-\[var\(--oke-danger\)\]\/10{background-color:color-mix(in oklab, var(--oke-danger) 10%, transparent)}}.bg-\[var\(--oke-external\)\]{background-color:var(--oke-external)}.bg-\[var\(--oke-fg\)\]{background-color:var(--oke-fg)}.bg-\[var\(--oke-line\)\],.bg-\[var\(--oke-line\)\]\/40{background-color:var(--oke-line)}@supports (color:color-mix(in lab, red, red)){.bg-\[var\(--oke-line\)\]\/40{background-color:color-mix(in oklab, var(--oke-line) 40%, transparent)}}.bg-black\/20{background-color:#0003}@supports (color:color-mix(in lab, red, red)){.bg-black\/20{background-color:color-mix(in oklab, var(--color-black) 20%, transparent)}}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab, var(--color-black) 50%, transparent)}}.bg-transparent{background-color:#0000}.fill-\[var\(--oke-fg\)\]{fill:var(--oke-fg)}.fill-\[var\(--oke-muted\)\]{fill:var(--oke-muted)}.p-0{padding:0}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-left{text-align:left}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-10{--tw-leading:calc(var(--spacing) * 10);line-height:calc(var(--spacing) * 10)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.2em\]{--tw-tracking:.2em;letter-spacing:.2em}.tracking-\[0\.14em\]{--tw-tracking:.14em;letter-spacing:.14em}.tracking-\[0\.15em\]{--tw-tracking:.15em;letter-spacing:.15em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[var\(--oke-accent\)\]{color:var(--oke-accent)}.text-\[var\(--oke-danger\)\]{color:var(--oke-danger)}.text-\[var\(--oke-external\)\]{color:var(--oke-external)}.text-\[var\(--oke-fg\)\]{color:var(--oke-fg)}.text-\[var\(--oke-muted\)\]{color:var(--oke-muted)}.text-\[var\(--oke-warn\,var\(--oke-muted\)\)\]{color:var(--oke-warn,var(--oke-muted))}.text-amber-800{color:var(--color-amber-800)}.text-black{color:var(--color-black)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.decoration-\[var\(--oke-line\)\]{-webkit-text-decoration-color:var(--oke-line);-webkit-text-decoration-color:var(--oke-line);text-decoration-color:var(--oke-line)}.underline-offset-2{text-underline-offset:2px}.opacity-40{opacity:.4}.opacity-70{opacity:.7}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-\[var\(--oke-danger\)\]{outline-color:var(--oke-danger)}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.placeholder\:text-\[var\(--oke-muted\)\]::placeholder{color:var(--oke-muted)}@media (hover:hover){.hover\:bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_3\%\,transparent\)\]:hover{background-color:var(--oke-fg)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-\[color-mix\(in_oklab\,var\(--oke-fg\)_3\%\,transparent\)\]:hover{background-color:color-mix(in oklab,var(--oke-fg) 3%,transparent)}}.hover\:bg-\[var\(--oke-line\)\]\/20:hover{background-color:var(--oke-line)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-\[var\(--oke-line\)\]\/20:hover{background-color:color-mix(in oklab, var(--oke-line) 20%, transparent)}}.hover\:bg-\[var\(--oke-line\)\]\/40:hover{background-color:var(--oke-line)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-\[var\(--oke-line\)\]\/40:hover{background-color:color-mix(in oklab, var(--oke-line) 40%, transparent)}}.hover\:text-\[var\(--oke-fg\)\]:hover{color:var(--oke-fg)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:not-sr-only:focus{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.focus\:absolute:focus{position:absolute}.focus\:top-2:focus{top:calc(var(--spacing) * 2)}.focus\:left-2:focus{left:calc(var(--spacing) * 2)}.focus\:z-50:focus{z-index:50}.focus\:bg-\[var\(--oke-bg\)\]:focus{background-color:var(--oke-bg)}.focus\:px-3:focus{padding-inline:calc(var(--spacing) * 3)}.focus\:py-2:focus{padding-block:calc(var(--spacing) * 2)}.disabled\:opacity-40:disabled{opacity:.4}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (width>=48rem){.md\:max-w-xs{max-width:var(--container-xs)}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,1\.4fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,1.4fr)}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,1\.5fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,1.5fr)}}@media (width>=64rem){.lg\:w-80{width:calc(var(--spacing) * 80)}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-\[1fr_1fr_1\.2fr\]{grid-template-columns:1fr 1fr 1.2fr}.lg\:grid-cols-\[14rem_1fr_18rem\]{grid-template-columns:14rem 1fr 18rem}.lg\:grid-cols-\[minmax\(0\,1\.2fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1.2fr) minmax(0,1fr)}.lg\:flex-row{flex-direction:row}.lg\:border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.lg\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.lg\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.lg\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}}}:root{--oke-bg:#0e1116;--oke-fg:#e8eaed;--oke-muted:#9aa3b2;--oke-line:var(--oke-fg)}@supports (color:color-mix(in lab, red, red)){:root{--oke-line:color-mix(in oklab, var(--oke-fg) 12%, transparent)}}:root{--oke-accent:#3d9a6a;--oke-external:#d97706;--oke-danger:#c44b4b;--oke-font:"IBM Plex Sans", "Segoe UI", sans-serif;--oke-mono:"IBM Plex Mono", ui-monospace, monospace;--oke-dim:.38}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:400 600;font-display:swap;src:local(IBM Plex Sans)}html,body,#root{height:100%}body{font-family:var(--oke-font);background:radial-gradient(1200px 600px at 10% -10%, #1a2433 0%, transparent 55%), radial-gradient(900px 500px at 100% 0%, #13201a 0%, transparent 50%), var(--oke-bg);color:var(--oke-fg);font-variant-numeric:tabular-nums;margin:0}*{box-sizing:border-box}a{color:inherit;text-decoration:none}:focus-visible{outline:2px solid var(--oke-accent);outline-offset:2px}@media (prefers-reduced-motion:reduce){*,:before,:after{transition:none!important;animation:none!important}}.sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}
|
|
3
|
+
/*$vite$:1*/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
|
|
2
|
+
<rect width="96" height="96" rx="20" fill="#0D0D0D"/>
|
|
3
|
+
<path
|
|
4
|
+
d="M70.2 74.5C68.5 74.75 66.6 74.88 64.5 74.88H32.1C27 74.88 23 74.15 20.1 72.7C17.2 71.25 15.1 69.05 13.9 66.15C12.7 63.25 12.1 59.65 12.1 55.35V30.15C12.1 25.95 12.7 22.4 13.9 19.55C15.1 16.65 17.15 14.45 20 13C22.9 11.45 26.95 10.7 32.1 10.7H64.5C66.6 10.7 68.5 10.83 70.2 11.08V21.05H83.1C83.95 23.6 84.4 26.6 84.4 30.15V55.35C84.4 58.75 84 61.75 83.2 64.3H70.2V74.5ZM35.3 64.4H61C63.7 64.4 65.75 63.7 67.2 62.3C68.75 60.9 69.5 59 69.5 56.6V28.85C69.5 26.75 68.75 24.95 67.2 23.5C65.65 21.95 63.55 21.2 61 21.2H35.3C32.7 21.2 30.55 21.9 28.95 23.3C27.35 24.7 26.55 26.55 26.55 28.85V56.6C26.55 59.05 27.3 61 28.85 62.4C30.45 63.7 32.6 64.4 35.3 64.4Z"
|
|
5
|
+
fill="#FFFFFF"
|
|
6
|
+
/>
|
|
7
|
+
</svg>
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
6
7
|
<title>oke Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Dy4jht9P.js"></script>
|
|
8
9
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CNC7AqOf.js">
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/style-
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/style-C8MxEWPd.css">
|
|
10
11
|
</head>
|
|
11
12
|
<body>
|
|
12
13
|
<div id="root"></div>
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { useQuery } from "@tanstack/react-query";
|
|
6
6
|
import { Outlet } from "@tanstack/react-router";
|
|
7
|
-
import {
|
|
7
|
+
import { useEffect } from "react";
|
|
8
|
+
import { consoleCalls, setAccessToken } from "./client.ts";
|
|
8
9
|
import { Shell } from "./layout/Shell.tsx";
|
|
9
10
|
import { SetupWizard } from "./setup/Wizard.tsx";
|
|
10
11
|
|
|
@@ -16,18 +17,34 @@ export function App() {
|
|
|
16
17
|
queryKey: ["console.setup.status"],
|
|
17
18
|
queryFn: async () => {
|
|
18
19
|
const res = await consoleCalls.setupStatus();
|
|
19
|
-
if (res.error)
|
|
20
|
-
|
|
20
|
+
if (res.error) {
|
|
21
|
+
if (res.error.code === "Unauthorized") setAccessToken(null);
|
|
22
|
+
throw new Error(res.error.code);
|
|
23
|
+
}
|
|
24
|
+
return res.data as { setupClosed: boolean; claimRequired: boolean };
|
|
21
25
|
},
|
|
26
|
+
retry: 2,
|
|
22
27
|
});
|
|
23
28
|
|
|
29
|
+
// Stale tokens after a process restart block nothing useful — clear on claim.
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (status.data?.claimRequired) {
|
|
32
|
+
setAccessToken(null);
|
|
33
|
+
}
|
|
34
|
+
}, [status.data?.claimRequired]);
|
|
35
|
+
|
|
24
36
|
const me = useQuery({
|
|
25
37
|
queryKey: ["console.session.me"],
|
|
26
38
|
enabled: status.data?.setupClosed === true,
|
|
27
39
|
retry: false,
|
|
28
40
|
queryFn: async () => {
|
|
29
41
|
const res = await consoleCalls.sessionMe();
|
|
30
|
-
if (res.error)
|
|
42
|
+
if (res.error) {
|
|
43
|
+
if (res.error.code === "Unauthorized" || res.error.code === "AuthFailed") {
|
|
44
|
+
setAccessToken(null);
|
|
45
|
+
}
|
|
46
|
+
throw new Error(res.error.code);
|
|
47
|
+
}
|
|
31
48
|
return res.data;
|
|
32
49
|
},
|
|
33
50
|
});
|
|
@@ -40,6 +57,29 @@ export function App() {
|
|
|
40
57
|
);
|
|
41
58
|
}
|
|
42
59
|
|
|
60
|
+
if (status.isError) {
|
|
61
|
+
return (
|
|
62
|
+
<main className="grid h-full place-items-center px-6 text-center">
|
|
63
|
+
<div className="flex max-w-md flex-col gap-2">
|
|
64
|
+
<p className="text-[var(--oke-fg)]">Console unreachable</p>
|
|
65
|
+
<p className="text-sm text-[var(--oke-muted)]">
|
|
66
|
+
{status.error instanceof Error
|
|
67
|
+
? status.error.message
|
|
68
|
+
: "Could not load setup status. Is `oke dev` still running?"}
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
</main>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (status.data?.setupClosed && me.isLoading) {
|
|
76
|
+
return (
|
|
77
|
+
<main className="grid h-full place-items-center text-[var(--oke-muted)]">
|
|
78
|
+
Loading…
|
|
79
|
+
</main>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
43
83
|
if (!status.data?.setupClosed || me.isError || !me.data) {
|
|
44
84
|
return (
|
|
45
85
|
<main>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OKE wordmark logo (Console chrome).
|
|
3
|
+
*
|
|
4
|
+
* Mirrored from `site/components/oke-logo.tsx` — the marketing site package
|
|
5
|
+
* cannot be imported into the `okengine` Console SPA.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SVGProps } from "react";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* OKE wordmark for Console navigation and setup.
|
|
12
|
+
*
|
|
13
|
+
* @param props - Standard SVG attributes; `className` controls size and color
|
|
14
|
+
*/
|
|
15
|
+
export function OkeLogo({ className, ...props }: SVGProps<SVGSVGElement>) {
|
|
16
|
+
return (
|
|
17
|
+
<svg
|
|
18
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
19
|
+
viewBox="0 0 327 87"
|
|
20
|
+
fill="none"
|
|
21
|
+
role="img"
|
|
22
|
+
aria-label="OKE"
|
|
23
|
+
className={className}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
<path
|
|
27
|
+
d="M231.08 84.672V0H320.648V13.824H250.088V33.696H303.944V47.952H250.088V70.848H321.224L326.984 83.088C326.984 83.472 325.688 83.856 323.096 84.24C320.504 84.528 316.04 84.672 309.704 84.672H231.08Z"
|
|
28
|
+
fill="currentColor"
|
|
29
|
+
/>
|
|
30
|
+
<path
|
|
31
|
+
d="M136.638 13.672V36.864H144.702C146.622 36.864 148.206 36.624 149.454 36.144C150.702 35.664 151.854 34.896 152.91 33.84C155.598 31.152 158.67 27.792 162.126 23.76C165.582 19.728 168.942 15.6 172.206 11.376C175.566 7.05599 178.446 3.264 180.846 0H204.174L198.27 8.64C195.582 11.424 192.702 14.448 189.63 17.712C186.558 20.976 183.534 24.192 180.558 27.36C177.582 30.432 174.894 33.168 172.494 35.568C170.094 37.872 168.318 39.552 167.166 40.608C169.758 41.472 172.11 42.576 174.222 43.92C176.43 45.264 179.118 47.232 182.286 49.824C184.686 51.744 186.99 53.712 189.198 55.728C191.502 57.744 194.142 60.144 197.118 62.928C200.478 66 203.646 68.256 206.622 69.696C209.598 71.136 212.094 72.048 214.11 72.432C216.222 72.816 217.566 73.008 218.142 73.008L215.262 84.672C214.974 84.96 214.11 85.248 212.67 85.536C211.23 85.92 209.166 86.112 206.478 86.112C201.774 86.112 197.694 85.152 194.238 83.232C190.782 81.312 187.374 78.72 184.014 75.456C180.366 71.616 175.95 67.2 170.766 62.208C168.366 59.712 165.918 57.648 163.422 56.016C160.926 54.384 158.094 53.136 154.926 52.272C151.854 51.408 148.014 50.976 143.406 50.976H136.638V70.672H117.63V13.672H136.638Z"
|
|
32
|
+
fill="currentColor"
|
|
33
|
+
/>
|
|
34
|
+
<path
|
|
35
|
+
d="M75.865 84.191C73.608 84.512 71.12 84.672 68.4 84.672H26.352C19.632 84.672 14.352 83.712 10.512 81.792C6.672 79.872 3.936 76.992 2.304 73.152C0.768001 69.312 0 64.56 0 58.896V25.632C0 20.064 0.768001 15.408 2.304 11.664C3.84 7.824 6.528 4.944 10.368 3.024C14.208 1.008 19.536 0 26.352 0H68.4C71.135 0 73.623 0.166992 75.865 0.500992V13.672H92.928C94.048 17.033 94.608 21.019 94.608 25.632V58.896C94.608 63.392 94.078 67.318 93.017 70.672H75.865V84.191ZM30.528 70.848H64.368C67.92 70.848 70.656 69.936 72.576 68.112C74.592 66.288 75.6 63.792 75.6 60.624V23.904C75.6 21.12 74.592 18.768 72.576 16.848C70.56 14.832 67.824 13.824 64.368 13.824H30.528C27.072 13.824 24.288 14.736 22.176 16.56C20.064 18.384 19.008 20.832 19.008 23.904V60.624C19.008 63.888 20.016 66.432 22.032 68.256C24.144 69.984 26.976 70.848 30.528 70.848Z"
|
|
36
|
+
fill="currentColor"
|
|
37
|
+
/>
|
|
38
|
+
</svg>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Link, Outlet, useRouterState } from "@tanstack/react-router";
|
|
6
6
|
import { clsx } from "clsx";
|
|
7
7
|
import type { ReactNode } from "react";
|
|
8
|
+
import { OkeLogo } from "../components/oke-logo.tsx";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Primary nav — eight elements, then cross-cutting panels (console §9).
|
|
@@ -52,9 +53,7 @@ export function Shell({ children }: { readonly children?: ReactNode }) {
|
|
|
52
53
|
aria-label="Console"
|
|
53
54
|
className="flex h-12 shrink-0 items-center gap-1 overflow-x-auto border-b border-[var(--oke-line)] px-3"
|
|
54
55
|
>
|
|
55
|
-
<
|
|
56
|
-
oke
|
|
57
|
-
</span>
|
|
56
|
+
<OkeLogo className="mr-3 h-4 w-auto shrink-0 text-[var(--oke-fg)]" />
|
|
58
57
|
{NAV.map((item) =>
|
|
59
58
|
item.enabled ? (
|
|
60
59
|
<Link
|
|
@@ -188,6 +188,14 @@ export function OverviewPanel() {
|
|
|
188
188
|
</header>
|
|
189
189
|
|
|
190
190
|
<main id="overview-main" className="flex flex-col gap-8 px-6 py-6">
|
|
191
|
+
{!loading && !manifestQuery.data ? (
|
|
192
|
+
<p className="text-sm text-[var(--oke-muted)]">
|
|
193
|
+
No Manifest yet — save a Flow under{" "}
|
|
194
|
+
<code className="font-mono text-[var(--oke-fg)]">src/</code> and
|
|
195
|
+
the Console will refresh.
|
|
196
|
+
</p>
|
|
197
|
+
) : null}
|
|
198
|
+
|
|
191
199
|
{view.hasDeclaredSlos ? (
|
|
192
200
|
<BudgetStrip
|
|
193
201
|
label="Error budgets"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
|
|
2
|
+
<rect width="96" height="96" rx="20" fill="#0D0D0D"/>
|
|
3
|
+
<path
|
|
4
|
+
d="M70.2 74.5C68.5 74.75 66.6 74.88 64.5 74.88H32.1C27 74.88 23 74.15 20.1 72.7C17.2 71.25 15.1 69.05 13.9 66.15C12.7 63.25 12.1 59.65 12.1 55.35V30.15C12.1 25.95 12.7 22.4 13.9 19.55C15.1 16.65 17.15 14.45 20 13C22.9 11.45 26.95 10.7 32.1 10.7H64.5C66.6 10.7 68.5 10.83 70.2 11.08V21.05H83.1C83.95 23.6 84.4 26.6 84.4 30.15V55.35C84.4 58.75 84 61.75 83.2 64.3H70.2V74.5ZM35.3 64.4H61C63.7 64.4 65.75 63.7 67.2 62.3C68.75 60.9 69.5 59 69.5 56.6V28.85C69.5 26.75 68.75 24.95 67.2 23.5C65.65 21.95 63.55 21.2 61 21.2H35.3C32.7 21.2 30.55 21.9 28.95 23.3C27.35 24.7 26.55 26.55 26.55 28.85V56.6C26.55 59.05 27.3 61 28.85 62.4C30.45 63.7 32.6 64.4 35.3 64.4Z"
|
|
5
|
+
fill="#FFFFFF"
|
|
6
|
+
/>
|
|
7
|
+
</svg>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
7
7
|
import { useState, type FormEvent } from "react";
|
|
8
8
|
import { consoleCalls, setAccessToken } from "../client.ts";
|
|
9
|
+
import { OkeLogo } from "../components/oke-logo.tsx";
|
|
9
10
|
import { Button, Field, Input } from "../components/ui.tsx";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -55,7 +56,8 @@ export function SetupWizard() {
|
|
|
55
56
|
if (status.data?.setupClosed) {
|
|
56
57
|
return (
|
|
57
58
|
<section className="mx-auto flex max-w-md flex-col gap-4 px-6 py-16">
|
|
58
|
-
<
|
|
59
|
+
<OkeLogo className="h-7 w-auto text-[var(--oke-fg)]" />
|
|
60
|
+
<h1 className="text-2xl font-semibold tracking-tight">Console</h1>
|
|
59
61
|
<p className="text-[var(--oke-muted)]">
|
|
60
62
|
Setup is closed. Sign in with an existing operator account.
|
|
61
63
|
</p>
|
|
@@ -73,10 +75,11 @@ export function SetupWizard() {
|
|
|
73
75
|
return (
|
|
74
76
|
<section className="mx-auto flex max-w-md flex-col gap-6 px-6 py-16">
|
|
75
77
|
<header className="flex flex-col gap-2">
|
|
78
|
+
<OkeLogo className="h-7 w-auto text-[var(--oke-fg)]" />
|
|
76
79
|
<p className="text-xs uppercase tracking-[0.2em] text-[var(--oke-muted)]">
|
|
77
80
|
First admin
|
|
78
81
|
</p>
|
|
79
|
-
<h1 className="text-3xl font-semibold tracking-tight">
|
|
82
|
+
<h1 className="text-3xl font-semibold tracking-tight">Console</h1>
|
|
80
83
|
<p className="text-[var(--oke-muted)]">
|
|
81
84
|
Enter the claim code printed once to the boot log. This wizard closes
|
|
82
85
|
permanently after the first operator.
|
package/src/docker/compose.ts
CHANGED
|
@@ -21,12 +21,32 @@ import type {
|
|
|
21
21
|
ServiceCredentials,
|
|
22
22
|
ServiceSpec,
|
|
23
23
|
} from "./types.ts";
|
|
24
|
+
import { DEFAULT_DOCKER_DIR } from "./types.ts";
|
|
24
25
|
import { generateCredentials } from "./credentials.ts";
|
|
26
|
+
import { hostPortForInstance } from "./stack-id.ts";
|
|
25
27
|
import { APP_PORT } from "../runtime/types.ts";
|
|
26
28
|
|
|
27
29
|
/** Canonical layer-4 filename — never written by derivation. */
|
|
28
30
|
export const COMPOSE_OVERRIDE = "compose.override.yml";
|
|
29
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Relative path refs for compose files living under {@link DeriveOptions.composeDir}.
|
|
34
|
+
*
|
|
35
|
+
* @param composeDir - Directory relative to project root (`docker` or `.`)
|
|
36
|
+
*/
|
|
37
|
+
export function composePathRefs(composeDir: string = DEFAULT_DOCKER_DIR): {
|
|
38
|
+
readonly envFile: string;
|
|
39
|
+
readonly buildContext: string;
|
|
40
|
+
readonly dockerfile: string;
|
|
41
|
+
} {
|
|
42
|
+
const flat = composeDir === "." || composeDir === "";
|
|
43
|
+
return {
|
|
44
|
+
envFile: flat ? ".env.stack" : "../.env.stack",
|
|
45
|
+
buildContext: flat ? "." : "..",
|
|
46
|
+
dockerfile: "Dockerfile",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
/**
|
|
31
51
|
* Build normalised {@link ServiceSpec} list from image pins.
|
|
32
52
|
*
|
|
@@ -40,12 +60,15 @@ export function buildSpecs(options: DeriveOptions): ServiceSpec[] {
|
|
|
40
60
|
const creds =
|
|
41
61
|
options.credentials?.[role] ?? generateCredentials(role);
|
|
42
62
|
const port = recipe.port;
|
|
63
|
+
const hostPort = options.instanceId
|
|
64
|
+
? hostPortForInstance(role, port, options.instanceId)
|
|
65
|
+
: defaultHostPort(role, port);
|
|
43
66
|
specs.push({
|
|
44
67
|
role,
|
|
45
68
|
serviceName: serviceNameFor(role),
|
|
46
69
|
image,
|
|
47
70
|
port,
|
|
48
|
-
hostPort
|
|
71
|
+
hostPort,
|
|
49
72
|
credentials: creds,
|
|
50
73
|
});
|
|
51
74
|
}
|
|
@@ -65,24 +88,31 @@ export function emitComposeLayers(
|
|
|
65
88
|
const recipes = options.recipes ?? [];
|
|
66
89
|
const appPort = options.appPort ?? APP_PORT;
|
|
67
90
|
const app = options.app ?? "app";
|
|
91
|
+
const includeApp = options.includeApp !== false;
|
|
92
|
+
const paths = composePathRefs(options.composeDir ?? DEFAULT_DOCKER_DIR);
|
|
68
93
|
const files: GeneratedFile[] = [];
|
|
69
94
|
|
|
70
|
-
// Layer 1 —
|
|
71
|
-
const base = {
|
|
95
|
+
// Layer 1 — project name + network (+ optional app for deploy / oke docker)
|
|
96
|
+
const base: Record<string, unknown> = {
|
|
72
97
|
name: `oke-${app}`,
|
|
73
|
-
|
|
98
|
+
networks: { oke: { driver: "bridge" } },
|
|
99
|
+
};
|
|
100
|
+
if (includeApp) {
|
|
101
|
+
base.services = {
|
|
74
102
|
app: {
|
|
75
|
-
build: {
|
|
103
|
+
build: {
|
|
104
|
+
context: paths.buildContext,
|
|
105
|
+
dockerfile: paths.dockerfile,
|
|
106
|
+
},
|
|
76
107
|
ports: [`${appPort}:${appPort}`],
|
|
77
|
-
env_file: [
|
|
108
|
+
env_file: [paths.envFile],
|
|
78
109
|
depends_on: Object.fromEntries(
|
|
79
110
|
specs.map((s) => [s.serviceName, { condition: "service_healthy" }]),
|
|
80
111
|
),
|
|
81
112
|
networks: ["oke"],
|
|
82
113
|
},
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
};
|
|
114
|
+
};
|
|
115
|
+
}
|
|
86
116
|
files.push({ path: "compose.yml", content: `${toYaml(base)}\n` });
|
|
87
117
|
|
|
88
118
|
// Layer 2 — per-role
|
|
@@ -93,7 +123,7 @@ export function emitComposeLayers(
|
|
|
93
123
|
image: spec.image,
|
|
94
124
|
ports: [`${spec.hostPort}:${spec.port}`],
|
|
95
125
|
networks: ["oke"],
|
|
96
|
-
env_file: [
|
|
126
|
+
env_file: [paths.envFile],
|
|
97
127
|
};
|
|
98
128
|
if (applied.environment) service.environment = applied.environment;
|
|
99
129
|
if (applied.command) service.command = applied.command;
|
|
@@ -111,8 +141,9 @@ export function emitComposeLayers(
|
|
|
111
141
|
|
|
112
142
|
// Layer 3 — prod overlay
|
|
113
143
|
if (options.prod) {
|
|
114
|
-
const prodServices: Record<string, unknown> = {
|
|
115
|
-
|
|
144
|
+
const prodServices: Record<string, unknown> = {};
|
|
145
|
+
if (includeApp) {
|
|
146
|
+
prodServices.app = {
|
|
116
147
|
deploy: {
|
|
117
148
|
replicas: 1,
|
|
118
149
|
resources: {
|
|
@@ -120,8 +151,8 @@ export function emitComposeLayers(
|
|
|
120
151
|
},
|
|
121
152
|
},
|
|
122
153
|
secrets: specs.flatMap((s) => secretNames(s)),
|
|
123
|
-
}
|
|
124
|
-
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
125
156
|
for (const spec of specs) {
|
|
126
157
|
prodServices[spec.serviceName] = {
|
|
127
158
|
deploy: {
|
package/src/docker/derive.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Derive Dockerfile + compose files from config image pins.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { mkdirSync } from "node:fs";
|
|
5
6
|
import {
|
|
6
7
|
assertNoCredentialsInYaml,
|
|
7
8
|
buildSpecs,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
} from "./compose.ts";
|
|
12
13
|
import { emitDockerfile } from "./dockerfile.ts";
|
|
13
14
|
import type { DeriveOptions, DeriveResult, GeneratedFile } from "./types.ts";
|
|
15
|
+
import { DEFAULT_DOCKER_DIR } from "./types.ts";
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Derive infrastructure files from normalised image pins.
|
|
@@ -23,18 +25,27 @@ import type { DeriveOptions, DeriveResult, GeneratedFile } from "./types.ts";
|
|
|
23
25
|
*/
|
|
24
26
|
export function deriveInfrastructure(options: DeriveOptions): DeriveResult {
|
|
25
27
|
if (!options.images || Object.keys(options.images).length === 0) {
|
|
26
|
-
throw new Error(
|
|
28
|
+
throw new Error(
|
|
29
|
+
"oke docker: no images configured — set `images` in oke.config.ts (or prod drivers postgres/redis for defaults)",
|
|
30
|
+
);
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
const
|
|
33
|
+
const normalised: DeriveOptions = {
|
|
34
|
+
...options,
|
|
35
|
+
composeDir: options.composeDir ?? DEFAULT_DOCKER_DIR,
|
|
36
|
+
includeApp: options.includeApp !== false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const specs = buildSpecs(normalised);
|
|
30
40
|
const { files: composeFilesContent, composeFiles } = emitComposeLayers(
|
|
31
41
|
specs,
|
|
32
|
-
|
|
42
|
+
normalised,
|
|
33
43
|
);
|
|
34
44
|
const dockerfile: GeneratedFile = {
|
|
35
45
|
path: "Dockerfile",
|
|
36
|
-
content: emitDockerfile({ appPort:
|
|
46
|
+
content: emitDockerfile({ appPort: normalised.appPort }),
|
|
37
47
|
};
|
|
48
|
+
// Always emit Dockerfile for deploy; stack-only runs ignore it.
|
|
38
49
|
const files = [dockerfile, ...composeFilesContent];
|
|
39
50
|
|
|
40
51
|
for (const f of files) {
|
|
@@ -48,8 +59,8 @@ export function deriveInfrastructure(options: DeriveOptions): DeriveResult {
|
|
|
48
59
|
|
|
49
60
|
const stackEnv = buildStackEnv(
|
|
50
61
|
specs,
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
normalised.recipes ?? [],
|
|
63
|
+
normalised.host ?? "127.0.0.1",
|
|
53
64
|
);
|
|
54
65
|
|
|
55
66
|
return { specs, files, stackEnv, composeFiles };
|
|
@@ -60,22 +71,33 @@ export function deriveInfrastructure(options: DeriveOptions): DeriveResult {
|
|
|
60
71
|
* credential values into YAML. Optionally writes `.env.stack`.
|
|
61
72
|
*
|
|
62
73
|
* @param result - Derive result
|
|
63
|
-
* @param outDir - Destination
|
|
74
|
+
* @param outDir - Destination for Dockerfile / compose (usually `docker/`)
|
|
64
75
|
* @param options - Write controls
|
|
65
76
|
*/
|
|
66
77
|
export async function writeDerivedFiles(
|
|
67
78
|
result: DeriveResult,
|
|
68
79
|
outDir: string,
|
|
69
|
-
options: {
|
|
80
|
+
options: {
|
|
81
|
+
readonly writeStackEnv?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Directory for `.env.stack` (project root). Defaults to `outDir`.
|
|
84
|
+
* Prefer the project cwd when compose lives under `docker/`.
|
|
85
|
+
*/
|
|
86
|
+
readonly stackEnvDir?: string;
|
|
87
|
+
} = {},
|
|
70
88
|
): Promise<readonly string[]> {
|
|
71
89
|
const written: string[] = [];
|
|
90
|
+
const root = outDir.replace(/\/$/, "");
|
|
91
|
+
mkdirSync(root, { recursive: true });
|
|
72
92
|
for (const file of result.files) {
|
|
73
|
-
const path = `${
|
|
93
|
+
const path = `${root}/${file.path}`;
|
|
74
94
|
await Bun.write(path, file.content);
|
|
75
95
|
written.push(path);
|
|
76
96
|
}
|
|
77
97
|
if (options.writeStackEnv) {
|
|
78
|
-
const
|
|
98
|
+
const envRoot = (options.stackEnvDir ?? outDir).replace(/\/$/, "");
|
|
99
|
+
mkdirSync(envRoot, { recursive: true });
|
|
100
|
+
const envPath = `${envRoot}/.env.stack`;
|
|
79
101
|
await Bun.write(envPath, formatStackEnv(result.stackEnv));
|
|
80
102
|
written.push(envPath);
|
|
81
103
|
}
|
|
@@ -125,8 +125,13 @@ describe("deriveInfrastructure", () => {
|
|
|
125
125
|
expect(sqlYml).toContain("pgvector/pgvector:pg17");
|
|
126
126
|
expect(sqlYml).toContain("POSTGRES_PASSWORD");
|
|
127
127
|
expect(sqlYml).toContain("${OKE_STORE_SQL_PASSWORD}");
|
|
128
|
+
expect(sqlYml).toContain("../.env.stack");
|
|
128
129
|
expect(sqlYml).not.toContain(fixedCreds["store.sql"].password);
|
|
129
130
|
|
|
131
|
+
const baseYml = result.files.find((f) => f.path === "compose.yml")!.content;
|
|
132
|
+
expect(baseYml).toContain("context: \"..\"");
|
|
133
|
+
expect(baseYml).toContain("app:");
|
|
134
|
+
|
|
130
135
|
for (const f of result.files) {
|
|
131
136
|
assertNoCredentialsInYaml(
|
|
132
137
|
f.content,
|
|
@@ -140,6 +145,20 @@ describe("deriveInfrastructure", () => {
|
|
|
140
145
|
);
|
|
141
146
|
});
|
|
142
147
|
|
|
148
|
+
test("includeApp false omits app service (infra-only stack)", () => {
|
|
149
|
+
const result = deriveInfrastructure({
|
|
150
|
+
images: { "store.sql": "postgres:16" },
|
|
151
|
+
credentials: { "store.sql": fixedCreds["store.sql"] },
|
|
152
|
+
includeApp: false,
|
|
153
|
+
app: "dev",
|
|
154
|
+
});
|
|
155
|
+
const base = result.files.find((f) => f.path === "compose.yml")!.content;
|
|
156
|
+
expect(base).toContain("oke-dev");
|
|
157
|
+
expect(base).toContain("networks:");
|
|
158
|
+
expect(base).not.toContain("app:");
|
|
159
|
+
expect(base).not.toContain("build:");
|
|
160
|
+
});
|
|
161
|
+
|
|
143
162
|
test("prod overlay adds deploy.replicas and is layer 3", () => {
|
|
144
163
|
const result = deriveInfrastructure({
|
|
145
164
|
images: { "store.sql": "postgres:16" },
|
|
@@ -160,15 +179,20 @@ describe("deriveInfrastructure", () => {
|
|
|
160
179
|
});
|
|
161
180
|
|
|
162
181
|
test("writeDerivedFiles never writes compose.override.yml", async () => {
|
|
163
|
-
const
|
|
182
|
+
const root = await mkdtemp(join(tmpdir(), "oke-docker-"));
|
|
183
|
+
const dockerDir = join(root, "docker");
|
|
164
184
|
const result = deriveInfrastructure({
|
|
165
185
|
images: { "store.sql": "postgres:16" },
|
|
166
186
|
credentials: { "store.sql": fixedCreds["store.sql"] },
|
|
167
187
|
});
|
|
168
|
-
const written = await writeDerivedFiles(result,
|
|
188
|
+
const written = await writeDerivedFiles(result, dockerDir, {
|
|
189
|
+
writeStackEnv: true,
|
|
190
|
+
stackEnvDir: root,
|
|
191
|
+
});
|
|
169
192
|
expect(written.some((p) => p.endsWith(COMPOSE_OVERRIDE))).toBe(false);
|
|
170
|
-
expect(await Bun.file(join(
|
|
171
|
-
|
|
193
|
+
expect(await Bun.file(join(root, ".env.stack")).exists()).toBe(true);
|
|
194
|
+
expect(await Bun.file(join(dockerDir, "compose.yml")).exists()).toBe(true);
|
|
195
|
+
const envText = await Bun.file(join(root, ".env.stack")).text();
|
|
172
196
|
expect(envText).toContain("DATABASE_URL=");
|
|
173
197
|
expect(formatStackEnv(result.stackEnv)).toContain("OKE_STORE_SQL_PASSWORD=");
|
|
174
198
|
});
|
|
@@ -73,7 +73,7 @@ console.log("listening", server.port);
|
|
|
73
73
|
|
|
74
74
|
// No lockfile — adjust Dockerfile install for the fixture.
|
|
75
75
|
const derived = deriveInfrastructure({
|
|
76
|
-
images: { "store.sql": "postgres:
|
|
76
|
+
images: { "store.sql": "postgres:18-alpine" },
|
|
77
77
|
credentials: {
|
|
78
78
|
"store.sql": {
|
|
79
79
|
user: "oke",
|
|
@@ -82,6 +82,8 @@ console.log("listening", server.port);
|
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
84
|
app: "fixture",
|
|
85
|
+
// Flat fixture root (build context `.`).
|
|
86
|
+
composeDir: ".",
|
|
85
87
|
});
|
|
86
88
|
await writeDerivedFiles(derived, dir);
|
|
87
89
|
|
package/src/docker/index.ts
CHANGED
|
@@ -19,11 +19,14 @@ export type {
|
|
|
19
19
|
ServiceSpec,
|
|
20
20
|
} from "./types.ts";
|
|
21
21
|
|
|
22
|
+
export { DEFAULT_DOCKER_DIR } from "./types.ts";
|
|
23
|
+
|
|
22
24
|
export {
|
|
23
25
|
COMPOSE_OVERRIDE,
|
|
24
26
|
assertNoCredentialsInYaml,
|
|
25
27
|
buildSpecs,
|
|
26
28
|
buildStackEnv,
|
|
29
|
+
composePathRefs,
|
|
27
30
|
emitComposeLayers,
|
|
28
31
|
formatStackEnv,
|
|
29
32
|
} from "./compose.ts";
|
|
@@ -43,6 +46,13 @@ export {
|
|
|
43
46
|
resolveStack,
|
|
44
47
|
type StackRow,
|
|
45
48
|
} from "./stack.ts";
|
|
49
|
+
export {
|
|
50
|
+
hostPortForInstance,
|
|
51
|
+
loadExistingStackCredentials,
|
|
52
|
+
parseStackCredentials,
|
|
53
|
+
stackAppSlug,
|
|
54
|
+
stackInstanceId,
|
|
55
|
+
} from "./stack-id.ts";
|
|
46
56
|
export {
|
|
47
57
|
formatImagesLock,
|
|
48
58
|
pinImages,
|