vector-framework 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/auth/protected.d.ts +4 -4
- package/dist/auth/protected.d.ts.map +1 -1
- package/dist/auth/protected.js +10 -7
- package/dist/auth/protected.js.map +1 -1
- package/dist/cache/manager.d.ts +2 -0
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +21 -4
- package/dist/cache/manager.js.map +1 -1
- package/dist/checkpoint/artifacts/compressor.d.ts +5 -0
- package/dist/checkpoint/artifacts/compressor.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/compressor.js +24 -0
- package/dist/checkpoint/artifacts/compressor.js.map +1 -0
- package/dist/checkpoint/artifacts/decompress-worker.d.ts +2 -0
- package/dist/checkpoint/artifacts/decompress-worker.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/decompress-worker.js +31 -0
- package/dist/checkpoint/artifacts/decompress-worker.js.map +1 -0
- package/dist/checkpoint/artifacts/hasher.d.ts +2 -0
- package/dist/checkpoint/artifacts/hasher.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/hasher.js +7 -0
- package/dist/checkpoint/artifacts/hasher.js.map +1 -0
- package/dist/checkpoint/artifacts/manifest.d.ts +6 -0
- package/dist/checkpoint/artifacts/manifest.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/manifest.js +55 -0
- package/dist/checkpoint/artifacts/manifest.js.map +1 -0
- package/dist/checkpoint/artifacts/materializer.d.ts +16 -0
- package/dist/checkpoint/artifacts/materializer.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/materializer.js +168 -0
- package/dist/checkpoint/artifacts/materializer.js.map +1 -0
- package/dist/checkpoint/artifacts/packager.d.ts +12 -0
- package/dist/checkpoint/artifacts/packager.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/packager.js +82 -0
- package/dist/checkpoint/artifacts/packager.js.map +1 -0
- package/dist/checkpoint/artifacts/repository.d.ts +11 -0
- package/dist/checkpoint/artifacts/repository.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/repository.js +29 -0
- package/dist/checkpoint/artifacts/repository.js.map +1 -0
- package/dist/checkpoint/artifacts/store.d.ts +13 -0
- package/dist/checkpoint/artifacts/store.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/store.js +85 -0
- package/dist/checkpoint/artifacts/store.js.map +1 -0
- package/dist/checkpoint/artifacts/types.d.ts +21 -0
- package/dist/checkpoint/artifacts/types.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/types.js +2 -0
- package/dist/checkpoint/artifacts/types.js.map +1 -0
- package/dist/checkpoint/artifacts/worker-decompressor.d.ts +17 -0
- package/dist/checkpoint/artifacts/worker-decompressor.d.ts.map +1 -0
- package/dist/checkpoint/artifacts/worker-decompressor.js +148 -0
- package/dist/checkpoint/artifacts/worker-decompressor.js.map +1 -0
- package/dist/checkpoint/asset-store.d.ts +10 -0
- package/dist/checkpoint/asset-store.d.ts.map +1 -0
- package/dist/checkpoint/asset-store.js +46 -0
- package/dist/checkpoint/asset-store.js.map +1 -0
- package/dist/checkpoint/bundler.d.ts +15 -0
- package/dist/checkpoint/bundler.d.ts.map +1 -0
- package/dist/checkpoint/bundler.js +45 -0
- package/dist/checkpoint/bundler.js.map +1 -0
- package/dist/checkpoint/cli.d.ts +2 -0
- package/dist/checkpoint/cli.d.ts.map +1 -0
- package/dist/checkpoint/cli.js +157 -0
- package/dist/checkpoint/cli.js.map +1 -0
- package/dist/checkpoint/entrypoint-generator.d.ts +17 -0
- package/dist/checkpoint/entrypoint-generator.d.ts.map +1 -0
- package/dist/checkpoint/entrypoint-generator.js +251 -0
- package/dist/checkpoint/entrypoint-generator.js.map +1 -0
- package/dist/checkpoint/forwarder.d.ts +6 -0
- package/dist/checkpoint/forwarder.d.ts.map +1 -0
- package/dist/checkpoint/forwarder.js +74 -0
- package/dist/checkpoint/forwarder.js.map +1 -0
- package/dist/checkpoint/gateway.d.ts +11 -0
- package/dist/checkpoint/gateway.d.ts.map +1 -0
- package/dist/checkpoint/gateway.js +30 -0
- package/dist/checkpoint/gateway.js.map +1 -0
- package/dist/checkpoint/ipc.d.ts +12 -0
- package/dist/checkpoint/ipc.d.ts.map +1 -0
- package/dist/checkpoint/ipc.js +96 -0
- package/dist/checkpoint/ipc.js.map +1 -0
- package/dist/checkpoint/manager.d.ts +20 -0
- package/dist/checkpoint/manager.d.ts.map +1 -0
- package/dist/checkpoint/manager.js +214 -0
- package/dist/checkpoint/manager.js.map +1 -0
- package/dist/checkpoint/process-manager.d.ts +35 -0
- package/dist/checkpoint/process-manager.d.ts.map +1 -0
- package/dist/checkpoint/process-manager.js +203 -0
- package/dist/checkpoint/process-manager.js.map +1 -0
- package/dist/checkpoint/resolver.d.ts +25 -0
- package/dist/checkpoint/resolver.d.ts.map +1 -0
- package/dist/checkpoint/resolver.js +95 -0
- package/dist/checkpoint/resolver.js.map +1 -0
- package/dist/checkpoint/socket-path.d.ts +2 -0
- package/dist/checkpoint/socket-path.d.ts.map +1 -0
- package/dist/checkpoint/socket-path.js +51 -0
- package/dist/checkpoint/socket-path.js.map +1 -0
- package/dist/checkpoint/types.d.ts +54 -0
- package/dist/checkpoint/types.d.ts.map +1 -0
- package/dist/checkpoint/types.js +2 -0
- package/dist/checkpoint/types.js.map +1 -0
- package/dist/cli/index.js +10 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/option-resolution.d.ts +1 -1
- package/dist/cli/option-resolution.d.ts.map +1 -1
- package/dist/cli/option-resolution.js.map +1 -1
- package/dist/cli.js +3817 -350
- package/dist/core/config-loader.d.ts +1 -0
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +10 -2
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/router.d.ts +24 -3
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +398 -249
- package/dist/core/router.js.map +1 -1
- package/dist/core/server.d.ts +3 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +35 -10
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +3 -0
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +51 -1
- package/dist/core/vector.js.map +1 -1
- package/dist/dev/route-scanner.d.ts.map +1 -1
- package/dist/dev/route-scanner.js +2 -1
- package/dist/dev/route-scanner.js.map +1 -1
- package/dist/errors/index.cjs +2 -0
- package/dist/http.d.ts +32 -7
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +144 -13
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +2657 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1433
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1301 -77
- package/dist/middleware/manager.d.ts +3 -3
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +9 -8
- package/dist/middleware/manager.js.map +1 -1
- package/dist/openapi/docs-ui.d.ts.map +1 -1
- package/dist/openapi/docs-ui.js +1097 -61
- package/dist/openapi/docs-ui.js.map +1 -1
- package/dist/openapi/generator.d.ts +2 -1
- package/dist/openapi/generator.d.ts.map +1 -1
- package/dist/openapi/generator.js +332 -16
- package/dist/openapi/generator.js.map +1 -1
- package/dist/types/index.d.ts +71 -28
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +24 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +3 -2
- package/dist/utils/validation.js.map +1 -1
- package/package.json +9 -14
- package/src/auth/protected.ts +11 -8
- package/src/cache/manager.ts +23 -4
- package/src/checkpoint/artifacts/compressor.ts +30 -0
- package/src/checkpoint/artifacts/decompress-worker.ts +49 -0
- package/src/checkpoint/artifacts/hasher.ts +6 -0
- package/src/checkpoint/artifacts/manifest.ts +72 -0
- package/src/checkpoint/artifacts/materializer.ts +211 -0
- package/src/checkpoint/artifacts/packager.ts +100 -0
- package/src/checkpoint/artifacts/repository.ts +36 -0
- package/src/checkpoint/artifacts/store.ts +102 -0
- package/src/checkpoint/artifacts/types.ts +24 -0
- package/src/checkpoint/artifacts/worker-decompressor.ts +192 -0
- package/src/checkpoint/asset-store.ts +61 -0
- package/src/checkpoint/bundler.ts +64 -0
- package/src/checkpoint/cli.ts +177 -0
- package/src/checkpoint/entrypoint-generator.ts +275 -0
- package/src/checkpoint/forwarder.ts +84 -0
- package/src/checkpoint/gateway.ts +40 -0
- package/src/checkpoint/ipc.ts +107 -0
- package/src/checkpoint/manager.ts +254 -0
- package/src/checkpoint/process-manager.ts +250 -0
- package/src/checkpoint/resolver.ts +124 -0
- package/src/checkpoint/socket-path.ts +61 -0
- package/src/checkpoint/types.ts +63 -0
- package/src/cli/index.ts +11 -2
- package/src/cli/option-resolution.ts +5 -1
- package/src/core/config-loader.ts +11 -2
- package/src/core/router.ts +505 -264
- package/src/core/server.ts +51 -11
- package/src/core/vector.ts +60 -1
- package/src/dev/route-scanner.ts +2 -1
- package/src/http.ts +219 -19
- package/src/index.ts +3 -2
- package/src/middleware/manager.ts +10 -10
- package/src/openapi/docs-ui.ts +1097 -61
- package/src/openapi/generator.ts +380 -13
- package/src/types/index.ts +83 -30
- package/src/utils/validation.ts +5 -3
package/src/openapi/docs-ui.ts
CHANGED
|
@@ -144,6 +144,124 @@ export function renderOpenAPIDocsHtml(
|
|
|
144
144
|
.dark .json-number { color: #7dc9ff; }
|
|
145
145
|
.dark .json-boolean { color: #93a4bf; }
|
|
146
146
|
.dark .json-null { color: #7c8ba3; }
|
|
147
|
+
.param-row {
|
|
148
|
+
--param-row-bg-rgb: 255 255 255;
|
|
149
|
+
}
|
|
150
|
+
.dark .param-row {
|
|
151
|
+
--param-row-bg-rgb: 10 10 10;
|
|
152
|
+
}
|
|
153
|
+
.param-row-head {
|
|
154
|
+
display: grid;
|
|
155
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: 0.5rem;
|
|
158
|
+
min-width: 0;
|
|
159
|
+
width: 100%;
|
|
160
|
+
}
|
|
161
|
+
.param-row-main {
|
|
162
|
+
min-width: 0;
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: 0.4rem;
|
|
166
|
+
overflow: hidden;
|
|
167
|
+
}
|
|
168
|
+
.param-tooltip-trigger {
|
|
169
|
+
border: 0;
|
|
170
|
+
margin: 0;
|
|
171
|
+
padding: 0;
|
|
172
|
+
background: transparent;
|
|
173
|
+
color: inherit;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
font: inherit;
|
|
176
|
+
text-align: left;
|
|
177
|
+
min-width: 0;
|
|
178
|
+
}
|
|
179
|
+
.param-tooltip-trigger:focus-visible {
|
|
180
|
+
outline: 2px solid rgba(0, 161, 255, 0.65);
|
|
181
|
+
outline-offset: 2px;
|
|
182
|
+
border-radius: 0.375rem;
|
|
183
|
+
}
|
|
184
|
+
.param-name-trigger {
|
|
185
|
+
min-width: 0;
|
|
186
|
+
flex: 1 1 auto;
|
|
187
|
+
overflow: hidden;
|
|
188
|
+
}
|
|
189
|
+
.param-name-text {
|
|
190
|
+
display: block;
|
|
191
|
+
max-width: 100%;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
text-overflow: ellipsis;
|
|
194
|
+
white-space: nowrap;
|
|
195
|
+
mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 18px), transparent 100%);
|
|
196
|
+
-webkit-mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 18px), transparent 100%);
|
|
197
|
+
}
|
|
198
|
+
.param-type-fade {
|
|
199
|
+
position: relative;
|
|
200
|
+
z-index: 1;
|
|
201
|
+
display: block;
|
|
202
|
+
max-width: none;
|
|
203
|
+
overflow: visible;
|
|
204
|
+
text-overflow: clip;
|
|
205
|
+
padding-left: 1.25rem;
|
|
206
|
+
white-space: nowrap;
|
|
207
|
+
text-align: left;
|
|
208
|
+
justify-self: end;
|
|
209
|
+
background: linear-gradient(
|
|
210
|
+
90deg,
|
|
211
|
+
rgba(var(--param-row-bg-rgb), 0) 0%,
|
|
212
|
+
rgba(var(--param-row-bg-rgb), 0.76) 36%,
|
|
213
|
+
rgba(var(--param-row-bg-rgb), 0.94) 68%,
|
|
214
|
+
rgba(var(--param-row-bg-rgb), 1) 100%
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
#param-value-tooltip {
|
|
218
|
+
position: fixed;
|
|
219
|
+
top: 0;
|
|
220
|
+
left: 0;
|
|
221
|
+
z-index: 70;
|
|
222
|
+
width: min(42rem, calc(100vw - 0.75rem));
|
|
223
|
+
border-radius: 0.5rem;
|
|
224
|
+
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
225
|
+
background: rgba(255, 255, 255, 0.92);
|
|
226
|
+
color: #111111;
|
|
227
|
+
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.14);
|
|
228
|
+
backdrop-filter: blur(12px) saturate(145%);
|
|
229
|
+
-webkit-backdrop-filter: blur(12px) saturate(145%);
|
|
230
|
+
padding: 0.4rem 0.6rem;
|
|
231
|
+
opacity: 0;
|
|
232
|
+
pointer-events: none;
|
|
233
|
+
transform: translateY(6px) scale(0.98);
|
|
234
|
+
transition:
|
|
235
|
+
opacity var(--motion-fast) var(--motion-ease),
|
|
236
|
+
transform var(--motion-fast) var(--motion-ease);
|
|
237
|
+
}
|
|
238
|
+
#param-value-tooltip.is-visible {
|
|
239
|
+
opacity: 1;
|
|
240
|
+
pointer-events: auto;
|
|
241
|
+
transform: translateY(0) scale(1);
|
|
242
|
+
}
|
|
243
|
+
.dark #param-value-tooltip {
|
|
244
|
+
border-color: rgba(148, 163, 184, 0.24);
|
|
245
|
+
background: rgba(17, 17, 17, 0.9);
|
|
246
|
+
color: #ededed;
|
|
247
|
+
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.45);
|
|
248
|
+
}
|
|
249
|
+
#param-tooltip-line {
|
|
250
|
+
margin: 0;
|
|
251
|
+
font-size: 11px;
|
|
252
|
+
line-height: 1.3;
|
|
253
|
+
font-family: "JetBrains Mono", monospace;
|
|
254
|
+
white-space: normal;
|
|
255
|
+
word-break: break-word;
|
|
256
|
+
}
|
|
257
|
+
#param-tooltip-description {
|
|
258
|
+
margin: 0.2rem 0 0;
|
|
259
|
+
font-size: 11px;
|
|
260
|
+
line-height: 1.3;
|
|
261
|
+
opacity: 0.8;
|
|
262
|
+
white-space: normal;
|
|
263
|
+
word-break: break-word;
|
|
264
|
+
}
|
|
147
265
|
</style>
|
|
148
266
|
</head>
|
|
149
267
|
<body class="bg-light-bg text-light-text dark:bg-dark-bg dark:text-dark-text font-sans antialiased flex h-screen overflow-hidden">
|
|
@@ -173,6 +291,16 @@ export function renderOpenAPIDocsHtml(
|
|
|
173
291
|
/>
|
|
174
292
|
</div>
|
|
175
293
|
</div>
|
|
294
|
+
<div id="auth-panel" class="border-b border-light-border dark:border-dark-border">
|
|
295
|
+
<button id="auth-toggle" class="w-full flex items-center justify-between px-4 py-2.5 text-xs font-semibold uppercase tracking-wider opacity-60 hover:opacity-100 transition-opacity">
|
|
296
|
+
<span class="flex items-center gap-1.5">
|
|
297
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
|
|
298
|
+
Auth
|
|
299
|
+
</span>
|
|
300
|
+
<svg id="auth-chevron" class="w-3.5 h-3.5 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
|
301
|
+
</button>
|
|
302
|
+
<div id="auth-fields" class="px-4 pb-3 space-y-2"></div>
|
|
303
|
+
</div>
|
|
176
304
|
<nav class="flex-1 overflow-y-auto px-3 py-2 space-y-6 text-sm" id="sidebar-nav"></nav>
|
|
177
305
|
</aside>
|
|
178
306
|
|
|
@@ -206,6 +334,9 @@ export function renderOpenAPIDocsHtml(
|
|
|
206
334
|
<span id="endpoint-method" class="px-2.5 py-0.5 rounded-full text-xs font-mono font-medium"></span>
|
|
207
335
|
<h2 class="text-xl font-semibold tracking-tight" id="endpoint-title">Operation</h2>
|
|
208
336
|
</div>
|
|
337
|
+
<div id="deprecated-banner" class="hidden mb-4 px-3 py-2 rounded border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 text-xs font-medium">
|
|
338
|
+
! This operation is deprecated
|
|
339
|
+
</div>
|
|
209
340
|
<p class="text-sm opacity-80 mb-8 font-mono" id="endpoint-path">/</p>
|
|
210
341
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-10">
|
|
211
342
|
<div class="lg:col-span-5 space-y-8" id="params-column"></div>
|
|
@@ -301,6 +432,10 @@ export function renderOpenAPIDocsHtml(
|
|
|
301
432
|
<pre id="expand-viewer" class="hidden w-full h-[70vh] text-sm p-3 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg overflow-auto font-mono"></pre>
|
|
302
433
|
</div>
|
|
303
434
|
</div>
|
|
435
|
+
<div id="param-value-tooltip" aria-hidden="true" role="tooltip">
|
|
436
|
+
<p id="param-tooltip-line"></p>
|
|
437
|
+
<p id="param-tooltip-description" class="hidden"></p>
|
|
438
|
+
</div>
|
|
304
439
|
|
|
305
440
|
<script>
|
|
306
441
|
const spec = ${specJson};
|
|
@@ -371,14 +506,71 @@ export function renderOpenAPIDocsHtml(
|
|
|
371
506
|
return ops;
|
|
372
507
|
}
|
|
373
508
|
|
|
509
|
+
const AUTH_STATE_KEY = "vector-docs-auth-v1";
|
|
510
|
+
const AUTH_SELECTION_KEY = "vector-docs-auth-selection-v1";
|
|
511
|
+
const HEADERS_STATE_KEY = "vector-docs-headers-v1";
|
|
512
|
+
|
|
513
|
+
function loadSavedHeaders() {
|
|
514
|
+
try {
|
|
515
|
+
const raw = localStorage.getItem(HEADERS_STATE_KEY);
|
|
516
|
+
if (raw) {
|
|
517
|
+
const parsed = JSON.parse(raw);
|
|
518
|
+
if (Array.isArray(parsed) && parsed.length > 0) return parsed;
|
|
519
|
+
}
|
|
520
|
+
} catch {}
|
|
521
|
+
return [{ key: "", value: "" }];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function saveHeaders() {
|
|
525
|
+
try { localStorage.setItem(HEADERS_STATE_KEY, JSON.stringify(requestHeaders)); } catch {}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function loadAuthState() {
|
|
529
|
+
try {
|
|
530
|
+
const raw = localStorage.getItem(AUTH_STATE_KEY);
|
|
531
|
+
if (raw) return JSON.parse(raw);
|
|
532
|
+
} catch {}
|
|
533
|
+
return {};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function saveAuthState() {
|
|
537
|
+
try { localStorage.setItem(AUTH_STATE_KEY, JSON.stringify(authState)); } catch {}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function loadAuthSelectionState() {
|
|
541
|
+
try {
|
|
542
|
+
const raw = localStorage.getItem(AUTH_SELECTION_KEY);
|
|
543
|
+
if (raw) {
|
|
544
|
+
const parsed = JSON.parse(raw);
|
|
545
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
546
|
+
return parsed;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
} catch {}
|
|
550
|
+
return {};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function saveAuthSelectionState() {
|
|
554
|
+
try { localStorage.setItem(AUTH_SELECTION_KEY, JSON.stringify(authSelectionState)); } catch {}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const authSchemes = (spec.components && spec.components.securitySchemes) || {};
|
|
558
|
+
let authState = loadAuthState();
|
|
559
|
+
let authSelectionState = loadAuthSelectionState();
|
|
560
|
+
|
|
374
561
|
const operations = getOperations();
|
|
375
562
|
let selected = operations[0] || null;
|
|
376
563
|
const operationParamValues = new Map();
|
|
377
564
|
const operationBodyDrafts = new Map();
|
|
378
|
-
const requestHeaders =
|
|
565
|
+
const requestHeaders = loadSavedHeaders();
|
|
379
566
|
let expandModalMode = null;
|
|
380
567
|
let isMobileSidebarOpen = false;
|
|
381
568
|
let sidebarSearchQuery = "";
|
|
569
|
+
const paramTooltipRoot = document.getElementById("param-value-tooltip");
|
|
570
|
+
const paramTooltipLine = document.getElementById("param-tooltip-line");
|
|
571
|
+
const paramTooltipDescription = document.getElementById("param-tooltip-description");
|
|
572
|
+
let activeParamTooltipTrigger = null;
|
|
573
|
+
let paramTooltipHideTimer = null;
|
|
382
574
|
|
|
383
575
|
function setMobileSidebarOpen(open) {
|
|
384
576
|
const sidebar = document.getElementById("docs-sidebar");
|
|
@@ -398,6 +590,29 @@ export function renderOpenAPIDocsHtml(
|
|
|
398
590
|
return op.method + " " + op.path;
|
|
399
591
|
}
|
|
400
592
|
|
|
593
|
+
function getOpHash(op) {
|
|
594
|
+
var tag = op.tag || "default";
|
|
595
|
+
var id = (op.operation && op.operation.operationId)
|
|
596
|
+
? op.operation.operationId
|
|
597
|
+
: op.method.toLowerCase() + "_" + op.path.split("/").filter(Boolean).join("_").replace(/[{}]/g, "");
|
|
598
|
+
return "#/" + encodeURIComponent(tag) + "/" + encodeURIComponent(id);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function findOpByHash(hash) {
|
|
602
|
+
if (!hash || hash.length <= 1) return null;
|
|
603
|
+
var parts = hash.slice(1).split("/").filter(Boolean);
|
|
604
|
+
if (parts.length < 2) return null;
|
|
605
|
+
var hashTag = decodeURIComponent(parts[0]);
|
|
606
|
+
var hashId = decodeURIComponent(parts[1]);
|
|
607
|
+
return operations.find(function(op) {
|
|
608
|
+
if (op.tag !== hashTag) return false;
|
|
609
|
+
var id = (op.operation && op.operation.operationId)
|
|
610
|
+
? op.operation.operationId
|
|
611
|
+
: op.method.toLowerCase() + "_" + op.path.split("/").filter(Boolean).join("_").replace(/[{}]/g, "");
|
|
612
|
+
return id === hashId;
|
|
613
|
+
}) || null;
|
|
614
|
+
}
|
|
615
|
+
|
|
401
616
|
function getOperationParameterGroups(op) {
|
|
402
617
|
const params =
|
|
403
618
|
op &&
|
|
@@ -447,7 +662,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
447
662
|
return resolved;
|
|
448
663
|
}
|
|
449
664
|
|
|
450
|
-
function buildRequestPath(op, pathParams, queryParams, values) {
|
|
665
|
+
function buildRequestPath(op, pathParams, queryParams, values, extraQuery) {
|
|
451
666
|
const resolvedPath = resolvePath(op.path, pathParams, values);
|
|
452
667
|
const query = new URLSearchParams();
|
|
453
668
|
|
|
@@ -459,6 +674,13 @@ export function renderOpenAPIDocsHtml(
|
|
|
459
674
|
query.append(param.name, String(rawValue));
|
|
460
675
|
}
|
|
461
676
|
|
|
677
|
+
if (extraQuery) {
|
|
678
|
+
for (const key of Object.keys(extraQuery)) {
|
|
679
|
+
const val = extraQuery[key];
|
|
680
|
+
if (val) query.set(key, String(val));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
462
684
|
const queryString = query.toString();
|
|
463
685
|
return queryString ? resolvedPath + "?" + queryString : resolvedPath;
|
|
464
686
|
}
|
|
@@ -598,14 +820,25 @@ export function renderOpenAPIDocsHtml(
|
|
|
598
820
|
|
|
599
821
|
const name = document.createElement("span");
|
|
600
822
|
name.textContent = op.name;
|
|
823
|
+
if (op.operation && op.operation.deprecated) {
|
|
824
|
+
name.style.textDecoration = "line-through";
|
|
825
|
+
name.style.opacity = "0.5";
|
|
826
|
+
}
|
|
601
827
|
|
|
602
828
|
row.appendChild(method);
|
|
603
829
|
row.appendChild(name);
|
|
830
|
+
if (op.operation && op.operation.deprecated) {
|
|
831
|
+
const badge = document.createElement("span");
|
|
832
|
+
badge.className = "text-[9px] px-1 py-0.5 rounded bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-500 font-semibold shrink-0";
|
|
833
|
+
badge.textContent = "deprecated";
|
|
834
|
+
row.appendChild(badge);
|
|
835
|
+
}
|
|
604
836
|
a.appendChild(row);
|
|
605
837
|
|
|
606
838
|
a.onclick = (e) => {
|
|
607
839
|
e.preventDefault();
|
|
608
840
|
selected = op;
|
|
841
|
+
history.pushState(null, "", getOpHash(op));
|
|
609
842
|
renderSidebar();
|
|
610
843
|
renderEndpoint();
|
|
611
844
|
if (window.innerWidth < 768) {
|
|
@@ -619,51 +852,257 @@ export function renderOpenAPIDocsHtml(
|
|
|
619
852
|
}
|
|
620
853
|
}
|
|
621
854
|
|
|
855
|
+
function hideParamTooltip() {
|
|
856
|
+
if (!paramTooltipRoot) return;
|
|
857
|
+
if (paramTooltipHideTimer) {
|
|
858
|
+
window.clearTimeout(paramTooltipHideTimer);
|
|
859
|
+
paramTooltipHideTimer = null;
|
|
860
|
+
}
|
|
861
|
+
paramTooltipRoot.classList.remove("is-visible");
|
|
862
|
+
paramTooltipRoot.setAttribute("aria-hidden", "true");
|
|
863
|
+
if (activeParamTooltipTrigger) {
|
|
864
|
+
activeParamTooltipTrigger.setAttribute("aria-expanded", "false");
|
|
865
|
+
}
|
|
866
|
+
activeParamTooltipTrigger = null;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function scheduleParamTooltipHide() {
|
|
870
|
+
if (paramTooltipHideTimer) {
|
|
871
|
+
window.clearTimeout(paramTooltipHideTimer);
|
|
872
|
+
}
|
|
873
|
+
paramTooltipHideTimer = window.setTimeout(() => {
|
|
874
|
+
hideParamTooltip();
|
|
875
|
+
}, 95);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function positionParamTooltip(trigger) {
|
|
879
|
+
if (!paramTooltipRoot || !trigger) return;
|
|
880
|
+
const viewportPadding = 8;
|
|
881
|
+
const spacing = 10;
|
|
882
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
883
|
+
const tooltipRect = paramTooltipRoot.getBoundingClientRect();
|
|
884
|
+
let left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
|
885
|
+
left = Math.max(viewportPadding, Math.min(left, window.innerWidth - tooltipRect.width - viewportPadding));
|
|
886
|
+
let top = triggerRect.top - tooltipRect.height - spacing;
|
|
887
|
+
if (top < viewportPadding) {
|
|
888
|
+
top = triggerRect.bottom + spacing;
|
|
889
|
+
}
|
|
890
|
+
if (top + tooltipRect.height > window.innerHeight - viewportPadding) {
|
|
891
|
+
top = window.innerHeight - tooltipRect.height - viewportPadding;
|
|
892
|
+
}
|
|
893
|
+
paramTooltipRoot.style.left = Math.round(left) + "px";
|
|
894
|
+
paramTooltipRoot.style.top = Math.round(top) + "px";
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function showParamTooltip(trigger) {
|
|
898
|
+
if (
|
|
899
|
+
!paramTooltipRoot ||
|
|
900
|
+
!paramTooltipLine ||
|
|
901
|
+
!paramTooltipDescription ||
|
|
902
|
+
!trigger
|
|
903
|
+
) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (paramTooltipHideTimer) {
|
|
907
|
+
window.clearTimeout(paramTooltipHideTimer);
|
|
908
|
+
paramTooltipHideTimer = null;
|
|
909
|
+
}
|
|
910
|
+
const label = trigger.getAttribute("data-param-tooltip-label") || "Value";
|
|
911
|
+
const value = trigger.getAttribute("data-param-tooltip-value") || "";
|
|
912
|
+
const related = trigger.getAttribute("data-param-tooltip-related") || "";
|
|
913
|
+
const description = trigger.getAttribute("data-param-tooltip-description") || "";
|
|
914
|
+
if (activeParamTooltipTrigger && activeParamTooltipTrigger !== trigger) {
|
|
915
|
+
activeParamTooltipTrigger.setAttribute("aria-expanded", "false");
|
|
916
|
+
}
|
|
917
|
+
activeParamTooltipTrigger = trigger;
|
|
918
|
+
activeParamTooltipTrigger.setAttribute("aria-expanded", "true");
|
|
919
|
+
const pathLabel = related ? " | path: " + related : "";
|
|
920
|
+
paramTooltipLine.textContent = label + ": " + value + pathLabel;
|
|
921
|
+
if (description.trim()) {
|
|
922
|
+
paramTooltipDescription.textContent = description;
|
|
923
|
+
paramTooltipDescription.classList.remove("hidden");
|
|
924
|
+
} else {
|
|
925
|
+
paramTooltipDescription.textContent = "";
|
|
926
|
+
paramTooltipDescription.classList.add("hidden");
|
|
927
|
+
}
|
|
928
|
+
paramTooltipRoot.classList.add("is-visible");
|
|
929
|
+
paramTooltipRoot.setAttribute("aria-hidden", "false");
|
|
930
|
+
positionParamTooltip(trigger);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function registerParamTooltipTargets(scope) {
|
|
934
|
+
if (!scope) return;
|
|
935
|
+
const targets = scope.querySelectorAll("[data-param-tooltip-value]");
|
|
936
|
+
for (const target of targets) {
|
|
937
|
+
target.addEventListener("click", (event) => {
|
|
938
|
+
event.preventDefault();
|
|
939
|
+
if (
|
|
940
|
+
activeParamTooltipTrigger === target &&
|
|
941
|
+
paramTooltipRoot &&
|
|
942
|
+
paramTooltipRoot.classList.contains("is-visible")
|
|
943
|
+
) {
|
|
944
|
+
hideParamTooltip();
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
showParamTooltip(target);
|
|
948
|
+
});
|
|
949
|
+
target.addEventListener("mouseenter", () => {
|
|
950
|
+
showParamTooltip(target);
|
|
951
|
+
});
|
|
952
|
+
target.addEventListener("mouseleave", (event) => {
|
|
953
|
+
const related = event.relatedTarget;
|
|
954
|
+
if (paramTooltipRoot && related && paramTooltipRoot.contains(related)) return;
|
|
955
|
+
scheduleParamTooltipHide();
|
|
956
|
+
});
|
|
957
|
+
target.addEventListener("focus", () => {
|
|
958
|
+
showParamTooltip(target);
|
|
959
|
+
});
|
|
960
|
+
target.addEventListener("blur", (event) => {
|
|
961
|
+
const related = event.relatedTarget;
|
|
962
|
+
if (paramTooltipRoot && related && paramTooltipRoot.contains(related)) return;
|
|
963
|
+
scheduleParamTooltipHide();
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
622
968
|
function renderParamSection(title, params) {
|
|
623
969
|
if (!params.length) return "";
|
|
624
970
|
let rows = "";
|
|
625
971
|
for (const p of params) {
|
|
626
|
-
const
|
|
627
|
-
const
|
|
628
|
-
|
|
972
|
+
const schema = resolveSchemaRef(p.schema || {});
|
|
973
|
+
const typeRaw = getSchemaTypeLabel(schema);
|
|
974
|
+
const type = escapeHtml(typeRaw);
|
|
975
|
+
const nameRaw = p.name || "";
|
|
976
|
+
const name = escapeHtml(nameRaw);
|
|
977
|
+
const tooltipName = escapeHtmlAttribute(nameRaw);
|
|
978
|
+
const tooltipType = escapeHtmlAttribute(typeRaw);
|
|
979
|
+
const tooltipDescription = (typeof p.description === "string" && p.description.trim())
|
|
980
|
+
? escapeHtmlAttribute(p.description.trim())
|
|
981
|
+
: (typeof schema.description === "string" && schema.description.trim())
|
|
982
|
+
? escapeHtmlAttribute(schema.description.trim())
|
|
983
|
+
: "";
|
|
984
|
+
const desc = (typeof p.description === "string" && p.description.trim())
|
|
985
|
+
? '<p class="text-xs opacity-60 mt-0.5 leading-snug">' + renderMarkdown(p.description.trim()) + '</p>'
|
|
986
|
+
: "";
|
|
987
|
+
const extra = buildSchemaExtra(schema);
|
|
988
|
+
rows +=
|
|
989
|
+
'<div class="param-row py-2 border-b border-light-border/50 dark:border-dark-border/50">' +
|
|
990
|
+
'<div class="param-row-head">' +
|
|
991
|
+
'<div class="param-row-main">' +
|
|
992
|
+
'<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Parameter" data-param-tooltip-value="' +
|
|
993
|
+
tooltipName +
|
|
994
|
+
'" data-param-tooltip-description="' +
|
|
995
|
+
tooltipDescription +
|
|
996
|
+
'" aria-expanded="false">' +
|
|
997
|
+
'<code class="text-sm font-mono param-name-text">' +
|
|
998
|
+
name +
|
|
999
|
+
"</code></button>" +
|
|
1000
|
+
'<span class="text-xs text-brand shrink-0">' +
|
|
1001
|
+
(p.required ? "required" : "optional") +
|
|
1002
|
+
"</span></div>" +
|
|
1003
|
+
'<button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
|
|
1004
|
+
tooltipType +
|
|
1005
|
+
'" data-param-tooltip-description="' +
|
|
1006
|
+
tooltipDescription +
|
|
1007
|
+
'" aria-expanded="false">' +
|
|
1008
|
+
type +
|
|
1009
|
+
"</button></div>" +
|
|
1010
|
+
desc +
|
|
1011
|
+
extra +
|
|
1012
|
+
"</div>";
|
|
629
1013
|
}
|
|
630
1014
|
return '<div><h3 class="text-sm font-semibold mb-3 flex items-center border-b border-light-border dark:border-dark-border pb-2">' + escapeHtml(title) + "</h3>" + rows + "</div>";
|
|
631
1015
|
}
|
|
632
1016
|
|
|
633
1017
|
function getSchemaTypeLabel(schema) {
|
|
634
|
-
|
|
635
|
-
if (
|
|
636
|
-
if (
|
|
637
|
-
if (
|
|
638
|
-
if (
|
|
639
|
-
if (
|
|
640
|
-
if (Array.isArray(
|
|
641
|
-
if (Array.isArray(
|
|
1018
|
+
const resolved = resolveSchemaRef(schema);
|
|
1019
|
+
if (!resolved || typeof resolved !== "object") return "unknown";
|
|
1020
|
+
if (Array.isArray(resolved.type)) return resolved.type.join(" | ");
|
|
1021
|
+
if (resolved.type) return String(resolved.type);
|
|
1022
|
+
if (resolved.properties) return "object";
|
|
1023
|
+
if (resolved.items) return "array";
|
|
1024
|
+
if (Array.isArray(resolved.oneOf)) return "oneOf";
|
|
1025
|
+
if (Array.isArray(resolved.anyOf)) return "anyOf";
|
|
1026
|
+
if (Array.isArray(resolved.allOf)) return "allOf";
|
|
642
1027
|
return "unknown";
|
|
643
1028
|
}
|
|
644
1029
|
|
|
1030
|
+
function buildSchemaExtra(schema) {
|
|
1031
|
+
const resolved = resolveSchemaRef(schema);
|
|
1032
|
+
if (!resolved || typeof resolved !== "object") return "";
|
|
1033
|
+
const chips = [];
|
|
1034
|
+
if (resolved.format) chips.push(escapeHtml(String(resolved.format)));
|
|
1035
|
+
if (Array.isArray(resolved.enum) && resolved.enum.length > 0) {
|
|
1036
|
+
const shown = resolved.enum.slice(0, 5).map(function(v) { return escapeHtml(JSON.stringify(v)); });
|
|
1037
|
+
chips.push(shown.join(" | ") + (resolved.enum.length > 5 ? " …" : ""));
|
|
1038
|
+
}
|
|
1039
|
+
if (resolved.minimum !== undefined) chips.push("min: " + resolved.minimum);
|
|
1040
|
+
if (resolved.maximum !== undefined) chips.push("max: " + resolved.maximum);
|
|
1041
|
+
if (typeof resolved.exclusiveMinimum === "number") chips.push(">" + resolved.exclusiveMinimum);
|
|
1042
|
+
if (typeof resolved.exclusiveMaximum === "number") chips.push("<" + resolved.exclusiveMaximum);
|
|
1043
|
+
if (resolved.minLength !== undefined) chips.push("minLen: " + resolved.minLength);
|
|
1044
|
+
if (resolved.maxLength !== undefined) chips.push("maxLen: " + resolved.maxLength);
|
|
1045
|
+
if (resolved.minItems !== undefined) chips.push("minItems: " + resolved.minItems);
|
|
1046
|
+
if (resolved.maxItems !== undefined) chips.push("maxItems: " + resolved.maxItems);
|
|
1047
|
+
if (resolved.uniqueItems) chips.push("unique");
|
|
1048
|
+
if (resolved.pattern) chips.push("/" + escapeHtml(String(resolved.pattern)) + "/");
|
|
1049
|
+
if (!chips.length) return "";
|
|
1050
|
+
return '<div class="flex flex-wrap gap-1 mt-1.5">' +
|
|
1051
|
+
chips.map(function(c) {
|
|
1052
|
+
return '<span class="text-[10px] px-1.5 py-0.5 rounded bg-black/5 dark:bg-white/5 font-mono opacity-80">' + c + '</span>';
|
|
1053
|
+
}).join("") +
|
|
1054
|
+
'</div>';
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function resolveSchemaRef(schema, visitedRefs) {
|
|
1058
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1059
|
+
const ref = typeof schema.$ref === "string" ? schema.$ref : "";
|
|
1060
|
+
if (!ref || !ref.startsWith("#/components/schemas/")) {
|
|
1061
|
+
return schema;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const seen = visitedRefs || new Set();
|
|
1065
|
+
if (seen.has(ref)) return schema;
|
|
1066
|
+
seen.add(ref);
|
|
1067
|
+
|
|
1068
|
+
const parts = ref.split("/");
|
|
1069
|
+
const schemaName = parts[parts.length - 1];
|
|
1070
|
+
const referenced = spec && spec.components && spec.components.schemas && spec.components.schemas[schemaName];
|
|
1071
|
+
if (!referenced || typeof referenced !== "object") return schema;
|
|
1072
|
+
|
|
1073
|
+
const merged = Object.assign({}, referenced, schema);
|
|
1074
|
+
delete merged.$ref;
|
|
1075
|
+
return resolveSchemaRef(merged, seen);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
645
1078
|
function buildSchemaChildren(schema) {
|
|
646
|
-
|
|
1079
|
+
const resolved = resolveSchemaRef(schema);
|
|
1080
|
+
if (!resolved || typeof resolved !== "object") return [];
|
|
647
1081
|
|
|
648
1082
|
const children = [];
|
|
649
1083
|
|
|
650
|
-
if (
|
|
1084
|
+
if (resolved.properties && typeof resolved.properties === "object") {
|
|
651
1085
|
const requiredSet = new Set(
|
|
652
|
-
Array.isArray(
|
|
1086
|
+
Array.isArray(resolved.required) ? resolved.required : [],
|
|
653
1087
|
);
|
|
654
|
-
for (const [name, childSchema] of Object.entries(
|
|
1088
|
+
for (const [name, childSchema] of Object.entries(resolved.properties)) {
|
|
1089
|
+
const childDef = childSchema || {};
|
|
1090
|
+
const isArrayType = Array.isArray(childDef.type)
|
|
1091
|
+
? childDef.type.includes("array")
|
|
1092
|
+
: childDef.type === "array";
|
|
1093
|
+
const isArrayLike = isArrayType || childDef.items !== undefined;
|
|
655
1094
|
children.push({
|
|
656
|
-
name,
|
|
657
|
-
schema:
|
|
1095
|
+
name: isArrayLike ? (name + "[]") : name,
|
|
1096
|
+
schema: childDef,
|
|
658
1097
|
required: requiredSet.has(name),
|
|
659
1098
|
});
|
|
660
1099
|
}
|
|
661
1100
|
}
|
|
662
1101
|
|
|
663
|
-
if (
|
|
1102
|
+
if (resolved.items) {
|
|
664
1103
|
children.push({
|
|
665
|
-
name:
|
|
666
|
-
schema:
|
|
1104
|
+
name: getArrayItemNodeName(resolved.items),
|
|
1105
|
+
schema: resolved.items,
|
|
667
1106
|
required: true,
|
|
668
1107
|
});
|
|
669
1108
|
}
|
|
@@ -671,45 +1110,100 @@ export function renderOpenAPIDocsHtml(
|
|
|
671
1110
|
return children;
|
|
672
1111
|
}
|
|
673
1112
|
|
|
674
|
-
function
|
|
675
|
-
|
|
676
|
-
const
|
|
1113
|
+
function getArrayItemNodeName(itemSchema) {
|
|
1114
|
+
if (!itemSchema || typeof itemSchema !== "object") return "item";
|
|
1115
|
+
const title =
|
|
1116
|
+
typeof itemSchema.title === "string" && itemSchema.title.trim()
|
|
1117
|
+
? itemSchema.title.trim()
|
|
1118
|
+
: "";
|
|
1119
|
+
if (title) return title;
|
|
1120
|
+
|
|
1121
|
+
const ref =
|
|
1122
|
+
typeof itemSchema.$ref === "string" && itemSchema.$ref.trim()
|
|
1123
|
+
? itemSchema.$ref.trim()
|
|
1124
|
+
: "";
|
|
1125
|
+
if (ref) {
|
|
1126
|
+
const parts = ref.split("/").filter(Boolean);
|
|
1127
|
+
const last = parts[parts.length - 1];
|
|
1128
|
+
if (last) return last;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const typeLabel = getSchemaTypeLabel(itemSchema);
|
|
1132
|
+
if (typeLabel && typeLabel !== "unknown") return typeLabel;
|
|
1133
|
+
return "type";
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function renderSchemaFieldNode(field, depth, parentPath) {
|
|
1137
|
+
const schema = resolveSchemaRef(field.schema || {});
|
|
1138
|
+
const nameRaw = field.name || "field";
|
|
1139
|
+
const name = escapeHtml(nameRaw);
|
|
677
1140
|
const requiredLabel = field.required ? "required" : "optional";
|
|
678
|
-
const
|
|
1141
|
+
const typeRaw = getSchemaTypeLabel(schema);
|
|
1142
|
+
const type = escapeHtml(typeRaw);
|
|
1143
|
+
const tooltipName = escapeHtmlAttribute(nameRaw);
|
|
1144
|
+
const tooltipType = escapeHtmlAttribute(typeRaw);
|
|
1145
|
+
const fieldPath = parentPath ? (parentPath + "." + nameRaw) : nameRaw;
|
|
1146
|
+
const tooltipPath = escapeHtmlAttribute(fieldPath);
|
|
1147
|
+
const tooltipDescription = (typeof schema.description === "string" && schema.description.trim())
|
|
1148
|
+
? escapeHtmlAttribute(schema.description.trim())
|
|
1149
|
+
: "";
|
|
679
1150
|
const children = buildSchemaChildren(schema);
|
|
680
1151
|
const padding = depth * 14;
|
|
1152
|
+
const extra = buildSchemaExtra(schema);
|
|
681
1153
|
|
|
682
1154
|
if (!children.length) {
|
|
683
1155
|
return (
|
|
684
|
-
'<div class="py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
|
|
1156
|
+
'<div class="param-row py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
|
|
685
1157
|
padding +
|
|
686
|
-
'px"><div class="
|
|
1158
|
+
'px"><div class="param-row-head"><div class="param-row-main">' +
|
|
1159
|
+
'<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Field" data-param-tooltip-value="' +
|
|
1160
|
+
tooltipName +
|
|
1161
|
+
'" data-param-tooltip-related="' +
|
|
1162
|
+
tooltipPath +
|
|
1163
|
+
'" data-param-tooltip-description="' +
|
|
1164
|
+
tooltipDescription +
|
|
1165
|
+
'" aria-expanded="false"><code class="text-sm font-mono param-name-text">' +
|
|
687
1166
|
name +
|
|
688
|
-
'</code><span class="text-xs text-brand
|
|
1167
|
+
'</code></button><span class="text-xs text-brand shrink-0">' +
|
|
689
1168
|
requiredLabel +
|
|
690
|
-
'</span></div><
|
|
1169
|
+
'</span></div><button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
|
|
1170
|
+
tooltipType +
|
|
1171
|
+
'" data-param-tooltip-description="' +
|
|
1172
|
+
tooltipDescription +
|
|
1173
|
+
'" aria-expanded="false">' +
|
|
691
1174
|
type +
|
|
692
|
-
"</
|
|
1175
|
+
"</button></div>" + extra + "</div>"
|
|
693
1176
|
);
|
|
694
1177
|
}
|
|
695
1178
|
|
|
696
1179
|
let nested = "";
|
|
697
1180
|
for (const child of children) {
|
|
698
|
-
nested += renderSchemaFieldNode(child, depth + 1);
|
|
1181
|
+
nested += renderSchemaFieldNode(child, depth + 1, fieldPath);
|
|
699
1182
|
}
|
|
700
1183
|
|
|
701
1184
|
return (
|
|
702
|
-
'<details
|
|
703
|
-
'<summary class="list-none cursor-pointer py-2
|
|
1185
|
+
'<details open>' +
|
|
1186
|
+
'<summary class="list-none cursor-pointer py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
|
|
704
1187
|
padding +
|
|
705
|
-
'px"><div class="
|
|
1188
|
+
'px"><div class="param-row-head"><div class="param-row-main"><span class="text-xs opacity-70 shrink-0">▾</span>' +
|
|
1189
|
+
'<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Field" data-param-tooltip-value="' +
|
|
1190
|
+
tooltipName +
|
|
1191
|
+
'" data-param-tooltip-related="' +
|
|
1192
|
+
tooltipPath +
|
|
1193
|
+
'" data-param-tooltip-description="' +
|
|
1194
|
+
tooltipDescription +
|
|
1195
|
+
'" aria-expanded="false"><code class="text-sm font-mono param-name-text">' +
|
|
706
1196
|
name +
|
|
707
|
-
'</code><span class="text-xs text-brand">' +
|
|
1197
|
+
'</code></button><span class="text-xs text-brand shrink-0">' +
|
|
708
1198
|
requiredLabel +
|
|
709
|
-
'</span></div><
|
|
1199
|
+
'</span></div><button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
|
|
1200
|
+
tooltipType +
|
|
1201
|
+
'" data-param-tooltip-description="' +
|
|
1202
|
+
tooltipDescription +
|
|
1203
|
+
'" aria-expanded="false">' +
|
|
710
1204
|
type +
|
|
711
|
-
"</
|
|
712
|
-
|
|
1205
|
+
"</button></div>" + extra + "</summary>" +
|
|
1206
|
+
"<div>" +
|
|
713
1207
|
nested +
|
|
714
1208
|
"</div></details>"
|
|
715
1209
|
);
|
|
@@ -722,7 +1216,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
722
1216
|
|
|
723
1217
|
let rows = "";
|
|
724
1218
|
for (const child of rootChildren) {
|
|
725
|
-
rows += renderSchemaFieldNode(child, 0);
|
|
1219
|
+
rows += renderSchemaFieldNode(child, 0, "");
|
|
726
1220
|
}
|
|
727
1221
|
|
|
728
1222
|
return (
|
|
@@ -749,27 +1243,40 @@ export function renderOpenAPIDocsHtml(
|
|
|
749
1243
|
const responseDef = responses[statusCode];
|
|
750
1244
|
if (!responseDef || typeof responseDef !== "object") continue;
|
|
751
1245
|
|
|
1246
|
+
const responseDesc = (typeof responseDef.description === "string" && responseDef.description.trim())
|
|
1247
|
+
? responseDef.description.trim()
|
|
1248
|
+
: "";
|
|
1249
|
+
|
|
752
1250
|
const jsonSchema =
|
|
753
1251
|
responseDef.content &&
|
|
754
1252
|
responseDef.content["application/json"] &&
|
|
755
1253
|
responseDef.content["application/json"].schema;
|
|
756
1254
|
|
|
757
|
-
if (!jsonSchema || typeof jsonSchema !== "object") continue;
|
|
758
|
-
|
|
759
|
-
const rootChildren = buildSchemaChildren(jsonSchema);
|
|
760
|
-
if (!rootChildren.length) continue;
|
|
761
|
-
|
|
762
1255
|
let rows = "";
|
|
763
|
-
|
|
764
|
-
|
|
1256
|
+
if (jsonSchema && typeof jsonSchema === "object") {
|
|
1257
|
+
const rootChildren = buildSchemaChildren(jsonSchema);
|
|
1258
|
+
for (const child of rootChildren) {
|
|
1259
|
+
rows += renderSchemaFieldNode(child, 0, "");
|
|
1260
|
+
}
|
|
765
1261
|
}
|
|
766
1262
|
|
|
1263
|
+
if (!responseDesc && !rows) continue;
|
|
1264
|
+
|
|
1265
|
+
const descHtml = responseDesc
|
|
1266
|
+
? ' <span class="normal-case font-sans opacity-70 ml-1">— ' + escapeHtml(responseDesc) + '</span>'
|
|
1267
|
+
: "";
|
|
1268
|
+
const contentHtml = rows || '<p class="text-xs opacity-60 mt-1">No schema fields</p>';
|
|
1269
|
+
|
|
767
1270
|
sections +=
|
|
768
|
-
'<
|
|
1271
|
+
'<details class="mb-4">' +
|
|
1272
|
+
'<summary class="list-none cursor-pointer">' +
|
|
1273
|
+
'<h4 class="text-xs font-mono uppercase tracking-wider opacity-70 mb-2">Status ' +
|
|
769
1274
|
escapeHtml(statusCode) +
|
|
1275
|
+
descHtml +
|
|
770
1276
|
"</h4>" +
|
|
771
|
-
|
|
772
|
-
|
|
1277
|
+
"</summary>" +
|
|
1278
|
+
contentHtml +
|
|
1279
|
+
"</details>";
|
|
773
1280
|
}
|
|
774
1281
|
|
|
775
1282
|
if (!sections) return "";
|
|
@@ -861,6 +1368,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
861
1368
|
"w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
|
|
862
1369
|
keyInput.addEventListener("input", () => {
|
|
863
1370
|
entry.key = keyInput.value;
|
|
1371
|
+
saveHeaders();
|
|
864
1372
|
updateRequestPreview();
|
|
865
1373
|
});
|
|
866
1374
|
|
|
@@ -875,6 +1383,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
875
1383
|
"w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
|
|
876
1384
|
valueInput.addEventListener("input", () => {
|
|
877
1385
|
entry.value = valueInput.value;
|
|
1386
|
+
saveHeaders();
|
|
878
1387
|
updateRequestPreview();
|
|
879
1388
|
});
|
|
880
1389
|
|
|
@@ -888,6 +1397,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
888
1397
|
'<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12h12"></path></svg>';
|
|
889
1398
|
removeButton.addEventListener("click", () => {
|
|
890
1399
|
requestHeaders.splice(index, 1);
|
|
1400
|
+
saveHeaders();
|
|
891
1401
|
renderHeaderInputs();
|
|
892
1402
|
updateRequestPreview();
|
|
893
1403
|
});
|
|
@@ -904,15 +1414,32 @@ export function renderOpenAPIDocsHtml(
|
|
|
904
1414
|
return Object.keys(headers).some((key) => key.toLowerCase() === target);
|
|
905
1415
|
}
|
|
906
1416
|
|
|
907
|
-
function
|
|
908
|
-
const
|
|
1417
|
+
function buildCookieHeaderValue(cookieValues) {
|
|
1418
|
+
const entries = Object.entries(cookieValues);
|
|
1419
|
+
if (!entries.length) return "";
|
|
1420
|
+
return entries
|
|
1421
|
+
.map(([name, value]) => String(name) + "=" + encodeURIComponent(String(value)))
|
|
1422
|
+
.join("; ");
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function getRequestHeadersObject(op) {
|
|
1426
|
+
const auth = getAuthHeaders(op);
|
|
1427
|
+
const authCookies = getAuthCookieParams(op);
|
|
1428
|
+
if (Object.keys(authCookies).length > 0) {
|
|
1429
|
+
const cookieHeader = buildCookieHeaderValue(authCookies);
|
|
1430
|
+
if (cookieHeader) {
|
|
1431
|
+
auth["Cookie"] = cookieHeader;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
const manual = {};
|
|
909
1435
|
for (const entry of requestHeaders) {
|
|
910
1436
|
const key = String(entry.key || "").trim();
|
|
911
1437
|
const value = String(entry.value || "").trim();
|
|
912
1438
|
if (!key || !value) continue;
|
|
913
|
-
|
|
1439
|
+
manual[key] = value;
|
|
914
1440
|
}
|
|
915
|
-
|
|
1441
|
+
// Auth provides defaults; manual headers win on conflict
|
|
1442
|
+
return Object.assign({}, auth, manual);
|
|
916
1443
|
}
|
|
917
1444
|
|
|
918
1445
|
function buildCurl(op, headers, body, requestPath) {
|
|
@@ -949,6 +1476,33 @@ export function renderOpenAPIDocsHtml(
|
|
|
949
1476
|
.replace(/>/g, ">");
|
|
950
1477
|
}
|
|
951
1478
|
|
|
1479
|
+
function escapeHtmlAttribute(value) {
|
|
1480
|
+
return String(value)
|
|
1481
|
+
.replace(/&/g, "&")
|
|
1482
|
+
.replace(/</g, "<")
|
|
1483
|
+
.replace(/>/g, ">")
|
|
1484
|
+
.replace(/"/g, """)
|
|
1485
|
+
.replace(/'/g, "'");
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function renderMarkdown(text) {
|
|
1489
|
+
if (!text || typeof text !== "string") return "";
|
|
1490
|
+
var s = escapeHtml(text);
|
|
1491
|
+
// inline code — process first to protect content inside backticks
|
|
1492
|
+
s = s.replace(/\`([^\`\\n]+)\`/g, '<code class="text-xs font-mono bg-black/5 dark:bg-white/5 px-1 py-0.5 rounded">$1</code>');
|
|
1493
|
+
// bold **text**
|
|
1494
|
+
s = s.replace(/\\*\\*([^*\\n]+)\\*\\*/g, '<strong>$1</strong>');
|
|
1495
|
+
// italic *text*
|
|
1496
|
+
s = s.replace(/\\*([^*\\n]+)\\*/g, '<em>$1</em>');
|
|
1497
|
+
// links [text](url)
|
|
1498
|
+
s = s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, function(m, txt, url) {
|
|
1499
|
+
var lc = url.toLowerCase().replace(/\\s/g, "");
|
|
1500
|
+
if (lc.indexOf("javascript:") === 0 || lc.indexOf("data:") === 0 || lc.indexOf("vbscript:") === 0) return txt;
|
|
1501
|
+
return '<a href="' + url.replace(/"/g, '"') + '" target="_blank" rel="noopener noreferrer" class="text-brand hover:underline">' + txt + '</a>';
|
|
1502
|
+
});
|
|
1503
|
+
return s;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
952
1506
|
function toPrettyJson(value) {
|
|
953
1507
|
const trimmed = (value || "").trim();
|
|
954
1508
|
if (!trimmed) return null;
|
|
@@ -1082,10 +1636,10 @@ export function renderOpenAPIDocsHtml(
|
|
|
1082
1636
|
|
|
1083
1637
|
const { path, query } = getOperationParameterGroups(selected);
|
|
1084
1638
|
const values = getParameterValues(selected);
|
|
1085
|
-
const requestPath = buildRequestPath(selected, path, query, values);
|
|
1639
|
+
const requestPath = buildRequestPath(selected, path, query, values, getAuthQueryParams(selected));
|
|
1086
1640
|
const bodyInput = document.getElementById("body-input");
|
|
1087
1641
|
const body = bodyInput ? bodyInput.value.trim() : "";
|
|
1088
|
-
const headers = getRequestHeadersObject();
|
|
1642
|
+
const headers = getRequestHeadersObject(selected);
|
|
1089
1643
|
if (body && !hasHeaderName(headers, "Content-Type")) {
|
|
1090
1644
|
headers["Content-Type"] = "application/json";
|
|
1091
1645
|
}
|
|
@@ -1136,8 +1690,13 @@ export function renderOpenAPIDocsHtml(
|
|
|
1136
1690
|
}
|
|
1137
1691
|
setResponseContent("", "", false);
|
|
1138
1692
|
|
|
1693
|
+
const deprecatedBanner = document.getElementById("deprecated-banner");
|
|
1694
|
+
if (deprecatedBanner) {
|
|
1695
|
+
deprecatedBanner.classList.toggle("hidden", !op.deprecated);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1139
1698
|
document.getElementById("tag-title").textContent = selected.tag;
|
|
1140
|
-
document.getElementById("tag-description").
|
|
1699
|
+
document.getElementById("tag-description").innerHTML = op.description ? renderMarkdown(op.description) : "Interactive API documentation.";
|
|
1141
1700
|
const methodNode = document.getElementById("endpoint-method");
|
|
1142
1701
|
methodNode.textContent = selected.method;
|
|
1143
1702
|
methodNode.className = "px-2.5 py-0.5 rounded-full text-xs font-mono font-medium " + (methodBadge[selected.method] || methodBadgeDefault);
|
|
@@ -1154,7 +1713,13 @@ export function renderOpenAPIDocsHtml(
|
|
|
1154
1713
|
|
|
1155
1714
|
html += renderRequestBodySchemaSection(reqSchema);
|
|
1156
1715
|
html += renderResponseSchemasSection(op.responses);
|
|
1157
|
-
document.getElementById("params-column")
|
|
1716
|
+
const paramsColumn = document.getElementById("params-column");
|
|
1717
|
+
if (paramsColumn) {
|
|
1718
|
+
hideParamTooltip();
|
|
1719
|
+
paramsColumn.innerHTML = html || '<div class="text-sm opacity-70">No parameters</div>';
|
|
1720
|
+
registerParamTooltipTargets(paramsColumn);
|
|
1721
|
+
}
|
|
1722
|
+
renderAuthPanel();
|
|
1158
1723
|
renderTryItParameterInputs(path, query);
|
|
1159
1724
|
renderHeaderInputs();
|
|
1160
1725
|
updateRequestPreview();
|
|
@@ -1165,6 +1730,24 @@ export function renderOpenAPIDocsHtml(
|
|
|
1165
1730
|
document.getElementById("copy-curl").addEventListener("click", async () => {
|
|
1166
1731
|
try { await navigator.clipboard.writeText(document.getElementById("curl-code").textContent || ""); } catch {}
|
|
1167
1732
|
});
|
|
1733
|
+
if (paramTooltipRoot) {
|
|
1734
|
+
paramTooltipRoot.addEventListener("mouseenter", () => {
|
|
1735
|
+
if (paramTooltipHideTimer) {
|
|
1736
|
+
window.clearTimeout(paramTooltipHideTimer);
|
|
1737
|
+
paramTooltipHideTimer = null;
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
paramTooltipRoot.addEventListener("mouseleave", () => {
|
|
1741
|
+
scheduleParamTooltipHide();
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
document.addEventListener("pointerdown", (event) => {
|
|
1745
|
+
if (!paramTooltipRoot) return;
|
|
1746
|
+
const target = event.target;
|
|
1747
|
+
if (target && paramTooltipRoot.contains(target)) return;
|
|
1748
|
+
if (target && target.closest && target.closest("[data-param-tooltip-value]")) return;
|
|
1749
|
+
hideParamTooltip();
|
|
1750
|
+
});
|
|
1168
1751
|
document.getElementById("sidebar-search").addEventListener("input", (event) => {
|
|
1169
1752
|
sidebarSearchQuery = event.currentTarget.value || "";
|
|
1170
1753
|
renderSidebar();
|
|
@@ -1190,7 +1773,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
1190
1773
|
return;
|
|
1191
1774
|
}
|
|
1192
1775
|
|
|
1193
|
-
const requestPath = buildRequestPath(selected, path, query, values);
|
|
1776
|
+
const requestPath = buildRequestPath(selected, path, query, values, getAuthQueryParams(selected));
|
|
1194
1777
|
formatBodyJsonInput();
|
|
1195
1778
|
updateBodyJsonPresentation();
|
|
1196
1779
|
const op = selected.operation || {};
|
|
@@ -1199,7 +1782,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
1199
1782
|
const bodyInput = document.getElementById("body-input");
|
|
1200
1783
|
const body =
|
|
1201
1784
|
supportsBody && bodyInput ? bodyInput.value.trim() : "";
|
|
1202
|
-
const headers = getRequestHeadersObject();
|
|
1785
|
+
const headers = getRequestHeadersObject(selected);
|
|
1203
1786
|
if (body && !hasHeaderName(headers, "Content-Type")) {
|
|
1204
1787
|
headers["Content-Type"] = "application/json";
|
|
1205
1788
|
}
|
|
@@ -1207,7 +1790,13 @@ export function renderOpenAPIDocsHtml(
|
|
|
1207
1790
|
setSubmitLoading(true);
|
|
1208
1791
|
try {
|
|
1209
1792
|
const requestStart = performance.now();
|
|
1210
|
-
|
|
1793
|
+
applyAuthCookies(selected);
|
|
1794
|
+
const response = await fetch(requestPath, {
|
|
1795
|
+
method: selected.method,
|
|
1796
|
+
headers,
|
|
1797
|
+
body: body || undefined,
|
|
1798
|
+
credentials: "same-origin",
|
|
1799
|
+
});
|
|
1211
1800
|
const text = await response.text();
|
|
1212
1801
|
const responseTimeMs = Math.round(performance.now() - requestStart);
|
|
1213
1802
|
const contentType = response.headers.get("content-type") || "unknown";
|
|
@@ -1283,6 +1872,7 @@ export function renderOpenAPIDocsHtml(
|
|
|
1283
1872
|
|
|
1284
1873
|
document.getElementById("add-header-btn").addEventListener("click", () => {
|
|
1285
1874
|
requestHeaders.push({ key: "", value: "" });
|
|
1875
|
+
saveHeaders();
|
|
1286
1876
|
renderHeaderInputs();
|
|
1287
1877
|
updateRequestPreview();
|
|
1288
1878
|
});
|
|
@@ -1396,12 +1986,21 @@ export function renderOpenAPIDocsHtml(
|
|
|
1396
1986
|
setMobileSidebarOpen(false);
|
|
1397
1987
|
});
|
|
1398
1988
|
window.addEventListener("resize", () => {
|
|
1989
|
+
if (activeParamTooltipTrigger) {
|
|
1990
|
+
positionParamTooltip(activeParamTooltipTrigger);
|
|
1991
|
+
}
|
|
1399
1992
|
if (window.innerWidth >= 768 && isMobileSidebarOpen) {
|
|
1400
1993
|
setMobileSidebarOpen(false);
|
|
1401
1994
|
}
|
|
1402
1995
|
});
|
|
1996
|
+
window.addEventListener("scroll", () => {
|
|
1997
|
+
if (activeParamTooltipTrigger) {
|
|
1998
|
+
positionParamTooltip(activeParamTooltipTrigger);
|
|
1999
|
+
}
|
|
2000
|
+
}, true);
|
|
1403
2001
|
document.addEventListener("keydown", (event) => {
|
|
1404
2002
|
if (event.key === "Escape") {
|
|
2003
|
+
hideParamTooltip();
|
|
1405
2004
|
if (isMobileSidebarOpen) {
|
|
1406
2005
|
setMobileSidebarOpen(false);
|
|
1407
2006
|
}
|
|
@@ -1426,7 +2025,444 @@ export function renderOpenAPIDocsHtml(
|
|
|
1426
2025
|
}
|
|
1427
2026
|
});
|
|
1428
2027
|
|
|
2028
|
+
function getOperationSecurityRequirements(op) {
|
|
2029
|
+
const operationSecurity = op && op.operation && Array.isArray(op.operation.security)
|
|
2030
|
+
? op.operation.security
|
|
2031
|
+
: null;
|
|
2032
|
+
if (operationSecurity) {
|
|
2033
|
+
return operationSecurity;
|
|
2034
|
+
}
|
|
2035
|
+
return Array.isArray(spec.security) ? spec.security : [];
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
function getAuthSelectionKeyForOperation(op) {
|
|
2039
|
+
if (!op) return "";
|
|
2040
|
+
return getOperationKey(op);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
function getAuthSchemeOptionsForOperation(op) {
|
|
2044
|
+
const requirements = getOperationSecurityRequirements(op).filter((requirement) =>
|
|
2045
|
+
requirement && typeof requirement === "object" && !Array.isArray(requirement)
|
|
2046
|
+
);
|
|
2047
|
+
if (!requirements.length) return [];
|
|
2048
|
+
|
|
2049
|
+
const seen = new Set();
|
|
2050
|
+
const options = [];
|
|
2051
|
+
for (const requirement of requirements) {
|
|
2052
|
+
for (const schemeName of Object.keys(requirement)) {
|
|
2053
|
+
if (!Object.prototype.hasOwnProperty.call(authSchemes, schemeName)) continue;
|
|
2054
|
+
if (seen.has(schemeName)) continue;
|
|
2055
|
+
seen.add(schemeName);
|
|
2056
|
+
options.push(schemeName);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return options;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
function getSelectedAuthSchemeForOperation(op) {
|
|
2063
|
+
const selectionKey = getAuthSelectionKeyForOperation(op);
|
|
2064
|
+
if (!selectionKey) return null;
|
|
2065
|
+
|
|
2066
|
+
const selectedScheme = authSelectionState[selectionKey];
|
|
2067
|
+
if (!selectedScheme || typeof selectedScheme !== "string") return null;
|
|
2068
|
+
|
|
2069
|
+
const options = Object.keys(authSchemes);
|
|
2070
|
+
if (!options.includes(selectedScheme)) {
|
|
2071
|
+
delete authSelectionState[selectionKey];
|
|
2072
|
+
saveAuthSelectionState();
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
return selectedScheme;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function setSelectedAuthSchemeForOperation(op, schemeName) {
|
|
2080
|
+
const selectionKey = getAuthSelectionKeyForOperation(op);
|
|
2081
|
+
if (!selectionKey) return;
|
|
2082
|
+
|
|
2083
|
+
if (!schemeName) {
|
|
2084
|
+
delete authSelectionState[selectionKey];
|
|
2085
|
+
} else {
|
|
2086
|
+
authSelectionState[selectionKey] = schemeName;
|
|
2087
|
+
}
|
|
2088
|
+
saveAuthSelectionState();
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
function hasAuthStateForScheme(schemeName) {
|
|
2092
|
+
const scheme = authSchemes[schemeName];
|
|
2093
|
+
if (!scheme) return false;
|
|
2094
|
+
|
|
2095
|
+
const state = authState[schemeName] || {};
|
|
2096
|
+
const type = (scheme.type || "").toLowerCase();
|
|
2097
|
+
const httpScheme = (scheme.scheme || "").toLowerCase();
|
|
2098
|
+
|
|
2099
|
+
if (type === "http" && httpScheme === "basic") {
|
|
2100
|
+
return Boolean(state.username && state.password);
|
|
2101
|
+
}
|
|
2102
|
+
if (type === "http") {
|
|
2103
|
+
return Boolean(state.token);
|
|
2104
|
+
}
|
|
2105
|
+
if (type === "apikey") {
|
|
2106
|
+
return Boolean(state.value);
|
|
2107
|
+
}
|
|
2108
|
+
if (type === "oauth2" || type === "openidconnect") {
|
|
2109
|
+
return Boolean(state.token);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
function chooseOperationSecurityRequirement(op) {
|
|
2116
|
+
const requirements = getOperationSecurityRequirements(op).filter((requirement) =>
|
|
2117
|
+
requirement && typeof requirement === "object" && !Array.isArray(requirement)
|
|
2118
|
+
);
|
|
2119
|
+
if (!requirements.length) return null;
|
|
2120
|
+
|
|
2121
|
+
const selectedScheme = getSelectedAuthSchemeForOperation(op);
|
|
2122
|
+
if (selectedScheme) {
|
|
2123
|
+
const selectedRequirement = requirements.find((requirement) =>
|
|
2124
|
+
Object.prototype.hasOwnProperty.call(requirement, selectedScheme)
|
|
2125
|
+
);
|
|
2126
|
+
if (selectedRequirement) return selectedRequirement;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
let bestRequirement = null;
|
|
2130
|
+
let bestScore = -1;
|
|
2131
|
+
|
|
2132
|
+
for (const requirement of requirements) {
|
|
2133
|
+
const schemeNames = Object.keys(requirement).filter((schemeName) =>
|
|
2134
|
+
Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
|
|
2135
|
+
);
|
|
2136
|
+
if (!schemeNames.length) continue;
|
|
2137
|
+
|
|
2138
|
+
const providedCount = schemeNames.filter((schemeName) => hasAuthStateForScheme(schemeName)).length;
|
|
2139
|
+
const isComplete = providedCount === schemeNames.length;
|
|
2140
|
+
const score = isComplete ? 1000 + providedCount : providedCount;
|
|
2141
|
+
|
|
2142
|
+
if (score > bestScore) {
|
|
2143
|
+
bestScore = score;
|
|
2144
|
+
bestRequirement = requirement;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
return bestRequirement || requirements[0];
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
function getAuthSchemeNamesForOperation(op) {
|
|
2152
|
+
const schemeNames = Object.keys(authSchemes);
|
|
2153
|
+
if (!schemeNames.length) return [];
|
|
2154
|
+
|
|
2155
|
+
const selectedScheme = getSelectedAuthSchemeForOperation(op);
|
|
2156
|
+
if (selectedScheme) {
|
|
2157
|
+
const requirement = chooseOperationSecurityRequirement(op);
|
|
2158
|
+
if (requirement && Object.prototype.hasOwnProperty.call(requirement, selectedScheme)) {
|
|
2159
|
+
return Object.keys(requirement).filter((schemeName) =>
|
|
2160
|
+
Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
return [selectedScheme];
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
const requirement = chooseOperationSecurityRequirement(op);
|
|
2167
|
+
if (!requirement) return [];
|
|
2168
|
+
|
|
2169
|
+
return Object.keys(requirement).filter((schemeName) =>
|
|
2170
|
+
Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
function getAuthHeaders(op) {
|
|
2175
|
+
const headers = {};
|
|
2176
|
+
const schemeNames = getAuthSchemeNamesForOperation(op);
|
|
2177
|
+
const allSchemeNames = Object.keys(authSchemes);
|
|
2178
|
+
|
|
2179
|
+
if (!allSchemeNames.length) {
|
|
2180
|
+
const state = authState["__default__"] || {};
|
|
2181
|
+
if (state.token) headers["Authorization"] = "Bearer " + state.token;
|
|
2182
|
+
return headers;
|
|
2183
|
+
}
|
|
2184
|
+
if (!schemeNames.length) return headers;
|
|
2185
|
+
|
|
2186
|
+
for (const schemeName of schemeNames) {
|
|
2187
|
+
const scheme = authSchemes[schemeName];
|
|
2188
|
+
const state = authState[schemeName] || {};
|
|
2189
|
+
const type = (scheme.type || "").toLowerCase();
|
|
2190
|
+
const httpScheme = (scheme.scheme || "").toLowerCase();
|
|
2191
|
+
|
|
2192
|
+
if (type === "http" && httpScheme === "basic") {
|
|
2193
|
+
if (state.username && state.password) {
|
|
2194
|
+
try {
|
|
2195
|
+
headers["Authorization"] = "Basic " + btoa(state.username + ":" + state.password);
|
|
2196
|
+
} catch {}
|
|
2197
|
+
}
|
|
2198
|
+
} else if (type === "http") {
|
|
2199
|
+
if (state.token) headers["Authorization"] = "Bearer " + state.token;
|
|
2200
|
+
} else if (type === "apikey" && (scheme.in || "").toLowerCase() === "header") {
|
|
2201
|
+
if (state.value && scheme.name) headers[scheme.name] = state.value;
|
|
2202
|
+
} else if (type === "oauth2" || type === "openidconnect") {
|
|
2203
|
+
if (state.token) headers["Authorization"] = "Bearer " + state.token;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return headers;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
function getAuthQueryParams(op) {
|
|
2210
|
+
const params = {};
|
|
2211
|
+
for (const schemeName of getAuthSchemeNamesForOperation(op)) {
|
|
2212
|
+
const scheme = authSchemes[schemeName];
|
|
2213
|
+
if ((scheme.type || "").toLowerCase() === "apikey" && (scheme.in || "").toLowerCase() === "query") {
|
|
2214
|
+
const state = authState[schemeName] || {};
|
|
2215
|
+
if (state.value && scheme.name) params[scheme.name] = state.value;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
return params;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
function getAuthCookieParams(op) {
|
|
2222
|
+
const cookies = {};
|
|
2223
|
+
for (const schemeName of getAuthSchemeNamesForOperation(op)) {
|
|
2224
|
+
const scheme = authSchemes[schemeName];
|
|
2225
|
+
if ((scheme.type || "").toLowerCase() !== "apikey") continue;
|
|
2226
|
+
if ((scheme.in || "").toLowerCase() !== "cookie") continue;
|
|
2227
|
+
const state = authState[schemeName] || {};
|
|
2228
|
+
if (state.value && scheme.name) {
|
|
2229
|
+
cookies[scheme.name] = state.value;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
return cookies;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
function applyAuthCookies(op) {
|
|
2236
|
+
const cookies = getAuthCookieParams(op);
|
|
2237
|
+
for (const [name, value] of Object.entries(cookies)) {
|
|
2238
|
+
try {
|
|
2239
|
+
document.cookie = encodeURIComponent(String(name)) + "=" + encodeURIComponent(String(value)) + "; path=/";
|
|
2240
|
+
} catch {}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function renderAuthPanel() {
|
|
2245
|
+
const fields = document.getElementById("auth-fields");
|
|
2246
|
+
if (!fields) return;
|
|
2247
|
+
fields.innerHTML = "";
|
|
2248
|
+
|
|
2249
|
+
const schemeNames = Object.keys(authSchemes);
|
|
2250
|
+
const op = selected;
|
|
2251
|
+
const operationSchemeOptions = op ? getAuthSchemeOptionsForOperation(op) : [];
|
|
2252
|
+
const availableSchemeOptions = Object.keys(authSchemes);
|
|
2253
|
+
const selectedScheme = op ? getSelectedAuthSchemeForOperation(op) : null;
|
|
2254
|
+
|
|
2255
|
+
function getAuthSchemeDisplayLabel(schemeName) {
|
|
2256
|
+
const scheme = authSchemes[schemeName] || {};
|
|
2257
|
+
const type = (scheme.type || "").toLowerCase();
|
|
2258
|
+
const httpScheme = (scheme.scheme || "").toLowerCase();
|
|
2259
|
+
const location = (scheme.in || "").toLowerCase();
|
|
2260
|
+
|
|
2261
|
+
if (type === "http" && httpScheme === "basic") return "HTTP Basic";
|
|
2262
|
+
if (type === "http" && httpScheme === "bearer") return "HTTP Bearer";
|
|
2263
|
+
if (type === "http" && httpScheme === "digest") return "HTTP Digest";
|
|
2264
|
+
if (type === "http") return "HTTP " + (httpScheme || "Token");
|
|
2265
|
+
if (type === "apikey") return "API Key" + (location ? " (" + location + ")" : "");
|
|
2266
|
+
if (type === "oauth2") return "OAuth 2.0";
|
|
2267
|
+
if (type === "openidconnect") return "OpenID Connect";
|
|
2268
|
+
if (type === "mutualtls") return "Mutual TLS";
|
|
2269
|
+
return schemeName;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
function makeInput(placeholder, value, onInput, type) {
|
|
2273
|
+
const inp = document.createElement("input");
|
|
2274
|
+
inp.type = type || "text";
|
|
2275
|
+
inp.value = value || "";
|
|
2276
|
+
inp.placeholder = placeholder;
|
|
2277
|
+
inp.className = "w-full text-xs px-2.5 py-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
|
|
2278
|
+
inp.addEventListener("input", onInput);
|
|
2279
|
+
return inp;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
function makeLabel(text, small) {
|
|
2283
|
+
const el = document.createElement("p");
|
|
2284
|
+
el.className = small
|
|
2285
|
+
? "text-[10px] font-medium uppercase tracking-wider opacity-55"
|
|
2286
|
+
: "text-[10px] font-semibold uppercase tracking-wider opacity-55";
|
|
2287
|
+
el.textContent = text;
|
|
2288
|
+
return el;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
function makeField(label, input) {
|
|
2292
|
+
const wrapper = document.createElement("div");
|
|
2293
|
+
wrapper.className = "space-y-1";
|
|
2294
|
+
wrapper.appendChild(makeLabel(label, true));
|
|
2295
|
+
wrapper.appendChild(input);
|
|
2296
|
+
return wrapper;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function makeSchemeCard(title, subtitle) {
|
|
2300
|
+
const card = document.createElement("div");
|
|
2301
|
+
card.className = "space-y-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg/40 dark:bg-dark-bg/40 p-2.5";
|
|
2302
|
+
|
|
2303
|
+
const titleRow = document.createElement("div");
|
|
2304
|
+
titleRow.className = "flex items-center justify-between gap-2";
|
|
2305
|
+
|
|
2306
|
+
const heading = document.createElement("p");
|
|
2307
|
+
heading.className = "text-[11px] font-semibold tracking-wide";
|
|
2308
|
+
heading.textContent = title;
|
|
2309
|
+
titleRow.appendChild(heading);
|
|
2310
|
+
|
|
2311
|
+
if (subtitle) {
|
|
2312
|
+
const note = document.createElement("span");
|
|
2313
|
+
note.className = "text-[10px] opacity-60 font-mono";
|
|
2314
|
+
note.textContent = subtitle;
|
|
2315
|
+
titleRow.appendChild(note);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
card.appendChild(titleRow);
|
|
2319
|
+
return card;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
if (!schemeNames.length) {
|
|
2323
|
+
if (!authState["__default__"]) authState["__default__"] = {};
|
|
2324
|
+
const defaultCard = makeSchemeCard("Default Auth", "bearer");
|
|
2325
|
+
defaultCard.appendChild(makeField("Token", makeInput("Enter token…", authState["__default__"].token, function(e) {
|
|
2326
|
+
authState["__default__"].token = e.target.value;
|
|
2327
|
+
saveAuthState();
|
|
2328
|
+
updateRequestPreview();
|
|
2329
|
+
})));
|
|
2330
|
+
fields.appendChild(defaultCard);
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
if (op && availableSchemeOptions.length > 0) {
|
|
2335
|
+
const selectorWrap = document.createElement("div");
|
|
2336
|
+
selectorWrap.className = "space-y-1";
|
|
2337
|
+
selectorWrap.appendChild(makeLabel("Auth Type"));
|
|
2338
|
+
const select = document.createElement("select");
|
|
2339
|
+
select.className = "w-full text-xs px-2.5 py-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
|
|
2340
|
+
|
|
2341
|
+
const autoOption = document.createElement("option");
|
|
2342
|
+
autoOption.value = "";
|
|
2343
|
+
autoOption.textContent = "Auto";
|
|
2344
|
+
select.appendChild(autoOption);
|
|
2345
|
+
|
|
2346
|
+
for (const schemeName of availableSchemeOptions) {
|
|
2347
|
+
const option = document.createElement("option");
|
|
2348
|
+
option.value = schemeName;
|
|
2349
|
+
const isOperationScheme = operationSchemeOptions.includes(schemeName);
|
|
2350
|
+
const label = getAuthSchemeDisplayLabel(schemeName);
|
|
2351
|
+
option.textContent = isOperationScheme
|
|
2352
|
+
? label
|
|
2353
|
+
: (label + " • override");
|
|
2354
|
+
select.appendChild(option);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
select.value = selectedScheme || "";
|
|
2358
|
+
select.addEventListener("change", function(e) {
|
|
2359
|
+
setSelectedAuthSchemeForOperation(op, e.target.value || "");
|
|
2360
|
+
renderAuthPanel();
|
|
2361
|
+
updateRequestPreview();
|
|
2362
|
+
});
|
|
2363
|
+
selectorWrap.appendChild(select);
|
|
2364
|
+
fields.appendChild(selectorWrap);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
const schemesToRender = selectedScheme
|
|
2368
|
+
? [selectedScheme]
|
|
2369
|
+
: (operationSchemeOptions.length ? operationSchemeOptions : schemeNames);
|
|
2370
|
+
|
|
2371
|
+
for (const schemeName of schemesToRender) {
|
|
2372
|
+
const scheme = authSchemes[schemeName];
|
|
2373
|
+
if (!authState[schemeName]) authState[schemeName] = {};
|
|
2374
|
+
const state = authState[schemeName];
|
|
2375
|
+
const type = (scheme.type || "").toLowerCase();
|
|
2376
|
+
const httpScheme = (scheme.scheme || "").toLowerCase();
|
|
2377
|
+
const card = makeSchemeCard(getAuthSchemeDisplayLabel(schemeName), schemeName);
|
|
2378
|
+
|
|
2379
|
+
if (type === "http" && httpScheme === "basic") {
|
|
2380
|
+
card.appendChild(makeField("Username", makeInput("Username", state.username, function(e) {
|
|
2381
|
+
authState[schemeName].username = e.target.value;
|
|
2382
|
+
saveAuthState();
|
|
2383
|
+
updateRequestPreview();
|
|
2384
|
+
})));
|
|
2385
|
+
card.appendChild(makeField("Password", makeInput("Password", state.password, function(e) {
|
|
2386
|
+
authState[schemeName].password = e.target.value;
|
|
2387
|
+
saveAuthState();
|
|
2388
|
+
updateRequestPreview();
|
|
2389
|
+
}, "password")));
|
|
2390
|
+
} else if (type === "apikey") {
|
|
2391
|
+
const paramName = scheme.name || "key";
|
|
2392
|
+
const location = (scheme.in || "header").toLowerCase();
|
|
2393
|
+
card.appendChild(makeField("API Key", makeInput(paramName + " (" + location + ")", state.value, function(e) {
|
|
2394
|
+
authState[schemeName].value = e.target.value;
|
|
2395
|
+
saveAuthState();
|
|
2396
|
+
updateRequestPreview();
|
|
2397
|
+
})));
|
|
2398
|
+
} else if (type === "oauth2") {
|
|
2399
|
+
card.appendChild(makeField("Access Token", makeInput("OAuth2 access token…", state.token, function(e) {
|
|
2400
|
+
authState[schemeName].token = e.target.value;
|
|
2401
|
+
saveAuthState();
|
|
2402
|
+
updateRequestPreview();
|
|
2403
|
+
})));
|
|
2404
|
+
} else if (type === "openidconnect") {
|
|
2405
|
+
card.appendChild(makeField("ID Token / Access Token", makeInput("OpenID Connect token…", state.token, function(e) {
|
|
2406
|
+
authState[schemeName].token = e.target.value;
|
|
2407
|
+
saveAuthState();
|
|
2408
|
+
updateRequestPreview();
|
|
2409
|
+
})));
|
|
2410
|
+
} else if (type === "http" && httpScheme === "digest") {
|
|
2411
|
+
card.appendChild(makeField("Digest Credential", makeInput("Digest token…", state.token, function(e) {
|
|
2412
|
+
authState[schemeName].token = e.target.value;
|
|
2413
|
+
saveAuthState();
|
|
2414
|
+
updateRequestPreview();
|
|
2415
|
+
})));
|
|
2416
|
+
} else if (type === "http" && httpScheme === "bearer") {
|
|
2417
|
+
card.appendChild(makeField("Bearer Token", makeInput("Bearer token…", state.token, function(e) {
|
|
2418
|
+
authState[schemeName].token = e.target.value;
|
|
2419
|
+
saveAuthState();
|
|
2420
|
+
updateRequestPreview();
|
|
2421
|
+
})));
|
|
2422
|
+
} else if (type === "mutualtls") {
|
|
2423
|
+
const hint = document.createElement("p");
|
|
2424
|
+
hint.className = "text-xs opacity-70 leading-relaxed";
|
|
2425
|
+
hint.textContent = "Configured by your client certificate. No token input required.";
|
|
2426
|
+
card.appendChild(hint);
|
|
2427
|
+
} else {
|
|
2428
|
+
card.appendChild(makeField("Token", makeInput("Token…", state.token, function(e) {
|
|
2429
|
+
authState[schemeName].token = e.target.value;
|
|
2430
|
+
saveAuthState();
|
|
2431
|
+
updateRequestPreview();
|
|
2432
|
+
})));
|
|
2433
|
+
}
|
|
2434
|
+
fields.appendChild(card);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
let authPanelOpen = true;
|
|
2439
|
+
document.getElementById("auth-toggle").addEventListener("click", function() {
|
|
2440
|
+
authPanelOpen = !authPanelOpen;
|
|
2441
|
+
const fieldsEl = document.getElementById("auth-fields");
|
|
2442
|
+
const chevron = document.getElementById("auth-chevron");
|
|
2443
|
+
if (fieldsEl) fieldsEl.classList.toggle("hidden", !authPanelOpen);
|
|
2444
|
+
if (chevron) chevron.style.transform = authPanelOpen ? "" : "rotate(-90deg)";
|
|
2445
|
+
});
|
|
2446
|
+
|
|
2447
|
+
// Restore selected operation from URL hash, or set hash for the default selection
|
|
2448
|
+
var initMatch = findOpByHash(window.location.hash);
|
|
2449
|
+
if (initMatch) {
|
|
2450
|
+
selected = initMatch;
|
|
2451
|
+
} else if (selected) {
|
|
2452
|
+
history.replaceState(null, "", getOpHash(selected));
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
window.addEventListener("popstate", function() {
|
|
2456
|
+
var match = findOpByHash(window.location.hash);
|
|
2457
|
+
if (match) {
|
|
2458
|
+
selected = match;
|
|
2459
|
+
renderSidebar();
|
|
2460
|
+
renderEndpoint();
|
|
2461
|
+
}
|
|
2462
|
+
});
|
|
2463
|
+
|
|
1429
2464
|
setMobileSidebarOpen(false);
|
|
2465
|
+
renderAuthPanel();
|
|
1430
2466
|
renderSidebar();
|
|
1431
2467
|
renderEndpoint();
|
|
1432
2468
|
</script>
|