caddydb/templates/index.html
2025-03-24 19:30:00 +01:00

175 lines
No EOL
7.5 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Caddy Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
background-color: #0f0f0f;
background-image:
radial-gradient(circle at 25% 25%, rgba(40, 40, 40, 0.05) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(40, 40, 40, 0.05) 0%, transparent 50%);
}
/* Subtle grid pattern */
.bg-grid {
background-size: 50px 50px;
background-image:
linear-gradient(to right, rgba(40, 40, 40, 0.05) 1px, transparent 1px),
linear-gradient(to bottom, rgba(40, 40, 40, 0.05) 1px, transparent 1px);
}
.card {
transition: all 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.search-popup {
opacity: 0;
visibility: hidden;
transition: opacity 0.15s ease, visibility 0.15s;
}
.search-popup.active {
opacity: 1;
visibility: visible;
}
</style>
<script>
function toggleSearch() {
let searchContainer = document.getElementById("search-container");
searchContainer.classList.toggle("active");
if (searchContainer.classList.contains("active")) {
document.getElementById("search-box").focus();
}
}
function filterEntries() {
let query = document.getElementById("search-box").value.toLowerCase();
let cards = document.querySelectorAll(".domain-card");
cards.forEach(card => {
let domain = card.dataset.domain.toLowerCase();
card.style.display = domain.includes(query) ? "" : "none";
});
}
function deleteServer(serverName) {
if (confirm(`Möchten Sie den Server "${serverName}" wirklich löschen?`)) {
fetch('/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "server": serverName })
}).then(response => {
if (response.ok) {
const serverBox = document.getElementById("server-box-" + serverName);
serverBox.classList.add('opacity-0');
setTimeout(() => {
serverBox.remove();
}, 300);
}
});
}
}
document.addEventListener("keydown", function (event) {
if (event.key === "/" && !event.ctrlKey && !event.metaKey) {
event.preventDefault();
toggleSearch();
}
if (event.key === "Escape") {
let searchContainer = document.getElementById("search-container");
if (searchContainer.classList.contains("active")) {
toggleSearch();
}
}
});
</script>
</head>
<body class="text-gray-200 min-h-screen bg-grid">
<!-- Minimal header -->
<header class="py-4 px-6 bg-black/50 backdrop-blur-sm border-b border-gray-800/50">
<div class="container mx-auto flex justify-between items-center">
<h1 class="text-xl font-medium tracking-tight text-white">Caddy Dashboard</h1>
<button onclick="toggleSearch()" class="text-sm px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 rounded-md
transition-colors flex items-center gap-2">
<i class="fas fa-search text-xs"></i>
<span class="hidden sm:inline">/</span>
</button>
</div>
</header>
<!-- Search popup -->
<div id="search-container"
class="search-popup fixed inset-0 z-50 flex items-start justify-center pt-20 px-4 bg-black/50 backdrop-blur-sm">
<div class="w-full max-w-xl bg-zinc-900 border border-zinc-800 rounded-lg overflow-hidden shadow-2xl">
<div class="flex items-center px-4 border-b border-zinc-800">
<i class="fas fa-search text-gray-500 mr-3"></i>
<input type="text" id="search-box"
class="w-full py-3 bg-transparent text-white border-none focus:outline-none"
placeholder="Domain suchen..." onkeyup="filterEntries()">
<button onclick="toggleSearch()" class="text-gray-500 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-2 text-xs text-gray-500">
Drücke <kbd class="px-1.5 py-0.5 bg-zinc-800 rounded border border-zinc-700">/</kbd> zum Öffnen, <kbd
class="px-1.5 py-0.5 bg-zinc-800 rounded border border-zinc-700">ESC</kbd> zum Schließen
</div>
</div>
</div>
<div class="container mx-auto p-4 md:p-6">
{% for server, entries in proxies.items() %}
<div id="server-box-{{ server }}" class="mb-8 opacity-100 transition-opacity duration-300">
<div class="flex justify-between items-center mb-4 px-1">
<div>
<h2 class="text-lg font-medium text-white">{{ server }}</h2>
<p class="text-xs text-gray-500 mt-0.5">Zuletzt aktualisiert: {{ timestamps[server] }}</p>
</div>
<button onclick="deleteServer('{{ server }}')" class="text-xs px-3 py-1.5 bg-zinc-900 hover:bg-red-900/70 border border-zinc-800 hover:border-red-800/50 text-gray-400 hover:text-white rounded
transition-colors">
<i class="fas fa-trash-alt mr-1.5"></i>Löschen
</button>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{% for domain, target in entries.items() %}
<div class="domain-card card bg-zinc-900 border border-zinc-800 rounded-lg overflow-hidden"
data-domain="{{ domain }}">
<div class="px-4 py-3 flex justify-between items-center border-b border-zinc-800">
<a href="https://{{ domain }}" target="_blank"
class="text-white hover:text-blue-400 text-sm font-medium truncate transition-colors flex items-center gap-2 max-w-[calc(100%-24px)]">
<i class="fas fa-globe text-xs text-gray-500"></i>
<span class="truncate">{{ domain }}</span>
</a>
<div class="ml-2 flex-shrink-0">
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
</div>
</div>
<div class="px-4 py-2.5">
<p class="text-gray-400 text-xs font-mono truncate">{{ target }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<footer class="py-4 mt-12 border-t border-zinc-900">
<div class="container mx-auto px-4 text-center text-xs text-gray-600">
Caddy Dashboard
</div>
</footer>
</body>
</html>