okengine 0.19.4 → 0.19.6
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 +1 -1
- package/site/content/docs/elements/flow/index.mdx +3 -6
- package/site/content/docs/reference/cli.mdx +20 -1
- package/site/content/docs/understand/try-it.mdx +1 -1
- package/src/cli/dev-app-runner.ts +23 -2
- package/src/cli/dev.test.ts +47 -0
- package/src/cli/dev.ts +11 -0
- package/src/compiler/extract-skip.test.ts +43 -0
- package/src/compiler/extract.ts +11 -4
- package/src/compiler/flow-path.test.ts +13 -0
- package/src/compiler/flow-path.ts +18 -0
- package/src/compiler/generate-adopt.ts +12 -2
- package/src/console/server/serve-static.test.ts +74 -0
- package/src/console/server/serve.ts +17 -3
- package/src/console/ui-next/dist/assets/{access-page-rO2HLPab.js → access-page-DZtT3hKL.js} +1 -1
- package/src/console/ui-next/dist/assets/{agent-disclosure-ae8w8JLx.js → agent-disclosure-BFnHhgjD.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-C-uHdh5d.js → cache-glyph-BguHj4PA.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-BJH54w4s.js → call-pii-button-DJmzLtht.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-CaE8cs9p.js → collapsible-BGuiLYBV.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-DdOQkQVK.js → duration-tone-C6X7Gcfr.js} +1 -1
- package/src/console/ui-next/dist/assets/{flows-page-DWeBszQQ.js → flows-page-40_zEnuY.js} +1 -1
- package/src/console/ui-next/dist/assets/{highlighted-json-B2gBsC4B.js → highlighted-json-DPc-p38T.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-BnMl0dJt.js → http-method-BaqumPKP.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-CBAP48v5.js → index-B0GkEkAT.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-DVpcJEnW.js → observability-page-BYG33yY_.js} +1 -1
- package/src/console/ui-next/dist/assets/{replica-lag-BUQf2Nf6.js → replica-lag-BufW34k2.js} +1 -1
- package/src/console/ui-next/dist/assets/{request-meta-CPkGDQgT.js → request-meta-iGMEf-yI.js} +1 -1
- package/src/console/ui-next/dist/assets/{store-page-CwS_cY4V.js → store-page-BhAEVbQw.js} +1 -1
- package/src/console/ui-next/dist/assets/{trace-detail-sheet-DNX7Y2bd.js → trace-detail-sheet-HOK-3aLD.js} +1 -1
- package/src/console/ui-next/dist/assets/{tree-expand-toggle-Bhdk27aF.js → tree-expand-toggle-BV1Yf8iy.js} +1 -1
- package/src/console/ui-next/dist/assets/{units-page-CsBh0XbI.js → units-page-mTT6I9zg.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-CsTlPtK9.js → vault-page-Dn0Pqj_8.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/kernel/boot.ts +9 -5
- package/src/kernel/effects-stamping.test.ts +44 -0
- package/src/kernel/errors.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.6",
|
|
4
4
|
"description": "One law. Eight elements. One contract. The backend model stays small; operational surfaces are derived from it instead of maintained separately.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -570,12 +570,9 @@ Default is `true` once `gate.auth.tenant` is on.
|
|
|
570
570
|
</Accordion>
|
|
571
571
|
|
|
572
572
|
<Accordion title="OKE1020 — no declared effects">
|
|
573
|
-
Cause: `Flow "{flow}" has no declared effects and no Manifest to derive them from.`
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
{}`. Real `fx` usage still needs inference (app source) or an explicit non-empty `effects` block
|
|
577
|
-
when extract cannot see the body. If extract failed (`Manifest extract failed`), install
|
|
578
|
-
`oxc-parser` and retry.
|
|
573
|
+
Cause: `Flow "{flow}" has no declared effects and no Manifest to derive them from.` Extract
|
|
574
|
+
failures append `Manifest extract failed — …`. Boot with `oke dev` / `oke build`; install
|
|
575
|
+
`oxc-parser` if extract cannot load. On Windows use `bun run dev` / `bunx oke dev`.
|
|
579
576
|
</Accordion>
|
|
580
577
|
|
|
581
578
|
</Accordions>
|
|
@@ -34,9 +34,11 @@ cd notes
|
|
|
34
34
|
### Run the dev loop
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
37
|
+
bun run dev
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
`bun run dev` is the portable form (`bunx oke dev` is the same). Bare `oke` needs `node_modules/.bin` on PATH — PowerShell does not add it.
|
|
41
|
+
|
|
40
42
|
Compose comes up (pull / create / start progress streams into the boot status
|
|
41
43
|
lines), the backend listens on **6530**, Console on **6533**, app MCP on **6535**,
|
|
42
44
|
docs MCP on **6536**. Open `http://localhost:6533`. Quit with **Ctrl+C**.
|
|
@@ -177,6 +179,23 @@ oke db search-backfill notes --batch 500
|
|
|
177
179
|
|
|
178
180
|
<Accordions>
|
|
179
181
|
|
|
182
|
+
<Accordion title="oke is not recognized (Windows PowerShell)">
|
|
183
|
+
PowerShell has no local `oke` on PATH. Use `bun run dev` or `bunx oke dev`. New Cursor/VS Code
|
|
184
|
+
terminals inherit `node_modules/.bin` from `.vscode/settings.json`. Global: `bun install -g
|
|
185
|
+
okengine`.
|
|
186
|
+
</Accordion>
|
|
187
|
+
|
|
188
|
+
<Accordion title="Console shows Shell assets not built">
|
|
189
|
+
`:6533` is up but the SPA files were not found. Published `okengine` ships them. Upgrade past the
|
|
190
|
+
Windows path bug (`file://` `/C:/…` lookup), then restart `bun run dev`. From a framework
|
|
191
|
+
checkout, run `bun run build` or keep `oke dev` so Vite HMR attaches.
|
|
192
|
+
</Accordion>
|
|
193
|
+
|
|
194
|
+
<Accordion title="OKE1020 on oke dev (Compose)">
|
|
195
|
+
The app child had no Manifest to stamp effects (`main.health`). `oke dev` now hands the parent
|
|
196
|
+
extract to the child. A failed extract appends `Manifest extract failed — …` (`oxc-parser`).
|
|
197
|
+
</Accordion>
|
|
198
|
+
|
|
180
199
|
<Accordion title="oke start: no entry found">
|
|
181
200
|
Set `package.json` `okengine.entry` or `main`, pass `--entry`, or keep a conventional
|
|
182
201
|
`src/app.ts`. Production imports that module; the app must call `createBunRuntime().serve` itself.
|
|
@@ -18,7 +18,7 @@ docker info
|
|
|
18
18
|
```bash
|
|
19
19
|
bunx create-oke@latest my-app
|
|
20
20
|
cd my-app
|
|
21
|
-
|
|
21
|
+
bun run dev
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Open the Console at the address printed in the terminal and claim it with the code shown there. The Flows listed weren't configured anywhere — they were derived from the code you just scaffolded.
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* - `PORT` — listen port (`0` = ephemeral)
|
|
12
12
|
* - `OKE_HOSTNAME` — listen hostname (default `127.0.0.1`)
|
|
13
13
|
* - `OKE_READY_PATH` — when set, write bound port here once listening
|
|
14
|
+
* - `OKE_ROOT_DIR` — project root for Manifest extract (defaults to cwd)
|
|
15
|
+
* - `OKE_MANIFEST_PATH` — optional JSON Manifest from the parent `oke dev`
|
|
16
|
+
* extract (avoids a second extract in the child; Windows-safe fallback)
|
|
14
17
|
*
|
|
15
18
|
* Soft reload must not clear the TTY — the parent `oke dev` board owns
|
|
16
19
|
* Docker status and the first-admin claim code.
|
|
@@ -19,6 +22,7 @@
|
|
|
19
22
|
import { resolve } from "node:path";
|
|
20
23
|
import { pathToFileURL } from "node:url";
|
|
21
24
|
import { installGracefulShutdown, type GracefulShutdownApp } from "../kernel/graceful-shutdown.ts";
|
|
25
|
+
import type { Manifest } from "../manifest/types.ts";
|
|
22
26
|
import { createBunRuntime } from "../runtime/bun.ts";
|
|
23
27
|
import { APP_PORT, type FetchApp } from "../runtime/types.ts";
|
|
24
28
|
import { formatAppReadyLine } from "../term.ts";
|
|
@@ -28,7 +32,7 @@ export const DEV_APP_SERVE_ID = "oke-dev-app";
|
|
|
28
32
|
|
|
29
33
|
/** App entry shape — FetchApp plus boot before serve. */
|
|
30
34
|
type BootableApp = FetchApp & {
|
|
31
|
-
boot(): Promise<unknown>;
|
|
35
|
+
boot(overrides?: { readonly rootDir?: string; readonly manifest?: Manifest }): Promise<unknown>;
|
|
32
36
|
stop(): Promise<void>;
|
|
33
37
|
readonly bootResult?: GracefulShutdownApp["bootResult"];
|
|
34
38
|
};
|
|
@@ -49,6 +53,20 @@ const readyPath = Bun.env["OKE_READY_PATH"];
|
|
|
49
53
|
// already the project root here (Bun.spawn's `cwd` option in dev.ts).
|
|
50
54
|
process.env["OKE_ROOT_DIR"] ??= process.cwd();
|
|
51
55
|
|
|
56
|
+
const manifestPath = Bun.env["OKE_MANIFEST_PATH"];
|
|
57
|
+
let manifest: Manifest | undefined;
|
|
58
|
+
if (manifestPath !== undefined && manifestPath.length > 0) {
|
|
59
|
+
try {
|
|
60
|
+
manifest = (await Bun.file(manifestPath).json()) as Manifest;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(
|
|
63
|
+
`oke dev-app-runner: OKE_MANIFEST_PATH unreadable — ${
|
|
64
|
+
err instanceof Error ? err.message : String(err)
|
|
65
|
+
}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
const absoluteEntry = resolve(entry);
|
|
53
71
|
const mod = (await import(pathToFileURL(absoluteEntry).href)) as {
|
|
54
72
|
app?: BootableApp;
|
|
@@ -63,7 +81,10 @@ if (
|
|
|
63
81
|
process.exit(1);
|
|
64
82
|
}
|
|
65
83
|
|
|
66
|
-
await mod.app.boot(
|
|
84
|
+
await mod.app.boot({
|
|
85
|
+
rootDir: process.env["OKE_ROOT_DIR"],
|
|
86
|
+
...(manifest !== undefined ? { manifest } : {}),
|
|
87
|
+
});
|
|
67
88
|
const handle = createBunRuntime().serve(mod.app, {
|
|
68
89
|
port,
|
|
69
90
|
hostname,
|
package/src/cli/dev.test.ts
CHANGED
|
@@ -1070,8 +1070,55 @@ export const posts = store.schema.table("posts", {
|
|
|
1070
1070
|
});
|
|
1071
1071
|
|
|
1072
1072
|
describe("oke dev Console vault config", () => {
|
|
1073
|
+
let session: DevSession | undefined;
|
|
1074
|
+
|
|
1075
|
+
afterEach(() => {
|
|
1076
|
+
session?.stop();
|
|
1077
|
+
session = undefined;
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1073
1080
|
test("hands loaded oke.config to Console so drivers.vault is not the env default", async () => {
|
|
1074
1081
|
const src = await Bun.file(new URL("./dev.ts", import.meta.url)).text();
|
|
1075
1082
|
expect(src).toMatch(/okeConfig:\s*loadedConfig/);
|
|
1076
1083
|
});
|
|
1084
|
+
|
|
1085
|
+
test("writes OKE_MANIFEST_PATH for the app child when a Manifest is available", async () => {
|
|
1086
|
+
const runner = await Bun.file(new URL("./dev-app-runner.ts", import.meta.url)).text();
|
|
1087
|
+
expect(runner).toContain("OKE_MANIFEST_PATH");
|
|
1088
|
+
expect(runner).toMatch(/mod\.app\.boot\(\{/);
|
|
1089
|
+
|
|
1090
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-dev-manifest-handoff-"));
|
|
1091
|
+
await Bun.write(join(dir, "src/app.ts"), "export {}\n");
|
|
1092
|
+
await Bun.write(join(dir, "oke.manifest.json"), JSON.stringify(LIVE_MANIFEST));
|
|
1093
|
+
let seen: Record<string, string> | undefined;
|
|
1094
|
+
const result = await runDev({
|
|
1095
|
+
stdinIsTTY: false,
|
|
1096
|
+
cwd: dir,
|
|
1097
|
+
silentClaim: true,
|
|
1098
|
+
keepAlive: false,
|
|
1099
|
+
appPort: 0,
|
|
1100
|
+
consolePort: 0,
|
|
1101
|
+
mcpPort: 0,
|
|
1102
|
+
docsMcpPort: 0,
|
|
1103
|
+
...stubCompose(),
|
|
1104
|
+
startApp: async (_entry, env) => {
|
|
1105
|
+
seen = env;
|
|
1106
|
+
return { stop() {} };
|
|
1107
|
+
},
|
|
1108
|
+
regenClient: async () => {},
|
|
1109
|
+
write: () => {},
|
|
1110
|
+
serveConsole: async () => ({ stop() {} }),
|
|
1111
|
+
serveMcp: async () => ({ stop() {} }),
|
|
1112
|
+
serveDocsMcp: async () => ({
|
|
1113
|
+
stop() {},
|
|
1114
|
+
port: 1,
|
|
1115
|
+
url: new URL("http://127.0.0.1:1"),
|
|
1116
|
+
}),
|
|
1117
|
+
});
|
|
1118
|
+
session = result.session;
|
|
1119
|
+
expect(result.code).toBe(0);
|
|
1120
|
+
expect(seen?.["OKE_MANIFEST_PATH"]).toBeTruthy();
|
|
1121
|
+
const dumped = JSON.parse(await Bun.file(seen!["OKE_MANIFEST_PATH"]!).text()) as Manifest;
|
|
1122
|
+
expect(dumped.app).toBe("dev-live");
|
|
1123
|
+
});
|
|
1077
1124
|
});
|
package/src/cli/dev.ts
CHANGED
|
@@ -1067,6 +1067,13 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
|
|
|
1067
1067
|
const seedManifest =
|
|
1068
1068
|
options.manifest !== undefined ? options.manifest : await tryLoadProjectManifest(cwd);
|
|
1069
1069
|
|
|
1070
|
+
let seedManifestPath: string | undefined;
|
|
1071
|
+
if (seedManifest) {
|
|
1072
|
+
seedManifestPath = join(tmpdir(), `oke-dev-manifest-${crypto.randomUUID()}.json`);
|
|
1073
|
+
await Bun.write(seedManifestPath, `${JSON.stringify(seedManifest)}\n`);
|
|
1074
|
+
env.OKE_MANIFEST_PATH = seedManifestPath;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1070
1077
|
async function refreshManifestInto(state: ConsoleState | null): Promise<void> {
|
|
1071
1078
|
if (!state) return;
|
|
1072
1079
|
try {
|
|
@@ -1401,6 +1408,10 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
|
|
|
1401
1408
|
consoleVite = null;
|
|
1402
1409
|
void vite?.stop();
|
|
1403
1410
|
void clearDevSessionLock(cwd);
|
|
1411
|
+
if (seedManifestPath) {
|
|
1412
|
+
void unlink(seedManifestPath).catch(() => {});
|
|
1413
|
+
seedManifestPath = undefined;
|
|
1414
|
+
}
|
|
1404
1415
|
if (dockerStarted) {
|
|
1405
1416
|
const started = dockerStarted;
|
|
1406
1417
|
dockerStarted = null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest extract must not walk `node_modules` — Windows glob paths use `\`
|
|
3
|
+
* and a naive `includes("node_modules/")` miss lets framework fixtures throw
|
|
4
|
+
* during extract, which Docker-first `oke dev` surfaces as **OKE1020**.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { extractManifest } from "./extract.ts";
|
|
12
|
+
|
|
13
|
+
describe("extractManifest — skip dependency trees", () => {
|
|
14
|
+
test("does not parse node_modules even when a poison file would fail extract", async () => {
|
|
15
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-extract-skip-"));
|
|
16
|
+
try {
|
|
17
|
+
await mkdir(join(dir, "src/flows/main"), { recursive: true });
|
|
18
|
+
await writeFile(
|
|
19
|
+
join(dir, "src/flows/main/health.ts"),
|
|
20
|
+
`
|
|
21
|
+
import { on, flow, http } from "okengine/http";
|
|
22
|
+
export const health = on(
|
|
23
|
+
http.get("/health").public(),
|
|
24
|
+
flow({ do: () => ({ ok: true as const }) }),
|
|
25
|
+
);
|
|
26
|
+
`,
|
|
27
|
+
);
|
|
28
|
+
await mkdir(join(dir, "node_modules/okengine"), { recursive: true });
|
|
29
|
+
// Nameless Signal consumer — **OKE1072** if this file is scanned.
|
|
30
|
+
await writeFile(
|
|
31
|
+
join(dir, "node_modules/okengine/poison.ts"),
|
|
32
|
+
`
|
|
33
|
+
import { on, flow, signal } from "okengine";
|
|
34
|
+
export const inbound = on(signal.once("x"), flow({ do: () => ({}) }));
|
|
35
|
+
`,
|
|
36
|
+
);
|
|
37
|
+
const manifest = await extractManifest({ rootDir: dir });
|
|
38
|
+
expect(manifest.flows?.["main.health"]).toBeDefined();
|
|
39
|
+
} finally {
|
|
40
|
+
await rm(dir, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
package/src/compiler/extract.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { parseSync } from "oxc-parser";
|
|
10
|
+
import { join } from "node:path";
|
|
10
11
|
|
|
11
12
|
import {
|
|
12
13
|
AI_NATIVE_DRIVER_IDS,
|
|
@@ -55,7 +56,13 @@ import {
|
|
|
55
56
|
type InferBinding,
|
|
56
57
|
type Literal,
|
|
57
58
|
} from "./effects-infer.ts";
|
|
58
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
isFlowsTreeFile,
|
|
61
|
+
isSkippedExtractSource,
|
|
62
|
+
nameFromFlowFile,
|
|
63
|
+
pathFromFlowFile,
|
|
64
|
+
toPosixPath,
|
|
65
|
+
} from "./flow-path.ts";
|
|
59
66
|
import {
|
|
60
67
|
defaultListInSchema,
|
|
61
68
|
jsonSchemaFromAst,
|
|
@@ -295,9 +302,9 @@ async function readSources(rootDir: string, pattern: string): Promise<SourceFile
|
|
|
295
302
|
cwd: rootDir,
|
|
296
303
|
onlyFiles: true,
|
|
297
304
|
})) {
|
|
298
|
-
if (
|
|
299
|
-
const abs =
|
|
300
|
-
files.push({ path, source: await Bun.file(abs).text() });
|
|
305
|
+
if (isSkippedExtractSource(path)) continue;
|
|
306
|
+
const abs = join(rootDir, path);
|
|
307
|
+
files.push({ path: toPosixPath(path), source: await Bun.file(abs).text() });
|
|
301
308
|
}
|
|
302
309
|
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
303
310
|
return files;
|
|
@@ -6,6 +6,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
6
6
|
import {
|
|
7
7
|
importSpecifierFromWalked,
|
|
8
8
|
isFlowsTreeFile,
|
|
9
|
+
isSkippedExtractSource,
|
|
9
10
|
nameFromFlowFile,
|
|
10
11
|
pathFromFlowFile,
|
|
11
12
|
toPosixPath,
|
|
@@ -117,3 +118,15 @@ describe("isFlowsTreeFile", () => {
|
|
|
117
118
|
expect(isFlowsTreeFile("lib/inbound.ts")).toBe(false);
|
|
118
119
|
});
|
|
119
120
|
});
|
|
121
|
+
|
|
122
|
+
describe("isSkippedExtractSource", () => {
|
|
123
|
+
test("skips node_modules as a path segment, including Windows separators", () => {
|
|
124
|
+
expect(isSkippedExtractSource("node_modules/okengine/src/index.ts")).toBe(true);
|
|
125
|
+
expect(isSkippedExtractSource("node_modules\\okengine\\src\\index.ts")).toBe(true);
|
|
126
|
+
expect(isSkippedExtractSource("web/node_modules/vite/index.ts")).toBe(true);
|
|
127
|
+
expect(isSkippedExtractSource(".git/hooks/pre-commit.ts")).toBe(true);
|
|
128
|
+
expect(isSkippedExtractSource("src/flows/main/health.test.ts")).toBe(true);
|
|
129
|
+
expect(isSkippedExtractSource("src/flows/main/health.ts")).toBe(false);
|
|
130
|
+
expect(isSkippedExtractSource("src/core.ts")).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -148,6 +148,24 @@ export function nameFromFlowFile(
|
|
|
148
148
|
return `${unit}.${exportName}`;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* True when a walked source should be omitted from Manifest extract.
|
|
153
|
+
*
|
|
154
|
+
* `node_modules` / `.git` are path *segments* (POSIX-normalized) so Windows
|
|
155
|
+
* `node_modules\okengine\…` is skipped the same as `node_modules/okengine/…`.
|
|
156
|
+
* Scanning dependency trees would otherwise parse framework fixtures and
|
|
157
|
+
* throw during extract — Docker-first `oke dev` then hard-fails **OKE1020**.
|
|
158
|
+
*
|
|
159
|
+
* @param relPath - Glob-relative path (any separator)
|
|
160
|
+
*/
|
|
161
|
+
export function isSkippedExtractSource(relPath: string): boolean {
|
|
162
|
+
const posix = toPosixPath(relPath);
|
|
163
|
+
const parts = posix.split("/").filter(Boolean);
|
|
164
|
+
if (parts.includes("node_modules") || parts.includes(".git")) return true;
|
|
165
|
+
const base = parts[parts.length - 1] ?? "";
|
|
166
|
+
return base.endsWith(".test.ts") || base.endsWith(".test.tsx");
|
|
167
|
+
}
|
|
168
|
+
|
|
151
169
|
/**
|
|
152
170
|
* True when the path is under a flows tree (`src/flows/…` or `…/flows/…`).
|
|
153
171
|
*
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `index` is also a reserved leaf).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
17
|
+
import { mkdir, readdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
|
|
20
20
|
import {
|
|
@@ -110,7 +110,17 @@ export async function writeAdoptBarrel(
|
|
|
110
110
|
// missing — write
|
|
111
111
|
}
|
|
112
112
|
await writeFile(tmp, source);
|
|
113
|
-
|
|
113
|
+
try {
|
|
114
|
+
await rename(tmp, target);
|
|
115
|
+
} catch {
|
|
116
|
+
// Windows cannot rename onto an existing path (EEXIST / EPERM).
|
|
117
|
+
await writeFile(target, source);
|
|
118
|
+
try {
|
|
119
|
+
await unlink(tmp);
|
|
120
|
+
} catch {
|
|
121
|
+
// tmp already replaced or removed
|
|
122
|
+
}
|
|
123
|
+
}
|
|
114
124
|
return { written: true };
|
|
115
125
|
}
|
|
116
126
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console SPA static files — filesystem paths, not `file://` URL pathnames.
|
|
3
|
+
*
|
|
4
|
+
* Windows `new URL(...).pathname` is `/C:/…`; concatenating that with
|
|
5
|
+
* `index.html` misses the built SPA and serves the fallback shell.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
9
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { resolveConsoleStaticDir, serveConsole, type ConsoleServerHandle } from "./serve.ts";
|
|
14
|
+
|
|
15
|
+
describe("resolveConsoleStaticDir", () => {
|
|
16
|
+
test("is a filesystem path next to this module, not a file URL pathname", () => {
|
|
17
|
+
const dir = resolveConsoleStaticDir();
|
|
18
|
+
const fromMeta = join(import.meta.dir, "../ui-next/dist");
|
|
19
|
+
const fromFileUrl = fileURLToPath(new URL("../ui-next/dist/", import.meta.url));
|
|
20
|
+
expect(dir).toBe(fromMeta);
|
|
21
|
+
expect(dir).toBe(fromFileUrl.replace(/[/\\]+$/, ""));
|
|
22
|
+
expect(dir.startsWith("file:")).toBe(false);
|
|
23
|
+
// URL.pathname on Windows is `/C:/Users/…` — not a Win32 path.
|
|
24
|
+
expect(dir).not.toMatch(/^\/[A-Za-z]:/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("keeps an explicit override", () => {
|
|
28
|
+
expect(resolveConsoleStaticDir("/tmp/console-spa")).toBe("/tmp/console-spa");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("serveConsole staticDir", () => {
|
|
33
|
+
let server: ConsoleServerHandle | undefined;
|
|
34
|
+
let cwd: string | undefined;
|
|
35
|
+
let staticDir: string | undefined;
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await Promise.resolve(server?.stop(true));
|
|
39
|
+
if (cwd) await rm(cwd, { recursive: true, force: true });
|
|
40
|
+
if (staticDir) await rm(staticDir, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("serves index.html from a directory without a trailing separator", async () => {
|
|
44
|
+
cwd = await mkdtemp(join(tmpdir(), "oke-console-static-cwd-"));
|
|
45
|
+
staticDir = await mkdtemp(join(tmpdir(), "oke-console-static-spa-"));
|
|
46
|
+
await writeFile(join(staticDir, "index.html"), "<!doctype html><title>console-spa</title>");
|
|
47
|
+
await writeFile(join(staticDir, "app.js"), "export {}\n");
|
|
48
|
+
|
|
49
|
+
server = await serveConsole({
|
|
50
|
+
port: 0,
|
|
51
|
+
hostname: "127.0.0.1",
|
|
52
|
+
cwd,
|
|
53
|
+
secret: "static-dir-secret",
|
|
54
|
+
silentClaim: true,
|
|
55
|
+
env: "test",
|
|
56
|
+
persist: false,
|
|
57
|
+
staticDir,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const html = await server.fetch(
|
|
61
|
+
new Request(String(server.url), { headers: { host: "127.0.0.1" } }),
|
|
62
|
+
);
|
|
63
|
+
expect(html.status).toBe(200);
|
|
64
|
+
const body = await html.text();
|
|
65
|
+
expect(body).toContain("console-spa");
|
|
66
|
+
expect(body).not.toContain("Shell assets not built");
|
|
67
|
+
|
|
68
|
+
const asset = await server.fetch(
|
|
69
|
+
new Request(String(new URL("/app.js", server.url)), { headers: { host: "127.0.0.1" } }),
|
|
70
|
+
);
|
|
71
|
+
expect(asset.status).toBe(200);
|
|
72
|
+
expect(await asset.text()).toContain("export");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* SameSite=Strict cookies are applied from the first response.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { join } from "node:path";
|
|
8
9
|
import {
|
|
9
10
|
checkRequestSecurity,
|
|
10
11
|
forbiddenResponse,
|
|
@@ -65,6 +66,19 @@ export function isConsoleKernelPath(pathname: string): boolean {
|
|
|
65
66
|
return pathname.startsWith("/console/") || pathname.startsWith("/plugin-frame/");
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Filesystem directory of the prebuilt Console SPA (`ui-next/dist`).
|
|
71
|
+
*
|
|
72
|
+
* Must be a real OS path. `new URL(...).pathname` on Windows is `/C:/…`, which
|
|
73
|
+
* `Bun.file` cannot open — the kernel then serves the "Shell assets not built"
|
|
74
|
+
* fallback even when the SPA is on disk.
|
|
75
|
+
*
|
|
76
|
+
* @param override - Explicit directory from {@link ServeConsoleOptions.staticDir}
|
|
77
|
+
*/
|
|
78
|
+
export function resolveConsoleStaticDir(override?: string): string {
|
|
79
|
+
return override ?? join(import.meta.dir, "../ui-next/dist");
|
|
80
|
+
}
|
|
81
|
+
|
|
68
82
|
/**
|
|
69
83
|
* Use the caller's `oke.config` when given; otherwise load it from `cwd`
|
|
70
84
|
* so Console sees `drivers.vault` (built-in vs the env default).
|
|
@@ -166,7 +180,7 @@ export async function serveConsole(
|
|
|
166
180
|
await bindManifestStoreRuntime(handle.state);
|
|
167
181
|
await bindManifestVaultRuntime(handle.state);
|
|
168
182
|
|
|
169
|
-
const staticDir = options.staticDir
|
|
183
|
+
const staticDir = resolveConsoleStaticDir(options.staticDir);
|
|
170
184
|
|
|
171
185
|
const live = createLiveWebsocket(handle.state);
|
|
172
186
|
const allowed = resolveAllowedHosts(hostname, options.allowedHosts);
|
|
@@ -210,7 +224,7 @@ export async function serveConsole(
|
|
|
210
224
|
}
|
|
211
225
|
|
|
212
226
|
const spa = isConsoleSpaPath(url.pathname);
|
|
213
|
-
const index = Bun.file(
|
|
227
|
+
const index = Bun.file(join(staticDir, "index.html"));
|
|
214
228
|
if (await index.exists()) {
|
|
215
229
|
return withConsoleSecurityHeaders(
|
|
216
230
|
new Response(index, {
|
|
@@ -430,7 +444,7 @@ export async function proxySpa(origin: string, request: Request): Promise<Respon
|
|
|
430
444
|
async function serveStatic(staticDir: string, pathname: string): Promise<Response | null> {
|
|
431
445
|
if (pathname.includes("..")) return null;
|
|
432
446
|
const path = pathname === "/" ? "index.html" : pathname.replace(/^\//, "");
|
|
433
|
-
const file = Bun.file(
|
|
447
|
+
const file = Bun.file(join(staticDir, path));
|
|
434
448
|
if (!(await file.exists())) return null;
|
|
435
449
|
return new Response(file);
|
|
436
450
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{r as t}from"./react-dom-Ddte4I-Q.js";import{A as n}from"./xyflow-yApv7D4e.js";import{Cr as r,Dr as i,E as a,Er as o,F as s,Fr as c,I as l,Ir as u,Lr as d,Nr as f,Pr as p,R as m,Rr as h,Sr as g,Tr as _,Vr as v,Yi as y,_r as b,ar as x,br as S,c as C,cr as w,dr as T,ha as ee,i as E,kr as D,l as O,lr as k,mr as A,nr as j,o as M,pr as N,rr as P,s as F,sr as I,tr as L,u as te,ur as R,vr as z,wr as B,x as V,xr as H}from"./shortcut-keys-3ILd8oGn.js";import{i as ne,t as re}from"./http-method-
|
|
1
|
+
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{r as t}from"./react-dom-Ddte4I-Q.js";import{A as n}from"./xyflow-yApv7D4e.js";import{Cr as r,Dr as i,E as a,Er as o,F as s,Fr as c,I as l,Ir as u,Lr as d,Nr as f,Pr as p,R as m,Rr as h,Sr as g,Tr as _,Vr as v,Yi as y,_r as b,ar as x,br as S,c as C,cr as w,dr as T,ha as ee,i as E,kr as D,l as O,lr as k,mr as A,nr as j,o as M,pr as N,rr as P,s as F,sr as I,tr as L,u as te,ur as R,vr as z,wr as B,x as V,xr as H}from"./shortcut-keys-3ILd8oGn.js";import{i as ne,t as re}from"./http-method-BaqumPKP.js";import{a as ie,c as ae,h as oe,i as se,n as ce,o as le,r as ue,s as U,t as de}from"./element-icons-BI8cJgdh.js";import{t as fe}from"./UserIcon-DaE2PB5_.js";import{t as pe}from"./PlusSignIcon-CwG3nxfu.js";import{bt as W,ct as me,it as he}from"./index-B0GkEkAT.js";import{t as ge}from"./explorer-empty-CJs5A-wm.js";import{a as G,c as _e,l as K,n as q,o as ve,r as ye,s as be,t as xe}from"./detail-header-DVWNjWjg.js";import{n as Se,t as J}from"./copy-inline-button-CAYD18cr.js";var Ce=[[`path`,{d:`M5 21H19`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:`1.5`,key:`0`}],[`path`,{d:`M12.125 12.75H12M12.25 12.75C12.25 12.8881 12.1381 13 12 13C11.8619 13 11.75 12.8881 11.75 12.75C11.75 12.6119 11.8619 12.5 12 12.5C12.1381 12.5 12.25 12.6119 12.25 12.75Z`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:`1.5`,key:`1`}],[`path`,{d:`M14.9152 7.61089L13.8078 5.38179C13.019 3.79393 12.6246 3 12 3C11.3754 3 10.981 3.79393 10.1922 5.38179L9.08483 7.61089C8.58107 8.62494 8.32919 9.13197 7.87976 9.24608C7.8485 9.25401 7.81689 9.26043 7.78503 9.26533C7.32682 9.3357 6.89919 8.96678 6.04393 8.22895C4.0124 6.47635 2.99663 5.60004 2.38034 5.94899C2.34045 5.97157 2.30213 5.99686 2.26565 6.02467C1.70197 6.45439 2.09541 7.74136 2.88229 10.3153L4.04783 14.1279C4.47098 15.5121 4.68255 16.2042 5.21787 16.6021C5.75318 17 6.47261 17 7.91147 17L16.0886 16.9999C17.5274 16.9999 18.2468 16.9999 18.7821 16.602C19.3175 16.2041 19.529 15.512 19.9522 14.1279L21.1177 10.3153C21.9046 7.74137 22.298 6.4544 21.7344 6.02468C21.6979 5.99687 21.6595 5.97158 21.6197 5.94899C21.0034 5.60006 19.9876 6.47636 17.9561 8.22896C17.1008 8.96679 16.6732 9.3357 16.215 9.26533C16.1831 9.26043 16.1515 9.25401 16.1202 9.24607C15.6708 9.13197 15.4189 8.62494 14.9152 7.61089Z`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:`1.5`,key:`2`}]],Y=e(t(),1);function we(e,t){if(e==null)return`never`;let n=e-t;if(Math.abs(n)<6e4)return`just now`;let r=Ee(Math.abs(n));return n<0?`${r} ago`:`in ${r}`}function Te(e,t){return e==null?`never`:e<=t?`expired`:we(e,t)}function Ee(e){if(e<36e5)return`${Math.floor(e/6e4)}m`;if(e<864e5)return`${Math.floor(e/36e5)}h`;let t=Math.floor(e/864e5);return t<100?`${t}d`:`${Math.floor(t/30)}mo`}var X=864e5;function De(e){let t=/^(\d+)(ms|s|m|h|d)$/.exec(e.trim());if(!t)return 0;let n=Number(t[1]);switch(t[2]){case`ms`:return n;case`s`:return n*1e3;case`m`:return n*6e4;case`h`:return n*36e5;case`d`:return n*X;default:return 0}}function Oe(e,t,n=``){if(e===`never`)return null;if(e===`30d`)return t+30*X;if(e===`90d`)return t+90*X;let r=De(n);return r>0?t+r:null}var Z=36e5;function ke(e,t){if(e==null)return{choice:`never`,custom:``};let n=e-t;return n<=0?{choice:`never`,custom:``}:Math.abs(n-30*X)<Z?{choice:`30d`,custom:``}:Math.abs(n-90*X)<Z?{choice:`90d`,custom:``}:{choice:`custom`,custom:Me(n)}}var Ae=7*X;function je(e,t){if(e==null||e<=t)return{choice:`90d`,custom:``};let n=e-t;return Math.abs(n-30*X)<=Ae?{choice:`30d`,custom:``}:Math.abs(n-90*X)<=Ae?{choice:`90d`,custom:``}:{choice:`custom`,custom:Me(n)}}function Me(e){return e<=0?``:e>=X&&e%X===0?`${e/X}d`:e>=Z&&e%Z===0?`${e/Z}h`:e>=6e4&&e%6e4==0?`${e/6e4}m`:e>=X?`${Math.max(1,Math.round(e/X))}d`:e>=Z?`${Math.max(1,Math.round(e/Z))}h`:`${Math.max(1,Math.round(e/6e4))}m`}var Ne=[`s`,`m`,`h`,`d`,`ms`],Pe={s:`Seconds`,m:`Minutes`,h:`Hours`,d:`Days`,ms:`Milliseconds`};function Fe(e,t,n){let r=e.trim(),i=t.trim();if(r.length===0&&i.length===0)return null;if(r.length===0||i.length===0||!/^\d+$/.test(r)||!/^\d+$/.test(i))return;let a=Number(r),o=Number(i);if(a<=0||o<=0)return;let s=`${o}${n}`;if(!(De(s)<=0))return{max:a,per:s}}function Ie(e){let t=/^(\d+)(ms|s|m|h|d)$/.exec(e.trim());return t?{count:t[1],unit:t[2]}:{count:``,unit:`m`}}function Le(e){let t=ze(e);return t.length===0?null:Be(t)?`ip`:Ve(t)?`host`:null}function Re(e){let t=new Set,n=[];for(let r of e.split(/[\s,]+/)){let e=ze(r);e.length===0||t.has(e)||Le(e)!=null&&(t.add(e),n.push(e))}return n}function ze(e){let t=e.trim().toLowerCase();if(t=t.replace(/^https?:\/\//,``),t.startsWith(`[`)&&t.includes(`]`)){let e=t.indexOf(`]`),n=t.slice(1,e),r=t.slice(e+1);t=r.startsWith(`:`)||r.startsWith(`/`)?n:`${n}${r}`}let n=t.indexOf(`/`);return n>=0&&(t=t.slice(0,n)),t.endsWith(`.`)&&(t=t.slice(0,-1)),t}function Be(e){return/^\d{1,3}(?:\.\d{1,3}){3}$/.test(e)?e.split(`.`).every(e=>Number(e)<=255):!e.includes(`:`)||!/^[0-9a-f:]+$/.test(e)||e.split(`::`).length>2?!1:e.split(`:`).filter(e=>e.length>0).length>=1}function Ve(e){if(e===`localhost`)return!0;if(e.length>253||e.includes(`:`))return!1;let t=e.split(`.`);return t.length<2?!1:t.every((e,n)=>!(e.length===0||e.length>63||!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(e)||n===t.length-1&&!/^[a-z]{2,63}$/.test(e)))}function He(e){let t=[],n=new Map;for(let r of e){let e=r.indexOf(`:`),i=e<=0?r:r.slice(0,e),a=e<=0?r:r.slice(e+1),o=n.get(i);if(o){o.push({scope:r,action:a});continue}t.push(i),n.set(i,[{scope:r,action:a}])}return t.map(e=>({group:e,items:n.get(e)??[]}))}var Q=n(),Ue=`GET /secure
|
|
2
2
|
Authorization: Bearer <secret>`,We=`{ "ok": true }`;function Ge({keyRow:e,blast:t,now:n,leading:r,onEdit:a,onRefresh:s,onRevoke:c,onRotate:l,rotatePending:u}){let d=e.revokedAt!==null,f=e.expiresAt!==null&&e.expiresAt<=n,p=t?.lastUsedAt??e.lastUsedAt,m=t?.callVolume??0,h=Te(e.expiresAt,n),g=we(p,n),_=e.rateLimit?`${e.rateLimit.max} / ${e.rateLimit.per}`:`none`,v=(0,Y.useMemo)(()=>He(e.scopes),[e.scopes]),y=e.creatorScopes.length,b=y>0&&e.scopes.length<y;return(0,Q.jsxs)(`div`,{className:`flex h-full min-h-0 flex-col overflow-y-auto`,"data-slot":`access-detail`,"aria-label":`Access key detail`,"aria-live":`polite`,children:[(0,Q.jsx)(xe,{dataSlot:`access-detail-header`,leading:r,icon:(0,Q.jsx)(i,{icon:oe,className:`size-4`}),title:e.name,badge:(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(`span`,{className:`inline-flex items-center gap-1 font-mono text-[10px] font-medium tracking-[0.08em] text-muted-foreground uppercase`,children:[(0,Q.jsx)(i,{icon:e.plane===`operator`?Ce:fe,className:`size-3`,"aria-hidden":!0}),e.plane]}),d?(0,Q.jsx)(`span`,{className:`font-mono text-[10px] text-destructive`,children:`revoked`}):null,f&&!d?(0,Q.jsx)(`span`,{className:`font-mono text-[10px] text-amber-700 dark:text-amber-400`,children:`expired`}):null,e.unused90d&&!d&&!f?(0,Q.jsx)(`span`,{className:`font-mono text-[10px] text-amber-700 dark:text-amber-400`,children:`unused`}):null]}),subtitle:(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsx)(`code`,{className:`min-w-0 truncate font-mono text-[11px] leading-none text-foreground/80`,children:e.id}),(0,Q.jsx)(J,{value:e.id,label:`Copy ${e.id}`})]}),actions:(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsx)(U,{label:`Rename or re-attenuate scopes`,className:`flex self-stretch`,children:(0,Q.jsx)(V,{type:`button`,variant:`ghost`,size:`sm`,className:`h-full rounded-none`,disabled:d,onClick:a,children:`Edit`})}),(0,Q.jsx)(U,{label:`Reset expiry from now. The secret stays the same.`,className:`flex self-stretch`,children:(0,Q.jsx)(V,{type:`button`,variant:`ghost`,size:`sm`,className:`h-full rounded-none`,"data-slot":`access-refresh`,disabled:d,onClick:s,children:`Refresh`})}),(0,Q.jsx)(U,{label:`Mint a new secret. The current secret stops working after residual TTL.`,className:`flex self-stretch`,children:(0,Q.jsx)(V,{type:`button`,variant:`ghost`,size:`sm`,className:`h-full rounded-none`,disabled:u||d,onClick:l,children:`Rotate`})}),(0,Q.jsx)(U,{label:`Irreversible. Residual sessions may continue for the access TTL.`,className:`flex self-stretch`,children:(0,Q.jsx)(V,{type:`button`,variant:`destructive`,size:`sm`,className:`h-full rounded-none`,disabled:d,onClick:c,children:`Revoke`})})]})}),(0,Q.jsx)(Ke,{revoked:d,expired:f,unused:e.unused90d,residual:t?.residualAccessNote}),(0,Q.jsxs)(`div`,{className:z,"data-slot":`access-briefing`,role:`group`,"aria-label":`Usage`,children:[(0,Q.jsx)(Xe,{label:`calls`,value:String(m)}),(0,Q.jsx)(Xe,{label:`used`,value:g,hint:p?new Date(p).toISOString():`No recorded call`,warn:p==null&&!d}),(0,Q.jsx)(Xe,{label:`expires`,value:h,hint:e.expiresAt?new Date(e.expiresAt).toISOString():`No expiry`,warn:f&&!d}),(0,Q.jsx)(Xe,{label:`rate`,value:_})]}),(0,Q.jsxs)(`section`,{className:`shrink-0 border-b border-border/60`,"aria-label":`Scopes`,children:[(0,Q.jsx)($,{title:`Scopes`,meta:b?`${e.scopes.length} of ${y}`:String(e.scopes.length),children:e.scopes.length>0?(0,Q.jsx)(J,{value:e.scopes.join(`
|
|
3
3
|
`),label:`Copy scopes`}):null}),v.length===0?(0,Q.jsx)(`p`,{className:`px-2 py-1.5 text-[11px] text-muted-foreground`,children:`No scopes granted.`}):(0,Q.jsx)(`ul`,{"data-slot":`access-scopes`,children:v.map(e=>(0,Q.jsxs)(`li`,{className:A,"aria-label":e.group,children:[(0,Q.jsx)(`span`,{className:o(B,`w-[5.5rem] shrink-0`),children:e.group}),(0,Q.jsx)(`div`,{className:`flex min-w-0 flex-1 flex-wrap items-center gap-x-2`,children:e.items.map(e=>(0,Q.jsx)(`span`,{title:e.scope,className:`font-mono text-[11px] text-foreground`,children:e.action},e.scope))}),(0,Q.jsx)(Ze,{value:e.items.map(e=>e.scope).join(`
|
|
4
4
|
`),label:`Copy ${e.group} scopes`})]},e.group))})]}),(0,Q.jsxs)(`section`,{className:`shrink-0 border-b border-border/60`,"aria-label":`Key`,children:[(0,Q.jsx)($,{title:`Key`}),(0,Q.jsxs)(`dl`,{"data-slot":`access-key-facts`,children:[(0,Q.jsx)(Qe,{label:`Issuer`,value:e.creatorId,copy:e.creatorId,copyLabel:`Copy ${e.creatorId}`}),(0,Q.jsx)(Qe,{label:`Created`,value:we(e.createdAt,n),hint:new Date(e.createdAt).toISOString()}),(0,Q.jsx)(Qe,{label:`Expires`,value:h,hint:e.expiresAt?new Date(e.expiresAt).toISOString():void 0,warn:f&&!d}),(0,Q.jsx)(Qe,{label:`Rate`,value:_})]})]}),(0,Q.jsx)(qe,{entries:e.ipAllowlist}),(0,Q.jsx)(Je,{sourceAddresses:t?.sourceAddresses??[],residual:t?.residualAccessNote,lastUsed:p,now:n}),(0,Q.jsx)(Ye,{scopes:e.scopes})]})}function Ke({revoked:e,expired:t,unused:n,residual:r}){return e?(0,Q.jsx)(`section`,{className:o(z,`border-destructive/25 bg-destructive/5`),"aria-label":`Revoked`,role:`status`,children:(0,Q.jsxs)(`p`,{className:`flex min-w-0 flex-1 items-center px-2 text-[12px] text-destructive`,children:[`Revoked`,(0,Q.jsxs)(`span`,{className:`text-muted-foreground`,children:[` `,`· `,r??`This secret no longer authenticates.`]})]})}):t?(0,Q.jsx)(`section`,{className:o(z,`border-amber-500/25 bg-amber-500/5`),"aria-label":`Expired`,role:`status`,children:(0,Q.jsxs)(`p`,{className:`flex min-w-0 flex-1 items-center px-2 text-[12px] text-amber-900 dark:text-amber-300`,children:[`Expired`,r?(0,Q.jsxs)(`span`,{className:`text-muted-foreground`,children:[` · `,r]}):null]})}):n?(0,Q.jsx)(`section`,{className:o(z,`border-amber-500/25 bg-amber-500/5`),"aria-label":`Unused`,role:`status`,children:(0,Q.jsxs)(`p`,{className:`flex min-w-0 flex-1 items-center px-2 text-[12px] text-amber-900 dark:text-amber-300`,children:[`No calls in 90 days`,(0,Q.jsx)(`span`,{className:`text-muted-foreground`,children:` · rotate or revoke`})]})}):null}function qe({entries:e}){return(0,Q.jsxs)(`section`,{className:`shrink-0 border-b border-border/60`,"aria-label":`Allow`,children:[(0,Q.jsx)($,{title:`Allow`,meta:e.length>0?String(e.length):`any`}),e.length===0?(0,Q.jsx)(`p`,{className:`px-2 py-1.5 text-[11px] text-muted-foreground`,children:`Any origin.`}):(0,Q.jsx)(`ul`,{"data-slot":`access-allow`,children:e.map(e=>{let t=Le(e);return(0,Q.jsxs)(`li`,{className:A,children:[(0,Q.jsx)(`span`,{className:o(B,`w-[5.5rem] shrink-0`),children:t===`ip`?`IP`:t===`host`?`Host`:`—`}),(0,Q.jsx)(`span`,{className:`min-w-0 flex-1 truncate font-mono text-[11px]`,children:e}),(0,Q.jsx)(Ze,{value:e,label:`Copy ${e}`})]},e)})})]})}function Je({sourceAddresses:e,residual:t,lastUsed:n,now:r}){return(0,Q.jsxs)(`section`,{className:`shrink-0 border-b border-border/60`,"aria-label":`Usage`,children:[(0,Q.jsx)($,{title:`Usage`,meta:n?we(n,r):`never`}),e.length===0?(0,Q.jsx)(`p`,{className:`px-2 py-1.5 text-[11px] text-muted-foreground`,children:`No observed source IPs.`}):(0,Q.jsx)(`ul`,{"data-slot":`access-sources`,children:e.map(e=>(0,Q.jsxs)(`li`,{className:A,children:[(0,Q.jsx)(`span`,{className:o(B,`w-[5.5rem] shrink-0`),children:`IP`}),(0,Q.jsx)(`span`,{className:`min-w-0 flex-1 truncate font-mono text-[11px]`,children:e}),(0,Q.jsx)(Ze,{value:e,label:`Copy ${e}`})]},e))}),t?(0,Q.jsx)(`p`,{className:`border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground`,children:t}):null]})}function Ye({scopes:e}){return(0,Q.jsxs)(`section`,{"data-slot":`access-call-example`,children:[(0,Q.jsx)($,{title:`Request`,children:(0,Q.jsx)(J,{value:Ue,label:`Copy request`})}),(0,Q.jsxs)(`div`,{className:`flex min-w-0 border-b border-border/60`,children:[(0,Q.jsx)(`div`,{className:o(`w-1 shrink-0 self-stretch`,ne(`GET`)),"aria-hidden":!0}),(0,Q.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,Q.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2 px-2.5 py-2`,children:[(0,Q.jsx)(`span`,{className:o(`shrink-0 font-mono text-[10px] font-semibold tracking-[0.08em] uppercase`,_(re(`GET`))),children:`GET`}),(0,Q.jsx)(`span`,{className:`min-w-0 flex-1 truncate font-mono text-xs text-foreground select-all`,children:`/secure`})]}),(0,Q.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2 border-t border-border/60 px-2.5 py-2`,children:[(0,Q.jsx)(`span`,{className:`w-[7.5rem] shrink-0 truncate font-mono text-[11px] font-medium text-sky-600 dark:text-sky-400`,children:`Authorization`}),(0,Q.jsx)(`span`,{className:`min-w-0 flex-1 truncate font-mono text-[11px] text-muted-foreground`,children:`Bearer <secret>`})]})]})]}),(0,Q.jsx)($,{title:`Response`,children:(0,Q.jsx)(J,{value:We,label:`Copy response`})}),(0,Q.jsxs)(`div`,{className:`flex min-w-0 border-b border-border/60`,children:[(0,Q.jsx)(`div`,{className:`w-1 shrink-0 self-stretch bg-emerald-500`,"aria-hidden":!0}),(0,Q.jsx)(`p`,{className:`min-w-0 flex-1 px-2.5 py-2 font-mono text-[11px] text-foreground/80 select-all`,children:We})]}),(0,Q.jsxs)(`p`,{className:`px-2 py-1.5 text-[11px] text-muted-foreground`,children:[`Seed the Bearer secret from create / rotate. The key is the issuer; gates see`,` `,e.join(`, `)||`its scopes`,`.`]})]})}function $({title:e,meta:t,children:n}){return(0,Q.jsxs)(`header`,{className:o(z,`items-center gap-2 px-2`),children:[(0,Q.jsx)(`h3`,{className:o(B,`shrink-0 leading-none`),children:e}),t?(0,Q.jsx)(`span`,{className:o(x,!n&&`ml-auto`),children:t}):null,n?(0,Q.jsx)(`div`,{className:`ml-auto flex items-center`,children:n}):null]})}function Xe({label:e,value:t,hint:n,warn:r=!1}){return(0,Q.jsxs)(`span`,{title:n,className:o(S,H,`gap-1.5`),children:[(0,Q.jsx)(`span`,{className:`tracking-[0.08em] uppercase`,children:e}),(0,Q.jsx)(`span`,{className:o(`font-mono tabular-nums text-foreground`,r&&`text-amber-800 dark:text-amber-400`),children:t})]})}function Ze({value:e,label:t}){return(0,Q.jsx)(`div`,{className:`opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100`,children:(0,Q.jsx)(J,{value:e,label:t})})}function Qe({label:e,value:t,hint:n,copy:r,copyLabel:i,warn:a=!1}){return(0,Q.jsxs)(`div`,{className:A,children:[(0,Q.jsx)(`dt`,{className:o(B,`w-[5.5rem] shrink-0`),children:e}),(0,Q.jsx)(`dd`,{className:o(`min-w-0 flex-1 truncate font-mono text-[11px]`,a&&`text-amber-800 dark:text-amber-400`),title:n,children:t}),r?(0,Q.jsx)(Ze,{value:r,label:i??`Copy ${e}`}):null]})}var $e=[`console.access.list`];function et(){return y({queryKey:$e,queryFn:async()=>{let e=await c();if(e.error||!e.data)throw Error(e.error?.message??`Empty access list`);return e.data}})}function tt({rows:e,selectedKey:t,query:n,loading:r=!1,onSelect:i}){let a=e.filter(e=>e.plane===`user`),o=e.filter(e=>e.plane===`operator`);return(0,Q.jsxs)(`div`,{id:`access-list`,"data-slot":`access-list`,className:`min-h-0 flex-1 overflow-y-auto`,children:[r&&e.length===0?(0,Q.jsx)(`p`,{className:R,children:`Loading…`}):null,!r&&e.length===0&&n.trim().length>0?(0,Q.jsx)(`p`,{className:R,children:`No keys match.`}):null,(0,Q.jsx)(nt,{label:`User`,icon:fe,rows:a,selectedKey:t,onSelect:i}),(0,Q.jsx)(nt,{label:`Operator`,icon:Ce,rows:o,selectedKey:t,onSelect:i})]})}function nt(e){return(0,Q.jsxs)(`section`,{className:L,"aria-label":e.label,children:[(0,Q.jsxs)(`div`,{className:j,children:[(0,Q.jsx)(i,{icon:e.icon,className:o(k,`text-muted-foreground`),"aria-hidden":!0}),(0,Q.jsx)(`span`,{className:o(P,`flex-1`),children:e.label}),(0,Q.jsx)(`span`,{className:x,children:e.rows.length})]}),e.rows.map(t=>{let n=t.id===e.selectedKey,r=t.revokedAt!==null;return(0,Q.jsxs)(`button`,{type:`button`,"data-slot":`access-key-row`,className:o(A,n&&`bg-muted/70 text-foreground`),onClick:()=>e.onSelect(t.id),children:[(0,Q.jsx)(`span`,{className:o(N,n&&`bg-sky-500`)}),(0,Q.jsx)(i,{icon:oe,className:o(k,`text-muted-foreground`)}),(0,Q.jsx)(`span`,{className:o(`min-w-0 truncate`,r&&`text-muted-foreground line-through`),children:t.name}),r?(0,Q.jsx)(`span`,{className:`ml-auto text-[10px] text-muted-foreground`,children:`revoked`}):t.unused90d?(0,Q.jsx)(`span`,{className:`ml-auto text-[10px] text-amber-700 dark:text-amber-400`,children:`unused`}):(0,Q.jsx)(`span`,{className:o(`ml-auto tabular-nums text-[10px] text-muted-foreground`),children:t.scopes.length})]},t.id)})]})}function rt({entries:e,rateMax:t,rateCount:n,rateUnit:r,onEntries:c,onRateMax:u,onRateCount:d,onRateUnit:f}){let[p,h]=(0,Y.useState)(``),_=Re(p),v=p.trim().length>0&&_.length===0,y=()=>{if(_.length===0)return;let t=new Set(e),n=[...e];for(let e of _)t.has(e)||(t.add(e),n.push(e));c(n),h(``)};return(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(`div`,{className:`border-b border-border/50`,"data-slot":`access-allow-field`,children:[(0,Q.jsxs)(`div`,{className:`flex items-stretch`,children:[(0,Q.jsx)(`p`,{className:`flex h-8 w-[5.5rem] shrink-0 items-center px-4 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase`,children:`Allow`}),(0,Q.jsx)(a,{value:p,onChange:e=>h(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),y())},placeholder:`203.0.113.4 or ci.example.com`,autoComplete:`off`,"aria-label":`Add allow entry`,"aria-invalid":v,flat:!0,className:o(q,`min-w-0 flex-1 font-mono`)}),e.length>0?(0,Q.jsx)(`span`,{className:o(x,`flex h-8 items-center pr-2`),children:e.length}):null,(0,Q.jsx)(`button`,{type:`button`,className:o(w,`h-8`),"aria-label":`Add allow entry`,disabled:_.length===0,onClick:y,children:(0,Q.jsx)(i,{icon:pe,className:`size-3.5`,"aria-hidden":!0})})]}),v?(0,Q.jsx)(`p`,{className:`border-t border-border/50 px-4 py-2 text-[11px] text-destructive`,children:`Need an IP or host — 203.0.113.4, ::1, localhost, or ci.example.com.`}):e.length===0?(0,Q.jsx)(`p`,{className:`border-t border-border/50 px-4 py-2 text-[11px] text-muted-foreground`,children:`Any client. Add an IP or host.`}):(0,Q.jsx)(`div`,{role:`list`,"aria-label":`Allow list`,children:e.map(t=>{let n=Le(t);return(0,Q.jsxs)(`div`,{role:`listitem`,"data-slot":`access-allow-entry`,"data-kind":n??`invalid`,className:o(A,`last:border-b-0`),children:[(0,Q.jsx)(`span`,{className:o(B,`w-[5.5rem] shrink-0 px-1.5`,n==null&&`text-destructive`),children:n===`ip`?`IP`:n===`host`?`Host`:`Bad`}),(0,Q.jsx)(`span`,{className:o(`min-w-0 truncate font-mono text-[11px]`,n==null&&`text-destructive`),children:t}),(0,Q.jsx)(`button`,{type:`button`,className:o(I,`ml-auto opacity-0 group-hover:opacity-100 focus-visible:opacity-100`),"aria-label":`Remove ${t}`,onClick:()=>c(e.filter(e=>e!==t)),children:(0,Q.jsx)(i,{icon:D,className:`size-3`,"aria-hidden":!0})})]},t)})})]}),(0,Q.jsxs)(`div`,{className:`flex items-stretch border-b border-border/50`,"data-slot":`access-rate-field`,children:[(0,Q.jsx)(`p`,{className:`flex h-8 w-[5.5rem] shrink-0 items-center px-4 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase`,children:`Rate`}),(0,Q.jsxs)(`label`,{className:`flex min-w-0 flex-1 items-stretch border-r border-border/50`,children:[(0,Q.jsx)(`span`,{className:`flex h-8 shrink-0 items-center pl-1 text-[10px] font-semibold tracking-[0.08em] text-muted-foreground uppercase`,children:`Max`}),(0,Q.jsx)(a,{value:t,onChange:e=>u(e.target.value),placeholder:`none`,inputMode:`numeric`,autoComplete:`off`,"aria-label":`Rate max`,flat:!0,className:o(q,`min-w-0 flex-1 px-2 font-mono tabular-nums`)})]}),(0,Q.jsxs)(`label`,{className:`flex min-w-0 flex-1 items-stretch`,children:[(0,Q.jsx)(`span`,{className:`flex h-8 shrink-0 items-center pl-2 text-[10px] font-semibold tracking-[0.08em] text-muted-foreground uppercase`,children:`Every`}),(0,Q.jsx)(a,{value:n,onChange:e=>d(e.target.value),placeholder:`1`,inputMode:`numeric`,autoComplete:`off`,"aria-label":`Rate window`,flat:!0,className:o(q,`min-w-0 flex-1 px-2 font-mono tabular-nums`)})]}),(0,Q.jsx)(`div`,{className:`flex h-8 shrink-0 items-stretch`,role:`group`,"aria-label":`Rate unit`,children:Ne.map(e=>(0,Q.jsxs)(s,{children:[(0,Q.jsx)(m,{render:t=>(0,Q.jsx)(`button`,{...t,type:`button`,"aria-pressed":r===e,"aria-label":Pe[e],onClick:n=>{t.onClick?.(n),f(e)},className:o(S,`h-8 px-1.5 font-mono font-semibold`,r===e?g:H),children:e})}),(0,Q.jsx)(l,{side:`bottom`,className:`text-[11px]`,children:Pe[e]})]},e))})]})]})}function it({expires:e,customExpires:t,onExpires:n,onCustomExpires:r}){return(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(ve,{label:`Expires`,children:[(0,Q.jsx)(G,{active:e===`never`,onClick:()=>n(`never`),children:`never`}),(0,Q.jsx)(G,{active:e===`30d`,onClick:()=>n(`30d`),children:`30d`}),(0,Q.jsx)(G,{active:e===`90d`,onClick:()=>n(`90d`),children:`90d`}),(0,Q.jsx)(G,{active:e===`custom`,onClick:()=>n(`custom`),children:`custom`})]}),e===`custom`?(0,Q.jsx)(_e,{label:`Duration`,hint:`7d · 12h · 30m`,children:(0,Q.jsx)(a,{value:t,onChange:e=>r(e.target.value),placeholder:`7d`,autoComplete:`off`,"aria-label":`Custom expiry duration`,flat:!0,className:o(q,`font-mono`)})}):null]})}function at({value:e,placeholder:t,label:n,onChange:r}){return(0,Q.jsxs)(`label`,{className:`relative min-w-0 flex-1`,children:[(0,Q.jsx)(i,{icon:he,className:`pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2 text-muted-foreground`,"aria-hidden":!0}),(0,Q.jsx)(a,{value:e,onChange:e=>r(e.target.value),placeholder:t,"aria-label":n,autoComplete:`off`,flat:!0,className:o(ye)})]})}function ot({scopes:e,selected:t,empty:n,onChange:r}){let[i,a]=(0,Y.useState)(``),s=new Set(t),c=(0,Y.useMemo)(()=>st(e,i),[e,i]),l=(0,Y.useMemo)(()=>He(c),[c]),u=e.join(`\0`);return(0,Y.useEffect)(()=>{a(``)},[u]),(0,Q.jsxs)(`div`,{className:`border-b border-border/50`,"data-slot":`access-scope-field`,children:[(0,Q.jsxs)(`div`,{className:`flex items-stretch`,children:[(0,Q.jsx)(`p`,{className:`flex h-8 w-[5.5rem] shrink-0 items-center px-4 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase`,children:`Scopes`}),e.length>0?(0,Q.jsx)(at,{value:i,placeholder:`Search scopes`,label:`Search scopes`,onChange:a}):null]}),e.length===0?(0,Q.jsx)(`p`,{className:`border-t border-border/50 px-4 py-3 text-[11px] text-muted-foreground`,children:n}):c.length===0?(0,Q.jsx)(`p`,{className:`border-t border-border/50 px-4 py-3 text-[11px] text-muted-foreground`,children:`No scopes match.`}):(0,Q.jsx)(`div`,{className:`max-h-56 overflow-y-auto`,role:`group`,"aria-label":`Scopes`,children:l.map(e=>(0,Q.jsxs)(`div`,{className:`flex items-stretch border-t border-border/50`,role:`group`,"aria-label":e.group,children:[(0,Q.jsx)(`p`,{className:`flex w-[5.5rem] shrink-0 items-center px-4 font-mono text-[10px] font-semibold tracking-[0.08em] text-muted-foreground uppercase`,children:e.group}),(0,Q.jsx)(`div`,{className:`flex min-w-0 flex-1 flex-wrap items-stretch`,children:e.items.map(e=>{let n=s.has(e.scope);return(0,Q.jsx)(`button`,{type:`button`,"aria-pressed":n,"aria-label":e.scope,"data-slot":`access-scope-chip`,title:e.scope,onClick:()=>{r(n?t.filter(t=>t!==e.scope):[...t,e.scope])},className:o(`inline-flex h-8 min-w-0 items-center px-2 font-mono text-[10px] font-semibold outline-none select-none`,`focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset`,n?`bg-muted text-foreground`:`text-muted-foreground hover:text-foreground`),children:e.action},e.scope)})})]},e.group))})]})}function st(e,t){let n=t.trim().toLowerCase();return n.length===0?e:e.filter(e=>e.toLowerCase().includes(n))}function ct({open:e,users:t,userScopes:n,operatorScopes:r,pending:i,error:s,onOpenChange:c,onSubmit:l}){let[u,d]=(0,Y.useState)(`user`),[f,p]=(0,Y.useState)(``),[m,h]=(0,Y.useState)(``),[g,_]=(0,Y.useState)([]),[v,y]=(0,Y.useState)(`never`),[b,x]=(0,Y.useState)(``),[S,w]=(0,Y.useState)([]),[T,ee]=(0,Y.useState)(``),[D,k]=(0,Y.useState)(``),[j,P]=(0,Y.useState)(`m`),[I,L]=(0,Y.useState)(``),R=t.find(e=>e.id===m)??null,z=(0,Y.useMemo)(()=>{let e=I.trim().toLowerCase(),n=e.length===0?t:t.filter(t=>t.name.toLowerCase().includes(e)||t.id.toLowerCase().includes(e)||t.email.toLowerCase().includes(e));return R&&!n.some(e=>e.id===R.id)?[R,...n]:n},[t,I,R]),B=(0,Y.useMemo)(()=>u===`operator`?r:R==null?[]:R.scopes.length>0?R.scopes:n,[u,n,r,R]);(0,Y.useEffect)(()=>{e||(d(`user`),p(``),h(``),_([]),y(`never`),x(``),w([]),ee(``),k(``),P(`m`),L(``))},[e]),(0,Y.useEffect)(()=>{_(e=>{let t=e.filter(e=>B.includes(e));if(t.length>0)return t;let n=B[0];return n?[n]:[]})},[B]);let V=De(b),H=Fe(T,D,j),ne=f.trim().length>0&&g.length>0&&!i&&H!==void 0&&(u===`operator`||m.length>0)&&(v!==`custom`||V>0);return(0,Q.jsx)(E,{open:e,modal:!1,disablePointerDismissal:!0,onOpenChange:c,children:(0,Q.jsxs)(M,{side:`right`,showOverlay:!1,className:`gap-0 p-0 data-[side=right]:sm:max-w-md`,"data-slot":`access-create-sheet`,children:[(0,Q.jsxs)(O,{className:`gap-1 border-b border-border/50`,children:[(0,Q.jsx)(te,{className:`text-sm`,children:`Create API key`}),(0,Q.jsx)(F,{className:`text-[11px]`,children:u===`user`?`User-plane keys require an issuer. The key cannot exceed that user's grants.`:`Operator keys inherit the actor ceiling. Absence is impossible — only grantable scopes appear.`})]}),(0,Q.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col overflow-y-auto`,children:[(0,Q.jsxs)(`div`,{className:`flex items-stretch border-b border-border/50`,"data-slot":`access-name-field`,children:[(0,Q.jsx)(`p`,{className:`flex h-8 w-[5.5rem] shrink-0 items-center px-4 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase`,children:`Name`}),(0,Q.jsx)(a,{value:f,onChange:e=>p(e.target.value),placeholder:`CI deploy`,autoComplete:`off`,"aria-label":`Key name`,flat:!0,className:o(q,`min-w-0 flex-1`)})]}),(0,Q.jsxs)(ve,{label:`Plane`,children:[(0,Q.jsx)(G,{active:u===`user`,icon:fe,onClick:()=>{d(`user`)},children:`user`}),(0,Q.jsx)(G,{active:u===`operator`,icon:Ce,onClick:()=>{d(`operator`),h(``),L(``)},children:`operator`})]}),(0,Q.jsx)(it,{expires:v,customExpires:b,onExpires:y,onCustomExpires:x}),(0,Q.jsx)(rt,{entries:S,rateMax:T,rateCount:D,rateUnit:j,onEntries:w,onRateMax:ee,onRateCount:k,onRateUnit:P}),u===`user`?(0,Q.jsxs)(`div`,{className:`border-b border-border/50`,"data-slot":`access-issuer-field`,children:[(0,Q.jsxs)(`div`,{className:`flex items-stretch`,children:[(0,Q.jsx)(`p`,{className:`flex h-8 w-[5.5rem] shrink-0 items-center px-4 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase`,children:`Issuer`}),t.length>0?(0,Q.jsx)(at,{value:I,placeholder:`Search users`,label:`Search users`,onChange:L}):null]}),t.length===0?(0,Q.jsx)(`p`,{className:`border-t border-border/50 px-4 py-3 text-[11px] text-muted-foreground`,children:`No users on this plane.`}):z.length===0?(0,Q.jsx)(`p`,{className:`border-t border-border/50 px-4 py-3 text-[11px] text-muted-foreground`,children:`No users match.`}):(0,Q.jsx)(`div`,{className:`max-h-56 overflow-y-auto`,children:z.map(e=>{let t=e.id===m;return(0,Q.jsxs)(`button`,{type:`button`,"data-slot":`access-creator-user`,"aria-pressed":t,className:o(A,t&&`bg-muted/70 text-foreground`),onClick:()=>h(e.id),children:[(0,Q.jsx)(`span`,{className:o(N,t&&`bg-sky-500`)}),(0,Q.jsx)(`span`,{className:`min-w-0 truncate`,children:e.name}),(0,Q.jsx)(`span`,{className:`ml-auto shrink-0 font-mono text-[10px] text-muted-foreground`,children:e.id})]},e.id)})})]}):null,(0,Q.jsx)(ot,{scopes:B,selected:g,empty:u===`user`&&m.length===0?`Select an issuer to see grantable scopes.`:u===`user`?`This user has no grants.`:`No grantable scopes on this plane.`,onChange:_}),s?(0,Q.jsx)(be,{slot:`access-create-error`,children:s}):null]}),(0,Q.jsxs)(C,{children:[(0,Q.jsx)(K,{split:!0,onClick:()=>c(!1),disabled:i,children:`Cancel`}),(0,Q.jsx)(K,{variant:`default`,disabled:!ne,"data-slot":`access-create-submit`,onClick:()=>{if(H===void 0)return;let e=Oe(v,Date.now(),b);l({plane:u,name:f.trim(),scopes:g,...u===`user`?{creatorUserId:m}:{},...e==null?{}:{expiresAt:e},...H==null?{}:{rateLimit:H},...S.length>0?{ipAllowlist:S}:{}})},children:i?`Creating…`:`Create`})]})]})})}function lt({open:e,keyRow:t,users:n,operators:r,operatorScopes:s,pending:c,error:l,now:u=Date.now(),onOpenChange:d,onSubmit:f}){let p=ke(t.expiresAt,u),[m,h]=(0,Y.useState)(t.name),[g,_]=(0,Y.useState)(t.scopes),[v,y]=(0,Y.useState)(p.choice),[b,x]=(0,Y.useState)(p.custom),[S,w]=(0,Y.useState)(t.ipAllowlist),T=Ie(t.rateLimit?.per??``),[ee,D]=(0,Y.useState)(t.rateLimit?String(t.rateLimit.max):``),[k,A]=(0,Y.useState)(T.count),[j,N]=(0,Y.useState)(T.unit);(0,Y.useEffect)(()=>{if(!e)return;let n=ke(t.expiresAt,u);h(t.name),_(t.scopes),y(n.choice),x(n.custom),w(t.ipAllowlist);let r=Ie(t.rateLimit?.per??``);D(t.rateLimit?String(t.rateLimit.max):``),A(r.count),N(r.unit)},[e,t.id]);let P=t.creatorId??``,I=(0,Y.useMemo)(()=>P.length===0?null:n.find(e=>e.id===P)??r.find(e=>e.id===P)??null,[P,r,n]),L=I?I.name.trim()||I.email.trim()||I.id:P||`Unknown`,R=(0,Y.useMemo)(()=>{let e=t.creatorScopes??[];return e.length>0?e:t.plane===`operator`?I&&I.scopes.length>0?I.scopes:s:I?.scopes??[]},[I,t.creatorScopes,t.plane,s]),z=Array.from(new Set([...R,...t.scopes])),B=De(b),V=Fe(ee,k,j),H=m.trim().length>0&&g.length>0&&!c&&V!==void 0&&(v!==`custom`||B>0);return(0,Q.jsx)(E,{open:e,modal:!1,disablePointerDismissal:!0,onOpenChange:d,children:(0,Q.jsxs)(M,{side:`right`,showOverlay:!1,className:`gap-0 p-0 data-[side=right]:sm:max-w-md`,"data-slot":`access-edit-sheet`,children:[(0,Q.jsxs)(O,{className:`gap-1 border-b border-border/50`,children:[(0,Q.jsx)(te,{className:`text-sm`,children:`Edit key`}),(0,Q.jsx)(F,{className:`text-[11px]`,children:`Change name, expiry, allow, rate, and scopes. Issuer stays frozen.`})]}),(0,Q.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col overflow-y-auto`,children:[(0,Q.jsxs)(`div`,{className:`flex items-stretch border-b border-border/50`,"data-slot":`access-name-field`,children:[(0,Q.jsx)(`p`,{className:`flex h-8 w-[5.5rem] shrink-0 items-center px-4 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase`,children:`Name`}),(0,Q.jsx)(a,{value:m,onChange:e=>h(e.target.value),placeholder:`CI deploy`,autoComplete:`off`,"aria-label":`Key name`,flat:!0,className:o(q,`min-w-0 flex-1`)})]}),(0,Q.jsxs)(`div`,{className:`flex items-stretch border-b border-border/50`,"data-slot":`access-issuer-field`,children:[(0,Q.jsx)(`p`,{className:`flex h-8 w-[5.5rem] shrink-0 items-center px-4 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase`,children:`Issuer`}),(0,Q.jsxs)(`p`,{className:`flex min-w-0 flex-1 items-center gap-1.5 px-4 text-[12px] text-muted-foreground`,"aria-label":`Issuer ${L}`,children:[(0,Q.jsx)(i,{icon:t.plane===`operator`?Ce:fe,className:`size-3 shrink-0`,"aria-hidden":!0}),(0,Q.jsx)(`span`,{className:`min-w-0 truncate text-foreground`,children:L})]}),P.length>0&&P!==L?(0,Q.jsx)(`span`,{className:`flex h-8 shrink-0 items-center pr-4 font-mono text-[10px] text-muted-foreground`,children:P}):null]}),(0,Q.jsx)(it,{expires:v,customExpires:b,onExpires:y,onCustomExpires:x}),(0,Q.jsx)(rt,{entries:S,rateMax:ee,rateCount:k,rateUnit:j,onEntries:w,onRateMax:D,onRateCount:A,onRateUnit:N}),(0,Q.jsx)(ot,{scopes:z,selected:g,empty:t.plane===`user`&&I==null?`Issuer is gone. Only scopes already on this key remain.`:t.plane===`user`?`This issuer has no grants.`:`No grantable scopes on this plane.`,onChange:_}),l?(0,Q.jsx)(be,{slot:`access-edit-error`,children:l}):null]}),(0,Q.jsxs)(C,{children:[(0,Q.jsx)(K,{split:!0,onClick:()=>d(!1),disabled:c,children:`Cancel`}),(0,Q.jsx)(K,{variant:`default`,disabled:!H,"data-slot":`access-edit-submit`,onClick:()=>{V!==void 0&&f({name:m.trim(),scopes:g,expiresAt:Oe(v,Date.now(),b),rateLimit:V,ipAllowlist:S})},children:c?`Saving…`:`Save`})]})]})})}function ut({open:e,keyRow:t,pending:n,error:r,now:i=Date.now(),onOpenChange:a,onSubmit:o}){let s=je(t.expiresAt,i),[c,l]=(0,Y.useState)(s.choice),[u,d]=(0,Y.useState)(s.custom);(0,Y.useEffect)(()=>{if(!e)return;let n=je(t.expiresAt,i);l(n.choice),d(n.custom)},[e,t.id]);let f=De(u);return(0,Q.jsx)(E,{open:e,modal:!1,disablePointerDismissal:!0,onOpenChange:a,children:(0,Q.jsxs)(M,{side:`right`,showOverlay:!1,className:`gap-0 p-0 data-[side=right]:sm:max-w-md`,"data-slot":`access-refresh-sheet`,children:[(0,Q.jsxs)(O,{className:`gap-1 border-b border-border/50`,children:[(0,Q.jsx)(te,{className:`text-sm`,children:`Refresh expiry`}),(0,Q.jsx)(F,{className:`text-[11px]`,children:`Reset the deadline from now. The secret does not change.`})]}),(0,Q.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col overflow-y-auto`,children:[(0,Q.jsx)(it,{expires:c,customExpires:u,onExpires:l,onCustomExpires:d}),r?(0,Q.jsx)(be,{slot:`access-refresh-error`,children:r}):null]}),(0,Q.jsxs)(C,{children:[(0,Q.jsx)(K,{split:!0,onClick:()=>a(!1),disabled:n,children:`Cancel`}),(0,Q.jsx)(K,{variant:`default`,disabled:!(!n&&(c!==`custom`||f>0)),"data-slot":`access-refresh-submit`,onClick:()=>{o(Oe(c,Date.now(),u))},children:n?`Refreshing…`:`Refresh`})]})]})})}function dt({secret:e,title:t=`New API key`,onDismiss:n}){let[r,i]=(0,Y.useState)(!1);return(0,Q.jsx)(E,{open:e!==null,onOpenChange:e=>{e||n()},children:(0,Q.jsxs)(M,{side:`right`,showOverlay:!1,className:`gap-0 p-0 data-[side=right]:sm:max-w-md`,"data-slot":`access-secret-sheet`,children:[(0,Q.jsxs)(O,{className:`gap-1 border-b border-border/50`,children:[(0,Q.jsx)(te,{className:`text-sm`,children:t}),(0,Q.jsx)(F,{className:`text-[11px]`,children:`Shown once. Store it offline. This Console process does not keep it for you.`})]}),(0,Q.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col overflow-y-auto`,children:[(0,Q.jsx)(`p`,{className:`border-b border-border/50 px-4 py-3 font-mono text-[11px] break-all`,children:e}),(0,Q.jsx)(`p`,{className:`px-4 py-3 text-[11px] text-muted-foreground`,role:`status`,children:`Copying puts the secret on the clipboard. Anyone with this device can read it until you clear it.`})]}),(0,Q.jsxs)(C,{children:[(0,Q.jsx)(K,{split:!0,disabled:!e,onClick:()=>{e&&(navigator.clipboard?.writeText(e),i(!0))},children:r?`Copied`:`Copy (clipboard warning)`}),(0,Q.jsx)(K,{variant:`destructive`,onClick:()=>{i(!1),n()},children:`I have stored this key`})]})]})})}function ft(){let e=et(),t=ee(),{query:n,selectedKey:a,action:o,setQuery:s,setSelectedKey:c,setAction:l}=me(),m=ce(),[g]=(0,Y.useState)(()=>Date.now()),[_,x]=(0,Y.useState)(null),[S,C]=(0,Y.useState)(`New API key`),E=(0,Y.useMemo)(()=>{let t=e.data?.userPlane.keys??[],n=e.data?.operatorPlane.keys??[];return[...t,...n]},[e.data]),D=(0,Y.useMemo)(()=>{let e=n.trim().toLowerCase();return e?E.filter(t=>t.name.toLowerCase().includes(e)||t.id.toLowerCase().includes(e)||t.scopes.some(t=>t.toLowerCase().includes(e))):E},[E,n]),O=D.find(e=>e.id===a)??E.find(e=>e.id===a),k=y({queryKey:[`console.access.blast`,O?.id],queryFn:async()=>{if(!O)throw Error(`no key`);let e=await p(O.id);if(e.error||!e.data)throw Error(e.error?.message??`blast failed`);return e.data},enabled:O!==void 0});(0,Y.useEffect)(()=>{let e=e=>{if(e.key!==`/`||e.metaKey||e.ctrlKey||e.altKey)return;let t=e.target;if(t instanceof HTMLElement){let e=t.tagName;if(e===`INPUT`||e===`TEXTAREA`||t.isContentEditable)return}e.preventDefault(),document.querySelector(`[data-slot='access-search-input']`)?.focus()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[]);let A=()=>{t.invalidateQueries({queryKey:$e})},j=W({mutationFn:f,onSuccess:e=>{if(e.error||!e.data)throw Error(e.error?v(e.error):`create failed`);C(`New API key`),x(e.data.secret),c(e.data.key.id),l(null),A()}}),M=W({mutationFn:e=>d({keyId:O.id,confirmation:e.confirmation,reason:e.reason}),onSuccess:e=>{if(e.error||!e.data)throw Error(e.error?v(e.error):`rotate failed`);C(`Rotated API key`),x(e.data.secret),l(null),A()}}),N=W({mutationFn:e=>u({keyId:O.id,confirmation:e.confirmation,reason:e.reason}),onSuccess:e=>{if(e.error||!e.data)throw Error(e.error?v(e.error):`revoke failed`);l(null),A()}}),P=W({mutationFn:h,onSuccess:e=>{if(e.error||!e.data)throw Error(e.error?v(e.error):`update failed`);l(null),A()}}),F=W({mutationFn:e=>h({keyId:O.id,expiresAt:e}),onSuccess:e=>{if(e.error||!e.data)throw Error(e.error?v(e.error):`refresh failed`);l(null),A()}}),I=(0,Q.jsx)(le,{open:m.open,onToggle:m.toggle,noun:`keys`,controlsId:`access-list`,dataSlot:`access-list-toggle`});return(0,Q.jsxs)(`div`,{className:T,"data-slot":`access-page`,children:[(0,Q.jsxs)(ie,{orientation:`horizontal`,className:`min-h-0 flex-1`,children:[(0,Q.jsx)(se,{panelRef:m.panelRef,collapsible:!0,collapsedSize:0,defaultSize:b.start.defaultSize,minSize:b.start.minSize,onResize:m.onResize,className:`min-h-0 overflow-hidden`,children:(0,Q.jsxs)(`div`,{className:`flex h-full min-h-0 flex-col overflow-hidden`,children:[(0,Q.jsxs)(`div`,{className:r,children:[(0,Q.jsx)(ae,{"data-slot":`access-search-input`,placeholder:`Search keys`,"aria-label":`Search keys`,value:n,onChange:e=>s(e.target.value)}),(0,Q.jsx)(U,{label:`Create key`,className:`flex self-stretch`,children:(0,Q.jsx)(`button`,{type:`button`,className:w,"data-slot":`access-create`,"aria-label":`Create key`,onClick:()=>l(`create`),children:(0,Q.jsx)(i,{icon:pe,className:`size-3.5`})})})]}),(0,Q.jsx)(tt,{rows:D,selectedKey:a,query:n,loading:e.isLoading,onSelect:c})]})}),m.open?(0,Q.jsx)(ue,{withHandle:!0}):null,(0,Q.jsx)(se,{defaultSize:b.end.defaultSize,minSize:b.end.minSize,className:`min-h-0 overflow-hidden`,children:O?(0,Q.jsx)(Ge,{keyRow:O,blast:k.data,now:g,leading:I,onEdit:()=>l(`edit`),onRefresh:()=>l(`refresh`),onRevoke:()=>l(`revoke`),onRotate:()=>l(`rotate`),rotatePending:M.isPending}):(0,Q.jsx)(ge,{icon:de.gate.icon,iconClassName:`border-border/70`,title:e.isLoading?`Loading keys…`:E.length>0?`Select a key`:`No keys yet`,leading:I,description:e.isLoading?`Reading planes and grantable scopes.`:E.length>0&&n.trim().length>0?(0,Q.jsxs)(Q.Fragment,{children:[`Nothing matches `,(0,Q.jsx)(`span`,{className:`font-mono`,children:n}),`.`,` `,(0,Q.jsx)(`button`,{type:`button`,className:`underline underline-offset-2 hover:text-foreground`,onClick:()=>s(``),children:`Clear search`})]}):`Create one to mint a secret once.`})})]}),(0,Q.jsx)(ct,{open:o===`create`,users:e.data?.userPlane.users??[],userScopes:e.data?.userPlane.grantableScopes??[],operatorScopes:e.data?.operatorPlane.grantableScopes??[],pending:j.isPending,error:j.error instanceof Error?j.error.message:null,onOpenChange:e=>{e||l(null)},onSubmit:e=>j.mutate(e)}),O?(0,Q.jsx)(lt,{open:o===`edit`,keyRow:O,users:e.data?.userPlane.users??[],operators:e.data?.operatorPlane.operators??[],operatorScopes:e.data?.operatorPlane.grantableScopes??[],pending:P.isPending,error:P.error instanceof Error?P.error.message:null,onOpenChange:e=>{e||l(null)},onSubmit:e=>P.mutate({keyId:O.id,...e})}):null,O?(0,Q.jsx)(ut,{open:o===`refresh`,keyRow:O,pending:F.isPending,error:F.error instanceof Error?F.error.message:null,onOpenChange:e=>{e||l(null)},onSubmit:e=>F.mutate(e)}):null,O?(0,Q.jsx)(Se,{open:o===`revoke`,onOpenChange:e=>{e||l(null)},phrase:`REVOKE`,title:`Revoke ${O.name}`,description:`Irreversible. Residual sessions may continue for the access TTL.`,pending:N.isPending,error:N.error instanceof Error?N.error.message:null,confirmLabel:`Revoke key`,slot:`access-revoke-sheet`,onConfirm:e=>N.mutate({confirmation:`REVOKE`,reason:e.reason})}):null,O?(0,Q.jsx)(Se,{open:o===`rotate`,onOpenChange:e=>{e||l(null)},phrase:`ROTATE`,title:`Rotate ${O.name}`,description:`Mints a new secret once. The current secret stops after residual TTL.`,pending:M.isPending,error:M.error instanceof Error?M.error.message:null,confirmLabel:`Rotate key`,confirmVariant:`ghost`,slot:`access-rotate-sheet`,onConfirm:e=>M.mutate({confirmation:`ROTATE`,reason:e.reason})}):null,(0,Q.jsx)(dt,{secret:_,title:S,onDismiss:()=>x(null)})]})}export{ft as AccessPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{A as e}from"./xyflow-yApv7D4e.js";import{Er as t}from"./shortcut-keys-3ILd8oGn.js";import{X as n,c as r,l as i}from"./index-
|
|
1
|
+
import{A as e}from"./xyflow-yApv7D4e.js";import{Er as t}from"./shortcut-keys-3ILd8oGn.js";import{X as n,c as r,l as i}from"./index-B0GkEkAT.js";var a=e();function o({open:e,openHeight:o=`auto`,className:s,style:c,transition:l,...u}){let d=i()??!1;return(0,a.jsx)(r.div,{...u,"aria-hidden":!e,inert:!e||void 0,initial:!1,animate:d?{opacity:+!!e}:{opacity:+!!e,clipPath:e?`inset(0 0 0% 0)`:`inset(0 0 100% 0)`,y:e?0:-4},transition:l??{duration:d?0:e?.22:.14,ease:n},className:t(`overflow-hidden`,s),style:{...c,height:e?o:0,pointerEvents:e?void 0:`none`,transformOrigin:`top`}})}export{o as t};
|