feat(gui): security hardening, UI overhaul, light theme
- CSRF protection on all POST forms (session token) - ensure_schema() moved to module-level, removed from before_request - gunicorn now binds to 127.0.0.1 only, runs as unprivileged user wgadmin - nginx reverse proxy with HTTPS (Let's Encrypt, wg.4mont.ru) - HTTP → HTTPS redirect before Basic Auth prompt - Auth moved to nginx level (auth_basic), wg-peerctl called via sudo - ufw firewall: only 22/80/443/51820 open - fail2ban: SSH + nginx (5 attempts → 1h ban) - Add Enable/Disable toggle buttons in peer table - Add .conf file download route - Light theme: white background, blue accent, subtle shadows - Modern sidebar layout, styled badges, responsive forms Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+50
-41
@@ -1,46 +1,55 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<h2>Новый peer</h2>
|
||||
<form method="post" class="card">
|
||||
<label>Имя клиента
|
||||
<input name="name" required placeholder="astra" />
|
||||
</label>
|
||||
<label>Режим
|
||||
<select name="mode" id="mode">
|
||||
<option value="full">full (весь трафик через VPN)</option>
|
||||
<option value="split">split (только выбранные сети)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>AllowedIPs (для split)
|
||||
<input
|
||||
id="allowed_ips"
|
||||
name="allowed_ips"
|
||||
placeholder="{{ meta.get('WG_NETWORK','10.66.66.0/24') }}"
|
||||
data-default="{{ meta.get('WG_NETWORK','10.66.66.0/24') }}"
|
||||
/>
|
||||
</label>
|
||||
<label>Сети за клиентом (роуты)
|
||||
<input name="routes" placeholder="192.168.33.0/24,10.10.0.0/16" />
|
||||
</label>
|
||||
<button type="submit">Создать</button>
|
||||
</form>
|
||||
<div class="page-header">
|
||||
<h2>Новый peer</h2>
|
||||
</div>
|
||||
|
||||
<div class="card form-card">
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Имя клиента</label>
|
||||
<input type="text" name="name" placeholder="например: phone-ruslan" autofocus required />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Режим</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="mode" value="full" checked onchange="toggleRoutes(this)">
|
||||
<span>Полный туннель (0.0.0.0/0)</span>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="mode" value="split" onchange="toggleRoutes(this)">
|
||||
<span>Split-tunnel (только нужные сети)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="allowed-ips-group" style="display:none">
|
||||
<label>AllowedIPs для клиента</label>
|
||||
<input type="text" name="allowed_ips" placeholder="{{ meta.get('WG_NETWORK','10.66.66.0/24') }}" />
|
||||
<small>Через запятую. Оставьте пустым — подставится сеть WG.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Дополнительные роуты (advertised)</label>
|
||||
<input type="text" name="routes" placeholder="192.168.1.0/24, 10.0.0.0/8" />
|
||||
<small>Сети, которые клиент анонсирует другим участникам. Необязательно.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Создать peer</button>
|
||||
<a href="{{ url_for('index') }}" class="btn">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const mode = document.getElementById("mode");
|
||||
const allowed = document.getElementById("allowed_ips");
|
||||
const def = allowed.dataset.default || "10.66.66.0/24";
|
||||
|
||||
function syncAllowed() {
|
||||
if (mode.value === "split") {
|
||||
if (!allowed.value.trim()) allowed.value = def;
|
||||
allowed.readOnly = false;
|
||||
} else {
|
||||
allowed.readOnly = true;
|
||||
}
|
||||
}
|
||||
|
||||
mode.addEventListener("change", syncAllowed);
|
||||
syncAllowed();
|
||||
})();
|
||||
function toggleRoutes(el) {
|
||||
document.getElementById('allowed-ips-group').style.display =
|
||||
el.value === 'split' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user