mirror of
https://github.com/kingmo888/rustdesk-api-server.git
synced 2026-02-21 10:27:23 +08:00
V1.2 增加创建分享链接功能
This commit is contained in:
parent
0f116462f6
commit
66a65b7923
@ -3,7 +3,7 @@
|
||||
<p align="center">
|
||||
<i>一个 python 实现的 Rustdesk API 接口,支持 WebUI 管理</i>
|
||||
<br/>
|
||||
<img src ="https://img.shields.io/badge/Version-1.1-blueviolet.svg"/>
|
||||
<img src ="https://img.shields.io/badge/Version-1.2-blueviolet.svg"/>
|
||||
<img src ="https://img.shields.io/badge/Python-3.7|3.8|3.9|3.10|3.11-blue.svg" />
|
||||
<img src ="https://img.shields.io/badge/Django-3.2+|4.x-yelow.svg" />
|
||||
<br/>
|
||||
@ -137,7 +137,7 @@ services:
|
||||
|
||||
## 开发计划
|
||||
|
||||
- [ ] 分享设备给其他已注册用户
|
||||
- [-] 分享设备给其他已注册用户
|
||||
|
||||
> 说明:类似网盘url分享,url激活后可以获得某个或某组或某个标签下的设备
|
||||
> 备注:其实web api作为中间件,可做的不多,更多功能还是需要修改客户端来实现,就不太值当了。
|
||||
|
||||
@ -93,6 +93,7 @@ admin.site.register(models.RustDeskToken, models.RustDeskTokenAdmin)
|
||||
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.unregister(Group)
|
||||
admin.site.site_header = 'RustDesk自建Web'
|
||||
admin.site.site_title = '未定义'
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.2.7 on 2023-12-04 21:05
|
||||
# Generated by Django 4.2.7 on 2023-12-14 12:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
@ -138,6 +138,34 @@ class Migration(migrations.Migration):
|
||||
"ordering": ("-username",),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ShareLink",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("uid", models.CharField(max_length=16, verbose_name="用户ID")),
|
||||
("shash", models.CharField(max_length=60, verbose_name="链接Key")),
|
||||
("peers", models.CharField(max_length=20, verbose_name="机器ID列表")),
|
||||
("is_used", models.BooleanField(default=False, verbose_name="是否使用")),
|
||||
("is_expired", models.BooleanField(default=False, verbose_name="是否过期")),
|
||||
(
|
||||
"create_time",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="生成时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "分享链接",
|
||||
"verbose_name_plural": "链接列表",
|
||||
"ordering": ("-create_time",),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="UserProfile",
|
||||
fields=[
|
||||
|
||||
@ -87,4 +87,28 @@ class RustDesDeviceAdmin(admin.ModelAdmin):
|
||||
list_display = ('rid', 'hostname', 'memory', 'uuid', 'version', 'create_time', 'update_time')
|
||||
search_fields = ('hostname', 'memory')
|
||||
list_filter = ('rid', )
|
||||
|
||||
|
||||
|
||||
|
||||
class ShareLink(models.Model):
|
||||
''' 分享链接
|
||||
'''
|
||||
uid = models.CharField(verbose_name='用户ID', max_length=16)
|
||||
shash = models.CharField(verbose_name='链接Key', max_length=60)
|
||||
peers = models.CharField(verbose_name='机器ID列表', max_length=20)
|
||||
is_used = models.BooleanField(verbose_name='是否使用', default=False)
|
||||
is_expired = models.BooleanField(verbose_name='是否过期', default=False)
|
||||
create_time = models.DateTimeField(verbose_name='生成时间', auto_now_add=True)
|
||||
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ('-create_time',)
|
||||
verbose_name = "分享链接"
|
||||
verbose_name_plural = "链接列表"
|
||||
|
||||
|
||||
class ShareLinkAdmin(admin.ModelAdmin):
|
||||
list_display = ('shash', 'uid', 'peers', 'is_used', 'is_expired', 'create_time')
|
||||
search_fields = ('peers', )
|
||||
list_filter = ('is_used', 'uid', 'is_expired' )
|
||||
44
api/templates/base.html
Normal file
44
api/templates/base.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% load static %}<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="stylesheet" href="{% static 'layui/css/layui.css' %}">
|
||||
{% block link %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<script src={% static "layui/layui.js" %}></script>
|
||||
<script>
|
||||
layui.use('element', function(){
|
||||
var element = layui.element; //导航的hover效果、二级菜单等功能,需要依赖element模块
|
||||
|
||||
//监听导航点击
|
||||
element.on('nav(demo)', function(elem){
|
||||
//console.log(elem)
|
||||
layer.msg(elem.text());
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<ul class="layui-nav">
|
||||
<li class="layui-nav-item"><a href="/">首页</a></li>
|
||||
<li class="layui-nav-item"><a href="/api/share">分享</a></li>
|
||||
|
||||
{% if u.is_admin %}
|
||||
<li class="layui-nav-item"><a href="/admin">管理后台</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
|
||||
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
|
||||
<legend>{% block legend_name %}{% endblock %}</legend>
|
||||
</fieldset>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
12
api/templates/msg.html
Normal file
12
api/templates/msg.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{title}}{% endblock %}
|
||||
{% block legend_name %}信息{% endblock %}
|
||||
{% block content %}
|
||||
<div style="padding: 20px; background-color: #F2F2F2;">
|
||||
<div class="layui-row layui-col-space15">
|
||||
{% autoescape off %}
|
||||
{{msg}}
|
||||
{% endautoescape %}
|
||||
</div></div>
|
||||
|
||||
{% endblock %}
|
||||
105
api/templates/share.html
Normal file
105
api/templates/share.html
Normal file
@ -0,0 +1,105 @@
|
||||
|
||||
{% extends "base.html" %}{% load static %}
|
||||
{% block title %}分享机器{% endblock %}
|
||||
{% block link %}<link rel="stylesheet" href="{% static 'layui/css/style.css' %}">{% endblock %}
|
||||
{% block legend_name %}分享机器给其他用户{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="layui-container">
|
||||
<div class="layui-card layui-col-md3-offset2">
|
||||
<div class="layui-card-header">请将要分享的机器调整到右侧</div>
|
||||
<div id="showdevice"></div>
|
||||
<button id="create" type="button" class="layui-btn padding-5" lay-on="getData">生成分享链接</button>
|
||||
</div>
|
||||
<div class="layui-card">1、链接有效期为15分钟,切勿随意分享给他人。</div>
|
||||
<div class="layui-card">2、所分享的机器,被分享人享有相同的权限,如果机器设置了保存密码,被分享人也可以直接连接。</div>
|
||||
<div class="layui-card">3、为保障安全,链接有效期为15分钟、链接仅有效1次。链接一旦被(非分享人的登录用户)访问,分享生效,后续访问链接失效。</div>
|
||||
|
||||
<div class="layui-card layui-col-md6-offset1">
|
||||
<table class="layui-table">
|
||||
<colgroup>
|
||||
<col width="30">
|
||||
<col width="100">
|
||||
<col width="300">
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>链接地址</th>
|
||||
<th>创建时间</th>
|
||||
<th>ID列表</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for one in sharelinks %}
|
||||
<tr>
|
||||
<td>{{one.shash}} </td>
|
||||
<td>{{one.create_time}} </td>
|
||||
<td>{{one.peers}} </td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['transfer', 'jquery', 'layer'], function(){
|
||||
var transfer = layui.transfer;
|
||||
var $ = layui.jquery;
|
||||
var layer = layui.layer;
|
||||
|
||||
//渲染
|
||||
transfer.render({
|
||||
elem: '#showdevice' //绑定元素
|
||||
,title: ['我的机器', '分享机器'] //自定义标题
|
||||
//,width: 500 //定义宽度
|
||||
//,height: 300 //定义高度
|
||||
,data: [//定义数据源
|
||||
{%for peer in peers %}
|
||||
{"value": "{{peer.id}}", "title": "{{peer.name}}"},
|
||||
{%endfor%}
|
||||
|
||||
] //disabled 是否禁用 checked 是否选中
|
||||
,id: 'device' //定义索引 重新加载reload或者获取右侧数据时可以用到
|
||||
});
|
||||
$("#create_bak").click(function(){
|
||||
|
||||
var getData = transfer.getData('device');
|
||||
alert(JSON.stringify(getData));
|
||||
|
||||
});
|
||||
$("#create").click(function(){
|
||||
var getData = transfer.getData('device');
|
||||
$.ajax({
|
||||
url:'/api/share',
|
||||
type:'post',
|
||||
dataType:'json',
|
||||
data:{
|
||||
data:JSON.stringify(getData),
|
||||
},
|
||||
success:function(data){
|
||||
if (data.code == 1) {
|
||||
// var myMsg = layer.msg('处理中', {
|
||||
// shade: 0.4,
|
||||
// time:false //取消自动关闭
|
||||
// });
|
||||
//layer.msg('注册成功,请前往登录页登录。');
|
||||
layer.alert('成功!如需分享,请复制以下链接给其他人:<br>'+ window.location + '/' +data.shash, function (index) {
|
||||
location.reload();});
|
||||
}else {
|
||||
layer.msg(data.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,46 +1,7 @@
|
||||
{% load static %}<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>RustDesk WebUI</title>
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="stylesheet" href="{% static 'layui/css/layui.css' %}">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script src={% static "layui/layui.js" %}></script>
|
||||
<script>
|
||||
layui.use('element', function(){
|
||||
var element = layui.element; //导航的hover效果、二级菜单等功能,需要依赖element模块
|
||||
|
||||
//监听导航点击
|
||||
element.on('nav(demo)', function(elem){
|
||||
//console.log(elem)
|
||||
layer.msg(elem.text());
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<ul class="layui-nav">
|
||||
<li class="layui-nav-item"><a href="/">首页</a></li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% if u.is_admin %}
|
||||
<li class="layui-nav-item"><a href="/admin">管理后台</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
|
||||
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
|
||||
<legend>综合屏</legend>
|
||||
</fieldset>
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% block title %}RustDesk WebUI{% endblock %}
|
||||
{% block legend_name %}综合屏{% endblock %}
|
||||
{% block content %}
|
||||
<div style="padding: 20px; background-color: #F2F2F2;">
|
||||
<div class="layui-row layui-col-space15">
|
||||
<div class="layui-col-md15">
|
||||
@ -154,5 +115,4 @@ layui.use('element', function(){
|
||||
|
||||
</div></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@ -18,4 +18,5 @@ urlpatterns = [
|
||||
#url(r'^register',views.register),
|
||||
url(r'^user_action',views.user_action), # 前端
|
||||
url(r'^work',views.work), # 前端
|
||||
url(r'^share',views.share), # 前端
|
||||
]
|
||||
|
||||
@ -11,17 +11,7 @@ from django.db.models import Q
|
||||
import copy
|
||||
from .views_front import *
|
||||
|
||||
salt = 'xiaomo'
|
||||
EFFECTIVE_SECONDS = 7200
|
||||
|
||||
def getStrMd5(s):
|
||||
if not isinstance(s, (str,)):
|
||||
s = str(s)
|
||||
|
||||
myHash = hashlib.md5()
|
||||
myHash.update(s.encode())
|
||||
|
||||
return myHash.hexdigest()
|
||||
|
||||
def login(request):
|
||||
result = {}
|
||||
@ -45,7 +35,7 @@ def login(request):
|
||||
user.rid = rid
|
||||
user.uuid = uuid
|
||||
user.autoLogin = autoLogin
|
||||
user.rtype = rtype
|
||||
user.rtype = rtype
|
||||
user.deviceInfo = json.dumps(deviceInfo)
|
||||
user.save()
|
||||
|
||||
|
||||
@ -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
|
||||
from api.models import RustDeskPeer, RustDesDevice, UserProfile, ShareLink
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
from itertools import chain
|
||||
@ -14,7 +14,20 @@ from django.db.models.fields import DateTimeField, DateField, CharField, TextFie
|
||||
import datetime
|
||||
from django.db.models import Model
|
||||
import json
|
||||
import time
|
||||
import hashlib
|
||||
|
||||
salt = 'xiaomo'
|
||||
EFFECTIVE_SECONDS = 7200
|
||||
|
||||
def getStrMd5(s):
|
||||
if not isinstance(s, (str,)):
|
||||
s = str(s)
|
||||
|
||||
myHash = hashlib.md5()
|
||||
myHash.update(s.encode())
|
||||
|
||||
return myHash.hexdigest()
|
||||
|
||||
def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=None):
|
||||
"""
|
||||
@ -87,7 +100,8 @@ def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=No
|
||||
|
||||
|
||||
def index(request):
|
||||
#return render(request, 'login3.html', {'info':''})
|
||||
if request.user and request.user.username!='AnonymousUser':
|
||||
return HttpResponseRedirect('/api/work')
|
||||
return HttpResponseRedirect('/api/user_action?action=login')
|
||||
|
||||
|
||||
@ -199,3 +213,87 @@ def work(request):
|
||||
print(all_info)
|
||||
|
||||
return render(request, 'show_work.html', {'single_info':single_info, 'all_info':all_info, 'u':u})
|
||||
|
||||
|
||||
def check_sharelink_expired(sharelink):
|
||||
now = datetime.datetime.now()
|
||||
if sharelink.create_time > now:
|
||||
return False
|
||||
if (now - sharelink.create_time).seconds <15 * 60:
|
||||
return False
|
||||
else:
|
||||
sharelink.is_expired = True
|
||||
sharelink.save()
|
||||
return True
|
||||
|
||||
|
||||
@login_required(login_url='/api/user_action?action=login')
|
||||
def share(request):
|
||||
peers = RustDeskPeer.objects.filter(Q(uid=request.user.id))
|
||||
sharelinks = ShareLink.objects.filter(Q(uid=request.user.id) & Q(is_used=False) & Q(is_expired=False))
|
||||
|
||||
|
||||
# 省资源:处理已过期请求,不主动定时任务轮询请求,在任意地方请求时,检查是否过期,过期则保存。
|
||||
now = datetime.datetime.now()
|
||||
for sl in sharelinks:
|
||||
check_sharelink_expired(sl)
|
||||
sharelinks = ShareLink.objects.filter(Q(uid=request.user.id) & Q(is_used=False) & Q(is_expired=False))
|
||||
peers = [{'id':ix+1, 'name':f'{p.rid}|{p.alias}'} for ix, p in enumerate(peers)]
|
||||
sharelinks = [{'shash':s.shash, 'is_used':s.is_used, 'is_expired':s.is_expired, 'create_time':s.create_time, 'peers':s.peers} for ix, s in enumerate(sharelinks)]
|
||||
|
||||
if request.method == 'GET':
|
||||
url = request.build_absolute_uri()
|
||||
if url.endswith('share'):
|
||||
return render(request, 'share.html', {'peers':peers, 'sharelinks':sharelinks})
|
||||
else:
|
||||
shash = url.split('/')[-1]
|
||||
sharelink = ShareLink.objects.filter(Q(shash=shash))
|
||||
msg = ''
|
||||
title = '成功'
|
||||
if not sharelink:
|
||||
title = '错误'
|
||||
msg = f'链接{url}:<br>分享链接不存在或已失效。'
|
||||
else:
|
||||
sharelink = sharelink[0]
|
||||
if str(request.user.id) == str(sharelink.uid):
|
||||
title = '错误'
|
||||
msg = f'链接{url}:<br><br>咱就说,你不能把链接分享给自己吧?!'
|
||||
else:
|
||||
sharelink.is_used = True
|
||||
sharelink.save()
|
||||
peers = sharelink.peers
|
||||
peers = peers.split(',')
|
||||
# 自己的peers若重叠,需要跳过
|
||||
peers_self_ids = [x.rid for x in RustDeskPeer.objects.filter(Q(uid=request.user.id))]
|
||||
peers_share = RustDeskPeer.objects.filter(rid__in=peers)
|
||||
peers_share_ids = [x.rid for x in peers_share]
|
||||
|
||||
for peer in peers_share:
|
||||
if peer.rid in peers_self_ids:
|
||||
continue
|
||||
peer = RustDeskPeer.objects.get(rid=peer.rid)
|
||||
peer.id = None
|
||||
peer.uid = request.user.id
|
||||
peer.save()
|
||||
msg += f"{peer.rid},"
|
||||
|
||||
msg += '已被成功获取。'
|
||||
|
||||
return render(request, 'msg.html', {'title':msg, 'msg':msg})
|
||||
else:
|
||||
data = request.POST.get('data', '[]')
|
||||
|
||||
data = json.loads(data)
|
||||
if not data:
|
||||
return JsonResponse({'code':0, 'msg':'数据为空。'})
|
||||
rustdesk_ids = [x['title'].split('|')[0] for x in data]
|
||||
rustdesk_ids = ','.join(rustdesk_ids)
|
||||
sharelink = ShareLink(
|
||||
uid=request.user.id,
|
||||
shash = getStrMd5(str(time.time())+salt),
|
||||
peers=rustdesk_ids,
|
||||
)
|
||||
sharelink.save()
|
||||
|
||||
return JsonResponse({'code':1, 'shash':sharelink.shash})
|
||||
|
||||
|
||||
BIN
db.sqlite3_bak
Normal file
BIN
db.sqlite3_bak
Normal file
Binary file not shown.
BIN
db/db.sqlite3
BIN
db/db.sqlite3
Binary file not shown.
Loading…
Reference in New Issue
Block a user