From 9bd48960408210d66451f9fd427de77d00abe3cf Mon Sep 17 00:00:00 2001 From: Ultradesu Date: Sun, 20 Jul 2025 23:04:58 +0300 Subject: [PATCH] Added User UI --- mysite/urls.py | 3 +- vpn/admin.py | 31 +- vpn/templates/vpn/user_portal.html | 498 +++++++++++++++++++++++ vpn/templates/vpn/user_portal_error.html | 148 +++++++ vpn/views.py | 82 +++- 5 files changed, 754 insertions(+), 8 deletions(-) create mode 100644 vpn/templates/vpn/user_portal.html create mode 100644 vpn/templates/vpn/user_portal_error.html diff --git a/mysite/urls.py b/mysite/urls.py index ac70c5c..1b13fd3 100644 --- a/mysite/urls.py +++ b/mysite/urls.py @@ -17,12 +17,13 @@ Including another URLconf from django.contrib import admin from django.urls import path, include from django.views.generic import RedirectView -from vpn.views import shadowsocks, userFrontend +from vpn.views import shadowsocks, userFrontend, userPortal urlpatterns = [ path('admin/', admin.site.urls), path('ss/', shadowsocks, name='shadowsocks'), path('dynamic/', shadowsocks, name='shadowsocks'), path('stat/', userFrontend, name='userFrontend'), + path('u/', userPortal, name='userPortal'), path('', RedirectView.as_view(url='/admin/', permanent=False)), ] diff --git a/vpn/admin.py b/vpn/admin.py index bfd36fa..792ef6e 100644 --- a/vpn/admin.py +++ b/vpn/admin.py @@ -386,10 +386,17 @@ class UserAdmin(admin.ModelAdmin): search_fields = ('username', 'hash') readonly_fields = ('hash_link',) - @admin.display(description='API access', ordering='hash') + @admin.display(description='User Portal', ordering='hash') def hash_link(self, obj): - url = f"{EXTERNAL_ADDRESS}/stat/{obj.hash}" - return format_html('JSON server list', url, obj.hash) + portal_url = f"{EXTERNAL_ADDRESS}/u/{obj.hash}" + json_url = f"{EXTERNAL_ADDRESS}/stat/{obj.hash}" + return format_html( + '
' + + '🌐 Portal' + + '📄 JSON' + + '
', + portal_url, json_url + ) @admin.display(description='Allowed servers', ordering='server_count') def server_count(self, obj): @@ -482,11 +489,23 @@ class ACLAdmin(admin.ModelAdmin): logger.error(f"Failed to get user info for {user.username} on {server.name}: {e}") return mark_safe(f"Server connection error: {e}") - @admin.display(description='Dynamic Config Links') + @admin.display(description='User Links') def display_links(self, obj): links = obj.links.all() - formatted_links = [f"{link.comment} - {EXTERNAL_ADDRESS}/ss/{link.link}#{link.acl.server.name}" for link in links] - return mark_safe('
'.join(formatted_links)) + portal_url = f"{EXTERNAL_ADDRESS}/u/{obj.user.hash}" + + links_html = [] + for link in links: + link_url = f"{EXTERNAL_ADDRESS}/ss/{link.link}#{obj.server.name}" + links_html.append(f"{link.comment} - {link_url}") + + links_text = '
'.join(links_html) if links_html else 'No links' + + return format_html( + '
{}
' + + '🌐 User Portal', + links_text, portal_url + ) try: from django_celery_results.models import GroupResult, TaskResult diff --git a/vpn/templates/vpn/user_portal.html b/vpn/templates/vpn/user_portal.html new file mode 100644 index 0000000..a55b892 --- /dev/null +++ b/vpn/templates/vpn/user_portal.html @@ -0,0 +1,498 @@ + + + + + + VPN Access Portal - {{ user.username }} + + + + +
+
+

🚀 VPN Access Portal

+
Welcome back, {{ user.username }}
+
+
+ {{ total_servers }} + Available Servers +
+
+ {{ total_links }} + Active Connections +
+
+
+ + {% if servers_data %} +
+ {% for server_name, server_data in servers_data.items %} +
+
+
{{ server_name }}
+
{{ server_data.server_type }}
+
+ +
+ {% if server_data.accessible %} +
+
+ Online & Ready +
+ {% else %} +
+
+ Connection Issues +
+ {% endif %} +
+ + +
+ {% endfor %} +
+ {% else %} +
+

No VPN Access Available

+

You don't have access to any VPN servers yet. Please contact your administrator.

+
+ {% endif %} + + +
+ + + + \ No newline at end of file diff --git a/vpn/templates/vpn/user_portal_error.html b/vpn/templates/vpn/user_portal_error.html new file mode 100644 index 0000000..2025853 --- /dev/null +++ b/vpn/templates/vpn/user_portal_error.html @@ -0,0 +1,148 @@ + + + + + + {{ error_title }} - VPN Portal + + + +
+
+
🚫
+

{{ error_title }}

+

{{ error_message }}

+ + ← Go Back +
+ + +
+ + + + \ No newline at end of file diff --git a/vpn/views.py b/vpn/views.py index 1998c46..83701f4 100644 --- a/vpn/views.py +++ b/vpn/views.py @@ -1,6 +1,86 @@ +def userPortal(request, user_hash): + """HTML portal for user to view their VPN access links and server information""" + from .models import User, ACLLink + import logging + + logger = logging.getLogger(__name__) + + try: + user = get_object_or_404(User, hash=user_hash) + logger.info(f"User portal accessed for user {user.username}") + except Http404: + logger.warning(f"User portal access attempt with invalid hash: {user_hash}") + from django.shortcuts import render + return render(request, 'vpn/user_portal_error.html', { + 'error_title': 'Access Denied', + 'error_message': 'Invalid access link. Please contact your administrator.' + }, status=403) + + try: + # Get all ACL links for the user with server information + acl_links = ACLLink.objects.filter(acl__user=user).select_related('acl__server', 'acl') + + # Group links by server + servers_data = {} + total_links = 0 + + for link in acl_links: + server = link.acl.server + server_name = server.name + + if server_name not in servers_data: + # Get server status and info + try: + server_status = server.get_server_status() + server_accessible = True + server_error = None + except Exception as e: + logger.warning(f"Could not get status for server {server_name}: {e}") + server_status = {} + server_accessible = False + server_error = str(e) + + servers_data[server_name] = { + 'server': server, + 'status': server_status, + 'accessible': server_accessible, + 'error': server_error, + 'links': [], + 'server_type': server.server_type, + } + + # Add link information + link_url = f"{EXTERNAL_ADDRESS}/ss/{link.link}#{server_name}" + servers_data[server_name]['links'].append({ + 'link': link, + 'url': link_url, + 'qr_data': link_url, # For QR code generation + 'comment': link.comment or 'Default', + }) + total_links += 1 + + context = { + 'user': user, + 'servers_data': servers_data, + 'total_servers': len(servers_data), + 'total_links': total_links, + 'external_address': EXTERNAL_ADDRESS, + } + + from django.shortcuts import render + return render(request, 'vpn/user_portal.html', context) + + except Exception as e: + logger.error(f"Error loading user portal for {user.username}: {e}") + from django.shortcuts import render + return render(request, 'vpn/user_portal_error.html', { + 'error_title': 'Server Error', + 'error_message': 'Unable to load your VPN information. Please try again later or contact support.' + }, status=500) import yaml import json -from django.shortcuts import get_object_or_404 +import logging +from django.shortcuts import get_object_or_404, render from django.http import JsonResponse, HttpResponse, Http404 from mysite.settings import EXTERNAL_ADDRESS