Compare commits

...

39 Commits

Author SHA1 Message Date
kingmo888
71c6f290d1 更新说明 2024-09-25 12:46:58 +08:00
kingmo888
d04154aa7e 当客户端未登录时,前端所属用户显示[未登录] 2024-09-10 00:07:08 +08:00
kingmo888
175e1165db 解决反代时IP不正确的情况 2024-09-09 23:56:47 +08:00
kingmo888
7b8951d441 add ip_address for model resolves #137, resolves #15 2024-09-09 10:50:13 +08:00
kingmo888
3978cf6ec8 fixed 133,132 2024-09-03 09:43:31 +08:00
kingmo888
6a4eeac871 登录时自动添加当前设备.web控制时根据https自动ws/wss切换 2024-09-01 21:54:33 +08:00
kingmo888
e2bbf9c04c 为不细心的人更新 2024-08-14 10:15:46 +08:00
kingmo888
f8f6fc5668 更新key说明 2024-08-13 23:32:34 +08:00
kingmo888
67ce71773b . 2024-08-12 16:39:08 +08:00
kingmo888
38e4269025 . 2024-08-12 16:35:17 +08:00
kingmo888
f08d67ab78 . 2024-08-12 16:24:08 +08:00
kingmo888
e465b8eb08 解决ISSUES提醒强迫症,未start直接关闭! 2024-08-12 11:31:49 +08:00
kingmo888
352faed075 解决ISSUES提醒强迫症,未start直接关闭! 2024-08-12 11:25:47 +08:00
kingmo888
e26dc2d073 code for flake8, change menu level 2024-08-09 18:35:58 +08:00
kingmo888
f90f707fd0 . 2024-08-08 17:49:26 +08:00
kingmo888
557dafe7ba Fixes #95,#100,#81,#83,89 - fix rustdesk version>1.2.6 connect timeout 2024-08-08 17:35:42 +08:00
cococo
0748c8aace
Merge pull request #101 from Sam5440/patch-1
client1.2.7,server1.1.11恢复正常连接速度方法
2024-08-08 17:09:43 +08:00
Sam5440
20597e4769
client1.2.7,server1.1.11恢复正常连接速度方法 2024-08-05 20:02:00 +08:00
kingmo888
51c53bb132 update 2024-06-18 23:19:26 +08:00
kingmo888
3d58e134aa fixed #80, fixed #78 2024-06-13 10:54:46 +08:00
kingmo888
1226b6ceee update 2024-05-15 10:56:56 +08:00
kingmo888
de5979bf3c update 2024-05-15 10:52:24 +08:00
kingmo888
44fa7a73e3 update log 2024-05-14 10:49:01 +08:00
kingmo888
81341d5b6d Fix bugs and adjust internationalization of new pages 2024-05-14 10:47:41 +08:00
kingmo888
aba300a0fa update log 2024-05-05 22:58:36 +08:00
kingmo888
cefb253dad update readme 2024-05-05 16:07:40 +08:00
cococo
33c85f8b08
Merge pull request #73 from Cp0204/master
镜像同步发布到 DockerHub
2024-05-05 01:42:19 +08:00
Cp0204
908d489326 镜像同步发布到 DockerHub 2024-05-04 23:57:38 +08:00
kingmo888
e30db99700 del mysqlclient, Users need to install it themselves 2024-04-19 21:13:53 +08:00
kingmo888
a44bf5eb61 update 2024-04-19 19:56:34 +08:00
kingmo888
bad2eeef7c update 2024-04-19 19:19:24 +08:00
cococo
5ac578422b
Merge pull request #65 from bryangerlach/km-logs
add connection and file transfer logs
2024-04-19 19:18:06 +08:00
Bryan Gerlach
fcd5ccedd4 added back the translate code, I hope it is correct. 2024-04-17 17:44:28 -05:00
Bryan Gerlach
c5fe3e125c add connection and file transfer logs 2024-04-17 08:39:02 -05:00
cococo
53068fd8de
Merge pull request #62 from exalented/master
Fix LANGUAGE_CODE environment variable
2024-04-04 18:23:16 +08:00
Curtis Alexander
efa3decc31
Fix LANGUAGE_CODE environment variable 2024-04-04 10:19:10 +00:00
kingmo888
acf11d5ab8 update template 2024-03-22 21:53:05 +08:00
kingmo888
a9c21ffad4 update template 2024-03-20 14:19:52 +08:00
kingmo888
5020b45962 0320 2024-03-20 10:31:49 +08:00
34 changed files with 10137 additions and 218 deletions

View File

@ -1,5 +1,5 @@
name: Bug Report name: Bug Report
description: File a bug report description: Submit an error report. Failure to respond within 3 days after receiving a response will result in closure. Attention! Asking questions without starting is very unethical.(提交错误报告。在收到回复后3天内未做出回复将导致关闭。注意不star却提问是很没有道德的。)
title: "[Bug]: " title: "[Bug]: "
labels: ["bug"] labels: ["bug"]

56
.github/workflows/auto-close-issues.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: Auto Close Issues if Not Starred
on:
issues:
types: [opened]
jobs:
close_issue:
runs-on: ubuntu-latest
steps:
- name: Check if user has starred the repo
id: check_star
run: |
ISSUE_USER=$(jq -r '.issue.user.login' < $GITHUB_EVENT_PATH)
echo "Issue created by user: $ISSUE_USER"
PAGE=1
STARRED=""
while [[ -z "$STARRED" ]]; do
STARRED_RESPONSE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/${{ github.repository }}/stargazers?per_page=100&page=$PAGE")
echo "STARRED_RESPONSE=$STARRED_RESPONSE" # 输出 API 返回的数据以检查结构
if [[ -z "$STARRED_RESPONSE" || "$STARRED_RESPONSE" == "[]" ]]; then
break
fi
STARRED=$(echo "$STARRED_RESPONSE" | jq -r '.[] | select(.login == "'$ISSUE_USER'") | .login')
PAGE=$((PAGE + 1))
done
if [[ -z "$STARRED" ]]; then
echo "User has not starred the repo."
else
echo "User has starred the repo."
fi
echo "starred=$STARRED" >> $GITHUB_ENV
- name: Close issue if not starred
if: env.starred == '0'
run: |
ISSUE_NUMBER=$(jq -r '.issue.number' < $GITHUB_EVENT_PATH)
echo "Closing issue #$ISSUE_NUMBER"
curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUMBER/comments \
-d '{"body":"Please star the project before submitting an issue."}'
curl -s -X PATCH -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUMBER \
-d '{"state":"closed"}'

119
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,119 @@
name: RustDesk Web Api
on:
workflow_dispatch:
inputs:
docker_username:
description: 'docker user name'
required: true
default: ''
docker_password:
description: 'docker user password'
required: true
default: ''
jobs:
alpine:
runs-on: ubuntu-latest
name: Build Docker Image (Alpine)
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Release version
id: release_version
run: |
app_version=$(cat version.py |sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
echo "app_version=${app_version}" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ github.event.inputs.docker_username }}/rustdesk-api-server
tags: |
type=raw,value=${{ env.app_version }}
type=raw,value=latest
-
name: Set Up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set Up Buildx
uses: docker/setup-buildx-action@v2
-
name: Login DockerHub
uses: docker/login-action@v2
with:
username: ${{ github.event.inputs.docker_username }}
password: ${{ github.event.inputs.docker_password }}
-
name: Build Image
uses: docker/build-push-action@v4
with:
context: docker
file: docker/Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
debian:
runs-on: ubuntu-latest
name: Build Docker Image (Debian)
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Release version
id: release_version
run: |
app_version=$(cat version.py |sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
echo "app_version=${app_version}-debian" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ github.event.inputs.docker_username }}/rustdesk-api-server
tags: |
type=raw,value=${{ env.app_version }}
type=raw,value=debian
-
name: Set Up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set Up Buildx
uses: docker/setup-buildx-action@v2
-
name: Login DockerHub
uses: docker/login-action@v2
with:
username: ${{ github.event.inputs.docker_username }}
password: ${{ github.event.inputs.docker_password }}
-
name: Build Image
uses: docker/build-push-action@v4
with:
context: docker
file: docker/debian.Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@ -10,10 +10,9 @@ on:
branches: branches:
- master - master
tags: tags:
- "v*.*" - "v*"
env: env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
@ -21,13 +20,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: |
${{ env.IMAGE_NAME }}
ghcr.io/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@ -38,10 +42,16 @@ jobs:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
@ -58,3 +68,12 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
- name: Update repo description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ env.IMAGE_NAME }}
short-description: ${{ github.event.repository.description }}
enable-url-completion: true

4
.gitignore vendored
View File

@ -26,4 +26,6 @@ dist_py38
LICENSE.rst LICENSE.rst
db/test_db.sqlite3 db/test_db.sqlite3
job2en.py job2en.py
新建文本文档.txt

View File

@ -3,6 +3,13 @@ FROM python:3.10.3-alpine
WORKDIR /rustdesk-api-server WORKDIR /rustdesk-api-server
ADD . /rustdesk-api-server ADD . /rustdesk-api-server
# 安装系统依赖
RUN apk add --no-cache \
gcc \
musl-dev \
mariadb-connector-c-dev \
pkgconfig
RUN set -ex \ RUN set -ex \
&& pip install --no-cache-dir --disable-pip-version-check -r requirements.txt \ && pip install --no-cache-dir --disable-pip-version-check -r requirements.txt \
&& rm -rf /var/cache/apk/* \ && rm -rf /var/cache/apk/* \

View File

@ -1,13 +1,12 @@
# rustdesk-api-server # rustdesk-api-server
## 请使用客户端最新版本1.2.3
[The English explanation is available by clicking here.](https://github.com/kingmo888/rustdesk-api-server/blob/master/README_EN.md) [The English explanation is available by clicking here.](https://github.com/kingmo888/rustdesk-api-server/blob/master/README_EN.md)
<p align="center"> <p align="center">
<i>一个 python 实现的 Rustdesk API 接口,支持 WebUI 管理</i> <i>一个 python 实现的 Rustdesk API 接口,支持 WebUI 管理</i>
<br/> <br/>
<img src ="https://img.shields.io/badge/Version-1.4.9-blueviolet.svg"/> <img src ="https://img.shields.io/badge/Version-1.5.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/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" /> <img src ="https://img.shields.io/badge/Django-3.2+|4.x-yelow.svg" />
<br/> <br/>
@ -15,6 +14,30 @@
<img src ="https://img.shields.io/badge/Docker-arm|arm64|amd64-blue.svg" /> <img src ="https://img.shields.io/badge/Docker-arm|arm64|amd64-blue.svg" />
</p> </p>
# 1.2.3版本与1.2.6+版本区别
#### **请使用自定义key因不填写key、或使用服务端自动生成的key而引起的链接超时或建立链接时间过长的问题不在本项目解决范围内。**
> rustdesk官方在其新版服务端中已[强制要求key](https://rustdesk.com/docs/zh-cn/self-host/rustdesk-server-oss/install/#key)(rustdesk-server版本号大概>=1.1.10)
- rustdesk版本<=1.2.3, 服务端请配合使用rustdesk-server<=1.1.10
- 根据自身需要来选择是否配置服务端的key参数。
- rustdesk版本>1.2.3, 服务端请配合使用rustdesk-server>=1.1.11
- 当使用rustdesk-server自动生成的key时会出现链接缓慢甚至链接超时。
- 解决办法使用自定义k——配置rustdesk-server时传入k参数来自定义key值同时客户端同步配置相同的key即可秒连。
- rustdesk-server的dock-compose配置参考
![demo](images/compose_demo.png)
`key是保证别人不能在知道你中继服务器的IP后利用你的IP做中继。如果不配置key就做好中继IP的保密工作不要泄露给其他人。
而只要服务端配置了密钥无论是随机生成生成后本身就固定了还是自定义的如果控制客户端不配置对应key就无法控制其他机器被控机器可以不填key`
对于自定义key是否生效请看rustdesk server中`hbbs`的日志:
![demo](images/key_activate.png)
## 展示
![主页面](images/front_main.png) ![主页面](images/front_main.png)
## 功能特点 ## 功能特点
@ -170,6 +193,10 @@ services:
这种操作大概率是docker配置+nginx反代+SSL的组合要注意修改CSRF_TRUSTED_ORIGINS如果是ssl那就是https开头否则就是http。 这种操作大概率是docker配置+nginx反代+SSL的组合要注意修改CSRF_TRUSTED_ORIGINS如果是ssl那就是https开头否则就是http。
- Mysql版本要求
如果你使用的是Mysql数据库需要注意django4.x版本需要Mysql8.0如果要使用mysql5.8则需要将django版本降至3.2。
## 开发计划 ## 开发计划
- [x] 分享设备给其他已注册用户v1.3+ - [x] 分享设备给其他已注册用户v1.3+
@ -180,7 +207,7 @@ services:
- [x] 集成Web客户端形式v1.4+ - [x] 集成Web客户端形式v1.4+
> 将大神的web客户端集成进来已集成。 [来源](https://www.52pojie.cn/thread-1708319-1-1.html) > 将大神的web客户端集成进来已集成。 [来源](https://www.52pojie.cn/thread-1708319-1-1.html)
- [x] 对过期(不在线)设备的过滤,用以区分在线&离线设备(1.4.7) - [x] 对过期(不在线)设备的过滤,用以区分在线&离线设备(1.4.7)
> 通过配置方式,对过期超过指定时间的设备清理或过滤。 > 通过配置方式,对过期超过指定时间的设备清理或过滤。
@ -195,6 +222,11 @@ services:
- [x] 支持mysql及sqlite3迁移mysql(1.4.8)。 - [x] 支持mysql及sqlite3迁移mysql(1.4.8)。
- [-] 用户前端修改密码。
- [-] 前端改造,所有页面自适应,前后端分离(计划V2)。
## 其他相关工具 ## 其他相关工具
- [可以修改客户端ID的CMD脚本](https://github.com/abdullah-erturk/RustDesk-ID-Changer) - [可以修改客户端ID的CMD脚本](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
@ -205,3 +237,8 @@ services:
## Stargazers over time ## Stargazers over time
[![Stargazers over time](https://starchart.cc/kingmo888/rustdesk-api-server.svg?variant=adaptive)](https://starchart.cc/kingmo888/rustdesk-api-server) [![Stargazers over time](https://starchart.cc/kingmo888/rustdesk-api-server.svg?variant=adaptive)](https://starchart.cc/kingmo888/rustdesk-api-server)
## 联络我
![wechat](images/wechat.png)

View File

@ -9,7 +9,7 @@
<p align="center"> <p align="center">
<i>A Rustdesk API interface implemented in Python, with WebUI management support</i> <i>A Rustdesk API interface implemented in Python, with WebUI management support</i>
<br/> <br/>
<img src ="https://img.shields.io/badge/Version-1.4.8-blueviolet.svg"/> <img src ="https://img.shields.io/badge/Version-1.5.1-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/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" /> <img src ="https://img.shields.io/badge/Django-3.2+|4.x-yelow.svg" />
<br/> <br/>

View File

@ -94,6 +94,8 @@ admin.site.register(models.RustDeskTag, models.RustDeskTagAdmin)
admin.site.register(models.RustDeskPeer, models.RustDeskPeerAdmin) admin.site.register(models.RustDeskPeer, models.RustDeskPeerAdmin)
admin.site.register(models.RustDesDevice, models.RustDesDeviceAdmin) admin.site.register(models.RustDesDevice, models.RustDesDeviceAdmin)
admin.site.register(models.ShareLink, models.ShareLinkAdmin) 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.unregister(Group)
admin.site.site_header = _('RustDesk自建Web') admin.site.site_header = _('RustDesk自建Web')
admin.site.site_title = _('未定义') admin.site.site_title = _('未定义')

View File

@ -57,4 +57,22 @@ _('用户名')
_('3、为保障安全链接有效期为15分钟、链接仅有效1次。链接一旦被非分享人的登录用户访问分享生效后续访问链接失效。') _('3、为保障安全链接有效期为15分钟、链接仅有效1次。链接一旦被非分享人的登录用户访问分享生效后续访问链接失效。')
_('系统') _('系统')
_('我的机器') _('我的机器')
_('信息') _('信息')
_('远程ID')
_('远程别名')
_('用户ID')
_('用户别名')
_('用户IP')
_('文件大小')
_('发送/接受')
_('记录于')
_('连接开始时间')
_('连接结束时间')
_('时长')
_('连接日志')
_('文件传输日志')
_('页码 #')
_('下一页 #')
_('上一页 #')
_('第一页')
_('上页')

View File

@ -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')),
],
),
]

View File

@ -0,0 +1,240 @@
# Generated by Django 4.2.7 on 2024-05-14 10:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("api", "0005_connlog_filelog"),
]
operations = [
migrations.AlterModelOptions(
name="rustdesdevice",
options={
"ordering": ("-rid",),
"verbose_name": "Device",
"verbose_name_plural": "Device List",
},
),
migrations.AlterModelOptions(
name="rustdeskpeer",
options={
"ordering": ("-username",),
"verbose_name": "Peers",
"verbose_name_plural": "Peers List",
},
),
migrations.AlterModelOptions(
name="rustdesktag",
options={
"ordering": ("-uid",),
"verbose_name": "Tags",
"verbose_name_plural": "Tags List",
},
),
migrations.AlterModelOptions(
name="rustdesktoken",
options={
"ordering": ("-username",),
"verbose_name": "Token",
"verbose_name_plural": "Token List",
},
),
migrations.AlterModelOptions(
name="sharelink",
options={
"ordering": ("-create_time",),
"verbose_name": "Share Link",
"verbose_name_plural": "Link List",
},
),
migrations.AlterModelOptions(
name="userprofile",
options={
"permissions": (
("view_task", "Can see available tasks"),
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
),
"verbose_name": "User",
"verbose_name_plural": "User List",
},
),
migrations.AlterField(
model_name="rustdesdevice",
name="create_time",
field=models.DateTimeField(
auto_now_add=True, verbose_name="Device Registration Time"
),
),
migrations.AlterField(
model_name="rustdesdevice",
name="hostname",
field=models.CharField(max_length=100, verbose_name="Hostname"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="memory",
field=models.CharField(max_length=100, verbose_name="Memory"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="os",
field=models.CharField(max_length=100, verbose_name="Operating System"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="rid",
field=models.CharField(blank=True, max_length=60, verbose_name="Client ID"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="username",
field=models.CharField(
blank=True, max_length=100, verbose_name="System Username"
),
),
migrations.AlterField(
model_name="rustdesdevice",
name="version",
field=models.CharField(max_length=100, verbose_name="Client Version"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="alias",
field=models.CharField(max_length=30, verbose_name="Alias"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="hostname",
field=models.CharField(max_length=30, verbose_name="Operating System Name"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="platform",
field=models.CharField(max_length=30, verbose_name="Platform"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="rhash",
field=models.CharField(
max_length=60, verbose_name="Device Connection Password"
),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="rid",
field=models.CharField(max_length=60, verbose_name="Client ID"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="tags",
field=models.CharField(max_length=30, verbose_name="Tag"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="uid",
field=models.CharField(max_length=16, verbose_name="User ID"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="username",
field=models.CharField(max_length=20, verbose_name="System Username"),
),
migrations.AlterField(
model_name="rustdesktag",
name="tag_color",
field=models.CharField(blank=True, max_length=60, verbose_name="Tag Color"),
),
migrations.AlterField(
model_name="rustdesktag",
name="tag_name",
field=models.CharField(max_length=60, verbose_name="Tag Name"),
),
migrations.AlterField(
model_name="rustdesktag",
name="uid",
field=models.CharField(max_length=16, verbose_name="Belongs to User ID"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="access_token",
field=models.CharField(
blank=True, max_length=60, verbose_name="Access Token"
),
),
migrations.AlterField(
model_name="rustdesktoken",
name="create_time",
field=models.DateTimeField(auto_now_add=True, verbose_name="Login Time"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="uid",
field=models.CharField(max_length=16, verbose_name="User ID"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="username",
field=models.CharField(max_length=20, verbose_name="Username"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="uuid",
field=models.CharField(max_length=60, verbose_name="UUID"),
),
migrations.AlterField(
model_name="sharelink",
name="create_time",
field=models.DateTimeField(
auto_now_add=True, verbose_name="Generation Time"
),
),
migrations.AlterField(
model_name="sharelink",
name="is_expired",
field=models.BooleanField(default=False, verbose_name="Is Expired"),
),
migrations.AlterField(
model_name="sharelink",
name="is_used",
field=models.BooleanField(default=False, verbose_name="Is Used"),
),
migrations.AlterField(
model_name="sharelink",
name="peers",
field=models.CharField(max_length=20, verbose_name="Machine ID List"),
),
migrations.AlterField(
model_name="sharelink",
name="shash",
field=models.CharField(max_length=60, verbose_name="Link Key"),
),
migrations.AlterField(
model_name="sharelink",
name="uid",
field=models.CharField(max_length=16, verbose_name="User ID"),
),
migrations.AlterField(
model_name="userprofile",
name="deviceInfo",
field=models.TextField(blank=True, verbose_name="Login Information:"),
),
migrations.AlterField(
model_name="userprofile",
name="is_active",
field=models.BooleanField(default=True, verbose_name="Is Active"),
),
migrations.AlterField(
model_name="userprofile",
name="is_admin",
field=models.BooleanField(default=False, verbose_name="Is Administrator"),
),
migrations.AlterField(
model_name="userprofile",
name="username",
field=models.CharField(max_length=50, unique=True, verbose_name="Username"),
),
]

View File

@ -0,0 +1,232 @@
# Generated by Django 4.2.7 on 2024-05-14 10:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("api", "0006_alter_rustdesdevice_options_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="rustdesdevice",
options={
"ordering": ("-rid",),
"verbose_name": "设备",
"verbose_name_plural": "设备列表",
},
),
migrations.AlterModelOptions(
name="rustdeskpeer",
options={
"ordering": ("-username",),
"verbose_name": "Peers",
"verbose_name_plural": "Peers列表",
},
),
migrations.AlterModelOptions(
name="rustdesktag",
options={
"ordering": ("-uid",),
"verbose_name": "Tags",
"verbose_name_plural": "Tags列表",
},
),
migrations.AlterModelOptions(
name="rustdesktoken",
options={
"ordering": ("-username",),
"verbose_name": "Token",
"verbose_name_plural": "Token列表",
},
),
migrations.AlterModelOptions(
name="sharelink",
options={
"ordering": ("-create_time",),
"verbose_name": "分享链接",
"verbose_name_plural": "链接列表",
},
),
migrations.AlterModelOptions(
name="userprofile",
options={
"permissions": (
("view_task", "Can see available tasks"),
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
),
"verbose_name": "用户",
"verbose_name_plural": "用户列表",
},
),
migrations.AlterField(
model_name="rustdesdevice",
name="create_time",
field=models.DateTimeField(auto_now_add=True, verbose_name="设备注册时间"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="hostname",
field=models.CharField(max_length=100, verbose_name="主机名"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="memory",
field=models.CharField(max_length=100, verbose_name="内存"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="os",
field=models.CharField(max_length=100, verbose_name="操作系统"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="rid",
field=models.CharField(blank=True, max_length=60, verbose_name="客户端ID"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="username",
field=models.CharField(blank=True, max_length=100, verbose_name="系统用户名"),
),
migrations.AlterField(
model_name="rustdesdevice",
name="version",
field=models.CharField(max_length=100, verbose_name="客户端版本"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="alias",
field=models.CharField(max_length=30, verbose_name="别名"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="hostname",
field=models.CharField(max_length=30, verbose_name="操作系统名"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="platform",
field=models.CharField(max_length=30, verbose_name="平台"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="rhash",
field=models.CharField(max_length=60, verbose_name="设备链接密码"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="rid",
field=models.CharField(max_length=60, verbose_name="客户端ID"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="tags",
field=models.CharField(max_length=30, verbose_name="标签"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="uid",
field=models.CharField(max_length=16, verbose_name="用户ID"),
),
migrations.AlterField(
model_name="rustdeskpeer",
name="username",
field=models.CharField(max_length=20, verbose_name="系统用户名"),
),
migrations.AlterField(
model_name="rustdesktag",
name="tag_color",
field=models.CharField(blank=True, max_length=60, verbose_name="标签颜色"),
),
migrations.AlterField(
model_name="rustdesktag",
name="tag_name",
field=models.CharField(max_length=60, verbose_name="标签名称"),
),
migrations.AlterField(
model_name="rustdesktag",
name="uid",
field=models.CharField(max_length=16, verbose_name="所属用户ID"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="access_token",
field=models.CharField(
blank=True, max_length=60, verbose_name="access_token"
),
),
migrations.AlterField(
model_name="rustdesktoken",
name="create_time",
field=models.DateTimeField(auto_now_add=True, verbose_name="登录时间"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="uid",
field=models.CharField(max_length=16, verbose_name="用户ID"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="username",
field=models.CharField(max_length=20, verbose_name="用户名"),
),
migrations.AlterField(
model_name="rustdesktoken",
name="uuid",
field=models.CharField(max_length=60, verbose_name="uuid"),
),
migrations.AlterField(
model_name="sharelink",
name="create_time",
field=models.DateTimeField(auto_now_add=True, verbose_name="生成时间"),
),
migrations.AlterField(
model_name="sharelink",
name="is_expired",
field=models.BooleanField(default=False, verbose_name="是否过期"),
),
migrations.AlterField(
model_name="sharelink",
name="is_used",
field=models.BooleanField(default=False, verbose_name="是否使用"),
),
migrations.AlterField(
model_name="sharelink",
name="peers",
field=models.CharField(max_length=20, verbose_name="机器ID列表"),
),
migrations.AlterField(
model_name="sharelink",
name="shash",
field=models.CharField(max_length=60, verbose_name="链接Key"),
),
migrations.AlterField(
model_name="sharelink",
name="uid",
field=models.CharField(max_length=16, verbose_name="用户ID"),
),
migrations.AlterField(
model_name="userprofile",
name="deviceInfo",
field=models.TextField(blank=True, verbose_name="登录信息:"),
),
migrations.AlterField(
model_name="userprofile",
name="is_active",
field=models.BooleanField(default=True, verbose_name="是否激活"),
),
migrations.AlterField(
model_name="userprofile",
name="is_admin",
field=models.BooleanField(default=False, verbose_name="是否管理员"),
),
migrations.AlterField(
model_name="userprofile",
name="username",
field=models.CharField(max_length=50, unique=True, verbose_name="用户名"),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2 on 2024-09-09 10:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0007_alter_rustdesdevice_options_and_more'),
]
operations = [
migrations.AddField(
model_name='rustdesdevice',
name='ip_address',
field=models.CharField(blank=True, max_length=60, verbose_name='IP'),
),
]

View File

@ -3,6 +3,7 @@ from django.db import models
from django.contrib import admin from django.contrib import admin
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
class RustDeskToken(models.Model): class RustDeskToken(models.Model):
''' Token ''' Token
''' '''
@ -12,17 +13,19 @@ class RustDeskToken(models.Model):
uuid = models.CharField(verbose_name=_('uuid'), max_length=60) uuid = models.CharField(verbose_name=_('uuid'), max_length=60)
access_token = models.CharField(verbose_name=_('access_token'), max_length=60, blank=True) access_token = models.CharField(verbose_name=_('access_token'), max_length=60, blank=True)
create_time = models.DateTimeField(verbose_name=_('登录时间'), auto_now_add=True) create_time = models.DateTimeField(verbose_name=_('登录时间'), auto_now_add=True)
#expire_time = models.DateTimeField(verbose_name='过期时间') # expire_time = models.DateTimeField(verbose_name='过期时间')
class Meta: class Meta:
ordering = ('-username',) ordering = ('-username',)
verbose_name = "Token" verbose_name = "Token"
verbose_name_plural = _("Token列表") verbose_name_plural = _("Token列表")
class RustDeskTokenAdmin(admin.ModelAdmin): class RustDeskTokenAdmin(admin.ModelAdmin):
list_display = ('username', 'uid') list_display = ('username', 'uid')
search_fields = ('username', 'uid') search_fields = ('username', 'uid')
list_filter = ('create_time', ) #过滤器 list_filter = ('create_time', ) # 过滤器
class RustDeskTag(models.Model): class RustDeskTag(models.Model):
''' Tags ''' Tags
@ -30,17 +33,18 @@ class RustDeskTag(models.Model):
uid = models.CharField(verbose_name=_('所属用户ID'), max_length=16) uid = models.CharField(verbose_name=_('所属用户ID'), max_length=16)
tag_name = models.CharField(verbose_name=_('标签名称'), max_length=60) tag_name = models.CharField(verbose_name=_('标签名称'), max_length=60)
tag_color = models.CharField(verbose_name=_('标签颜色'), max_length=60, blank=True) tag_color = models.CharField(verbose_name=_('标签颜色'), max_length=60, blank=True)
class Meta: class Meta:
ordering = ('-uid',) ordering = ('-uid',)
verbose_name = "Tags" verbose_name = "Tags"
verbose_name_plural = _("Tags列表") verbose_name_plural = _("Tags列表")
class RustDeskTagAdmin(admin.ModelAdmin): class RustDeskTagAdmin(admin.ModelAdmin):
list_display = ('tag_name', 'uid', 'tag_color') list_display = ('tag_name', 'uid', 'tag_color')
search_fields = ('tag_name', 'uid') search_fields = ('tag_name', 'uid')
list_filter = ('uid', ) list_filter = ('uid', )
class RustDeskPeer(models.Model): class RustDeskPeer(models.Model):
''' Pees ''' Pees
@ -53,19 +57,19 @@ class RustDeskPeer(models.Model):
platform = models.CharField(verbose_name=_('平台'), max_length=30) platform = models.CharField(verbose_name=_('平台'), max_length=30)
tags = models.CharField(verbose_name=_('标签'), max_length=30) tags = models.CharField(verbose_name=_('标签'), max_length=30)
rhash = models.CharField(verbose_name=_('设备链接密码'), max_length=60) rhash = models.CharField(verbose_name=_('设备链接密码'), max_length=60)
class Meta: class Meta:
ordering = ('-username',) ordering = ('-username',)
verbose_name = "Peers" verbose_name = "Peers"
verbose_name_plural = _("Peers列表" ) verbose_name_plural = _("Peers列表")
class RustDeskPeerAdmin(admin.ModelAdmin): class RustDeskPeerAdmin(admin.ModelAdmin):
list_display = ('rid', 'uid', 'username', 'hostname', 'platform', 'alias', 'tags') list_display = ('rid', 'uid', 'username', 'hostname', 'platform', 'alias', 'tags')
search_fields = ('deviceid', 'alias') search_fields = ('deviceid', 'alias')
list_filter = ('rid', 'uid', ) list_filter = ('rid', 'uid', )
class RustDesDevice(models.Model): class RustDesDevice(models.Model):
rid = models.CharField(verbose_name=_('客户端ID'), max_length=60, blank=True) rid = models.CharField(verbose_name=_('客户端ID'), max_length=60, blank=True)
cpu = models.CharField(verbose_name='CPU', max_length=100) cpu = models.CharField(verbose_name='CPU', max_length=100)
@ -75,20 +79,57 @@ class RustDesDevice(models.Model):
uuid = models.CharField(verbose_name='uuid', max_length=100) uuid = models.CharField(verbose_name='uuid', max_length=100)
username = models.CharField(verbose_name=_('系统用户名'), max_length=100, blank=True) username = models.CharField(verbose_name=_('系统用户名'), max_length=100, blank=True)
version = models.CharField(verbose_name=_('客户端版本'), max_length=100) version = models.CharField(verbose_name=_('客户端版本'), max_length=100)
ip_address = models.CharField(verbose_name=_('IP'), max_length=60, blank=True)
create_time = models.DateTimeField(verbose_name=_('设备注册时间'), auto_now_add=True) create_time = models.DateTimeField(verbose_name=_('设备注册时间'), auto_now_add=True)
update_time = models.DateTimeField(verbose_name=('设备更新时间'), auto_now=True, blank=True) update_time = models.DateTimeField(verbose_name=('设备更新时间'), auto_now=True, blank=True)
class Meta: class Meta:
ordering = ('-rid',) ordering = ('-rid',)
verbose_name = _("设备") verbose_name = _("设备")
verbose_name_plural = _("设备列表" ) verbose_name_plural = _("设备列表")
class RustDesDeviceAdmin(admin.ModelAdmin): class RustDesDeviceAdmin(admin.ModelAdmin):
list_display = ('rid', 'hostname', 'memory', 'uuid', 'version', 'create_time', 'update_time') list_display = ('rid', 'hostname', 'memory', 'uuid', 'version', 'create_time', 'update_time')
search_fields = ('hostname', 'memory') search_fields = ('hostname', 'memory')
list_filter = ('rid', ) 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): class ShareLink(models.Model):
''' 分享链接 ''' 分享链接
@ -99,16 +140,14 @@ class ShareLink(models.Model):
is_used = models.BooleanField(verbose_name=_('是否使用'), default=False) is_used = models.BooleanField(verbose_name=_('是否使用'), default=False)
is_expired = models.BooleanField(verbose_name=_('是否过期'), default=False) is_expired = models.BooleanField(verbose_name=_('是否过期'), default=False)
create_time = models.DateTimeField(verbose_name=_('生成时间'), auto_now_add=True) create_time = models.DateTimeField(verbose_name=_('生成时间'), auto_now_add=True)
class Meta: class Meta:
ordering = ('-create_time',) ordering = ('-create_time',)
verbose_name = _("分享链接") verbose_name = _("分享链接")
verbose_name_plural = _("链接列表" ) verbose_name_plural = _("链接列表")
class ShareLinkAdmin(admin.ModelAdmin): class ShareLinkAdmin(admin.ModelAdmin):
list_display = ('shash', 'uid', 'peers', 'is_used', 'is_expired', 'create_time') list_display = ('shash', 'uid', 'peers', 'is_used', 'is_expired', 'create_time')
search_fields = ('peers', ) search_fields = ('peers', )
list_filter = ('is_used', 'uid', 'is_expired' ) list_filter = ('is_used', 'uid', 'is_expired')

View File

@ -37,8 +37,14 @@
<li class="layui-nav-item"><a href="/webui" target="_blank">{% trans "网页控制" %}</a></li> <li class="layui-nav-item"><a href="/webui" target="_blank">{% trans "网页控制" %}</a></li>
{% if u.is_admin %} {% if u.is_admin %}
<li class="layui-nav-item"><a href="/admin" target="_blank">{% trans "管理后台" %}</a> <li class="layui-nav-item">
<a href="">{% trans "日志" %}</a>
<dl class="layui-nav-child">
<dd><a href="/api/conn_log">{% trans "连接日志" %}</a></dd>
<dd><a href="/api/file_log">{% trans "文件传输日志" %}</a></dd>
</dl>
</li> </li>
<li class="layui-nav-item"><a href="/admin" target="_blank">{% trans "管理后台" %}</a></li>
{% endif %} {% endif %}
<li class="layui-nav-item"><a href="/api/user_action?action=logout" target="_blank">{% trans "退出" %}</a></li> <li class="layui-nav-item"><a href="/api/user_action?action=logout" target="_blank">{% trans "退出" %}</a></li>
</ul> </ul>

View File

@ -0,0 +1,62 @@
{% extends "base.html" %}
{% load my_filters %}
{% block title %}RustDesk WebUI{% endblock %}
{% block content %}
<div style="padding: 20px; background-color: #F2F2F2;">
<div class="layui-row layui-col-space15">
<div class="layui-col-md15">
<div class="layui-card">
<div class="layui-card-header">{{ "连接日志" | translate }}:{{u.username}}</div>
<div class="layui-card-body">
<table class="layui-table">
<thead>
<tr>
<th>{{ "用户IP" | translate }}</th>
<th>{{ "用户ID" | translate }}</th>
<th>{{ "用户别名" | translate }}</th>
<th>{{ "远程ID" | translate }}</th>
<th>{{ "远程别名" | translate }}</th>
<th>{{ "连接开始时间" | translate }}</th>
<th>{{ "连接结束时间" | translate }}</th>
<th>{{ "时长" | translate }}(HH:MM:SS)</th>
</tr>
</thead>
<tbody>
{% for one in page_obj %}
<tr>
<td>{{one.from_ip}} </td>
<td>{{one.from_id}}</td>
<td>{{one.from_alias}}</td>
<td>{{one.rid}}</td>
<td>{{one.alias}}</td>
<td>{{one.conn_start}}</td>
<td>{{one.conn_end}}</td>
<td>{{one.duration}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="layui-col-md4 layui-col-md-offset4">
<span class="step-links">
{% if page_obj.has_previous %}
<button class="layui-btn" ><a href="?page=1">&laquo; {{ "First" | translate }}</a></button>
<button class="layui-btn" ><a href="?page={{ page_obj.previous_page_number }}">{{ "Previous" | translate }}</a></button>
{% endif %}
{% if page_obj.paginator.num_pages > 1 %}
<span class="current">
{{ "页码 #" | translate }} {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}
</span>
{% endif %}
{% if page_obj.has_next %}
<button class="layui-btn" > <a href="?page={{ page_obj.next_page_number }}">{{ "Next" | translate }}</a></button>
<button class="layui-btn" ><a href="?page={{ page_obj.paginator.num_pages }}">{{ "Last" | translate }} &raquo;</a></button>
{% endif %}
</span>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,68 @@
{% extends "base.html" %}
{% load my_filters %}
{% block title %}RustDesk WebUI{% endblock %}
{% block content %}
<div style="padding: 20px; background-color: #F2F2F2;">
<div class="layui-row layui-col-space15">
<div class="layui-col-md15">
<div class="layui-card">
<div class="layui-card-header">{{ "文件传输日志" | translate }}:{{u.username}}</div>
<div class="layui-card-body">
<table class="layui-table">
<thead>
<tr>
<th>{{ "文件" | translate }}</th>
<th>{{ "远程ID" | translate }}</th>
<th>{{ "远程别名" | translate }}</th>
<th>{{ "用户ID" | translate }}</th>
<th>{{ "用户别名" | translate }}</th>
<th>{{ "用户IP" | translate }}</th>
<th>{{ "文件大小" | translate }}</th>
<th>{{ "发送/接受" | translate }}</th>
<th>{{ "记录于" | translate }}</th>
</tr>
</thead>
<tbody>
{% for one in page_obj %}
<tr>
<td>{{one.file}}</td>
<td>{{one.remote_id}} </td>
<td>{{one.remote_alias}}</td>
<td>{{one.user_id}}</td>
<td>{{one.user_alias}}</td>
<td>{{one.user_ip}}</td>
<td>{{one.filesize}}</td>
{% if one.direction == 0 %}
<td>User Received File</td>
{% else %}
<td>User Sent File</td>
{% endif %}
<td>{{one.logged_at}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="layui-col-md4 layui-col-md-offset4">
<span class="step-links">
{% if page_obj.has_previous %}
<button class="layui-btn" ><a href="?page=1">&laquo; {{ "首页" | translate }}</a></button>
<button class="layui-btn" ><a href="?page={{ page_obj.previous_page_number }}">{{ "Previous" | translate }}</a></button>
{% endif %}
{% if page_obj.paginator.num_pages > 1 %}
<span class="current">
{{ "页码 #" | translate }} {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}
</span>
{% endif %}
{% if page_obj.has_next %}
<button class="layui-btn" > <a href="?page={{ page_obj.next_page_number }}">{{ "Next" | translate }}</a></button>
<button class="layui-btn" ><a href="?page={{ page_obj.paginator.num_pages }}">{{ "Last" | translate }} &raquo;</a></button>
{% endif %}
</span>
</div>
</div>
</div>
{% endblock %}

View File

@ -23,6 +23,7 @@
<th>{{ "系统" | translate }}</th> <th>{{ "系统" | translate }}</th>
<th>{{ "CPU" | translate }}</th> <th>{{ "CPU" | translate }}</th>
<th>{{ "内存" | translate }}</th> <th>{{ "内存" | translate }}</th>
<th>{{ "IP" | translate }}</th>
<th>{{ "注册时间" | translate }}</th> <th>{{ "注册时间" | translate }}</th>
<th>{{ "更新时间" | translate }}</th> <th>{{ "更新时间" | translate }}</th>
<th>{{ "状态" | translate }}</th> <th>{{ "状态" | translate }}</th>
@ -41,6 +42,7 @@
<td>{{one.os}}</td> <td>{{one.os}}</td>
<td>{{one.cpu}}</td> <td>{{one.cpu}}</td>
<td>{{one.memory}}</td> <td>{{one.memory}}</td>
<td>{{one.ip_address}}</td>
<td>{{one.create_time}}</td> <td>{{one.create_time}}</td>
<td>{{one.update_time}}</td> <td>{{one.update_time}}</td>
<td>{{one.status}} </td> <td>{{one.status}} </td>
@ -67,10 +69,10 @@
<button class="layui-btn" ><a href="?page={{ page_obj.paginator.num_pages }}">{{ "尾页" | translate }} &raquo;</a></button> <button class="layui-btn" ><a href="?page={{ page_obj.paginator.num_pages }}">{{ "尾页" | translate }} &raquo;</a></button>
{% endif %} {% endif %}
</span> </span>
</div> </div>
{% endif %} {% endif %}
{% if u.is_admin and show_all %} {% if u.is_admin and show_all %}
<div class="layui-col-md15"> <div class="layui-col-md15">
<div class="layui-card"> <div class="layui-card">
@ -89,6 +91,7 @@
<th>{{ "系统" | translate }}</th> <th>{{ "系统" | translate }}</th>
<th>{{ "CPU" | translate }}</th> <th>{{ "CPU" | translate }}</th>
<th>{{ "内存" | translate }}</th> <th>{{ "内存" | translate }}</th>
<th>{{ "IP" | translate }}</th>
<th>{{ "注册日期" | translate }}</th> <th>{{ "注册日期" | translate }}</th>
<th>{{ "更新时间" | translate }}</th> <th>{{ "更新时间" | translate }}</th>
<th>{{ "状态" | translate }}</th> <th>{{ "状态" | translate }}</th>
@ -106,6 +109,7 @@
<td>{{one.os}} </td> <td>{{one.os}} </td>
<td>{{one.cpu}} </td> <td>{{one.cpu}} </td>
<td>{{one.memory}} </td> <td>{{one.memory}} </td>
<td>{{one.ip_address}}</td>
<td>{{one.create_time}} </td> <td>{{one.create_time}} </td>
<td>{{one.update_time}} </td> <td>{{one.update_time}} </td>
<td>{{one.status}} </td> <td>{{one.status}} </td>
@ -132,11 +136,11 @@
<button class="layui-btn" ><a href="?show_type=admin&page={{ page_obj.paginator.num_pages }}">{{ "尾页" | translate }} &raquo;</a></button> <button class="layui-btn" ><a href="?show_type=admin&page={{ page_obj.paginator.num_pages }}">{{ "尾页" | translate }} &raquo;</a></button>
{% endif %} {% endif %}
</span> </span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -21,4 +21,7 @@ urlpatterns = [
url(r'^work',views.work), # 前端 url(r'^work',views.work), # 前端
url(r'^down_peers$',views.down_peers), # 前端 url(r'^down_peers$',views.down_peers), # 前端
url(r'^share',views.share), # 前端 url(r'^share',views.share), # 前端
url(r'^conn_log',views.conn_log),
url(r'^file_log',views.file_log),
url(r'^audit',views.audit),
] ]

View File

@ -3,16 +3,26 @@ from django.http import JsonResponse
import json import json
import time import time
import datetime import datetime
import hashlib # import hashlib
import math
from django.contrib import auth from django.contrib import auth
from django.forms.models import model_to_dict # 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 from django.db.models import Q
import copy import copy
from .views_front import * from .views_front import *
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def login(request): def login(request):
result = {} result = {}
if request.method == 'GET': if request.method == 'GET':
@ -20,7 +30,7 @@ def login(request):
return JsonResponse(result) return JsonResponse(result)
data = json.loads(request.body.decode()) data = json.loads(request.body.decode())
username = data.get('username', '') username = data.get('username', '')
password = data.get('password', '') password = data.get('password', '')
rid = data.get('id', '') rid = data.get('id', '')
@ -28,19 +38,31 @@ def login(request):
autoLogin = data.get('autoLogin', True) autoLogin = data.get('autoLogin', True)
rtype = data.get('type', '') rtype = data.get('type', '')
deviceInfo = data.get('deviceInfo', '') deviceInfo = data.get('deviceInfo', '')
user = auth.authenticate(username=username,password=password) user = auth.authenticate(username=username, password=password)
if not user: if not user:
result['error'] = _('帐号或密码错误请重试多次重试后将被锁定IP') result['error'] = _('帐号或密码错误请重试多次重试后将被锁定IP')
return JsonResponse(result) return JsonResponse(result)
user.rid = rid user.rid = rid
user.uuid = uuid user.uuid = uuid
user.autoLogin = autoLogin user.autoLogin = autoLogin
user.rtype = rtype user.rtype = rtype
user.deviceInfo = json.dumps(deviceInfo) user.deviceInfo = json.dumps(deviceInfo)
user.save() user.save()
# 绑定设备 20240819
peer = RustDeskPeer.objects.filter(Q(rid=rid)).first()
if not peer:
device = RustDesDevice.objects.filter(Q(uuid=uuid)).first()
if device:
peer = RustDeskPeer()
peer.uid = user.id
peer.rid = device.rid
# peer.abid = ab.guid # v2, current version not used
peer.hostname = device.hostname
peer.username = device.username
peer.save()
token = RustDeskToken.objects.filter(Q(uid=user.id) & Q(username=user.username) & Q(rid=user.rid)).first() token = RustDeskToken.objects.filter(Q(uid=user.id) & Q(username=user.username) & Q(rid=user.rid)).first()
# 检查是否过期 # 检查是否过期
if token: if token:
now_t = datetime.datetime.now() now_t = datetime.datetime.now()
@ -48,7 +70,7 @@ def login(request):
if nums >= EFFECTIVE_SECONDS: if nums >= EFFECTIVE_SECONDS:
token.delete() token.delete()
token = None token = None
if not token: if not token:
# 获取并保存token # 获取并保存token
token = RustDeskToken( token = RustDeskToken(
@ -56,33 +78,33 @@ def login(request):
uid=user.id, uid=user.id,
uuid=user.uuid, uuid=user.uuid,
rid=user.rid, rid=user.rid,
access_token=getStrMd5(str(time.time())+salt) access_token=getStrMd5(str(time.time()) + salt)
) )
token.save() token.save()
result['access_token'] = token.access_token result['access_token'] = token.access_token
result['type'] = 'access_token' result['type'] = 'access_token'
result['user'] = {'name':user.username} result['user'] = {'name': user.username}
return JsonResponse(result) return JsonResponse(result)
def logout(request): def logout(request):
if request.method == 'GET': if request.method == 'GET':
result = {'error':_('请求方式错误!')} result = {'error': _('请求方式错误!')}
return JsonResponse(result) return JsonResponse(result)
data = json.loads(request.body.decode()) data = json.loads(request.body.decode())
rid = data.get('id', '') rid = data.get('id', '')
uuid = data.get('uuid', '') uuid = data.get('uuid', '')
user = UserProfile.objects.filter(Q(rid=rid) & Q(uuid=uuid)).first() user = UserProfile.objects.filter(Q(rid=rid) & Q(uuid=uuid)).first()
if not user: if not user:
result = {'error':_('异常请求!')} result = {'error': _('异常请求!')}
return JsonResponse(result) return JsonResponse(result)
token = RustDeskToken.objects.filter(Q(uid=user.id) & Q(rid=user.rid)).first() token = RustDeskToken.objects.filter(Q(uid=user.id) & Q(rid=user.rid)).first()
if token: if token:
token.delete() token.delete()
result = {'code':1} result = {'code': 1}
return JsonResponse(result) return JsonResponse(result)
@ -91,17 +113,17 @@ def currentUser(request):
if request.method == 'GET': if request.method == 'GET':
result['error'] = _('错误的提交方式!') result['error'] = _('错误的提交方式!')
return JsonResponse(result) return JsonResponse(result)
postdata = json.loads(request.body) # postdata = json.loads(request.body)
rid = postdata.get('id', '') # rid = postdata.get('id', '')
uuid = postdata.get('uuid', '') # uuid = postdata.get('uuid', '')
access_token = request.META.get('HTTP_AUTHORIZATION', '') access_token = request.META.get('HTTP_AUTHORIZATION', '')
access_token = access_token.split('Bearer ')[-1] access_token = access_token.split('Bearer ')[-1]
token = RustDeskToken.objects.filter(Q(access_token=access_token) ).first() token = RustDeskToken.objects.filter(Q(access_token=access_token)).first()
user = None user = None
if token: if token:
user = UserProfile.objects.filter(Q(id=token.uid)).first() user = UserProfile.objects.filter(Q(id=token.uid)).first()
if user: if user:
if token: if token:
result['access_token'] = token.access_token result['access_token'] = token.access_token
@ -115,53 +137,53 @@ def ab(request):
''' '''
access_token = request.META.get('HTTP_AUTHORIZATION', '') access_token = request.META.get('HTTP_AUTHORIZATION', '')
access_token = access_token.split('Bearer ')[-1] access_token = access_token.split('Bearer ')[-1]
token = RustDeskToken.objects.filter(Q(access_token=access_token) ).first() token = RustDeskToken.objects.filter(Q(access_token=access_token)).first()
if not token: if not token:
result = {'error':_('拉取列表错误!')} result = {'error': _('拉取列表错误!')}
return JsonResponse(result) return JsonResponse(result)
if request.method == 'GET': if request.method == 'GET':
result = {} result = {}
uid = token.uid uid = token.uid
tags = RustDeskTag.objects.filter(Q(uid=uid) ) tags = RustDeskTag.objects.filter(Q(uid=uid))
tag_names = [] tag_names = []
tag_colors = {} tag_colors = {}
if tags: if tags:
tag_names = [str(x.tag_name) for x in tags] tag_names = [str(x.tag_name) for x in tags]
tag_colors = {str(x.tag_name):int(x.tag_color) for x in tags if x.tag_color!=''} tag_colors = {str(x.tag_name): int(x.tag_color) for x in tags if x.tag_color != ''}
peers_result = [] peers_result = []
peers = RustDeskPeer.objects.filter(Q(uid=uid) ) peers = RustDeskPeer.objects.filter(Q(uid=uid))
if peers: if peers:
for peer in peers: for peer in peers:
tmp = { tmp = {
'id':peer.rid, 'id': peer.rid,
'username':peer.username, 'username': peer.username,
'hostname':peer.hostname, 'hostname': peer.hostname,
'alias':peer.alias, 'alias': peer.alias,
'platform':peer.platform, 'platform': peer.platform,
'tags':peer.tags.split(','), 'tags': peer.tags.split(','),
'hash':peer.rhash, 'hash': peer.rhash,
} }
peers_result.append(tmp) peers_result.append(tmp)
result['updated_at'] = datetime.datetime.now() result['updated_at'] = datetime.datetime.now()
result['data'] = { result['data'] = {
'tags':tag_names, 'tags': tag_names,
'peers':peers_result, 'peers': peers_result,
'tag_colors':json.dumps(tag_colors) 'tag_colors': json.dumps(tag_colors)
} }
result['data'] = json.dumps(result['data']) result['data'] = json.dumps(result['data'])
return JsonResponse(result) return JsonResponse(result)
else: else:
postdata = json.loads(request.body.decode()) postdata = json.loads(request.body.decode())
data = postdata.get('data', '') data = postdata.get('data', '')
data = {} if data=='' else json.loads(data) data = {} if data == '' else json.loads(data)
tagnames = data.get('tags', []) tagnames = data.get('tags', [])
tag_colors = data.get('tag_colors', '') tag_colors = data.get('tag_colors', '')
tag_colors = {} if tag_colors=='' else json.loads(tag_colors) tag_colors = {} if tag_colors == '' else json.loads(tag_colors)
peers = data.get('peers', []) peers = data.get('peers', [])
if tagnames: if tagnames:
# 删除旧的tag # 删除旧的tag
RustDeskTag.objects.filter(uid=token.uid).delete() RustDeskTag.objects.filter(uid=token.uid).delete()
@ -188,32 +210,34 @@ def ab(request):
platform=one['platform'], platform=one['platform'],
tags=','.join(one['tags']), tags=','.join(one['tags']),
rhash=one['hash'], rhash=one['hash'],
) )
newlist.append(peer) newlist.append(peer)
RustDeskPeer.objects.bulk_create(newlist) RustDeskPeer.objects.bulk_create(newlist)
result = { result = {
'code':102, 'code': 102,
'data':_('更新地址簿有误') 'data': _('更新地址簿有误')
} }
return JsonResponse(result) return JsonResponse(result)
def ab_get(request): def ab_get(request):
# 兼容 x86-sciter 版客户端,此版客户端通过访问 "POST /api/ab/get" 来获取地址簿 # 兼容 x86-sciter 版客户端,此版客户端通过访问 "POST /api/ab/get" 来获取地址簿
request.method = 'GET' request.method = 'GET'
return ab(request) return ab(request)
def sysinfo(request): def sysinfo(request):
# 客户端注册服务后,才会发送设备信息 # 客户端注册服务后,才会发送设备信息
result = {} result = {}
if request.method == 'GET': if request.method == 'GET':
result['error'] = _('错误的提交方式!') result['error'] = _('错误的提交方式!')
return JsonResponse(result) return JsonResponse(result)
client_ip = get_client_ip(request)
postdata = json.loads(request.body) postdata = json.loads(request.body)
device = RustDesDevice.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid']) ).first() device = RustDesDevice.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid'])).first()
if not device: if not device:
device = RustDesDevice( device = RustDesDevice(
rid=postdata['id'], rid=postdata['id'],
@ -224,38 +248,101 @@ def sysinfo(request):
username=postdata.get('username', '-'), username=postdata.get('username', '-'),
uuid=postdata['uuid'], uuid=postdata['uuid'],
version=postdata['version'], version=postdata['version'],
ip_address=client_ip
) )
device.save() device.save()
else: else:
postdata2 = copy.copy(postdata) postdata2 = copy.copy(postdata)
postdata2['rid'] = postdata2['id'] postdata2['rid'] = postdata2['id']
postdata2.pop('id') postdata2.pop('id')
RustDesDevice.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid']) ).update(**postdata2) RustDesDevice.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid'])).update(**postdata2)
result['data'] = 'ok' result['data'] = 'ok'
return JsonResponse(result) return JsonResponse(result)
def heartbeat(request): def heartbeat(request):
postdata = json.loads(request.body) postdata = json.loads(request.body)
device = RustDesDevice.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid']) ).first() device = RustDesDevice.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid'])).first()
if device: if device:
client_ip = get_client_ip(request)
device.ip_address = client_ip
device.save() device.save()
# token保活 # token保活
create_time = datetime.datetime.now() + datetime.timedelta(seconds=EFFECTIVE_SECONDS) create_time = datetime.datetime.now() + datetime.timedelta(seconds=EFFECTIVE_SECONDS)
RustDeskToken.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid']) ).update(create_time=create_time) RustDeskToken.objects.filter(Q(rid=postdata['id']) & Q(uuid=postdata['uuid'])).update(create_time=create_time)
result = {} result = {}
result['data'] = _('在线') result['data'] = _('在线')
return JsonResponse(result) return JsonResponse(result)
def users(request):
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 Exception as e:
print(postdata, e)
result = { result = {
'code':1, 'code': 1,
'data':_('好的') 'data': 'ok'
} }
return JsonResponse(result) 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 = {
'code': 1,
'data': _('好的')
}
return JsonResponse(result)
def peers(request): def peers(request):
result = { result = {
'code':1, 'code': 1,
'data':'ok' 'data': 'ok'
} }
return JsonResponse(result) return JsonResponse(result)

View File

@ -6,7 +6,7 @@ from django.http import JsonResponse
from django.db.models import Q from django.db.models import Q
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import auth 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.forms.models import model_to_dict
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.http import HttpResponse from django.http import HttpResponse
@ -28,6 +28,7 @@ from django.utils.translation import gettext as _
salt = 'xiaomo' salt = 'xiaomo'
EFFECTIVE_SECONDS = 7200 EFFECTIVE_SECONDS = 7200
def getStrMd5(s): def getStrMd5(s):
if not isinstance(s, (str,)): if not isinstance(s, (str,)):
s = str(s) s = str(s)
@ -37,6 +38,7 @@ def getStrMd5(s):
return myHash.hexdigest() return myHash.hexdigest()
def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=None): def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=None):
""" """
:params instance: 模型对象不能是queryset数据集 :params instance: 模型对象不能是queryset数据集
@ -49,21 +51,21 @@ def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=No
if not isinstance(instance, Model): if not isinstance(instance, Model):
raise Exception(_('model_to_dict接收的参数必须是模型对象')) raise Exception(_('model_to_dict接收的参数必须是模型对象'))
# 对替换数据库字段名字校验 # 对替换数据库字段名字校验
if replace and type(replace) == dict: if replace and type(replace) == dict: # noqa
for replace_field in replace.values(): for replace_field in replace.values():
if hasattr(instance, replace_field): if hasattr(instance, replace_field):
raise Exception(_(f'model_to_dict,要替换成{replace_field}字段已经存在了')) raise Exception(_(f'model_to_dict,要替换成{replace_field}字段已经存在了'))
# 对要新增的默认值进行校验 # 对要新增的默认值进行校验
if default and type(default) == dict: if default and type(default) == dict: # noqa
for default_key in default.keys(): for default_key in default.keys():
if hasattr(instance, default_key): if hasattr(instance, default_key):
raise Exception(_(f'model_to_dict,要新增默认值,但字段{default_key}已经存在了')) raise Exception(_(f'model_to_dict,要新增默认值,但字段{default_key}已经存在了')) # noqa
opts = instance._meta opts = instance._meta
data = {} data = {}
for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many): for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
# 源码下:这块代码会将时间字段剔除掉,我加上一层判断,让其不再剔除时间字段 # 源码下:这块代码会将时间字段剔除掉,我加上一层判断,让其不再剔除时间字段
if not getattr(f, 'editable', False): if not getattr(f, 'editable', False):
if type(f) == DateField or type(f) == DateTimeField: if type(f) == DateField or type(f) == DateTimeField: # noqa
pass pass
else: else:
continue continue
@ -76,22 +78,22 @@ def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=No
key = f.name key = f.name
# 获取字段对应的数据 # 获取字段对应的数据
if type(f) == DateTimeField: if type(f) == DateTimeField: # noqa
# 字段类型是DateTimeFiled 使用自己的方式操作 # 字段类型是DateTimeFiled 使用自己的方式操作
value = getattr(instance, key) value = getattr(instance, key)
value = datetime.datetime.strftime(value, '%Y-%m-%d %H:%M') value = datetime.datetime.strftime(value, '%Y-%m-%d %H:%M')
elif type(f) == DateField: elif type(f) == DateField: # noqa
# 字段类型是DateFiled 使用自己的方式操作 # 字段类型是DateFiled 使用自己的方式操作
value = getattr(instance, key) value = getattr(instance, key)
value = datetime.datetime.strftime(value, '%Y-%m-%d') value = datetime.datetime.strftime(value, '%Y-%m-%d')
elif type(f) == CharField or type(f) == TextField: elif type(f) == CharField or type(f) == TextField: # noqa
# 字符串数据是否可以进行序列化转成python结构数据 # 字符串数据是否可以进行序列化转成python结构数据
value = getattr(instance, key) value = getattr(instance, key)
try: try:
value = json.loads(value) value = json.loads(value)
except Exception as _: except Exception as _: # noqa
value = value value = value
else:#其他类型的字段 else: # 其他类型的字段
# value = getattr(instance, key) # value = getattr(instance, key)
key = f.name key = f.name
value = f.value_from_object(instance) value = f.value_from_object(instance)
@ -100,15 +102,15 @@ def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=No
if replace and key in replace.keys(): if replace and key in replace.keys():
key = replace.get(key) key = replace.get(key)
data[key] = value data[key] = value
#2、新增默认的字段数据 # 2、新增默认的字段数据
if default: if default:
data.update(default) data.update(default)
return data return data
def index(request): def index(request):
print('sdf',sys.argv) print('sdf', sys.argv)
if request.user and request.user.username!='AnonymousUser': if request.user and request.user.username != 'AnonymousUser':
return HttpResponseRedirect('/api/work') return HttpResponseRedirect('/api/work')
return HttpResponseRedirect('/api/user_action?action=login') return HttpResponseRedirect('/api/user_action?action=login')
@ -124,6 +126,7 @@ def user_action(request):
else: else:
return return
def user_login(request): def user_login(request):
if request.method == 'GET': if request.method == 'GET':
return render(request, 'login.html') return render(request, 'login.html')
@ -131,14 +134,15 @@ def user_login(request):
username = request.POST.get('account', '') username = request.POST.get('account', '')
password = request.POST.get('password', '') password = request.POST.get('password', '')
if not username or not password: if not username or not password:
return JsonResponse({'code':0, 'msg':_('出了点问题,未获取用户名或密码。')}) return JsonResponse({'code': 0, 'msg': _('出了点问题,未获取用户名或密码。')})
user = auth.authenticate(username=username,password=password) user = auth.authenticate(username=username, password=password)
if user: if user:
auth.login(request, user) auth.login(request, user)
return JsonResponse({'code':1, 'url':'/api/work'}) return JsonResponse({'code': 1, 'url': '/api/work'})
else: else:
return JsonResponse({'code':0, 'msg':_('帐号或密码错误!')}) return JsonResponse({'code': 0, 'msg': _('帐号或密码错误!')})
def user_register(request): def user_register(request):
info = '' info = ''
@ -146,8 +150,8 @@ def user_register(request):
return render(request, 'reg.html') return render(request, 'reg.html')
ALLOW_REGISTRATION = settings.ALLOW_REGISTRATION ALLOW_REGISTRATION = settings.ALLOW_REGISTRATION
result = { result = {
'code':0, 'code': 0,
'msg':'' 'msg': ''
} }
if not ALLOW_REGISTRATION: if not ALLOW_REGISTRATION:
result['msg'] = _('当前未开放注册,请联系管理员!') result['msg'] = _('当前未开放注册,请联系管理员!')
@ -161,7 +165,7 @@ def user_register(request):
result['msg'] = info result['msg'] = info
return JsonResponse(result) return JsonResponse(result)
if len(password1)<8 or len(password1)>20: if len(password1) < 8 or len(password1) > 20:
info = _('密码长度不符合要求, 应在8~20位。') info = _('密码长度不符合要求, 应在8~20位。')
result['msg'] = info result['msg'] = info
return JsonResponse(result) return JsonResponse(result)
@ -174,28 +178,30 @@ def user_register(request):
user = UserProfile( user = UserProfile(
username=username, username=username,
password=make_password(password1), password=make_password(password1),
is_admin = True if UserProfile.objects.count()==0 else False, is_admin=True if UserProfile.objects.count() == 0 else False,
is_superuser = True if UserProfile.objects.count()==0 else False, is_superuser=True if UserProfile.objects.count() == 0 else False,
is_active = True is_active=True
) )
user.save() user.save()
result['msg'] = info result['msg'] = info
result['code'] = 1 result['code'] = 1
return JsonResponse(result) return JsonResponse(result)
@login_required(login_url='/api/user_action?action=login') @login_required(login_url='/api/user_action?action=login')
def user_logout(request): def user_logout(request):
info = '' # info=''
auth.logout(request) auth.logout(request)
return HttpResponseRedirect('/api/user_action?action=login') return HttpResponseRedirect('/api/user_action?action=login')
def get_single_info(uid): def get_single_info(uid):
peers = RustDeskPeer.objects.filter(Q(uid=uid)) peers = RustDeskPeer.objects.filter(Q(uid=uid))
rids = [x.rid for x in peers] rids = [x.rid for x in peers]
peers = {x.rid:model_to_dict(x) for x in peers} peers = {x.rid: model_to_dict(x) for x in peers}
#print(peers) # print(peers)
devices = RustDesDevice.objects.filter(rid__in=rids) devices = RustDesDevice.objects.filter(rid__in=rids)
devices = {x.rid:x for x in devices} devices = {x.rid: x for x in devices}
now = datetime.datetime.now() now = datetime.datetime.now()
for rid, device in devices.items(): for rid, device in devices.items():
peers[rid]['create_time'] = device.create_time.strftime('%Y-%m-%d') peers[rid]['create_time'] = device.create_time.strftime('%Y-%m-%d')
@ -204,39 +210,45 @@ def get_single_info(uid):
peers[rid]['memory'] = device.memory peers[rid]['memory'] = device.memory
peers[rid]['cpu'] = device.cpu peers[rid]['cpu'] = device.cpu
peers[rid]['os'] = device.os peers[rid]['os'] = device.os
peers[rid]['status'] = _('在线') if (now-device.update_time).seconds <=120 else _('离线') peers[rid]['status'] = _('在线') if (now - device.update_time).seconds <= 120 else _('离线')
for rid in peers.keys(): for rid in peers.keys():
peers[rid]['has_rhash'] = _('') if len(peers[rid]['rhash'])>1 else _('') peers[rid]['has_rhash'] = _('') if len(peers[rid]['rhash']) > 1 else _('')
return [v for k, v in peers.items()]
return [v for k,v in peers.items()]
def get_all_info(): def get_all_info():
devices = RustDesDevice.objects.all() devices = RustDesDevice.objects.all()
peers = RustDeskPeer.objects.all() peers = RustDeskPeer.objects.all()
devices = {x.rid:model_to_dict2(x) for x in devices} devices = {x.rid: model_to_dict2(x) for x in devices}
now = datetime.datetime.now() now = datetime.datetime.now()
for peer in peers: for peer in peers:
user = UserProfile.objects.filter(Q(id=peer.uid)).first() user = UserProfile.objects.filter(Q(id=peer.uid)).first()
device = devices.get(peer.rid, None) device = devices.get(peer.rid, None)
if device: if device:
devices[peer.rid]['rust_user'] = user.username devices[peer.rid]['rust_user'] = user.username
for rid in devices.keys():
if not devices[rid].get('rust_user', ''):
devices[rid]['rust_user'] = _('未登录')
for k, v in devices.items(): for k, v in devices.items():
devices[k]['status'] = _('在线') if (now-datetime.datetime.strptime(v['update_time'], '%Y-%m-%d %H:%M')).seconds <=120 else _('离线') devices[k]['status'] = _('在线') if (now - datetime.datetime.strptime(v['update_time'], '%Y-%m-%d %H:%M')).seconds <= 120 else _('离线')
return [v for k,v in devices.items()] return [v for k, v in devices.items()]
@login_required(login_url='/api/user_action?action=login') @login_required(login_url='/api/user_action?action=login')
def work(request): def work(request):
username = request.user username = request.user
u = UserProfile.objects.get(username=username) u = UserProfile.objects.get(username=username)
show_type = request.GET.get('show_type', '') show_type = request.GET.get('show_type', '')
show_all = True if show_type == 'admin' and u.is_admin else False show_all = True if show_type == 'admin' and u.is_admin else False
paginator = Paginator(get_all_info(), 15) if show_type == 'admin' and u.is_admin else Paginator(get_single_info(u.id), 15) paginator = Paginator(get_all_info(), 15) if show_type == 'admin' and u.is_admin else Paginator(get_single_info(u.id), 15)
page_number = request.GET.get('page') page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
return render(request, 'show_work.html', {'u':u, 'show_all':show_all, 'page_obj':page_obj}) return render(request, 'show_work.html', {'u': u, 'show_all': show_all, 'page_obj': page_obj})
@login_required(login_url='/api/user_action?action=login') @login_required(login_url='/api/user_action?action=login')
def down_peers(request): def down_peers(request):
@ -246,7 +258,7 @@ def down_peers(request):
if not u.is_admin: if not u.is_admin:
print(u.is_admin) print(u.is_admin)
return HttpResponseRedirect('/api/work') return HttpResponseRedirect('/api/work')
all_info = get_all_info() all_info = get_all_info()
f = xlwt.Workbook(encoding='utf-8') f = xlwt.Workbook(encoding='utf-8')
sheet1 = f.add_sheet(_(u'设备信息表'), cell_overwrite_ok=True) sheet1 = f.add_sheet(_(u'设备信息表'), cell_overwrite_ok=True)
@ -257,7 +269,7 @@ def down_peers(request):
if i == 0: if i == 0:
# 写入列名 # 写入列名
sheet1.write(i, j, name) sheet1.write(i, j, name)
sheet1.write(i+1, j, one.get(name, '-')) sheet1.write(i + 1, j, one.get(name, '-'))
sio = BytesIO() sio = BytesIO()
f.save(sio) f.save(sio)
@ -266,12 +278,13 @@ def down_peers(request):
response['Content-Disposition'] = 'attachment; filename=DeviceInfo.xls' response['Content-Disposition'] = 'attachment; filename=DeviceInfo.xls'
response.write(sio.getvalue()) response.write(sio.getvalue())
return response return response
def check_sharelink_expired(sharelink): def check_sharelink_expired(sharelink):
now = datetime.datetime.now() now = datetime.datetime.now()
if sharelink.create_time > now: if sharelink.create_time > now:
return False return False
if (now - sharelink.create_time).seconds <15 * 60: if (now - sharelink.create_time).seconds < 15 * 60:
return False return False
else: else:
sharelink.is_expired = True sharelink.is_expired = True
@ -284,19 +297,18 @@ def share(request):
peers = RustDeskPeer.objects.filter(Q(uid=request.user.id)) 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)) sharelinks = ShareLink.objects.filter(Q(uid=request.user.id) & Q(is_used=False) & Q(is_expired=False))
# 省资源:处理已过期请求,不主动定时任务轮询请求,在任意地方请求时,检查是否过期,过期则保存。 # 省资源:处理已过期请求,不主动定时任务轮询请求,在任意地方请求时,检查是否过期,过期则保存。
now = datetime.datetime.now() # now = datetime.datetime.now()
for sl in sharelinks: for sl in sharelinks:
check_sharelink_expired(sl) check_sharelink_expired(sl)
sharelinks = ShareLink.objects.filter(Q(uid=request.user.id) & Q(is_used=False) & Q(is_expired=False)) 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)] 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)] 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': if request.method == 'GET':
url = request.build_absolute_uri() url = request.build_absolute_uri()
if url.endswith('share'): if url.endswith('share'):
return render(request, 'share.html', {'peers':peers, 'sharelinks':sharelinks}) return render(request, 'share.html', {'peers': peers, 'sharelinks': sharelinks})
else: else:
shash = url.split('/')[-1] shash = url.split('/')[-1]
sharelink = ShareLink.objects.filter(Q(shash=shash)) sharelink = ShareLink.objects.filter(Q(shash=shash))
@ -318,20 +330,20 @@ def share(request):
# 自己的peers若重叠需要跳过 # 自己的peers若重叠需要跳过
peers_self_ids = [x.rid for x in RustDeskPeer.objects.filter(Q(uid=request.user.id))] peers_self_ids = [x.rid for x in RustDeskPeer.objects.filter(Q(uid=request.user.id))]
peers_share = RustDeskPeer.objects.filter(Q(rid__in=peers) & Q(uid=sharelink.uid)) peers_share = RustDeskPeer.objects.filter(Q(rid__in=peers) & Q(uid=sharelink.uid))
peers_share_ids = [x.rid for x in peers_share] # peers_share_ids = [x.rid for x in peers_share]
for peer in peers_share: for peer in peers_share:
if peer.rid in peers_self_ids: if peer.rid in peers_self_ids:
continue continue
#peer = RustDeskPeer.objects.get(rid=peer.rid) # peer = RustDeskPeer.objects.get(rid=peer.rid)
peer_f = RustDeskPeer.objects.filter(Q(rid=peer.rid) & Q(uid=sharelink.uid)) peer_f = RustDeskPeer.objects.filter(Q(rid=peer.rid) & Q(uid=sharelink.uid))
if not peer_f: if not peer_f:
msg += f"{peer.rid}已存在," msg += f"{peer.rid}已存在,"
continue continue
if len(peer_f) > 1: if len(peer_f) > 1:
msg += f'{peer.rid}存在多个,已经跳过。 ' msg += f'{peer.rid}存在多个,已经跳过。 '
continue continue
peer = peer_f[0] peer = peer_f[0]
peer.id = None peer.id = None
peer.uid = request.user.id peer.uid = request.user.id
@ -342,21 +354,97 @@ def share(request):
title = _(title) title = _(title)
msg = _(msg) msg = _(msg)
return render(request, 'msg.html', {'title':msg, 'msg':msg}) return render(request, 'msg.html', {'title': msg, 'msg': msg})
else: else:
data = request.POST.get('data', '[]') data = request.POST.get('data', '[]')
data = json.loads(data) data = json.loads(data)
if not data: if not data:
return JsonResponse({'code':0, 'msg':_('数据为空。')}) return JsonResponse({'code': 0, 'msg': _('数据为空。')})
rustdesk_ids = [x['title'].split('|')[0] for x in data] rustdesk_ids = [x['title'].split('|')[0] for x in data]
rustdesk_ids = ','.join(rustdesk_ids) rustdesk_ids = ','.join(rustdesk_ids)
sharelink = ShareLink( sharelink = ShareLink(
uid=request.user.id, uid=request.user.id,
shash = getStrMd5(str(time.time())+salt), shash=getStrMd5(str(time.time()) + salt),
peers=rustdesk_ids, peers=rustdesk_ids,
) )
sharelink.save() sharelink.save()
return JsonResponse({'code':1, 'shash':sharelink.shash}) 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: # noqa
logs[k]['alias'] = _('UNKNOWN')
try:
peer = RustDeskPeer.objects.get(rid=v['from_id'])
logs[k]['from_alias'] = peer.alias
except: # noqa
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: # noqa
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: # noqa
logs[k]['remote_alias'] = _('UNKNOWN')
try:
peer_user = RustDeskPeer.objects.get(rid=v['user_id'])
logs[k]['user_alias'] = peer_user.alias
except: # noqa
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})

Binary file not shown.

Binary file not shown.

BIN
images/compose_demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

After

Width:  |  Height:  |  Size: 343 KiB

BIN
images/key_activate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-15 21:10+0800\n" "POT-Creation-Date: 2024-05-14 10:44+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -32,15 +32,15 @@ msgstr "Password Hash Value"
msgid "基本信息" msgid "基本信息"
msgstr "Basic Information" msgstr "Basic Information"
#: .\api\admin_user.py:98 #: .\api\admin_user.py:100
msgid "RustDesk自建Web" msgid "RustDesk自建Web"
msgstr "RustDesk Self-Hosted Web" msgstr "RustDesk Self-Hosted Web"
#: .\api\admin_user.py:99 #: .\api\admin_user.py:101
msgid "未定义" msgid "未定义"
msgstr "Undefined" msgstr "Undefined"
#: .\api\front_locale.py:4 .\api\templates\base.html:40 #: .\api\front_locale.py:4 .\api\templates\base.html:42
msgid "管理后台" msgid "管理后台"
msgstr "Admin Panel" msgstr "Admin Panel"
@ -54,7 +54,8 @@ msgstr "Share Machine"
#: .\api\front_locale.py:7 #: .\api\front_locale.py:7
msgid "这么简易的东西,忘记密码这功能就没必要了吧。" msgid "这么简易的东西,忘记密码这功能就没必要了吧。"
msgstr "For such a simple thing, the forgot password feature is unnecessary, right?" msgstr ""
"For such a simple thing, the forgot password feature is unnecessary, right?"
#: .\api\front_locale.py:8 #: .\api\front_locale.py:8
msgid "立即注册" msgid "立即注册"
@ -77,7 +78,8 @@ msgid ""
"2、所分享的机器被分享人享有相同的权限如果机器设置了保存密码被分享人也可" "2、所分享的机器被分享人享有相同的权限如果机器设置了保存密码被分享人也可"
"以直接连接。" "以直接连接。"
msgstr "" msgstr ""
"2. The shared machine grants the same permissions to the recipient. If the machine is set to save password, the recipient can also connect directly." "2. The shared machine grants the same permissions to the recipient. If the "
"machine is set to save password, the recipient can also connect directly."
#: .\api\front_locale.py:13 #: .\api\front_locale.py:13
msgid "导出xlsx" msgid "导出xlsx"
@ -89,7 +91,9 @@ msgstr "Generate Share Link"
#: .\api\front_locale.py:15 #: .\api\front_locale.py:15
msgid "请输入8~20位密码。可以包含字母、数字和特殊字符。" msgid "请输入8~20位密码。可以包含字母、数字和特殊字符。"
msgstr "Please enter a password of 8~20 characters. It can contain letters, numbers, and special characters." msgstr ""
"Please enter a password of 8~20 characters. It can contain letters, numbers, "
"and special characters."
#: .\api\front_locale.py:16 #: .\api\front_locale.py:16
msgid "尾页" msgid "尾页"
@ -196,8 +200,7 @@ msgid "请输入用户名"
msgstr "Please enter username" msgstr "Please enter username"
#: .\api\front_locale.py:43 #: .\api\front_locale.py:43
msgid "" msgid "1、链接有效期为15分钟切勿随意分享给他人。"
"1、链接有效期为15分钟切勿随意分享给他人。"
msgstr "" msgstr ""
"1. The link is valid for 15 minutes. Do not share it with others casually." "1. The link is valid for 15 minutes. Do not share it with others casually."
@ -217,7 +220,7 @@ msgstr "Next Page"
msgid "登录" msgid "登录"
msgstr "Login" msgstr "Login"
#: .\api\front_locale.py:48 .\api\templates\base.html:43 #: .\api\front_locale.py:48 .\api\templates\base.html:45
msgid "退出" msgid "退出"
msgstr "Logout" msgstr "Logout"
@ -227,7 +230,8 @@ msgstr "Please adjust the machines to be shared to the right"
#: .\api\front_locale.py:50 #: .\api\front_locale.py:50
msgid "成功!如需分享,请复制以下链接给其他人:<br>" msgid "成功!如需分享,请复制以下链接给其他人:<br>"
msgstr "Success! If you need to share, please copy the following link to others:<br>" msgstr ""
"Success! If you need to share, please copy the following link to others:<br>"
#: .\api\front_locale.py:51 #: .\api\front_locale.py:51
msgid "忘记密码?" msgid "忘记密码?"
@ -258,12 +262,102 @@ msgid ""
"3、为保障安全链接有效期为15分钟、链接仅有效1次。链接一旦被非分享人的登录" "3、为保障安全链接有效期为15分钟、链接仅有效1次。链接一旦被非分享人的登录"
"用户)访问,分享生效,后续访问链接失效。" "用户)访问,分享生效,后续访问链接失效。"
msgstr "" msgstr ""
"3. For security reasons, the link is valid for 15 minutes and only valid once. Once the link is accessed by a user (other than the sharing person), the sharing becomes effective, and subsequent access to the link will be invalid." "3. For security reasons, the link is valid for 15 minutes and only valid "
"once. Once the link is accessed by a user (other than the sharing person), "
"the sharing becomes effective, and subsequent access to the link will be "
"invalid."
#: .\api\front_locale.py:58 #: .\api\front_locale.py:58
msgid "系统" msgid "系统"
msgstr "System" msgstr "System"
#: .\api\front_locale.py:59
msgid "我的机器"
msgstr "My Machine"
#: .\api\front_locale.py:60
#, fuzzy
#| msgid "基本信息"
msgid "信息"
msgstr "Basic Information"
#: .\api\front_locale.py:61
msgid "远程ID"
msgstr "Remote ID"
#: .\api\front_locale.py:62
msgid "远程别名"
msgstr "Remote Alias"
#: .\api\front_locale.py:63 .\api\models_work.py:11 .\api\models_work.py:48
#: .\api\models_work.py:126
msgid "用户ID"
msgstr "User ID"
#: .\api\front_locale.py:64
msgid "用户别名"
msgstr "User Alias"
#: .\api\front_locale.py:65
msgid "用户IP"
msgstr "User IP"
#: .\api\front_locale.py:66
msgid "文件大小"
msgstr "Filesize"
#: .\api\front_locale.py:67
msgid "发送/接受"
msgstr "Sent/Received"
#: .\api\front_locale.py:68
msgid "记录于"
msgstr "Logged At"
#: .\api\front_locale.py:69
msgid "连接开始时间"
msgstr "Connection Start Time\t"
#: .\api\front_locale.py:70
msgid "连接结束时间"
msgstr "Connection End Time"
#: .\api\front_locale.py:71
msgid "时长"
msgstr "Duration"
#: .\api\front_locale.py:72 .\api\templates\base.html:40
msgid "连接日志"
msgstr "Connection Log"
#: .\api\front_locale.py:73 .\api\templates\base.html:41
msgid "文件传输日志"
msgstr "File Transfer Log"
#: .\api\front_locale.py:74
msgid "页码 #"
msgstr "Page #"
#: .\api\front_locale.py:75
msgid "下一页 #"
msgstr "Next #"
#: .\api\front_locale.py:76
msgid "上一页 #"
msgstr "Previous #"
#: .\api\front_locale.py:77
#, fuzzy
#| msgid "上一页"
msgid "第一页"
msgstr "First"
#: .\api\front_locale.py:78
#, fuzzy
#| msgid "上一页"
msgid "上页"
msgstr "Previous Page"
#: .\api\models_user.py:40 #: .\api\models_user.py:40
msgid "登录信息:" msgid "登录信息:"
msgstr "Login Information:" msgstr "Login Information:"
@ -288,10 +382,6 @@ msgstr "User List"
msgid "RustDesk ID" msgid "RustDesk ID"
msgstr "RustDesk ID" msgstr "RustDesk ID"
#: .\api\models_work.py:11 .\api\models_work.py:48 .\api\models_work.py:96
msgid "用户ID"
msgstr "User ID"
#: .\api\models_work.py:12 #: .\api\models_work.py:12
msgid "uuid" msgid "uuid"
msgstr "UUID" msgstr "UUID"
@ -364,67 +454,69 @@ msgstr "Device"
msgid "设备列表" msgid "设备列表"
msgstr "Device List" msgstr "Device List"
#: .\api\models_work.py:97 #: .\api\models_work.py:127
msgid "链接Key" msgid "链接Key"
msgstr "Link Key" msgstr "Link Key"
#: .\api\models_work.py:98 #: .\api\models_work.py:128
msgid "机器ID列表" msgid "机器ID列表"
msgstr "Machine ID List" msgstr "Machine ID List"
#: .\api\models_work.py:99 #: .\api\models_work.py:129
msgid "是否使用" msgid "是否使用"
msgstr "Is Used" msgstr "Is Used"
#: .\api\models_work.py:100 #: .\api\models_work.py:130
msgid "是否过期" msgid "是否过期"
msgstr "Is Expired" msgstr "Is Expired"
#: .\api\models_work.py:101 #: .\api\models_work.py:131
msgid "生成时间" msgid "生成时间"
msgstr "Generation Time" msgstr "Generation Time"
#: .\api\models_work.py:107 #: .\api\models_work.py:137
msgid "分享链接" msgid "分享链接"
msgstr "Share Link" msgstr "Share Link"
#: .\api\models_work.py:108 #: .\api\models_work.py:138
msgid "链接列表" msgid "链接列表"
msgstr "Link List" msgstr "Link List"
#: .\api\views_api.py:19 #: .\api\views_api.py:20
msgid "请求方式错误请使用POST方式。" msgid "请求方式错误请使用POST方式。"
msgstr "Request method error! Please use the POST method." msgstr "Request method error! Please use the POST method."
#: .\api\views_api.py:33 #: .\api\views_api.py:34
msgid "帐号或密码错误请重试多次重试后将被锁定IP" msgid "帐号或密码错误请重试多次重试后将被锁定IP"
msgstr "Account or password error! Please retry. After multiple retries, the IP will be locked!" msgstr ""
"Account or password error! Please retry. After multiple retries, the IP will "
"be locked!"
#: .\api\views_api.py:71 #: .\api\views_api.py:72
msgid "请求方式错误!" msgid "请求方式错误!"
msgstr "Request method error!" msgstr "Request method error!"
#: .\api\views_api.py:79 #: .\api\views_api.py:80
msgid "异常请求!" msgid "异常请求!"
msgstr "Abnormal request!" msgstr "Abnormal request!"
#: .\api\views_api.py:92 .\api\views_api.py:212 #: .\api\views_api.py:93 .\api\views_api.py:213
msgid "错误的提交方式!" msgid "错误的提交方式!"
msgstr "Incorrect submission method!" msgstr "Incorrect submission method!"
#: .\api\views_api.py:120 #: .\api\views_api.py:121
msgid "拉取列表错误!" msgid "拉取列表错误!"
msgstr "Error fetching list!" msgstr "Error fetching list!"
#: .\api\views_api.py:199 #: .\api\views_api.py:200
msgid "更新地址簿有误" msgid "更新地址簿有误"
msgstr "Error updating address book" msgstr "Error updating address book"
#: .\api\views_api.py:246 .\api\views_front.py:207 .\api\views_front.py:226 #: .\api\views_api.py:247 .\api\views_front.py:207 .\api\views_front.py:226
msgid "在线" msgid "在线"
msgstr "Online" msgstr "Online"
#: .\api\views_api.py:252 #: .\api\views_api.py:308
msgid "好的" msgid "好的"
msgstr "Okay" msgstr "Okay"
@ -435,12 +527,15 @@ msgstr "The parameter received by model_to_dict must be a model object"
#: .\api\views_front.py:55 #: .\api\views_front.py:55
#, python-brace-format #, python-brace-format
msgid "model_to_dict,要替换成{replace_field}字段已经存在了" msgid "model_to_dict,要替换成{replace_field}字段已经存在了"
msgstr "model_to_dict, the field to be replaced with {replace_field} already exists" msgstr ""
"model_to_dict, the field to be replaced with {replace_field} already exists"
#: .\api\views_front.py:60 #: .\api\views_front.py:60
#, python-brace-format #, python-brace-format
msgid "model_to_dict,要新增默认值,但字段{default_key}已经存在了" msgid "model_to_dict,要新增默认值,但字段{default_key}已经存在了"
msgstr "model_to_dict, to add default values, but the field {default_key} already exists" msgstr ""
"model_to_dict, to add default values, but the field {default_key} already "
"exists"
#: .\api\views_front.py:134 #: .\api\views_front.py:134
msgid "出了点问题,未获取用户名或密码。" msgid "出了点问题,未获取用户名或密码。"
@ -460,7 +555,9 @@ msgstr "Username must be at least 3 characters"
#: .\api\views_front.py:165 #: .\api\views_front.py:165
msgid "密码长度不符合要求, 应在8~20位。" msgid "密码长度不符合要求, 应在8~20位。"
msgstr "Password length does not meet requirements, should be between 8~20 characters." msgstr ""
"Password length does not meet requirements, should be between 8~20 "
"characters."
#: .\api\views_front.py:171 #: .\api\views_front.py:171
msgid "用户名已存在。" msgid "用户名已存在。"
@ -485,3 +582,11 @@ msgstr "Device Information Table"
#: .\api\views_front.py:351 #: .\api\views_front.py:351
msgid "数据为空。" msgid "数据为空。"
msgstr "Data is empty." msgstr "Data is empty."
#: .\api\views_front.py:373 .\api\views_front.py:378 .\api\views_front.py:409
#: .\api\views_front.py:414
msgid "UNKNOWN"
msgstr ""
#~ msgid "未知"
#~ msgstr "UNKNOWN"

View File

@ -1,2 +1,3 @@
django django
xlwt xlwt
mysqlclient

View File

@ -30,20 +30,23 @@ ID_SERVER = os.environ.get("ID_SERVER", '')
DEBUG = os.environ.get("DEBUG", False) DEBUG = os.environ.get("DEBUG", False)
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
AUTH_USER_MODEL = 'api.UserProfile' #AppName.自定义user AUTH_USER_MODEL = 'api.UserProfile' # AppName.自定义user
ALLOW_REGISTRATION = os.environ.get("ALLOW_REGISTRATION", "True") == "True" # 是否允许注册, True为允许False为不允许 ALLOW_REGISTRATION = os.environ.get("ALLOW_REGISTRATION", "True") # 是否允许注册, True为允许False为不允许
ALLOW_REGISTRATION = True if ALLOW_REGISTRATION.lower() == 'true' else False
#==========数据库配置 开始=====================
# ==========数据库配置 开始=====================
DATABASE_TYPE = os.environ.get("DATABASE_TYPE", 'SQLITE') DATABASE_TYPE = os.environ.get("DATABASE_TYPE", 'SQLITE')
MYSQL_DBNAME = os.environ.get("MYSQL_DBNAME", '-') MYSQL_DBNAME = os.environ.get("MYSQL_DBNAME", '-')
MYSQL_HOST = os.environ.get("MYSQL_HOST", '127.0.0.1') MYSQL_HOST = os.environ.get("MYSQL_HOST", '127.0.0.1')
MYSQL_USER = os.environ.get("MYSQL_USER", '-') MYSQL_USER = os.environ.get("MYSQL_USER", '-')
MYSQL_PASSWORD = os.environ.get("MYSQL_PASSWORD", '-') MYSQL_PASSWORD = os.environ.get("MYSQL_PASSWORD", '-')
MYSQL_PORT = os.environ.get("MYSQL_PORT", '3306') MYSQL_PORT = os.environ.get("MYSQL_PORT", '3306')
#==========数据库配置 结束===================== # ==========数据库配置 结束=====================
LANGUAGE_CODE = os.environ.get("MYSQL_PORT", 'zh-hans') LANGUAGE_CODE = os.environ.get("LANGUAGE_CODE", 'zh-hans')
# #LANGUAGE_CODE = os.environ.get("LANGUAGE_CODE", 'en')
# Application definition # Application definition
@ -63,7 +66,7 @@ MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
#'django.middleware.csrf.CsrfViewMiddleware', # 取消post的验证。 # 'django.middleware.csrf.CsrfViewMiddleware', # 取消post的验证。
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
@ -101,19 +104,19 @@ DATABASES = {
'NAME': BASE_DIR / 'db/db.sqlite3', 'NAME': BASE_DIR / 'db/db.sqlite3',
} }
} }
if DATABASE_TYPE == 'MYSQL' and MYSQL_DBNAME!='-' and MYSQL_USER!= '-' and MYSQL_PASSWORD!='-': if DATABASE_TYPE == 'MYSQL' and MYSQL_DBNAME != '-' and MYSQL_USER != '-' and MYSQL_PASSWORD != '-':
# 简单通过数据库名、账密信息过滤下防止用户未配置mysql却使用mysql # 简单通过数据库名、账密信息过滤下防止用户未配置mysql却使用mysql
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': MYSQL_DBNAME, # 数据库名 'NAME': MYSQL_DBNAME, # 数据库名
'HOST': MYSQL_HOST, # 数据库服务器IP 'HOST': MYSQL_HOST, # 数据库服务器IP
'USER': MYSQL_USER, # 数据库用户名 'USER': MYSQL_USER, # 数据库用户名
'PASSWORD': MYSQL_PASSWORD, # 数据库密码 'PASSWORD': MYSQL_PASSWORD, # 数据库密码
'PORT': MYSQL_PORT, # 端口 'PORT': MYSQL_PORT, # 端口
'OPTIONS': {'charset': 'utf8'}, 'OPTIONS': {'charset': 'utf8'},
}
} }
}
# Password validation # Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
@ -137,7 +140,7 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/ # https://docs.djangoproject.com/en/3.1/topics/i18n/
#LANGUAGE_CODE = 'zh-hans' # LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai' TIME_ZONE = 'Asia/Shanghai'
@ -145,7 +148,7 @@ USE_I18N = True
USE_L10N = True USE_L10N = True
#USE_TZ = True # USE_TZ = True
USE_TZ = False USE_TZ = False
@ -158,16 +161,14 @@ if DEBUG:
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
else: else:
STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 新增 STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 新增
LANGUAGES = ( LANGUAGES = (
('zh-hans', '中文简体'), ('zh-hans', '中文简体'),
('en', 'English'), ('en', 'English'),
) )
LOCALE_PATHS = ( LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'), os.path.join(BASE_DIR, 'locale'),
) )

File diff suppressed because one or more lines are too long

1
version.py Normal file
View File

@ -0,0 +1 @@
APP_VERSION = 'v3.3.16'

View File

@ -1,4 +0,0 @@
python manage.py makemessages -l zh_Hans -i xlwt -i django
python manage.py makemessages -l en -i xlwt -i django
python manage.py compilemessages --pythonpath E:\python_workspace\rustdesk-server-api-python\rustdesk_server_api_github\locale\zh_Hans\LC_MESSAGES