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,132 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Kontejneri – Guacamole Manager{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h4 class="mb-4"><i class="bi bi-boxes me-2"></i>Docker kontejneri</h4>
|
||||
|
||||
<!-- Status kartice -->
|
||||
<div class="row g-3 mb-4">
|
||||
{% for name, running in containers.items() %}
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="status-dot {{ 'status-ok' if running else 'status-err' }}"></span>
|
||||
<div>
|
||||
<div class="fw-semibold">{{ name }}</div>
|
||||
<small class="{{ 'text-success' if running else 'text-danger' }}">
|
||||
{{ 'Aktivan' if running else 'Nije aktivan' }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Akcije -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header fw-semibold">Upravljanje</div>
|
||||
<div class="card-body d-flex gap-2 flex-wrap">
|
||||
<button class="btn btn-success" onclick="containerAction('start')">
|
||||
<i class="bi bi-play-fill"></i> Start
|
||||
</button>
|
||||
<button class="btn btn-warning" onclick="containerAction('restart')">
|
||||
<i class="bi bi-arrow-clockwise"></i> Restart
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="containerAction('stop')">
|
||||
<i class="bi bi-stop-fill"></i> Stop
|
||||
</button>
|
||||
<div class="ms-auto">
|
||||
<select id="log-source" class="form-select form-select-sm d-inline-block w-auto"
|
||||
onchange="switchLog(this.value)">
|
||||
<option value="guacamole">guacamole</option>
|
||||
<option value="guacd">guacd</option>
|
||||
<option value="postgres">postgres</option>
|
||||
<option value="nginx">nginx access</option>
|
||||
<option value="nginx-error">nginx error</option>
|
||||
<option value="fail2ban">fail2ban</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline-secondary ms-1" onclick="toggleLog()">
|
||||
<i class="bi bi-terminal" id="log-icon"></i>
|
||||
<span id="log-btn-txt">Log</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live log -->
|
||||
<div id="log-panel" class="card d-none">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold">
|
||||
<i class="bi bi-terminal me-1"></i>
|
||||
Live log: <span id="log-title">guacamole</span>
|
||||
</span>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="clearLog()">
|
||||
<i class="bi bi-trash"></i> Očisti
|
||||
</button>
|
||||
</div>
|
||||
<div id="log-output"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let logSource = null;
|
||||
let logVisible = false;
|
||||
|
||||
async function containerAction(action) {
|
||||
const btn = event.target.closest('button');
|
||||
const orig = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = `<span class="spinner-border spinner-border-sm"></span> ${action}...`;
|
||||
|
||||
const r = await api(`/api/guacamole/${action}`);
|
||||
showToast(r.ok ? `${action} izvršen` : r.msg, r.ok);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = orig;
|
||||
if (r.ok) setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
|
||||
function switchLog(service) {
|
||||
if (logVisible) {
|
||||
stopLog();
|
||||
startLog(service);
|
||||
}
|
||||
document.getElementById('log-title').textContent = service;
|
||||
}
|
||||
|
||||
function toggleLog() {
|
||||
if (logVisible) {
|
||||
stopLog();
|
||||
logVisible = false;
|
||||
document.getElementById('log-panel').classList.add('d-none');
|
||||
document.getElementById('log-btn-txt').textContent = 'Log';
|
||||
} else {
|
||||
logVisible = true;
|
||||
document.getElementById('log-panel').classList.remove('d-none');
|
||||
document.getElementById('log-btn-txt').textContent = 'Zatvori';
|
||||
startLog(document.getElementById('log-source').value);
|
||||
}
|
||||
}
|
||||
|
||||
function startLog(service) {
|
||||
stopLog();
|
||||
const out = document.getElementById('log-output');
|
||||
logSource = new EventSource(`/api/logs/${service}`);
|
||||
logSource.onmessage = e => {
|
||||
const line = document.createElement('div');
|
||||
line.textContent = JSON.parse(e.data);
|
||||
out.appendChild(line);
|
||||
out.scrollTop = out.scrollHeight;
|
||||
};
|
||||
}
|
||||
|
||||
function stopLog() {
|
||||
if (logSource) { logSource.close(); logSource = null; }
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
document.getElementById('log-output').innerHTML = '';
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user