mirror of https://github.com/interlegis/sapl.git
Browse Source
Adiciona /api/doc/ com documentação auto-gerada de todos os endpoints REST, incluindo testador interativo com suporte a GET, POST, PUT, PATCH e DELETE, autenticação por token e busca por endpoints.pull/3858/head
3 changed files with 581 additions and 1 deletions
@ -0,0 +1,514 @@ |
|||||
|
{% extends "base.html" %} |
||||
|
{% load i18n %} |
||||
|
|
||||
|
{% block head_title %}API - Documentacao{% endblock %} |
||||
|
|
||||
|
{% block base_content %} |
||||
|
<style> |
||||
|
.api-doc { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } |
||||
|
.api-doc h2 { border-bottom: 2px solid #dee2e6; padding-bottom: .5rem; } |
||||
|
.api-app-section { margin-bottom: 1.5rem; } |
||||
|
.api-app-section h4 { cursor: pointer; padding: .5rem .75rem; background: #f8f9fa; border-radius: .25rem; margin-bottom: 0; } |
||||
|
.api-app-section h4:hover { background: #e9ecef; } |
||||
|
.api-app-section h4 .badge { font-size: .75rem; } |
||||
|
.api-endpoint-list { display: none; } |
||||
|
.api-endpoint-list.show { display: block; } |
||||
|
.api-endpoint { border: 1px solid #dee2e6; border-radius: .25rem; margin: .5rem 0; } |
||||
|
.api-endpoint-header { padding: .5rem .75rem; cursor: pointer; display: flex; align-items: center; gap: .5rem; background: #fff; } |
||||
|
.api-endpoint-header:hover { background: #f8f9fa; } |
||||
|
.api-endpoint-body { display: none; padding: 1rem; border-top: 1px solid #dee2e6; background: #fdfdfe; } |
||||
|
.api-endpoint-body.show { display: block; } |
||||
|
.method-badge { display: inline-block; font-size: .7rem; font-weight: 700; padding: .15rem .4rem; border-radius: .2rem; color: #fff; min-width: 50px; text-align: center; } |
||||
|
.method-get { background: #28a745; } |
||||
|
.method-post { background: #007bff; } |
||||
|
.method-put { background: #fd7e14; } |
||||
|
.method-patch { background: #6f42c1; } |
||||
|
.method-delete { background: #dc3545; } |
||||
|
.method-head, .method-options { background: #6c757d; } |
||||
|
.endpoint-url { font-family: SFMono-Regular, Menlo, Monaco, monospace; font-size: .85rem; color: #333; } |
||||
|
.response-area { background: #1e1e1e; color: #d4d4d4; border-radius: .25rem; padding: 1rem; font-family: SFMono-Regular, Menlo, Monaco, monospace; font-size: .8rem; max-height: 400px; overflow: auto; white-space: pre-wrap; word-break: break-all; } |
||||
|
.param-input { font-size: .85rem; } |
||||
|
.auth-panel { background: #f0f7ff; border: 1px solid #b8daff; border-radius: .25rem; padding: 1rem; margin-bottom: 1.5rem; } |
||||
|
.filter-input { margin-bottom: 1rem; } |
||||
|
.btn-send { min-width: 100px; } |
||||
|
.status-code { font-weight: 700; } |
||||
|
.status-code.s2xx { color: #28a745; } |
||||
|
.status-code.s4xx { color: #fd7e14; } |
||||
|
.status-code.s5xx { color: #dc3545; } |
||||
|
.spinner-border-sm { width: 1rem; height: 1rem; border-width: .15em; } |
||||
|
.readonly-methods .method-badge { opacity: .5; font-size: .6rem; } |
||||
|
.tab-pane { padding-top: 1rem; } |
||||
|
.json-editor { width: 100%; min-height: 120px; font-family: SFMono-Regular, Menlo, Monaco, monospace; font-size: .8rem; border: 1px solid #ced4da; border-radius: .25rem; padding: .5rem; } |
||||
|
.quick-stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; } |
||||
|
.quick-stats .card { min-width: 120px; text-align: center; } |
||||
|
.quick-stats .card-body { padding: .75rem; } |
||||
|
.quick-stats .display-6 { font-size: 1.5rem; font-weight: 700; } |
||||
|
</style> |
||||
|
|
||||
|
<div class="api-doc"> |
||||
|
<div class="d-flex justify-content-between align-items-center mb-3"> |
||||
|
<h2 class="mb-0">API REST - Documentacao</h2> |
||||
|
<div> |
||||
|
<a href="{% url 'sapl.api:swagger_ui_schema_api' %}" class="btn btn-sm btn-outline-secondary" target="_blank">Swagger UI</a> |
||||
|
<a href="{% url 'sapl.api:redoc_schema_api' %}" class="btn btn-sm btn-outline-secondary" target="_blank">ReDoc</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="quick-stats"> |
||||
|
<div class="card"> |
||||
|
<div class="card-body"> |
||||
|
<div class="display-6" id="stat-apps">-</div> |
||||
|
<small class="text-muted">Apps</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="card"> |
||||
|
<div class="card-body"> |
||||
|
<div class="display-6" id="stat-models">-</div> |
||||
|
<small class="text-muted">Models</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="card"> |
||||
|
<div class="card-body"> |
||||
|
<div class="display-6" id="stat-endpoints">-</div> |
||||
|
<small class="text-muted">Endpoints</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Auth Panel --> |
||||
|
<div class="auth-panel"> |
||||
|
<h5>Autenticacao</h5> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-6"> |
||||
|
<div class="form-group"> |
||||
|
<label for="auth-token"><strong>Token</strong> <small class="text-muted">(Authorization: Token xxx)</small></label> |
||||
|
<div class="input-group"> |
||||
|
<input type="text" id="auth-token" class="form-control form-control-sm" placeholder="Cole seu token aqui..."> |
||||
|
<div class="input-group-append"> |
||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="clearToken()">Limpar</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-6"> |
||||
|
<div class="form-group"> |
||||
|
<label><strong>Obter token</strong></label> |
||||
|
<div class="input-group"> |
||||
|
<input type="text" id="auth-username" class="form-control form-control-sm" placeholder="Usuario"> |
||||
|
<input type="password" id="auth-password" class="form-control form-control-sm" placeholder="Senha"> |
||||
|
<div class="input-group-append"> |
||||
|
<button class="btn btn-sm btn-primary" onclick="obtainToken()">Obter Token</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div id="auth-message" class="mt-2" style="display:none;"></div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Search --> |
||||
|
<div class="filter-input"> |
||||
|
<input type="text" id="search-endpoints" class="form-control" placeholder="Buscar endpoints... (ex: parlamentar, sessao, materia)"> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Endpoints --> |
||||
|
<div id="api-endpoints-container"> |
||||
|
<div class="text-center py-5"> |
||||
|
<div class="spinner-border" role="status"></div> |
||||
|
<p class="mt-2 text-muted">Carregando endpoints...</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<script> |
||||
|
document.addEventListener('DOMContentLoaded', function() { |
||||
|
fetch('{% url "sapl.api:api_doc_data" %}') |
||||
|
.then(r => r.json()) |
||||
|
.then(data => renderEndpoints(data)) |
||||
|
.catch(err => { |
||||
|
document.getElementById('api-endpoints-container').innerHTML = |
||||
|
'<div class="alert alert-danger">Erro ao carregar endpoints: ' + err.message + '</div>'; |
||||
|
}); |
||||
|
|
||||
|
document.getElementById('search-endpoints').addEventListener('input', function() { |
||||
|
filterEndpoints(this.value.toLowerCase()); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
function getBaseUrl() { |
||||
|
return window.location.origin; |
||||
|
} |
||||
|
|
||||
|
function getAuthHeaders() { |
||||
|
var headers = {'Content-Type': 'application/json'}; |
||||
|
var token = document.getElementById('auth-token').value.trim(); |
||||
|
if (token) { |
||||
|
headers['Authorization'] = 'Token ' + token; |
||||
|
} |
||||
|
return headers; |
||||
|
} |
||||
|
|
||||
|
function obtainToken() { |
||||
|
var username = document.getElementById('auth-username').value; |
||||
|
var password = document.getElementById('auth-password').value; |
||||
|
var msgEl = document.getElementById('auth-message'); |
||||
|
|
||||
|
fetch(getBaseUrl() + '/api/auth/token', { |
||||
|
method: 'POST', |
||||
|
headers: {'Content-Type': 'application/json'}, |
||||
|
body: JSON.stringify({username: username, password: password}) |
||||
|
}) |
||||
|
.then(r => r.json().then(data => ({status: r.status, data: data}))) |
||||
|
.then(result => { |
||||
|
if (result.data.token) { |
||||
|
document.getElementById('auth-token').value = result.data.token; |
||||
|
msgEl.className = 'mt-2 alert alert-success'; |
||||
|
msgEl.textContent = 'Token obtido com sucesso!'; |
||||
|
} else { |
||||
|
msgEl.className = 'mt-2 alert alert-danger'; |
||||
|
msgEl.textContent = 'Erro: ' + JSON.stringify(result.data); |
||||
|
} |
||||
|
msgEl.style.display = 'block'; |
||||
|
setTimeout(function() { msgEl.style.display = 'none'; }, 5000); |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
msgEl.className = 'mt-2 alert alert-danger'; |
||||
|
msgEl.textContent = 'Erro de conexao: ' + err.message; |
||||
|
msgEl.style.display = 'block'; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function clearToken() { |
||||
|
document.getElementById('auth-token').value = ''; |
||||
|
} |
||||
|
|
||||
|
function renderEndpoints(data) { |
||||
|
var container = document.getElementById('api-endpoints-container'); |
||||
|
var html = ''; |
||||
|
var totalModels = 0; |
||||
|
var totalEndpoints = 0; |
||||
|
var appCount = 0; |
||||
|
|
||||
|
var apps = data.apps; |
||||
|
var appNames = Object.keys(apps).sort(); |
||||
|
|
||||
|
appNames.forEach(function(appName) { |
||||
|
appCount++; |
||||
|
var models = apps[appName]; |
||||
|
var modelNames = Object.keys(models).sort(); |
||||
|
var modelCount = modelNames.length; |
||||
|
totalModels += modelCount; |
||||
|
|
||||
|
html += '<div class="api-app-section" data-app="' + appName + '">'; |
||||
|
html += '<h4 onclick="toggleApp(this)">'; |
||||
|
html += '<i class="fa fa-chevron-right mr-2"></i>'; |
||||
|
html += appName; |
||||
|
html += ' <span class="badge badge-secondary">' + modelCount + ' models</span>'; |
||||
|
html += '</h4>'; |
||||
|
html += '<div class="api-endpoint-list">'; |
||||
|
|
||||
|
modelNames.forEach(function(modelName) { |
||||
|
var info = models[modelName]; |
||||
|
var methods = info.methods || ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; |
||||
|
var url = info.url; |
||||
|
var actions = info.actions || []; |
||||
|
totalEndpoints++; |
||||
|
|
||||
|
html += '<div class="api-endpoint" data-search="' + appName + ' ' + modelName + ' ' + url + '">'; |
||||
|
|
||||
|
// Header |
||||
|
html += '<div class="api-endpoint-header" onclick="toggleEndpoint(this)">'; |
||||
|
methods.forEach(function(m) { |
||||
|
html += '<span class="method-badge method-' + m.toLowerCase() + '">' + m + '</span>'; |
||||
|
}); |
||||
|
html += '<span class="endpoint-url">/api/' + url + '/</span>'; |
||||
|
html += '<span class="text-muted ml-auto" style="font-size:.8rem;">' + modelName + '</span>'; |
||||
|
html += '</div>'; |
||||
|
|
||||
|
// Body |
||||
|
html += '<div class="api-endpoint-body">'; |
||||
|
html += buildEndpointTester(appName, modelName, url, methods, actions); |
||||
|
html += '</div>'; |
||||
|
|
||||
|
html += '</div>'; |
||||
|
|
||||
|
// Custom actions |
||||
|
actions.forEach(function(act) { |
||||
|
totalEndpoints++; |
||||
|
var actMethods = act.methods || ['GET']; |
||||
|
var actUrl = act.url; |
||||
|
|
||||
|
html += '<div class="api-endpoint" data-search="' + appName + ' ' + modelName + ' ' + actUrl + ' ' + act.name + '">'; |
||||
|
html += '<div class="api-endpoint-header" onclick="toggleEndpoint(this)">'; |
||||
|
actMethods.forEach(function(m) { |
||||
|
html += '<span class="method-badge method-' + m.toLowerCase() + '">' + m + '</span>'; |
||||
|
}); |
||||
|
html += '<span class="endpoint-url">/api/' + actUrl + '</span>'; |
||||
|
html += '<span class="badge badge-info ml-2">action</span>'; |
||||
|
html += '<span class="text-muted ml-auto" style="font-size:.8rem;">' + act.name + '</span>'; |
||||
|
html += '</div>'; |
||||
|
|
||||
|
html += '<div class="api-endpoint-body">'; |
||||
|
html += buildActionTester(actUrl, actMethods); |
||||
|
html += '</div>'; |
||||
|
|
||||
|
html += '</div>'; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
html += '</div></div>'; |
||||
|
}); |
||||
|
|
||||
|
container.innerHTML = html; |
||||
|
|
||||
|
document.getElementById('stat-apps').textContent = appCount; |
||||
|
document.getElementById('stat-models').textContent = totalModels; |
||||
|
document.getElementById('stat-endpoints').textContent = totalEndpoints; |
||||
|
} |
||||
|
|
||||
|
function buildEndpointTester(appName, modelName, url, methods, actions) { |
||||
|
var id = appName + '_' + modelName; |
||||
|
var fullUrl = '/api/' + url + '/'; |
||||
|
|
||||
|
var h = ''; |
||||
|
h += '<ul class="nav nav-tabs" role="tablist">'; |
||||
|
h += '<li class="nav-item"><a class="nav-link active" data-toggle="tab" href="#tab-list-' + id + '">Listar (GET)</a></li>'; |
||||
|
if (methods.indexOf('POST') !== -1) { |
||||
|
h += '<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-create-' + id + '">Criar (POST)</a></li>'; |
||||
|
} |
||||
|
if (methods.indexOf('PUT') !== -1) { |
||||
|
h += '<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-update-' + id + '">Atualizar (PUT)</a></li>'; |
||||
|
} |
||||
|
if (methods.indexOf('DELETE') !== -1) { |
||||
|
h += '<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#tab-delete-' + id + '">Deletar (DELETE)</a></li>'; |
||||
|
} |
||||
|
h += '</ul>'; |
||||
|
|
||||
|
h += '<div class="tab-content">'; |
||||
|
|
||||
|
// GET tab |
||||
|
h += '<div class="tab-pane active" id="tab-list-' + id + '">'; |
||||
|
h += '<div class="form-row align-items-end mb-2">'; |
||||
|
h += '<div class="col-md-2"><label class="mb-0"><small>ID (opcional)</small></label>'; |
||||
|
h += '<input type="text" class="form-control form-control-sm param-input" id="pk-' + id + '" placeholder="pk"></div>'; |
||||
|
h += '<div class="col-md-3"><label class="mb-0"><small>Filtros</small></label>'; |
||||
|
h += '<input type="text" class="form-control form-control-sm param-input" id="filter-' + id + '" placeholder="campo=valor&campo2=valor2"></div>'; |
||||
|
h += '<div class="col-md-2"><label class="mb-0"><small>Pagina</small></label>'; |
||||
|
h += '<input type="number" class="form-control form-control-sm param-input" id="page-' + id + '" placeholder="1" value="1" min="1"></div>'; |
||||
|
h += '<div class="col-md-2"><label class="mb-0"><small>Itens/pag</small></label>'; |
||||
|
h += '<input type="number" class="form-control form-control-sm param-input" id="psize-' + id + '" value="10" min="1" max="100"></div>'; |
||||
|
h += '<div class="col-md-3">'; |
||||
|
h += '<button class="btn btn-sm btn-success btn-send" onclick="sendGet(\'' + id + '\', \'' + fullUrl + '\')">Enviar GET</button></div>'; |
||||
|
h += '</div>'; |
||||
|
h += '<div class="response-area" id="resp-list-' + id + '">Clique em "Enviar GET" para testar...</div>'; |
||||
|
h += '</div>'; |
||||
|
|
||||
|
// POST tab |
||||
|
if (methods.indexOf('POST') !== -1) { |
||||
|
h += '<div class="tab-pane" id="tab-create-' + id + '">'; |
||||
|
h += '<p class="text-muted"><small>Envie um JSON com os campos do model. Os campos obrigatorios variam por model.</small></p>'; |
||||
|
h += '<textarea class="json-editor" id="body-create-' + id + '" placeholder=\'{"campo": "valor"}\'></textarea>'; |
||||
|
h += '<button class="btn btn-sm btn-primary btn-send mt-2" onclick="sendPost(\'' + id + '\', \'' + fullUrl + '\')">Enviar POST</button>'; |
||||
|
h += '<div class="response-area mt-2" id="resp-create-' + id + '">...</div>'; |
||||
|
h += '</div>'; |
||||
|
} |
||||
|
|
||||
|
// PUT tab |
||||
|
if (methods.indexOf('PUT') !== -1) { |
||||
|
h += '<div class="tab-pane" id="tab-update-' + id + '">'; |
||||
|
h += '<div class="form-row mb-2">'; |
||||
|
h += '<div class="col-md-2"><label class="mb-0"><small>ID (obrigatorio)</small></label>'; |
||||
|
h += '<input type="text" class="form-control form-control-sm" id="pk-update-' + id + '" placeholder="pk"></div>'; |
||||
|
h += '</div>'; |
||||
|
h += '<textarea class="json-editor" id="body-update-' + id + '" placeholder=\'{"campo": "novo_valor"}\'></textarea>'; |
||||
|
h += '<button class="btn btn-sm btn-warning btn-send mt-2" onclick="sendPut(\'' + id + '\', \'' + fullUrl + '\')">Enviar PUT</button>'; |
||||
|
h += '<button class="btn btn-sm btn-outline-secondary btn-send mt-2 ml-1" onclick="sendPatch(\'' + id + '\', \'' + fullUrl + '\')">PATCH</button>'; |
||||
|
h += '<div class="response-area mt-2" id="resp-update-' + id + '">...</div>'; |
||||
|
h += '</div>'; |
||||
|
} |
||||
|
|
||||
|
// DELETE tab |
||||
|
if (methods.indexOf('DELETE') !== -1) { |
||||
|
h += '<div class="tab-pane" id="tab-delete-' + id + '">'; |
||||
|
h += '<div class="form-row align-items-end mb-2">'; |
||||
|
h += '<div class="col-md-3"><label class="mb-0"><small>ID (obrigatorio)</small></label>'; |
||||
|
h += '<input type="text" class="form-control form-control-sm" id="pk-delete-' + id + '" placeholder="pk"></div>'; |
||||
|
h += '<div class="col-md-3">'; |
||||
|
h += '<button class="btn btn-sm btn-danger btn-send" onclick="sendDelete(\'' + id + '\', \'' + fullUrl + '\')">Enviar DELETE</button></div>'; |
||||
|
h += '</div>'; |
||||
|
h += '<div class="response-area mt-2" id="resp-delete-' + id + '">...</div>'; |
||||
|
h += '</div>'; |
||||
|
} |
||||
|
|
||||
|
h += '</div>'; |
||||
|
return h; |
||||
|
} |
||||
|
|
||||
|
function buildActionTester(actUrl, methods) { |
||||
|
var id = actUrl.replace(/[^a-zA-Z0-9]/g, '_'); |
||||
|
var fullUrl = '/api/' + actUrl; |
||||
|
var method = methods[0] || 'GET'; |
||||
|
|
||||
|
var h = ''; |
||||
|
h += '<div class="form-row align-items-end mb-2">'; |
||||
|
h += '<div class="col-md-6"><label class="mb-0"><small>URL completa</small></label>'; |
||||
|
h += '<input type="text" class="form-control form-control-sm" id="url-' + id + '" value="' + fullUrl + '"></div>'; |
||||
|
h += '<div class="col-md-3"><label class="mb-0"><small>Filtros (query string)</small></label>'; |
||||
|
h += '<input type="text" class="form-control form-control-sm" id="qs-' + id + '" placeholder="param=valor"></div>'; |
||||
|
h += '<div class="col-md-3">'; |
||||
|
|
||||
|
if (method === 'GET') { |
||||
|
h += '<button class="btn btn-sm btn-success btn-send" onclick="sendAction(\'' + id + '\', \'GET\')">GET</button>'; |
||||
|
} else { |
||||
|
h += '<button class="btn btn-sm btn-primary btn-send" onclick="sendAction(\'' + id + '\', \'' + method + '\')"> ' + method + '</button>'; |
||||
|
} |
||||
|
h += '</div></div>'; |
||||
|
|
||||
|
if (method !== 'GET') { |
||||
|
h += '<textarea class="json-editor" id="body-' + id + '" placeholder=\'{"campo": "valor"}\'></textarea>'; |
||||
|
} |
||||
|
|
||||
|
h += '<div class="response-area mt-2" id="resp-' + id + '">...</div>'; |
||||
|
return h; |
||||
|
} |
||||
|
|
||||
|
// API call functions |
||||
|
function sendGet(id, url) { |
||||
|
var pk = document.getElementById('pk-' + id).value.trim(); |
||||
|
var filter = document.getElementById('filter-' + id).value.trim(); |
||||
|
var page = document.getElementById('page-' + id).value || '1'; |
||||
|
var psize = document.getElementById('psize-' + id).value || '10'; |
||||
|
|
||||
|
var fullUrl = getBaseUrl() + url; |
||||
|
if (pk) fullUrl += pk + '/'; |
||||
|
|
||||
|
var params = 'page=' + page + '&page_size=' + psize; |
||||
|
if (filter) params += '&' + filter; |
||||
|
if (!pk) fullUrl += '?' + params; |
||||
|
|
||||
|
doFetch('resp-list-' + id, fullUrl, 'GET'); |
||||
|
} |
||||
|
|
||||
|
function sendPost(id, url) { |
||||
|
var body = document.getElementById('body-create-' + id).value; |
||||
|
doFetch('resp-create-' + id, getBaseUrl() + url, 'POST', body); |
||||
|
} |
||||
|
|
||||
|
function sendPut(id, url) { |
||||
|
var pk = document.getElementById('pk-update-' + id).value.trim(); |
||||
|
if (!pk) { alert('Informe o ID para atualizar.'); return; } |
||||
|
var body = document.getElementById('body-update-' + id).value; |
||||
|
doFetch('resp-update-' + id, getBaseUrl() + url + pk + '/', 'PUT', body); |
||||
|
} |
||||
|
|
||||
|
function sendPatch(id, url) { |
||||
|
var pk = document.getElementById('pk-update-' + id).value.trim(); |
||||
|
if (!pk) { alert('Informe o ID para atualizar.'); return; } |
||||
|
var body = document.getElementById('body-update-' + id).value; |
||||
|
doFetch('resp-update-' + id, getBaseUrl() + url + pk + '/', 'PATCH', body); |
||||
|
} |
||||
|
|
||||
|
function sendDelete(id, url) { |
||||
|
var pk = document.getElementById('pk-delete-' + id).value.trim(); |
||||
|
if (!pk) { alert('Informe o ID para deletar.'); return; } |
||||
|
if (!confirm('Confirma exclusao do registro #' + pk + '?')) return; |
||||
|
doFetch('resp-delete-' + id, getBaseUrl() + url + pk + '/', 'DELETE'); |
||||
|
} |
||||
|
|
||||
|
function sendAction(id, method) { |
||||
|
var url = document.getElementById('url-' + id).value.trim(); |
||||
|
var qs = document.getElementById('qs-' + id).value.trim(); |
||||
|
var fullUrl = getBaseUrl() + url; |
||||
|
if (qs) fullUrl += (fullUrl.indexOf('?') === -1 ? '?' : '&') + qs; |
||||
|
|
||||
|
var body = null; |
||||
|
var bodyEl = document.getElementById('body-' + id); |
||||
|
if (bodyEl) body = bodyEl.value; |
||||
|
|
||||
|
doFetch('resp-' + id, fullUrl, method, body); |
||||
|
} |
||||
|
|
||||
|
function doFetch(respId, url, method, body) { |
||||
|
var respEl = document.getElementById(respId); |
||||
|
respEl.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Enviando...'; |
||||
|
|
||||
|
var opts = { |
||||
|
method: method, |
||||
|
headers: getAuthHeaders() |
||||
|
}; |
||||
|
|
||||
|
if (body && method !== 'GET' && method !== 'DELETE') { |
||||
|
try { |
||||
|
opts.body = JSON.stringify(JSON.parse(body)); |
||||
|
} catch(e) { |
||||
|
opts.body = body; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var startTime = Date.now(); |
||||
|
|
||||
|
fetch(url, opts) |
||||
|
.then(function(r) { |
||||
|
var elapsed = Date.now() - startTime; |
||||
|
var statusClass = r.status < 300 ? 's2xx' : (r.status < 500 ? 's4xx' : 's5xx'); |
||||
|
|
||||
|
return r.text().then(function(text) { |
||||
|
var formatted; |
||||
|
try { |
||||
|
formatted = JSON.stringify(JSON.parse(text), null, 2); |
||||
|
} catch(e) { |
||||
|
formatted = text; |
||||
|
} |
||||
|
|
||||
|
respEl.innerHTML = '<span class="status-code ' + statusClass + '">' + |
||||
|
method + ' ' + r.status + ' ' + r.statusText + '</span>' + |
||||
|
' <span class="text-muted">(' + elapsed + 'ms)</span>\n' + |
||||
|
'<span class="text-muted">' + url + '</span>\n\n' + formatted; |
||||
|
}); |
||||
|
}) |
||||
|
.catch(function(err) { |
||||
|
respEl.innerHTML = '<span class="status-code s5xx">ERRO</span> ' + err.message; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function toggleApp(el) { |
||||
|
var list = el.nextElementSibling; |
||||
|
var icon = el.querySelector('i'); |
||||
|
list.classList.toggle('show'); |
||||
|
icon.classList.toggle('fa-chevron-right'); |
||||
|
icon.classList.toggle('fa-chevron-down'); |
||||
|
} |
||||
|
|
||||
|
function toggleEndpoint(el) { |
||||
|
el.nextElementSibling.classList.toggle('show'); |
||||
|
} |
||||
|
|
||||
|
function filterEndpoints(query) { |
||||
|
if (!query) { |
||||
|
document.querySelectorAll('.api-app-section').forEach(function(s) { s.style.display = ''; }); |
||||
|
document.querySelectorAll('.api-endpoint').forEach(function(e) { e.style.display = ''; }); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
document.querySelectorAll('.api-app-section').forEach(function(section) { |
||||
|
var hasMatch = false; |
||||
|
section.querySelectorAll('.api-endpoint').forEach(function(ep) { |
||||
|
var searchText = ep.getAttribute('data-search') || ''; |
||||
|
if (searchText.toLowerCase().indexOf(query) !== -1) { |
||||
|
ep.style.display = ''; |
||||
|
hasMatch = true; |
||||
|
} else { |
||||
|
ep.style.display = 'none'; |
||||
|
} |
||||
|
}); |
||||
|
section.style.display = hasMatch ? '' : 'none'; |
||||
|
if (hasMatch) { |
||||
|
section.querySelector('.api-endpoint-list').classList.add('show'); |
||||
|
var icon = section.querySelector('h4 i'); |
||||
|
icon.classList.remove('fa-chevron-right'); |
||||
|
icon.classList.add('fa-chevron-down'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
{% endblock base_content %} |
||||
Loading…
Reference in new issue