Files
Api-Gateway/app/templates/base.html
T
Samuel AmarandClaude Haiku 4.5 77d7a50fa9 Initial commit: API gateway with admin console
- FastAPI async gateway with httpx proxying to multiple upstreams
- SQLite database with SQLAlchemy ORM
- Admin console: manage services, users, API keys, endpoint access
- Per-key, per-endpoint granular access control
- OpenAPI document sync and caching (5-minute TTL)
- Request/response logging with full transaction inspection
- In-memory rate limiting (per-key, fixed-window)
- Tiered log retention (7d payloads, 90d rows, incremental vacuum)
- TLS verification toggle per service (for self-signed certificates)
- Service connectivity validation with automatic endpoint refresh
- Request browser with filters and deep-link inspection
- Docker setup with persistent volume
- Modal forms for create/edit flows

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-29 14:00:22 +02:00

91 lines
3.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}API Gateway{% endblock %}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="layout">
<nav class="sidebar">
<div class="brand">API <span>Gateway</span></div>
<a class="nav-item {% if active == 'dashboard' %}active{% endif %}" href="/admin">Dashboard</a>
<a class="nav-item {% if active == 'services' %}active{% endif %}" href="/admin/services">Services</a>
<a class="nav-item {% if active == 'users' %}active{% endif %}" href="/admin/users">Users</a>
<a class="nav-item {% if active == 'keys' %}active{% endif %}" href="/admin/keys">API Keys</a>
<a class="nav-item {% if active == 'requests' %}active{% endif %}" href="/admin/requests">Requests</a>
<a class="nav-item {% if active == 'monitoring' %}active{% endif %}" href="/admin/monitoring">Monitoring</a>
<div class="spacer"></div>
{% if user %}
<div class="whoami">{{ user.username }}</div>
<a class="nav-item" href="/admin/logout">Log out</a>
{% endif %}
</nav>
<main class="main">
{% block content %}{% endblock %}
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<script>
if (window.Chart) {
Chart.defaults.color = '#898781';
Chart.defaults.borderColor = '#2c2c2a';
Chart.defaults.font.family = 'system-ui, -apple-system, "Segoe UI", sans-serif';
Chart.defaults.plugins.legend.labels.boxWidth = 12;
Chart.defaults.plugins.legend.labels.boxHeight = 12;
Chart.defaults.animation.duration = 300;
}
const GW = {
blue: '#3987e5',
red: '#e66767',
ink2: '#c3c2b7',
grid: '#2c2c2a',
async fetchJSON(url) { const r = await fetch(url); return r.json(); },
};
// Destructive forms carry data-confirm. First click arms the button
// ("Confirm?"), a second click within 5s submits. No native dialogs —
// confirm() is unreliable in embedded browsers.
document.addEventListener('submit', e => {
const form = e.target;
if (!form.dataset.confirm) return;
if (form.dataset.armed === '1') return; // second click: let it through
e.preventDefault();
const btn = form.querySelector('button');
form.dataset.armed = '1';
if (!btn.dataset.orig) btn.dataset.orig = btn.innerHTML;
btn.innerHTML = 'Confirm?';
btn.classList.add('armed');
btn.title = form.dataset.confirm;
setTimeout(() => {
form.dataset.armed = '';
btn.innerHTML = btn.dataset.orig;
btn.classList.remove('armed');
}, 5000);
}, true);
// Modal overlays: [data-modal] opens the overlay with that id; the × button,
// a click on the backdrop, or Escape closes it.
document.addEventListener('click', e => {
const opener = e.target.closest('[data-modal]');
if (opener) {
e.preventDefault();
document.getElementById(opener.dataset.modal).classList.add('open');
return;
}
if (e.target.closest('.modal-close')) {
e.target.closest('.modal-overlay').classList.remove('open');
return;
}
if (e.target.classList.contains('modal-overlay')) e.target.classList.remove('open');
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape')
document.querySelectorAll('.modal-overlay.open').forEach(m => m.classList.remove('open'));
});
</script>
{% block scripts %}{% endblock %}
</body>
</html>