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,147 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="hr" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Guacamole Manager{% endblock %}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||
<style>
|
||||
body { min-height: 100vh; background: #0f1117; }
|
||||
#sidebar {
|
||||
width: 230px; min-height: 100vh; background: #161b22;
|
||||
border-right: 1px solid #30363d; position: fixed; top: 0; left: 0;
|
||||
display: flex; flex-direction: column; z-index: 100;
|
||||
}
|
||||
#sidebar .brand {
|
||||
padding: 1.25rem 1rem; border-bottom: 1px solid #30363d;
|
||||
font-weight: 700; font-size: 1rem; color: #58a6ff;
|
||||
display: flex; align-items: center; gap: .5rem;
|
||||
}
|
||||
#sidebar .nav-link {
|
||||
color: #8b949e; padding: .55rem 1rem; border-radius: 6px;
|
||||
margin: 2px 8px; display: flex; align-items: center; gap: .6rem;
|
||||
font-size: .9rem; transition: all .15s;
|
||||
}
|
||||
#sidebar .nav-link:hover { background: #21262d; color: #c9d1d9; }
|
||||
#sidebar .nav-link.active { background: #1f6feb22; color: #58a6ff; }
|
||||
#sidebar .nav-link i { font-size: 1rem; width: 18px; text-align: center; }
|
||||
#sidebar .sidebar-footer {
|
||||
margin-top: auto; padding: 1rem; border-top: 1px solid #30363d; font-size: .8rem;
|
||||
}
|
||||
#content { margin-left: 230px; padding: 2rem; }
|
||||
.status-dot {
|
||||
width: 10px; height: 10px; border-radius: 50%; display: inline-block;
|
||||
}
|
||||
.status-ok { background: #3fb950; box-shadow: 0 0 6px #3fb95066; }
|
||||
.status-err { background: #f85149; box-shadow: 0 0 6px #f8514966; }
|
||||
.status-warn { background: #d29922; box-shadow: 0 0 6px #d2992266; }
|
||||
.card { background: #161b22; border: 1px solid #30363d; }
|
||||
.card-header { background: #1c2128; border-bottom: 1px solid #30363d; }
|
||||
.badge-country {
|
||||
background: #1f6feb33; color: #58a6ff; border: 1px solid #1f6feb66;
|
||||
font-size: .75rem; padding: 3px 8px; border-radius: 12px; margin: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
#log-output {
|
||||
background: #010409; color: #c9d1d9; font-family: monospace;
|
||||
font-size: .8rem; height: 400px; overflow-y: auto; padding: 1rem;
|
||||
border-radius: 6px; border: 1px solid #30363d;
|
||||
}
|
||||
.toast-container { position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 9999; }
|
||||
@media (max-width: 768px) {
|
||||
#sidebar { width: 100%; min-height: auto; position: relative; }
|
||||
#content { margin-left: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div id="sidebar">
|
||||
<div class="brand">
|
||||
<i class="bi bi-display"></i> Guacamole
|
||||
</div>
|
||||
<nav class="nav flex-column mt-2">
|
||||
<a class="nav-link {% if request.endpoint == 'dashboard' %}active{% endif %}"
|
||||
href="{{ url_for('dashboard') }}">
|
||||
<i class="bi bi-grid-1x2"></i> Dashboard
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'basicauth' %}active{% endif %}"
|
||||
href="{{ url_for('basicauth') }}">
|
||||
<i class="bi bi-person-lock"></i> Basic Auth
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'geoip' %}active{% endif %}"
|
||||
href="{{ url_for('geoip') }}">
|
||||
<i class="bi bi-globe"></i> GeoIP
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'fail2ban' %}active{% endif %}"
|
||||
href="{{ url_for('fail2ban') }}">
|
||||
<i class="bi bi-shield-x"></i> fail2ban
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'guacamole' %}active{% endif %}"
|
||||
href="{{ url_for('guacamole') }}">
|
||||
<i class="bi bi-boxes"></i> Kontejneri
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'ssl' %}active{% endif %}"
|
||||
href="{{ url_for('ssl') }}">
|
||||
<i class="bi bi-lock"></i> SSL
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'updates' %}active{% endif %}"
|
||||
href="{{ url_for('updates') }}">
|
||||
<i class="bi bi-arrow-up-circle"></i> Ažuriranja
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'backup' %}active{% endif %}"
|
||||
href="{{ url_for('backup') }}">
|
||||
<i class="bi bi-archive"></i> Backup
|
||||
</a>
|
||||
<a class="nav-link {% if request.endpoint == 'settings' %}active{% endif %}"
|
||||
href="{{ url_for('settings') }}">
|
||||
<i class="bi bi-gear"></i> Postavke
|
||||
</a>
|
||||
</nav>
|
||||
<div class="sidebar-footer text-muted">
|
||||
<a href="{{ url_for('logout') }}" class="text-danger text-decoration-none">
|
||||
<i class="bi bi-box-arrow-right"></i> Odjava
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Toast container -->
|
||||
<div class="toast-container">
|
||||
<div id="toast" class="toast align-items-center border-0" role="alert">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body" id="toast-body"></div>
|
||||
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const toast = new bootstrap.Toast(document.getElementById('toast'), {delay: 4000});
|
||||
|
||||
function showToast(msg, ok=true) {
|
||||
const el = document.getElementById('toast');
|
||||
el.className = `toast align-items-center border-0 text-bg-${ok ? 'success' : 'danger'}`;
|
||||
document.getElementById('toast-body').textContent = msg;
|
||||
toast.show();
|
||||
}
|
||||
|
||||
async function api(url, body={}) {
|
||||
const r = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return r.json();
|
||||
}
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user