Commit a0bdc4ca authored by Jalin's avatar Jalin

增加 Web 管理页面

parent b1e826b2
...@@ -65,6 +65,14 @@ EMAIL_SERVER_HOST = 'localhost' # 邮件服务 host ...@@ -65,6 +65,14 @@ EMAIL_SERVER_HOST = 'localhost' # 邮件服务 host
EMAIL_SERVER_USER = '' EMAIL_SERVER_USER = ''
EMAIL_SERVER_PASSWORD = '' EMAIL_SERVER_PASSWORD = ''
# Web 管理
WEB_ENABLE = 1 # 是否打开 Web 管理
WEB_USER = { # 登录信息
'username': 'admin',
'password': 'password'
}
WEB_PORT = 8008 # 监听端口
# 查询任务 # 查询任务
QUERY_JOBS = [ QUERY_JOBS = [
{ {
......
...@@ -54,15 +54,22 @@ REDIS_HOST = 'localhost' # Redis host ...@@ -54,15 +54,22 @@ REDIS_HOST = 'localhost' # Redis host
REDIS_PORT = '6379' # Redis post REDIS_PORT = '6379' # Redis post
REDIS_PASSWORD = '' # # Redis 密码 没有可以留空 REDIS_PASSWORD = '' # # Redis 密码 没有可以留空
# 邮箱配置 # 邮箱配置
EMAIL_ENABLED = 0 # 是否开启邮件通知 EMAIL_ENABLED = 0 # 是否开启邮件通知
EMAIL_SENDER = 'sender@example.com' # 邮件发送者 EMAIL_SENDER = 'sender@example.com' # 邮件发送者
EMAIL_RECEIVER = 'receiver@example.com' # 邮件接受者 # 可以多个 [email1@gmail.com, email2@gmail.com] EMAIL_RECEIVER = 'receiver@example.com' # 邮件接受者 # 可以多个 [email1@gmail.com, email2@gmail.com]
EMAIL_SERVER_HOST = 'localhost' # 邮件服务 host EMAIL_SERVER_HOST = 'localhost' # 邮件服务 host
EMAIL_SERVER_USER = '' EMAIL_SERVER_USER = ''
EMAIL_SERVER_PASSWORD = '' EMAIL_SERVER_PASSWORD = ''
# Web 管理
WEB_ENABLE = 1 # 是否打开 Web 管理
WEB_USER = { # 登录信息
'username': 'admin',
'password': 'password'
}
WEB_PORT = 8008 # 监听端口
# 查询任务 # 查询任务
QUERY_JOBS = [ QUERY_JOBS = [
{ {
......
...@@ -5,6 +5,7 @@ from py12306.app import * ...@@ -5,6 +5,7 @@ from py12306.app import *
from py12306.log.common_log import CommonLog from py12306.log.common_log import CommonLog
from py12306.query.query import Query from py12306.query.query import Query
from py12306.user.user import User from py12306.user.user import User
from py12306.web.web import Web
def main(): def main():
...@@ -18,13 +19,14 @@ def main(): ...@@ -18,13 +19,14 @@ def main():
Query.check_before_fun() Query.check_before_fun()
####### 运行任务 ####### 运行任务
Web.run()
User.run() User.run()
Query.run() Query.run()
if not Const.IS_TEST: if not Const.IS_TEST:
while True: while True:
sleep(10000) sleep(10000)
else: else:
if Config().is_cluster_enabled(): stay_second(5) # 等待接受完通知 if Config().is_cluster_enabled(): stay_second(5) # 等待接受完通知
CommonLog.print_test_complete() CommonLog.print_test_complete()
......
...@@ -64,6 +64,7 @@ class Config: ...@@ -64,6 +64,7 @@ class Config:
WEB_ENABLE = 0 WEB_ENABLE = 0
WEB_USER = {} WEB_USER = {}
WEB_PORT = 8080 WEB_PORT = 8080
WEB_ENTER_HTML_PATH = PROJECT_DIR + 'py12306/web/static/index.html'
envs = [] envs = []
retry_time = 5 retry_time = 5
......
...@@ -100,6 +100,11 @@ def get_file_modify_time(filePath): ...@@ -100,6 +100,11 @@ def get_file_modify_time(filePath):
return timestamp_to_time(timestamp) return timestamp_to_time(timestamp)
def get_file_total_line_num(file, encoding='utf-8'):
with open(file, 'r', encoding=encoding) as f:
return len(f.readlines())
def str_to_time(str): def str_to_time(str):
return datetime.datetime.strptime(str, '%Y-%m-%d %H:%M:%S.%f') return datetime.datetime.strptime(str, '%Y-%m-%d %H:%M:%S.%f')
......
...@@ -72,7 +72,7 @@ class BaseLog: ...@@ -72,7 +72,7 @@ class BaseLog:
return self return self
def notification(self, title, content=''): def notification(self, title, content=''):
if sys.platform == 'darwin': # if sys.platform == 'darwin': # 不太友好 先关闭,之前没考虑到 mac 下会请求权限
os.system( # os.system( 'osascript -e \'tell app "System Events" to display notification "{content}" with title "{title}"\''.format(
'osascript -e \'tell app "System Events" to display notification "{content}" with title "{title}"\''.format( # title=title, content=content))
title=title, content=content)) pass
...@@ -30,6 +30,8 @@ class CommonLog(BaseLog): ...@@ -30,6 +30,8 @@ class CommonLog(BaseLog):
MESSAGE_SEND_EMAIL_SUCCESS = '邮件发送成功,请检查收件箱' MESSAGE_SEND_EMAIL_SUCCESS = '邮件发送成功,请检查收件箱'
MESSAGE_SEND_EMAIL_FAIL = '邮件发送失败,请手动检查配置,错误原因 {}' MESSAGE_SEND_EMAIL_FAIL = '邮件发送失败,请手动检查配置,错误原因 {}'
MESSAGE_OUTPUT_TO_FILE_IS_UN_ENABLE = '请先打开配置:输出到文件'
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.init_data() self.init_data()
......
...@@ -153,8 +153,9 @@ class QueryLog(BaseLog): ...@@ -153,8 +153,9 @@ class QueryLog(BaseLog):
def print_job_start(cls, job_name): def print_job_start(cls, job_name):
self = cls() self = cls()
self.add_log( self.add_log(
'=== 正在进行第 {query_count} 次查询 {job_name} === {time}'.format(query_count=self.data.get('query_count') + 1, '=== 正在进行第 {query_count} 次查询 {job_name} === {time}'.format(
job_name=job_name, time=datetime.datetime.now())) query_count=(self.data.get('query_count', 0)) + 1,
job_name=job_name, time=datetime.datetime.now()))
self.refresh_data() self.refresh_data()
if is_main_thread(): if is_main_thread():
self.flush(publish=False) self.flush(publish=False)
......
...@@ -200,7 +200,7 @@ class UserJob: ...@@ -200,7 +200,7 @@ class UserJob:
self.info = {**self.info, **info} self.info = {**self.info, **info}
def get_name(self): def get_name(self):
return self.info.get('user_name') return self.info.get('user_name', '')
def save_user(self): def save_user(self):
if Config().is_cluster_enabled(): if Config().is_cluster_enabled():
......
from flask import Blueprint, request import json
import re
from flask import Blueprint, request, send_file
from flask.json import jsonify from flask.json import jsonify
from flask_jwt_extended import (jwt_required) from flask_jwt_extended import (jwt_required)
...@@ -9,6 +12,21 @@ from py12306.user.user import User ...@@ -9,6 +12,21 @@ from py12306.user.user import User
app = Blueprint('app', __name__) app = Blueprint('app', __name__)
@app.route('/', methods=['GET', 'POST'])
def index():
file = Config().WEB_ENTER_HTML_PATH
result = ''
with open(file, 'r', encoding='utf-8') as f:
result = f.read()
config = {
'API_BASE_URL': '' # TODO 自定义 Host
}
result = re.sub(r'<script>[\s\S]*?<\/script>', '<script>window.config={}</script>'.format(json.dumps(config)),
result)
return result
@app.route('/app/menus', methods=['GET']) @app.route('/app/menus', methods=['GET'])
@jwt_required @jwt_required
def menus(): def menus():
...@@ -17,8 +35,10 @@ def menus(): ...@@ -17,8 +35,10 @@ def menus():
""" """
menus = [ menus = [
{"id": 10, "name": "首页", "url": "/", "icon": "fa fa-tachometer-alt"}, {"id": 10, "name": "首页", "url": "/", "icon": "fa fa-tachometer-alt"},
{"id": 40, "name": "数据分析", "url": "/analyze", "icon": "fa fa-signature"}, {"id": 20, "name": "用户管理", "url": "/user", "icon": "fa fa-user"},
{"id": 50, "name": "帮助中心", "url": "/help", "icon": "fa fa-search"} {"id": 30, "name": "查询任务", "url": "/query", "icon": "fa fa-infinity"},
{"id": 40, "name": "实时日志", "url": "/log/realtime", "icon": "fa fa-signature"},
{"id": 50, "name": "帮助", "url": "/help", "icon": "fa fa-search"}
] ]
return jsonify(menus) return jsonify(menus)
...@@ -30,6 +50,6 @@ def actions(): ...@@ -30,6 +50,6 @@ def actions():
操作列表 操作列表
""" """
actions = [ actions = [
{"text": "退出登录", "link": "", "icon": "fa fa-sign-out-alt"} {"text": "退出登录", "key": 'logout', "link": "", "icon": "fa fa-sign-out-alt"}
] ]
return jsonify(actions) return jsonify(actions)
import linecache
from flask import Blueprint, request
from flask.json import jsonify
from flask_jwt_extended import (jwt_required)
from py12306.config import Config
from py12306.helpers.func import get_file_total_line_num
from py12306.log.common_log import CommonLog
from py12306.query.query import Query
from py12306.user.user import User
log = Blueprint('log', __name__)
@log.route('/log/output', methods=['GET'])
@jwt_required
def log_output():
"""
日志
:return:
"""
last_line = int(request.args.get('line', 0))
limit = int(request.args.get('limit', 10))
max_old = 200 # 取最新时 往后再取的数
file = Config().OUT_PUT_LOG_TO_FILE_PATH
res = []
if last_line == -1:
total_line = get_file_total_line_num(file)
last_line = total_line - max_old if total_line > max_old else 0
ranges = range(max_old + limit)
else:
ranges = range(limit)
if Config().OUT_PUT_LOG_TO_FILE_ENABLED:
# with open(Config().OUT_PUT_LOG_TO_FILE_PATH, 'r', encoding='utf-8') as f:
# res = f.readlines()[last_line:limit]
linecache.updatecache(file)
for i in ranges:
tmp = linecache.getline(file, last_line + i)
if tmp != '': res.append(tmp)
last_line += len(res)
else:
res = CommonLog.MESSAGE_OUTPUT_TO_FILE_IS_UN_ENABLE
return jsonify({
'last_line': last_line,
'data': res
})
...@@ -26,6 +26,8 @@ def convert_job_to_info(job: Job): ...@@ -26,6 +26,8 @@ def convert_job_to_info(job: Job):
'name': job.job_name, 'name': job.job_name,
'left_dates': job.left_dates, 'left_dates': job.left_dates,
'stations': job.stations, 'stations': job.stations,
'members': job.members,
'member_num': job.member_num,
'allow_seats': job.allow_seats, 'allow_seats': job.allow_seats,
'allow_train_numbers': job.allow_train_numbers, 'allow_train_numbers': job.allow_train_numbers,
'passengers': job.passengers, 'passengers': job.passengers,
......
...@@ -3,6 +3,7 @@ from flask.json import jsonify ...@@ -3,6 +3,7 @@ from flask.json import jsonify
from flask_jwt_extended import (jwt_required, create_access_token) from flask_jwt_extended import (jwt_required, create_access_token)
from py12306.config import Config from py12306.config import Config
from py12306.helpers.func import str_to_time, timestamp_to_time
from py12306.user.job import UserJob from py12306.user.job import UserJob
from py12306.user.user import User from py12306.user.user import User
...@@ -21,7 +22,7 @@ def login(): ...@@ -21,7 +22,7 @@ def login():
'password'): 'password'):
access_token = create_access_token(identity=username) access_token = create_access_token(identity=username)
return jsonify(access_token=access_token) return jsonify(access_token=access_token)
return jsonify({"msg": "用户名或密码错误"}), 401 return jsonify({"msg": "用户名或密码错误"}), 422
@user.route('/users', methods=['GET']) @user.route('/users', methods=['GET'])
...@@ -36,11 +37,24 @@ def users(): ...@@ -36,11 +37,24 @@ def users():
return jsonify(result) return jsonify(result)
@user.route('/user/info', methods=['GET'])
@jwt_required
def user_info():
"""
获取用户信息
:return:
"""
result = {
'name': Config().WEB_USER.get('username')
}
return jsonify(result)
def convert_job_to_info(job: UserJob): def convert_job_to_info(job: UserJob):
return { return {
'key': job.key, 'key': job.key,
'user_name': job.user_name, 'user_name': job.user_name,
'name': job.get_name(),
'is_ready': job.is_ready, 'is_ready': job.is_ready,
'is_loaded': job.user_loaded, # 是否成功加载 ready 是当前是否可用 'is_loaded': job.user_loaded, # 是否成功加载 ready 是当前是否可用
'last_heartbeat': job.last_heartbeat 'last_heartbeat': timestamp_to_time(job.last_heartbeat)
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>py12306 购票助手</title><link href=../../static/css/app.dfb5ffed622907edd7c5f81709f2b782.css rel=stylesheet></head><body><script>window.config = {
API_BASE_URL: 'http://localhost:8080/',
}</script><div id=app></div><script type=text/javascript src=../../static/js/manifest.82f431004cf9bb6ad2cb.js></script><script type=text/javascript src=../../static/js/vendor.aebd1de04bf90e88d9c7.js></script><script type=text/javascript src=../../static/js/app.96ef02c9e5601eb5ebcb.js></script></body></html>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
!function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a<e.length;a++)i=e[a],o[i]&&l.push(o[i][0]),o[i]=0;for(f in u)Object.prototype.hasOwnProperty.call(u,f)&&(r[f]=u[f]);for(n&&n(e,u,c);l.length;)l.shift()();if(c)for(a=0;a<c.length;a++)p=t(t.s=c[a]);return p};var e={},o={2:0};function t(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return r[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=r,t.c=e,t.d=function(r,n,e){t.o(r,n)||Object.defineProperty(r,n,{configurable:!1,enumerable:!0,get:e})},t.n=function(r){var n=r&&r.__esModule?function(){return r.default}:function(){return r};return t.d(n,"a",n),n},t.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},t.p="../../",t.oe=function(r){throw console.error(r),r}}([]);
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>py12306 购票助手</title><link href=../../static/css/app.dfb5ffed622907edd7c5f81709f2b782.css rel=stylesheet></head><body><script>window.config = {
API_BASE_URL: 'http://localhost:8080/',
}</script><div id=app></div><script type=text/javascript src=../../static/js/manifest.82f431004cf9bb6ad2cb.js></script><script type=text/javascript src=../../static/js/vendor.532ecf213e49d36e5e9e.js></script><script type=text/javascript src=../../static/js/app.680b1bbd04444c6d9d3a.js></script></body></html>
\ No newline at end of file
...@@ -18,28 +18,38 @@ class Web: ...@@ -18,28 +18,38 @@ class Web:
self.session = Flask(__name__) self.session = Flask(__name__)
self.register_blueprint() self.register_blueprint()
self.session.config['JWT_SECRET_KEY'] = 'secret' # 目前都是本地,暂不用放配置文件 self.session.config['JWT_SECRET_KEY'] = 'secret' # 目前都是本地,暂不用放配置文件
self.session.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(seconds=60 * 60 * 24 * 7) # Token 超时时间 7 天 self.session.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(seconds=60 * 60 * 24 * 7) # Token 超时时间 7 天
self.jwt = JWTManager(self.session) self.jwt = JWTManager(self.session)
pass
def register_blueprint(self): def register_blueprint(self):
from py12306.web.handler.user import user from py12306.web.handler.user import user
from py12306.web.handler.stat import stat from py12306.web.handler.stat import stat
from py12306.web.handler.app import app from py12306.web.handler.app import app
from py12306.web.handler.query import query from py12306.web.handler.query import query
from py12306.web.handler.log import log
self.session.register_blueprint(user) self.session.register_blueprint(user)
self.session.register_blueprint(stat) self.session.register_blueprint(stat)
self.session.register_blueprint(app) self.session.register_blueprint(app)
self.session.register_blueprint(query) self.session.register_blueprint(query)
self.session.register_blueprint(log)
@classmethod @classmethod
def run(cls): def run(cls):
self = cls() self = cls()
self.start() self.start()
pass
def start(self): def start(self):
self.session.run(debug=Config().IS_DEBUG, port=Config().WEB_PORT, host='0.0.0.0') if not Config().WEB_ENABLE or Config().is_slave(): return
if Config().IS_DEBUG:
self.run_session()
else:
create_thread_and_run(self, 'run_session', wait=False)
def run_session(self):
debug = False
if is_main_thread():
debug = Config().IS_DEBUG
self.session.run(debug=debug, port=Config().WEB_PORT, host='0.0.0.0')
if __name__ == '__main__': if __name__ == '__main__':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment