更新插件功能

This commit is contained in:
wojiaoyishang
2023-02-10 21:29:39 +08:00
parent 51db1944ed
commit 7bef64f632
24 changed files with 1442 additions and 137 deletions
+16 -13
View File
@@ -1,7 +1,7 @@
# flask配置 # flask配置
FLASK_APP=app.py FLASK_APP = app.py
FLASK_ENV=development FLASK_ENV = development
FLASK_DEBUG=1 FLASK_DEBUG = 1
FLASK_RUN_HOST = 127.0.0.1 FLASK_RUN_HOST = 127.0.0.1
FLASK_RUN_PORT = 5000 FLASK_RUN_PORT = 5000
@@ -9,21 +9,24 @@ FLASK_RUN_PORT = 5000
SYSTEM_NAME = Pear Admin SYSTEM_NAME = Pear Admin
# MySql配置信息 # MySql配置信息
MYSQL_HOST=127.0.0.1 MYSQL_HOST = 127.0.0.1
# MYSQL_HOST=dbserver # MYSQL_HOST = dbserver
MYSQL_PORT=3306 MYSQL_PORT = 3306
MYSQL_DATABASE=PearAdminFlask MYSQL_DATABASE = PearAdminFlask
MYSQL_USERNAME=root MYSQL_USERNAME = root
MYSQL_PASSWORD=root MYSQL_PASSWORD = root
# Redis 配置 # Redis 配置
# REDIS_HOST=127.0.0.1 # REDIS_HOST=127.0.0.1
# REDIS_PORT=6379 # REDIS_PORT=6379
# 密钥配置(记得改) # 密钥配置(记得改)
SECRET_KEY='pear-admin-flask' SECRET_KEY = 'pear-admin-flask'
# 邮箱配置 # 邮箱配置
MAIL_SERVER='smtp.qq.com' MAIL_SERVER = 'smtp.qq.com'
MAIL_USERNAME='123@qq.com' MAIL_USERNAME = '123@qq.com'
MAIL_PASSWORD='XXXXX' # 生成的授权码 MAIL_PASSWORD = 'XXXXX' # 生成的授权码
# 插件配置
PLUGIN_ENABLE_FOLDERS = ["HelloWorld", "helloworld"]
+15
View File
@@ -5,6 +5,18 @@ from urllib.parse import quote_plus as urlquote
from apscheduler.executors.pool import ThreadPoolExecutor from apscheduler.executors.pool import ThreadPoolExecutor
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore 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: class BaseConfig:
@@ -75,6 +87,9 @@ class BaseConfig:
'max_instances': 3 'max_instances': 3
} }
# 插件配置
PLUGIN_ENABLE_FOLDERS = os.getenv('PLUGIN_ENABLE_FOLDERS')
# 配置多个数据库连接的连接串写法示例 # 配置多个数据库连接的连接串写法示例
# HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称 # HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称
# MSSQL: f"mssql+pymssql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=cp936" # MSSQL: f"mssql+pymssql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=cp936"
+9
View File
@@ -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
+89
View File
@@ -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)
+108
View File
@@ -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: 父公司ID0未总公司
: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
+83
View File
@@ -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
+138
View File
@@ -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: 父ID0为顶级菜单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
+157
View File
@@ -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
+160
View File
@@ -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
+2 -1
View File
@@ -3,7 +3,7 @@ from applications.view.index import register_index_views
from applications.view.passport import register_passport_views from applications.view.passport import register_passport_views
from applications.view.rights import register_rights_view from applications.view.rights import register_rights_view
from applications.view.department import register_dept_views from applications.view.department import register_dept_views
from applications.view.plugin import register_plugin_views
def init_view(app): def init_view(app):
register_admin_views(app) register_admin_views(app)
@@ -11,3 +11,4 @@ def init_view(app):
register_rights_view(app) register_rights_view(app)
register_passport_views(app) register_passport_views(app)
register_dept_views(app) register_dept_views(app)
register_plugin_views(app)
+16 -6
View File
@@ -1,9 +1,10 @@
import os import os
import platform
import re import re
from datetime import datetime import sys
import time import time
import psutil import psutil
import platform
from datetime import datetime
from flask import Blueprint, render_template, jsonify from flask import Blueprint, render_template, jsonify
from applications.common.utils.rights import authorize from applications.common.utils.rights import authorize
@@ -22,8 +23,8 @@ def main():
python_version = platform.python_version() python_version = platform.python_version()
# 逻辑cpu数量 # 逻辑cpu数量
cpu_count = psutil.cpu_count() cpu_count = psutil.cpu_count()
# cup使用率 # cpu使用率
cpus_percent = psutil.cpu_percent(interval=0.1) cpus_percent = psutil.cpu_percent(interval=0.1, percpu=False) # percpu 获取主使用率
# 内存 # 内存
memory_information = psutil.virtual_memory() memory_information = psutil.virtual_memory()
# 内存使用率 # 内存使用率
@@ -79,10 +80,19 @@ def main():
@admin_monitor_bp.get('/polling') @admin_monitor_bp.get('/polling')
@authorize("admin:monitor:main") @authorize("admin:monitor:main")
def ajax_polling(): def ajax_polling():
# 获取cup使用率 # 获取cpu使用率
cpus_percent = psutil.cpu_percent(interval=0.1) cpus_percent = psutil.cpu_percent(interval=0.1, percpu=False) # percpu 获取主使用率
# 获取内存使用率 # 获取内存使用率
memory_information = psutil.virtual_memory() memory_information = psutil.virtual_memory()
memory_usage = memory_information.percent memory_usage = memory_information.percent
time_now = time.strftime('%H:%M:%S ', time.localtime(time.time())) 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) 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)
+169
View File
@@ -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))
+4
View File
@@ -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 (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 (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 (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 -- Table structure for admin_role
+9
View File
@@ -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": "-"
}
+18
View File
@@ -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)
+19
View File
@@ -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
View File
@@ -1,114 +1,129 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> {% include 'admin/common/header.html' %}
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title> Pear Admin Layui </title>
<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/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/loader.css') }}"/> <link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/admin.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/admin.css') }}"/> </head>
<!-- 主 题 更 换 --> <!-- 结 构 代 码 -->
<style id="pearadmin-bg-color"></style> <body class="layui-layout-body pear-admin">
</head> <!-- 布 局 框 架 -->
<!-- 结 构 代 码 --> <div class="layui-layout layui-layout-admin">
<body class="layui-layout-body pear-admin"> <!-- 顶 部 样 式 -->
<!-- 布 局 框 架 --> <div class="layui-header">
<div class="layui-layout layui-layout-admin"> <!-- 菜 单 顶 部 -->
<div class="layui-header"> <div class="layui-logo">
<!-- 顶 部 左 侧 功 能 --> <!-- 图 标 -->
<ul class="layui-nav layui-layout-left"> <img class="logo">
<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="刷新页面" <span class="title"></span>
loading=600></a></li> </div>
</ul> <!-- 顶 部 左 侧 功 能 -->
<!-- 顶 部 右 侧 菜 单 --> <ul class="layui-nav layui-layout-left">
<div id="control" class="layui-layout-control"></div> <li class="collapse layui-nav-item"><a href="#" class="layui-icon layui-icon-shrink-right"></a></li>
<ul class="layui-nav layui-layout-right"> <li class="refresh layui-nav-item"><a href="#" class="layui-icon layui-icon-refresh-1" loading = 600></a></li>
<li class="layui-nav-item layui-hide-xs"> </ul>
<a href="#" class="fullScreen layui-icon layui-icon-screen-full"></a> <!-- 多 系 统 菜 单 -->
</li> <div id="control" class="layui-layout-control"></div>
<li class="layui-nav-item layui-hide-xs"> <!-- 顶 部 右 侧 菜 单 -->
<a href="http://www.pearadmin.com" class="layui-icon layui-icon-website"></a> <ul class="layui-nav layui-layout-right">
</li> <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 message"></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 user"> <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>
<a href="javascript:;"> <li class="layui-nav-item user">
<img src="{{ user.avatar }}" id="avatar" class="layui-nav-img"> <!-- 头 像 -->
<span>{{ user.realname }}</span> <a class="layui-icon layui-icon-username" href="javascript:;"></a>
</a> <!-- 功 能 菜 单 -->
<!-- 功 能 菜 单 --> <dl class="layui-nav-child">
<dl class="layui-nav-child"> <dd><a user-menu-url="/admin/user/center" user-menu-id="5555" user-menu-title="基本资料">基本资料</a></dd>
<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>
<dd><a href="javascript:void(0);" class="logout">注销登录</a></dd> </dl>
</dl> </li>
</li> <!-- 主 题 配 置 -->
<li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li> <li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li>
</ul> </ul>
</div> </div>
<!-- 侧 边 区 域 --> <!-- 侧 边 区 域 -->
<div class="layui-side layui-bg-black"> <div class="layui-side layui-bg-black">
<!-- 顶 部 图 标 --> <!-- 菜 单 顶 部 -->
<div class="layui-logo"> <div class="layui-logo">
<img class="logo"/> <!-- 图 标 -->
<span class="title"></span> <img class="logo">
</div> <!-- 标 题 -->
<!-- 侧 边 菜 单 --> <span class="title"></span>
<div class="layui-side-scroll"> </div>
<div id="sideMenu"></div> <!-- 菜 单 内 容 -->
</div> <div class="layui-side-scroll">
</div> <div id="sideMenu"></div>
<div class="layui-body"> </div>
<div id="content"></div> </div>
</div> <!-- 视 图 页 面 -->
</div> <div class="layui-body">
<!-- 遮 盖 层 --> <!-- 内 容 页 面 -->
<div class="pear-cover"></div> <div id="content"></div>
<!-- 移 动 端 便 捷 操 作 --> </div>
<div class="pear-collasped-pe collaspe"><a href="#" class="layui-icon layui-icon-shrink-right"></a></div> <!-- 页脚 -->
<!-- 加 载 动 画--> <div class="layui-footer layui-text">
<div class="loader-main"> <span class="left">
<div class="loader"></div> <span>
</div> 官网:<a href="http://www.pearadmin.com/team.html" target="_blank">www.pearadmin.com</a>&nbsp;&nbsp;&nbsp;
<!-- 依 赖 脚 本 --> 仓库:<a href="https://gitee.com/pear-admin/Pear-Admin-Layui" target="_blank">gitee.com/pear-admin</a>
{% include 'admin/common/footer.html' %} </span>
<script> </span>
layui.use(['admin', 'jquery', 'layer', 'popup'], function () { <span class="center"></span>
let admin = layui.admin <span class="right"></span>
let $ = layui.jquery </div>
let layer = layui.layer <!-- 遮 盖 层 -->
let popup = layui.popup <div class="pear-cover"></div>
<!-- 加 载 动 画 -->
// 框架初始化时会读取 根目录下 pear.configs.yml 文件作为初始化配置 <div class="loader-main">
// 你可以通过 admin.setConfigPath 方法修改配置文件位置 <!-- 动 画 对 象 -->
// 你可以通过 admin.setConfigType 方法修改配置文件类型 <div class="loader"></div>
admin.setConfigType('json') </div>
admin.setConfigPath("{{ url_for('rights.configs') }}") </div>
<!-- 移 动 端 便 捷 操 作 -->
admin.render() <div class="pear-collapsed-pe collapse">
<a href="#" class="layui-icon layui-icon-shrink-right"></a>
// 注销实现 </div>
admin.logout(function () { <!-- 依 赖 脚 本 -->
let loading = layer.load() {% include 'admin/common/footer.html' %}
$.ajax({ <!-- 框 架 初 始 化 -->
url: '/passport/logout', <script>
dataType: 'json', layui.use(['admin','jquery','popup','drawer'], function() {
type: 'post', var $ = layui.jquery;
success: function (result) { var admin = layui.admin;
layer.close(loading) var popup = layui.popup;
if (result.success) {
popup.success(result.msg, function () { admin.setConfigType('json')
location.href = '/admin' admin.setConfigPath("{{ url_for('rights.configs') }}")
})
return true admin.render();
}
} // 登出逻辑
}) admin.logout(function () {
}) let loading = layer.load()
$.ajax({
// 初始化消息事件 url: '/passport/logout',
admin.message() dataType: 'json',
}) type: 'post',
</script> success: function (result) {
</body> layer.close(loading)
</html> if (result.success) {
popup.success(result.msg, function () {
location.href = '/admin'
})
return true
}
}
})
})
// 自定义消息点击回调
admin.message(function(id, title, context, form) {});
})
</script>
</body>
</html>
+1 -1
View File
@@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<title>登录</title> <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/component/pear/css/pear.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/other/login.css') }}"/> <link rel="stylesheet" href="{{ url_for('static', filename='admin/admin/css/other/login.css') }}"/>
+1 -1
View File
@@ -118,7 +118,7 @@
skin: 'line', skin: 'line',
height: 'full-148', height: 'full-148',
toolbar: '#mail-toolbar', /*工具栏*/ toolbar: '#mail-toolbar', /*工具栏*/
text: { none: '暂无人员信息' }, text: { none: '暂无邮件信息' },
defaultToolbar: [{ layEvent: 'refresh', icon: 'layui-icon-refresh' }, 'filter', 'print', 'exports'] /*默认工具栏*/ defaultToolbar: [{ layEvent: 'refresh', icon: 'layui-icon-refresh' }, 'filter', 'print', 'exports'] /*默认工具栏*/
}) })
+7
View File
@@ -145,6 +145,13 @@
<td>python版本</td> <td>python版本</td>
<td>{{ python_version }}</td> <td>{{ python_version }}</td>
</tr> </tr>
<tr>
<td>程序操作</td>
<td>
<a href="javascript:kill();"
class="pear-btn pear-btn-xs pear-btn-primary">关闭程序</a>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
+292
View File
@@ -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>
+1 -3
View File
@@ -4,9 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>pear-admin-flask</title> <title>pear-admin-flask</title>
<meta name="description" content="pear-admin-flask"> <meta name="description" content="pear-admin-flask">
<meta name="keyword" content="pear-admin-flask"> {% include 'admin/common/header.html' %}
<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="stylesheet" href="{{ url_for('static', filename='index/layui/css/layui.css') }}"/> <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/main.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename='index/css/load.css') }}"/> <link rel="stylesheet" href="{{ url_for('static', filename='index/css/load.css') }}"/>