Multi-interface management, topology view, performance tuning, precompiled CSS

- Interface model with per-interface subnet/port/keys; import/adopt existing wg-quick configs (key-less imported peers, optional key rotation), cascade delete
- Split wireguard.py into a package (keys via cryptography X25519, status, addressing, conf parse/render, sync, host tuning)
- ECharts horizontal topology view (interface -> peers -> site subnets)
- Advanced options: MTU, MSS clamping, FwMark/Table, custom PostUp/PostDown, per-peer keepalive override
- Runtime settings (sample interval/retention, online threshold, UI refresh) with traffic sample pruning; host tuning (UDP buffers, backlog, GRO forwarding)
- Precompiled Tailwind CSS replacing Play CDN runtime (fixes FOUC); stable table layout and diffed polling renders
- Host network mode in compose; NAT/isolation iptables moved into app sync

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
lofyer
2026-07-05 14:01:44 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent 192960ad3e
commit 1b227c2470
31 changed files with 2361 additions and 583 deletions
+36 -1
View File
@@ -6,7 +6,13 @@
<p class="mb-4 rounded-md bg-red-50 px-3 py-2 text-sm text-red-700">{{ error }}</p>
{% endif %}
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">{{ peer.name }}</h1>
<h1 class="flex items-center gap-2 text-2xl font-bold">
{{ peer.name }}
<span class="rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-500">{{ iface.name }}</span>
{% if not peer.has_private_key %}
<span class="rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-700">imported</span>
{% endif %}
</h1>
<div class="flex gap-2">
<form method="post" action="/peers/{{ peer.id }}/toggle">
<button type="submit"
@@ -96,6 +102,13 @@
value="{{ '%.1f' % (peer.quota_bytes / 1073741824) if peer.quota_bytes else '' }}"
class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
</label>
<label class="block text-sm font-medium text-slate-600">Keepalive (s)
<input name="persistent_keepalive" type="number" min="0" max="3600"
value="{{ peer.persistent_keepalive if peer.persistent_keepalive is not none else '' }}"
placeholder="interface default ({{ iface.persistent_keepalive }})"
class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
<span class="mt-1 block text-xs text-slate-400">0 disables keepalive (fixed-IP site-to-site peers). Empty = interface default.</span>
</label>
<label class="block text-sm font-medium text-slate-600">Site subnets behind this peer
<input name="extra_allowed_ips" value="{{ peer.extra_allowed_ips }}" placeholder="e.g. 192.168.5.0/24"
class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
@@ -147,6 +160,7 @@
<div class="rounded-xl bg-white p-5 shadow-sm">
<h3 class="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-400">Client config</h3>
{% if peer.has_private_key %}
<img src="/peers/{{ peer.id }}/qr" alt="QR code" class="mx-auto w-48 rounded-lg border border-slate-200">
<div class="mt-4 flex justify-center gap-2">
<a href="/peers/{{ peer.id }}/config"
@@ -158,14 +172,34 @@
Copy
</button>
</div>
{% else %}
<p class="text-sm text-slate-500">
This peer was imported from an existing config, so the server only knows its public key.
The client keeps using its current config and everything works, but the panel cannot
generate a .conf or QR code.
</p>
<p class="mt-3 text-sm text-slate-500">
To manage the client config from here, rotate its keys and install the new config on the device.
</p>
<form method="post" action="/peers/{{ peer.id }}/rotate" class="mt-4 text-center"
onsubmit="return confirm('Rotate keys? The device stays offline until the new config is installed.')">
<button type="submit"
class="rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700">
Rotate keys and generate config
</button>
</form>
{% endif %}
</div>
</div>
{% if client_config %}
<div class="mt-4 rounded-xl bg-white p-5 shadow-sm">
<h3 class="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-400">{{ peer.name }}.conf</h3>
<pre id="client-config" class="overflow-x-auto rounded-lg bg-slate-900 p-4 text-xs leading-relaxed text-slate-200">{{ client_config }}</pre>
</div>
{% endif %}
{% if client_config %}
<div class="mt-4 rounded-xl bg-white p-5 shadow-sm">
<h3 class="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-400">Client setup guide</h3>
<div class="space-y-5 text-sm">
@@ -205,6 +239,7 @@ nmcli connection up {{ peer.name }}</pre>
</div>
</div>
</div>
{% endif %}
<script>
function copyConfig(btn) {