diff --git a/vpn/admin.py b/vpn/admin.py index 6ab3715..68f5ff5 100644 --- a/vpn/admin.py +++ b/vpn/admin.py @@ -719,10 +719,19 @@ class UserAdmin(admin.ModelAdmin): @admin.register(AccessLog) class AccessLogAdmin(admin.ModelAdmin): - list_display = ('user', 'server', 'action', 'formatted_timestamp') + list_display = ('user', 'server', 'acl_link_display', 'action', 'formatted_timestamp') list_filter = ('user', 'server', 'action', 'timestamp') - search_fields = ('user', 'server', 'action', 'timestamp', 'data') - readonly_fields = ('server', 'user', 'formatted_timestamp', 'action', 'formated_data') + search_fields = ('user', 'server', 'acl_link_id', 'action', 'timestamp', 'data') + readonly_fields = ('server', 'user', 'acl_link_id', 'formatted_timestamp', 'action', 'formated_data') + + @admin.display(description='Link', ordering='acl_link_id') + def acl_link_display(self, obj): + if obj.acl_link_id: + return format_html( + '{}', + obj.acl_link_id[:12] + '...' if len(obj.acl_link_id) > 12 else obj.acl_link_id + ) + return '-' @admin.display(description='Timestamp') def formatted_timestamp(self, obj): diff --git a/vpn/migrations/0006_accesslog_acl_link_id.py b/vpn/migrations/0006_accesslog_acl_link_id.py new file mode 100644 index 0000000..17e4ffe --- /dev/null +++ b/vpn/migrations/0006_accesslog_acl_link_id.py @@ -0,0 +1,22 @@ +# Generated migration for AccessLog acl_link_id field + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('vpn', '0005_userstatistics'), + ] + + operations = [ + migrations.AddField( + model_name='accesslog', + name='acl_link_id', + field=models.CharField(blank=True, editable=False, help_text='ID of the ACL link used', max_length=1024, null=True), + ), + migrations.AddIndex( + model_name='accesslog', + index=models.Index(fields=['acl_link_id'], name='vpn_accessl_acl_lin_b23c6e_idx'), + ), + ] diff --git a/vpn/migrations/0007_merge_20250721_1345.py b/vpn/migrations/0007_merge_20250721_1345.py new file mode 100644 index 0000000..dbc841c --- /dev/null +++ b/vpn/migrations/0007_merge_20250721_1345.py @@ -0,0 +1,14 @@ +# Generated by Django 5.1.7 on 2025-07-21 10:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('vpn', '0006_accesslog_acl_link_id'), + ('vpn', '0006_rename_vpn_usersta_user_id_1c7cd0_idx_vpn_usersta_user_id_512036_idx_and_more'), + ] + + operations = [ + ] diff --git a/vpn/models.py b/vpn/models.py index d771856..fc48415 100644 --- a/vpn/models.py +++ b/vpn/models.py @@ -69,6 +69,7 @@ class TaskExecutionLog(models.Model): class AccessLog(models.Model): user = models.CharField(max_length=256, blank=True, null=True, editable=False) server = models.CharField(max_length=256, blank=True, null=True, editable=False) + acl_link_id = models.CharField(max_length=1024, blank=True, null=True, editable=False, help_text="ID of the ACL link used") action = models.CharField(max_length=100, editable=False) data = models.TextField(default="", blank=True, editable=False) timestamp = models.DateTimeField(auto_now_add=True) @@ -77,12 +78,14 @@ class AccessLog(models.Model): indexes = [ models.Index(fields=['user']), models.Index(fields=['server']), + models.Index(fields=['acl_link_id']), models.Index(fields=['timestamp']), models.Index(fields=['action', 'timestamp']), ] def __str__(self): - return f"{self.action} {self.user} request for {self.server} at {self.timestamp}" + link_part = f" (link: {self.acl_link_id})" if self.acl_link_id else "" + return f"{self.action} {self.user} request for {self.server}{link_part} at {self.timestamp}" class User(AbstractUser): #is_active = False diff --git a/vpn/tasks.py b/vpn/tasks.py index 408533e..704d2c6 100644 --- a/vpn/tasks.py +++ b/vpn/tasks.py @@ -252,10 +252,11 @@ def update_user_statistics(self): for link in acl_links: server_name = link.acl.server.name - # Calculate total connections for this server (all time) + # Calculate total connections for this specific link (all time) total_connections = AccessLog.objects.filter( user=user.username, server=server_name, + acl_link_id=link.link, action='Success' ).count() @@ -263,6 +264,7 @@ def update_user_statistics(self): recent_connections = AccessLog.objects.filter( user=user.username, server=server_name, + acl_link_id=link.link, action='Success', timestamp__gte=thirty_days_ago ).count() @@ -278,6 +280,7 @@ def update_user_statistics(self): day_connections = AccessLog.objects.filter( user=user.username, server=server_name, + acl_link_id=link.link, action='Success', timestamp__gte=day_start, timestamp__lt=day_end @@ -300,7 +303,7 @@ def update_user_statistics(self): ) action = "created" if created else "updated" - logger.debug(f"{action} stats for {user.username} on {server_name} (link: {link.link})") + logger.debug(f"{action} stats for {user.username} on {server_name} (link: {link.link}): {total_connections} total, {recent_connections} recent") updated_count += 1 diff --git a/vpn/views.py b/vpn/views.py index c498694..e5aed0d 100644 --- a/vpn/views.py +++ b/vpn/views.py @@ -203,8 +203,13 @@ def shadowsocks(request, link): logger.info(f"Found ACL link for user {acl.user.username} on server {acl.server.name}") except Http404: logger.warning(f"ACL link not found: {link}") - AccessLog.objects.create(user=None, server="Unknown", action="Failed", - data=f"ACL not found for link: {link}") + AccessLog.objects.create( + user=None, + server="Unknown", + acl_link_id=link, + action="Failed", + data=f"ACL not found for link: {link}" + ) return JsonResponse({"error": "Not allowed"}, status=403) try: @@ -212,8 +217,13 @@ def shadowsocks(request, link): logger.info(f"Successfully retrieved user credentials for {acl.user.username} from {acl.server.name}") except Exception as e: logger.error(f"Failed to get user credentials for {acl.user.username} from {acl.server.name}: {e}") - AccessLog.objects.create(user=acl.user, server=acl.server.name, action="Failed", - data=f"Failed to get credentials: {e}") + AccessLog.objects.create( + user=acl.user.username, + server=acl.server.name, + acl_link_id=acl_link.link, + action="Failed", + data=f"Failed to get credentials: {e}" + ) return JsonResponse({"error": f"Couldn't get credentials from server. {e}"}, status=500) if request.GET.get('mode') == 'json': @@ -258,8 +268,14 @@ def shadowsocks(request, link): acl_link.last_access_time = timezone.now() acl_link.save(update_fields=['last_access_time']) - # Still create AccessLog for audit purposes - AccessLog.objects.create(user=acl.user, server=acl.server.name, action="Success", data=response) + # Create AccessLog with specific link tracking + AccessLog.objects.create( + user=acl.user.username, + server=acl.server.name, + acl_link_id=acl_link.link, + action="Success", + data=response + ) return HttpResponse(response, content_type=f"{ 'application/json; charset=utf-8' if request.GET.get('mode') == 'json' else 'text/html' }")