okengine 0.10.2 → 0.11.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/package.json +76 -10
- package/site/content/docs/ai/mcp.mdx +0 -1
- package/site/content/docs/deployment/docker-swarm.mdx +48 -111
- package/site/content/docs/deployment/docker.mdx +43 -36
- package/site/content/docs/deployment/index.mdx +9 -9
- package/site/content/docs/deployment/kubernetes.mdx +4 -4
- package/site/content/docs/elements/ai.mdx +7 -13
- package/site/content/docs/elements/channel.mdx +9 -13
- package/site/content/docs/elements/clock.mdx +8 -14
- package/site/content/docs/elements/flow.mdx +46 -54
- package/site/content/docs/elements/gate.mdx +6 -11
- package/site/content/docs/elements/signal.mdx +2 -8
- package/site/content/docs/elements/store.mdx +79 -93
- package/site/content/docs/elements/vault.mdx +89 -76
- package/site/content/docs/get-started/basic-usage.mdx +8 -14
- package/site/content/docs/get-started/installation.mdx +36 -36
- package/site/content/docs/get-started/introduction.mdx +11 -0
- package/site/content/docs/get-started/why.mdx +3 -4
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/plugins/anonymous.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +11 -11
- package/site/content/docs/plugins/otp.mdx +2 -2
- package/site/content/docs/plugins/passkey.mdx +1 -1
- package/site/content/docs/plugins/two-factor.mdx +1 -1
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +1 -1
- package/site/content/docs/recipes/cockroachdb.mdx +2 -2
- package/site/content/docs/recipes/dragonfly.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -2
- package/site/content/docs/recipes/llama-cpp.mdx +21 -23
- package/site/content/docs/recipes/mailpit.mdx +11 -11
- package/site/content/docs/recipes/meilisearch.mdx +10 -10
- package/site/content/docs/recipes/meta.json +0 -1
- package/site/content/docs/recipes/ollama.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +12 -9
- package/site/content/docs/recipes/postgres.mdx +3 -3
- package/site/content/docs/recipes/redis.mdx +2 -2
- package/site/content/docs/recipes/rustfs.mdx +10 -7
- package/site/content/docs/recipes/sglang.mdx +1 -1
- package/site/content/docs/recipes/supabase-docker.mdx +1 -1
- package/site/content/docs/recipes/timescale.mdx +2 -2
- package/site/content/docs/recipes/valkey.mdx +1 -1
- package/site/content/docs/recipes/vllm.mdx +1 -1
- package/site/content/docs/recipes/yugabytedb.mdx +2 -2
- package/site/content/docs/reference/cli.md +61 -38
- package/site/content/docs/reference/configuration.mdx +145 -70
- package/site/content/docs/reference/environment-variables.mdx +36 -28
- package/site/content/docs/reference/errors.mdx +28 -29
- package/site/content/docs/reference/fx.mdx +14 -7
- package/site/content/docs/reference/i18n.mdx +17 -6
- package/site/content/docs/reference/index.mdx +5 -0
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/migrating-environments.mdx +158 -0
- package/site/content/docs/reference/plugins.mdx +2 -2
- package/src/auth/gate-auth.test.ts +12 -10
- package/src/cli/ai-setup/ai-setup.test.ts +30 -16
- package/src/cli/ai-setup/apply.ts +111 -15
- package/src/cli/ai-setup/catalog.ts +3 -3
- package/src/cli/ai-setup/index.ts +1 -1
- package/src/cli/ai-setup/prompts.ts +1 -1
- package/src/cli/ai-setup/recommend.test.ts +1 -1
- package/src/cli/ask-seed.test.ts +3 -3
- package/src/cli/build.ts +8 -9
- package/src/cli/db-auto-push.test.ts +6 -3
- package/src/cli/db-auto-push.ts +7 -5
- package/src/cli/db-seed.ts +7 -8
- package/src/cli/db.test.ts +70 -71
- package/src/cli/db.ts +86 -12
- package/src/cli/dev-controller.test.ts +35 -0
- package/src/cli/dev-controller.ts +298 -0
- package/src/cli/dev-controls.ts +11 -65
- package/src/cli/dev-db-push.test.ts +47 -15
- package/src/cli/dev-schema-sync.test.ts +11 -11
- package/src/cli/dev-schema-sync.ts +10 -10
- package/src/cli/dev-session-lock.test.ts +106 -0
- package/src/cli/dev-session-lock.ts +209 -0
- package/src/cli/dev.test.ts +246 -114
- package/src/cli/dev.ts +153 -205
- package/src/cli/doc-staleness.test.ts +3 -6
- package/src/cli/docker-clean.test.ts +33 -1
- package/src/cli/docker-clean.ts +28 -33
- package/src/cli/docker-cli.test.ts +12 -11
- package/src/cli/docker.ts +95 -20
- package/src/cli/doctor.test.ts +40 -0
- package/src/cli/doctor.ts +142 -9
- package/src/cli/drizzle-env.test.ts +11 -13
- package/src/cli/drizzle-env.ts +18 -11
- package/src/cli/ensure-drizzle-config.ts +11 -14
- package/src/cli/hero-meta.test.ts +15 -15
- package/src/cli/hero-meta.ts +10 -9
- package/src/cli/index.ts +36 -6
- package/src/cli/load-config.images.test.ts +44 -10
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +35 -29
- package/src/cli/meilisearch-local.ts +1 -2
- package/src/cli/mode.ts +7 -58
- package/src/cli/registry.ts +175 -19
- package/src/cli/resolve-dev-sql-env.test.ts +8 -16
- package/src/cli/resolve-dev-sql-env.ts +13 -15
- package/src/cli/safe-defaults.test.ts +29 -11
- package/src/cli/schema.ts +65 -16
- package/src/cli/test.ts +58 -0
- package/src/cli/tui/App.tsx +252 -0
- package/src/cli/tui/DevLive.tsx +116 -0
- package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
- package/src/cli/tui/components/HeaderBar.tsx +67 -0
- package/src/cli/tui/components/LogStream.tsx +47 -0
- package/src/cli/tui/components/PanelCard.tsx +39 -0
- package/src/cli/tui/components/SelectRow.tsx +37 -0
- package/src/cli/tui/components/SlashPalette.tsx +172 -0
- package/src/cli/tui/components/Spinner.tsx +32 -0
- package/src/cli/tui/components/StatusBar.tsx +51 -0
- package/src/cli/tui/components/StatusDot.tsx +37 -0
- package/src/cli/tui/components/TabBar.tsx +49 -0
- package/src/cli/tui/components/Wordmark.tsx +43 -0
- package/src/cli/tui/format.ts +38 -0
- package/src/cli/tui/keys.test.ts +18 -0
- package/src/cli/tui/keys.ts +26 -0
- package/src/cli/tui/launch.test.ts +18 -0
- package/src/cli/tui/launch.ts +57 -0
- package/src/cli/tui/panels/Dashboard.tsx +201 -0
- package/src/cli/tui/panels/Database.tsx +198 -0
- package/src/cli/tui/panels/Dev.tsx +257 -0
- package/src/cli/tui/panels/Docker.tsx +357 -0
- package/src/cli/tui/panels/Navigator.test.ts +22 -0
- package/src/cli/tui/panels/Navigator.tsx +213 -0
- package/src/cli/tui/slash-catalog.test.ts +31 -0
- package/src/cli/tui/slash-catalog.ts +184 -0
- package/src/cli/tui/slash-run.ts +54 -0
- package/src/cli/tui/state/ports.ts +95 -0
- package/src/cli/tui/theme.ts +24 -0
- package/src/cli/vault-cmd.test.ts +402 -0
- package/src/cli/vault-cmd.ts +727 -69
- package/src/cli/vault-secure-input.test.ts +56 -0
- package/src/cli/vault-secure-input.ts +124 -0
- package/src/compiler/effects-infer.ts +7 -0
- package/src/compiler/extract.ts +12 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler/generate-adopt.ts +27 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +115 -0
- package/src/config/driver-defaults.ts +156 -0
- package/src/config/index.ts +246 -131
- package/src/config/resolve-driver.test.ts +14 -19
- package/src/console/server/app.ts +4 -2
- package/src/console/server/console.test.ts +58 -1
- package/src/console/server/flows.ts +22 -2
- package/src/console/server/index.ts +3 -0
- package/src/console/server/operator-db.test.ts +55 -13
- package/src/console/server/operator-db.ts +305 -263
- package/src/console/server/serve.ts +16 -7
- package/src/console/server/state.ts +31 -9
- package/src/console/server/vault.test.ts +120 -5
- package/src/console/server/vault.ts +208 -12
- package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
- package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
- package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/shell/client.ts +16 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
- package/src/console/ui/vault/VaultA11yView.tsx +33 -1
- package/src/console/ui/vault/backend.test.ts +75 -0
- package/src/console/ui/vault/backend.ts +117 -0
- package/src/console/ui/vault/fixture.ts +21 -2
- package/src/console/ui/vault/index.ts +11 -1
- package/src/console/ui/vault/types.ts +26 -0
- package/src/docker/compose.ts +164 -61
- package/src/docker/derive.ts +106 -12
- package/src/docker/docker.test.ts +156 -110
- package/src/docker/helpers.ts +98 -0
- package/src/docker/images-config.test.ts +163 -0
- package/src/docker/index.ts +30 -5
- package/src/docker/recipes/index.ts +0 -3
- package/src/docker/recipes/llama-cpp.ts +4 -4
- package/src/docker/recipes/pgdog.ts +9 -2
- package/src/docker/resources.test.ts +71 -0
- package/src/docker/resources.ts +176 -0
- package/src/docker/stack-id.test.ts +11 -4
- package/src/docker/stack-id.ts +42 -32
- package/src/docker/stack.integration.test.ts +4 -15
- package/src/docker/types.ts +35 -3
- package/src/drivers/ai-ollama.integration.test.ts +2 -2
- package/src/drivers/conformance.test.ts +11 -12
- package/src/drivers/drizzle-dialect.test.ts +0 -4
- package/src/drivers/drizzle-dialect.ts +7 -13
- package/src/drivers/index.ts +24 -7
- package/src/drivers/journal-postgres.test.ts +3 -1
- package/src/drivers/journal-postgres.ts +4 -2
- package/src/drivers/meilisearch.ts +1 -1
- package/src/drivers/pglite.ts +28 -5
- package/src/drivers/types.ts +9 -11
- package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
- package/src/drivers/vault-aws-secrets-manager.ts +333 -0
- package/src/drivers/vault-builtin.test.ts +110 -0
- package/src/drivers/vault-builtin.ts +184 -0
- package/src/drivers/vault-driver-removal.test.ts +13 -0
- package/src/drivers/vault-managed.test.ts +95 -0
- package/src/drivers/vault-managed.ts +101 -25
- package/src/drivers/vault-types.ts +25 -5
- package/src/elements/ai/runtime.ts +1 -1
- package/src/elements/clock/chaos.test.ts +4 -1
- package/src/elements/clock/durable.ts +57 -4
- package/src/elements/clock.test.ts +13 -14
- package/src/elements/gate/constants.ts +18 -0
- package/src/elements/gate.test.ts +1 -1
- package/src/elements/gate.ts +2 -2
- package/src/elements/store/domain-ddl.test.ts +5 -12
- package/src/elements/store/emit-drizzle.ts +44 -19
- package/src/elements/store/index-boot.test.ts +15 -116
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/resource-list-docs.fixture.ts +12 -6
- package/src/elements/store/resource-list-docs.test.ts +64 -44
- package/src/elements/store/resource.test.ts +17 -10
- package/src/elements/store/runtime.ts +9 -12
- package/src/elements/store/schema-decl.test.ts +16 -32
- package/src/elements/store/seed.test.ts +1 -2
- package/src/elements/store/seed.ts +7 -8
- package/src/elements/store/sql-condition.test.ts +2 -2
- package/src/elements/store/sql-session.test.ts +8 -10
- package/src/elements/store/sql-session.ts +10 -2
- package/src/elements/store.test.ts +13 -13
- package/src/elements/vault/audit.ts +233 -0
- package/src/elements/vault/boot-chain.test.ts +78 -0
- package/src/elements/vault/boot-chain.ts +43 -39
- package/src/elements/vault/builtin-adapter.test.ts +479 -0
- package/src/elements/vault/builtin-adapter.ts +1184 -0
- package/src/elements/vault/crypto.test.ts +237 -0
- package/src/elements/vault/crypto.ts +431 -0
- package/src/elements/vault/declare.ts +164 -3
- package/src/elements/vault/errors.ts +56 -0
- package/src/elements/vault/kms-unseal.test.ts +76 -0
- package/src/elements/vault/kms-unseal.ts +181 -0
- package/src/elements/vault/path.ts +90 -0
- package/src/elements/vault/resilience.test.ts +102 -0
- package/src/elements/vault/resilience.ts +107 -0
- package/src/elements/vault/runtime.ts +36 -4
- package/src/elements/vault/security-checklist.test.ts +130 -0
- package/src/elements/vault/storage.ts +448 -0
- package/src/elements/vault/types.ts +288 -0
- package/src/elements/vault/unseal.ts +150 -0
- package/src/elements/vault.test.ts +72 -4
- package/src/elements/vault.ts +91 -2
- package/src/full.ts +248 -0
- package/src/http.ts +41 -0
- package/src/i18n/interpolate-simple.ts +37 -0
- package/src/i18n/messages.ts +51 -4
- package/src/i18n-entry.ts +7 -0
- package/src/index.ts +35 -145
- package/src/journal-entry.ts +20 -0
- package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
- package/src/kernel/app.ts +40 -42
- package/src/kernel/auto-registry.test.ts +2 -2
- package/src/kernel/boot-bind/ai.test.ts +5 -5
- package/src/kernel/boot-bind/ai.ts +2 -1
- package/src/kernel/boot-bind/channel.test.ts +6 -6
- package/src/kernel/boot-bind/channel.ts +5 -4
- package/src/kernel/boot-bind/clock.ts +5 -5
- package/src/kernel/boot-bind/gate.ts +6 -7
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +5 -5
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +55 -37
- package/src/kernel/boot-bind/store.ts +24 -34
- package/src/kernel/boot-bind/vault.ts +19 -4
- package/src/kernel/boot.test.ts +12 -4
- package/src/kernel/boot.ts +31 -19
- package/src/kernel/budget-entry.ts +1 -1
- package/src/kernel/compensate.test.ts +288 -1
- package/src/kernel/compensate.ts +126 -0
- package/src/kernel/concurrency.test.ts +10 -9
- package/src/kernel/effects-stamping.test.ts +27 -2
- package/src/kernel/element-registries.ts +5 -3
- package/src/kernel/flow.ts +4 -2
- package/src/kernel/fx.test.ts +119 -3
- package/src/kernel/fx.ts +215 -40
- package/src/kernel/index.ts +14 -0
- package/src/kernel/journal.ts +130 -7
- package/src/kernel/pipeline.test.ts +20 -6
- package/src/kernel/redacted.test.ts +35 -0
- package/src/kernel/redacted.ts +24 -2
- package/src/kernel/router/create-default.ts +16 -0
- package/src/kernel/router/create-edge.ts +18 -0
- package/src/kernel/router/create.ts +23 -0
- package/src/kernel/router/index.ts +23 -0
- package/src/kernel/router/linear.ts +88 -0
- package/src/kernel/router/regexp.ts +188 -0
- package/src/kernel/router/smart.ts +120 -0
- package/src/kernel/router/trie.ts +108 -0
- package/src/kernel/router/types.ts +56 -0
- package/src/kernel/router.ts +18 -532
- package/src/kernel-entry.ts +43 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/plugins/config-source.test.ts +7 -7
- package/src/release/build-lib.ts +58 -0
- package/src/release/exports.test.ts +4 -2
- package/src/release/exports.ts +30 -4
- package/src/release/measure.ts +101 -22
- package/src/runs/duckdb.ts +7 -2
- package/src/shared/optional-peer.ts +41 -0
- package/src/term.test.ts +3 -44
- package/src/test/create-test-app.ts +12 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/src/test/reset-element-registries.ts +2 -0
- package/src/upgrade/codemods.test.ts +7 -20
- package/src/upgrade/codemods.ts +93 -60
- package/site/content/docs/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
- package/site/content/docs/recipes/openbao.mdx +0 -141
- package/src/cli/ask-dev-mode.ts +0 -40
- package/src/cli/dev-mode.test.ts +0 -45
- package/src/cli/dev-mode.ts +0 -78
- package/src/cli/openbao-bootstrap.test.ts +0 -150
- package/src/cli/openbao-bootstrap.ts +0 -321
- package/src/cli/openbao-restart.integration.test.ts +0 -145
- package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
- package/src/docker/recipes/openbao.ts +0 -47
- package/src/drivers/libsql.ts +0 -179
- package/src/drivers/sqlite.ts +0 -44
- package/src/drivers/vault-openbao.test.ts +0 -97
- package/src/drivers/vault-openbao.ts +0 -143
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev panel — Not Running / Running states, inline shortcuts, live logs.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Box, Text, useInput } from "ink";
|
|
6
|
+
import { useEffect, useMemo, useState, type ReactElement } from "react";
|
|
7
|
+
import { DevController } from "../../dev-controller.ts";
|
|
8
|
+
import type { DevOwnership } from "../../dev-session-lock.ts";
|
|
9
|
+
import type { FooterStatus } from "../components/StatusBar.tsx";
|
|
10
|
+
import { StatusDot } from "../components/StatusDot.tsx";
|
|
11
|
+
import { formatUptime } from "../format.ts";
|
|
12
|
+
import { mapDevControlInput } from "../keys.ts";
|
|
13
|
+
import { pollPortSnapshot, type PortSnapshot } from "../state/ports.ts";
|
|
14
|
+
import { TUI_ACCENT, TUI_MUTED, TUI_OK, TUI_WARN } from "../theme.ts";
|
|
15
|
+
|
|
16
|
+
/** Props for {@link DevPanel}. */
|
|
17
|
+
export type DevPanelProps = {
|
|
18
|
+
readonly cwd: string;
|
|
19
|
+
readonly controller: DevController;
|
|
20
|
+
readonly onLog: (line: string) => void;
|
|
21
|
+
readonly active: boolean;
|
|
22
|
+
readonly logs: readonly string[];
|
|
23
|
+
readonly onStatus: (status: FooterStatus) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ActionId = "start" | "stop" | "restart" | "attach";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Dev controls panel.
|
|
30
|
+
*
|
|
31
|
+
* @param props - Controller + cwd
|
|
32
|
+
*/
|
|
33
|
+
export function DevPanel(props: DevPanelProps): ReactElement {
|
|
34
|
+
const [ownership, setOwnership] = useState<DevOwnership>("stopped");
|
|
35
|
+
const [snap, setSnap] = useState<PortSnapshot | null>(null);
|
|
36
|
+
const [busy, setBusy] = useState(false);
|
|
37
|
+
const [startedAt, setStartedAt] = useState<number | null>(null);
|
|
38
|
+
const [now, setNow] = useState(Date.now());
|
|
39
|
+
const [selected, setSelected] = useState(0);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const off = props.controller.on((e) => {
|
|
43
|
+
if (e.type === "ownership") setOwnership(e.ownership);
|
|
44
|
+
if (e.type === "ready") {
|
|
45
|
+
setOwnership(e.ownership);
|
|
46
|
+
if (e.ownership === "managed") setStartedAt(Date.now());
|
|
47
|
+
}
|
|
48
|
+
if (e.type === "log") props.onLog(e.text);
|
|
49
|
+
if (e.type === "error") {
|
|
50
|
+
props.onLog(`${e.message}\n`);
|
|
51
|
+
props.onStatus({ kind: "done", label: e.message });
|
|
52
|
+
}
|
|
53
|
+
if (e.type === "exit") setStartedAt(null);
|
|
54
|
+
});
|
|
55
|
+
void props.controller.attach(props.cwd).then((r) => {
|
|
56
|
+
setOwnership(r.ownership);
|
|
57
|
+
if (r.ownership === "managed" && r.lock) {
|
|
58
|
+
setStartedAt(Date.parse(r.lock.startedAt) || Date.now());
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return off;
|
|
62
|
+
}, [props.controller, props.cwd, props.onLog, props.onStatus]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const id = setInterval(() => {
|
|
66
|
+
void pollPortSnapshot(props.cwd).then((s) => {
|
|
67
|
+
setOwnership(s.ownership);
|
|
68
|
+
setSnap(s);
|
|
69
|
+
});
|
|
70
|
+
setNow(Date.now());
|
|
71
|
+
}, 1500);
|
|
72
|
+
return () => clearInterval(id);
|
|
73
|
+
}, [props.cwd]);
|
|
74
|
+
|
|
75
|
+
const actions = useMemo(() => {
|
|
76
|
+
const running = ownership === "managed";
|
|
77
|
+
const external = ownership === "external";
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
id: "start" as const,
|
|
81
|
+
label: "[S]tart",
|
|
82
|
+
enabled: ownership === "stopped",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "restart" as const,
|
|
86
|
+
label: "[R]estart",
|
|
87
|
+
enabled: running,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "stop" as const,
|
|
91
|
+
label: "[X]top",
|
|
92
|
+
enabled: running,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "attach" as const,
|
|
96
|
+
label: "[A]ttach",
|
|
97
|
+
enabled: true,
|
|
98
|
+
},
|
|
99
|
+
].filter((a) => !(external && (a.id === "start" || a.id === "restart" || a.id === "stop")));
|
|
100
|
+
}, [ownership]);
|
|
101
|
+
|
|
102
|
+
useInput(
|
|
103
|
+
(input, key) => {
|
|
104
|
+
if (!props.active || busy) return;
|
|
105
|
+
const ctrl = mapDevControlInput(input, key);
|
|
106
|
+
if (ctrl === "q") return; // Esc handled by App
|
|
107
|
+
if (ctrl === "r" || input === "r" || input === "R") {
|
|
108
|
+
void runAction("restart");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (input === "s" || input === "S") {
|
|
112
|
+
void runAction("start");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (input === "x" || input === "X") {
|
|
116
|
+
void runAction("stop");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (input === "a" || input === "A") {
|
|
120
|
+
void runAction("attach");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (key.upArrow) {
|
|
124
|
+
setSelected((i) => Math.max(0, i - 1));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (key.downArrow) {
|
|
128
|
+
setSelected((i) => Math.min(actions.length - 1, i + 1));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (key.return) {
|
|
132
|
+
const action = actions[selected];
|
|
133
|
+
if (action) void runAction(action.id);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{ isActive: props.active },
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const runAction = async (id: ActionId): Promise<void> => {
|
|
140
|
+
if (busy) return;
|
|
141
|
+
setBusy(true);
|
|
142
|
+
const labels: Record<ActionId, string> = {
|
|
143
|
+
start: "Starting oke dev…",
|
|
144
|
+
stop: "Stopping…",
|
|
145
|
+
restart: "Restarting…",
|
|
146
|
+
attach: "Attaching…",
|
|
147
|
+
};
|
|
148
|
+
props.onStatus({ kind: "running", label: labels[id] });
|
|
149
|
+
try {
|
|
150
|
+
if (id === "start") {
|
|
151
|
+
const code = await props.controller.start({ cwd: props.cwd });
|
|
152
|
+
props.onStatus({
|
|
153
|
+
kind: "done",
|
|
154
|
+
label: code === 0 ? "Started" : "Start failed",
|
|
155
|
+
});
|
|
156
|
+
if (code === 0) setStartedAt(Date.now());
|
|
157
|
+
} else if (id === "stop") {
|
|
158
|
+
await props.controller.stop();
|
|
159
|
+
props.onStatus({ kind: "done", label: "Stopped" });
|
|
160
|
+
setStartedAt(null);
|
|
161
|
+
} else if (id === "restart") {
|
|
162
|
+
if (ownership !== "managed") {
|
|
163
|
+
props.onStatus({ kind: "done", label: "Nothing to restart" });
|
|
164
|
+
} else {
|
|
165
|
+
const code = await props.controller.restart({ cwd: props.cwd });
|
|
166
|
+
props.onStatus({
|
|
167
|
+
kind: "done",
|
|
168
|
+
label: code === 0 ? "Restarted" : "Restart failed",
|
|
169
|
+
});
|
|
170
|
+
if (code === 0) setStartedAt(Date.now());
|
|
171
|
+
}
|
|
172
|
+
} else if (id === "attach") {
|
|
173
|
+
const r = await props.controller.attach(props.cwd);
|
|
174
|
+
setOwnership(r.ownership);
|
|
175
|
+
props.onStatus({ kind: "done", label: `Ownership: ${r.ownership}` });
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
setBusy(false);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const running = ownership === "managed" || ownership === "external";
|
|
183
|
+
const uptime = startedAt && ownership === "managed" ? formatUptime(now - startedAt) : null;
|
|
184
|
+
const app = snap?.surfaces.find((s) => s.id === "app");
|
|
185
|
+
const consoleSurf = snap?.surfaces.find((s) => s.id === "console");
|
|
186
|
+
const logTail = props.logs.slice(-Math.max(6, 12));
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
190
|
+
<Box flexDirection="column">
|
|
191
|
+
<Text bold color={TUI_ACCENT}>
|
|
192
|
+
Dev Server
|
|
193
|
+
</Text>
|
|
194
|
+
{!running ? (
|
|
195
|
+
<Box flexDirection="column">
|
|
196
|
+
<Box gap={1}>
|
|
197
|
+
<Text color={TUI_WARN} bold>
|
|
198
|
+
●
|
|
199
|
+
</Text>
|
|
200
|
+
<Text bold color={TUI_WARN}>
|
|
201
|
+
Not Running
|
|
202
|
+
</Text>
|
|
203
|
+
</Box>
|
|
204
|
+
<Text color={TUI_MUTED}>Press [S] to start</Text>
|
|
205
|
+
</Box>
|
|
206
|
+
) : (
|
|
207
|
+
<Box flexDirection="column">
|
|
208
|
+
<Box gap={1}>
|
|
209
|
+
<StatusDot status="ready" />
|
|
210
|
+
<Text bold color={TUI_OK}>
|
|
211
|
+
{ownership === "external" ? "Running (external)" : "Running"}
|
|
212
|
+
</Text>
|
|
213
|
+
{uptime ? <Text color={TUI_MUTED}>· uptime {uptime}</Text> : null}
|
|
214
|
+
</Box>
|
|
215
|
+
<Box gap={2}>
|
|
216
|
+
<Text color={TUI_MUTED}>Ports</Text>
|
|
217
|
+
<Text color={TUI_ACCENT}>App :{app?.port ?? "—"}</Text>
|
|
218
|
+
<Text color={TUI_ACCENT}>Console :{consoleSurf?.port ?? "—"}</Text>
|
|
219
|
+
</Box>
|
|
220
|
+
{ownership === "external" ? (
|
|
221
|
+
<Text color={TUI_WARN}>Read-only — will not start a second stack</Text>
|
|
222
|
+
) : null}
|
|
223
|
+
</Box>
|
|
224
|
+
)}
|
|
225
|
+
</Box>
|
|
226
|
+
|
|
227
|
+
<Box gap={2}>
|
|
228
|
+
{actions.map((a, i) => (
|
|
229
|
+
<Text
|
|
230
|
+
key={a.id}
|
|
231
|
+
bold={props.active && i === selected}
|
|
232
|
+
color={!a.enabled ? TUI_MUTED : props.active && i === selected ? TUI_ACCENT : undefined}
|
|
233
|
+
dimColor={!a.enabled}
|
|
234
|
+
>
|
|
235
|
+
{props.active && i === selected ? "› " : " "}
|
|
236
|
+
{a.label}
|
|
237
|
+
</Text>
|
|
238
|
+
))}
|
|
239
|
+
</Box>
|
|
240
|
+
|
|
241
|
+
<Box flexDirection="column" flexGrow={1} minHeight={6}>
|
|
242
|
+
<Text bold color={TUI_ACCENT}>
|
|
243
|
+
Live Logs
|
|
244
|
+
</Text>
|
|
245
|
+
{logTail.length === 0 ? (
|
|
246
|
+
<Text color={TUI_MUTED}>Start the dev server to see logs</Text>
|
|
247
|
+
) : (
|
|
248
|
+
logTail.map((line, i) => (
|
|
249
|
+
<Text key={`${i}-${line.slice(0, 24)}`} wrap="truncate">
|
|
250
|
+
{line.replace(/\n$/, "")}
|
|
251
|
+
</Text>
|
|
252
|
+
))
|
|
253
|
+
)}
|
|
254
|
+
</Box>
|
|
255
|
+
</Box>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker panel — live stack by project name; clean tears down whole projects.
|
|
3
|
+
*
|
|
4
|
+
* Never shells into interactive `oke docker clean` (clack) while Ink is mounted —
|
|
5
|
+
* that fights stdout and leaves ghost frames.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Box, Text, useInput } from "ink";
|
|
9
|
+
import { useEffect, useState, type ReactElement } from "react";
|
|
10
|
+
import type { DevStatus } from "../../../term.ts";
|
|
11
|
+
import { composeRowToStatus } from "../../../docker/compose-health.ts";
|
|
12
|
+
import {
|
|
13
|
+
composeProjectName,
|
|
14
|
+
downStack,
|
|
15
|
+
listOkeComposeProjects,
|
|
16
|
+
listStackContainers,
|
|
17
|
+
type OkeStack,
|
|
18
|
+
} from "../../../docker/cleanup.ts";
|
|
19
|
+
import { ConfirmDialog } from "../components/ConfirmDialog.tsx";
|
|
20
|
+
import type { FooterStatus } from "../components/StatusBar.tsx";
|
|
21
|
+
import { StatusDot, statusWord } from "../components/StatusDot.tsx";
|
|
22
|
+
import { TUI_ACCENT, TUI_MUTED, TUI_OK, TUI_WARN } from "../theme.ts";
|
|
23
|
+
|
|
24
|
+
/** Props for {@link DockerPanel}. */
|
|
25
|
+
export type DockerPanelProps = {
|
|
26
|
+
readonly cwd: string;
|
|
27
|
+
readonly active: boolean;
|
|
28
|
+
readonly onLog: (line: string) => void;
|
|
29
|
+
readonly onStatus: (status: FooterStatus) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type DockerAction = "derive" | "clean" | "restart";
|
|
33
|
+
|
|
34
|
+
type ServiceRow = {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly status: DevStatus;
|
|
37
|
+
readonly detail: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type Mode = "main" | "pickClean" | "confirmClean";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Compose-oriented panel.
|
|
44
|
+
*
|
|
45
|
+
* @param props - cwd + log
|
|
46
|
+
*/
|
|
47
|
+
export function DockerPanel(props: DockerPanelProps): ReactElement {
|
|
48
|
+
const project = composeProjectName(props.cwd);
|
|
49
|
+
const [rows, setRows] = useState<readonly ServiceRow[]>([]);
|
|
50
|
+
const [stacks, setStacks] = useState<readonly OkeStack[]>([]);
|
|
51
|
+
const [selected, setSelected] = useState(0);
|
|
52
|
+
const [mode, setMode] = useState<Mode>("main");
|
|
53
|
+
const [cleanFocus, setCleanFocus] = useState(0);
|
|
54
|
+
const [cleanPicked, setCleanPicked] = useState<ReadonlySet<string>>(() => new Set());
|
|
55
|
+
const [busy, setBusy] = useState(false);
|
|
56
|
+
|
|
57
|
+
const actions: readonly { id: DockerAction; label: string }[] = [
|
|
58
|
+
{ id: "derive", label: "[D]erive" },
|
|
59
|
+
{ id: "clean", label: "[C]lean" },
|
|
60
|
+
{ id: "restart", label: "[R]estart stack" },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const refresh = async (): Promise<void> => {
|
|
64
|
+
try {
|
|
65
|
+
const containers = await listStackContainers(project);
|
|
66
|
+
setRows(
|
|
67
|
+
containers.map((c) => ({
|
|
68
|
+
name: c.service || c.name,
|
|
69
|
+
status: composeRowToStatus({
|
|
70
|
+
Service: c.service,
|
|
71
|
+
State: c.state,
|
|
72
|
+
Status: c.state,
|
|
73
|
+
}),
|
|
74
|
+
detail: c.state || c.name,
|
|
75
|
+
})),
|
|
76
|
+
);
|
|
77
|
+
const all = await listOkeComposeProjects();
|
|
78
|
+
setStacks(all);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
props.onLog(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
void refresh();
|
|
86
|
+
const id = setInterval(() => void refresh(), 4000);
|
|
87
|
+
return () => clearInterval(id);
|
|
88
|
+
}, [props.cwd]);
|
|
89
|
+
|
|
90
|
+
useInput(
|
|
91
|
+
(input, key) => {
|
|
92
|
+
if (!props.active || busy) return;
|
|
93
|
+
|
|
94
|
+
if (mode === "pickClean") {
|
|
95
|
+
if (key.escape) {
|
|
96
|
+
setMode("main");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (key.upArrow) setCleanFocus((i) => Math.max(0, i - 1));
|
|
100
|
+
if (key.downArrow) setCleanFocus((i) => Math.min(Math.max(0, stacks.length - 1), i + 1));
|
|
101
|
+
if (input === " ") {
|
|
102
|
+
const stack = stacks[cleanFocus];
|
|
103
|
+
if (!stack) return;
|
|
104
|
+
setCleanPicked((prev) => {
|
|
105
|
+
const next = new Set(prev);
|
|
106
|
+
if (next.has(stack.project)) next.delete(stack.project);
|
|
107
|
+
else next.add(stack.project);
|
|
108
|
+
return next;
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (key.return) {
|
|
113
|
+
if (cleanPicked.size === 0) return;
|
|
114
|
+
setMode("confirmClean");
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (mode !== "main") return;
|
|
120
|
+
|
|
121
|
+
if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
|
|
122
|
+
if (key.downArrow) setSelected((i) => Math.min(actions.length - 1, i + 1));
|
|
123
|
+
const lower = input.toLowerCase();
|
|
124
|
+
if (lower === "d") void run("derive");
|
|
125
|
+
else if (lower === "c") void beginClean();
|
|
126
|
+
else if (lower === "r") void run("restart");
|
|
127
|
+
else if (key.return) {
|
|
128
|
+
const a = actions[selected];
|
|
129
|
+
if (!a) return;
|
|
130
|
+
if (a.id === "clean") void beginClean();
|
|
131
|
+
else void run(a.id);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{ isActive: props.active && mode !== "confirmClean" },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const beginClean = async (): Promise<void> => {
|
|
138
|
+
try {
|
|
139
|
+
const all = await listOkeComposeProjects();
|
|
140
|
+
setStacks(all);
|
|
141
|
+
if (all.length === 0) {
|
|
142
|
+
props.onStatus({ kind: "done", label: "No oke-dev-* stacks" });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const initial = new Set<string>();
|
|
146
|
+
if (all.some((s) => s.project === project)) initial.add(project);
|
|
147
|
+
else if (all[0]) initial.add(all[0].project);
|
|
148
|
+
setCleanPicked(initial);
|
|
149
|
+
setCleanFocus(
|
|
150
|
+
Math.max(
|
|
151
|
+
0,
|
|
152
|
+
all.findIndex((s) => s.project === project),
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
setMode("pickClean");
|
|
156
|
+
} catch (err) {
|
|
157
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
158
|
+
props.onLog(`${m}\n`);
|
|
159
|
+
props.onStatus({ kind: "done", label: m });
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const runClean = async (): Promise<void> => {
|
|
164
|
+
setBusy(true);
|
|
165
|
+
setMode("main");
|
|
166
|
+
const targets = [...cleanPicked].sort();
|
|
167
|
+
props.onStatus({
|
|
168
|
+
kind: "running",
|
|
169
|
+
label: `Removing ${targets.length} stack(s)…`,
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
for (const p of targets) {
|
|
173
|
+
props.onLog(`oke docker clean: down -v ${p}\n`);
|
|
174
|
+
await downStack(p);
|
|
175
|
+
}
|
|
176
|
+
props.onStatus({
|
|
177
|
+
kind: "done",
|
|
178
|
+
label: `Removed ${targets.length} stack(s)`,
|
|
179
|
+
});
|
|
180
|
+
await refresh();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
183
|
+
props.onLog(`${m}\n`);
|
|
184
|
+
props.onStatus({ kind: "done", label: m });
|
|
185
|
+
} finally {
|
|
186
|
+
setBusy(false);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const run = async (id: Exclude<DockerAction, "clean">): Promise<void> => {
|
|
191
|
+
setBusy(true);
|
|
192
|
+
const labels = {
|
|
193
|
+
derive: "Running oke docker…",
|
|
194
|
+
restart: `Restarting ${project}…`,
|
|
195
|
+
} as const;
|
|
196
|
+
props.onStatus({ kind: "running", label: labels[id] });
|
|
197
|
+
try {
|
|
198
|
+
if (id === "derive") {
|
|
199
|
+
const { dockerCli } = await import("../../docker.ts");
|
|
200
|
+
const prev = process.cwd();
|
|
201
|
+
process.chdir(props.cwd);
|
|
202
|
+
let code = 1;
|
|
203
|
+
try {
|
|
204
|
+
code = await dockerCli([]);
|
|
205
|
+
} finally {
|
|
206
|
+
process.chdir(prev);
|
|
207
|
+
}
|
|
208
|
+
props.onLog(`oke docker → ${code}\n`);
|
|
209
|
+
props.onStatus({
|
|
210
|
+
kind: "done",
|
|
211
|
+
label: code === 0 ? "Derive ok" : `Derive exited ${code}`,
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
const proc = Bun.spawn(["docker", "compose", "-p", project, "restart"], {
|
|
215
|
+
stdout: "pipe",
|
|
216
|
+
stderr: "pipe",
|
|
217
|
+
});
|
|
218
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
219
|
+
new Response(proc.stdout).text(),
|
|
220
|
+
new Response(proc.stderr).text(),
|
|
221
|
+
proc.exited,
|
|
222
|
+
]);
|
|
223
|
+
if (stdout) props.onLog(stdout);
|
|
224
|
+
if (stderr) props.onLog(stderr);
|
|
225
|
+
props.onStatus({
|
|
226
|
+
kind: "done",
|
|
227
|
+
label: code === 0 ? "Stack restarted" : `Restart exited ${code}`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
await refresh();
|
|
231
|
+
} finally {
|
|
232
|
+
setBusy(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (mode === "confirmClean") {
|
|
237
|
+
const list = [...cleanPicked].sort().join(", ");
|
|
238
|
+
return (
|
|
239
|
+
<ConfirmDialog
|
|
240
|
+
message={`Remove stack(s) — containers, networks, volumes?\n${list}`}
|
|
241
|
+
onConfirm={() => void runClean()}
|
|
242
|
+
onCancel={() => setMode("pickClean")}
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (mode === "pickClean") {
|
|
248
|
+
return (
|
|
249
|
+
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
250
|
+
<Text bold color={TUI_ACCENT}>
|
|
251
|
+
Clean stacks
|
|
252
|
+
</Text>
|
|
253
|
+
<Text color={TUI_MUTED} dimColor>
|
|
254
|
+
Space toggle · Enter confirm · Esc cancel — whole project, not per-service
|
|
255
|
+
</Text>
|
|
256
|
+
{stacks.length === 0 ? (
|
|
257
|
+
<Text color={TUI_WARN}>No oke-dev-* stacks on this machine</Text>
|
|
258
|
+
) : (
|
|
259
|
+
stacks.map((s, i) => {
|
|
260
|
+
const on = i === cleanFocus;
|
|
261
|
+
const picked = cleanPicked.has(s.project);
|
|
262
|
+
const n = s.containers.length;
|
|
263
|
+
const current = s.project === project;
|
|
264
|
+
return (
|
|
265
|
+
<Text key={s.project} bold={on} color={on ? TUI_ACCENT : undefined}>
|
|
266
|
+
{on ? "› " : " "}
|
|
267
|
+
{picked ? "●" : "○"} {s.project}
|
|
268
|
+
<Text color={TUI_MUTED}>
|
|
269
|
+
{` ${n} container${n === 1 ? "" : "s"}`}
|
|
270
|
+
{current ? " · this project" : ""}
|
|
271
|
+
</Text>
|
|
272
|
+
</Text>
|
|
273
|
+
);
|
|
274
|
+
})
|
|
275
|
+
)}
|
|
276
|
+
</Box>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const online = rows.filter((r) => r.status === "ready").length;
|
|
281
|
+
const total = rows.length;
|
|
282
|
+
const allOnline = total > 0 && online === total;
|
|
283
|
+
const stackKnown = stacks.some((s) => s.project === project);
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
287
|
+
<Text bold color={TUI_ACCENT}>
|
|
288
|
+
Docker Compose
|
|
289
|
+
</Text>
|
|
290
|
+
<Box gap={1}>
|
|
291
|
+
<StatusDot status={allOnline ? "ready" : total === 0 ? "idle" : "pending"} />
|
|
292
|
+
<Text bold color={allOnline ? TUI_OK : TUI_WARN}>
|
|
293
|
+
{total === 0
|
|
294
|
+
? stackKnown
|
|
295
|
+
? `0 services · ${project}`
|
|
296
|
+
: `No stack · ${project}`
|
|
297
|
+
: `${online}/${total} services online`}
|
|
298
|
+
</Text>
|
|
299
|
+
{total > 0 ? <Text color={TUI_MUTED}>· {project}</Text> : null}
|
|
300
|
+
</Box>
|
|
301
|
+
|
|
302
|
+
<Box>
|
|
303
|
+
<Box width={22}>
|
|
304
|
+
<Text bold color={TUI_MUTED}>
|
|
305
|
+
Service
|
|
306
|
+
</Text>
|
|
307
|
+
</Box>
|
|
308
|
+
<Box width={10}>
|
|
309
|
+
<Text bold color={TUI_MUTED}>
|
|
310
|
+
Status
|
|
311
|
+
</Text>
|
|
312
|
+
</Box>
|
|
313
|
+
<Text bold color={TUI_MUTED}>
|
|
314
|
+
Detail
|
|
315
|
+
</Text>
|
|
316
|
+
</Box>
|
|
317
|
+
<Text color={TUI_MUTED}>{"─".repeat(48)}</Text>
|
|
318
|
+
|
|
319
|
+
{rows.length === 0 ? (
|
|
320
|
+
<Text color={TUI_MUTED}>
|
|
321
|
+
No containers for {project}. Start `oke dev` or run [D]erive + compose up.
|
|
322
|
+
</Text>
|
|
323
|
+
) : (
|
|
324
|
+
rows.map((r) => {
|
|
325
|
+
const sw = statusWord(r.status);
|
|
326
|
+
return (
|
|
327
|
+
<Box key={r.name}>
|
|
328
|
+
<Box width={22} gap={1}>
|
|
329
|
+
<StatusDot status={r.status} />
|
|
330
|
+
<Text>{r.name}</Text>
|
|
331
|
+
</Box>
|
|
332
|
+
<Box width={10}>
|
|
333
|
+
<Text color={sw.color}>{sw.word}</Text>
|
|
334
|
+
</Box>
|
|
335
|
+
<Text color={TUI_MUTED}>{r.detail}</Text>
|
|
336
|
+
</Box>
|
|
337
|
+
);
|
|
338
|
+
})
|
|
339
|
+
)}
|
|
340
|
+
|
|
341
|
+
<Box gap={2} marginTop={1}>
|
|
342
|
+
{actions.map((a, i) => (
|
|
343
|
+
<Text
|
|
344
|
+
key={a.id}
|
|
345
|
+
bold={props.active && i === selected}
|
|
346
|
+
color={
|
|
347
|
+
props.active && i === selected ? TUI_ACCENT : a.id === "clean" ? TUI_WARN : undefined
|
|
348
|
+
}
|
|
349
|
+
>
|
|
350
|
+
{props.active && i === selected ? "› " : " "}
|
|
351
|
+
{a.label}
|
|
352
|
+
</Text>
|
|
353
|
+
))}
|
|
354
|
+
</Box>
|
|
355
|
+
</Box>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { Manifest } from "../../../manifest/types.ts";
|
|
3
|
+
import { manifestToNavTree } from "./Navigator.tsx";
|
|
4
|
+
|
|
5
|
+
describe("manifestToNavTree", () => {
|
|
6
|
+
test("builds element sections from a Manifest", () => {
|
|
7
|
+
const manifest = {
|
|
8
|
+
oke: "1.0",
|
|
9
|
+
app: "demo",
|
|
10
|
+
flows: { hello: {} },
|
|
11
|
+
signals: { ping: {} },
|
|
12
|
+
stores: {},
|
|
13
|
+
ai: { models: { m1: {} }, prompts: {}, agents: {} },
|
|
14
|
+
} as unknown as Manifest;
|
|
15
|
+
const tree = manifestToNavTree(manifest);
|
|
16
|
+
expect(tree.some((n) => n.label.includes("app: demo"))).toBe(true);
|
|
17
|
+
const flows = tree.find((n) => n.id === "flows");
|
|
18
|
+
expect(flows?.children?.some((c) => c.label === "hello")).toBe(true);
|
|
19
|
+
const ai = tree.find((n) => n.id === "ai");
|
|
20
|
+
expect(ai?.children?.some((c) => c.id === "ai.models")).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
});
|