2024-10-20 21:57:12 +00:00
|
|
|
import json
|
|
|
|
from polymorphic.admin import (
|
|
|
|
PolymorphicParentModelAdmin,
|
|
|
|
)
|
|
|
|
from django.contrib import admin
|
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from django.db.models import Count
|
|
|
|
|
2024-10-27 01:06:37 +00:00
|
|
|
from django.contrib.auth.admin import UserAdmin
|
2024-10-28 17:15:49 +00:00
|
|
|
from .models import User, AccessLog
|
|
|
|
from django.utils.timezone import localtime
|
|
|
|
from vpn.models import User, ACL, ACLLink
|
2024-10-20 21:57:12 +00:00
|
|
|
from vpn.forms import UserForm
|
2024-11-18 20:34:54 +00:00
|
|
|
from mysite.settings import EXTERNAL_ADDRESS
|
2024-10-20 21:57:12 +00:00
|
|
|
from .server_plugins import (
|
|
|
|
Server,
|
|
|
|
WireguardServer,
|
|
|
|
WireguardServerAdmin,
|
|
|
|
OutlineServer,
|
|
|
|
OutlineServerAdmin)
|
|
|
|
|
|
|
|
|
2024-10-28 00:06:35 +00:00
|
|
|
admin.site.site_title = "VPN Manager"
|
|
|
|
admin.site.site_header = "VPN Manager"
|
|
|
|
admin.site.index_title = "OutFleet"
|
|
|
|
|
2024-10-28 17:15:49 +00:00
|
|
|
def format_object(data):
|
|
|
|
try:
|
|
|
|
if isinstance(data, dict):
|
|
|
|
formatted_data = json.dumps(data, indent=2)
|
|
|
|
return mark_safe(f"<pre>{formatted_data}</pre>")
|
|
|
|
elif isinstance(data, str):
|
|
|
|
return mark_safe(f"<pre>{data}</pre>")
|
|
|
|
else:
|
|
|
|
return mark_safe(f"<pre>{str(data)}</pre>")
|
|
|
|
except Exception as e:
|
|
|
|
return mark_safe(f"<span style='color: red;'>Error: {e}</span>")
|
|
|
|
|
|
|
|
class UserNameFilter(admin.SimpleListFilter):
|
|
|
|
title = 'User'
|
|
|
|
parameter_name = 'user'
|
|
|
|
|
|
|
|
def lookups(self, request, model_admin):
|
|
|
|
users = set(User.objects.values_list('username', flat=True))
|
|
|
|
return [(user, user) for user in users]
|
|
|
|
|
|
|
|
def queryset(self, request, queryset):
|
|
|
|
if self.value():
|
|
|
|
return queryset.filter(user__username=self.value())
|
|
|
|
return queryset
|
|
|
|
|
|
|
|
class ServerNameFilter(admin.SimpleListFilter):
|
|
|
|
title = 'Server Name'
|
|
|
|
parameter_name = 'acl__server__name'
|
|
|
|
|
|
|
|
def lookups(self, request, model_admin):
|
|
|
|
servers = set(ACL.objects.values_list('server__name', flat=True))
|
|
|
|
return [(server, server) for server in servers]
|
|
|
|
|
|
|
|
def queryset(self, request, queryset):
|
|
|
|
if self.value():
|
|
|
|
return queryset.filter(acl__server__name=self.value())
|
|
|
|
return queryset
|
2024-10-28 00:06:35 +00:00
|
|
|
|
2024-10-20 21:57:12 +00:00
|
|
|
@admin.register(Server)
|
|
|
|
class ServerAdmin(PolymorphicParentModelAdmin):
|
|
|
|
base_model = Server
|
|
|
|
child_models = (OutlineServer, WireguardServer)
|
|
|
|
list_display = ('name', 'server_type', 'comment', 'registration_date', 'user_count', 'server_status_inline')
|
|
|
|
search_fields = ('name', 'comment')
|
|
|
|
list_filter = ('server_type', )
|
|
|
|
|
|
|
|
@admin.display(description='User Count', ordering='user_count')
|
|
|
|
def user_count(self, obj):
|
|
|
|
return obj.user_count
|
|
|
|
|
|
|
|
@admin.display(description='Status')
|
|
|
|
def server_status_inline(self, obj):
|
|
|
|
status = obj.get_server_status()
|
|
|
|
if 'error' in status:
|
|
|
|
return mark_safe(f"<span style='color: red;'>Error: {status['error']}</span>")
|
|
|
|
import json
|
|
|
|
pretty_status = ", ".join(f"{key}: {value}" for key, value in status.items())
|
|
|
|
return mark_safe(f"<pre>{pretty_status}</pre>")
|
|
|
|
server_status_inline.short_description = "Status"
|
|
|
|
|
|
|
|
def get_queryset(self, request):
|
|
|
|
qs = super().get_queryset(request)
|
|
|
|
qs = qs.annotate(user_count=Count('acl'))
|
|
|
|
return qs
|
|
|
|
|
2024-10-27 01:06:37 +00:00
|
|
|
#admin.site.register(User, UserAdmin)
|
2024-10-20 21:57:12 +00:00
|
|
|
@admin.register(User)
|
|
|
|
class UserAdmin(admin.ModelAdmin):
|
|
|
|
form = UserForm
|
2024-10-27 01:06:37 +00:00
|
|
|
list_display = ('username', 'comment', 'registration_date', 'hash', 'server_count')
|
|
|
|
search_fields = ('username', 'hash')
|
2024-10-20 21:57:12 +00:00
|
|
|
readonly_fields = ('hash',)
|
|
|
|
|
|
|
|
|
|
|
|
@admin.display(description='Allowed servers', ordering='server_count')
|
|
|
|
def server_count(self, obj):
|
|
|
|
return obj.server_count
|
|
|
|
|
|
|
|
def get_queryset(self, request):
|
|
|
|
qs = super().get_queryset(request)
|
|
|
|
qs = qs.annotate(server_count=Count('acl'))
|
|
|
|
return qs
|
|
|
|
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
|
|
super().save_model(request, obj, form, change)
|
|
|
|
selected_servers = form.cleaned_data.get('servers', [])
|
|
|
|
|
|
|
|
ACL.objects.filter(user=obj).exclude(server__in=selected_servers).delete()
|
|
|
|
|
|
|
|
for server in selected_servers:
|
|
|
|
ACL.objects.get_or_create(user=obj, server=server)
|
|
|
|
|
2024-10-28 17:15:49 +00:00
|
|
|
@admin.register(AccessLog)
|
|
|
|
class AccessLogAdmin(admin.ModelAdmin):
|
|
|
|
list_display = ('user', 'server', 'action', 'formatted_timestamp')
|
2025-01-10 11:32:30 +00:00
|
|
|
list_filter = ('user', 'server', 'action', 'timestamp')
|
|
|
|
search_fields = ('user', 'server', 'action', 'timestamp')
|
2024-10-28 17:15:49 +00:00
|
|
|
readonly_fields = ('server', 'user', 'formatted_timestamp', 'action', 'formated_data')
|
|
|
|
|
|
|
|
@admin.display(description='Timestamp')
|
|
|
|
def formatted_timestamp(self, obj):
|
|
|
|
local_time = localtime(obj.timestamp)
|
|
|
|
return local_time.strftime('%Y-%m-%d %H:%M:%S %Z')
|
|
|
|
|
|
|
|
@admin.display(description='Details')
|
|
|
|
def formated_data(self, obj):
|
|
|
|
return format_object(obj.data)
|
|
|
|
|
|
|
|
|
|
|
|
class ACLLinkInline(admin.TabularInline):
|
|
|
|
model = ACLLink
|
|
|
|
extra = 1
|
|
|
|
help_text = 'Add or change ACL links'
|
|
|
|
verbose_name = 'Dynamic link'
|
|
|
|
verbose_name_plural = 'Dynamic links'
|
2025-01-09 17:24:12 +00:00
|
|
|
fields = ('link', 'comment')
|
2024-10-20 21:57:12 +00:00
|
|
|
|
|
|
|
@admin.register(ACL)
|
|
|
|
class ACLAdmin(admin.ModelAdmin):
|
2024-10-28 17:15:49 +00:00
|
|
|
|
|
|
|
list_display = ('user', 'server', 'server_type', 'display_links', 'created_at')
|
2025-01-09 17:24:12 +00:00
|
|
|
#list_editable = ('server', )
|
2024-10-28 17:15:49 +00:00
|
|
|
list_filter = (UserNameFilter, 'server__server_type', ServerNameFilter)
|
|
|
|
search_fields = ('user__name', 'server__name', 'server__comment', 'user__comment', 'links__link')
|
|
|
|
readonly_fields = ('user_info',)
|
|
|
|
inlines = [ACLLinkInline]
|
2024-10-20 21:57:12 +00:00
|
|
|
|
|
|
|
@admin.display(description='Server Type', ordering='server__server_type')
|
|
|
|
def server_type(self, obj):
|
|
|
|
return obj.server.get_server_type_display()
|
|
|
|
|
|
|
|
@admin.display(description='Client info')
|
|
|
|
def user_info(self, obj):
|
|
|
|
server = obj.server
|
|
|
|
user = obj.user
|
|
|
|
try:
|
|
|
|
data = server.get_user(user)
|
2024-10-28 17:15:49 +00:00
|
|
|
return format_object(data)
|
2024-10-20 21:57:12 +00:00
|
|
|
except Exception as e:
|
2024-10-28 17:15:49 +00:00
|
|
|
return mark_safe(f"<span style='color: red;'>Error: {e}</span>")
|
|
|
|
|
2024-11-18 20:34:54 +00:00
|
|
|
@admin.display(description='Dynamic Config Links')
|
2024-10-28 17:15:49 +00:00
|
|
|
def display_links(self, obj):
|
|
|
|
links = obj.links.all()
|
2025-01-09 17:24:12 +00:00
|
|
|
formatted_links = [f"{EXTERNAL_ADDRESS}/ss/{link.link}#{link.acl.server.name}" for link in links]
|
2024-11-18 20:34:54 +00:00
|
|
|
return mark_safe('<br>'.join(formatted_links))
|
2024-10-28 17:15:49 +00:00
|
|
|
|