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
+122
View File
@@ -0,0 +1,122 @@
{% extends 'base.html' %}
{% block title %}Basic Auth Guacamole Manager{% endblock %}
{% block content %}
<h4 class="mb-4"><i class="bi bi-person-lock me-2"></i>Basic Auth korisnici</h4>
<div class="row g-4">
<!-- Lista korisnika -->
<div class="col-md-5">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="fw-semibold">Korisnici</span>
<span class="badge bg-secondary">{{ users|length }}</span>
</div>
<ul class="list-group list-group-flush" id="user-list">
{% for user in users %}
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background:transparent; border-color:#30363d">
<span><i class="bi bi-person me-2 text-muted"></i>{{ user }}</span>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary btn-sm"
onclick="fillUpdate('{{ user }}')">
<i class="bi bi-key"></i>
</button>
<button class="btn btn-outline-danger btn-sm"
onclick="deleteUser('{{ user }}')">
<i class="bi bi-trash"></i>
</button>
</div>
</li>
{% else %}
<li class="list-group-item text-muted" style="background:transparent">
Nema korisnika
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- Forme -->
<div class="col-md-7">
<!-- Dodaj korisnika -->
<div class="card mb-3">
<div class="card-header fw-semibold">
<i class="bi bi-person-plus me-1"></i> Dodaj korisnika
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-5">
<input id="add-user" type="text" class="form-control form-control-sm"
placeholder="Korisničko ime">
</div>
<div class="col-5">
<input id="add-pass" type="password" class="form-control form-control-sm"
placeholder="Lozinka">
</div>
<div class="col-2">
<button class="btn btn-primary btn-sm w-100" onclick="addUser()">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Promijeni lozinku -->
<div class="card">
<div class="card-header fw-semibold">
<i class="bi bi-key me-1"></i> Promijeni lozinku
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-5">
<input id="upd-user" type="text" class="form-control form-control-sm"
placeholder="Korisničko ime">
</div>
<div class="col-5">
<input id="upd-pass" type="password" class="form-control form-control-sm"
placeholder="Nova lozinka">
</div>
<div class="col-2">
<button class="btn btn-outline-primary btn-sm w-100" onclick="updateUser()">
<i class="bi bi-check-lg"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function fillUpdate(user) {
document.getElementById('upd-user').value = user;
document.getElementById('upd-pass').focus();
}
async function addUser() {
const username = document.getElementById('add-user').value.trim();
const password = document.getElementById('add-pass').value;
const r = await api('/api/basicauth/add', {username, password});
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 1000);
}
async function updateUser() {
const username = document.getElementById('upd-user').value.trim();
const password = document.getElementById('upd-pass').value;
const r = await api('/api/basicauth/update', {username, password});
showToast(r.msg, r.ok);
}
async function deleteUser(username) {
if (!confirm(`Ukloniti korisnika "${username}"?`)) return;
const r = await api('/api/basicauth/delete', {username});
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 1000);
}
</script>
{% endblock %}