Files
ruslan fe1cba2d02 fix(gui): correct wg dump indexes, status, traffic, UX improvements
- Fix off-by-one bug in wg_dump(): handshake was read from parts[5] (rx_bytes),
  now correctly reads from parts[4]; rx/tx shifted accordingly
- Run wg show via sudo to work under unprivileged wgadmin user
- Remove NoNewPrivileges from systemd service (needed for sudo)
- Merge Handshake column into Status badge (shows "online · 2м назад")
- Add humanize_ago() for human-readable handshake time
- Add next_free_ip() to suggest next available IP in new peer form
- Add device type quick-select buttons (Phone/Laptop/PC/Router/Server/Tablet)
- Placeholder in AllowedIPs now shows the real next free IP
- Traffic column shows ↓ rx / ↑ tx separately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 10:18:38 +03:00

75 lines
3.3 KiB
HTML

{% extends 'base.html' %}
{% block content %}
<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" id="name-input" placeholder="например: iphone-ruslan" autofocus required />
<div class="name-hints">
<span class="hint-label">Быстрый выбор:</span>
<button type="button" class="hint-btn" onclick="setPrefix('phone')">Phone</button>
<button type="button" class="hint-btn" onclick="setPrefix('laptop')">Laptop</button>
<button type="button" class="hint-btn" onclick="setPrefix('pc')">PC</button>
<button type="button" class="hint-btn" onclick="setPrefix('router')">Router</button>
<button type="button" class="hint-btn" onclick="setPrefix('server')">Server</button>
<button type="button" class="hint-btn" onclick="setPrefix('tablet')">Tablet</button>
</div>
<small>Рекомендуем формат <code>тип-место</code>: <code>laptop-home</code>, <code>router-office</code>, <code>phone-moscow</code></small>
</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>Полный туннель — весь трафик через VPN (<code>0.0.0.0/0</code>)</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="{{ next_ip or meta.get('WG_NETWORK','10.66.66.0/24') }}" />
<small>Следующий свободный IP: <code>{{ next_ip or 'не определён' }}</code>. Оставьте пустым — подставится автоматически.</small>
</div>
<div class="form-group">
<label>Дополнительные роуты <span class="optional">необязательно</span></label>
<input type="text" name="routes" placeholder="192.168.1.0/24, 10.0.0.0/8" />
<small>Локальные сети за клиентом, доступные другим участникам VPN.</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>
function toggleRoutes(el) {
document.getElementById('allowed-ips-group').style.display =
el.value === 'split' ? 'block' : 'none';
}
function setPrefix(type) {
const inp = document.getElementById('name-input');
const cur = inp.value;
const m = cur.match(/^[a-z]+-?(.*)$/i);
const suffix = (m && m[1]) ? m[1] : '';
inp.value = suffix ? type + '-' + suffix : type + '-';
inp.focus();
inp.setSelectionRange(inp.value.length, inp.value.length);
}
</script>
{% endblock %}