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">
|
<footer class="main-footer">
|
||||||
wireguard-webadmin
|
wireguard-webadmin
|
||||||
<div class="float-right d-none d-sm-inline-block">
|
<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>
|
</div>
|
||||||
</footer>
|
</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>
|
<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>
|
<h2>TODO list</h2>
|
||||||
<ul>
|
<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>The DNS server provided to the peer is still hardcoded.</li>
|
||||||
<li>AllowedIPs on client configuration side.</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>
|
</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="tab-pane fade show active" id="custom-content-below-home" role="tabpanel" aria-labelledby="custom-content-below-home-tab">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% for peer in peer_list %}
|
{% for peer in peer_list %}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6" id="peer-{{ peer.public_key }}">
|
||||||
<div class="callout callout-success">
|
<div class="callout">
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<h5>
|
<h5>
|
||||||
{% if peer.name %}
|
{% if peer.name %}
|
||||||
@ -34,6 +34,30 @@
|
|||||||
<a href="/peer/manage/?peer={{ peer.uuid }}"><i class="far fa-edit"></i></a></span>
|
<a href="/peer/manage/?peer={{ peer.uuid }}"><i class="far fa-edit"></i></a></span>
|
||||||
</div>
|
</div>
|
||||||
{% comment %}This needs to be improved{% endcomment %}
|
{% 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 %}
|
<p>{% for address in peer.peerallowedip_set.all %}{% if address.priority == 0 %}
|
||||||
{% if address.missing_from_wireguard %}
|
{% 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>
|
<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 %}
|
{% else %}
|
||||||
{{ address }}
|
{{ address }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}{% endfor %}</p>
|
{% endif %}{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -95,10 +122,105 @@
|
|||||||
|
|
||||||
|
|
||||||
{% block custom_page_scripts %}
|
{% 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>
|
<script>
|
||||||
function openImageLightbox(url) {
|
function openImageLightbox(url) {
|
||||||
window.open(url, 'Image', 'width=500,height=500,toolbar=0,location=0,menubar=0');
|
window.open(url, 'Image', 'width=500,height=500,toolbar=0,location=0,menubar=0');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -30,6 +30,9 @@ class PeerAllowedIPForm(forms.ModelForm):
|
|||||||
priority = cleaned_data.get('priority')
|
priority = cleaned_data.get('priority')
|
||||||
allowed_ip = cleaned_data.get('allowed_ip')
|
allowed_ip = cleaned_data.get('allowed_ip')
|
||||||
netmask = cleaned_data.get('netmask')
|
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)
|
wireguard_network = ipaddress.ip_network(f"{self.current_peer.wireguard_instance.address}/{self.current_peer.wireguard_instance.netmask}", strict=False)
|
||||||
|
|
||||||
if priority == 0:
|
if priority == 0:
|
||||||
|
@ -119,7 +119,7 @@ def view_wireguard_peer_manage(request):
|
|||||||
if current_peer.name:
|
if current_peer.name:
|
||||||
page_title += current_peer.name
|
page_title += current_peer.name
|
||||||
else:
|
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':
|
if request.method == 'POST':
|
||||||
form = PeerForm(request.POST, instance=current_peer)
|
form = PeerForm(request.POST, instance=current_peer)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -157,7 +157,7 @@ def view_manage_ip_address(request):
|
|||||||
if current_peer.name:
|
if current_peer.name:
|
||||||
page_title += current_peer.name
|
page_title += current_peer.name
|
||||||
else:
|
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('action') == 'delete':
|
||||||
if request.GET.get('confirmation') == 'delete':
|
if request.GET.get('confirmation') == 'delete':
|
||||||
current_ip.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 user_manager.views import view_user_list, view_manage_user
|
||||||
from accounts.views import view_create_first_user, view_login, view_logout
|
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 wireguard_tools.views import export_wireguard_configs, download_config_or_qrcode, restart_wireguard_interfaces
|
||||||
|
from api.views import wireguard_status
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -41,4 +42,6 @@ urlpatterns = [
|
|||||||
path('accounts/create_first_user/', view_create_first_user, name='create_first_user'),
|
path('accounts/create_first_user/', view_create_first_user, name='create_first_user'),
|
||||||
path('accounts/login/', view_login, name='login'),
|
path('accounts/login/', view_login, name='login'),
|
||||||
path('accounts/logout/', view_logout, name='logout'),
|
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