更新插件功能
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# flask配置
|
||||
FLASK_APP=app.py
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=1
|
||||
FLASK_APP = app.py
|
||||
FLASK_ENV = development
|
||||
FLASK_DEBUG = 1
|
||||
FLASK_RUN_HOST = 127.0.0.1
|
||||
FLASK_RUN_PORT = 5000
|
||||
|
||||
@@ -9,21 +9,24 @@ FLASK_RUN_PORT = 5000
|
||||
SYSTEM_NAME = Pear Admin
|
||||
|
||||
# MySql配置信息
|
||||
MYSQL_HOST=127.0.0.1
|
||||
# MYSQL_HOST=dbserver
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DATABASE=PearAdminFlask
|
||||
MYSQL_USERNAME=root
|
||||
MYSQL_PASSWORD=root
|
||||
MYSQL_HOST = 127.0.0.1
|
||||
# MYSQL_HOST = dbserver
|
||||
MYSQL_PORT = 3306
|
||||
MYSQL_DATABASE = PearAdminFlask
|
||||
MYSQL_USERNAME = root
|
||||
MYSQL_PASSWORD = root
|
||||
|
||||
# Redis 配置
|
||||
# REDIS_HOST=127.0.0.1
|
||||
# REDIS_PORT=6379
|
||||
|
||||
# 密钥配置(记得改)
|
||||
SECRET_KEY='pear-admin-flask'
|
||||
SECRET_KEY = 'pear-admin-flask'
|
||||
|
||||
# 邮箱配置
|
||||
MAIL_SERVER='smtp.qq.com'
|
||||
MAIL_USERNAME='123@qq.com'
|
||||
MAIL_PASSWORD='XXXXX' # 生成的授权码
|
||||
MAIL_SERVER = 'smtp.qq.com'
|
||||
MAIL_USERNAME = '123@qq.com'
|
||||
MAIL_PASSWORD = 'XXXXX' # 生成的授权码
|
||||
|
||||
# 插件配置
|
||||
PLUGIN_ENABLE_FOLDERS = ["HelloWorld", "helloworld"]
|
||||
@@ -5,6 +5,18 @@ from urllib.parse import quote_plus as urlquote
|
||||
from apscheduler.executors.pool import ThreadPoolExecutor
|
||||
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
||||
|
||||
# 强制读入 flaskenv 中的环境变量
|
||||
with open(".flaskenv", "r", encoding='utf-8') as f:
|
||||
for line in f.read().split("\n"):
|
||||
pos = line.find("#")
|
||||
if pos != -1:
|
||||
line = line[:pos]
|
||||
line = line.strip()
|
||||
if line == "":
|
||||
continue
|
||||
_ = line.split("=")
|
||||
key, value = _[0], '='.join(_[1:])
|
||||
os.environ[key.strip()] = value.strip()
|
||||
|
||||
class BaseConfig:
|
||||
|
||||
@@ -75,6 +87,9 @@ class BaseConfig:
|
||||
'max_instances': 3
|
||||
}
|
||||
|
||||
# 插件配置
|
||||
PLUGIN_ENABLE_FOLDERS = os.getenv('PLUGIN_ENABLE_FOLDERS')
|
||||
|
||||
# 配置多个数据库连接的连接串写法示例
|
||||
# HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称
|
||||
# MSSQL: f"mssql+pymssql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=cp936"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from applications.dev import user
|
||||
from applications.dev import role
|
||||
from applications.dev import power
|
||||
from applications.dev import department
|
||||
from applications.dev import console
|
||||
from flask import Flask
|
||||
|
||||
# 获取app应用实例,会被初始化插件时重新赋值
|
||||
app = None # type: Flask
|
||||
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
输出控制台日志
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
import ctypes
|
||||
|
||||
NONE = "\033[m"
|
||||
RED = "\033[0;32;31m"
|
||||
LIGHT_RED = "\033[1;31m"
|
||||
GREEN = "\033[0;32;32m"
|
||||
LIGHT_GREEN = "\033[1;32m"
|
||||
BLUE = "\033[0;32;34m"
|
||||
LIGHT_BLUE = "\033[1;34m"
|
||||
DARY_GRAY = "\033[1;30m"
|
||||
CYAN = "\033[0;36m"
|
||||
LIGHT_CYAN = "\033[1;36m"
|
||||
PURPLE = "\033[0;35m"
|
||||
LIGHT_PURPLE = "\033[1;35m"
|
||||
BROWN = "\033[0;33m"
|
||||
YELLOW = "\033[1;33m"
|
||||
LIGHT_GRAY = "\033[0;37m"
|
||||
WHITE = "\033[1;37m"
|
||||
|
||||
# 开启 Windows 下对于 ESC控制符 的支持
|
||||
if sys.platform == "win32":
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
||||
|
||||
|
||||
def _print(level, msg):
|
||||
time_ = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
|
||||
level_name = {10: "Plain",
|
||||
11: "Log",
|
||||
12: "Info",
|
||||
13: "Debug",
|
||||
14: "Success",
|
||||
15: "Warning",
|
||||
16: "Error"}
|
||||
|
||||
color = {10: NONE,
|
||||
11: LIGHT_CYAN,
|
||||
12: LIGHT_BLUE,
|
||||
13: PURPLE,
|
||||
14: GREEN,
|
||||
15: YELLOW,
|
||||
16: RED}
|
||||
|
||||
print(f'{color.get(level, NONE)}[{time_}]({level_name.get(level, "Plain")}):', msg, f"{NONE}")
|
||||
|
||||
|
||||
def plain(*args, sep=' '):
|
||||
msg = sep.join(str(_) for _ in args)
|
||||
_print(10, msg)
|
||||
|
||||
|
||||
def log(*args, sep=' '):
|
||||
msg = sep.join(str(_) for _ in args)
|
||||
_print(11, msg)
|
||||
|
||||
|
||||
def info(*args, sep=' '):
|
||||
msg = sep.join(str(_) for _ in args)
|
||||
_print(12, msg)
|
||||
|
||||
|
||||
def debug(*args, sep=' '):
|
||||
msg = sep.join(str(_) for _ in args)
|
||||
_print(13, msg)
|
||||
|
||||
|
||||
def success(*args, sep=' '):
|
||||
msg = sep.join(str(_) for _ in args)
|
||||
_print(14, msg)
|
||||
|
||||
|
||||
def warn(*args):
|
||||
warning(*args)
|
||||
|
||||
|
||||
def warning(*args, sep=' '):
|
||||
msg = sep.join(str(_) for _ in args)
|
||||
_print(15, msg)
|
||||
|
||||
|
||||
def error(*args, sep=' '):
|
||||
msg = sep.join(str(_) for _ in args)
|
||||
_print(16, msg)
|
||||
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
集成了对 Pear Admin Flask 的部门的操作,并给了相对应的示例。
|
||||
"""
|
||||
|
||||
from applications.common import curd
|
||||
from applications.extensions import db
|
||||
from applications.models import Dept, User
|
||||
from applications.schemas import DeptOutSchema
|
||||
|
||||
|
||||
def get_all():
|
||||
"""
|
||||
获取全部权限,会返回一个列表,每个列表是一个字典。
|
||||
|
||||
字典构成如下::
|
||||
|
||||
{
|
||||
"address":"这是总公司", # 地址
|
||||
"deptId":1, # 公司ID
|
||||
"deptName":"总公司", # 公司名
|
||||
"email":"1", # 公司 email
|
||||
"leader":"", # 公司领导人
|
||||
"parentId":0, # 父ID
|
||||
"phone":"", # 联系电话
|
||||
"sort":1, # 排序
|
||||
"status":"1" # 状态 1-开启 0-关闭
|
||||
}
|
||||
|
||||
:return: 列表
|
||||
"""
|
||||
dept = Dept.query.order_by(Dept.sort).all()
|
||||
return curd.model_to_dicts(schema=DeptOutSchema, data=dept)
|
||||
|
||||
|
||||
def add(parentId, deptName, sort, leader, phone, email, status, address):
|
||||
"""
|
||||
添加一个公司
|
||||
|
||||
:param parentId: 父公司ID,0未总公司
|
||||
:param deptName: 公司名称
|
||||
:param sort: 排序
|
||||
:param leader: 负责人
|
||||
:param phone: 手机
|
||||
:param email: 邮箱
|
||||
:param status: 状态 1-打开 0-关闭
|
||||
:param address: 地址
|
||||
:return: 是否成功
|
||||
"""
|
||||
dept = Dept(
|
||||
parent_id=parentId,
|
||||
dept_name=deptName,
|
||||
sort=sort,
|
||||
leader=leader,
|
||||
phone=phone,
|
||||
email=email,
|
||||
status=status,
|
||||
address=address
|
||||
)
|
||||
r = db.session.add(dept)
|
||||
db.session.commit()
|
||||
if r:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def update(deptId, data):
|
||||
"""
|
||||
更新公司信息
|
||||
|
||||
可更新内容如下::
|
||||
|
||||
"dept_name"
|
||||
"sort"
|
||||
"leader"
|
||||
"phone"
|
||||
"email"
|
||||
"status"
|
||||
"address"
|
||||
|
||||
:param deptId: 公司ID
|
||||
:param data: 要更新公司字典
|
||||
:return: 是否成功
|
||||
"""
|
||||
d = Dept.query.filter_by(id=deptId).update(data)
|
||||
if not d:
|
||||
return False
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
|
||||
def delete(deptId):
|
||||
"""
|
||||
删除公司
|
||||
|
||||
:param deptId: 公司ID
|
||||
:return: 是否成功
|
||||
"""
|
||||
d = Dept.query.filter_by(id=deptId).delete()
|
||||
if not d:
|
||||
return False
|
||||
res = User.query.filter_by(dept_id=deptId).update({"dept_id": None})
|
||||
db.session.commit()
|
||||
if res:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
集成了对 Pear Admin Flask 二次开发的的邮件操作,并给了相对应的示例。
|
||||
"""
|
||||
from flask import current_app
|
||||
from flask_mail import Message
|
||||
|
||||
from applications.common.curd import model_to_dicts
|
||||
from applications.common.helper import ModelFilter
|
||||
from applications.extensions import db, flask_mail
|
||||
from applications.models import Mail
|
||||
from applications.schemas import MailOutSchema
|
||||
|
||||
|
||||
def get_all(receiver=None, subject=None, content=None):
|
||||
"""
|
||||
获取邮件
|
||||
|
||||
返回的列表中的字典构造如下::
|
||||
|
||||
{
|
||||
"content": "", # html内容
|
||||
"create_at": "2022-12-25T10:51:17", # 时间
|
||||
"id": 17, # 邮件ID
|
||||
"realname": "超级管理", # 创建者
|
||||
"receiver": "", # 接收者
|
||||
"subject": "" # 主题
|
||||
}
|
||||
|
||||
:param receiver: 发送者
|
||||
:param subject: 邮件标题
|
||||
:param content: 邮件内容
|
||||
:return: 列表
|
||||
"""
|
||||
# 查询参数构造
|
||||
mf = ModelFilter()
|
||||
if receiver:
|
||||
mf.contains(field_name="receiver", value=receiver)
|
||||
if subject:
|
||||
mf.contains(field_name="subject", value=subject)
|
||||
if content:
|
||||
mf.exact(field_name="content", value=content)
|
||||
# orm查询
|
||||
# 使用分页获取data需要.items
|
||||
mail = Mail.query.filter(mf.get_filter(Mail)).layui_paginate()
|
||||
return model_to_dicts(schema=MailOutSchema, data=mail.items)
|
||||
|
||||
|
||||
def add(receiver, subject, content, user_id):
|
||||
"""
|
||||
发送一封邮件,若发送成功立刻提交数据库。
|
||||
|
||||
:param receiver: 接收者 多个用英文逗号隔开
|
||||
:param subject: 邮件主题
|
||||
:param content: 邮件 html
|
||||
:param user_id: 发送用户ID(谁发送的?) 可以用 from flask_login import current_user ; current_user.id 来表示当前登录用户
|
||||
:return: 成功与否
|
||||
"""
|
||||
try:
|
||||
msg = Message(subject=subject, recipients=receiver.split(";"), html=content)
|
||||
flask_mail.send(msg)
|
||||
except BaseException as e:
|
||||
current_app.log_exception(e)
|
||||
return False
|
||||
|
||||
mail = Mail(receiver=receiver, subject=subject, content=content, user_id=user_id)
|
||||
|
||||
db.session.add(mail)
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
|
||||
def delete(id):
|
||||
"""
|
||||
删除邮件记录,立刻写入数据库。
|
||||
|
||||
:param id: 邮件ID
|
||||
:return: 成功与否
|
||||
"""
|
||||
res = Mail.query.filter_by(id=id).delete()
|
||||
if not res:
|
||||
return False
|
||||
db.session.commit()
|
||||
return True
|
||||
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
集成了对 Pear Admin Flask 的权限操作,并给了相对应的示例。
|
||||
注意:此权限相当于添加后台菜单。
|
||||
"""
|
||||
|
||||
from applications.common import curd
|
||||
from applications.extensions import db
|
||||
from applications.models import Power
|
||||
|
||||
from applications.extensions import ma
|
||||
from marshmallow import fields
|
||||
|
||||
|
||||
class PowerOutSchema2(ma.Schema): # 序列化类
|
||||
powerId = fields.Str(attribute="id")
|
||||
powerName = fields.Str(attribute="name")
|
||||
powerType = fields.Str(attribute="type")
|
||||
powerUrl = fields.Str(attribute="url")
|
||||
powerCode = fields.Str(attribute="code")
|
||||
openType = fields.Str(attribute="open_type")
|
||||
parentId = fields.Str(attribute="parent_id")
|
||||
icon = fields.Str()
|
||||
sort = fields.Integer()
|
||||
create_time = fields.DateTime()
|
||||
update_time = fields.DateTime()
|
||||
enable = fields.Integer()
|
||||
|
||||
|
||||
def get_all():
|
||||
"""
|
||||
获取所有权限,会返回一个含有菜单的列表。每个列表都是一个字典。
|
||||
|
||||
字典构成如下::
|
||||
{
|
||||
"powerType": "0", # 权限类型 0目录 1菜单 2按钮
|
||||
"powerUrl": None, # 路径
|
||||
"powerCode": "", # 权限标识
|
||||
"update_time": None, # 更新时间
|
||||
"sort": 1, # 排序
|
||||
"openType": None, # 打开方式 _iframe框架 _blank新页面
|
||||
"icon": "layui-icon layui-icon-set-fill", # 图标
|
||||
"powerName": "系统管理", # 名称
|
||||
"create_time": None, # 创建时间
|
||||
"parentId": "0", # 父id
|
||||
"powerId": "1", # 自己的id
|
||||
"enable": 1 # 是否启用
|
||||
}
|
||||
|
||||
:return: 菜单列表。
|
||||
"""
|
||||
power = Power.query.all()
|
||||
res = curd.model_to_dicts(schema=PowerOutSchema2, data=power)
|
||||
res.append({"powerId": 0, "powerName": "顶级权限", "parentId": -1})
|
||||
return res
|
||||
|
||||
|
||||
def add(parentId, powerName, powerType, icon, sort: int, enable: bool, powerCode="", powerUrl="", openType=""):
|
||||
"""
|
||||
新建一个菜单权限。
|
||||
|
||||
参考代码::
|
||||
|
||||
dev.power.add("0", "测试", "1", "layui-icon-time", 0, True, "testfor", "https://baidu.com", "_iframe")
|
||||
|
||||
:param parentId: 父ID,0为顶级菜单ID
|
||||
:param powerName: 菜单名称
|
||||
:param powerType: 权限类型(状态) 0目录 1菜单 2按钮
|
||||
:param icon: 图标,详细查看layui的图标
|
||||
:param sort: 排序
|
||||
:param enable: 是否启用
|
||||
|
||||
:param powerCode: 权限标识
|
||||
:param powerUrl: 权限URL,菜单打开的网址,或者是路径。可选,菜单和按钮类型必填。
|
||||
:param openType: 打开方式,_iframe框架 _blank新页面。可选,菜单和按钮类型必填。
|
||||
|
||||
|
||||
|
||||
:return: 返回新权限ID
|
||||
"""
|
||||
power = Power(
|
||||
icon=icon,
|
||||
open_type=openType,
|
||||
parent_id=parentId,
|
||||
code=powerCode,
|
||||
name=powerName,
|
||||
type=powerType,
|
||||
url=powerUrl,
|
||||
sort=sort,
|
||||
enable=1
|
||||
)
|
||||
r = db.session.add(power)
|
||||
db.session.commit()
|
||||
return power.id
|
||||
|
||||
|
||||
def update(powerId, data):
|
||||
"""
|
||||
更新权限。
|
||||
|
||||
data可选::
|
||||
|
||||
"icon"
|
||||
"open_type"
|
||||
"parent_id"
|
||||
"code"
|
||||
"name"
|
||||
"type"
|
||||
"url"
|
||||
"sort"
|
||||
|
||||
:param powerId: 要更新的权限ID
|
||||
|
||||
:return: 是否成功
|
||||
"""
|
||||
res = Power.query.filter_by(id=powerId).update(data)
|
||||
db.session.commit()
|
||||
if res:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def delete(powerId):
|
||||
"""
|
||||
删除权限。
|
||||
|
||||
:param powerId: 要更新的权限ID
|
||||
:return: 是否成功
|
||||
"""
|
||||
power = Power.query.filter_by(id=powerId).first()
|
||||
power.role = []
|
||||
|
||||
r = Power.query.filter_by(id=powerId).delete()
|
||||
db.session.commit()
|
||||
if r:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
集成了对 Pear Admin Flask 的角色操作,并给了相对应的示例。
|
||||
"""
|
||||
from applications.extensions import db
|
||||
from applications.models import Role, Power
|
||||
from applications.schemas import PowerOutSchema2
|
||||
|
||||
|
||||
def filter_by(**kwargs):
|
||||
"""
|
||||
检索角色字段信息,可用于获取角色ID等。系统中默认管理员角色id为1,普通用户为2。
|
||||
内部采用的是使用 Role.query.filter_by(**kwargs) 进行数据库查询。
|
||||
|
||||
注意:此函数返回的结果为构造的 SQL的查询字符串 ,以 role_filter 命名,但是并不是用户数据。
|
||||
|
||||
返回的字段如下::
|
||||
|
||||
id name code enable remark details sort create_time update_time power
|
||||
具体参考 applications/models/admin_role.py 中的模型定义。
|
||||
|
||||
参考调用如下::
|
||||
|
||||
roleinfo = dev.role.filter_by(code='admin').first() # 第一个符合要求的角色信息
|
||||
print(role.id, role.name) # 输出角色名称与角色标识
|
||||
|
||||
# 找出所有角色id
|
||||
for role in dev.role.filter_by().all():
|
||||
print(role.id, role.name)
|
||||
|
||||
:param kwargs: 查询参数
|
||||
:return: 角色SQL的查询字符串
|
||||
"""
|
||||
return Role.query.filter_by(**kwargs)
|
||||
|
||||
|
||||
def add(roleName, roleCode, enable, sort, details):
|
||||
"""
|
||||
添加一个角色。此函数直接写入数据库。此函数不会检测角色是否已经存在(官方在API接口中也没有检测)。
|
||||
|
||||
:param roleName: 角色名称 (如管理员)
|
||||
:param roleCode: 角色标识 (如admin)
|
||||
:param enable: 是否启用 True or False
|
||||
:param sort: 排序
|
||||
:param details: 描述
|
||||
:return: None
|
||||
"""
|
||||
role = Role(
|
||||
details=details,
|
||||
enable=enable,
|
||||
code=roleCode,
|
||||
name=roleName,
|
||||
sort=sort
|
||||
)
|
||||
db.session.add(role)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def get_power(role_filter, detail=False, p=0):
|
||||
"""
|
||||
获取角色的权限。
|
||||
|
||||
如果是非详细数据,会返回一个含有权限id的列表。
|
||||
如果是详细数据返回此函数,将会返回一个列表,列表中会包含字典,字典的键如下::
|
||||
|
||||
{
|
||||
"checkArr": "1", # 是否有权限 1 为有 0为无
|
||||
"create_time": null, # 权限创建
|
||||
"enable": 1, # 权限是否启用
|
||||
"icon": "layui-icon layui-icon-set-fill", # 权限图标
|
||||
"openType": null, # 开启状态
|
||||
"parentId": "0", # 父权限ID
|
||||
"powerId": "1", # 权限ID
|
||||
"powerName": "系统管理", # 权限名称
|
||||
"powerType": "0", # 权限类型
|
||||
"powerUrl": null, # 权限URL
|
||||
"sort": 1, # 权限排序
|
||||
"update_time": null # 权限更新时间
|
||||
}
|
||||
|
||||
|
||||
:param role_filter: dev.role.filter_by() 返回结果。
|
||||
:param detail: 返回详细数据
|
||||
:param p: 如果有多个结果被找到,p可以确定使用第几个结果。内部使用 role_filter.all()[p]
|
||||
:return: 用户拥有的权限列表。
|
||||
"""
|
||||
role = role_filter.all()[p]
|
||||
check_powers = role.power
|
||||
check_powers_list = []
|
||||
for cp in check_powers:
|
||||
check_powers_list.append(cp.id)
|
||||
if not detail:
|
||||
return check_powers_list
|
||||
powers = Power.query.all()
|
||||
power_schema = PowerOutSchema2(many=True) # 用已继承ma.ModelSchema类的自定制类生成序列化类
|
||||
output = power_schema.dump(powers) # 生成可序列化对象
|
||||
for i in output:
|
||||
if int(i.get("powerId")) in check_powers_list:
|
||||
i["checkArr"] = "1"
|
||||
else:
|
||||
i["checkArr"] = "0"
|
||||
return output
|
||||
|
||||
|
||||
def set_power(role_filter, powerIds, p=0):
|
||||
"""
|
||||
保存角色权限。此函数会直接写入数据库。
|
||||
|
||||
:param role_filter: dev.role.filter_by() 返回结果。
|
||||
:param powerIds: 必须是一个包含权限ID的列表。如 [1, 2, 3]
|
||||
:param p: 如果有多个结果被找到,p可以确定使用第几个结果。内部使用 role_filter.all()[p]
|
||||
:return: None
|
||||
"""
|
||||
role = role_filter.all()[p]
|
||||
powers = Power.query.filter(Power.id.in_(powerIds)).all()
|
||||
role.power = powers
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def update(role_filter, data):
|
||||
"""
|
||||
更新角色数据。此功能将直接写入数据库。
|
||||
|
||||
可更新的字段如下::
|
||||
|
||||
id name code enable remark details sort create_time update_time power
|
||||
具体参考 applications/models/admin_role.py 中的模型定义。
|
||||
|
||||
参考调用如下::
|
||||
|
||||
role_filter = dev.role.filter_by(id=0) # 获取指定角色ID的角色,注意不要使用会引起歧义的查询条件,否则会匹配到多个角色。
|
||||
dev.role.update(role_filter, {enable: 0}) # 禁用
|
||||
|
||||
:param role_filter: dev.role.filter_by() 返回结果。
|
||||
:param data: 要更新的数据,必须是字典。
|
||||
:return: None
|
||||
"""
|
||||
role_filter.update(data)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def delete(role_filter):
|
||||
"""
|
||||
删除角色。此功能将直接写入数据库。
|
||||
|
||||
:param role_filter: dev.role.filter_by() 返回结果。
|
||||
:return: 是否成功。
|
||||
"""
|
||||
role = role_filter.first()
|
||||
# 删除该角色的权限和用户
|
||||
role.power = []
|
||||
role.user = []
|
||||
|
||||
r = role_filter.delete()
|
||||
db.session.commit()
|
||||
return r
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
集成了对 Pear Admin Flask 的用户操作,并给了相对应的示例。
|
||||
|
||||
调用示例::
|
||||
|
||||
from applications import dev
|
||||
dev.user.login_required # 用户是否登录
|
||||
dev.user.current_user # 当前登录用户
|
||||
dev.user.authorize("XXX", log=True) # 用户是否有此权限
|
||||
|
||||
"""
|
||||
|
||||
from applications.extensions import db
|
||||
from applications.models import Role
|
||||
from applications.models import User
|
||||
|
||||
|
||||
def filter_by(**kwargs):
|
||||
"""
|
||||
用于在用户数据中查询用户信息,可以通过用户名、用户id等进行检索。(建议使用id检索)
|
||||
内部采用的是使用 User.query.filter_by(**kwargs) 进行数据库查询
|
||||
|
||||
注意:此函数返回的结果为构造的 SQL的查询字符串 ,以 user_filter 命名,但是并不是用户数据。
|
||||
|
||||
返回的字段如下::
|
||||
|
||||
id username password_hash create_at update_at
|
||||
enable realname remark avatar dept_id
|
||||
具体参考 applications/models/admin_user.py 中的模型定义。
|
||||
|
||||
|
||||
参考调用如下::
|
||||
|
||||
userinfo = dev.user.filter_by(username='zsq').first() # 查询符合要求的第一个用户
|
||||
print(userinfo.realname) # 获取用户真实名字
|
||||
|
||||
|
||||
:param kwargs: 查询参数
|
||||
:return: 用户数据SQL的查询字符串
|
||||
"""
|
||||
return User.query.filter_by(**kwargs)
|
||||
|
||||
|
||||
def update(user_filter, data):
|
||||
"""
|
||||
更新用户数据,修改将直接保存到数据库中。
|
||||
注意:更新用户角色(role)请使用 dev.user.update_role() 函数。
|
||||
|
||||
可更新的字段如下::
|
||||
|
||||
id username password_hash create_at update_at
|
||||
enable realname remark avatar dept_id
|
||||
具体参考 applications/models/admin_user.py 中的模型定义。
|
||||
|
||||
参考调用如下::
|
||||
|
||||
user_filter = dev.user.filter_by(id=0) # 获取指定用户ID的用户,注意不要使用会引起歧义的查询条件,否则会匹配到多个用户。
|
||||
dev.user.update(user_filter, {username: 'zsq1314'}) # 更新其用户名
|
||||
|
||||
|
||||
|
||||
:param user_filter: dev.user.filter_by() 的结果。
|
||||
:param data: 要更新的数据,必须是字典。
|
||||
:return: None
|
||||
"""
|
||||
user_filter.update(data)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def update_role(user_filter, roleIds):
|
||||
"""
|
||||
更新用户角色,修改将直接保存到数据库中。
|
||||
|
||||
参考调用如下::
|
||||
|
||||
user_filter = dev.user.filter_by(username='zsq') # 获取符合要求的第一个用户
|
||||
roleIds = []
|
||||
roleIds.append(dev.role.filter_by(code='admin').first().id) # 管理员角色ID
|
||||
roleIds.append(dev.role.filter_by(code='common').first().id) # 普通用户角色ID
|
||||
dev.user.update_role(user_filter, roleIds)
|
||||
|
||||
|
||||
:param user_filter: dev.user.filter_by() 的结果。
|
||||
:param roleIds: 要更新的角色ID,作为列表传入。
|
||||
:return: None
|
||||
"""
|
||||
user_filter.first().role = Role.query.filter(Role.id.in_(roleIds)).all()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def get_role(user_filter):
|
||||
"""
|
||||
获取用户的所有角色ID,将会返回一个整数列表。
|
||||
|
||||
:param user_filter: dev.user.filter_by() 的结果。
|
||||
:return: 列表 (roleIds)
|
||||
"""
|
||||
checked_roles = []
|
||||
for r in user_filter.first().role:
|
||||
checked_roles.append(r.id)
|
||||
return checked_roles
|
||||
|
||||
|
||||
def set_password(user_filter, password):
|
||||
"""
|
||||
设置用户密码,此函数不会验证用户原始密码哈希值,直接写入新密码哈希值。
|
||||
|
||||
参考调用如下::
|
||||
|
||||
user_filter = dev.user.filter_by(username='zsq') # 获取符合要求的用户
|
||||
dev.user.set_password(user_filter, 'zsq1314') # 设置密码
|
||||
|
||||
:param user_filter: dev.user.filter_by() 的结果。
|
||||
:param password: 新密码。
|
||||
:return: None
|
||||
"""
|
||||
user = user_filter.first()
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def add(username, realname, password, roleIds):
|
||||
"""
|
||||
添加一个新用户。函数会判断用户名是否已存在,存在返回 False ,成功返回用户数据(userinfo)。此函数会直接写入数据库。
|
||||
注意:此函数创建出来的用户默认是禁用的,可以使用 enable 启用。
|
||||
|
||||
:param username: 新用户名
|
||||
:param realname: 真实名字
|
||||
:param password: 密码字符串
|
||||
:param roleIds: 角色id列表,如 [1, 2],角色id具体查看 dev.role.get_all() 函数。
|
||||
:return: 是否成功
|
||||
"""
|
||||
if bool(User.query.filter_by(username=username).count()):
|
||||
return False
|
||||
user = User(username=username, realname=realname)
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
roles = Role.query.filter(Role.id.in_(roleIds)).all()
|
||||
for r in roles:
|
||||
user.role.append(r)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
def delete(user_filter):
|
||||
"""
|
||||
删除一个用户。此函数立刻写入数据库。
|
||||
|
||||
:param user_filter: dev.user.filter_by() 的结果。
|
||||
:return: 是否成功
|
||||
"""
|
||||
user = user_filter.first()
|
||||
user.role = []
|
||||
res = user_filter.delete()
|
||||
db.session.commit()
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from applications.view.index import register_index_views
|
||||
from applications.view.passport import register_passport_views
|
||||
from applications.view.rights import register_rights_view
|
||||
from applications.view.department import register_dept_views
|
||||
|
||||
from applications.view.plugin import register_plugin_views
|
||||
|
||||
def init_view(app):
|
||||
register_admin_views(app)
|
||||
@@ -11,3 +11,4 @@ def init_view(app):
|
||||
register_rights_view(app)
|
||||
register_passport_views(app)
|
||||
register_dept_views(app)
|
||||
register_plugin_views(app)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import time
|
||||
import psutil
|
||||
import platform
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, render_template, jsonify
|
||||
from applications.common.utils.rights import authorize
|
||||
|
||||
@@ -22,8 +23,8 @@ def main():
|
||||
python_version = platform.python_version()
|
||||
# 逻辑cpu数量
|
||||
cpu_count = psutil.cpu_count()
|
||||
# cup使用率
|
||||
cpus_percent = psutil.cpu_percent(interval=0.1)
|
||||
# cpu使用率
|
||||
cpus_percent = psutil.cpu_percent(interval=0.1, percpu=False) # percpu 获取主使用率
|
||||
# 内存
|
||||
memory_information = psutil.virtual_memory()
|
||||
# 内存使用率
|
||||
@@ -79,10 +80,19 @@ def main():
|
||||
@admin_monitor_bp.get('/polling')
|
||||
@authorize("admin:monitor:main")
|
||||
def ajax_polling():
|
||||
# 获取cup使用率
|
||||
cpus_percent = psutil.cpu_percent(interval=0.1)
|
||||
# 获取cpu使用率
|
||||
cpus_percent = psutil.cpu_percent(interval=0.1, percpu=False) # percpu 获取主使用率
|
||||
# 获取内存使用率
|
||||
memory_information = psutil.virtual_memory()
|
||||
memory_usage = memory_information.percent
|
||||
time_now = time.strftime('%H:%M:%S ', time.localtime(time.time()))
|
||||
return jsonify(cups_percent=cpus_percent, memory_used=memory_usage, time_now=time_now)
|
||||
|
||||
# 关闭程序
|
||||
@admin_monitor_bp.get('/kill')
|
||||
@authorize("admin:monitor:main")
|
||||
def kill():
|
||||
for proc in psutil.process_iter():
|
||||
if proc.pid == os.getpid():
|
||||
proc.kill()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,169 @@
|
||||
import shutil
|
||||
|
||||
from flask import Flask
|
||||
from flask import Blueprint, render_template, request, jsonify, escape
|
||||
from applications.common.utils.http import table_api, fail_api, success_api
|
||||
import os
|
||||
import json
|
||||
import traceback
|
||||
import importlib
|
||||
|
||||
import applications.dev
|
||||
from applications.common.utils.rights import authorize
|
||||
|
||||
plugin_bp = Blueprint('plugin', __name__, url_prefix='/plugin')
|
||||
PLUGIN_ENABLE_FOLDERS = []
|
||||
|
||||
def register_plugin_views(app: Flask):
|
||||
global PLUGIN_ENABLE_FOLDERS
|
||||
applications.dev.app = app # 对app重新赋值 便于插件简单调用
|
||||
app.register_blueprint(plugin_bp)
|
||||
# 载入插件过程
|
||||
# plugin_folder 配置的是插件的文件夹名
|
||||
PLUGIN_ENABLE_FOLDERS = json.loads(app.config['PLUGIN_ENABLE_FOLDERS'])
|
||||
for plugin_folder in PLUGIN_ENABLE_FOLDERS:
|
||||
plugin_info = {}
|
||||
try:
|
||||
with open("plugins/" + plugin_folder + "/__init__.json", "r", encoding='utf-8') as f:
|
||||
plugin_info = json.loads(f.read())
|
||||
print(f"* Plugin: Loading {plugin_info['plugin_name']} ......")
|
||||
# 初始化完成事件
|
||||
try:
|
||||
getattr(importlib.import_module('plugins.' + plugin_folder), "event_init")(app)
|
||||
except AttributeError: # 没有插件启用事件就不调用
|
||||
pass
|
||||
except BaseException as error:
|
||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
||||
print(f"* Plugin: Loaded {plugin_info['plugin_name']} !")
|
||||
except BaseException as e:
|
||||
info = f"* Plugin: Crash a error when loading {plugin_info['plugin_name'] if len(plugin_info) != 0 else 'plugin'} :" + "\n"
|
||||
info += 'str(Exception):\t' + str(Exception) + "\n"
|
||||
info += 'str(e):\t\t' + str(e) + "\n"
|
||||
info += 'repr(e):\t' + repr(e) + "\n"
|
||||
info += 'traceback.format_exc():\n%s' + traceback.format_exc()
|
||||
print(info)
|
||||
|
||||
|
||||
@plugin_bp.get('/')
|
||||
@authorize("admin:plugin:main", log=True)
|
||||
def main():
|
||||
"""此处渲染管理模板"""
|
||||
return render_template('admin/plugin/main.html')
|
||||
|
||||
|
||||
@plugin_bp.get('/data')
|
||||
@authorize("admin:plugin:main", log=True)
|
||||
def data():
|
||||
"""请求插件数据"""
|
||||
plugin_name = escape(request.args.get("plugin_name"))
|
||||
all_plugins = []
|
||||
count = 0
|
||||
for filename in os.listdir("plugins"):
|
||||
try:
|
||||
with open("plugins/" + filename + "/__init__.json", "r", encoding='utf-8') as f:
|
||||
info = json.loads(f.read())
|
||||
|
||||
if plugin_name is None:
|
||||
if info['plugin_name'].find(plugin_name) == -1:
|
||||
continue
|
||||
|
||||
all_plugins.append(
|
||||
{
|
||||
"plugin_name": info["plugin_name"],
|
||||
"plugin_version": info["plugin_version"],
|
||||
"plugin_description": info["plugin_description"],
|
||||
"developer_name": info["developer_name"],
|
||||
"developer_website": info["developer_website"],
|
||||
"developer_email": info["developer_email"],
|
||||
"developer_phone": info["developer_phone"],
|
||||
"plugin_folder_name": filename,
|
||||
"enable": "1" if filename in PLUGIN_ENABLE_FOLDERS else "0"
|
||||
}
|
||||
)
|
||||
count += 1
|
||||
except BaseException as error:
|
||||
print(filename, error)
|
||||
continue
|
||||
return table_api(data=all_plugins, count=count)
|
||||
|
||||
|
||||
@plugin_bp.put('/enable')
|
||||
@authorize("admin:plugin:enable", log=True)
|
||||
def enable():
|
||||
"""启用插件"""
|
||||
plugin_folder_name = request.json.get('plugin_folder_name')
|
||||
if plugin_folder_name:
|
||||
try:
|
||||
if plugin_folder_name not in PLUGIN_ENABLE_FOLDERS:
|
||||
PLUGIN_ENABLE_FOLDERS.append(plugin_folder_name)
|
||||
with open(".flaskenv", "r", encoding='utf-8') as f:
|
||||
flaskenv = f.read() # type: str
|
||||
pos1 = flaskenv.find("PLUGIN_ENABLE_FOLDERS")
|
||||
pos2 = flaskenv.find("\n", pos1)
|
||||
with open(".flaskenv", "w", encoding='utf-8') as f:
|
||||
if pos2 == -1:
|
||||
f.write(flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS))
|
||||
else:
|
||||
f.write(
|
||||
flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS) + flaskenv[
|
||||
pos2:])
|
||||
# 启用插件事件
|
||||
try:
|
||||
getattr(importlib.import_module('plugins.' + plugin_folder_name), "event_enable")()
|
||||
except AttributeError: # 没有插件启用事件就不调用
|
||||
pass
|
||||
except BaseException as error:
|
||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
||||
|
||||
except BaseException as error:
|
||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
||||
return success_api(msg="启用成功,要使修改生效需要重启程序。")
|
||||
return fail_api(msg="数据错误")
|
||||
|
||||
|
||||
@plugin_bp.put('/disable')
|
||||
@authorize("admin:plugin:enable", log=True)
|
||||
def disable():
|
||||
"""禁用插件"""
|
||||
plugin_folder_name = request.json.get('plugin_folder_name')
|
||||
if plugin_folder_name:
|
||||
try:
|
||||
if plugin_folder_name in PLUGIN_ENABLE_FOLDERS:
|
||||
PLUGIN_ENABLE_FOLDERS.remove(plugin_folder_name)
|
||||
with open(".flaskenv", "r", encoding='utf-8') as f:
|
||||
flaskenv = f.read() # type: str
|
||||
pos1 = flaskenv.find("PLUGIN_ENABLE_FOLDERS")
|
||||
pos2 = flaskenv.find("\n", pos1)
|
||||
with open(".flaskenv", "w", encoding='utf-8') as f:
|
||||
if pos2 == -1:
|
||||
f.write(flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS))
|
||||
else:
|
||||
f.write(
|
||||
flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS) + flaskenv[
|
||||
pos2:])
|
||||
|
||||
# 禁用插件事件
|
||||
try:
|
||||
getattr(importlib.import_module('plugins.' + plugin_folder_name), "event_disable")()
|
||||
except AttributeError: # 没有插件禁用事件就不调用
|
||||
pass
|
||||
except BaseException as error:
|
||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
||||
|
||||
except BaseException as error:
|
||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
||||
return success_api(msg="禁用成功,要使修改生效需要重启程序。")
|
||||
return fail_api(msg="数据错误")
|
||||
|
||||
|
||||
# 删除
|
||||
@plugin_bp.delete('/remove/<string:plugin_folder_name>')
|
||||
@authorize("admin:mail:remove", log=True)
|
||||
def delete(plugin_folder_name):
|
||||
if plugin_folder_name in PLUGIN_ENABLE_FOLDERS:
|
||||
return fail_api(msg="您必须先禁用插件!")
|
||||
try:
|
||||
shutil.rmtree(os.path.abspath("plugins/" + plugin_folder_name))
|
||||
return success_api(msg="删除成功")
|
||||
except BaseException as error:
|
||||
return fail_api(msg="删除失败!原因:" + str(error))
|
||||
@@ -211,6 +211,10 @@ INSERT INTO `admin_power` VALUES (56, '任务删除', '2', 'admin:task:remove',
|
||||
INSERT INTO `admin_power` VALUES (57, '邮件管理', '1', 'admin:mail:main', '/admin/mail', '_iframe', '1', 'layui-icon layui-icon layui-icon-release', 7, '2022-10-11 11:21:05', '2022-10-11 11:21:22', 1);
|
||||
INSERT INTO `admin_power` VALUES (58, '邮件发送', '2', 'admin:mail:add', '', '', '57', 'layui-icon layui-icon-ok-circle', 1, '2022-10-11 11:22:26', '2022-10-11 11:22:26', 1);
|
||||
INSERT INTO `admin_power` VALUES (59, '邮件删除', '2', 'admin:mail:remove', '', '', '57', 'layui-icon layui-icon layui-icon-close', 2, '2022-10-11 11:23:06', '2022-10-11 11:23:18', 1);
|
||||
INSERT INTO `admin_power` VALUES (60, '拓展插件', '0', '', '', '', '0', 'layui-icon layui-icon layui-icon-senior', 2, '2022-12-18 12:28:19', '2022-12-18 12:30:25', 1);
|
||||
INSERT INTO `admin_power` VALUES (61, '插件管理', '1', 'admin:plugin:main', '/plugin', '_iframe', '57', 'layui-icon layui-icon layui-icon layui-icon ', 1, '2022-12-18 12:30:13', '2022-12-18 13:57:20', 1);
|
||||
INSERT INTO `admin_power` VALUES (62, '启禁插件', '2', 'admin:plugin:enable', '', '', '61', 'layui-icon ', 1, '2022-12-18 13:25:37', '2022-12-18 13:25:37', 1);
|
||||
INSERT INTO `admin_power` VALUES (63, '删除插件', '2', 'admin:plugin:remove', '', '', '61', 'layui-icon layui-icon ', 2, '2022-12-18 13:26:30', '2022-12-18 13:27:17', 1);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for admin_role
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"plugin_name": "Hello World",
|
||||
"plugin_version": "1.0.0.1",
|
||||
"plugin_description": "一个测试的插件。",
|
||||
"developer_name": "Yishang",
|
||||
"developer_website": "https://lovepikachu.top",
|
||||
"developer_email": "422880152@qq.com",
|
||||
"developer_phone": "-"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
初始化插件
|
||||
"""
|
||||
from flask import Flask
|
||||
from .main import helloworld_blueprint
|
||||
|
||||
def event_enable():
|
||||
"""当此插件被启用时会调用此处"""
|
||||
print("启用插件")
|
||||
|
||||
|
||||
def event_disable():
|
||||
"""当此插件被禁用时会调用此处"""
|
||||
print("禁用插件")
|
||||
|
||||
def event_init(app: Flask):
|
||||
"""初始化完成时会调用这里"""
|
||||
app.register_blueprint(helloworld_blueprint)
|
||||
@@ -0,0 +1,19 @@
|
||||
from applications.dev import *
|
||||
|
||||
from flask import render_template, Blueprint
|
||||
|
||||
import os
|
||||
import psutil
|
||||
|
||||
# 获取插件所在的目录(结尾没有分割符号)
|
||||
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
||||
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
||||
|
||||
# 创建蓝图
|
||||
helloworld_blueprint = Blueprint('hello_world', __name__, template_folder='templates', static_folder="static",
|
||||
url_prefix="/hello_world")
|
||||
|
||||
@helloworld_blueprint.route("/")
|
||||
def index():
|
||||
return render_template("helloworld_index.html")
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<h1>Test!</h1>
|
||||
+127
-112
@@ -1,114 +1,129 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title> Pear Admin Flask </title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/component/pear/css/pear.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/loader.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/admin.css') }}"/>
|
||||
<!-- 主 题 更 换 -->
|
||||
<style id="pearadmin-bg-color"></style>
|
||||
</head>
|
||||
<!-- 结 构 代 码 -->
|
||||
<body class="layui-layout-body pear-admin">
|
||||
<!-- 布 局 框 架 -->
|
||||
<div class="layui-layout layui-layout-admin">
|
||||
<div class="layui-header">
|
||||
<!-- 顶 部 左 侧 功 能 -->
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<li class="collaspe layui-nav-item"><a href="#" class="layui-icon layui-icon-shrink-right"></a></li>
|
||||
<li class="refresh layui-nav-item"><a href="#" class="layui-icon layui-icon-refresh-1" title="刷新页面"
|
||||
loading=600></a></li>
|
||||
</ul>
|
||||
<!-- 顶 部 右 侧 菜 单 -->
|
||||
<div id="control" class="layui-layout-control"></div>
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<li class="layui-nav-item layui-hide-xs">
|
||||
<a href="#" class="fullScreen layui-icon layui-icon-screen-full"></a>
|
||||
</li>
|
||||
<li class="layui-nav-item layui-hide-xs">
|
||||
<a href="http://www.pearadmin.com" class="layui-icon layui-icon-website"></a>
|
||||
</li>
|
||||
<li class="layui-nav-item layui-hide-xs message"></li>
|
||||
<li class="layui-nav-item user">
|
||||
<!-- 头 像 -->
|
||||
<a href="javascript:;">
|
||||
<img src="{{ user.avatar }}" id="avatar" class="layui-nav-img">
|
||||
<span>{{ user.realname }}</span>
|
||||
</a>
|
||||
<!-- 功 能 菜 单 -->
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a user-menu-url="/admin/user/center" user-menu-id="5555" user-menu-title="基本资料">基本资料</a></dd>
|
||||
<dd><a href="javascript:void(0);" class="logout">注销登录</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 侧 边 区 域 -->
|
||||
<div class="layui-side layui-bg-black">
|
||||
<!-- 顶 部 图 标 -->
|
||||
<div class="layui-logo">
|
||||
<img class="logo"/>
|
||||
<span class="title"></span>
|
||||
</div>
|
||||
<!-- 侧 边 菜 单 -->
|
||||
<div class="layui-side-scroll">
|
||||
<div id="sideMenu"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-body">
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 遮 盖 层 -->
|
||||
<div class="pear-cover"></div>
|
||||
<!-- 移 动 端 便 捷 操 作 -->
|
||||
<div class="pear-collasped-pe collaspe"><a href="#" class="layui-icon layui-icon-shrink-right"></a></div>
|
||||
<!-- 加 载 动 画-->
|
||||
<div class="loader-main">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
<!-- 依 赖 脚 本 -->
|
||||
{% include 'admin/common/footer.html' %}
|
||||
<script>
|
||||
layui.use(['admin', 'jquery', 'layer', 'popup'], function () {
|
||||
let admin = layui.admin
|
||||
let $ = layui.jquery
|
||||
let layer = layui.layer
|
||||
let popup = layui.popup
|
||||
|
||||
// 框架初始化时会读取 根目录下 pear.configs.yml 文件作为初始化配置
|
||||
// 你可以通过 admin.setConfigPath 方法修改配置文件位置
|
||||
// 你可以通过 admin.setConfigType 方法修改配置文件类型
|
||||
admin.setConfigType('json')
|
||||
admin.setConfigPath("{{ url_for('rights.configs') }}")
|
||||
|
||||
admin.render()
|
||||
|
||||
// 注销实现
|
||||
admin.logout(function () {
|
||||
let loading = layer.load()
|
||||
$.ajax({
|
||||
url: '/passport/logout',
|
||||
dataType: 'json',
|
||||
type: 'post',
|
||||
success: function (result) {
|
||||
layer.close(loading)
|
||||
if (result.success) {
|
||||
popup.success(result.msg, function () {
|
||||
location.href = '/admin'
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 初始化消息事件
|
||||
admin.message()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
{% include 'admin/common/header.html' %}
|
||||
<title> Pear Admin Layui </title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/component/pear/css/pear.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/loader.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/admin.css') }}"/>
|
||||
</head>
|
||||
<!-- 结 构 代 码 -->
|
||||
<body class="layui-layout-body pear-admin">
|
||||
<!-- 布 局 框 架 -->
|
||||
<div class="layui-layout layui-layout-admin">
|
||||
<!-- 顶 部 样 式 -->
|
||||
<div class="layui-header">
|
||||
<!-- 菜 单 顶 部 -->
|
||||
<div class="layui-logo">
|
||||
<!-- 图 标 -->
|
||||
<img class="logo">
|
||||
<!-- 标 题 -->
|
||||
<span class="title"></span>
|
||||
</div>
|
||||
<!-- 顶 部 左 侧 功 能 -->
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<li class="collapse layui-nav-item"><a href="#" class="layui-icon layui-icon-shrink-right"></a></li>
|
||||
<li class="refresh layui-nav-item"><a href="#" class="layui-icon layui-icon-refresh-1" loading = 600></a></li>
|
||||
</ul>
|
||||
<!-- 多 系 统 菜 单 -->
|
||||
<div id="control" class="layui-layout-control"></div>
|
||||
<!-- 顶 部 右 侧 菜 单 -->
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<li class="layui-nav-item layui-hide-xs"><a href="#" class="menuSearch layui-icon layui-icon-search"></a></li>
|
||||
<li class="layui-nav-item layui-hide-xs"><a href="#" class="fullScreen layui-icon layui-icon-screen-full"></a></li>
|
||||
<li class="layui-nav-item layui-hide-xs"><a href="http://www.pearadmin.com" class="layui-icon layui-icon-website"></a></li>
|
||||
<li class="layui-nav-item layui-hide-xs message"></li>
|
||||
<li class="layui-nav-item user">
|
||||
<!-- 头 像 -->
|
||||
<a class="layui-icon layui-icon-username" href="javascript:;"></a>
|
||||
<!-- 功 能 菜 单 -->
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a user-menu-url="/admin/user/center" user-menu-id="5555" user-menu-title="基本资料">基本资料</a></dd>
|
||||
<dd><a href="javascript:void(0);" class="logout">注销登录</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<!-- 主 题 配 置 -->
|
||||
<li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 侧 边 区 域 -->
|
||||
<div class="layui-side layui-bg-black">
|
||||
<!-- 菜 单 顶 部 -->
|
||||
<div class="layui-logo">
|
||||
<!-- 图 标 -->
|
||||
<img class="logo">
|
||||
<!-- 标 题 -->
|
||||
<span class="title"></span>
|
||||
</div>
|
||||
<!-- 菜 单 内 容 -->
|
||||
<div class="layui-side-scroll">
|
||||
<div id="sideMenu"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 视 图 页 面 -->
|
||||
<div class="layui-body">
|
||||
<!-- 内 容 页 面 -->
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
<!-- 页脚 -->
|
||||
<div class="layui-footer layui-text">
|
||||
<span class="left">
|
||||
<span>
|
||||
官网:<a href="http://www.pearadmin.com/team.html" target="_blank">www.pearadmin.com</a>
|
||||
仓库:<a href="https://gitee.com/pear-admin/Pear-Admin-Layui" target="_blank">gitee.com/pear-admin</a>
|
||||
</span>
|
||||
</span>
|
||||
<span class="center"></span>
|
||||
<span class="right"></span>
|
||||
</div>
|
||||
<!-- 遮 盖 层 -->
|
||||
<div class="pear-cover"></div>
|
||||
<!-- 加 载 动 画 -->
|
||||
<div class="loader-main">
|
||||
<!-- 动 画 对 象 -->
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 移 动 端 便 捷 操 作 -->
|
||||
<div class="pear-collapsed-pe collapse">
|
||||
<a href="#" class="layui-icon layui-icon-shrink-right"></a>
|
||||
</div>
|
||||
<!-- 依 赖 脚 本 -->
|
||||
{% include 'admin/common/footer.html' %}
|
||||
<!-- 框 架 初 始 化 -->
|
||||
<script>
|
||||
layui.use(['admin','jquery','popup','drawer'], function() {
|
||||
var $ = layui.jquery;
|
||||
var admin = layui.admin;
|
||||
var popup = layui.popup;
|
||||
|
||||
admin.setConfigType('json')
|
||||
admin.setConfigPath("{{ url_for('rights.configs') }}")
|
||||
|
||||
admin.render();
|
||||
|
||||
// 登出逻辑
|
||||
admin.logout(function () {
|
||||
let loading = layer.load()
|
||||
$.ajax({
|
||||
url: '/passport/logout',
|
||||
dataType: 'json',
|
||||
type: 'post',
|
||||
success: function (result) {
|
||||
layer.close(loading)
|
||||
if (result.success) {
|
||||
popup.success(result.msg, function () {
|
||||
location.href = '/admin'
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 自定义消息点击回调
|
||||
admin.message(function(id, title, context, form) {});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>登录</title>
|
||||
{% include 'admin/common/header.html' %}
|
||||
<!-- 样 式 文 件 -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/component/pear/css/pear.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/other/login.css') }}"/>
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
skin: 'line',
|
||||
height: 'full-148',
|
||||
toolbar: '#mail-toolbar', /*工具栏*/
|
||||
text: { none: '暂无人员信息' },
|
||||
text: { none: '暂无邮件信息' },
|
||||
defaultToolbar: [{ layEvent: 'refresh', icon: 'layui-icon-refresh' }, 'filter', 'print', 'exports'] /*默认工具栏*/
|
||||
})
|
||||
|
||||
|
||||
@@ -145,6 +145,13 @@
|
||||
<td>python版本</td>
|
||||
<td>{{ python_version }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>程序操作</td>
|
||||
<td>
|
||||
<a href="javascript:kill();"
|
||||
class="pear-btn pear-btn-xs pear-btn-primary">关闭程序</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>插件管理</title>
|
||||
{% include 'admin/common/header.html' %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='/admin/admin/css/other/user.css') }}"/>
|
||||
</head>
|
||||
<body class="pear-container">
|
||||
{# 查询表单 #}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" action="" lay-filter="plugin-query-form">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">插件名称</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="plugin_name" placeholder="" class="layui-input">
|
||||
</div>
|
||||
<button class="pear-btn pear-btn-md pear-btn-primary" lay-submit lay-filter="plugin-query">
|
||||
<i class="layui-icon layui-icon-search"></i>
|
||||
查询
|
||||
</button>
|
||||
<button type="reset" class="pear-btn pear-btn-md">
|
||||
<i class="layui-icon layui-icon-refresh"></i>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{# 用户表格 #}
|
||||
<div class="user-plugin user-collasped">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body">
|
||||
<table id="plugin-table" lay-filter="plugin-table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
{# 表格操作 #}
|
||||
<script type="text/html" id="plugin-toolbar">
|
||||
{% if authorize("admin:plugin:enable") %}
|
||||
{# <button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add">#}
|
||||
{# <i class="pear-icon pear-icon-add"></i>#}
|
||||
{# 新增#}
|
||||
{# </button>#}
|
||||
{% endif %}
|
||||
{% if authorize("admin:plugin:remove") %}
|
||||
{# <button class="pear-btn pear-btn-md" lay-event="batchRemove">#}
|
||||
{# <i class="pear-icon pear-icon-ashbin"></i>#}
|
||||
{# 删除#}
|
||||
{# </button>#}
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
{# 用户修改操作 #}
|
||||
<script type="text/html" id="plugin-bar">
|
||||
{% if authorize("admin:plugin:remove") %}
|
||||
<button class="pear-btn pear-btn-danger pear-btn-sm" lay-event="remove"><i
|
||||
class="pear-icon pear-icon-ashbin"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
{# 启动与禁用 #}
|
||||
<script type="text/html" id="plugin-enable">
|
||||
<input type="checkbox" name="enable" value="{{ "{{ d.plugin_folder_name }}" }}" lay-skin="switch"
|
||||
lay-text="启用|禁用"
|
||||
lay-filter="plugin-enable"
|
||||
{{ "{{# if(d.enable==1){ }} checked {{# } }}" }} />
|
||||
</script>
|
||||
|
||||
{# 用户注册时间 #}
|
||||
<script type="text/html" id="plugin-createTime">
|
||||
{{ ' {{layui.util.toDateString(d.create_at, "yyyy-MM-dd HH:mm:ss")}' |safe }}}
|
||||
</script>
|
||||
|
||||
{% include 'admin/common/footer.html' %}
|
||||
|
||||
<script>
|
||||
// 重启程序
|
||||
function restart() {
|
||||
layui.use(['layer'], function () {
|
||||
var $ = layui.jquery;
|
||||
let popup = layui.popup;
|
||||
layer.confirm('需要重启程序吗?这样可以使插件更改生效。(如果没有守护进程需要手动启动。)', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, function (index) {
|
||||
layer.close(index)
|
||||
let loading = layer.load()
|
||||
$.ajax({
|
||||
url: '/admin/monitor/kill',
|
||||
dataType: 'json',
|
||||
type: 'get',
|
||||
error: function (result) {
|
||||
setInterval(function () {
|
||||
tip = false
|
||||
$.ajax({
|
||||
url: '/admin/monitor',
|
||||
type: 'get',
|
||||
success: function (result) {
|
||||
layer.close(loading)
|
||||
if (tip == false) {
|
||||
popup.success("重启程序成功!", function () {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}, 1000);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
layui.use(['table', 'form', 'jquery', 'popup', 'common'], function () {
|
||||
let table = layui.table
|
||||
let form = layui.form
|
||||
let $ = layui.jquery
|
||||
let dtree = layui.dtree
|
||||
let popup = layui.popup
|
||||
let common = layui.common
|
||||
let MODULE_PATH = '/plugin/'
|
||||
|
||||
// 表格数据
|
||||
let cols = [
|
||||
[
|
||||
{% if authorize("admin:plugin:remove") %}
|
||||
{type: 'checkbox'},
|
||||
{% endif %}
|
||||
{field: 'plugin_name', minWidth: 100, title: '插件名称'},
|
||||
{field: 'plugin_version', title: '插件版本'},
|
||||
{field: 'plugin_description', title: '插件描述'},
|
||||
{field: 'developer_name', title: '开发者名称'},
|
||||
{field: 'developer_website', title: '开发者网站'},
|
||||
{field: 'developer_email', title: '开发者邮箱'},
|
||||
{field: 'developer_phone', title: '开发者电话'},
|
||||
{field: 'plugin_folder_name', hide: true},
|
||||
{field: 'enable', title: '状态', templet: '#plugin-enable', width: 100, align: 'center'},
|
||||
{title: '操作', templet: '#plugin-bar', width: 120, align: 'center'}
|
||||
]
|
||||
]
|
||||
|
||||
// 渲染表格数据
|
||||
table.render({
|
||||
elem: '#plugin-table',
|
||||
url: MODULE_PATH + 'data',
|
||||
page: true,
|
||||
cols: cols,
|
||||
skin: 'line',
|
||||
height: 'full-148',
|
||||
toolbar: '#plugin-toolbar', /*工具栏*/
|
||||
text: {none: '暂无人员信息'},
|
||||
defaultToolbar: [{layEvent: 'refresh', icon: 'layui-icon-refresh'}, 'filter', 'print', 'exports'] /*默认工具栏*/
|
||||
})
|
||||
|
||||
|
||||
// 禁用启用
|
||||
form.on('switch(plugin-enable)', function (obj) {
|
||||
let operate
|
||||
if (obj.elem.checked) {
|
||||
operate = 'enable'
|
||||
} else {
|
||||
operate = 'disable'
|
||||
}
|
||||
let loading = layer.load()
|
||||
$.ajax({
|
||||
url: MODULE_PATH + operate,
|
||||
data: JSON.stringify({plugin_folder_name: this.value}),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'put',
|
||||
success: function (result) {
|
||||
layer.close(loading)
|
||||
if (result.success) {
|
||||
popup.success(result.msg)
|
||||
restart()
|
||||
} else {
|
||||
popup.failure(result.msg)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
table.on('tool(plugin-table)', function (obj) {
|
||||
if (obj.event === 'remove') {
|
||||
window.remove(obj)
|
||||
}
|
||||
})
|
||||
|
||||
table.on('toolbar(plugin-table)', function (obj) {
|
||||
if (obj.event === 'add') {
|
||||
window.add()
|
||||
} else if (obj.event === 'refresh') {
|
||||
window.refresh()
|
||||
} else if (obj.event === 'batchRemove') {
|
||||
window.batchRemove(obj)
|
||||
}
|
||||
})
|
||||
|
||||
form.on('submit(plugin-query)', function (data) {
|
||||
window.refresh(data.field)
|
||||
return false
|
||||
})
|
||||
|
||||
window.add = function () {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '新增',
|
||||
shade: 0.1,
|
||||
area: ['550px', '550px'],
|
||||
content: MODULE_PATH + 'add'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
window.remove = function (obj) {
|
||||
layer.confirm('确定要删除', {icon: 3, title: '提示'}, function (index) {
|
||||
layer.close(index)
|
||||
let loading = layer.load()
|
||||
$.ajax({
|
||||
url: MODULE_PATH + 'remove/' + obj.data['plugin_folder_name'],
|
||||
dataType: 'json',
|
||||
type: 'delete',
|
||||
success: function (result) {
|
||||
layer.close(loading)
|
||||
if (result.success) {
|
||||
popup.success(result.msg, function () {
|
||||
obj.del()
|
||||
})
|
||||
} else {
|
||||
popup.failure(result.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
window.batchRemove = function (obj) {
|
||||
let data = table.checkStatus(obj.config.id).data
|
||||
if (data.length === 0) {
|
||||
layer.msg('未选中数据', {
|
||||
icon: 3,
|
||||
time: 1000
|
||||
})
|
||||
return false
|
||||
}
|
||||
var ids = []
|
||||
var hasCheck = table.checkStatus('plugin-table')
|
||||
var hasCheckData = hasCheck.data
|
||||
if (hasCheckData.length > 0) {
|
||||
$.each(hasCheckData, function (index, element) {
|
||||
ids.push(element.id)
|
||||
})
|
||||
}
|
||||
{#console.log(ids);#}
|
||||
layer.confirm('确定要删除选中数据', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, function (index) {
|
||||
layer.close(index)
|
||||
let loading = layer.load()
|
||||
$.ajax({
|
||||
|
||||
url: MODULE_PATH + 'batchRemove',
|
||||
data: {ids: ids},
|
||||
dataType: 'json',
|
||||
type: 'delete',
|
||||
success: function (result) {
|
||||
layer.close(loading)
|
||||
if (result.success) {
|
||||
popup.success(result.msg, function () {
|
||||
table.reload('plugin-table')
|
||||
})
|
||||
} else {
|
||||
popup.failure(result.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
window.refresh = function (param) {
|
||||
table.reload('plugin-table', {where: param})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
@@ -4,9 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>pear-admin-flask</title>
|
||||
<meta name="description" content="pear-admin-flask">
|
||||
<meta name="keyword" content="pear-admin-flask">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
{% include 'admin/common/header.html' %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='index/layui/css/layui.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='index/css/main.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='index/css/load.css') }}"/>
|
||||
|
||||
Reference in New Issue
Block a user