mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-08-21 14:37:16 +00:00
Added statistics cache
This commit is contained in:
15
vpn/admin.py
15
vpn/admin.py
@@ -719,10 +719,19 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(AccessLog)
|
@admin.register(AccessLog)
|
||||||
class AccessLogAdmin(admin.ModelAdmin):
|
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')
|
list_filter = ('user', 'server', 'action', 'timestamp')
|
||||||
search_fields = ('user', 'server', 'action', 'timestamp', 'data')
|
search_fields = ('user', 'server', 'acl_link_id', 'action', 'timestamp', 'data')
|
||||||
readonly_fields = ('server', 'user', 'formatted_timestamp', 'action', 'formated_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(
|
||||||
|
'<span style="font-family: monospace; color: #2563eb;">{}</span>',
|
||||||
|
obj.acl_link_id[:12] + '...' if len(obj.acl_link_id) > 12 else obj.acl_link_id
|
||||||
|
)
|
||||||
|
return '-'
|
||||||
|
|
||||||
@admin.display(description='Timestamp')
|
@admin.display(description='Timestamp')
|
||||||
def formatted_timestamp(self, obj):
|
def formatted_timestamp(self, obj):
|
||||||
|
22
vpn/migrations/0006_accesslog_acl_link_id.py
Normal file
22
vpn/migrations/0006_accesslog_acl_link_id.py
Normal file
@@ -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'),
|
||||||
|
),
|
||||||
|
]
|
14
vpn/migrations/0007_merge_20250721_1345.py
Normal file
14
vpn/migrations/0007_merge_20250721_1345.py
Normal file
@@ -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 = [
|
||||||
|
]
|
@@ -69,6 +69,7 @@ class TaskExecutionLog(models.Model):
|
|||||||
class AccessLog(models.Model):
|
class AccessLog(models.Model):
|
||||||
user = models.CharField(max_length=256, blank=True, null=True, editable=False)
|
user = models.CharField(max_length=256, blank=True, null=True, editable=False)
|
||||||
server = 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)
|
action = models.CharField(max_length=100, editable=False)
|
||||||
data = models.TextField(default="", blank=True, editable=False)
|
data = models.TextField(default="", blank=True, editable=False)
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
@@ -77,12 +78,14 @@ class AccessLog(models.Model):
|
|||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['user']),
|
models.Index(fields=['user']),
|
||||||
models.Index(fields=['server']),
|
models.Index(fields=['server']),
|
||||||
|
models.Index(fields=['acl_link_id']),
|
||||||
models.Index(fields=['timestamp']),
|
models.Index(fields=['timestamp']),
|
||||||
models.Index(fields=['action', 'timestamp']),
|
models.Index(fields=['action', 'timestamp']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
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):
|
class User(AbstractUser):
|
||||||
#is_active = False
|
#is_active = False
|
||||||
|
@@ -252,10 +252,11 @@ def update_user_statistics(self):
|
|||||||
for link in acl_links:
|
for link in acl_links:
|
||||||
server_name = link.acl.server.name
|
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(
|
total_connections = AccessLog.objects.filter(
|
||||||
user=user.username,
|
user=user.username,
|
||||||
server=server_name,
|
server=server_name,
|
||||||
|
acl_link_id=link.link,
|
||||||
action='Success'
|
action='Success'
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
@@ -263,6 +264,7 @@ def update_user_statistics(self):
|
|||||||
recent_connections = AccessLog.objects.filter(
|
recent_connections = AccessLog.objects.filter(
|
||||||
user=user.username,
|
user=user.username,
|
||||||
server=server_name,
|
server=server_name,
|
||||||
|
acl_link_id=link.link,
|
||||||
action='Success',
|
action='Success',
|
||||||
timestamp__gte=thirty_days_ago
|
timestamp__gte=thirty_days_ago
|
||||||
).count()
|
).count()
|
||||||
@@ -278,6 +280,7 @@ def update_user_statistics(self):
|
|||||||
day_connections = AccessLog.objects.filter(
|
day_connections = AccessLog.objects.filter(
|
||||||
user=user.username,
|
user=user.username,
|
||||||
server=server_name,
|
server=server_name,
|
||||||
|
acl_link_id=link.link,
|
||||||
action='Success',
|
action='Success',
|
||||||
timestamp__gte=day_start,
|
timestamp__gte=day_start,
|
||||||
timestamp__lt=day_end
|
timestamp__lt=day_end
|
||||||
@@ -300,7 +303,7 @@ def update_user_statistics(self):
|
|||||||
)
|
)
|
||||||
|
|
||||||
action = "created" if created else "updated"
|
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
|
updated_count += 1
|
||||||
|
|
||||||
|
28
vpn/views.py
28
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}")
|
logger.info(f"Found ACL link for user {acl.user.username} on server {acl.server.name}")
|
||||||
except Http404:
|
except Http404:
|
||||||
logger.warning(f"ACL link not found: {link}")
|
logger.warning(f"ACL link not found: {link}")
|
||||||
AccessLog.objects.create(user=None, server="Unknown", action="Failed",
|
AccessLog.objects.create(
|
||||||
data=f"ACL not found for link: {link}")
|
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)
|
return JsonResponse({"error": "Not allowed"}, status=403)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -212,8 +217,13 @@ def shadowsocks(request, link):
|
|||||||
logger.info(f"Successfully retrieved user credentials for {acl.user.username} from {acl.server.name}")
|
logger.info(f"Successfully retrieved user credentials for {acl.user.username} from {acl.server.name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get user credentials for {acl.user.username} from {acl.server.name}: {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",
|
AccessLog.objects.create(
|
||||||
data=f"Failed to get credentials: {e}")
|
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)
|
return JsonResponse({"error": f"Couldn't get credentials from server. {e}"}, status=500)
|
||||||
|
|
||||||
if request.GET.get('mode') == 'json':
|
if request.GET.get('mode') == 'json':
|
||||||
@@ -258,8 +268,14 @@ def shadowsocks(request, link):
|
|||||||
acl_link.last_access_time = timezone.now()
|
acl_link.last_access_time = timezone.now()
|
||||||
acl_link.save(update_fields=['last_access_time'])
|
acl_link.save(update_fields=['last_access_time'])
|
||||||
|
|
||||||
# Still create AccessLog for audit purposes
|
# Create AccessLog with specific link tracking
|
||||||
AccessLog.objects.create(user=acl.user, server=acl.server.name, action="Success", data=response)
|
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' }")
|
return HttpResponse(response, content_type=f"{ 'application/json; charset=utf-8' if request.GET.get('mode') == 'json' else 'text/html' }")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user