Initial YakPanel commit
This commit is contained in:
495
mod/base/push_mod/__init__.py
Normal file
495
mod/base/push_mod/__init__.py
Normal file
@@ -0,0 +1,495 @@
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Union
|
||||
|
||||
from .mods import TaskConfig, TaskTemplateConfig, TaskRecordConfig, SenderConfig, load_task_template_by_config, \
|
||||
load_task_template_by_file, UPDATE_MOD_PUSH_FILE, UPDATE_VERSION_FILE, PUSH_DATA_PATH
|
||||
from .base_task import BaseTask
|
||||
from .send_tool import WxAccountMsg, WxAccountLoginMsg, WxAccountMsgBase
|
||||
from .system import PushSystem, get_push_public_data, push_by_task_keyword, push_by_task_id
|
||||
from .manager import PushManager
|
||||
from .util import read_file, write_file
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TaskConfig",
|
||||
"TaskTemplateConfig",
|
||||
"TaskRecordConfig",
|
||||
"SenderConfig",
|
||||
"load_task_template_by_config",
|
||||
"load_task_template_by_file",
|
||||
"BaseTask",
|
||||
"WxAccountMsg",
|
||||
"WxAccountLoginMsg",
|
||||
"WxAccountMsgBase",
|
||||
"PushSystem",
|
||||
"get_push_public_data",
|
||||
"PushManager",
|
||||
"push_by_task_keyword",
|
||||
"push_by_task_id",
|
||||
"UPDATE_MOD_PUSH_FILE",
|
||||
"update_mod_push_system",
|
||||
"UPDATE_VERSION_FILE",
|
||||
"PUSH_DATA_PATH",
|
||||
"get_default_module_dict",
|
||||
]
|
||||
|
||||
|
||||
def update_mod_push_system():
|
||||
if os.path.exists(UPDATE_MOD_PUSH_FILE):
|
||||
return
|
||||
|
||||
# 只将已有的告警任务("site_push", "system_push", "database_push") 移动
|
||||
|
||||
try:
|
||||
push_data = json.loads(read_file("/www/server/panel/class/push/push.json"))
|
||||
except:
|
||||
return
|
||||
|
||||
if not isinstance(push_data, dict):
|
||||
return
|
||||
pmgr = PushManager()
|
||||
default_module_dict = get_default_module_dict()
|
||||
for key, value in push_data.items():
|
||||
if key == "site_push":
|
||||
_update_site_push(value, pmgr, default_module_dict)
|
||||
elif key == "system_push":
|
||||
_update_system_push(value, pmgr, default_module_dict)
|
||||
elif key == "database_push":
|
||||
_update_database_push(value, pmgr, default_module_dict)
|
||||
elif key == "rsync_push":
|
||||
_update_rsync_push(value, pmgr, default_module_dict)
|
||||
elif key == "load_balance_push":
|
||||
_update_load_push(value, pmgr, default_module_dict)
|
||||
elif key == "task_manager_push":
|
||||
_update_task_manager_push(value, pmgr, default_module_dict)
|
||||
|
||||
write_file(UPDATE_MOD_PUSH_FILE, "")
|
||||
|
||||
|
||||
def get_default_module_dict():
|
||||
res = {}
|
||||
# wx_account_list = []
|
||||
for data in SenderConfig().config:
|
||||
if not data["used"]:
|
||||
continue
|
||||
if data.get("original", False):
|
||||
res[data["sender_type"]] = data["id"]
|
||||
|
||||
if data["sender_type"] == "webhook":
|
||||
res[data["data"].get("title")] = data["id"]
|
||||
|
||||
# if data["sender_type"] == "wx_account":
|
||||
# wx_account_list.append(data)
|
||||
|
||||
# wx_account_list.sort(key=lambda x: x.get("data", {}).get("create_time", ""))
|
||||
# if wx_account_list:
|
||||
# res["wx_account"] = wx_account_list[0]["id"]
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _update_site_push(old_data: Dict[str, Dict[str, Union[str, int, float, list]]],
|
||||
pmgr: PushManager,
|
||||
df_mdl: Dict[str, str]):
|
||||
|
||||
for k, v in old_data.items():
|
||||
sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl]
|
||||
if v["type"] == "ssl":
|
||||
push_data = {
|
||||
"template_id": "1",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"project": v.get("project", "all"),
|
||||
"cycle": v.get("cycle", 15)
|
||||
},
|
||||
"number_rule": {
|
||||
"total": v.get("push_count", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "site_endtime":
|
||||
push_data = {
|
||||
"template_id": "2",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"cycle": v.get("cycle", 7)
|
||||
},
|
||||
"number_rule": {
|
||||
"total": v.get("push_count", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "panel_pwd_endtime":
|
||||
push_data = {
|
||||
"template_id": "3",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"cycle": v.get("cycle", 15),
|
||||
"interval": 600
|
||||
},
|
||||
"number_rule": {
|
||||
"total": v.get("push_count", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "ssh_login_error":
|
||||
push_data = {
|
||||
"template_id": "4",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"cycle": v.get("cycle", 30),
|
||||
"count": v.get("count", 3),
|
||||
"interval": v.get("interval", 600)
|
||||
},
|
||||
"number_rule": {
|
||||
"day_num": v.get("day_limit", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "services":
|
||||
push_data = {
|
||||
"template_id": "5",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"project": v.get("project", "nginx"),
|
||||
"count": v.get("count", 3),
|
||||
"interval": v.get("interval", 600)
|
||||
},
|
||||
"number_rule": {
|
||||
"day_num": v.get("day_limit", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "panel_safe_push":
|
||||
push_data = {
|
||||
"template_id": "6",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {},
|
||||
"number_rule": {
|
||||
"day_num": v.get("day_limit", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "ssh_login":
|
||||
push_data = {
|
||||
"template_id": "7",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {},
|
||||
"number_rule": {}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "panel_login":
|
||||
push_data = {
|
||||
"template_id": "8",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {},
|
||||
"number_rule": {}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "project_status":
|
||||
push_data = {
|
||||
"template_id": "9",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"cycle": v.get("cycle", 1),
|
||||
"project": v.get("project", 0),
|
||||
"count": v.get("count", 2) if v.get("count", 2) not in (1, 2) else 2,
|
||||
"interval": v.get("interval", 600)
|
||||
},
|
||||
"number_rule": {
|
||||
"day_num": v.get("push_count", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "panel_update":
|
||||
push_data = {
|
||||
"template_id": "10",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {},
|
||||
"number_rule": {
|
||||
"day_num": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
send_type = None
|
||||
login_send_type_conf = "/www/server/panel/data/panel_login_send.pl"
|
||||
if os.path.exists(login_send_type_conf):
|
||||
send_type = read_file(login_send_type_conf).strip()
|
||||
else:
|
||||
# user_info["server_id"]之前的
|
||||
if os.path.exists("/www/server/panel/data/login_send_type.pl"):
|
||||
send_type = read_file("/www/server/panel/data/login_send_type.pl")
|
||||
else:
|
||||
if os.path.exists('/www/server/panel/data/login_send_mail.pl'):
|
||||
send_type = "mail"
|
||||
if os.path.exists('/www/server/panel/data/login_send_dingding.pl'):
|
||||
send_type = "dingding"
|
||||
|
||||
if isinstance(send_type, str):
|
||||
sender_list = [df_mdl[i.strip()] for i in send_type.split(",") if i.strip() in df_mdl]
|
||||
push_data = {
|
||||
"template_id": "8",
|
||||
"task_data": {
|
||||
"status": True,
|
||||
"sender": sender_list,
|
||||
"task_data": {},
|
||||
"number_rule": {}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
login_send_type_conf = "/www/server/panel/data/ssh_send_type.pl"
|
||||
if os.path.exists(login_send_type_conf):
|
||||
ssh_send_type = read_file(login_send_type_conf).strip()
|
||||
if isinstance(ssh_send_type, str):
|
||||
sender_list = [df_mdl[i.strip()] for i in ssh_send_type.split(",") if i.strip() in df_mdl]
|
||||
push_data = {
|
||||
"template_id": "7",
|
||||
"task_data": {
|
||||
"status": True,
|
||||
"sender": sender_list,
|
||||
"task_data": {},
|
||||
"number_rule": {}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
return
|
||||
|
||||
|
||||
def _update_system_push(old_data: Dict[str, Dict[str, Union[str, int, float, list]]],
|
||||
pmgr: PushManager,
|
||||
df_mdl: Dict[str, str]):
|
||||
|
||||
for k, v in old_data.items():
|
||||
sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl]
|
||||
if v["type"] == "disk":
|
||||
push_data = {
|
||||
"template_id": "20",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"project": v.get("project", "/"),
|
||||
"cycle": v.get("cycle", 2) if v.get("cycle", 2) not in (1, 2) else 2,
|
||||
"count": v.get("count", 80),
|
||||
},
|
||||
"number_rule": {
|
||||
"total": v.get("push_count", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
if v["type"] == "disk":
|
||||
push_data = {
|
||||
"template_id": "21",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"cycle": v.get("cycle", 5) if v.get("cycle", 5) not in (3, 5, 15) else 5,
|
||||
"count": v.get("count", 80),
|
||||
},
|
||||
"number_rule": {
|
||||
"total": v.get("push_count", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
if v["type"] == "load":
|
||||
push_data = {
|
||||
"template_id": "22",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"cycle": v.get("cycle", 5) if v.get("cycle", 5) not in (1, 5, 15) else 5,
|
||||
"count": v.get("count", 80),
|
||||
},
|
||||
"number_rule": {
|
||||
"total": v.get("push_count", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
if v["type"] == "mem":
|
||||
push_data = {
|
||||
"template_id": "23",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"cycle": v.get("cycle", 5) if v.get("cycle", 5) not in (3, 5, 15) else 5,
|
||||
"count": v.get("count", 80),
|
||||
},
|
||||
"number_rule": {
|
||||
"total": v.get("push_count", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def _update_database_push(old_data: Dict[str, Dict[str, Union[str, int, float, list]]],
|
||||
pmgr: PushManager,
|
||||
df_mdl: Dict[str, str]):
|
||||
|
||||
for k, v in old_data.items():
|
||||
sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl]
|
||||
if v["type"] == "mysql_pwd_endtime":
|
||||
push_data = {
|
||||
"template_id": "30",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"project": v.get("project", []),
|
||||
"cycle": v.get("cycle", 15),
|
||||
},
|
||||
"number_rule": {}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
elif v["type"] == "mysql_replicate_status":
|
||||
push_data = {
|
||||
"template_id": "31",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"project": v.get("project", []),
|
||||
"count": v.get("cycle", 15),
|
||||
"interval": v.get("interval", 600)
|
||||
},
|
||||
"number_rule": {}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _update_rsync_push(
|
||||
old_data: Dict[str, Dict[str, Union[str, int, float, list]]],
|
||||
pmgr: PushManager,
|
||||
df_mdl: Dict[str, str]):
|
||||
|
||||
for k, v in old_data.items():
|
||||
sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl]
|
||||
push_data = {
|
||||
"template_id": "40",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"interval": v.get("interval", 600)
|
||||
},
|
||||
"number_rule": {
|
||||
"day_num": v.get("push_count", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
|
||||
def _update_load_push(
|
||||
old_data: Dict[str, Dict[str, Union[str, int, float, list]]],
|
||||
pmgr: PushManager,
|
||||
df_mdl: Dict[str, str]):
|
||||
|
||||
for k, v in old_data.items():
|
||||
sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl]
|
||||
push_data = {
|
||||
"template_id": "50",
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"project": v.get("project", ""),
|
||||
"cycle": v.get("cycle", "200|301|302|403|404")
|
||||
},
|
||||
"number_rule": {
|
||||
"day_num": v.get("push_count", 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
|
||||
def _update_task_manager_push(
|
||||
old_data: Dict[str, Dict[str, Union[str, int, float, list]]],
|
||||
pmgr: PushManager,
|
||||
df_mdl: Dict[str, str]):
|
||||
|
||||
for k, v in old_data.items():
|
||||
sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl]
|
||||
template_id_dict = {
|
||||
"task_manager_cpu": "60",
|
||||
"task_manager_mem": "61",
|
||||
"task_manager_process": "62"
|
||||
}
|
||||
if v["type"] in template_id_dict:
|
||||
push_data = {
|
||||
"template_id": template_id_dict[v["type"]],
|
||||
"task_data": {
|
||||
"status": bool(v.get("status", True)),
|
||||
"sender": sender_list,
|
||||
"task_data": {
|
||||
"project": v.get("project", ""),
|
||||
"count": v.get("count", 80),
|
||||
"interval": v.get("count", 600),
|
||||
},
|
||||
"number_rule": {
|
||||
"day_num": v.get("push_count", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
pmgr.set_task_conf_data(push_data)
|
||||
|
||||
BIN
mod/base/push_mod/__pycache__/site_push.cpython-314.pyc
Normal file
BIN
mod/base/push_mod/__pycache__/site_push.cpython-314.pyc
Normal file
Binary file not shown.
215
mod/base/push_mod/base_task.py
Normal file
215
mod/base/push_mod/base_task.py
Normal file
@@ -0,0 +1,215 @@
|
||||
from typing import Union, Optional, List, Tuple
|
||||
from .send_tool import WxAccountMsg
|
||||
|
||||
|
||||
# 告警系统在处理每个任务时,都会重新建立有一个Task的对象,(请勿在__init__的初始化函数中添加任何参数)
|
||||
# 故每个对象中都可以大胆存放本任务所有数据,不会影响同类型的其他任务
|
||||
class BaseTask:
|
||||
|
||||
def __init__(self):
|
||||
self.source_name: str = ''
|
||||
self.title: str = '' # 这个是告警任务的标题(根据实际情况改变)
|
||||
self.template_name: str = '' # 这个告警模板的标题(不会改变)
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查设置的告警参数(是否合理)
|
||||
@param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
||||
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
||||
当检查有误时, 直接返回错误信息的字符串
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字,
|
||||
后续通过规则id和来源tamper 查询并使用
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个标题
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
if self.title:
|
||||
return self.title
|
||||
return self.template_name
|
||||
|
||||
def task_run_end_hook(self, res: dict) -> None:
|
||||
"""
|
||||
在告警系统中。执行完了任务后,会去掉用这个函数
|
||||
@type res: dict, 执行任务的结果
|
||||
@return:
|
||||
"""
|
||||
return
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> None:
|
||||
"""
|
||||
在告警管理中。更新任务数据后,会去掉用这个函数
|
||||
@return:
|
||||
"""
|
||||
return
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
"""
|
||||
在告警管理中。移除这个任务后,会去掉用这个函数
|
||||
@return:
|
||||
"""
|
||||
return
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> None:
|
||||
"""
|
||||
在告警管理中。新建这个任务后,会去掉用这个函数
|
||||
@return:
|
||||
"""
|
||||
return
|
||||
|
||||
def check_time_rule(self, time_rule: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查和修改设置的告警的时间控制参数是是否合理
|
||||
可以添加参数 get_by_func 字段用于指定使用本类中的那个函数执行时间判断标准, 替换标准的时间规则判断功能
|
||||
↑示例如本类中的: can_send_by_time_rule
|
||||
@param time_rule: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
||||
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
||||
当检查有误时, 直接返回错误信息的字符串
|
||||
"""
|
||||
return time_rule
|
||||
|
||||
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查和修改设置的告警的次数控制参数是是否合理
|
||||
可以添加参数 get_by_func 字段用于指定使用本类中的那个函数执行次数判断标准, 替换标准的次数规则判断功能
|
||||
↑示例如本类中的: can_send_by_num_rule
|
||||
@param num_rule: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
||||
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
||||
当检查有误时, 直接返回错误信息的字符串
|
||||
"""
|
||||
return num_rule
|
||||
|
||||
def can_send_by_num_rule(self, task_id: str, task_data: dict, number_rule: dict, push_data: dict) -> Optional[str]:
|
||||
"""
|
||||
这是一个通过函数判断是否能够发送告警的示例,并非每一个告警任务都需要有
|
||||
@param task_id: 任务id
|
||||
@param task_data: 告警参数信息
|
||||
@param number_rule: 次数控制信息
|
||||
@param push_data: 本次要发送的告警信息的原文,应当为字典, 来自 get_push_data 函数的返回值
|
||||
@return: 返回None
|
||||
"""
|
||||
return None
|
||||
|
||||
def can_send_by_time_rule(self, task_id: str, task_data: dict, time_rule: dict, push_data: dict) -> Optional[str]:
|
||||
"""
|
||||
这是一个通过函数判断是否能够发送告警的示例,并非每一个告警任务都需要有
|
||||
@param task_id: 任务id
|
||||
@param task_data: 告警参数信息
|
||||
@param time_rule: 时间控制信息
|
||||
@param push_data: 本次要发送的告警信息的原文,应当为字典, 来自 get_push_data 函数的返回值
|
||||
@return:
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
"""
|
||||
判断这个任务是否需要返送
|
||||
@param task_id: 任务id
|
||||
@param task_data: 任务的告警参数
|
||||
@return: 如果触发了告警,返回一个dict的原文,作为告警信息,否则应当返回None表示未触发
|
||||
返回之中应当包含一个 msg_list 的键(值为List[str]类型),将主要的信息返回
|
||||
用于以下信息的自动序列化包含[dingding, feishu, mail, weixin, web_hook]
|
||||
短信和微信公众号由于长度问题,必须每个任务手动实现
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
"""
|
||||
过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务
|
||||
@param template: 任务的模板信息
|
||||
@return:
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
# push_public_data 公共的告警参数提取位置
|
||||
# 内容包含:
|
||||
# ip 网络ip
|
||||
# local_ip 本机ip
|
||||
# time 时间日志的字符串
|
||||
# timestamp 当前的时间戳
|
||||
# server_name 服务器别名
|
||||
def to_dingding_msg(self, push_data: dict, push_public_data: dict) -> str:
|
||||
msg_list = push_data.get('msg_list', None)
|
||||
if msg_list is None:
|
||||
raise ValueError("Task: {} alert push data parameter error, there is no msg_list field".format(self.title))
|
||||
return self.public_headers_msg(push_public_data,dingding=True) + "\n\n" + "\n\n".join(msg_list)
|
||||
|
||||
def to_feishu_msg(self, push_data: dict, push_public_data: dict) -> str:
|
||||
msg_list = push_data.get('msg_list', None)
|
||||
if msg_list is None:
|
||||
raise ValueError("Task: {} alert push data parameter error, there is no msg_list field".format(self.title))
|
||||
return self.public_headers_msg(push_public_data) + "\n\n" + "\n\n".join(msg_list)
|
||||
|
||||
def to_mail_msg(self, push_data: dict, push_public_data: dict) -> str:
|
||||
msg_list = push_data.get('msg_list', None)
|
||||
if msg_list is None:
|
||||
raise ValueError("Task: {} alert push data parameter error, there is no msg_list field".format(self.title))
|
||||
public_headers = self.public_headers_msg(push_public_data, "<br>")
|
||||
return public_headers + "<br>" + "<br>".join(msg_list)
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
"""
|
||||
返回 短信告警的类型和数据
|
||||
@param push_data:
|
||||
@param push_public_data:
|
||||
@return: 第一项是类型, 第二项是数据
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
def to_tg_msg(self, push_data: dict, push_public_data: dict) -> str:
|
||||
msg_list = push_data.get('msg_list', None)
|
||||
if msg_list is None:
|
||||
raise ValueError("Task: {} alert push data parameter error, there is no msg_list field".format(self.title))
|
||||
public_headers = self.public_headers_msg(push_public_data, "<br>")
|
||||
return public_headers + "<br>" + "<br>".join(msg_list)
|
||||
def to_weixin_msg(self, push_data: dict, push_public_data: dict) -> str:
|
||||
msg_list = push_data.get('msg_list', None)
|
||||
if msg_list is None:
|
||||
raise ValueError("Task: {} alert push data parameter error, there is no msg_list field".format(self.title))
|
||||
spc = "\n "
|
||||
public_headers = self.public_headers_msg(push_public_data, "\n ")
|
||||
return public_headers + spc + spc.join(msg_list)
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
raise NotImplementedError()
|
||||
|
||||
def to_web_hook_msg(self, push_data: dict, push_public_data: dict) -> str:
|
||||
msg_list = push_data.get('msg_list', None)
|
||||
if msg_list is None:
|
||||
raise ValueError("Task: {} alert push data parameter error, there is no msg_list field".format(self.title))
|
||||
public_headers = self.public_headers_msg(push_public_data, "\n")
|
||||
return public_headers + "\n" + "\n".join(msg_list)
|
||||
|
||||
def public_headers_msg(self, push_public_data: dict, spc: str = None,dingding=False) -> str:
|
||||
if spc is None:
|
||||
spc = "\n\n"
|
||||
title = self.title
|
||||
print(title)
|
||||
if dingding:
|
||||
print("dingdingtitle",title)
|
||||
if "yakpanel" not in title:
|
||||
title += "yakpanel"
|
||||
print("dingdingtitle",title)
|
||||
|
||||
print(title)
|
||||
return spc.join([
|
||||
"#### {}".format(title),
|
||||
">Server:" + push_public_data['server_name'],
|
||||
">IPAddress: {}(Internet) {}(Internal)".format(push_public_data['ip'], push_public_data['local_ip']),
|
||||
">SendingTime: " + push_public_data['time']
|
||||
])
|
||||
|
||||
class BaseTaskViewMsg:
|
||||
|
||||
def get_msg(self, task: dict) -> Optional[str]:
|
||||
return ""
|
||||
27
mod/base/push_mod/compatible.py
Normal file
27
mod/base/push_mod/compatible.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
from .util import read_file, write_file
|
||||
|
||||
|
||||
def rsync_compatible():
|
||||
files = [
|
||||
"/www/server/panel/class/push/rsync_push.py",
|
||||
"/www/server/panel/plugin/rsync/rsync_push.py",
|
||||
]
|
||||
for f in files:
|
||||
print(f)
|
||||
if not os.path.exists(f):
|
||||
continue
|
||||
src_data = read_file(f)
|
||||
if src_data.find("push_rsync_by_task_name") != -1:
|
||||
continue
|
||||
src_data = src_data.replace("""if __name__ == "__main__":
|
||||
rsync_push().main()""", """
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.path.insert(0, "/www/server/panel")
|
||||
from mod.base.push_mod.rsync_push import push_rsync_by_task_name
|
||||
push_rsync_by_task_name(sys.argv[1])
|
||||
except:
|
||||
rsync_push().main()
|
||||
""")
|
||||
write_file(f, src_data)
|
||||
239
mod/base/push_mod/database_push.py
Normal file
239
mod/base/push_mod/database_push.py
Normal file
@@ -0,0 +1,239 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import ipaddress
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Tuple, Union, Optional
|
||||
|
||||
from .send_tool import WxAccountMsg
|
||||
from .base_task import BaseTask
|
||||
from .util import read_file, DB, GET_CLASS
|
||||
|
||||
try:
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
from panel_msg.collector import DatabasePushMsgCollect
|
||||
except ImportError:
|
||||
DatabasePushMsgCollect = None
|
||||
|
||||
|
||||
def is_ipaddress(ip_data: str) -> bool:
|
||||
try:
|
||||
ipaddress.ip_address(ip_data)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# class MysqlPwdEndTimeTask(BaseTask):
|
||||
#
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
# self.template_name = "MySQL数据库密码到期"
|
||||
# self.source_name = "mysql_pwd_end"
|
||||
#
|
||||
# self.push_db_user = ""
|
||||
#
|
||||
# def get_title(self, task_data: dict) -> str:
|
||||
# return "Msql:" + task_data["project"][1] + "用户密码到期提醒"
|
||||
#
|
||||
# def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
# task_data["interval"] = 600
|
||||
# if not (isinstance(task_data["project"], list) and len(task_data["project"]) == 3):
|
||||
# return "设置的用户格式错误"
|
||||
# project = task_data["project"]
|
||||
# if not (isinstance(project[0], int) and isinstance(project[1], str) and is_ipaddress(project[2])):
|
||||
# return "设置的检测用户格式错误"
|
||||
#
|
||||
# if not (isinstance(task_data["cycle"], int) and task_data["cycle"] >= 1):
|
||||
# return "到期时间参数错误,至少为 1 天"
|
||||
# return task_data
|
||||
#
|
||||
# def get_keyword(self, task_data: dict) -> str:
|
||||
# return "_".join([str(i) for i in task_data["project"]])
|
||||
#
|
||||
# def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
|
||||
# num_rule["day_num"] = 1
|
||||
# return num_rule
|
||||
#
|
||||
# def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
# sid = task_data["project"][0]
|
||||
# username = task_data["project"][1]
|
||||
# host = task_data["project"][2]
|
||||
#
|
||||
# if "/www/server/panel/class" not in sys.path:
|
||||
# sys.path.insert(0, "/www/server/panel/class")
|
||||
# try:
|
||||
# import panelMysql
|
||||
# import db_mysql
|
||||
# except ImportError:
|
||||
# return None
|
||||
#
|
||||
# if sid == 0:
|
||||
# try:
|
||||
# db_port = int(panelMysql.panelMysql().query("show global variables like 'port'")[0][1])
|
||||
# if db_port == 0:
|
||||
# db_port = 3306
|
||||
# except:
|
||||
# db_port = 3306
|
||||
# conn_config = {
|
||||
# "db_host": "localhost",
|
||||
# "db_port": db_port,
|
||||
# "db_user": "root",
|
||||
# "db_password": DB("config").where("id=?", (1,)).getField("mysql_root"),
|
||||
# "ps": "local server",
|
||||
# }
|
||||
# else:
|
||||
# conn_config = DB("database_servers").where("id=? AND LOWER(db_type)=LOWER('mysql')", (sid,)).find()
|
||||
# if not conn_config:
|
||||
# return None
|
||||
#
|
||||
# mysql_obj = db_mysql.panelMysql().set_host(conn_config["db_host"], conn_config["db_port"], None,
|
||||
# conn_config["db_user"], conn_config["db_password"])
|
||||
# if isinstance(mysql_obj, bool):
|
||||
# return None
|
||||
#
|
||||
# data_list = mysql_obj.query(
|
||||
# "SELECT password_last_changed FROM mysql.user WHERE user='{}' AND host='{}';".format(username, host))
|
||||
#
|
||||
# if not isinstance(data_list, list) or not data_list:
|
||||
# return None
|
||||
#
|
||||
# try:
|
||||
# # todo:检查这里的时间转化逻辑问题
|
||||
# last_time = data_list[0][0]
|
||||
# expire_time = last_time + timedelta(days=task_data["cycle"])
|
||||
# except:
|
||||
# return None
|
||||
#
|
||||
# if datetime.now() > expire_time:
|
||||
# self.title = self.get_title(task_data)
|
||||
# self.push_db_user = username
|
||||
# return {"msg_list": [
|
||||
# ">告警类型:MySQL密码即将到期",
|
||||
# ">Content of alarm: {} {}@{} 密码过期时间<font color=#ff0000>{} 天</font>".format(
|
||||
# conn_config["ps"], username, host, expire_time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
# ]}
|
||||
#
|
||||
# def filter_template(self, template: dict) -> Optional[dict]:
|
||||
# return template
|
||||
#
|
||||
# def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
# return "", {}
|
||||
#
|
||||
# def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
# msg = WxAccountMsg.new_msg()
|
||||
# msg.thing_type = "MySQL数据库密码到期"
|
||||
# msg.msg = "Mysql用户:{}的密码即将过期,请注意".format(self.push_db_user)
|
||||
# msg.next_msg = "Please log in to the panel to view the host status"
|
||||
# return msg
|
||||
|
||||
|
||||
class MysqlReplicateStatusTask(BaseTask):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.template_name = "MySQL主从复制异常告警"
|
||||
self.source_name = "mysql_replicate_status"
|
||||
self.title = "MySQL主从复制异常告警"
|
||||
|
||||
self.slave_ip = ''
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if not (isinstance(task_data["project"], str) and task_data["project"]):
|
||||
return "请选择告警的从库!"
|
||||
|
||||
if not (isinstance(task_data["count"], int) and task_data["count"] in (1, 2)):
|
||||
return "是否自动修复选择错误!"
|
||||
|
||||
if not (isinstance(task_data["interval"], int) and task_data["interval"] >= 60):
|
||||
return "检查间隔时间错误,至少需要60s的间隔"
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
import PluginLoader
|
||||
|
||||
args = GET_CLASS()
|
||||
args.slave_ip = task_data["project"]
|
||||
res = PluginLoader.plugin_run("mysql_replicate", "get_replicate_status", args)
|
||||
if res.get("status", False) is False:
|
||||
return None
|
||||
|
||||
self.slave_ip = task_data["project"]
|
||||
if len(res.get("data", [])) == 0:
|
||||
s_list = [">告警类型:MySQL主从复制异常告警",
|
||||
">Content of alarm: <font color=#ff0000>从库 {} 主从复制已停止,请尽快登录面板查看详情</font>".format(
|
||||
task_data["project"])]
|
||||
return {"msg_list": s_list}
|
||||
|
||||
sql_status = io_status = False
|
||||
for item in res.get("data", []):
|
||||
if item["name"] == "Slave_IO_Running" and item["value"] == "Yes":
|
||||
io_status = True
|
||||
if item["name"] == "Slave_SQL_Running" and item["value"] == "Yes":
|
||||
sql_status = True
|
||||
if io_status is True and sql_status is True:
|
||||
break
|
||||
|
||||
if io_status is False or sql_status is False:
|
||||
repair_txt = "请尽快登录面板查看详情"
|
||||
if task_data["count"] == 1: # 自动修复
|
||||
PluginLoader.plugin_run("mysql_replicate", "repair_replicate", args)
|
||||
repair_txt = ",正在尝试修复"
|
||||
|
||||
s_list = [">告警类型:MySQL主从复制异常告警",
|
||||
">Content of alarm: <font color=#ff0000>从库 {} 主从复制发生异常{}</font>".format(
|
||||
task_data["project"], repair_txt)]
|
||||
return {"msg_list": s_list}
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_mysql_replicate():
|
||||
slave_list = []
|
||||
mysql_replicate_path = os.path.join("/www/server/panel/plugin", "mysql_replicate", "config.json")
|
||||
if os.path.isfile(mysql_replicate_path):
|
||||
conf = read_file(mysql_replicate_path)
|
||||
try:
|
||||
conf = json.loads(conf)
|
||||
slave_list = [{"title": slave_ip, "value": slave_ip} for slave_ip in conf["slave"].keys()]
|
||||
except:
|
||||
pass
|
||||
return slave_list
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
template["field"][0]["items"] = self._get_mysql_replicate()
|
||||
if not template["field"][0]["items"]:
|
||||
return None
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return '', {}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "MySQL主从复制异常告警"
|
||||
msg.msg = "从库 {} 主从复制发生异常".format(self.slave_ip)
|
||||
msg.next_msg = "请登录面板,在[软件商店-MySQL主从复制(重构版)]中查看"
|
||||
return msg
|
||||
|
||||
|
||||
class ViewMsgFormat(object):
|
||||
_FORMAT = {
|
||||
"30": (
|
||||
lambda x: "<span>剩余时间小于{}天{}</span>".format(
|
||||
x["task_data"].get("cycle"),
|
||||
("(如未处理,次日会重新发送1次,持续%d天)" % x.get("number_rule", {}).get("day_num", 0)) if x.get(
|
||||
"number_rule", {}).get("day_num", 0) else ""
|
||||
|
||||
)
|
||||
),
|
||||
"31": (lambda x: "<span>MySQL主从复制异常告警</span>".format()),
|
||||
}
|
||||
|
||||
def get_msg(self, task: dict) -> Optional[str]:
|
||||
if task["template_id"] in self._FORMAT:
|
||||
return self._FORMAT[task["template_id"]](task)
|
||||
return None
|
||||
81
mod/base/push_mod/database_push_template.json
Normal file
81
mod/base/push_mod/database_push_template.json
Normal file
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"id": "31",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "mysql_replicate_status",
|
||||
"title": "Msql主从同步告警",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.database_push",
|
||||
"name": "MysqlReplicateStatusTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "选择监控的从库",
|
||||
"type": "select",
|
||||
"default": null,
|
||||
"items": []
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "自动修复",
|
||||
"type": "radio",
|
||||
"suffix": "",
|
||||
"default": 1,
|
||||
"items": [
|
||||
{
|
||||
"title": "自动尝试修复",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"title": "不做修复尝试",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "interval",
|
||||
"name": "间隔时间",
|
||||
"type": "number",
|
||||
"unit": "秒",
|
||||
"suffix": "后再次监控检测条件",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
],
|
||||
[
|
||||
"interval"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"project": "",
|
||||
"count": 2,
|
||||
"interval": 600
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"wx_account",
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": false
|
||||
}
|
||||
]
|
||||
178
mod/base/push_mod/domain_blcheck_push.py
Normal file
178
mod/base/push_mod/domain_blcheck_push.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
import psutil
|
||||
from datetime import datetime
|
||||
from importlib import import_module
|
||||
from typing import Tuple, Union, Optional, List
|
||||
|
||||
from .send_tool import WxAccountMsg, WxAccountLoginMsg
|
||||
from .base_task import BaseTask
|
||||
from .mods import PUSH_DATA_PATH, TaskConfig, SenderConfig, PANEL_PATH
|
||||
from .util import read_file, DB, write_file, check_site_status,GET_CLASS, ExecShell, get_config_value, public_get_cache_func, \
|
||||
public_set_cache_func, get_network_ip, public_get_user_info, public_http_post, panel_version
|
||||
from mod.base.web_conf import RealSSLManger
|
||||
|
||||
# 邮局域名进入黑名单
|
||||
class MailDomainBlcheck(BaseTask):
|
||||
push_tip_file = "/www/server/panel/data/mail_domain_blcheck_send_type.pl"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "mail_domain_black"
|
||||
self.template_name = "Your IP is on the email blacklist"
|
||||
self.title = "Your IP is on the email blacklist"
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
return {}
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "mail_domain_black"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
def filter_template(self, template) -> dict:
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return "", {}
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> None:
|
||||
|
||||
sender = task["sender"]
|
||||
if len(sender) > 0:
|
||||
send_id = sender[0]
|
||||
else:
|
||||
return
|
||||
|
||||
sender_data = SenderConfig().get_by_id(send_id)
|
||||
if sender_data:
|
||||
write_file(self.push_tip_file, sender_data["sender_type"])
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> None:
|
||||
return self.task_config_update_hook(task)
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
if os.path.exists(self.push_tip_file):
|
||||
os.remove(self.push_tip_file)
|
||||
|
||||
# 邮局服务异常告警
|
||||
class MailServerDown(BaseTask):
|
||||
push_tip_file = "/www/server/panel/data/mail_server_down_send_type.pl"
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "mail_server_status"
|
||||
self.template_name = "Your Mail Service is down"
|
||||
self.title = "Your Mail Service is down"
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
return {}
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "mail_server_status"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
def filter_template(self, template) -> dict:
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return "", {}
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> None:
|
||||
|
||||
sender = task["sender"]
|
||||
if len(sender) > 0:
|
||||
send_id = sender[0]
|
||||
else:
|
||||
return
|
||||
|
||||
sender_data = SenderConfig().get_by_id(send_id)
|
||||
if sender_data:
|
||||
write_file(self.push_tip_file, sender_data["sender_type"])
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> None:
|
||||
return self.task_config_update_hook(task)
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
if os.path.exists(self.push_tip_file):
|
||||
os.remove(self.push_tip_file)
|
||||
|
||||
# 邮局服务异常告警
|
||||
class MailDomainQuota(BaseTask):
|
||||
push_tip_file = "/www/server/panel/data/mail_domain_quota_alert_send_type.pl"
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "mail_domain_quota_alert"
|
||||
self.template_name = "Your Mail Domain Quota Alert"
|
||||
self.title = "Your Mail Domain Quota Alert"
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
return {}
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "mail_domain_quota_alert"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
def filter_template(self, template) -> dict:
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return "", {}
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> None:
|
||||
|
||||
sender = task["sender"]
|
||||
if len(sender) > 0:
|
||||
send_id = sender[0]
|
||||
else:
|
||||
return
|
||||
|
||||
sender_data = SenderConfig().get_by_id(send_id)
|
||||
if sender_data:
|
||||
write_file(self.push_tip_file, sender_data["sender_type"])
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> None:
|
||||
return self.task_config_update_hook(task)
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
if os.path.exists(self.push_tip_file):
|
||||
os.remove(self.push_tip_file)
|
||||
|
||||
|
||||
class ViewMsgFormat(object):
|
||||
_FORMAT = {
|
||||
"1": (
|
||||
lambda x: "<span>When your MailServer domain is blacklisted, an alarm is generated</span>"
|
||||
),
|
||||
"2": (
|
||||
lambda x: "<span>When your Mail Service is down, an alarm is generated</span>"
|
||||
),
|
||||
"3": (
|
||||
lambda x: "<span>When your Mail domain usage exceeds quota, an alarm is generated</span>"
|
||||
)
|
||||
}
|
||||
|
||||
def get_msg(self, task: dict) -> Optional[str]:
|
||||
if task["template_id"] in ["80"]:
|
||||
return self._FORMAT["1"](task)
|
||||
if task["template_id"] in ["81"]:
|
||||
return self._FORMAT["2"](task)
|
||||
if task["template_id"] in ["82"]:
|
||||
return self._FORMAT["3"](task)
|
||||
if task["template_id"] in self._FORMAT:
|
||||
return self._FORMAT[task["template_id"]](task)
|
||||
return None
|
||||
105
mod/base/push_mod/domain_blcheck_push_template.json
Normal file
105
mod/base/push_mod/domain_blcheck_push_template.json
Normal file
@@ -0,0 +1,105 @@
|
||||
[
|
||||
{
|
||||
"id": "80",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "mail_domain_black",
|
||||
"title": "Your IP is on the email blacklist",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.domain_blcheck_push",
|
||||
"name": "MailDomainBlcheck"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
],
|
||||
"sorted": [
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "81",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "mail_server_status",
|
||||
"title": "Your Mail Service is down",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.domain_blcheck_push",
|
||||
"name": "MailServerDown"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
],
|
||||
"sorted": [
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "82",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "mail_domain_quota_alert",
|
||||
"title": "Your Mail Domain Quota Alert",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.domain_blcheck_push",
|
||||
"name": "MailDomainQuota"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
],
|
||||
"sorted": [
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 1
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
}
|
||||
]
|
||||
|
||||
274
mod/base/push_mod/load_push.py
Normal file
274
mod/base/push_mod/load_push.py
Normal file
@@ -0,0 +1,274 @@
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Tuple, Union, Optional
|
||||
|
||||
from .mods import PUSH_DATA_PATH, TaskTemplateConfig
|
||||
from .send_tool import WxAccountMsg
|
||||
from .base_task import BaseTask
|
||||
from .util import read_file, DB, GET_CLASS, write_file
|
||||
|
||||
|
||||
class NginxLoadTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "nginx_load_push"
|
||||
self.template_name = "Load balancing alarm"
|
||||
# self.title = "Load balancing alarm"
|
||||
self._tip_counter = None
|
||||
|
||||
@property
|
||||
def tip_counter(self) -> dict:
|
||||
if self._tip_counter is not None:
|
||||
return self._tip_counter
|
||||
tip_counter = '{}/load_balance_push.json'.format(PUSH_DATA_PATH)
|
||||
if os.path.exists(tip_counter):
|
||||
try:
|
||||
self._tip_counter = json.loads(read_file(tip_counter))
|
||||
except json.JSONDecodeError:
|
||||
self._tip_counter = {}
|
||||
else:
|
||||
self._tip_counter = {}
|
||||
return self._tip_counter
|
||||
|
||||
def save_tip_counter(self):
|
||||
tip_counter = '{}/load_balance_push.json'.format(PUSH_DATA_PATH)
|
||||
write_file(tip_counter, json.dumps(self.tip_counter))
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
if task_data["project"] == "all":
|
||||
return "Load balancing alarm"
|
||||
return "Load balancing alarm -- [{}] ".format(task_data["project"])
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
all_upstream_name = DB("upstream").field("name").select()
|
||||
if isinstance(all_upstream_name, str) and all_upstream_name.startswith("error"):
|
||||
return 'Alarms cannot be set without load balancing configuration'
|
||||
all_upstream_name = [i["name"] for i in all_upstream_name]
|
||||
if not bool(all_upstream_name):
|
||||
return 'Alarms cannot be set without load balancing configuration'
|
||||
if task_data["project"] not in all_upstream_name and task_data["project"] != "all":
|
||||
return 'Without this load balancer configuration, alarms cannot be set'
|
||||
|
||||
cycle = []
|
||||
for i in task_data["cycle"].split("|"):
|
||||
if bool(i) and i.isdecimal():
|
||||
code = int(i)
|
||||
if 100 <= code < 600:
|
||||
cycle.append(str(code))
|
||||
if not bool(cycle):
|
||||
return 'If no error code is specified, the alarm cannot be set'
|
||||
|
||||
task_data["cycle"] = "|".join(cycle)
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def _check_func(self, upstream_name: str, codes: str) -> list:
|
||||
import PluginLoader
|
||||
get_obj = GET_CLASS()
|
||||
get_obj.upstream_name = upstream_name
|
||||
# 调用外部插件检查负载均衡的健康状况
|
||||
upstreams = PluginLoader.plugin_run("load_balance", "get_check_upstream", get_obj)
|
||||
access_codes = [int(i) for i in codes.split("|") if bool(i.strip())]
|
||||
res_list = []
|
||||
for upstream in upstreams:
|
||||
# 检查每个节点,返回有问题的节点信息
|
||||
res = upstream.check_nodes(access_codes, return_nodes=True)
|
||||
for ping_url in res:
|
||||
if ping_url in self.tip_counter:
|
||||
self.tip_counter[ping_url].append(int(time.time()))
|
||||
idx = 0
|
||||
for i in self.tip_counter[ping_url]:
|
||||
# 清理超过4分钟的记录
|
||||
if time.time() - i > 60 * 4:
|
||||
idx += 1
|
||||
self.tip_counter[ping_url] = self.tip_counter[ping_url][idx:]
|
||||
print("self.tip_counter[ping_url]",self.tip_counter[ping_url])
|
||||
# 如果一个节点连续三次出现在告警列表中,则视为需要告警
|
||||
if len(self.tip_counter[ping_url]) >= 3:
|
||||
res_list.append(ping_url)
|
||||
self.tip_counter[ping_url] = []
|
||||
else:
|
||||
self.tip_counter[ping_url] = [int(time.time()), ]
|
||||
self.save_tip_counter()
|
||||
return res_list
|
||||
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
err_nodes = self._check_func(task_data["project"], task_data["cycle"])
|
||||
if not err_nodes:
|
||||
return None
|
||||
pj = "load balancing:【{}】".format(task_data["project"]) if task_data["project"] != "all" else "load balancing"
|
||||
nodes = '、'.join(err_nodes)
|
||||
self.title = self.get_title(task_data)
|
||||
return {
|
||||
"msg_list": [
|
||||
">Notification type: Enterprise Edition load balancing alarm",
|
||||
">Content of alarm: <font color=#ff0000>{}The node [{}] under the configuration has access error, please pay attention to the node situation in time and deal with it.</font> ".format(
|
||||
pj, nodes),
|
||||
],
|
||||
"pj": pj,
|
||||
"nodes": nodes
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
if not os.path.exists("/www/server/panel/plugin/load_balance/load_balance_main.py"):
|
||||
return None
|
||||
all_upstream = DB("upstream").field("name").select()
|
||||
if isinstance(all_upstream, str) and all_upstream.startswith("error"):
|
||||
return None
|
||||
all_upstream_name = [i["name"] for i in all_upstream]
|
||||
if not all_upstream_name:
|
||||
return None
|
||||
for name in all_upstream_name:
|
||||
template["field"][0]["items"].append({
|
||||
"title": name,
|
||||
"value": name
|
||||
})
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return '', {}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Load balancing alarm"
|
||||
msg.msg = "If the node is abnormal, log in to the panel"
|
||||
return msg
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> None:
|
||||
old_config_file = "/www/server/panel/class/push/push.json"
|
||||
try:
|
||||
old_config = json.loads(read_file(old_config_file))
|
||||
except:
|
||||
return
|
||||
if "load_balance_push" not in old_config:
|
||||
old_config["load_balance_push"] = {}
|
||||
old_data = {
|
||||
"push_count": task["number_rule"].get("day_num", 2),
|
||||
"cycle": task["task_data"].get("cycle", "200|301|302|403|404"),
|
||||
"interval": task["task_data"].get("interval", 60),
|
||||
"title": task["title"],
|
||||
"status": task['status'],
|
||||
"module": ",".join(task["sender"])
|
||||
}
|
||||
for k, v in old_config["load_balance_push"].items():
|
||||
if v["project"] == task["task_data"]["project"]:
|
||||
v.update(old_data)
|
||||
else:
|
||||
old_data["project"] = task["task_data"]["project"]
|
||||
old_config["load_balance_push"][int(time.time())] = old_data
|
||||
|
||||
write_file(old_config_file, json.dumps(old_config))
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> None:
|
||||
return self.task_config_create_hook(task)
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
old_config_file = "/www/server/panel/class/push/push.json"
|
||||
try:
|
||||
old_config = json.loads(read_file(old_config_file))
|
||||
except:
|
||||
return
|
||||
if "load_balance_push" not in old_config:
|
||||
old_config["load_balance_push"] = {}
|
||||
old_config["load_balance_push"] = {
|
||||
k: v for k, v in old_config["load_balance_push"].items()
|
||||
if v["project"] != task["task_data"]["project"]
|
||||
}
|
||||
|
||||
|
||||
def load_load_template():
|
||||
if TaskTemplateConfig().get_by_id("50"):
|
||||
return None
|
||||
|
||||
from .mods import load_task_template_by_config
|
||||
load_task_template_by_config(
|
||||
[{
|
||||
"id": "50",
|
||||
"ver": "1",
|
||||
"used": True,
|
||||
"source": "nginx_load_push",
|
||||
"title": "load balancing",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.load_push",
|
||||
"name": "NginxLoadTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "The name of the payload",
|
||||
"type": "select",
|
||||
"default": "all",
|
||||
"unit": "",
|
||||
"suffix": (
|
||||
"<i style='color: #999;font-style: initial;font-size: 12px;margin-right: 5px'>*</i>"
|
||||
"<span style='color:#999'>If a node fails to access a node in the selected load configuration, an alarm is triggered</span>"
|
||||
),
|
||||
"items": [
|
||||
{
|
||||
"title": "All configured loads",
|
||||
"value": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "The status code of the success",
|
||||
"type": "textarea",
|
||||
"unit": "",
|
||||
"suffix": (
|
||||
"<br><i style='color: #999;font-style: initial;font-size: 12px;margin-right: 5px'>*</i>"
|
||||
"<span style='color:#999'>Status codes are separated by vertical bars, for example:200|301|302|403|404</span>"
|
||||
),
|
||||
"width": "400px",
|
||||
"style": {
|
||||
'height': '70px',
|
||||
},
|
||||
"default": "200|301|302|403|404"
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"cycle"
|
||||
]
|
||||
],
|
||||
},
|
||||
"default": {
|
||||
"project": "all",
|
||||
"cycle": "200|301|302|403|404"
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"wx_account",
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg",
|
||||
],
|
||||
"unique": False
|
||||
}]
|
||||
)
|
||||
|
||||
|
||||
class ViewMsgFormat(object):
|
||||
|
||||
@staticmethod
|
||||
def get_msg(task: dict) -> Optional[str]:
|
||||
if task["template_id"] == "50":
|
||||
return "<span>When the node access is abnormal, the alarm message is pushed (it is not pushed after {} times per day)<span>".format(
|
||||
task.get("number_rule", {}).get("day_num"))
|
||||
return None
|
||||
296
mod/base/push_mod/manager.py
Normal file
296
mod/base/push_mod/manager.py
Normal file
@@ -0,0 +1,296 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Union, Optional
|
||||
|
||||
from mod.base import json_response
|
||||
from .mods import TaskTemplateConfig, TaskConfig, SenderConfig, TaskRecordConfig
|
||||
from .system import PushSystem
|
||||
|
||||
sys.path.insert(0, "/www/server/panel/class/")
|
||||
import public
|
||||
|
||||
|
||||
class PushManager:
|
||||
def __init__(self):
|
||||
self.template_conf = TaskTemplateConfig()
|
||||
self.task_conf = TaskConfig()
|
||||
self.send_config = SenderConfig()
|
||||
self._send_conf_cache = {}
|
||||
|
||||
def _get_sender_conf(self, sender_id):
|
||||
if sender_id in self._send_conf_cache:
|
||||
return self._send_conf_cache[sender_id]
|
||||
tmp = self.send_config.get_by_id(sender_id)
|
||||
self._send_conf_cache[sender_id] = tmp
|
||||
return tmp
|
||||
|
||||
def normalize_task_config(self, task, template) -> Union[dict, str]:
|
||||
result = {}
|
||||
sender = task.get("sender", None)
|
||||
if sender is None:
|
||||
return "No alarm channel is configured"
|
||||
if not isinstance(sender, list):
|
||||
return "The alarm channel is incorrect"
|
||||
|
||||
new_sender = []
|
||||
for i in sender:
|
||||
sender_conf = self._get_sender_conf(i)
|
||||
if not sender_conf:
|
||||
continue
|
||||
else:
|
||||
new_sender.append(i)
|
||||
if sender_conf["sender_type"] not in template["send_type_list"]:
|
||||
if sender_conf["sender_type"] == "sms":
|
||||
return "SMS alerts are not supported"
|
||||
return "Unsupported alerting methods:{}".format(sender_conf['data']["title"])
|
||||
if not sender_conf["used"]:
|
||||
if sender_conf["sender_type"] == "sms":
|
||||
return "The SMS alert channel has been closed"
|
||||
return "Closed alert mode:{}".format(sender_conf['data']["title"])
|
||||
|
||||
result["sender"] = new_sender
|
||||
|
||||
if "default" in template and template["default"]:
|
||||
task_data = task.get("task_data", {})
|
||||
for k, v in template["default"].items():
|
||||
if k not in task_data:
|
||||
task_data[k] = v
|
||||
|
||||
result["task_data"] = task_data
|
||||
# 避免default为空时,无数据
|
||||
else:
|
||||
result["task_data"] = task.get("task_data", {})
|
||||
|
||||
if "task_data" not in result:
|
||||
result["task_data"] = {}
|
||||
|
||||
time_rule = task.get("time_rule", {})
|
||||
|
||||
if "send_interval" in time_rule:
|
||||
if not isinstance(time_rule["send_interval"], int):
|
||||
return "The minimum interval is set incorrectly"
|
||||
if time_rule["send_interval"] < 0:
|
||||
return "The minimum interval is set incorrectly"
|
||||
|
||||
if "time_range" in time_rule:
|
||||
if not isinstance(time_rule["time_range"], list):
|
||||
return "The time range is set incorrectly"
|
||||
if not len(time_rule["time_range"]) == 2:
|
||||
del time_rule["time_range"]
|
||||
else:
|
||||
time_range = time_rule["time_range"]
|
||||
if not (isinstance(time_range[0], int) and isinstance(time_range[1], int) and
|
||||
0 <= time_range[0] < time_range[1] <= 60 * 60 * 24):
|
||||
return "The time range is set incorrectly"
|
||||
|
||||
result["time_rule"] = time_rule
|
||||
|
||||
number_rule = task.get("number_rule", {})
|
||||
if "day_num" in number_rule:
|
||||
if not (isinstance(number_rule["day_num"], int) and number_rule["day_num"] >= 0):
|
||||
return "The minimum number of times per day is set incorrectly"
|
||||
|
||||
if "total" in number_rule:
|
||||
if not (isinstance(number_rule["total"], int) and number_rule["total"] >= 0):
|
||||
return "The maximum number of alarms is set incorrectly"
|
||||
|
||||
result["number_rule"] = number_rule
|
||||
|
||||
if "status" not in task:
|
||||
result["status"] = True
|
||||
if "status" in task:
|
||||
if isinstance(task["status"], bool):
|
||||
result["status"] = task["status"]
|
||||
|
||||
return result
|
||||
|
||||
def set_task_conf_data(self, push_data: dict) -> Optional[str]:
|
||||
task_id = push_data.get("task_id", None)
|
||||
template_id = push_data.get("template_id")
|
||||
task = push_data.get("task_data")
|
||||
|
||||
target_task_conf = None
|
||||
if task_id is not None:
|
||||
tmp = self.task_conf.get_by_id(task_id)
|
||||
if tmp is None:
|
||||
target_task_conf = tmp
|
||||
|
||||
template = self.template_conf.get_by_id(template_id)
|
||||
|
||||
if not template:
|
||||
# 如果没有找到模板,则尝试加载默认的安全推送模板
|
||||
from .mods import load_task_template_by_file
|
||||
if not os.path.exists("/www/server/panel/mod/base/push_mod/safe_mod_push_template.json"):
|
||||
return "No alarm template was found"
|
||||
load_task_template_by_file("/www/server/panel/mod/base/push_mod/safe_mod_push_template.json")
|
||||
self.template_conf = TaskTemplateConfig()
|
||||
template = self.template_conf.get_by_id(template_id)
|
||||
if not template:
|
||||
return "No alarm template was found"
|
||||
|
||||
if template["unique"] and not target_task_conf:
|
||||
for i in self.task_conf.config:
|
||||
if i["template_id"] == template["id"]:
|
||||
target_task_conf = i
|
||||
break
|
||||
|
||||
task_obj = PushSystem().get_task_object(template_id, template["load_cls"])
|
||||
if not task_obj:
|
||||
return "Loading task type error, you can try to fix the panel"
|
||||
|
||||
res = self.normalize_task_config(task, template)
|
||||
if isinstance(res, str):
|
||||
return res
|
||||
|
||||
task_data = task_obj.check_task_data(res["task_data"])
|
||||
if isinstance(task_data, str):
|
||||
return task_data
|
||||
|
||||
number_rule = task_obj.check_num_rule(res["number_rule"])
|
||||
if isinstance(number_rule, str):
|
||||
return number_rule
|
||||
|
||||
time_rule = task_obj.check_time_rule(res["time_rule"])
|
||||
if isinstance(time_rule, str):
|
||||
return time_rule
|
||||
|
||||
res["task_data"] = task_data
|
||||
res["number_rule"] = number_rule
|
||||
res["time_rule"] = time_rule
|
||||
|
||||
res["keyword"] = task_obj.get_keyword(task_data)
|
||||
res["source"] = task_obj.source_name
|
||||
res["title"] = task_obj.get_title(task_data)
|
||||
|
||||
if not target_task_conf:
|
||||
tmp = self.task_conf.get_by_keyword(res["source"], res["keyword"])
|
||||
if tmp:
|
||||
target_task_conf = tmp
|
||||
|
||||
if not target_task_conf:
|
||||
res["id"] = self.task_conf.nwe_id()
|
||||
res["template_id"] = template_id
|
||||
res["status"] = True
|
||||
res["pre_hook"] = {}
|
||||
res["after_hook"] = {}
|
||||
res["last_check"] = 0
|
||||
res["last_send"] = 0
|
||||
res["number_data"] = {}
|
||||
res["create_time"] = time.time()
|
||||
res["record_time"] = 0
|
||||
self.task_conf.config.append(res)
|
||||
task_obj.task_config_create_hook(res)
|
||||
else:
|
||||
target_task_conf.update(res)
|
||||
target_task_conf["last_check"] = 0
|
||||
target_task_conf["number_data"] = {} # 次数控制数据置空
|
||||
task_obj.task_config_update_hook(target_task_conf)
|
||||
|
||||
self.task_conf.save_config()
|
||||
return None
|
||||
|
||||
def update_task_status(self, get):
|
||||
# 先调用 set_task_conf 修改任务配置
|
||||
set_conf_response = self.set_task_conf(get)
|
||||
|
||||
if set_conf_response['status'] != 0:
|
||||
return set_conf_response # 返回错误信息
|
||||
|
||||
# 读取任务数据
|
||||
file_path = '{}/data/mod_push_data/task.json'.format(public.get_panel_path())
|
||||
try:
|
||||
with open(file_path, 'r') as file:
|
||||
tasks = json.load(file)
|
||||
except (IOError, json.JSONDecodeError):
|
||||
return json_response(status=False, msg=public.lang("Unable to read task data."))
|
||||
# 查找对应的 task_id
|
||||
task_title = get.title.strip() # 假设 get 中有 title 参数
|
||||
task_id = None
|
||||
|
||||
for task in tasks:
|
||||
if task.get('title') == task_title:
|
||||
task_id = task.get('id')
|
||||
break
|
||||
|
||||
if not task_id:
|
||||
return json_response(status=False, msg=public.lang("The task has not been found."))
|
||||
|
||||
# 调用 change_task_conf 修改任务状态
|
||||
get.task_id = task_id
|
||||
return self.change_task_conf(get)
|
||||
|
||||
def set_task_conf(self, get):
|
||||
task_id = None
|
||||
try:
|
||||
if hasattr(get, "task_id"):
|
||||
task_id = get.task_id.strip()
|
||||
if not task_id:
|
||||
task_id = None
|
||||
else:
|
||||
self.remove_task_conf(get)
|
||||
template_id = get.template_id.strip()
|
||||
task = json.loads(get.task_data.strip())
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="The parameter is incorrect")
|
||||
push_data = {
|
||||
"task_id": task_id,
|
||||
"template_id": template_id,
|
||||
"task_data": task,
|
||||
}
|
||||
res = self.set_task_conf_data(push_data)
|
||||
if res:
|
||||
return json_response(status=False, msg=res)
|
||||
return json_response(status=True, msg="The alarm task is saved successfully")
|
||||
|
||||
def change_task_conf(self, get):
|
||||
try:
|
||||
task_id = get.task_id.strip()
|
||||
status = int(get.status) # 获取status字段并转换为整数
|
||||
except (AttributeError, ValueError):
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
if status not in [0, 1]:
|
||||
return json_response(status=False, msg="Invalid status value")
|
||||
|
||||
tmp = self.task_conf.get_by_id(task_id)
|
||||
if tmp is None:
|
||||
return json_response(status=True, msg="No alarm task was queried")
|
||||
|
||||
tmp["status"] = bool(status) # 将status转换为布尔值并设置
|
||||
|
||||
self.task_conf.save_config()
|
||||
return json_response(status=True, msg="operate successfully")
|
||||
|
||||
def change_task(self, task_id, status):
|
||||
tmp = self.task_conf.get_by_id(task_id)
|
||||
tmp["status"] = bool(status) # 将status转换为布尔值并设置
|
||||
self.task_conf.save_config()
|
||||
|
||||
def remove_task_conf(self, get):
|
||||
try:
|
||||
task_id = get.task_id.strip()
|
||||
except AttributeError:
|
||||
return json_response(status=False, msg="The parameter is incorrect")
|
||||
|
||||
tmp = self.task_conf.get_by_id(task_id)
|
||||
if tmp is None:
|
||||
return json_response(status=True, msg="No alarm task was queried")
|
||||
|
||||
self.task_conf.config.remove(tmp)
|
||||
|
||||
self.task_conf.save_config()
|
||||
template = self.template_conf.get_by_id(tmp["template_id"])
|
||||
if template:
|
||||
task_obj = PushSystem().get_task_object(template["id"], template["load_cls"])
|
||||
if task_obj:
|
||||
task_obj.task_config_remove_hook(tmp)
|
||||
|
||||
return json_response(status=True, msg="operate successfully")
|
||||
|
||||
@staticmethod
|
||||
def clear_task_record_by_task_id(task_id):
|
||||
tr_conf = TaskRecordConfig(task_id)
|
||||
if os.path.exists(tr_conf.config_file_path):
|
||||
os.remove(tr_conf.config_file_path)
|
||||
371
mod/base/push_mod/mods.py
Normal file
371
mod/base/push_mod/mods.py
Normal file
@@ -0,0 +1,371 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# yakpanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2017 yakpanel(https://www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: baozi <baozi@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
# 新告警的所有数据库操作
|
||||
# ------------------------------
|
||||
import json
|
||||
import os
|
||||
import types
|
||||
from typing import Any, Dict, Optional, List
|
||||
from threading import Lock
|
||||
from uuid import uuid4
|
||||
|
||||
import fcntl
|
||||
|
||||
from .util import Sqlite, write_log, read_file, write_file
|
||||
|
||||
_push_db_lock = Lock()
|
||||
|
||||
|
||||
# 代替 class/db.py 中, 离谱的两个query函数
|
||||
def msg_db_query_func(self, sql, param=()):
|
||||
# 执行SQL语句返回数据集
|
||||
self._Sql__GetConn()
|
||||
try:
|
||||
return self._Sql__DB_CONN.execute(sql, self._Sql__to_tuple(param))
|
||||
except Exception as ex:
|
||||
return "error: " + str(ex)
|
||||
|
||||
|
||||
def get_push_db():
|
||||
db_file = "/www/server/panel/data/db/mod_push.db"
|
||||
if not os.path.isdir(os.path.dirname(db_file)):
|
||||
os.makedirs(os.path.dirname(db_file))
|
||||
|
||||
db = Sqlite()
|
||||
setattr(db, "_Sql__DB_FILE", db_file)
|
||||
setattr(db, "query", types.MethodType(msg_db_query_func, db))
|
||||
return db
|
||||
|
||||
|
||||
def get_table(table_name: str):
|
||||
db = get_push_db()
|
||||
db.table = table_name
|
||||
return db
|
||||
|
||||
|
||||
def lock_push_db():
|
||||
with open("/www/server/panel/data/db/mod_push.db", mode="rb") as msg_fd:
|
||||
fcntl.flock(msg_fd.fileno(), fcntl.LOCK_EX)
|
||||
_push_db_lock.locked()
|
||||
|
||||
|
||||
def unlock_push_db():
|
||||
with open("/www/server/panel/data/db/mod_push.db", mode="rb") as msg_fd:
|
||||
fcntl.flock(msg_fd.fileno(), fcntl.LOCK_UN)
|
||||
_push_db_lock.acquire()
|
||||
|
||||
|
||||
def push_db_locker(func):
|
||||
def inner_func(*args, **kwargs):
|
||||
lock_push_db()
|
||||
try:
|
||||
res = func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
unlock_push_db() # 即使 报错了 也先解锁再操作
|
||||
raise e
|
||||
else:
|
||||
unlock_push_db()
|
||||
return res
|
||||
|
||||
return inner_func
|
||||
|
||||
|
||||
DB_INIT_ERROR = False
|
||||
|
||||
PANEL_PATH = "/www/server/panel"
|
||||
PUSH_DATA_PATH = "{}/data/mod_push_data".format(PANEL_PATH)
|
||||
UPDATE_VERSION_FILE = "{}/update_panel.pl".format(PUSH_DATA_PATH)
|
||||
UPDATE_MOD_PUSH_FILE = "{}/update_mod.pl".format(PUSH_DATA_PATH)
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
config_file_path = ""
|
||||
# config_file_path = "/www/server/panel/data/mod_push_data/task.json"
|
||||
# /www/server/panel/data/mod_push_data/sender.json
|
||||
|
||||
def __init__(self):
|
||||
if not os.path.exists(PUSH_DATA_PATH):
|
||||
os.makedirs(PUSH_DATA_PATH)
|
||||
self._config: Optional[List[Dict[str, Any]]] = None
|
||||
|
||||
@property
|
||||
def config(self) -> List[Dict[str, Any]]:
|
||||
if self._config is None:
|
||||
|
||||
try:
|
||||
self._config = json.loads(read_file(self.config_file_path))
|
||||
except:
|
||||
self._config = []
|
||||
return self._config
|
||||
|
||||
def save_config(self) -> None:
|
||||
write_file(self.config_file_path, json.dumps(self.config))
|
||||
|
||||
@staticmethod
|
||||
def nwe_id() -> str:
|
||||
return uuid4().hex[::2]
|
||||
|
||||
def get_by_id(self, target_id: str) -> Optional[Dict[str, Any]]:
|
||||
for i in self.config:
|
||||
if i.get("id", None) == target_id:
|
||||
return i
|
||||
|
||||
|
||||
class TaskTemplateConfig(BaseConfig):
|
||||
config_file_path = "{}/task_template.json".format(PUSH_DATA_PATH)
|
||||
|
||||
|
||||
class TaskConfig(BaseConfig):
|
||||
config_file_path = "{}/task.json".format(PUSH_DATA_PATH)
|
||||
|
||||
def get_by_keyword(self, source: str, keyword: str) -> Optional[Dict[str, Any]]:
|
||||
for i in self.config:
|
||||
if i.get("source", None) == source and i.get("keyword", None) == keyword:
|
||||
return i
|
||||
|
||||
|
||||
class TaskRecordConfig(BaseConfig):
|
||||
config_file_path_fmt = "%s/task_record_{}.json" % PUSH_DATA_PATH
|
||||
|
||||
def __init__(self, task_id: str):
|
||||
super().__init__()
|
||||
self.config_file_path = self.config_file_path_fmt.format(task_id)
|
||||
|
||||
|
||||
class SenderConfig(BaseConfig):
|
||||
config_file_path = "{}/sender.json".format(PUSH_DATA_PATH)
|
||||
|
||||
def __init__(self):
|
||||
super(SenderConfig, self).__init__()
|
||||
if not os.path.exists(self.config_file_path):
|
||||
write_file(self.config_file_path, json.dumps([{
|
||||
"id": self.nwe_id(),
|
||||
"used": True,
|
||||
"sender_type": "sms",
|
||||
"data": {},
|
||||
"original": True
|
||||
}]))
|
||||
|
||||
|
||||
def init_db():
|
||||
global DB_INIT_ERROR
|
||||
# id 模板id 必须唯一, 后端开发需要协商
|
||||
# ver 模板版本号, 用于更新
|
||||
# used 是否在使用用
|
||||
# source 来源, 如Waf, rsync
|
||||
# title 标题
|
||||
# load_cls 要加载的类,或者从那种调用方法中获取到任务处理对象
|
||||
# template 给前端,用于展示的数据
|
||||
# default 默认数据,用于数据过滤, 和默认值
|
||||
# unique 是否仅可唯一设置
|
||||
# create_time 创建时间
|
||||
create_task_template_sql = (
|
||||
"CREATE TABLE IF NOT EXISTS 'task_template' ("
|
||||
"'id' INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"'ver' TEXT NOT NULL DEFAULT '1.0.0', "
|
||||
"'used' INTEGER NOT NULL DEFAULT 1, "
|
||||
"'source' TEXT NOT NULL DEFAULT 'site_push', "
|
||||
"'title' TEXT NOT NULL DEFAULT '', "
|
||||
"'load_cls' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'template' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'default' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'send_type_list' TEXT NOT NULL DEFAULT '[]', "
|
||||
"'unique' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))"
|
||||
");"
|
||||
)
|
||||
# source 来源, 例如waf(防火墙), rsync(文件同步)
|
||||
# keyword 关键词, 不同的来源在使用中可以以此查出具体的任务,需要每个来源自己约束
|
||||
# task_data 任务数据字典,字段可以自由设计
|
||||
# sender 告警通道信息,为字典,可通过get_by_func字段指定从某个函数获取,用于发送
|
||||
# time_rule 告警的时间规则,包含 间隔时间(send_interval), (time-range)
|
||||
# number_rule 告警的次数规则,包含 每日次数(day_num), 总次数(total), 通过函数判断(get_by_func)
|
||||
# status 状态是否开启
|
||||
# pre_hook, after_hook 前置处理和后置处理
|
||||
# record_time, 告警记录存储时间, 默认为0, 认为长时间储存
|
||||
# last_check, 上次执行检查的时间
|
||||
# last_send, 上次次发送时间
|
||||
# number_data, 发送次数信息
|
||||
create_task_sql = (
|
||||
"CREATE TABLE IF NOT EXISTS 'task' ("
|
||||
"'id' INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"'template_id' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'source' TEXT NOT NULL DEFAULT '', "
|
||||
"'keyword' TEXT NOT NULL DEFAULT '', "
|
||||
"'title' TEXT NOT NULL DEFAULT '', "
|
||||
"'task_data' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'sender' TEXT NOT NULL DEFAULT '[]', "
|
||||
"'time_rule' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'number_rule' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'status' INTEGER NOT NULL DEFAULT 1, "
|
||||
"'pre_hook' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'after_hook' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'last_check' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'last_send' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'number_data' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'create_time' INTEGER NOT NULL DEFAULT (strftime('%s')), "
|
||||
"'record_time' INTEGER NOT NULL DEFAULT 0"
|
||||
");"
|
||||
)
|
||||
|
||||
create_task_record_sql = (
|
||||
"CREATE TABLE IF NOT EXISTS 'task_record' ("
|
||||
"'id' INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"'template_id' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'task_id' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'do_send' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'send_data' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'result' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))"
|
||||
");"
|
||||
)
|
||||
|
||||
create_send_record_sql = (
|
||||
"CREATE TABLE IF NOT EXISTS 'send_record' ("
|
||||
"'id' INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"'record_id' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'sender_name' TEXT NOT NULL DEFAULT '', "
|
||||
"'sender_id' INTEGER NOT NULL DEFAULT 0, "
|
||||
"'sender_type' TEXT NOT NULL DEFAULT '', "
|
||||
"'send_data' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'result' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))"
|
||||
");"
|
||||
)
|
||||
|
||||
create_sender_sql = (
|
||||
"CREATE TABLE IF NOT EXISTS 'sender' ("
|
||||
"'id' INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"'used' INTEGER NOT NULL DEFAULT 1, "
|
||||
"'sender_type' TEXT NOT NULL DEFAULT '', "
|
||||
"'name' TEXT NOT NULL DEFAULT '', "
|
||||
"'data' TEXT NOT NULL DEFAULT '{}', "
|
||||
"'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))"
|
||||
");"
|
||||
)
|
||||
|
||||
lock_push_db()
|
||||
with get_push_db() as db:
|
||||
db.execute("pragma journal_mode=wal")
|
||||
|
||||
res = db.execute(create_task_template_sql)
|
||||
if isinstance(res, str) and res.startswith("error"):
|
||||
write_log("warning system", "task_template Data table creation error:" + res)
|
||||
DB_INIT_ERROR = True
|
||||
return
|
||||
|
||||
res = db.execute(create_task_sql)
|
||||
if isinstance(res, str) and res.startswith("error"):
|
||||
write_log("warning system", "task Data table creation error:" + res)
|
||||
DB_INIT_ERROR = True
|
||||
return
|
||||
|
||||
res = db.execute(create_task_record_sql)
|
||||
if isinstance(res, str) and res.startswith("error"):
|
||||
write_log("warning system", "task_recorde Data table creation error:" + res)
|
||||
DB_INIT_ERROR = True
|
||||
return
|
||||
|
||||
res = db.execute(create_send_record_sql)
|
||||
if isinstance(res, str) and res.startswith("error"):
|
||||
write_log("warning system", "send_record Data table creation error:" + res)
|
||||
DB_INIT_ERROR = True
|
||||
return
|
||||
|
||||
res = db.execute(create_sender_sql)
|
||||
if isinstance(res, str) and res.startswith("error"):
|
||||
write_log("warning system", "sender Data table creation error:" + res)
|
||||
DB_INIT_ERROR = True
|
||||
return
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO 'sender' (id, sender_type, data) VALUES (?,?,?)",
|
||||
(1, 'sms', json.dumps({"count": 0, "total": 0}))
|
||||
) # 插入短信
|
||||
|
||||
unlock_push_db()
|
||||
|
||||
init_template_file = "/www/server/panel/config/mod_push_init.json"
|
||||
err = load_task_template_by_file(init_template_file)
|
||||
if err:
|
||||
write_log("warning system", "task template data table initial data load failed:" + res)
|
||||
|
||||
|
||||
def _check_fields(template: dict) -> bool:
|
||||
if not isinstance(template, dict):
|
||||
return False
|
||||
|
||||
fields = ("id", "ver", "used", "source", "title", "load_cls", "template", "default", "unique", "create_time")
|
||||
for field in fields:
|
||||
if field not in template:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def load_task_template_by_config(templates: List[Dict]) -> None:
|
||||
"""
|
||||
通过 传入的配置信息 执行一次模板更新操作
|
||||
@param templates: 模板内容,为一个数据列表
|
||||
@return: 报错信息,如果返回None则表示执行成功
|
||||
"""
|
||||
|
||||
task_template_config = TaskTemplateConfig()
|
||||
add_list = []
|
||||
for template in templates:
|
||||
tmp = task_template_config.get_by_id(template['id'])
|
||||
if tmp is not None:
|
||||
tmp.update(template)
|
||||
else:
|
||||
add_list.append(template)
|
||||
|
||||
task_template_config.config.extend(add_list)
|
||||
task_template_config.save_config()
|
||||
|
||||
# with get_table('task_template') as table:
|
||||
# for template in templates:
|
||||
# if not _check_fields(template):
|
||||
# continue
|
||||
# res = table.where("id = ?", (template['id'])).field('ver').select()
|
||||
# if isinstance(res, str):
|
||||
# return "数据库损坏:" + res
|
||||
# if not res: # 没有就插入
|
||||
# table.insert(template)
|
||||
# else:
|
||||
# # 版本不一致就更新版本
|
||||
# if res['ver'] != template['ver']:
|
||||
# template.pop("id")
|
||||
# table.where("id = ?", (template['id'])).update(template)
|
||||
#
|
||||
|
||||
|
||||
def load_task_template_by_file(template_file: str) -> Optional[str]:
|
||||
"""
|
||||
执行一次模板更新操作
|
||||
@param template_file: 模板文件路径
|
||||
@return: 报错信息,如果返回None则表示执行成功
|
||||
"""
|
||||
if not os.path.isfile(template_file):
|
||||
return "The template file does not exist and the update fails"
|
||||
|
||||
if DB_INIT_ERROR:
|
||||
return "An error is reported during database initialization and cannot be updated"
|
||||
|
||||
res = read_file(template_file)
|
||||
if not isinstance(res, str):
|
||||
return "Data read failed"
|
||||
|
||||
try:
|
||||
templates = json.loads(res)
|
||||
except (json.JSONDecoder, TypeError, ValueError):
|
||||
return "Only JSON data is supported"
|
||||
|
||||
if not isinstance(templates, list):
|
||||
return "The data is in the wrong format and should be a list"
|
||||
|
||||
return load_task_template_by_config(templates)
|
||||
302
mod/base/push_mod/rsync_push.py
Normal file
302
mod/base/push_mod/rsync_push.py
Normal file
@@ -0,0 +1,302 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Tuple, Union, Optional, Iterator
|
||||
|
||||
from .send_tool import WxAccountMsg
|
||||
from .base_task import BaseTask
|
||||
from .mods import TaskTemplateConfig
|
||||
from .util import read_file
|
||||
|
||||
|
||||
def rsync_ver_is_38() -> Optional[bool]:
|
||||
"""
|
||||
检查rsync的版本是否为3.8。
|
||||
该函数不接受任何参数。
|
||||
返回值:
|
||||
- None: 如果无法确定rsync的版本或文件不存在。
|
||||
- bool: 如果版本确定为3.8,则返回True;否则返回False。
|
||||
"""
|
||||
push_file = "/www/server/panel/plugin/rsync/rsync_push.py"
|
||||
if not os.path.exists(push_file):
|
||||
return None
|
||||
ver_info_file = "/www/server/panel/plugin/rsync/info.json"
|
||||
if not os.path.exists(ver_info_file):
|
||||
return None
|
||||
try:
|
||||
info = json.loads(read_file(ver_info_file))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return None
|
||||
ver = info["versions"]
|
||||
ver_tuples = [int(i) for i in ver.split(".")]
|
||||
if len(ver_tuples) < 3:
|
||||
ver_tuples = ver_tuples.extend([0] * (3 - len(ver_tuples)))
|
||||
if ver_tuples[0] < 3:
|
||||
return None
|
||||
if ver_tuples[1] <= 8 and ver_tuples[0] == 3:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class Rsync38Task(BaseTask):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "rsync_push"
|
||||
self.template_name = "File synchronization alarm"
|
||||
self.title = "File synchronization alarm"
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if "interval" not in task_data or not isinstance(task_data["interval"], int):
|
||||
task_data["interval"] = 600
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "rsync_push"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
has_err = self._check(task_data.get("interval", 600))
|
||||
if not has_err:
|
||||
return None
|
||||
|
||||
return {
|
||||
"msg_list": [
|
||||
">Notification type: File synchronization alarm",
|
||||
">Content of alarm: <font color=#ff0000>If an error occurs during file synchronization, please pay attention to the file synchronization situation and handle it in a timely manner.</font> ",
|
||||
]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _check(interval: int) -> bool:
|
||||
if not isinstance(interval, int):
|
||||
return False
|
||||
start_time = datetime.now() - timedelta(seconds=interval * 1.2)
|
||||
log_file = "{}/plugin/rsync/lsyncd.log".format("/www/server/panel")
|
||||
if not os.path.exists(log_file):
|
||||
return False
|
||||
return LogChecker(log_file=log_file, start_time=start_time)()
|
||||
|
||||
def check_time_rule(self, time_rule: dict) -> Union[dict, str]:
|
||||
if "send_interval" not in time_rule or not isinstance(time_rule["interval"], int):
|
||||
time_rule["send_interval"] = 3 * 60
|
||||
if time_rule["send_interval"] < 60:
|
||||
time_rule["send_interval"] = 60
|
||||
return time_rule
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
res = rsync_ver_is_38()
|
||||
if res is None:
|
||||
return None
|
||||
if res:
|
||||
return template
|
||||
else:
|
||||
return None
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return '', {}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "File synchronization alarm"
|
||||
msg.msg = "There was an error in the synchronization. Please keep an eye on the synchronization"
|
||||
return msg
|
||||
|
||||
|
||||
class Rsync39Task(BaseTask):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "rsync_push"
|
||||
self.template_name = "File synchronization alarm"
|
||||
self.title = "File synchronization alarm"
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if "interval" not in task_data or not isinstance(task_data["interval"], int):
|
||||
task_data["interval"] = 600
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "rsync_push"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
"""
|
||||
不返回数据,以实时触发为主
|
||||
"""
|
||||
return None
|
||||
|
||||
def check_time_rule(self, time_rule: dict) -> Union[dict, str]:
|
||||
if "send_interval" not in time_rule or not isinstance(time_rule["send_interval"], int):
|
||||
time_rule["send_interval"] = 3 * 60
|
||||
if time_rule["send_interval"] < 60:
|
||||
time_rule["send_interval"] = 60
|
||||
return time_rule
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
res = rsync_ver_is_38()
|
||||
if res is None:
|
||||
return None
|
||||
if res is False:
|
||||
return template
|
||||
else:
|
||||
return None
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return '', {}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
task_name = push_data.get("task_name", None)
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "File synchronization alarm"
|
||||
if task_name:
|
||||
msg.msg = "An error occurred on file synchronization task {}".format(task_name)
|
||||
else:
|
||||
msg.msg = "There was an error in the synchronization. Please keep an eye on the synchronization"
|
||||
return msg
|
||||
|
||||
|
||||
class LogChecker:
|
||||
"""
|
||||
排序查询并获取日志内容
|
||||
"""
|
||||
rep_time = re.compile(r'(?P<target>(\w{3}\s+){2}(\d{1,2})\s+(\d{2}:?){3}\s+\d{4})')
|
||||
format_str = '%a %b %d %H:%M:%S %Y'
|
||||
err_datetime = datetime.fromtimestamp(0)
|
||||
err_list = ("error", "Error", "ERROR", "exitcode = 10", "failed")
|
||||
|
||||
def __init__(self, log_file: str, start_time: datetime):
|
||||
self.log_file = log_file
|
||||
self.start_time = start_time
|
||||
self.is_over_time = None # None:还没查到时间,未知, False: 可以继续网上查询, True:比较早的数据了,不再向上查询
|
||||
self.has_err = False # 目前已查询的内容中是否有报错信息
|
||||
|
||||
def _format_time(self, log_line) -> Optional[datetime]:
|
||||
try:
|
||||
date_str_res = self.rep_time.search(log_line)
|
||||
if date_str_res:
|
||||
time_str = date_str_res.group("target")
|
||||
return datetime.strptime(time_str, self.format_str)
|
||||
except Exception:
|
||||
return self.err_datetime
|
||||
return None
|
||||
|
||||
# 返回日志内容
|
||||
def __call__(self):
|
||||
_buf = b""
|
||||
file_size, fp = os.stat(self.log_file).st_size - 1, open(self.log_file, mode="rb")
|
||||
fp.seek(-1, 2)
|
||||
while file_size:
|
||||
read_size = min(1024, file_size)
|
||||
fp.seek(-read_size, 1)
|
||||
buf: bytes = fp.read(read_size) + _buf
|
||||
fp.seek(-read_size, 1)
|
||||
if file_size > 1024:
|
||||
idx = buf.find(ord("\n"))
|
||||
_buf, buf = buf[:idx], buf[idx + 1:]
|
||||
for i in self._get_log_line_from_buf(buf):
|
||||
self._check(i)
|
||||
if self.is_over_time:
|
||||
return self.has_err
|
||||
file_size -= read_size
|
||||
return False
|
||||
|
||||
# 从缓冲中读取日志
|
||||
@staticmethod
|
||||
def _get_log_line_from_buf(buf: bytes) -> Iterator[str]:
|
||||
n, m = 0, 0
|
||||
buf_len = len(buf) - 1
|
||||
for i in range(buf_len, -1, -1):
|
||||
if buf[i] == ord("\n"):
|
||||
log_line = buf[buf_len + 1 - m: buf_len - n + 1].decode("utf-8")
|
||||
yield log_line
|
||||
n = m = m + 1
|
||||
else:
|
||||
m += 1
|
||||
yield buf[0: buf_len - n + 1].decode("utf-8")
|
||||
|
||||
# 格式化并筛选查询条件
|
||||
def _check(self, log_line: str) -> None:
|
||||
# 筛选日期
|
||||
for err in self.err_list:
|
||||
if err in log_line:
|
||||
self.has_err = True
|
||||
|
||||
ck_time = self._format_time(log_line)
|
||||
if ck_time:
|
||||
self.is_over_time = self.start_time > ck_time
|
||||
|
||||
|
||||
def load_rsync_template():
|
||||
"""
|
||||
加载rsync模板
|
||||
"""
|
||||
if TaskTemplateConfig().get_by_id("40"):
|
||||
return None
|
||||
from .mods import load_task_template_by_config
|
||||
load_task_template_by_config(
|
||||
[{
|
||||
"id": "40",
|
||||
"ver": "1",
|
||||
"used": True,
|
||||
"source": "rsync_push",
|
||||
"title": "File synchronization alarm",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.rsync_push",
|
||||
"name": "RsyncTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
],
|
||||
"sorted": [
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"wx_account",
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg",
|
||||
],
|
||||
"unique": True
|
||||
}]
|
||||
)
|
||||
|
||||
|
||||
RsyncTask = Rsync39Task
|
||||
if rsync_ver_is_38() is True:
|
||||
RsyncTask = Rsync38Task
|
||||
|
||||
|
||||
def push_rsync_by_task_name(task_name: str):
|
||||
from .system import push_by_task_keyword
|
||||
|
||||
push_data = {
|
||||
"task_name": task_name,
|
||||
"msg_list": [
|
||||
">Notification type: File synchronization alarm",
|
||||
">Content of alarm: <font color=#ff0000>File synchronization task {} has failed during the execution, please pay attention to the file synchronization situation and deal with it.</font> ".format(
|
||||
task_name),
|
||||
]
|
||||
}
|
||||
push_by_task_keyword("rsync_push", "rsync_push", push_data=push_data)
|
||||
|
||||
|
||||
class ViewMsgFormat(object):
|
||||
|
||||
@staticmethod
|
||||
def get_msg(task: dict) -> Optional[str]:
|
||||
if task["template_id"] == "40":
|
||||
return "<span>Push alarm information when there is an exception in file synchronization (push {} times per day and then not push)<span>".format(
|
||||
task.get("number_rule", {}).get("day_num"))
|
||||
return None
|
||||
615
mod/base/push_mod/safe_mod_push.py
Normal file
615
mod/base/push_mod/safe_mod_push.py
Normal file
@@ -0,0 +1,615 @@
|
||||
import json
|
||||
from typing import Tuple, Union, Optional, Dict, List
|
||||
|
||||
from . import WxAccountMsg
|
||||
from .mods import TaskConfig
|
||||
from .base_task import BaseTask, BaseTaskViewMsg
|
||||
from .site_push import web_info
|
||||
from .util import get_db_by_file, DB, GET_CLASS, read_file, write_file
|
||||
from .manager import PushManager
|
||||
from .system import push_by_task_keyword
|
||||
|
||||
|
||||
|
||||
class SiteMonitorViolationWordTask(BaseTask): # 网站违规词告警检查
|
||||
DB_FILE = "/www/server/panel/class/projectModel/content/content.db"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name: str = 'site_monitor_violation_word'
|
||||
self.title: str = 'Website violation keyword alert'
|
||||
self.template_name: str = 'Website violation keyword alert'
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查设置的告警参数(是否合理)
|
||||
@param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
||||
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
||||
当检查有误时, 直接返回错误信息的字符串
|
||||
"""
|
||||
if "interval" in task_data:
|
||||
task_data.pop("interval")
|
||||
if "mvw_id" in task_data and not task_data["mvw_id"]:
|
||||
task_data.pop("mvw_id")
|
||||
if "site_name" in task_data and not task_data["site_name"]:
|
||||
task_data.pop("site_name")
|
||||
return task_data
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
return "Violation keyword check on website {}".format(task_data["site_name"])
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "site_mvw_{}".format(task_data.get("mvw_id", 0))
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
"""
|
||||
过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务
|
||||
@param template: 任务的模板信息
|
||||
@return:
|
||||
"""
|
||||
_, items_type = web_info(project_types=("PHP",))
|
||||
template["field"][0]["items"].extend(items_type["PHP"])
|
||||
return template
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
||||
task_data = task["task_data"]
|
||||
if "mvw_id" not in task_data or not task_data["mvw_id"]:
|
||||
return "No corresponding website violation word scanning task, unable to add alert"
|
||||
if "site_name" not in task_data or not task_data["site_name"]:
|
||||
return "No corresponding website violation word scanning task, unable to add alert"
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
||||
task_data = task["task_data"]
|
||||
if "mvw_id" not in task_data or not task_data["mvw_id"]:
|
||||
return "No corresponding website violation word scanning task, unable to add alert"
|
||||
if "site_name" not in task_data or not task_data["site_name"]:
|
||||
return "No corresponding website violation word scanning task, unable to add alert"
|
||||
|
||||
db_obj = get_db_by_file(self.DB_FILE)
|
||||
if not db_obj:
|
||||
return "Website violation word scanning task database file does not exist"
|
||||
pdata = {
|
||||
"send_msg": int(task["status"]),
|
||||
"send_type": ",".join(task["sender"])
|
||||
}
|
||||
try:
|
||||
db_obj.table("monitor_site").where("id=?", task_data["mvw_id"]).update(pdata)
|
||||
db_obj.close()
|
||||
except:
|
||||
return "Website violation word scanning task update failed"
|
||||
# 保障keyword的id正确
|
||||
task["keyword"] = self.get_keyword(task_data)
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
task_data = task["task_data"]
|
||||
if "mvw_id" not in task_data or not task_data["mvw_id"]:
|
||||
return
|
||||
db_obj = get_db_by_file(self.DB_FILE)
|
||||
if not db_obj:
|
||||
return
|
||||
try:
|
||||
db_obj.table("monitor_site").where("id=?", task_data["mvw_id"]).update({"send_msg": 0})
|
||||
db_obj.close()
|
||||
except:
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def set_push_task(cls, mvw_id: int, site_name: str, status: bool, sender: list):
|
||||
task_conf = TaskConfig()
|
||||
old_task = task_conf.get_by_keyword("site_monitor_violation_word", "site_mvw_{}".format(mvw_id))
|
||||
if not old_task:
|
||||
push_data = {
|
||||
"template_id": "121",
|
||||
"task_data": {
|
||||
"status": True,
|
||||
"sender": sender,
|
||||
"task_data": {
|
||||
"site_name": site_name,
|
||||
"mvw_id": mvw_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
from .manager import PushManager
|
||||
PushManager().set_task_conf_data(push_data)
|
||||
else:
|
||||
old_task["sender"] = sender
|
||||
old_task["status"] = status
|
||||
task_conf.save_config()
|
||||
|
||||
@classmethod
|
||||
def remove_push_task(cls, mvw_id: int):
|
||||
task_conf = TaskConfig()
|
||||
old_task = task_conf.get_by_keyword("site_monitor_violation_word", "site_mvw_{}".format(mvw_id))
|
||||
if old_task:
|
||||
task_conf.config.remove(old_task)
|
||||
|
||||
task_conf.save_config()
|
||||
|
||||
|
||||
class VulnerabilityScanningTask(BaseTask):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name: str = 'vulnerability_scanning'
|
||||
self.title: str = 'Website vulnerability alert'
|
||||
self.template_name: str = 'Website vulnerability alert'
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查设置的告警参数(是否合理)
|
||||
@param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
||||
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
||||
当检查有误时, 直接返回错误信息的字符串
|
||||
"""
|
||||
if "cycle" not in task_data or not task_data["cycle"] or not isinstance(task_data["cycle"], int):
|
||||
return "Cycle cannot be empty"
|
||||
|
||||
return {"interval": 60*60*24 * (task_data["cycle"] + 1), "cycle": task_data["cycle"]}
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字,
|
||||
后续通过规则id和来源tamper 查询并使用
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
return "vulnerability_scanning"
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个标题
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
return 'Website vulnerability alert'
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
"""
|
||||
判断这个任务是否需要返送
|
||||
@param task_id: 任务id
|
||||
@param task_data: 任务的告警参数
|
||||
@return: 如果触发了告警,返回一个dict的原文,作为告警信息,否则应当返回None表示未触发
|
||||
返回之中应当包含一个 msg_list 的键(值为List[str]类型),将主要的信息返回
|
||||
用于以下信息的自动序列化包含[dingding, feishu, mail, weixin, web_hook]
|
||||
短信和微信公众号由于长度问题,必须每个任务手动实现
|
||||
"""
|
||||
return None
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
"""
|
||||
过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务
|
||||
@param template: 任务的模板信息
|
||||
@return:
|
||||
"""
|
||||
return template
|
||||
|
||||
@classmethod
|
||||
def set_push_task(cls, status: bool, day: int, sender: list):
|
||||
push_data = {
|
||||
"template_id": "122",
|
||||
"task_data": {
|
||||
"status": status,
|
||||
"sender": sender,
|
||||
"task_data": {
|
||||
"cycle": day,
|
||||
}
|
||||
}
|
||||
}
|
||||
return PushManager().set_task_conf_data(push_data)
|
||||
|
||||
@staticmethod
|
||||
def del_crontab():
|
||||
"""
|
||||
@name 删除项目定时清理任务
|
||||
@auther hezhihong<2022-10-31>
|
||||
@return
|
||||
"""
|
||||
cron_name = '[Do not delete] Vulnerability scanning scheduled task'
|
||||
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
||||
|
||||
if cron_list:
|
||||
for i in cron_list:
|
||||
if not i:
|
||||
continue
|
||||
args = {"id": i['id']}
|
||||
import crontab
|
||||
crontab.crontab().DelCrontab(args)
|
||||
|
||||
def add_crontab(self, day, channel):
|
||||
"""
|
||||
@name 构造计划任务
|
||||
"""
|
||||
cron_name = '[Do not delete] Vulnerability scanning scheduled task'
|
||||
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
||||
if cron_list:
|
||||
self.del_crontab()
|
||||
if not DB('crontab').where('name=?',(cron_name,)).count():
|
||||
args = {
|
||||
"name": cron_name,
|
||||
"type": 'day-n',
|
||||
"where1": day,
|
||||
"hour": '10',
|
||||
"minute": '30',
|
||||
"sName": "",
|
||||
"sType": 'toShell',
|
||||
"notice": '0',
|
||||
"notice_channel": channel,
|
||||
"save": '',
|
||||
"save_local": '1',
|
||||
"backupTo": '',
|
||||
"sBody": 'btpython /www/server/panel/script/cron_scaning.py {}'.format(channel),
|
||||
"urladdress": '',
|
||||
"user": 'root'
|
||||
}
|
||||
import crontab
|
||||
res = crontab.crontab().AddCrontab(args)
|
||||
if res and "id" in res.keys():
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
||||
return self.task_config_update_hook(task)
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
||||
if task["status"]:
|
||||
day = task['task_data']['cycle']
|
||||
channel = ",".join(task['sender'])
|
||||
if self.add_crontab(day, channel):
|
||||
return None
|
||||
return "Failed to add scheduled task"
|
||||
else:
|
||||
self.del_crontab()
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
self.del_crontab()
|
||||
|
||||
|
||||
class FileDetectTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name: str = 'file_detect'
|
||||
self.title: str = 'System file integrity reminder'
|
||||
self.template_name: str = 'System file integrity reminder'
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查设置的告警参数(是否合理)
|
||||
@param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
||||
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
||||
当检查有误时, 直接返回错误信息的字符串
|
||||
"""
|
||||
if not isinstance(task_data["hour"], int) or not isinstance(task_data["minute"], int):
|
||||
return "Hours and minutes must be integers"
|
||||
|
||||
if task_data["hour"] < 0 or task_data["hour"] > 23:
|
||||
return "The hour must be an integer between 0 and 23"
|
||||
|
||||
if task_data["minute"] < 0 or task_data["minute"] > 59:
|
||||
return "Minutes must be an integer between 0 and 59"
|
||||
|
||||
return {
|
||||
"interval": 60 * 60 * 24,
|
||||
"hour": task_data["hour"],
|
||||
"minute": task_data["minute"],
|
||||
}
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字,
|
||||
后续通过规则id和来源tamper 查询并使用
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
return "file_detect"
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个标题
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
return 'System file integrity reminder'
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
"""
|
||||
过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务
|
||||
@param template: 任务的模板信息
|
||||
@return:
|
||||
"""
|
||||
return template
|
||||
|
||||
def add_crontab(self, hour, minute, channel):
|
||||
"""
|
||||
@name 构造计划任务
|
||||
"""
|
||||
cron_name = '[Do not delete] File integrity monitoring scheduled task'
|
||||
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
||||
|
||||
if cron_list:
|
||||
self.del_crontab()
|
||||
if not DB('crontab').where('name=?', (cron_name,)).count():
|
||||
args = {
|
||||
"name": cron_name,
|
||||
"type": 'day',
|
||||
"where1": '',
|
||||
"hour": hour,
|
||||
"minute": minute,
|
||||
"sName": "",
|
||||
"sType": 'toShell',
|
||||
"notice": '0',
|
||||
"notice_channel": channel,
|
||||
"save": '',
|
||||
"save_local": '1',
|
||||
"backupTo": '',
|
||||
"sBody": 'btpython /www/server/panel/script/cron_file.py {}'.format(channel),
|
||||
"urladdress": ''
|
||||
}
|
||||
import crontab
|
||||
res = crontab.crontab().AddCrontab(args)
|
||||
if res and "id" in res.keys():
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
|
||||
# 删除项目定时清理任务
|
||||
@staticmethod
|
||||
def del_crontab():
|
||||
cron_name = '[Do not delete] File integrity monitoring scheduled task'
|
||||
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
||||
if cron_list:
|
||||
for i in cron_list:
|
||||
if not i: continue
|
||||
args = {"id": i['id']}
|
||||
import crontab
|
||||
crontab.crontab().DelCrontab(args)
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
||||
return self.task_config_update_hook(task)
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
||||
if task["status"]:
|
||||
hour = task['task_data']['hour']
|
||||
minute = task['task_data']['minute']
|
||||
channel = ",".join(task['sender'])
|
||||
if self.add_crontab(hour, minute, channel):
|
||||
return None
|
||||
return "Failed to add scheduled task"
|
||||
else:
|
||||
self.del_crontab()
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
self.del_crontab()
|
||||
|
||||
@classmethod
|
||||
def set_push_task(cls, status: bool, hour: int, minute: int, sender: list):
|
||||
push_data = {
|
||||
"template_id": "123",
|
||||
"task_data": {
|
||||
"status": status,
|
||||
"sender": sender,
|
||||
"task_data": {
|
||||
"hour": hour,
|
||||
"minute": minute,
|
||||
}
|
||||
}
|
||||
}
|
||||
from .manager import PushManager
|
||||
return PushManager().set_task_conf_data(push_data)
|
||||
|
||||
|
||||
class SafeCloudTask(BaseTask):
|
||||
_config_file = "/www/server/panel/data/safeCloud/config.json"
|
||||
_all_safe_type = ("webshell", )
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "safe_cloud_hinge"
|
||||
self.title = "Yakpanel Cloud Security Center Alarm"
|
||||
self.template_name = "Yakpanel Cloud Security Center Alarm"
|
||||
|
||||
self._safe_cloud_conf: Optional[dict] = None
|
||||
|
||||
@property
|
||||
def safe_cloud_conf(self) -> Optional[dict]:
|
||||
"""
|
||||
获取云安全配置
|
||||
:return: 云安全配置
|
||||
"""
|
||||
if self._safe_cloud_conf and isinstance(self._safe_cloud_conf, dict):
|
||||
return self._safe_cloud_conf
|
||||
try:
|
||||
self._safe_cloud_conf = json.loads(read_file(self._config_file))
|
||||
return self._safe_cloud_conf
|
||||
except:
|
||||
self._init_config()
|
||||
try:
|
||||
self._safe_cloud_conf = json.loads(read_file(self._config_file))
|
||||
return self._safe_cloud_conf
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
"""
|
||||
过滤模板
|
||||
:param template: 模板
|
||||
:return: 过滤后的模板
|
||||
"""
|
||||
return template
|
||||
|
||||
def save_safe_cloud_conf(self):
|
||||
"""
|
||||
保存云安全配置
|
||||
"""
|
||||
write_file(self._config_file, json.dumps(self._safe_cloud_conf))
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查任务数据
|
||||
:param task_data: 任务数据
|
||||
:return: 检查后的任务数据
|
||||
"""
|
||||
if "safe_type" in task_data:
|
||||
for i in task_data["safe_type"]:
|
||||
if i not in self._all_safe_type:
|
||||
return "Security type error"
|
||||
else:
|
||||
task_data["safe_type"] = ["webshell"]
|
||||
|
||||
task_data["interval"] = 60 * 60 * 3
|
||||
return task_data
|
||||
|
||||
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查告警数量规则,一天只能告警20次
|
||||
:param num_rule: 告警数量规则
|
||||
:return: 检查后的告警数量规则
|
||||
"""
|
||||
num_rule["day_num"] = 20
|
||||
return num_rule
|
||||
|
||||
def check_time_rule(self, time_rule: dict) -> Union[dict, str]:
|
||||
"""
|
||||
检查告警时间规则[写死]
|
||||
:param time_rule: 告警时间规则
|
||||
:return: 检查后的告警时间规则
|
||||
"""
|
||||
# 测试数据为1秒 ,正常数据为 1200 20*60 20分钟告警一次
|
||||
time_rule["send_interval"] = 1200
|
||||
return time_rule
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字,
|
||||
后续通过规则id和来源tamper 查询并使用
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
return "safe_cloud_hinge"
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
"""
|
||||
返回一个标题
|
||||
@param task_data: 通过check_args后生成的告警参数字典
|
||||
@return: 返回一个关键词字符串
|
||||
"""
|
||||
return 'Yakpanel Cloud Security Center Alarm'
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
"""
|
||||
判断这个任务是否需要返送
|
||||
@param task_id: 任务id
|
||||
@param task_data: 任务的告警参数
|
||||
@return: 如果触发了告警,返回一个dict的原文,作为告警信息,否则应当返回None表示未触发
|
||||
返回之中应当包含一个 msg_list 的键(值为List[str]类型),将主要的信息返回
|
||||
用于以下信息的自动序列化包含[dingding, feishu, mail, weixin, web_hook]
|
||||
短信和微信公众号由于长度问题,必须每个任务手动实现
|
||||
"""
|
||||
|
||||
return None
|
||||
|
||||
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
||||
return self.task_config_update_hook(task)
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
||||
"""
|
||||
更新告警配置
|
||||
:param task: 任务
|
||||
:return: 更新后的任务
|
||||
"""
|
||||
if not self.safe_cloud_conf:
|
||||
return "Failed to initialize configuration file, unable to add"
|
||||
|
||||
alert_data = self.safe_cloud_conf["alertable"]
|
||||
alert_data["safe_type"] = task["task_data"].get("safe_type", ["webshell"])
|
||||
alert_data["interval"] = task["task_data"].get("interval", 60*60*3)
|
||||
alert_data["status"] = task["status"]
|
||||
alert_data["sender"] = task["sender"]
|
||||
alert_data["time_rule"] = task["time_rule"]
|
||||
alert_data["number_rule"] = task["number_rule"]
|
||||
self.save_safe_cloud_conf()
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> Optional[str]:
|
||||
"""
|
||||
删除告警配置
|
||||
:param task: 任务
|
||||
:return: 删除后的任务
|
||||
"""
|
||||
if not self.safe_cloud_conf:
|
||||
return None
|
||||
|
||||
alert_data = self.safe_cloud_conf["alertable"]
|
||||
alert_data["safe_type"] = task["task_data"].get("safe_type", ["webshell"])
|
||||
alert_data["interval"] = task["task_data"].get("interval", 60*60*3)
|
||||
alert_data["status"] = False
|
||||
alert_data["sender"] = []
|
||||
alert_data["time_rule"] = task["time_rule"]
|
||||
alert_data["number_rule"] = task["number_rule"]
|
||||
self.save_safe_cloud_conf()
|
||||
|
||||
# 更新告警配置
|
||||
@staticmethod
|
||||
def set_push_conf(alert_data: dict) -> Optional[str]:
|
||||
"""
|
||||
将告警信息设置到告警任务中去
|
||||
:param alert_data:
|
||||
:return:
|
||||
"""
|
||||
pm = PushManager()
|
||||
p_data = {
|
||||
"template_id": "124",
|
||||
"task_data": {
|
||||
"status": alert_data.get("status", True),
|
||||
"sender": alert_data.get("sender", []),
|
||||
"task_data": {
|
||||
"safe_type": alert_data.get("safe_type", ["webshell"]),
|
||||
"interval": alert_data.get("interval", 60*60*3),
|
||||
},
|
||||
"time_rule": alert_data.get("time_rule", {}),
|
||||
"number_rule": alert_data.get("number_rule", {}),
|
||||
}
|
||||
}
|
||||
return pm.set_task_conf_data(p_data)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _init_config():
|
||||
"""
|
||||
初始化配置
|
||||
"""
|
||||
try:
|
||||
import PluginLoader
|
||||
|
||||
args = GET_CLASS()
|
||||
args.model_index = 'project'
|
||||
PluginLoader.module_run("safecloud", "init_config", args)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ViewMsgFormat(BaseTaskViewMsg):
|
||||
|
||||
def get_msg(self, task: dict) -> Optional[str]:
|
||||
if task["template_id"] == "122":
|
||||
return "Every {} days, identify and scan for vulnerabilities in common open-source CMS programs across all websites and send alerts".format(task["task_data"]["cycle"])
|
||||
if task["template_id"] == "123":
|
||||
return "Scan the critical executable files in the system daily at 【{}:{}】, and send alerts when changes are detected".format(
|
||||
task["task_data"]["hour"], task["task_data"]["minute"]
|
||||
)
|
||||
if task["template_id"] == "124":
|
||||
return "Every {} hours, scan the server files to identify abnormal situations such as high resource consumption and malicious control of the server, and send alerts".format(
|
||||
int(task["task_data"]["interval"] / 3600)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
SiteMonitorViolationWordTask.VIEW_MSG = VulnerabilityScanningTask.VIEW_MSG = FileDetectTask.VIEW_MSG = SafeCloudTask.VIEW_MSG = ViewMsgFormat
|
||||
196
mod/base/push_mod/safe_mod_push_template.json
Normal file
196
mod/base/push_mod/safe_mod_push_template.json
Normal file
@@ -0,0 +1,196 @@
|
||||
[
|
||||
{
|
||||
"id": "122",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "vulnerability_scanning",
|
||||
"title": "Website vulnerability alert",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.safe_mod_push",
|
||||
"name": "VulnerabilityScanningTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "Interval period",
|
||||
"type": "number",
|
||||
"suffix": "",
|
||||
"unit": "day",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"attr": "help",
|
||||
"name": "source",
|
||||
"type": "help",
|
||||
"unit": "",
|
||||
"style": {
|
||||
"margin-top": "6px"
|
||||
},
|
||||
"list": [
|
||||
"【Website】-> 【Vulnerability Scanning】-> 【Automatic Scanning】"
|
||||
],
|
||||
"suffix": "",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle"
|
||||
],
|
||||
[
|
||||
"help"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 1
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true,
|
||||
"tags": [
|
||||
"safe",
|
||||
"site"
|
||||
],
|
||||
"description": "Regularly scan all websites on the server and identify various popular open-source CMS programs to help users quickly discover potential security vulnerabilities on the website and send alert notifications",
|
||||
"is_pro": true
|
||||
},
|
||||
{
|
||||
"id": "123",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "file_detect",
|
||||
"title": "System file integrity reminder",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.safe_mod_push",
|
||||
"name": "FileDetectTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "hour",
|
||||
"name": "daily",
|
||||
"type": "number",
|
||||
"suffix": "",
|
||||
"unit": "hour",
|
||||
"default": 4
|
||||
},
|
||||
{
|
||||
"attr": "minute",
|
||||
"name": " ",
|
||||
"type": "number",
|
||||
"suffix": "Execute detection tasks",
|
||||
"unit": "minute",
|
||||
"default": 10
|
||||
},
|
||||
{
|
||||
"attr": "help",
|
||||
"name": "source",
|
||||
"type": "help",
|
||||
"unit": "",
|
||||
"style": {
|
||||
"margin-top": "6px"
|
||||
},
|
||||
"list": [
|
||||
"【Security】-> 【Security testing】-> 【File integrity check】-> 【Regular scanning】"
|
||||
],
|
||||
"suffix": "",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"hour",
|
||||
"minute"
|
||||
],
|
||||
[
|
||||
"help"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 1
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true,
|
||||
"tags": [
|
||||
"safe"
|
||||
],
|
||||
"is_pro": true,
|
||||
"description": "Regularly scan the system's critical file directory (such as /var/bin) according to the rules, identify abnormal situations such as file tampering, deletion, or movement, and send alert notifications to the administrator"
|
||||
},
|
||||
{
|
||||
"id": "124",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "safe_cloud_hinge",
|
||||
"title": "Cloud Security Center Alert",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.safe_mod_push",
|
||||
"name": "SafeCloudTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "help",
|
||||
"name": "help",
|
||||
"type": "link",
|
||||
"unit": "",
|
||||
"style": {
|
||||
"margin-top": "6px"
|
||||
},
|
||||
"list": [
|
||||
"<a href=\"/\">Go to 【 Home>Baota Cloud Security Center 】 to view details</a>"
|
||||
]
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"help"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {},
|
||||
"advanced_default": {},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"wx_account",
|
||||
"tg"
|
||||
],
|
||||
"unique": true,
|
||||
"tags": [
|
||||
"safe"
|
||||
],
|
||||
"is_pro": true,
|
||||
"description": "The Baota Cloud Security Center will scan server files, identify abnormal situations such as occupying a large amount of resources or maliciously controlling servers, and send alert notifications to administrators."
|
||||
}
|
||||
]
|
||||
133
mod/base/push_mod/send_tool.py
Normal file
133
mod/base/push_mod/send_tool.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import ipaddress
|
||||
import re
|
||||
|
||||
from .util import get_config_value
|
||||
|
||||
|
||||
class WxAccountMsgBase:
|
||||
|
||||
@classmethod
|
||||
def new_msg(cls):
|
||||
return cls()
|
||||
|
||||
def set_ip_address(self, server_ip, local_ip):
|
||||
pass
|
||||
|
||||
def to_send_data(self):
|
||||
return "", {}
|
||||
|
||||
|
||||
class WxAccountMsg(WxAccountMsgBase):
|
||||
def __init__(self):
|
||||
self.ip_address: str = ""
|
||||
self.thing_type: str = ""
|
||||
self.msg: str = ""
|
||||
self.next_msg: str = ""
|
||||
|
||||
def set_ip_address(self, server_ip, local_ip):
|
||||
self.ip_address = "{}({})".format(server_ip, local_ip)
|
||||
if len(self.ip_address) > 32:
|
||||
self.ip_address = self.ip_address[:29] + "..."
|
||||
|
||||
def to_send_data(self):
|
||||
res = {
|
||||
"first": {},
|
||||
"keyword1": {
|
||||
"value": self.ip_address,
|
||||
},
|
||||
"keyword2": {
|
||||
"value": self.thing_type,
|
||||
},
|
||||
"keyword3": {
|
||||
"value": self.msg,
|
||||
}
|
||||
}
|
||||
|
||||
if self.next_msg != "":
|
||||
res["keyword4"] = {"value": self.next_msg}
|
||||
|
||||
return "", res
|
||||
|
||||
|
||||
class WxAccountLoginMsg(WxAccountMsgBase):
|
||||
tid = "RJNG8dBZ5Tb9EK6j6gOlcAgGs2Fjn5Fb07vZIsYg1P4"
|
||||
|
||||
def __init__(self):
|
||||
self.login_name: str = ""
|
||||
self.login_ip: str = ""
|
||||
self.thing_type: str = ""
|
||||
self.login_type: str = ""
|
||||
self.address: str = ""
|
||||
self._server_name: str = ""
|
||||
|
||||
def set_ip_address(self, server_ip, local_ip):
|
||||
if self._server_name == "":
|
||||
self._server_name = "服务器IP{}".format(server_ip)
|
||||
|
||||
def _get_server_name(self):
|
||||
data = get_config_value("title") # 若获得别名,则使用别名
|
||||
if data != "":
|
||||
self._server_name = data
|
||||
|
||||
def to_send_data(self):
|
||||
self._get_server_name()
|
||||
if self.address.startswith(">Place of Ownership:"):
|
||||
self.address = self.address[5:]
|
||||
if self.address == "":
|
||||
self.address = "Unknown place of ownership"
|
||||
|
||||
if not _is_ipv4(self.login_ip):
|
||||
self.login_ip = "ipv6-can not show"
|
||||
|
||||
res = {
|
||||
"thing10": {
|
||||
"value": self._server_name,
|
||||
},
|
||||
"character_string9": {
|
||||
"value": self.login_ip,
|
||||
},
|
||||
"thing7": {
|
||||
"value": self.login_type,
|
||||
},
|
||||
"thing11": {
|
||||
"value": self.address,
|
||||
},
|
||||
"thing2": {
|
||||
"value": self.login_name,
|
||||
}
|
||||
}
|
||||
return self.tid, res
|
||||
|
||||
|
||||
# 处理短信告警信息的不规范问题
|
||||
def sms_msg_normalize(sm_args: dict) -> dict:
|
||||
for key, val in sm_args.items():
|
||||
sm_args[key] = _norm_sms_push_argv(str(val))
|
||||
return sm_args
|
||||
|
||||
|
||||
def _norm_sms_push_argv(data):
|
||||
"""
|
||||
@处理短信参数,否则会被拦截
|
||||
"""
|
||||
if _is_ipv4(data):
|
||||
tmp1 = data.split('.')
|
||||
return '{}_***_***_{}'.format(tmp1[0], tmp1[3])
|
||||
|
||||
data = data.replace(".", "_").replace("+", "+")
|
||||
return data
|
||||
|
||||
|
||||
def _is_ipv4(data: str) -> bool:
|
||||
try:
|
||||
ipaddress.IPv4Address(data)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _is_domain(domain):
|
||||
rep_domain = re.compile(r"^([\w\-*]{1,100}\.){1,10}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$")
|
||||
if rep_domain.match(domain):
|
||||
return True
|
||||
return False
|
||||
1151
mod/base/push_mod/site_push.py
Normal file
1151
mod/base/push_mod/site_push.py
Normal file
File diff suppressed because it is too large
Load Diff
538
mod/base/push_mod/site_push_template.json
Normal file
538
mod/base/push_mod/site_push_template.json
Normal file
@@ -0,0 +1,538 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "ssl",
|
||||
"title": "Certificate (SSL) expiration",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "SSLCertificateTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "Model",
|
||||
"type": "select",
|
||||
"default": "all",
|
||||
"items": [
|
||||
{
|
||||
"title": "ALL SSL",
|
||||
"value": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "Remaining days",
|
||||
"type": "number",
|
||||
"suffix": "",
|
||||
"unit": "day(s)",
|
||||
"default": 15
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"cycle"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"project": "all",
|
||||
"cycle": 15
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"total": 2
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"sms",
|
||||
"tg"
|
||||
],
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "site_end_time",
|
||||
"title": "Website expiration",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "SiteEndTimeTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "Remaining days",
|
||||
"type": "number",
|
||||
"unit": "day(s)",
|
||||
"suffix": "",
|
||||
"default": 7
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cycle": 7
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"total": 2
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "panel_pwd_end_time",
|
||||
"title": "YakPanel password expiration date",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "PanelPwdEndTimeTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "Remaining days",
|
||||
"type": "number",
|
||||
"unit": "day(s)",
|
||||
"suffix": "",
|
||||
"default": 15
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cycle": 15
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"total": 2
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "ssh_login_error",
|
||||
"title": "SSH login failure alarm",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "SSHLoginErrorTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "Trigger conditions",
|
||||
"type": "number",
|
||||
"unit": "minute(s)",
|
||||
"suffix": "less than ",
|
||||
"default": 30
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "Login failed",
|
||||
"type": "number",
|
||||
"unit": "time(s)",
|
||||
"suffix": "",
|
||||
"default": 3
|
||||
},
|
||||
{
|
||||
"attr": "interval",
|
||||
"name": "Interval",
|
||||
"type": "number",
|
||||
"unit": "second(s)",
|
||||
"suffix": "more than ",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle",
|
||||
"count"
|
||||
],
|
||||
[
|
||||
"interval"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cycle": 30,
|
||||
"count": 3,
|
||||
"interval": 600
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
},
|
||||
"time_rule": {
|
||||
"send_interval": 600
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "services",
|
||||
"title": "Service Stop Alert",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "ServicesTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "Notification type",
|
||||
"type": "select",
|
||||
"default": null,
|
||||
"items": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "Auto-restart",
|
||||
"type": "radio",
|
||||
"suffix": "",
|
||||
"default": 1,
|
||||
"items": [
|
||||
{
|
||||
"title": "Automatically attempt to restart the project",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"title": "Do not attempt to restart",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "interval",
|
||||
"name": "Interval",
|
||||
"type": "number",
|
||||
"unit": "second(s)",
|
||||
"suffix": "more than ",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
],
|
||||
[
|
||||
"interval"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"project": "",
|
||||
"count": 2,
|
||||
"interval": 600
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "panel_safe_push",
|
||||
"title": "YakPanel security alarms",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "PanelSafePushTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "help",
|
||||
"name": "Alarm content",
|
||||
"type": "help",
|
||||
"unit": "",
|
||||
"style": {
|
||||
"margin-top": "6px"
|
||||
},
|
||||
"list": [
|
||||
"Panel user changes, panel logs are deleted, panels are opened for developers"
|
||||
],
|
||||
"suffix": "",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"help"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "ssh_login",
|
||||
"title": "SSH login alert",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "SSHLoginTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
],
|
||||
"sorted": [
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "panel_login",
|
||||
"title": "YakPanel login alarm",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "PanelLoginTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
],
|
||||
"sorted": [
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"sms",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "project_status",
|
||||
"title": "Project Stop Alert",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.site_push",
|
||||
"name": "ProjectStatusTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "project type",
|
||||
"type": "select",
|
||||
"default": 1,
|
||||
"items": [
|
||||
{
|
||||
"title": "Node Project",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"title": "Go Project",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"title": "Python Project",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"title": "Other Project",
|
||||
"value": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "Project name",
|
||||
"type": "select",
|
||||
"default": null,
|
||||
"all_items": null,
|
||||
"items": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "interval",
|
||||
"name": "interval",
|
||||
"type": "number",
|
||||
"unit": "second(s)",
|
||||
"suffix": "more than ",
|
||||
"default": 600
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "Auto-restart",
|
||||
"type": "radio",
|
||||
"suffix": "",
|
||||
"default": 1,
|
||||
"items": [
|
||||
{
|
||||
"title": "Automatically attempt to restart the project",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"title": "Do not attempt to restart",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle"
|
||||
],
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"interval"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cycle": 1,
|
||||
"project": "",
|
||||
"interval": 600,
|
||||
"count": 2
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"sms",
|
||||
"tg"
|
||||
],
|
||||
"unique": false,
|
||||
"tags": ["site", "common"]
|
||||
}
|
||||
]
|
||||
|
||||
348
mod/base/push_mod/ssl_push.py
Normal file
348
mod/base/push_mod/ssl_push.py
Normal file
@@ -0,0 +1,348 @@
|
||||
import json
|
||||
import time
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Tuple, Union, Optional
|
||||
|
||||
from .send_tool import WxAccountMsg
|
||||
from .base_task import BaseTask
|
||||
from .mods import PUSH_DATA_PATH, TaskConfig, PANEL_PATH
|
||||
from .util import read_file, DB, write_file
|
||||
from mod.base.web_conf import RealSSLManger
|
||||
import public
|
||||
|
||||
class DomainEndTimeTask(BaseTask):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "domain_endtime"
|
||||
self.template_name = "Domain expiration"
|
||||
# self.title = "Domain expiration"
|
||||
self._tip_file = "{}/domain_endtime.tip".format(PUSH_DATA_PATH)
|
||||
self._tip_data: Optional[dict] = None
|
||||
self._task_config = TaskConfig()
|
||||
|
||||
# 每次任务使用
|
||||
self.domain_list = []
|
||||
self.push_keys = []
|
||||
self.task_id = None
|
||||
|
||||
@property
|
||||
def tips(self) -> dict:
|
||||
if self._tip_data is not None:
|
||||
return self._tip_data
|
||||
try:
|
||||
self._tip_data = json.loads(read_file(self._tip_file))
|
||||
except:
|
||||
self._tip_data = {}
|
||||
return self._tip_data
|
||||
|
||||
def save_tip(self):
|
||||
write_file(self._tip_file, json.dumps(self.tips))
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
self.title = self.get_title(task_data)
|
||||
# 过滤单独设置提醒的域名
|
||||
not_push_web = [i["task_data"]["project"] for i in self._task_config.config if i["source"] == self.source_name]
|
||||
|
||||
sql = DB("ssl_domains")
|
||||
total = self._task_config.get_by_id(task_id).get("number_rule", {}).get("total", 1)
|
||||
if "all" in not_push_web:
|
||||
not_push_web.remove("all")
|
||||
# need_check_list = []
|
||||
if task_data["project"] == "all":
|
||||
# 所有域名
|
||||
domain_list = sql.select()
|
||||
for domain in domain_list:
|
||||
if domain['domain'] in not_push_web:
|
||||
continue
|
||||
if self.tips.get(task_id, {}).get(domain['domain'], 0) > total:
|
||||
continue
|
||||
end_time = datetime.strptime(domain['endtime'], '%Y-%m-%d')
|
||||
if int((end_time.timestamp() - time.time()) / 86400) <= task_data['cycle']:
|
||||
self.push_keys.append(domain['domain'])
|
||||
self.domain_list.append(domain)
|
||||
# need_check_list.append(domain['domain'])
|
||||
|
||||
else:
|
||||
find = sql.where('domain=?', (task_data['project'],)).find()
|
||||
if not find:
|
||||
return None
|
||||
|
||||
end_time = datetime.strptime(find['endtime'], '%Y-%m-%d')
|
||||
if int((end_time.timestamp() - time.time()) / 86400) <= task_data['cycle']:
|
||||
self.push_keys.append(find['domain'])
|
||||
self.domain_list.append(find)
|
||||
|
||||
# need_check_list.append((find['domain']))
|
||||
|
||||
# for name, project_type in need_check_list:
|
||||
# info = self._check_end_time(name, task_data['cycle'], project_type)
|
||||
# if isinstance(info, dict): # 返回的是详情,说明需要推送了
|
||||
# info['site_name'] = name
|
||||
# self.push_keys.append(name)
|
||||
# self.domain_list.append(info)
|
||||
|
||||
if len(self.domain_list) == 0:
|
||||
return None
|
||||
|
||||
s_list = ['>About to expire: <font color=#ff0000>{} </font>'.format(len(self.domain_list))]
|
||||
for x in self.domain_list:
|
||||
s_list.append(">Domain: {} Expiration:{}".format(x['domain'], x['endtime']))
|
||||
|
||||
self.task_id = task_id
|
||||
return {"msg_list": s_list}
|
||||
|
||||
@staticmethod
|
||||
def _check_end_time(site_name, limit, prefix) -> Optional[dict]:
|
||||
info = RealSSLManger(conf_prefix=prefix).get_site_ssl_info(site_name)
|
||||
if info is not None:
|
||||
end_time = datetime.strptime(info['notAfter'], '%Y-%m-%d')
|
||||
if int((end_time.timestamp() - time.time()) / 86400) <= limit:
|
||||
return info
|
||||
return None
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
if task_data["project"] == "all":
|
||||
return "Domain expiration -- All"
|
||||
return "Domain expiration -- [{}]".format(task_data["project"])
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return 'domain_end|Domain expiration reminders', {
|
||||
"name": push_public_data["ip"],
|
||||
"domain": self.domain_list[0]['domain'],
|
||||
'time': self.domain_list[0]["endtime"],
|
||||
'total': len(self.domain_list)
|
||||
}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Domain expiration reminders"
|
||||
msg.msg = "There are {} domain names that will expire and will affect access".format(len(self.domain_list))
|
||||
return msg
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天
|
||||
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] > 1):
|
||||
return "The remaining time parameter is incorrect, at least 1 day"
|
||||
return task_data
|
||||
|
||||
def filter_template(self, template) -> dict:
|
||||
domain_list = DB("ssl_domains").select()
|
||||
|
||||
items = [{"title": i["domain"], "value": i["domain"]}for i in domain_list]
|
||||
|
||||
template["field"][0]["items"].extend(items)
|
||||
return template
|
||||
|
||||
|
||||
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
|
||||
num_rule["get_by_func"] = "can_send_by_num_rule"
|
||||
return num_rule
|
||||
|
||||
# 实际的次数检查已在 get_push_data 其他位置完成
|
||||
def can_send_by_num_rule(self, task_id: str, task_data: dict, number_rule: dict, push_data: dict) -> Optional[str]:
|
||||
return None
|
||||
|
||||
def task_run_end_hook(self, res) -> None:
|
||||
if not res["do_send"]:
|
||||
return
|
||||
if self.task_id:
|
||||
if self.task_id not in self.tips:
|
||||
self.tips[self.task_id] = {}
|
||||
|
||||
for w in self.push_keys:
|
||||
if w in self.tips[self.task_id]:
|
||||
self.tips[self.task_id][w] += 1
|
||||
else:
|
||||
self.tips[self.task_id][w] = 1
|
||||
|
||||
self.save_tip()
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> None:
|
||||
if task["id"] in self.tips:
|
||||
self.tips.pop(task["id"])
|
||||
self.save_tip()
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
if task["id"] in self.tips:
|
||||
self.tips.pop(task["id"])
|
||||
self.save_tip()
|
||||
|
||||
|
||||
class CertEndTimeTask(BaseTask):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "cert_endtime"
|
||||
self.template_name = "Certificate expiration"
|
||||
# self.title = "Certificate expiration"
|
||||
self._tip_file = "{}/cert_endtime.tip".format(PUSH_DATA_PATH)
|
||||
self._tip_data: Optional[dict] = None
|
||||
self._task_config = TaskConfig()
|
||||
|
||||
# 每次任务使用
|
||||
self.cert_list = []
|
||||
self.push_keys = []
|
||||
self.task_id = None
|
||||
|
||||
@property
|
||||
def tips(self) -> dict:
|
||||
if self._tip_data is not None:
|
||||
return self._tip_data
|
||||
try:
|
||||
self._tip_data = json.loads(read_file(self._tip_file))
|
||||
except:
|
||||
self._tip_data = {}
|
||||
return self._tip_data
|
||||
|
||||
def save_tip(self):
|
||||
write_file(self._tip_file, json.dumps(self.tips))
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
from .util import get_cert_list, to_dict_obj
|
||||
|
||||
exclude_ids = [i["task_data"]["project"] for i in self._task_config.config if i["source"] == self.source_name]
|
||||
total = self._task_config.get_by_id(task_id).get("number_rule", {}).get("total", 1)
|
||||
|
||||
if "all" in exclude_ids:
|
||||
exclude_ids.remove("all")
|
||||
data = get_cert_list(to_dict_obj({"status_id": 1}))['data']
|
||||
if task_data["project"] == "all":
|
||||
for cert in data:
|
||||
if cert["ssl_id"] in exclude_ids:
|
||||
continue
|
||||
if self.tips.get(task_id, {}).get(cert['ssl_id'], 0) > total:
|
||||
continue
|
||||
if not cert.get("endDay") and cert.get("endDay") != 0:
|
||||
continue
|
||||
if cert["endDay"] <= task_data["cycle"]:
|
||||
self.cert_list.append(cert)
|
||||
else:
|
||||
for cert in data:
|
||||
if cert["ssl_id"] != task_data["project"]:
|
||||
continue
|
||||
if not cert.get("endDay") and cert.get("endDay") != 0:
|
||||
continue
|
||||
if cert["endDay"] <= task_data["cycle"]:
|
||||
self.cert_list.append(cert)
|
||||
self.title = self.get_title(task_data)
|
||||
if len(self.cert_list) == 0:
|
||||
return None
|
||||
|
||||
s_list = ['>About to expire: <font color=#ff0000>{} </font>'.format(len(self.cert_list))]
|
||||
for x in self.cert_list:
|
||||
s_list.append(
|
||||
">Certificates: {} [{}]expire in days Websites that may be affected:{}".format("{} | {}".format(x["title"],",".join(x.get("domainName", []) or "None")), x['endDay'], ','.join(x.get('use_site', [])) or "None")
|
||||
)
|
||||
|
||||
self.task_id = task_id
|
||||
return {"msg_list": s_list}
|
||||
|
||||
@staticmethod
|
||||
def _check_end_time(site_name, limit, prefix) -> Optional[dict]:
|
||||
info = RealSSLManger(conf_prefix=prefix).get_site_ssl_info(site_name)
|
||||
if info is not None:
|
||||
end_time = datetime.strptime(info['notAfter'], '%Y-%m-%d')
|
||||
if int((end_time.timestamp() - time.time()) / 86400) <= limit:
|
||||
return info
|
||||
return None
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
from .util import get_cert_list, to_dict_obj
|
||||
if task_data["project"] == "all":
|
||||
return "Certificate expiration -- All"
|
||||
data = get_cert_list(to_dict_obj({}))['data']
|
||||
for cert in data:
|
||||
if cert["ssl_id"] == task_data["project"]:
|
||||
return "Certificate expiration -- [{} | {}]".format(cert["title"],",".join(cert.get("domainName", []) or "None"))
|
||||
return "Domain [{}] expiration reminder".format(task_data["project"])
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return 'cert_end|Certificate expiration reminders', {
|
||||
"name": push_public_data["ip"],
|
||||
"cert": self.cert_list[0]['domain'],
|
||||
'time': self.cert_list[0]["endtime"],
|
||||
'total': len(self.cert_list)
|
||||
}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Certificate expiration reminders"
|
||||
msg.msg = "There are {} certificates that will expire and will affect access".format(len(self.cert_list))
|
||||
return msg
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天
|
||||
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] > 1):
|
||||
return "The remaining time parameter is incorrect, at least 1 day"
|
||||
return task_data
|
||||
|
||||
def filter_template(self, template) -> dict:
|
||||
from .util import get_cert_list, to_dict_obj
|
||||
|
||||
items = [
|
||||
{"title": "{} | {}".format(i["title"],",".join(i.get("domainName", []) or "None")), "value": i["ssl_id"]}
|
||||
for i in get_cert_list(to_dict_obj({}))['data']
|
||||
if i.get("endDay")
|
||||
]
|
||||
|
||||
template["field"][0]["items"].extend(items)
|
||||
|
||||
return template
|
||||
|
||||
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
|
||||
num_rule["get_by_func"] = "can_send_by_num_rule"
|
||||
return num_rule
|
||||
|
||||
# 实际的次数检查已在 get_push_data 其他位置完成
|
||||
def can_send_by_num_rule(self, task_id: str, task_data: dict, number_rule: dict, push_data: dict) -> Optional[str]:
|
||||
return None
|
||||
|
||||
def task_run_end_hook(self, res) -> None:
|
||||
if not res["do_send"]:
|
||||
return
|
||||
if self.task_id:
|
||||
if self.task_id not in self.tips:
|
||||
self.tips[self.task_id] = {}
|
||||
|
||||
for w in self.push_keys:
|
||||
if w in self.tips[self.task_id]:
|
||||
self.tips[self.task_id][w] += 1
|
||||
else:
|
||||
self.tips[self.task_id][w] = 1
|
||||
|
||||
self.save_tip()
|
||||
|
||||
def task_config_update_hook(self, task: dict) -> None:
|
||||
if task["id"] in self.tips:
|
||||
self.tips.pop(task["id"])
|
||||
self.save_tip()
|
||||
|
||||
def task_config_remove_hook(self, task: dict) -> None:
|
||||
if task["id"] in self.tips:
|
||||
self.tips.pop(task["id"])
|
||||
self.save_tip()
|
||||
|
||||
class ViewMsgFormat(object):
|
||||
_FORMAT = {
|
||||
"1": (
|
||||
lambda x: "<span>Time remaining less than {} days {}</span>".format(
|
||||
x["task_data"].get("cycle"),
|
||||
("(If it is not processed, it will be resent 1 time the next day for %d days)" % x.get("number_rule", {}).get("total", 0)) if x.get("number_rule", {}).get("total", 0) else ""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def get_msg(self, task: dict) -> Optional[str]:
|
||||
if task["template_id"] in ["70", "71"]:
|
||||
return self._FORMAT["1"](task)
|
||||
if task["template_id"] in self._FORMAT:
|
||||
return self._FORMAT[task["template_id"]](task)
|
||||
return None
|
||||
127
mod/base/push_mod/ssl_push_template.json
Normal file
127
mod/base/push_mod/ssl_push_template.json
Normal file
@@ -0,0 +1,127 @@
|
||||
[
|
||||
{
|
||||
"id": "70",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "domain_endtime",
|
||||
"title": "Domain expiration",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.ssl_push",
|
||||
"name": "DomainEndTimeTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "domain",
|
||||
"type": "select",
|
||||
"default": "all",
|
||||
"items": [
|
||||
{
|
||||
"title": "All domain",
|
||||
"value": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "Remaining days",
|
||||
"type": "number",
|
||||
"suffix": "",
|
||||
"unit": "day(s)",
|
||||
"default": 30
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"cycle"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"project": "all",
|
||||
"cycle": 30
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"total": 2
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"id": "71",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "cert_endtime",
|
||||
"title": "Certificate expiration",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.ssl_push",
|
||||
"name": "CertEndTimeTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "Certificate",
|
||||
"type": "select",
|
||||
"default": "all",
|
||||
"items": [
|
||||
{
|
||||
"title": "all certificates",
|
||||
"value": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "Remaining days",
|
||||
"type": "number",
|
||||
"suffix": "",
|
||||
"unit": "day(s)",
|
||||
"default": 30
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"cycle"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"project": "all",
|
||||
"cycle": 30
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"total": 2
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg"
|
||||
],
|
||||
"unique": false
|
||||
}
|
||||
]
|
||||
|
||||
491
mod/base/push_mod/system.py
Normal file
491
mod/base/push_mod/system.py
Normal file
@@ -0,0 +1,491 @@
|
||||
import datetime
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import Optional, List, Dict, Type, Union, Any
|
||||
|
||||
import public
|
||||
from .base_task import BaseTask
|
||||
from .compatible import rsync_compatible
|
||||
from .mods import TaskTemplateConfig, TaskConfig, TaskRecordConfig, SenderConfig
|
||||
from .send_tool import sms_msg_normalize
|
||||
from .tool import load_task_cls_by_path, load_task_cls_by_function, T_CLS
|
||||
from .util import get_server_ip, get_network_ip, format_date, get_config_value
|
||||
|
||||
WAIT_TASK_LIST: List[Thread] = []
|
||||
|
||||
|
||||
class PushSystem:
|
||||
def __init__(self):
|
||||
self.task_cls_cache: Dict[str, Type[T_CLS]] = {} # NOQA
|
||||
self._today_zero: Optional[datetime.datetime] = None
|
||||
self._sender_type_class: Optional[dict] = {}
|
||||
self.sd_cfg = SenderConfig()
|
||||
|
||||
def sender_cls(self, sender_type: str):
|
||||
if not self._sender_type_class:
|
||||
from mod.base.msg import WeiXinMsg, MailMsg, WebHookMsg, FeiShuMsg, DingDingMsg, SMSMsg, TgMsg
|
||||
self._sender_type_class = {
|
||||
"weixin": WeiXinMsg,
|
||||
"mail": MailMsg,
|
||||
"webhook": WebHookMsg,
|
||||
"feishu": FeiShuMsg,
|
||||
"dingding": DingDingMsg,
|
||||
"sms": SMSMsg,
|
||||
# "wx_account": WeChatAccountMsg,
|
||||
"tg": TgMsg,
|
||||
}
|
||||
return self._sender_type_class[sender_type]
|
||||
|
||||
@staticmethod
|
||||
def remove_old_task(task: dict):
|
||||
if not task.get("id"):
|
||||
return
|
||||
task_id = task["id"]
|
||||
try:
|
||||
from . import PushManager
|
||||
PushManager().remove_task_conf(public.to_dict_obj(
|
||||
{"task_id": task_id}
|
||||
))
|
||||
except:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def can_run_task_list():
|
||||
result = []
|
||||
result_template = {}
|
||||
for task in TaskConfig().config: # all task
|
||||
# ======== 移除旧任务 ==============
|
||||
if task.get("source") == "cert_endtime" and task.get("task_data", {}).get(
|
||||
"title") == "Certificate expiration":
|
||||
PushSystem.remove_old_task(task) # 移除旧的ssl通知
|
||||
# ======== 移除旧任务 End ==========
|
||||
|
||||
if not task["status"]:
|
||||
continue
|
||||
# 间隔检测时间未到跳过
|
||||
if "interval" in task["task_data"] and isinstance(task["task_data"]["interval"], int):
|
||||
if time.time() < task["last_check"] + task["task_data"]["interval"]:
|
||||
continue
|
||||
result.append(task)
|
||||
for template in TaskTemplateConfig().config: # task's template
|
||||
if template.get("id") == task["template_id"] and template.get("used"):
|
||||
result_template.update({task["id"]: template})
|
||||
break
|
||||
return result, result_template
|
||||
|
||||
def get_task_object(self, template_id, load_cls_data: dict) -> Optional[BaseTask]:
|
||||
if template_id in self.task_cls_cache:
|
||||
return self.task_cls_cache[template_id]()
|
||||
if "load_type" not in load_cls_data:
|
||||
return None
|
||||
if load_cls_data["load_type"] == "func":
|
||||
cls = load_task_cls_by_function(
|
||||
name=load_cls_data["name"],
|
||||
func_name=load_cls_data["func_name"],
|
||||
is_model=load_cls_data.get("is_model", False),
|
||||
model_index=load_cls_data.get("is_model", ''),
|
||||
args=load_cls_data.get("args", None),
|
||||
sub_name=load_cls_data.get("sub_name", None),
|
||||
)
|
||||
else:
|
||||
cls_path = load_cls_data["cls_path"]
|
||||
cls = load_task_cls_by_path(cls_path, load_cls_data["name"])
|
||||
|
||||
if not cls:
|
||||
return None
|
||||
self.task_cls_cache[template_id] = cls
|
||||
return cls()
|
||||
|
||||
def run(self):
|
||||
rsync_compatible()
|
||||
task_list, task_template = self.can_run_task_list()
|
||||
try:
|
||||
for t in task_list:
|
||||
template = task_template[t["id"]]
|
||||
print(PushRunner(t, template, self)())
|
||||
except Exception as e:
|
||||
import traceback
|
||||
public.print_log(f"run task error %s", e)
|
||||
public.print_log(traceback.format_exc())
|
||||
|
||||
global WAIT_TASK_LIST
|
||||
if WAIT_TASK_LIST: # 有任务启用子线程的,要等到这个线程结束,再结束主线程
|
||||
for i in WAIT_TASK_LIST:
|
||||
i.join()
|
||||
|
||||
def get_today_zero(self) -> datetime.datetime:
|
||||
if self._today_zero is None:
|
||||
t = datetime.datetime.today()
|
||||
t_zero = datetime.datetime.combine(t, datetime.time.min)
|
||||
self._today_zero = t_zero
|
||||
return self._today_zero
|
||||
|
||||
|
||||
class PushRunner:
|
||||
def __init__(self, task: dict, template: dict, push_system: PushSystem, custom_push_data: Optional[dict] = None):
|
||||
self._public_push_data: Optional[dict] = None
|
||||
self.result: dict = {
|
||||
"do_send": False,
|
||||
"stop_msg": "",
|
||||
"push_data": {},
|
||||
"check_res": False,
|
||||
"check_stop_on": "",
|
||||
"send_data": {},
|
||||
} # 记录结果
|
||||
self.change_fields = set() # 记录task变化值
|
||||
self.task_obj: Optional[BaseTask] = None
|
||||
self.task = task
|
||||
self.template = template
|
||||
self.push_system = push_system
|
||||
self._add_hook_msg: Optional[str] = None # 记录前置钩子处理后的追加信息
|
||||
self.custom_push_data = custom_push_data
|
||||
|
||||
self.tr_cfg = TaskRecordConfig(task["id"])
|
||||
self.is_number_rule_by_func = False # 记录这个任务是否使用自定义的次数检测, 如果是,就不需要做次数更新
|
||||
|
||||
def save_result(self):
|
||||
t = TaskConfig()
|
||||
tmp = t.get_by_id(self.task["id"])
|
||||
if tmp:
|
||||
for f in self.change_fields:
|
||||
tmp[f] = self.task[f]
|
||||
|
||||
if self.result["do_send"]:
|
||||
tmp["last_send"] = int(time.time())
|
||||
tmp["last_check"] = int(time.time())
|
||||
|
||||
t.save_config()
|
||||
|
||||
if self.result["push_data"]:
|
||||
result_data = self.result.copy()
|
||||
self.tr_cfg.config.append(
|
||||
{
|
||||
"id": self.tr_cfg.nwe_id(),
|
||||
"template_id": self.template["id"],
|
||||
"task_id": self.task["id"],
|
||||
"do_send": result_data.pop("do_send"),
|
||||
"send_data": result_data.pop("push_data"),
|
||||
"result": result_data,
|
||||
"create_time": int(time.time()),
|
||||
}
|
||||
)
|
||||
self.tr_cfg.save_config()
|
||||
|
||||
@property
|
||||
def public_push_data(self) -> dict:
|
||||
if self._public_push_data is None:
|
||||
self._public_push_data = {
|
||||
'ip': get_server_ip(),
|
||||
'local_ip': get_network_ip(),
|
||||
'server_name': get_config_value('title')
|
||||
}
|
||||
data = self._public_push_data.copy()
|
||||
data['time'] = format_date()
|
||||
data['timestamp'] = int(time.time())
|
||||
return data
|
||||
|
||||
def __call__(self):
|
||||
self.run()
|
||||
self.save_result()
|
||||
if self.task_obj:
|
||||
self.task_obj.task_run_end_hook(self.result)
|
||||
return self.result_to_return()
|
||||
|
||||
def result_to_return(self) -> dict:
|
||||
return self.result
|
||||
|
||||
def _append_msg_list_for_hook(self, push_data: dict) -> dict:
|
||||
for key in ["pre_hook", "after_hook"]:
|
||||
if not self.task.get("task_data", {}).get(key):
|
||||
continue
|
||||
for k, v in self.task["task_data"][key].items():
|
||||
try:
|
||||
val = ", ".join(v) if isinstance(v, list) else str(v)
|
||||
act = k.capitalize() if k and isinstance(k , str) else k
|
||||
push_data['msg_list'].append(f">{key.capitalize()}: {act} - {val} ")
|
||||
except Exception as e:
|
||||
public.print_log(f"Append {key} hook msg error: {e}")
|
||||
continue
|
||||
return push_data
|
||||
|
||||
def run(self):
|
||||
self.task_obj = self.push_system.get_task_object(self.template["id"], self.template["load_cls"])
|
||||
if not self.task_obj:
|
||||
self.result["stop_msg"] = "The task class failed to load"
|
||||
return
|
||||
if self.custom_push_data is None:
|
||||
push_data = None
|
||||
try:
|
||||
push_data = self.task_obj.get_push_data(self.task["id"], self.task["task_data"])
|
||||
except Exception:
|
||||
import traceback
|
||||
public.print_log(f"get_push_data error: {traceback.format_exc()}")
|
||||
if not push_data:
|
||||
return
|
||||
else:
|
||||
push_data = self.custom_push_data
|
||||
|
||||
self.result["push_data"] = push_data
|
||||
# 执行全局前置钩子
|
||||
if self.task.get("pre_hook"):
|
||||
if not self.run_hook(self.task["pre_hook"], "pre_hook"):
|
||||
self.result["stop_msg"] = "Task global pre hook stopped execution"
|
||||
return
|
||||
# 执行任务自身前置钩子
|
||||
if self.task.get("task_data", {}).get("pre_hook"):
|
||||
if not self.run_hook(self.task["task_data"]["pre_hook"], "pre_hook"):
|
||||
self.result["stop_msg"] = "Task pre hook stopped execution"
|
||||
return
|
||||
|
||||
# 执行时间规则判断
|
||||
if not self.run_time_rule(self.task["time_rule"]):
|
||||
return
|
||||
|
||||
# 执行频率规则判断
|
||||
if not self.number_rule(self.task["number_rule"]):
|
||||
return
|
||||
|
||||
# 注入任务自身更多钩子消息, 全局钩子静默处理
|
||||
push_data = self._append_msg_list_for_hook(push_data)
|
||||
|
||||
# 执行发送信息
|
||||
self.send_message(push_data)
|
||||
|
||||
self.change_fields.add("number_data")
|
||||
if "day_num" not in self.task["number_data"]:
|
||||
self.task["number_data"]["day_num"] = 0
|
||||
if "total" not in self.task["number_data"]:
|
||||
self.task["number_data"]["total"] = 0
|
||||
self.task["number_data"]["day_num"] += 1
|
||||
self.task["number_data"]["total"] += 1
|
||||
self.task["number_data"]["time"] = int(time.time())
|
||||
|
||||
# 执行任务自身后置钩子
|
||||
if self.task.get("task_data", {}).get("after_hook"):
|
||||
self.run_hook(self.task["task_data"]["after_hook"], "after_hook")
|
||||
# 执行全局后置钩子
|
||||
if self.task.get("after_hook"):
|
||||
self.run_hook(self.task["after_hook"], "after_hook")
|
||||
|
||||
# hook函数, 额外扩展
|
||||
def run_hook(self, hook_data: Dict[str, List[Any]], hook_name: str) -> bool:
|
||||
"""
|
||||
执行hook操作,并返回是否继续执行, 并将hook的执行结果记录
|
||||
@param hook_name: 钩子的名称,如:after_hook, pre_hook
|
||||
@param hook_data: 执行的内容
|
||||
@return: bool
|
||||
"""
|
||||
if not isinstance(hook_data, dict) or not isinstance(hook_name, str):
|
||||
return False
|
||||
|
||||
if hook_name == "pre_hook":
|
||||
return True
|
||||
|
||||
elif hook_name == "after_hook":
|
||||
from script.restart_services import ServicesHelper
|
||||
# restart action
|
||||
if hook_data.get("restart"):
|
||||
for s in hook_data.get("restart", []):
|
||||
if not s or not isinstance(s, str):
|
||||
continue
|
||||
service_obj = ServicesHelper(s.strip())
|
||||
if not service_obj.is_install:
|
||||
continue
|
||||
service_obj.script("restart", "Alarm Triggered")
|
||||
return True
|
||||
|
||||
# module action
|
||||
elif hook_data.get("module"):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def run_time_rule(self, time_rule: dict) -> bool:
|
||||
if "send_interval" in time_rule and time_rule["send_interval"] > 0:
|
||||
if self.task["last_send"] + time_rule["send_interval"] > time.time():
|
||||
self.result['stop_msg'] = 'If the minimum send time is less, no sending will be made'
|
||||
self.result['check_stop_on'] = "time_rule_send_interval"
|
||||
return False
|
||||
|
||||
time_range = time_rule.get("time_range", None)
|
||||
if time_range and isinstance(time_range, list) and len(time_range) == 2:
|
||||
t_zero = self.push_system.get_today_zero()
|
||||
start_time = t_zero + datetime.timedelta(seconds=time_range[0]) # NOQA
|
||||
end_time = t_zero + datetime.timedelta(seconds=time_range[1]) # NOQA
|
||||
if not start_time < datetime.datetime.now() < end_time:
|
||||
self.result['stop_msg'] = 'It is not within the time frame within which the alarm can be sent'
|
||||
self.result['check_stop_on'] = "time_rule_time_range"
|
||||
return False
|
||||
return True
|
||||
|
||||
def number_rule(self, number_rule: dict) -> bool:
|
||||
number_data = self.task.get("number_data", {})
|
||||
# 判断通过 自定义函数的方式确认是否达到发送次数
|
||||
if "get_by_func" in number_rule and isinstance(number_rule["get_by_func"], str):
|
||||
f = getattr(self.task_obj, number_rule["get_by_func"], None)
|
||||
if f is not None and callable(f):
|
||||
res = f(self.task["id"], self.task["task_data"], number_data, self.result["push_data"])
|
||||
if isinstance(res, str):
|
||||
self.result['stop_msg'] = res
|
||||
self.result['check_stop_on'] = "number_rule_get_by_func"
|
||||
return False
|
||||
|
||||
# 只要是走了使用函数检查的,不再处理默认情况 change_fields 中不添加 number_data
|
||||
return True
|
||||
|
||||
if "day_num" in number_rule and isinstance(number_rule["day_num"], int) and number_rule["day_num"] > 0:
|
||||
record_time = number_data.get("time", 0)
|
||||
if record_time < self.push_system.get_today_zero().timestamp(): # 昨日触发
|
||||
self.task["number_data"]["day_num"] = record_num = 0
|
||||
self.task["number_data"]["time"] = time.time()
|
||||
self.change_fields.add("number_data")
|
||||
else:
|
||||
record_num = self.task["number_data"].get("day_num")
|
||||
if record_num >= number_rule["day_num"]:
|
||||
self.result['stop_msg'] = "Exceeding the daily limit:{}".format(number_rule["day_num"])
|
||||
self.result['check_stop_on'] = "number_rule_day_num"
|
||||
return False
|
||||
|
||||
if "total" in number_rule and isinstance(number_rule["total"], int) and number_rule["total"] > 0:
|
||||
record_total = number_data.get("total", 0)
|
||||
if record_total >= number_rule["total"]:
|
||||
self.result['stop_msg'] = "The maximum number of times the limit is exceeded:{}".format(
|
||||
number_rule["total"])
|
||||
self.result['check_stop_on'] = "number_rule_total"
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def send_message(self, push_data: dict):
|
||||
self.result["do_send"] = True
|
||||
self.result["push_data"] = push_data
|
||||
for sender_id in self.task["sender"]:
|
||||
conf = self.push_system.sd_cfg.get_by_id(sender_id)
|
||||
if conf is None:
|
||||
continue
|
||||
if not conf["used"]:
|
||||
self.result["send_data"][sender_id] = "The alarm channel {} is closed, skip sending".format(
|
||||
conf["data"].get("title"))
|
||||
continue
|
||||
sd_cls = self.push_system.sender_cls(conf["sender_type"])
|
||||
res = None
|
||||
if conf["sender_type"] == "weixin":
|
||||
res = sd_cls(conf).send_msg(
|
||||
self.task_obj.to_weixin_msg(push_data, self.public_push_data),
|
||||
self.task_obj.title
|
||||
)
|
||||
|
||||
elif conf["sender_type"] == "mail":
|
||||
res = sd_cls(conf).send_msg(
|
||||
self.task_obj.to_mail_msg(push_data, self.public_push_data),
|
||||
self.task_obj.title
|
||||
)
|
||||
|
||||
elif conf["sender_type"] == "webhook":
|
||||
res = sd_cls(conf).send_msg(
|
||||
self.task_obj.to_web_hook_msg(push_data, self.public_push_data),
|
||||
self.task_obj.title,
|
||||
)
|
||||
|
||||
elif conf["sender_type"] == "feishu":
|
||||
res = sd_cls(conf).send_msg(
|
||||
self.task_obj.to_feishu_msg(push_data, self.public_push_data),
|
||||
self.task_obj.title
|
||||
)
|
||||
elif conf["sender_type"] == "dingding":
|
||||
res = sd_cls(conf).send_msg(
|
||||
self.task_obj.to_dingding_msg(push_data, self.public_push_data),
|
||||
self.task_obj.title
|
||||
)
|
||||
elif conf["sender_type"] == "sms":
|
||||
sm_type, sm_args = self.task_obj.to_sms_msg(push_data, self.public_push_data)
|
||||
if not sm_type or not sm_args:
|
||||
continue
|
||||
sm_args = sms_msg_normalize(sm_args)
|
||||
res = sd_cls(conf).send_msg(sm_type, sm_args)
|
||||
|
||||
elif conf["sender_type"] == "tg":
|
||||
# public.print_log("tg -- 发送数据 {}".format(self.task_obj.to_tg_msg(push_data, self.public_push_data)))
|
||||
from mod.base.msg import TgMsg
|
||||
# Home CPU alarms<br>
|
||||
# >Server:xxx<br>
|
||||
# >IPAddress: xxx.xxx.xxx.xxx(Internet) xxx.xxx.xxx.xxx(Internal)<br>
|
||||
# >SendingTime: 2024-00-00 00:00:00<br>
|
||||
# >Notification type: High CPU usage alarm<br>
|
||||
# >Content of alarm: The average CPU usage of the machine in the last 5 minutes is 3.24%, which is higher than the alarm value 1%.
|
||||
|
||||
try:
|
||||
res = sd_cls(conf).send_msg(
|
||||
# res = TgMsg().send_msg(
|
||||
self.task_obj.to_tg_msg(push_data, self.public_push_data),
|
||||
self.task_obj.title
|
||||
)
|
||||
except:
|
||||
public.print_log(public.get_error_info())
|
||||
else:
|
||||
continue
|
||||
if isinstance(res, str) and res.find("Traceback") != -1:
|
||||
self.result["send_data"][sender_id] = ("An error occurred during the execution of the message "
|
||||
"transmission, and the transmission was not successful")
|
||||
if isinstance(res, str):
|
||||
self.result["send_data"][sender_id] = res
|
||||
else:
|
||||
self.result["send_data"][sender_id] = 1
|
||||
|
||||
|
||||
def push_by_task_keyword(source: str, keyword: str, push_data: Optional[dict] = None) -> Union[str, dict]:
|
||||
"""
|
||||
通过关键字查询告警任务,并发送信息
|
||||
@param push_data:
|
||||
@param source:
|
||||
@type keyword:
|
||||
@return:
|
||||
"""
|
||||
push_system = PushSystem()
|
||||
target_task = {}
|
||||
for i in TaskConfig().config:
|
||||
if i["source"] == source and i["keyword"] == keyword:
|
||||
target_task = i
|
||||
break
|
||||
if not target_task:
|
||||
return "The task was not found"
|
||||
|
||||
target_template = TaskTemplateConfig().get_by_id(target_task["template_id"]) # NOQA
|
||||
if not target_template["used"]:
|
||||
return "This task type has been banned"
|
||||
if not target_task['status']:
|
||||
return "The task has been stopped"
|
||||
|
||||
return PushRunner(target_task, target_template, push_system, push_data)()
|
||||
|
||||
|
||||
def push_by_task_id(task_id: str, push_data: Optional[dict] = None):
|
||||
"""
|
||||
通过任务id触发告警 并 发送信息
|
||||
@param push_data:
|
||||
@param task_id:
|
||||
@return:
|
||||
"""
|
||||
push_system = PushSystem()
|
||||
target_task = TaskConfig().get_by_id(task_id)
|
||||
if not target_task:
|
||||
return "The task was not found"
|
||||
|
||||
target_template = TaskTemplateConfig().get_by_id(target_task["template_id"])
|
||||
if not target_template["used"]:
|
||||
return "This task type has been banned"
|
||||
if not target_task['status']:
|
||||
return "The task has been stopped"
|
||||
|
||||
return PushRunner(target_task, target_template, push_system, push_data)()
|
||||
|
||||
|
||||
def get_push_public_data():
|
||||
data = {
|
||||
'ip': get_server_ip(),
|
||||
'local_ip': get_network_ip(),
|
||||
'server_name': get_config_value('title'),
|
||||
'time': format_date(),
|
||||
'timestamp': int(time.time())}
|
||||
|
||||
return data
|
||||
418
mod/base/push_mod/system_push.py
Normal file
418
mod/base/push_mod/system_push.py
Normal file
@@ -0,0 +1,418 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from importlib import import_module
|
||||
from typing import Tuple, Union, Optional, List
|
||||
|
||||
import psutil
|
||||
|
||||
from .base_task import BaseTask
|
||||
from .mods import PUSH_DATA_PATH
|
||||
from .send_tool import WxAccountMsg
|
||||
from .system import WAIT_TASK_LIST
|
||||
from .util import read_file, write_file, get_config_value, generate_fields
|
||||
|
||||
try:
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
from panel_msg.collector import SitePushMsgCollect, SystemPushMsgCollect
|
||||
except ImportError:
|
||||
SitePushMsgCollect = None
|
||||
SystemPushMsgCollect = None
|
||||
|
||||
|
||||
def _get_panel_name() -> str:
|
||||
data = get_config_value("title") # 若获得别名,则使用别名
|
||||
if data == "":
|
||||
data = "YakPanel"
|
||||
return data
|
||||
|
||||
|
||||
class PanelSysDiskTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "system_disk"
|
||||
self.template_name = "Home disk alerts"
|
||||
self.title = "Home disk alerts"
|
||||
|
||||
self.wx_msg = ""
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
return "Home disk alerts -- Mount directory[{}]".format(task_data["project"])
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if task_data["project"] not in [i[0] for i in self._get_disk_name()]:
|
||||
return "The specified disk does not exist"
|
||||
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] in (1, 2)):
|
||||
return "The type parameter is incorrect"
|
||||
if not (isinstance(task_data['count'], int) and task_data['count'] >= 1):
|
||||
return "The threshold parameter is incorrect"
|
||||
if task_data['cycle'] == 2 and task_data['count'] >= 100:
|
||||
return "The threshold parameter is incorrect, and the set check range is incorrect"
|
||||
task_data['interval'] = 600
|
||||
return task_data
|
||||
|
||||
@staticmethod
|
||||
def _get_disk_name() -> list:
|
||||
"""获取硬盘挂载点"""
|
||||
if "/www/server/panel" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel")
|
||||
|
||||
system_modul = import_module('.system', package="class")
|
||||
system = getattr(system_modul, "system")
|
||||
|
||||
disk_info = system.GetDiskInfo2(None, human=False)
|
||||
|
||||
return [(d.get("path"), d.get("size")[0]) for d in disk_info]
|
||||
|
||||
@staticmethod
|
||||
def _get_disk_info() -> list:
|
||||
"""获取硬盘挂载点"""
|
||||
if "/www/server/panel" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel")
|
||||
|
||||
system_modul = import_module('.system', package="class")
|
||||
system = getattr(system_modul, "system")
|
||||
|
||||
disk_info = system.GetDiskInfo2(None, human=False)
|
||||
|
||||
return disk_info
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
disk_info = self._get_disk_info()
|
||||
unsafe_disk_list = []
|
||||
|
||||
for d in disk_info:
|
||||
if task_data["project"] != d["path"]:
|
||||
continue
|
||||
free = int(d["size"][2]) / 1048576
|
||||
proportion = int(d["size"][3] if d["size"][3][-1] != "%" else d["size"][3][:-1])
|
||||
|
||||
if task_data["cycle"] == 1 and free < task_data["count"]:
|
||||
unsafe_disk_list.append(
|
||||
"The remaining capacity of the disk mounted on {} is {}G, which is less than the alarm value {}G.".format(
|
||||
d["path"], round(free, 2), task_data["count"])
|
||||
)
|
||||
self.wx_msg = "The remaining capacity is less than {}G".format(task_data["count"])
|
||||
|
||||
elif task_data["cycle"] == 2 and proportion > task_data["count"]:
|
||||
unsafe_disk_list.append(
|
||||
"The used capacity of the disk mounted on {} is {}%, which is greater than the alarm value {}%.".format(
|
||||
d["path"], round(proportion, 2), task_data["count"])
|
||||
)
|
||||
self.wx_msg = "Occupancy greater than {}%".format(task_data["count"])
|
||||
|
||||
if len(unsafe_disk_list) == 0:
|
||||
return None
|
||||
|
||||
return {
|
||||
"msg_list": [
|
||||
">Notification type: Disk Balance Alert",
|
||||
">Alarm content:\n" + "\n".join(unsafe_disk_list)
|
||||
]
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
for (path, total_size) in self._get_disk_name():
|
||||
template["field"][0]["items"].append({
|
||||
"title": "[{}] disk".format(path),
|
||||
"value": path,
|
||||
"count_default": round((int(total_size) * 0.2) / 1024 / 1024, 1)
|
||||
})
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return 'machine_exception|Disk Balance Alert', {
|
||||
'name': _get_panel_name(),
|
||||
'type': "Insufficient disk space",
|
||||
}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Home disk alerts"
|
||||
if len(self.wx_msg) > 20:
|
||||
self.wx_msg = self.wx_msg[:17] + "..."
|
||||
msg.msg = self.wx_msg
|
||||
return msg
|
||||
|
||||
|
||||
class PanelSysCPUTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "system_cpu"
|
||||
self.template_name = "Home CPU alarms"
|
||||
self.title = "Home CPU alarms"
|
||||
|
||||
self.cpu_count = 0
|
||||
|
||||
self._tip_file = "{}/system_cpu.tip".format(PUSH_DATA_PATH)
|
||||
self._tip_data: Optional[List[Tuple[float, float]]] = None
|
||||
|
||||
@property
|
||||
def cache_list(self) -> List[Tuple[float, float]]:
|
||||
if self._tip_data is not None:
|
||||
return self._tip_data
|
||||
try:
|
||||
self._tip_data = json.loads(read_file(self._tip_file))
|
||||
except:
|
||||
self._tip_data = []
|
||||
return self._tip_data
|
||||
|
||||
def save_cache_list(self):
|
||||
write_file(self._tip_file, json.dumps(self.cache_list))
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1):
|
||||
return "The time parameter is incorrect"
|
||||
if not (isinstance(task_data['count'], int) and task_data['count'] >= 1):
|
||||
return "Threshold parameter error, at least 1%"
|
||||
task_data['interval'] = 60
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "system_cpu"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
expiration = datetime.now() - timedelta(seconds=task_data["cycle"] * 60 + 10)
|
||||
for i in range(len(self.cache_list) - 1, -1, -1):
|
||||
data_time, _ = self.cache_list[i]
|
||||
if datetime.fromtimestamp(data_time) < expiration:
|
||||
del self.cache_list[i]
|
||||
|
||||
# 记录下次的
|
||||
def thread_get_cpu_data():
|
||||
self.cache_list.append((time.time(), psutil.cpu_percent(10)))
|
||||
self.save_cache_list()
|
||||
|
||||
thread_active = threading.Thread(target=thread_get_cpu_data, args=())
|
||||
thread_active.start()
|
||||
WAIT_TASK_LIST.append(thread_active)
|
||||
|
||||
if len(self.cache_list) < task_data["cycle"]: # 小于指定次数不推送
|
||||
return None
|
||||
|
||||
if len(self.cache_list) > 0:
|
||||
avg_data = sum(i[1] for i in self.cache_list) / len(self.cache_list)
|
||||
else:
|
||||
avg_data = 0
|
||||
|
||||
if avg_data < task_data["count"]:
|
||||
return None
|
||||
else:
|
||||
self.cache_list.clear()
|
||||
self.cpu_count = round(avg_data, 2)
|
||||
s_list = [
|
||||
">Notification type: High CPU usage alarm",
|
||||
">Content of alarm: The average CPU usage of the machine in the last {} minutes is {}%, which is higher than the alarm value {}%.".format(
|
||||
task_data["cycle"], round(avg_data, 2), task_data["count"]),
|
||||
]
|
||||
|
||||
return {
|
||||
"msg_list": s_list,
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
template = generate_fields(template, "restart")
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return 'machine_exception|High CPU usage alarm', {
|
||||
'name': _get_panel_name(),
|
||||
'type': "High CPU usage",
|
||||
}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Home CPU alarms"
|
||||
msg.msg = "The CPU usage of the host is exceeded:{}%".format(self.cpu_count)
|
||||
msg.next_msg = "Please log in to the panel to view the host status"
|
||||
return msg
|
||||
|
||||
|
||||
class PanelSysLoadTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "system_load"
|
||||
self.template_name = "Home load alerts"
|
||||
self.title = "Home load alerts"
|
||||
|
||||
self.avg_data = 0
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1):
|
||||
return "The time parameter is incorrect"
|
||||
if not (isinstance(task_data['count'], int) and task_data['count'] >= 1):
|
||||
return "Threshold parameter error, at least 1%"
|
||||
task_data['interval'] = 60 * task_data['cycle']
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "system_load"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
now_load = os.getloadavg()
|
||||
cpu_count = psutil.cpu_count()
|
||||
now_load = [i / (cpu_count * 2) * 100 for i in now_load]
|
||||
need_push = False
|
||||
avg_data = 0
|
||||
if task_data["cycle"] == 15 and task_data["count"] < now_load[2]:
|
||||
avg_data = now_load[2]
|
||||
need_push = True
|
||||
elif task_data["cycle"] == 5 and task_data["count"] < now_load[1]:
|
||||
avg_data = now_load[1]
|
||||
need_push = True
|
||||
elif task_data["cycle"] == 1 and task_data["count"] < now_load[0]:
|
||||
avg_data = now_load[0]
|
||||
need_push = True
|
||||
|
||||
if not need_push:
|
||||
return None
|
||||
|
||||
self.avg_data = avg_data
|
||||
|
||||
return {
|
||||
"msg_list": [
|
||||
">Notification type: Alarm when the load exceeds the standard",
|
||||
">Content of alarm: The average load factor of the machine in the last {} minutes is {}%, which is higher than the alarm value of {}%.".format(
|
||||
task_data["cycle"], round(avg_data, 2), task_data["count"]),
|
||||
]
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
template = generate_fields(template, "restart")
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return 'machine_exception|Alarm when the load exceeds the standard', {
|
||||
'name': _get_panel_name(),
|
||||
'type': "The average load is too high",
|
||||
}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Home load alerts"
|
||||
msg.msg = "The host load exceeds:{}%".format(round(self.avg_data, 2))
|
||||
msg.next_msg = "Please log in to the panel to view the host status"
|
||||
return msg
|
||||
|
||||
|
||||
class PanelSysMEMTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "system_mem"
|
||||
self.template_name = "Home memory alarms"
|
||||
self.title = "Home memory alarms"
|
||||
|
||||
self.wx_data = 0
|
||||
|
||||
self._tip_file = "{}/system_mem.tip".format(PUSH_DATA_PATH)
|
||||
self._tip_data: Optional[List[Tuple[float, float]]] = None
|
||||
|
||||
@property
|
||||
def cache_list(self) -> List[Tuple[float, float]]:
|
||||
if self._tip_data is not None:
|
||||
return self._tip_data
|
||||
try:
|
||||
self._tip_data = json.loads(read_file(self._tip_file))
|
||||
except:
|
||||
self._tip_data = []
|
||||
return self._tip_data
|
||||
|
||||
def save_cache_list(self):
|
||||
write_file(self._tip_file, json.dumps(self.cache_list))
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1):
|
||||
return "The number parameter is incorrect"
|
||||
if not (isinstance(task_data['count'], int) and task_data['count'] >= 1):
|
||||
return "Threshold parameter error, at least 1%"
|
||||
task_data['interval'] = task_data['cycle'] * 60
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return "system_mem"
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
mem = psutil.virtual_memory()
|
||||
real_used: float = (mem.total - mem.free - mem.buffers - mem.cached) / mem.total
|
||||
stime = datetime.now()
|
||||
expiration = stime - timedelta(seconds=task_data["cycle"] * 60 + 10)
|
||||
|
||||
self.cache_list.append((stime.timestamp(), real_used))
|
||||
|
||||
for i in range(len(self.cache_list) - 1, -1, -1):
|
||||
data_time, _ = self.cache_list[i]
|
||||
if datetime.fromtimestamp(data_time) < expiration:
|
||||
del self.cache_list[i]
|
||||
|
||||
avg_data = sum(i[1] for i in self.cache_list) / len(self.cache_list)
|
||||
|
||||
if avg_data * 100 < task_data["count"]:
|
||||
self.save_cache_list()
|
||||
return None
|
||||
else:
|
||||
self.cache_list.clear()
|
||||
self.save_cache_list()
|
||||
self.wx_data = round(avg_data * 100, 2)
|
||||
return {
|
||||
'msg_list': [
|
||||
">Notification type: High memory usage alarm",
|
||||
">Content of alarm: The average memory usage of the machine in the last {} minutes is {}%, which is higher than the alarm value {}%.".format(
|
||||
task_data["cycle"], round(avg_data * 100, 2), task_data["count"]),
|
||||
]
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
template = generate_fields(template, "restart")
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return 'machine_exception|High memory usage alarm', {
|
||||
'name': _get_panel_name(),
|
||||
'type': "High memory usage",
|
||||
}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Home memory alarms"
|
||||
msg.msg = "Host memory usage exceeded: {}%".format(self.wx_data)
|
||||
msg.next_msg = "Please log in to the panel to view the host status"
|
||||
return msg
|
||||
|
||||
|
||||
class ViewMsgFormat(object):
|
||||
_FORMAT = {
|
||||
"20": (
|
||||
lambda x: "<span>Triggered by {} disk mounted on {}</span>".format(
|
||||
x.get("project"),
|
||||
"The margin is less than %.1f G" % round(x.get("count"), 1) if x.get(
|
||||
"cycle") == 1 else "ake up more than %d%%" % x.get("count"),
|
||||
)
|
||||
),
|
||||
"21": (
|
||||
lambda x: "<span>Triggers when the average CPU usage exceeds {}% in {} minutes</span>".format(
|
||||
x.get("count"), x.get("cycle")
|
||||
)
|
||||
),
|
||||
"22": (
|
||||
lambda x: "<span>Triggered by an average load exceeding {}% in {} minutes</span>".format(
|
||||
x.get("count"), x.get("cycle")
|
||||
)
|
||||
),
|
||||
"23": (
|
||||
lambda x: "<span>Triggered if the memory usage exceeds {}% within {} minutes</span>".format(
|
||||
x.get("count"), x.get("cycle")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def get_msg(self, task: dict) -> Optional[str]:
|
||||
if task["template_id"] in self._FORMAT:
|
||||
return self._FORMAT[task["template_id"]](task["task_data"])
|
||||
return None
|
||||
294
mod/base/push_mod/system_push_template.json
Normal file
294
mod/base/push_mod/system_push_template.json
Normal file
@@ -0,0 +1,294 @@
|
||||
[
|
||||
{
|
||||
"id": "20",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "system_disk",
|
||||
"title": "Home disk alerts",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.system_push",
|
||||
"name": "PanelSysDiskTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "disk information",
|
||||
"type": "select",
|
||||
"items": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "detection type",
|
||||
"type": "radio",
|
||||
"suffix": "",
|
||||
"default": 2,
|
||||
"items": [
|
||||
{
|
||||
"title": "Remaining capacity",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"title": "percentage occupied",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "occupancy rate exceeds",
|
||||
"type": "number",
|
||||
"unit": "%",
|
||||
"suffix": "will trigger an alarm",
|
||||
"default": 80,
|
||||
"err_msg_prefix": "disk threshold"
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"cycle"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"project": "/",
|
||||
"cycle": 2,
|
||||
"count": 80
|
||||
},
|
||||
"send_type_list": [
|
||||
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"sms",
|
||||
"tg"
|
||||
],
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"id": "21",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "system_cpu",
|
||||
"title": "Home CPU alarms",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.system_push",
|
||||
"name": "PanelSysCPUTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "every time",
|
||||
"type": "select",
|
||||
"unit": "minute(s)",
|
||||
"suffix": "average ",
|
||||
"width": "70px",
|
||||
"disabled": true,
|
||||
"default": 5,
|
||||
"items": [
|
||||
{
|
||||
"title": "1",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"title": "5",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"title": "15",
|
||||
"value": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "CPU usage exceeded",
|
||||
"type": "number",
|
||||
"unit": "%",
|
||||
"suffix": "will trigger an alarm",
|
||||
"default": 80,
|
||||
"err_msg_prefix": "CPU"
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cycle": 5,
|
||||
"count": 80
|
||||
},
|
||||
"send_type_list": [
|
||||
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"sms",
|
||||
"tg"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "22",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "system_load",
|
||||
"title": "Home load alerts",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.system_push",
|
||||
"name": "PanelSysLoadTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "every time",
|
||||
"type": "select",
|
||||
"unit": "minute(s)",
|
||||
"suffix": "average ",
|
||||
"default": 5,
|
||||
"width": "70px",
|
||||
"disabled": true,
|
||||
"items": [
|
||||
{
|
||||
"title": "1",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"title": "5",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"title": "15",
|
||||
"value": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": " load over",
|
||||
"type": "number",
|
||||
"unit": "%",
|
||||
"suffix": "will trigger an alarm",
|
||||
"default": 80,
|
||||
"err_msg_prefix": "Load"
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cycle": 5,
|
||||
"count": 80
|
||||
},
|
||||
"send_type_list": [
|
||||
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg",
|
||||
"sms"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"id": "23",
|
||||
"ver": "1",
|
||||
"used": true,
|
||||
"source": "system_mem",
|
||||
"title": "Home memory alarms",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.system_push",
|
||||
"name": "PanelSysMEMTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "cycle",
|
||||
"name": "every time",
|
||||
"type": "select",
|
||||
"unit": "minute(s)",
|
||||
"suffix": "average ",
|
||||
"width": "70px",
|
||||
"disabled": true,
|
||||
"default": 5,
|
||||
"items": [
|
||||
{
|
||||
"title": "1",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"title": "5",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"title": "15",
|
||||
"value": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "Memory usage is more than",
|
||||
"type": "number",
|
||||
"unit": "%",
|
||||
"suffix": "will trigger an alarm",
|
||||
"default": 80,
|
||||
"err_msg_prefix": "Memory"
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"cycle"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
]
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cycle": 5,
|
||||
"count": 80
|
||||
},
|
||||
"send_type_list": [
|
||||
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg",
|
||||
"sms"
|
||||
],
|
||||
"unique": true
|
||||
}
|
||||
]
|
||||
506
mod/base/push_mod/task_manager_push.py
Normal file
506
mod/base/push_mod/task_manager_push.py
Normal file
@@ -0,0 +1,506 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from importlib import import_module
|
||||
from typing import Tuple, Union, Optional, List
|
||||
|
||||
import psutil
|
||||
|
||||
from .send_tool import WxAccountMsg
|
||||
from .base_task import BaseTask
|
||||
from .mods import PUSH_DATA_PATH, TaskTemplateConfig
|
||||
from .util import read_file, write_file, get_config_value, GET_CLASS
|
||||
|
||||
|
||||
class _ProcessInfo:
|
||||
|
||||
def __init__(self):
|
||||
self.data = None
|
||||
self.last_time = 0
|
||||
|
||||
def __call__(self) -> list:
|
||||
if self.data is not None and time.time() - self.last_time < 60:
|
||||
return self.data
|
||||
|
||||
try:
|
||||
import PluginLoader
|
||||
get_obj = GET_CLASS()
|
||||
get_obj.sort = "status"
|
||||
p_info = PluginLoader.plugin_run("task_manager", "get_process_list", get_obj)
|
||||
except:
|
||||
return []
|
||||
|
||||
if isinstance(p_info, dict) and "process_list" in p_info and isinstance(
|
||||
p_info["process_list"], list):
|
||||
self._process_info = p_info["process_list"]
|
||||
self.last_time = time.time()
|
||||
return self._process_info
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
get_process_info = _ProcessInfo()
|
||||
|
||||
|
||||
def have_task_manager_plugin():
|
||||
"""
|
||||
通过文件判断是否有进程管理器
|
||||
"""
|
||||
return os.path.exists("/www/server/panel/plugin/task_manager/task_manager_push.py")
|
||||
|
||||
|
||||
def load_task_manager_template():
|
||||
if TaskTemplateConfig().get_by_id("60"):
|
||||
return None
|
||||
|
||||
from .mods import load_task_template_by_config
|
||||
load_task_template_by_config([
|
||||
{
|
||||
"id": "60",
|
||||
"ver": "1",
|
||||
"used": True,
|
||||
"source": "task_manager_cpu",
|
||||
"title": "Task Manager CPU usage alarm",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.task_manager_push",
|
||||
"name": "TaskManagerCPUTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "project name",
|
||||
"type": "select",
|
||||
"items": {
|
||||
"url": "plugin?action=a&name=task_manager&s=get_process_list_to_push"
|
||||
}
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "Occupancy exceeded",
|
||||
"type": "number",
|
||||
"unit": "%",
|
||||
"suffix": "trigger an alarm",
|
||||
"default": 80,
|
||||
"err_msg_prefix": "CPU occupancy"
|
||||
},
|
||||
{
|
||||
"attr": "interval",
|
||||
"name": "Interval",
|
||||
"type": "number",
|
||||
"unit": "second(s)",
|
||||
"suffix": "monitor the detection conditions again",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
],
|
||||
[
|
||||
"interval"
|
||||
]
|
||||
],
|
||||
},
|
||||
"default": {
|
||||
"project": '',
|
||||
"count": 80,
|
||||
"interval": 600
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg",
|
||||
],
|
||||
"unique": False
|
||||
},
|
||||
{
|
||||
"id": "61",
|
||||
"ver": "1",
|
||||
"used": True,
|
||||
"source": "task_manager_mem",
|
||||
"title": "Task Manager memory usage alarm",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.task_manager_push",
|
||||
"name": "TaskManagerMEMTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "project name",
|
||||
"type": "select",
|
||||
"items": {
|
||||
"url": "plugin?action=a&name=task_manager&s=get_process_list_to_push"
|
||||
}
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "The occupancy is more than",
|
||||
"type": "number",
|
||||
"unit": "MB",
|
||||
"suffix": "trigger an alarm",
|
||||
"default": None,
|
||||
"err_msg_prefix": "Occupancy"
|
||||
},
|
||||
{
|
||||
"attr": "interval",
|
||||
"name": "Interval",
|
||||
"type": "number",
|
||||
"unit": "second(s)",
|
||||
"suffix": "monitor the detection conditions again",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
],
|
||||
[
|
||||
"interval"
|
||||
]
|
||||
],
|
||||
},
|
||||
"default": {
|
||||
"project": '',
|
||||
"count": 80,
|
||||
"interval": 600
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg",
|
||||
],
|
||||
"unique": False
|
||||
},
|
||||
{
|
||||
"id": "62",
|
||||
"ver": "1",
|
||||
"used": True,
|
||||
"source": "task_manager_process",
|
||||
"title": "Task Manager Process Overhead Alert",
|
||||
"load_cls": {
|
||||
"load_type": "path",
|
||||
"cls_path": "mod.base.push_mod.task_manager_push",
|
||||
"name": "TaskManagerProcessTask"
|
||||
},
|
||||
"template": {
|
||||
"field": [
|
||||
{
|
||||
"attr": "project",
|
||||
"name": "project name",
|
||||
"type": "select",
|
||||
"items": {
|
||||
"url": "plugin?action=a&name=task_manager&s=get_process_list_to_push"
|
||||
}
|
||||
},
|
||||
{
|
||||
"attr": "count",
|
||||
"name": "Number of processes exceeds",
|
||||
"type": "number",
|
||||
"unit": "of them",
|
||||
"suffix": "trigger an alarm",
|
||||
"default": 20,
|
||||
"err_msg_prefix": "NumberOfProcesses"
|
||||
},
|
||||
{
|
||||
"attr": "interval",
|
||||
"name": "Interval",
|
||||
"type": "number",
|
||||
"unit": "second(s)",
|
||||
"suffix": "monitor the detection conditions again",
|
||||
"default": 600
|
||||
}
|
||||
],
|
||||
"sorted": [
|
||||
[
|
||||
"project"
|
||||
],
|
||||
[
|
||||
"count"
|
||||
],
|
||||
[
|
||||
"interval"
|
||||
]
|
||||
],
|
||||
},
|
||||
"default": {
|
||||
"project": '',
|
||||
"count": 80,
|
||||
"interval": 600
|
||||
},
|
||||
"advanced_default": {
|
||||
"number_rule": {
|
||||
"day_num": 3
|
||||
}
|
||||
},
|
||||
"send_type_list": [
|
||||
"dingding",
|
||||
"feishu",
|
||||
"mail",
|
||||
"weixin",
|
||||
"webhook",
|
||||
"tg",
|
||||
],
|
||||
"unique": False
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
class TaskManagerCPUTask(BaseTask):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "task_manager_cpu"
|
||||
self.template_name = "Task Manager CPU usage alarm"
|
||||
# self.title = "Task Manager CPU usage alarm"
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
return "Task Manager CPU usage alarm -- [{}]".format(task_data["project"])
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if "interval" not in task_data or not isinstance(task_data["interval"], int):
|
||||
task_data["interval"] = 600
|
||||
if task_data["interval"] < 60:
|
||||
task_data["interval"] = 60
|
||||
if "count" not in task_data or not isinstance(task_data["count"], int):
|
||||
return "The check range is set incorrectly"
|
||||
if not 1 <= task_data["count"] < 100:
|
||||
return "The check range is set incorrectly"
|
||||
if not task_data["project"]:
|
||||
return "Please select a process"
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
process_info = get_process_info()
|
||||
self.title = self.get_title(task_data)
|
||||
count = used = 0
|
||||
for p in process_info:
|
||||
if p["name"] == task_data['project']:
|
||||
used += p["cpu_percent"]
|
||||
count += 1 if "children" not in p else len(p["children"]) + 1
|
||||
|
||||
if used <= task_data['count']:
|
||||
return None
|
||||
|
||||
return {
|
||||
'msg_list':
|
||||
[
|
||||
">Notification type: Task Manager CPU usage alarm",
|
||||
">Alarm content: There are {} processes with the process name [{}], and the proportion of CPU resources consumed is {}%, which is greater than the alarm threshold {}%.".format(
|
||||
task_data['project'], count, used, task_data['count']
|
||||
)
|
||||
],
|
||||
"project": task_data['project'],
|
||||
"count": int(task_data['count'])
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
if not have_task_manager_plugin():
|
||||
return None
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return '', {}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Task Manager CPU usage alarm"
|
||||
if len(push_data["project"]) > 11:
|
||||
project = push_data["project"][:9] + ".."
|
||||
else:
|
||||
project = push_data["project"]
|
||||
|
||||
msg.msg = "The CPU of {} exceeds {}%".format(project, push_data["count"])
|
||||
return msg
|
||||
|
||||
|
||||
class TaskManagerMEMTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "task_manager_mem"
|
||||
self.template_name = "Task Manager memory usage alarm"
|
||||
# self.title = "Task Manager memory usage alarm"
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
return "Task Manager memory usage alarm -- [{}].".format(task_data["project"])
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if not task_data["project"]:
|
||||
return "Please select a process"
|
||||
if "interval" not in task_data or not isinstance(task_data["interval"], int):
|
||||
task_data["interval"] = 600
|
||||
task_data["interval"] = max(60, task_data["interval"])
|
||||
if "count" not in task_data or not isinstance(task_data["count"], int):
|
||||
return "The check range is set incorrectly"
|
||||
if task_data["count"] < 1:
|
||||
return "The check range is set incorrectly"
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
process_info = get_process_info()
|
||||
self.title = self.get_title(task_data)
|
||||
|
||||
used = count = 0
|
||||
for p in process_info:
|
||||
if p["name"] == task_data['project']:
|
||||
used += p["memory_used"]
|
||||
count += 1 if "children" not in p else len(p["children"]) + 1
|
||||
|
||||
if used <= task_data['count'] * 1024 * 1024:
|
||||
return None
|
||||
return {
|
||||
'msg_list': [
|
||||
">Notification type: Task Manager memory usage alarm",
|
||||
">Alarm content: There are {} processes with process name [{}], and the memory resources consumed are {}MB, which is greater than the alarm threshold {}MB.".format(
|
||||
task_data['project'], count, int(used / 1024 / 1024), task_data['count']
|
||||
)
|
||||
],
|
||||
"project": task_data['project']
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
if not have_task_manager_plugin():
|
||||
return None
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return '', {}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
if len(push_data["project"]) > 11:
|
||||
project = push_data["project"][:9] + ".."
|
||||
else:
|
||||
project = push_data["project"]
|
||||
msg.thing_type = "Task Manager memory usage alarm"
|
||||
msg.msg = "The memory of {} exceeds the alarm value".format(project)
|
||||
return msg
|
||||
|
||||
|
||||
class TaskManagerProcessTask(BaseTask):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_name = "task_manager_process"
|
||||
self.template_name = "Task Manager Process Overhead Alert"
|
||||
self.title = "Task Manager Process Overhead Alert"
|
||||
|
||||
def get_title(self, task_data: dict) -> str:
|
||||
return "Task Manager Process Overhead Alert [{}]".format(task_data["project"])
|
||||
|
||||
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
||||
if not task_data["project"]:
|
||||
return "Please select a process"
|
||||
if "interval" not in task_data or not isinstance(task_data["interval"], int):
|
||||
task_data["interval"] = 600
|
||||
task_data["interval"] = max(60, task_data["interval"])
|
||||
if "count" not in task_data or not isinstance(task_data["count"], int):
|
||||
return "The check range is set incorrectly"
|
||||
if task_data["count"] < 1:
|
||||
return "The check range is set incorrectly"
|
||||
return task_data
|
||||
|
||||
def get_keyword(self, task_data: dict) -> str:
|
||||
return task_data["project"]
|
||||
|
||||
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
||||
process_info = get_process_info()
|
||||
count = 0
|
||||
for p in process_info:
|
||||
if p["name"] == task_data['project']:
|
||||
count += 1 if "children" not in p else len(p["children"]) + 1
|
||||
|
||||
if count <= task_data['count']:
|
||||
return None
|
||||
|
||||
return {
|
||||
'msg_list':
|
||||
[
|
||||
">Notification type: Task Manager Process Overhead Alert",
|
||||
">Alarm content: There are {} processes with process name {}, which is greater than the alarm threshold.".format(
|
||||
task_data['project'], count, task_data['count']
|
||||
)
|
||||
],
|
||||
"project": task_data['project'],
|
||||
"count": task_data['count'],
|
||||
}
|
||||
|
||||
def filter_template(self, template: dict) -> Optional[dict]:
|
||||
if not have_task_manager_plugin():
|
||||
return None
|
||||
return template
|
||||
|
||||
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
|
||||
return '', {}
|
||||
|
||||
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
|
||||
msg = WxAccountMsg.new_msg()
|
||||
msg.thing_type = "Task Manager Process Overhead Alert"
|
||||
if len(push_data["project"]) > 11:
|
||||
project = push_data["project"][:9] + ".."
|
||||
else:
|
||||
project = push_data["project"]
|
||||
|
||||
if push_data["count"] > 100: # 节省字数
|
||||
push_data["count"] = "LIMIT"
|
||||
|
||||
msg.msg = "{} has more children than {}".format(project, push_data["count"])
|
||||
return msg
|
||||
|
||||
|
||||
class ViewMsgFormat(object):
|
||||
_FORMAT = {
|
||||
"60": (
|
||||
lambda x: "<span>Process: The CPU occupation of {} is more than {}% triggered</span>".format(
|
||||
x.get("project"), x.get("count")
|
||||
)
|
||||
),
|
||||
"61": (
|
||||
lambda x: "<span>Process: Triggered when the memory usage of {} exceeds {}MB</span>".format(
|
||||
x.get("project"), x.get("count")
|
||||
)
|
||||
),
|
||||
"62": (
|
||||
lambda x: "<span>Process: Triggered when the number of child processes exceeds {}</span>".format(
|
||||
x.get("project"), x.get("count")
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
def get_msg(self, task: dict) -> Optional[str]:
|
||||
if task["template_id"] in self._FORMAT:
|
||||
return self._FORMAT[task["template_id"]](task["task_data"])
|
||||
return None
|
||||
92
mod/base/push_mod/tool.py
Normal file
92
mod/base/push_mod/tool.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Type, TypeVar
|
||||
import traceback
|
||||
from importlib import import_module
|
||||
|
||||
from .base_task import BaseTask
|
||||
from .util import GET_CLASS, get_client_ip, debug_log
|
||||
|
||||
|
||||
T_CLS = TypeVar('T_CLS', bound=BaseTask)
|
||||
|
||||
|
||||
def load_task_cls_by_function(
|
||||
name: str,
|
||||
func_name: str,
|
||||
is_model: bool = False,
|
||||
model_index: str = '',
|
||||
args: Optional[dict] = None,
|
||||
sub_name: Optional[str] = None,
|
||||
) -> Optional[Type[T_CLS]]:
|
||||
"""
|
||||
从执行函数的结果中获取任务类
|
||||
@param model_index: 模块来源,例如:新场景就是mod
|
||||
@param name: 名称
|
||||
@param func_name: 函数名称
|
||||
@param is_model: 是否在Model中,不在Model中,就应该在插件中
|
||||
@param args: 请求这个接口的参数, 默认为空
|
||||
@param sub_name: 自分类名称, 如果有,则会和主名称name做拼接
|
||||
@return: 返回None 或者有效的任务类
|
||||
"""
|
||||
import PluginLoader
|
||||
real_name = name
|
||||
if isinstance(sub_name, str):
|
||||
real_name = "{}/{}".format(name, sub_name)
|
||||
|
||||
get_obj = GET_CLASS()
|
||||
if args is not None and isinstance(args, dict):
|
||||
for key, value in args.items():
|
||||
setattr(get_obj, key, value)
|
||||
try:
|
||||
if is_model:
|
||||
get_obj.model_index = model_index
|
||||
res = PluginLoader.module_run(real_name, func_name, get_obj)
|
||||
else:
|
||||
get_obj.fun = func_name
|
||||
get_obj.s = func_name
|
||||
get_obj.client_ip = get_client_ip
|
||||
res = PluginLoader.plugin_run(name, func_name, get_obj)
|
||||
except:
|
||||
debug_log(traceback.format_exc())
|
||||
return None
|
||||
if isinstance(res, dict):
|
||||
return None
|
||||
elif isinstance(res, BaseTask):
|
||||
return res.__class__
|
||||
elif issubclass(res, BaseTask):
|
||||
return res
|
||||
return None
|
||||
|
||||
|
||||
def load_task_cls_by_path(path: str, cls_name: str) -> Optional[Type[T_CLS]]:
|
||||
try:
|
||||
# 插件优先检测路径
|
||||
path_sep = path.split(".")
|
||||
if len(path_sep) >= 2 and path_sep[0] == "plugin":
|
||||
plugin_path = "/www/server/panel/plugin/{}".format(path_sep[1])
|
||||
if not os.path.isdir(plugin_path):
|
||||
return None
|
||||
|
||||
module = import_module(path)
|
||||
cls = getattr(module, cls_name, None)
|
||||
if issubclass(cls, BaseTask):
|
||||
return cls
|
||||
elif isinstance(cls, BaseTask):
|
||||
return cls.__class__
|
||||
else:
|
||||
debug_log("Error: The loaded class is not a subclass of BaseTask")
|
||||
return None
|
||||
except ModuleNotFoundError as e:
|
||||
# todo暂时忽略 ssl_push
|
||||
if 'mod.base.push_mod.ssl_push' in str(e):
|
||||
return None
|
||||
else:
|
||||
debug_log(traceback.format_exc())
|
||||
debug_log("ModuleNotFoundError: {}".format(str(e)))
|
||||
return None
|
||||
except Exception:
|
||||
print(traceback.format_exc())
|
||||
print(sys.path)
|
||||
debug_log(traceback.format_exc())
|
||||
return None
|
||||
193
mod/base/push_mod/util.py
Normal file
193
mod/base/push_mod/util.py
Normal file
@@ -0,0 +1,193 @@
|
||||
import sys
|
||||
from typing import Optional, Callable
|
||||
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
|
||||
import public
|
||||
from db import Sql
|
||||
import os
|
||||
from sslModel import certModel
|
||||
|
||||
|
||||
def write_file(filename: str, s_body: str, mode='w+') -> bool:
|
||||
"""
|
||||
写入文件内容
|
||||
@filename 文件名
|
||||
@s_body 欲写入的内容
|
||||
return bool 若文件不存在则尝试自动创建
|
||||
"""
|
||||
try:
|
||||
fp = open(filename, mode=mode)
|
||||
fp.write(s_body)
|
||||
fp.close()
|
||||
return True
|
||||
except:
|
||||
try:
|
||||
fp = open(filename, mode=mode, encoding="utf-8")
|
||||
fp.write(s_body)
|
||||
fp.close()
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def read_file(filename, mode='r') -> Optional[str]:
|
||||
"""
|
||||
读取文件内容
|
||||
@filename 文件名
|
||||
return string(bin) 若文件不存在,则返回None
|
||||
"""
|
||||
import os
|
||||
if not os.path.exists(filename):
|
||||
return None
|
||||
fp = None
|
||||
try:
|
||||
fp = open(filename, mode=mode)
|
||||
f_body = fp.read()
|
||||
except:
|
||||
return None
|
||||
finally:
|
||||
if fp and not fp.closed:
|
||||
fp.close()
|
||||
return f_body
|
||||
|
||||
|
||||
ExecShell: Callable = public.ExecShell
|
||||
|
||||
write_log: Callable = public.WriteLog
|
||||
|
||||
Sqlite: Callable = Sql
|
||||
|
||||
GET_CLASS: Callable = public.dict_obj
|
||||
|
||||
debug_log: Callable = public.print_log
|
||||
|
||||
get_config_value: Callable = public.GetConfigValue
|
||||
|
||||
get_server_ip: Callable = public.get_server_ip
|
||||
|
||||
get_network_ip: Callable = public.get_network_ip
|
||||
|
||||
format_date: Callable = public.format_date
|
||||
|
||||
public_get_cache_func: Callable = public.get_cache_func
|
||||
|
||||
public_set_cache_func: Callable = public.set_cache_func
|
||||
|
||||
public_get_user_info: Callable = public.get_user_info
|
||||
|
||||
public_http_post = public.httpPost
|
||||
|
||||
panel_version = public.version
|
||||
|
||||
try:
|
||||
get_cert_list = certModel.main().get_cert_list
|
||||
to_dict_obj = public.to_dict_obj
|
||||
|
||||
except:
|
||||
public.print_log(public.get_error_info())
|
||||
|
||||
|
||||
def get_client_ip() -> str:
|
||||
return public.GetClientIp()
|
||||
|
||||
|
||||
class _DB:
|
||||
|
||||
def __call__(self, table: str):
|
||||
import db
|
||||
with db.Sql() as t:
|
||||
t.table(table)
|
||||
return t
|
||||
|
||||
|
||||
DB = _DB()
|
||||
|
||||
|
||||
def check_site_status(web):
|
||||
panelPath = '/www/server/panel/'
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0, panelPath)
|
||||
|
||||
if web['project_type'] == "Java":
|
||||
from mod.project.java.projectMod import main as java
|
||||
if not java().get_project_stat(web)['pid']:
|
||||
return None
|
||||
if web['project_type'] == "Node":
|
||||
from projectModelV2.nodejsModel import main as nodejs
|
||||
if not nodejs().get_project_run_state(project_name=web['name']):
|
||||
return None
|
||||
if web['project_type'] == "Go":
|
||||
from projectModel.goModel import main as go # NOQA
|
||||
if not go().get_project_run_state(project_name=web['name']):
|
||||
return None
|
||||
if web['project_type'] == "Python":
|
||||
from projectModelV2.pythonModel import main as python
|
||||
if not python().get_project_run_state(project_name=web['name']):
|
||||
return None
|
||||
if web['project_type'] == "Other":
|
||||
from projectModel.otherModel import main as other # NOQA
|
||||
if not other().get_project_run_state(project_name=web['name']):
|
||||
return None
|
||||
return True
|
||||
|
||||
|
||||
def get_db_by_file(file: str):
|
||||
import db
|
||||
if not os.path.exists(file):
|
||||
return None
|
||||
db_obj = db.Sql()
|
||||
db_obj._Sql__DB_FILE = file
|
||||
return db_obj
|
||||
|
||||
|
||||
def generate_fields(template: dict, add_type: str) -> dict:
|
||||
""""动态表单生成附加选项 hook"""
|
||||
if add_type not in [
|
||||
"restart", "module",
|
||||
]:
|
||||
return template
|
||||
if add_type == "restart":
|
||||
template = generate_restart_fields(template)
|
||||
elif add_type == "module":
|
||||
template = generate_module_fields(template)
|
||||
return template
|
||||
|
||||
|
||||
def generate_restart_fields(template: dict) -> dict:
|
||||
"""动态表单生成重启服务 hook 选项"""
|
||||
from script.restart_services import SERVICES_MAP, ServicesHelper
|
||||
f = {
|
||||
"attr": "after_hook",
|
||||
"name": "After the alarm excutes",
|
||||
"suffix": "select after alarm action (Optional)",
|
||||
"type": "multiple-select",
|
||||
"items": [
|
||||
{
|
||||
"title": f"Restart {x}",
|
||||
"type": "restart",
|
||||
"value": x
|
||||
} for x in SERVICES_MAP.keys() if ServicesHelper(x).is_install
|
||||
],
|
||||
"default": []
|
||||
}
|
||||
if "field" in template:
|
||||
template["field"].append(f)
|
||||
else:
|
||||
template["field"] = [f]
|
||||
|
||||
if "sorted" in template and isinstance(template["sorted"], list):
|
||||
if ["after_hook"] not in template["sorted"]:
|
||||
template["sorted"].append(["after_hook"])
|
||||
elif "sorted" not in template:
|
||||
template["sorted"] = [["after_hook"]]
|
||||
else:
|
||||
template["sorted"] = [template["sorted"], ["after_hook"]]
|
||||
return template
|
||||
|
||||
|
||||
def generate_module_fields(template: dict) -> dict:
|
||||
"""动态表单生成模块调用 hook 选项"""
|
||||
# todo
|
||||
return template
|
||||
Reference in New Issue
Block a user