第一版v1.0,支持签到功能

This commit is contained in:
sftang
2022-03-08 15:31:53 +08:00
commit cd8d7144d9
602 changed files with 78930 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/logs/*
/db/*
/backups/*
*.pyc

77
Dockerfile Normal file
View File

@@ -0,0 +1,77 @@
# pull official base image
FROM python:3.9-slim-buster
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ARG DEBIAN_FRONTEND=noninteractive
# create the appropriate directories
ENV APP_HOME=/home/data/www
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# install dependencies
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
sed -i 's/security.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \
apt-get update && \
apt-get install -y apt-utils gnupg libgl1-mesa-glx libglib2.0
RUN pip install --upgrade pip -i https://pypi.mirrors.ustc.edu.cn/simple/
RUN pip install --no-cache-dir --upgrade pip -i https://pypi.mirrors.ustc.edu.cn/simple/
#RUN pip install --no-cache-dir --upgrade setuptools -i https://pypi.mirrors.ustc.edu.cn/simple/
COPY requirements.txt .
#将需要的包体全部打包成直接安装的包whl
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.mirrors.ustc.edu.cn/simple/
#安装nginx
#RUN apt install curl gnupg2 ca-certificates lsb-release -y
#RUN curl -o /tmp/nginx_signing.key https://nginx.org/keys/nginx_signing.key && \
COPY nginx_signing.key /tmp
RUN cat /tmp/nginx_signing.key | apt-key add -
RUN echo "deb http://nginx.org/packages/debian buster nginx" > /etc/apt/sources.list.d/nginx.list && \
apt update && \
#apt install nginx supervisor -y && \
apt install nginx -y && \
rm -f /etc/nginx/conf.d/default.conf && \
ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log
#RUN pip install --no-cache-dir supervisor -i https://pypi.mirrors.ustc.edu.cn/simple/
COPY conf/nginx.conf /etc/nginx/nginx.conf
COPY conf/vhost.conf /etc/nginx/conf.d/vhost.conf
COPY conf/supervisord.conf /etc/supervisord.conf
COPY start.sh /usr/local/bin/start.sh
RUN chmod +x /usr/local/bin/start.sh
RUN apt-get purge gnupg -y && \
apt-get autoremove -y && \
apt-get clean
# copy project
COPY . $APP_HOME
RUN ln -s /home/data/www/db /db && \
ln -s /home/data/www/logs /logs && \
ln -s /home/data/www/backups /backups && \
rm -rf conf && \
rm -rf docker-compose.yml && \
rm -rf Dockerfile && \
rm -rf README.md && \
rm -rf requirements.txt && \
rm -rf db/* logs/* backups/* \
rm -rf .git*
EXPOSE 80
#apt-get安装路径
#CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
#pip安装路径
#CMD ["/usr/local/bin/supervisord", "-c", "/etc/supervisord.conf"]
CMD ["/usr/local/bin/start.sh"]

60
Dockerfile.alpine Normal file
View File

@@ -0,0 +1,60 @@
# pull official base image
FROM python:3.9-alpine
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# create the appropriate directories
ENV APP_HOME=/home/data/www
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && apk update
RUN apk add --update-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && apk del tzdata && \
rm -rf /var/cache/apk/*
#apt-get install -y apt-utils gnupg libgl1-mesa-glx libglib2.0
RUN apk add --no-cache --virtual .build-deps \
ca-certificates gcc linux-headers musl-dev \
libffi-dev jpeg-dev zlib-dev
RUN pip install --upgrade pip -i https://pypi.mirrors.ustc.edu.cn/simple/
RUN pip install --no-cache-dir --upgrade pip -i https://pypi.mirrors.ustc.edu.cn/simple/
#RUN pip install --no-cache-dir --upgrade setuptools -i https://pypi.mirrors.ustc.edu.cn/simple/
COPY requirements.txt .
#将需要的包体全部打包成直接安装的包whl
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.mirrors.ustc.edu.cn/simple/
COPY start.sh /usr/local/bin/start.sh
RUN chmod +x /usr/local/bin/start.sh
RUN apk del .build-deps
RUN rm -rf /var/cache/apk/*
# copy project
COPY . $APP_HOME
RUN ln -s /home/data/www/db /db && \
ln -s /home/data/www/logs /logs && \
ln -s /home/data/www/backups /backups && \
rm -rf conf && \
rm -rf docker-compose.yml && \
rm -rf Dockerfile && \
rm -rf README.md && \
rm -rf requirements.txt && \
rm -rf db/* logs/* backups/* \
rm -rf .git*
EXPOSE 80
#apt-get安装路径
#CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
#pip安装路径
#CMD ["/usr/local/bin/supervisord", "-c", "/etc/supervisord.conf"]
CMD ["/usr/local/bin/start.sh"]

98
README.md Normal file
View File

@@ -0,0 +1,98 @@
使用Djano编写PT助手,代码完全开放,欢迎有兴趣的小伙伴进行修改发布。禁止用于商业用途。
由于涉及ocr本地验证,因此docker镜像相对比较大。
# v1.0 说明
1、支持签到功能
2、支持配置导出导入
3、支持签到信息发送IYUU,Telegram,邮箱
## 支持站点
站点 | 签到支持|
--- |--- |
红豆饭(hdfans) | ✔
壹PT吧(1ptba) | ✔
铂金学院(ptchina) | ✔
瓷器(hdchina) | ✔
芒果(3wmg) | ✔
碟粉(discfan) | ✔
杜比(hddolby) | ✔
阿童木(hdatmos) | ✔
聆音(soulvoice) | ✔
铂金家(pthome) | ✔
高清时间(hdtime) | ✔
高清地带(hdzone) | ✔
海棠(htpt) | ✔
观众(audiences) | ✔
老师(nicept) | ✔
家园(hdhome) | ✔
时间(pttime) | ✔
柠檬(lemonhd) | ✔
我堡(ourbits) | ✔
猫站(pterclub) | ✔
城市(hdcity) | ✔
高清视界(hdarea) | ✔
白兔(hares) | ✔
A-SOUL(asf) | ✔
听听歌(totheglory) | ✔
52PT(52pt) | ✔
海胆(haidan) | ✔
皇后(open) | ✔
天空(hdsky) | ✔
备胎(beitai) | ✘
马杀鸡(msg) | ✘
奥申(oshen) | ✘
爱薇(avgv) | ✘
吐鲁番(eastgame) | ✘
小蚂蚁(hdmayi) | ✘
朋友(keepfrds) | ✘
北洋园(tjupt) | ✘
他吹吹风(et8)) | ✘
海带(hd) | ✘
海豹(greatposterwall) | ✘
## 暂时不支持站点
站点 |
--- |
馒头 |
## 已知问题站点
站点 |--- |
--- |--- |
学校(btschool) | ✔
## 截图
1. IYUU
![](https://gcore.jsdelivr.net/gh/xingsu1021/pthelper@master/static/screenshot/iyuu.png)
2. Telegram
![](https://gcore.jsdelivr.net/gh/xingsu1021/pthelper@master/static/screenshot/tg.png)
3. 邮箱
![](https://gcore.jsdelivr.net/gh/xingsu1021/pthelper@master/static/screenshot/mail.png)
4. 补签
![](https://gcore.jsdelivr.net/gh/xingsu1021/pthelper@master/static/screenshot/buqian.png)
## 感谢
带带弟弟OCR通用验证码识别:https://github.com/sml2h3/ddddocr
爱语飞飞:https://iyuu.cn
https://github.com/ledccn/IYUUPlus
PTPP:https://github.com/ronggang/PT-Plugin-Plus
PTPP增强版:白大版PTPP@菩提蛋(没找到仓库:()
以下忽略,只做个人记录
-------------------------------------------------------------
# 本地启动测试命令
python manage.py runserver --settings=pthelper.local_settings
# 编译镜像
docker build -t pthelper:v1.0 .
# py文件编译pyc
python -O -m compileall -b .
# 上传镜像
docker commit 4f7d7c5a8b58 xingsu1021/pthelper:latest
docker push xingsu1021/pthelper

0
apps/__init__.py Normal file
View File

0
apps/common/__init__.py Normal file
View File

3
apps/common/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/common/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class CommonConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'common'

88
apps/common/exportdata.py Normal file
View File

@@ -0,0 +1,88 @@
import xlwt
import io
import json
from django.http import HttpResponse
#导出Excel表格
def get_excel_stream(file):
# StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
excel_stream = io.BytesIO()
# 这点很重要,传给save函数的不是保存文件名,而是一个BytesIO流(在内存中读写)
file.save(excel_stream)
# getvalue方法用于获得写入后的byte将结果返回给re
res = excel_stream.getvalue()
excel_stream.close()
return res
def export_excel(data, name,header):
# 打开一个Excel工作簿
file = xlwt.Workbook()
# 新建一个sheet,如果对一个单元格重复操作,会引发异常,所以加上参数cell_overwrite_ok=True
table = file.add_sheet(name, cell_overwrite_ok=True)
if data is None:
return file
# 表示行
l = 0
n = len(header)
# 将data中的数据依次写入到table中
for line in data:
for i in range(n):
table.write(l, i, line[header[i]])
l += 1
# 获取将数据保存到流
res=get_excel_stream(file)
# 返回一个response响应
response = HttpResponse(content_type='application/vnd.ms-excel')
from urllib import parse
response['Content-Disposition'] = 'attachment;filename=' + parse.quote(excel_name) + '.xls'
# 将文件流写入到response返回
response.write(res)
return response
#导出json文件
def get_stream(item):
# 开始这里我用ByteIO流总是出错,但是后来参考廖雪峰网站用StringIO就没问题
file = io.StringIO()
data=json.dumps(data)
file.write(data)
res=file.getvalue()
file.close()
return res
def export_json(item):
try:
json_stream=get_stream(item)
response = HttpResponse(content_type='application/json')
from urllib import parse
response['Content-Disposition'] = 'attachment;filename=' + parse.quote(file_name) + '.json'
response.write(json_stream)
return response
except Exception as e:
logging.error(e)
print(e)
#导出压缩包
def write_zip(e_data, j_data, export_name):
try:
# 保存到本地文件
# 返回文件名,注意此时保存的方法和前面导出保存的json、excel文件区别
j_name = write_json(j_data, export_name[1])
e_name = write_excel(e_data, export_name[1])
# 本地文件写入zip,重命名,然后删除本地临时文件
z_name='export.zip'
z_file = zipfile.ZipFile(z_name, 'w')
z_file.write(j_name)
z_file.write(e_name)
os.remove(j_name)
os.remove(e_name)
z_file.close()
# 再次读取zip文件,将文件流返回,但是此时打开方式要以二进制方式打开
z_file = open(z_name, 'rb')
data = z_file.read()
z_file.close()
os.remove(z_file.name)
response = HttpResponse(data, content_type='application/zip')
from urllib import parse
response['Content-Disposition'] = 'attachment;filename=' + parse.quote(z_name)
return response
except Exception as e:
logging.error(e)
print(e)

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.2 on 2022-03-06 15:50
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='BaiDuOcr',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('app_id', models.CharField(max_length=50, unique=True, verbose_name='APP_ID')),
('app_key', models.CharField(max_length=50, unique=True, verbose_name='API_KEY')),
('secret_key', models.CharField(max_length=50, unique=True, verbose_name='SECRET_KEY')),
],
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.0.2 on 2022-03-06 17:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('common', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='BackupInfo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(db_index=True, max_length=50, verbose_name='文件名')),
('url', models.CharField(max_length=200, verbose_name='备份下载地址')),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
],
),
]

View File

@@ -0,0 +1,16 @@
# Generated by Django 4.0.2 on 2022-03-07 19:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('common', '0002_backupinfo'),
]
operations = [
migrations.DeleteModel(
name='BaiDuOcr',
),
]

View File

18
apps/common/models.py Normal file
View File

@@ -0,0 +1,18 @@
from django.db import models
# Create your models here.
#class BaiDuOcr(models.Model):
#"""
#百度OCR信息
#"""
#app_id = models.CharField('APP_ID', max_length=50, unique=True)
#app_key = models.CharField('API_KEY', max_length=50, unique=True)
#secret_key = models.CharField('SECRET_KEY', max_length=50, unique=True)
class BackupInfo(models.Model):
"""
备份信息
"""
name = models.CharField('文件名', max_length=50,db_index = True)
url = models.CharField('备份下载地址', max_length=200,)
created_at = models.DateTimeField('创建时间', auto_now_add=True,db_index = True)

1161
apps/common/sites_sign.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<!-- 表单开始 -->
<form class="layui-form" id="backuploadForm" lay-filter="backuploadForm" method="POST">{% csrf_token %}
<input name="id" type="hidden" value="{{id}}"/>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-filter="backupSubmit" lay-submit>&emsp;备份&emsp;</button>
<button class="layui-btn layui-btn-normal" lay-filter="loadSubmit" lay-submit>&emsp;恢复&emsp;</button>
</div>
</div>
</form>
<!-- //表单结束 -->
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">说明</div>
<div class="layui-card-body">
<blockquote class="layui-elem-quote layui-quote-nm">备份恢复数据
</blockquote>
</div>
</div>
</div>
<!-- js部分 -->
<script>
layui.use(['layer', 'form', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
form.on('submit(backupSubmit)', function (data) {
layer.load(2);
admin.req('common/backup', {'action':'backup'}, function (data) {
layer.closeAll('loading');
if (data.code == 1) {
layer.msg(data.msg, {icon: 1});
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#backuploadForm').attr('method'));
return false;
});
// form.on('submit(backuploadcrtestSubmit)', function (data) {
// layer.load(2);
// admin.req('common/backuploadcrtest', data.field, function (data) {
// layer.closeAll('loading');
// if (data.code == 1) {
// layer.msg(data.msg, {icon: 1});
// } else {
// layer.msg(data.msg, {icon: 2});
// }
// }, $('#backuploadcrForm').attr('method'));
// return false;
// });
});
</script>

View File

@@ -0,0 +1,134 @@
<style>
#baiduocrForm {
max-width: 700px;
}
#baiduocrForm .layui-form-item {
margin-bottom: 25px;
}
#img {
width: 25px;
height: 15px;
position: absolute;
right: 5px;
margin-top: 12px;
top: 1px;
text-align: center;
}
</style>
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<!-- 表单开始 -->
<form class="layui-form" id="baiduocrForm" lay-filter="baiduocrForm" method="POST">{% csrf_token %}
<input name="id" type="hidden" value="{{id}}"/>
<div class="layui-form-item">
<label class="layui-form-label">APP_ID</label>
<div class="layui-input-block">
<input name="app_id" class="layui-input"
lay-verify="required" required value="{{app_id}}"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">API_KEY</label>
<div class="layui-input-block">
<input name="app_key" class="layui-input"
lay-verify="required" required value="{{app_key}}"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">SECRET_KEY</label>
<div class="layui-input-block">
<input type="password" name="secret_key" id="secret_key" class="layui-input" lay-verify="required" required value="{{secret_key}}"/>
<!--在输入框后接img标签-->
<img id="img" onclick="hideShowPsw()"
src="/static/assets/images/icon-visible.png">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-filter="baiduocrSubmit" lay-submit>&emsp;提交&emsp;</button>
<!--
<button class="layui-btn layui-btn-normal" lay-filter="baiduocrtestSubmit" lay-submit>&emsp;测试&emsp;</button>
-->
</div>
</div>
</form>
<!-- //表单结束 -->
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">说明</div>
<div class="layui-card-body">
<blockquote class="layui-elem-quote layui-quote-nm">申请百度云的免费图片和文字识别,用于验证码识别,天空、皇后必须
</blockquote>
</div>
</div>
</div>
<!-- js部分 -->
<script>
layui.use(['layer', 'form', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
form.on('submit(baiduocrSubmit)', function (data) {
layer.load(2);
admin.req('common/baiduocr', data.field, function (data) {
layer.closeAll('loading');
if (data.code == 1) {
layer.msg(data.msg, {icon: 1});
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#baiduocrForm').attr('method'));
return false;
});
// form.on('submit(baiduocrtestSubmit)', function (data) {
// layer.load(2);
// admin.req('common/baiduocrtest', data.field, function (data) {
// layer.closeAll('loading');
// if (data.code == 1) {
// layer.msg(data.msg, {icon: 1});
// } else {
// layer.msg(data.msg, {icon: 2});
// }
// }, $('#baiduocrForm').attr('method'));
// return false;
// });
});
var demoImg = document.getElementById("img");
var PWD = document.getElementById("secret_key");
function hideShowPsw() {
if (PWD.type == "password") {
PWD.type = "text";
demoImg.src = "/static/assets/images/icon-invisible.png"; //图片路径(闭眼图片)
} else {
PWD.type = "password";
demoImg.src = "/static/assets/images/icon-visible.png"; // 图片路径(睁眼图片)
}
}
</script>

3
apps/common/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,19 @@
# coding:utf-8
from django.urls import path
from .. import views, views_request
app_name = 'common'
urlpatterns = [
# Resource asset path
#百度ocr
#path('baiduocr', views.BaiDuOcrView.as_view()),
#备份恢复
path('backupload', views.BackupLoadView.as_view()),
path('backup', views_request.backup),
#补签
path('signagain', views_request.signAgain),
]

670
apps/common/utils.py Normal file
View File

@@ -0,0 +1,670 @@
# -*- coding: utf-8 -*-
#
from django.core.files.storage import FileSystemStorage
from django.conf import settings
from django.utils.encoding import filepath_to_uri
from collections import OrderedDict
import os
import sys
import logging
import datetime
import time
import hashlib
from email.utils import formatdate
import calendar
import threading
import uuid
import requests
import simplejson as json
import ftplib
import socket
from urllib.parse import urlparse
import pytz
from collections import OrderedDict
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import telegram
from urllib import parse
#from PIL import Image
#from io import BytesIO
logger = logging.getLogger('django')
def download_file(url, fname):
"""
下载文件
"""
download_path = os.path.join(settings.BASE_DIR, 'downloads')
if not os.path.exists(download_path):
os.makedirs(download_path)
download_file = os.path.join(download_path, fname)
data = requests.get(url, stream=True)
try:
data.raise_for_status()
with open(download_file, "wb") as updatefile:
for chunk in data.iter_content(chunk_size=1024):
if chunk:
updatefile.write(chunk)
return True, download_file
except Exception as exc:
print('There was a problem: %s' % (exc))
return False, None
def check_filename(name, extname = ['rpm','tar','tar.gz','gz'], is_url=True):
"""
检查文件是否是需要的扩展名
返回: True/False, 文件名
"""
if is_url:
#解析url
parse_url = urlparse(name)
url_path=parse_url.path
pkg_name = (os.path.basename(url_path))
#file_name, extension_name = os.path.splitext(pkg_name)
extension_name = os.path.splitext(pkg_name)[-1][1:]
else:
pkg_name = (os.path.basename(name))
extension_name = os.path.splitext(pkg_name)[-1][1:]
if extension_name in extname:
return True, pkg_name
else:
return False, pkg_name
def get_logger(name=None):
return logging.getLogger('%s' % name)
def get_clientip(request):
if 'HTTP_X_FORWARDED_FOR' in request.META:
clientip = request.META['HTTP_X_FORWARDED_FOR']
else:
clientip = request.META['REMOTE_ADDR']
return {'clientip': clientip, 'user': request.user}
def timesince(dt, since='', default="just now"):
"""
Returns string representing "time since" e.g.
3 days, 5 hours.
"""
if since == '':
since = datetime.datetime.utcnow()
if since == None:
return default
diff = since - dt
periods = (
(diff.days / 365, "year", "years"),
(diff.days / 30, "month", "months"),
(diff.days / 7, "week", "weeks"),
(diff.days, "day", "days"),
(diff.seconds / 3600, "hour", "hours"),
(diff.seconds / 60, "minute", "minutes"),
(diff.seconds, "second", "seconds"),
)
for period, singular, plural in periods:
if period:
return "%d %s" % (period, singular if period == 1 else plural)
return default
_STRPTIME_LOCK = threading.Lock()
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
def now_date():
"""当前时间"""
return datetime.datetime.now()
def date_to_unixtime(datetime):
"""将datetime转换成unixtime"""
return time.mktime(datetime.timetuple())
def to_unixtime_old(time_string, format_string):
time_string = time_string.decode("ascii")
with _STRPTIME_LOCK:
return int(calendar.timegm(time.strptime(time_string, format_string)))
def to_unixtime(time_string, format_string):
"""
时间字符串转成unix时间戳
"""
return int(calendar.timegm(time.strptime(time_string, format_string)))
def http_date(timeval=None):
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
但不能使用strftime,因为strftime的结果是和locale相关的。
"""
return formatdate(timeval, usegmt=True)
def http_to_unixtime(time_string):
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
"""
return to_unixtime(time_string, _GMT_FORMAT)
def cst_to_unixtime(time_string):
"""
2018-10-17 12:39:30 CST+0800
"""
_CST_FORMAT = "%Y-%m-%d %H:%M:%S CST%z"
return to_unixtime(time_string, _CST_FORMAT)
def iso8601_to_unixtime(time_string):
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
return to_unixtime(time_string, _ISO8601_FORMAT)
def str2datetime(st, s_format='%Y-%m-%d %H:%M:%S'):
"""字符串转成datetime
CST_FORMAT = "%Y-%m-%d %H:%M:%S CST%z"
"""
return datetime.datetime.strptime(st, s_format)
def str2utcdatetime(st,from_format='%Y-%m-%d %H:%M:%S', utc_format='%Y-%m-%dT%H:%M:%SZ',tz=True):
"""字符串时间转UTC时间(-8:00)"""
local_st = datetime.datetime.strptime(st, from_format)
time_struct = time.mktime(local_st.timetuple())
utc_st = datetime.datetime.utcfromtimestamp(time_struct)
if tz:
return utc_st.strftime(utc_format)
else:
return utc_st
def datetime2utc(local_st):
"""本地时间转UTC时间(-8:00)"""
time_struct = time.mktime(local_st.timetuple())
utc_st = datetime.datetime.utcfromtimestamp(time_struct)
return utc_st
def utc2datetime(utc_st):
"""UTC时间转本地时间(+8:00)"""
now_stamp = time.time()
local_time = datetime.datetime.fromtimestamp(now_stamp)
utc_time = datetime.datetime.utcfromtimestamp(now_stamp)
offset = local_time - utc_time
local_st = utc_st + offset
return local_st
def utc_to_local_unixtime(utc_time_str, utc_format='%Y-%m-%dT%H:%M:%SZ'):
"""
utc字符转转换成unix时间戳
'2019-01-16T11:30:00Z'
'2019-01-16 11:30:00+00:00', utc_format='%Y-%m-%d %H:%M:%S+00:00'
"""
local_tz = pytz.timezone('Asia/Shanghai')
local_format = "%Y-%m-%d %H:%M:%S"
utc_dt = datetime.datetime.strptime(utc_time_str, utc_format)
local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
time_str = local_dt.strftime(local_format)
return int(time.mktime(time.strptime(time_str, local_format)))
def capacity_convert(size, expect='auto', rate=1000):
"""
:param size: '100MB', '1G'
:param expect: 'K, M, G, T
:param rate: Default 1000, may be 1024
:return:
"""
rate_mapping = (
('K', rate),
('KB', rate),
('M', rate**2),
('MB', rate**2),
('G', rate**3),
('GB', rate**3),
('T', rate**4),
('TB', rate**4),
)
rate_mapping = OrderedDict(rate_mapping)
std_size = 0 # To KB
for unit in rate_mapping:
if size.endswith(unit):
try:
std_size = float(size.strip(unit).strip()) * rate_mapping[unit]
except ValueError:
pass
if expect == 'auto':
for unit, rate_ in rate_mapping.items():
if rate > std_size/rate_ > 1:
expect = unit
break
expect_size = std_size / rate_mapping[expect]
return expect_size, expect
def filesizeformat(value, baseMB=False):
"""Format the value like a 'human-readable' file size (i.e. 13 KB,
4.1 MB, 102 bytes, etc). Per default decimal prefixes are used (mega,
giga etc.), if the second parameter is set to `True` the binary
prefixes are (mebi, gibi).
"""
try:
bytes = float(value)
except:
return 0
if baseMB is True:
bytes = bytes * 1024 * 1024
base = 1024
if bytes == 0:
return '0'
ret = '0'
if bytes < base:
ret = '%d Bytes' % (bytes)
elif bytes < base * base:
ret = '%d KB' % (bytes / base)
elif bytes < base * base * base:
ret = '%d MB' % (bytes / (base * base))
elif bytes < base * base * base * base:
if bytes % (base * base * base) == 0:
ret = '%d GB' % (bytes / (base * base * base))
else:
ret = "%d MB" % (bytes / (base * base))
else:
ret = '%.1f TB' % (bytes / (base * base * base * base))
return ret
def sum_capacity(cap_list):
total = 0
for cap in cap_list:
size, _ = capacity_convert(cap, expect='K')
total += size
total = '{} K'.format(total)
return capacity_convert(total, expect='auto')
def str_to_md5(strs):
m=hashlib.md5()
m.update(strs.encode("utf8"))
return m.hexdigest()
def int_random():
"""使用uuid生产随机数,返回128位整数"""
u = uuid.uuid1()
return u.int
def hex_random():
"""使用uuid生产随机数,返回32位十六进制"""
u = uuid.uuid1()
return u.hex
def iso8601_to_time(time_string):
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
return time_string.strftime('%Y-%m-%d %H:%M:%S')
def str_to_datetime(string):
"""字符串 2017-10-21 00:00:00 格式化为datetime"""
return datetime.datetime.strptime(string,"%Y-%m-%d %H:%M:%S")
def sumplus(*args):
"""求和"""
r = 0.0
for n in args:
r += float(n)
return "%.2f" % r
def sumplusInt(*args):
"""求和"""
r = 0
for n in args:
r += int(n)
return r
class ImageStorage(FileSystemStorage):
"""
图片上传
"""
def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL):
#初始化
super(ImageStorage, self).__init__(location, base_url)
#重写 _save方法
def _save(self, name, content):
#文件扩展名
ext = os.path.splitext(name)[1]
#文件目录
d = os.path.dirname(name)
#定义文件名,年月日时分秒随机数
year = time.strftime('%Y')
month = time.strftime('%m')
fn = time.strftime('%Y%m%d%H%M%S')
fn = fn + str(hex_random())
#重写合成文件名
name = os.path.join('images', year,month, d, fn + ext)
#调用父类方法
return super(ImageStorage, self)._save(name, content)
def htmlfilesave(name, content, gameid):
"""
保存文件,返回url地址
"""
#文件扩展名
ext = os.path.splitext(name)[1]
#定义文件名,年月日时分秒随机数
year = time.strftime('%Y')
month = time.strftime('%m')
fn = time.strftime('%Y%m%d%H%M%S')
fn = fn + str(hex_random())
#重写合成文件名
name = os.path.join('html', year,month, gameid, fn + ext)
#完整的文件路径
full_name = os.path.join(settings.MEDIA_ROOT, name)
#得到url
url = filepath_to_uri(os.path.join(settings.MEDIA_URL, name))
directory = os.path.dirname(full_name)
if not os.path.exists(directory):
try:
os.makedirs(directory)
except FileNotFoundError:
pass
if not os.path.isdir(directory):
raise IOError("%s exists and is not a directory." % directory)
#写入文件
#open(full_name,'w', encoding='utf8').writelines(content.replace(settings.HTML_HREF_REPLACE_KEY, app_url))
open(full_name,'w', encoding='utf8').writelines(content)
return url
def writefile(content, apk_url, ext='html'):
"""
写文件,返回文件路径
"""
ext = '.' + ext
fn = time.strftime('%Y%m%d%H%M%S')
fn = fn + str(hex_random())
#重写合成文件名
name = os.path.join('cdnfile', fn + ext)
#完整的文件路径
full_name = os.path.join(settings.MEDIA_ROOT, name)
directory = os.path.dirname(full_name)
if not os.path.exists(directory):
os.makedirs(directory)
#写入文件
open(full_name,'w', encoding='utf8').writelines(content.replace(settings.HTML_HREF_REPLACE_KEY, apk_url))
return full_name
def ftpupload(host, port, username, password, upload_dir, upload_file, rmfile=False):
"""上传文件到ftp"""
timeout = 10
ftp = ftplib.FTP()
filename = os.path.basename(upload_file)
try:
#连接ftp
ftp.connect(host=host, port=port, timeout=timeout)
except (socket.error, socket.gaierror):
return False, "连接FTP失败 %s" % host
try:
#登录
ftp.login(username, password)
except ftplib.error_perm:
return False, "ftp用户名或密码错误"
try:
#切换到操作目录
ftp.cwd(upload_dir)
except:
#切换目录失败,目录不存在
#创建目录
ftp.mkd(upload_dir)
#切换目录
ftp.cwd(upload_dir)
fd=open(upload_file,'rb')
#上传文件
ftp.storbinary('STOR %s' % filename, fd)
fd.close()
ftp.quit()
#删除文件
if rmfile:
os.remove(upload_file)
#返回写入的ftp地址
url = filepath_to_uri(os.path.join('/', upload_dir, filename))
return True,url
class ExcelStorage(FileSystemStorage):
"""
excel文件上传
"""
def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL):
#初始化
super(ExcelStorage, self).__init__(location, base_url)
#重写 _save方法
def _save(self, name, content):
#文件扩展名
ext = os.path.splitext(name)[1]
#文件目录
d = os.path.dirname(name)
#定义文件名,年月日时分秒随机数
year = time.strftime('%Y')
month = time.strftime('%m')
fn = time.strftime('%Y%m%d%H%M%S')
#fn = fn + str(hex_random())
#重写合成文件名
name = os.path.join('excel', year,month, d, fn + ext)
#调用父类方法
return super(ExcelStorage, self)._save(name, content)
def send_email(mail_type,smtp_user,smtp_password,receiver_users = [],send_data = [] ,isTest=False):
"""
发送邮件
"""
now = datetime.datetime.now()
time = now.strftime("%Y-%m-%d %H:%M:%S")
if isTest:
content = "<h1>测试</h1>"
else:
content = '''
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<style>
.p1{text-indent: 40px;}
.p2{text-indent: 2em;}
</style>
</head>
<body>
<p class="p2">%s</p>
%s
</body>
</html>
''' % (time,"<br>".join(send_data))
title = "PT助手"
#receiver = ["249545020@qq.com"]
receiver = receiver_users
msg = ''
msg = MIMEText(content, 'html', 'utf-8')
# 设置发送人
msg['From'] = Header(smtp_user)
# 设置接收人
msg['To'] = Header(str(";".join(receiver)))
# 设置邮件标题
msg['Subject'] = Header(title)
try:
if mail_type == "qq":
smtp_client = smtplib.SMTP_SSL("smtp.qq.com", 465)
elif mail_type == "163":
smtp_client = smtplib.SMTP_SSL("smtp.163.com", 465)
elif mail_type == "sina":
smtp_client = smtplib.SMTP_SSL("smtp.sina.com", 465)
else:
return False,"未知邮件类型"
smtp_client.login(smtp_user, smtp_password)
smtp_client.sendmail(smtp_user, receiver, msg.as_string())
smtp_client.quit()
except smtplib.SMTPException as e:
logging.error(str(e))
return False,str(e)
return True,"发送成功"
def send_telegram(tg_chat_id,tg_token,send_data = [] ,isTest=False):
bot = telegram.Bot(token = tg_token)
now = datetime.datetime.now()
time = now.strftime("%Y-%m-%d %H:%M:%S")
try:
if isTest:
bot.send_message(chat_id=tg_chat_id,
text='<b>%s</b><i><b>[测试]</b></i>' % time,
parse_mode=telegram.ParseMode.HTML)
else:
bot.send_message(chat_id=tg_chat_id,
text='<b>%s</b><i><b>\r\n%s</b></i>' % (time,"\r\n".join(send_data)),
parse_mode=telegram.ParseMode.HTML)
except:
ex_type, ex_val, ex_stack = sys.exc_info()
logging.error(str(ex_val))
if 'Chat not found' in str(ex_val):
msg = "发送测试失败,未找到频道ID"
elif 'Unauthorized' in str(ex_val):
msg = "发送测试失败,令牌配置错误"
else:
msg = "发送测试失败,请联系管理员"
return False, msg
return True,"发送成功"
def send_iyuu(iyuu_key,send_data = [] ,isTest=False):
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36'
#针对不同通知类型进行处理
now = datetime.datetime.now()
time = now.strftime("%Y-%m-%d %H:%M:%S")
headers = {
'user_agent': user_agent,
'Content-type': 'application/x-www-form-urlencoded'
}
now_time = parse.quote('<center><b><font color="#55a7e3">') + parse.unquote(time) + parse.quote('</font></b></center><br>')
api = 'https://iyuu.cn/' + iyuu_key + '.send'
if isTest:
send_txts = parse.quote('<center><b><font color="#4CAF50">[测试]</font></b></center><br>')
sen_url = api + '?text='+ parse.quote('测试提示') + '&desp=' + now_time + send_txts
else:
send_txts = parse.quote('<br>'.join(send_data))
sen_url = api + '?text='+ parse.quote('签到提示') + '&desp=' + now_time + send_txts
try:
response = requests.get(sen_url, headers=headers ,verify=False)
response_msg = json.loads(response.text)
if response.status_code == 200 and response_msg['errcode'] == 0:
msg = "发送成功"
else:
msg = "发送测试失败,%s" % response_msg['errmsg']
return False, msg
except:
logging.error("连接IYUU失败")
return False, "连接IYUU失败"
return True,"发送成功"
def getSiteUrl(url):
"""
获取官网url
"""
u = urlparse(url)
site_url = u.scheme + "://" + u.netloc
return site_url
def parseUrl(url):
"""
拆解url
#scheme='https', netloc='tr.ikaixin.win:10443', path='/', params='', query='', fragment=''
"""
data = {}
u = urlparse(url)
# http,https
data['protocol'] = u.scheme
data['url'] = u.netloc
data['host'] = u.netloc.split(":")[0]
data['port'] = u.port
data['path'] = u.path
data['params'] = u.params
data['query'] = u.query
return data
#def deal_img(img_name):
#'''图片进行预处理,并返回bytes类型数据'''
#img = Image.open(img_name)
#buf = BytesIO()
## 将图片转化为灰度图像
#image = img.convert('L')
## 设置默认的阈值(可以根据阈值得到更加清晰的验证码图)
#threshold = 128
#table = []
#for i in range(256):
#if i < threshold:
#table.append(0)
#else:
#table.append(1)
## 图片的像素点什么的
#image = image.point(table,'1')
#image.save(buf,'png')
#return buf.getvalue()
def cookie_parse(cookie_str):
cookie_dict = {}
cookies = cookie_str.split(";")
for cookie in cookies:
cookie = cookie.split("=")
cookie_dict[cookie[0]] = cookie[1]
return cookie_dict

91
apps/common/views.py Normal file
View File

@@ -0,0 +1,91 @@
from django.shortcuts import render
from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
# Create your views here.
#------------------------------
#class BaiDuOcrView(LoginRequiredMixin,TemplateView):
#"""
#百度OCR配置
#"""
#template_name = 'common/baidu_ocr.html'
##显示添加模板
#def get(self, request, *args, **kwargs):
#"""
#得到
#"""
#return super(BaiDuOcrView, self).get(request, *args, **kwargs)
##显示编辑模板
#def get_context_data(self, **kwargs):
##获取记录
#orm_count = BaiDuOcr.objects.count()
#if orm_count == 0:
#context = {}
#else:
##获取一条数据
#ormdata = BaiDuOcr.objects.first()
#context = {
#'id': ormdata.id,
#'app_id': ormdata.app_id,
#'app_key': ormdata.app_key,
#'secret_key': ormdata.secret_key,
#}
#kwargs.update(context)
#return super(BaiDuOcrView, self).get_context_data(**kwargs)
##数据提交接收方法
#def post(self, request, *args, **kwargs):
#"""
#数据提交
#"""
#_id = request.POST.get('id','')
#app_id = request.POST.get("app_id").strip()
#app_key = request.POST.get("app_key").strip()
#secret_key = request.POST.get("secret_key").strip()
#if _id == '':
#ormdata = BaiDuOcr.objects.create(app_id=app_id,
#app_key=app_key,
#secret_key=secret_key,
#)
#else:
#ormdata = BaiDuOcr.objects.get(id=_id)
#ormdata.app_id = app_id
#ormdata.app_key = app_key
#ormdata.secret_key = secret_key
#ormdata.save()
#response_data={"code":1,"msg":"添加成功"}
#return JsonResponse(response_data)
class BackupLoadView(LoginRequiredMixin,TemplateView):
"""
备份恢复
"""
template_name = 'common/backup_load.html'
#显示添加模板
def get(self, request, *args, **kwargs):
"""
得到
"""
return super(BackupLoadView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
}
kwargs.update(context)
return super(BackupLoadView, self).get_context_data(**kwargs)

View File

@@ -0,0 +1,147 @@
from django.shortcuts import render
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.core.management import call_command
import logging
import tempfile
import os
import shutil
from sites.models import SiteInfo,SiteConfig
from cron.models import Log
from .sites_sign import hdchina, general,noSign,keepfrds,tjupt,pterclub,hdarea,hdcity,btschool,hares,hd,ttg,pt52,greatposterwall,hdsky,opencd
#==================
@login_required
def backup(request):
"""
备份数据
"""
logger = logging.getLogger('django')
if request.method == "POST":
action = request.POST.get('action','')
backup_file = os.path.join(settings.BACKUP_DIR,'pthelper.json')
logger.info("Starting backup process.")
with tempfile.TemporaryDirectory() as d:
dump_path = os.path.join(d, 'dump.json')
logger.info("Starting data dump...")
#call_command('dumpdata', '--exclude','cron.Log',natural_foreign=True, output=dump_path)
call_command('dumpdata', exclude=['cron.Log'], natural_foreign=True, output=dump_path)
logger.info("Data dumped.")
logger.info("Copying BACKUP_DIR to %s...", backup_file)
#shutil.copytree(settings.MEDIA_ROOT, os.path.join(d, 'media'))
shutil.copyfile(dump_path, backup_file)
logger.info('Copy done.')
#backup_path = os.path.join(settings.BACKUP_DIR, datetime.date.today().strftime("%Y-%m-%d.zip"))
#with zipfile.ZipFile(backup_path, mode='w') as backup_zip:
#for root, dirs, files in os.walk(d):
#for file in files:
#filepath = os.path.join(root, file)
#logger.info("Compressing {}...".format(filepath))
#backup_zip.write(filepath,
#arcname=os.path.relpath(filepath, d))
#logger.info("{} created.".format(backup_path))
response_data={"code":0,"msg":"ok" }
return JsonResponse(response_data)
#==================
@login_required
def load(request):
"""
数据恢复
"""
logger = logging.getLogger('django')
if request.method == "POST":
action = request.POST.get('action','')
backup_file = os.path.join(settings.BACKUP_DIR,'pthelper.json')
logger.info("Starting load process.")
call_command('loaddata', 'test_dump.json')
#==================
@login_required
def signAgain(request):
"""
补签
"""
if request.method == "POST":
action = request.POST.get('action')
name = request.POST.get('name')
_id = request.POST.get('id','')
if action != 'again' or _id == "":
response_data={"code":0,"msg":"未知操作" }
return JsonResponse(response_data)
#获取配置的站点信息
site_info = SiteInfo.objects.get(siteconfig_name=name)
site_name = site_info.siteconfig_name
site_cookie = site_info.cookie
#获取站点配置信息
site_config = SiteConfig.objects.get(name=site_name)
site_url = site_config.index_url
site_name_cn = site_config.name_cn
if site_name == 'hdchina':
flag, data = hdchina(site_name, site_name_cn, site_url, site_cookie)
elif site_name in ['hdfans','1ptba','ptchina','3wmg','discfan','hddolby','hdatmos','soulvoice',
'pthome','hdtime','hdzone','htpt','audiences','nicept','hdhome','pttime',
'lemonhd','ourbits','asf']:
flag, data = general(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'pterclub':
flag, data = pterclub(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hdarea':
flag, data = hdarea(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hdcity':
flag, data = hdcity(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'btschool':
flag, data = btschool(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hares':
flag, data = hares(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'totheglory':
flag, data = ttg(site_name, site_name_cn, site_url, site_cookie)
elif site_name == '52pt':
flag, data = pt52(site_name, site_name_cn, site_url, site_cookie)
elif site_name in ['beitai','msg','hdmayi','oshen','avgv','eastgame']:
flag, data = noSign(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'keepfrds':
flag, data = keepfrds(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'tjupt':
flag, data = tjupt(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hd':
flag, data = hd(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'greatposterwall':
flag, data = greatposterwall(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'open':
flag, data = opencd(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hdsky':
flag, data = hdsky(site_name, site_name_cn, site_url, site_cookie)
else:
flag, data = (False,'%s 未匹配站点' % site_name)
#补签成功,刷新状态
if flag:
Log.objects.filter(id=_id).update(status=flag)
print(site_name,data)
if flag:
response_data={"code":1,"msg":data }
else:
response_data={"code":0,"msg":data }
return JsonResponse(response_data)

0
apps/cron/__init__.py Normal file
View File

3
apps/cron/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/cron/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class CronConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'cron'

121
apps/cron/crontabs.py Normal file
View File

@@ -0,0 +1,121 @@
# coding=utf-8
import requests
import datetime
from urllib import parse
import os
import logging
from bs4 import BeautifulSoup
from common.utils import send_email,send_telegram,send_iyuu
from common.sites_sign import hdchina, general,noSign,keepfrds,tjupt,pterclub,hdarea,hdcity,btschool,hares,hd,ttg,pt52,greatposterwall,hdsky,opencd,haidan
from sites.models import SiteConfig, SiteInfo
from .models import Job, Log
from notify.models import NotifyConfig
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36'
def sign(crontab_id):
"""
执行签到
参数:
crontab_id:执行任务的ID
"""
logger = logging.getLogger('sign')
#保存最后发送的结果
send_data = []
site_count = SiteInfo.objects.count()
if site_count == 0:
send_data.append('未配置任何站点')
else:
#获取所有已经配置的站点
sites = SiteInfo.objects.all()
for i in sites:
site_name = i.siteconfig_name
site_cookie = i.cookie
#获取站点配置信息
site_config = SiteConfig.objects.get(name=site_name)
site_url = site_config.index_url
site_name_cn = site_config.name_cn
#headers = {
#'user-agent': user_agent,
#'referer': site_url,
#'cookie': site_cookie
#}
if site_name == 'hdchina':
flag, data = hdchina(site_name, site_name_cn, site_url, site_cookie)
elif site_name in ['hdfans','1ptba','ptchina','3wmg','discfan','hddolby','hdatmos','soulvoice',
'pthome','hdtime','hdzone','htpt','audiences','nicept','hdhome','pttime',
'lemonhd','ourbits','asf']:
flag, data = general(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'pterclub':
flag, data = pterclub(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hdarea':
flag, data = hdarea(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hdcity':
flag, data = hdcity(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'btschool':
flag, data = btschool(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hares':
flag, data = hares(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'totheglory':
flag, data = ttg(site_name, site_name_cn, site_url, site_cookie)
elif site_name == '52pt':
flag, data = pt52(site_name, site_name_cn, site_url, site_cookie)
elif site_name in ['beitai','msg','hdmayi','oshen','avgv','eastgame','et8']:
flag, data = noSign(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'keepfrds':
flag, data = keepfrds(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'tjupt':
flag, data = tjupt(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hd':
flag, data = hd(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'greatposterwall':
flag, data = greatposterwall(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'open':
flag, data = opencd(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'hdsky':
flag, data = hdsky(site_name, site_name_cn, site_url, site_cookie)
elif site_name == 'haidan':
flag, data = haidan(site_name, site_name_cn, site_url, site_cookie)
else:
flag, data = (False,'%s 未匹配站点' % site_name)
#print(site_name,data)
try:
Log.objects.create(name = '签到',type_id = 1000, crontab_id = crontab_id, site_name=site_name, message = data, status = flag)
except:
logger.error("%s(%s)数据返回出错" % (site_name, site_name_cn))
send_data.append(data)
logger.info("-------------send_data-------------------")
logger.info(",".join(send_data))
#获取对应任务ID的记录详情
notify_ormdata = Job.objects.get(crontab_id = crontab_id)
if 'iyuu' in notify_ormdata.notifys:
iyuu_ormdata = NotifyConfig.objects.get(name='iyuu')
flag,msg = send_iyuu(iyuu_ormdata.iyuu_key, send_data)
logger.info("-------------iyuu-------------------")
logger.info(msg)
if 'telegram' in notify_ormdata.notifys:
tg_ormdata = NotifyConfig.objects.get(name='telegram')
flag,msg = send_telegram(tg_ormdata.tg_chat_id,tg_ormdata.tg_token,send_data)
logger.info("-------------telegram-------------------")
logger.info(msg)
if 'email' in notify_ormdata.notifys:
email_ormdata = NotifyConfig.objects.get(name='email')
receiver_users = [x for x in email_ormdata.receive_user.split(',')]
flag,msg = send_email(email_ormdata.mail_type,email_ormdata.smtp_user,email_ormdata.smtp_password,receiver_users,send_data)
logger.info("-------------email-------------------")
logger.info(msg)

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.0.2 on 2022-02-23 06:29
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='JobType',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, verbose_name='名称')),
('type_id', models.IntegerField(verbose_name='类型ID')),
],
options={
'unique_together': {('name', 'type_id')},
},
),
]

View File

@@ -0,0 +1,27 @@
# Generated by Django 4.0.2 on 2022-02-23 06:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0001_initial'),
]
operations = [
migrations.AlterUniqueTogether(
name='jobtype',
unique_together=set(),
),
migrations.AlterField(
model_name='jobtype',
name='name',
field=models.CharField(max_length=20, unique=True, verbose_name='名称'),
),
migrations.AlterField(
model_name='jobtype',
name='type_id',
field=models.IntegerField(unique=True, verbose_name='类型ID'),
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 4.0.2 on 2022-02-24 11:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0002_alter_jobtype_unique_together_alter_jobtype_name_and_more'),
]
operations = [
migrations.CreateModel(
name='Job',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=30, unique=True, verbose_name='任务名称')),
('jobtype_id', models.IntegerField(verbose_name='类型ID,签到任务只允许配置一个')),
('cron_time_type', models.CharField(max_length=10, verbose_name='执行周期类型,秒,分,时,天,周,月')),
('crontab', models.CharField(max_length=100, verbose_name='执行时间')),
('crontab_id', models.CharField(max_length=64, verbose_name='执行时间')),
('crontab_status', models.BooleanField(default=True, verbose_name='任务状态,运行,禁用')),
('week', models.IntegerField(verbose_name='周')),
('day', models.IntegerField(verbose_name='天')),
('hour', models.IntegerField(verbose_name='时')),
('minute', models.IntegerField(verbose_name='分')),
('second', models.IntegerField(verbose_name='秒')),
],
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 4.0.2 on 2022-02-25 01:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0003_job'),
]
operations = [
migrations.RenameField(
model_name='job',
old_name='cron_time_type',
new_name='crontab_time_type',
),
migrations.RemoveField(
model_name='job',
name='crontab',
),
migrations.AddField(
model_name='job',
name='crontab_time',
field=models.CharField(max_length=100, null=True, verbose_name='执行周期'),
),
migrations.AlterField(
model_name='job',
name='crontab_id',
field=models.CharField(max_length=64, verbose_name='执行ID'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.2 on 2022-02-25 11:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0004_rename_cron_time_type_job_crontab_time_type_and_more'),
]
operations = [
migrations.AddField(
model_name='job',
name='sites',
field=models.CharField(max_length=300, null=True, verbose_name='站点,使用,号分割'),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 4.0.2 on 2022-02-26 10:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0005_job_sites'),
]
operations = [
migrations.AlterField(
model_name='job',
name='day',
field=models.IntegerField(default=0, verbose_name='天'),
),
migrations.AlterField(
model_name='job',
name='hour',
field=models.IntegerField(default=0, verbose_name='时'),
),
migrations.AlterField(
model_name='job',
name='minute',
field=models.IntegerField(default=0, verbose_name='分'),
),
migrations.AlterField(
model_name='job',
name='second',
field=models.IntegerField(default=0, verbose_name='秒'),
),
migrations.AlterField(
model_name='job',
name='week',
field=models.IntegerField(default=0, verbose_name='周'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.2 on 2022-02-28 00:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0006_alter_job_day_alter_job_hour_alter_job_minute_and_more'),
]
operations = [
migrations.AddField(
model_name='job',
name='notifys',
field=models.CharField(max_length=100, null=True, verbose_name='通知类型,使用,号分割'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.0.2 on 2022-02-28 07:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0007_job_notifys'),
]
operations = [
migrations.CreateModel(
name='Log',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, verbose_name='任务名称')),
('type_id', models.IntegerField(verbose_name='类型ID')),
('crontab_id', models.CharField(max_length=64, verbose_name='执行ID')),
('message', models.CharField(max_length=300, verbose_name='日志记录')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('status', models.BooleanField(default=True, verbose_name='是否执行成功')),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.2 on 2022-02-28 07:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0008_log'),
]
operations = [
migrations.AlterField(
model_name='log',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.2 on 2022-02-28 18:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0009_alter_log_created_at'),
]
operations = [
migrations.AlterField(
model_name='job',
name='crontab_time',
field=models.CharField(max_length=100, null=True, verbose_name='执行周期,* * * * *'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.2 on 2022-03-07 09:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cron', '0010_alter_job_crontab_time'),
]
operations = [
migrations.AddField(
model_name='log',
name='site_name',
field=models.CharField(max_length=50, null=True, verbose_name='站点名称'),
),
]

View File

41
apps/cron/models.py Normal file
View File

@@ -0,0 +1,41 @@
from django.db import models
# Create your models here.
class JobType(models.Model):
"""
任务类型
"""
name = models.CharField('名称', max_length=20, unique=True)
type_id = models.IntegerField("类型ID", unique=True)
class Job(models.Model):
"""
任务
"""
name = models.CharField('任务名称', max_length=30, unique=True)
jobtype_id = models.IntegerField("类型ID,签到任务只允许配置一个")
crontab_time_type = models.CharField('执行周期类型,秒,分,时,天,周,月',max_length=10)
crontab_time = models.CharField('执行周期,* * * * *',max_length=100,null=True)
crontab_id = models.CharField('执行ID',max_length=64)
crontab_status = models.BooleanField('任务状态,运行,禁用',default=True)
week = models.IntegerField('周',default=0)
day = models.IntegerField('天',default=0)
hour = models.IntegerField('时',default=0)
minute = models.IntegerField('分',default=0)
second = models.IntegerField('秒',default=0)
sites = models.CharField('站点,使用,号分割',max_length=300,null=True)
notifys = models.CharField('通知类型,使用,号分割',max_length=100,null=True)
class Log(models.Model):
"""
任务日志
"""
name = models.CharField('任务名称', max_length=20)
type_id = models.IntegerField("类型ID")
crontab_id = models.CharField('执行ID',max_length=64)
site_name = models.CharField('站点名称', max_length=50,null=True)
message = models.CharField('日志记录',max_length=300)
created_at = models.DateTimeField('创建时间', auto_now_add=True,db_index = True)
status = models.BooleanField('是否执行成功',default=True)

View File

@@ -0,0 +1,207 @@
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-form toolbar">
<div class="layui-form-item">
<div class="layui-inline searchInput">
<input id="jobSearchValue" class="layui-input" type="text" placeholder="输入关键字"/>
</div>
<div class="layui-inline">
<button id="jobBtnSearch" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>
<button id="jobBtnAdd" class="layui-btn icon-btn"><i class="layui-icon">&#xe654;</i>添加</button>
<button id="jobBtnDel" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</button>
</div>
</div>
</div>
<table class="layui-table" id="jobTable" lay-filter="jobTable"></table>
</div>
</div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="jobTableBar">
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
form.render('select');
// 渲染表格
var insTb = tableX.render({
elem: '#jobTable',
url: setter.base_server + 'cron/job/list',
method: 'GET',
page: true,
limit:15,
limits:[15,50,100],
cols: [[
{type: 'checkbox'},
{field: 'id', title: 'ID',hide:true},
{field: 'name', sort: true, title: '任务名称'},
{field: 'jobtype_id', sort: true, title: '任务类型'},
{field: 'crontab_time', sort: true, title: '执行周期'},
{field: 'crontab_status', sort: true, title: '任务状态'},
{align: 'center', toolbar: '#jobTableBar', title: '操作'}
]]
});
// 添加按钮点击事件
$('#jobBtnAdd').click(function () {
showAddModel();
});
// 工具条点击事件
table.on('tool(jobTable)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
if (layEvent === 'edit') { // 修改
showEditModel(data);
} else if(layEvent === 'del'){
//var csrftoken = getCookie('csrftoken');
layer.confirm('确定删除此记录 ' + data.name + '?',{icon: 3, title: '警告', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
$.ajax({
url : "cron/job/del",
type: 'POST',
dataType : "json",
data: {'ids':[data.id], csrfmiddlewaretoken: '{{ csrf_token }}' },
// beforeSend: function(request) {
// //django需要csrf验证,Forbidden (CSRF cookie not set.):
// request.setRequestHeader("X-CSRFToken", csrftoken);
// },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('jobTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
//显示表单弹窗
var showAddModel = function () {
var title = '添加站点';
var index = admin.open({
title: title,
maxmin: true,
area:['70%','80%'],
url: 'cron/job/add',
restore:function(){//最大化回调方法
$('.layui-layer-min').attr('style','display:inline- block;')
},
success: function (layero, index) {
$(layero).children('.layui-layer-content').css('overflow', 'visible'); // 禁止出现滚动条
$(layero).find('.layui-layer-min').remove(); //去掉最小化按钮
}
});
//打开最大化
//layer.full(index);
};
var showEditModel = function (data) {
var title = '编辑';
admin.open({
title: title,
//area: ['600px', '500px'],
url: 'cron/job/edit?id='+data.id,
success: function (layero, index) {
$(layero).children('.layui-layer-content').css('overflow', 'visible'); // 禁止出现滚动条
}
});
};
// 批量删除按钮点击事件
$('#jobBtnDel').click(function () {
//选中事件
var checkStatus = table.checkStatus('jobTable')
//得到选择长度
dataLength = checkStatus.data.length
data = checkStatus.data;
var ids = [];
if (dataLength==0){
//未选中
layer.msg("请选择需要删除的记录", {icon: 2});
}else{
layer.confirm('确定删除选中的'+dataLength+'个记录?',{icon: 3, title: '警告', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
for (var i = 0; i < data.length; i++) {
ids.push(data[i].id);
}
$.ajax({
url : "cron/job/del",
type: 'POST',
dataType : "json",
data: {'ids':ids, csrfmiddlewaretoken: '{{ csrf_token }}' },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('recordTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
// 监听enter键,光标在搜索框
$('#jobSearchValue').keydown(function (event) {
if (event.keyCode === 13) {
var key = 'search';
var value = $('#jobSearchValue').val();
table.reload('jobTable', {where: {searchKey: key, searchValue: value}});
} else if (event.keyCode === 8 && !$psw.val()) {
$("#jobSearchValue").val('');
ptable();
if (event.preventDefault) event.preventDefault();
if (event.returnValue) event.returnValue = false;
}
});
// 搜索按钮点击事件
$('#jobBtnSearch').click(function () {
var key = 'search';
var value = $('#jobSearchValue').val();
table.reload('jobTable', {where: {searchKey: key, searchValue: value}});
});
});
</script>

View File

@@ -0,0 +1,715 @@
<style>
.layui-form-item .layui-input-company {width: auto;padding-right: 10px;line-height: 38px;}
</style>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-form toolbar">
<div class="layui-form-item">
<div class="layui-inline searchInput">
<input id="jobSearchValue" class="layui-input" type="text" placeholder="输入关键字"/>
</div>
<div class="layui-inline">
<button id="jobBtnSearch" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>
<button id="jobBtnDel" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</button>
</div>
</div>
</div>
<table class="layui-table" id="jobTable" lay-filter="jobTable"></table>
<fieldset class="layui-elem-field">
<legend>新增/编辑计划任务</legend>
<div class="layui-field-box">
<form class="layui-form" lay-filter="form" id="form">
<input type="hidden" name="config_filename" value="crontab">
<div class="layui-form-item">
<label class="layui-form-label required">任务类型</label>
<div class="layui-input-inline">
<select name="type" lay-filter="type" lay-verify="required" required lay-reqtext="任务类型不能为空" id="type">
<option value="">请选择任务类型</option>
<option value="reseed">辅种任务</option>
<option value="rss">Rss下载任务</option>
<option value="spiders">免费种爬虫下载任务</option>
<!-- <option value="url">访问URL</option>
<option value="shell">Shell脚本</option>
<option value="patch">备份目录</option>
<option value="ddns">动态域名DDNS</option>
<option value="clear">清除日志</option> -->
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">任务名称</label>
<div class="layui-input-block">
<input type="text" name="name" lay-verify="required" maxlength="30" placeholder="请输入任务名称" class="layui-input" id="name" lay-verify="required" required/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务状态</label>
<div class="layui-input-block">
<input type="checkbox" name="switch" lay-skin="switch" lay-text="运行|禁用" lay-filter="switch" id="switch">
</div>
</div>
<div class="layuimini-form" id="crontab_type_view"></div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
<button class="layui-btn layui-btn-primary" data-refresh="刷新">刷新</button>
</div>
</div>
</form>
</div>
</fieldset>
</div>
</div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="jobTableBar">
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
{% verbatim myblock %}
<!-- 模板:计划任务配置字段 -->
<script type="text/html" id="crontab_type_tpl">
<input type="hidden" name="action" value="{{ d.action || 'add'}}">
{{# if (d.action === 'edit') { }}
<input type="hidden" name="uuid" value="">
<input type="hidden" name="startTime" value="">
{{# } }}
<div class="layui-form-item">
<label class="layui-form-label required">执行周期</label>
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<select name="where" lay-filter="where" lay-verify="required" lay-reqtext="执行周期不能为空" id="where">
<option value="day" {{ d.where === 'day' ? 'selected' : ''}}>每天</option>
<option value="day_n" {{ d.where === 'day_n' ? 'selected' : ''}}>N天</option>
<option value="hour" {{ d.where === 'hour' ? 'selected' : ''}}>每小时</option>
<option value="hour_n" {{ d.where === 'hour_n' ? 'selected' : ''}}>N小时</option>
<option value="minute_n" {{ d.where === 'minute_n' ? 'selected' : ''}}>N分钟</option>
<option value="week" {{ d.where === 'week' ? 'selected' : ''}}>每星期</option>
<option value="month" {{ d.where === 'month' ? 'selected' : ''}}>每月</option>
</select>
</div>
</div>
<!--执行周期联动-->
<span id="crontab_where_view"></span>
</div>
{{# if (d.type === 'reseed') { }}
<div class="layui-form-item">
<label class="layui-form-label required">辅种的站点</label>
<div class="layui-input-block" id="reseed_sites_view">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">辅种的下载器</label>
<div class="layui-input-block" id="reseed_clients_view">
</div>
<div class="layui-form-mid layui-word-aux"><i class="layui-icon layui-icon-tips"></i> 如果您新增站点或者新增下载器,需要再次编辑计划任务并勾选。</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">自动校验</label>
<div class="layui-input-block">
<input type="checkbox" name="auto_check" lay-skin="switch" lay-text="YES|NO" lay-filter="auto_check" id="auto_check">
<tip>辅种添加的任务,自动开始校验(如果您的QB下载器,添加的辅种任务不会自动开始校验,可以勾选此设置)。此功能在TR以及低版本QB中属于默认行为,是否勾选都不影响自动校验;</tip>
</div>
</div>
{{# } }}
{{# if (d.type === 'rss') { }}
<div class="layui-form-item">
<label class="layui-form-label required">Rss站点</label>
<div class="layui-input-block">
<div id="rss_sites_view"></div>
<div class="layui-form-mid layui-word-aux"><i class="layui-icon layui-icon-tips"></i> 未配置的站点,将会显示为禁用状态。<a href="javascript:;" layuimini-href="page/setting/sites.html" target="_self" style="color:#1e9fff">站点设置</a></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">RSS订阅地址</label>
<div class="layui-input-block">
<input type="text" name="urladdress" placeholder="请输入RSS订阅地址" class="layui-input" value="">
<tip>未配置URL地址时,将会执行默认。</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">下载器</label>
<div class="layui-input-block" id="rss_clients_view">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">下载模式</label>
<div class="layui-input-block">
<input type="checkbox" name="workingMode" lay-skin="switch" lay-text="Watch模式|Rpc模式" lay-filter="workingMode" id="workingMode">
<tip>Rpc模式:可以远程访问下载器的API接口,POST种子元数据;Watch模式:仅往下载器设置的watch监控目录内写入种子文件。</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据目录</label>
<div class="layui-input-block">
<input type="text" name="downloadsDir" placeholder="请输入数据目录" class="layui-input">
<tip><span class="layui-badge">优先级:中</span>下载器的数据目录。如果此处留空,将会使用过滤器设置的数据目录或者下载器设置的数据目录;如果都未设置,将会采用下载器默认值。</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">筛选规则</label>
<div class="layui-input-block">
<div id="rss_filter_view"></div>
<div class="layui-form-mid layui-word-aux"><i class="layui-icon layui-icon-tips"></i> 满足所选的规则,Rss时才会下载。</div>
</div>
</div>
{{# } }}
{{# if (d.type === 'spiders') { }}
<div class="layui-form-item">
<label class="layui-form-label required">站点爬虫</label>
<div class="layui-input-block">
<div id="spiders_sites_view"></div>
<div class="layui-form-mid layui-word-aux"><i class="layui-icon layui-icon-tips"></i> 未配置的站点,将会显示为禁用状态。<a href="javascript:;" layuimini-href="page/setting/sites.html" target="_self" style="color:#1e9fff">站点设置</a></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">种子列表页地址</label>
<div class="layui-input-block">
<input type="text" name="urladdress" placeholder="请输入种子列表页地址" class="layui-input" value="">
<tip>未配置URL地址时,将会执行默认。</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">下载器</label>
<div class="layui-input-block" id="spiders_clients_view">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">下载模式</label>
<div class="layui-input-block">
<input type="checkbox" name="workingMode" lay-skin="switch" lay-text="Watch模式|Rpc模式" lay-filter="workingMode" id="workingMode">
<tip>Rpc模式:可以远程访问下载器的API接口,POST种子元数据;Watch模式:仅往下载器设置的watch监控目录内写入种子文件。</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">数据目录</label>
<div class="layui-input-block">
<input type="text" name="downloadsDir" placeholder="请输入数据目录" class="layui-input">
<tip><span class="layui-badge">优先级:中</span>下载器的数据目录。如果此处留空,将会使用过滤器设置的数据目录或者下载器设置的数据目录;如果都未设置,将会采用下载器默认值。</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">筛选规则</label>
<div class="layui-input-block">
<div id="spiders_filter_view"></div>
<div class="layui-form-mid layui-word-aux"><i class="layui-icon layui-icon-tips"></i> 满足所选的规则,爬虫才会向下载器推送下载任务。</div>
</div>
</div>
{{# } }}
</script>
<!-- 模板:辅种任务 [站点] 复选框 -->
<script id="reseed_sites_tpl" type="text/html">
{{# layui.each(d, function(index, item){ }}
<input type="checkbox" name="sites[{{item}}]" title="{{item}}" checked>
{{# }); }}
</script>
<!-- 模板:辅种任务 [下载器] 复选框 -->
<script id="reseed_clients_tpl" type="text/html">
{{# layui.each(d, function(index, item){ }}
<input type="checkbox" name="clients[{{item.uuid}}]" title="{{ item.name }}" checked>
{{# }); }}
</script>
<!-- 模板:下载任务 [站点] 单选框 -->
<script id="rss_sites_tpl" type="text/html">
{{# layui.each(d, function(index, item){ }}
<input type="radio" name="site" value="{{item.site}}" title="{{ item.site }}{{ item.nickname && item.site != item.nickname ? ' ' + item.nickname : '' }}" {{ item.disabled ? ' disabled' : '' }}>
{{# }); }}
</script>
<!-- 模板:下载任务 [下载器] 单选框 -->
<script id="rss_clients_tpl" type="text/html">
{{# layui.each(d, function(index, item){ }}
<input type="radio" name="clients" value="{{item.uuid}}" title="{{ item.name }}">
{{# }); }}
</script>
<!-- 模板:下载任务 [筛选规则过滤器] 单选框 -->
<script id="rss_filter_tpl" type="text/html">
{{# layui.each(d, function(index, item){ }}
<input type="radio" name="filter" value="{{item.uuid}}" title="{{ item.name }}">
{{# }); }}
</script>
<!-- 模板:计划任务 [执行周期] 组合 -->
<script type="text/html" id="crontab_where_tpl">
{{# if (d.weeks) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<select name="weeks" lay-filter="weeks" lay-verify="required" id="weeks">
<option value="1" selected>周一</option>
<option value="2">周二</option>
<option value="3">周三</option>
<option value="4">周四</option>
<option value="5">周五</option>
<option value="6">周六</option>
<option value="0">周日</option>
</select>
</div>
</div>
{{# } }}
{{# if (d.day) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 140px;">
<input type="number" name="day" lay-verify="required" lay-reqtext="{{ d.where === 'day_n' ? '间隔天数' : '每月第几日' }}不能为空" placeholder="请输入{{ d.where === 'day_n' ? '间隔天数' : '每月第几日' }}" class="layui-input" id="day">
</div>
<div class="layui-form-mid">{{ d.where === 'day_n' ? '天' : '日' }}</div>
</div>
{{# } }}
{{# if (d.hour) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<input type="number" name="hour" lay-verify="required" lay-reqtext="小时不能为空" placeholder="请输入小时" class="layui-input" id="hour">
</div>
<div class="layui-form-mid">小时</div>
</div>
{{# } }}
{{# if (d.minute) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<input type="number" name="minute" lay-verify="required" lay-reqtext="分钟不能为空" placeholder="请输入分钟" class="layui-input" id="minute">
</div>
<div class="layui-form-mid">分钟</div>
</div>
{{# } }}
</script>
{% endverbatim myblock %}
<script>
/**
* 根据ID获取DOM元素对象
* @param {string} id
*/
function getById(id) {
return document.getElementById(id);
}
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'xmSelect', 'formX', 'dropdown','laytpl','transfer','code'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
var laytpl = layui.laytpl;
var transfer = layui.transfer;
const path_filter = 'path_filter'
,path_selector = 'path_selector'
,_height = 250;
/**
* 任务类型 选择框
* @param {string} type 任务类型
* @param {string} action 动作(增、删、改、查)
* @param {string} where 执行周期
* @param {object} formData 编辑时表单数据
*/
let crontab_sel_action = function(type = '', action = '', where = 'day', formData = null) {
let rule = {
type: type
,action: action
,where: where
}
,getTpl = crontab_type_tpl.innerHTML
,view = getById('crontab_type_view');
//初始清空
view.innerHTML = '';
if (type === '') {
getById('switch').checked = false;
form.render();
return ;
}
//新增任务时,清空任务名称 选中任务使能
if (action === '' || action === 'add') {
getById('name').value = '';
getById('switch').checked = true;
}
laytpl(getTpl).render(rule, function(html) {
view.innerHTML = html;
});
form.render();
//联动:任务周期
crontab_where_sel_action(rule.where);
//联动:辅种任务
reseed_sel_action(type, formData);
//联动:RSSS下载任务
rss_sel_action(type, formData);
//联动:免费种爬虫下载任务
spiders_sel_action(type, formData);
};
/**
* 任务周期选择框
* @param {string} where 执行周期
*/
let crontab_where_sel_action = function(where = '') {
let rule = {
where: where
,weeks: where === 'week'
,day: $.inArray(where, ['day_n', 'month']) >= 0
,hour: $.inArray(where, ['hour', 'minute_n']) < 0
,minute: !!where
}
,getTpl = crontab_where_tpl.innerHTML
,view = getById('crontab_where_view');
//初始清空
view.innerHTML = '';
if (where === '') {
return ;
}
laytpl(getTpl).render(rule, function(html) {
view.innerHTML = html;
});
form.render();
};
/**
* 任务选择框联动,辅种任务
* @param {string} type 任务类型
* @param {object} formData 编辑时表单数据
*/
let reseed_sel_action = function(type = '', formData) {
if (type !== 'reseed') {
return;
}
let callback = null;
if (formData !== null) {
format_input('sites', formData);
format_input('clients', formData);
delete formData.sites;
delete formData.clients;
console.log(formData);
//定义回调
callback = [
function(){
//取消复选框
$('#reseed_sites_view').find(':input').each(function () {
$(this).attr('checked', false);
});
form.val("form", formData);
},
function(){
//取消复选框
$('#reseed_clients_view :input').removeAttr("checked");
form.val("form", formData);
}
];
}
//辅种站点
$.getJSON('/Api/Config?config_filename=user_sites&action=list', function (d) {
let getTpl = reseed_sites_tpl.innerHTML
,view = getById('reseed_sites_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置站点,将会为您跳转到站点设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/sites.html';
});
return;
}
let data = Object.keys(d.data.items).map(function (key){
return d.data.items[key].name;
});
laytpl(getTpl).render(data, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback[0] === 'function' && callback[0]();
form.render('checkbox');
});
//辅种下载器
$.getJSON('/Api/Config?config_filename=clients&action=list', function (d) {
let getTpl = reseed_clients_tpl.innerHTML
,view = getById('reseed_clients_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置下载器,将会为您跳转到下载器设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/clients.html';
});
return;
}
laytpl(getTpl).render(d.data.items, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback[1] === 'function' && callback[1]();
form.render('checkbox');
});
};
/**
* 任务选择框联动,RSS下载任务
* @param {string} type 任务类型
* @param {object} formData 编辑时表单数据
*/
let rss_sel_action = function(type = '', formData) {
if (type !== 'rss') {
return;
}
let callback = null;
if (formData !== null) {
console.log(formData);
//定义回调
callback = function(){
form.val("form", formData);
};
}
//RSS支持的所有站点
$.getJSON('/Api/getAllRssClass', {filter: true}, function (d) {
let getTpl = rss_sites_tpl.innerHTML
,view = getById('rss_sites_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置站点,将会为您跳转到站点设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/sites.html';
});
return;
}
laytpl(getTpl).render(d.data.items, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback === 'function' && callback();
form.render('radio');
});
//RSS使用的下载器
$.getJSON('/Api/Config?config_filename=clients&action=list', function (d) {
let getTpl = rss_clients_tpl.innerHTML
,view = getById('rss_clients_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置下载器,将会为您跳转到下载器设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/clients.html';
});
return;
}
laytpl(getTpl).render(d.data.items, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback === 'function' && callback();
form.render('radio');
});
//RSS使用的筛选规则过滤器
$.getJSON('/Api/Config?config_filename=filter&action=list', function (d) {
let getTpl = rss_filter_tpl.innerHTML
,view = getById('rss_filter_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置筛选规则过滤器,将会为您跳转到过滤器设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/filter.html';
});
return;
}
laytpl(getTpl).render(d.data.items, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback === 'function' && callback();
form.render('radio');
});
};
/**
* 任务选择框联动,免费种爬虫下载任务
* @param {string} type 任务类型
* @param {object} formData 编辑时表单数据
*/
let spiders_sel_action = function(type = '', formData) {
if (type !== 'spiders') {
return;
}
let callback = null;
if (formData !== null) {
console.log(formData);
//定义回调
callback = function(){
form.val("form", formData);
};
}
//支持的所有站点
$.getJSON('/Api/getAllSpidersClass', {filter: true}, function (d) {
let getTpl = rss_sites_tpl.innerHTML
,view = getById('spiders_sites_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置站点,将会为您跳转到站点设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/sites.html';
});
return;
}
laytpl(getTpl).render(d.data.items, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback === 'function' && callback();
form.render('radio');
});
//使用的下载器
$.getJSON('/Api/Config?config_filename=clients&action=list', function (d) {
let getTpl = rss_clients_tpl.innerHTML
,view = getById('spiders_clients_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置下载器,将会为您跳转到下载器设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/clients.html';
});
return;
}
laytpl(getTpl).render(d.data.items, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback === 'function' && callback();
form.render('radio');
});
//使用的筛选规则过滤器
$.getJSON('/Api/Config?config_filename=filter&action=list', function (d) {
let getTpl = rss_filter_tpl.innerHTML
,view = getById('spiders_filter_view');
if (d.ret !== 200) {
return;
}
if (d.data.total <= 0) {
layer.alert('您还没有配置筛选规则过滤器,将会为您跳转到过滤器设置!', function(index){
layer.close(index);
window.location = '/index.html#/page/setting/filter.html';
});
return;
}
laytpl(getTpl).render(d.data.items, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback === 'function' && callback();
form.render('radio');
});
};
// 渲染表格
var insTb = tableX.render({
elem: '#jobTable',
url: setter.base_server + 'cron/job/list',
method: 'GET',
page: true,
limit:15,
limits:[15,50,100],
cols: [[
{type: 'checkbox'},
{field: 'id', title: 'ID',hide:true},
{field: 'name', sort: true, title: '任务名称'},
{field: 'jobtype_id', sort: true, title: '任务类型'},
{field: 'crontab_time', title: '执行周期'},
{field: 'crontab_status', sort: true, title: '任务状态'},
{align: 'center', toolbar: '#jobTableBar', title: '操作'}
]]
});
/**
* 初始化表单,要加上,不然刷新部分组件可能会不加载
*/
form.render();
//自定义验证规则
form.verify({
path_filter: function (value) {
let elem = getById(path_filter)
,obj = transfer.getData(path_filter);
elem.value = obj ? obj.map(v => {return v.value}).toString() : '';
}
,path_selector: function (value) {
let elem = getById(path_selector)
,obj = transfer.getData(path_selector);
elem.value = obj ? obj.map(v => {return v.value}).toString() : '';
}
});
//监听选择:类型
form.on('select(type)', function(data){
crontab_sel_action(data.value, 'add');
});
//监听选择:执行周期where
form.on('select(where)', function(data){
crontab_where_sel_action(data.value);
});
//监听提交
form.on('submit(saveBtn)', function (data) {
$.ajax({
url: API.create,
type:'post',
data: data.field,
success:function(d){
console.log(d);
if (d.ret !== 200) {
layer.msg(d.msg);
return;
}
layer.msg('保存成功', function(){
//miniPage.hashHome();
window.location.reload();
});
},
complete: function () {
},
error : function(request) {
layer.alert('未知错误,请截图当前界面,然后求助于QQ群:859882209、931954050、924099912!', {icon: 2, title: '出错提示'});
}
});
return false;
});
});
</script>

View File

@@ -0,0 +1,321 @@
<!-- 表单弹窗 -->
<form id="jobAddForm" lay-filter="jobAddForm" class="layui-form model-form" method="POST">{% csrf_token %}
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">任务类型</label>
<div class="layui-input-block">
<div id="job_type"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">任务名称</label>
<div class="layui-input-block">
<input name="name" type="text" class="layui-input" maxlength="30" lay-verify="required" required/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">执行周期</label>
<div class="layui-input-inline" style="width: 150px;">
<div id="crontab_time" class="xm-select-demo"></div>
</div>
<span id="crontab_where_view"></span>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务状态</label>
<div class="layui-input-block">
<input type="radio" name="crontab_status" value="1" title="激活" checked/>
<input type="radio" name="crontab_status" value="0" title="禁用"/>
</div>
</div>
<div class="layui-form-itemm" id="crontab_type_view"></div>
<div class="layui-form-item text-right">
<button class="layui-btn layui-btn-primary" ew-event="closeDialog" type="button">取消</button>
<button class="layui-btn" lay-filter="jobAddForm-submit" lay-submit>保存</button>
</div>
</form>
<script>
/**
* 根据ID获取DOM元素对象
* @param {string} id
*/
function getById(id) {
return document.getElementById(id);
}
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'formX', 'dropdown','laytpl'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var laytpl = layui.laytpl;
var jobTypeList = [];
form.render('radio');
form.render('select', 'jobAddForm');
// 渲染多选下拉框
var insSel = xmSelect.render({
el: '#job_type',
name: 'job_type',
prop: {
name: 'name',
value: 'value',
},
layVerify: 'required',
radio: true,
clickClose: true,
data: [],
on: function(data){
//isAdd, 此次操作是新增还是删除
var isAdd = data.isAdd;
//点击获取数组对象
//console.log(data.arr)
if (isAdd){
crontab_sel_action(data.arr[0].value)
}else{
crontab_sel_action('')
}
}
});
/* 获取所有记录类型 */
var loadIndex = layer.load(2);
admin.req('cron/jobtypeinfo/list', function (res) {
layer.close(loadIndex);
if (0 === res.code) {
$.each(res.data, function (index, item) {
jobTypeList.push({name: item.name, value: item.type_id});
});
insSel.update({
data: jobTypeList,
autoRow: true,
})
} else {
layer.msg(res.msg, {icon: 2});
}
});
var crontab_time = xmSelect.render({
el: '#crontab_time',
radio: true,
clickClose: true,
height: "252px",
theme: {
color: '#0081ff',
},
data: [
{name: '每小时', value: "hour"},
{name: '每天', value: "day"},
{name: '每星期', value: "week"},
{name: '每月', value: "month"},
{name: 'N分钟', value: "minute_n"},
{name: 'N小时', value: "hour_n"},
{name: 'N天', value: "day_n"},
],
on: function(data){
//isAdd, 此次操作是新增还是删除
var isAdd = data.isAdd;
//点击获取数组对象
//console.log(data.arr)
if (isAdd){
crontab_where_sel_action(data.arr[0].value)
}else{
crontab_where_sel_action('')
}
}
})
form.on('submit(jobAddForm-submit)', function (data) {
layer.load(2);
admin.req('cron/job/add', data.field, function (data) {
layer.closeAll('loading');
if (data.code == 1) {
layer.msg(data.msg, {icon: 1});
table.reload('jobTable', {});
admin.closeDialog('#jobAddForm');
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#jobAddForm').attr('method'));
return false;
});
/**
* 任务类型 选择框
* @param {string} type 任务类型
*/
let crontab_sel_action = function(type = '') {
console.log(type)
let rule = {
type: type
}
,getTpl = crontab_type_tpl.innerHTML
,view = getById('crontab_type_view');
//初始清空
view.innerHTML = '';
if (type === '') {
getById('switch').checked = false;
form.render();
return ;
}
laytpl(getTpl).render(rule, function(html) {
view.innerHTML = html;
});
form.render();
//联动:签到任务
sign_sel_action(type);
};
/**
* 任务周期选择框
* @param {string} where 执行周期
*/
let crontab_where_sel_action = function(where = '') {
let rule = {
where: where
,weeks: where === 'week'
,day: $.inArray(where, ['day_n', 'month']) >= 0
,hour: $.inArray(where, ['hour', 'minute_n']) < 0
,minute: !!where
}
,getTpl = crontab_where_tpl.innerHTML
,view = document.getElementById('crontab_where_view');
//初始清空
view.innerHTML = '';
if (where === '') {
return ;
}
laytpl(getTpl).render(rule, function(html) {
view.innerHTML = html;
});
form.render();
};
/**
* 任务选择框联动,签到任务
* @param {string} type 任务类型
*/
let sign_sel_action = function(type = '') {
if (type !== 1000) {
return;
}
let callback = null;
//站点
$.getJSON('/sites/siteconfigname/list', function (d) {
let getTpl = sign_sites_tpl.innerHTML
,view = getById('sign_sites_view');
if (d.code !== 0) {
return;
}
if (d.data.count <= 0) {
layer.alert('请先配置站点信息', function(index){
layer.close(index);
});
return;
}
let data = Object.keys(d.data).map(function (key){
return d.data[key].name;
});
laytpl(getTpl).render(data, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback[0] === 'function' && callback[0]();
form.render('checkbox');
});
};
});
</script>
{% verbatim myblock %}
<script type="text/html" id="crontab_type_tpl">
{{# if (d.type === 1000) { }}
<div class="layui-form-item">
<label class="layui-form-label required">签到站点</label>
<div class="layui-input-block" id="sign_sites_view">
</div>
<div class="layui-input-block">
<br><span class="layui-badge layui-badge-red"><h2>默认所有站点,忽略配置!</h2></span>&nbsp;
</div>
</div>
{{# } }}
</script>
<!-- 模板:签到任务 [站点] 复选框 -->
<script id="sign_sites_tpl" type="text/html">
{{# layui.each(d, function(index, item){ }}
<input type="checkbox" name="sites[{{item}}]" title="{{item}}" checked>
{{# }); }}
</script>
<!-- 模板:计划任务 [执行周期] 组合 -->
<script type="text/html" id="crontab_where_tpl">
{{# if (d.weeks) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<select name="weeks" lay-filter="weeks" lay-verify="required" id="weeks">
<option value="1" selected>周一</option>
<option value="2">周二</option>
<option value="3">周三</option>
<option value="4">周四</option>
<option value="5">周五</option>
<option value="6">周六</option>
<option value="0">周日</option>
</select>
</div>
</div>
{{# } }}
{{# if (d.day) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 140px;">
<input type="number" name="day" lay-verify="required" lay-reqtext="{{ d.where === 'day_n' ? '间隔天数' : '每月第几日' }}不能为空" placeholder="请输入{{ d.where === 'day_n' ? '间隔天数' : '每月第几日' }}" class="layui-input" id="day">
</div>
<div class="layui-form-mid">{{ d.where === 'day_n' ? '天' : '日' }}</div>
</div>
{{# } }}
{{# if (d.hour) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<input type="number" name="hour" lay-verify="required" lay-reqtext="小时不能为空" placeholder="请输入小时" class="layui-input" id="hour">
</div>
<div class="layui-form-mid">小时</div>
</div>
{{# } }}
{{# if (d.minute) { }}
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<input type="number" name="minute" lay-verify="required" lay-reqtext="分钟不能为空" placeholder="请输入分钟" class="layui-input" id="minute">
</div>
<div class="layui-form-mid">分钟</div>
</div>
{{# } }}
</script>
{% endverbatim myblock %}

View File

@@ -0,0 +1,197 @@
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-form toolbar">
<div class="layui-form-item">
<div class="layui-inline searchInput">
<input id="jobtypeSearchValue" class="layui-input" type="text" placeholder="输入关键字"/>
</div>
<div class="layui-inline">
<button id="jobtypeBtnSearch" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>
<button id="jobtypeBtnAdd" class="layui-btn icon-btn"><i class="layui-icon">&#xe654;</i>添加</button>
<button id="jobtypeBtnDel" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</button>
</div>
</div>
</div>
<table class="layui-table" id="jobtypeTable" lay-filter="jobtypeTable"></table>
</div>
</div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="jobtypeTableBar">
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
form.render('select');
// 渲染表格
var insTb = tableX.render({
elem: '#jobtypeTable',
url: setter.base_server + 'cron/jobtype/list',
method: 'GET',
page: true,
limit:15,
limits:[15,50,100],
cols: [[
{type: 'checkbox'},
{field: 'id', title: 'ID',hide:true},
{field: 'name', sort: true, title: '类型名'},
{field: 'type_id', sort: true, title: '标识ID'},
{align: 'center', toolbar: '#jobtypeTableBar', title: '操作'}
]]
});
// 添加按钮点击事件
$('#jobtypeBtnAdd').click(function () {
showAddModel();
});
// 工具条点击事件
table.on('tool(jobtypeTable)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
if (layEvent === 'edit') { // 修改
showEditModel(data);
} else if(layEvent === 'del'){
//var csrftoken = getCookie('csrftoken');
layer.confirm('确定删除此记录 ' + data.name + '?',{icon: 3, title: '警告', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
$.ajax({
url : "cron/jobtype/del",
type: 'POST',
dataType : "json",
data: {'ids':[data.id], csrfmiddlewaretoken: '{{ csrf_token }}' },
// beforeSend: function(request) {
// //django需要csrf验证,Forbidden (CSRF cookie not set.):
// request.setRequestHeader("X-CSRFToken", csrftoken);
// },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('jobtypeTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
//显示表单弹窗
var showAddModel = function () {
var title = '添加站点';
admin.open({
title: title,
url: 'cron/jobtype/add',
success: function (layero, index) {
$(layero).children('.layui-layer-content').css('overflow', 'visible'); // 禁止出现滚动条
}
});
};
var showEditModel = function (data) {
var title = '编辑';
admin.open({
title: title,
//area: ['600px', '500px'],
url: 'cron/jobtype/edit?id='+data.id,
success: function (layero, index) {
$(layero).children('.layui-layer-content').css('overflow', 'visible'); // 禁止出现滚动条
}
});
};
// 批量删除按钮点击事件
$('#jobtypeBtnDel').click(function () {
//选中事件
var checkStatus = table.checkStatus('jobtypeTable')
//得到选择长度
dataLength = checkStatus.data.length
data = checkStatus.data;
var ids = [];
if (dataLength==0){
//未选中
layer.msg("请选择需要删除的记录", {icon: 2});
}else{
layer.confirm('确定删除选中的'+dataLength+'个记录?',{icon: 3, title: '警告', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
for (var i = 0; i < data.length; i++) {
ids.push(data[i].id);
}
$.ajax({
url : "cron/jobtype/del",
type: 'POST',
dataType : "json",
data: {'ids':ids, csrfmiddlewaretoken: '{{ csrf_token }}' },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('jobtypeTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
// 监听enter键,光标在搜索框
$('#jobtypeSearchValue').keydown(function (event) {
if (event.keyCode === 13) {
var key = 'search';
var value = $('#jobtypeSearchValue').val();
table.reload('jobtypeTable', {where: {searchKey: key, searchValue: value}});
} else if (event.keyCode === 8 && !$psw.val()) {
$("#jobtypeSearchValue").val('');
ptable();
if (event.preventDefault) event.preventDefault();
if (event.returnValue) event.returnValue = false;
}
});
// 搜索按钮点击事件
$('#jobtypeBtnSearch').click(function () {
var key = 'search';
var value = $('#jobtypeSearchValue').val();
table.reload('jobtypeTable', {where: {searchKey: key, searchValue: value}});
});
});
</script>

View File

@@ -0,0 +1,55 @@
<!-- 表单弹窗 -->
<form id="jobtypeAddForm" lay-filter="jobtypeAddForm" class="layui-form model-form" method="POST">{% csrf_token %}
<div class="layui-form-item">
<label class="layui-form-label">类型名</label>
<div class="layui-input-block">
<input name="name" type="text" class="layui-input" lay-verify="required" required maxlength="20" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">唯一标识ID</label>
<div class="layui-input-block">
<input name="type_id" type="text" class="layui-input" maxlength="4" lay-verify="required|numberX" required/>
</div>
</div>
<div class="layui-form-item text-right">
<button class="layui-btn layui-btn-primary" ew-event="closeDialog" type="button">取消</button>
<button class="layui-btn" lay-filter="jobtypeAddForm-submit" lay-submit>保存</button>
</div>
</form>
<script>
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
form.render('select', 'jobtypeAddForm');
form.on('submit(jobtypeAddForm-submit)', function (data) {
layer.load(2);
admin.req('cron/jobtype/add', data.field, function (data) {
layer.closeAll('loading');
if (data.code == 1) {
layer.msg(data.msg, {icon: 1});
table.reload('jobtypeTable', {});
admin.closeDialog('#jobtypeAddForm');
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#jobtypeAddForm').attr('method'));
return false;
});
});
</script>

View File

@@ -0,0 +1,54 @@
<!-- 表单弹窗 -->
<form id="jobtypeEditForm" lay-filter="jobtypeEditForm" class="layui-form model-form" method="POST">{% csrf_token %}
<input name="id" type="hidden" value="{{id}}"/>
<div class="layui-form-item">
<label class="layui-form-label">类型名</label>
<div class="layui-input-block">
<input name="name" type="text" class="layui-input" lay-verify="required" required maxlength="20" value="{{name}}" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">唯一标识ID</label>
<div class="layui-input-block">
<input name="type_id" type="text" class="layui-input" maxlength="4" lay-verify="required|numberX" required value="{{type_id}}"/>
</div>
</div>
<div class="layui-form-item text-right">
<button class="layui-btn layui-btn-primary" ew-event="closeDialog" type="button">取消</button>
<button class="layui-btn" lay-filter="jobtypeEditForm-submit" lay-submit>保存</button>
</div>
</form>
<script>
layui.use(['layer', 'form', 'xmSelect', 'table', 'admin','formX'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var xmSelect = layui.xmSelect;
var table = layui.table;
var admin = layui.admin;
var formX = layui.formX;
form.render('select',"jobtypeEditForm");
form.on('submit(jobtypeEditForm-submit)', function (data) {
layer.load(2);
admin.req('cron/jobtype/edit', data.field, function (data) {
layer.closeAll('loading');
if (data.code == 1) {
layer.msg(data.msg, {icon: 1});
table.reload('jobtypeTable', {});
admin.closeDialog('#jobtypeEditForm');
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#jobtypeEditForm').attr('method'));
return false;
});
});
</script>

View File

@@ -0,0 +1,169 @@
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-form toolbar">
<div class="layui-form-item">
<div class="layui-inline searchInput">
<input id="logSearchValue" class="layui-input" type="text" placeholder="输入关键字"/>
</div>
<div class="layui-inline">
<button id="logBtnSearch" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
</button>
<button id="logBtnDel" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</button>
</div>
</div>
</div>
<table class="layui-table" id="logTable" lay-filter="logTable"></table>
</div>
</div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="logTableBar">
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
form.render('select');
// 渲染表格
var insTb = tableX.render({
elem: '#logTable',
url: setter.base_server + 'cron/log/list',
method: 'GET',
page: true,
limit:15,
limits:[15,50,100],
cols: [[
{type: 'checkbox'},
{field: 'id', title: 'ID',hide:true},
{field: 'name', sort: true, title: '任务名称'},
{field: 'type_id', sort: true, title: '任务类型'},
{field: 'crontab_id', sort: true, title: '任务ID'},
{field: 'message', sort: true, title: '执行结果'},
{field: 'created_at', sort: true, title: '执行时间'},
{field: 'status', sort: true, title: '执行状态'},
{align: 'center', toolbar: '#logTableBar', title: '操作'}
]]
});
// 工具条点击事件
table.on('tool(logTable)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
if(layEvent === 'del'){
//var csrftoken = getCookie('csrftoken');
layer.confirm('确定删除此记录 ' + data.name + '?',{icon: 3, title: '警告', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
$.ajax({
url : "cron/log/del",
type: 'POST',
dataType : "json",
data: {'ids':[data.id], csrfmiddlewaretoken: '{{ csrf_token }}' },
// beforeSend: function(request) {
// //django需要csrf验证,Forbidden (CSRF cookie not set.):
// request.setRequestHeader("X-CSRFToken", csrftoken);
// },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('logTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
// 批量删除按钮点击事件
$('#logBtnDel').click(function () {
//选中事件
var checkStatus = table.checkStatus('logTable')
//得到选择长度
dataLength = checkStatus.data.length
data = checkStatus.data;
var ids = [];
if (dataLength==0){
//未选中
layer.msg("请选择需要删除的记录", {icon: 2});
}else{
layer.confirm('确定删除选中的'+dataLength+'个记录?',{icon: 3, title: '警告', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
for (var i = 0; i < data.length; i++) {
ids.push(data[i].id);
}
$.ajax({
url : "cron/log/del",
type: 'POST',
dataType : "json",
data: {'ids':ids, csrfmiddlewaretoken: '{{ csrf_token }}' },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('logTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
// 监听enter键,光标在搜索框
$('#logSearchValue').keydown(function (event) {
if (event.keyCode === 13) {
var key = 'search';
var value = $('#logSearchValue').val();
table.reload('logTable', {where: {searchKey: key, searchValue: value}});
} else if (event.keyCode === 8 && !$psw.val()) {
$("#logSearchValue").val('');
ptable();
if (event.preventDefault) event.preventDefault();
if (event.returnValue) event.returnValue = false;
}
});
// 搜索按钮点击事件
$('#logBtnSearch').click(function () {
var key = 'search';
var value = $('#logSearchValue').val();
table.reload('logTable', {where: {searchKey: key, searchValue: value}});
});
});
</script>

View File

@@ -0,0 +1,183 @@
<style>
#signForm {
max-width: 700px;
}
#signForm .layui-form-item {
margin-bottom: 25px;
}
</style>
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<!-- 表单开始 -->
<form class="layui-form" id="signForm" lay-filter="signForm" method="POST">{% csrf_token %}
<input name="id" type="hidden" value="{{id}}"/>
<input name="crontab_id" type="hidden" value="{{crontab_id}}"/>
<input name="jobtype_id" type="hidden" value="1000"/>
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">每天执行时间:</label>
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<input type="number" name="hour" lay-verify="required|numberX|h5" lay-reqtext="小时不能为空"
maxlength="2" min="0" max="24" class="layui-input" id="hour" value="{{hour}}"/>
</div>
<div class="layui-form-mid">点</div>
</div>
<div class="layui-inline">
<div class="layui-input-inline" style="width: 100px;">
<input type="number" name="minute" lay-verify="required|numberX|h5" lay-reqtext="分钟不能为空" placeholder="请输入分钟"
maxlength="2" min="0" max="59" class="layui-input" id="minute" value="{{minute}}"/>
</div>
<div class="layui-form-mid">分</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务状态<i class="layui-icon layui-icon-tips" lay-tips="禁用模式为直接删除"
lay-direction="1" lay-offset="0,-10px"></i></label>
{% if crontab_status %}
<div class="layui-input-block">
<input type="radio" name="crontab_status" value="1" title="激活" checked/>
<input type="radio" name="crontab_status" value="0" title="禁用"/>
</div>
{% else %}
<div class="layui-input-block">
<input type="radio" name="crontab_status" value="1" title="激活"/>
<input type="radio" name="crontab_status" value="0" title="禁用" checked/>
</div>
{% endif %}
</div>
<div class="layui-form-item">
<label class="layui-form-label">消息通知</label>
<div class="layui-inline-block" style="margin: 10px;">
{{ notifys|safe }}
<!--
<input type="checkbox" name="notifys[iyuu]" title="IYUU"/>
<input type="checkbox" name="notifys[telegram]" title="Telegram"/>
<input type="checkbox" name="notifys[email]" title="邮件"/>
-->
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-filter="signSubmit" lay-submit>&emsp;提交&emsp;</button>
</div>
</div>
</form>
<!-- //表单结束 -->
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">日志</div>
<div class="layui-card-body">
<blockquote class="layui-elem-quote layui-quote-nm">
<div id="log_view"></div>
</blockquote>
</div>
</div>
</div>
{% verbatim myblock %}
<script type="text/html" id="log_tpl">
{{# layui.each(d, function(index, item){ }}
<span>{{item}}</span>&nbsp;<br>
{{# }); }}
</script>
{% endverbatim myblock %}
<!-- js部分 -->
<script>
/**
* 根据ID获取DOM元素对象
* @param {string} id
*/
function getById(id) {
return document.getElementById(id);
}
var timer1=null;
layui.use(['layer', 'form', 'util', 'admin', 'xmSelect', 'formX', 'dropdown','laytpl'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
var laytpl = layui.laytpl;
form.render('radio');
form.render('checkbox');
form.on('submit(signSubmit)', function (data) {
layer.load(2);
//将页面全部复选框选中的值拼接到一个数组中
var arr_box = [];
$('input[type=checkbox]:checked').each(function() {
arr_box.push($(this).val());
});
data.field.notifys = arr_box
admin.req('cron/sign', data.field, function (data) {
layer.closeAll('loading');
if (data.code == 1) {
layer.msg(data.msg, {icon: 1});
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#signForm').attr('method'));
return false;
});
let joblog_action = function() {
let callback = null;
//站点
$.getJSON('/cron/loginfo?crontab_id={{crontab_id}}', function (d) {
let getTpl = log_tpl.innerHTML
,view = getById('log_view');
if (d.code !== 0) {
return;
}
let data = Object.keys(d.data).map(function (key){
console.log(d.data)
return d.data[key].message;
});
laytpl(getTpl).render(data, function(html) {
view.innerHTML = html;
});
//回调
callback !== null && typeof callback[0] === 'function' && callback[0]();
});
};
joblog_action()
//定时请求刷新
// timer1=setInterval(function(){
// joblog_action()
// },5000);
});
</script>

3
apps/cron/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,33 @@
# coding:utf-8
from django.urls import path
from .. import views, views_request
app_name = 'cron'
urlpatterns = [
# Resource asset path
#任务类型
path('jobtype', views.JobTypeListView.as_view()),
path('jobtype/add', views.JobTypeAddView.as_view()),
path('jobtype/edit', views.JobTypeEditView.as_view()),
path('jobtype/del', views.JobTypeDelView.as_view()),
path('jobtype/list', views_request.jobtype),
path('jobtypeinfo/list', views_request.jobtypeinfo),
#任务
path('job', views.JobListView.as_view()),
path('job/add', views.JobAddView.as_view()),
path('job/edit', views.JobEditView.as_view()),
path('job/del', views.JobDelView.as_view()),
path('job/list', views_request.job),
#签到任务
path('sign', views.SignJobView.as_view()),
#日志
path('log', views.LogListView.as_view()),
path('log/del', views.LogDelView.as_view()),
path('log/list', views_request.log),
path('loginfo', views_request.loginfo),
]

557
apps/cron/views.py Normal file
View File

@@ -0,0 +1,557 @@
from django.shortcuts import render
from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.conf import settings
import time
import sys
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
from django_apscheduler.jobstores import DjangoJobStore, register_job
from .models import JobType, Job
from notify.models import NotifyConfig
from .crontabs import *
import logging
import uuid
logger = logging.getLogger('django')
executors = {
# 执行器的线程与进程数
'default': ThreadPoolExecutor(10),
'processpool': ProcessPoolExecutor(10)
}
jobstores = {
'default': DjangoJobStore()
}
job_defaults = {
# 最近多久时间内允许存在的任务数
'misfire_grace_time': 10,
# 该定时任务允许最大的实例个数
'max_instances': 2,
# 是否运行一次最新的任务,当多个任务堆积时
#'coalesce': True,
#存在相同任务直接覆盖
'replace_existing': True,
}
# 1.实例化调度器
scheduler = BackgroundScheduler(timezone=settings.TIME_ZONE,job_defaults=job_defaults)
# 2.调度器使用DjangoJobStore()
scheduler.add_jobstore(DjangoJobStore(), "default")
# 5.开启定时任务
scheduler.start()
#try:
## 3.设置定时任务
## 另一种方式为每天固定时间执行任务,对应代码为
#@register_job(scheduler,"cron", second="*/10",id="test", replace_existing=True)
#def my_job():
## 这里写你要执行的任务
#format_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
#print(format_time)
## 5.开启定时任务
#scheduler.start()
#print("Scheduler started!")
#except Exception as e:
#print(e)
## 有错误就停止定时器
#scheduler.shutdown()
class JobTypeListView(LoginRequiredMixin,TemplateView):
"""
显示
"""
template_name = 'cron/jobtype.html'
def get(self, request, *args, **kwargs):
"""
"""
return super(JobTypeListView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
# 'data': self.ormdata_game
}
kwargs.update(context)
return super(JobTypeListView, self).get_context_data(**kwargs)
class JobTypeAddView(LoginRequiredMixin,TemplateView):
"""
添加
"""
template_name = 'cron/jobtypeadd.html'
#显示添加模板
def get(self, request, *args, **kwargs):
"""
"""
return super(JobTypeAddView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
}
kwargs.update(context)
return super(JobTypeAddView, self).get_context_data(**kwargs)
#数据提交接收方法
def post(self, request, *args, **kwargs):
"""
数据提交
"""
name = request.POST.get("name").strip()
type_id = request.POST.get("type_id").strip()
try:
ormdata = JobType.objects.create(name=name,
type_id=type_id,
)
ormdata.save()
response_data={"code":1,"msg":"添加成功"}
except:
ex_type, ex_val, ex_stack = sys.exc_info()
if 'UNIQUE' in str(ex_val):
response_data={"code":0,"msg":"存在相同记录"}
else:
response_data={"code":0,"msg":"操作失败,请联系管理员"}
return JsonResponse(response_data)
class JobTypeEditView(LoginRequiredMixin,TemplateView):
"""
编辑域名
"""
template_name = 'cron/jobtypeedit.html'
#显示添加模板
def get(self, request, *args, **kwargs):
"""
得到
"""
#记录ID
self._id = request.GET.get('id')
self.ormdata = JobType.objects.get(id=self._id)
return super(JobTypeEditView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
'id': self._id,
'name': self.ormdata.name,
'type_id': self.ormdata.type_id,
}
kwargs.update(context)
return super(JobTypeEditView, self).get_context_data(**kwargs)
#数据提交接收方法
def post(self, request, *args, **kwargs):
"""
数据提交
"""
name = request.POST.get("name").strip()
type_id = request.POST.get("type_id").strip()
_id = request.POST.get('id')
ormdata = JobType.objects.get(id=_id)
ormdata.name = name
ormdata.type_id = type_id
ormdata.save()
response_data={"code":1,"msg":"添加成功"}
return JsonResponse(response_data)
class JobTypeDelView(LoginRequiredMixin,TemplateView):
"""
删除
"""
def post(self, request, *args, **kwargs):
"""
数据提交
"""
#得到批量或者单个要删除的id
ids = request.POST.getlist("ids[]")
#print("ids====>",ids)
JobType.objects.filter(id__in=ids).delete()
#Site.objects.filter(id=i).delete()
response_data={"code":1,"msg":"操作成功"}
return JsonResponse(response_data)
#===================================================================
class JobListView(LoginRequiredMixin,TemplateView):
"""
显示
"""
template_name = 'cron/job.html'
def get(self, request, *args, **kwargs):
"""
"""
return super(JobListView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
# 'data': self.ormdata_game
}
kwargs.update(context)
return super(JobListView, self).get_context_data(**kwargs)
class JobAddView(LoginRequiredMixin,TemplateView):
"""
添加
"""
template_name = 'cron/jobadd.html'
#显示添加模板
def get(self, request, *args, **kwargs):
"""
"""
return super(JobAddView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
}
kwargs.update(context)
return super(JobAddView, self).get_context_data(**kwargs)
#数据提交接收方法
def post(self, request, *args, **kwargs):
"""
数据提交
"""
name = request.POST.get("name").strip()
jobtype_id = request.POST.get("job_type")
crontab_status = request.POST.get("crontab_status")
week = request.POST.get("week",0)
day = request.POST.get("day",0)
hour = request.POST.get("hour",0)
minute = request.POST.get("minute",0)
second = request.POST.get("second",0)
crontab_time_type = request.POST.get("select")
sites = request.POST.getlist("sites")
if crontab_time_type == 'day':
#每天几点几分运行
crontab_time = minute + ' ' + hour + '* * *'
try:
ormdata = Job.objects.create(name=name,
jobtype_id=jobtype_id,
crontab_time_type = crontab_time_type,
crontab_status = crontab_status,
week = week,
day = day,
hour = hour,
minute = minute,
second = second,
sites = ",".join(sites),
crontab_time = crontab_time,
crontab_id = uuid.uuid1()
)
ormdata.save()
response_data={"code":1,"msg":"添加成功"}
except:
ex_type, ex_val, ex_stack = sys.exc_info()
if 'UNIQUE' in str(ex_val):
response_data={"code":0,"msg":"存在相同记录"}
else:
response_data={"code":0,"msg":"操作失败,请联系管理员"}
return JsonResponse(response_data)
class JobEditView(LoginRequiredMixin,TemplateView):
"""
编辑域名
"""
template_name = 'cron/jobedit.html'
#显示添加模板
def get(self, request, *args, **kwargs):
"""
得到
"""
#记录ID
self._id = request.GET.get('id')
self.ormdata = JobType.objects.get(id=self._id)
return super(JobEditView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
'id': self._id,
'name': self.ormdata.name,
'type_id': self.ormdata.type_id,
}
kwargs.update(context)
return super(JobEditView, self).get_context_data(**kwargs)
#数据提交接收方法
def post(self, request, *args, **kwargs):
"""
数据提交
"""
name = request.POST.get("name").strip()
type_id = request.POST.get("type_id").strip()
_id = request.POST.get('id')
ormdata = JobType.objects.get(id=_id)
ormdata.name = name
ormdata.type_id = type_id
ormdata.save()
response_data={"code":1,"msg":"添加成功"}
return JsonResponse(response_data)
class JobDelView(LoginRequiredMixin,TemplateView):
"""
删除
"""
def post(self, request, *args, **kwargs):
"""
数据提交
"""
#得到批量或者单个要删除的id
ids = request.POST.getlist("ids[]")
#print("ids====>",ids)
Job.objects.filter(id__in=ids).delete()
#Site.objects.filter(id=i).delete()
response_data={"code":1,"msg":"操作成功"}
return JsonResponse(response_data)
#------------------------------
class SignJobView(LoginRequiredMixin,TemplateView):
"""
签到任务
"""
template_name = 'cron/sign.html'
#显示添加模板
def get(self, request, *args, **kwargs):
"""
得到
"""
return super(SignJobView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
html = []
notify_type = {'iyuu':'IYUU','telegram':'Telegram','email':'邮箱'}
#获取记录
sign_count = Job.objects.filter(jobtype_id=1000).count()
if sign_count == 0:
#获取通知配置
notifys_count = NotifyConfig.objects.count()
if notifys_count == 0:
html.append('<b><span style="color: red;"><font size="2">请先配置通知</font></span></b>')
else:
for d in NotifyConfig.objects.all():
html.append('<input type="checkbox" name="notifys[]" value="' + d.name +'" title="' + notify_type[d.name] + '"/>')
context = {'notifys':"".join(html),
}
else:
#获取通知配置
notifys_count = NotifyConfig.objects.count()
ormdata = Job.objects.get(jobtype_id=1000)
if notifys_count == 0:
html.append('<p>请先配置通知</p>')
else:
#notify_ormdata = NotifyConfig.objects.all()
for d in NotifyConfig.objects.all():
if ormdata.notifys == None:
html.append('<input type="checkbox" name="notifys[]" value="' + d.name +'" title="' + notify_type[d.name] + '"/>')
elif d.name in ormdata.notifys:
html.append('<input type="checkbox" name="notifys[]" value="' + d.name +'" title="' + notify_type[d.name] + '" checked />')
else:
html.append('<input type="checkbox" name="notifys[]" value="' + d.name +'" title="' + notify_type[d.name] + '"/>')
context = {
'id': ormdata.id,
'jobtype_id': ormdata.jobtype_id,
'hour':ormdata.hour,
'minute':ormdata.minute,
'crontab_status':ormdata.crontab_status,
'notifys':"".join(html),
'crontab_id': ormdata.crontab_id
}
kwargs.update(context)
return super(SignJobView, self).get_context_data(**kwargs)
#数据提交接收方法
def post(self, request, *args, **kwargs):
"""
数据提交
"""
_id = request.POST.get('id','')
crontab_status = request.POST.get("crontab_status")
hour = request.POST.get("hour")
minute = request.POST.get("minute")
#需要html配合
notifys = request.POST.getlist('notifys[]')
crontab_id = request.POST.get("crontab_id",'')
if crontab_id == "" or crontab_id == None:
crontab_id = str(uuid.uuid1())
jobtype_id = 1000
crontab_time_type = 'day'
#每天几点几分运行
crontab_time = minute + ' ' + hour + '* * *'
name = "签到"
#未配置消息通知不予通过
if len(notifys) == 0:
response_data={"code":0,"msg":"请配置消息通知!"}
return JsonResponse(response_data)
data_count = Job.objects.filter(jobtype_id=1000).count()
if data_count == 0:
ormdata = Job.objects.create(name=name,
crontab_status=crontab_status,
hour=hour,
minute=minute,
jobtype_id=jobtype_id,
crontab_time_type=crontab_time_type,
crontab_time=crontab_time,
crontab_id = crontab_id,
notifys = ",".join(notifys)
)
else:
ormdata = Job.objects.get(id=_id)
ormdata.crontab_status = crontab_status
ormdata.hour = hour
ormdata.minute = minute
ormdata.crontab_time = crontab_time
ormdata.notifys = ",".join(notifys)
ormdata.save()
crontab_id = ormdata.crontab_id
# 创建任务
#scheduler.add_job(test, 'cron', hour=hour, minute=minute, second=second, args=[s])
isJob = scheduler.get_job(crontab_id)
if isJob != None:
#激活
if crontab_status == '1':
#已经存在的任务不能做时间修改,必须删除在添加
scheduler.remove_job(crontab_id)
scheduler.add_job(sign, 'cron', hour=hour, minute=minute, id=crontab_id, args=[crontab_id])
else:
#禁用状态,直接删除任务
scheduler.remove_job(crontab_id)
#暂停任务
#scheduler.pause_job(crontab_id)
else:
#启用添加,禁用忽略
if crontab_status == '1':
scheduler.add_job(sign, 'cron', hour=hour, minute=minute, id=crontab_id, args=[crontab_id])
response_data={"code":1,"msg":"添加成功"}
return JsonResponse(response_data)
#===================================================================
class LogListView(LoginRequiredMixin,TemplateView):
"""
显示
"""
template_name = 'cron/log.html'
def get(self, request, *args, **kwargs):
"""
"""
return super(LogListView, self).get(request, *args, **kwargs)
#显示编辑模板
def get_context_data(self, **kwargs):
context = {
# 'data': self.ormdata_game
}
kwargs.update(context)
return super(LogListView, self).get_context_data(**kwargs)
class LogDelView(LoginRequiredMixin,TemplateView):
"""
删除
"""
def post(self, request, *args, **kwargs):
"""
数据提交
"""
#得到批量或者单个要删除的id
ids = request.POST.getlist("ids[]")
#print("ids====>",ids)
Log.objects.filter(id__in=ids).delete()
#Site.objects.filter(id=i).delete()
response_data={"code":1,"msg":"操作成功"}
return JsonResponse(response_data)

311
apps/cron/views_request.py Normal file
View File

@@ -0,0 +1,311 @@
#coding=utf-8
from django.http import JsonResponse
from django.db.models import Q
from django.contrib.auth.decorators import login_required
import datetime
from .models import JobType, Job, Log
#直接使用检查是否管理员
#from django.contrib.auth.decorators import user_passes_test
#@user_passes_test(lambda u: u.is_superuser)
@login_required
def jobtype(request):
"""
提供相应的数据
"""
#得到偏移量
pageIndex= int(request.GET.get('page',1))
#得到查询个数
pageSize = int(request.GET.get('limit',10))
#搜索字段
searchKey = request.GET.get('searchKey','')
searchValue = request.GET.get('searchValue','')
#去掉空格
searchValue = searchValue.strip()
#得到排序字段
sort = request.GET.get('sort','id')
#得到排序规则
order_by_type = request.GET.get('order','desc')
if order_by_type == 'desc':
order_by = sort
else:
order_by = '-' + sort
data = {}
data['code'] = 0
data['msg'] = ""
data['data'] = []
if searchValue == '':
data['count'] = JobType.objects.count()
if pageIndex == 1:
ormdata = JobType.objects.order_by(order_by)[:pageSize] #offset:limit
else:
ormdata = JobType.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
else:
data['count'] = JobType.objects.filter(Q(name__icontains=searchValue)).count()
if pageIndex == 1:
ormdata = JobType.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit
else:
ormdata = JobType.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
for i in ormdata:
data['data'].append({"id":i.id,
"name":i.name,
"type_id":i.type_id,
})
#返回json串
#return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json")
return JsonResponse(data)
#------------------------------------------
@login_required
def job(request):
"""
提供相应的数据
"""
#得到偏移量
pageIndex= int(request.GET.get('page',1))
#得到查询个数
pageSize = int(request.GET.get('limit',10))
#搜索字段
searchKey = request.GET.get('searchKey','')
searchValue = request.GET.get('searchValue','')
#去掉空格
searchValue = searchValue.strip()
#得到排序字段
sort = request.GET.get('sort','id')
#得到排序规则
order_by_type = request.GET.get('order','desc')
if order_by_type == 'desc':
order_by = sort
else:
order_by = '-' + sort
data = {}
data['code'] = 0
data['msg'] = ""
data['data'] = []
if searchValue == '':
data['count'] = Job.objects.count()
if pageIndex == 1:
ormdata = Job.objects.order_by(order_by)[:pageSize] #offset:limit
else:
ormdata = Job.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
else:
data['count'] = Job.objects.filter(Q(name__icontains=searchValue)).count()
if pageIndex == 1:
ormdata = Job.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit
else:
ormdata = Job.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
for i in ormdata:
data['data'].append({"id":i.id,
"name":i.name,
"jobtype_id":i.jobtype_id,
"crontab_time":i.crontab_time,
"crontab_status":i.crontab_status,
})
#返回json串
#return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json")
return JsonResponse(data)
#------------------------------------------
@login_required
def log(request):
"""
提供相应的数据
"""
#得到偏移量
pageIndex= int(request.GET.get('page',1))
#得到查询个数
pageSize = int(request.GET.get('limit',10))
#搜索字段
searchKey = request.GET.get('searchKey','')
searchValue = request.GET.get('searchValue','')
#去掉空格
searchValue = searchValue.strip()
#得到排序字段
sort = request.GET.get('sort','id')
#得到排序规则
order_by_type = request.GET.get('order','desc')
if order_by_type == 'desc':
order_by = sort
else:
order_by = '-' + sort
data = {}
data['code'] = 0
data['msg'] = ""
data['data'] = []
if searchValue == '':
data['count'] = Log.objects.count()
if pageIndex == 1:
ormdata = Log.objects.order_by(order_by)[:pageSize] #offset:limit
else:
ormdata = Log.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
else:
data['count'] = Log.objects.filter(Q(name__icontains=searchValue)).count()
if pageIndex == 1:
ormdata = Log.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit
else:
ormdata = Log.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
for i in ormdata:
data['data'].append({"id":i.id,
"name":i.name,
"type_id":i.type_id,
"crontab_id":i.crontab_id,
"site_name":i.site_name,
"message":i.message,
"created_at":i.created_at,
"status":i.status,
})
#返回json串
#return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json")
return JsonResponse(data)
#==================
@login_required
def jobtypeinfo(request):
"""
任务类型
xmSelect
"""
data = {}
data['code'] = 0
data['msg'] = ""
data['data'] = []
#得到排序字段
sort = request.GET.get('sort','type_id')
#得到排序规则
order_by_type = request.GET.get('order','desc')
if order_by_type == 'desc':
order_by = sort
else:
order_by = '-' + sort
#获取任务信息中是否配置了签到
qiandao_count = Job.objects.filter(jobtype_id=1000).count()
#获取所有记录
data['count'] = JobType.objects.count()
ormdata = JobType.objects.order_by(order_by).all()
for i in ormdata:
if qiandao_count == 0:
data['data'].append({
"name":i.name,
"type_id":i.type_id
})
else:
if i.type_id != 1000:
data['data'].append({
"name":i.name,
"type_id":i.type_id
})
return JsonResponse(data)
@login_required
def loginfo(request):
"""
任务日志
"""
data = {}
data['code'] = 0
data['msg'] = ""
data['data'] = []
#GET用于签到任务日志展示
if request.method == "GET":
#获取任务id
crontab_id = request.GET.get('crontab_id')
#得到排序字段
sort = request.GET.get('sort','created_at')
#得到排序规则
order_by_type = request.GET.get('order','desc')
if order_by_type == 'desc':
order_by = sort
else:
order_by = '-' + sort
if crontab_id == "" or crontab_id == None:
response_data={"code":0,"msg":"无任务记录"}
return JsonResponse(response_data)
#获取今天日期
today = datetime.datetime.today()
#获取所有记录
data['count'] = Log.objects.filter(crontab_id=crontab_id).filter(created_at__date=today).count()
ormdata = Log.objects.filter(crontab_id=crontab_id).filter(created_at__date=today).order_by(order_by)
for i in ormdata:
data['data'].append({'id':i.id,
"name":i.name,
"type_id":i.type_id,
"message":i.message
})
return JsonResponse(data)
#POST用于首页展示识别列表
if request.method == "POST":
order_by = 'created_at'
#获取今天日期
today = datetime.datetime.today()
#获取今天签到失败记录
data['count'] = Log.objects.filter(type_id=1000).filter(status=False).filter(created_at__date=today).count()
ormdata = Log.objects.filter(type_id=1000).filter(status=False).filter(created_at__date=today).order_by(order_by)
for i in ormdata:
data['data'].append({'id':i.id,
"name":i.site_name,
"message":i.message
})
return JsonResponse(data)

View File

3
apps/dashboard/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/dashboard/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class DashboardConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'dashboard'

View File

3
apps/dashboard/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,184 @@
{% load static %}
<style>
/** 最新动态时间线 */
.layui-timeline-dynamic .layui-timeline-item {
padding-bottom: 0;
}
.layui-timeline-dynamic .layui-timeline-item:before {
top: 16px;
}
.layui-timeline-dynamic .layui-timeline-axis {
width: 9px;
height: 9px;
left: 1px;
top: 7px;
background-color: #cbd0db;
}
.layui-timeline-dynamic .layui-timeline-axis.active {
background-color: #0c64eb;
box-shadow: 0 0 0 2px rgba(12, 100, 235, .3);
}
</style>
<!-- 正文开始 -->
<div class="layui-fluid ew-console-wrapper">
<div class="layui-row layui-col-space15">
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
PT站数量<span class="layui-badge layui-badge-green pull-right">个</span>
</div>
<div class="layui-card-body">
<p class="lay-big-font">{{sites_num}}</p>
</div>
</div>
</div>
<!--
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
今日签到成功<span class="layui-badge layui-badge-blue pull-right">个</span>
</div>
<div class="layui-card-body">
<p class="lay-big-font">1</p>
</div>
</div>
</div>
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
今日签到失败<span class="layui-badge layui-badge-red pull-right">个</span>
</div>
<div class="layui-card-body">
<p class="lay-big-font">1</p>
</div>
</div>
</div>
<div class="layui-col-xs12 layui-col-sm6 layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">
总做种下载量<span class="layui-badge layui-badge-red pull-right">GB</span>
</div>
<div class="layui-card-body">
<p>做种<span class="pull-right">1G</span></p>
<p>下载<span class="pull-right">1G</span></p>
</div>
</div>
</div>
-->
</div>
<div class="layui-row layui-col-space15">
<div class="layui-col-sm6">
<div class="layui-card">
<div class="layui-card-header">今日签到失败信息</div>
<div class="layui-card-body">
<table class="layui-table" id="signFailTable" lay-filter="signFailTable"></table>
</div>
</div>
</div>
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">已知问题</div>
<div class="layui-card-body dynamic-card-body mini-bar" style="height: 265px;">
<ul class="layui-timeline layui-timeline-dynamic">
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis"></i>
<div class="layui-timeline-content layui-text">
<div class="layui-timeline-title">学校由于CF干扰,需要同一浏览器访问后在执行补签
</div>
</li>
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis active"></i>
<div class="layui-timeline-content layui-text">
<div class="layui-timeline-title">暂不支持全站备份和恢复
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="signFailTableBar">
<a class="layui-btn layui-btn-sm layui-btn-danger" lay-event="signAgain">补签</a>
</script>
<!-- js部分 -->
<script>
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
// 渲染表格
var insTb = tableX.render({
elem: '#signFailTable',
url: setter.base_server + 'cron/loginfo',
method: 'POST',
page: true,
limit:20,
limits:[20,50,100],
cols: [[
{type: 'checkbox'},
{field: 'id', title: 'ID',hide:true},
{field: 'name', sort: true, title: '站点'},
{field: 'message', title: '失败原因'},
{align: 'center', toolbar: '#signFailTableBar', title: '操作'}
]]
});
// 工具条点击事件
table.on('tool(signFailTable)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
if(layEvent === 'signAgain'){
//var csrftoken = getCookie('csrftoken');
layer.confirm('确定对 ' + data.name + '进行补签操作?',{icon: 0, title: '提示', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
$.ajax({
url : "common/signagain",
type: 'POST',
dataType : "json",
data: {'id':data.id,'action':'again', 'name':data.name, csrfmiddlewaretoken: '{{ csrf_token }}' },
// beforeSend: function(request) {
// //django需要csrf验证,Forbidden (CSRF cookie not set.):
// request.setRequestHeader("X-CSRFToken", csrftoken);
// },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('signFailTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
});
</script>

3
apps/dashboard/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,12 @@
# coding:utf-8
from django.urls import path
from .. import views
app_name = 'dashboard'
urlpatterns = [
# Resource asset url
path('', views.DashboardListView.as_view(), name='dashboard'),
]

23
apps/dashboard/views.py Normal file
View File

@@ -0,0 +1,23 @@
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
from sites.models import SiteInfo
# Create your views here.
class DashboardListView(LoginRequiredMixin, TemplateView):
"""
打开首页的控制台
"""
template_name = 'dashboard/dashboard.html'
def get_context_data(self, **kwargs):
#配置站点总数
sites_count = SiteInfo.objects.count()
context = {
'sites_num': sites_count,
# 'action': 'Asset list',
}
kwargs.update(context)
return super(DashboardListView, self).get_context_data(**kwargs)

16
apps/global_settings.py Normal file
View File

@@ -0,0 +1,16 @@
from django.conf import settings
"""
模板直接使用settings变量
"""
def get_global_settings(request):
context = {
'SITE_NAME': settings.SITE_NAME,
'SITE_URL': settings.SITE_URL,
'SITE_VERSION': settings.SITE_VERSION,
'SITE_COPYRIGHT': settings.SITE_COPYRIGHT,
}
return context

0
apps/index/__init__.py Normal file
View File

3
apps/index/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/index/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class IndexConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'index'

View File

3
apps/index/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,84 @@
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-lg12 layui-col-md12">
<div class="layui-card">
<div class="layui-card-header"><h2>所有被管理主机需要安装agent</h2></div>
<div class="layui-card-body">
<ul class="layui-timeline">
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis">&#xe63f;</i>
<div class="layui-timeline-content layui-text">
<h3 class="layui-timeline-title">
Centos 安装
<small>需要使用自定义yum源</small>&emsp;
</h3>
<ul>
<li>yum install python36-setuptools python36-django python36-osquery
python36-requests python36-simplejson python36-djangorestframework
python36-gunicorn python36-greenlet python36-gevent python36-supervisor osquery</li>
<li>yum install opsadmin-agent</li>
</ul>
</div>
</li>
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis">&#xe63f;</i>
<div class="layui-timeline-content layui-text">
<h3 class="layui-timeline-title">
Debian 安装
<small>需要使用自定义apt源</small>&emsp;
</h3>
<ul>
<li>apt-get install python3-setuptools python3-django python3-osquery
python3-requests python3-simplejson python3-djangorestframework
python3-gunicorn python3-greenlet python3-gevent python3-supervisor osquery</li>
<li>apt-get install opsadmin-agent</li>
</ul>
</div>
</li>
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis">&#xe63f;</i>
<div class="layui-timeline-content layui-text">
<h3 class="layui-timeline-title">
Agent启动方式
</h3>
<ul>
<li>systemctl start supervisord</li>
<li>启动后使用端口20080,访问http://ip:20080检查是否安装成功</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">更新日志</div>
<div class="layui-card-body">
<ul class="layui-timeline">
<li class="layui-timeline-item">
<i class="layui-icon layui-timeline-axis">&#xe63f;</i>
<div class="layui-timeline-content layui-text">
<h3 class="layui-timeline-title">
V1.0.0
<small>基础功能实现</small>&emsp;
<span class="layui-badge-rim">2019-01-08</span>
</h3>
<ul>
<li>网易云ssh秘钥管理,直接可以操作网易云平台</li>
<li>网易云VPC管理,只支持同步查看</li>
<li>网易云DNS管理,支持同步和操作网易云平台</li>
<li>根据项目分类云实例,并后台同步云实例</li>
<li>对实例进行主机名修改以及DNS记录同步</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1 @@
{% extends 'base.html' %}

View File

@@ -0,0 +1,426 @@
<div class="lock-screen-wrapper">
<div class="lock-screen-time"></div>
<div class="lock-screen-date"></div>
<div class="lock-screen-form">
<input placeholder="输入123456解锁" class="lock-screen-psw" maxlength="20" type="password"/>
<i class="layui-icon layui-icon-right lock-screen-enter"></i>
<br/>
<div class="lock-screen-tip"></div>
</div>
<div class="lock-screen-tool">
<div class="lock-screen-tool-item">
<i class="layui-icon layui-icon-logout" ew-event="logout" data-confirm="false"
data-url="components/template/login/login.html"></i>
<div class="lock-screen-tool-tip">退出登录</div>
</div>
</div>
</div>
<script>
layui.use(['util', 'admin'], function () {
var $ = layui.jquery;
var util = layui.util;
var admin = layui.admin;
var rightPsw = 'e10adc3949ba59abbe56e057f20f883e'; // 正确密码的MD5密文
// 获取各个组件
var $form = $('.lock-screen-wrapper .lock-screen-form');
var $psw = $form.find('.lock-screen-psw');
var $tip = $form.find('.lock-screen-tip');
var $time = $('.lock-screen-wrapper .lock-screen-time');
var $date = $('.lock-screen-wrapper .lock-screen-date');
// 监听enter键
$(window).keydown(function (event) {
if (admin.isLockScreen) {
if (event.keyCode === 13) {
doVer();
} else if (event.keyCode === 8 && !$psw.val()) {
restForm();
if (event.preventDefault) event.preventDefault();
if (event.returnValue) event.returnValue = false;
}
}
});
// 监听输入
$psw.on('input', function () {
var psw = $psw.val();
if (rightPsw === toMd5Hex(psw)) {
restForm();
admin.unlockScreen();
} else if (psw) {
$form.removeClass('show-back');
$tip.text('');
} else {
$form.addClass('show-back');
}
});
// 监听按钮点击
$form.find('.lock-screen-enter').click(function (e) {
doVer(true);
});
// 处理事件
function doVer(emptyRest) {
if ($form.hasClass('show-psw')) {
$psw.focus();
var psw = $psw.val();
if (!psw) {
emptyRest ? restForm() : $tip.text('请输入解锁密码');
} else if (rightPsw === toMd5Hex(psw)) {
restForm();
admin.unlockScreen();
} else {
$psw.val('');
$tip.text('密码不正确');
$form.addClass('show-back');
}
} else {
$form.addClass('show-psw show-back');
$psw.focus();
}
}
// 重置
function restForm() {
$psw.blur();
$psw.val('');
$tip.text('');
$form.removeClass('show-psw show-back');
}
// 时间、日期
function setDate() {
var date = new Date();
$time.text(util.toDateString(date, 'HH:mm'));
var weeks = ['日', '一', '二', '三', '四', '五', '六'];
$date.text(util.toDateString(date, 'MM月dd日,星期') + weeks[date.getDay()]);
}
setDate();
setInterval(function () {
if (admin.isLockScreen) setDate();
}, 1000);
});
</script>
<style>
.lock-screen-wrapper {
color: #ffffff;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 50px 60px 0 60px;
background-color: #000000;
background-image: url("assets/images/bg-screen.jpg");
background-repeat: no-repeat;
background-size: cover;
position: relative;
}
.lock-screen-time {
font-size: 88px;
}
.lock-screen-date {
font-size: 24px;
padding: 0 0 0 13px;
}
.lock-screen-form {
position: absolute;
left: 0;
right: 0;
top: 65%;
width: 100%;
text-align: center;
}
.lock-screen-psw {
color: #ffffff;
font-size: 22px;
width: 0;
height: 0;
line-height: 40px;
border-radius: 40px;
border: 2px solid transparent;
background-color: transparent;
box-sizing: border-box;
vertical-align: middle;
-webkit-transition: all .3s;
transition: all .3s;
text-align: center;
}
.lock-screen-psw::-webkit-input-placeholder {
color: #F6F6F6;
font-size: 16px;
}
.lock-screen-psw::-moz-placeholder {
color: #F6F6F6;
font-size: 16px;
}
.lock-screen-psw::-ms-input-placeholder {
color: #F6F6F6;
font-size: 16px;
}
.lock-screen-enter {
width: 65px;
height: 65px;
line-height: 1;
font-size: 28px;
padding-top: 18px;
border: 2px solid #ffffff;
border-radius: 50%;
text-align: center;
display: inline-block;
box-sizing: border-box;
vertical-align: middle;
transition: all .3s;
cursor: pointer;
}
.lock-screen-enter:hover {
background-color: rgba(255, 255, 255, .3);
}
.lock-screen-form.show-psw .lock-screen-psw {
height: 40px;
width: 180px;
padding: 0 5px;
margin-right: 10px;
border-color: #ffffff;
background-color: rgba(255, 255, 255, .3);
}
.lock-screen-form.show-psw .lock-screen-enter {
width: 40px;
height: 40px;
font-size: 20px;
padding-top: 8px;
}
.lock-screen-form.show-back .lock-screen-enter:before {
content: "\e603";
}
.lock-screen-tip {
color: red;
width: 230px;
padding: 0 3px;
font-size: 14px;
text-align: left;
box-sizing: border-box;
display: none;
}
.lock-screen-form.show-psw .lock-screen-tip {
display: inline-block;
}
.lock-screen-tool {
width: 20px;
position: absolute;
right: 20px;
bottom: 20px;
}
.lock-screen-tool .lock-screen-tool-item {
position: relative;
width: 20px;
height: 20px;
line-height: 20px;
margin-top: 15px;
cursor: pointer;
}
.lock-screen-tool .lock-screen-tool-item:hover .layui-icon {
color: #ffffff;
}
.lock-screen-tool .layui-icon {
font-size: 20px;
}
.lock-screen-tool .lock-screen-tool-item .lock-screen-tool-tip {
position: absolute;
top: 50%;
right: 100%;
height: 24px;
line-height: 24px;
width: 60px;
width: max-content;
text-align: center;
margin-right: 10px;
margin-top: -12px;
font-size: 12px;
padding: 0 8px;
color: #eee;
border-radius: 3px;
background-color: rgba(255, 255, 255, .3);
word-break: break-all;
display: none;
}
.lock-screen-tool .lock-screen-tool-item:hover .lock-screen-tool-tip {
display: inline-block;
}
.lock-screen-tool .lock-screen-tool-item .lock-screen-tool-tip:before {
content: "";
border: 4px solid transparent;
border-left-color: rgba(255, 255, 255, .3);
position: absolute;
right: -8px;
top: 50%;
margin-top: -4px;
}
</style>
<script>
/** MD5加密 */
function toMd5Hex(text) {
var hexcase = 0;
var chrsz = 8;
function core_md5(x, len) {
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for (var i = 0; i < x.length; i += 16) {
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
function md5_cmn(q, a, b, x, s, t) {
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
}
function md5_ff(a, b, c, d, x, s, t) {
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t) {
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t) {
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t) {
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
function safe_add(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function bit_rol(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
function str2binl(str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz)
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32);
return bin;
}
function binl2hex(binarray) {
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
}
return str;
}
return binl2hex(core_md5(str2binl(text), text.length * chrsz));
}
</script>

View File

@@ -0,0 +1,252 @@
<div class="layui-card" style="box-shadow: none;border: none;">
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title" style="text-align: center;">
<li class="layui-this">通知(5)</li>
<li>私信(12)</li>
<li>待办(3)</li>
</ul>
<div class="layui-tab-content" style="padding: 0;">
<!-- tab1 -->
<div class="layui-tab-item layui-show">
<div class="message-list">
<!-- 实际项目请使用后台数据循环出来 -->
<a class="message-list-item" href="javascript:;">
<i class="layui-icon layui-icon-speaker message-item-icon"></i>
<div class="message-item-right">
<h2 class="message-item-title">你收到了14份新周报</h2>
<p class="message-item-text">10个月前</p>
</div>
</a>
<a class="message-list-item" href="javascript:;">
<i class="layui-icon layui-icon-speaker message-item-icon"></i>
<div class="message-item-right">
<h2 class="message-item-title">你收到了14份新周报</h2>
<p class="message-item-text">10个月前</p>
</div>
</a>
<a class="message-list-item" href="javascript:;">
<i class="layui-icon layui-icon-speaker message-item-icon"></i>
<div class="message-item-right">
<h2 class="message-item-title">你收到了14份新周报</h2>
<p class="message-item-text">10个月前</p>
</div>
</a>
</div>
<!-- 列表为空 -->
<div class="message-list-empty">
<i class="layui-icon layui-icon-notice"></i>
<div>没有通知</div>
</div>
<a id="messageClearBtn1" class="message-btn-clear">全部标记已读</a>
</div>
<!-- tab2 -->
<div class="layui-tab-item">
<div class="message-list">
<a class="message-list-item" href="javascript:;">
<img class="message-item-icon" src="assets/images/head.jpg" alt="">
<div class="message-item-right">
<h2 class="message-item-title">xx评论了你</h2>
<p class="message-item-text">哈哈哈哈哈哈</p>
<p class="message-item-text">10个月前</p>
</div>
</a>
<a class="message-list-item" href="javascript:;">
<img class="message-item-icon" src="assets/images/head.jpg" alt="">
<div class="message-item-right">
<h2 class="message-item-title">xx评论了你</h2>
<p class="message-item-text">哈哈哈哈哈哈</p>
<p class="message-item-text">10个月前</p>
</div>
</a>
<a class="message-list-item" href="javascript:;">
<img class="message-item-icon" src="assets/images/head.jpg" alt="">
<div class="message-item-right">
<h2 class="message-item-title">xx评论了你</h2>
<p class="message-item-text">哈哈哈哈哈哈</p>
<p class="message-item-text">10个月前</p>
</div>
</a>
<a id="messageMoreBtn2" class="message-btn-more">加载更多</a>
</div>
<!-- 列表为空 -->
<div class="message-list-empty">
<i class="layui-icon layui-icon-dialogue"></i>
<div>没有消息</div>
</div>
<a id="messageClearBtn2" class="message-btn-clear">清空消息</a>
</div>
<!-- tab3 -->
<div class="layui-tab-item">
<div class="message-list">
<a class="message-list-item" href="javascript:;">
<span class="layui-badge layui-badge-yellow">待完成</span>
<div class="message-item-right">
<h2 class="message-item-title">你收到了14份新周报</h2>
<p class="message-item-text">10个月前</p>
</div>
</a>
<a class="message-list-item" href="javascript:;">
<span class="layui-badge layui-badge-green">已完成</span>
<div class="message-item-right">
<h2 class="message-item-title">你收到了14份新周报</h2>
<p class="message-item-text">10个月前</p>
</div>
</a>
<a class="message-list-item" href="javascript:;">
<span class="layui-badge layui-badge-red">未完成</span>
<div class="message-item-right">
<h2 class="message-item-title">你收到了14份新周报</h2>
<p class="message-item-text">10个月前</p>
</div>
</a>
</div>
<!-- 列表为空 -->
<div class="message-list-empty">
<i class="layui-icon layui-icon-flag"></i>
<div>没有待办</div>
</div>
<a id="messageClearBtn3" class="message-btn-clear">清空待办</a>
</div>
</div>
</div>
</div>
<script>
layui.use(['element', 'admin'], function () {
var $ = layui.jquery;
var admin = layui.admin;
/* 加载更多按钮点击事件 */
$('#messageMoreBtn2').click(function () {
var $that = $(this);
admin.btnLoading($that);
setTimeout(function () {
admin.btnLoading($that, false);
$that.before($that.prev()[0].outerHTML);
}, 300);
});
/* 清空消息点击事件 */
$('#messageClearBtn1,#messageClearBtn2,#messageClearBtn3').click(function () {
$(this).parents('.layui-tab-item').addClass('show-empty');
});
});
</script>
<style>
/** 消息列表样式 */
.message-list {
position: absolute;
top: 48px;
left: 0;
right: 0;
bottom: 45px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.message-list-item {
display: block;
padding: 10px 20px;
line-height: 24px;
position: relative;
border-bottom: 1px solid #e8e8e8;
}
.message-list-item:hover, .message-btn-clear:hover, .message-btn-more:hover {
background: #F2F2F2;
}
.message-list-item .message-item-icon {
width: 40px;
height: 40px;
line-height: 40px;
margin-top: -20px;
border-radius: 50%;
position: absolute;
left: 20px;
top: 50%;
}
.message-list-item .message-item-icon.layui-icon {
color: #fff;
font-size: 22px;
text-align: center;
background-color: #FE5D58;
}
.message-list-item .message-item-icon + .message-item-right {
margin-left: 55px;
}
.message-list-item .message-item-title {
color: #666;
font-size: 14px;
}
.message-list-item .message-item-text {
color: #999;
font-size: 12px;
}
.message-list-item > .layui-badge {
position: absolute;
right: 20px;
top: 12px;
}
.message-list-item > .layui-badge + .message-item-right {
margin-right: 50px;
}
.message-btn-clear, .message-btn-more {
color: #666;
display: block;
padding: 10px 5px;
line-height: 24px;
text-align: center;
cursor: pointer;
}
.message-btn-clear {
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-top: 1px solid #e8e8e8;
}
.message-btn-more {
color: #666;
font-size: 13px;
}
.message-btn-more.ew-btn-loading > .ew-btn-loading-text {
font-size: 13px !important;
}
.message-list-empty {
color: #999;
padding: 100px 0;
text-align: center;
display: none;
}
.message-list-empty > .layui-icon {
color: #ccc;
display: block;
font-size: 45px;
margin-bottom: 15px;
}
.show-empty .message-list-empty {
display: block;
}
.show-empty .message-btn-clear, .show-empty .message-list {
display: none;
}
/** //消息列表样式结束 */
</style>

View File

@@ -0,0 +1,210 @@
<div class="layui-card-header">本地便签</div>
<div class="note-wrapper"></div>
<div class="note-empty">
<i class="layui-icon layui-icon-face-surprised"></i>
<p>没有便签</p>
</div>
<div class="btn-circle" id="noteAddBtn" title="添加便签" style="position: absolute;">
<i class="layui-icon layui-icon-add-1"></i>
</div>
<script>
layui.use(['layer', 'form', 'util', 'admin'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var util = layui.util;
var admin = layui.admin;
var dataList = []; // 便签列表
var $noteWrapper = $('.note-wrapper');
/* 渲染列表 */
function renderList() {
$noteWrapper.empty();
dataList = layui.data(layui.setter.tableName).notes;
if (dataList === undefined) dataList = [];
for (var i = 0; i < dataList.length; i++) {
var item = dataList[i];
var str = '<div class="note-item" data-id="' + item.id + '">';
str += ' <div class="note-item-content">' + item.content + '</div>';
str += ' <div class="note-item-time">' + item.time + '</div>';
str += ' <i class="layui-icon layui-icon-close-fill note-item-del"></i>';
str += ' </div>';
$noteWrapper.prepend(str);
}
$('.note-empty').css('display', dataList.length === 0 ? 'block' : 'none');
// 点击修改
$('.note-item').click(function () {
var index = parseInt($(this).attr('data-id'));
showNote(dataList[index]);
});
// 点击删除
$('.note-item-del').click(function (e) {
var id = parseInt($(this).parent().attr('data-id'));
layer.confirm('确认删除吗?', {
skin: 'layui-layer-admin',
shade: .1,
shadeClose: true
}, function (index) {
layer.close(index);
dataList.splice(id, 1);
for (var i = 0; i < dataList.length; i++) {
dataList[i].id = i;
}
putDataList();
renderList();
});
e.stopPropagation();
});
}
renderList();
/* 添加 */
$('#noteAddBtn').click(function () {
showNote();
});
// 显示编辑弹窗
function showNote(data) {
var id = data ? data.id : undefined, content = data ? data.content : '';
admin.open({
id: 'layer-note-edit',
title: '便签',
type: 1,
area: 'auto',
offset: '50px',
shadeClose: true,
content: '<textarea id="noteEditText" placeholder="请输入内容" style="width: 280px;height: 150px;border: none;color: #666666;word-wrap: break-word;padding: 10px 20px;resize: vertical;">' + content + '</textarea>',
success: function () {
$('#noteEditText').change(function () {
content = $(this).val();
});
},
end: function () {
if (id !== undefined) {
if (!content) {
dataList.splice(id, 1);
for (var i = 0; i < dataList.length; i++) {
dataList[i].id = i;
}
} else if (content !== dataList[id].content) {
dataList[id].content = content;
dataList[id].time = util.toDateString(new Date(), 'yyyy-MM-dd HH:mm');
}
} else if (content) {
dataList.push({
id: dataList.length,
content: content,
time: util.toDateString(new Date(), 'yyyy-MM-dd HH:mm')
});
}
putDataList();
renderList();
}
});
}
/* 更新本地缓存 */
function putDataList() {
layui.data(layui.setter.tableName, {key: 'notes', value: dataList});
}
});
</script>
<style>
.note-wrapper {
padding: 15px 0 15px 15px;
background-color: #fbfbfb;
position: absolute;
top: 43px;
left: 0;
right: 0;
bottom: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.note-wrapper .note-item {
display: inline-block;
width: 110px;
padding: 12px;
cursor: pointer;
position: relative;
border-radius: 8px;
margin: 0 15px 15px 0;
border: 1px solid #eeeeee;
background-color: #ffffff;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transition: all .3s ease;
-moz-transition: all .3s ease;
-ms-transition: all .3s ease;
-o-transition: all .3s ease;
transition: all .3s ease;
}
.note-wrapper .note-item:hover {
box-shadow: 0 0 8px rgba(0, 0, 0, .05);
-webkit-transform: scale(1.02);
-moz-transform: scale(1.02);
-ms-transform: scale(1.02);
-o-transform: scale(1.02);
transform: scale(1.02);
}
.note-wrapper .note-item .note-item-content {
color: #666;
height: 80px;
font-size: 14px;
overflow: hidden;
word-break: break-all;
}
.note-wrapper .note-item .note-item-time {
color: #999;
font-size: 12px;
margin-top: 8px;
}
.note-wrapper .note-item .note-item-del {
position: absolute;
top: 2px;
right: 2px;
color: #FF5722;
font-size: 24px;
height: 24px;
width: 24px;
background-color: #fff;
border-radius: 50%;
visibility: hidden;
-webkit-transition: all .3s ease;
-moz-transition: all .3s ease;
-ms-transition: all .3s ease;
-o-transition: all .3s ease;
transition: all .3s ease;
opacity: 0;
}
.note-wrapper .note-item:hover .note-item-del {
visibility: visible;
opacity: 1;
}
.note-empty {
color: #999;
padding: 80px 0;
text-align: center;
display: none;
position: relative;
z-index: 1;
}
.note-empty .layui-icon {
font-size: 60px;
margin-bottom: 10px;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,54 @@
<form class="layui-form model-form">
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">原始密码</label>
<div class="layui-input-block">
<input type="password" name="oldPsw" placeholder="请输入原始密码" class="layui-input"
lay-verType="tips" lay-verify="required" required/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">新密码</label>
<div class="layui-input-block">
<input type="password" name="newPsw" placeholder="请输入新密码" class="layui-input"
id="pswDialogEdt" lay-verType="tips" lay-verify="required|psw" required/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label layui-form-required">确认密码</label>
<div class="layui-input-block">
<input type="password" name="rePsw" placeholder="请再次输入新密码" class="layui-input"
lay-verType="tips" lay-verify="required|equalTo" lay-equalTo="#pswDialogEdt" required/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block text-right">
<button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">取消</button>
<button class="layui-btn" lay-filter="submit-psw" lay-submit>保存</button>
</div>
</div>
</form>
<!-- js部分 -->
<script>
layui.use(['layer', 'form', 'formX', 'admin'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var admin = layui.admin;
// 监听提交
form.on('submit(submit-psw)', function (data) {
var loadIndex = layer.load(1);
admin.req('/main/password', data.field, function (res) {
layer.close(loadIndex);
if (res.code === 0) {
layer.msg(res.msg, {icon: 1});
admin.closeDialog('[lay-filter="submit-psw"]');
} else {
layer.msg(res.msg, {icon: 2});
}
}, 'put');
return false;
});
});
</script>

3
apps/index/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,15 @@
# coding:utf-8
from django.urls import path
from .. import views
app_name = 'index'
urlpatterns = [
# Resource asset url
path('', views.IndexView.as_view()),
path('index/theme', views.IndexThemeView.as_view()),
path('index/note', views.IndexNoteView.as_view()),
path('index/message', views.IndexMessageView.as_view()),
path('index/about', views.IndexAboutView.as_view()),
]

98
apps/index/views.py Normal file
View File

@@ -0,0 +1,98 @@
from django.views.generic import TemplateView
from django.utils import timezone
from django.db.models import Count
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.http import HttpResponse, JsonResponse
from django.conf import settings
class IndexView(LoginRequiredMixin,TemplateView):
template_name = 'index/index.html'
def get(self, request, *args, **kwargs):
return super(IndexView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
# 'html': " ".join(self.html),
# 'key': settings.HTML_HREF_REPLACE_KEY,
# 'tag': self.group_tag
}
kwargs.update(context)
return super(IndexView, self).get_context_data(**kwargs)
class IndexThemeView(LoginRequiredMixin,TemplateView):
template_name = 'index/tpl/theme.html'
def get(self, request, *args, **kwargs):
return super(IndexThemeView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
# 'html': " ".join(self.html),
# 'key': settings.HTML_HREF_REPLACE_KEY,
# 'tag': self.group_tag
}
kwargs.update(context)
return super(IndexThemeView, self).get_context_data(**kwargs)
class IndexNoteView(LoginRequiredMixin,TemplateView):
template_name = 'index/tpl/note.html'
def get(self, request, *args, **kwargs):
return super(IndexNoteView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
# 'html': " ".join(self.html),
# 'key': settings.HTML_HREF_REPLACE_KEY,
# 'tag': self.group_tag
}
kwargs.update(context)
return super(IndexNoteView, self).get_context_data(**kwargs)
class IndexMessageView(LoginRequiredMixin,TemplateView):
template_name = 'index/tpl/message.html'
def get(self, request, *args, **kwargs):
return super(IndexMessageView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
# 'html': " ".join(self.html),
# 'key': settings.HTML_HREF_REPLACE_KEY,
# 'tag': self.group_tag
}
kwargs.update(context)
return super(IndexMessageView, self).get_context_data(**kwargs)
class IndexAboutView(LoginRequiredMixin,TemplateView):
template_name = 'index/about.html'
def get(self, request, *args, **kwargs):
return super(IndexAboutView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
# 'html': " ".join(self.html),
# 'key': settings.HTML_HREF_REPLACE_KEY,
# 'tag': self.group_tag
}
kwargs.update(context)
return super(IndexAboutView, self).get_context_data(**kwargs)

View File

@@ -0,0 +1,2 @@
default_app_config = 'initdata.apps.InitdataConfig'

3
apps/initdata/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

13
apps/initdata/apps.py Normal file
View File

@@ -0,0 +1,13 @@
from django.apps import AppConfig
from django.db.models.signals import post_migrate
def do_init_data(sender, **kwargs):
from initdata.initDatas import init_datas
init_datas()
class InitdataConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'initdata'
def ready(self):
post_migrate.connect(do_init_data, sender=self)

126
apps/initdata/initDatas.py Normal file
View File

@@ -0,0 +1,126 @@
from myauth.models import User
from django.contrib.auth.hashers import make_password
from sites.models import SiteConfig
from cron.models import JobType
from notify.models import MailType
def init_datas():
"""
初始化数据库
"""
print('开始初始化用户数据库...')
if User.objects.count() == 0:
#添加管理员
User.objects.create(name='admin',
nickname='admin',
email = 'admin@admin.com',
password = make_password('123456'),
mobile='13911111111',
is_superuser=True,
is_active=True
)
print('初始化管理员成功.')
else:
print("管理员已经存在,忽略...")
print('开始初始化站点配置...')
sites_data = [{'name':'hdfans','name_cn':'红豆饭','index_url':'https://hdfans.org'},
{'name':'1ptba','name_cn':'壹PT吧','index_url':'https://1ptba.com'},
{'name':'ptchina','name_cn':'铂金学院','index_url':'https://ptchina.org'},
{'name':'hdchina','name_cn':'瓷器','index_url':'https://hdchina.org'},
{'name':'hdmayi','name_cn':'小蚂蚁','index_url':'http://hdmayi.com'},
{'name':'msg','name_cn':'马杀鸡','index_url':'https://pt.msg.vg'},
{'name':'beitai','name_cn':'备胎','index_url':'https://www.beitai.pt'},
{'name':'oshen','name_cn':'奥申','index_url':'http://www.oshen.win'},
{'name':'avgv','name_cn':'爱薇','index_url':'http://avgv.cc'},
{'name':'eastgame','name_cn':'吐鲁番','index_url':'https://pt.eastgame.org'},
{'name':'keepfrds','name_cn':'朋友','index_url':'https://pt.keepfrds.com'},
{'name':'tjupt','name_cn':'北洋园','index_url':'https://tjupt.org'},
{'name':'itzmx','name_cn':'分享站','index_url':'https://pt.itzmx.com'},
{'name':'greatposterwall','name_cn':'海豹','index_url':'https://greatposterwall.com'},
{'name':'hd','name_cn':'海带','index_url':'https://www.hd.ai'},
{'name':'m-team','name_cn':'馒头','index_url':'https://kp.m-team.cc'},
{'name':'lemonhd','name_cn':'柠檬','index_url':'https://lemonhd.org'},
{'name':'btschool','name_cn':'学校','index_url':'https://pt.btschool.club'},
{'name':'pthome','name_cn':'铂金家','index_url':'https://pthome.net'},
{'name':'totheglory','name_cn':'听听歌','index_url':'https://totheglory.im'},
{'name':'hdhome','name_cn':'家园','index_url':'https://hdhome.org'},
{'name':'pterclub','name_cn':'猫站','index_url':'https://pterclub.com'},
{'name':'ourbits','name_cn':'我堡','index_url':'https://ourbits.club'},
{'name':'hdsky','name_cn':'天空','index_url':'https://hdsky.me'},
{'name':'hares','name_cn':'白兔','index_url':'https://club.hares.top'},
{'name':'et8','name_cn':'他吹吹风','index_url':'https://et8.org'},
{'name':'audiences','name_cn':'观众','index_url':'https://audiences.me'},
{'name':'hdcity','name_cn':'城市','index_url':'https://hdcity.city'},
{'name':'open','name_cn':'皇后','index_url':'https://open.cd'},
{'name':'hdarea','name_cn':'高清视界','index_url':'https://www.hdarea.co'},
{'name':'soulvoice','name_cn':'聆音','index_url':'https://pt.soulvoice.club'},
{'name':'nicept','name_cn':'老师','index_url':'https://www.nicept.net'},
{'name':'haidan','name_cn':'海胆','index_url':'https://www.haidan.video'},
{'name':'3wmg','name_cn':'芒果','index_url':'https://www.3wmg.com'},
{'name':'discfan','name_cn':'碟粉','index_url':'https://discfan.net'},
{'name':'htpt','name_cn':'海棠','index_url':'https://www.htpt.cc'},
{'name':'hddolby','name_cn':'杜比','index_url':'https://www.hddolby.com'},
{'name':'hdzone','name_cn':'高清地带','index_url':'https://hdzone.me'},
{'name':'pttime','name_cn':'时间','index_url':'https://www.pttime.org'},
{'name':'hdatmos','name_cn':'阿童木','index_url':'https://hdatmos.club'},
{'name':'52pt','name_cn':'52PT','index_url':'https://52pt.site'},
{'name':'hdtime','name_cn':'高清时间','index_url':'https://hdtime.org'},
{'name':'asf','name_cn':'A-SOUL','index_url':'https://p2p.bbs.asf.ink'},
]
for site in sites_data:
get_site = SiteConfig.objects.filter(name=site['name']).count()
if get_site == 0:
SiteConfig.objects.create(name = site['name'],
name_cn = site['name_cn'],
index_url = site['index_url'],
)
print('添加站点 [%s] 成功' % site['name'])
else:
#更新站点配置
#SiteConfig.objects.filter(name=site['name']).update(name_cn = site['name_cn'],
#index_url=site['index_url'],
#)
#print('站点 [%s] 更新成功' % site['name'])
print('站点 [%s] 已经存在,忽略...' % site['name'])
print('开始初始化任务类型...')
jobtypes_data = [{'name':'签到','type_id':1000},
{'name':'辅种','type_id':1001},
{'name':'刷流','type_id':1002},
]
for jobtype in jobtypes_data:
get_jobtype = JobType.objects.filter(name=jobtype['name']).count()
if get_jobtype == 0:
JobType.objects.create(name = jobtype['name'],
type_id = jobtype['type_id'],
)
print('添加任务类型 [%s] 成功' % jobtype['name'])
else:
print('任务类型 [%s] 已经存在,忽略...' % jobtype['name'])
print('开始初始化通知邮箱类型...')
mailtypes_data = [{'name':'QQ邮箱','alias_name':'qq','smtp_server':'smtp.qq.com','smtp_port':465},
{'name':'新浪邮箱(非vip)','alias_name':'sina','smtp_server':'smtp.sina.com','smtp_port':465},
{'name':'网易163邮箱','alias_name':'163','smtp_server':'smtp.163.com','smtp_port':465},
]
for mailtype in mailtypes_data:
get_mailtype = MailType.objects.filter(name=mailtype['name']).count()
if get_mailtype == 0:
MailType.objects.create(name = mailtype['name'],
alias_name = mailtype['alias_name'],
smtp_server = mailtype['smtp_server'],
smtp_port = mailtype['smtp_port'],
)
print('添加邮箱类型 [%s] 成功' % mailtype['name'])
else:
print('邮箱类型 [%s] 已经存在,忽略...' % mailtype['name'])
print('初始化成功.')

View File

3
apps/initdata/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
apps/initdata/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
apps/initdata/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

0
apps/login/__init__.py Normal file
View File

3
apps/login/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
apps/login/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class LoginConfig(AppConfig):
name = 'login'

View File

3
apps/login/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,255 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="../../../assets/images/favicon.ico" rel="icon">
<title>重置密码</title>
<link rel="stylesheet" href="../../../assets/libs/layui/css/layui.css"/>
<link rel="stylesheet" href="../../../assets/module/admin.css?v=318">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<style>
body {
background-image: url("../../../assets/images/bg-login.jpg");
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
}
body:before {
content: "";
background-color: rgba(0, 0, 0, .2);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.login-wrapper {
max-width: 420px;
padding: 20px;
margin: 0 auto;
position: relative;
box-sizing: border-box;
z-index: 2;
}
.login-wrapper > .layui-form {
padding: 25px 30px;
background-color: #fff;
box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.19);
box-sizing: border-box;
border-radius: 4px;
}
.login-wrapper > .layui-form > h2 {
color: #333;
font-size: 18px;
text-align: center;
margin-bottom: 25px;
}
.login-wrapper > .layui-form > .layui-form-item {
margin-bottom: 25px;
position: relative;
}
.login-wrapper > .layui-form > .layui-form-item:last-child {
margin-bottom: 0;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-input {
height: 46px;
line-height: 1;
border-radius: 2px !important;
}
.login-wrapper .layui-input-icon-group > .layui-input {
padding-left: 46px;
}
.login-wrapper .layui-input-icon-group > .layui-icon {
width: 46px;
height: 46px;
line-height: 46px;
font-size: 20px;
color: #909399;
position: absolute;
left: 0;
top: 0;
text-align: center;
}
.login-wrapper > .layui-form > .layui-form-item.login-captcha-group {
padding-right: 135px;
}
.login-wrapper > .layui-form > .layui-form-item.login-captcha-group > .login-captcha {
height: 46px;
width: 120px;
cursor: pointer;
box-sizing: border-box;
border: 1px solid #e6e6e6;
border-radius: 2px !important;
position: absolute;
right: 0;
top: 0;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-form-checkbox {
margin: 0 !important;
padding-left: 25px;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-form-checkbox > .layui-icon {
width: 15px !important;
height: 15px !important;
}
.login-wrapper > .layui-form .layui-btn-fluid {
height: 48px;
line-height: 48px;
font-size: 16px;
border-radius: 2px !important;
}
.login-wrapper > .layui-form > .layui-form-item.login-oauth-group > a > .layui-icon {
font-size: 26px;
}
.login-copyright {
color: #eee;
padding-bottom: 20px;
text-align: center;
position: relative;
z-index: 1;
}
@media screen and (min-height: 550px) {
.login-wrapper {
margin: -250px auto 0;
position: absolute;
top: 50%;
left: 0;
right: 0;
width: 100%;
}
.login-copyright {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
}
.layui-btn {
background-color: #5FB878;
border-color: #5FB878;
}
.layui-link {
color: #5FB878 !important;
}
.login-captcha-btn {
line-height: 44px;
text-align: center;
background-color: transparent;
outline: none;
color: #666;
padding: 0 !important;
}
</style>
</head>
<body>
<div class="login-wrapper layui-anim layui-anim-scale layui-hide">
<form class="layui-form">
<h2>重置密码</h2>
<div class="layui-form-item layui-input-icon-group">
<i class="layui-icon layui-icon-email"></i>
<input class="layui-input" name="email" placeholder="请输入邮箱账号" autocomplete="off"
lay-verType="tips" lay-verify="email" required/>
</div>
<div class="layui-form-item layui-input-icon-group">
<i class="layui-icon layui-icon-password"></i>
<input class="layui-input" name="password" placeholder="请输入新的登录密码" type="password"
lay-verType="tips" lay-verify="required" required/>
</div>
<div class="layui-form-item layui-input-icon-group">
<i class="layui-icon layui-icon-key"></i>
<input class="layui-input" name="password2" placeholder="请再次输入登录密码" type="password"
lay-verType="tips" lay-verify="equalTo" lay-equalTo="[name=password]" required/>
</div>
<div class="layui-form-item layui-input-icon-group login-captcha-group">
<i class="layui-icon layui-icon-auz"></i>
<input class="layui-input" name="code" placeholder="请输入验证码" autocomplete="off"
lay-verType="tips" lay-verify="required" required/>
<button type="button" id="btnGetCode" class="login-captcha login-captcha-btn layui-input">获取验证码</button>
</div>
<div class="layui-form-item">
<a href="login.html" class="layui-link pull-right">返回登录</a>
</div>
<div class="layui-form-item" style="margin-bottom: 20px;">
<button class="layui-btn layui-btn-fluid" lay-filter="loginSubmit" lay-submit>修改密码</button>
</div>
</form>
</div>
<div class="login-copyright">{{SITE_NAME_COPYRIGHT}}</div>
<!-- js部分 -->
<script type="text/javascript" src="../../../assets/libs/layui/layui.js"></script>
<script>
layui.config({
base: '../../../assets/module/'
}).use(['layer', 'form', 'formX', 'admin'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
$('.login-wrapper').removeClass('layui-hide');
/* 表单提交 */
form.on('submit(loginSubmit)', function (obj) {
// console.log(obj.field);
layer.msg('注册成功', {icon: 1, time: 1500}, function () {
location.href = 'login.html';
});
/*var loadIndex = layer.load(2);
$.post('reg', obj.field, function (res) {
layer.close(loadIndex);
if (res.code === 0) {
layer.msg('注册成功', {icon: 1, time: 1500}, function () {
location.href = 'login.html';
});
} else {
layer.msg(res.msg, {icon: 2, anim: 6});
}
}, 'json');*/
return false;
});
$('#btnGetCode').click(function () {
var $btn = $(this);
var $inputEmail = $('input[name="email"]');
var email = $inputEmail.val();
/*if (!email) {
layer.tips('请输入邮箱账号', $inputEmail, {tips: [1, '#ff4c4c']});
return;
}
var emailReg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
if (!emailReg.test(email)) {
layer.tips('邮箱格式不正确', $inputEmail, {tips: [1, '#ff4c4c']});
return;
}*/
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,230 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<script>if (window !== top) top.location.replace(location.href);</script>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="icon" href="{% static 'assets/images/favicon.ico' %}"/>
<title>用户登录</title>
<link rel="stylesheet" href="{% static 'assets/libs/layui/css/layui.css' %}"/>
<link rel="stylesheet" href="{% static 'assets/module/admin.css' %}"/>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<style>
body {
background-image: url("{% static 'assets/images/bg-login.jpg' %}");
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
}
body:before {
content: "";
background-color: rgba(0, 0, 0, .2);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.login-wrapper {
max-width: 420px;
padding: 20px;
margin: 0 auto;
position: relative;
box-sizing: border-box;
z-index: 2;
}
.login-wrapper > .layui-form {
padding: 25px 30px;
background-color: #fff;
box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.19);
box-sizing: border-box;
border-radius: 4px;
}
.login-wrapper > .layui-form > h2 {
color: #333;
font-size: 18px;
text-align: center;
margin-bottom: 25px;
}
.login-wrapper > .layui-form > .layui-form-item {
margin-bottom: 25px;
position: relative;
}
.login-wrapper > .layui-form > .layui-form-item:last-child {
margin-bottom: 0;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-input {
height: 46px;
line-height: 1;
border-radius: 2px !important;
}
.login-wrapper .layui-input-icon-group > .layui-input {
padding-left: 46px;
}
.login-wrapper .layui-input-icon-group > .layui-icon {
width: 46px;
height: 46px;
line-height: 46px;
font-size: 20px;
color: #909399;
position: absolute;
left: 0;
top: 0;
text-align: center;
}
.login-wrapper > .layui-form > .layui-form-item.login-captcha-group {
padding-right: 135px;
}
.login-wrapper > .layui-form > .layui-form-item.login-captcha-group > .login-captcha {
height: 46px;
width: 120px;
cursor: pointer;
box-sizing: border-box;
border: 1px solid #e6e6e6;
border-radius: 2px !important;
position: absolute;
right: 0;
top: 0;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-form-checkbox {
margin: 0 !important;
padding-left: 25px;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-form-checkbox > .layui-icon {
width: 15px !important;
height: 15px !important;
}
.login-wrapper > .layui-form .layui-btn-fluid {
height: 48px;
line-height: 48px;
font-size: 16px;
border-radius: 2px !important;
}
.login-wrapper > .layui-form > .layui-form-item.login-oauth-group > a > .layui-icon {
font-size: 26px;
}
.login-copyright {
color: #eee;
padding-bottom: 20px;
text-align: center;
position: relative;
z-index: 1;
}
@media screen and (min-height: 550px) {
.login-wrapper {
margin: -250px auto 0;
position: absolute;
top: 50%;
left: 0;
right: 0;
width: 100%;
}
.login-copyright {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
}
.layui-btn {
background-color: #5FB878;
border-color: #5FB878;
}
.layui-link {
color: #5FB878 !important;
}
</style>
</head>
<body>
<div class="login-wrapper layui-anim layui-anim-scale layui-hide">
<form class="layui-form">
<h2>用户登录</h2>
<div class="layui-form-item layui-input-icon-group">{% csrf_token %}
<i class="layui-icon layui-icon-username"></i>
<input class="layui-input" name="username" placeholder="请输入登录账号" autocomplete="off"
lay-verType="tips" lay-verify="required" required/>
</div>
<div class="layui-form-item layui-input-icon-group">
<i class="layui-icon layui-icon-password"></i>
<input class="layui-input" name="password" placeholder="请输入登录密码" type="password"
lay-verType="tips" lay-verify="required" required/>
</div>
<div class="layui-form-item">
<input type="checkbox" name="remember" title="记住密码" lay-skin="primary" checked>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-filter="loginSubmit" lay-submit>登录</button>
</div>
</form>
</div>
<div class="login-copyright">{{SITE_NAME}} {{SITE_VERSION}}</div>
<!-- js部分 -->
<script type="text/javascript" src="{% static 'assets/libs/layui/layui.js' %}"></script>
<script>
layui.config({
base: "{% static 'assets/module/' %}"
}).use(['layer', 'form', 'setter', 'admin'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var setter = layui.setter;
$('.login-wrapper').removeClass('layui-hide');
/* 表单提交 */
form.on('submit(loginSubmit)', function (data) {
var loadIndex = layer.msg('登录中...', {icon: 16, shade: 0.01, time: false});
$.ajax({
//url: setter.baseServer + '/login',
url: '/login',
data: data.field,
type: 'POST',
dataType: 'JSON',
success: function (res) {
layer.close(loadIndex);
if (1 === res.code) {
setter.putToken(res);
layer.msg(res.msg, {icon: 1, time: 1000}, function () {
location.replace('/');
});
} else {
layer.msg(res.msg || '登录失败', {icon: 5});
}
},
error: function (xhr) {
layer.close(loadIndex);
layer.msg('系统出错了~', {icon: 5});
}
});
return false;
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,225 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="../../../assets/images/favicon.ico" rel="icon">
<title>用户注册</title>
<link rel="stylesheet" href="../../../assets/libs/layui/css/layui.css"/>
<link rel="stylesheet" href="../../../assets/module/admin.css?v=318">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<style>
body {
background-image: url("../../../assets/images/bg-login.jpg");
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
}
body:before {
content: "";
background-color: rgba(0, 0, 0, .2);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.login-wrapper {
max-width: 420px;
padding: 20px;
margin: 0 auto;
position: relative;
box-sizing: border-box;
z-index: 2;
}
.login-wrapper > .layui-form {
padding: 25px 30px;
background-color: #fff;
box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.19);
box-sizing: border-box;
border-radius: 4px;
}
.login-wrapper > .layui-form > h2 {
color: #333;
font-size: 18px;
text-align: center;
margin-bottom: 25px;
}
.login-wrapper > .layui-form > .layui-form-item {
margin-bottom: 25px;
position: relative;
}
.login-wrapper > .layui-form > .layui-form-item:last-child {
margin-bottom: 0;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-input {
height: 46px;
line-height: 1;
border-radius: 2px !important;
}
.login-wrapper .layui-input-icon-group > .layui-input {
padding-left: 46px;
}
.login-wrapper .layui-input-icon-group > .layui-icon {
width: 46px;
height: 46px;
line-height: 46px;
font-size: 20px;
color: #909399;
position: absolute;
left: 0;
top: 0;
text-align: center;
}
.login-wrapper > .layui-form > .layui-form-item.login-captcha-group {
padding-right: 135px;
}
.login-wrapper > .layui-form > .layui-form-item.login-captcha-group > .login-captcha {
height: 46px;
width: 120px;
cursor: pointer;
box-sizing: border-box;
border: 1px solid #e6e6e6;
border-radius: 2px !important;
position: absolute;
right: 0;
top: 0;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-form-checkbox {
margin: 0 !important;
padding-left: 25px;
}
.login-wrapper > .layui-form > .layui-form-item > .layui-form-checkbox > .layui-icon {
width: 15px !important;
height: 15px !important;
}
.login-wrapper > .layui-form .layui-btn-fluid {
height: 48px;
line-height: 48px;
font-size: 16px;
border-radius: 2px !important;
}
.login-wrapper > .layui-form > .layui-form-item.login-oauth-group > a > .layui-icon {
font-size: 26px;
}
.login-copyright {
color: #eee;
padding-bottom: 20px;
text-align: center;
position: relative;
z-index: 1;
}
@media screen and (min-height: 550px) {
.login-wrapper {
margin: -250px auto 0;
position: absolute;
top: 50%;
left: 0;
right: 0;
width: 100%;
}
.login-copyright {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
}
.layui-btn {
background-color: #5FB878;
border-color: #5FB878;
}
.layui-link {
color: #5FB878 !important;
}
</style>
</head>
<body>
<div class="login-wrapper layui-anim layui-anim-scale layui-hide">
<form class="layui-form">
<h2>用户注册</h2>
<div class="layui-form-item layui-input-icon-group">
<i class="layui-icon layui-icon-username"></i>
<input class="layui-input" name="username" placeholder="请输入登录账号" autocomplete="off"
lay-verType="tips" lay-verify="required" required/>
</div>
<div class="layui-form-item layui-input-icon-group">
<i class="layui-icon layui-icon-password"></i>
<input class="layui-input" name="password" placeholder="请输入登录密码" type="password"
lay-verType="tips" lay-verify="required" required/>
</div>
<div class="layui-form-item layui-input-icon-group">
<i class="layui-icon layui-icon-key"></i>
<input class="layui-input" name="password2" placeholder="请再次输入登录密码" type="password"
lay-verType="tips" lay-verify="equalTo" lay-equalTo="[name=password]" required/>
</div>
<div class="layui-form-item">
<a href="login.html" class="layui-link">返回登录</a>
<a href="forget.html" class="layui-link pull-right">忘记密码</a>
</div>
<div class="layui-form-item" style="margin-bottom: 20px;">
<button class="layui-btn layui-btn-fluid" lay-filter="loginSubmit" lay-submit>注册</button>
</div>
</form>
</div>
<div class="login-copyright">{{SITE_NAME_COPYRIGHT}}</div>
<!-- js部分 -->
<script type="text/javascript" src="../../../assets/libs/layui/layui.js"></script>
<script>
layui.config({
base: '../../../assets/module/'
}).use(['layer', 'form', 'formX', 'admin'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
$('.login-wrapper').removeClass('layui-hide');
/* 表单提交 */
form.on('submit(loginSubmit)', function (obj) {
// console.log(obj.field);
layer.msg('注册成功', {icon: 1, time: 1500}, function () {
location.href = 'login.html';
});
/*var loadIndex = layer.load(2);
$.post('reg', obj.field, function (res) {
layer.close(loadIndex);
if (res.code === 0) {
layer.msg('注册成功', {icon: 1, time: 1500}, function () {
location.href = 'login.html';
});
} else {
layer.msg(res.msg, {icon: 2, anim: 6});
}
}, 'json');*/
return false;
});
});
</script>
</body>
</html>

3
apps/login/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,14 @@
# coding:utf-8
from django.urls import path
from .. import views
app_name = 'login'
urlpatterns = [
# Resource asset url
path('login', views.LoginView.as_view()),
path('login/', views.LoginView.as_view()),
path('logout', views.logout),
]

90
apps/login/views.py Normal file
View File

@@ -0,0 +1,90 @@
from myauth.models import User
from django.views.generic import TemplateView
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.contrib import auth
from django.views.decorators.csrf import csrf_exempt
from django.template import RequestContext
from rest_framework.authtoken.models import Token
# Create your views here.
class LoginView(TemplateView):
"""
登录
"""
template_name = 'login/login.html'
model = User
def get(self, request, *args, **kwargs):
"""
登录
"""
#登录失效后,再次登录需要上次的url
self.next = request.GET.get('next')
if self.next == None:
self.next = '/'
return super(LoginView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
'next': self.next
# 'action': 'Asset list',
}
kwargs.update(context)
return super(LoginView, self).get_context_data(**kwargs)
#数据提交接收方法
def post(self, request, *args, **kwargs):
"""
数据提交
"""
id = self.request.POST.get('id')
username = request.POST.get("username")
password = request.POST.get("password")
#记住密码
remember = request.POST.get("remember")
if username == "" or username == None:
data = {"code":0, "msg":"用户名不能为空"}
return JsonResponse(data)
if password == "" or password == None:
data = {"code":0, "msg":"密码不能为空"}
return JsonResponse(data)
user = auth.authenticate(username=username,password=password)
if user is not None:
if user.is_active:
auth.login(request, user)
# 更新token值
try:
token = Token.objects.get(user=user)
except:
#当用户token不存在,则创建token
token = Token.objects.create(user=user)
#token.delete()
#token = Token.objects.create(user=user)
next = request.POST.get('next','/')
data={"code": 1, "token":token.key, "url": next, "msg":"登录成功"}
else:
msg="账户禁止登录"
data={"code": 0, "msg":msg}
else:
msg="用户名或密码错误"
data={"code": 0, "msg":msg}
return JsonResponse(data)
def logout(request):
"""
登出
"""
auth.logout(request)
return HttpResponseRedirect('/login')

0
apps/myauth/__init__.py Normal file
View File

Some files were not shown because too many files have changed in this diff Show More