Initial commit — GuacPanel v1.0

All-in-one installer za Apache Guacamole s Nginx reverse proxy,
fail2ban, SSL (Let's Encrypt / Self-signed), GeoIP blokiranjem
i Flask web GUI za upravljanje.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
njomac
2026-04-26 11:21:44 +02:00
commit 50c125e0db
17 changed files with 5249 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
{% extends 'base.html' %}
{% block title %}Backup{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-archive me-2"></i>Backup</h4>
<button class="btn btn-primary" onclick="createBackup(this)">
<i class="bi bi-plus-circle"></i> Napravi backup
</button>
</div>
<div class="alert alert-info small mb-4">
<i class="bi bi-info-circle me-1"></i>
Backup uključuje: Guacamole konfiguraciju, PostgreSQL bazu, nginx konfig,
Basic Auth korisnike, fail2ban pravila i SSL certifikate.
Sprema se u <code>/root/</code> (čitljivo samo root-u).
</div>
<!-- Lista backupa -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="fw-semibold">Dostupni backupi</span>
<span class="badge bg-secondary">{{ backups|length }}</span>
</div>
{% if backups %}
<div class="list-group list-group-flush">
{% for b in backups %}
<div class="list-group-item d-flex justify-content-between align-items-center"
style="background:transparent; border-color:#30363d">
<div>
<i class="bi bi-file-zip me-2 text-muted"></i>
<span class="font-monospace small">{{ b }}</span>
</div>
<button class="btn btn-outline-danger btn-sm" onclick="deleteBackup('{{ b }}')">
<i class="bi bi-trash"></i>
</button>
</div>
{% endfor %}
</div>
{% else %}
<div class="card-body text-muted">
<i class="bi bi-inbox me-1"></i> Nema dostupnih backupa
</div>
{% endif %}
</div>
<!-- Progress -->
<div id="backup-progress" class="card mt-4 d-none">
<div class="card-body d-flex align-items-center gap-3">
<div class="spinner-border spinner-border-sm text-primary"></div>
<span>Kreiranje backupa, molim pričekajte...</span>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
async function createBackup(btn) {
const orig = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Backup...';
document.getElementById('backup-progress').classList.remove('d-none');
const r = await api('/api/backup/create');
showToast(r.msg, r.ok);
btn.disabled = false;
btn.innerHTML = orig;
document.getElementById('backup-progress').classList.add('d-none');
if (r.ok) setTimeout(() => location.reload(), 1500);
}
async function deleteBackup(name) {
if (!confirm(`Obrisati backup "${name}"?`)) return;
const r = await api('/api/backup/delete', {name});
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 800);
}
</script>
{% endblock %}