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:
@@ -22,7 +22,6 @@
|
||||
<th>IP</th>
|
||||
<th>Роуты</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Handshake</th>
|
||||
<th>RX / TX</th>
|
||||
<th>Публичный ключ</th>
|
||||
<th>Действия</th>
|
||||
@@ -33,15 +32,19 @@
|
||||
<tr class="{{ 'row-disabled' if not p.enabled else '' }}">
|
||||
<td><span class="peer-name">{{ p.name }}</span></td>
|
||||
<td>
|
||||
<span class="badge {{ p.status }}">
|
||||
<i class="dot"></i>{{ p.status }}
|
||||
<span class="badge {{ p.status }}" title="{{ p.handshake_ago }}">
|
||||
<i class="dot"></i>
|
||||
<span>{{ p.status }}</span>
|
||||
<span class="badge-ago">{{ p.handshake_ago }}</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="mono-sm">{{ p.client_address }}</td>
|
||||
<td class="mono-sm text-muted">{{ p.routes }}</td>
|
||||
<td class="mono-sm text-muted">{{ p.endpoint }}</td>
|
||||
<td class="text-muted">{{ p.latest_handshake }}</td>
|
||||
<td class="text-muted">{{ p.rx }} / {{ p.tx }}</td>
|
||||
<td class="text-muted traffic">
|
||||
<span title="Получено">↓ {{ p.rx }}</span>
|
||||
<span title="Отправлено">↑ {{ p.tx }}</span>
|
||||
</td>
|
||||
<td><span class="pubkey" title="{{ p.public_key }}">{{ p.public_key[:20] }}…</span></td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
@@ -77,7 +80,7 @@
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="9" class="empty">Пиров нет. <a href="{{ url_for('new_peer') }}">Добавить первый</a></td>
|
||||
<td colspan="8" class="empty">Пиров нет. <a href="{{ url_for('new_peer') }}">Добавить первый</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user