first commit
This commit is contained in:
625
templates/api.html
Normal file
625
templates/api.html
Normal file
@@ -0,0 +1,625 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}API Documentation - {{ app_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
|
||||
<div class="mb-4">
|
||||
<h2>API Documentation</h2>
|
||||
<p class="lead">RESTful API for programmatic access to geo-blocking configuration generation.</p>
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>Base URL:</strong> <code id="baseUrl"></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint 1: Get Countries -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint1')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-info me-2">GET</span>
|
||||
<code class="api-path">/api/countries</code>
|
||||
<span class="ms-3 text-muted">Get available countries</span>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body collapse" id="endpoint1">
|
||||
<h6 class="fw-bold">Description</h6>
|
||||
<p>Returns a list of all available countries with their ISO codes and flag emojis.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Schema</h6>
|
||||
<pre><code>{
|
||||
"success": <span class="text-success">true</span>,
|
||||
"countries": [
|
||||
{
|
||||
"code": <span class="text-warning">"CN"</span>,
|
||||
"name": <span class="text-warning">"China"</span>,
|
||||
"flag": <span class="text-warning">"🇨🇳"</span>
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Try it out</h6>
|
||||
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('countries')">
|
||||
<i class="fas fa-play me-1"></i>Execute
|
||||
</button>
|
||||
|
||||
<div id="response-countries" class="mt-3" style="display:none;">
|
||||
<h6 class="fw-bold">Response</h6>
|
||||
<pre><code id="response-countries-body"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint 2: Database Status -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint2')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-info me-2">GET</span>
|
||||
<code class="api-path">/api/database/status</code>
|
||||
<span class="ms-3 text-muted">Check database status</span>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body collapse" id="endpoint2">
|
||||
<h6 class="fw-bold">Description</h6>
|
||||
<p>Returns the current status of the MaxMind GeoIP database, including last update time and whether an update is needed.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Schema</h6>
|
||||
<pre><code>{
|
||||
"success": <span class="text-success">true</span>,
|
||||
"exists": <span class="text-success">true</span>,
|
||||
"needs_update": <span class="text-danger">false</span>,
|
||||
"last_update": <span class="text-warning">"2026-02-10T08:00:00"</span>,
|
||||
"file_size": <span class="text-info">5242880</span>,
|
||||
"auto_update": <span class="text-success">true</span>
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Try it out</h6>
|
||||
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('database/status')">
|
||||
<i class="fas fa-play me-1"></i>Execute
|
||||
</button>
|
||||
|
||||
<div id="response-database-status" class="mt-3" style="display:none;">
|
||||
<h6 class="fw-bold">Response</h6>
|
||||
<pre><code id="response-database-status-body"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint 3: Update Database -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint3')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-success me-2">POST</span>
|
||||
<code class="api-path">/api/database/update</code>
|
||||
<span class="ms-3 text-muted">Update database manually</span>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body collapse" id="endpoint3">
|
||||
<h6 class="fw-bold">Description</h6>
|
||||
<p>Manually triggers a download and update of the MaxMind GeoIP database from configured sources.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Schema</h6>
|
||||
<pre><code>{
|
||||
"success": <span class="text-success">true</span>,
|
||||
"url": <span class="text-warning">"https://github.com/..."</span>,
|
||||
"size": <span class="text-info">5242880</span>
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Try it out</h6>
|
||||
<button class="btn btn-sm btn-success" onclick="tryEndpoint('database/update', 'POST')">
|
||||
<i class="fas fa-play me-1"></i>Execute
|
||||
</button>
|
||||
|
||||
<div id="response-database-update" class="mt-3" style="display:none;">
|
||||
<h6 class="fw-bold">Response</h6>
|
||||
<pre><code id="response-database-update-body"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint 4: Progress Status -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint4')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-info me-2">GET</span>
|
||||
<code class="api-path">/api/progress</code>
|
||||
<span class="ms-3 text-muted">Get current generation progress</span>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body collapse" id="endpoint4">
|
||||
<h6 class="fw-bold">Description</h6>
|
||||
<p>Returns the current progress status of any active configuration generation process. Poll this endpoint to monitor long-running operations.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Schema</h6>
|
||||
<pre><code>{
|
||||
"active": <span class="text-success">true</span>,
|
||||
"message": <span class="text-warning">"[1/3] CN: Scanning MaxMind: 234 networks found"</span>,
|
||||
"progress": <span class="text-info">30</span>,
|
||||
"total": <span class="text-info">100</span>
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Fields</h6>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>active</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Whether a generation process is currently active</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>message</code></td>
|
||||
<td>string</td>
|
||||
<td>Current progress message with detailed status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>progress</code></td>
|
||||
<td>integer</td>
|
||||
<td>Current progress value (0-100)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total progress value (always 100)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6 class="fw-bold mt-3">Try it out</h6>
|
||||
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('progress')">
|
||||
<i class="fas fa-play me-1"></i>Execute
|
||||
</button>
|
||||
|
||||
<div id="response-progress" class="mt-3" style="display:none;">
|
||||
<h6 class="fw-bold">Response</h6>
|
||||
<pre><code id="response-progress-body"></code></pre>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mt-3">Polling Example</h6>
|
||||
<pre><code>// Poll every 500ms during generation
|
||||
const pollProgress = setInterval(async () => {
|
||||
const response = await fetch('/api/progress');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.active) {
|
||||
console.log(`Progress: ${data.progress}% - ${data.message}`);
|
||||
} else {
|
||||
clearInterval(pollProgress);
|
||||
console.log('Generation complete!');
|
||||
}
|
||||
}, 500);</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint 5: Generate Preview -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint5')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-success me-2">POST</span>
|
||||
<code class="api-path">/api/generate/preview</code>
|
||||
<span class="ms-3 text-muted">Preview configuration (JSON response)</span>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body collapse" id="endpoint5">
|
||||
<h6 class="fw-bold">Description</h6>
|
||||
<p>Generates configuration and returns it as JSON (instead of file download). Perfect for previewing or integrating into other applications.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Request Body</h6>
|
||||
<pre><code>{
|
||||
"countries": [<span class="text-warning">"CN"</span>, <span class="text-warning">"RU"</span>],
|
||||
"app_type": <span class="text-warning">"nginx"</span>,
|
||||
"app_variant": <span class="text-warning">"map"</span>,
|
||||
"aggregate": <span class="text-success">true</span>,
|
||||
"use_cache": <span class="text-success">true</span>
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Parameters</h6>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>countries</code></td>
|
||||
<td>array</td>
|
||||
<td><span class="badge bg-danger">required</span></td>
|
||||
<td>List of ISO 3166-1 alpha-2 country codes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>app_type</code></td>
|
||||
<td>string</td>
|
||||
<td><span class="badge bg-danger">required</span></td>
|
||||
<td>One of: <code>nginx</code>, <code>apache</code>, <code>haproxy</code>, <code>raw-cidr</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>app_variant</code></td>
|
||||
<td>string</td>
|
||||
<td><span class="badge bg-danger">required</span></td>
|
||||
<td>Configuration style (depends on app_type)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>aggregate</code></td>
|
||||
<td>boolean</td>
|
||||
<td><span class="badge bg-secondary">optional</span></td>
|
||||
<td>Aggregate IP networks to reduce count (default: true)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>use_cache</code></td>
|
||||
<td>boolean</td>
|
||||
<td><span class="badge bg-secondary">optional</span></td>
|
||||
<td>Use Redis cache if available (default: true). Set to <code>false</code> to force fresh data from SQLite/MaxMind</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Schema</h6>
|
||||
<pre><code>{
|
||||
"success": <span class="text-success">true</span>,
|
||||
"config": <span class="text-warning">"# Nginx Map Module Configuration\n..."</span>,
|
||||
"stats": {
|
||||
"countries": <span class="text-info">2</span>,
|
||||
"total_networks": <span class="text-info">4567</span>,
|
||||
"per_country": {
|
||||
"CN": <span class="text-info">2834</span>,
|
||||
"RU": <span class="text-info">1733</span>
|
||||
}
|
||||
},
|
||||
"from_cache": <span class="text-success">true</span>,
|
||||
"cache_type": <span class="text-warning">"redis"</span>,
|
||||
"generated_at": <span class="text-warning">"2026-02-16T10:30:00"</span>
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Fields</h6>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>from_cache</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Whether the config was served from cache or freshly generated</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cache_type</code></td>
|
||||
<td>string</td>
|
||||
<td><code>redis</code> (from Redis cache) or <code>sqlite</code> (from SQLite/fresh scan)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>generated_at</code></td>
|
||||
<td>string</td>
|
||||
<td>ISO 8601 timestamp when the config was generated</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6 class="fw-bold mt-3">cURL Examples</h6>
|
||||
|
||||
<p class="mb-2"><strong>With cache (default):</strong></p>
|
||||
<pre><code>curl -X POST <span id="curlUrl1"></span>/api/generate/preview \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"countries": ["CN", "RU"],
|
||||
"app_type": "nginx",
|
||||
"app_variant": "map",
|
||||
"aggregate": true,
|
||||
"use_cache": true
|
||||
}' | jq .</code></pre>
|
||||
|
||||
<p class="mb-2 mt-3"><strong>Force fresh data (bypass cache):</strong></p>
|
||||
<pre><code>curl -X POST <span id="curlUrl1b"></span>/api/generate/preview \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"countries": ["CN", "RU"],
|
||||
"app_type": "nginx",
|
||||
"app_variant": "map",
|
||||
"aggregate": true,
|
||||
"use_cache": false
|
||||
}' | jq .</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint 6: Generate Raw CIDR -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint6')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-success me-2">POST</span>
|
||||
<code class="api-path">/api/generate/raw</code>
|
||||
<span class="ms-3 text-muted">Generate raw CIDR blocklist</span>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body collapse" id="endpoint6">
|
||||
<h6 class="fw-bold">Description</h6>
|
||||
<p>Generates a raw CIDR blocklist without application-specific configuration. Perfect for iptables, fail2ban, or custom implementations.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Request Body</h6>
|
||||
<pre><code>{
|
||||
"countries": [<span class="text-warning">"CN"</span>, <span class="text-warning">"RU"</span>],
|
||||
"app_variant": <span class="text-warning">"txt"</span>,
|
||||
"aggregate": <span class="text-success">true</span>,
|
||||
"use_cache": <span class="text-success">true</span>
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Parameters</h6>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>countries</code></td>
|
||||
<td>array</td>
|
||||
<td><span class="badge bg-danger">required</span></td>
|
||||
<td>List of ISO 3166-1 alpha-2 country codes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>app_variant</code></td>
|
||||
<td>string</td>
|
||||
<td><span class="badge bg-secondary">optional</span></td>
|
||||
<td>Output format: <code>txt</code> (default) or <code>csv</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>aggregate</code></td>
|
||||
<td>boolean</td>
|
||||
<td><span class="badge bg-secondary">optional</span></td>
|
||||
<td>Aggregate IP networks (default: true)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>use_cache</code></td>
|
||||
<td>boolean</td>
|
||||
<td><span class="badge bg-secondary">optional</span></td>
|
||||
<td>Use Redis cache if available (default: true)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response</h6>
|
||||
<p>Returns plain text file with CIDR blocks (one per line) or CSV with CIDR and country columns.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Headers</h6>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>X-From-Cache</code></td>
|
||||
<td><code>true</code> or <code>false</code> - indicates if served from Redis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>X-Cache-Type</code></td>
|
||||
<td><code>redis</code> or <code>sqlite</code> - data source type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>X-Generated-At</code></td>
|
||||
<td>Timestamp when config was generated</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6 class="fw-bold mt-3">cURL Examples</h6>
|
||||
|
||||
<p class="mb-2"><strong>With cache (faster):</strong></p>
|
||||
<pre><code>curl -X POST <span id="curlUrl2"></span>/api/generate/raw \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"countries": ["CN", "RU"],
|
||||
"app_variant": "txt",
|
||||
"aggregate": true,
|
||||
"use_cache": true
|
||||
}' \
|
||||
-o blocklist.txt</code></pre>
|
||||
|
||||
<p class="mb-2 mt-3"><strong>Force fresh data (slower but guaranteed up-to-date):</strong></p>
|
||||
<pre><code>curl -X POST <span id="curlUrl2b"></span>/api/generate/raw \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"countries": ["CN", "RU"],
|
||||
"app_variant": "txt",
|
||||
"aggregate": true,
|
||||
"use_cache": false
|
||||
}' \
|
||||
-o blocklist_fresh.txt</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Endpoint 7: Generate Configuration -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint7')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-success me-2">POST</span>
|
||||
<code class="api-path">/api/generate</code>
|
||||
<span class="ms-3 text-muted">Generate application configuration</span>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body collapse" id="endpoint7">
|
||||
<h6 class="fw-bold">Description</h6>
|
||||
<p>Generates application-specific geo-blocking configuration for Nginx, Apache, or HAProxy and returns it as a downloadable file.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Request Body</h6>
|
||||
<pre><code>{
|
||||
"countries": [<span class="text-warning">"CN"</span>, <span class="text-warning">"RU"</span>],
|
||||
"app_type": <span class="text-warning">"nginx"</span>,
|
||||
"app_variant": <span class="text-warning">"map"</span>,
|
||||
"aggregate": <span class="text-success">true</span>,
|
||||
"use_cache": <span class="text-success">true</span>
|
||||
}</code></pre>
|
||||
|
||||
<h6 class="fw-bold mt-3">Parameters</h6>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>countries</code></td>
|
||||
<td>array</td>
|
||||
<td><span class="badge bg-danger">required</span></td>
|
||||
<td>List of ISO 3166-1 alpha-2 country codes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>app_type</code></td>
|
||||
<td>string</td>
|
||||
<td><span class="badge bg-danger">required</span></td>
|
||||
<td>One of: <code>nginx</code>, <code>apache</code>, <code>haproxy</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>app_variant</code></td>
|
||||
<td>string</td>
|
||||
<td><span class="badge bg-danger">required</span></td>
|
||||
<td>Configuration style (depends on app_type)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>aggregate</code></td>
|
||||
<td>boolean</td>
|
||||
<td><span class="badge bg-secondary">optional</span></td>
|
||||
<td>Aggregate IP networks (default: true)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>use_cache</code></td>
|
||||
<td>boolean</td>
|
||||
<td><span class="badge bg-secondary">optional</span></td>
|
||||
<td>Use Redis cache if available (default: true)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6 class="fw-bold mt-3">Available Variants</h6>
|
||||
<ul>
|
||||
<li><strong>nginx:</strong> <code>geo</code>, <code>map</code>, <code>deny</code></li>
|
||||
<li><strong>apache:</strong> <code>22</code> (Apache 2.2), <code>24</code> (Apache 2.4)</li>
|
||||
<li><strong>haproxy:</strong> <code>acl</code>, <code>lua</code></li>
|
||||
</ul>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response</h6>
|
||||
<p>Returns configuration file as <code>text/plain</code> with Content-Disposition header for download.</p>
|
||||
|
||||
<h6 class="fw-bold mt-3">Response Headers</h6>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>X-From-Cache</code></td>
|
||||
<td><code>true</code> or <code>false</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>X-Cache-Type</code></td>
|
||||
<td><code>redis</code> or <code>sqlite</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>X-Generated-At</code></td>
|
||||
<td>ISO 8601 timestamp</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6 class="fw-bold mt-3">Cache Behavior</h6>
|
||||
<div class="alert alert-info">
|
||||
<strong>With Redis enabled:</strong>
|
||||
<ul class="mb-0">
|
||||
<li><code>use_cache: true</code> - Check Redis first, return cached config if available (fast, <1s)</li>
|
||||
<li><code>use_cache: false</code> - Bypass Redis, fetch from SQLite cache or scan MaxMind (slower, 5-30s)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mt-3">cURL Examples</h6>
|
||||
|
||||
<p class="mb-2"><strong>With cache (recommended for production):</strong></p>
|
||||
<pre><code>curl -X POST <span id="curlUrl3"></span>/api/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"countries": ["CN", "RU"],
|
||||
"app_type": "nginx",
|
||||
"app_variant": "map",
|
||||
"aggregate": true,
|
||||
"use_cache": true
|
||||
}' \
|
||||
-o geoblock.conf
|
||||
|
||||
# Check if it was cached:
|
||||
curl -I -X POST <span id="curlUrl3b"></span>/api/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"countries":["CN"],"app_type":"nginx","app_variant":"map"}' \
|
||||
| grep "X-From-Cache"</code></pre>
|
||||
|
||||
<p class="mb-2 mt-3"><strong>Force fresh scan (for testing or updates):</strong></p>
|
||||
<pre><code>curl -X POST <span id="curlUrl3c"></span>/api/generate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"countries": ["CN", "RU"],
|
||||
"app_type": "nginx",
|
||||
"app_variant": "map",
|
||||
"aggregate": true,
|
||||
"use_cache": false
|
||||
}' \
|
||||
-o geoblock_fresh.conf</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/api.js') }}?v={{ js_hash }}"></script>
|
||||
{% endblock %}
|
||||
47
templates/base.html
Normal file
47
templates/base.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}{{ app_name }}{% endblock %}</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}?v={{ css_hash }}">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-light bg-light border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ logo_link }}">
|
||||
{% if logo_url %}
|
||||
<img src="{{ logo_url }}" alt="{{ app_name }}" height="30">
|
||||
{% else %}
|
||||
{{ app_name }}
|
||||
{% endif %}
|
||||
</a>
|
||||
<div>
|
||||
<a href="/" class="btn btn-sm btn-outline-secondary me-2">Home</a>
|
||||
<a href="/api-docs" class="btn btn-sm btn-outline-primary">API Docs</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="footer mt-auto">
|
||||
<div class="container text-center">
|
||||
<span class="text-muted">{{ footer_text }}</span>
|
||||
{% if footer_link %}
|
||||
<span class="text-muted mx-2">|</span>
|
||||
<a href="{{ footer_link }}">{{ footer_link_text }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/app.js') }}?v={{ js_hash }}"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
237
templates/index.html
Normal file
237
templates/index.html
Normal file
@@ -0,0 +1,237 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
|
||||
<!-- Database & Redis Status -->
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-md-8">
|
||||
<div id="dbStatus" class="alert alert-info mb-0">
|
||||
<i class="fas fa-database me-2"></i>
|
||||
<span>Checking database status...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% if redis_enabled %}
|
||||
{% if redis_connected %}
|
||||
<div class="alert alert-success mb-0">
|
||||
<i class="fas fa-bolt me-2"></i>Redis Cache: <strong>Active</strong>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning mb-0">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>Redis: <strong>Offline</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="alert alert-secondary mb-0">
|
||||
<i class="fas fa-bolt me-2"></i>Redis: <strong>Disabled</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cache Stats Panel (Collapsible) -->
|
||||
{% if redis_enabled and redis_connected %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-light" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#cacheStatsPanel">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<i class="fas fa-chart-bar me-2"></i>Cache Statistics
|
||||
</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cacheStatsPanel" class="collapse">
|
||||
<div class="card-body">
|
||||
<div id="cacheStatsContent">
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
|
||||
<span class="ms-2">Loading cache statistics...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 border-top pt-3">
|
||||
<button class="btn btn-sm btn-warning" onclick="flushCache()">
|
||||
<i class="fas fa-trash me-1"></i>Flush Cache (redis)
|
||||
</button>
|
||||
<button class="btn btn-sm btn-info" onclick="loadCacheStats()">
|
||||
<i class="fas fa-sync me-1"></i>Refresh Stats
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Main Card -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h4 class="mb-0">
|
||||
<i class="fas fa-globe me-2"></i>Generate Geo-Blocking Configuration
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form id="generateForm">
|
||||
|
||||
<!-- Country Selection -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="fas fa-flag me-2"></i>Select Countries to Block
|
||||
</label>
|
||||
<div class="row g-1 mb-2" id="countryList">
|
||||
{% for country in countries %}
|
||||
<div class="col-xxl-1 col-xl-2 col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="form-check form-check-compact">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="countries" value="{{ country.code }}"
|
||||
id="country_{{ country.code }}">
|
||||
<label class="form-check-label" for="country_{{ country.code }}" title="{{ country.name }}">
|
||||
{{ country.flag }} {{ country.code }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="selectAll()">
|
||||
<i class="fas fa-check-double me-1"></i>Select All
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="deselectAll()">
|
||||
<i class="fas fa-times me-1"></i>Deselect All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Application Type -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="fas fa-server me-2"></i>Output Format
|
||||
</label>
|
||||
<select class="form-select form-select-lg" id="appType" name="app_type" onchange="updateVariants()">
|
||||
<option value="raw-cidr">Raw CIDR List</option>
|
||||
<option value="nginx">Nginx</option>
|
||||
<option value="apache">Apache</option>
|
||||
<option value="haproxy">HAProxy</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Variant Selection -->
|
||||
<div class="mb-4" id="variantSection">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="fas fa-cog me-2"></i>Configuration Style
|
||||
</label>
|
||||
<select class="form-select form-select-lg" id="appVariant" name="app_variant" onchange="updateVariantDescription()">
|
||||
</select>
|
||||
<div id="variantDescription" class="mt-2" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aggregate Option -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="fas fa-sliders-h me-2"></i>Options
|
||||
</label>
|
||||
<div class="aggregate-card">
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
id="aggregate" name="aggregate" checked>
|
||||
<label class="form-check-label" for="aggregate">
|
||||
<strong>Aggregate IP networks</strong><br>
|
||||
<small class="text-muted">Combines adjacent networks into larger CIDR blocks for smaller files</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{% if redis_enabled and redis_connected %}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
id="useCache" name="use_cache" checked>
|
||||
<label class="form-check-label" for="useCache">
|
||||
<strong><i class="fas fa-bolt text-warning"></i> Use Redis Cache</strong><br>
|
||||
<small class="text-muted">Instant generation for previously cached configurations (<100ms)</small>
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<button type="button" class="btn btn-outline-primary btn-lg w-100" onclick="previewConfiguration()">
|
||||
<i class="fas fa-eye me-2"></i>Preview
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<button type="submit" class="btn btn-primary btn-lg w-100" id="generateBtn">
|
||||
<i class="fas fa-download me-2"></i>Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Progress -->
|
||||
<div id="progressSection" class="mt-3" style="display: none;">
|
||||
<div class="progress" style="height: 2rem;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar"
|
||||
style="width: 0%"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
<span id="progressPercentage">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<small class="text-muted" id="progressMessage">Initializing...</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result -->
|
||||
<div id="resultSection" class="mt-3" style="display: none;">
|
||||
<div class="alert" role="alert">
|
||||
<span id="resultMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Modal -->
|
||||
<div class="modal fade" id="previewModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-file-code me-2"></i>Configuration Preview
|
||||
<span id="cacheIndicator"></span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="copyToClipboard()">
|
||||
<i class="fas fa-copy me-1"></i>Copy to Clipboard
|
||||
</button>
|
||||
<span id="previewStats" class="ms-3 text-muted"></span>
|
||||
</div>
|
||||
<pre class="mb-0"><code id="previewContent"></code></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" onclick="downloadFromPreview()">
|
||||
<i class="fas fa-download me-2"></i>Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/progress.js') }}?v={{ js_hash }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/cache.js') }}?v={{ js_hash }}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user