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
+159
View File
@@ -0,0 +1,159 @@
{% extends 'base.html' %}
{% block title %}GeoIP Guacamole Manager{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-globe me-2"></i>GeoIP blokiranje</h4>
{% if enabled %}
<span class="badge bg-success fs-6">Aktivno</span>
{% else %}
<span class="badge bg-secondary fs-6">Neaktivno</span>
{% endif %}
</div>
{% if enabled %}
<!-- GeoIP je aktivan -->
<div class="row g-4">
<div class="col-md-6">
<div class="card">
<div class="card-header fw-semibold">Dozvoljene države</div>
<div class="card-body">
<div id="country-badges" class="mb-3">
{% for cc in countries.split() %}
<span class="badge-country">{{ cc }}</span>
{% endfor %}
</div>
<div class="input-group input-group-sm">
<input id="countries-input" type="text" class="form-control"
placeholder="HR BA SI RS AT" value="{{ countries }}">
<button class="btn btn-primary" onclick="updateCountries()">
<i class="bi bi-check-lg"></i> Spremi
</button>
</div>
<small class="text-muted">ISO-2 kodovi zemalja, razdvojeni razmakom</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header fw-semibold">Akcije</div>
<div class="card-body d-flex flex-column gap-2">
<button class="btn btn-outline-info" onclick="refreshDB()">
<i class="bi bi-cloud-download"></i> Ažuriraj GeoIP bazu
</button>
<button class="btn btn-outline-danger" onclick="disableGeoIP()">
<i class="bi bi-x-circle"></i> Onemogući GeoIP
</button>
</div>
</div>
</div>
</div>
{% else %}
<!-- GeoIP nije aktivan -->
<div class="card">
<div class="card-header fw-semibold">
<i class="bi bi-globe me-1"></i> Konfiguracija GeoIP blokiranja
</div>
<div class="card-body">
<div class="alert alert-info small mb-4">
<i class="bi bi-info-circle me-1"></i>
Potreban je besplatni MaxMind račun:
<a href="https://www.maxmind.com/en/geolite2/signup" target="_blank" class="alert-link">
maxmind.com/en/geolite2/signup
</a>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label small text-muted">MaxMind Account ID</label>
<input id="mm-id" type="text" class="form-control" value="{{ mm_id }}"
placeholder="123456">
</div>
<div class="col-md-6">
<label class="form-label small text-muted">MaxMind License Key</label>
<input id="mm-key" type="password" class="form-control"
placeholder="••••••••••••••••">
</div>
<div class="col-12">
<label class="form-label small text-muted">Dozvoljene države (ISO-2 kodovi)</label>
<input id="countries-input" type="text" class="form-control"
placeholder="HR BA SI RS AT DE">
<small class="text-muted">Sve ostale države bit će blokirane</small>
</div>
<div class="col-12">
<button class="btn btn-success" onclick="enableGeoIP()">
<i class="bi bi-globe-americas"></i> Aktiviraj GeoIP blokiranje
</button>
</div>
</div>
</div>
</div>
<!-- Referenca ISO kodova -->
<div class="card mt-3">
<div class="card-header small text-muted">Česti ISO-2 kodovi</div>
<div class="card-body">
{% for cc, name in [('HR','Hrvatska'),('BA','Bosna i Hercegovina'),('SI','Slovenija'),
('RS','Srbija'),('ME','Crna Gora'),('MK','Sjeverna Makedonija'),
('AT','Austrija'),('DE','Njemačka'),('CH','Švicarska')] %}
<span class="badge-country" style="cursor:pointer"
onclick="addCountry('{{ cc }}')" title="{{ name }}">
{{ cc }} <small class="text-muted">{{ name }}</small>
</span>
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
async function enableGeoIP() {
const btn = event.target;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Aktivacija...';
const r = await api('/api/geoip/enable', {
mm_id: document.getElementById('mm-id').value.trim(),
mm_key: document.getElementById('mm-key').value.trim(),
countries: document.getElementById('countries-input').value.trim()
});
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 1500);
else { btn.disabled = false; btn.innerHTML = '<i class="bi bi-globe-americas"></i> Aktiviraj GeoIP blokiranje'; }
}
async function updateCountries() {
const r = await api('/api/geoip/update', {
countries: document.getElementById('countries-input').value.trim()
});
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 1000);
}
async function disableGeoIP() {
if (!confirm('Sigurno onemogućiti GeoIP?')) return;
const r = await api('/api/geoip/disable');
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 1000);
}
async function refreshDB() {
const btn = event.target;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Ažuriranje...';
const r = await api('/api/geoip/refresh');
showToast(r.ok ? 'GeoIP baza ažurirana' : r.msg, r.ok);
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-cloud-download"></i> Ažuriraj GeoIP bazu';
}
function addCountry(cc) {
const inp = document.getElementById('countries-input');
const existing = inp.value.split(' ').filter(x => x);
if (!existing.includes(cc)) {
inp.value = [...existing, cc].join(' ');
}
}
</script>
{% endblock %}