mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-08-21 14:37:16 +00:00
Django UI
This commit is contained in:
0
vpn/__init__.py
Normal file
0
vpn/__init__.py
Normal file
100
vpn/admin.py
Normal file
100
vpn/admin.py
Normal file
@@ -0,0 +1,100 @@
|
||||
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
|
||||
|
||||
from vpn.models import User, ACL
|
||||
from vpn.forms import UserForm
|
||||
from .server_plugins import (
|
||||
Server,
|
||||
WireguardServer,
|
||||
WireguardServerAdmin,
|
||||
OutlineServer,
|
||||
OutlineServerAdmin)
|
||||
|
||||
|
||||
@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>")
|
||||
# Преобразуем JSON в красивый формат
|
||||
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
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
form = UserForm
|
||||
list_display = ('name', 'comment', 'registration_date', 'hash', 'server_count')
|
||||
search_fields = ('name', 'hash')
|
||||
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)
|
||||
|
||||
|
||||
@admin.register(ACL)
|
||||
class ACLAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'server', 'server_type', 'link', 'created_at')
|
||||
list_filter = ('user', 'server__server_type')
|
||||
search_fields = ('user__name', 'server__name', 'server__comment', 'user__comment', 'link')
|
||||
readonly_fields = ('user_info', )
|
||||
|
||||
@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)
|
||||
|
||||
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>")
|
6
vpn/apps.py
Normal file
6
vpn/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class VPN(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'vpn'
|
14
vpn/forms.py
Normal file
14
vpn/forms.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django import forms
|
||||
from .models import User
|
||||
from .server_plugins import Server
|
||||
|
||||
class UserForm(forms.ModelForm):
|
||||
servers = forms.ModelMultipleChoiceField(
|
||||
queryset=Server.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['name', 'comment', 'servers']
|
59
vpn/models.py
Normal file
59
vpn/models.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import uuid
|
||||
from django.db import models
|
||||
from vpn.tasks import sync_user
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from .server_plugins import Server
|
||||
import shortuuid
|
||||
|
||||
class User(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
comment = models.TextField(default="", blank=True)
|
||||
registration_date = models.DateTimeField(auto_now_add=True)
|
||||
servers = models.ManyToManyField('Server', through='ACL', blank=True)
|
||||
last_access = models.DateTimeField(null=True, blank=True)
|
||||
hash = models.CharField(max_length=64, unique=True)
|
||||
|
||||
def get_servers(self):
|
||||
return Server.objects.filter(acl__user=self)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.hash:
|
||||
self.hash = shortuuid.ShortUUID().random(length=16)
|
||||
sync_user.delay_on_commit(self.id)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ACL(models.Model):
|
||||
user = models.ForeignKey('User', on_delete=models.CASCADE)
|
||||
server = models.ForeignKey('Server', on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
link = models.CharField(max_length=64, unique=True, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['user', 'server'], name='unique_user_server')
|
||||
]
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.name} - {self.server.name}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.link:
|
||||
self.link = shortuuid.ShortUUID().random(length=16)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@receiver(post_save, sender=ACL)
|
||||
def acl_created_or_updated(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
sync_user.delay(instance.user.id)
|
||||
else:
|
||||
pass
|
||||
|
||||
@receiver(pre_delete, sender=ACL)
|
||||
def acl_deleted(sender, instance, **kwargs):
|
||||
sync_user.delay(instance.user.id)
|
4
vpn/server_plugins/__init__.py
Normal file
4
vpn/server_plugins/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .generic import Server
|
||||
from .outline import OutlineServer, OutlineServerAdmin
|
||||
from .wireguard import WireguardServer, WireguardServerAdmin
|
||||
from .urls import urlpatterns
|
44
vpn/server_plugins/generic.py
Normal file
44
vpn/server_plugins/generic.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from django.db import models
|
||||
from vpn.tasks import sync_server
|
||||
|
||||
|
||||
class Server(PolymorphicModel):
|
||||
SERVER_TYPE_CHOICES = (
|
||||
('Outline', 'Outline'),
|
||||
('Wireguard', 'Wireguard'),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
comment = models.TextField(default="", blank=True)
|
||||
registration_date = models.DateTimeField(auto_now_add=True)
|
||||
server_type = models.CharField(max_length=50, choices=SERVER_TYPE_CHOICES, editable=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
sync_server.delay(self.id)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_server_status(self, *args, **kwargs):
|
||||
return {"name": self.name}
|
||||
|
||||
def sync(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def add_user(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_user(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def delete_user(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Server"
|
||||
verbose_name_plural = "Servers"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
225
vpn/server_plugins/outline.py
Normal file
225
vpn/server_plugins/outline.py
Normal file
@@ -0,0 +1,225 @@
|
||||
import logging
|
||||
from venv import logger
|
||||
import requests
|
||||
from django.db import models
|
||||
from .generic import Server
|
||||
from urllib3 import PoolManager
|
||||
from outline_vpn.outline_vpn import OutlineVPN, OutlineLibraryException
|
||||
from polymorphic.admin import PolymorphicChildModelAdmin
|
||||
from django.contrib import admin
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.db.models import Count
|
||||
|
||||
|
||||
class OutlineConnectionError(Exception):
|
||||
def __init__(self, message, original_exception=None):
|
||||
super().__init__(message)
|
||||
self.original_exception = original_exception
|
||||
|
||||
class _FingerprintAdapter(requests.adapters.HTTPAdapter):
|
||||
"""
|
||||
This adapter injected into the requests session will check that the
|
||||
fingerprint for the certificate matches for every request
|
||||
"""
|
||||
|
||||
def __init__(self, fingerprint=None, **kwargs):
|
||||
self.fingerprint = str(fingerprint)
|
||||
super(_FingerprintAdapter, self).__init__(**kwargs)
|
||||
|
||||
def init_poolmanager(self, connections, maxsize, block=False):
|
||||
self.poolmanager = PoolManager(
|
||||
num_pools=connections,
|
||||
maxsize=maxsize,
|
||||
block=block,
|
||||
assert_fingerprint=self.fingerprint,
|
||||
)
|
||||
|
||||
|
||||
class OutlineServer(Server):
|
||||
logger = logging.getLogger(__name__)
|
||||
admin_url = models.URLField()
|
||||
admin_access_cert = models.CharField(max_length=255)
|
||||
client_server_name = models.CharField(max_length=255)
|
||||
client_hostname = models.CharField(max_length=255)
|
||||
client_port = models.CharField(max_length=5)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Outline'
|
||||
verbose_name_plural = 'Outline'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.server_type = 'Outline'
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self.get_server_status(raw=True)
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return OutlineVPN(api_url=self.admin_url, cert_sha256=self.admin_access_cert)
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.client_hostname}:{self.client_port})"
|
||||
|
||||
def get_server_status(self, raw=False):
|
||||
status = {}
|
||||
try:
|
||||
info = self.client.get_server_information()
|
||||
if raw:
|
||||
status = info
|
||||
else:
|
||||
status.update(info)
|
||||
except Exception as e:
|
||||
status.update({f"error": e})
|
||||
return status
|
||||
|
||||
def sync(self):
|
||||
status = {}
|
||||
try:
|
||||
state = self.client.get_server_information()
|
||||
if state["name"] != self.name:
|
||||
self.client.set_server_name(self.name)
|
||||
status["name"] = f"{state['name']} -> {self.name}"
|
||||
elif state["hostnameForAccessKeys"] != self.client_hostname:
|
||||
self.client.set_hostname(self.client_hostname)
|
||||
status["hostnameForAccessKeys"] = f"{state['hostnameForAccessKeys']} -> {self.client_hostname}"
|
||||
elif int(state["portForNewAccessKeys"]) != int(self.client_port):
|
||||
self.client.set_port_new_for_access_keys(int(self.client_port))
|
||||
status["portForNewAccessKeys"] = f"{state['portForNewAccessKeys']} -> {self.client_port}"
|
||||
if len(status) == 0:
|
||||
status = {"status": "Nothing to do"}
|
||||
return status
|
||||
except AttributeError as e:
|
||||
raise OutlineConnectionError("Client error. Can't connect.", original_exception=e)
|
||||
|
||||
def _get_key(self, user):
|
||||
try:
|
||||
return self.client.get_key(user.hash)
|
||||
except Exception as e:
|
||||
logger.warning(f"sync error: {e}")
|
||||
return None
|
||||
|
||||
def get_user(self, user, raw=False):
|
||||
user_info = self._get_key(user)
|
||||
if raw:
|
||||
return user_info
|
||||
else:
|
||||
outline_key_dict = user_info.__dict__
|
||||
|
||||
outline_key_dict = {
|
||||
key: value
|
||||
for key, value in user_info.__dict__.items()
|
||||
if not key.startswith('_') and key not in [] # fields to mask
|
||||
}
|
||||
return outline_key_dict
|
||||
|
||||
|
||||
def add_user(self, user):
|
||||
server_user = self._get_key(user)
|
||||
logger.warning(server_user)
|
||||
result = {}
|
||||
key = None
|
||||
|
||||
if server_user:
|
||||
self.client.delete_key(user.hash)
|
||||
key = self.client.create_key(
|
||||
name=user.name,
|
||||
method=server_user.method,
|
||||
password=user.hash,
|
||||
data_limit=None,
|
||||
port=server_user.port
|
||||
)
|
||||
else:
|
||||
key = self.client.create_key(
|
||||
key_id=user.hash,
|
||||
name=user.name,
|
||||
method=server_user.method,
|
||||
password=user.hash,
|
||||
data_limit=None,
|
||||
port=server_user.port
|
||||
)
|
||||
try:
|
||||
result['key_id'] = key.key_id
|
||||
result['name'] = key.name
|
||||
result['method'] = key.method
|
||||
result['password'] = key.password
|
||||
result['data_limit'] = key.data_limit
|
||||
result['port'] = key.port
|
||||
except Exception as e:
|
||||
result = {"error": str(e)}
|
||||
return result
|
||||
|
||||
def delete_user(self, user):
|
||||
server_user = self._get_key(user)
|
||||
result = None
|
||||
|
||||
if server_user:
|
||||
self.logger.info(f"[{self.name}] TEST")
|
||||
self.client.delete_key(server_user.key_id)
|
||||
result = {"status": "User was deleted"}
|
||||
self.logger.info(f"[{self.name}] User deleted: {user.name} on server {self.name}")
|
||||
else:
|
||||
result = {"status": "User absent, nothing to do."}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class OutlineServerAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = OutlineServer
|
||||
show_in_index = False # Не отображать в главном списке админки
|
||||
list_display = (
|
||||
'name',
|
||||
'admin_url',
|
||||
'admin_access_cert',
|
||||
'client_server_name',
|
||||
'client_hostname',
|
||||
'client_port',
|
||||
'server_status_inline',
|
||||
'user_count',
|
||||
'registration_date'
|
||||
)
|
||||
readonly_fields = ('server_status_full', )
|
||||
exclude = ('server_type',)
|
||||
|
||||
@admin.display(description='Clients', ordering='user_count')
|
||||
def user_count(self, obj):
|
||||
return obj.user_count
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
qs = qs.annotate(user_count=Count('acl__user'))
|
||||
return qs
|
||||
|
||||
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>")
|
||||
# Преобразуем JSON в красивый формат
|
||||
import json
|
||||
pretty_status = json.dumps(status, indent=4)
|
||||
return mark_safe(f"<pre>{pretty_status}</pre>")
|
||||
server_status_inline.short_description = "Status"
|
||||
|
||||
def server_status_full(self, obj):
|
||||
if obj and obj.pk:
|
||||
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 = json.dumps(status, indent=4)
|
||||
return mark_safe(f"<pre>{pretty_status}</pre>")
|
||||
return "N/A"
|
||||
|
||||
server_status_full.short_description = "Server Status"
|
||||
|
||||
def get_model_perms(self, request):
|
||||
"""It disables display for sub-model"""
|
||||
return {}
|
||||
|
||||
admin.site.register(OutlineServer, OutlineServerAdmin)
|
6
vpn/server_plugins/urls.py
Normal file
6
vpn/server_plugins/urls.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from vpn.views import shadowsocks
|
||||
|
||||
urlpatterns = [
|
||||
path('ss/<str:hash_value>/', shadowsocks, name='shadowsocks'),
|
||||
]
|
83
vpn/server_plugins/wireguard.py
Normal file
83
vpn/server_plugins/wireguard.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from .generic import Server
|
||||
from django.db import models
|
||||
from polymorphic.admin import (
|
||||
PolymorphicChildModelAdmin,
|
||||
)
|
||||
from django.contrib import admin
|
||||
from django.db.models import Count
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
class WireguardServer(Server):
|
||||
address = models.CharField(max_length=100)
|
||||
port = models.IntegerField()
|
||||
client_private_key = models.CharField(max_length=255)
|
||||
server_publick_key = models.CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Wireguard'
|
||||
verbose_name_plural = 'Wireguard'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.server_type = 'Wireguard'
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.address})"
|
||||
|
||||
def get_server_status(self):
|
||||
status = super().get_server_status()
|
||||
status.update({
|
||||
"address": self.address,
|
||||
"port": self.port,
|
||||
"client_private_key": self.client_private_key,
|
||||
"server_publick_key": self.server_publick_key,
|
||||
})
|
||||
return status
|
||||
|
||||
class WireguardServerAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = WireguardServer
|
||||
show_in_index = False # Не отображать в главном списке админки
|
||||
list_display = (
|
||||
'name',
|
||||
'address',
|
||||
'port',
|
||||
'server_publick_key',
|
||||
'client_private_key',
|
||||
'server_status_inline',
|
||||
'user_count',
|
||||
'registration_date'
|
||||
)
|
||||
readonly_fields = ('server_status_full', )
|
||||
exclude = ('server_type',)
|
||||
|
||||
@admin.display(description='Clients', ordering='user_count')
|
||||
def user_count(self, obj):
|
||||
return obj.user_count
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
qs = qs.annotate(user_count=Count('acl__user'))
|
||||
return qs
|
||||
|
||||
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>")
|
||||
return mark_safe(f"<pre>{status}</pre>")
|
||||
server_status_inline.short_description = "Server Status"
|
||||
|
||||
def server_status_full(self, obj):
|
||||
if obj and obj.pk:
|
||||
status = obj.get_server_status()
|
||||
if 'error' in status:
|
||||
return mark_safe(f"<span style='color: red;'>Error: {status['error']}</span>")
|
||||
return mark_safe(f"<pre>{status}</pre>")
|
||||
return "N/A"
|
||||
|
||||
server_status_full.short_description = "Server Status"
|
||||
|
||||
def get_model_perms(self, request):
|
||||
"""It disables display for sub-model"""
|
||||
return {}
|
||||
|
||||
admin.site.register(WireguardServer, WireguardServerAdmin)
|
50
vpn/tasks.py
Normal file
50
vpn/tasks.py
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
import logging
|
||||
from celery import shared_task
|
||||
#from django_celery_results.models import TaskResult
|
||||
from outline_vpn.outline_vpn import OutlineServerErrorException
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TaskFailedException(Exception):
|
||||
def __init__(self, message=""):
|
||||
self.message = message
|
||||
super().__init__(f"{self.message}")
|
||||
|
||||
|
||||
@shared_task(name="sync.server")
|
||||
def sync_server(id):
|
||||
from vpn.server_plugins import Server
|
||||
# task_result = TaskResult.objects.get_task(self.request.id)
|
||||
# task_result.status='RUNNING'
|
||||
# task_result.save()
|
||||
return {"status": Server.objects.get(id=id).sync()}
|
||||
|
||||
@shared_task(name="sync.user")
|
||||
def sync_user(id):
|
||||
from .models import User, ACL
|
||||
from vpn.server_plugins import Server
|
||||
|
||||
errors = {}
|
||||
result = {}
|
||||
user = User.objects.get(id=id)
|
||||
acls = ACL.objects.filter(user=user)
|
||||
|
||||
servers = Server.objects.all()
|
||||
|
||||
for server in servers:
|
||||
try:
|
||||
if acls.filter(server=server).exists():
|
||||
result[server.name] = server.add_user(user)
|
||||
else:
|
||||
result[server.name] = server.delete_user(user)
|
||||
except Exception as e:
|
||||
errors[server.name] = {"error": e}
|
||||
finally:
|
||||
if errors:
|
||||
logger.error("ERROR ERROR")
|
||||
raise TaskFailedException(message=f"Errors during taks: {errors}")
|
||||
else:
|
||||
logger.error(f"PUK PUEK. {errors}")
|
||||
return result
|
10
vpn/templates/admin/polls/user/change_form.html
Normal file
10
vpn/templates/admin/polls/user/change_form.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends "admin/change_form.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block after_field_sets %}
|
||||
<div>
|
||||
<h2>Create ACLs</h2>
|
||||
{{ adminform.form.servers }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
3
vpn/tests.py
Normal file
3
vpn/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
26
vpn/views.py
Normal file
26
vpn/views.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
# views.py
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import JsonResponse
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def shadowsocks(request, link):
|
||||
from .models import ACL
|
||||
acl = get_object_or_404(ACL, link=link)
|
||||
server_user = acl.server.get_user(acl.user, raw=True)
|
||||
config = {
|
||||
"info": "Managed by OutFleet_v2 [github.com/house-of-vanity/OutFleet/]",
|
||||
"password": server_user.password,
|
||||
"method": server_user.method,
|
||||
"prefix": "\u0005\u00dc_\u00e0\u0001",
|
||||
"server": acl.server.client_server_name,
|
||||
"server_port": server_user.port,
|
||||
"access_url": server_user.access_url,
|
||||
}
|
||||
return JsonResponse(config)
|
||||
|
||||
|
Reference in New Issue
Block a user