diff --git a/.gitignore b/.gitignore index b9dd27a0b..5111785c9 100644 --- a/.gitignore +++ b/.gitignore @@ -177,5 +177,5 @@ debug.out -mdioks.session-journal -*.session +mdioks.session-journal +*.session diff --git a/README.md b/README.md index cde48015f..020ee003e 100644 --- a/README.md +++ b/README.md @@ -100,12 +100,20 @@ docker run -itd --name mw-server --privileged=true -p 7200:7200 -p 80:80 -p 443: ``` -### 版本更新 0.15.4 +### 版本更新 0.16.0 -* OP防火墙-验证优化[cf缓存问题解决]。 -* OP防火墙-加入地区限制(最好卸载重装)。 -* 在aarch64架构下低于PHP70的版本[安装/扩展安装]优化。 -* Mongodb安装优化。 +yum install -y libargon2-devel +apt install -y libargon2-dev + +OP防火墙 - 重载即可 + +* 增加PHP83。 +* 面板日志单独为一页并加入日志审计功能。 +* 修复webssh服务器读取问题。 +* 站点批量删除修复。 +* OP防火墙-修复`自动强制验证`关闭不生效。 +* 新增OneDrive备份插件。 +* FTP备份优化。 ### JSDelivr安装地址 diff --git a/class/core/cert_api.py b/class/core/cert_api.py index 6c9e40205..45912048b 100644 --- a/class/core/cert_api.py +++ b/class/core/cert_api.py @@ -1496,8 +1496,9 @@ fullchain.pem 粘贴到证书输入框 'cert_timeout'] = int(time.time()) if self.__config['orders'][i]['cert_timeout'] > start_time: - writeLog( - "|-本次跳过域名: {},未过期!".format(self.__config['orders'][i]['domains'][0])) + msg = "|-本次跳过域名: {},未过期!".format( + self.__config['orders'][i]['domains'][0]) + writeLog(msg) continue # 已删除的网站直接跳过续签 @@ -1514,8 +1515,9 @@ fullchain.pem 粘贴到证书输入框 break if not mw.M('domain').where("name=?", (domain,)).count() and not mw.M('binding').where("domain=?", domain).count(): auth_to = None - writeLog( - "|-跳过被删除的域名: {}".format(self.__config['orders'][i]['domains'])) + msg = "|-跳过被删除的域名: {}".format( + self.__config['orders'][i]['domains']) + writeLog(msg) if not auth_to: continue @@ -1526,15 +1528,17 @@ fullchain.pem 粘贴到证书输入框 timeout = self.__config['orders'][i][ 'next_retry_time'] - int(time.time()) if timeout > 0: - writeLog('|-本次跳过域名:{},因第上次续签失败,还需要等待{}小时后再重试'.format( - self.__config['orders'][i]['domains'], int(timeout / 60 / 60))) + msg = '|-本次跳过域名:{},因第上次续签失败,还需要等待{}小时后再重试'.format( + self.__config['orders'][i]['domains'], int(timeout / 60 / 60)) + writeLog(msg) continue # 是否到了最大重试次数 if 'retry_count' in self.__config['orders'][i]: if self.__config['orders'][i]['retry_count'] >= 5: - writeLog('|-本次跳过域名:{},因连续5次续签失败,不再续签此证书(可尝试手动续签此证书,成功后错误次数将被重置)'.format( - self.__config['orders'][i]['domains'])) + msg = '|-本次跳过域名:{},因连续5次续签失败,不再续签此证书(可尝试手动续签此证书,成功后错误次数将被重置)'.format( + self.__config['orders'][i]['domains']) + writeLog(msg) continue # 加入到续签订单 diff --git a/class/core/config_api.py b/class/core/config_api.py index 426d4209f..c1511fdec 100755 --- a/class/core/config_api.py +++ b/class/core/config_api.py @@ -27,7 +27,7 @@ from flask import request class config_api: - __version = '0.15.4' + __version = '0.16.0' __api_addr = 'data/api.json' def __init__(self): diff --git a/class/core/crontab_api.py b/class/core/crontab_api.py index facaaba1c..6e5f2f14e 100755 --- a/class/core/crontab_api.py +++ b/class/core/crontab_api.py @@ -602,20 +602,22 @@ echo "-------------------------------------------------------------------------- # 将Shell脚本写到文件 def writeShell(self, config): - u_file = '/var/spool/cron/crontabs/root' - if not os.path.exists(u_file): + file = '/var/spool/cron/crontabs/root' + current_os = mw.getOs() + if current_os == 'darwin': + file = '/etc/crontab' + elif current_os.startswith("freebsd"): + file = '/var/cron/tabs/root' + + if not os.path.exists(file): file = '/var/spool/cron/root' - if mw.isAppleSystem(): - file = '/etc/crontab' - else: - file = u_file if not os.path.exists(file): mw.writeFile(file, '') conf = mw.readFile(file) conf += str(config) + "\n" if mw.writeFile(file, conf): - if not os.path.exists(u_file): + if not os.path.exists(file): mw.execShell("chmod 600 '" + file + "' && chown root.root " + file) else: diff --git a/class/core/files_api.py b/class/core/files_api.py index b668645ce..332fb9574 100755 --- a/class/core/files_api.py +++ b/class/core/files_api.py @@ -64,8 +64,13 @@ class files_api: filename = request.args.get('filename', '') if not os.path.exists(filename): return '' + + is_attachment = True + if filename.endswith(".svg"): + is_attachment = False + response = make_response(send_from_directory( - os.path.dirname(filename), os.path.basename(filename), as_attachment=True)) + os.path.dirname(filename), os.path.basename(filename), as_attachment=is_attachment)) return response def zipApi(self): @@ -708,10 +713,7 @@ class files_api: return not path in nDirs def getDirSize(self, path): - if mw.isAppleSystem(): - tmp = mw.execShell('du -sh ' + path) - else: - tmp = mw.execShell('du -sbh ' + path) + tmp = mw.execShell('du -sh ' + path) return tmp def checkFileName(self, filename): diff --git a/class/core/firewall_api.py b/class/core/firewall_api.py index 9175552f6..a7bc93d93 100755 --- a/class/core/firewall_api.py +++ b/class/core/firewall_api.py @@ -178,12 +178,6 @@ class firewall_api: limit = request.form.get('limit', '10').strip() return self.getList(int(p), int(limit)) - def getLogListApi(self): - p = request.form.get('p', '1').strip() - limit = request.form.get('limit', '10').strip() - search = request.form.get('search', '').strip() - return self.getLogList(int(p), int(limit), search) - def getSshInfoApi(self): data = {} @@ -389,11 +383,6 @@ class firewall_api: return mw.returnData(True, '设置成功!') - def delPanelLogsApi(self): - mw.M('logs').where('id>?', (0,)).delete() - mw.writeLog('面板设置', '面板操作日志已清空!') - return mw.returnJson(True, '面板操作日志已清空!') - ##### ----- start ----- ### def getList(self, page, limit): @@ -414,28 +403,6 @@ class firewall_api: data['page'] = mw.getPage(_page) return mw.getJson(data) - def getLogList(self, page, limit, search=''): - find_search = '' - if search != '': - find_search = "type like '%" + search + "%' or log like '%" + \ - search + "%' or addtime like '%" + search + "%'" - - start = (page - 1) * limit - - _list = mw.M('logs').where(find_search, ()).field( - 'id,type,log,addtime').limit(str(start) + ',' + str(limit)).order('id desc').select() - data = {} - data['data'] = _list - - count = mw.M('logs').where(find_search, ()).count() - _page = {} - _page['count'] = count - _page['tojs'] = 'getLogs' - _page['p'] = page - - data['page'] = mw.getPage(_page) - return mw.getJson(data) - def addAcceptPort(self, port): if self.__isUfw: mw.execShell('ufw allow ' + port + '/tcp') diff --git a/class/core/logs_api.py b/class/core/logs_api.py new file mode 100644 index 000000000..5018c929e --- /dev/null +++ b/class/core/logs_api.py @@ -0,0 +1,352 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 日志类操作 +# --------------------------------------------------------------------------------- + +import psutil +import time +import os +import sys +import mw +import re +import json +import pwd + +from datetime import datetime +from flask import request + + +class logs_api: + + __months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', + 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + + def __init__(self): + pass + + def delPanelLogsApi(self): + mw.M('logs').where('id>?', (0,)).delete() + mw.writeLog('面板设置', '面板操作日志已清空!') + return mw.returnJson(True, '面板操作日志已清空!') + + def getLogListApi(self): + p = request.form.get('p', '1').strip() + limit = request.form.get('limit', '10').strip() + search = request.form.get('search', '').strip() + return self.getLogList(int(p), int(limit), search) + + def getLogList(self, page, limit, search=''): + find_search = '' + if search != '': + find_search = "type like '%" + search + "%' or log like '%" + \ + search + "%' or addtime like '%" + search + "%'" + + start = (page - 1) * limit + + _list = mw.M('logs').where(find_search, ()).field( + 'id,type,log,addtime').limit(str(start) + ',' + str(limit)).order('id desc').select() + data = {} + data['data'] = _list + + count = mw.M('logs').where(find_search, ()).count() + _page = {} + _page['count'] = count + _page['tojs'] = 'getLogs' + _page['p'] = page + + data['page'] = mw.getPage(_page) + return mw.getJson(data) + + def getLogsTitle(self, log_name): + log_name = log_name.replace('.1', '') + if log_name in ['mw-update.log']: + return '面板更新日志' + if log_name in ['mw-install.log']: + return '面板安装日志' + if log_name in ['auth.log', 'secure'] or log_name.find('auth.') == 0: + return '授权日志' + if log_name in ['dmesg'] or log_name.find('dmesg') == 0: + return '内核缓冲区日志' + if log_name in ['syslog'] or log_name.find('syslog') == 0: + return '系统警告/错误日志' + if log_name in ['btmp']: + return '失败的登录记录' + if log_name in ['utmp', 'wtmp']: + return '登录和重启记录' + if log_name in ['lastlog']: + return '用户最后登录' + if log_name in ['yum.log']: + return 'yum包管理器日志' + if log_name in ['anaconda.log']: + return 'Anaconda日志' + if log_name in ['dpkg.log']: + return 'dpkg日志' + if log_name in ['daemon.log']: + return '系统后台守护进程日志' + if log_name in ['boot.log']: + return '启动日志' + if log_name in ['kern.log']: + return '内核日志' + if log_name in ['maillog', 'mail.log']: + return '邮件日志' + if log_name.find('Xorg') == 0: + return 'Xorg日志' + if log_name in ['cron.log']: + return '定时任务日志' + if log_name in ['alternatives.log']: + return '更新替代信息' + if log_name in ['debug']: + return '调试信息' + if log_name.find('apt') == 0: + return 'apt-get相关日志' + if log_name.find('installer') == 0: + return '系统安装相关日志' + if log_name in ['messages']: + return '综合日志' + return '{}日志'.format(log_name.split('.')[0]) + + def getAuditLogsFilesApi(self): + log_dir = '/var/log' + log_files = [] + for log_file in os.listdir(log_dir): + log_suffix = log_file.split('.')[-1:] + if log_suffix[0] in ['gz', 'xz', 'bz2', 'asl']: + continue + + if log_file in ['.', '..', 'faillog', 'fontconfig.log', 'unattended-upgrades', 'tallylog']: + continue + + filename = os.path.join(log_dir, log_file) + if os.path.isfile(filename): + file_size = os.path.getsize(filename) + if not file_size: + continue + + tmp = { + 'name': log_file, + 'size': file_size, + 'log_file': filename, + 'title': self.getLogsTitle(log_file), + 'uptime': os.path.getmtime(filename) + } + + log_files.append(tmp) + else: + for next_name in os.listdir(filename): + if next_name[-3:] in ['.gz', '.xz']: + continue + next_file = os.path.join(filename, next_name) + if not os.path.isfile(next_file): + continue + file_size = os.path.getsize(next_file) + if not file_size: + continue + log_name = '{}/{}'.format(log_file, next_name) + tmp = { + 'name': log_name, + 'size': file_size, + 'log_file': next_file, + 'title': self.getLogsTitle(log_name), + 'uptime': os.path.getmtime(next_file) + } + log_files.append(tmp) + log_files = sorted(log_files, key=lambda x: x['name'], reverse=True) + return mw.getJson(log_files) + + def getAuditFileApi(self): + log_name = request.form.get('log_name', '').strip() + return self.getAuditLogsName(log_name) + + def __to_date2(self, date_str): + tmp = date_str.split() + s_date = str(tmp[-1]) + '-' + self.__months.get(tmp[1], + tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + + def __to_date3(self, date_str): + tmp = date_str.split() + s_date = str(datetime.now().year) + '-' + \ + self.__months.get(tmp[1], tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + + def __to_date4(self, date_str): + tmp = date_str.split() + s_date = str(datetime.now().year) + '-' + \ + self.__months.get(tmp[0], tmp[0]) + '-' + tmp[1] + ' ' + tmp[2] + return s_date + + def getAuditLast(self, log_name): + # 获取日志 + cmd = '''LANG=en_US.UTF-8 last -n 200 -x -f {} |grep -v 127.0.0.1|grep -v " begins"'''.format( + '/var/log/' + log_name) + result = mw.execShell(cmd) + lastlog_list = [] + for _line in result[0].split("\n"): + if not _line: + continue + tmp = {} + sp_arr = _line.split() + tmp['用户'] = sp_arr[0] + if sp_arr[0] == 'runlevel': + tmp['来源'] = sp_arr[4] + tmp['端口'] = ' '.join(sp_arr[1:4]) + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[5:])) + ' ' + ' '.join(sp_arr[-2:]) + elif sp_arr[0] in ['reboot', 'shutdown']: + tmp['来源'] = sp_arr[3] + tmp['端口'] = ' '.join(sp_arr[1:3]) + if sp_arr[-3] == '-': + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-3:]) + else: + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-2:]) + elif sp_arr[1] in ['tty1', 'tty', 'tty2', 'tty3', 'hvc0', 'hvc1', 'hvc2'] or len(sp_arr) == 9: + tmp['来源'] = '' + tmp['端口'] = sp_arr[1] + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[2:])) + ' ' + ' '.join(sp_arr[-3:]) + else: + tmp['来源'] = sp_arr[2] + tmp['端口'] = sp_arr[1] + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[3:])) + ' ' + ' '.join(sp_arr[-3:]) + + # tmp['_line'] = _line + lastlog_list.append(tmp) + # lastlog_list = sorted(lastlog_list,key=lambda x:x['时间'],reverse=True) + return mw.returnData(True, 'ok!', lastlog_list) + + def getAuditLastLog(self): + cmd = '''LANG=en_US.UTF-8 lastlog|grep -v Username''' + result = mw.execShell(cmd) + lastlog_list = [] + for _line in result[0].split("\n"): + if not _line: + continue + tmp = {} + sp_arr = _line.split() + tmp['用户'] = sp_arr[0] + # tmp['_line'] = _line + if _line.find('Never logged in') != -1: + tmp['最后登录时间'] = '0' + tmp['最后登录来源'] = '-' + tmp['最后登录端口'] = '-' + lastlog_list.append(tmp) + continue + tmp['最后登录来源'] = sp_arr[2] + tmp['最后登录端口'] = sp_arr[1] + tmp['最后登录时间'] = self.__to_date2(' '.join(sp_arr[3:])) + + lastlog_list.append(tmp) + lastlog_list = sorted(lastlog_list, key=lambda x: x[ + '最后登录时间'], reverse=True) + for i in range(len(lastlog_list)): + if lastlog_list[i]['最后登录时间'] == '0': + lastlog_list[i]['最后登录时间'] = '从未登录过' + return mw.returnData(True, 'ok!', lastlog_list) + + def parseAuditFile(self, log_name, result): + log_list = [] + is_string = True + for _line in result.split("\n"): + if not _line.strip(): + continue + if log_name.find('sa/sa') == -1: + if _line[:3] in self.__months: + _msg = _line[16:] + _tmp = _msg.split(": ") + _act = '' + if len(_tmp) > 1: + _act = _tmp[0] + _msg = _tmp[1] + else: + _msg = _tmp[0] + _line = { + "时间": self.__to_date4(_line[:16].strip()), + "角色": _act, + "事件": _msg + } + is_string = False + elif _line[:2] in ['19', '20', '21', '22', '23', '24']: + _msg = _line[19:] + _tmp = _msg.split(" ") + _act = _tmp[1] + _msg = ' '.join(_tmp[2:]) + _line = { + "时间": _line[:19].strip(), + "角色": _act, + "事件": _msg + } + is_string = False + elif log_name.find('alternatives') == 0: + _tmp = _line.split(": ") + _last = _tmp[0].split(" ") + _act = _last[0] + _msg = ' '.join(_tmp[1:]) + _line = { + "时间": ' '.join(_last[1:]).strip(), + "角色": _act, + "事件": _msg + } + is_string = False + else: + if not is_string: + if type(_line) != dict: + continue + + log_list.append(_line) + + return log_list + + def getAuditLogsName(self, log_name): + # print(log_name) + if log_name in ['wtmp', 'btmp', 'utmp'] or log_name.find('wtmp') == 0 or log_name.find('btmp') == 0 or log_name.find('utmp') == 0: + return self.getAuditLast(log_name) + + if log_name.find('lastlog') == 0: + return self.getAuditLastLog() + + if log_name.find('sa/sa') == 0: + if log_name.find('sa/sar') == -1: + return mw.execShell("sar -f /var/log/{}".format(log_name))[0] + log_dir = '/var/log' + log_file = log_dir + '/' + log_name + if not os.path.exists(log_file): + return mw.returnData(False, '日志文件不存在!') + result = mw.getLastLine(log_file, 100) + try: + log_list = self.parseAuditFile(log_name, result) + _string = [] + _dict = [] + _list = [] + for _line in log_list: + if isinstance(_line, str): + _string.append(_line.strip()) + elif isinstance(_line, dict): + _dict.append(_line) + elif isinstance(_line, list): + _list.append(_line) + else: + continue + _str_len = len(_string) + _dict_len = len(_dict) + _list_len = len(_list) + if _str_len > _dict_len + _list_len: + return "\n".join(_string) + elif _dict_len > _str_len + _list_len: + return mw.returnData(True, 'ok!', _dict) + else: + return mw.returnData(True, 'ok!', _list) + + except: + return mw.returnData(True, 'ok!', result) diff --git a/class/core/mw.py b/class/core/mw.py index ab91070e0..03706c8a9 100755 --- a/class/core/mw.py +++ b/class/core/mw.py @@ -1356,6 +1356,12 @@ def getCpuType(): cpuinfo = execShell(cmd) return cpuinfo[0].strip() + current_os = getOs() + if current_os.startswith('freebsd'): + cmd = "sysctl -a | egrep -i 'hw.model' | awk -F ':' '{print $2}'" + cpuinfo = execShell(cmd) + return cpuinfo[0].strip() + # 取CPU类型 cpuinfo = open('/proc/cpuinfo', 'r').read() rep = "model\s+name\s+:\s+(.+)" @@ -1412,7 +1418,7 @@ def makeConf(): file = getRunDir() + '/data/json/config.json' if not os.path.exists(file): c = {} - c['title'] = '金灵面板' + c['title'] = '老子面板' c['home'] = 'http://github/midoks/mdserver-web' c['recycle_bin'] = True c['template'] = 'default' @@ -2100,15 +2106,15 @@ done def echoStart(tag): - print("=" * 90) + print("=" * 89) print("★开始{}[{}]".format(tag, formatDate())) - print("=" * 90) + print("=" * 89) def echoEnd(tag): - print("=" * 90) + print("=" * 89) print("☆{}完成[{}]".format(tag, formatDate())) - print("=" * 90) + print("=" * 89) print("\n") diff --git a/class/core/system_api.py b/class/core/system_api.py index 945cc7c90..6d006c9c4 100755 --- a/class/core/system_api.py +++ b/class/core/system_api.py @@ -274,26 +274,43 @@ class system_api: return title def getSystemVersion(self): + # # 取操作系统版本 - if mw.getOs() == 'darwin': + current_os = mw.getOs() + # mac + if current_os == 'darwin': data = mw.execShell('sw_vers')[0] data_list = data.strip().split("\n") mac_version = '' for x in data_list: mac_version += x.split("\t")[1] + ' ' - return mac_version + + arch_ver = mw.execShell("arch") + return mac_version + " (" + arch_ver[0].strip() + ")" + + # freebsd + if current_os.startswith('freebsd'): + version = mw.execShell( + "cat /etc/*-release | grep PRETTY_NAME | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + arch_ver = mw.execShell( + "sysctl -a | egrep -i 'hw.machine_arch' | awk -F ':' '{print $2}'") + return version[0].strip() + " (" + arch_ver[0].strip() + ")" redhat_series = '/etc/redhat-release' if os.path.exists(redhat_series): version = mw.readFile('/etc/redhat-release') version = version.replace('release ', '').strip() - return version + + arch_ver = mw.execShell("arch") + return version + " (" + arch_ver[0].strip() + ")" os_series = '/etc/os-release' if os.path.exists(os_series): version = mw.execShell( "cat /etc/*-release | grep PRETTY_NAME | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") - return version[0].strip() + + arch_ver = mw.execShell("arch") + return version[0].strip() + " (" + arch_ver[0].strip() + ")" return '未识别系统信息' @@ -318,7 +335,7 @@ class system_api: cpuCount = psutil.cpu_count() cpuLogicalNum = psutil.cpu_count(logical=False) used = psutil.cpu_percent(interval=interval) - + cpuLogicalNum = 0 if os.path.exists('/proc/cpuinfo'): c_tmp = mw.readFile('/proc/cpuinfo') d_tmp = re.findall("physical id.+", c_tmp) diff --git a/cli.sh b/cli.sh index 68d639d35..d97930aec 100755 --- a/cli.sh +++ b/cli.sh @@ -7,6 +7,10 @@ MDIR=$(dirname "$DIR") PATH=$PATH:$DIR/bin if [ -f bin/activate ];then source bin/activate + + if [ "$?" != "0" ];then + echo "load local python env fail!" + fi fi export LC_ALL="en_US.UTF-8" diff --git a/plugins/backup_ftp/index.py b/plugins/backup_ftp/index.py index a4a6e660e..5ba0a45dc 100755 --- a/plugins/backup_ftp/index.py +++ b/plugins/backup_ftp/index.py @@ -14,6 +14,7 @@ if sys.platform != "darwin": sys.path.append(os.getcwd() + "/class/core") import mw +import db _ver = sys.version_info is_py2 = (_ver[0] == 2) @@ -185,6 +186,35 @@ def backupAllFunc(stype): "path": "path", } + backups = [] + sql = db.Sql() + + # print("stype:", stype) + # 提前获取-清理多余备份 + if stype == 'site': + pid = sql.table('sites').where('name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('0', pid)).field('id,filename').select() + if stype == 'database': + db_path = mw.getServerDir() + '/mysql' + pid = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('1', pid)).field('id,filename').select() + if stype == 'path': + backup_path = backup_dir + '/path' + _name = 'path_{}'.format(os.path.basename(name)) + backups = findPathName(backup_path, _name) + + # 其他类型关系性数据库(mysql类的) + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + db_path = mw.getServerDir() + '/' + plugin_name + pid = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('1', pid)).field('id,filename').select() + args = stype + " " + name + " " + num cmd = 'python3 ' + mw.getRunDir() + '/scripts/backup.py ' + args if stype.find('database_') > -1: @@ -213,19 +243,31 @@ def backupAllFunc(stype): filename = mw.execShell(find_new_file)[0].strip() if filename == "": mw.echoInfo("not find upload file!") - return False + return '' print("|-准备上传文件 {}".format(filename)) ftp = FtpPSClient() ftp.uploadFile(filename, stype) + + # print(backups) + backups = sorted(backups, key=lambda x: x['filename'], reverse=False) + mw.echoStart('开始删除远程备份') + num = int(num) + sep = len(backups) - num + if sep > -1: + for backup in backups: + fn = os.path.basename(backup['filename']) + object_name = ftp.buildDirName(stype, fn) + ftp.deleteFile(object_name) + mw.echoInfo("---已清理远程过期备份文件:" + object_name) + sep -= 1 + if sep < 0: + break + mw.echoEnd('结束删除远程备份') + return '' -def backupSite(): - # 备份站点 - pass - - def in_array(name, arr=[]): for x in arr: if name == x: diff --git a/plugins/dynamic-tracking/index.html b/plugins/dynamic-tracking/index.html index 33a926b0e..0583f4900 100755 --- a/plugins/dynamic-tracking/index.html +++ b/plugins/dynamic-tracking/index.html @@ -4,7 +4,6 @@

服务

自启动

-

日志

@@ -13,7 +12,8 @@
\ No newline at end of file diff --git a/plugins/dynamic-tracking/index.py b/plugins/dynamic-tracking/index.py index 9a5509a13..12a4d4a93 100755 --- a/plugins/dynamic-tracking/index.py +++ b/plugins/dynamic-tracking/index.py @@ -32,23 +32,13 @@ def getInitDFile(): return '/etc/init.d/' + getPluginName() -def getConf(): - path = getServerDir() + "/redis.conf" - return path - - -def getConfTpl(): - path = getPluginDir() + "/config/redis.conf" - return path - - def getInitDTpl(): path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" return path def getArgs(): - args = sys.argv[3:] + args = sys.argv[2:] tmp = {} args_len = len(args) @@ -67,70 +57,25 @@ def getArgs(): return tmp +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + def status(): + dir_path = getServerDir() + '/trace' + if not os.path.exists(dir_path): + os.mkdir(dir_path) return 'start' -def initDreplace(): - - file_tpl = getInitDTpl() - service_path = os.path.dirname(os.getcwd()) - - initD_path = getServerDir() + '/init.d' - if not os.path.exists(initD_path): - os.mkdir(initD_path) - file_bin = initD_path + '/' + getPluginName() - - # initd replace - if not os.path.exists(file_bin): - content = mw.readFile(file_tpl) - content = content.replace('{$SERVER_PATH}', service_path) - mw.writeFile(file_bin, content) - mw.execShell('chmod +x ' + file_bin) - - # log - dataLog = getServerDir() + '/data' - if not os.path.exists(dataLog): - mw.execShell('chmod +x ' + file_bin) - - # config replace - dst_conf = getServerDir() + '/redis.conf' - dst_conf_init = getServerDir() + '/init.pl' - if not os.path.exists(dst_conf_init): - conf_content = mw.readFile(getConfTpl()) - conf_content = conf_content.replace('{$SERVER_PATH}', service_path) - conf_content = conf_content.replace( - '{$REDIS_PASS}', mw.getRandomString(10)) - - mw.writeFile(dst_conf, conf_content) - mw.writeFile(dst_conf_init, 'ok') - - # systemd - systemDir = mw.systemdCfgDir() - systemService = systemDir + '/redis.service' - systemServiceTpl = getPluginDir() + '/init.d/redis.service.tpl' - if os.path.exists(systemDir) and not os.path.exists(systemService): - service_path = mw.getServerDir() - se_content = mw.readFile(systemServiceTpl) - se_content = se_content.replace('{$SERVER_PATH}', service_path) - mw.writeFile(systemService, se_content) - mw.execShell('systemctl daemon-reload') - - return file_bin - - def dtOp(method): - file = initDreplace() - return 'ok' - # data = mw.execShell(file + ' start') - # if data[1] == '': - # return 'ok' - # return 'fail' def start(): - return dtOp('start') @@ -140,9 +85,6 @@ def stop(): def restart(): status = dtOp('restart') - - log_file = runLog() - mw.execShell("echo '' > " + log_file) return status @@ -151,33 +93,105 @@ def reload(): def initdStatus(): - if mw.isAppleSystem(): - return "Apple Computer does not support" - - shell_cmd = 'systemctl status ' + \ - getPluginName() + ' | grep loaded | grep "enabled;"' - data = mw.execShell(shell_cmd) - if data[0] == '': - return 'fail' return 'ok' def initdInstall(): - if mw.isAppleSystem(): - return "Apple Computer does not support" - - mw.execShell('systemctl enable ' + getPluginName()) return 'ok' def initdUinstall(): - if mw.isAppleSystem(): - return "Apple Computer does not support" - - mw.execShell('systemctl disable ' + getPluginName()) return 'ok' +def get_file(args): + dir_path = getServerDir() + '/trace' + + path = dir_path + '/' + args['file'] + '/main.svg' + + if os.path.exists(path): + d = mw.readFile(path) + return mw.returnData(True, 'ok', d) + else: + return mw.returnData(False, '无效目录') + + +def get_file_path(args): + dir_path = getServerDir() + '/trace' + path = dir_path + '/' + args['file'] + '/main.svg' + if os.path.exists(path): + return mw.returnData(True, 'ok', path) + else: + return mw.returnData(False, '无效目录') + + +def dtGetFilePath(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + dir_path = getServerDir() + '/trace' + path = dir_path + '/' + args['file'] + '/main.svg' + if os.path.exists(path): + return mw.returnJson(True, 'ok', path) + else: + return mw.returnJson(False, '无效目录') + + +def dtRemoveFilePath(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + dir_path = getServerDir() + '/trace' + path = dir_path + '/' + args['file'] + if os.path.exists(path): + mw.execShell('rm -rf ' + path) + return mw.returnJson(True, '删除成功!') + + +def dtFileList(): + dir_path = getServerDir() + '/trace' + if not os.path.exists(dir_path): + os.mkdir(dir_path) + + file_info = [] + for name in os.listdir(dir_path): + if name == ".DS_Store": + continue + + # print(name) + info = {} + try: + info['name'] = name + info['abs_path'] = dir_path + '/' + name + '/main.svg' + except Exception as e: + return mw.returnJson(False, str(e)) + + file_info.append(info) + + file_info = sorted(file_info, key=lambda x: x['name'], reverse=False) + return mw.returnJson(True, 'ok!', file_info) + + +def dtSimpleTrace(): + if mw.isAppleSystem(): + return mw.returnJson(False, 'macosx只能手动执行!') + + args = getArgs() + data = checkArgs(args, ['pid']) + if not data[0]: + return data[1] + + plugins_shell = getPluginDir() + '/shell/simple_trace.sh' + cmd = plugins_shell + ' "' + args['pid'] + '"' + + data = mw.execShell("bash " + cmd + " &") + return mw.returnJson(True, '执行成功!') + + if __name__ == "__main__": func = sys.argv[1] if func == 'status': @@ -202,5 +216,13 @@ if __name__ == "__main__": print(getConf()) elif func == 'run_log': print(runLog()) + elif func == 'file_list': + print(dtFileList()) + elif func == 'get_file_path': + print(dtGetFilePath()) + elif func == 'remove_file_path': + print(dtRemoveFilePath()) + elif func == 'simple_trace': + print(dtSimpleTrace()) else: print('error') diff --git a/plugins/dynamic-tracking/info.json b/plugins/dynamic-tracking/info.json index ba1f43a67..e72837d9a 100755 --- a/plugins/dynamic-tracking/info.json +++ b/plugins/dynamic-tracking/info.json @@ -1,4 +1,24 @@ { + "hook":[ + { + "tag":"menu", + "menu": { + "title":"动态追踪", + "name":"dynamic-tracking", + "path":"static/html/index.html", + "css_path":"static/css/dynamic-tracking.css", + "js_path":"static/js/dynamic-tracking.js" + } + }, + { + "tag":"global_static", + "global_static": { + "title":"动态追踪", + "name":"dynamic-tracking", + "css_path":"static/css/ico.css" + } + } + ], "sort": 7, "ps": "专业使用,动态追踪调试程序<无侵入>![潜龙勿用|实验]", "name": "dynamic-tracking", diff --git a/plugins/dynamic-tracking/install.sh b/plugins/dynamic-tracking/install.sh index 0339072b5..32f5ec861 100755 --- a/plugins/dynamic-tracking/install.sh +++ b/plugins/dynamic-tracking/install.sh @@ -35,17 +35,36 @@ Install_App() mkdir -p $serverPath/source mkdir -p $serverPath/dynamic-tracking + cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") + HTTP_PREFIX="https://" + if [ ! -z "$cn" ];then + HTTP_PREFIX="https://ghproxy.com/" + fi + + # FlameGraph start + if [ ! -d $serverPath/dynamic-tracking/FlameGraph ];then + if [ ! -f $serverPath/source/FlameGraph.zip ]; then + wget --no-check-certificate -O $serverPath/source/FlameGraph.zip ${HTTP_PREFIX}github.com/brendangregg/FlameGraph/archive/refs/heads/master.zip + fi - echo "开发中..." + cd $serverPath/source && unzip $serverPath/source/FlameGraph.zip + mv $serverPath/source/FlameGraph-master $serverPath/dynamic-tracking/FlameGraph + fi + # FlameGraph end - shell_file=${curPath}/versions/${VERSION}/${OSNAME}.sh + - if [ -f $shell_file ];then - bash -x $shell_file - else + shell_file=${curPath}/versions/${OSNAME}.sh + echo $shell_file + if [ ! -f $shell_file ];then echo '不支持...' > $install_tmp exit 1 fi + + bash -x $shell_file + + echo "cp -rf ${curPath}/shell $serverPath/dynamic-tracking" + cp -rf ${curPath}/shell $serverPath/dynamic-tracking echo "${VERSION}" > $serverPath/dynamic-tracking/version.pl echo '安装完成' > $install_tmp @@ -55,7 +74,7 @@ Install_App() } -Uninstall_Docker() +Uninstall_App() { rm -rf $serverPath/dynamic-tracking echo "Uninstall_App" > $install_tmp diff --git a/plugins/dynamic-tracking/js/dynamic-tracking.js b/plugins/dynamic-tracking/js/dynamic-tracking.js deleted file mode 100755 index e31d7ae97..000000000 --- a/plugins/dynamic-tracking/js/dynamic-tracking.js +++ /dev/null @@ -1,54 +0,0 @@ -function redisPost(method, version, args,callback){ - var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); - - var req_data = {}; - req_data['name'] = 'dynamic-tracking'; - req_data['func'] = method; - req_data['version'] = version; - - if (typeof(args) == 'string'){ - req_data['args'] = JSON.stringify(toArrayObject(args)); - } else { - req_data['args'] = JSON.stringify(args); - } - - $.post('/plugins/run', req_data, function(data) { - layer.close(loadT); - if (!data.status){ - //错误展示10S - layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); - return; - } - - if(typeof(callback) == 'function'){ - callback(data); - } - },'json'); -} - -function redisPostCallbak(method, version, args,callback){ - var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); - - var req_data = {}; - req_data['name'] = 'dynamic-tracking'; - req_data['func'] = method; - args['version'] = version; - - if (typeof(args) == 'string'){ - req_data['args'] = JSON.stringify(toArrayObject(args)); - } else { - req_data['args'] = JSON.stringify(args); - } - - $.post('/plugins/callback', req_data, function(data) { - layer.close(loadT); - if (!data.status){ - layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); - return; - } - - if(typeof(callback) == 'function'){ - callback(data); - } - },'json'); -} diff --git a/plugins/dynamic-tracking/shell/simple_macosx_trace.sh b/plugins/dynamic-tracking/shell/simple_macosx_trace.sh new file mode 100644 index 000000000..a8929266d --- /dev/null +++ b/plugins/dynamic-tracking/shell/simple_macosx_trace.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + + +# debug +# cd /www/server/mdserver-web +# bash /www/server/mdserver-web/plugins/dynamic-tracking/shell/simple_macosx_trace.sh "22431" + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web +# bash /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/dynamic-tracking/shell/simple_macosx_trace.sh "22431" + +# dtrace -x ustackframes=100 -n 'pid$target::mach_msg_trap:entry { @[ustack()] = count(); } tick-30s { exit(0); }' -p 18572 -o out.SystemUIServer_stacks +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/stackcollapse.pl out.SystemUIServer_stacks > kernel.cbt +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/flamegraph.pl kernel.cbt > kernel.svg + +curPath=`pwd` +rootPath=$(dirname "$curPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +PID=$1 + +echo $rootPath # /Users/midoks/Desktop/mwdev/server +echo $curPath # /Users/midoks/Desktop/mwdev/server/mdserver-web + +APP_DIR=${rootPath}/dynamic-tracking +DST_FILE_DIR=${APP_DIR}/trace/PID_${PID} +mkdir -p $DST_FILE_DIR + +DST_FILE=${DST_FILE_DIR}/out.SystemUIServer_stacks + +if [ ! -f $DST_FILE ];then + sudo dtrace -x ustackframes=100 -n 'pid$target::mach_msg_trap:entry { @[ustack()] = count(); } tick-30s { exit(0); }' -p "$PID" -o $DST_FILE +fi + +${APP_DIR}/FlameGraph/stackcollapse.pl $DST_FILE > ${DST_FILE_DIR}/kernel.cbt +${APP_DIR}/FlameGraph/flamegraph.pl ${DST_FILE_DIR}/kernel.cbt > ${DST_FILE_DIR}/main.svg + diff --git a/plugins/dynamic-tracking/shell/simple_trace.sh b/plugins/dynamic-tracking/shell/simple_trace.sh new file mode 100644 index 000000000..d16795236 --- /dev/null +++ b/plugins/dynamic-tracking/shell/simple_trace.sh @@ -0,0 +1,43 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + + +# debug +# cd /www/server/mdserver-web +# bash /www/server/mdserver-web/plugins/dynamic-tracking/shell/simple_trace.sh "2401699" + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +PID=$1 + +echo $rootPath # /Users/midoks/Desktop/mwdev/server +echo $curPath # /Users/midoks/Desktop/mwdev/server/mdserver-web + +APP_DIR=/www/server/dynamic-tracking +DST_FILE_DIR=${APP_DIR}/trace/PID_${PID} +mkdir -p $DST_FILE_DIR + +# DST_FILE=${DST_FILE_DIR}/main.strace + +# perf record -F 99 -p 335584 -g -- sleep 30 + +cd ${DST_FILE_DIR} +perf record -F 99 -p ${PID} -g -- sleep 30 + +perf script > out.perf +${APP_DIR}/FlameGraph/stackcollapse-perf.pl ${DST_FILE_DIR}/out.perf > ${DST_FILE_DIR}/out.folded +${APP_DIR}/FlameGraph/flamegraph.pl ${DST_FILE_DIR}/out.folded > ${DST_FILE_DIR}/main.svg + +# perf record -F 99 -p 2401699 -g -- sleep 30 +# perf script > out.perf +# /www/server/dynamic-tracking/FlameGraph/stackcollapse-perf.pl out.perf > out.folded +# /www/server/dynamic-tracking/FlameGraph/flamegraph.pl out.folded > php-zend-flame-graph.svg + +# ${APP_DIR}/FlameGraph/stackcollapse.pl $DST_FILE > ${DST_FILE_DIR}/kernel.cbt +# ${APP_DIR}/FlameGraph/flamegraph.pl ${DST_FILE_DIR}/kernel.cbt > ${DST_FILE_DIR}/main.svg + diff --git a/plugins/dynamic-tracking/static/css/dynamic-tracking.css b/plugins/dynamic-tracking/static/css/dynamic-tracking.css new file mode 100644 index 000000000..a6c04f524 --- /dev/null +++ b/plugins/dynamic-tracking/static/css/dynamic-tracking.css @@ -0,0 +1,75 @@ +.screen-all { + padding: 5px; +} + +.s-right .panel-default{ + margin-bottom: 10px; +} + +.s-left .panel-default{ + margin-bottom: 10px; +} + + +.data-collect { + width: 45px; + height: 30px; + border: 0; + cursor: pointer; + margin-right: 2px; + margin-top: 10px; + color: #fff; + background-color: #20a53a; + border-color: #20a53a; +} + + +#file_list .list { + list-style: none; + border-top: none; + border: 1px solid #ececec; + overflow-y: auto; +} + +#file_list .list li.active { + background-color: #f2f2f2; +} + +#file_list .tab-con{ + padding: 0px; + margin-top: 10px; +} + +#file_list .list li>span { + vertical-align: top; + display: inline-block; +} + +#file_list .list li { + display: inline-block; + height: 38px; + line-height: 38px; + color: #444; + border-bottom: 1px solid #ececec; + font-size: 13px; + cursor: pointer; + position: relative; + width: 100%; +} + +#file_list .file { + padding-left: 10px; +} + +#file_list .list li .tootls{ + width: 70px; + text-align: right; + padding-right: 10px; + /* display: none; */ + position: absolute; + right: 5px; +} + +/*.tab-con .tab-block.on { + display: inline-block; +}*/ \ No newline at end of file diff --git a/plugins/dynamic-tracking/static/css/ico.css b/plugins/dynamic-tracking/static/css/ico.css new file mode 100644 index 000000000..679eac375 --- /dev/null +++ b/plugins/dynamic-tracking/static/css/ico.css @@ -0,0 +1,11 @@ + +/* menu start */ +.menu .current .menu_dynamic-tracking:hover { + background-image: url("/plugins/file?name=dynamic-tracking&f=ico.png"); +} + +.menu .menu_plugin_dynamic-tracking { + background-image: url("/plugins/file?name=dynamic-tracking&f=ico.png"); +} + +/* menu end */ \ No newline at end of file diff --git a/plugins/dynamic-tracking/static/html/index.html b/plugins/dynamic-tracking/static/html/index.html new file mode 100644 index 000000000..2560182d5 --- /dev/null +++ b/plugins/dynamic-tracking/static/html/index.html @@ -0,0 +1,81 @@ + + +
+
+
+
+ 首页/动态追踪 +
+ + +
+ + +
+ +
+
+ 文件列表 +
+ +
+
+
    +
    +
    +
    + +
    +
    + 火焰图 +
    + +
    + +
    +
    + +
    + +
    +
    + + diff --git a/plugins/dynamic-tracking/static/js/dynamic-tracking.js b/plugins/dynamic-tracking/static/js/dynamic-tracking.js new file mode 100755 index 000000000..466613e40 --- /dev/null +++ b/plugins/dynamic-tracking/static/js/dynamic-tracking.js @@ -0,0 +1,178 @@ +function changeDivH(){ + var l = $(window).height(); + var w = $(window).width(); + + $('#ff_box').css('height',l-80-60); + + $('#file_list').css('height',l-80-60); + $('#file_list .tab-con .list').css('height', l-80-60-70); + + + $('#flame_graph').css('height',l-80-60).css('width',w-300-200-40); + $('#flame_graph iframe').css('height',l-80-60-70); +} + + +$(document).ready(function(){ + var tag = $.getUrlParam('tag'); + if(tag == 'dynamic-tracking'){ + dynamicTrackingLoad(); + } +}); + +function dynamicTrackingLoad(){ + changeDivH(); + $(window).resize(function(){ + changeDivH(); + }); + + //加载采样数据列表 + dtFileList(); +} + + +$('.data-collect').click(function(){ + layer.msg('开始采样',{icon:0,time:2000}); + var pid = $('#searchValue').val(); + + dtPost('simple_trace', '', {pid:pid}, function(rdata){ + // console.log(rdata); + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']}); + }); +}); + + +function dtPost(method, version, args,callback){ + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + var req_data = {}; + req_data['name'] = 'dynamic-tracking'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function dtPostCb(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var pdata = {}; + pdata['name'] = 'dynamic-tracking'; + pdata['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + pdata['args'] = JSON.stringify(toArrayObject(args)); + } else { + pdata['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', pdata, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function dtFileList(){ + dtPost('file_list', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + var alist = rdata.data; + + if (alist.length == 0){ + var em ='
  • 无数据
  • '; + $('#file_list .list').html(em); + return; + } + + var tli = ''; + for (var i = 0; i < alist.length; i++) { + if (i==0){ + tli +='
  • \ + '+alist[i]['name']+'\ + \ + \ + \ + \ +
  • '; + } else{ + tli +='
  • \ + '+alist[i]['name']+'\ + \ + \ + \ + \ +
  • '; + } + } + + $('#file_list .list').html(tli); + + dtGetFile(alist[0]['name']); + $('#file_list li .file').click(function(){ + $('#file_list li').removeClass('active'); + $(this).parent().addClass('active'); + var i = $(this).parent().data('index'); + dtGetFile(alist[i]['name']); + }); + + + $('#file_list li .glyphicon-link').click(function(){ + var i = $(this).parent().parent().data('index'); + var abs_p = alist[i]['abs_path']; + + var durl = '/files/download?filename='+abs_p; + window.open(durl); + }); + + $('#file_list li .glyphicon-trash').click(function(){ + var i = $(this).parent().parent().data('index'); + var f = alist[i]['name']; + dtPost('remove_file_path', '',{file:f}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000}); + if (rdata.status){ + dtFileList(); + } + }); + }); + }); +} + +function dtGetFile(file){ + dtPost('get_file_path', '', {file:file}, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [2, '#000']}); + return; + } + var durl = '/files/download?filename='+rdata.data; + $('#flame_graph .tab-con .tab-block').attr('src',durl); + }); +} \ No newline at end of file diff --git a/plugins/dynamic-tracking/toolkit/readme.md b/plugins/dynamic-tracking/toolkit/readme.md new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/dynamic-tracking/vesions/centos.sh b/plugins/dynamic-tracking/versions/centos.sh similarity index 100% rename from plugins/dynamic-tracking/vesions/centos.sh rename to plugins/dynamic-tracking/versions/centos.sh diff --git a/plugins/dynamic-tracking/versions/debian.sh b/plugins/dynamic-tracking/versions/debian.sh new file mode 100644 index 000000000..c42eff5eb --- /dev/null +++ b/plugins/dynamic-tracking/versions/debian.sh @@ -0,0 +1,26 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +install_tmp=${rootPath}/tmp/mw_install.pl +SYS_ARCH=`arch` + +# apt-get install linux-perf +# apt-get update -y +# strace -p $(ps -ef|grep "pool www" | awk '{print $2}' | grep -v grep | tr '\n' ',' ) + +# perf record -F 99 -p 2401699 -g -- sleep 30 +# perf script > out.perf +# /www/server/dynamic-tracking/FlameGraph/stackcollapse-perf.pl out.perf > out.folded +# /www/server/dynamic-tracking/FlameGraph/flamegraph.pl out.folded > php-zend-flame-graph.svg + +# WWW_PID=$(ps -ef|grep "pool www" | awk '{print $2}' | grep -v grep | tr '\n' ',') +# WWW_PID=${WWW_PID//,/} +# WWW_PID=${var%,} +# perf record -F 99 -p "1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1127,1128,1129,1130,1131,1473,1768,2848,2962,21582,35891" \ +# -g -- sleep 30 \ No newline at end of file diff --git a/plugins/dynamic-tracking/versions/macos.sh b/plugins/dynamic-tracking/versions/macos.sh new file mode 100644 index 000000000..812f66746 --- /dev/null +++ b/plugins/dynamic-tracking/versions/macos.sh @@ -0,0 +1,21 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +install_tmp=${rootPath}/tmp/mw_install.pl +SYS_ARCH=`arch` + +# 关于macosx使用dtrace + +# https://groups.google.com/g/openresty/c/MswlH_8DDHA +# http://dtrace.org/blogs/brendan/2012/11/14/dtracing-in-anger/ + + +# dtrace -x ustackframes=100 -n 'pid$target::mach_msg_trap:entry { @[ustack()] = count(); } tick-30s { exit(0); }' -p 18572 -o out.SystemUIServer_stacks +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/stackcollapse.pl out.SystemUIServer_stacks > kernel.cbt +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/flamegraph.pl kernel.cbt > kernel.svg diff --git a/plugins/dynamic-tracking/vesions/macos.sh b/plugins/dynamic-tracking/versions/ubuntu.sh similarity index 94% rename from plugins/dynamic-tracking/vesions/macos.sh rename to plugins/dynamic-tracking/versions/ubuntu.sh index f71fbaa58..e2ba2d38e 100644 --- a/plugins/dynamic-tracking/vesions/macos.sh +++ b/plugins/dynamic-tracking/versions/ubuntu.sh @@ -8,4 +8,4 @@ rootPath=$(dirname "$rootPath") serverPath=$(dirname "$rootPath") install_tmp=${rootPath}/tmp/mw_install.pl -SYS_ARCH=`arch` \ No newline at end of file +SYS_ARCH=`arch` diff --git a/plugins/mariadb/scripts/backup.py b/plugins/mariadb/scripts/backup.py index 8028774fa..f45c83d95 100755 --- a/plugins/mariadb/scripts/backup.py +++ b/plugins/mariadb/scripts/backup.py @@ -87,7 +87,7 @@ class backupTools: pid = mw.M('databases').dbPos(db_path, db_name).where( 'name=?', (name,)).getField('id') - mw.M('backup').add('type,name,pid,filename,addtime,size', (1, os.path.basename( + mw.M('backup').add('type,name,pid,filename,addtime,size', (3, os.path.basename( filename), pid, filename, endDate, os.path.getsize(filename))) log = "数据库[" + name + "]备份成功,用时[" + str(round(outTime, 2)) + "]秒" mw.writeLog('计划任务', log) @@ -97,7 +97,7 @@ class backupTools: # 清理多余备份 backups = mw.M('backup').where( - 'type=? and pid=?', ('1', pid)).field('id,filename').select() + 'type=? and pid=?', ('3', pid)).field('id,filename').select() num = len(backups) - int(count) if num > 0: diff --git a/plugins/memcached/index.py b/plugins/memcached/index.py index a3d379fad..13cc99969 100755 --- a/plugins/memcached/index.py +++ b/plugins/memcached/index.py @@ -31,8 +31,13 @@ def getServerDir(): def getInitDFile(): - if app_debug: + current_os = mw.getOs() + if current_os == 'darwin': return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() @@ -88,7 +93,7 @@ def checkArgs(data, ck=[]): def status(): data = mw.execShell( - "ps -ef|grep " + getPluginName() + " |grep -v grep | grep -v python | awk '{print $2}'") + "ps aux|grep " + getPluginName() + " |grep -v grep | grep -v python | awk '{print $2}'") if data[0] == '': return 'stop' return 'start' @@ -113,8 +118,8 @@ def initDreplace(): # systemd systemDir = mw.systemdCfgDir() systemService = systemDir + '/memcached.service' - systemServiceTpl = getPluginDir() + '/init.d/memcached.service.tpl' if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/memcached.service.tpl' service_path = mw.getServerDir() se_content = mw.readFile(systemServiceTpl) se_content = se_content.replace('{$SERVER_PATH}', service_path) @@ -137,13 +142,20 @@ def initDreplace(): def memOp(method): file = initDreplace() - if not mw.isAppleSystem(): - data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) if data[1] == '': return 'ok' return data[1] - data = mw.execShell(file + ' ' + method) + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) if data[1] == '': return 'ok' return data[1] @@ -226,9 +238,15 @@ def saveConf(): def initdStatus(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + shell_cmd = 'systemctl status ' + \ getPluginName() + ' | grep loaded | grep "enabled;"' data = mw.execShell(shell_cmd) @@ -238,17 +256,34 @@ def initdStatus(): def initdInstall(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + mw.execShell('systemctl enable ' + getPluginName()) return 'ok' def initdUinstall(): - if not app_debug: - if mw.isAppleSystem(): - return "Apple Computer does not support" + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' mw.execShell('systemctl disable ' + getPluginName()) return 'ok' diff --git a/plugins/msonedrive/class/msodclient.py b/plugins/msonedrive/class/msodclient.py new file mode 100644 index 000000000..d17efab9c --- /dev/null +++ b/plugins/msonedrive/class/msodclient.py @@ -0,0 +1,820 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import io + +sys.path.append(os.getcwd() + "/class/core") +import mw + +import oauthlib +import requests +import datetime +from requests_oauthlib import OAuth2Session + +DEBUG = False + + +def setDebug(d=False): + DEBUG = d + + +class UnauthorizedError(Exception): + pass + + +class ObjectNotFoundError(Exception): + pass + + +class msodclient: + + plugin_dir = '' + server_dir = '' + credential_file = 'credentials.json' + user_conf = "user.conf" + token_file = 'token.pickle' + + def __init__(self, plugin_dir, server_dir): + self.plugin_dir = plugin_dir + self.server_dir = server_dir + self.load() + + def setDebug(self, d=False): + DEBUG = d + + def load(self): + credential_path = os.path.join(self.plugin_dir, self.credential_file) + credential = json.loads(mw.readFile(credential_path)) + # print(credential) + self.credential = credential["onedrive-international"] + + self.authorize_url = '{0}{1}'.format( + self.credential['authority'], + self.credential['authorize_endpoint']) + self.token_url = '{0}{1}'.format( + self.credential['authority'], + self.credential['token_endpoint']) + + self.token_path = os.path.join(self.server_dir, self.token_file) + self.root_uri = self.credential["api_uri"] + "/me/drive/root" + + self.backup_path = 'backup' + + def store_token(self, token): + """存储token""" + enstr = mw.enDoubleCrypt('msodc', json.dumps(token)) + mw.writeFile(self.token_path, enstr) + return True + + def get_store_token(self): + rdata = mw.readFile(self.token_path) + destr = mw.deDoubleCrypt('msodc', rdata) + return json.loads(destr) + + def clear_token(self): + """清除token记录""" + try: + if os.path.isfile(self.token_path): + os.remove(self.token_path) + except: + if DEBUG: + print("清除token失败。") + + def refresh_token(self, origin_token): + """刷新token""" + + os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' + os.environ['OAUTHLIB_IGNORE_SCOPE_CHANGE'] = '1' + refresh_token = origin_token["refresh_token"] + aad_auth = OAuth2Session( + self.credential["client_id"], + scope=self.credential["scopes"], + redirect_uri=self.credential["redirect_uri"]) + + new_token = aad_auth.refresh_token( + self.token_url, + refresh_token=refresh_token, + client_id=self.credential["client_id"], + client_secret=self.credential["client_secret"]) + return new_token + + def get_token_from_authorized_url(self, authorized_url, expected_state=None): + """通过授权编码获取访问token""" + + # 忽略token scope与已请求的scope不一致 + os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' + os.environ['OAUTHLIB_IGNORE_SCOPE_CHANGE'] = '1' + aad_auth = OAuth2Session(self.credential["client_id"], + state=expected_state, + scope=self.credential['scopes'], + redirect_uri=self.credential['redirect_uri']) + + token = aad_auth.fetch_token( + self.token_url, + client_secret=self.credential["client_secret"], + authorization_response=authorized_url) + + return token + + def get_token(self): + token = self.get_store_token() + now = time.time() + + expire_time = token["expires_at"] - 300 + if now >= expire_time: + new_token = self.refresh_token(token) + self.store_token(new_token) + return new_token + + return token + + def get_sign_in_url(self): + """生成签名地址""" + + # Initialize the OAuth client + aad_auth = OAuth2Session(self.credential["client_id"], + scope=self.credential['scopes'], + redirect_uri=self.credential['redirect_uri']) + + sign_in_url, state = aad_auth.authorization_url(self.authorize_url, + prompt='login') + + return sign_in_url, state + + def get_authorized_header(self): + token_obj = self.get_token() + token = token_obj["access_token"] + header = { + "Authorization": "Bearer " + token, + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/67.0.3396.99 Safari/537.36' + } + return header + + def get_user_from_ms(self): + """查询用户信息""" + try: + headers = self.get_authorized_header() + user_api_base = self.credential["api_uri"] + "/me" + # select_user_info_uri = self.build_uri(base=user_api_base) + response = requests.get(user_api_base, headers=headers) + if DEBUG: + print("Debug get user:") + print(response.status_code) + print(response.text) + if response.status_code == 200: + response_data = response.json() + user_principal_name = response_data["userPrincipalName"] + return user_principal_name + except oauthlib.oauth2.rfc6749.errors.InvalidGrantError: + self.clear_auth() + if DEBUG: + print("用户授权已过期。") + return None + + def clear_auth(self): + self.clear_token() + self.clear_user() + + def clear_user(self): + try: + # 清空user + path = os.path.join(self.server_dir, self.user_conf) + if os.path.isfile(path): + os.remove(path) + except: + if DEBUG: + print("清除user失败。") + + def store_user(self): + """更新并存储用户信息""" + user = self.get_user_from_ms() + if user: + path = os.path.join(self.server_dir, self.user_conf) + mw.writeFile(path, user) + else: + raise RuntimeError("无法获取用户信息。") + + # --------------------- 文件操作功能 ---------------------- + + # 取目录路径 + def get_path(self, path): + sep = ":" + if path == '/': + path = '' + if path[-1:] == '/': + path = path[:-1] + if path[:1] != "/" and path[:1] != sep: + path = "/" + path + if path == '/': + path = '' + # if path[:1] != sep: + # path = sep + path + try: + from urllib.parse import quote + except: + from urllib import quote + # path = quote(path) + + return path.replace('//', '/') + + def build_uri(self, path="", operate=None, base=None): + """构建请求URL + + API请求URI格式参考: + https://graph.microsoft.com/v1.0/me/drive/root:/bt_backup/:content + --------------------------------------------- ---------- -------- + base path operate + 各部分之间用“:”连接。 + :param path 子资源路径 + :param operate 对文件进行的操作,比如content,children + :return 请求url + """ + + if base is None: + base = self.root_uri + path = self.get_path(path) + sep = ":" + if operate: + if operate[:1] != "/": + operate = "/" + operate + + if path: + uri = base + sep + path + if operate: + uri += sep + operate + else: + uri = base + if operate: + uri += operate + + return uri + + def get_list(self, path="/"): + """获取存储空间中的所有文件对象""" + + list_uri = self.build_uri(path, operate="/children") + if DEBUG: + print("List uri:") + print(list_uri) + + data = [] + response = requests.get(list_uri, headers=self.get_authorized_header()) + status_code = response.status_code + if status_code == 200: + if DEBUG: + print("DEBUG:") + print(response.json()) + response_data = response.json() + drive_items = response_data["value"] + + for item in drive_items: + tmp = {} + tmp['name'] = item["name"] + tmp['size'] = item["size"] + if "folder" in item: + # print("{} is folder:".format(item["name"])) + # print(item["folder"]) + tmp["type"] = None + tmp['download'] = "" + if "file" in item: + tmp["type"] = "File" + tmp['download'] = item["@microsoft.graph.downloadUrl"] + # print("{} is file:".format(item["name"])) + # print(item["file"]) + + formats = ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"] + t = None + for time_format in formats: + try: + t = datetime.datetime.strptime( + item["lastModifiedDateTime"], time_format) + break + except: + continue + t += datetime.timedelta(hours=8) + ts = int( + (time.mktime(t.timetuple()) + t.microsecond / 1000000.0)) + tmp['time'] = ts + data.append(tmp) + + mlist = {'path': path, 'list': data} + return mlist + + def get_object(self, object_name): + """查询对象信息""" + try: + get_uri = self.build_uri(path=object_name) + if DEBUG: + print("Get uri:") + print(get_uri) + response = requests.get(get_uri, + headers=self.get_authorized_header()) + if response.status_code in [200]: + response_data = response.json() + if DEBUG: + print("Object info:") + print(response_data) + return response_data + if response.status_code == 404: + if DEBUG: + print("对象不存在。") + if DEBUG: + print("Get Object debug:") + print(response.status_code) + print(response.text) + except Exception as e: + if DEBUG: + print("Get object has excepiton:") + print(e) + return None + + def is_folder(self, obj): + if "folder" in obj: + return True + return False + + def delete_object_by_os(self, object_name): + """删除对象 + + :param object_name: + :return: True 删除成功 + 其他 删除失败 + """ + obj = self.get_object(object_name) + if obj is None: + if DEBUG: + print("对象不存在,删除操作未执行。") + return True + if self.is_folder(obj): + child_count = obj["folder"]["childCount"] + if child_count > 0: + if DEBUG: + print("文件夹不是空文件夹无法删除。") + return False + + headers = self.get_authorized_header() + delete_uri = self.build_uri(object_name) + response = requests.delete(delete_uri, headers=headers) + if response.status_code == 204: + if DEBUG: + print("对象: {} 已被删除。".format(object_name)) + return True + return False + + def delete_object(self, object_name, retries=2): + """删除对象 + + :param object_name: + :param retries: 重试次数,默认2次 + :return: True 删除成功 + 其他 删除失败 + """ + + try: + return self.delete_object_by_os(object_name) + except Exception as e: + print("删除文件异常:") + print(e) + + # 重试 + if retries > 0: + print("重新尝试删除文件{}...".format(object_name)) + return self.delete_object( + object_name, + retries=retries - 1) + return False + + def build_object_name(self, data_type, file_name): + """根据数据类型构建对象存储名称 + + :param data_type: + :param file_name: + :return: + """ + + import re + + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + file_regx = prefix_dict.get(data_type) + "_(.+)_20\d+_\d+(?:\.|_)" + sub_search = re.search(file_regx, file_name) + sub_path_name = "" + if sub_search: + sub_path_name = sub_search.groups()[0] + sub_path_name += '/' + + # 构建OS存储路径 + object_name = self.backup_path + '/' + \ + data_type + '/' + \ + sub_path_name + \ + file_name + + if object_name[:1] == "/": + object_name = object_name[1:] + + return object_name + + def delete_file(self, file_name, data_type=None): + """删除文件 + + 根据传入的文件名称和文件数据类型构建对象名称,再删除 + :param file_name: + :param data_type: 数据类型 site/database/path + :return: True 删除成功 + 其他 删除失败 + """ + + object_name = self.build_object_name(data_type, file_name) + return self.delete_object(object_name) + + def create_dir_by_step(self, parent_folder, sub_folder): + create_uri = self.build_uri(path=parent_folder, operate="/children") + + if DEBUG: + print("Create dir uri:") + print(create_uri) + post_data = { + "name": sub_folder, + "folder": {"@odata.type": "microsoft.graph.folder"}, + "@microsoft.graph.conflictBehavior": "fail" + } + + headers = self.get_authorized_header() + headers.update({"Content-type": "application/json"}) + response = requests.post(create_uri, headers=headers, json=post_data) + if response.status_code in [201, 409]: + if DEBUG: + if response.status_code == 409: + print("目录:{} 已经存在。".format(sub_folder)) + return True + else: + if DEBUG: + print("目录:{} 创建失败:".format(sub_folder)) + print(response.status_code) + print(response.text) + return False + + def create_dir(self, dir_name): + """创建远程目录 + + # API 请求结构 + # POST /me/drive/root/children + # or + # POST /me/drive/root:/bt_backup/:/children + # Content - Type: application / json + + # { + # "name": "New Folder", + # "folder": {}, + # "@microsoft.graph.conflictBehavior": "rename" + # } + + # Response: status code == 201 新创建/ 409 已存在 + # @microsoft.graph.conflictBehavior: fail/rename/replace + + :param dir_name: 目录名称 + :param parent_id: 父目录ID + :return: True/False + """ + + dir_name = self.get_path(dir_name.strip()) + onedrive_business_reserved = r"[\*<>?:|#%]" + if re.search(onedrive_business_reserved, dir_name) \ + or dir_name[-1] == "." or dir_name[:1] == "~": + if DEBUG: + print("文件夹名称包含非法字符。") + return False + + parent_folder = self.get_path(os.path.split(dir_name)[0]) + sub_folder = os.path.split(dir_name)[1] + + # print("create_dir:", dir_name) + obj = self.get_object(dir_name) + # 判断对象是否存在 + if obj is None: + if not self.create_dir_by_step(parent_folder, sub_folder): + + # 兼容OneDrive 商业版文件夹创建 + folder_array = dir_name.split("/") + parent_folder = self.get_path(folder_array[0]) + for i in range(1, len(folder_array)): + sub_folder = folder_array[i] + if DEBUG: + print("Parent folder: {}".format(parent_folder)) + print("Sub folder: {}".format(sub_folder)) + if self.create_dir_by_step(parent_folder, sub_folder): + parent_folder += "/" + folder_array[i] + else: + return False + return True + else: + if self.is_folder(obj): + if DEBUG: + print("文件夹已存在。") + return True + + def resumable_upload(self, + local_file_name, + object_name=None, + progress_callback=None, + progress_file_name=None, + multipart_threshold=1024 * 1024 * 2, + part_size=1024 * 1024 * 5, + store_dir="/tmp", + auto_cancel=True, + retries=5, + ): + """断点续传 + + :param local_file_name: 本地文件名称 + :param object_name: 指定OS中存储的对象名称 + :param part_size: 指定分片上传的每个分片的大小。必须是320*1024的整数倍。 + :param multipart_threshold: 文件长度大于该值时,则用分片上传。 + :param progress_callback: 进度回调函数,默认是把进度信息输出到标准输出。 + :param progress_file_name: 进度信息保存文件,进度格式参见[report_progress] + :param store_dir: 上传分片存储目录, 默认/tmp。 + :param auto_cancel: 当备份失败是否自动取消上传记录 + :param retries: 上传重试次数 + :return: True上传成功/False or None上传失败 + """ + + try: + file_size_separation_value = 4 * 1024 * 1024 + if part_size % 320 != 0: + if DEBUG: + print("Part size 必须是320的整数倍。") + return False + + if object_name is None: + temp_file_name = os.path.split(local_file_name)[1] + object_name = os.path.join(self.backup_path, temp_file_name) + + # if progress_file_name: + # os.environ[PROGRESS_FILE_NAME] = progress_file_name + # progress_callback = report_progress + + print("|-正在上传到 {}...".format(object_name)) + dir_name = os.path.split(object_name)[0] + if not self.create_dir(dir_name): + if DEBUG: + print("目录创建失败!") + return False + + local_file_size = os.path.getsize(local_file_name) + # if local_file_size < file_size_separation_value: + if False: + # 小文件上传 + upload_uri = self.build_uri(path=object_name, + operate="/content") + if DEBUG: + print("Upload uri:") + print(upload_uri) + headers = self.get_authorized_header() + # headers.update({ + # "Content-Type": "application/octet-stream" + # }) + # files = {"file": (object_name, open(local_file_name, "rb"))} + file_data = open(local_file_name, "rb") + response = requests.put(upload_uri, + headers=headers, + data=file_data) + if DEBUG: + print("status code:") + print(response.status_code) + # print(response.text) + if response.status_code in [201, 200]: + if DEBUG: + print("文件上传成功!") + return True + else: + # 大文件上传 + + # 1. 创建上传session + create_session_uri = self.build_uri( + path=object_name, + operate="createUploadSession") + headers = self.get_authorized_header() + response = requests.post(create_session_uri, headers=headers) + if response.status_code == 200: + response_data = response.json() + upload_url = response_data["uploadUrl"] + expiration_date_time = response_data["expirationDateTime"] + + if DEBUG: + print("上传session已建立。") + print("Upload url: {}".format(upload_url)) + print("Expiration datetime: {}".format( + expiration_date_time)) + + # 2. 分片上传文件 + requests.adapters.DEFAULT_RETRIES = 1 + session = requests.session() + session.keep_alive = False + + # 开始分片上传 + import math + parts = int(math.ceil(local_file_size / part_size)) + for i in range(parts): + if DEBUG: + if i == parts - 1: + num = "最后" + else: + num = "第{}".format(i + 1) + print("正在上传{}部分...".format(num)) + + upload_range_start = i * part_size + upload_range_end = min(upload_range_start + part_size, + local_file_size) + content_length = upload_range_end - upload_range_start + + headers = { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/67.0.3396.99 Safari/537.36' + } + # 开发记录 + # Content-Range和标准的http请求头中的Range作用有所不同 + # Content-Range是OneDrive自定义的分片上传标识,格式也不一样 + headers.update({ + "Content-Length": repr(content_length), + "Content-Range": "bytes {}-{}/{}".format( + upload_range_start, + upload_range_end - 1, + local_file_size), + "Content-Type": "application/octet-stream" + }) + + if DEBUG: + print("Headers:") + print(headers) + + '''# TODO 优化read的读取占用内存''' + f = io.open(local_file_name, "rb") + f.seek(upload_range_start) + upload_data = f.read(content_length) + sub_response = session.put(upload_url, + headers=headers, + data=upload_data) + + expected_status_code = [200, 201, 202] + if sub_response.status_code in expected_status_code: + if DEBUG: + print("Response status code: {}, " + "bytes {}-{} 已上传成功。".format( + sub_response.status_code, + upload_range_start, + upload_range_end - 1) + ) + print(sub_response.text) + if sub_response.status_code in [200, 201]: + if DEBUG: + print("文件 {} 上传成功。".format(object_name)) + return True + else: + print(sub_response.status_code) + print(sub_response.text) + _error_msg = "Bytes {}-{} 分片上传失败。".format( + upload_range_start, + upload_range_end + ) + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += _error_msg + raise RuntimeError(_error_msg) + + time.sleep(0.5) + else: + raise RuntimeError("session创建失败。") + + except UnauthorizedError as e: + _error_msg = str(e) + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += _error_msg + print(_error_msg) + return False + except Exception as e: + print("文件上传出现错误:") + print(e) + + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件{}上传出现错误:{}".format(object_name, str(e)) + + try: + if upload_url: + if DEBUG: + print("正在清理上传session.") + session.delete(upload_url) + except: + pass + finally: + try: + f.close() + except: + pass + try: + session.close() + except: + pass + + # 重试断点续传 + if retries > 0: + print("重试上传文件....") + return self.resumable_upload( + local_file_name, + object_name=object_name, + store_dir=store_dir, + part_size=part_size, + multipart_threshold=multipart_threshold, + progress_callback=progress_callback, + progress_file_name=progress_file_name, + retries=retries - 1, + ) + else: + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件{}上传失败。".format(object_name) + return False + + def upload_abs_file(self, file_name, remote_dir, *args, **kwargs): + """按照数据类型上传文件 + + :param file_name: 上传文件名称 + :param data_type: 数据类型 site/database/path + :return: True/False + """ + try: + import re + # 根据数据类型提取子分类名称 + # 比如data_type=database,子分类名称是数据库的名称。 + # 提取方式是从file_name中利用正则规则去提取。 + self.error_msg = "" + + file_name = os.path.abspath(file_name) + temp_name = os.path.split(file_name)[1] + object_name = 'backup/' + temp_name + + print(file_name) + print(object_name) + + return self.resumable_upload(file_name, + object_name=object_name, + *args, + **kwargs) + except Exception as e: + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件上传出现错误:{}".format(str(e)) + return False + + def upload_file(self, file_name, data_type, *args, **kwargs): + """按照数据类型上传文件 + + :param file_name: 上传文件名称 + :param data_type: 数据类型 site/database/path + :return: True/False + """ + try: + import re + # 根据数据类型提取子分类名称 + # 比如data_type=database,子分类名称是数据库的名称。 + # 提取方式是从file_name中利用正则规则去提取。 + self.error_msg = "" + + if not file_name or not data_type: + _error_msg = "文件参数错误。" + print(_error_msg) + self.error_msg = _error_msg + return False + + file_name = os.path.abspath(file_name) + temp_name = os.path.split(file_name)[1] + object_name = self.build_object_name(data_type, temp_name) + + # dir_name = os.path.dirname(object_name) + # self.create_dir(dir_name) + if DEBUG: + print(file_name) + print(object_name) + print(dir_name) + + return self.resumable_upload(file_name, + object_name=object_name, + *args, + **kwargs) + except Exception as e: + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件上传出现错误:{}".format(str(e)) + return False diff --git a/plugins/msonedrive/credentials.json b/plugins/msonedrive/credentials.json new file mode 100644 index 000000000..123443172 --- /dev/null +++ b/plugins/msonedrive/credentials.json @@ -0,0 +1,12 @@ +{ + "onedrive-international": { + "client_id": "08125e6b-6502-4ac9-9548-ad682f00848d", + "client_secret": "0WA8Q~sZkZFZKv50ryP4ux~.fpVtbHw7BuTZmbQB", + "authority": "https://login.microsoftonline.com/common", + "token_endpoint": "/oauth2/v2.0/token", + "authorize_endpoint": "/oauth2/v2.0/authorize", + "scopes": "offline_access Files.ReadWrite.All User.Read", + "redirect_uri": "http://localhost", + "api_uri": "https://graph.microsoft.com/v1.0" + } +} \ No newline at end of file diff --git a/plugins/msonedrive/ico.png b/plugins/msonedrive/ico.png new file mode 100644 index 000000000..fb837e43f Binary files /dev/null and b/plugins/msonedrive/ico.png differ diff --git a/plugins/msonedrive/index.html b/plugins/msonedrive/index.html new file mode 100644 index 000000000..410ea7984 --- /dev/null +++ b/plugins/msonedrive/index.html @@ -0,0 +1,311 @@ + +
    +
    + + +
    +
      +
      + + + +
      + +
      +
      + + + +
      名称大小更新时间操作
      +
      +
      +
      + + + + + + + \ No newline at end of file diff --git a/plugins/msonedrive/index.py b/plugins/msonedrive/index.py new file mode 100644 index 000000000..2a451df2c --- /dev/null +++ b/plugins/msonedrive/index.py @@ -0,0 +1,351 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + + +# print(sys.platform) +if sys.platform != "darwin": + os.chdir("/www/server/mdserver-web") + + +sys.path.append(os.getcwd() + "/class/core") +import mw +import db + +_ver = sys.version_info +is_py2 = (_ver[0] == 2) +is_py3 = (_ver[0] == 3) + +DEBUG = False + +if is_py2: + reload(sys) + sys.setdefaultencoding('utf-8') + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'msonedrive' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def in_array(name, arr=[]): + for x in arr: + if name == x: + return True + return False + + +sys.path.append(getPluginDir() + "/class") +from msodclient import msodclient + + +msodc = msodclient(getPluginDir(), getServerDir()) +msodc.setDebug(False) + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + return 'start' + + +def isAuthApi(): + cfg = getServerDir() + "/user.conf" + if os.path.exists(cfg): + return True + return False + + +def getConf(): + if not isAuthApi(): + sign_in_url, state = msodc.get_sign_in_url() + return mw.returnJson(False, "未授权!", {'auth_url': sign_in_url}) + return mw.returnJson(True, "OK") + + +def setAuthUrl(): + args = getArgs() + data = checkArgs(args, ['url']) + if not data[0]: + return data[1] + + url = args['url'] + + try: + if url.startswith("http://"): + url = url.replace("http://", "https://") + token = msodc.get_token_from_authorized_url(authorized_url=url) + msodc.store_token(token) + msodc.store_user() + return mw.returnJson(True, "授权成功!") + except Exception as e: + return mw.returnJson(False, "授权失败2!:" + str(e)) + return mw.returnJson(False, "授权失败!:" + str(e)) + + +def clearAuth(): + cfg = getServerDir() + "/user.conf" + if os.path.exists(cfg): + os.remove(cfg) + + token = getServerDir() + "/token.pickle" + if os.path.exists(token): + os.remove(token) + + return mw.returnJson(True, "清空授权成功!") + + +def getList(): + cfg = getServerDir() + "/user.conf" + if not os.path.exists(cfg): + return mw.returnJson(False, "未配置,请点击`授权`", []) + + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + try: + flist = msodc.get_list(args['path']) + return mw.returnJson(True, "ok", flist) + except Exception as e: + return mw.returnJson(False, str(e), []) + + +def createDir(): + cfg = getServerDir() + "/user.conf" + if not os.path.exists(cfg): + return mw.returnJson(False, "未配置OneDrive,请点击`授权`", []) + + args = getArgs() + data = checkArgs(args, ['path', 'name']) + if not data[0]: + return data[1] + + file = args['path'] + "/" + args['name'] + isok = msodc.create_dir(file) + if isok: + return mw.returnJson(True, "创建成功") + return mw.returnJson(False, "创建失败") + + +def deleteDir(): + args = getArgs() + data = checkArgs(args, ['dir_name', 'path']) + if not data[0]: + return data[1] + + file = args['path'] + "/" + args['dir_name'] + file = file.strip('/') + isok = msodc.delete_object(file) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "文件不为空,删除失败!") + + +def deleteFile(): + args = getArgs() + data = checkArgs(args, ['path', 'filename']) + if not data[0]: + return data[1] + + file = args['path'] + "/" + args['filename'] + file = file.strip('/') + isok = msodc.delete_object(file) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "删除失败") + + +def findPathName(path, filename): + f = os.scandir(path) + l = [] + for ff in f: + t = {} + if ff.name.find(filename) > -1: + t['filename'] = path + '/' + ff.name + l.append(t) + return l + + +def backupAllFunc(stype): + if not isAuthApi(): + mw.echoInfo("未授权API,无法使用!!!") + return '' + + os.chdir(mw.getRunDir()) + backup_dir = mw.getBackupDir() + run_dir = mw.getRunDir() + + stype = sys.argv[1] + name = sys.argv[2] + num = sys.argv[3] + + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + + backups = [] + sql = db.Sql() + + # print("stype:", stype) + # 提前获取-清理多余备份 + if stype == 'site': + pid = sql.table('sites').where('name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('0', pid)).field('id,filename').select() + if stype == 'database': + db_path = mw.getServerDir() + '/mysql' + pid = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('1', pid)).field('id,filename').select() + if stype == 'path': + backup_path = backup_dir + '/path' + _name = 'path_{}'.format(os.path.basename(name)) + backups = findPathName(backup_path, _name) + + # 其他类型关系性数据库(mysql类的) + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + db_path = mw.getServerDir() + '/' + plugin_name + pid = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('1', pid)).field('id,filename').select() + + args = stype + " " + name + " " + num + cmd = 'python3 ' + run_dir + '/scripts/backup.py ' + args + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + args = "database " + name + " " + num + cmd = 'python3 ' + run_dir + '/plugins/' + \ + plugin_name + '/scripts/backup.py ' + args + + if stype == 'path': + name = os.path.basename(name) + + # print("cmd:", cmd) + os.system(cmd) + + # 开始执行上传信息. + if stype.find('database_') > -1: + bk_name = 'database' + plugin_name = stype.replace('database_', '') + bk_prefix = plugin_name + '/db' + stype = 'database' + else: + bk_prefix = prefix_dict[stype] + bk_name = stype + + find_path = backup_dir + '/' + bk_name + '/' + bk_prefix + '_' + name + find_new_file = "ls " + find_path + \ + "_* | grep '.gz' | cut -d \ -f 1 | awk 'END {print}'" + + # print(find_new_file) + + filename = mw.execShell(find_new_file)[0].strip() + if filename == "": + mw.echoInfo("not find upload file!") + return False + + mw.echoInfo("准备上传文件 {}".format(filename)) + mw.echoStart('开始上传') + msodc.upload_file(filename, stype) + mw.echoEnd('上传成功') + + # print(backups) + backups = sorted(backups, key=lambda x: x['filename'], reverse=False) + mw.echoStart('开始删除远程备份') + num = int(num) + sep = len(backups) - num + if sep > -1: + for backup in backups: + fn = os.path.basename(backup['filename']) + msodc.delete_file(fn, stype) + mw.echoInfo("---已清理远程过期备份文件:" + fn) + sep -= 1 + if sep < 0: + break + mw.echoEnd('结束删除远程备份') + + return '' + + +def installPreInspection(): + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'set_auth_url': + print(setAuthUrl()) + elif func == 'clear_auth': + print(clearAuth()) + elif func == "get_list": + print(getList()) + elif func == "create_dir": + print(createDir()) + elif func == "delete_dir": + print(deleteDir()) + elif func == 'delete_file': + print(deleteFile()) + elif in_array(func, ['site', 'database', 'path']) or func.find('database_') > -1: + print(backupAllFunc(func)) + else: + print('error') diff --git a/plugins/msonedrive/info.json b/plugins/msonedrive/info.json new file mode 100644 index 000000000..0fae6c8fd --- /dev/null +++ b/plugins/msonedrive/info.json @@ -0,0 +1,14 @@ +{ + "hook":["backup"], + "title": "OneDrive", + "tip": "lib", + "name": "msonedrive", + "type": "sort", + "ps": "微软家的云网盘服务。", + "versions": "1.0", + "shell": "install.sh", + "checks": "server/msonedrive", + "author": "midoks", + "date": "2023-8-18", + "pid": "4" +} diff --git a/plugins/msonedrive/install.sh b/plugins/msonedrive/install.sh new file mode 100644 index 000000000..549c9012d --- /dev/null +++ b/plugins/msonedrive/install.sh @@ -0,0 +1,41 @@ +#!/bin/bash +PATH=/www/server/panel/pyenv/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +install_tmp=${rootPath}/tmp/mw_install.pl +VERSION=$2 + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +Install_App() +{ + pip install requests-oauthlib==1.3.0 + mkdir -p $serverPath/msonedrive + echo '正在安装脚本文件...' > $install_tmp + + echo "${VERSION}" > $serverPath/msonedrive/version.pl + echo '安装完成' > $install_tmp + + echo "Successify" +} + + +Uninstall_App() +{ + rm -rf $serverPath/msonedrive +} + +if [ "${1}" == 'install' ];then + Install_App +elif [ "${1}" == 'uninstall' ];then + Uninstall_App +else + echo 'Error!'; +fi diff --git a/plugins/msonedrive/js/msonedrive.js b/plugins/msonedrive/js/msonedrive.js new file mode 100644 index 000000000..929a63468 --- /dev/null +++ b/plugins/msonedrive/js/msonedrive.js @@ -0,0 +1,258 @@ + +function msodPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'msonedrive', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function createDir(){ + layer.open({ + type: 1, + area: "400px", + title: "创建目录", + closeBtn: 1, + shift: 5, + shadeClose: false, + btn: ['确定','取消'], + content:'
      \ +

      \ + 目录名称:\ + \ +

      \ +
      ', + success:function(){ + $("input[name='newPath']").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(index,layero){ + var name = $("input[name='newPath']").val(); + if(name == ''){ + layer.msg('目录名称不能为空!',{icon:2}); + return; + } + var path = $("#myPath").val(); + var dirname = name; + var loadT = layer.msg('正在创建目录['+dirname+']...',{icon:16,time:0,shade: [0.3, '#000']}); + msodPost('create_dir', {path:path,name:dirname}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if(rdata.status) { + showMsg(rdata.msg, function(){ + layer.close(index); + odList(path); + } ,{icon:1}, 2000); + } else{ + layer.msg(rdata.msg,{icon:2}); + } + }); + } + }); +} + + +//设置API +function authApi(){ + + msodPost('conf', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + // console.log(rdata); + // console.log(rdata.data.auth_url); + var apicon = ''; + if (rdata.status){ + + var html = ''; + html += ''; + + var loadOpen = layer.open({ + type: 1, + title: '已授权', + area: '240px', + content:'
      '+html+'
      ', + success: function(){ + $('#clear_auth').click(function(){ + msodPost('clear_auth', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + layer.close(loadOpen); + odList('/'); + },{icon:rdata.status?1:2},2000); + }); + }); + } + }); + return true; + + } else{ + apicon = '
      '+$("#check_api").html()+'
      '; + } + + var layer_auth = layer.open({ + type: 1, + area: "620px", + title: "OneDrive授权", + closeBtn: 1, + shift: 5, + shadeClose: false, + content:apicon, + success:function(layero,index){ + // console.log(layero,index); + if (!rdata.status){ + $('.check_api .step_two_url').val(rdata.data['auth_url']); + $('.check_api .open_btlink').attr('href',rdata.data['auth_url']); + + $('.check_api .ico-copy').click(function(){ + copyPass(rdata.data['auth_url']); + }); + + $('.check_api .set_auth_btn').click(function(){ + + var url = $('.check_api .OneDrive').val(); + if ( url == ''){ + layer.msg("验证URL不能为空",{icon:2}); + return; + } + // console.log(url); + msodPost('set_auth_url', {url:url}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var show_time = 2000; + if (!rdata.status){ + show_time = 10000; + } + + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(layer_auth); + odList('/'); + } + },{icon:rdata.status?1:2},show_time); + }); + }); + + + } + + } + }); + }); +} + +//计算当前目录偏移 +function upPathLeft(){ + var UlWidth = $(".place-input ul").width(); + var SpanPathWidth = $(".place-input").width() - 20; + var Ml = UlWidth - SpanPathWidth; + if(UlWidth > SpanPathWidth ){ + $(".place-input ul").css("left",-Ml) + } + else{ + $(".place-input ul").css("left",0) + } +} + +function odList(path){ + msodPost('get_list', {path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if(rdata.status === false){ + showMsg(rdata.msg,function(){ + authApi(); + },{icon:2},2000); + return; + } + + var mlist = rdata.data; + var listBody = '' + var listFiles = '' + for(var i=0;i\'+mlist.list[i].name+'\ + -\ + -\ + 删除' + }else{ + listFiles += '\'+mlist.list[i].name+'\ + '+toSize(mlist.list[i].size)+'\ + '+getLocalTime(mlist.list[i].time)+'\ + 下载 | 删除' + } + } + listBody += listFiles; + + var pathLi=''; + var tmp = path.split('/') + var pathname = ''; + var n = 0; + for(var i=0;i 0 && tmp[i] == '') continue; + var dirname = tmp[i]; + if(dirname == '') { + dirname = '根目录'; + n++; + } + pathname += '/' + tmp[i]; + pathname = pathname.replace('//','/'); + pathLi += '
    • '+dirname+'
    • '; + } + var um = 1; + if(tmp[tmp.length-1] == '') um = 2; + var backPath = tmp.slice(0,tmp.length-um).join('/') || '/'; + $('#myPath').val(path); + $(".upyunCon .place-input ul").html(pathLi); + $(".upyunlist .list-list").html(listBody); + + upPathLeft(); + + $('#backBtn').unbind().click(function() { + odList(backPath); + }); + + $('.upyunCon .refreshBtn').unbind().click(function(){ + odList(path); + }); + }); +} + + +//删除文件 +function deleteFile(name, is_dir){ + if (is_dir === false){ + safeMessage('删除文件','删除后将无法恢复,真的要删除['+name+']吗?',function(){ + var path = $("#myPath").val(); + var filename = name; + msodPost('delete_file', {filename:filename,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + odList(path); + },{icon:rdata.status?1:2},2000); + }); + }); + } else { + safeMessage('删除文件夹','删除后将无法恢复,真的要删除['+name+']吗?',function(){ + var path = $("#myPath").val(); + msodPost('delete_dir', {dir_name:name,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + odList(path); + },{icon:rdata.status?1:2},2000); + }); + }); + } +} \ No newline at end of file diff --git a/plugins/msonedrive/t/test.py b/plugins/msonedrive/t/test.py new file mode 100644 index 000000000..565f507ac --- /dev/null +++ b/plugins/msonedrive/t/test.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# coding: utf-8 + +# python3 plugins/msonedrive/t/test.py + + +# https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/08125e6b-6502-4ac9-9548-ad682f00848d/objectId/62b1d655-9828-47ed-be99-65eb18c3a929/isMSAApp~/false/defaultBlade/Overview/appSignInAudience/AzureADandPersonalMicrosoftAccount/servicePrincipalCreated~/true + +# 0WA8Q~sZkZFZKv50ryP4ux~.fpVtbHw7BuTZmbQB +# client_id:d9878fac-8526-4ff6-8036-e1c92dd9dd80 + +# 08125e6b-6502-4ac9-9548-ad682f00848d + + +# https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id=d9878fac-8526-4ff6-8036-e1c92dd9dd80&redirect_uri=http://localhost&scope=offline_access+Files.ReadWrite.All+User.Read&state=cwopMdHiPIkze4MvgFL6WfSl8LdYAl&prompt=login + + +# http://localhost/?code=M.C106_BAY.2.3e12c859-6107-0c5b-9ef4-14b3fb8269ba&state=JzHdzHXmA7x6zl7Be6cJ6uOlf9Bg69 + + +# python3 /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/msonedrive/index.py site t1.cn 3 +# python3 /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/msonedrive/index.py database t1 3 +# python3 +# /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/msonedrive/index.py +# path /Users/midoks/Desktop/dev/python 3 + + +import sys +import io +import os +import time +import re +import json + + +from requests_oauthlib import OAuth2Session + +sys.path.append(os.getcwd() + "/class/core") +import mw + + +def getPluginName(): + return 'msonedrive' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +sys.path.append(getPluginDir() + "/class") +from msodclient import msodclient + +msodclient.setDebug(True) +msodc = msodclient(getPluginDir(), getServerDir()) + + +# sign_in_url, state = msodc.get_sign_in_url() +# print(sign_in_url) + + +def set_auth_url(url): + try: + if url.startswith("http://"): + url = url.replace("http://", "https://") + token = msodc.get_token_from_authorized_url( + authorized_url=url) + msodc.store_token(token) + msodc.store_user() + return mw.returnJson(True, "授权成功!") + except Exception as e: + print(e) + return mw.returnJson(False, "授权失败2!:" + str(e)) + return mw.returnJson(False, "授权失败!:" + str(e)) + +# url = 'http://localhost/?code=M.C106_BAY.2.310112f3-a158-c400-9667-d158cbd1de6c&state=jEJz0ucR9bpZYD9PGxp2GgRDotrzO6' +# token = set_auth_url(url) +# print(token) + +# token = msodc.get_token() +# print("token:", token) + +# t = msodc.get_list('/backup') +# print(t) + +# t = msodc.create_dir('backup') +# print(t) + +# t = msodc.delete_object('backup') +# print(t) + + +t = msodc.upload_file('web_t1.cn_20230830_134549.tar.gz', 'site') +print(t) +# print(msodc.error_msg) + +# /Users/midoks/Desktop/mwdev/server/mdserver-web/paramiko.log +# backup/site/paramiko.log +# |-正在上传到 backup/site/paramiko.log... +# True + + +# t = msodc.upload_abs_file( +# 'web_t1.cn_20230830_134549.tar.gz', 'site') +# print(t) +# print(msodc.error_msg) diff --git a/plugins/mysql-apt/index.py b/plugins/mysql-apt/index.py index a82a460d7..8deeae18f 100755 --- a/plugins/mysql-apt/index.py +++ b/plugins/mysql-apt/index.py @@ -55,19 +55,20 @@ def is_number(s): def getArgs(): args = sys.argv[2:] - tmp = {} args_len = len(args) - if args_len == 1: t = args[0].strip('{').strip('}') - t = t.split(':') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] tmp[t[0]] = t[1] elif args_len > 1: for i in range(len(args)): - t = args[i].split(':') + t = args[i].split(':', 1) tmp[t[0]] = t[1] - return tmp @@ -2195,40 +2196,10 @@ def updateSlaveSSH(version=''): def getSlaveList(version=''): - db = pMysqlDb() dlist = db.query('show slave status') - ret = [] - for x in range(0, len(dlist)): - tmp = {} - tmp['Master_User'] = dlist[x]["Master_User"] - tmp['Master_Host'] = dlist[x]["Master_Host"] - tmp['Master_Port'] = dlist[x]["Master_Port"] - tmp['Master_Log_File'] = dlist[x]["Master_Log_File"] - tmp['Slave_IO_Running'] = dlist[x]["Slave_IO_Running"] - tmp['Slave_SQL_Running'] = dlist[x]["Slave_SQL_Running"] - tmp['Last_Error'] = dlist[x]["Last_Error"] - tmp['Last_IO_Error'] = dlist[x]["Last_IO_Error"] - tmp['Last_SQL_Error'] = dlist[x]["Last_SQL_Error"] - tmp['Slave_SQL_Running_State'] = dlist[x]["Slave_SQL_Running_State"] - - tmp['Error'] = '' - if tmp['Last_Error'] != '': - tmp['Error'] = tmp['Last_Error'] - - if tmp['Last_IO_Error'] != '': - tmp['Error'] = tmp['Last_IO_Error'] - - if tmp['Last_SQL_Error'] != '': - tmp['Error'] = tmp['Last_SQL_Error'] - - if tmp['Error'] == '': - tmp['Error'] = tmp['Slave_SQL_Running_State'] - - ret.append(tmp) data = {} - data['data'] = ret - + data['data'] = dlist return mw.getJson(data) @@ -2751,8 +2722,8 @@ def installPreInspection(version): if not sysName in ('debian', 'ubuntu'): return '仅支持debian,ubuntu' - if (sysName == 'debian' and not sysId in('11', '10')): - return 'debian支持10,11' + if (sysName == 'debian' and not sysId in('12', '11', '10')): + return 'debian支持10,11,12' if (sysName == 'ubuntu' and version == '5.7' and not sysId in ('18.04')): return "Ubuntu Apt MySQL[" + version + "] 仅支持18.04" diff --git a/plugins/mysql-yum/index.py b/plugins/mysql-yum/index.py index 2539c7de9..fda6c4788 100755 --- a/plugins/mysql-yum/index.py +++ b/plugins/mysql-yum/index.py @@ -46,19 +46,20 @@ def getServerDir(): def getArgs(): args = sys.argv[2:] - tmp = {} args_len = len(args) - if args_len == 1: t = args[0].strip('{').strip('}') - t = t.split(':') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] tmp[t[0]] = t[1] elif args_len > 1: for i in range(len(args)): - t = args[i].split(':') + t = args[i].split(':', 1) tmp[t[0]] = t[1] - return tmp diff --git a/plugins/mysql/index.py b/plugins/mysql/index.py index 4b9d4cb92..092598e91 100755 --- a/plugins/mysql/index.py +++ b/plugins/mysql/index.py @@ -45,8 +45,12 @@ def getServerDir(): def getInitDFile(): - if app_debug: + current_os = mw.getOs() + if current_os == 'darwin': return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() return '/etc/init.d/' + getPluginName() @@ -524,12 +528,26 @@ def initMysql8Pwd(): def myOp(version, method): # import commands init_file = initDreplace(version) + current_os = mw.getOs() try: isInited = initMysqlData() if not isInited: - mw.execShell('systemctl start mysql') + + if current_os.startswith("freebsd"): + mw.execShell('service ' + getPluginName() + ' start') + else: + mw.execShell('systemctl start mysql') + initMysqlPwd() - mw.execShell('systemctl stop mysql') + + if current_os.startswith("freebsd"): + mw.execShell('service ' + getPluginName() + ' stop') + else: + mw.execShell('systemctl stop mysql') + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + return 'ok' mw.execShell('systemctl ' + method + ' mysql') return 'ok' @@ -610,9 +628,15 @@ def reload(version=''): def initdStatus(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + shell_cmd = 'systemctl status mysql | grep loaded | grep "enabled;"' data = mw.execShell(shell_cmd) if data[0] == '': @@ -621,17 +645,34 @@ def initdStatus(): def initdInstall(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + mw.execShell('systemctl enable mysql') return 'ok' def initdUinstall(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + mw.execShell('systemctl disable mysql') return 'ok' @@ -748,6 +789,9 @@ def runInfo(version): def myDbStatus(version): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + result = {} db = pMysqlDb() data = db.query('show variables') @@ -2368,6 +2412,9 @@ def updateSlaveSSH(version=''): def getSlaveList(version=''): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + db = pMysqlDb() dlist = db.query('show slave status') data = {} @@ -2383,6 +2430,9 @@ def getSlaveSyncCmd(version=''): def initSlaveStatus(version=''): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + mode_file = getSyncModeFile() if not os.path.exists(mode_file): return mw.returnJson(False, '需要先设置同步配置') diff --git a/plugins/mysql/versions/5.5/install.sh b/plugins/mysql/versions/5.5/install.sh index dc638a3a2..36ab1e646 100755 --- a/plugins/mysql/versions/5.5/install.sh +++ b/plugins/mysql/versions/5.5/install.sh @@ -66,6 +66,15 @@ Install_mysql() cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-5.5.62.tar.gz fi + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ ! -f $WHERE_DIR_GCC ];then + WHERE_DIR_GCC=`which gcc` + fi + + if [ ! -f $WHERE_DIR_GPP ];then + WHERE_DIR_GPP=`which g++` + fi if [ ! -d $serverPath/mysql ];then cd ${mysqlDir}/mysql-5.5.62 && cmake \ @@ -81,8 +90,8 @@ Install_mysql() -DEXTRA_CHARSETS=all \ -DDEFAULT_CHARSET=utf8mb4 \ -DDEFAULT_COLLATION=utf8mb4_general_ci \ - -DCMAKE_C_COMPILER=/usr/bin/gcc \ - -DCMAKE_CXX_COMPILER=/usr/bin/g++ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP make -j${cpuCore} && make install && make clean if [ -d $serverPath/mysql ];then diff --git a/plugins/op_waf/index.py b/plugins/op_waf/index.py index 88de6ef52..f4d36f5f2 100755 --- a/plugins/op_waf/index.py +++ b/plugins/op_waf/index.py @@ -1434,9 +1434,13 @@ def testRun(): # if not data[0]: # return data[1] - default_site = getServerDir() + "/waf/default.pl" + default_path = getServerDir() + "/waf/default.pl" + default_site = mw.readFile(default_path) url = "http://" + default_site + '/?t=../etc/passwd' returnData = mw.httpGet(url, 10) + + # url = "https://" + default_site + '/?t=../etc/passwd' + # returnData = mw.httpGet(url, 3) return mw.returnJson(True, '测试运行成功!', returnData) diff --git a/plugins/op_waf/js/op_waf.js b/plugins/op_waf/js/op_waf.js index b418877d9..3575e6fe3 100755 --- a/plugins/op_waf/js/op_waf.js +++ b/plugins/op_waf/js/op_waf.js @@ -484,7 +484,7 @@ function scanRule() { var tmp = $.parseJSON(data.data); var rdata = $.parseJSON(tmp.data); - create_l = ({ + create_l = layer.open({ type: 1, title: "常用扫描器过滤规则", area: '650px', diff --git a/plugins/op_waf/waf/lua/init.lua b/plugins/op_waf/waf/lua/init.lua index 624bb7ac1..bc922c3f4 100644 --- a/plugins/op_waf/waf/lua/init.lua +++ b/plugins/op_waf/waf/lua/init.lua @@ -309,14 +309,16 @@ local function is_open_waf_cc_increase() end -- C:D("waf config:"..json.encode(config)) - if cpu_percent >= config['safe_verify']['cpu'] then - return true - end - - if site_config[server_name] and site_config[server_name]['safe_verify']['open'] then - if cpu_percent >= site_config[server_name]['safe_verify']['cpu'] then + if config['safe_verify']['auto'] then + if cpu_percent >= config['safe_verify']['cpu'] then return true end + + if site_config[server_name] and site_config[server_name]['safe_verify']['open'] then + if cpu_percent >= site_config[server_name]['safe_verify']['cpu'] then + return true + end + end end return false diff --git a/plugins/openresty/index.py b/plugins/openresty/index.py index b4a3e3ff4..fd9a4ee15 100755 --- a/plugins/openresty/index.py +++ b/plugins/openresty/index.py @@ -11,9 +11,7 @@ import re sys.path.append(os.getcwd() + "/class/core") import mw - app_debug = False - if mw.isAppleSystem(): app_debug = True @@ -31,8 +29,13 @@ def getServerDir(): def getInitDFile(): - if app_debug: + current_os = mw.getOs() + if current_os == 'darwin': return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() @@ -95,6 +98,14 @@ def getInitDTpl(): return path +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = 'pid\s*(.*);' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + def getFileOwner(filename): import pwd stat = os.lstat(filename) @@ -118,13 +129,16 @@ def confReplace(): user = 'www' user_group = 'www' - if mw.getOs() == 'darwin': + current_os = mw.getOs() + if current_os == 'darwin': # macosx do user = mw.execShell( "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() # user = 'root' user_group = 'staff' content = content.replace('{$EVENT_MODEL}', 'kqueue') + elif current_os.startswith('freebsd'): + content = content.replace('{$EVENT_MODEL}', 'kqueue') else: content = content.replace('{$EVENT_MODEL}', 'epoll') @@ -222,8 +236,8 @@ def initDreplace(): # /usr/lib/systemd/system systemDir = mw.systemdCfgDir() systemService = systemDir + '/openresty.service' - systemServiceTpl = getPluginDir() + '/init.d/openresty.service.tpl' if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/openresty.service.tpl' se_content = mw.readFile(systemServiceTpl) se_content = se_content.replace('{$SERVER_PATH}', service_path) mw.writeFile(systemService, se_content) @@ -233,9 +247,8 @@ def initDreplace(): def status(): - data = mw.execShell( - "ps -ef|grep openresty |grep -v grep | grep -v python | awk '{print $2}'") - if data[0] == '': + pid_file = getPidFile() + if not os.path.exists(pid_file): return 'stop' return 'start' @@ -249,20 +262,33 @@ def restyOp(method): if not check_data[1].find('test is successful') > -1: return check_data[1] - if not mw.isAppleSystem(): - data = mw.execShell('systemctl ' + method + ' openresty') + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) if data[1] == '': return 'ok' return data[1] - data = mw.execShell(file + ' ' + method) + if current_os.startswith("freebsd"): + data = mw.execShell('service openresty ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' openresty') if data[1] == '': return 'ok' return data[1] def op_submit_systemctl_restart(): + current_os = mw.getOs() + if current_os.startswith("freebsd"): + mw.execShell('service openresty restart') + return True + mw.execShell('systemctl restart openresty') + return True def op_submit_init_restart(file): @@ -280,11 +306,9 @@ def restyOp_restart(): if not mw.isAppleSystem(): threading.Timer(2, op_submit_systemctl_restart, args=()).start() - # submit_restart1() return 'ok' threading.Timer(2, op_submit_init_restart, args=(file,)).start() - # submit_restart2(file) return 'ok' @@ -293,7 +317,11 @@ def start(): def stop(): - return restyOp('stop') + r = restyOp('stop') + pid_file = getPidFile() + if os.path.exists(pid_file): + os.remove(pid_file) + return r def restart(): @@ -306,10 +334,15 @@ def reload(): def initdStatus(): - - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + shell_cmd = 'systemctl status openresty | grep loaded | grep "enabled;"' data = mw.execShell(shell_cmd) if data[0] == '': @@ -318,17 +351,35 @@ def initdStatus(): def initdInstall(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + mw.execShell('systemctl enable openresty') return 'ok' def initdUinstall(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + mw.execShell('systemctl disable openresty') return 'ok' diff --git a/plugins/openresty/install.sh b/plugins/openresty/install.sh index 12ef07679..ba6683124 100755 --- a/plugins/openresty/install.sh +++ b/plugins/openresty/install.sh @@ -2,7 +2,7 @@ PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH -# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.1 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 curPath=`pwd` rootPath=$(dirname "$curPath") @@ -77,10 +77,14 @@ Install_openresty() cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + OPTIONS='' + if [ "$VERSION" != "1.21.4.2" ];then + OPTIONS=" ${OPTIONS} --with-ipv6 " + fi # --with-openssl=$serverPath/source/lib/openssl-1.0.2q cd ${openrestyDir}/openresty-${VERSION} && ./configure \ --prefix=$serverPath/openresty \ - --with-ipv6 \ + $OPTIONS \ --with-stream \ --with-http_v2_module \ --with-http_ssl_module \ @@ -92,7 +96,7 @@ Install_openresty() # --with-debug # 用于调式 - make -j${cpuCore} && make install && make clean + gmake -j${cpuCore} && gmake install && gmake clean if [ -d $serverPath/openresty ];then echo "${VERSION}" > $serverPath/openresty/version.pl diff --git a/plugins/php/index.py b/plugins/php/index.py index 88a17f1bf..b98956443 100755 --- a/plugins/php/index.py +++ b/plugins/php/index.py @@ -12,8 +12,6 @@ import shutil # sys.setdefaultencoding('utf8') sys.path.append(os.getcwd() + "/class/core") -# sys.path.append("/usr/local/lib/python3.6/site-packages") - import mw if mw.isAppleSystem(): @@ -40,8 +38,12 @@ def getServerDir(): def getInitDFile(version): - if app_debug: + current_os = mw.getOs() + if current_os == 'darwin': return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() return '/etc/init.d/' + getPluginName() + version @@ -52,13 +54,16 @@ def getArgs(): if args_len == 1: t = args[0].strip('{').strip('}') - t = t.split(':') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] tmp[t[0]] = t[1] elif args_len > 1: for i in range(len(args)): t = args[i].split(':') tmp[t[0]] = t[1] - return tmp @@ -80,7 +85,7 @@ def getFpmConfFile(version): def status_progress(version): # ps -ef|grep 'php/81' |grep -v grep | grep -v python | awk '{print $2} - cmd = "ps -ef|grep 'php/" + version + \ + cmd = "ps aux|grep 'php/" + version + \ "' |grep -v grep | grep -v python | awk '{print $2}'" data = mw.execShell(cmd) if data[0] == '': @@ -147,7 +152,7 @@ def contentReplace(content, version): def makeOpenrestyConf(): phpversions = ['00', '52', '53', '54', '55', '56', - '70', '71', '72', '73', '74', '80', '81', '82'] + '70', '71', '72', '73', '74', '80', '81', '82', '83'] sdir = mw.getServerDir() @@ -277,11 +282,11 @@ def initReplace(version): # systemd systemDir = mw.systemdCfgDir() systemService = systemDir + '/php' + version + '.service' - systemServiceTpl = getPluginDir() + '/init.d/php.service.tpl' - if version == '52': - systemServiceTpl = getPluginDir() + '/init.d/php.service.52.tpl' if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/php.service.tpl' + if version == '52': + systemServiceTpl = getPluginDir() + '/init.d/php.service.52.tpl' service_path = mw.getServerDir() se_content = mw.readFile(systemServiceTpl) se_content = se_content.replace('{$VERSION}', version) @@ -295,16 +300,23 @@ def initReplace(version): def phpOp(version, method): file = initReplace(version) - if not mw.isAppleSystem(): - if method == 'stop' or method == 'restart': - mw.execShell(file + ' ' + 'stop') - - data = mw.execShell('systemctl ' + method + ' php' + version) + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) if data[1] == '': return 'ok' return data[1] - data = mw.execShell(file + ' ' + method) + if current_os.startswith("freebsd"): + data = mw.execShell('service php' + version + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if method == 'stop' or method == 'restart': + mw.execShell(file + ' ' + 'stop') + + data = mw.execShell('systemctl ' + method + ' php' + version) if data[1] == '': return 'ok' return data[1] @@ -336,9 +348,15 @@ def reload(version): def initdStatus(version): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile(version) + if os.path.exists(initd_bin): + return 'ok' + shell_cmd = 'systemctl status php' + version + ' | grep loaded | grep "enabled;"' data = mw.execShell(shell_cmd) if data[0] == '': @@ -347,17 +365,32 @@ def initdStatus(version): def initdInstall(version): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + import shutil + source_bin = initReplace(version) + initd_bin = getInitDFile(version) + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + return 'ok' + mw.execShell('systemctl enable php' + version) return 'ok' def initdUinstall(version): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile(version) + os.remove(initd_bin) + return 'ok' + mw.execShell('systemctl disable php' + version) return 'ok' @@ -857,7 +890,7 @@ def get_php_info(args): return getPhpinfo(args['version']) -def getLibConf(version): +def libConfCommon(version): fname = getConf(version) if not os.path.exists(fname): return mw.returnJson(False, '指定PHP版本不存在!') @@ -886,6 +919,17 @@ def getLibConf(version): else: lib['status'] = True libs.append(lib) + return libs + + +def get_lib_conf(data): + libs = libConfCommon(data['version']) + # print(libs) + return mw.returnData(True, 'OK!', libs) + + +def getLibConf(version): + libs = libConfCommon(version) return mw.returnJson(True, 'OK!', libs) diff --git a/plugins/php/info.json b/plugins/php/info.json index 825f0bd3b..370874866 100755 --- a/plugins/php/info.json +++ b/plugins/php/info.json @@ -5,8 +5,8 @@ "name": "php", "title": "PHP", "coexist": true, - "versions": ["53","54","55","56","70","71","72","73","74","80","81","82"], - "updates": ["5.3.29","5.4.45","5.6.36","7.0.30","7.1.33","7.2.32","7.3.20","7.4.30","8.0.30","8.1.8","8.2.0"], + "versions": ["53","54","55","56","70","71","72","73","74","80","81","82","83"], + "updates": ["5.3.29","5.4.45","5.6.36","7.0.30","7.1.33","7.2.32","7.3.20","7.4.30","8.0.30","8.1.8","8.2.9","8.3.0"], "tip": "soft", "install_pre_inspection":true, "checks": "server/php/VERSION/bin/php", diff --git a/plugins/php/js/php.js b/plugins/php/js/php.js index 87484bab5..6460bd474 100755 --- a/plugins/php/js/php.js +++ b/plugins/php/js/php.js @@ -34,7 +34,7 @@ function phpPostCallbak(method, version, args,callback){ req_data['func'] = method; args['version'] = version; - if (typeof(args) == 'string'){ + if (typeof(args) == 'string' && args == ''){ req_data['args'] = JSON.stringify(toArrayObject(args)); } else { req_data['args'] = JSON.stringify(args); @@ -582,9 +582,13 @@ function getPHPInfo(version) { function phpLibConfig(version){ + + // phpPost('get_lib_conf', version, {}, function(rdata){ + // var rdata = $.parseJSON(rdata.data); + // }); - phpPost('get_lib_conf', version, '', function(data){ - var rdata = $.parseJSON(data.data); + phpPostCallbak('get_lib_conf', version, {}, function(rdata){ + var rdata = rdata.data; if (!rdata.status){ layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); diff --git a/plugins/php/versions/80/install.sh b/plugins/php/versions/80/install.sh index ab8d2ed9b..1b9285961 100755 --- a/plugins/php/versions/80/install.sh +++ b/plugins/php/versions/80/install.sh @@ -16,7 +16,7 @@ function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } -version=8.0.29 +version=8.0.30 PHP_VER=80 Install_php() { @@ -140,6 +140,7 @@ if [ ! -d $serverPath/php/${PHP_VER} ];then --enable-sysvshm \ --disable-intl \ --disable-fileinfo \ + --with-password-argon2 \ $OPTIONS \ --enable-fpm make clean && make -j${cpuCore} && make install && make clean diff --git a/plugins/php/versions/81/install.sh b/plugins/php/versions/81/install.sh index 8c6cef457..1d3938319 100755 --- a/plugins/php/versions/81/install.sh +++ b/plugins/php/versions/81/install.sh @@ -16,7 +16,7 @@ function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } -version=8.1.20 +version=8.1.22 PHP_VER=81 Install_php() { @@ -133,6 +133,7 @@ if [ ! -d $serverPath/php/${PHP_VER} ];then --enable-sysvshm \ --disable-intl \ --disable-fileinfo \ + --with-password-argon2 \ $OPTIONS \ --enable-fpm make clean && make -j${cpuCore} && make install && make clean diff --git a/plugins/php/versions/82/install.sh b/plugins/php/versions/82/install.sh index e2b8957c4..d66ce1883 100755 --- a/plugins/php/versions/82/install.sh +++ b/plugins/php/versions/82/install.sh @@ -16,7 +16,7 @@ function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } -version=8.2.8 +version=8.2.9 PHP_VER=82 Install_php() { @@ -135,6 +135,7 @@ if [ ! -d $serverPath/php/${PHP_VER} ];then --enable-sysvshm \ --disable-intl \ --disable-fileinfo \ + --with-password-argon2 \ $OPTIONS \ --enable-fpm make clean && make -j${cpuCore} && make install && make clean diff --git a/plugins/php/versions/83/install.sh b/plugins/php/versions/83/install.sh new file mode 100755 index 000000000..ababb315e --- /dev/null +++ b/plugins/php/versions/83/install.sh @@ -0,0 +1,157 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +install_tmp=${rootPath}/tmp/mw_install.pl + +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } +function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } +function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } +function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } + +version=8.3.0alpha2 +PHP_VER=83 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." > $install_tmp +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + # cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + # LOCAL_ADDR=common + # if [ ! -z "$cn" ];then + # LOCAL_ADDR=cn + # fi + + # if [ "$LOCAL_ADDR" == "cn" ];then + # if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + # wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.sohu.com/php/php-${version}.tar.xz + # fi + # fi + # ----------------------------------------------------------------------- # + + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://downloads.php.net/~eric/php-${version}.tar.xz + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='' +if [ $sysName == 'Darwin' ]; then + OPTIONS='--without-iconv' + OPTIONS="${OPTIONS} --with-curl=${serverPath}/lib/curl" +else + OPTIONS='--without-iconv' + OPTIONS="${OPTIONS} --with-curl" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +ZIP_OPTION='--with-zip' +libzip_version=`pkg-config libzip --modversion` +if version_lt "$libzip_version" "0.11.0" ;then + cd ${rootPath}/plugins/php/lib && /bin/bash libzip.sh + export PKG_CONFIG_PATH=$serverPath/lib/libzip/lib/pkgconfig + ZIP_OPTION="--with-zip=$serverPath/lib/libzip" +fi + + +echo "$sourcePath/php/php${PHP_VER}" + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + ./buildconf --force + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --with-zlib-dir=$serverPath/lib/zlib \ + $ZIP_OPTION \ + --enable-mbstring \ + --enable-ftp \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + --with-password-argon2 \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." > $install_tmp +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/all_test.sh b/plugins/php/versions/all_test.sh index a2b05f79b..a751fa84b 100644 --- a/plugins/php/versions/all_test.sh +++ b/plugins/php/versions/all_test.sh @@ -26,7 +26,7 @@ ROOT_DIR=$(cd "$(dirname "$0")"; pwd) # echo "php${PHP_VER} -- end" -PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82) +PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82 83) # PHP_VER_LIST=(81) for PHP_VER in ${PHP_VER_LIST[@]}; do echo "php${PHP_VER} -- start" @@ -37,7 +37,7 @@ for PHP_VER in ${PHP_VER_LIST[@]}; do done cd $DIR -PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82) +PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82 83) # yar PHP_EXT_LIST=(ZendGuardLoader pdo mysqlnd sqlite3 openssl pcntl opcache mcrypt fileinfo \ exif gd intl memcache memcached redis imagemagick xdebug \ diff --git a/plugins/php/versions/phplib.conf b/plugins/php/versions/phplib.conf index a180107a5..46735b7c5 100755 --- a/plugins/php/versions/phplib.conf +++ b/plugins/php/versions/phplib.conf @@ -36,7 +36,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "脚本解密", "msg": "用于解密SG11加密脚本!", @@ -71,7 +72,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "加密库", "msg": "Sodium加密库的包装器", @@ -88,7 +90,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "运算库", "msg": "一个开源的数学运算库", @@ -109,7 +112,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "缓存器", "msg": "用于加速PHP脚本!", @@ -130,7 +134,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "SSL!", @@ -146,7 +151,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "加密软件!", @@ -167,7 +173,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "高精度计算!", @@ -188,7 +195,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "缓存器", "msg": "用于信号控制!", @@ -210,7 +218,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "编码转换!", @@ -230,7 +239,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "用于FILE!", @@ -250,7 +260,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "用于图像文件格式!", @@ -267,7 +278,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "序列化扩展!", @@ -289,7 +301,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "国际化与本地化!", @@ -311,7 +324,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "通用GD库!", @@ -333,7 +347,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "提供国际化支持", @@ -355,7 +370,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "缓存器", "msg": "强大的内容缓存器,不支持集群", @@ -376,7 +392,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "缓存器", "msg": "强大的内容缓存器,支持集群", @@ -398,7 +415,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "缓存器", "msg": "更强大的内容缓存器,支持集群", @@ -445,7 +463,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "比GD更强大的图形库", @@ -466,7 +485,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "调试器", "msg": "不多说,不了解的不要安装", @@ -488,7 +508,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "性能分析", "msg": "不多说,不了解的不要安装!", @@ -509,7 +530,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "异步、并行、高性能网络通信引擎", @@ -553,7 +575,9 @@ "73", "74", "80", - "81" + "81", + "82", + "83" ], "type": "框架", "msg": "Yaf是一个C语言编写的PHP框架", @@ -605,7 +629,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "通用扩展", "msg": "Mongodb数据库连接驱动", @@ -622,7 +647,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "缓存器", "msg": "高性能无锁共享内存Cache", @@ -643,7 +669,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "大数据", "msg": "SOLR全文搜索服务", @@ -664,7 +691,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "日志", "msg": "SeasLog高性能日志记录", @@ -678,7 +706,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "压缩", "msg": "压缩组件", @@ -699,7 +728,8 @@ "74", "80", "81", - "82" + "82", + "83" ], "type": "压缩", "msg": "压缩组件", diff --git a/plugins/redis/index.py b/plugins/redis/index.py index 334ab4d19..eead87e57 100755 --- a/plugins/redis/index.py +++ b/plugins/redis/index.py @@ -27,8 +27,13 @@ def getServerDir(): def getInitDFile(): - if app_debug: + current_os = mw.getOs() + if current_os == 'darwin': return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() @@ -69,7 +74,7 @@ def getArgs(): def status(): data = mw.execShell( - "ps -ef|grep redis |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + "ps aux|grep redis |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") if data[0] == '': return 'stop' @@ -113,8 +118,8 @@ def initDreplace(): # systemd systemDir = mw.systemdCfgDir() systemService = systemDir + '/redis.service' - systemServiceTpl = getPluginDir() + '/init.d/redis.service.tpl' if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/redis.service.tpl' service_path = mw.getServerDir() se_content = mw.readFile(systemServiceTpl) se_content = se_content.replace('{$SERVER_PATH}', service_path) @@ -127,16 +132,23 @@ def initDreplace(): def redisOp(method): file = initDreplace() - if not mw.isAppleSystem(): - data = mw.execShell('systemctl ' + method + ' redis') + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) if data[1] == '': return 'ok' return data[1] - data = mw.execShell(file + ' start') + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' redis') if data[1] == '': return 'ok' - return 'fail' + return data[1] def start(): @@ -211,9 +223,15 @@ def runInfo(): def initdStatus(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + shell_cmd = 'systemctl status ' + \ getPluginName() + ' | grep loaded | grep "enabled;"' data = mw.execShell(shell_cmd) @@ -223,17 +241,35 @@ def initdStatus(): def initdInstall(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + mw.execShell('systemctl enable ' + getPluginName()) return 'ok' def initdUinstall(): - if mw.isAppleSystem(): + current_os = mw.getOs() + if current_os == 'darwin': return "Apple Computer does not support" + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + mw.execShell('systemctl disable ' + getPluginName()) return 'ok' diff --git a/plugins/redis/install.sh b/plugins/redis/install.sh index 69fc64b3a..4ac45c95b 100755 --- a/plugins/redis/install.sh +++ b/plugins/redis/install.sh @@ -25,7 +25,7 @@ Install_redis() mkdir -p $serverPath/redis mkdir -p $serverPath/redis/data - cd redis-${VERSION} && make PREFIX=$serverPath/redis install + cd redis-${VERSION} && gmake PREFIX=$serverPath/redis install sed '/^ *#/d' redis.conf > $serverPath/redis/redis.conf if [ -d $serverPath/redis ];then diff --git a/plugins/tgbot/startup/extend/init_cmd.py b/plugins/tgbot/startup/extend/init_cmd.py index b5192f95c..ae3fdc4b0 100644 --- a/plugins/tgbot/startup/extend/init_cmd.py +++ b/plugins/tgbot/startup/extend/init_cmd.py @@ -22,6 +22,7 @@ def init(bot): bot.set_my_commands( commands=[ telebot.types.BotCommand("start", "查看帮助信息"), - telebot.types.BotCommand("faq", "查看bbs帖子主题【?:】"), + telebot.types.BotCommand("faq", ""), + telebot.types.BotCommand("music", "搜索网易音乐"), ], ) diff --git a/plugins/tgbot/startup/extend/push_notice_msg.py b/plugins/tgbot/startup/extend/push_notice_msg.py index d3a0ef273..459d2ccb6 100644 --- a/plugins/tgbot/startup/extend/push_notice_msg.py +++ b/plugins/tgbot/startup/extend/push_notice_msg.py @@ -100,7 +100,7 @@ def send_msg(bot, tag='ad', trigger_time=300): markup = types.InlineKeyboardMarkup(keyboard) msg_notice = "由于在解决的问题的时候,不给信息,无法了解情况。以后不再群里回答技术问题。全部去论坛提问。在解决问题的过程中,可能需要面板信息,和SSH信息,如无法提供请不要提问。为了让群里都知晓。轮播一年!\n" - msg_notice += "为了不打扰双方,私聊解决问题先转100U,否则无视!\n" + msg_notice += "为了不打扰双方,私聊解决问题先转1000U,否则无视!\n" msg = bot.send_message(chat_id, msg_notice, reply_markup=markup) # print(msg.message_id) diff --git a/plugins/tgbot/startup/extend/receive_faq.py b/plugins/tgbot/startup/extend/receive_faq.py index 6b619af86..df4f3eb4c 100644 --- a/plugins/tgbot/startup/extend/receive_faq.py +++ b/plugins/tgbot/startup/extend/receive_faq.py @@ -191,20 +191,17 @@ def run(bot, message): # print(text_body) if isThisCmd('/?:', text_body): cmd_text = getReadCmd('/?:', text_body) + cmd_text = cmd_text.strip().strip(":") + if cmd_text == "": + return bot.send_message(message.chat.id, "搜索内容不能为空, 例如:/?: 数据库") return searchFaq(bot, message, cmd_text) - # if text_body.find('?') > -1 or text_body.find('?') > -1: - # return_msg = "你似乎在寻找【" + text_body + "】答案:\n" - # return_msg += "/faq:开始寻找你的问题\n" - # keyboard = [ - # [ - # types.InlineKeyboardButton( - # text="如未找到,可以在论坛补充你的提问!", url='https://bbs.midoks.me'), - # ] - - # ] - # markup = types.InlineKeyboardMarkup(keyboard) - # bot.reply_to(message, return_msg, reply_markup=markup) + if isThisCmd('/faq', text_body): + cmd_text = getReadCmd('/faq', text_body) + cmd_text = cmd_text.strip().strip(":") + if cmd_text == "": + return bot.send_message(message.chat.id, "搜索内容不能为空, 例如:/faq 数据库") + return searchFaq(bot, message, cmd_text) return bot diff --git a/plugins/tgbot/startup/extend/receive_music163_search.py b/plugins/tgbot/startup/extend/receive_music163_search.py new file mode 100644 index 000000000..ac1efcca2 --- /dev/null +++ b/plugins/tgbot/startup/extend/receive_music163_search.py @@ -0,0 +1,341 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + + +import telebot +from telebot import types +from telebot.util import quick_markup + +# 网易音乐搜索 + + +def isThisCmd(cmd, msg): + clen = len(cmd) + msg_len = len(msg) + if msg_len < clen: + return False + + check_msg = msg[0:clen] + if cmd == check_msg: + return True + return False + + +def getReadCmd(cmd, msg): + clen = len(cmd) + msg_len = len(msg) + real_msg = msg[clen:] + return real_msg + + +def ip2long(ip): + import struct + import socket + return struct.unpack("!L", socket.inet_aton(ip))[0] + + +def long2ip(longip): + import struct + import socket + return socket.inet_ntoa(struct.pack('!L', longip)) + + +def mt_rand(a, b): + import random + return random.randint(a, b) + + +def httpPost(url, data, timeout=10): + """ + 发送POST请求 + @url 被请求的URL地址(必需) + @data POST参数,可以是字符串或字典(必需) + @timeout 超时时间默认60秒 + return string + """ + try: + import urllib.request + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + + headers = { + 'Referer': 'https://music.163.com/', + 'Cookie': 'appver=8.2.30; os=iPhone OS; osver=15.0; EVNSM=1.0.0; buildver=2206; channel=distribution; machineid=iPhone13.3', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 CloudMusic/0.1.1 NeteaseMusic/8.2.30', + 'X-Real-IP': long2ip(mt_rand(1884815360, 1884890111)), + 'Accept': '*/*', + 'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4', + 'Connection': 'keep-alive', + 'Content-Type': 'application/x-www-form-urlencoded', + } + data = urllib.parse.urlencode(data).encode('utf-8') + req = urllib.request.Request(url, data, headers=headers) + response = urllib.request.urlopen(req, timeout=timeout) + result = response.read() + if type(result) == bytes: + result = result.decode('utf-8') + return result + except Exception as ex: + return str(ex) + + +def musicSearch(kw, page=1, page_size=5): + m_offset = (int(page) - 1) * int(page_size) + data = httpPost('http://music.163.com/api/cloudsearch/pc', { + 's': kw, + 'type': '1', + 'total': 'true', + 'limit': page_size, + 'offset': m_offset, + }) + # data_a = json.loads(data) + # print(data) + # exit() + return json.loads(data) + + +def musicSongD(mid): + data = httpPost('http://music.163.com/api/v3/song/detail/', { + 'c': '[{"id":' + str(mid) + ',"v":0}]', + }) + # print(data) + return json.loads(data) + + +def musicSongDataUrl(mid): + data = httpPost('http://music.163.com/api/song/enhance/player/url', { + 'br': 320 * 1000, + 'ids': [mid], + }) + return json.loads(data) + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = mw.getServerDir() + '/tgbot/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + + +def tgSearchMusic_t(cmd_text): + data = musicSearch(cmd_text, 1, 5) + + if data['code'] == 200 and len(data['result']['songs']) > 0: + slist = data['result']['songs'] + print(slist) + else: + keyboard = [ + [ + types.InlineKeyboardButton( + text="论坛", url='https://bbs.midoks.me'), + types.InlineKeyboardButton( + text="搜索", url='https://bbs.midoks.me/search.php') + ], + [ + types.InlineKeyboardButton( + text="关闭消息", callback_data='bbs_search_close') + ] + + ] + markup = types.InlineKeyboardMarkup(keyboard) + bot.send_message( + message.chat.id, "未找到合适内容,请在官方论坛[bbs.midoks.me]提问!", reply_markup=markup) + return True + + +def tgSearchMusic(bot, message, cmd_text): + import math + data = musicSearch(cmd_text, 1, 5) + if data['code'] == 200 and len(data['result']['songs']) > 0: + keyboard = [] + slist = data['result']['songs'] + page_total = math.ceil(data['result']['songCount'] / 5) + + for x in slist: + author = '' + if len(x['ar']) > 0: + author = ' - ' + x['ar'][0]['name'] + keyboard.append([types.InlineKeyboardButton( + text=x['name'] + author, callback_data='m163_id:' + str(x['id']))]) + + keyboard.append([ + types.InlineKeyboardButton( + text="下一页", callback_data='m163_next_page_2'), + types.InlineKeyboardButton( + text="第1页,共" + str(page_total) + "页", callback_data='m163_page_total') + ]) + + keyboard.append([types.InlineKeyboardButton( + text="关闭消息", callback_data='m163_search_close')]) + + # print(keyboard) + markup = types.InlineKeyboardMarkup(keyboard) + bot.send_message(message.chat.id, "寻找【" + + cmd_text.strip() + "】歌曲如下:", reply_markup=markup) + else: + bot.send_message(message.chat.id, "未找到合适内容") + return True + + +def getFaqKw(cmd): + matchObj = re.match(r'寻找【(.*?)】歌曲如下', cmd, re.M | re.I) + data = matchObj.groups() + if len(data) > 0: + return True, data[0] + return False, '' + + +def cleanMusicFileExpire(dir): + pass + + +def downloadAndUpMusic(bot, chat_id, mid, title): + import requests + murl_data = musicSongDataUrl(int(mid)) + murl = murl_data['data'][0]['url'] + def_dir = '/tmp/tgbot_music' + if not os.path.exists(def_dir): + os.mkdir(def_dir) + + def_abs_path = def_dir + '/' + title + '.mp3' + # print('downloadAndUpMusic' + ":" + str(murl)) + if murl: + msg_t = bot.send_message(chat_id, "已经获取资源URL,本地下载中...") + response = requests.get(murl) + + with open(def_abs_path, "wb") as f: + f.write(response.content) + + bot.edit_message_text( + chat_id=chat_id, message_id=msg_t.message_id, text="本地下载完,正在上传中...") + + audio = open(def_abs_path, 'rb') + bot.send_audio(chat_id, audio) + + bot.edit_message_text( + chat_id=chat_id, message_id=msg_t.message_id, text="上传结束...1s自动删除") + + time.sleep(1) + bot.delete_message(chat_id=chat_id, message_id=msg_t.message_id) + else: + bot.send_message(chat_id, "无效资源") + + if os.path.exists(def_abs_path): + os.remove(def_abs_path) + + # cleanMusicFileExpire(def_dir) + return True + + +def answer_callback_query(bot, call): + import math + keyword = call.data + # print(keyword) + if keyword == 'm163_search_close': + bot.delete_message(chat_id=call.message.chat.id, + message_id=call.message.message_id) + return + + # 音乐下载 + if keyword.startswith('m163_id:'): + t = keyword.split(":") + inline_keyboard = call.json['message'][ + "reply_markup"]["inline_keyboard"] + + def_file_name = 'demo' + for x in inline_keyboard: + # print(x) + if x[0]['callback_data'] == keyword: + def_file_name = x[0]['text'] + # print(call.message) + downloadAndUpMusic(bot, call.message.chat.id, t[1], def_file_name) + bot.delete_message(chat_id=call.message.chat.id, + message_id=call.message.message_id) + return True + + is_m163_page = False + p = 1 + if keyword.startswith('m163_next_page'): + is_m163_page = True + p = keyword.replace('m163_next_page_', '') + + if keyword.startswith('m163_pre_page'): + is_m163_page = True + p = keyword.replace('m163_pre_page_', '') + + if is_m163_page: + is_match, cmd_text = getFaqKw(call.message.text) + if not is_match: + bot.edit_message_text( + chat_id=call.message.chat.id, message_id=call.message.message_id, text="出现错误!") + return + + data = musicSearch(cmd_text, p, 5) + + dlist = data['result']['songs'] + page_total = math.ceil(data['result']['songCount'] / 5) + + keyboard = [] + for x in dlist: + author = '' + if len(x['ar']) > 0: + author = ' - ' + x['ar'][0]['name'] + keyboard.append([types.InlineKeyboardButton( + text=x['name'] + author, callback_data='m163_id:' + str(x['id']))]) + + page_nav = [] + if int(p) > 1: + page_nav.append(types.InlineKeyboardButton( + text="上一页", callback_data='m163_pre_page_' + str(int(p) - 1))) + + if int(p) < page_total: + page_nav.append(types.InlineKeyboardButton( + text="下一页", callback_data='m163_next_page_' + str(int(p) + 1))) + + page_nav.append(types.InlineKeyboardButton( + text="第" + str(p) + "页,共" + str(page_total) + "页", callback_data='m163_page_total')) + + keyboard.append(page_nav) + + keyboard.append([types.InlineKeyboardButton( + text="关闭消息", callback_data='m163_search_close')]) + + markup = types.InlineKeyboardMarkup(keyboard) + bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, + text=call.message.text, reply_markup=markup) + + +def run(bot, message): + text_body = message.text + + if isThisCmd('/music', text_body): + cmd_text = getReadCmd('/music', text_body) + cmd_text = cmd_text.strip().strip(":") + if cmd_text == "": + return bot.send_message(message.chat.id, "搜索内容不能为空, 例如:/music 刀郎") + return tgSearchMusic(bot, message, cmd_text) + + return bot + + +if __name__ == '__main__': + # cleanMusicFileExpire("/tmp/tgbot_music") + # tgSearchMusic_t("一夜泥工") + # print(long2ip(mt_rand(1884815360, 1884890111))) + t = musicSongDataUrl(2063487880) + print(t['data']) + print("111") diff --git a/plugins/webssh/index.py b/plugins/webssh/index.py index 678f4cc85..ad624bc32 100755 --- a/plugins/webssh/index.py +++ b/plugins/webssh/index.py @@ -113,7 +113,7 @@ class App(): def getSshInfo(self, file): rdata = mw.readFile(file) - destr = mw.enDoubleCrypt('mdserver-web', rdata) + destr = mw.deDoubleCrypt('mdserver-web', rdata) return json.loads(destr) def get_server_by_host(self): @@ -150,15 +150,19 @@ class App(): info_file = self.__host_dir + '/' + name + '/info.json' if not os.path.exists(info_file): continue + + host_info = {} try: info_tmp = self.getSshInfo(info_file) - host_info = {} + host_info['host'] = name host_info['port'] = info_tmp['port'] host_info['ps'] = info_tmp['ps'] # host_info['sort'] = int(info_tmp['sort']) except Exception as e: - print(e) + # print(e) + return mw.returnJson(False, str(e)) + # if os.path.exists(info_file): # os.remove(info_file) # continue diff --git a/route/__init__.py b/route/__init__.py index c6af89b06..ae168e753 100755 --- a/route/__init__.py +++ b/route/__init__.py @@ -513,7 +513,7 @@ def index(reqClass=None, reqAction=None, reqData=None): if reqClass == None: reqClass = 'index' - pageFile = ('config', 'control', 'crontab', 'files', 'firewall', + pageFile = ('config', 'control', 'crontab', 'files', 'logs', 'firewall', 'index', 'plugins', 'login', 'system', 'site', 'cert', 'ssl', 'task', 'soft') if reqClass == 'login': @@ -553,7 +553,7 @@ def index(reqClass=None, reqAction=None, reqData=None): return 'error request!' # API请求 - classFile = ('config_api', 'crontab_api', 'files_api', 'firewall_api', + classFile = ('config_api', 'crontab_api', 'files_api', 'logs_api', 'firewall_api', 'plugins_api', 'system_api', 'site_api', 'task_api', 'vip_api') className = reqClass + '_api' if not className in classFile: diff --git a/route/static/app/crontab.js b/route/static/app/crontab.js index d0f8284a5..5fe03df1b 100755 --- a/route/static/app/crontab.js +++ b/route/static/app/crontab.js @@ -33,7 +33,7 @@ function getLogs(id){ function getBackupName(hook_data, name){ for (var i = 0; i < hook_data.length; i++) { - if (hook_data[i]['name'] == 'backup_ftp'){ + if (hook_data[i]['name'] == name){ return hook_data[i]['title']; } } diff --git a/route/static/app/files.js b/route/static/app/files.js index 9ec2d36f4..9bee48b4b 100755 --- a/route/static/app/files.js +++ b/route/static/app/files.js @@ -298,21 +298,29 @@ function openFilename(obj){ } if (inArray(ext,['svg'])){ - - $.post("/files/get_body", "path=" + encodeURIComponent(path), function(rdata) { - if (rdata.data.status){ - layer.open({ - type:1, - closeBtn: 1, - title:"SVG预览", - area: '400px', - shadeClose: true, - content: '
      '+rdata.data.data+'
      ' - }); - } else { - layer.msg("无法预览"); - } - },'json'); + var url = '/files/download?filename='+path; + layer.open({ + type:1, + closeBtn: 1, + title:"SVG预览", + area: ['600px','500px'], + maxmin:true, + shadeClose: true, + content: '' + }); } } diff --git a/route/static/app/firewall.js b/route/static/app/firewall.js index 2423f1114..dbbacee10 100755 --- a/route/static/app/firewall.js +++ b/route/static/app/firewall.js @@ -6,10 +6,6 @@ setTimeout(function(){ showAccept(1); },1000); -setTimeout(function(){ - getLogs(1); -},1500); - $(function(){ // start @@ -124,7 +120,6 @@ function getSshInfo(){ $("#firewall_status").html(fStatus); showAccept(1); - getLogs(1); },'json'); } @@ -371,39 +366,3 @@ function delAcceptPort(id, port) { },'json'); }); } - - -/** - * 取回数据 - * @param {Int} page 分页号 - */ -function getLogs(page,search) { - search = search == undefined ? '':search; - var loadT = layer.load(); - $.post('/firewall/get_log_list','limit=10&p=' + page+"&search="+search, function(data) { - layer.close(loadT); - var body = ''; - for (var i = 0; i < data.data.length; i++) { - body += "\ - " + data.data[i].id + "\ - " + data.data[i].type + "\ - " + data.data[i].log + "\ - " + data.data[i].addtime + "\ - "; - } - $("#logsBody").html(body); - $("#logsPage").html(data.page); - },'json'); -} - -//清理面板日志 -function delLogs(){ - layer.confirm('即将清空面板日志,继续吗?',{title:'清空日志',closeBtn:2},function(){ - var loadT = layer.msg('正在清理,请稍候...',{icon:16}); - $.post('/firewall/del_panel_logs','',function(rdata){ - layer.close(loadT); - layer.msg(rdata.msg,{icon:rdata.status?1:2}); - getLogs(1); - },'json'); - }); -} \ No newline at end of file diff --git a/route/static/app/logs.js b/route/static/app/logs.js new file mode 100644 index 000000000..c61f3d7b1 --- /dev/null +++ b/route/static/app/logs.js @@ -0,0 +1,205 @@ + +$(document).ready(function(){ + logsLoad(); +}); + +function changeLogsViewH(){ + var l = $(window).height(); + $('.container-fluid .tab-view-box').css('height',l-80-40); + + $('#panelLogs').css('height',l-80-40-50); + + $('#logAudit .logAuditTab').css('height',l-80-40-50); + $('#logAudit .logAuditContent').css('height',l-80-40-50); + +} + +function logsLoad(){ + changeLogsViewH(); + $(window).resize(function(){ + changeLogsViewH(); + }); + + getLogs(1); +} + + + +$('#cutTab .tabs-item').click(function(){ + var type = $(this).data('name'); + + $('#cutTab .tabs-item').removeClass('active'); + $(this).addClass('active'); + + + $('.tab-view-box .tab-con').addClass('hide').removeClass('show').removeClass('w-full'); + $('#'+type).addClass('show').addClass('w-full'); + + switch(type){ + case 'panelLogs': + getLogs(1); + break; + case 'logAudit': + getAuditLogsFiles(); + break; + } + +}); + + +$('#panelLogs .refresh').click(function(){ + getLogs(1); +}); + +$('#panelLogs .clear').click(function(){ + delLogs(1); +}); + + +function getAuditLogsFiles(){ + var loadT = layer.msg('正在获取日志审计列表...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/logs/get_audit_logs_files',{}, function(data) { + layer.close(loadT); + var option = ''; + for (var i = 0; i < data.length; i++) { + var tip = data[i]['name'] +' - '+data[i]['title'] + '(' + toSize(data[i]['size']) + ')'; + if (i==0){ + option += '
      '+tip+'
      '; + } else { + option += '
      '+tip+'
      '; + } + } + $("#logAudit .logAuditTab").html(option); + + getAuditFile(data[0]['name']); + $('#logAudit .logAuditItem').click(function(){ + $('#logAudit .logAuditItem').removeClass('active'); + $(this).addClass('active'); + getAuditFile($(this).data('file')); + }); + + },'json'); +} + + +function getAuditFile(log_name){ + var loadT = layer.msg('正在获取日志审计内容...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/logs/get_audit_file',{log_name:log_name}, function(data) { + layer.close(loadT); + // console.log(data); + try{ + if (typeof(data) == 'object'){ + var plist = data.data; + + var pre_html ='
      \ +
      \ +
      \ + \ +
      \ + \ +
      \ +
      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
      用户来源端口时间
      root117.139.193.29pts/02023-08-25 13:27 still logged in
      \ +
      \ +
      \ +
      \ +
      \ +
      \ +
      \ +
      ' + + // var pre_html = '\ + // \ + // \ + //
      时间角色事件
      '; + $('#logAudit .logAuditContent').html(pre_html); + + if (plist.length>0){ + var tmp = plist[0]; + var thead = ''; + tbody += '' + for (var i in tmp) { + tbody+=''+ i + ''; + } + tbody += ''; + $('#logAudit .logAuditContent thead').html(tbody); + } + + + var tbody = ''; + for (var i = 0; i < plist.length; i++) { + tbody += ''; + for (var vv in plist[i]) { + tbody+= ''+ plist[i][vv] + '' + } + tbody += ''; + } + $('#logAudit .logAuditContent tbody').html(tbody); + + $('#logAudit .refresh').click(function(){ + getAuditFile(log_name); + }); + } + + if (typeof(data) == 'string'){ + var cc = '
      \ +
      '+data+'
      \ +
      '; + $('#logAudit .logAuditContent').html(cc); + } + } catch (e) { + layer.msg(str(e),{icon:2,time:10000,shade: [0.3, '#000']}); + } + }); +} + +function getLogs(page,search) { + search = search == undefined ? '':search; + var loadT = layer.load(); + $.post('/logs/get_log_list','limit=10&p=' + page+"&search="+search, function(data) { + layer.close(loadT); + var body = ''; + for (var i = 0; i < data.data.length; i++) { + body += "\ + " + data.data[i].id + "\ + " + data.data[i].type + "\ + " + data.data[i].log + "\ + " + data.data[i].addtime + "\ + "; + } + $("#operationLog tbody").html(body); + $("#panelLogs .page").html(data.page); + },'json'); +} + +function delLogs(){ + layer.confirm('即将清空面板日志,继续吗?',{title:'清空日志',closeBtn:2},function(){ + var loadT = layer.msg('正在清理,请稍候...',{icon:16}); + $.post('/logs/del_panel_logs','',function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + getLogs(1); + },'json'); + }); +} \ No newline at end of file diff --git a/route/static/app/site.js b/route/static/app/site.js index c548a6018..36f3d4aab 100755 --- a/route/static/app/site.js +++ b/route/static/app/site.js @@ -575,38 +575,33 @@ function allDeleteSite(){ if($("#delpath").is(":checked")){ path='&path=1'; } - syncDeleteSite(dataList,0,'',path); + syncDeleteSite(dataList, 0,'',path); },thtml); } //模拟同步开始批量删除 function syncDeleteSite(dataList,successCount,errorMsg,path){ if(dataList.length < 1) { - layer.msg(lan.get('del_all_site_ok',[successCount]),{icon:1}); + showMsg(lan.get('del_all_site_ok',[successCount]), function(){ + // location.reload(); + },{icon:1}); return; } var loadT = layer.msg(lan.get('del_all_task_the',[dataList[0].name]),{icon:16,time:0,shade: [0.3, '#000']}); - $.ajax({ - type:'POST', - url:'/site?action=DeleteSite', - data:'id='+dataList[0].id+'&webname='+dataList[0].name+path, - async: true, - success:function(frdata){ - layer.close(loadT); - if(frdata.status){ - successCount++; - $("input[title='"+dataList[0].name+"']").parents("tr").remove(); - }else{ - if(!errorMsg){ - errorMsg = '

      '+lan.site.del_err+':

      '; - } - errorMsg += '
    • '+dataList[0].name+' -> '+frdata.msg+'
    • ' - } - - dataList.splice(0,1); - syncDeleteSite(dataList,successCount,errorMsg,path); + $.post('/site/delete', 'id='+dataList[0].id+'&webname='+dataList[0].name+path , function(rdata){ + layer.close(loadT); + if(rdata.status){ + successCount++; + $("input[title='"+dataList[0].name+"']").parents("tr").remove(); + } else { + if(!errorMsg){ + errorMsg = '

      '+lan.site.del_err+':

      '; } - }); + errorMsg += '
    • '+dataList[0].name+' -> '+rdata.msg+'
    • ' + } + dataList.splice(0,1); + syncDeleteSite(dataList,successCount,errorMsg,path); + },'json'); } diff --git a/route/static/css/site.css b/route/static/css/site.css index 2e606b418..7f07492de 100755 --- a/route/static/css/site.css +++ b/route/static/css/site.css @@ -826,7 +826,7 @@ html { .menu li a:hover { background-color: #2c3138; color: #fff; - border-left: #20a53a 2px solid + border-left: #20a53a 2px solid; } .menu .menu_home { @@ -892,6 +892,15 @@ html { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAEPSURBVDiNpdO9SkNBEIbh58RoFKwEwT8QFOwEQQvt1NbW+7Cx8B7svAZLa1tbG0sbEQQh2KigjcZoxsI9YT2EGMnCMt8us+98s8sWEWGYUausp7HTJ38bU/lGPYt72MUyFjFeOfyOfdziAudol4BjPOMRS3jCRA/AJ5pYwxaORISIOIuImaTnU+w1F1KcjYjTiOi28IVD3KDoYT93EVhJ+hfgDtdooJNA1SeqoYVRbOSAFq5wmR0q+mhYzQEjaeYJ/XQ3v1bZlCrFH7rw03bXwSBVc10rYaWDzgBVc/0ivVTpYAwf/3DwJrVcAprYTJCx1F/15huZXsc9FOk3TuIAc2hXqlVHHQ84wWsx7Hf+BgvadUGnT3fcAAAAAElFTkSuQmCC") } +.menu .menu_logs { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAApJJREFUeJztm71y4yAQx3dvzn4t41dIm8pzAV+6K65LIblId8WVMeReIa9g/FaZcbFXBGdkjBD6iBAyv5kUktB6+QcW7bAAZDKZWwZDGimlGBEV5pJ9nTuDoBFRc853IY2/hTQiogN8dJx192s0GBGVSqmiuWmAAKGGpgYRlSHtGqeAlJIuXkAMMhyLascRcc05177239sYR8QydG7FQCnFrFsrANC+d4JiwJzJAsR2IDZZgNgOxCYLENuB2LT6DnAhpXwFgB8D+BIEEb0tl8v7zWbzPoS9XiNgv98/wYidBwBAxLvT6fR3KHs3PwV6CbDdbp8B4N9AvgRBRG+LxeLXUPZ6xwAhxAMAPAzgSxTyFIjtQGyyALEdiE0WILYDsbl5AXIu0OflnAvMgJwL9DWQc4HEyQLEdmBIOOe6sncZtEXeOwZMDdPp4P3LWY2ALiQ/As71C0RU2rvXSqnCbJdrRNy5tspb1QdMbXu80kEbDY5qFpf/U8oFXoQQjy1+90BErOax874pnYGqCFPKBX5KKX+HNkZE3fF3jtWLmw+CU8oFXoQQf0Ibe4a/rvy5WFUvkguCJvAxuJ7nV/WBpr7x4DDzuSokNQVM4CvBHeF39j/HfBmuHaYYER2UUkVSAkB9oaauK4ezPo8vIKIyNQGcIGLnaTkLAcAKbG2YiwBePCtGWgL45nJdTbO5z2pMprUKcM53Jqpr+5lLhLpcARFLRFwLIdbJfQec8SRCTWghxOfSmNQIsDg2N7nGziFSFqBz5K+SsgBduRg5ITHgfFzm44WRD0z4Yo4rDhj/jgCwsp45zxK1FmBsQgJvNfo7kqHCd4iqUQBPRjUKX73yNMYAzrkWQmCks0IaOkb7TCaTCeE/v6cw3cuvVicAAAAASUVORK5CYII=") +} + +.menu .current .menu_logs, +.menu .menu_logs:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAf9JREFUaIHtmUFOwzAQRccWmyaN2HIEOAXpIag4BnsWcRfsOQZSD9EgNhyBI3SLnMnSw6IpciI7iUMdB8lv0zTxKDPyOF/6AxCJ/AnW9xARCyLKGWP5TPm0IKKSMVamabqzrbEWgIgFAAgfibmilNpkWVaanl31xInzBREZg30zZuf7CgCAU/Lr9XpzkYwckFL+ti7n/B4AStM6PmNOXogFhCYWEJrBr5AOEV0j4idj7PbCeezTNN1OCXTagbqunzwkDwDwUNf145RApwKSJHkloq8pLxpgnyTJ25RApxZijH0DwN2UF/ni3x/iWEBoYgGhiUJ2IaKQjSIK2ZwgIiEiVVV1CJhDMfn9SyhgDItsISll3vhSxv86TofYN10zDRFBdwYRUQCA0J26PmeOANq+0B+E7Mg5365Wqw/bAillzjkf1a66UzeXkN0opZ4nxA0yl5AdOecvvYmc3LcWRFQO2ZrBhayxEAsAyPX7XUO3qqqDZjUemvMQ/hAzxgqDiSu6bjQR7TrrBMBCP6OmeUCWZcZ2WmQBNkx2+yILMImWTciCF9BojOjcFl0l7q4holIptXESMp+4CBloahx8B87YZmBDLKYAF5RS7+drawvpwnHpIZ9tdGoY6wr9t1Hmnb5bIceswjb/bQ5tq62klLmpzQYH3eChiDED7EhkJn4A1qwofp3F9mcAAAAASUVORK5CYII=") +} + .menu .menu_control { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF5SURBVDiNpdM9ixNRFMbx3yRZ3/ANbCxEcBubpNJSLIQFaxs7D4JfQSz9CpYWItyPYCOIFtqvhQxopSyCb1gpEuNukrHImewQY7UHLsOce85z/vPMvVXTNA4SAyilfMI2Zpn/n2rV6RtGxOYgE9sITLK5Qa8jVGGezwpH8VgWwRTjiJigj6vYwrVcW5nrZ824pW0Jeh0xSVJhr4M86ez32/qBf6OH04nZCmzk1HbI0qN1AhO8zuYfOJ5i8xWKZq1AREzxpZRyAlfwJiI+r5RVrUCLNOvgtnER93FpDeV0laBnYcwMSimHcQpP7Z+V2xZelA7FkuA8opRyK9/P4gIeYq+Ucj2/fwM3Lc7Mua7AVzzH91LKHZxBExE/s/EudiwOz0u8wLeuwDgidvAMf3APr3LvHR7gfUT8joiPw+HwQwqrmqZRSnmSWL9wEpt4a/Hr5qPRaLeu60OdgcfwKCJutCZeTnNmuXZxpDWqruvG/j1o0vDhkuAg8RdE7nuSY6nc+gAAAABJRU5ErkJggg==") } @@ -960,6 +969,10 @@ html .menu .menu_soft:hover { background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAOPj4+Hh4d/f393d3dvb29nZ2dfX19XV1dPT09HR0c/Pz83NzcvLy8nJycfHx8XFxcPDw7+/v729vbu7u6+vr62traurq6mpqaenp6WlpZmZmZeXl5WVlRTDQBTDPpOTkxLDPhbBQBi/QBTBQBq9Qha/QJGRkRi9Qhi9QI+Pjxq7Qhy5RB63RBy5QiC1RiC1RI2NjSKzRiSxRouLiyKxRiavSCitSCSvSImJiSatSCypSiirSCynSoWFhYODg4GBgTadUDadTjabUDSdTjibUDiZUDyXUn5+fjqXUjyVUnx8fHh4eESLVkaLVkaJWEaJVkqHWEiHWEqFWE6DWkyDWk6BWlB+WlB8XFJ8XFJ6XFR6XlR4Xlh2Xlp0YGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFHgBeACwAAAEAEAAPAAAHtoBeXTEdIR0qWF4/BgECAQtKXjIrQkBAMjVeEAwVFBUHCl4iRV6lUy5eCyalXhgAXidArE6oCxqsFQGiRKxStR+sGLonLk5MTTwsXgoSHBscCwJeWDUu1jFUXjMLCt0OOKzh4uI+DwndDDNeOAzdCQ89XgUKzhwTCcsR9dFeAxasKUItAFZKmBcCFFhxEHirVK5+F1jBwKeKVYZXDA50qtDAgZcHnDwdwKdEQaMABcD1KHAywZFAACH5BAUeAF4ALAAAAQAQAAcAAAdhgF5LDAECAQY+XloqHSEdMVxeDQcVFBUND147MkBAQisuXgMXXqUwCV4uUKVeRh1eBBSsHApeMUysRK8EFqwptTGrpa6wChwbHBMIqTxNTE4xIV49DwrWCzBeUzEu3TVWgQAh+QQFHgBeACwJAAEABwAPAAAHWIA/BgECAQtKEAwVFBUHCgsmXpIYAAsakl4Vhh+YGAEKEhwbHAsCMwsKqQ44mK1WMS6xNlguOk1MTjEhMVCYRh0xTJhEHS69kkkdNjJAQEIrLlckHSHFWoEAIfkECR4AXgAsAAABABAADwAAB3CAXoKDhIWGh4iJiouMjY6PiVg2LpQxVV44DAoKCQ89XiQuTkxNPCxeChEcGxwLAl4iRINSLl4LH4MYAV4nQINOtQsagxW7IkWDU8EmgxkAXjErQkBAMjVeDwwVFBUHCV5cLh0hHSRWXj0FAQIBCUeBADs=") } +html .menu .menu_logs:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAPcAACszOzM7Q9vb2+Pj49PT0ztDQzMzOwPDM+vr6/Pz8wuzM5ubm2Nja3uDgys7O2tzc0NDS8vLy4ODi2trc7Ozs7u7u1NTWzs7Q3t7g8PLy0tTU0tTW8PDywPLMwurOyNbOyNLOwujMx9XLx9fNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQEBAD/ACwAAAAAEAAQAAAIcgABADBAsKBBggIFVhBAoKFDAgIELDCQcEDCiwINELhoEWNCjRw9ftxYUWRGkgI7igRZ0iTLlCYHUgi5EgODCS09SkiAIMEFmB41IFgAgIMAoBg1DJgYoaNKjBgQ9NQg0GjEqxEgXGhw82OACwG+hvUYEAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAQAAQAIAAQAAAgUAAkIHCgQgMGDCBMCCOHBgwOEAQEAIfkEBAQA/wAsBgAAAAgABQAACB8ALxgIQDDAhQAEEioUsACAw4cIH0KM4CFERQ8OEAYEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAQAAAAKAAgAAAgyAC8YCBBgIMELAQgoXKhQwAIAECNCNEBAosSEIRQcUMBRAYiEFiNSDDmRQIiTDiRSDAgAIfkEBAQA/wAsBwAAAAYACAAACCYALxgIYODCBQIIEQoAwJBhgIYOFSg4oEDEQ4gXGwYIwTGEgwABAQAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwEAAAABwALAAAIJgANCBR4IQCBgwgJAFjIsGHDEAcURIzosCLDEBgzhrBoMYQIhwEBACH5BAQEAP8ALAUAAAAJAAsAAAhDAA0YuDDQQIALAQgoXCiAwAIAECMCuEBAYsSEBxRkPHBAxIUIFiEmDAkgYQgPIVKGcDAypAEKLjEw4HggBEQJCRAEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIf8LWE1QIERhdGFYTVA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA2LjAtYzAwMyA3OS4xNjQ1MjcsIDIwMjAvMTAvMTUtMTc6NDg6MzIgICAgICAgICI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcERNPSJodHRwOi8vbnMuYWRvYmUuY29tL3htcC8xLjAvRHluYW1pY01lZGlhLyIKICAgIHhtbG5zOnN0RGltPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvRGltZW5zaW9ucyMiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ZmYyZWY0OTItM2JkZC05OTQ3LTk5YWMtMmQzMzI3MDVhMzIzIgogICB4bXBNTTpEb2N1bWVudElEPSJmZWI4MmYyNS1mZmZiLTNkYjgtY2EzMC0wNjE4MDAwMDAwM2YiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxYTVlZTUyOS03NWE4LTg0NDctODMzMi01NDRiZTAyMTFlYzUiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjItMTItMTVUMTc6Mzc6NTErMDg6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDIyLTEyLTE1VDE3OjM3OjUxKzA4OjAwIgogICB4bXA6Q3JlYXRlRGF0ZT0iMjAyMi0xMi0xNVQxNzozNzoyMSswODowMCIKICAgZGM6Zm9ybWF0PSLliqjnlLsgR0lGIgogICB4bXBETTp2aWRlb0ZyYW1lUmF0ZT0iMjUuMDAwMDAwIgogICB4bXBETTp2aWRlb0ZpZWxkT3JkZXI9IlByb2dyZXNzaXZlIgogICB4bXBETTp2aWRlb1BpeGVsQXNwZWN0UmF0aW89IjEvMSIKICAgeG1wRE06c3RhcnRUaW1lU2NhbGU9IjI1IgogICB4bXBETTpzdGFydFRpbWVTYW1wbGVTaXplPSIxIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9IjlkMWU4YTJjLWE4NGUtM2U1Ni04ZDI0LWRjZjEwMDAwMDA2YyIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0xMi0xNVQxNzozNzo1MSswODowMCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgQWRvYmUgTWVkaWEgRW5jb2RlciAyMDIwLjAgKFdpbmRvd3MpIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249ImNyZWF0ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MTJjMmZmZmItZGM3NC02ODQyLWIxOTEtMDhhMzgzM2RhMDgyIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE0OjUwOjAyKzA4OjAwIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmYxNDI3ZWYyLTQ1ZjEtMmY0Zi1hNDY0LTU4MzgyOGMxNjJlNyIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0xMi0xNVQxNDo1MjoxNCswODowMCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iL2NvbnRlbnQiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NTZjODkxYmQtMzQ4YS01OTQ3LTk2MDItMjFkM2JlZmRiZjAwIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE1OjQwOjE5KzA4OjAwIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvY29udGVudCIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJkZXJpdmVkIgogICAgICBzdEV2dDpwYXJhbWV0ZXJzPSJzYXZlZCB0byBuZXcgbG9jYXRpb24iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NThkMTQ4M2QtZjAyMy02ZTQ4LWE0NTYtZDg3YTA1YWEzMWQ1IgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE1OjQwOjI2KzA4OjAwIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmRmMzhmYzNiLTE5ZGEtNGY0MC1iMjU2LWVhYzIxOWFjZGNmMiIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0xMi0xNVQxNjowMzo0NiswODowMCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iL2NvbnRlbnQiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NzZjOTFlOGYtZTkyZC1iMjQ1LWJkYWItODdiYThmMDU5ZjkwIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE3OjM3OjQ4KzA4OjAwIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvY29udGVudCIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiNDJmZDUyMi00NGM4LTdmNDUtYTJjNS1iMjU0ZDdiNDdjMDciCiAgICAgIHN0RXZ0OndoZW49IjIwMjItMTItMTVUMTc6Mzc6NDgrMDg6MDAiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MThjMjc4NjktMDAyMS04YzRmLThjMDMtMzVlZTU3MzAwZGQ5IgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE3OjM3OjUxKzA4OjAwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBBZG9iZSBNZWRpYSBFbmNvZGVyIDIwMjAuMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZmYyZWY0OTItM2JkZC05OTQ3LTk5YWMtMmQzMzI3MDVhMzIzIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE3OjM3OjUxKzA4OjAwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBBZG9iZSBNZWRpYSBFbmNvZGVyIDIwMjAuMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii9tZXRhZGF0YSIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgIDx4bXBNTTpEZXJpdmVkRnJvbQogICAgc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2ZGUxZmEzNC0wNzNlLTJiNDgtYTNmYi0yY2Y3ZjFhMzczNjAiCiAgICBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjZkZTFmYTM0LTA3M2UtMmI0OC1hM2ZiLTJjZjdmMWEzNzM2MCIKICAgIHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxMmMyZmZmYi1kYzc0LTY4NDItYjE5MS0wOGEzODMzZGEwODIiLz4KICAgPHhtcE1NOkluZ3JlZGllbnRzPgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQogICAgICBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjg1NmYwYzA0LWU0MzUtNzk0Ny1iMmQwLTkwMDliMjlhYzdmNiIKICAgICAgc3RSZWY6ZnJvbVBhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgIHN0UmVmOnRvUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgc3RSZWY6bWFza01hcmtlcnM9Ik5vbmUiLz4KICAgIDwvcmRmOkJhZz4KICAgPC94bXBNTTpJbmdyZWRpZW50cz4KICAgPHhtcE1NOlBhbnRyeT4KICAgIDxyZGY6QmFnPgogICAgIDxyZGY6bGk+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24KICAgICAgIGRjOmZvcm1hdD0iYXBwbGljYXRpb24vdm5kLmFkb2JlLmFmdGVyZWZmZWN0cy5sYXllciIKICAgICAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NWI1MGU1ZTktMTRlMy0zZDQyLWEyMmMtMDY5NjNjNGY3ZWJjIj4KICAgICAgPGRjOnRpdGxlPgogICAgICAgPHJkZjpBbHQ+CiAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij7mt7HoibIg5ZOB6JOd6ImyIOe6r+iJsiAxPC9yZGY6bGk+CiAgICAgICA8L3JkZjpBbHQ+CiAgICAgIDwvZGM6dGl0bGU+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgIDwvcmRmOmxpPgogICAgIDxyZGY6bGk+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24KICAgICAgIGRjOmZvcm1hdD0iYXBwbGljYXRpb24vdm5kLmFkb2JlLmFmdGVyZWZmZWN0cy5sYXllciIKICAgICAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6N2U1M2FmMjctYzQzNS1iNjQ5LTk4MTAtNTYzYjE4ZmI5ZDI1Ij4KICAgICAgPGRjOnRpdGxlPgogICAgICAgPHJkZjpBbHQ+CiAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij7nn6nlvaIgODEucG5nPC9yZGY6bGk+CiAgICAgICA8L3JkZjpBbHQ+CiAgICAgIDwvZGM6dGl0bGU+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgIDwvcmRmOmxpPgogICAgIDxyZGY6bGk+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24KICAgICAgIGRjOmZvcm1hdD0iYXBwbGljYXRpb24vdm5kLmFkb2JlLmFmdGVyZWZmZWN0cy5jb21wIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4NTZmMGMwNC1lNDM1LTc5NDctYjJkMC05MDA5YjI5YWM3ZjYiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPuWQiOaIkCAxPC9yZGY6bGk+CiAgICAgICA8L3JkZjpBbHQ+CiAgICAgIDwvZGM6dGl0bGU+CiAgICAgIDx4bXBNTTpJbmdyZWRpZW50cz4KICAgICAgIDxyZGY6QmFnPgogICAgICAgIDxyZGY6bGkKICAgICAgICAgc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1YjUwZTVlOS0xNGUzLTNkNDItYTIyYy0wNjk2M2M0ZjdlYmMiCiAgICAgICAgIHN0UmVmOmZyb21QYXJ0PSJ0aW1lOjBkNzY4MDAwZjI1NjAwIgogICAgICAgICBzdFJlZjp0b1BhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOm1hc2tNYXJrZXJzPSJOb25lIi8+CiAgICAgICAgPHJkZjpsaQogICAgICAgICBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjdlNTNhZjI3LWM0MzUtYjY0OS05ODEwLTU2M2IxOGZiOWQyNSIKICAgICAgICAgc3RSZWY6ZnJvbVBhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOnRvUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgICAgc3RSZWY6bWFza01hcmtlcnM9Ik5vbmUiLz4KICAgICAgICA8cmRmOmxpCiAgICAgICAgIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6YWM5NGVmNzAtMmVkNC03YjQzLTg5YTktNzU0NTkwMjExOGVmIgogICAgICAgICBzdFJlZjpmcm9tUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgICAgc3RSZWY6dG9QYXJ0PSJ0aW1lOjBkNzY4MDAwZjI1NjAwIgogICAgICAgICBzdFJlZjptYXNrTWFya2Vycz0iTm9uZSIvPgogICAgICAgIDxyZGY6bGkKICAgICAgICAgc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMjY3NDgyNS05MjE4LTQ1NDYtYjA4ZS01ZTI3M2QyNDAyMjgiCiAgICAgICAgIHN0UmVmOmZyb21QYXJ0PSJ0aW1lOjBkNzY4MDAwZjI1NjAwIgogICAgICAgICBzdFJlZjp0b1BhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOm1hc2tNYXJrZXJzPSJOb25lIi8+CiAgICAgICAgPHJkZjpsaQogICAgICAgICBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmNiYmJlOTY0LTM0MmYtYTM0ZC1iM2Q2LTI3ZGUyOTE3MWFhYSIKICAgICAgICAgc3RSZWY6ZnJvbVBhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOnRvUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgICAgc3RSZWY6bWFza01hcmtlcnM9Ik5vbmUiLz4KICAgICAgIDwvcmRmOkJhZz4KICAgICAgPC94bXBNTTpJbmdyZWRpZW50cz4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICAgPHJkZjpsaT4KICAgICAgPHJkZjpEZXNjcmlwdGlvbgogICAgICAgZGM6Zm9ybWF0PSJhcHBsaWNhdGlvbi92bmQuYWRvYmUuYWZ0ZXJlZmZlY3RzLmxheWVyIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphYzk0ZWY3MC0yZWQ0LTdiNDMtODlhOS03NTQ1OTAyMTE4ZWYiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPue7hOWQiCAxNS5wbmc8L3JkZjpsaT4KICAgICAgIDwvcmRmOkFsdD4KICAgICAgPC9kYzp0aXRsZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICAgPHJkZjpsaT4KICAgICAgPHJkZjpEZXNjcmlwdGlvbgogICAgICAgZGM6Zm9ybWF0PSJhcHBsaWNhdGlvbi92bmQuYWRvYmUuYWZ0ZXJlZmZlY3RzLmxheWVyIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpiMjY3NDgyNS05MjE4LTQ1NDYtYjA4ZS01ZTI3M2QyNDAyMjgiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPuefqeW9oiA2NS5wbmc8L3JkZjpsaT4KICAgICAgIDwvcmRmOkFsdD4KICAgICAgPC9kYzp0aXRsZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICAgPHJkZjpsaT4KICAgICAgPHJkZjpEZXNjcmlwdGlvbgogICAgICAgZGM6Zm9ybWF0PSJhcHBsaWNhdGlvbi92bmQuYWRvYmUuYWZ0ZXJlZmZlY3RzLmxheWVyIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpjYmJiZTk2NC0zNDJmLWEzNGQtYjNkNi0yN2RlMjkxNzFhYWEiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPuefqeW9oiA4MC5wbmc8L3JkZjpsaT4KICAgICAgIDwvcmRmOkFsdD4KICAgICAgPC9kYzp0aXRsZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICA8L3JkZjpCYWc+CiAgIDwveG1wTU06UGFudHJ5PgogICA8eG1wRE06dmlkZW9GcmFtZVNpemUKICAgIHN0RGltOnc9IjE2IgogICAgc3REaW06aD0iMTYiCiAgICBzdERpbTp1bml0PSJwaXhlbCIvPgogICA8eG1wRE06ZHVyYXRpb24KICAgIHhtcERNOnZhbHVlPSIyNSIKICAgIHhtcERNOnNjYWxlPSIxLzI1Ii8+CiAgIDx4bXBETTpzdGFydFRpbWVjb2RlCiAgICB4bXBETTp0aW1lRm9ybWF0PSIyNVRpbWVjb2RlIgogICAgeG1wRE06dGltZVZhbHVlPSIwMDowMDowMDowMCIvPgogICA8eG1wRE06YWx0VGltZWNvZGUKICAgIHhtcERNOnRpbWVWYWx1ZT0iMDA6MDA6MDA6MDAiCiAgICB4bXBETTp0aW1lRm9ybWF0PSIyNVRpbWVjb2RlIi8+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAOw==") +} + html .menu .menu_set:hover { background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAMfHx8XFxbm5ube3t7W1tbOzs7GxsQbROGqpeg7JPBDHPpWVlRLFPmylemCnchTDQBbBQFqnbBTBQFSpaBi/QEqrYpGRkRq9QlinbBa/QI+RkRi9QBi9Qh65RI+Pjxq7QkqpYhy5RIuPjRy5Qh63RCC1RIePiSKzRiSxRn6Rg4uLiyKxRiavSCitSCSvSImJiSqrSiatSIeHhyypSiirSCqpSiynSoWFhS6lTHyJfjCjTHyHfjKhTDKhToODgzSfTmaNcIGBgTadTjKfTjabUDSdTjqZUDiZUH5+fjyXUjqXUDqXUj6VUjyVUj6TVHx8fD6TUkCRVEKPVHp6ekKNVnJ8dECPVESNVkaLVkSLVnh4eEiJWEaJVkaJWEiHWEqHWHZ2dkyFWkqFWE6DWkyDWk6BWlB+XFB+WlB8XFJ8XFR6XlR6XFJ6XGR0aFR4XlZ4Xlp0YFh2XlZ2XlxyYFh0YFpyYFxwYlxwYF5uYmZmZgAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJDwB5ACwAAAEADwAOAAAHpoB5goN4Hxdwg4l5b2Z5W1JMUnlpb4lvJywzKHBzMDYkMJWCZS15Fj6CWgt5LGSJOi95WgYFSHkeNINzVCQyUwEyNwFINyFSc3kKTml5BKh5TwR5akYKyVuCA7Z5UwN5cFIMeXFRKDdBAEhIATdPIU6ieTwqeUEDAjJ5LzOJZvw3NwahOlFmkBoYmSi46gGjITODYfJgUVLmSh4yahQNgsMgQcZEgQAAIfkECQ8AeQAsAAABAA8ADgAAB7CAeYJ5c15pgjo2eIODeEIXLExJQh1mjIM/aXhGUXlRW4xzZVcsGj6CWggwMEeLJxdMGHlaBgVIeSJfJGR5NUp5MlMBMjcBSDdwLId5QlJ5BKd5TwR5PE6DUUx5A7d5UwPMRIJpNnk3QQBISAE3T3MwOHlbJWMmeUEDAjJ5E1IbM3nsCNHRocKNQTtKYBmzKM8iL+LGJJmzJd6lPGQeMHESpUWJIxcdtgCYx0yYhnkCAQAh+QQJDwB5ACwAAAAADwAPAAAHqoB5goN5c4SHhEIoUYiDb3lvNk4wW2UoSYQlEEYhZXlqLFg2KIQtWGkNPoJVC2SMg2FOL3laBgVIeQ4zhFI2MlMBMjcBSDdOJFh5RD+CBKp5TwSCNUlwM06CA7h5UwN5Ui1feU41RjdBAEhIATdAEHCEFBF5QQMCMnkpRPCDRmo3OQS1AZEGgo5BaQ4YoQIjTZ4eLZpQ8DRISY85ZWjAqCEITyNBTpKMORQIACH5BAkPAHkALAAAAQAPAA4AAAemgHmCg3gfF3CDiXlvZnlbUkxSeWlviW8nLDMocHMwNiQwlYJlLXkWPoJaC3ksZIk6L3laBgVIeR40g3NUJDJTATI3AUg3IVJzeQpOaXkEqHlPBHlqRgrJW4IDtnlTA3lwUgx5cVEoN0EASEgBN08hTqJ5PCp5QQMCMnkvM4lm/Dc3BqE6UWaQGhiZKLjqAaMhM4Nh8mBRUuZKHjJqFA2CwyBBxkSBAAAh+QQJDwB5ACwAAAEADwAOAAAHsIB5gnlzXmmCOjZ4g4N4QhcsTElCHWaMgz9peEZReVFbjHNlVywaPoJaCDAwR4snF0wYeVoGBUh5Il8kZHk1SnkyUwEyNwFIN3Ash3lCUnkEp3lPBHk8ToNRTHkDt3lTA8xEgmk2eTdBAEhIATdPczA4eVslYyZ5QQMCMnkTUhszeewI0dGhwo1BO0pgGbMozyIv4sYkmbMl3qU8ZB4wcRKlRYkjFx22AJjHTJiGeQIBACH5BAkPAHkALAAAAAAPAA8AAAeqgHmCg3lzhIeEQihRiINveW82TjBbZShJhCUQRiFleWosWDYohC1YaQ0+glULZIyDYU4veVoGBUh5DjOEUjYyUwEyNwFIN04kWHlEP4IEqnlPBII1SXAzToIDuHlTA3lSLV95TjVGN0EASEgBN0AQcIQUEXlBAwIyeSlE8INGajc5BLUBkQaCjkFpDhihAiNNnh4tmlDwNEhJjzllaMCoIQhPI0FOkow5FAgAIfkECQ8AeQAsAAABAA8ADgAAB6aAeYKDeB8XcIOJeW9meVtSTFJ5aW+JbycsMyhwczA2JDCVgmUteRY+gloLeSxkiToveVoGBUh5HjSDc1QkMlMBMjcBSDchUnN5Ck5peQSoeU8EeWpGCslbggO2eVMDeXBSDHlxUSg3QQBISAE3TyFOonk8KnlBAwIyeS8ziWb8NzcGoTpRZpAaGJkouOoBoyEzg2HyYFFS5koeMmoUDYLDIEHGRIEAACH5BAkPAHkALAAAAQAPAA4AAAewgHmCeXNeaYI6NniDg3hCFyxMSUIdZoyDP2l4RlF5UVuMc2VXLBo+gloIMDBHiycXTBh5WgYFSHkiXyRkeTVKeTJTATI3AUg3cCyHeUJSeQSneU8EeTxOg1FMeQO3eVMDzESCaTZ5N0EASEgBN09zMDh5WyVjJnlBAwIyeRNSGzN57AjR0aHCjUE7SmAZsyjPIi/ixiSZsyXepTxkHjBxEqVFiSMXHbYAmMdMmIZ5AgEAOw==") } @@ -973,9 +986,10 @@ html .menu .menu_exit:hover { } .pos-box { - height: 50px; - width: 100%; - box-shadow: 0 1px 2px 0 rgba(0,0,0,.1); + height: 50px; + width: 100%; + box-shadow: 0 3px 2px 0 rgba(0, 0, 0, 5%) !important; + border-radius: 4px; } .conter-box{ @@ -4872,3 +4886,67 @@ select[disabled]{ cursor: default; border: 1px solid #dbdbea; } + + +/* site的tab切换 */ +.tab-list .tabs-item:hover { + background: #20a53a08; +} + +.tab-list .tabs-item:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.tab-list .tabs-item { + display: inline-block; + height: 45px; + min-width: 100px; + padding: 0 20px; + line-height: 45px; + font-size: 13px; + cursor: pointer; + white-space: nowrap; + position: relative; + text-align: center; +} +@media screen and (max-width: 1300px) { + .tab-list .tabs-item{ + padding: 0 10px; + min-width: 80px; + } +} +.tab-list .tabs-item.active:after { + content: ''; + width: 20px; + height: 2px; + position: absolute; + left: 50%; + bottom: 0px; + background: red; + margin-left: -10px; + background: #20a53a; +} + +.tab-list .tabs-item.active { + color: #20a53a; + background: #20a53a10; +} + +.tab-view-box { + padding: 15px; + border-radius: 4px; + box-shadow: 0 0 1px 0 rgb(0 0 0 / 5%); +} + +.radius4 { + border-radius: 4px; + box-shadow: 0 0 1px 0 rgb(0 0 0 / 5%); +} +.radius2 { + border-radius: 2px; +} +.tootls_group { + height: 30px; + line-height: 30px; +} diff --git a/route/templates/default/firewall.html b/route/templates/default/firewall.html index 1215a61c3..5934d746b 100755 --- a/route/templates/default/firewall.html +++ b/route/templates/default/firewall.html @@ -106,36 +106,7 @@ - - -
      -
      -
      -
      -

      面板操作日志

      - 清空 -
      -
      -
      - - - - - - - - - - -
      编号操作类型详情操作时间
      -
      - -
      -
      -
      -
      + diff --git a/route/templates/default/layout.html b/route/templates/default/layout.html index 2fbd9474d..a4a680264 100755 --- a/route/templates/default/layout.html +++ b/route/templates/default/layout.html @@ -53,6 +53,7 @@
    • 监控
    • 安全
    • 文件
    • +
    • 日志
    • 计划任务
    • {% for menu in data['hook_menu'] %} @@ -85,7 +86,11 @@ - + + + + + diff --git a/route/templates/default/logs.html b/route/templates/default/logs.html new file mode 100644 index 000000000..ac3e0b876 --- /dev/null +++ b/route/templates/default/logs.html @@ -0,0 +1,189 @@ +{% extends "layout.html" %} +{% block content %} + + + +
      +
      +
      +
      +
      面板日志
      +
      日志审计
      +
      +
      + + +
      + + +
      +
      +
      +
      + + +
      +
      +
      + + + + + + + + +
      编号操作类型详情操作时间
      +
      +
      +
      +
      1共1条
      +
      +
      + + + +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      + +
      +
      +
      + + + + + + + + + + + + +
      用户来源端口时间
      +
      +
      +
      +
      +
      第 1 页
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + +{% endblock %} \ No newline at end of file diff --git a/scripts/init.d/mw.tpl b/scripts/init.d/mw.tpl index d1737382e..1e68c1aa1 100755 --- a/scripts/init.d/mw.tpl +++ b/scripts/init.d/mw.tpl @@ -22,6 +22,9 @@ PATH=$PATH:$mw_path/bin if [ -f $mw_path/bin/activate ];then source $mw_path/bin/activate + if [ "$?" != "0" ];then + echo "load local python env fail!" + fi fi mw_start_panel() @@ -106,16 +109,20 @@ mw_stop_task() mw_stop_panel() { echo -e "stopping mw-panel... \c"; + + pidfile=${mw_path}/logs/mw.pid + if [ -f $pidfile ];then + pid=`cat $pidfile` + kill -9 $pid > /dev/null 2>&1 + rm -f $pidfile + fi + arr=`ps aux|grep 'gunicorn -c setting.py app:app'|grep -v grep|awk '{print $2}'` for p in ${arr[@]} do kill -9 $p > /dev/null 2>&1 done - pidfile=${mw_path}/logs/mw.pid - if [ -f $pidfile ];then - rm -f $pidfile - fi echo -e "\033[32mdone\033[0m" } diff --git a/scripts/install/alma.sh b/scripts/install/alma.sh index 2aadcf3c9..0684aae64 100755 --- a/scripts/install/alma.sh +++ b/scripts/install/alma.sh @@ -104,6 +104,7 @@ yum install -y graphviz libxml2 libxml2-devel yum install -y sqlite-devel yum install -y oniguruma oniguruma-devel yum install -y ImageMagick ImageMagick-devel +yum install -y libargon2-devel for yumPack in make cmake gcc gcc-c++ flex bison file libtool libtool-libs autoconf kernel-devel patch wget gd gd-devel libxml2 libxml2-devel zlib zlib-devel glib2 glib2-devel tar bzip2 bzip2-devel libevent libevent-devel ncurses ncurses-devel curl curl-devel libcurl libcurl-devel e2fsprogs e2fsprogs-devel libidn libidn-devel vim-minimal gettext gettext-devel ncurses-devel gmp-devel libcap diffutils ca-certificates net-tools psmisc libXpm-devel git-core c-ares-devel libicu-devel libxslt libxslt-devel zip unzip glibc.i686 libstdc++.so.6 cairo-devel bison-devel ncurses-devel libaio-devel perl perl-devel perl-Data-Dumper lsof crontabs expat-devel readline-devel; diff --git a/scripts/install/amazon.sh b/scripts/install/amazon.sh index b857bb4ee..d1df562bd 100755 --- a/scripts/install/amazon.sh +++ b/scripts/install/amazon.sh @@ -97,7 +97,10 @@ yum install -y libzip libzip-devel yum install -y pcre pcre-devel yum install -y icu libicu-devel yum install -y freetype freetype-devel + yum install -y openssl openssl-devel +yum install -y libargon2-devel + yum install -y graphviz libxml2 libxml2-devel yum install -y sqlite-devel yum install -y oniguruma oniguruma-devel diff --git a/scripts/install/centos.sh b/scripts/install/centos.sh index 57fe01264..92c73b967 100755 --- a/scripts/install/centos.sh +++ b/scripts/install/centos.sh @@ -109,6 +109,7 @@ yum install -y libjpeg-devel libpng-devel libwebp libwebp-devel yum install -y net-tools yum install -y ncurses-devel yum install -y libXpm-devel +yum install -y libargon2-devel for yumPack in flex file libtool libtool-libs kernel-devel patch wget glib2 glib2-devel tar bzip2 bzip2-devel libevent libevent-devel ncurses ncurses-devel curl curl-devel libcurl libcurl-devel e2fsprogs e2fsprogs-devel libidn libidn-devel vim-minimal gettext gettext-devel ncurses-devel gmp-devel libcap diffutils ca-certificates net-tools psmisc git-core c-ares-devel libicu-devel libxslt libxslt-devel zip unzip glibc.i686 libstdc++.so.6 cairo-devel ncurses-devel libaio-devel perl perl-devel perl-Data-Dumper expat-devel readline-devel; diff --git a/scripts/install/debian.sh b/scripts/install/debian.sh index 481897bb7..1785d824c 100644 --- a/scripts/install/debian.sh +++ b/scripts/install/debian.sh @@ -160,6 +160,7 @@ apt install -y libunwind-dev apt install -y libpcre3 libpcre3-dev apt install -y openssl apt install -y libssl-dev +apt install -y libargon2-dev apt install -y libmemcached-dev apt install -y libsasl2-dev diff --git a/scripts/install/fedora.sh b/scripts/install/fedora.sh index 95f77915b..418aa1d59 100644 --- a/scripts/install/fedora.sh +++ b/scripts/install/fedora.sh @@ -88,6 +88,7 @@ yum install -y openldap openldap-devel yum install -y bison re2c yum install -y cmake cmake3 yum install -y autoconf +yum install -y libargon2-devel yum install -y libmemcached libmemcached-devel yum install -y curl curl-devel diff --git a/scripts/install/freebsd.sh b/scripts/install/freebsd.sh index be632588a..ed004491b 100644 --- a/scripts/install/freebsd.sh +++ b/scripts/install/freebsd.sh @@ -3,22 +3,40 @@ PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH LANG=en_US.UTF-8 +# -- debug start -- + +# https://www.freebsd.org/where/ + +# 手动升级到,可解决库找不到的问题。 +# freebsd-update -r 13.2-RELEASE upgrade +# freebsd-update -r 14-RELEASE upgrade + +# pkg install -y python39 +# python3 -m ensurepip +# pip3 install --upgrade setuptools +# python3 -m pip install --upgrade pip + +# echo "y" | pkg upgrade + +# -- debug end -- + if grep -Eq "FreeBSD" /etc/*-release && [ ! -f /bin/bash ]; then ln -sf /usr/local/bin/bash /bin/bash fi -# freebsd-update -r 13.0-RELEASE upgrade echo "y" | pkg update -# echo "y" | pkg upgrade -# echo "y" | freebsd-update fetch install +echo "y" | pkg bootstrap -f +echo "y" | freebsd-update install + pkg install -y python3 -# python3 -m ensurepip -# pip3 install --upgrade setuptools -# python3 -m pip install --upgrade pip +pkg install -y py39-pip + pkg install -y lsof +pkg install -y vim pkg install -y sqlite3 -pkg install -y py38-sqlite3 + +pkg install -y py39-sqlite3 pkg install -y gcc pkg install -y autoconf @@ -31,60 +49,41 @@ pkg install -y influxpkg-config pkg install -y expect pkg install -y pcre +pkg install -y libmemcached pkg install -y webp pkg install -y freetype pkg install -y oniguruma pkg install -y brotli pkg install -y harfbuzz +pkg install -y libevent +pkg install -y pidof +pkg install -y pstree + +# curl https://sh.rustup.rs -sSf | sh +pkg install -y rust pkg autoremove -y -SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` -if [ "$SSH_PORT" == "" ];then - SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` - SSH_PORT=${SSH_PORT_LINE/"Port "/""} -fi + +SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep -E "Port d*" | tail -1` +SSH_PORT=${SSH_PORT_LINE/"Port "/""} + echo "SSH PORT:${SSH_PORT}" -# if [ -f /usr/sbin/iptables ];then - -# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT -# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT -# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT -# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT -# service iptables save - -# iptables_status=`service iptables status | grep 'not running'` -# if [ "${iptables_status}" == '' ];then -# service iptables restart -# fi - -# #安装时不开启 -# service iptables stop -# fi - -if [ ! -f /usr/sbin/iptables ];then - pkg install -y firewalld - systemctl enable firewalld - systemctl start firewalld - - if [ "$SSH_PORT" != "" ];then - firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp - else - firewall-cmd --permanent --zone=public --add-port=22/tcp - fi - - firewall-cmd --permanent --zone=public --add-port=80/tcp - firewall-cmd --permanent --zone=public --add-port=443/tcp - firewall-cmd --permanent --zone=public --add-port=888/tcp - - - sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf - firewall-cmd --reload - #安装时不开启 - systemctl stop firewalld +# 检测防火墙是否开启 +FW_ENABLE=`cat /etc/rc.conf | grep firewall_enable` +if [ "$FW_ENABLE" == "" ];then + sysrc firewall_enable="YES" + sysrc firewall_type="open" + sysrc firewall_script="/etc/ipfw.rules" + sysrc firewall_logging="YES" + sysrc firewall_logif="YES" fi +# ipfw list +service ipfw stop + + cd /www/server/mdserver-web/scripts && bash lib.sh chmod 755 /www/server/mdserver-web/data diff --git a/scripts/install/opensuse.sh b/scripts/install/opensuse.sh index 9f1b20500..e00a6580f 100644 --- a/scripts/install/opensuse.sh +++ b/scripts/install/opensuse.sh @@ -49,6 +49,7 @@ zypper install -y libtomcrypt-devel zypper install -y libXpm-devel zypper install -y freetype2-devel +zypper install -y libargon2-devel # zypper install -y php-config diff --git a/scripts/install/rhel.sh b/scripts/install/rhel.sh index c2ee1c98a..fa7944724 100644 --- a/scripts/install/rhel.sh +++ b/scripts/install/rhel.sh @@ -172,7 +172,6 @@ if [ "$VERSION_ID" -ge "8" ];then # else # REPOS='--enablerepo=remi,appstream,baseos,epel,extras,powertools' # fi - for rpms in gcc gcc-c++ lsof autoconf bzip2 bzip2-devel c-ares-devel \ ca-certificates cairo-devel cmake crontabs curl curl-devel diffutils e2fsprogs e2fsprogs-devel \ expat-devel expect file flex gd gd-devel gettext gettext-devel glib2 glib2-devel glibc.i686 \ @@ -181,7 +180,7 @@ if [ "$VERSION_ID" -ge "8" ];then libpng libpng-devel libstdc++.so.6 libtirpc libtirpc-devel libtool libtool-libs libwebp libwebp-devel \ libxml2 libxml2-devel libxslt libxslt-devel libarchive make mysql-devel ncurses ncurses-devel net-tools \ oniguruma oniguruma-devel patch pcre pcre-devel perl perl-Data-Dumper perl-devel procps psmisc python3-devel \ - openssl openssl-devel patchelf\ + openssl openssl-devel patchelf libargon2-dev\ readline-devel rpcgen sqlite-devel tar unzip vim-minimal wget zip zlib zlib-devel; do # dnf --enablerepo=remi,appstream,baseos,epel,extras,powertools install -y oniguruma5php-devel @@ -202,7 +201,7 @@ else make mysql-devel ncurses ncurses-devel net-tools oniguruma oniguruma-devel openldap openldap-devel \ openssl openssl-devel patch pcre pcre-devel perl perl-Data-Dumper perl-devel psmisc python-devel \ python3-devel python3-pip re2c readline-devel rpcgen sqlite-devel tar unzip vim-minimal vixie-cron \ - wget zip zlib zlib-devel ImageMagick ImageMagick-devel patchelf; + wget zip zlib zlib-devel ImageMagick ImageMagick-devel patchelf libargon2-dev; do yum install -y $rpms; done diff --git a/scripts/install/rocky.sh b/scripts/install/rocky.sh index 2242b2dfc..452fc1774 100644 --- a/scripts/install/rocky.sh +++ b/scripts/install/rocky.sh @@ -71,6 +71,7 @@ yum install -y libpng libpng-devel libwebp libwebp-devel pcre pcre-devel gd gd-d yum install -y net-tools yum install -y ncurses ncurses-devel mysql-devel make cmake yum install -y sqlite-devel +yum install -y libargon2-dev # python-imaging # yum install -y MySQL-python diff --git a/scripts/install/ubuntu.sh b/scripts/install/ubuntu.sh index 786bb793c..867dd251f 100644 --- a/scripts/install/ubuntu.sh +++ b/scripts/install/ubuntu.sh @@ -92,6 +92,7 @@ apt install -y libunwind-dev apt install -y libpcre3 libpcre3-dev apt install -y openssl apt install -y libssl-dev +apt install -y libargon2-dev apt install -y libmemcached-dev apt install -y libsasl2-dev diff --git a/scripts/lib.sh b/scripts/lib.sh index 0fb075748..b9dd41c5b 100755 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -103,6 +103,8 @@ fi pip3 install --upgrade pip -i $PIPSRC pip3 install --upgrade setuptools -i $PIPSRC + +# --no-cache-dir cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/requirements.txt -i $PIPSRC diff --git a/scripts/update.sh b/scripts/update.sh index be2c37cab..bd20683a7 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -108,4 +108,4 @@ endTime=`date +%s` ((outTime=($endTime-$startTime)/60)) echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" -} 1> >(tee /www/server/mdserver-web/logs/mw-update.log) 2>&1 \ No newline at end of file +} 1> >(tee mw-update.log) 2>&1 \ No newline at end of file diff --git a/scripts/update/freebsd.sh b/scripts/update/freebsd.sh index 5c510cc02..5597f8699 100644 --- a/scripts/update/freebsd.sh +++ b/scripts/update/freebsd.sh @@ -14,7 +14,7 @@ fi echo -e "start mw" cd /www/server/mdserver-web && bash cli.sh start -isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +isStart=`ps -aux|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` n=0 while [[ ! -f /etc/rc.d/init.d/mw ]]; do diff --git a/scripts/update_dev.sh b/scripts/update_dev.sh index 7598d9c26..2ba251abc 100755 --- a/scripts/update_dev.sh +++ b/scripts/update_dev.sh @@ -119,4 +119,4 @@ endTime=`date +%s` ((outTime=($endTime-$startTime)/60)) echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" -} 1> >(tee /www/server/mdserver-web/logs/mw-update.log) 2>&1 \ No newline at end of file +} 1> >(tee mw-update.log) 2>&1 \ No newline at end of file