mirror of
https://github.com/ngfchl/ptools
synced 2023-07-10 13:41:22 +08:00
初步实现数据图表,待完善
This commit is contained in:
@@ -21,5 +21,8 @@ urlpatterns = [
|
||||
path(r'control_torrent', views.control_torrent, name='control_torrent'),
|
||||
path(r'torrent_info_page', views.render_torrents_page, name='torrent_info_page'),
|
||||
path(r'get_torrent_info_list', views.get_torrent_info_list, name='get_torrent_info_list'),
|
||||
path(r'site_status_api', views.site_status_api, name='site_status_api'),
|
||||
path(r'site_status', views.site_status, name='site_status'),
|
||||
path(r'downloading_status', views.downloading_status, name='downloading_status'),
|
||||
path(r'do_sql', views.do_sql, name='do_sql'),
|
||||
]
|
||||
|
||||
118
auto_pt/views.py
118
auto_pt/views.py
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
@@ -8,9 +9,11 @@ from datetime import datetime, timedelta
|
||||
import docker
|
||||
import git
|
||||
import qbittorrentapi
|
||||
import transmission_rpc
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
from pt_site.UtilityTool import FileSizeConvert
|
||||
from pt_site.models import SiteStatus, MySite, Site, Downloader, TorrentInfo
|
||||
from pt_site.views import scheduler, pt_spider
|
||||
from ptools.base import CommonResponse, StatusCodeEnum, DownloaderCategory
|
||||
@@ -100,14 +103,51 @@ def get_downloader(id):
|
||||
"""根据id获取下载实例"""
|
||||
logger.info('当前下载器id:{}'.format(id))
|
||||
downloader = Downloader.objects.filter(id=id).first()
|
||||
qb_client = qbittorrentapi.Client(
|
||||
host=downloader.host,
|
||||
port=downloader.port,
|
||||
username=downloader.username,
|
||||
password=downloader.password,
|
||||
SIMPLE_RESPONSES=True
|
||||
)
|
||||
return qb_client
|
||||
if downloader.category == DownloaderCategory.qBittorrent:
|
||||
client = qbittorrentapi.Client(
|
||||
host=downloader.host,
|
||||
port=downloader.port,
|
||||
username=downloader.username,
|
||||
password=downloader.password,
|
||||
SIMPLE_RESPONSES=True
|
||||
)
|
||||
if downloader.category == DownloaderCategory.Transmission:
|
||||
client = transmission_rpc.Client(
|
||||
host=downloader.host, port=downloader.port,
|
||||
username=downloader.username, password=downloader.password
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
def downloading_status(request):
|
||||
qb_list = Downloader.objects.filter(category=DownloaderCategory.qBittorrent)
|
||||
tr_list = Downloader.objects.filter(category=DownloaderCategory.Transmission)
|
||||
tr_info_list = []
|
||||
for downloader in tr_list:
|
||||
client = transmission_rpc.Client(
|
||||
host=downloader.host, port=downloader.port,
|
||||
username=downloader.username, password=downloader.password
|
||||
)
|
||||
session = transmission_rpc.session.Session(client=client)
|
||||
print(type(session))
|
||||
# print(client.get_torrents())
|
||||
session_list = client.get_session()
|
||||
session = {item: value for item, value in session_list.items()}
|
||||
tr_info = {
|
||||
# 'torrents': client.get_torrents(),
|
||||
'free_space': client.free_space('/downloads'),
|
||||
# 'session': session.values(),
|
||||
'protocol_version': client.protocol_version,
|
||||
'rpc_version': client.rpc_version,
|
||||
'session_id': client.session_id,
|
||||
# 'session_stats': client.session_stats(),
|
||||
'arguments': client.torrent_get_arguments,
|
||||
|
||||
}
|
||||
tr_info_list.append(tr_info)
|
||||
return JsonResponse(CommonResponse.success(data={
|
||||
'tr_info_list': tr_info_list
|
||||
}).to_dict(), safe=False)
|
||||
|
||||
|
||||
def get_trackers(request):
|
||||
@@ -520,3 +560,65 @@ def download_tasks():
|
||||
"""
|
||||
downloader_list = Downloader.objects.all()
|
||||
pass
|
||||
|
||||
|
||||
def site_status_api(request):
|
||||
my_site_list = MySite.objects.all()
|
||||
uploaded = 0
|
||||
downloaded = 0
|
||||
seeding = 0
|
||||
seeding_size = 0
|
||||
status_list = []
|
||||
now = datetime.now()
|
||||
for my_site in my_site_list:
|
||||
site_info = my_site.sitestatus_set.order_by('-pk').first()
|
||||
downloaded += site_info.downloaded
|
||||
uploaded += site_info.uploaded
|
||||
seeding += my_site.seed
|
||||
seeding_size += site_info.seed_vol
|
||||
weeks = (now - my_site.time_join).days // 7
|
||||
days = (now - my_site.time_join).days % 7
|
||||
site_info = {
|
||||
'name': my_site.site.name,
|
||||
'class': my_site.my_level,
|
||||
'invite': my_site.invitation,
|
||||
'sp_hour': my_site.sp_hour,
|
||||
'seeding': my_site.seed,
|
||||
'time_join': f'{weeks}周 {days}天',
|
||||
'hr': my_site.my_hr,
|
||||
'mail': my_site.mail,
|
||||
'sp': site_info.my_sp,
|
||||
'bonus': site_info.my_bonus,
|
||||
# 'uploaded': FileSizeConvert.parse_2_file_size(site_info.uploaded),
|
||||
# 'downloaded': FileSizeConvert.parse_2_file_size(site_info.downloaded),
|
||||
# 'seeding_size': FileSizeConvert.parse_2_file_size(site_info.seed_vol),
|
||||
'uploaded': site_info.uploaded,
|
||||
'downloaded': site_info.downloaded,
|
||||
'seeding_size': site_info.seed_vol,
|
||||
}
|
||||
status_list.append(site_info)
|
||||
# 按上传量排序
|
||||
# status_list.sort(key=lambda x: x['uploaded'], reverse=False)
|
||||
# sorted(status_list, key=lambda x: x['uploaded'])
|
||||
# 随机乱序
|
||||
random.shuffle(status_list)
|
||||
total_data = {
|
||||
# 'uploaded': FileSizeConvert.parse_2_file_size(uploaded),
|
||||
# 'downloaded': FileSizeConvert.parse_2_file_size(downloaded),
|
||||
# 'seeding_size': FileSizeConvert.parse_2_file_size(seeding_size),
|
||||
'uploaded': uploaded,
|
||||
'downloaded': downloaded,
|
||||
'seeding_size': seeding_size,
|
||||
'seeding': seeding,
|
||||
'ratio': round(uploaded / downloaded, 3),
|
||||
}
|
||||
# return render(request, 'auto_pt/status.html')
|
||||
return JsonResponse(data=CommonResponse.success(
|
||||
data={
|
||||
'total_data': total_data, 'status_list': status_list
|
||||
}
|
||||
).to_dict(), safe=False)
|
||||
|
||||
|
||||
def site_status(request):
|
||||
return render(request, 'auto_pt/status.html')
|
||||
|
||||
@@ -254,9 +254,13 @@ SIMPLEUI_CONFIG = {
|
||||
'icon': 'fa fa-user',
|
||||
'url': '/tasks/page_downloading'
|
||||
}, {
|
||||
'name': '查询种子',
|
||||
'name': '我的站点',
|
||||
'icon': 'fa fa-user',
|
||||
'url': '/downloader/ptspider/index'
|
||||
'url': '/tasks/site_status'
|
||||
}, {
|
||||
'name': '下载器展示',
|
||||
'icon': 'fa fa-user',
|
||||
'url': '/tasks/downloading_status'
|
||||
}]
|
||||
}, {
|
||||
'app': 'update',
|
||||
|
||||
27
static/js/echarts-component.js
Normal file
27
static/js/echarts-component.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 封装echarts,可以在vue中使用
|
||||
*/
|
||||
|
||||
Vue.component('charts', {
|
||||
props: ['option', 'style'], data: function () {
|
||||
return {}
|
||||
}, mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
var el = this.$el;
|
||||
var chart = echarts.init(el, 'dark');
|
||||
chart.setOption(this.option);
|
||||
this.chart = chart
|
||||
})
|
||||
}, watch: {
|
||||
obj: {
|
||||
option(newValue, oldValue) {
|
||||
// option发生变化时自动重新渲染
|
||||
this.chart.setOption(newValue)
|
||||
}, // immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
|
||||
}, template: '<div :style="style">{{option}}</div>'
|
||||
})
|
||||
|
||||
|
||||
45
static/js/echarts.min.js
vendored
Normal file
45
static/js/echarts.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
370
templates/auto_pt/status.html
Normal file
370
templates/auto_pt/status.html
Normal file
@@ -0,0 +1,370 @@
|
||||
{% load static %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
{% include 'admin/includes/css-part.html' %}
|
||||
<style>
|
||||
body {
|
||||
background-color: #2f4155;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
background-color: #130f2c;
|
||||
color: #ffefef;
|
||||
}
|
||||
|
||||
.chart-button {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status">
|
||||
<el-card class="box-card" shadow="hover">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>数据总量</span>
|
||||
<span>
|
||||
<el-button type="warning" size="small" class="chart-button"
|
||||
@click="setTree">矩形树图</el-button>
|
||||
<el-button type="success" size="small" class="chart-button"
|
||||
@click="setBar">柱状图</el-button>
|
||||
<el-button type="primary" size="small" class="chart-button"
|
||||
@click="setPie">饼图</el-button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<charts ref="charts" style="height: 700px;" :option="option"></charts>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
{% include 'admin/includes/js-part.html' %}
|
||||
<script src="{% static 'admin/simpleui-x/js/axios.min.js' %}"></script>
|
||||
<script src="{% static 'js/echarts.min.js' %}"></script>
|
||||
<script src="{% static 'js/echarts-component.js' %}"></script>
|
||||
<script>
|
||||
function renderSize(value) {
|
||||
if (null == value || value == '') {
|
||||
return 0;
|
||||
}
|
||||
var unitArr = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
var index = 0;
|
||||
var srcsize = parseFloat(value);
|
||||
index = Math.floor(Math.log(srcsize) / Math.log(1024));
|
||||
var size = srcsize / Math.pow(1024,
|
||||
index);
|
||||
size = size.toFixed(2);//保留的小数位数
|
||||
return size + ' ' + unitArr[index];
|
||||
}
|
||||
|
||||
const vm = new Vue({
|
||||
el: '#status',
|
||||
data() {
|
||||
return {
|
||||
chart: {},
|
||||
ptData: {},
|
||||
option: {},
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.chart = this.$refs.charts.chart
|
||||
this.getData()
|
||||
},
|
||||
watch: {
|
||||
obj: {
|
||||
option(newValue, oldValue) {
|
||||
// option发生变化时自动重新渲染
|
||||
this.chart.setOption(newValue)
|
||||
},
|
||||
// immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setPie() {
|
||||
let uploadedList = []
|
||||
let ptData = this.ptData
|
||||
ptData.status_list.forEach((site, index) => {
|
||||
uploadedList.push({
|
||||
'value': site.uploaded,
|
||||
'path': 'uploaded/' + site.name,
|
||||
'name': site.name
|
||||
})
|
||||
//downloadedList.push({
|
||||
// 'value': site.downloaded,
|
||||
// 'path': 'downloaded/' + site.name,
|
||||
// 'name': site.name
|
||||
//})
|
||||
})
|
||||
let option = {
|
||||
tooltip: {
|
||||
show: true,
|
||||
//formatter: function (params) {
|
||||
// return params.name + '\t' + renderSize(params.data.value)
|
||||
//}
|
||||
valueFormatter: function (value) {
|
||||
return renderSize(value)
|
||||
}
|
||||
},
|
||||
color: [
|
||||
'#f66c73',
|
||||
'#f68645',
|
||||
'#7af6ad',
|
||||
'#f4d55f',
|
||||
'#488ff6',
|
||||
'#0fba8d',
|
||||
'#8a47dc',
|
||||
'#d677f6',
|
||||
],
|
||||
legend: {
|
||||
show: false,
|
||||
{#type: 'scroll',#}
|
||||
{#top: 'bottom',#}
|
||||
{#right: '0',#}
|
||||
{#orient: 'vertical',#}
|
||||
},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
mark: {show: true},
|
||||
{#dataView: {show: true, readOnly: false},#}
|
||||
{#restore: {show: true},#}
|
||||
saveAsImage: {show: true}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'PT数据展示',
|
||||
type: 'pie',
|
||||
bottom: '5%',
|
||||
radius: '55%',
|
||||
{#visualDimension: 1,#}
|
||||
center: ['50%', '50%'],
|
||||
roseType: '',
|
||||
label: {
|
||||
show: true,
|
||||
formatter: function (params) {
|
||||
return params.name + ' \t ' + renderSize(params.data.value)
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 7
|
||||
},
|
||||
data: [
|
||||
{value: 40, name: 'rose 1'},
|
||||
{value: 38, name: 'rose 2'},
|
||||
{value: 32, name: 'rose 3'},
|
||||
{value: 30, name: 'rose 4'},
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
option.series[0].data = uploadedList
|
||||
this.option = option
|
||||
this.$refs.charts.chart.clear()
|
||||
this.$refs.charts.chart.setOption(this.option)
|
||||
},
|
||||
setTree() {
|
||||
let ptData = this.ptData
|
||||
let uploadedList = []
|
||||
let downloadedList = []
|
||||
ptData.status_list.forEach((site, index) => {
|
||||
uploadedList.push({
|
||||
'value': site.uploaded,
|
||||
'path': 'uploaded/' + site.name,
|
||||
'name': `${site.name}\t${renderSize(site.uploaded)}`
|
||||
})
|
||||
downloadedList.push({
|
||||
'value': site.downloaded,
|
||||
'path': 'downloaded/' + site.name,
|
||||
'name': `${site.name}\t${renderSize(site.downloaded)}`
|
||||
})
|
||||
})
|
||||
let option = {
|
||||
backgroundColor: '#130f2c',
|
||||
tooltip: {
|
||||
show: true,
|
||||
//formatter: function (params) {
|
||||
// return params.name + '\t' + renderSize(params.data.value)
|
||||
//}
|
||||
valueFormatter: function (value) {
|
||||
return renderSize(value)
|
||||
}
|
||||
},
|
||||
color: [
|
||||
'#f66c73',
|
||||
'#f68645',
|
||||
'#7af6ad',
|
||||
'#f4d55f',
|
||||
'#488ff6',
|
||||
'#0fba8d',
|
||||
'#8a47dc',
|
||||
'#d677f6',
|
||||
'#130f2c',
|
||||
'#2f4155',
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'treemap',
|
||||
name: '数据汇总',
|
||||
colorMappingBy: 'index',
|
||||
{#colorSaturation: [0.9, 0.9],#}
|
||||
label: {
|
||||
show: true,
|
||||
formatter: function (params) {
|
||||
return params.name + '\t' + renderSize(params.data.value)
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: '上传',
|
||||
value: 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
/**
|
||||
console.log(this.treeOption.series[0].data)
|
||||
this.treeOption.series[0].data.push({
|
||||
name: '总下载',
|
||||
value: ptData.total_data.downloaded,
|
||||
})
|
||||
let treeData = this.treeOption.series[0].data[0]
|
||||
treeData.value = ptData.total_data.uploaded
|
||||
treeData.children = dataList
|
||||
**/
|
||||
option.series[0].data.push({
|
||||
name: `总上传\t${renderSize(ptData.total_data.uploaded)}`,
|
||||
value: ptData.total_data.uploaded,
|
||||
path: "uploaded",
|
||||
children: uploadedList
|
||||
}, {
|
||||
name: `总下载\t${renderSize(ptData.total_data.downloaded)}`,
|
||||
value: ptData.total_data.downloaded,
|
||||
path: "downloaded",
|
||||
children: downloadedList
|
||||
})
|
||||
{#this.treeOption.series[0].data.push()#}
|
||||
{#this.$refs.charts.chart.setOption(treeOption)#}
|
||||
this.option = option
|
||||
this.$refs.charts.chart.clear()
|
||||
this.$refs.charts.chart.setOption(this.option)
|
||||
|
||||
},
|
||||
setBar() {
|
||||
let option = {
|
||||
title: {
|
||||
text: 'PT数据聚合图'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
show: true,
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
valueFormatter: function (value) {
|
||||
return renderSize(value)
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: function (value, index) {
|
||||
return renderSize(value);
|
||||
}
|
||||
},
|
||||
label: {
|
||||
formatter: function (value, index) {
|
||||
return renderSize(value);
|
||||
}
|
||||
},
|
||||
boundaryGap: [0, 0.01]
|
||||
},
|
||||
series: []
|
||||
};
|
||||
let ptData = this.ptData
|
||||
let uploadedList = []
|
||||
let downloadedList = []
|
||||
let siteList = []
|
||||
{#uploadedList.push(ptData.total_data.uploaded)#}
|
||||
{#downloadedList.push(ptData.total_data.downloaded)#}
|
||||
{#siteList.push('总量')#}
|
||||
ptData.status_list.forEach((site, index) => {
|
||||
downloadedList.push(site.downloaded)
|
||||
uploadedList.push(site.uploaded)
|
||||
siteList.push(site.name)
|
||||
})
|
||||
let uploaded = {
|
||||
name: '上传量',
|
||||
type: 'bar',
|
||||
stack: 'Ad',
|
||||
data: uploadedList
|
||||
}
|
||||
let downloaded = {
|
||||
name: '下载量',
|
||||
type: 'bar',
|
||||
stack: 'Ad',
|
||||
data: downloadedList
|
||||
}
|
||||
option.series.push(downloaded, uploaded)
|
||||
option.xAxis.data = siteList
|
||||
this.option = option
|
||||
this.$refs.charts.chart.setOption(option)
|
||||
},
|
||||
getData() {
|
||||
axios.get(
|
||||
"{% url "site_status_api" %}"
|
||||
).then(res => {
|
||||
console.log('获取数据列表成功', res.data)
|
||||
if (res.data.code === 0) {
|
||||
console.log(res.data.data)
|
||||
{#this.ptData = res.data.data#}
|
||||
this.ptData = res.data.data
|
||||
this.setBar()
|
||||
} else {
|
||||
this.loading = false
|
||||
this.$message({
|
||||
type: 'warning',
|
||||
message: '获取数据列表失败!'
|
||||
});
|
||||
}
|
||||
}).catch(res => {
|
||||
console.log('获取数据列表失败', res)
|
||||
this.$message({
|
||||
type: 'warning',
|
||||
message: '获取数据列表失败!' + res
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
10
templates/auto_pt/userdata.html
Normal file
10
templates/auto_pt/userdata.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
10
templates/auto_pt/userinfo.html
Normal file
10
templates/auto_pt/userinfo.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>用户数据</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user