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..1b3197b
--- /dev/null
+++ b/api/templates/show_conn_log.html
@@ -0,0 +1,61 @@
+{% extends "base.html" %}
+{% block title %}RustDesk WebUI{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+ | {{ "User IP" | translate }} |
+ {{ "User ID" | translate }} |
+ {{ "User Alias" | translate }} |
+ {{ "Remote ID" | translate }} |
+ {{ "Remote Alias" | translate }} |
+ {{ "Connection Start Time" | translate }} |
+ {{ "Connection End Time" | translate }} |
+ {{ "Duration (HH:MM:SS)" | translate }} |
+
+
+
+ {% for one in page_obj %}
+
+ | {{one.from_ip}} |
+ {{one.from_id}} |
+ {{one.from_alias}} |
+ {{one.rid}} |
+ {{one.alias}} |
+ {{one.conn_start}} |
+ {{one.conn_end}} |
+ {{one.duration}} |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+{% 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..004d85c
--- /dev/null
+++ b/api/templates/show_file_log.html
@@ -0,0 +1,67 @@
+{% extends "base.html" %}
+{% block title %}RustDesk WebUI{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+ | {{ "File" | translate }} |
+ {{ "Remote ID" | translate }} |
+ {{ "Remote Alias" | translate }} |
+ {{ "User ID" | translate }} |
+ {{ "User Alias" | translate }} |
+ {{ "User IP" | translate }} |
+ {{ "Filesize" | translate }} |
+ {{ "Sent/Received" | translate }} |
+ {{ "Logged At" | translate }} |
+
+
+
+ {% for one in page_obj %}
+
+ | {{one.file}} |
+ {{one.remote_id}} |
+ {{one.remote_alias}} |
+ {{one.user_id}} |
+ {{one.user_alias}} |
+ {{one.user_ip}} |
+ {{one.filesize}} |
+ {% if one.direction == 0 %}
+ User Received File |
+ {% else %}
+ User Sent File |
+ {% endif %}
+ {{one.logged_at}} |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+{% 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..286c8dc 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