okengine 0.22.0 → 0.23.1
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/AGENTS.md +7 -0
- package/manifest.v1.schema.json +44 -0
- package/package.json +32 -19
- package/site/content/docs/ai/mcp.mdx +14 -10
- package/site/content/docs/ai/skills.mdx +1 -1
- package/site/content/docs/elements/ai/agents.mdx +61 -13
- package/site/content/docs/elements/ai/decide.mdx +536 -0
- package/site/content/docs/elements/ai/events.mdx +373 -0
- package/site/content/docs/elements/ai/index.mdx +9 -3
- package/site/content/docs/elements/ai/meta.json +1 -1
- package/site/content/docs/elements/ai/prompts.mdx +4 -2
- package/site/content/docs/elements/channel/index.mdx +4 -4
- package/site/content/docs/elements/channel/receipts.mdx +7 -5
- package/site/content/docs/elements/channel/sms.mdx +2 -1
- package/site/content/docs/elements/flow/index.mdx +22 -18
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/signal/index.mdx +15 -14
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/plugins/otp.mdx +3 -3
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- package/site/content/docs/recipes/postgres.mdx +12 -8
- package/site/content/docs/recipes/redis.mdx +4 -4
- package/site/content/docs/reference/cli.mdx +2 -1
- package/site/content/docs/reference/configuration.mdx +9 -7
- package/site/content/docs/reference/fx.mdx +19 -10
- package/src/auth/api-key-sql.test.ts +44 -0
- package/src/auth/api-key-sql.ts +1 -0
- package/src/auth/api-keys.ts +30 -12
- package/src/auth/config.ts +6 -2
- package/src/auth/gate-auth.test.ts +20 -0
- package/src/auth/tenants.ts +17 -0
- package/src/cli/agents-md.test.ts +25 -0
- package/src/cli/ask-vault-gaps.test.ts +38 -1
- package/src/cli/ask-vault-gaps.ts +35 -2
- package/src/cli/decide.ts +174 -0
- package/src/cli/decision-lock-watch.test.ts +45 -0
- package/src/cli/decision-lock-watch.ts +51 -0
- package/src/cli/dev-app-runner.ts +34 -8
- package/src/cli/dev-auth-secret.test.ts +49 -0
- package/src/cli/dev-auth-secret.ts +49 -0
- package/src/cli/dev-hot.test.ts +41 -0
- package/src/cli/dev-hot.ts +48 -0
- package/src/cli/dev.test.ts +1 -1
- package/src/cli/dev.ts +24 -6
- package/src/cli/doctor-diff.test.ts +29 -1
- package/src/cli/doctor-diff.ts +35 -0
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.images.test.ts +2 -2
- package/src/cli/load-config.ts +1 -1
- package/src/cli/manifest-pr-diff.ts +16 -0
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/client/agent.ts +307 -0
- package/src/client/transport.test.ts +20 -0
- package/src/client/transport.ts +2 -1
- package/src/client/types.ts +1 -1
- package/src/client-react/index.ts +7 -1
- package/src/client-react/use-agent-run.test.ts +271 -0
- package/src/client-react/use-agent-run.ts +193 -0
- package/src/compiler/ai-approval.test.ts +36 -0
- package/src/compiler/ai-repair.test.ts +22 -0
- package/src/compiler/decisions.extract.test.ts +164 -0
- package/src/compiler/effects-fetch.test.ts +77 -0
- package/src/compiler/effects-infer.ts +114 -17
- package/src/compiler/effects-join.test.ts +48 -0
- package/src/compiler/extract.test.ts +28 -0
- package/src/compiler/extract.ts +279 -13
- package/src/compiler/fx-index.ts +58 -4
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/index.ts +1 -1
- package/src/console/server/ai-runs-flows.ts +269 -0
- package/src/console/server/ai-runs.test.ts +323 -0
- package/src/console/server/ai-runs.ts +562 -0
- package/src/console/server/ai.ts +12 -1
- package/src/console/server/app.ts +10 -1
- package/src/console/server/decisions-flows.ts +157 -0
- package/src/console/server/decisions.test.ts +66 -0
- package/src/console/server/decisions.ts +229 -0
- package/src/console/server/flows.ts +50 -2
- package/src/console/server/invoke-user-flow.ts +9 -0
- package/src/console/server/runs-ingest.ts +16 -0
- package/src/console/server/serve.ts +5 -0
- package/src/console/server/state.ts +25 -1
- package/src/console/server/store-stats.test.ts +47 -0
- package/src/console/server/store-stats.ts +92 -18
- package/src/console/ui-next/dist/assets/access-page-C_N4qvai.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-Cq1TxniW.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-BaI4uxZz.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DNyK7IIY.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-CRhBJMZR.js} +1 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
- package/src/console/ui-next/dist/assets/decisions-page-D9twj8Xy.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-CsHWlVPU.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-ZaTJAhR9.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-DQ-EMAfz.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-Cn_Fl79s.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-CmRFeWdT.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-Cn6R_dXz.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bx6EZhSe.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DmdiUrfh.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-8P6vGXnn.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-BfGvpFSv.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CWdUHbc0.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-CZOFZzay.js +1 -0
- package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-ki_ishmX.js +2 -0
- package/src/console/ui-next/dist/index.html +4 -3
- package/src/console/ui-next/seed-parked-approval.ts +126 -0
- package/src/console/ui-next/src/client.ts +224 -1
- package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
- package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
- package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
- package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
- package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
- package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
- package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
- package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
- package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
- package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
- package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
- package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
- package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +6 -2
- package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
- package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
- package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
- package/src/console/ui-next/src/router.tsx +17 -0
- package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
- package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
- package/src/console/ui-next/vite.config.ts +9 -0
- package/src/docker/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.ts +1 -1
- package/src/drivers/ai-anthropic.ts +212 -21
- package/src/drivers/ai-mock.ts +34 -1
- package/src/drivers/ai-openai-compatible.ts +67 -26
- package/src/drivers/ai-preconnect.ts +18 -0
- package/src/drivers/ai-stream.test.ts +62 -0
- package/src/drivers/ai-types.ts +20 -0
- package/src/drivers/journal-postgres.test.ts +22 -1
- package/src/drivers/journal-postgres.ts +511 -13
- package/src/drivers/memory-ddl.test.ts +18 -0
- package/src/drivers/memory.ts +21 -1
- package/src/drivers/pg-rls.test.ts +31 -0
- package/src/drivers/pg-rls.ts +15 -0
- package/src/drivers/postgres.test.ts +9 -0
- package/src/drivers/postgres.ts +49 -11
- package/src/drivers/signal-compete.test.ts +234 -0
- package/src/drivers/signal-postgres.ts +39 -19
- package/src/drivers/signal-redis.ts +167 -11
- package/src/drivers/signal-types.ts +12 -0
- package/src/elements/ai/agent-event-slot.ts +27 -0
- package/src/elements/ai/approval-http.ts +207 -0
- package/src/elements/ai/approval.test.ts +741 -0
- package/src/elements/ai/approval.ts +228 -0
- package/src/elements/ai/decisions/bind.ts +163 -0
- package/src/elements/ai/decisions/certificate.test.ts +291 -0
- package/src/elements/ai/decisions/certificate.ts +377 -0
- package/src/elements/ai/decisions/certify.ts +314 -0
- package/src/elements/ai/decisions/decide.test.ts +953 -0
- package/src/elements/ai/decisions/e2e.test.ts +399 -0
- package/src/elements/ai/decisions/export.test.ts +120 -0
- package/src/elements/ai/decisions/export.ts +146 -0
- package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
- package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
- package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
- package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
- package/src/elements/ai/decisions/http.test.ts +154 -0
- package/src/elements/ai/decisions/http.ts +240 -0
- package/src/elements/ai/decisions/labels.test.ts +282 -0
- package/src/elements/ai/decisions/labels.ts +257 -0
- package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
- package/src/elements/ai/decisions/openrouter.ts +36 -0
- package/src/elements/ai/decisions/provider.ts +102 -0
- package/src/elements/ai/decisions/typesafe.ts +36 -0
- package/src/elements/ai/declare.ts +252 -1
- package/src/elements/ai/events.test.ts +379 -0
- package/src/elements/ai/events.ts +138 -0
- package/src/elements/ai/mcp-protocol.ts +5 -6
- package/src/elements/ai/run-events.test.ts +461 -0
- package/src/elements/ai/run-events.ts +522 -0
- package/src/elements/ai/runtime.ts +1187 -105
- package/src/elements/ai/stream-turn.ts +214 -0
- package/src/elements/ai/stream.live.test.ts +76 -0
- package/src/elements/ai/subagent.test.ts +136 -0
- package/src/elements/ai.test.ts +497 -3
- package/src/elements/ai.ts +16 -1
- package/src/elements/channel/runtime.ts +11 -0
- package/src/elements/channel/sql-ledger.ts +251 -0
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/signal/runtime.ts +9 -0
- package/src/elements/signal.test.ts +1 -0
- package/src/elements/store/cache.test.ts +120 -15
- package/src/elements/store/cache.ts +200 -39
- package/src/elements/store/prepare-row.test.ts +6 -2
- package/src/elements/store/sql-session.test.ts +53 -0
- package/src/elements/store/sql-session.ts +206 -10
- package/src/elements/store.ts +1 -0
- package/src/i18n/catalogs/ar.ts +5 -0
- package/src/i18n/catalogs/en.ts +5 -0
- package/src/index.ts +2 -0
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +135 -17
- package/src/kernel/auto-cache.test.ts +228 -0
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/channel.ts +63 -2
- package/src/kernel/boot-bind/honor-config.test.ts +40 -10
- package/src/kernel/boot-bind/signal.ts +61 -25
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot.ts +31 -6
- package/src/kernel/builtin-errors.ts +2 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/decision-label-store.ts +441 -0
- package/src/kernel/effects.test.ts +2 -1
- package/src/kernel/effects.ts +13 -1
- package/src/kernel/element-registries.ts +3 -0
- package/src/kernel/errors-compiler.ts +11 -0
- package/src/kernel/errors-text.ts +20 -0
- package/src/kernel/errors.ts +10 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/flow.ts +33 -3
- package/src/kernel/fx-call-types.test.ts +32 -0
- package/src/kernel/fx-decide.ts +765 -0
- package/src/kernel/fx-sql-handle.ts +46 -7
- package/src/kernel/fx.test.ts +13 -8
- package/src/kernel/fx.ts +339 -45
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/idempotency.test.ts +2 -1
- package/src/kernel/journal.test.ts +114 -0
- package/src/kernel/journal.ts +471 -51
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline-tenant.ts +5 -1
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/pipeline.ts +5 -0
- package/src/kernel/signal-tx.ts +28 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/kernel/store-transaction.test.ts +95 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +29 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/docs-server.ts +19 -2
- package/src/mcp/mcp.test.ts +83 -0
- package/src/mcp/protocol.ts +8 -2
- package/src/mcp/server.ts +23 -2
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/mcp/versions.ts +60 -0
- package/src/plugins/index.ts +1 -0
- package/src/plugins/mena.ts +56 -0
- package/src/release/http-graph.test.ts +2 -0
- package/src/release/http-graph.ts +5 -0
- package/src/release/measure.ts +39 -14
- package/src/runs/collect.ts +4 -1
- package/src/runs/parquet.test.ts +27 -1
- package/src/runs/parquet.ts +19 -0
- package/src/runs/types.ts +31 -0
- package/src/runtime/json-code-block.test.ts +106 -47
- package/src/runtime/json-code-block.ts +1209 -46
- package/src/term.test.ts +13 -0
- package/src/term.ts +81 -49
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
- package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
- package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Browser GET → traces-language JSON page. Clients still get the envelope.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { HTTP_FRAME_REDACTED, sensitiveHeader } from "../kernel/http-frame.ts";
|
|
5
6
|
import { CONSOLE_PORT } from "./types.ts";
|
|
6
7
|
|
|
7
8
|
/** One highlighted JSON span. */
|
|
@@ -32,6 +33,11 @@ export interface JsonCodeBlockRenderOptions {
|
|
|
32
33
|
readonly cache?: JsonCodeCache;
|
|
33
34
|
/** Auth principal that handled the request (traces language). */
|
|
34
35
|
readonly auth?: JsonCodeAuth;
|
|
36
|
+
/**
|
|
37
|
+
* Response headers for the Fields view. Credential names are shown as
|
|
38
|
+
* `[redacted]`. Omit `content-length` — the page replaces the body.
|
|
39
|
+
*/
|
|
40
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
/** Wide-event cache dimension on the browser JSON page. */
|
|
@@ -481,6 +487,251 @@ export function jsonCodeLatencyTone(ms: number): JsonCodeLatencyTone {
|
|
|
481
487
|
return "critical";
|
|
482
488
|
}
|
|
483
489
|
|
|
490
|
+
/** Scalar or container kind for one response field. */
|
|
491
|
+
type ResponseFieldKind = "string" | "number" | "boolean" | "null" | "object" | "array";
|
|
492
|
+
|
|
493
|
+
/** One expandable row in the response Fields view. */
|
|
494
|
+
interface ResponseFieldRow {
|
|
495
|
+
readonly key: string;
|
|
496
|
+
/** Path from the JSON root, for copy. */
|
|
497
|
+
readonly path: readonly (string | number)[];
|
|
498
|
+
readonly display: string;
|
|
499
|
+
readonly kind: ResponseFieldKind;
|
|
500
|
+
readonly children: readonly ResponseFieldRow[] | null;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const PREVIEW_FIELDS = 3;
|
|
504
|
+
const PREVIEW_SCALAR_MAX = 42;
|
|
505
|
+
|
|
506
|
+
const STATUS_REASON: Readonly<Record<number, string>> = {
|
|
507
|
+
200: "OK",
|
|
508
|
+
201: "Created",
|
|
509
|
+
202: "Accepted",
|
|
510
|
+
204: "No Content",
|
|
511
|
+
301: "Moved",
|
|
512
|
+
302: "Found",
|
|
513
|
+
304: "Not Modified",
|
|
514
|
+
400: "Bad Request",
|
|
515
|
+
401: "Unauthorized",
|
|
516
|
+
403: "Forbidden",
|
|
517
|
+
404: "Not Found",
|
|
518
|
+
405: "Method Not Allowed",
|
|
519
|
+
409: "Conflict",
|
|
520
|
+
415: "Unsupported Media Type",
|
|
521
|
+
422: "Unprocessable Content",
|
|
522
|
+
429: "Too Many Requests",
|
|
523
|
+
500: "Internal Server Error",
|
|
524
|
+
502: "Bad Gateway",
|
|
525
|
+
503: "Service Unavailable",
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const FIELD_CHEV = `<span class="chev" aria-hidden="true"><svg width="12" height="12" viewBox="0 0 24 24" fill="none"><path d="M6 9.5 12 15.5 18 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>`;
|
|
529
|
+
const FIELD_COPY = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="8" y="8" width="11" height="11" rx="1.5" stroke="currentColor" stroke-width="1.5"/><path d="M5 16V5.5A1.5 1.5 0 0 1 6.5 4H16" stroke="currentColor" stroke-width="1.5"/></svg>`;
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Project a JSON value into field rows. Scalars and empty containers return null.
|
|
533
|
+
*
|
|
534
|
+
* @param value - Parsed response body or header map
|
|
535
|
+
* @param path - Path from the root
|
|
536
|
+
*/
|
|
537
|
+
function responseFieldRows(
|
|
538
|
+
value: unknown,
|
|
539
|
+
path: readonly (string | number)[] = [],
|
|
540
|
+
): readonly ResponseFieldRow[] | null {
|
|
541
|
+
if (Array.isArray(value)) {
|
|
542
|
+
if (value.length === 0) return null;
|
|
543
|
+
return value.map((item, index) => projectResponseField(String(index), item, [...path, index]));
|
|
544
|
+
}
|
|
545
|
+
if (value === null || typeof value !== "object") return null;
|
|
546
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
547
|
+
if (entries.length === 0) return null;
|
|
548
|
+
return entries.map(([key, item]) => projectResponseField(key, item, [...path, key]));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Short shape label for a body or container (`2 fields`, `5 items`).
|
|
553
|
+
*
|
|
554
|
+
* @param value - Parsed JSON value
|
|
555
|
+
*/
|
|
556
|
+
function responseShapeHint(value: unknown): string | null {
|
|
557
|
+
if (Array.isArray(value)) {
|
|
558
|
+
return `${value.length} ${value.length === 1 ? "item" : "items"}`;
|
|
559
|
+
}
|
|
560
|
+
if (value !== null && typeof value === "object") {
|
|
561
|
+
const n = Object.keys(value).length;
|
|
562
|
+
return `${n} ${n === 1 ? "field" : "fields"}`;
|
|
563
|
+
}
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Compact byte size of the serialized body.
|
|
569
|
+
*
|
|
570
|
+
* @param json - Serialized JSON text
|
|
571
|
+
*/
|
|
572
|
+
function responseByteLabel(json: string): string {
|
|
573
|
+
const bytes = new TextEncoder().encode(json).length;
|
|
574
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
575
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Fields / JSON control for the response header. Same pair as Console traces.
|
|
580
|
+
*
|
|
581
|
+
* @param hasFields - Whether the body can open as rows
|
|
582
|
+
* @param compact - Compact JSON is the code view
|
|
583
|
+
*/
|
|
584
|
+
function responseViewToggleHtml(hasFields: boolean, compact: boolean): string {
|
|
585
|
+
if (!hasFields) return "";
|
|
586
|
+
const fieldsOn = !compact;
|
|
587
|
+
const token = (view: "fields" | "json", label: string, icon: string, on: boolean) =>
|
|
588
|
+
`<button type="button" class="token${on ? " is-on" : ""}" data-response-view="${view}" aria-pressed="${on ? "true" : "false"}"><span class="tok-ico">${icon}</span>${label}</button>`;
|
|
589
|
+
return `<span class="view-toggle" role="group" aria-label="Response view" data-slot="json-code-view-toggle">${token("fields", "Fields", GLYPH.list, fieldsOn)}${token("json", "JSON", GLYPH.braces, !fieldsOn)}</span>`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Status, response headers, and an expandable body — the Console response frame.
|
|
594
|
+
*
|
|
595
|
+
* @param options - Envelope + headers
|
|
596
|
+
* @param rows - Projected body rows
|
|
597
|
+
* @param value - Parsed body
|
|
598
|
+
* @param bodyJson - Highlighted body, shown when the header is on JSON
|
|
599
|
+
*/
|
|
600
|
+
function responsePaneHtml(
|
|
601
|
+
options: JsonCodeBlockRenderOptions,
|
|
602
|
+
rows: readonly ResponseFieldRow[],
|
|
603
|
+
value: unknown,
|
|
604
|
+
bodyJson: string,
|
|
605
|
+
): string {
|
|
606
|
+
const tone = options.status >= 500 ? "err" : options.status >= 400 ? "warn" : "ok";
|
|
607
|
+
const reason = STATUS_REASON[options.status] ?? "";
|
|
608
|
+
const hint = [responseShapeHint(value), responseByteLabel(options.json)]
|
|
609
|
+
.filter(Boolean)
|
|
610
|
+
.join(" · ");
|
|
611
|
+
const headers = jsonCodeResponseHeaders(options.headers);
|
|
612
|
+
const headerRows = responseFieldRows(headers);
|
|
613
|
+
const headerBlock =
|
|
614
|
+
headerRows && headerRows.length > 0
|
|
615
|
+
? `<details class="resp-block" open><summary class="resp-label">${FIELD_CHEV}<span>Headers</span><span class="resp-hint">${headerRows.length} ${headerRows.length === 1 ? "field" : "fields"}</span></summary><ul class="fields" data-slot="json-code-response-headers">${fieldListHtml(headerRows)}</ul></details>`
|
|
616
|
+
: "";
|
|
617
|
+
return `<div class="resp" data-slot="json-code-response"><div class="resp-frame"><div class="resp-rail resp-${tone}" data-slot="json-code-response-rail"></div><div class="resp-main"><div class="resp-status" data-slot="json-code-response-status"><span class="resp-code ${tone}">${options.status}</span>${reason ? `<span class="resp-reason">${escapeHtml(reason)}</span>` : ""}</div>${headerBlock}<div class="resp-body" data-slot="json-code-response-body"><div class="resp-label"><span>Body</span>${hint ? `<span class="resp-hint">${escapeHtml(hint)}</span>` : ""}</div><ul class="fields" data-slot="json-code-fields">${fieldListHtml(rows)}</ul><pre>${bodyJson}</pre></div></div></div></div>`;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Redacted response headers for the Fields view.
|
|
622
|
+
*
|
|
623
|
+
* @param headers - Outgoing response headers, before the page replaces the body
|
|
624
|
+
*/
|
|
625
|
+
export function jsonCodeResponseHeaders(
|
|
626
|
+
headers: Headers | Readonly<Record<string, string>> | undefined,
|
|
627
|
+
): Record<string, string> {
|
|
628
|
+
const out: Record<string, string> = {};
|
|
629
|
+
const write = (key: string, value: string): void => {
|
|
630
|
+
const name = key.toLowerCase();
|
|
631
|
+
if (name === "content-length") return;
|
|
632
|
+
out[name] = sensitiveHeader(name) ? HTTP_FRAME_REDACTED : value;
|
|
633
|
+
};
|
|
634
|
+
if (headers instanceof Headers) {
|
|
635
|
+
headers.forEach((value, key) => {
|
|
636
|
+
write(key, value);
|
|
637
|
+
});
|
|
638
|
+
return out;
|
|
639
|
+
}
|
|
640
|
+
if (headers) {
|
|
641
|
+
for (const [key, value] of Object.entries(headers)) write(key, value);
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function projectResponseField(
|
|
647
|
+
key: string,
|
|
648
|
+
value: unknown,
|
|
649
|
+
path: readonly (string | number)[],
|
|
650
|
+
): ResponseFieldRow {
|
|
651
|
+
return {
|
|
652
|
+
key,
|
|
653
|
+
path,
|
|
654
|
+
display: formatResponseField(value),
|
|
655
|
+
kind: responseFieldKind(value),
|
|
656
|
+
children: responseFieldRows(value, path),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function responseFieldKind(value: unknown): ResponseFieldKind {
|
|
661
|
+
if (value === null) return "null";
|
|
662
|
+
if (Array.isArray(value)) return "array";
|
|
663
|
+
switch (typeof value) {
|
|
664
|
+
case "string":
|
|
665
|
+
return "string";
|
|
666
|
+
case "number":
|
|
667
|
+
return "number";
|
|
668
|
+
case "boolean":
|
|
669
|
+
return "boolean";
|
|
670
|
+
case "object":
|
|
671
|
+
return "object";
|
|
672
|
+
default:
|
|
673
|
+
return "string";
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function formatResponseField(value: unknown): string {
|
|
678
|
+
if (value === null) return "null";
|
|
679
|
+
if (typeof value === "string") return value;
|
|
680
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
681
|
+
if (Array.isArray(value)) {
|
|
682
|
+
if (value.length === 0) return "[]";
|
|
683
|
+
return `${value.length} ${value.length === 1 ? "item" : "items"}`;
|
|
684
|
+
}
|
|
685
|
+
if (typeof value === "object") return objectPreview(value as Record<string, unknown>);
|
|
686
|
+
return typeof value;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** Up to three short scalar fields, otherwise a count. */
|
|
690
|
+
function objectPreview(value: Record<string, unknown>): string {
|
|
691
|
+
const keys = Object.keys(value);
|
|
692
|
+
if (keys.length === 0) return "{}";
|
|
693
|
+
const bits: string[] = [];
|
|
694
|
+
for (const [key, item] of Object.entries(value)) {
|
|
695
|
+
if (bits.length >= PREVIEW_FIELDS) break;
|
|
696
|
+
if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") continue;
|
|
697
|
+
const text = typeof item === "string" ? item : String(item);
|
|
698
|
+
if (text.length === 0 || text.length > PREVIEW_SCALAR_MAX) continue;
|
|
699
|
+
bits.push(`${key}: ${text}`);
|
|
700
|
+
}
|
|
701
|
+
if (bits.length > 0) return bits.join(" · ");
|
|
702
|
+
return `${keys.length} ${keys.length === 1 ? "field" : "fields"}`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function fieldListHtml(rows: readonly ResponseFieldRow[]): string {
|
|
706
|
+
return rows.map((row) => fieldRowHtml(row)).join("");
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function fieldRowHtml(row: ResponseFieldRow): string {
|
|
710
|
+
const depth = Math.max(0, row.path.length - 1);
|
|
711
|
+
const tone =
|
|
712
|
+
row.kind === "string"
|
|
713
|
+
? " s"
|
|
714
|
+
: row.kind === "number"
|
|
715
|
+
? " m"
|
|
716
|
+
: row.kind === "boolean"
|
|
717
|
+
? " l"
|
|
718
|
+
: "";
|
|
719
|
+
const copy = `<button type="button" class="field-copy" data-field-copy="${escapeHtml(JSON.stringify(row.path))}" aria-label="Copy ${escapeHtml(row.key)}">${FIELD_COPY}</button>`;
|
|
720
|
+
const label = `<span class="field-key">${escapeHtml(row.key)}</span><span class="field-val${tone}${row.kind === "null" ? " is-null" : ""}">${escapeHtml(row.display)}</span><span class="field-kind">${row.kind}</span>`;
|
|
721
|
+
if (row.children) {
|
|
722
|
+
return `<li class="field" data-slot="json-code-field" data-kind="${row.kind}"><details><summary class="field-row" style="--depth:${depth}">${FIELD_CHEV}${label}</summary><ul class="field-kids">${fieldListHtml(row.children)}</ul></details>${copy}</li>`;
|
|
723
|
+
}
|
|
724
|
+
return `<li class="field" data-slot="json-code-field" data-kind="${row.kind}"><div class="field-row" style="--depth:${depth}"><span class="chev" aria-hidden="true"></span>${label}</div>${copy}</li>`;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function parseJsonValue(json: string): unknown {
|
|
728
|
+
try {
|
|
729
|
+
return JSON.parse(json) as unknown;
|
|
730
|
+
} catch {
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
484
735
|
/**
|
|
485
736
|
* Full-bleed traces-language page for one JSON envelope.
|
|
486
737
|
*
|
|
@@ -488,6 +739,10 @@ export function jsonCodeLatencyTone(ms: number): JsonCodeLatencyTone {
|
|
|
488
739
|
*/
|
|
489
740
|
export function renderJsonCodeBlockHtml(options: JsonCodeBlockRenderOptions): string {
|
|
490
741
|
const compact = options.compact === true;
|
|
742
|
+
const parsed = parseJsonValue(options.json);
|
|
743
|
+
const fieldRows = parsed === undefined ? null : responseFieldRows(parsed);
|
|
744
|
+
const hasFields = fieldRows !== null && fieldRows.length > 0;
|
|
745
|
+
const view = compact ? "raw" : hasFields ? "fields" : "pretty";
|
|
491
746
|
const code = formatJson(options.json, compact);
|
|
492
747
|
const tokens = tokenizeJson(code);
|
|
493
748
|
const lines = splitTokenLines(tokens);
|
|
@@ -500,6 +755,8 @@ export function renderJsonCodeBlockHtml(options: JsonCodeBlockRenderOptions): st
|
|
|
500
755
|
return `<span class="ln"><span class="n">${n}</span><span class="c${compact ? " wrap" : ""}">${inner}</span></span>`;
|
|
501
756
|
})
|
|
502
757
|
.join("");
|
|
758
|
+
const responseHtml =
|
|
759
|
+
hasFields && fieldRows ? responsePaneHtml(options, fieldRows, parsed, rows) : "";
|
|
503
760
|
|
|
504
761
|
return `<!doctype html>
|
|
505
762
|
<html lang="en">
|
|
@@ -548,6 +805,21 @@ export function renderJsonCodeBlockHtml(options: JsonCodeBlockRenderOptions): st
|
|
|
548
805
|
.cache-hit { color: oklch(0.685 0.169 237.323); }
|
|
549
806
|
.cache-miss { color: oklch(0.666 0.179 58.318); }
|
|
550
807
|
.auth-user, .auth-key, .auth-operator { color: oklch(0.685 0.169 237.323); }
|
|
808
|
+
.st-ok, .meth-get, .meth-query, .icon.meth-get, .icon.meth-query { color: oklch(0.55 0.14 163); }
|
|
809
|
+
.st-info, .meth-post, .icon.meth-post { color: oklch(0.5 0.12 230); }
|
|
810
|
+
.st-warn, .meth-put, .icon.meth-put { color: oklch(0.55 0.14 75); }
|
|
811
|
+
.meth-patch, .icon.meth-patch { color: oklch(0.48 0.16 300); }
|
|
812
|
+
.st-err, .meth-delete, .icon.meth-delete { color: oklch(0.55 0.2 22); }
|
|
813
|
+
.rail-acc[data-rail-section="query"] { --sec: oklch(0.5 0.12 230); }
|
|
814
|
+
.rail-acc[data-rail-section="body"] { --sec: oklch(0.55 0.14 75); }
|
|
815
|
+
.rail-acc[data-rail-section="cookies"] { --sec: oklch(0.55 0.14 55); }
|
|
816
|
+
.rail-acc[data-rail-section="headers"] { --sec: oklch(0.48 0.16 300); }
|
|
817
|
+
.rail-acc[data-rail-section="auth"] { --sec: oklch(0.48 0.12 175); }
|
|
818
|
+
.rail-acc[data-rail-section="path"] { --sec: oklch(0.48 0.12 250); }
|
|
819
|
+
.token[data-slot="json-code-global-auth"] .tok-ico { color: oklch(0.48 0.12 175); }
|
|
820
|
+
.token[data-slot="json-code-global-headers"] .tok-ico { color: oklch(0.48 0.16 300); }
|
|
821
|
+
.view-toggle .token.is-on[data-response-view="fields"] .tok-ico { color: oklch(0.5 0.12 230); }
|
|
822
|
+
.view-toggle .token.is-on[data-response-view="json"] .tok-ico { color: oklch(0.55 0.14 75); }
|
|
551
823
|
}
|
|
552
824
|
* { box-sizing: border-box; }
|
|
553
825
|
html, body {
|
|
@@ -582,6 +854,7 @@ body {
|
|
|
582
854
|
.count {
|
|
583
855
|
display: inline-flex;
|
|
584
856
|
align-items: center;
|
|
857
|
+
gap: .3rem;
|
|
585
858
|
padding: 0 .5rem;
|
|
586
859
|
font-size: 10px;
|
|
587
860
|
font-weight: 500;
|
|
@@ -604,6 +877,35 @@ body {
|
|
|
604
877
|
.auth-user, .auth-key, .auth-operator { color: oklch(0.746 0.16 232.661); }
|
|
605
878
|
.auth-public { color: var(--mute); }
|
|
606
879
|
.auth-none { color: color-mix(in oklab, var(--mute) 40%, transparent); }
|
|
880
|
+
.st-ok { color: var(--ready); }
|
|
881
|
+
.st-info { color: oklch(0.746 0.16 232.661); }
|
|
882
|
+
.st-warn { color: oklch(0.828 0.189 84.429); }
|
|
883
|
+
.st-err { color: var(--fail); }
|
|
884
|
+
.meth-get, .meth-query { color: var(--ready); }
|
|
885
|
+
.meth-post { color: oklch(0.746 0.16 232.661); }
|
|
886
|
+
.meth-put { color: oklch(0.828 0.189 84.429); }
|
|
887
|
+
.meth-patch { color: oklch(0.78 0.14 300); }
|
|
888
|
+
.meth-delete { color: var(--fail); }
|
|
889
|
+
.meth-head, .meth-options, .meth-other { color: var(--mute); }
|
|
890
|
+
.verb { font-weight: 600; }
|
|
891
|
+
.file-path { color: var(--ink); }
|
|
892
|
+
.kind { color: var(--str); }
|
|
893
|
+
.tok-ico, .sec-ico {
|
|
894
|
+
display: inline-flex;
|
|
895
|
+
align-items: center;
|
|
896
|
+
flex-shrink: 0;
|
|
897
|
+
}
|
|
898
|
+
.rail-acc[data-rail-section="query"] { --sec: oklch(0.746 0.16 232.661); }
|
|
899
|
+
.rail-acc[data-rail-section="body"] { --sec: oklch(0.828 0.189 84.429); }
|
|
900
|
+
.rail-acc[data-rail-section="cookies"] { --sec: oklch(0.75 0.16 55); }
|
|
901
|
+
.rail-acc[data-rail-section="headers"] { --sec: oklch(0.78 0.13 300); }
|
|
902
|
+
.rail-acc[data-rail-section="auth"] { --sec: oklch(0.765 0.15 175); }
|
|
903
|
+
.rail-acc[data-rail-section="path"] { --sec: oklch(0.78 0.09 250); }
|
|
904
|
+
.sec-ico { color: var(--sec, var(--mute)); }
|
|
905
|
+
.token[data-slot="json-code-global-auth"] .tok-ico { color: oklch(0.765 0.15 175); }
|
|
906
|
+
.token[data-slot="json-code-global-headers"] .tok-ico { color: oklch(0.78 0.13 300); }
|
|
907
|
+
.view-toggle .token.is-on[data-response-view="fields"] .tok-ico { color: oklch(0.746 0.16 232.661); }
|
|
908
|
+
.view-toggle .token.is-on[data-response-view="json"] .tok-ico { color: oklch(0.828 0.189 84.429); }
|
|
607
909
|
.auth-label {
|
|
608
910
|
overflow: hidden;
|
|
609
911
|
text-overflow: ellipsis;
|
|
@@ -617,6 +919,7 @@ body {
|
|
|
617
919
|
padding: 0 .5rem;
|
|
618
920
|
font-size: 10px;
|
|
619
921
|
font-weight: 500;
|
|
922
|
+
gap: .3rem;
|
|
620
923
|
letter-spacing: .08em;
|
|
621
924
|
text-transform: uppercase;
|
|
622
925
|
text-decoration: none;
|
|
@@ -630,6 +933,9 @@ body {
|
|
|
630
933
|
}
|
|
631
934
|
.token:hover { background: var(--hover); color: var(--ink); }
|
|
632
935
|
.token.is-on { color: var(--ink); }
|
|
936
|
+
.token[aria-expanded="true"] { color: var(--ink); }
|
|
937
|
+
.rail-acc .token.is-on { color: var(--sec, var(--ink)); }
|
|
938
|
+
.rail-acc[open] > .rail-acc-sum .sec-ico { color: var(--sec); }
|
|
633
939
|
.sep {
|
|
634
940
|
width: 1px;
|
|
635
941
|
align-self: stretch;
|
|
@@ -643,9 +949,15 @@ body {
|
|
|
643
949
|
width: 2rem;
|
|
644
950
|
color: var(--mute);
|
|
645
951
|
}
|
|
952
|
+
.icon.meth-get, .icon.meth-query { color: var(--ready); }
|
|
953
|
+
.icon.meth-post { color: oklch(0.746 0.16 232.661); }
|
|
954
|
+
.icon.meth-put { color: oklch(0.828 0.189 84.429); }
|
|
955
|
+
.icon.meth-patch { color: oklch(0.78 0.14 300); }
|
|
956
|
+
.icon.meth-delete { color: var(--fail); }
|
|
646
957
|
.file {
|
|
647
958
|
display: inline-flex;
|
|
648
959
|
align-items: center;
|
|
960
|
+
gap: .4rem;
|
|
649
961
|
min-width: 0;
|
|
650
962
|
padding-right: .5rem;
|
|
651
963
|
overflow: hidden;
|
|
@@ -692,6 +1004,97 @@ body {
|
|
|
692
1004
|
svg[hidden] { display: none !important; }
|
|
693
1005
|
.body { display: flex; flex: 1; min-height: 0; }
|
|
694
1006
|
.view { flex: 1; min-width: 0; min-height: 0; overflow: auto; }
|
|
1007
|
+
.page[data-view="fields"] .resp-body > pre { display: none; }
|
|
1008
|
+
.page:not([data-view="fields"]) .resp-body > .fields { display: none; }
|
|
1009
|
+
.view-toggle { display: inline-flex; align-items: stretch; height: 100%; }
|
|
1010
|
+
.resp { min-height: 100%; }
|
|
1011
|
+
.resp-frame { display: flex; min-height: 100%; }
|
|
1012
|
+
.resp-rail { width: 2px; flex-shrink: 0; }
|
|
1013
|
+
.resp-rail.ok { background: var(--ready); }
|
|
1014
|
+
.resp-rail.warn { background: oklch(0.8 0.12 80); }
|
|
1015
|
+
.resp-rail.err { background: var(--fail); }
|
|
1016
|
+
.resp-main { flex: 1; min-width: 0; }
|
|
1017
|
+
.resp-status { display: flex; align-items: baseline; gap: .5rem; padding: .5rem .625rem; }
|
|
1018
|
+
.resp-code { font: 600 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; }
|
|
1019
|
+
.resp-code.ok { color: var(--ready); }
|
|
1020
|
+
.resp-code.warn { color: oklch(0.8 0.12 80); }
|
|
1021
|
+
.resp-code.err { color: var(--fail); }
|
|
1022
|
+
.resp-reason { font: 12px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--ink); }
|
|
1023
|
+
.resp-label {
|
|
1024
|
+
display: flex;
|
|
1025
|
+
align-items: center;
|
|
1026
|
+
gap: .375rem;
|
|
1027
|
+
min-height: 2rem;
|
|
1028
|
+
padding: 0 .5rem;
|
|
1029
|
+
border-top: 1px solid var(--line);
|
|
1030
|
+
font: 600 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1031
|
+
letter-spacing: .08em;
|
|
1032
|
+
text-transform: uppercase;
|
|
1033
|
+
color: var(--mute);
|
|
1034
|
+
cursor: pointer;
|
|
1035
|
+
}
|
|
1036
|
+
.resp-label::-webkit-details-marker { display: none; }
|
|
1037
|
+
.resp-hint {
|
|
1038
|
+
font-weight: 400;
|
|
1039
|
+
letter-spacing: 0;
|
|
1040
|
+
text-transform: none;
|
|
1041
|
+
color: color-mix(in oklab, var(--mute) 80%, transparent);
|
|
1042
|
+
}
|
|
1043
|
+
.resp-block { border: 0; }
|
|
1044
|
+
.fields, .field-kids { margin: 0; padding: 0; list-style: none; }
|
|
1045
|
+
.field { position: relative; border-bottom: 1px solid var(--line); }
|
|
1046
|
+
.field-row {
|
|
1047
|
+
display: flex;
|
|
1048
|
+
align-items: flex-start;
|
|
1049
|
+
gap: .5rem;
|
|
1050
|
+
padding: .375rem 2rem .375rem calc(.625rem + var(--depth, 0) * .875rem);
|
|
1051
|
+
}
|
|
1052
|
+
.field-row:hover { background: var(--hover); }
|
|
1053
|
+
summary.field-row { cursor: pointer; }
|
|
1054
|
+
summary.field-row::-webkit-details-marker { display: none; }
|
|
1055
|
+
details:not([open]) > summary .chev { transform: rotate(-90deg); }
|
|
1056
|
+
.field-key {
|
|
1057
|
+
width: 7.5rem;
|
|
1058
|
+
flex-shrink: 0;
|
|
1059
|
+
overflow: hidden;
|
|
1060
|
+
text-overflow: ellipsis;
|
|
1061
|
+
font: 500 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1062
|
+
color: var(--key);
|
|
1063
|
+
}
|
|
1064
|
+
.field-val {
|
|
1065
|
+
flex: 1;
|
|
1066
|
+
min-width: 0;
|
|
1067
|
+
overflow-wrap: anywhere;
|
|
1068
|
+
font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1069
|
+
color: var(--ink);
|
|
1070
|
+
}
|
|
1071
|
+
.field-val.is-null { color: var(--mute); font-style: italic; }
|
|
1072
|
+
.field-kind {
|
|
1073
|
+
flex-shrink: 0;
|
|
1074
|
+
margin-top: .15rem;
|
|
1075
|
+
font: 9px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1076
|
+
letter-spacing: .06em;
|
|
1077
|
+
text-transform: uppercase;
|
|
1078
|
+
color: color-mix(in oklab, var(--mute) 70%, transparent);
|
|
1079
|
+
}
|
|
1080
|
+
.field-copy {
|
|
1081
|
+
position: absolute;
|
|
1082
|
+
top: .15rem;
|
|
1083
|
+
right: .15rem;
|
|
1084
|
+
display: inline-flex;
|
|
1085
|
+
align-items: center;
|
|
1086
|
+
justify-content: center;
|
|
1087
|
+
width: 1.5rem;
|
|
1088
|
+
height: 1.5rem;
|
|
1089
|
+
padding: 0;
|
|
1090
|
+
border: 0;
|
|
1091
|
+
background: transparent;
|
|
1092
|
+
color: var(--mute);
|
|
1093
|
+
cursor: pointer;
|
|
1094
|
+
opacity: 0;
|
|
1095
|
+
}
|
|
1096
|
+
.field:hover > .field-copy, .field:focus-within > .field-copy { opacity: 1; }
|
|
1097
|
+
.field-copy:hover { color: var(--ink); }
|
|
695
1098
|
.rail-check { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
|
696
1099
|
.rail {
|
|
697
1100
|
display: flex;
|
|
@@ -711,18 +1114,22 @@ svg[hidden] { display: none !important; }
|
|
|
711
1114
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
712
1115
|
}
|
|
713
1116
|
.rail-nav {
|
|
714
|
-
flex: 1;
|
|
715
|
-
min-height: 0;
|
|
716
|
-
overflow: auto;
|
|
717
1117
|
display: flex;
|
|
718
1118
|
flex-direction: column;
|
|
1119
|
+
flex-shrink: 0;
|
|
1120
|
+
max-height: 45%;
|
|
1121
|
+
min-height: 0;
|
|
1122
|
+
overflow: auto;
|
|
1123
|
+
border-top: 1px solid var(--line);
|
|
719
1124
|
}
|
|
1125
|
+
.routes-check { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
|
720
1126
|
.rail-sec-label {
|
|
721
1127
|
display: flex;
|
|
722
1128
|
align-items: center;
|
|
1129
|
+
justify-content: space-between;
|
|
723
1130
|
height: 2.5rem;
|
|
724
1131
|
margin: 0;
|
|
725
|
-
padding: 0 .5rem;
|
|
1132
|
+
padding: 0 .25rem 0 .5rem;
|
|
726
1133
|
flex-shrink: 0;
|
|
727
1134
|
border-bottom: 1px solid var(--line);
|
|
728
1135
|
font-size: 10px;
|
|
@@ -731,13 +1138,15 @@ svg[hidden] { display: none !important; }
|
|
|
731
1138
|
text-transform: uppercase;
|
|
732
1139
|
color: var(--mute);
|
|
733
1140
|
}
|
|
1141
|
+
.routes-check:not(:checked) ~ .rail-list { display: none; }
|
|
1142
|
+
.routes-check:not(:checked) ~ .rail-sec-label { border-bottom: 0; }
|
|
1143
|
+
.routes-check:checked ~ .rail-sec-label .routes-expand { display: none; }
|
|
1144
|
+
.routes-check:not(:checked) ~ .rail-sec-label .routes-collapse { display: none; }
|
|
734
1145
|
.rail-dock {
|
|
735
1146
|
display: flex;
|
|
736
1147
|
flex-direction: column;
|
|
737
|
-
flex
|
|
738
|
-
max-height: 55%;
|
|
1148
|
+
flex: 1;
|
|
739
1149
|
min-height: 0;
|
|
740
|
-
border-top: 1px solid var(--line);
|
|
741
1150
|
}
|
|
742
1151
|
.rail-dock-strip {
|
|
743
1152
|
justify-content: flex-start;
|
|
@@ -815,6 +1224,7 @@ svg[hidden] { display: none !important; }
|
|
|
815
1224
|
flex-direction: column;
|
|
816
1225
|
min-height: 0;
|
|
817
1226
|
}
|
|
1227
|
+
.rail-acc-body[hidden] { display: none; }
|
|
818
1228
|
.kv-editor {
|
|
819
1229
|
display: flex;
|
|
820
1230
|
flex-direction: column;
|
|
@@ -854,6 +1264,111 @@ svg[hidden] { display: none !important; }
|
|
|
854
1264
|
font-size: 11px;
|
|
855
1265
|
color: var(--mute);
|
|
856
1266
|
}
|
|
1267
|
+
.auth-field {
|
|
1268
|
+
display: flex;
|
|
1269
|
+
align-items: stretch;
|
|
1270
|
+
height: 2rem;
|
|
1271
|
+
border-bottom: 1px solid var(--line);
|
|
1272
|
+
}
|
|
1273
|
+
.auth-lab {
|
|
1274
|
+
display: flex;
|
|
1275
|
+
align-items: center;
|
|
1276
|
+
width: 42%;
|
|
1277
|
+
max-width: 42%;
|
|
1278
|
+
flex-shrink: 0;
|
|
1279
|
+
padding: 0 .5rem;
|
|
1280
|
+
border-right: 1px solid var(--line);
|
|
1281
|
+
font: 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1282
|
+
color: var(--mute);
|
|
1283
|
+
}
|
|
1284
|
+
.auth-field .kv-val { flex: 1; }
|
|
1285
|
+
.auth-field .kv-val::-ms-reveal { display: none; }
|
|
1286
|
+
.auth-reveal {
|
|
1287
|
+
display: grid;
|
|
1288
|
+
place-items: center;
|
|
1289
|
+
flex-shrink: 0;
|
|
1290
|
+
width: 2.5rem;
|
|
1291
|
+
border: 0;
|
|
1292
|
+
border-left: 1px solid var(--line);
|
|
1293
|
+
background: transparent;
|
|
1294
|
+
color: var(--mute);
|
|
1295
|
+
cursor: pointer;
|
|
1296
|
+
}
|
|
1297
|
+
.auth-reveal:hover { color: var(--ink); background: var(--hover); }
|
|
1298
|
+
.auth-reveal .eye-shut { display: none; }
|
|
1299
|
+
.auth-reveal[aria-pressed="true"] .eye-open { display: none; }
|
|
1300
|
+
.auth-reveal[aria-pressed="true"] .eye-shut { display: block; }
|
|
1301
|
+
.auth-in {
|
|
1302
|
+
display: inline-flex;
|
|
1303
|
+
align-items: stretch;
|
|
1304
|
+
flex: 1;
|
|
1305
|
+
min-width: 0;
|
|
1306
|
+
}
|
|
1307
|
+
.auth-pane[hidden] { display: none; }
|
|
1308
|
+
.global-pop {
|
|
1309
|
+
display: flex;
|
|
1310
|
+
align-items: stretch;
|
|
1311
|
+
height: 2.5rem;
|
|
1312
|
+
flex-shrink: 0;
|
|
1313
|
+
background: var(--field);
|
|
1314
|
+
border-bottom: 1px solid var(--line);
|
|
1315
|
+
}
|
|
1316
|
+
.global-pop[hidden] { display: none; }
|
|
1317
|
+
.global-line {
|
|
1318
|
+
display: flex;
|
|
1319
|
+
flex: 1;
|
|
1320
|
+
min-width: 0;
|
|
1321
|
+
align-items: stretch;
|
|
1322
|
+
}
|
|
1323
|
+
.global-line[hidden] { display: none; }
|
|
1324
|
+
.global-pop .auth-editor,
|
|
1325
|
+
.global-pop .auth-pane:not([hidden]) {
|
|
1326
|
+
display: flex;
|
|
1327
|
+
flex: 1;
|
|
1328
|
+
min-width: 0;
|
|
1329
|
+
align-items: stretch;
|
|
1330
|
+
}
|
|
1331
|
+
.global-pop .body-mode-strip { margin: 0; border-right: 1px solid var(--line); }
|
|
1332
|
+
.global-pop .auth-pane[hidden],
|
|
1333
|
+
.global-pop .kv-empty[hidden] { display: none; }
|
|
1334
|
+
.global-pop .auth-field {
|
|
1335
|
+
flex: 1;
|
|
1336
|
+
height: auto;
|
|
1337
|
+
min-width: 8rem;
|
|
1338
|
+
border-bottom: 0;
|
|
1339
|
+
}
|
|
1340
|
+
.global-pop .auth-lab {
|
|
1341
|
+
width: auto;
|
|
1342
|
+
max-width: none;
|
|
1343
|
+
padding: 0 .75rem;
|
|
1344
|
+
color: color-mix(in oklab, var(--mute) 80%, transparent);
|
|
1345
|
+
}
|
|
1346
|
+
.global-pop .kv-empty {
|
|
1347
|
+
display: flex;
|
|
1348
|
+
align-items: center;
|
|
1349
|
+
flex: 1;
|
|
1350
|
+
margin: 0;
|
|
1351
|
+
padding: 0 .75rem;
|
|
1352
|
+
white-space: nowrap;
|
|
1353
|
+
overflow: hidden;
|
|
1354
|
+
text-overflow: ellipsis;
|
|
1355
|
+
}
|
|
1356
|
+
.global-pop .kv-editor,
|
|
1357
|
+
.global-pop .kv-rows {
|
|
1358
|
+
display: flex;
|
|
1359
|
+
flex: 1;
|
|
1360
|
+
min-width: 0;
|
|
1361
|
+
max-height: none;
|
|
1362
|
+
align-items: stretch;
|
|
1363
|
+
overflow-x: auto;
|
|
1364
|
+
}
|
|
1365
|
+
.global-pop .kv-row {
|
|
1366
|
+
flex: 1 0 18rem;
|
|
1367
|
+
height: auto;
|
|
1368
|
+
border-bottom: 0;
|
|
1369
|
+
border-right: 1px solid var(--line);
|
|
1370
|
+
}
|
|
1371
|
+
.global-pop .kv-row:last-child { border-right: 0; }
|
|
857
1372
|
.body-mode-strip {
|
|
858
1373
|
display: inline-flex;
|
|
859
1374
|
align-items: stretch;
|
|
@@ -998,6 +1513,12 @@ details:not(.rail-acc):not([open]) .chev { transform: rotate(-90deg); }
|
|
|
998
1513
|
text-transform: uppercase;
|
|
999
1514
|
color: color-mix(in oklab, var(--mute) 70%, transparent);
|
|
1000
1515
|
}
|
|
1516
|
+
.leaf[data-method="GET"] .leaf-type,
|
|
1517
|
+
.leaf[data-method="QUERY"] .leaf-type { color: var(--ready); }
|
|
1518
|
+
.leaf[data-method="POST"] .leaf-type { color: oklch(0.746 0.16 232.661); }
|
|
1519
|
+
.leaf[data-method="PUT"] .leaf-type { color: oklch(0.828 0.189 84.429); }
|
|
1520
|
+
.leaf[data-method="PATCH"] .leaf-type { color: oklch(0.78 0.14 300); }
|
|
1521
|
+
.leaf[data-method="DELETE"] .leaf-type { color: var(--fail); }
|
|
1001
1522
|
.rail-thin {
|
|
1002
1523
|
display: none;
|
|
1003
1524
|
width: 1.75rem;
|
|
@@ -1022,6 +1543,12 @@ details:not(.rail-acc):not([open]) .chev { transform: rotate(-90deg); }
|
|
|
1022
1543
|
.rail-check:not(:checked) ~ .rail-thin { display: flex; }
|
|
1023
1544
|
@media (prefers-color-scheme: light) {
|
|
1024
1545
|
.leaf.is-on { background: oklch(0.967 0.001 286.375 / 0.7); }
|
|
1546
|
+
.leaf[data-method="GET"] .leaf-type,
|
|
1547
|
+
.leaf[data-method="QUERY"] .leaf-type { color: oklch(0.55 0.14 163); }
|
|
1548
|
+
.leaf[data-method="POST"] .leaf-type { color: oklch(0.5 0.12 230); }
|
|
1549
|
+
.leaf[data-method="PUT"] .leaf-type { color: oklch(0.55 0.14 75); }
|
|
1550
|
+
.leaf[data-method="PATCH"] .leaf-type { color: oklch(0.48 0.16 300); }
|
|
1551
|
+
.leaf[data-method="DELETE"] .leaf-type { color: oklch(0.55 0.2 22); }
|
|
1025
1552
|
}
|
|
1026
1553
|
pre {
|
|
1027
1554
|
margin: 0;
|
|
@@ -1051,45 +1578,91 @@ pre {
|
|
|
1051
1578
|
</style>
|
|
1052
1579
|
</head>
|
|
1053
1580
|
<body>
|
|
1054
|
-
<main class="page" data-slot="json-code-block" data-state="complete" data-view="${compact ? "raw" : "pretty"}" data-status="${options.status}" data-method="${escapeHtml(options.method)}" data-path="${escapeHtml(options.path)}">
|
|
1581
|
+
<main class="page" data-slot="json-code-block" data-state="complete" data-view="${view}" data-code-view="${compact ? "raw" : "pretty"}" data-status="${options.status}" data-method="${escapeHtml(options.method)}" data-path="${escapeHtml(options.path)}">
|
|
1055
1582
|
<header class="strip">
|
|
1056
1583
|
<span class="title">${escapeHtml(options.app)}</span>
|
|
1057
|
-
<span class="count">${options.status}</span>
|
|
1584
|
+
<span class="count st-${statusTone(options.status)}" data-slot="json-code-status">${GLYPH.dot}${options.status}</span>
|
|
1058
1585
|
${latencyHtml(options.latencyMs)}
|
|
1059
1586
|
${cacheHtml(options.cache)}
|
|
1060
1587
|
${authHtml(options.auth)}
|
|
1061
1588
|
<span class="grow"></span>
|
|
1062
|
-
<
|
|
1063
|
-
<
|
|
1064
|
-
class="token is-on"
|
|
1065
|
-
data-nav
|
|
1066
|
-
data-slot="json-code-view-toggle"
|
|
1067
|
-
href="${escapeHtml(compact ? options.prettyHref : options.rawHref)}"
|
|
1068
|
-
aria-pressed="${compact ? "true" : "false"}"
|
|
1069
|
-
title="${compact ? "Show pretty JSON" : "Show compact JSON"}"
|
|
1070
|
-
>${compact ? "Raw" : "Pretty"}</a>
|
|
1589
|
+
<button type="button" class="token" data-slot="json-code-global-auth" aria-expanded="false" aria-controls="json-code-global-panel" title="Global authentication"><span class="tok-ico">${GLYPH.key}</span>Auth</button>
|
|
1590
|
+
<button type="button" class="token" data-slot="json-code-global-headers" aria-expanded="false" aria-controls="json-code-global-panel" title="Global headers"><span class="tok-ico">${GLYPH.list}</span>Headers</button>
|
|
1071
1591
|
</header>
|
|
1592
|
+
<div class="global-pop" id="json-code-global-panel" data-slot="json-code-global-panel" data-pane="auth" hidden>
|
|
1593
|
+
<div class="global-line" data-global-pane="auth" data-auth-scope="global">
|
|
1594
|
+
${authEditorHtml("global")}
|
|
1595
|
+
</div>
|
|
1596
|
+
<div class="global-line" data-global-pane="headers" hidden>
|
|
1597
|
+
<div class="kv-editor">
|
|
1598
|
+
<div class="kv-rows" data-slot="json-code-kv-rows" data-kv="headers-global"></div>
|
|
1599
|
+
</div>
|
|
1600
|
+
</div>
|
|
1601
|
+
</div>
|
|
1072
1602
|
<header class="strip">
|
|
1073
|
-
<span class="icon" aria-hidden="true">
|
|
1074
|
-
|
|
1075
|
-
</span>
|
|
1076
|
-
<span class="file">${escapeHtml(title)}</span>
|
|
1077
|
-
<span class="head">json</span>
|
|
1603
|
+
<span class="icon meth meth-${methodTone(options.method)}" data-slot="json-code-method-icon" aria-hidden="true">${methodGlyph(options.method)}</span>
|
|
1604
|
+
<span class="file"><span class="verb meth-${methodTone(options.method)}" data-slot="json-code-method">${escapeHtml(options.method)}</span><span class="file-path" data-slot="json-code-path">${escapeHtml(options.path)}</span></span>
|
|
1605
|
+
<span class="head kind">json</span>
|
|
1078
1606
|
<span class="state">${ok ? "Ready" : options.status}</span>
|
|
1607
|
+
${responseViewToggleHtml(hasFields, compact)}
|
|
1079
1608
|
<button class="copy" type="button" data-slot="json-code-copy" aria-label="Copy code" title="Copy code">
|
|
1080
1609
|
<svg data-copy width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="8" y="8" width="11" height="11" rx="1.5" stroke="currentColor" stroke-width="1.5"/><path d="M5 16V5.5A1.5 1.5 0 0 1 6.5 4H16" stroke="currentColor" stroke-width="1.5"/></svg>
|
|
1081
1610
|
<svg data-done hidden width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12.5 9.2 17 19 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
1082
1611
|
</button>
|
|
1083
1612
|
</header>
|
|
1084
1613
|
<div class="body">
|
|
1085
|
-
<div class="view"
|
|
1614
|
+
<div class="view">${responseHtml || `<pre>${rows}</pre>`}</div>
|
|
1086
1615
|
${navHtml(options.nav)}
|
|
1087
1616
|
</div>
|
|
1088
1617
|
</main>
|
|
1089
1618
|
<textarea id="payload" hidden>${escapeHtml(code)}</textarea>
|
|
1090
1619
|
<script>
|
|
1091
1620
|
(() => {
|
|
1621
|
+
const METHOD_GLYPH = ${JSON.stringify({
|
|
1622
|
+
get: GLYPH.eye,
|
|
1623
|
+
query: GLYPH.search,
|
|
1624
|
+
post: GLYPH.plus,
|
|
1625
|
+
put: GLYPH.pencil,
|
|
1626
|
+
patch: GLYPH.pencil,
|
|
1627
|
+
delete: GLYPH.trash,
|
|
1628
|
+
head: GLYPH.file,
|
|
1629
|
+
options: GLYPH.sliders,
|
|
1630
|
+
other: GLYPH.file,
|
|
1631
|
+
})};
|
|
1632
|
+
function methodToneName(method) {
|
|
1633
|
+
switch (String(method || "").toUpperCase()) {
|
|
1634
|
+
case "GET": return "get";
|
|
1635
|
+
case "QUERY": return "query";
|
|
1636
|
+
case "POST": return "post";
|
|
1637
|
+
case "PUT": return "put";
|
|
1638
|
+
case "PATCH": return "patch";
|
|
1639
|
+
case "DELETE": return "delete";
|
|
1640
|
+
case "HEAD": return "head";
|
|
1641
|
+
case "OPTIONS": return "options";
|
|
1642
|
+
default: return "other";
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
function paintRequestLine(method, path) {
|
|
1646
|
+
const tone = methodToneName(method);
|
|
1647
|
+
const verb = document.querySelector("[data-slot=json-code-method]");
|
|
1648
|
+
const icon = document.querySelector("[data-slot=json-code-method-icon]");
|
|
1649
|
+
const pathEl = document.querySelector("[data-slot=json-code-path]");
|
|
1650
|
+
if (verb) {
|
|
1651
|
+
verb.textContent = method;
|
|
1652
|
+
verb.className = "verb meth-" + tone;
|
|
1653
|
+
}
|
|
1654
|
+
if (icon) {
|
|
1655
|
+
icon.className = "icon meth meth-" + tone;
|
|
1656
|
+
icon.innerHTML = METHOD_GLYPH[tone] || METHOD_GLYPH.other;
|
|
1657
|
+
}
|
|
1658
|
+
if (pathEl) pathEl.textContent = path;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1092
1661
|
const HEADERS_KEY = "oke:json-code:headers";
|
|
1662
|
+
const HEADERS_MODE_KEY = "oke:json-code:headers-mode";
|
|
1663
|
+
const HEADERS_GLOBAL_KEY = "oke:json-code:headers-global";
|
|
1664
|
+
const AUTH_KEY = "oke:json-code:auth";
|
|
1665
|
+
const AUTH_GLOBAL_KEY = "oke:json-code:auth-global";
|
|
1093
1666
|
const QUERY_KEY = "oke:json-code:query";
|
|
1094
1667
|
const PATH_KEY = "oke:json-code:path";
|
|
1095
1668
|
const PATH_TEMPLATE_KEY = "oke:json-code:path-template";
|
|
@@ -1099,7 +1672,7 @@ pre {
|
|
|
1099
1672
|
const BODY_JSON_KEY = "oke:json-code:body-json";
|
|
1100
1673
|
const SECTION_KEY = "oke:json-code:rail-section";
|
|
1101
1674
|
const ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
|
|
1102
|
-
const SECTIONS = ["query", "body", "cookies", "headers", "path"];
|
|
1675
|
+
const SECTIONS = ["query", "body", "cookies", "headers", "auth", "path"];
|
|
1103
1676
|
const page = document.querySelector("[data-slot=json-code-block]");
|
|
1104
1677
|
const railCheck = document.getElementById("json-code-rail");
|
|
1105
1678
|
const resetBtn = document.querySelector("[data-slot=json-code-reset]");
|
|
@@ -1113,6 +1686,35 @@ pre {
|
|
|
1113
1686
|
const bodyModeBtns = document.querySelectorAll("[data-slot=json-code-body-mode]");
|
|
1114
1687
|
const btn = document.querySelector("[data-slot=json-code-copy]");
|
|
1115
1688
|
const payload = document.getElementById("payload");
|
|
1689
|
+
document.querySelectorAll("[data-response-view]").forEach((el) => {
|
|
1690
|
+
el.addEventListener("click", () => {
|
|
1691
|
+
const next = el.getAttribute("data-response-view");
|
|
1692
|
+
if (!next || !page) return;
|
|
1693
|
+
page.setAttribute(
|
|
1694
|
+
"data-view",
|
|
1695
|
+
next === "fields" ? "fields" : page.getAttribute("data-code-view") || "pretty",
|
|
1696
|
+
);
|
|
1697
|
+
document.querySelectorAll("[data-response-view]").forEach((other) => {
|
|
1698
|
+
const on = other.getAttribute("data-response-view") === next;
|
|
1699
|
+
other.classList.toggle("is-on", on);
|
|
1700
|
+
other.setAttribute("aria-pressed", on ? "true" : "false");
|
|
1701
|
+
});
|
|
1702
|
+
});
|
|
1703
|
+
});
|
|
1704
|
+
document.addEventListener("click", (event) => {
|
|
1705
|
+
const copy = event.target instanceof Element ? event.target.closest("[data-field-copy]") : null;
|
|
1706
|
+
if (!copy || !payload) return;
|
|
1707
|
+
event.preventDefault();
|
|
1708
|
+
event.stopPropagation();
|
|
1709
|
+
let value;
|
|
1710
|
+
try {
|
|
1711
|
+
const path = JSON.parse(copy.getAttribute("data-field-copy") || "[]");
|
|
1712
|
+
value = JSON.parse(payload.value);
|
|
1713
|
+
for (const seg of path) value = value == null ? value : value[seg];
|
|
1714
|
+
} catch { return; }
|
|
1715
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
1716
|
+
navigator.clipboard.writeText(text).catch(() => {});
|
|
1717
|
+
});
|
|
1116
1718
|
const copyIcon = btn?.querySelector("[data-copy]");
|
|
1117
1719
|
const doneIcon = btn?.querySelector("[data-done]");
|
|
1118
1720
|
if (!page || !sendBtn) return;
|
|
@@ -1364,7 +1966,8 @@ pre {
|
|
|
1364
1966
|
section.open = true;
|
|
1365
1967
|
section.scrollIntoView({ block: "nearest" });
|
|
1366
1968
|
}
|
|
1367
|
-
if (name === "
|
|
1969
|
+
if (name === "auth") fillAuth("request");
|
|
1970
|
+
else if (name === "cookies" || name === "headers" || name === "query" || name === "path" || name === "body") {
|
|
1368
1971
|
if (name === "body") syncBodyMode(bodyMode());
|
|
1369
1972
|
else renderKv(name);
|
|
1370
1973
|
}
|
|
@@ -1449,7 +2052,7 @@ pre {
|
|
|
1449
2052
|
const key = (row.querySelector(".kv-key")?.value || "").trim();
|
|
1450
2053
|
const value = row.querySelector(".kv-val")?.value || "";
|
|
1451
2054
|
if (!key && !value.trim()) continue;
|
|
1452
|
-
if (kind === "headers" && key.toLowerCase() === "authorization") continue;
|
|
2055
|
+
if ((kind === "headers" || kind === "headers-global") && key.toLowerCase() === "authorization") continue;
|
|
1453
2056
|
out.push({ key, value });
|
|
1454
2057
|
}
|
|
1455
2058
|
return out;
|
|
@@ -1473,6 +2076,8 @@ pre {
|
|
|
1473
2076
|
} else if (kind === "headers") {
|
|
1474
2077
|
pairs = loadPairs(HEADERS_KEY).filter((p) => String(p.key).toLowerCase() !== "authorization");
|
|
1475
2078
|
if (pairs.length === 0) pairs = [{ key: "accept", value: ACCEPT }];
|
|
2079
|
+
} else if (kind === "headers-global") {
|
|
2080
|
+
pairs = loadPairs(HEADERS_GLOBAL_KEY).filter((p) => String(p.key).toLowerCase() !== "authorization");
|
|
1476
2081
|
} else if (kind === "body") {
|
|
1477
2082
|
pairs = loadPairs(BODY_FORM_KEY);
|
|
1478
2083
|
} else {
|
|
@@ -1491,6 +2096,12 @@ pre {
|
|
|
1491
2096
|
if (!sessionStorage.getItem(HEADERS_KEY)) {
|
|
1492
2097
|
savePairs(HEADERS_KEY, [{ key: "accept", value: ACCEPT }]);
|
|
1493
2098
|
}
|
|
2099
|
+
if (sessionStorage.getItem(HEADERS_MODE_KEY) !== "custom") {
|
|
2100
|
+
sessionStorage.setItem(HEADERS_MODE_KEY, "inherit");
|
|
2101
|
+
}
|
|
2102
|
+
if (!sessionStorage.getItem(HEADERS_GLOBAL_KEY)) savePairs(HEADERS_GLOBAL_KEY, []);
|
|
2103
|
+
if (!sessionStorage.getItem(AUTH_KEY)) sessionStorage.setItem(AUTH_KEY, JSON.stringify(emptyAuth()));
|
|
2104
|
+
if (!sessionStorage.getItem(AUTH_GLOBAL_KEY)) sessionStorage.setItem(AUTH_GLOBAL_KEY, JSON.stringify(emptyAuth()));
|
|
1494
2105
|
if (!sessionStorage.getItem(QUERY_KEY)) {
|
|
1495
2106
|
const pairs = [];
|
|
1496
2107
|
for (const [key, value] of new URLSearchParams(location.search)) pairs.push({ key, value });
|
|
@@ -1528,15 +2139,193 @@ pre {
|
|
|
1528
2139
|
}
|
|
1529
2140
|
}
|
|
1530
2141
|
|
|
1531
|
-
function
|
|
1532
|
-
|
|
1533
|
-
|
|
2142
|
+
function emptyAuth() {
|
|
2143
|
+
return { type: "none", token: "", username: "", password: "", key: "", value: "", in: "header", mode: "inherit" };
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
function authStorageKey(scope) {
|
|
2147
|
+
return scope === "global" ? AUTH_GLOBAL_KEY : AUTH_KEY;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
function loadAuth(key) {
|
|
2151
|
+
try {
|
|
2152
|
+
const raw = sessionStorage.getItem(key);
|
|
2153
|
+
if (!raw) return emptyAuth();
|
|
2154
|
+
const parsed = JSON.parse(raw);
|
|
2155
|
+
const type = parsed && parsed.type;
|
|
2156
|
+
if (type !== "bearer" && type !== "basic" && type !== "apikey" && type !== "none") return emptyAuth();
|
|
2157
|
+
return {
|
|
2158
|
+
type,
|
|
2159
|
+
token: typeof parsed.token === "string" ? parsed.token : "",
|
|
2160
|
+
username: typeof parsed.username === "string" ? parsed.username : "",
|
|
2161
|
+
password: typeof parsed.password === "string" ? parsed.password : "",
|
|
2162
|
+
key: typeof parsed.key === "string" ? parsed.key : "",
|
|
2163
|
+
value: typeof parsed.value === "string" ? parsed.value : "",
|
|
2164
|
+
in: parsed.in === "query" ? "query" : "header",
|
|
2165
|
+
mode: parsed.mode === "custom" ? "custom" : "inherit",
|
|
2166
|
+
};
|
|
2167
|
+
} catch {
|
|
2168
|
+
return emptyAuth();
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
function readAuth(scope) {
|
|
2173
|
+
const root = document.querySelector('[data-auth-scope="' + scope + '"]');
|
|
2174
|
+
if (!root) return loadAuth(authStorageKey(scope));
|
|
2175
|
+
const on = root.querySelector("[data-auth-type].is-on");
|
|
2176
|
+
const type = on ? on.getAttribute("data-auth-type") : "none";
|
|
2177
|
+
const field = (name) => {
|
|
2178
|
+
const input = root.querySelector('[data-auth-field="' + name + '"]');
|
|
2179
|
+
return input ? input.value : "";
|
|
2180
|
+
};
|
|
2181
|
+
const inBtn = root.querySelector("[data-auth-in].is-on");
|
|
2182
|
+
const modeBtn = root.querySelector("[data-auth-mode].is-on");
|
|
2183
|
+
const mode = modeBtn && modeBtn.getAttribute("data-auth-mode") === "custom" ? "custom" : "inherit";
|
|
2184
|
+
return {
|
|
2185
|
+
type: type === "bearer" || type === "basic" || type === "apikey" ? type : "none",
|
|
2186
|
+
token: field("token"),
|
|
2187
|
+
username: field("username"),
|
|
2188
|
+
password: field("password"),
|
|
2189
|
+
key: field("key"),
|
|
2190
|
+
value: field("value"),
|
|
2191
|
+
in: inBtn && inBtn.getAttribute("data-auth-in") === "query" ? "query" : "header",
|
|
2192
|
+
mode,
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
function authLabel(auth) {
|
|
2197
|
+
if (auth.type === "bearer") return "Bearer";
|
|
2198
|
+
if (auth.type === "basic") return "Basic";
|
|
2199
|
+
if (auth.type === "apikey") return "API";
|
|
2200
|
+
return "No";
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
function paintAuth(scope, auth) {
|
|
2204
|
+
const root = document.querySelector('[data-auth-scope="' + scope + '"]');
|
|
2205
|
+
if (root) {
|
|
2206
|
+
for (const btn of root.querySelectorAll("[data-auth-type]")) {
|
|
2207
|
+
const on = btn.getAttribute("data-auth-type") === auth.type;
|
|
2208
|
+
btn.classList.toggle("is-on", on);
|
|
2209
|
+
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
2210
|
+
}
|
|
2211
|
+
for (const pane of root.querySelectorAll("[data-auth-pane]")) {
|
|
2212
|
+
pane.hidden = pane.getAttribute("data-auth-pane") !== auth.type;
|
|
2213
|
+
}
|
|
2214
|
+
for (const btn of root.querySelectorAll("[data-auth-in]")) {
|
|
2215
|
+
const on = (btn.getAttribute("data-auth-in") || "header") === auth.in;
|
|
2216
|
+
btn.classList.toggle("is-on", on);
|
|
2217
|
+
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
2218
|
+
}
|
|
2219
|
+
const mode = auth.mode === "custom" ? "custom" : "inherit";
|
|
2220
|
+
for (const btn of root.querySelectorAll("[data-auth-mode]")) {
|
|
2221
|
+
const on = btn.getAttribute("data-auth-mode") === mode;
|
|
2222
|
+
btn.classList.toggle("is-on", on);
|
|
2223
|
+
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
2224
|
+
}
|
|
2225
|
+
const custom = root.querySelector("[data-auth-custom]");
|
|
2226
|
+
if (custom) custom.hidden = mode !== "custom";
|
|
2227
|
+
}
|
|
2228
|
+
if (scope === "global") {
|
|
2229
|
+
const btn = document.querySelector("[data-slot=json-code-global-auth]");
|
|
2230
|
+
if (btn) {
|
|
2231
|
+
const active = auth.type !== "none";
|
|
2232
|
+
btn.classList.toggle("is-on", active);
|
|
2233
|
+
btn.title = active ? "Global authentication · " + authLabel(auth) : "Global authentication";
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
function fillAuth(scope) {
|
|
2239
|
+
const auth = loadAuth(authStorageKey(scope));
|
|
2240
|
+
const root = document.querySelector('[data-auth-scope="' + scope + '"]');
|
|
2241
|
+
if (root) {
|
|
2242
|
+
const set = (name, value) => {
|
|
2243
|
+
const input = root.querySelector('[data-auth-field="' + name + '"]');
|
|
2244
|
+
if (input) input.value = value;
|
|
2245
|
+
};
|
|
2246
|
+
set("token", auth.token);
|
|
2247
|
+
set("username", auth.username);
|
|
2248
|
+
set("password", auth.password);
|
|
2249
|
+
set("key", auth.key);
|
|
2250
|
+
set("value", auth.value);
|
|
2251
|
+
}
|
|
2252
|
+
paintAuth(scope, auth);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
function storedAuth(auth) {
|
|
2256
|
+
return auth;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function forgetSecretsOnReload() {
|
|
2260
|
+
try {
|
|
2261
|
+
const nav = performance.getEntriesByType("navigation")[0];
|
|
2262
|
+
if (!nav || nav.type !== "reload") return;
|
|
2263
|
+
const mark = String(nav.startTime);
|
|
2264
|
+
if (sessionStorage.getItem("oke:json-code:reload-seen") === mark) return;
|
|
2265
|
+
sessionStorage.setItem("oke:json-code:reload-seen", mark);
|
|
2266
|
+
for (const key of [AUTH_KEY, AUTH_GLOBAL_KEY]) {
|
|
2267
|
+
const auth = loadAuth(key);
|
|
2268
|
+
sessionStorage.setItem(key, JSON.stringify({ ...auth, token: "", password: "", value: "" }));
|
|
2269
|
+
}
|
|
2270
|
+
} catch {}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
function persistAuth(scope) {
|
|
2274
|
+
const auth = readAuth(scope);
|
|
2275
|
+
sessionStorage.setItem(authStorageKey(scope), JSON.stringify(storedAuth(auth)));
|
|
2276
|
+
paintAuth(scope, auth);
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
function effectiveAuth() {
|
|
2280
|
+
const request = readAuth("request");
|
|
2281
|
+
if (request.mode === "custom") return request;
|
|
2282
|
+
return readAuth("global");
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function basicAuthorization(user, pass) {
|
|
2286
|
+
const bytes = new TextEncoder().encode(String(user) + ":" + String(pass));
|
|
2287
|
+
let bin = "";
|
|
2288
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
2289
|
+
return "Basic " + btoa(bin);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
function headersMode() {
|
|
2293
|
+
return sessionStorage.getItem(HEADERS_MODE_KEY) === "custom" ? "custom" : "inherit";
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
function paintHeadersMode(mode) {
|
|
2297
|
+
const next = mode === "custom" ? "custom" : "inherit";
|
|
2298
|
+
const root = document.querySelector('[data-rail-section="headers"]');
|
|
2299
|
+
if (!root) return;
|
|
2300
|
+
for (const btn of root.querySelectorAll("[data-headers-mode]")) {
|
|
2301
|
+
const on = btn.getAttribute("data-headers-mode") === next;
|
|
2302
|
+
btn.classList.toggle("is-on", on);
|
|
2303
|
+
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
2304
|
+
}
|
|
2305
|
+
const custom = root.querySelector("[data-headers-custom]");
|
|
2306
|
+
if (custom) custom.hidden = next !== "custom";
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
function applyHeaderPairs(headers, pairs) {
|
|
2310
|
+
for (const p of pairs) {
|
|
1534
2311
|
const k = (p.key || "").trim();
|
|
1535
|
-
if (!k) continue;
|
|
1536
|
-
const lower = k.toLowerCase();
|
|
1537
|
-
if (lower === "authorization") continue;
|
|
2312
|
+
if (!k || k.toLowerCase() === "authorization") continue;
|
|
1538
2313
|
headers[k] = p.value ?? "";
|
|
1539
2314
|
}
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function requestHeaders(contentType) {
|
|
2318
|
+
const headers = { accept: ACCEPT };
|
|
2319
|
+
applyHeaderPairs(headers, loadPairs(HEADERS_GLOBAL_KEY));
|
|
2320
|
+
if (headersMode() === "custom") applyHeaderPairs(headers, loadPairs(HEADERS_KEY));
|
|
2321
|
+
const auth = effectiveAuth();
|
|
2322
|
+
if (auth.type === "bearer" && auth.token.trim()) {
|
|
2323
|
+
headers.authorization = "Bearer " + auth.token.trim();
|
|
2324
|
+
} else if (auth.type === "basic" && (auth.username || auth.password)) {
|
|
2325
|
+
headers.authorization = basicAuthorization(auth.username, auth.password);
|
|
2326
|
+
} else if (auth.type === "apikey" && auth.in !== "query" && auth.key.trim()) {
|
|
2327
|
+
headers[auth.key.trim()] = auth.value ?? "";
|
|
2328
|
+
}
|
|
1540
2329
|
if (contentType) {
|
|
1541
2330
|
const hasCt = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
|
|
1542
2331
|
if (!hasCt) headers["content-type"] = contentType;
|
|
@@ -1551,6 +2340,10 @@ pre {
|
|
|
1551
2340
|
if (!k) continue;
|
|
1552
2341
|
params.set(k, p.value ?? "");
|
|
1553
2342
|
}
|
|
2343
|
+
const auth = effectiveAuth();
|
|
2344
|
+
if (auth.type === "apikey" && auth.in === "query" && auth.key.trim()) {
|
|
2345
|
+
params.set(auth.key.trim(), auth.value ?? "");
|
|
2346
|
+
}
|
|
1554
2347
|
const q = params.toString();
|
|
1555
2348
|
return q ? pathname + "?" + q : pathname;
|
|
1556
2349
|
}
|
|
@@ -1625,6 +2418,12 @@ pre {
|
|
|
1625
2418
|
HEADERS_KEY,
|
|
1626
2419
|
collectKv("headers").filter((p) => p.key.toLowerCase() !== "authorization"),
|
|
1627
2420
|
);
|
|
2421
|
+
savePairs(
|
|
2422
|
+
HEADERS_GLOBAL_KEY,
|
|
2423
|
+
collectKv("headers-global").filter((p) => p.key.toLowerCase() !== "authorization"),
|
|
2424
|
+
);
|
|
2425
|
+
persistAuth("request");
|
|
2426
|
+
persistAuth("global");
|
|
1628
2427
|
savePairs(QUERY_KEY, collectKv("query"));
|
|
1629
2428
|
savePairs(BODY_FORM_KEY, collectKv("body"));
|
|
1630
2429
|
if (bodyRaw) sessionStorage.setItem(BODY_JSON_KEY, bodyRaw.value);
|
|
@@ -1688,6 +2487,8 @@ pre {
|
|
|
1688
2487
|
|
|
1689
2488
|
function resetOptions() {
|
|
1690
2489
|
savePairs(HEADERS_KEY, [{ key: "accept", value: ACCEPT }]);
|
|
2490
|
+
sessionStorage.setItem(HEADERS_MODE_KEY, "inherit");
|
|
2491
|
+
sessionStorage.setItem(AUTH_KEY, JSON.stringify(emptyAuth()));
|
|
1691
2492
|
const pairs = [];
|
|
1692
2493
|
for (const [key, value] of new URLSearchParams(location.search)) pairs.push({ key, value });
|
|
1693
2494
|
savePairs(QUERY_KEY, pairs);
|
|
@@ -1699,6 +2500,8 @@ pre {
|
|
|
1699
2500
|
savePairs(PATH_KEY, pathNames(template).map((n) => ({ key: n, value: "" })));
|
|
1700
2501
|
}
|
|
1701
2502
|
for (const kind of ["cookies", "headers", "query", "path", "body"]) renderKv(kind);
|
|
2503
|
+
paintHeadersMode("inherit");
|
|
2504
|
+
fillAuth("request");
|
|
1702
2505
|
syncBodyMode("form");
|
|
1703
2506
|
syncPathChapter();
|
|
1704
2507
|
}
|
|
@@ -1710,10 +2513,47 @@ pre {
|
|
|
1710
2513
|
return names.every((n) => (stored[n] || "").trim().length > 0);
|
|
1711
2514
|
}
|
|
1712
2515
|
|
|
2516
|
+
function authHasCredentials(auth) {
|
|
2517
|
+
if (auth.type === "bearer") return auth.token.trim().length > 0;
|
|
2518
|
+
if (auth.type === "basic") return Boolean(auth.username || auth.password);
|
|
2519
|
+
if (auth.type === "apikey") return auth.key.trim().length > 0;
|
|
2520
|
+
return false;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/** Address-bar loads omit sessionStorage credentials. Replay a 401 once. */
|
|
2524
|
+
function replayStoredAuth() {
|
|
2525
|
+
const status = Number(page.getAttribute("data-status") || "0");
|
|
2526
|
+
if (status !== 401) {
|
|
2527
|
+
try { sessionStorage.removeItem("oke:json-code:auth-replay"); } catch {}
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
const auth = effectiveAuth();
|
|
2531
|
+
if (!authHasCredentials(auth)) return;
|
|
2532
|
+
const method = (page.getAttribute("data-method") || "GET").toUpperCase();
|
|
2533
|
+
const path = page.getAttribute("data-path") || location.pathname;
|
|
2534
|
+
const stamp = [
|
|
2535
|
+
method, path, location.search, auth.type, auth.token, auth.username,
|
|
2536
|
+
auth.password, auth.key, auth.value, auth.in,
|
|
2537
|
+
].join("\\0");
|
|
2538
|
+
try {
|
|
2539
|
+
if (sessionStorage.getItem("oke:json-code:auth-replay") === stamp) return;
|
|
2540
|
+
sessionStorage.setItem("oke:json-code:auth-replay", stamp);
|
|
2541
|
+
} catch {
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
rememberMethod(method, path);
|
|
2545
|
+
void sendRequest();
|
|
2546
|
+
}
|
|
2547
|
+
|
|
1713
2548
|
ensureSeeds();
|
|
1714
|
-
|
|
2549
|
+
forgetSecretsOnReload();
|
|
2550
|
+
for (const kind of ["cookies", "headers", "headers-global", "query", "path", "body"]) renderKv(kind);
|
|
2551
|
+
paintHeadersMode(headersMode());
|
|
2552
|
+
fillAuth("request");
|
|
2553
|
+
fillAuth("global");
|
|
1715
2554
|
syncBodyMode(bodyMode());
|
|
1716
2555
|
syncPathChapter();
|
|
2556
|
+
replayStoredAuth();
|
|
1717
2557
|
const saved = sessionStorage.getItem(SECTION_KEY) || "";
|
|
1718
2558
|
if (SECTIONS.includes(saved)) openSection(saved);
|
|
1719
2559
|
|
|
@@ -1732,6 +2572,63 @@ pre {
|
|
|
1732
2572
|
syncBodyMode(mode);
|
|
1733
2573
|
});
|
|
1734
2574
|
}
|
|
2575
|
+
for (const el of document.querySelectorAll("[data-auth-reveal]")) {
|
|
2576
|
+
el.addEventListener("click", (e) => {
|
|
2577
|
+
e.preventDefault();
|
|
2578
|
+
e.stopPropagation();
|
|
2579
|
+
const field = el.closest(".auth-field");
|
|
2580
|
+
const input = field ? field.querySelector("input") : null;
|
|
2581
|
+
if (!input) return;
|
|
2582
|
+
const show = input.type === "password";
|
|
2583
|
+
input.type = show ? "text" : "password";
|
|
2584
|
+
el.setAttribute("aria-pressed", show ? "true" : "false");
|
|
2585
|
+
el.setAttribute("aria-label", show ? "Hide secret" : "Show secret");
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
for (const el of document.querySelectorAll("[data-headers-mode]")) {
|
|
2589
|
+
el.addEventListener("click", (e) => {
|
|
2590
|
+
e.preventDefault();
|
|
2591
|
+
e.stopPropagation();
|
|
2592
|
+
const mode = el.getAttribute("data-headers-mode") === "custom" ? "custom" : "inherit";
|
|
2593
|
+
sessionStorage.setItem(HEADERS_MODE_KEY, mode);
|
|
2594
|
+
paintHeadersMode(mode);
|
|
2595
|
+
if (mode === "custom") renderKv("headers");
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
for (const el of document.querySelectorAll("[data-auth-mode]")) {
|
|
2599
|
+
el.addEventListener("click", (e) => {
|
|
2600
|
+
e.preventDefault();
|
|
2601
|
+
e.stopPropagation();
|
|
2602
|
+
const root = el.closest("[data-auth-scope]");
|
|
2603
|
+
if (!root) return;
|
|
2604
|
+
for (const btn of root.querySelectorAll("[data-auth-mode]")) {
|
|
2605
|
+
const on = btn === el;
|
|
2606
|
+
btn.classList.toggle("is-on", on);
|
|
2607
|
+
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
2608
|
+
}
|
|
2609
|
+
const custom = root.querySelector("[data-auth-custom]");
|
|
2610
|
+
if (custom) custom.hidden = el.getAttribute("data-auth-mode") !== "custom";
|
|
2611
|
+
persistAuth(root.getAttribute("data-auth-scope") || "request");
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
for (const el of document.querySelectorAll("[data-auth-type]")) {
|
|
2615
|
+
el.addEventListener("click", (e) => {
|
|
2616
|
+
e.preventDefault();
|
|
2617
|
+
e.stopPropagation();
|
|
2618
|
+
const root = el.closest("[data-auth-scope]");
|
|
2619
|
+
if (!root) return;
|
|
2620
|
+
for (const btn of root.querySelectorAll("[data-auth-type]")) {
|
|
2621
|
+
const on = btn === el;
|
|
2622
|
+
btn.classList.toggle("is-on", on);
|
|
2623
|
+
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
2624
|
+
}
|
|
2625
|
+
const type = el.getAttribute("data-auth-type") || "none";
|
|
2626
|
+
for (const pane of root.querySelectorAll("[data-auth-pane]")) {
|
|
2627
|
+
pane.hidden = pane.getAttribute("data-auth-pane") !== type;
|
|
2628
|
+
}
|
|
2629
|
+
persistAuth(root.getAttribute("data-auth-scope") || "request");
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
1735
2632
|
if (bodyRaw) {
|
|
1736
2633
|
bodyRaw.addEventListener("input", () => {
|
|
1737
2634
|
sessionStorage.setItem(BODY_JSON_KEY, bodyRaw.value);
|
|
@@ -1750,13 +2647,63 @@ pre {
|
|
|
1750
2647
|
const name = section.getAttribute("data-rail-section");
|
|
1751
2648
|
if (!name) return;
|
|
1752
2649
|
sessionStorage.setItem(SECTION_KEY, name);
|
|
1753
|
-
if (name === "
|
|
2650
|
+
if (name === "auth") fillAuth("request");
|
|
2651
|
+
else if (name === "cookies" || name === "headers" || name === "query" || name === "path" || name === "body") {
|
|
1754
2652
|
if (name === "body") syncBodyMode(bodyMode());
|
|
1755
2653
|
else renderKv(name);
|
|
1756
2654
|
}
|
|
1757
2655
|
});
|
|
1758
2656
|
}
|
|
1759
2657
|
|
|
2658
|
+
const globalPop = document.querySelector("[data-slot=json-code-global-panel]");
|
|
2659
|
+
const globalAuthBtn = document.querySelector("[data-slot=json-code-global-auth]");
|
|
2660
|
+
const globalHeadersBtn = document.querySelector("[data-slot=json-code-global-headers]");
|
|
2661
|
+
|
|
2662
|
+
function paintGlobalHeaders() {
|
|
2663
|
+
if (!globalHeadersBtn) return;
|
|
2664
|
+
const n = loadPairs(HEADERS_GLOBAL_KEY).filter((p) => (p.key || "").trim() && String(p.key).toLowerCase() !== "authorization").length;
|
|
2665
|
+
globalHeadersBtn.classList.toggle("is-on", n > 0);
|
|
2666
|
+
globalHeadersBtn.title = n > 0 ? "Global headers · " + n : "Global headers";
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
function closeGlobal() {
|
|
2670
|
+
persistAuth("global");
|
|
2671
|
+
if (globalPop) globalPop.hidden = true;
|
|
2672
|
+
if (globalAuthBtn) globalAuthBtn.setAttribute("aria-expanded", "false");
|
|
2673
|
+
if (globalHeadersBtn) globalHeadersBtn.setAttribute("aria-expanded", "false");
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
function openGlobal(pane) {
|
|
2677
|
+
if (!globalPop) return;
|
|
2678
|
+
const same = !globalPop.hidden && globalPop.getAttribute("data-pane") === pane;
|
|
2679
|
+
if (same) {
|
|
2680
|
+
closeGlobal();
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
globalPop.hidden = false;
|
|
2684
|
+
globalPop.setAttribute("data-pane", pane);
|
|
2685
|
+
const authPane = globalPop.querySelector('[data-global-pane="auth"]');
|
|
2686
|
+
const headersPane = globalPop.querySelector('[data-global-pane="headers"]');
|
|
2687
|
+
if (authPane) authPane.hidden = pane !== "auth";
|
|
2688
|
+
if (headersPane) headersPane.hidden = pane !== "headers";
|
|
2689
|
+
if (globalAuthBtn) globalAuthBtn.setAttribute("aria-expanded", pane === "auth" ? "true" : "false");
|
|
2690
|
+
if (globalHeadersBtn) globalHeadersBtn.setAttribute("aria-expanded", pane === "headers" ? "true" : "false");
|
|
2691
|
+
if (pane === "headers") renderKv("headers-global");
|
|
2692
|
+
else fillAuth("global");
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
paintGlobalHeaders();
|
|
2696
|
+
if (globalAuthBtn) globalAuthBtn.addEventListener("click", (e) => {
|
|
2697
|
+
e.preventDefault();
|
|
2698
|
+
e.stopPropagation();
|
|
2699
|
+
openGlobal("auth");
|
|
2700
|
+
});
|
|
2701
|
+
if (globalHeadersBtn) globalHeadersBtn.addEventListener("click", (e) => {
|
|
2702
|
+
e.preventDefault();
|
|
2703
|
+
e.stopPropagation();
|
|
2704
|
+
openGlobal("headers");
|
|
2705
|
+
});
|
|
2706
|
+
|
|
1760
2707
|
document.addEventListener("keydown", (e) => {
|
|
1761
2708
|
if (e.key !== "Enter") return;
|
|
1762
2709
|
const t = e.target;
|
|
@@ -1773,8 +2720,7 @@ pre {
|
|
|
1773
2720
|
leaf.classList.add("is-on");
|
|
1774
2721
|
const method = (leaf.getAttribute("data-method") || currentMethod()).toUpperCase();
|
|
1775
2722
|
const path = leaf.getAttribute("data-path") || leaf.getAttribute("data-param-template") || currentPath();
|
|
1776
|
-
|
|
1777
|
-
if (file) file.textContent = method + " " + path;
|
|
2723
|
+
paintRequestLine(method, path);
|
|
1778
2724
|
const title = document.querySelector("title");
|
|
1779
2725
|
if (title) {
|
|
1780
2726
|
const app = page.querySelector(".title")?.textContent || "";
|
|
@@ -1782,10 +2728,43 @@ pre {
|
|
|
1782
2728
|
}
|
|
1783
2729
|
}
|
|
1784
2730
|
|
|
2731
|
+
document.addEventListener("input", (e) => {
|
|
2732
|
+
const t = e.target;
|
|
2733
|
+
if (!(t instanceof HTMLInputElement)) return;
|
|
2734
|
+
const authRoot = t.closest("[data-auth-scope]");
|
|
2735
|
+
if (authRoot) {
|
|
2736
|
+
persistAuth(authRoot.getAttribute("data-auth-scope") || "request");
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
if (t.closest('[data-kv="headers-global"]')) {
|
|
2740
|
+
savePairs(
|
|
2741
|
+
HEADERS_GLOBAL_KEY,
|
|
2742
|
+
collectKv("headers-global").filter((p) => p.key.toLowerCase() !== "authorization"),
|
|
2743
|
+
);
|
|
2744
|
+
paintGlobalHeaders();
|
|
2745
|
+
}
|
|
2746
|
+
});
|
|
2747
|
+
|
|
1785
2748
|
document.addEventListener("click", (e) => {
|
|
1786
2749
|
const t = e.target;
|
|
1787
2750
|
const el = t instanceof Element ? t : t && t.parentElement;
|
|
1788
2751
|
if (!el) return;
|
|
2752
|
+
const authIn = el.closest("[data-auth-in]");
|
|
2753
|
+
if (authIn) {
|
|
2754
|
+
e.preventDefault();
|
|
2755
|
+
const root = authIn.closest("[data-auth-scope]");
|
|
2756
|
+
if (!root) return;
|
|
2757
|
+
for (const btn of root.querySelectorAll("[data-auth-in]")) {
|
|
2758
|
+
const on = btn === authIn;
|
|
2759
|
+
btn.classList.toggle("is-on", on);
|
|
2760
|
+
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
2761
|
+
}
|
|
2762
|
+
persistAuth(root.getAttribute("data-auth-scope") || "request");
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
if (globalPop && !globalPop.hidden && !el.closest("[data-slot=json-code-global-panel]") && !el.closest("[data-slot=json-code-global-auth]") && !el.closest("[data-slot=json-code-global-headers]")) {
|
|
2766
|
+
closeGlobal();
|
|
2767
|
+
}
|
|
1789
2768
|
const paramLeaf = el.closest("button.leaf[data-param-template]");
|
|
1790
2769
|
if (paramLeaf) {
|
|
1791
2770
|
e.preventDefault();
|
|
@@ -1906,6 +2885,7 @@ export async function asBrowserJsonCodeBlock(
|
|
|
1906
2885
|
latencyMs,
|
|
1907
2886
|
cache,
|
|
1908
2887
|
auth,
|
|
2888
|
+
headers: jsonCodeResponseHeaders(response.headers),
|
|
1909
2889
|
});
|
|
1910
2890
|
const headers = new Headers(response.headers);
|
|
1911
2891
|
headers.set("content-type", "text/html; charset=utf-8");
|
|
@@ -1916,11 +2896,121 @@ export async function asBrowserJsonCodeBlock(
|
|
|
1916
2896
|
return new Response(html, { status: response.status, headers });
|
|
1917
2897
|
}
|
|
1918
2898
|
|
|
2899
|
+
/** Stroke icon, 14px, currentColor. */
|
|
2900
|
+
function glyph(body: string, size = 14): string {
|
|
2901
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" aria-hidden="true">${body}</svg>`;
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
const GLYPH = {
|
|
2905
|
+
clock: glyph(
|
|
2906
|
+
`<circle cx="12" cy="12" r="7.5" stroke="currentColor" stroke-width="1.5"/><path d="M12 8v4.5l2.5 1.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>`,
|
|
2907
|
+
),
|
|
2908
|
+
dot: glyph(`<circle cx="12" cy="12" r="3.25" fill="currentColor"/>`, 12),
|
|
2909
|
+
eye: glyph(
|
|
2910
|
+
`<path d="M2.5 12S6 6.5 12 6.5 21.5 12 21.5 12 18 17.5 12 17.5 2.5 12 2.5 12Z" stroke="currentColor" stroke-width="1.5"/><circle cx="12" cy="12" r="2.25" stroke="currentColor" stroke-width="1.5"/>`,
|
|
2911
|
+
),
|
|
2912
|
+
plus: glyph(
|
|
2913
|
+
`<path d="M12 5.5v13M5.5 12h13" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>`,
|
|
2914
|
+
),
|
|
2915
|
+
pencil: glyph(
|
|
2916
|
+
`<path d="M4 16.5V20h3.5L19 8.5 15.5 5 4 16.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>`,
|
|
2917
|
+
),
|
|
2918
|
+
trash: glyph(
|
|
2919
|
+
`<path d="M5 7.5h14M9.5 7.5V5.5h5v2M8 7.5l.7 12h6.6L16 7.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
2920
|
+
),
|
|
2921
|
+
file: glyph(
|
|
2922
|
+
`<path d="M7 3.5h7.2L19 8.2V20a.5.5 0 0 1-.5.5h-11A.5.5 0 0 1 7 20V3.5Z" stroke="currentColor" stroke-width="1.5"/><path d="M14 3.5V8h5" stroke="currentColor" stroke-width="1.5"/>`,
|
|
2923
|
+
),
|
|
2924
|
+
sliders: glyph(
|
|
2925
|
+
`<path d="M4 8h16M4 16h16M9 6v4M16 14v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>`,
|
|
2926
|
+
),
|
|
2927
|
+
search: glyph(
|
|
2928
|
+
`<circle cx="11" cy="11" r="5.5" stroke="currentColor" stroke-width="1.5"/><path d="M15.5 15.5 19.5 19.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>`,
|
|
2929
|
+
),
|
|
2930
|
+
braces: glyph(
|
|
2931
|
+
`<path d="M9 5.5c-1.6 0-2.5 1-2.5 2.6v2.2c0 1-.5 1.5-1.7 1.7 1.2.2 1.7.7 1.7 1.7v2.2c0 1.6.9 2.6 2.5 2.6M15 5.5c1.6 0 2.5 1 2.5 2.6v2.2c0 1 .5 1.5 1.7 1.7-1.2.2-1.7.7-1.7 1.7v2.2c0 1.6-.9 2.6-2.5 2.6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>`,
|
|
2932
|
+
),
|
|
2933
|
+
cookie: glyph(
|
|
2934
|
+
`<circle cx="12" cy="12" r="7.5" stroke="currentColor" stroke-width="1.5"/><circle cx="9" cy="10" r=".9" fill="currentColor"/><circle cx="14" cy="9.5" r=".9" fill="currentColor"/><circle cx="13" cy="14" r=".9" fill="currentColor"/>`,
|
|
2935
|
+
),
|
|
2936
|
+
list: glyph(
|
|
2937
|
+
`<path d="M9 7h10M9 12h10M9 17h10M5 7h.01M5 12h.01M5 17h.01" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>`,
|
|
2938
|
+
),
|
|
2939
|
+
key: glyph(
|
|
2940
|
+
`<circle cx="8" cy="14" r="3.25" stroke="currentColor" stroke-width="1.5"/><path d="M11 12.2 19 4.5M16.2 4.5H19V7.2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
2941
|
+
),
|
|
2942
|
+
route: glyph(
|
|
2943
|
+
`<path d="M5 19V7.5A2 2 0 0 1 7 5.5h5M12 5.5 15.5 9 12 12.5M15 12h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
2944
|
+
),
|
|
2945
|
+
} as const;
|
|
2946
|
+
|
|
2947
|
+
/** Accent class for an HTTP method. Matches Console traces. */
|
|
2948
|
+
function methodTone(method: string): string {
|
|
2949
|
+
switch (method.toUpperCase()) {
|
|
2950
|
+
case "GET":
|
|
2951
|
+
return "get";
|
|
2952
|
+
case "QUERY":
|
|
2953
|
+
return "query";
|
|
2954
|
+
case "POST":
|
|
2955
|
+
return "post";
|
|
2956
|
+
case "PUT":
|
|
2957
|
+
return "put";
|
|
2958
|
+
case "PATCH":
|
|
2959
|
+
return "patch";
|
|
2960
|
+
case "DELETE":
|
|
2961
|
+
return "delete";
|
|
2962
|
+
case "HEAD":
|
|
2963
|
+
return "head";
|
|
2964
|
+
case "OPTIONS":
|
|
2965
|
+
return "options";
|
|
2966
|
+
default:
|
|
2967
|
+
return "other";
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
/** Glyph for an HTTP method. */
|
|
2972
|
+
function methodGlyph(method: string): string {
|
|
2973
|
+
switch (method.toUpperCase()) {
|
|
2974
|
+
case "GET":
|
|
2975
|
+
return GLYPH.eye;
|
|
2976
|
+
case "QUERY":
|
|
2977
|
+
return GLYPH.search;
|
|
2978
|
+
case "POST":
|
|
2979
|
+
return GLYPH.plus;
|
|
2980
|
+
case "PUT":
|
|
2981
|
+
case "PATCH":
|
|
2982
|
+
return GLYPH.pencil;
|
|
2983
|
+
case "DELETE":
|
|
2984
|
+
return GLYPH.trash;
|
|
2985
|
+
case "OPTIONS":
|
|
2986
|
+
return GLYPH.sliders;
|
|
2987
|
+
default:
|
|
2988
|
+
return GLYPH.file;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
const SECTION_GLYPH: Readonly<Record<string, string>> = {
|
|
2993
|
+
query: GLYPH.sliders,
|
|
2994
|
+
body: GLYPH.braces,
|
|
2995
|
+
cookies: GLYPH.cookie,
|
|
2996
|
+
headers: GLYPH.list,
|
|
2997
|
+
auth: GLYPH.key,
|
|
2998
|
+
path: GLYPH.route,
|
|
2999
|
+
};
|
|
3000
|
+
|
|
3001
|
+
/** Status accent: ok, redirect, client error, server error. */
|
|
3002
|
+
function statusTone(status: number): "ok" | "info" | "warn" | "err" {
|
|
3003
|
+
if (status >= 500) return "err";
|
|
3004
|
+
if (status >= 400) return "warn";
|
|
3005
|
+
if (status >= 300) return "info";
|
|
3006
|
+
return "ok";
|
|
3007
|
+
}
|
|
3008
|
+
|
|
1919
3009
|
function latencyHtml(ms: number | undefined): string {
|
|
1920
3010
|
if (ms === undefined) return "";
|
|
1921
3011
|
const tone = jsonCodeLatencyTone(ms);
|
|
1922
3012
|
const label = formatJsonCodeLatency(ms);
|
|
1923
|
-
return `<span class="count lat-${tone}" data-slot="json-code-latency" data-tone="${tone}" title="Latency">${escapeHtml(label)}</span>`;
|
|
3013
|
+
return `<span class="count lat-${tone}" data-slot="json-code-latency" data-tone="${tone}" title="Latency">${GLYPH.clock}${escapeHtml(label)}</span>`;
|
|
1924
3014
|
}
|
|
1925
3015
|
|
|
1926
3016
|
const CACHE_MARK: Readonly<
|
|
@@ -1992,13 +3082,57 @@ function authHtml(auth: JsonCodeAuth | undefined): string {
|
|
|
1992
3082
|
return `<span class="count auth auth-${value.kind}" data-slot="json-code-auth" data-auth="${value.kind}" title="${escapeHtml(title)}">${mark.icon}<span class="auth-label">${escapeHtml(label)}</span></span>`;
|
|
1993
3083
|
}
|
|
1994
3084
|
|
|
3085
|
+
function authSwitchHtml(): string {
|
|
3086
|
+
const opt = (type: string, text: string) =>
|
|
3087
|
+
`<button type="button" class="token${type === "none" ? " is-on" : ""}" data-auth-type="${type}" aria-pressed="${type === "none" ? "true" : "false"}">${text}</button>`;
|
|
3088
|
+
return `<span class="body-mode-strip" role="group" aria-label="Authorization">${opt("none", "No")}${opt("bearer", "Bearer")}${opt("basic", "Basic")}${opt("apikey", "API")}</span>`;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
const AUTH_EYE = `<svg class="eye-open" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M2.5 12S6 6.5 12 6.5 21.5 12 21.5 12 18 17.5 12 17.5 2.5 12 2.5 12Z" stroke="currentColor" stroke-width="1.5"/><circle cx="12" cy="12" r="2.5" stroke="currentColor" stroke-width="1.5"/></svg><svg class="eye-shut" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M3 4.5 21 19.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M9.2 6.9A11 11 0 0 1 12 6.5c6 0 9.5 5.5 9.5 5.5a17 17 0 0 1-3.4 3.7M6.1 8.2C3.9 9.7 2.5 12 2.5 12S6 17.5 12 17.5c1.1 0 2.1-.2 3.1-.7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>`;
|
|
3092
|
+
|
|
3093
|
+
/** Masked credential input. The value stays for this tab; a refresh clears it. */
|
|
3094
|
+
function secretInput(name: string, placeholder: string): string {
|
|
3095
|
+
return `<input class="kv-val" type="password" data-auth-field="${name}" spellcheck="false" autocomplete="off" placeholder="${placeholder}" /><button type="button" class="auth-reveal" data-auth-reveal aria-pressed="false" aria-label="Show secret">${AUTH_EYE}</button>`;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
function authEditorHtml(scope: "request" | "global"): string {
|
|
3099
|
+
const none =
|
|
3100
|
+
scope === "global"
|
|
3101
|
+
? "No global credentials. Each request uses its own Authorization."
|
|
3102
|
+
: "No credentials on this request.";
|
|
3103
|
+
const switchRow =
|
|
3104
|
+
scope === "global" ? authSwitchHtml() : `<div class="strip">${authSwitchHtml()}</div>`;
|
|
3105
|
+
return `<div class="auth-editor" data-slot="json-code-auth-editor">
|
|
3106
|
+
${switchRow}
|
|
3107
|
+
<p class="kv-empty" data-auth-pane="none">${none}</p>
|
|
3108
|
+
<div class="auth-pane" data-auth-pane="bearer" hidden>
|
|
3109
|
+
<label class="auth-field"><span class="auth-lab">Token</span>${secretInput("token", "Token")}</label>
|
|
3110
|
+
</div>
|
|
3111
|
+
<div class="auth-pane" data-auth-pane="basic" hidden>
|
|
3112
|
+
<label class="auth-field"><span class="auth-lab">Username</span><input class="kv-val" data-auth-field="username" spellcheck="false" autocomplete="off" placeholder="Username" /></label>
|
|
3113
|
+
<label class="auth-field"><span class="auth-lab">Password</span>${secretInput("password", "Password")}</label>
|
|
3114
|
+
</div>
|
|
3115
|
+
<div class="auth-pane" data-auth-pane="apikey" hidden>
|
|
3116
|
+
<label class="auth-field"><span class="auth-lab">Key</span><input class="kv-val" data-auth-field="key" spellcheck="false" autocomplete="off" placeholder="X-Api-Key" /></label>
|
|
3117
|
+
<label class="auth-field"><span class="auth-lab">Value</span>${secretInput("value", "Value")}</label>
|
|
3118
|
+
<div class="auth-field">
|
|
3119
|
+
<span class="auth-lab">Add to</span>
|
|
3120
|
+
<span class="auth-in" role="group" aria-label="API key location">
|
|
3121
|
+
<button type="button" class="token is-on" data-auth-in="header" aria-pressed="true">Header</button>
|
|
3122
|
+
<button type="button" class="token" data-auth-in="query" aria-pressed="false">Query</button>
|
|
3123
|
+
</span>
|
|
3124
|
+
</div>
|
|
3125
|
+
</div>
|
|
3126
|
+
</div>`;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
1995
3129
|
function navHtml(nav: readonly JsonCodeNavGroup[] | undefined): string {
|
|
1996
3130
|
const groups = (nav ?? []).map(navGroupHtml).join("");
|
|
1997
3131
|
const chev = `<span class="chev" aria-hidden="true"><svg width="12" height="12" viewBox="0 0 24 24" fill="none"><path d="M6 9.5 12 15.5 18 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>`;
|
|
1998
3132
|
const play = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M8 5.5v13l11-6.5L8 5.5Z" fill="currentColor"/></svg>`;
|
|
1999
3133
|
const kvSection = (kind: string, label: string) =>
|
|
2000
3134
|
`<details class="rail-acc" data-rail-section="${kind}">
|
|
2001
|
-
<summary class="rail-acc-sum">${chev}<span>${label}</span></summary>
|
|
3135
|
+
<summary class="rail-acc-sum">${chev}<span class="sec-ico">${SECTION_GLYPH[kind] ?? ""}</span><span>${label}</span></summary>
|
|
2002
3136
|
<div class="rail-acc-body">
|
|
2003
3137
|
<div class="kv-editor">
|
|
2004
3138
|
<div class="kv-rows" data-slot="json-code-kv-rows" data-kv="${kind}"></div>
|
|
@@ -2013,10 +3147,6 @@ function navHtml(nav: readonly JsonCodeNavGroup[] | undefined): string {
|
|
|
2013
3147
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M9 5.5 15.5 12 9 18.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
2014
3148
|
</label>
|
|
2015
3149
|
</header>
|
|
2016
|
-
<div class="rail-nav" data-rail-section="routes">
|
|
2017
|
-
<p class="rail-sec-label">Routes</p>
|
|
2018
|
-
<ul class="rail-list">${groups}</ul>
|
|
2019
|
-
</div>
|
|
2020
3150
|
<div class="rail-dock" data-slot="json-code-request-dock">
|
|
2021
3151
|
<div class="strip rail-dock-strip">
|
|
2022
3152
|
<span class="head">${play}<span>Request</span></span>
|
|
@@ -2027,9 +3157,10 @@ function navHtml(nav: readonly JsonCodeNavGroup[] | undefined): string {
|
|
|
2027
3157
|
</div>
|
|
2028
3158
|
<div class="rail-dock-body">
|
|
2029
3159
|
${kvSection("query", "Params")}
|
|
3160
|
+
${kvSection("path", "Path")}
|
|
2030
3161
|
<details class="rail-acc" data-rail-section="body">
|
|
2031
3162
|
<summary class="rail-acc-sum">
|
|
2032
|
-
${chev}<span>Body</span>
|
|
3163
|
+
${chev}<span class="sec-ico">${SECTION_GLYPH.body}</span><span>Body</span>
|
|
2033
3164
|
<span class="grow"></span>
|
|
2034
3165
|
<span class="body-mode-strip" role="group" aria-label="Body format">
|
|
2035
3166
|
<button type="button" class="token is-on" data-slot="json-code-body-mode" data-mode="form" aria-pressed="true">Form</button>
|
|
@@ -2049,11 +3180,43 @@ function navHtml(nav: readonly JsonCodeNavGroup[] | undefined): string {
|
|
|
2049
3180
|
</div>
|
|
2050
3181
|
</div>
|
|
2051
3182
|
</details>
|
|
3183
|
+
<details class="rail-acc" data-rail-section="headers">
|
|
3184
|
+
<summary class="rail-acc-sum">
|
|
3185
|
+
${chev}<span class="sec-ico">${SECTION_GLYPH.headers}</span><span>Headers</span>
|
|
3186
|
+
<span class="grow"></span>
|
|
3187
|
+
<span class="body-mode-strip" role="group" aria-label="Request headers">
|
|
3188
|
+
<button type="button" class="token is-on" data-headers-mode="inherit" aria-pressed="true">Inherit</button>
|
|
3189
|
+
<button type="button" class="token" data-headers-mode="custom" aria-pressed="false">Custom</button>
|
|
3190
|
+
</span>
|
|
3191
|
+
</summary>
|
|
3192
|
+
<div class="rail-acc-body" data-headers-custom hidden>
|
|
3193
|
+
<div class="kv-editor">
|
|
3194
|
+
<div class="kv-rows" data-slot="json-code-kv-rows" data-kv="headers"></div>
|
|
3195
|
+
</div>
|
|
3196
|
+
</div>
|
|
3197
|
+
</details>
|
|
3198
|
+
<details class="rail-acc" data-rail-section="auth" data-auth-scope="request">
|
|
3199
|
+
<summary class="rail-acc-sum">${chev}<span class="sec-ico">${SECTION_GLYPH.auth}</span><span>Auth</span><span class="grow"></span><span class="body-mode-strip" role="group" aria-label="Request auth"><button type="button" class="token is-on" data-auth-mode="inherit" aria-pressed="true">Inherit</button><button type="button" class="token" data-auth-mode="custom" aria-pressed="false">Custom</button></span></summary>
|
|
3200
|
+
<div class="rail-acc-body" data-auth-custom hidden>
|
|
3201
|
+
${authEditorHtml("request")}
|
|
3202
|
+
</div>
|
|
3203
|
+
</details>
|
|
2052
3204
|
${kvSection("cookies", "Cookies")}
|
|
2053
|
-
${kvSection("headers", "Headers")}
|
|
2054
|
-
${kvSection("path", "Path")}
|
|
2055
3205
|
</div>
|
|
2056
3206
|
</div>
|
|
3207
|
+
<div class="rail-nav" data-rail-section="routes">
|
|
3208
|
+
<input class="routes-check" type="checkbox" id="json-code-routes" checked>
|
|
3209
|
+
<p class="rail-sec-label">
|
|
3210
|
+
<span>Routes</span>
|
|
3211
|
+
<label class="copy routes-collapse" for="json-code-routes" aria-label="Collapse routes" title="Collapse routes">
|
|
3212
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9.5 12 15.5 18 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
3213
|
+
</label>
|
|
3214
|
+
<label class="copy routes-expand" for="json-code-routes" aria-label="Expand routes" title="Expand routes">
|
|
3215
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 14.5 12 8.5 18 14.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
3216
|
+
</label>
|
|
3217
|
+
</p>
|
|
3218
|
+
<ul class="rail-list">${groups}</ul>
|
|
3219
|
+
</div>
|
|
2057
3220
|
</aside>
|
|
2058
3221
|
<label class="rail-thin" for="json-code-rail" aria-label="Expand request" title="Expand request">
|
|
2059
3222
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M15 5.5 8.5 12 15 18.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|