Files
vacuum-wall/webui/templates/wireguard.html
T
mteehan 37039351be fix htmx refactor route mismatches and remaining TODO items
- wireguard: POST /peers with JSON encoding (was /add-peer)
- rules: delete by rule_id in URL path (was JSON body); pass rule objects with id from server; add hx-disable to initial render
- nat: port forward delete uses URL path params to match blueprint
- nat: masquerade toggle uses native hx-post/hx-vals (was inline fetch)
- app.js renderers updated to use URL path deletes for rules and forwards
- remove TODO.md
2026-05-17 01:17:12 +00:00

124 lines
5.5 KiB
HTML

{% extends "base.html" %}
{% block title %}WireGuard - Vacuum Wall{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h1>WireGuard</h1>
<div class="subtitle">VPN tunnel management</div>
</div>
<div class="flex gap-2">
<button class="btn {{ 'btn-outline' if (wg_status is defined and wg_status.get('state') == 'up') else 'btn-primary' }}"
hx-post="/api/wireguard/down"
hx-swap="none"
hx-on::after-request="if(evt.detail.successful){ showSuccessToast('Tunnel stopped'); }">
Stop Tunnel
</button>
<button class="btn {{ 'btn-outline' if (wg_status is not defined or wg_status.get('state') != 'up') else 'btn-primary' }}"
hx-post="/api/wireguard/apply"
hx-swap="none"
hx-on::after-request="if(evt.detail.successful){ showSuccessToast('Tunnel started'); }">
Start Tunnel
</button>
</div>
</div>
<!-- Tunnel Status -->
<div class="card mb-4">
<div class="flex items-center justify-between">
<div>
<h3>
<span class="status-dot {{ 'status-up' if (wg_status is defined and wg_status.get('state') == 'up') else 'status-down' }}"></span>
Tunnel State: <strong>{{ 'UP' if (wg_status is defined and wg_status.get('state') == 'up') else 'DOWN' }}</strong>
</h3>
</div>
<div class="text-sm text-muted">
{% if config %}
Listen Port: <strong>{{ config.get('listen_port', 'N/A') }}</strong> &nbsp;|&nbsp;
Public Key: <strong>{{ config.get('public_key', 'N/A')[:12] if config.get('public_key') else 'N/A' }}...</strong> &nbsp;|&nbsp;
Address: <strong>{{ config.get('address', 'N/A') }}</strong>
{% endif %}
</div>
</div>
</div>
<!-- Add Peer Form -->
<div class="section-title">Peers</div>
<div class="card mb-4">
<h3>Add Peer</h3>
<form hx-post="/api/wireguard/peers" hx-encoding="json" hx-swap="none" hx-on::after-request="if(evt.detail.successful){ refreshTable('/api/wireguard/peers', document.getElementById('peer-rows'), renderPeers); showSuccessToast('Peer added'); }">
<div class="inline-form">
<div class="form-group">
<label for="peer-name">Name</label>
<input type="text" id="peer-name" name="name" placeholder="client-1" required style="width:140px;">
</div>
<div class="form-group">
<label for="peer-pubkey">Public Key</label>
<input type="text" id="peer-pubkey" name="public_key" placeholder="Base64 public key (48 chars)" required style="width:260px;">
</div>
<div class="form-group">
<label for="peer-allowed">Allowed IPs</label>
<input type="text" id="peer-allowed" name="allowed_ips" placeholder="10.8.0.2/32" value="10.8.0.{% set next = (peers or []|length + 2) %}{{ next }}/32" required style="width:160px;">
</div>
<button type="submit" class="btn btn-primary">Add Peer</button>
</div>
</form>
</div>
<!-- Peers Table -->
<div class="card">
<table>
<thead>
<tr>
<th>Name</th>
<th>Public Key</th>
<th>Allowed IPs</th>
<th>Endpoint</th>
<th>Latest Handshake</th>
<th>Transfer</th>
<th style="width:160px;">Actions</th>
</tr>
</thead>
<tbody id="peer-rows">
{% for peer in (peers or []) %}
<tr>
<td>
<span class="status-dot {{ 'status-up' if peer.get('latest_handshake') else 'status-down' }}"></span>
<strong>{{ peer.get('name', 'unnamed') }}</strong>
</td>
<td style="font-family:monospace;font-size:11px;">{{ peer.get('public_key', 'N/A')[:20] }}...</td>
<td class="text-sm">{{ peer.get('allowed_ips', '-') }}</td>
<td class="text-sm">{{ peer.get('endpoint', '-') }}</td>
<td class="text-sm">{{ peer.get('latest_handshake', 'Never') or 'Never' }}</td>
<td class="text-sm">
<div>Recv: {{ peer.get('transfer_recv', '0') or '0' }}</div>
<div>Sent: {{ peer.get('transfer_sent', '0') or '0' }}</div>
</td>
<td>
<div class="flex gap-2">
<button class="btn btn-sm btn-outline" onclick="downloadPeerConfig('{{ peer.get('name', '') }}')">Config</button>
<form hx-delete="/api/wireguard/peers/{{ peer.get('name', '') }}" hx-swap="none" hx-confirm="Remove peer {{ peer.get('name', '') }}?" hx-on::after-request="if(evt.detail.successful){ refreshTable('/api/wireguard/peers', document.getElementById('peer-rows'), renderPeers); showSuccessToast('Peer removed'); }">
<button type="submit" class="btn btn-sm btn-danger">Remove</button>
</form>
</div>
</td>
</tr>
{% endfor %}
{% if not (peers or []) %}
<tr>
<td colspan="7" class="text-muted text-sm">No peers configured. Add a peer above.</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<script>
function downloadPeerConfig(peerName) {
var url = '/api/wireguard/peers/' + encodeURIComponent(peerName) + '/config';
window.open(url, '_blank');
}
</script>
{% endblock %}