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>
This commit is contained in:
2026-05-06 10:18:38 +03:00
parent 904582e7fa
commit fe1cba2d02
4 changed files with 100 additions and 26 deletions
+28 -9
View File
@@ -9,34 +9,44 @@
<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 />
<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>
<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>
<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>
<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>
<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>Дополнительные роуты (advertised)</label>
<label>Дополнительные роуты <span class="optional">необязательно</span></label>
<input type="text" name="routes" placeholder="192.168.1.0/24, 10.0.0.0/8" />
<small>Сети, которые клиент анонсирует другим участникам. Необязательно.</small>
<small>Локальные сети за клиентом, доступные другим участникам VPN.</small>
</div>
<div class="form-actions">
@@ -51,5 +61,14 @@ 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 %}