diff --git a/api/admin_user.py b/api/admin_user.py index 46d9618..9f85b70 100644 --- a/api/admin_user.py +++ b/api/admin_user.py @@ -94,6 +94,8 @@ admin.site.register(models.RustDeskTag, models.RustDeskTagAdmin) admin.site.register(models.RustDeskPeer, models.RustDeskPeerAdmin) admin.site.register(models.RustDesDevice, models.RustDesDeviceAdmin) admin.site.register(models.ShareLink, models.ShareLinkAdmin) +admin.site.register(models.ConnLog, models.ConnLogAdmin) +admin.site.register(models.FileLog, models.FileLogAdmin) admin.site.unregister(Group) admin.site.site_header = _('RustDesk自建Web') admin.site.site_title = _('未定义') \ No newline at end of file diff --git a/api/migrations/0005_connlog_filelog.py b/api/migrations/0005_connlog_filelog.py new file mode 100644 index 0000000..f48f48c --- /dev/null +++ b/api/migrations/0005_connlog_filelog.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.3 on 2024-04-17 08:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0004_alter_rustdesdevice_options_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ConnLog', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False, verbose_name='ID')), + ('action', models.CharField(max_length=20, null=True, verbose_name='Action')), + ('conn_id', models.CharField(max_length=10, null=True, verbose_name='Connection ID')), + ('from_ip', models.CharField(max_length=30, null=True, verbose_name='From IP')), + ('from_id', models.CharField(max_length=20, null=True, verbose_name='From ID')), + ('rid', models.CharField(max_length=20, null=True, verbose_name='To ID')), + ('conn_start', models.DateTimeField(null=True, verbose_name='Connected')), + ('conn_end', models.DateTimeField(null=True, verbose_name='Disconnected')), + ('session_id', models.CharField(max_length=60, null=True, verbose_name='Session ID')), + ('uuid', models.CharField(max_length=60, null=True, verbose_name='uuid')), + ], + ), + migrations.CreateModel( + name='FileLog', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.CharField(max_length=500, verbose_name='Path')), + ('remote_id', models.CharField(default='0', max_length=20, verbose_name='Remote ID')), + ('user_id', models.CharField(default='0', max_length=20, verbose_name='User ID')), + ('user_ip', models.CharField(default='0', max_length=20, verbose_name='User IP')), + ('filesize', models.CharField(default='', max_length=500, verbose_name='Filesize')), + ('direction', models.IntegerField(default=0, verbose_name='Direction')), + ('logged_at', models.DateTimeField(null=True, verbose_name='Logged At')), + ], + ), + ] diff --git a/api/models_work.py b/api/models_work.py index 70ec29a..2991d83 100644 --- a/api/models_work.py +++ b/api/models_work.py @@ -88,7 +88,37 @@ class RustDesDeviceAdmin(admin.ModelAdmin): search_fields = ('hostname', 'memory') list_filter = ('rid', ) +class ConnLog(models.Model): + id = models.IntegerField(verbose_name='ID',primary_key=True) + action = models.CharField(verbose_name='Action', max_length=20, null=True) + conn_id = models.CharField(verbose_name='Connection ID', max_length=10, null=True) + from_ip = models.CharField(verbose_name='From IP', max_length=30, null=True) + from_id = models.CharField(verbose_name='From ID', max_length=20, null=True) + rid = models.CharField(verbose_name='To ID', max_length=20, null=True) + conn_start = models.DateTimeField(verbose_name='Connected', null=True) + conn_end = models.DateTimeField(verbose_name='Disconnected', null=True) + session_id = models.CharField(verbose_name='Session ID', max_length=60, null=True) + uuid = models.CharField(verbose_name='uuid', max_length=60, null=True) +class ConnLogAdmin(admin.ModelAdmin): + list_display = ('id', 'action', 'conn_id', 'from_ip', 'from_id', 'rid', 'conn_start', 'conn_end', 'session_id', 'uuid') + search_fields = ('from_ip', 'rid') + list_filter = ('id', 'from_ip', 'from_id', 'rid', 'conn_start', 'conn_end') + +class FileLog(models.Model): + id = models.IntegerField(verbose_name='ID',primary_key=True) + file = models.CharField(verbose_name='Path', max_length=500) + remote_id = models.CharField(verbose_name='Remote ID', max_length=20, default='0') + user_id = models.CharField(verbose_name='User ID', max_length=20, default='0') + user_ip = models.CharField(verbose_name='User IP', max_length=20, default='0') + filesize = models.CharField(verbose_name='Filesize', max_length=500, default='') + direction = models.IntegerField(verbose_name='Direction', default=0) + logged_at = models.DateTimeField(verbose_name='Logged At', null=True) + +class FileLogAdmin(admin.ModelAdmin): + list_display = ('id', 'file', 'remote_id', 'user_id', 'user_ip', 'filesize', 'direction', 'logged_at') + search_fields = ('file', 'remote_id', 'user_id', 'user_ip') + list_filter = ('id', 'file', 'remote_id', 'user_id', 'user_ip', 'filesize', 'direction', 'logged_at') class ShareLink(models.Model): ''' 分享链接 diff --git a/api/templates/base.html b/api/templates/base.html index 89f1fa6..c5e03d0 100644 --- a/api/templates/base.html +++ b/api/templates/base.html @@ -39,6 +39,8 @@ {% if u.is_admin %}
  • {% trans "管理后台" %}
  • +
  • Connection Log
  • +
  • File Transfer Log
  • {% endif %}
  • {% trans "退出" %}
  • diff --git a/api/templates/show_conn_log.html b/api/templates/show_conn_log.html new file mode 100644 index 0000000..2cea72e --- /dev/null +++ b/api/templates/show_conn_log.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} +{% block title %}RustDesk WebUI{% endblock %} +{% block content %} +
    +
    +
    +
    +
    {{ "Connection Log" }}:【{{u.username}}】
    +
    + + + + + + + + + + + + + + + {% for one in page_obj %} + + + + + + + + + + + {% endfor %} + +
    User IPUser IDUser AliasRemote IDRemote AliasConnection Start TimeConnection End TimeDuration (HH:MM:SS)
    {{one.from_ip}} {{one.from_id}}{{one.from_alias}}{{one.rid}}{{one.alias}}{{one.conn_start}}{{one.conn_end}}{{one.duration}}
    +
    +
    +
    +
    + + {% if page_obj.has_previous %} + + + {% endif %} + {% if page_obj.paginator.num_pages > 1 %} + + {{ "Page #" }} {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} + + {% endif %} + {% if page_obj.has_next %} + + + {% endif %} + +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/api/templates/show_file_log.html b/api/templates/show_file_log.html new file mode 100644 index 0000000..17728bf --- /dev/null +++ b/api/templates/show_file_log.html @@ -0,0 +1,67 @@ +{% extends "base.html" %} +{% block title %}RustDesk WebUI{% endblock %} +{% block content %} +
    +
    +
    +
    +
    {{ "File Transfer Log" }}:【{{u.username}}】
    +
    + + + + + + + + + + + + + + + + {% for one in page_obj %} + + + + + + + + + {% if one.direction == 0 %} + + {% else %} + + {% endif %} + + + {% endfor %} + +
    FileRemote IDRemote AliasUser IDUser AliasUser IPFilesizeSent/ReceivedLogged At
    {{one.file}}{{one.remote_id}} {{one.remote_alias}}{{one.user_id}}{{one.user_alias}}{{one.user_ip}}{{one.filesize}}User Received FileUser Sent File{{one.logged_at}}
    +
    +
    +
    +
    + + {% if page_obj.has_previous %} + + + {% endif %} + {% if page_obj.paginator.num_pages > 1 %} + + {{ "page #" }} {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} + + {% endif %} + {% if page_obj.has_next %} + + + {% endif %} + +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index bb4cee3..6509105 100644 --- a/api/urls.py +++ b/api/urls.py @@ -21,4 +21,7 @@ urlpatterns = [ url(r'^work',views.work), # 前端 url(r'^down_peers$',views.down_peers), # 前端 url(r'^share',views.share), # 前端 + url(r'^conn_log',views.conn_log), + url(r'^file_log',views.file_log), + url(r'^audit',views.audit), ] diff --git a/api/views_api.py b/api/views_api.py index 63cb6f0..e38dc0e 100644 --- a/api/views_api.py +++ b/api/views_api.py @@ -4,9 +4,10 @@ import json import time import datetime import hashlib +import math from django.contrib import auth from django.forms.models import model_to_dict -from api.models import RustDeskToken, UserProfile, RustDeskTag, RustDeskPeer, RustDesDevice +from api.models import RustDeskToken, UserProfile, RustDeskTag, RustDeskPeer, RustDesDevice, ConnLog, FileLog from django.db.models import Q import copy from .views_front import * @@ -245,6 +246,61 @@ def heartbeat(request): result = {} result['data'] = _('在线') return JsonResponse(result) + +def audit(request): + postdata = json.loads(request.body) + #print(postdata) + audit_type = postdata['action'] if 'action' in postdata else '' + if audit_type == 'new': + new_conn_log = ConnLog( + action=postdata['action'] if 'action' in postdata else '', + conn_id=postdata['conn_id'] if 'conn_id' in postdata else 0, + from_ip=postdata['ip'] if 'ip' in postdata else '', + from_id='', + rid=postdata['id'] if 'id' in postdata else '', + conn_start=datetime.datetime.now(), + session_id=postdata['session_id'] if 'session_id' in postdata else 0, + uuid=postdata['uuid'] if 'uuid' in postdata else '', + ) + new_conn_log.save() + elif audit_type =="close": + ConnLog.objects.filter(Q(conn_id=postdata['conn_id'])).update(conn_end=datetime.datetime.now()) + elif 'is_file' in postdata: + print(postdata) + files = json.loads(postdata['info'])['files'] + filesize = convert_filesize(int(files[0][1])) + new_file_log = FileLog( + file=postdata['path'], + user_id=postdata['peer_id'], + user_ip=json.loads(postdata['info'])['ip'], + remote_id=postdata['id'], + filesize=filesize, + direction=postdata['type'], + logged_at=datetime.datetime.now(), + ) + new_file_log.save() + else: + try: + peer = postdata['peer'] + ConnLog.objects.filter(Q(conn_id=postdata['conn_id'])).update(session_id=postdata['session_id']) + ConnLog.objects.filter(Q(conn_id=postdata['conn_id'])).update(from_id=peer[0]) + except: + print(postdata) + + result = { + 'code':1, + 'data':'ok' + } + return JsonResponse(result) + +def convert_filesize(size_bytes): + if size_bytes == 0: + return "0B" + size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + i = int(math.floor(math.log(size_bytes, 1024))) + p = math.pow(1024, i) + s = round(size_bytes / p, 2) + return "%s %s" % (s, size_name[i]) def users(request): result = { diff --git a/api/views_front.py b/api/views_front.py index 922e148..5eccdc5 100644 --- a/api/views_front.py +++ b/api/views_front.py @@ -6,7 +6,7 @@ from django.http import JsonResponse from django.db.models import Q from django.contrib.auth.decorators import login_required from django.contrib import auth -from api.models import RustDeskPeer, RustDesDevice, UserProfile, ShareLink +from api.models import RustDeskPeer, RustDesDevice, UserProfile, ShareLink, ConnLog, FileLog from django.forms.models import model_to_dict from django.core.paginator import Paginator from django.http import HttpResponse @@ -360,3 +360,76 @@ def share(request): return JsonResponse({'code':1, 'shash':sharelink.shash}) + +def get_conn_log(): + logs = ConnLog.objects.all() + logs = {x.id:model_to_dict(x) for x in logs} + + for k, v in logs.items(): + try: + peer = RustDeskPeer.objects.get(rid=v['rid']) + logs[k]['alias'] = peer.alias + except: + logs[k]['alias'] = 'UNKNOWN' + try: + peer = RustDeskPeer.objects.get(rid=v['from_id']) + logs[k]['from_alias'] = peer.alias + except: + logs[k]['from_alias'] = 'UNKNOWN' + #from_zone = tz.tzutc() + #to_zone = tz.tzlocal() + #utc = logs[k]['logged_at'] + #utc = utc.replace(tzinfo=from_zone) + #logs[k]['logged_at'] = utc.astimezone(to_zone) + try: + duration = round((logs[k]['conn_end'] - logs[k]['conn_start']).total_seconds()) + m, s = divmod(duration, 60) + h, m = divmod(m, 60) + #d, h = divmod(h, 24) + logs[k]['duration'] = f'{h:02d}:{m:02d}:{s:02d}' + except: + logs[k]['duration'] = -1 + + sorted_logs = sorted(logs.items(), key=lambda x: x[1]['conn_start'], reverse=True) + new_ordered_dict = {} + for key, alog in sorted_logs: + new_ordered_dict[key] = alog + + return [v for k, v in new_ordered_dict.items()] + +def get_file_log(): + logs = FileLog.objects.all() + logs = {x.id:model_to_dict(x) for x in logs} + + for k, v in logs.items(): + try: + peer_remote = RustDeskPeer.objects.get(rid=v['remote_id']) + logs[k]['remote_alias'] = peer_remote.alias + except: + logs[k]['remote_alias'] = 'UNKNOWN' + try: + peer_user = RustDeskPeer.objects.get(rid=v['user_id']) + logs[k]['user_alias'] = peer_user.alias + except: + logs[k]['user_alias'] = 'UNKNOWN' + + sorted_logs = sorted(logs.items(), key=lambda x: x[1]['logged_at'], reverse=True) + new_ordered_dict = {} + for key, alog in sorted_logs: + new_ordered_dict[key] = alog + + return [v for k, v in new_ordered_dict.items()] + +@login_required(login_url='/api/user_action?action=login') +def conn_log(request): + paginator = Paginator(get_conn_log(), 20) + page_number = request.GET.get('page') + page_obj = paginator.get_page(page_number) + return render(request, 'show_conn_log.html', {'page_obj':page_obj}) + +@login_required(login_url='/api/user_action?action=login') +def file_log(request): + paginator = Paginator(get_file_log(), 20) + page_number = request.GET.get('page') + page_obj = paginator.get_page(page_number) + return render(request, 'show_file_log.html', {'page_obj':page_obj}) \ No newline at end of file