You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

711 lines
22 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<!-- External Dependencies -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--success-color: #27ae60;
--warning-color: #f39c12;
--danger-color: #e74c3c;
--light-bg: #f8f9fa;
--dark-bg: #2c3e50;
--sidebar-width: 300px;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--light-bg);
margin: 0;
padding: 0;
}
/* Main Container */
.documentation-container {
display: flex;
min-height: 100vh;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width);
background: linear-gradient(135deg, var(--dark-bg) 0%, #34495e 100%);
color: white;
position: fixed;
height: 100vh;
overflow-y: auto;
z-index: 1000;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
}
.sidebar-header {
padding: 20px;
background: rgba(0,0,0,0.2);
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.sidebar-header h3 {
margin: 0;
font-size: 1.2rem;
font-weight: 600;
}
.sidebar-header p {
margin: 5px 0 0 0;
font-size: 0.9rem;
opacity: 0.8;
}
/* App Navigation */
.app-list {
list-style: none;
padding: 0;
margin: 0;
}
.app-item {
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 20px;
cursor: pointer;
transition: all 0.3s ease;
background: transparent;
}
.app-header:hover {
background: rgba(255,255,255,0.1);
}
.app-header.active {
background: var(--secondary-color);
}
.app-name {
font-weight: 600;
font-size: 1rem;
}
.app-description {
font-size: 0.8rem;
opacity: 0.7;
margin-top: 2px;
}
.toggle-icon {
transition: transform 0.3s ease;
}
.toggle-icon.rotated {
transform: rotate(90deg);
}
/* Endpoints List */
.endpoints-list {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
background: rgba(0,0,0,0.2);
}
.endpoints-list.expanded {
max-height: 500px;
}
.endpoint-item {
padding: 10px 20px 10px 40px;
cursor: pointer;
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.endpoint-item:hover {
background: rgba(255,255,255,0.1);
border-left-color: var(--success-color);
}
.endpoint-item.active {
background: var(--success-color);
border-left-color: white;
}
/* HTTP Method Badges */
.endpoint-method {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: bold;
margin-right: 8px;
}
.method-get { background: var(--success-color); }
.method-post { background: var(--secondary-color); }
.method-put { background: var(--warning-color); }
.method-delete { background: var(--danger-color); }
.method-patch { background: #9b59b6; }
/* Main Content Area */
.main-content {
margin-left: var(--sidebar-width);
flex: 1;
padding: 30px;
background: white;
}
/* Content Header */
.content-header {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid var(--light-bg);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.content-header h1 {
color: var(--dark-bg);
margin-bottom: 10px;
font-weight: 700;
}
.content-header p {
color: #666;
font-size: 1.1rem;
margin: 0;
}
/* Action Buttons */
.action-buttons {
display: flex;
gap: 10px;
}
.btn-swagger {
background: #ff6b35;
color: white;
padding: 12px 20px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.btn-swagger:hover {
background: #e55a2b;
color: white;
text-decoration: none;
}
.btn-redoc {
background: #39b982;
color: white;
padding: 12px 20px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.btn-redoc:hover {
background: #2d8f66;
color: white;
text-decoration: none;
}
/* Mobile Toggle Button */
.mobile-toggle {
display: none;
position: fixed;
top: 20px;
left: 20px;
z-index: 1001;
background: var(--secondary-color);
color: white;
border: none;
padding: 10px;
border-radius: 6px;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
@media (max-width: 768px) {
.mobile-toggle {
display: block;
}
.sidebar {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.sidebar.mobile-open {
transform: translateX(0);
}
.main-content {
margin-left: 0;
padding: 20px 15px;
}
.header-top {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.action-buttons {
flex-direction: column;
width: 100%;
}
.btn-swagger,
.btn-redoc {
justify-content: center;
width: 100%;
}
}
/* Endpoint Documentation Styles */
.endpoint-section {
margin-bottom: 40px;
padding: 25px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.endpoint-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e9ecef;
flex-wrap: wrap;
gap: 15px;
}
.endpoint-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--dark-bg);
margin: 0;
}
.endpoint-url {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: var(--light-bg);
padding: 8px 12px;
border-radius: 6px;
font-size: 0.9rem;
color: var(--dark-bg);
border: 1px solid #dee2e6;
}
.endpoint-description {
color: #666;
margin-bottom: 25px;
line-height: 1.6;
font-size: 1rem;
}
/* Parameters Section */
.parameters-section {
margin-bottom: 25px;
}
.section-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--dark-bg);
margin-bottom: 15px;
display: flex;
align-items: center;
}
.section-title i {
margin-right: 8px;
color: var(--secondary-color);
}
.parameters-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.parameters-table th,
.parameters-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
.parameters-table th {
background: var(--light-bg);
font-weight: 600;
color: var(--dark-bg);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.parameters-table tbody tr:hover {
background: #f8f9fa;
}
.param-name {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-weight: 600;
color: var(--secondary-color);
background: #f8f9fa;
padding: 4px 8px;
border-radius: 4px;
}
.param-type {
background: #e3f2fd;
color: #1976d2;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
text-transform: uppercase;
}
.param-required {
background: #ffebee;
color: #c62828;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
text-transform: uppercase;
}
.param-optional {
background: #e8f5e8;
color: #2e7d32;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
text-transform: uppercase;
}
/* Response Examples Section */
.response-section {
margin-top: 25px;
}
.response-tabs {
display: flex;
margin-bottom: 15px;
border-bottom: 1px solid #e9ecef;
gap: 5px;
}
.response-tab {
padding: 10px 20px;
cursor: pointer;
border: none;
background: none;
color: #666;
font-weight: 500;
transition: all 0.3s ease;
border-radius: 6px 6px 0 0;
position: relative;
}
.response-tab:hover {
background: #f8f9fa;
color: var(--secondary-color);
}
.response-tab.active {
color: var(--secondary-color);
background: #f8f9fa;
border-bottom: 2px solid var(--secondary-color);
}
.json-viewer {
background: #1e1e1e;
border-radius: 8px;
padding: 20px;
margin-top: 15px;
overflow-x: auto;
border: 1px solid #e9ecef;
}
.json-viewer pre {
margin: 0;
color: #d4d4d4;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9rem;
line-height: 1.5;
}
</style>
</head>
<body>
<!-- Mobile Toggle Button -->
<button class="mobile-toggle" onclick="toggleSidebar()">
<i class="fas fa-bars"></i>
</button>
<div class="documentation-container">
<!-- Sidebar Navigation -->
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<h3><i class="fas fa-book"></i> API Documentation</h3>
<p>{{ description }}</p>
</div>
<ul class="app-list">
{% for app_key, app_data in api_structure.items %}
<li class="app-item">
<div class="app-header" onclick="toggleEndpoints('{{ app_key }}-endpoints')">
<div>
<div class="app-name">{{ app_data.name }}</div>
<div class="app-description">{{ app_data.description }}</div>
</div>
<i class="fas fa-chevron-right toggle-icon" id="{{ app_key }}-icon"></i>
</div>
<ul class="endpoints-list" id="{{ app_key }}-endpoints">
{% for endpoint in app_data.endpoints %}
<li class="endpoint-item" onclick="scrollToEndpoint('{{ app_key }}-{{ forloop.counter0 }}')">
<span class="endpoint-method method-{{ endpoint.method|lower }}">{{ endpoint.method }}</span>
{{ endpoint.name }}
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</div>
<!-- Main Content -->
<div class="main-content">
<div class="content-header">
<div class="header-top">
<div>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
<div class="action-buttons">
<a href="{% url 'schema-swagger-ui' %}" class="btn-swagger">
<i class="fas fa-code"></i>
Swagger UI
</a>
<a href="{% url 'schema-redoc' %}" class="btn-redoc">
<i class="fas fa-book"></i>
ReDoc
</a>
</div>
</div>
</div>
<!-- API Endpoints Documentation -->
{% for app_key, app_data in api_structure.items %}
{% for endpoint in app_data.endpoints %}
<div class="endpoint-section" id="{{ app_key }}-{{ forloop.counter0 }}">
<div class="endpoint-header">
<h2 class="endpoint-title">{{ endpoint.name }}</h2>
<span class="endpoint-method method-{{ endpoint.method|lower }}">{{ endpoint.method }}</span>
<code class="endpoint-url">{{ endpoint.url }}</code>
</div>
<p class="endpoint-description">{{ endpoint.description }}</p>
{% if endpoint.parameters %}
<div class="parameters-section">
<h3 class="section-title">
<i class="fas fa-cogs"></i>
Parameters
</h3>
<table class="parameters-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for param in endpoint.parameters %}
<tr>
<td><code class="param-name">{{ param.name }}</code></td>
<td><span class="param-type">{{ param.type }}</span></td>
<td>
{% if param.required %}
<span class="param-required">Required</span>
{% else %}
<span class="param-optional">Optional</span>
{% endif %}
</td>
<td>{{ param.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div class="response-section">
<h3 class="section-title">
<i class="fas fa-code"></i>
Response Examples
</h3>
<div class="response-tabs">
{% for response_type, response_data in endpoint.response_examples.items %}
<button class="response-tab {% if forloop.first %}active{% endif %}"
onclick="showResponse('{{ app_key }}-{{ forloop.parentloop.counter0 }}', '{{ response_type }}')">
{{ response_type|title }} Response
</button>
{% endfor %}
</div>
{% for response_type, response_data in endpoint.response_examples.items %}
<div class="json-viewer" id="{{ app_key }}-{{ forloop.parentloop.counter0 }}-{{ response_type }}"
style="{% if not forloop.first %}display: none;{% endif %}">
<pre><code class="language-json">{{ response_data|safe }}</code></pre>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
<!-- JavaScript for Functionality -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script>
// Toggle sidebar for mobile
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('mobile-open');
}
// Toggle endpoints list
function toggleEndpoints(endpointsId) {
const endpointsList = document.getElementById(endpointsId);
const icon = document.getElementById(endpointsId.replace('-endpoints', '-icon'));
const header = icon.closest('.app-header');
endpointsList.classList.toggle('expanded');
icon.classList.toggle('rotated');
header.classList.toggle('active');
}
// Scroll to endpoint section
function scrollToEndpoint(endpointId) {
const element = document.getElementById(endpointId);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Highlight the endpoint temporarily
element.style.boxShadow = '0 0 20px rgba(52, 152, 219, 0.3)';
setTimeout(() => {
element.style.boxShadow = '0 4px 20px rgba(0,0,0,0.08)';
}, 2000);
}
// Close sidebar on mobile after selection
if (window.innerWidth <= 768) {
document.getElementById('sidebar').classList.remove('mobile-open');
}
}
// Show response example
function showResponse(endpointId, responseType) {
// Hide all response examples for this endpoint
const allResponses = document.querySelectorAll(`[id^="${endpointId}-"]`);
allResponses.forEach(response => {
if (response.classList.contains('json-viewer')) {
response.style.display = 'none';
}
});
// Show selected response
const selectedResponse = document.getElementById(`${endpointId}-${responseType}`);
if (selectedResponse) {
selectedResponse.style.display = 'block';
}
// Update tab active state
const tabs = document.querySelectorAll('.response-tab');
tabs.forEach(tab => tab.classList.remove('active'));
event.target.classList.add('active');
}
// Format JSON in response examples
document.addEventListener('DOMContentLoaded', function() {
const jsonViewers = document.querySelectorAll('.json-viewer pre code');
jsonViewers.forEach(viewer => {
try {
const jsonText = viewer.textContent;
const jsonObj = JSON.parse(jsonText);
const formattedJson = JSON.stringify(jsonObj, null, 2);
viewer.textContent = formattedJson;
} catch (e) {
// If it's not valid JSON, leave it as is
console.log('Not valid JSON:', e);
}
});
// Initialize Prism.js for syntax highlighting
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
});
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(event) {
if (window.innerWidth <= 768) {
const sidebar = document.getElementById('sidebar');
const mobileToggle = document.querySelector('.mobile-toggle');
if (!sidebar.contains(event.target) && !mobileToggle.contains(event.target)) {
sidebar.classList.remove('mobile-open');
}
}
});
</script>
</body>
</html>