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:
2026-05-06 10:10:19 +03:00
parent 530e93c1df
commit 904582e7fa
7 changed files with 620 additions and 170 deletions
+34 -17
View File
@@ -7,23 +7,40 @@
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
</head>
<body>
<header class="top">
<h1>WG Admin</h1>
<nav>
<a href="{{ url_for('index') }}">Клиенты</a>
<a href="{{ url_for('new_peer') }}">Добавить peer</a>
<a href="{{ url_for('scripts') }}">Скрипты</a>
<aside class="sidebar">
<div class="sidebar-logo">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4l3 3"/></svg>
<span>WG Admin</span>
</div>
<nav class="sidebar-nav">
<a href="{{ url_for('index') }}" class="{{ 'active' if request.endpoint == 'index' else '' }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
Клиенты
</a>
<a href="{{ url_for('new_peer') }}" class="{{ 'active' if request.endpoint == 'new_peer' else '' }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
Добавить peer
</a>
<a href="{{ url_for('scripts') }}" class="{{ 'active' if request.endpoint == 'scripts' else '' }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
Скрипты
</a>
</nav>
</header>
<main>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert {{category}}">{{message}}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
</aside>
<div class="layout">
<main>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert {{ category }}">
<span>{{ message }}</span>
<button onclick="this.parentElement.remove()"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
</div>
</body>
</html>