mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2025-04-19 00:45:16 +00:00
Peer list with details
This commit is contained in:
parent
0a14192444
commit
cfcabed244
0
api/__init__.py
Normal file
0
api/__init__.py
Normal file
3
api/admin.py
Normal file
3
api/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
api/apps.py
Normal file
6
api/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'api'
|
0
api/migrations/__init__.py
Normal file
0
api/migrations/__init__.py
Normal file
3
api/models.py
Normal file
3
api/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
api/tests.py
Normal file
3
api/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
55
api/views.py
Normal file
55
api/views.py
Normal file
@ -0,0 +1,55 @@
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.contrib.auth.decorators import login_required
|
||||
import subprocess
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def wireguard_status(request):
|
||||
commands = {
|
||||
'latest-handshakes': "wg show all latest-handshakes | expand | tr -s ' '",
|
||||
'allowed-ips': "wg show all allowed-ips | expand | tr -s ' '",
|
||||
'transfer': "wg show all transfer | expand | tr -s ' '",
|
||||
'endpoints': "wg show all endpoints | expand | tr -s ' '",
|
||||
}
|
||||
|
||||
output = {}
|
||||
|
||||
for key, command in commands.items():
|
||||
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
return JsonResponse({'error': stderr}, status=400)
|
||||
|
||||
current_interface = None
|
||||
for line in stdout.strip().split('\n'):
|
||||
parts = line.split()
|
||||
if len(parts) >= 3:
|
||||
interface, peer, value = parts[0], parts[1], " ".join(parts[2:])
|
||||
current_interface = interface
|
||||
else:
|
||||
continue
|
||||
|
||||
if interface not in output:
|
||||
output[interface] = {}
|
||||
|
||||
if peer not in output[interface]:
|
||||
output[interface][peer] = {
|
||||
'allowed-ips': [],
|
||||
'latest-handshakes': '',
|
||||
'transfer': {'tx': 0, 'rx': 0},
|
||||
'endpoints': '',
|
||||
}
|
||||
|
||||
if key == 'allowed-ips':
|
||||
output[interface][peer]['allowed-ips'].append(value)
|
||||
elif key == 'transfer':
|
||||
tx, rx = value.split()[-2:]
|
||||
output[interface][peer]['transfer'] = {'tx': int(tx), 'rx': int(rx)}
|
||||
elif key == 'endpoints':
|
||||
output[interface][peer]['endpoints'] = value
|
||||
else:
|
||||
output[interface][peer][key] = value
|
||||
|
||||
return JsonResponse(output)
|
@ -192,7 +192,7 @@
|
||||
<footer class="main-footer">
|
||||
wireguard-webadmin
|
||||
<div class="float-right d-none d-sm-inline-block">
|
||||
<b>Version</b> 0.8.1 beta
|
||||
<b>Version</b> 0.8.3 beta
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
@ -7,10 +7,11 @@
|
||||
<p>If you encounter any issues or have suggestions, please open an issue on GitHub so I can review it.</p>
|
||||
<h2>TODO list</h2>
|
||||
<ul>
|
||||
<li>The peers page does not yet display data from WireGuard such as the last handshake and data transfer.</li>
|
||||
<li>The verification of allowed IPs against the output of wg show has not yet been implemented. This will help detect configuration errors for crypto routing.</li>
|
||||
<li>The DNS server provided to the peer is still hardcoded.</li>
|
||||
<li>AllowedIPs on client configuration side.</li>
|
||||
<li>Make Peer's last handshake permanent</li>
|
||||
<li>Setting for refresh interval in Peer list</li>
|
||||
<li>wireguard_webadmin Update notification</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
@ -19,8 +19,8 @@
|
||||
<div class="tab-pane fade show active" id="custom-content-below-home" role="tabpanel" aria-labelledby="custom-content-below-home-tab">
|
||||
<div class="row">
|
||||
{% for peer in peer_list %}
|
||||
<div class="col-md-6">
|
||||
<div class="callout callout-success">
|
||||
<div class="col-md-6" id="peer-{{ peer.public_key }}">
|
||||
<div class="callout">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h5>
|
||||
{% if peer.name %}
|
||||
@ -34,6 +34,30 @@
|
||||
<a href="/peer/manage/?peer={{ peer.uuid }}"><i class="far fa-edit"></i></a></span>
|
||||
</div>
|
||||
{% comment %}This needs to be improved{% endcomment %}
|
||||
<p>
|
||||
<b>Transfer:</b> <span id="peer-transfer-{{ peer.public_key }}"></span><br>
|
||||
<b>Latest Handshake:</b> <span id="peer-latest-handshake-{{ peer.public_key }}"></span><br>
|
||||
<b>Endpoints:</b> <span id="peer-endpoints-{{ peer.public_key }}"></span><br>
|
||||
<b>Allowed IPs: </b><span id="peer-allowed-ips-{{ peer.public_key }}">
|
||||
{% for address in peer.peerallowedip_set.all %}{% if address.priority == 0 %}
|
||||
{% if address.missing_from_wireguard %}
|
||||
<a href='#' class='bg-warning' title="This address does not appear in the wg show command output, likely indicating that another peer has an IP overlapping this network or that the configuration file is outdated.">{{ address }}</a>
|
||||
{% else %}
|
||||
{{ address }}
|
||||
{% endif %}
|
||||
{% endif %}{% endfor %}
|
||||
|
||||
{% for address in peer.peerallowedip_set.all %}{% if address.priority >= 1 %}
|
||||
{% if address.missing_from_wireguard %}
|
||||
<a href='#' class='bg-warning' title="This address does not appear in the wg show command output, likely indicating that another peer has an IP overlapping this network or that the configuration file is outdated.">{{ address }}</a>
|
||||
{% else %}
|
||||
{{ address }}
|
||||
{% endif %}
|
||||
{% endif %}{% endfor %}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
{% comment %}
|
||||
<p>{% for address in peer.peerallowedip_set.all %}{% if address.priority == 0 %}
|
||||
{% if address.missing_from_wireguard %}
|
||||
<a href='#' class='bg-warning' title="This address does not appear in the wg show command output, likely indicating that another peer has an IP overlapping this network or that the configuration file is outdated.">{{ address }}</a>
|
||||
@ -48,7 +72,10 @@
|
||||
{% else %}
|
||||
{{ address }}
|
||||
{% endif %}
|
||||
{% endif %}{% endfor %}</p>
|
||||
{% endif %}{% endfor %}
|
||||
</p>
|
||||
{% endcomment %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -95,10 +122,105 @@
|
||||
|
||||
|
||||
{% block custom_page_scripts %}
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const fetchWireguardStatus = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/wireguard_status/');
|
||||
const data = await response.json();
|
||||
updateUI(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching Wireguard status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchWireguardStatus();
|
||||
setInterval(fetchWireguardStatus, 30000);
|
||||
});
|
||||
|
||||
const updateUI = (data) => {
|
||||
for (const [interfaceName, peers] of Object.entries(data)) {
|
||||
for (const [peerId, peerInfo] of Object.entries(peers)) {
|
||||
const peerDiv = document.getElementById(`peer-${peerId}`);
|
||||
if (peerDiv) {
|
||||
updatePeerInfo(peerDiv, peerId, peerInfo);
|
||||
updateCalloutClass(peerDiv, peerInfo['latest-handshakes']);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updatePeerInfo = (peerDiv, peerId, peerInfo) => {
|
||||
const escapedPeerId = peerId.replace(/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g, '\\$1');
|
||||
const transfer = peerDiv.querySelector(`#peer-transfer-${escapedPeerId}`);
|
||||
const latestHandshake = peerDiv.querySelector(`#peer-latest-handshake-${escapedPeerId}`);
|
||||
const endpoints = peerDiv.querySelector(`#peer-endpoints-${escapedPeerId}`);
|
||||
const allowedIps = peerDiv.querySelector(`#peer-allowed-ips-${escapedPeerId}`);
|
||||
|
||||
transfer.textContent = `${convertBytes(peerInfo.transfer.tx)} TX, ${convertBytes(peerInfo.transfer.rx)} RX`;
|
||||
latestHandshake.textContent = `${peerInfo['latest-handshakes'] !== '0' ? new Date(parseInt(peerInfo['latest-handshakes']) * 1000).toLocaleString() : '0'}`;
|
||||
endpoints.textContent = `${peerInfo.endpoints}`;
|
||||
checkAllowedIps(allowedIps, peerInfo['allowed-ips']);
|
||||
};
|
||||
|
||||
const convertBytes = (bytes) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const checkAllowedIps = (allowedIpsElement, allowedIpsApiResponse) => {
|
||||
const apiIps = allowedIpsApiResponse[0].split(' ');
|
||||
const htmlIpsText = allowedIpsElement.textContent.trim();
|
||||
const htmlIpsArray = htmlIpsText.match(/\b(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}\b/g);
|
||||
|
||||
allowedIpsElement.innerHTML = '';
|
||||
htmlIpsArray.forEach((ip, index, array) => {
|
||||
const ipSpan = document.createElement('span');
|
||||
ipSpan.textContent = ip;
|
||||
allowedIpsElement.appendChild(ipSpan);
|
||||
|
||||
if (!apiIps.includes(ip)) {
|
||||
ipSpan.style.color = 'red';
|
||||
ipSpan.style.textDecoration = 'underline';
|
||||
ipSpan.title = 'This address does not appear in the wg show command output, likely indicating that another peer has an IP overlapping this network or that the configuration file is outdated.';
|
||||
}
|
||||
|
||||
if (index < array.length - 1) {
|
||||
allowedIpsElement.appendChild(document.createTextNode(', '));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const updateCalloutClass = (peerDiv, latestHandshake) => {
|
||||
const calloutDiv = peerDiv.querySelector('.callout');
|
||||
calloutDiv.classList.remove('callout-success', 'callout-info', 'callout-warning', 'callout-danger');
|
||||
const handshakeAge = Date.now() / 1000 - parseInt(latestHandshake);
|
||||
|
||||
if (latestHandshake === '0') {
|
||||
calloutDiv.classList.add('callout-danger');
|
||||
} else if (handshakeAge < 600) {
|
||||
calloutDiv.classList.add('callout-success');
|
||||
} else if (handshakeAge < 1800) {
|
||||
calloutDiv.classList.add('callout-info');
|
||||
} else if (handshakeAge < 21600) {
|
||||
calloutDiv.classList.add('callout-warning');
|
||||
}
|
||||
|
||||
calloutDiv.style.transition = 'all 5s';
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function openImageLightbox(url) {
|
||||
window.open(url, 'Image', 'width=500,height=500,toolbar=0,location=0,menubar=0');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
@ -30,6 +30,9 @@ class PeerAllowedIPForm(forms.ModelForm):
|
||||
priority = cleaned_data.get('priority')
|
||||
allowed_ip = cleaned_data.get('allowed_ip')
|
||||
netmask = cleaned_data.get('netmask')
|
||||
if allowed_ip is None:
|
||||
raise forms.ValidationError("Please provide a valid IP address.")
|
||||
|
||||
wireguard_network = ipaddress.ip_network(f"{self.current_peer.wireguard_instance.address}/{self.current_peer.wireguard_instance.netmask}", strict=False)
|
||||
|
||||
if priority == 0:
|
||||
|
@ -119,7 +119,7 @@ def view_wireguard_peer_manage(request):
|
||||
if current_peer.name:
|
||||
page_title += current_peer.name
|
||||
else:
|
||||
page_title += current_peer.public_key
|
||||
page_title += current_peer.public_key[:16] + ("..." if len(current_peer.public_key) > 16 else "")
|
||||
if request.method == 'POST':
|
||||
form = PeerForm(request.POST, instance=current_peer)
|
||||
if form.is_valid():
|
||||
@ -157,7 +157,7 @@ def view_manage_ip_address(request):
|
||||
if current_peer.name:
|
||||
page_title += current_peer.name
|
||||
else:
|
||||
page_title += current_peer.public_key
|
||||
page_title += current_peer.public_key[:10] + ("..." if len(current_peer.public_key) > 16 else "")
|
||||
if request.GET.get('action') == 'delete':
|
||||
if request.GET.get('confirmation') == 'delete':
|
||||
current_ip.delete()
|
||||
|
@ -22,6 +22,7 @@ from console.views import view_console
|
||||
from user_manager.views import view_user_list, view_manage_user
|
||||
from accounts.views import view_create_first_user, view_login, view_logout
|
||||
from wireguard_tools.views import export_wireguard_configs, download_config_or_qrcode, restart_wireguard_interfaces
|
||||
from api.views import wireguard_status
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@ -41,4 +42,6 @@ urlpatterns = [
|
||||
path('accounts/create_first_user/', view_create_first_user, name='create_first_user'),
|
||||
path('accounts/login/', view_login, name='login'),
|
||||
path('accounts/logout/', view_logout, name='logout'),
|
||||
path('api/wireguard_status/', wireguard_status, name='api_wireguard_status'),
|
||||
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user