ef62b8e94a
tokens instead of hardcoded, v9 everywhere, dialog guarded, confirm->dialog, empty unified, health out of nav, swagger icons/topbar, gutters, th uppercase, content 1280 centered.
104 lines
3.6 KiB
JavaScript
104 lines
3.6 KiB
JavaScript
async function loadSpec() {
|
|
const response = await fetch('/openapi.json');
|
|
const spec = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(spec.error || 'Failed to load OpenAPI spec');
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
function methodPayload(operation) {
|
|
const body = operation.requestBody?.content?.['application/json']?.schema;
|
|
if (!body) return '';
|
|
return JSON.stringify(body.example || body.properties || body, null, 2);
|
|
}
|
|
|
|
function renderSpec(spec) {
|
|
const root = document.getElementById('api-docs');
|
|
const sectionTemplate = document.getElementById('api-section-template');
|
|
const methodTemplate = document.getElementById('api-method-template');
|
|
root.innerHTML = '';
|
|
|
|
const paths = Object.entries(spec.paths || {});
|
|
if (!paths.length) {
|
|
const section = document.createElement('section');
|
|
section.className = 'panel';
|
|
const header = document.createElement('div');
|
|
header.className = 'panel-header';
|
|
const title = document.createElement('h2');
|
|
title.textContent = 'No endpoints';
|
|
header.appendChild(title);
|
|
const body = document.createElement('div');
|
|
body.className = 'panel-body';
|
|
const empty = document.createElement('div');
|
|
empty.className = 'empty-state';
|
|
const text = document.createElement('p');
|
|
text.className = 'muted';
|
|
text.textContent = 'The OpenAPI spec contains no paths.';
|
|
empty.appendChild(text);
|
|
body.appendChild(empty);
|
|
section.append(header, body);
|
|
root.appendChild(section);
|
|
return;
|
|
}
|
|
|
|
paths.forEach(([path, methods]) => {
|
|
const section = sectionTemplate.content.firstElementChild.cloneNode(true);
|
|
section.querySelector('.api-path').textContent = path;
|
|
const methodsRoot = section.querySelector('.api-methods');
|
|
|
|
Object.entries(methods).forEach(([method, operation]) => {
|
|
const node = methodTemplate.content.firstElementChild.cloneNode(true);
|
|
node.querySelector('.api-verb').textContent = method.toUpperCase();
|
|
node.querySelector('.api-summary').textContent = operation.summary || '';
|
|
node.querySelector('.api-description').textContent = operation.description || '';
|
|
|
|
const body = methodPayload(operation);
|
|
const bodyNode = node.querySelector('.api-body');
|
|
if (body) {
|
|
bodyNode.textContent = body;
|
|
} else {
|
|
bodyNode.remove();
|
|
}
|
|
|
|
methodsRoot.appendChild(node);
|
|
});
|
|
|
|
root.appendChild(section);
|
|
});
|
|
}
|
|
|
|
loadSpec()
|
|
.then(renderSpec)
|
|
.catch((error) => {
|
|
console.error(error);
|
|
const docsEl = document.getElementById('api-docs');
|
|
docsEl.textContent = '';
|
|
const section = document.createElement('section');
|
|
section.className = 'panel';
|
|
const header = document.createElement('div');
|
|
header.className = 'panel-header';
|
|
const title = document.createElement('h2');
|
|
title.textContent = 'Failed to load API spec';
|
|
header.appendChild(title);
|
|
const body = document.createElement('div');
|
|
body.className = 'panel-body';
|
|
const empty = document.createElement('div');
|
|
empty.className = 'empty-state';
|
|
const text = document.createElement('p');
|
|
text.className = 'muted';
|
|
text.textContent = error.message;
|
|
const actions = document.createElement('div');
|
|
actions.className = 'empty-actions';
|
|
const retry = document.createElement('button');
|
|
retry.className = 'button';
|
|
retry.type = 'button';
|
|
retry.textContent = 'Retry';
|
|
retry.addEventListener('click', () => window.location.reload());
|
|
actions.appendChild(retry);
|
|
empty.append(text, actions);
|
|
body.appendChild(empty);
|
|
section.append(header, body);
|
|
docsEl.appendChild(section);
|
|
});
|