Initial release — Salus by Stranto v1.6.1.0

FastAPI/Jinja2 web app for viewing and rebooting TP-Link Omada APs
across all sites. Authentik OIDC auth, SQLite audit log, Docker deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 14:36:02 +02:00
commit 284924e86d
17 changed files with 1646 additions and 0 deletions

103
app/templates/base.html Normal file
View File

@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en" class="h-full bg-white">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}Salus by Stranto{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: { 500: '#3b82f6', 600: '#2563eb', 700: '#1d4ed8' }
}
}
}
}
</script>
<style>
[x-cloak] { display: none !important; }
.toast { animation: slide-in 0.3s ease-out; }
@keyframes slide-in {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>
</head>
<body class="h-full text-gray-900 flex flex-col">
<!-- Nav -->
<nav class="bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center gap-8">
<a href="/" class="flex items-center gap-2 font-bold text-lg text-gray-900 hover:text-blue-600 transition-colors">
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v6m-3-3h6m6 0a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Salus <span class="font-normal text-gray-400 text-sm">by Stranto</span>
</a>
<div class="hidden sm:flex gap-1">
<a href="/"
class="px-3 py-2 rounded-md text-sm font-medium transition-colors
{% if request.url.path == '/' %}bg-gray-100 text-gray-900{% else %}text-gray-600 hover:text-gray-900 hover:bg-gray-100{% endif %}">
Access Points
</a>
<a href="/audit"
class="px-3 py-2 rounded-md text-sm font-medium transition-colors
{% if request.url.path == '/audit' %}bg-gray-100 text-gray-900{% else %}text-gray-600 hover:text-gray-900 hover:bg-gray-100{% endif %}">
Audit Log
</a>
</div>
</div>
{% if user %}
<div class="flex items-center gap-3">
<span class="hidden sm:block text-sm text-gray-500">
<span class="text-gray-900 font-medium">{{ user.username }}</span>
</span>
<a href="/auth/logout"
class="px-3 py-1.5 text-sm rounded-md bg-gray-100 text-gray-600 hover:bg-red-50 hover:text-red-700 border border-gray-200 transition-colors">
Logout
</a>
</div>
{% endif %}
</div>
</div>
</nav>
<!-- Page content -->
<main class="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="border-t border-gray-200 bg-white mt-auto">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between text-xs text-gray-400">
<span>Version 1.6.1.0</span>
<a href="https://www.stranto.com/" target="_blank" rel="noopener noreferrer"
class="hover:text-blue-500 transition-colors">stranto.com</a>
</div>
</footer>
<!-- Toast container -->
<div id="toast-container" class="fixed bottom-4 right-4 flex flex-col gap-2 z-50 pointer-events-none"></div>
<script>
function showToast(msg, type = 'success') {
const container = document.getElementById('toast-container');
const colors = type === 'success'
? 'bg-green-50 border-green-200 text-green-800'
: 'bg-red-50 border-red-200 text-red-800';
const el = document.createElement('div');
el.className = `toast pointer-events-auto px-4 py-3 rounded-lg border shadow text-sm max-w-xs ${colors}`;
el.textContent = msg;
container.appendChild(el);
setTimeout(() => { el.remove(); }, 4000);
}
</script>
{% block scripts %}{% endblock %}
</body>
</html>