mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-01-01 06:16:16 +00:00
Add cluster API for worker management and status reporting
This commit is contained in:
152
cluster/cluster_api.py
Normal file
152
cluster/cluster_api.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import glob
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from .models import ClusterSettings, Worker, WorkerStatus
|
||||||
|
|
||||||
|
|
||||||
|
def get_ip_address(request):
|
||||||
|
ip_address = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||||
|
if ip_address:
|
||||||
|
ip_address = ip_address.split(',')[0]
|
||||||
|
else:
|
||||||
|
ip_address = request.META.get('REMOTE_ADDR')
|
||||||
|
return ip_address
|
||||||
|
|
||||||
|
|
||||||
|
def get_worker(request):
|
||||||
|
min_worker_version = 1
|
||||||
|
success = True
|
||||||
|
ip_address = get_ip_address(request)
|
||||||
|
token = request.GET.get('token', '')
|
||||||
|
try:
|
||||||
|
worker = Worker.objects.get(token=token)
|
||||||
|
except:
|
||||||
|
return None, False
|
||||||
|
|
||||||
|
worker_status, created = WorkerStatus.objects.get_or_create(worker=worker)
|
||||||
|
try:
|
||||||
|
worker_config_version = int(request.GET.get('worker_config_version'))
|
||||||
|
worker_version = int(request.GET.get('worker_version'))
|
||||||
|
except:
|
||||||
|
worker.error_status = 'missing_version'
|
||||||
|
worker.save()
|
||||||
|
return worker, False
|
||||||
|
|
||||||
|
if worker.error_status == 'missing_version':
|
||||||
|
worker.error_status = ''
|
||||||
|
worker.save()
|
||||||
|
|
||||||
|
if worker_version < min_worker_version:
|
||||||
|
worker.error_status = 'update_required'
|
||||||
|
worker.save()
|
||||||
|
return worker, False
|
||||||
|
if worker.error_status == 'update_required':
|
||||||
|
worker.error_status = ''
|
||||||
|
worker.save()
|
||||||
|
|
||||||
|
if worker_status.config_version != worker_config_version:
|
||||||
|
worker_status.config_version = worker_config_version
|
||||||
|
if worker_status.worker_version != worker_version:
|
||||||
|
worker_status.worker_version = worker_version
|
||||||
|
worker_status.last_seen = timezone.now()
|
||||||
|
worker_status.save()
|
||||||
|
|
||||||
|
if not worker.ip_address:
|
||||||
|
worker.ip_address = ip_address
|
||||||
|
worker.save()
|
||||||
|
|
||||||
|
if worker.ip_lock:
|
||||||
|
if worker.ip_address == ip_address:
|
||||||
|
if worker.error_status == 'ip_lock':
|
||||||
|
worker.error_status = ''
|
||||||
|
worker.save()
|
||||||
|
else:
|
||||||
|
worker.error_status = 'ip_lock'
|
||||||
|
worker.save()
|
||||||
|
success = False
|
||||||
|
else:
|
||||||
|
if worker.ip_address != ip_address:
|
||||||
|
worker.ip_address = ip_address
|
||||||
|
worker.save()
|
||||||
|
|
||||||
|
if worker.enabled:
|
||||||
|
if worker.error_status == 'worker_disabled':
|
||||||
|
worker.error_status = ''
|
||||||
|
worker.save()
|
||||||
|
else:
|
||||||
|
worker.error_status = 'worker_disabled'
|
||||||
|
worker.save()
|
||||||
|
success = False
|
||||||
|
|
||||||
|
cluster_settings, created = ClusterSettings.objects.get_or_create(name='cluster_settings')
|
||||||
|
if cluster_settings.enabled:
|
||||||
|
if worker.error_status == 'cluster_disabled':
|
||||||
|
worker.error_status = ''
|
||||||
|
worker.save()
|
||||||
|
else:
|
||||||
|
worker.error_status = 'cluster_disabled'
|
||||||
|
worker.save()
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return worker, success
|
||||||
|
|
||||||
|
|
||||||
|
def api_get_worker_config_files(request):
|
||||||
|
worker, success = get_worker(request)
|
||||||
|
if worker:
|
||||||
|
if worker.error_status or not success:
|
||||||
|
data = {'status': 'error', 'message': worker.error_status}
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
else:
|
||||||
|
data = {'status': 'error', 'message': 'Worker not found'}
|
||||||
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
|
config_files = (
|
||||||
|
glob.glob('/etc/wireguard/wg*.conf') +
|
||||||
|
glob.glob('/etc/wireguard/wg-firewall.sh')
|
||||||
|
)
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
|
||||||
|
for path in config_files:
|
||||||
|
filename = os.path.basename(path)
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
files[filename] = f.read()
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
'status': 'success',
|
||||||
|
'files': files,
|
||||||
|
},
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def api_cluster_status(request):
|
||||||
|
worker, success = get_worker(request)
|
||||||
|
if worker:
|
||||||
|
if worker.error_status or not success:
|
||||||
|
data = {'status': 'error', 'message': worker.error_status}
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
else:
|
||||||
|
data = {'status': 'error', 'message': 'Worker not found'}
|
||||||
|
return JsonResponse(data, status=403)
|
||||||
|
cluster_settings, created = ClusterSettings.objects.get_or_create(name='cluster_settings')
|
||||||
|
data = {
|
||||||
|
'status': 'success',
|
||||||
|
'worker_error_status': worker.error_status,
|
||||||
|
'cluster_settings': {
|
||||||
|
'enabled': cluster_settings.enabled,
|
||||||
|
'primary_enable_wireguard': cluster_settings.primary_enable_wireguard,
|
||||||
|
'stats_sync_interval': cluster_settings.stats_sync_interval,
|
||||||
|
'stats_cache_interval': cluster_settings.stats_cache_interval,
|
||||||
|
'cluster_mode': cluster_settings.cluster_mode,
|
||||||
|
'restart_mode': cluster_settings.restart_mode,
|
||||||
|
'config_version': cluster_settings.config_version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse(data, status=200)
|
||||||
18
cluster/migrations/0006_worker_error_status.py
Normal file
18
cluster/migrations/0006_worker_error_status.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-29 21:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cluster', '0005_alter_worker_token'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='worker',
|
||||||
|
name='error_status',
|
||||||
|
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled')], default='', max_length=32),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
cluster/migrations/0007_workerstatus_worker_version.py
Normal file
18
cluster/migrations/0007_workerstatus_worker_version.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-30 12:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cluster', '0006_worker_error_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workerstatus',
|
||||||
|
name='worker_version',
|
||||||
|
field=models.BooleanField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
cluster/migrations/0008_alter_worker_error_status.py
Normal file
18
cluster/migrations/0008_alter_worker_error_status.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-30 13:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cluster', '0007_workerstatus_worker_version'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='worker',
|
||||||
|
name='error_status',
|
||||||
|
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled'), ('missing_version', 'Please report worker_config_version and worker_version in the API request.')], default='', max_length=32),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
cluster/migrations/0009_alter_worker_error_status.py
Normal file
18
cluster/migrations/0009_alter_worker_error_status.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-30 14:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cluster', '0008_alter_worker_error_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='worker',
|
||||||
|
name='error_status',
|
||||||
|
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled'), ('missing_version', 'Please report worker_config_version and worker_version in the API request.'), ('update_required', 'Worker configuration update is required.')], default='', max_length=32),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-30 14:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cluster', '0009_alter_worker_error_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='worker',
|
||||||
|
name='error_status',
|
||||||
|
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled'), ('missing_version', 'Please report worker_config_version and worker_version in the API request.'), ('update_required', 'Worker update is required.')], default='', max_length=32),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='workerstatus',
|
||||||
|
name='worker_version',
|
||||||
|
field=models.PositiveIntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ClusterSettings(models.Model):
|
class ClusterSettings(models.Model):
|
||||||
@@ -41,6 +42,15 @@ class Worker(models.Model):
|
|||||||
city = models.CharField(max_length=100, blank=True, null=True)
|
city = models.CharField(max_length=100, blank=True, null=True)
|
||||||
hostname = models.CharField(max_length=100, blank=True, null=True)
|
hostname = models.CharField(max_length=100, blank=True, null=True)
|
||||||
|
|
||||||
|
error_status = models.CharField(default='', max_length=32, choices=(
|
||||||
|
('', ''),
|
||||||
|
('ip_lock', _('IP lock is enabled, but the worker is attempting to access from a different IP address.')),
|
||||||
|
('worker_disabled', _('Worker is not enabled')),
|
||||||
|
('cluster_disabled', _('Cluster is not enabled')),
|
||||||
|
('missing_version', _('Please report worker_config_version and worker_version in the API request.')),
|
||||||
|
('update_required', _('Worker update is required.'))
|
||||||
|
))
|
||||||
|
|
||||||
updated = models.DateTimeField(auto_now=True)
|
updated = models.DateTimeField(auto_now=True)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||||
@@ -53,6 +63,7 @@ class WorkerStatus(models.Model):
|
|||||||
last_restart = models.DateTimeField(blank=True, null=True)
|
last_restart = models.DateTimeField(blank=True, null=True)
|
||||||
config_version = models.PositiveIntegerField(default=0)
|
config_version = models.PositiveIntegerField(default=0)
|
||||||
config_pending = models.BooleanField(default=False)
|
config_pending = models.BooleanField(default=False)
|
||||||
|
worker_version = models.PositiveIntegerField(default=0)
|
||||||
active_peers = models.PositiveIntegerField(default=0)
|
active_peers = models.PositiveIntegerField(default=0)
|
||||||
wireguard_status = models.JSONField(default=dict)
|
wireguard_status = models.JSONField(default=dict)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def cluster_main(request):
|
|||||||
|
|
||||||
page_title = _('Cluster')
|
page_title = _('Cluster')
|
||||||
workers = Worker.objects.all().order_by('name')
|
workers = Worker.objects.all().order_by('name')
|
||||||
context = {'page_title': page_title, 'workers': workers}
|
context = {'page_title': page_title, 'workers': workers, 'worker_version': 10}
|
||||||
return render(request, 'cluster/workers_list.html', context)
|
return render(request, 'cluster/workers_list.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,18 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ worker.name }}</td>
|
<td>{{ worker.name }}</td>
|
||||||
<td style="width: 1%; white-space: nowrap;">
|
<td style="width: 1%; white-space: nowrap;">
|
||||||
|
{% if worker.error_status %}
|
||||||
|
<i class="fas fa-exclamation-triangle text-danger blink" title="{{ worker.get_error_status_display }}"></i>
|
||||||
|
{% else %}
|
||||||
{% if worker.enabled %}
|
{% if worker.enabled %}
|
||||||
<i class="fas fa-check text-green"></i>
|
<i class="fas fa-check text-green"></i>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fas fa-times text-gray"></i>
|
<i class="fas fa-times text-gray"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if worker_version > worker.workerstatus.worker_version %}
|
||||||
|
<i class="fas fa-cloud-download-alt text-primary blink" title="{% trans 'The worker is outdated. Please update it to the latest version.' %}"></i>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if worker.ip_address %}
|
{% if worker.ip_address %}
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
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 api.views import api_instance_info, api_peer_invite, api_peer_list, cron_check_updates, \
|
from api.views import api_instance_info, api_peer_invite, api_peer_list, cron_check_updates, \
|
||||||
cron_update_peer_latest_handshake, peer_info, routerfleet_authenticate_session, routerfleet_get_user_token, \
|
cron_update_peer_latest_handshake, peer_info, routerfleet_authenticate_session, routerfleet_get_user_token, \
|
||||||
wireguard_status
|
wireguard_status
|
||||||
|
from cluster.cluster_api import api_cluster_status, api_get_worker_config_files
|
||||||
from cluster.views import cluster_main, cluster_settings, worker_manage
|
from cluster.views import cluster_main, cluster_settings, worker_manage
|
||||||
from console.views import view_console
|
from console.views import view_console
|
||||||
from dns.views import view_apply_dns_config, view_manage_dns_settings, view_manage_filter_list, view_manage_static_host, \
|
from dns.views import view_apply_dns_config, view_manage_dns_settings, view_manage_filter_list, view_manage_static_host, \
|
||||||
@@ -38,7 +40,7 @@ from wireguard_peer.views import view_manage_ip_address, view_wireguard_peer_lis
|
|||||||
from wireguard_tools.views import download_config_or_qrcode, export_wireguard_configs, restart_wireguard_interfaces
|
from wireguard_tools.views import download_config_or_qrcode, export_wireguard_configs, restart_wireguard_interfaces
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('', view_apply_db_patches, name='apply_db_patches'),
|
path('', view_apply_db_patches, name='apply_db_patches'),
|
||||||
path('status/', view_wireguard_status, name='wireguard_status'),
|
path('status/', view_wireguard_status, name='wireguard_status'),
|
||||||
path('dns/', view_static_host_list, name='static_host_list'),
|
path('dns/', view_static_host_list, name='static_host_list'),
|
||||||
@@ -74,6 +76,8 @@ urlpatterns = [
|
|||||||
path('api/peer_invite/', api_peer_invite, name='api_peer_invite'),
|
path('api/peer_invite/', api_peer_invite, name='api_peer_invite'),
|
||||||
path('api/cron_check_updates/', cron_check_updates, name='cron_check_updates'),
|
path('api/cron_check_updates/', cron_check_updates, name='cron_check_updates'),
|
||||||
path('api/cron_update_peer_latest_handshake/', cron_update_peer_latest_handshake, name='cron_update_peer_latest_handshake'),
|
path('api/cron_update_peer_latest_handshake/', cron_update_peer_latest_handshake, name='cron_update_peer_latest_handshake'),
|
||||||
|
path('api/cluster/status/', api_cluster_status, name='api_cluster_status'),
|
||||||
|
path('api/cluster/worker/get_config_files/', api_get_worker_config_files, name='api_get_worker_config_files'),
|
||||||
path('firewall/port_forward/', view_redirect_rule_list, name='redirect_rule_list'),
|
path('firewall/port_forward/', view_redirect_rule_list, name='redirect_rule_list'),
|
||||||
path('firewall/manage_port_forward_rule/', manage_redirect_rule, name='manage_redirect_rule'),
|
path('firewall/manage_port_forward_rule/', manage_redirect_rule, name='manage_redirect_rule'),
|
||||||
path('firewall/rule_list/', view_firewall_rule_list, name='firewall_rule_list'),
|
path('firewall/rule_list/', view_firewall_rule_list, name='firewall_rule_list'),
|
||||||
|
|||||||
Reference in New Issue
Block a user