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:
@@ -0,0 +1,130 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Dashboard – Guacamole Manager{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-0">Dashboard</h4>
|
||||
<small class="text-muted">{{ domain }}</small>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="loadStatus()">
|
||||
<i class="bi bi-arrow-clockwise"></i> Osvježi
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Kontejneri -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12"><h6 class="text-muted text-uppercase small mb-2">Docker kontejneri</h6></div>
|
||||
{% for name, label in [('guacamole','Guacamole Web'), ('guacd','Guacd Daemon'), ('guac_postgres','PostgreSQL')] %}
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3" id="card-{{ name }}">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="status-dot status-warn" id="dot-{{ name }}"></span>
|
||||
<div>
|
||||
<div class="fw-semibold">{{ label }}</div>
|
||||
<small class="text-muted" id="txt-{{ name }}">Učitavanje...</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Servisi -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12"><h6 class="text-muted text-uppercase small mb-2">Sistemski servisi</h6></div>
|
||||
{% for name, label, icon in [('nginx','Nginx','bi-server'), ('fail2ban','fail2ban','bi-shield'), ('docker','Docker','bi-box')] %}
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="status-dot status-warn" id="dot-svc-{{ name }}"></span>
|
||||
<div>
|
||||
<div class="fw-semibold"><i class="bi {{ icon }} me-1"></i>{{ label }}</div>
|
||||
<small class="text-muted" id="txt-svc-{{ name }}">Učitavanje...</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Info kartice -->
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<i class="bi bi-globe fs-3 text-info"></i>
|
||||
<div>
|
||||
<div class="small text-muted">GeoIP blokiranje</div>
|
||||
<span id="badge-geoip" class="badge bg-secondary">Provjera...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<i class="bi bi-lock fs-3 text-success"></i>
|
||||
<div>
|
||||
<div class="small text-muted">SSL certifikat</div>
|
||||
<span id="badge-ssl" class="badge bg-secondary">Provjera...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<i class="bi bi-shield-x fs-3 text-danger"></i>
|
||||
<div>
|
||||
<div class="small text-muted">Zabanjeni IP-ovi</div>
|
||||
<span id="badge-banned" class="fs-4 fw-bold">...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
async function loadStatus() {
|
||||
const data = await fetch('/api/status').then(r => r.json());
|
||||
|
||||
// Containers
|
||||
const containers = {
|
||||
guacamole: data.containers.guacamole,
|
||||
guacd: data.containers.guacd,
|
||||
guac_postgres: data.containers.guac_postgres
|
||||
};
|
||||
for (const [name, running] of Object.entries(containers)) {
|
||||
document.getElementById(`dot-${name}`).className =
|
||||
`status-dot ${running ? 'status-ok' : 'status-err'}`;
|
||||
document.getElementById(`txt-${name}`).textContent =
|
||||
running ? 'Aktivan' : 'Nije aktivan';
|
||||
}
|
||||
|
||||
// Services
|
||||
for (const [name, active] of Object.entries(data.services)) {
|
||||
document.getElementById(`dot-svc-${name}`).className =
|
||||
`status-dot ${active ? 'status-ok' : 'status-err'}`;
|
||||
document.getElementById(`txt-svc-${name}`).textContent =
|
||||
active ? 'Aktivan' : 'Nije aktivan';
|
||||
}
|
||||
|
||||
// Badges
|
||||
const geoip = document.getElementById('badge-geoip');
|
||||
geoip.textContent = data.geoip ? 'Aktivno' : 'Neaktivno';
|
||||
geoip.className = `badge ${data.geoip ? 'bg-success' : 'bg-secondary'}`;
|
||||
|
||||
const ssl = document.getElementById('badge-ssl');
|
||||
ssl.textContent = data.ssl ? 'Aktivan' : 'Nije aktivan';
|
||||
ssl.className = `badge ${data.ssl ? 'bg-success' : 'bg-warning text-dark'}`;
|
||||
|
||||
document.getElementById('badge-banned').textContent = data.banned;
|
||||
}
|
||||
|
||||
loadStatus();
|
||||
setInterval(loadStatus, 15000);
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user