update to 6.8.37

This commit is contained in:
zhw
2024-04-29 15:35:04 +08:00
parent fa9a865beb
commit ed34994f45
474 changed files with 125711 additions and 34665 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Regular → Executable
+456 -280
View File
File diff suppressed because it is too large Load Diff
Regular → Executable
+421 -114
View File
@@ -6,7 +6,8 @@
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------
from BTPanel import session,request,cache
from flask import session,request
import public,os,json,time,apache,psutil
class ajax:
__official_url = 'https://brandnew.aapanel.com'
@@ -43,16 +44,17 @@ class ajax:
if len(tmp) < 15: is_curl = True
if is_curl:
result = public.ExecShell('curl http://127.0.0.1/nginx_status')[0]
result = public.ExecShell(
'curl http://127.0.0.1/nginx_status')[0]
tmp = result.split()
data = {}
if "request_time" in tmp:
data['accepts'] = tmp[8]
data['handled'] = tmp[9]
data['accepts'] = tmp[8]
data['handled'] = tmp[9]
data['requests'] = tmp[10]
data['Reading'] = tmp[13]
data['Writing'] = tmp[15]
data['Waiting'] = tmp[17]
data['Reading'] = tmp[13]
data['Writing'] = tmp[15]
data['Waiting'] = tmp[17]
else:
data['accepts'] = tmp[9]
data['handled'] = tmp[7]
@@ -62,7 +64,7 @@ class ajax:
data['Waiting'] = tmp[15]
data['active'] = tmp[2]
data['worker'] = worker
data['workercpu'] = round(float(process_cpu["nginx"]),2)
data['workercpu'] = round(float(process_cpu["nginx"]), 2)
data['workermen'] = "%s%s" % (int(workermen), "MB")
return data
except Exception as ex:
@@ -185,6 +187,8 @@ class ajax:
filename = public.GetConfigValue('setup_path') + '/panel/data/'+get.name+'As.conf'
conf = get.access_key.strip() + '|' + get.secret_key.strip() + '|' + get.bucket_name.strip() + '|' + get.bucket_domain.strip()
public.writeFile(filename,conf)
if not os.path.exists(filename):
return public.return_msg_gettext(False, 'write file failed!')
public.ExecShell("chmod 600 " + filename)
result = public.ExecShell(public.get_python_bin() + " " + public.GetConfigValue('setup_path') + "/panel/script/backup_"+get.name+".py list")
@@ -244,59 +248,69 @@ class ajax:
tmp['type'] = 'tcp'
else:
tmp['type'] = 'udp'
tmp['family'] = netstat.family
tmp['laddr'] = netstat.laddr
tmp['raddr'] = netstat.raddr
tmp['status'] = netstat.status
tmp['family'] = netstat.family
tmp['laddr'] = netstat.laddr
tmp['raddr'] = netstat.raddr
tmp['status'] = netstat.status
p = psutil.Process(netstat.pid)
tmp['process'] = p.name()
tmp['pid'] = netstat.pid
tmp['process'] = p.name()
tmp['pid'] = netstat.pid
networkList.append(tmp)
del(p)
del(tmp)
networkList = sorted(networkList, key=lambda x : x['status'], reverse=True)
del (p)
del (tmp)
networkList = sorted(networkList,
key=lambda x: x['status'],
reverse=True)
return networkList
#取进程列表
def GetProcessList(self,get):
import psutil,pwd
def GetProcessList(self, get):
import psutil, pwd
Pids = psutil.pids()
processList = []
for pid in Pids:
try:
tmp = {}
p = psutil.Process(pid)
if p.exe() == "": continue
tmp['name'] = p.name(); #进程名称
tmp['name'] = p.name()
#进程名称
if self.GoToProcess(tmp['name']): continue
tmp['pid'] = pid #进程标识
tmp['status'] = p.status() #进程状态
tmp['user'] = p.username() #执行用户
tmp['pid'] = pid
#进程标识
tmp['status'] = p.status()
#进程状态
tmp['user'] = p.username()
#执行用户
cputimes = p.cpu_times()
tmp['cpu_percent'] = p.cpu_percent(0.1)
tmp['cpu_times'] = cputimes.user #进程占用的CPU时间
tmp['memory_percent'] = round(p.memory_percent(),3) #进程占用的内存比例
tmp['cpu_times'] = cputimes.user #进程占用的CPU时间
tmp['memory_percent'] = round(p.memory_percent(),
3) #进程占用的内存比例
pio = p.io_counters()
tmp['io_write_bytes'] = pio.write_bytes #进程总共写入字节数
tmp['io_read_bytes'] = pio.read_bytes #进程总共读取字节数
tmp['threads'] = p.num_threads() #进程总线程数
tmp['io_write_bytes'] = pio.write_bytes #进程总共写入字节数
tmp['io_read_bytes'] = pio.read_bytes #进程总共读取字节数
tmp['threads'] = p.num_threads() #进程总线程数
processList.append(tmp)
del(p)
del(tmp)
del (p)
del (tmp)
except:
continue
import operator
processList = sorted(processList, key=lambda x : x['memory_percent'], reverse=True)
processList = sorted(processList, key=lambda x : x['cpu_times'], reverse=True)
processList = sorted(processList,
key=lambda x: x['memory_percent'],
reverse=True)
processList = sorted(processList,
key=lambda x: x['cpu_times'],
reverse=True)
return processList
#结束指定进程
def KillProcess(self,get):
def KillProcess(self, get):
#return public.returnMsg(False,'演示服务器,禁止此操作!');
import psutil
p = psutil.Process(int(get.pid))
@@ -318,24 +332,138 @@ class ajax:
def GetNetWorkIo(self,get):
#取指定时间段的网络Io
data = public.M('network').dbfile('system').where("addtime>=? AND addtime<=?",(get.start,get.end)).field('id,up,down,total_up,total_down,down_packets,up_packets,addtime').order('id asc').select()
return self.ToAddtime(data,None)
def GetDiskIo(self,get):
data = public.M('network').dbfile('system').where(
"addtime>=? AND addtime<=?", (get.start, get.end)
).field(
'id,up,down,total_up,total_down,down_packets,up_packets,addtime'
).order('id desc').select()
return self.ToAddtime(data, None)
def GetDiskIo(self, get):
#取指定时间段的磁盘Io
data = public.M('diskio').dbfile('system').where("addtime>=? AND addtime<=?",(get.start,get.end)).field('id,read_count,write_count,read_bytes,write_bytes,read_time,write_time,addtime').order('id asc').select()
return self.ToAddtime(data)
def GetCpuIo(self,get):
__OPT_FIELD = "*"
tmp_cols = public.M('diskio').dbfile('system').query(
'PRAGMA table_info(diskio)', ())
cols = []
for col in tmp_cols:
if len(col) > 2: cols.append('`' + col[1] + '`')
if len(cols) > 0:
cols.append("disk_top")
__OPT_FIELD = ','.join(cols)
data = public.M('diskio').dbfile('system').query(
"SELECT diskio.*,process_top_list.disk_top from diskio inner join process_top_list on diskio.addtime=process_top_list.addtime where diskio.addtime>={} AND diskio.addtime<={} ORDER BY diskio.addtime desc;"
.format(get.start, get.end), ())
if isinstance(data, str) and data.find(
'error: no such table: process_top_list') != -1:
return public.M('diskio').dbfile('system').where(
"addtime>=? AND addtime<=?", (get.start, get.end)
).field(
'id,read_count,write_count,read_bytes,write_bytes,read_time,write_time,addtime'
).order('id asc').select()
try:
if __OPT_FIELD != "*":
fields = self.__format_field(__OPT_FIELD.split(','))
tmp = []
for row in data:
i = 0
tmp1 = {}
for key in fields:
tmp1[key.strip('`')] = row[i]
i += 1
tmp.append(tmp1)
del (tmp1)
data = tmp
except:
return []
return self.ToAddtime(data, True, 'disk')
def __format_field(self,field):
import re
fields = []
for key in field:
s_as = re.search(r'\s+as\s+',key,flags=re.IGNORECASE)
if s_as:
as_tip = s_as.group()
key = key.split(as_tip)[1]
fields.append(key)
return fields
def GetCpuIo(self, get):
#取指定时间段的CpuIo
data = public.M('cpuio').dbfile('system').where("addtime>=? AND addtime<=?",(get.start,get.end)).field('id,pro,mem,addtime').order('id asc').select()
return self.ToAddtime(data,True)
def get_load_average(self,get):
data = public.M('load_average').dbfile('system').where("addtime>=? AND addtime<=?",(get.start,get.end)).field('id,pro,one,five,fifteen,addtime').order('id asc').select()
return self.ToAddtime(data)
__OPT_FIELD = "*"
tmp_cols = public.M('cpuio').dbfile('system').query(
'PRAGMA table_info(cpuio)', ())
cols = []
for col in tmp_cols:
if len(col) > 2: cols.append('`' + col[1] + '`')
if len(cols) > 0:
cols.append("cpu_top")
cols.append("memory_top")
__OPT_FIELD = ','.join(cols)
data = public.M('cpuio').dbfile('system').query(
"SELECT cpuio.*,process_top_list.cpu_top,process_top_list.memory_top from cpuio inner join process_top_list on cpuio.addtime=process_top_list.addtime where cpuio.addtime>={} AND cpuio.addtime<={} ORDER BY cpuio.addtime desc;"
.format(get.start, get.end), ())
if isinstance(data, str) and data.find(
'error: no such table: process_top_list') != -1:
return public.M('cpuio').dbfile('system').where(
"addtime>=? AND addtime<=?",
(get.start, get.end
)).field('id,pro,mem,addtime').order('id asc').select()
try:
if __OPT_FIELD != "*":
fields = self.__format_field(__OPT_FIELD.split(','))
tmp = []
for row in data:
i = 0
tmp1 = {}
for key in fields:
tmp1[key.strip('`')] = row[i]
i += 1
tmp.append(tmp1)
del (tmp1)
data = tmp
except:
return []
return self.ToAddtime(data, True, 'cpu')
def get_load_average(self, get):
__OPT_FIELD = "*"
tmp_cols = public.M('load_average').dbfile('system').query(
'PRAGMA table_info(load_average)', ())
cols = []
for col in tmp_cols:
if len(col) > 2: cols.append('`' + col[1] + '`')
if len(cols) > 0:
cols.append("cpu_top")
__OPT_FIELD = ','.join(cols)
data = public.M('load_average').dbfile('system').query(
"SELECT load_average.*,process_top_list.cpu_top from load_average inner join process_top_list on load_average.addtime=process_top_list.addtime where load_average.addtime>={} AND load_average.addtime<={} ORDER BY load_average.addtime desc;"
.format(get.start, get.end), ())
if isinstance(data, str) and data.find(
'error: no such table: process_top_list') != -1:
return public.M('load_average').dbfile('system').where(
"addtime>=? AND addtime<=?",
(get.start, get.end)).field('id,pro,one,five,fifteen,addtime'
).order('id asc').select()
try:
if __OPT_FIELD != "*":
fields = self.__format_field(__OPT_FIELD.split(','))
tmp = []
for row in data:
i = 0
tmp1 = {}
for key in fields:
tmp1[key.strip('`')] = row[i]
i += 1
tmp.append(tmp1)
del (tmp1)
data = tmp
except:
return []
return self.ToAddtime(data, True, 'cpu')
def get_process_tops(self,get):
def get_process_tops(self, get):
'''
@name 获取进程开销排行
@author hwliang<2021-09-07>
@@ -345,11 +473,13 @@ class ajax:
}
@return list
'''
data = public.M('process_tops').dbfile('system').where("addtime>=? AND addtime<=?",(get.start,get.end)).field('id,process_list,addtime').order('id asc').select()
data = public.M('process_tops').dbfile('system').where(
"addtime>=? AND addtime<=?",
(get.start, get.end
)).field('id,process_list,addtime').order('id asc').select()
return self.ToAddtime(data)
def get_process_cpu_high(self,get):
def get_process_cpu_high(self, get):
'''
@name 获取CPU占用高的进程列表
@author hwliang<2021-09-07>
@@ -359,17 +489,16 @@ class ajax:
}
@return list
'''
data = public.M('process_high_percent').dbfile('system').where("addtime>=? AND addtime<=?",(get.start,get.end)).field('id,name,pid,cmdline,cpu_percent,memory,cpu_time_total,addtime').order('id asc').select()
data = public.M('process_high_percent').dbfile('system').where(
"addtime>=? AND addtime<=?", (get.start, get.end)).field(
'id,name,pid,cmdline,cpu_percent,memory,cpu_time_total,addtime'
).order('id asc').select()
return self.ToAddtime(data)
def ToAddtime(self,data,tomem = False):
def ToAddtime(self, data, tomem=False, types=None):
import time
#格式化addtime列
if tomem:
import psutil
mPre = (psutil.virtual_memory().total / 1024 / 1024) / 100
@@ -381,26 +510,71 @@ class ajax:
if he == 1:
for i in range(length):
try:
data[i]['addtime'] = time.strftime('%m/%d %H:%M',time.localtime(float(data[i]['addtime'])))
if types:
key = '{}_top'.format(types)
if key in data[i]:
data[i][key] = json.loads(data[i][key])
if 'memory_top' in data[i]:
data[i]['memory_top'] = json.loads(
data[i]['memory_top'])
data[i]['addtime'] = time.strftime(
'%m/%d %H:%M',
time.localtime(float(data[i]['addtime'])))
if 'process_list' in data[i]:
data[i]['process_list'] = json.loads(data[i]['process_list'])
if tomem and data[i]['mem'] > 100: data[i]['mem'] = data[i]['mem'] / mPre
data[i]['process_list'] = json.loads(
data[i]['process_list'])
if tomem and data[i]['mem'] > 100:
data[i]['mem'] = data[i]['mem'] / mPre
if tomem in [None]:
if type(data[i]['down_packets']) == str:
data[i]['down_packets'] = json.loads(data[i]['down_packets'])
data[i]['up_packets'] = json.loads(data[i]['up_packets'])
except: continue
data[i]['down_packets'] = json.loads(
data[i]['down_packets'])
data[i]['up_packets'] = json.loads(
data[i]['up_packets'])
except:
continue
return data
else:
count = 0
tmp = []
couns = 0
for value in data:
if count < he:
if count < he: # 0 1 2
count += 1
#cpu大于60的时候,随机取
if types == "cpu" and 'pro' in value and value['pro'] > 60:
couns += 1
#he等于3 的时候 百分之50的概率取 当he等于15的时候 百分之33的概率取
if (he == 3
and couns % 2 == 0) or (he == 15
and couns % 3 == 0):
if types:
key = '{}_top'.format(types)
if key in value:
value[key] = json.loads(value[key])
if 'memory_top' in value:
value['memory_top'] = json.loads(
value['memory_top'])
value['addtime'] = time.strftime(
'%m/%d %H:%M',
time.localtime(float(value['addtime'])))
if tomem and 'mem' in value and value['mem'] > 100:
value['mem'] = value['mem'] / mPre
if tomem in [None]:
if type(value['down_packets']) == str:
value['down_packets'] = json.loads(value['down_packets'])
value['up_packets'] = json.loads(value['up_packets'])
tmp.append(value)
continue
try:
if types:
key='{}_top'.format(types)
if key in value:
value[key] = json.loads(value[key])
if 'memory_top' in value:
value['memory_top'] = json.loads(value['memory_top'])
value['addtime'] = time.strftime('%m/%d %H:%M',time.localtime(float(value['addtime'])))
if tomem and value['mem'] > 100: value['mem'] = value['mem'] / mPre
if tomem and 'mem' in value and value['mem'] > 100: value['mem'] = value['mem'] / mPre
if tomem in [None]:
if type(value['down_packets']) == str:
value['down_packets'] = json.loads(value['down_packets'])
@@ -409,7 +583,9 @@ class ajax:
count = 0
except: continue
return tmp
def GetInstalleds(self,softlist):
softs = ''
for soft in softlist['data']:
@@ -421,7 +597,7 @@ class ajax:
return softs
#获取SSH爆破次数
def get_ssh_intrusion(self):
fp = open('/var/log/secure','rb')
@@ -498,7 +674,7 @@ class ajax:
# 更新面板
def UpdatePanel(self,get):
try:
if not public.IsRestart(): return public.return_msg_gettext(False,'Please run the program when all install tasks finished!')
@@ -541,7 +717,20 @@ class ajax:
if os.path.exists('/www/server/panel/data/is_beta.pl'):
updateInfo['is_beta'] = 1
session['updateInfo'] = updateInfo
# 输出忽略的版本
updateInfo['ignore'] = []
no_path = '{}/data/no_update.pl'.format(public.get_panel_path())
if os.path.exists(no_path):
try:
updateInfo['ignore'] = json.loads(public.readFile(no_path))
except:
pass
# 重启面板 默认开启系统监控
public.writeFile('data/control.conf', '30')
#检查是否需要升级
if not hasattr(get,'toUpdate'):
if updateInfo['is_beta'] == 1:
@@ -571,21 +760,7 @@ class ajax:
public.ExecShell("/etc/init.d/bt start")
public.writeFile('data/restart.pl','True')
return public.return_msg_gettext(True,'Successful to update to {}',(updateInfo['version'],))
#输出新版本信息
data = {
'status' : True,
'version': updateInfo['version'],
'updateMsg' : updateInfo['updateMsg']
}
# 输出忽略的版本
updateInfo['ignore'] = []
no_path = '{}/data/no_update.pl'.format(public.get_panel_path())
if os.path.exists(no_path):
try:
updateInfo['ignore'] = json.loads(public.readFile(no_path))
except:
pass
public.ExecShell('rm -rf /www/server/phpinfo/*')
return public.returnMsg(True,updateInfo)
except Exception as ex:
@@ -732,6 +907,13 @@ class ajax:
if 'tmp_login_id' in session:
return public.return_msg_gettext(False,'Permission denied!')
# 备份近100条日志
new_bak = public.M('logs').limit('100').select()
if len(new_bak) > 3:
bak_file = '{}/data/logs.bak'.format(public.get_panel_path())
public.writeFile(bak_file,json.dumps(new_bak))
public.add_security_logs("清空日志", '清空所有日志条数为:{}'.format(public.M('logs').count()))
# 清空日志
public.M('logs').where('id>?',(0,)).delete()
public.write_log_gettext('Panel setting','Panel Logs emptied!')
return public.return_msg_gettext(True,'Panel Logs emptied!')
@@ -802,27 +984,28 @@ class ajax:
if i == "nginx":
if not os.path.exists("/www/server/panel/vhost/apache/phpmyadmin.conf"):
return public.return_msg_gettext(False, 'Did not find the apache phpmyadmin ssl configuration file, please try to close the ssl port settings before opening')
rep = "listen\s*([0-9]+)\s*.*;"
rep = r"listen\s*([0-9]+)\s*.*;"
oldPort = re.search(rep, conf)
if not oldPort:
return public.return_msg_gettext(False, 'Did not detect the port that nginx phpmyadmin listens, please confirm whether the file has been manually modified.')
oldPort = oldPort.groups()[0]
conf = re.sub(rep, 'listen ' + get.port + ' ssl;', conf)
else:
rep = "Listen\s*([0-9]+)\s*\n"
rep = r"Listen\s*([0-9]+)\s*\n"
oldPort = re.search(rep, conf)
if not oldPort:
return public.return_msg_gettext(False, 'Did not detect the port that apache phpmyadmin listens, please confirm whether the file has been manually modified.')
oldPort = oldPort.groups()[0]
conf = re.sub(rep, "Listen " + get.port + "\n", conf, 1)
rep = "VirtualHost\s*\*:[0-9]+"
rep = r"VirtualHost\s*\*:[0-9]+"
conf = re.sub(rep, "VirtualHost *:" + get.port, conf, 1)
if oldPort == get.port: return public.return_msg_gettext(False, 'Port [{}] is in use!',(get.port,))
public.writeFile(file, conf)
public.serviceReload()
if i=="apache":
import firewalls
get.ps = public.getMsg('New phpMyAdmin Port')
# aapanel 使用 get_msg_gettext
get.ps = public.get_msg_gettext('New phpMyAdmin SSL Port')
fw = firewalls.firewalls()
fw.AddAcceptPort(get)
public.serviceReload()
@@ -994,12 +1177,16 @@ class ajax:
tmp = re.search(reg,conf)
if tmp:
oldPort = tmp.groups(1)
## 修复 openlitespeed 修改端口报错
oldPort = oldPort[0]
conf = re.sub(reg,"address *:{}".format(get.port),conf)
if oldPort == get.port: return public.returnMsg(False,'Port [{}] is in use!',(get.port,))
if oldPort == get.port: return public.return_msg_gettext(False,'Port [{}] is in use!',(get.port,))
public.writeFile(filename,conf)
import firewalls
get.ps = public.getMsg('New phpMyAdmin Port')
get.ps = public.get_msg_gettext('New phpMyAdmin Port')
fw = firewalls.firewalls()
fw.AddAcceptPort(get)
public.serviceReload()
@@ -1242,7 +1429,7 @@ class ajax:
#取指定日志
def GetOpeLogs(self,get):
if not os.path.exists(get.path): return public.return_msg_gettext(False,'Log file does NOT exist!')
return public.returnMsg(True,public.GetNumLines(get.path,1000))
return public.returnMsg(True,public.xsssec(public.GetNumLines(get.path,1000)))
def get_pd(self,get):
from BTPanel import cache
@@ -1304,15 +1491,15 @@ class ajax:
110, 62])
if tmp >= 0 and ltd in [-1, -2]:
if tmp == 0:
tmp2 = public.to_string([27704, 20037, 25480, 26435])
tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116,
112, 114, 111, 34, 62, 123, 48, 125, 60, 115, 112, 97, 110, 32, 115, 116,
121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54,
100,
50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116,
58, 32, 98, 111, 108, 100, 59, 34, 62, 123, 49, 125, 60, 47, 115,
112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62]).format(
public.to_string([21040, 26399, 26102, 38388, 65306]), tmp2)
tmp2 = public.to_string([76, 105, 102, 101, 116, 105, 109, 101])
tmp3 = public.to_string(
[60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112, 114,
111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 60, 115, 112, 97, 110, 32, 115,
116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54,
100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32,
98, 111, 108, 100, 59, 34, 62, 123, 48, 125, 60, 47, 115, 112, 97, 110, 62, 60,
47, 115, 112, 97, 110, 62]).format(tmp2)
else:
tmp2 = time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(tmp))
tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116,
@@ -1405,10 +1592,12 @@ class ajax:
#取指定行
def get_lines(self,args):
if not os.path.exists(args.filename): return public.returnMsg(False,'Logs emptied')
s_body = public.ExecShell("tail -n {} {}".format(args.num,args.filename))[0]
num = args.get('num/d',10)
s_body = public.GetNumLines(args.filename,num)
return public.returnMsg(True,s_body)
def log_analysis(self,get):
public.set_module_logs('log_analysis', 'log_analysis', 1)
import log_analysis
log_analysis=log_analysis.log_analysis()
return log_analysis.log_analysis(get)
@@ -1439,16 +1628,44 @@ class ajax:
"""
@name 获取推荐列表
"""
# spath = '{}/data/pay_type.json'.format(public.get_panel_path())
# if not os.path.exists(spath):
# public.run_thread(self.download_pay_type,(spath,))
# try:
# data = json.loads(public.readFile("data/pay_type.json"))
# except:
# public.run_thread(self.download_pay_type, (spath,))
# data = {}
#
# import panelPlugin
# plu_panel = panelPlugin.panelPlugin()
# plugin_list = plu_panel.get_cloud_list()
# if not 'pro' in plugin_list: plugin_list['pro'] = -1
#
# for item in data:
# if 'list' in item:
# item['list'] = self.__get_home_list(item['list'], item['type'], plugin_list, plu_panel)
# if item['type'] == 1:
# if len(item['list']) > 4: item['list'] = item['list'][:4]
# # if item['type'] == 0 and plugin_list['pro'] >= 0:
# # item['show'] = False
#
# return data
spath = '{}/data/pay_type.json'.format(public.get_panel_path())
down = cache.get('pay_type')
if not down:
if os.path.exists(spath) and os.path.getsize(spath) <= 0:
os.remove(spath)
if not os.path.exists(spath):
public.run_thread(self.download_pay_type, (spath,))
cache.set('pay_type', 1, 86400)
try:
data = json.loads(public.readFile("data/pay_type.json"))
except:
except json.decoder.JSONDecodeError:
os.remove(spath)
public.run_thread(self.download_pay_type, (spath,))
data = {}
data = json.loads(public.readFile("data/pay_type.json"))
except Exception:
data = self.get_default_pay_type()
import panelPlugin
plu_panel = panelPlugin.panelPlugin()
@@ -1457,13 +1674,103 @@ class ajax:
for item in data:
if 'list' in item:
item['list'] = self.__get_home_list(item['list'], item['type'], plugin_list, plu_panel)
item['list'] = self.__get_home_list(item['list'], item['type'],plugin_list, plu_panel)
if item['type'] == 1:
if len(item['list']) > 4: item['list'] = item['list'][:4]
# if item['type'] == 0 and plugin_list['pro'] >= 0:
# item['show'] = False
return data
@staticmethod
def get_default_pay_type():
spath = '{}/data/default_pay_type.json'.format(public.get_panel_path())
default = [{"type": -1}, {"type": -1}, {"type": -1}, {"type": -1},
{"type": -1}, {
"type": 5,
"describe": "网站-设置推荐",
"show": True,
"list": [
{
"title": "防火墙",
"name": "btwaf",
"pay": "46",
"pluginName": "Nginx网站防火墙",
"ps": "有效拦截SQL 注入、XSS跨站、恶意代码、网站挂马等常见攻击,过滤恶意访问,降低数据泄露的风险,保障网站的可用性。",
"preview": "https://www.bt.cn/new/product_nginx_firewall.html",
"dependent": "nginx",
"pluginType": "pro",
"eventList": [
{
"event": "site_waf_config('$siteName')",
"version": "5.2.0"
}
]
},
{
"title": "防火墙",
"name": "btwaf_httpd",
"pay": "46",
"pluginName": "网站防火墙",
"ps": "有效拦截SQL 注入、XSS跨站、恶意代码、网站挂马等常见攻击,过滤恶意访问,降低数据泄露的风险,保障网站的可用性。",
"preview": "https://www.bt.cn/new/product_nginx_firewall.html",
"dependent": "apache",
"pluginType": "pro",
"eventList": [
{
"event": "site_waf_config('$siteName')",
"version": "5.2.0"
}
]
},
{
"title": "统计",
"name": "total",
"pay": "47",
"pluginName": "网站监控报表",
"ps": "快速分析网站运行状况,实时精确统计网站流量、ip、uv、pv、请求、蜘蛛等数据,网站SEO优化利器",
"preview": "https://www.bt.cn/new/product_website_total.html",
"dependent": "apache",
"pluginType": "pro",
"eventList": [
{
"event": "WebsiteReport('$siteName')",
"version": "5.0"
}
]
},
{
"title": "统计",
"name": "total",
"pay": "47",
"pluginName": "网站监控报表",
"ps": "快速分析网站运行状况,实时精确统计网站流量、ip、uv、pv、请求、蜘蛛等数据,网站SEO优化利器",
"preview": "https://www.bt.cn/new/product_website_total.html",
"dependent": "nginx",
"pluginType": "pro",
"eventList": [
{
"event": "WebsiteReport('$siteName')",
"version": "5.0"
}
]
}
]
}, {"type": -1}, {"type": -1}]
if os.path.isfile(spath):
try:
res_data = json.loads(public.readFile(spath))
if isinstance(res_data, list):
return res_data
except json.JSONDecodeError:
pass
# 再次出错时,保障网站列表可以展示
return default
return default
def __get_home_list(self, sList, stype, plugin_list, plu_panel):
"""
@name 获取首页软件列表推荐
+2
View File
@@ -2,6 +2,7 @@
from cachelib.base import BaseCache, NullCache
from cachelib.simple import SimpleCache
from cachelib.session_simpile import SimpleCacheSession
from cachelib.file import FileSystemCache
from cachelib.memcached import MemcachedCache
from cachelib.redis import RedisCache
@@ -15,6 +16,7 @@ __all__ = [
'MemcachedCache',
'RedisCache',
'UWSGICache',
'SimpleCacheSession'
]
__version__ = '0.1'
+196
View File
@@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
from time import time
import os,struct
try:
import cPickle as pickle
except ImportError: # pragma: no cover
import pickle
from cachelib.base import BaseCache
import io
import builtins
safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins" and name in safe_builtins:
# print(name)
return getattr(builtins, name)
return None
def restricted_loads(s):
# return RestrictedUnpickler(io.BytesIO(s)).load()
return True
class SimpleCacheSession(BaseCache):
"""Simple memory cache for single process environments. This class exists
mainly for the development server and is not 100% thread safe. It tries
to use as many atomic operations as possible and no locks for simplicity
but it could happen under heavy load that keys are added multiple times.
:param threshold: the maximum number of items the cache stores before
it starts deleting some.
:param default_timeout: the default timeout that is used if no timeout is
specified on :meth:`~BaseCache.set`. A timeout of
0 indicates that the cache never expires.
"""
__session_key = 'BT_:'
__session_basedir = '/www/server/panel/data/session'
def __init__(self, threshold=500, default_timeout=300):
BaseCache.__init__(self, default_timeout)
self._cache = {}
self.clear = self._cache.clear
self._threshold = threshold
def _prune(self):
if len(self._cache) > self._threshold:
now = time()
toremove = []
for idx, (key, (expires, _)) in enumerate(self._cache.items()):
if (expires != 0 and expires <= now) or idx % 3 == 0:
toremove.append(key)
for key in toremove:
self._cache.pop(key, None)
self.del_session_by_file(key)
def _normalize_timeout(self, timeout):
timeout = BaseCache._normalize_timeout(self, timeout)
if timeout > 0:
timeout = time() + timeout
return timeout
def get_session_by_file(self,key):
try:
if key[:4] == self.__session_key:
filename = '/'.join((self.__session_basedir,self.md5(key)))
if not os.path.exists(filename): return None
with open(filename, 'rb') as fp:
_val = fp.read()
fp.close()
expires = struct.unpack('f',_val[:4])[0]
if expires == 0 or expires > time():
value = _val[4:]
self._cache[key] = (expires,value)
return pickle.loads(value)
except :pass
def set_session_by_file(self,key,_val,expires):
try:
if key[:4] == self.__session_key:
if not os.path.exists(self.__session_basedir): os.makedirs(self.__session_basedir,384)
expires = struct.pack('f',expires)
filename = '/'.join((self.__session_basedir,self.md5(key)))
fp = open(filename, 'wb+')
fp.write(expires + _val)
fp.close()
os.chmod(filename,384)
except :pass
def del_session_by_file(self,key):
try:
if key[:4] == self.__session_key:
filename = '/'.join((self.__session_basedir,self.md5(key)))
if os.path.exists(filename): os.remove(filename)
except : pass
def get(self, key):
if not isinstance(key,str): return None
try:
expires, value = self._cache[key]
if expires == 0 or expires > time():
return pickle.loads(value)
except (KeyError, pickle.PickleError):
return self.get_session_by_file(key)
def set(self, key, value, timeout=None):
# 类型判断
if not isinstance(key,str): return False
type_list=(int,float,bool,str,list,dict,tuple,set,bytes)
value_type=type(value)
if value_type not in type_list:
return False
# 过期清理
expires = self._normalize_timeout(timeout)
self._prune()
try:
restricted_loads(pickle.dumps(value))
except:
return False
# 转换
_val = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
self._cache[key] = (expires,_val)
self.set_session_by_file(key,_val,expires)
return True
def add(self, key, value, timeout=None):
# 类型判断
if not isinstance(key,str): return False
type_list=(int,float,bool,str,list,dict,tuple,set,bytes)
value_type=type(value)
if value_type not in type_list:
return False
expires = self._normalize_timeout(timeout)
self._prune()
try:
restricted_loads(pickle.dumps(value))
except:
return False
item = (expires, pickle.dumps(value,pickle.HIGHEST_PROTOCOL))
if key in self._cache:
return False
self._cache.setdefault(key, item)
self.set_session_by_file(key,item[1],expires)
return True
def delete(self, key):
result = self._cache.pop(key, None) is not None
self.del_session_by_file(key)
return result
def has(self, key):
try:
expires, value = self._cache[key]
return expires == 0 or expires > time()
except KeyError:
if self.get_session_by_file(key): return True
return False
def get_expire_time(self, key):
try:
expires, value = self._cache[key]
return expires
except KeyError:
return 0
def md5(self,strings):
"""
生成MD5
@strings 要被处理的字符串
return string(32)
"""
import hashlib
m = hashlib.md5()
m.update(strings.encode('utf-8'))
return m.hexdigest()
+36 -2
View File
@@ -7,6 +7,28 @@ except ImportError: # pragma: no cover
import pickle
from cachelib.base import BaseCache
import io
import builtins
safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins" and name in safe_builtins:
# print(name)
return getattr(builtins, name)
return None
def restricted_loads(s):
# return RestrictedUnpickler(io.BytesIO(s)).load()
return True
class SimpleCache(BaseCache):
@@ -99,11 +121,17 @@ class SimpleCache(BaseCache):
# 类型判断
if not isinstance(key,str): return False
type_list=(int,float,bool,str,list,dict,tuple,set,bytes)
if not isinstance(value,type_list): return False
value_type=type(value)
if value_type not in type_list:
return False
# 过期清理
expires = self._normalize_timeout(timeout)
self._prune()
try:
restricted_loads(pickle.dumps(value))
except:
return False
# 转换
_val = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
@@ -116,10 +144,16 @@ class SimpleCache(BaseCache):
# 类型判断
if not isinstance(key,str): return False
type_list=(int,float,bool,str,list,dict,tuple,set,bytes)
if not isinstance(value,type_list): return False
value_type=type(value)
if value_type not in type_list:
return False
expires = self._normalize_timeout(timeout)
self._prune()
try:
restricted_loads(pickle.dumps(value))
except:
return False
item = (expires, pickle.dumps(value,pickle.HIGHEST_PROTOCOL))
if key in self._cache:
return False
Regular → Executable
+39 -27
View File
@@ -6,7 +6,7 @@
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------
from BTPanel import session, cache , request, redirect, g
from BTPanel import session, cache , request, redirect, g,abort
from datetime import datetime
from public import dict_obj
import os
@@ -25,14 +25,16 @@ class panelSetup:
if g.ua:
ua = g.ua.lower()
if ua.find('spider') != -1 or g.ua.find('bot') != -1:
return redirect('https://www.google.com')
g.version = '6.8.27'
return abort(403)
g.version = '6.8.37'
g.title = public.GetConfigValue('title')
g.uri = request.path
g.debug = os.path.exists('data/debug.pl')
g.pyversion = sys.version_info[0]
session['version'] = g.version
if not public.get_improvement(): session['is_flush_soft_list'] = 1
if request.method == 'GET':
if not g.debug:
g.cdn_url = public.get_cdn_url()
@@ -150,83 +152,94 @@ class panelAdmin(panelSetup):
if not 'login' in session:
api_check = self.get_sk()
if api_check:
session.clear()
if not isinstance(api_check,dict):
if public.get_admin_path() == '/login':
return redirect('/login?err=1')
return api_check
g.api_request = True
else:
if session['login'] == False:
session.clear()
return redirect('/login')
return redirect(public.get_admin_path())
if 'tmp_login_expire' in session:
s_file = 'data/session/{}'.format(session['tmp_login_id'])
if session['tmp_login_expire'] < time.time():
session.clear()
if os.path.exists(s_file): os.remove(s_file)
return redirect('/login')
return redirect(public.get_admin_path())
if not os.path.exists(s_file):
session.clear()
return redirect('/login')
ua_md5 = public.md5(g.ua)
if ua_md5 != session.get('login_user_agent',ua_md5):
return redirect(public.get_admin_path())
if not public.check_client_hash():
session.clear()
return redirect('/login')
return redirect(public.get_admin_path())
if api_check:
now_time = time.time()
session_timeout = session.get('session_timeout',0)
if session_timeout < now_time and session_timeout != 0:
session.clear()
return redirect('/login?dologin=True&go=0')
return redirect(public.get_admin_path())
login_token = session.get('login_token','')
if login_token:
if login_token != public.get_login_token_auth():
session.clear()
return redirect('/login?dologin=True&go=1')
return redirect(public.get_admin_path())
# if api_check:
# filename = 'data/sess_files/' + public.get_sess_key()
# if not os.path.exists(filename):
# session.clear()
# return redirect('/login?dologin=True&go=2')
# return redirect(public.get_admin_path())
# 标记新的会话过期时间
session['session_timeout'] = time.time() + public.get_session_timeout()
# session['session_timeout'] = time.time() + public.get_session_timeout()
# 标记新的会话过期时间
self.check_session()
except:
public.WriteLog('Login auth',public.get_error_info())
# public.print_log(public.get_error_info())
session.clear()
return redirect('/login')
public.print_error()
return redirect('/login?id=2')
def check_session(self):
white_list = ['/favicon.ico', '/system?action=GetNetWork']
if g.uri in white_list:
return
session['session_timeout'] = time.time() + public.get_session_timeout()
# 获取sk
def get_sk(self):
save_path = '/www/server/panel/config/api.json'
if not os.path.exists(save_path):
return public.error_not_login('/login')
return public.redirect_to_login()
try:
api_config = json.loads(public.ReadFile(save_path))
except:
os.remove(save_path)
return public.error_not_login('/login')
return public.redirect_to_login()
if not api_config['open']:
return public.error_not_login('/login')
return public.redirect_to_login()
from BTPanel import get_input
get = get_input()
client_ip = public.GetClientIp()
if not 'client_bind_token' in get:
if not 'request_token' in get or not 'request_time' in get:
return public.error_not_login('/login')
return public.redirect_to_login()
num_key = client_ip + '_api'
if not public.get_error_num(num_key,20):
if not public.get_error_num(num_key, 20):
return public.returnJson(False,'20 consecutive verification failures, prohibited for 1 hour')
if not public.is_api_limit_ip(api_config['limit_addr'],client_ip): #client_ip in api_config['limit_addr']:
if not public.is_api_limit_ip(api_config['limit_addr'], client_ip): # client_ip in api_config['limit_addr']:
public.set_error_num(num_key)
return public.returnJson(False,'%s[' % public.get_msg_gettext("20 consecutive verification failures, prohibited for 1 hour")+client_ip+']')
else:
@@ -257,7 +270,7 @@ class panelAdmin(panelSetup):
get = get_input()
if not 'request_token' in get or not 'request_time' in get:
return public.error_not_login('/login')
return public.error_not_login('/login')
g.is_aes = True
g.aes_key = api_config['key']
request_token = public.md5(get.request_time + api_config['token'])
@@ -268,7 +281,6 @@ class panelAdmin(panelSetup):
return public.returnJson(False,'Secret key verification failed')
# 检查系统配置
def checkConfig(self):
if not 'config' in session:
session['config'] = public.M('config').where("id=?", ('1',)).field(
Regular → Executable
+1179 -158
View File
File diff suppressed because it is too large Load Diff
+90 -11
View File
@@ -6,7 +6,7 @@
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------
import public,db,os,time,re
import public,db,os,time,re, json
from BTPanel import session,cache
class crontab:
field = 'id,name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sName,sBody,sType,urladdress'
@@ -57,10 +57,63 @@ class crontab:
log_file = '/www/server/cron/{}.log'.format(tmp['echo'])
if os.path.exists(log_file):
tmp['addtime'] = public.format_date(times=int(os.path.getmtime(log_file)))
tmp['addtime'] = self.get_last_exec_time(log_file)
data.append(tmp)
return data
def get_backup_list(self, args):
'''
@name 获取指定备份任务的备份文件列表
@author hwliang
@param args<dict> 参数{
cron_id<int> 任务ID 必填
p<int> 页码 默认1
rows<int> 每页显示条数 默认10
callback<string> jsonp回调函数 默认为空
}
@return <dict>{
page<str> 分页HTML
data<list> 数据列表
}
'''
p = args.get('p/d', 1)
rows = args.get('rows/d', 10)
tojs = args.get('tojs/s', '')
callback = args.get('callback/s', '') if tojs else tojs
cron_id = args.get('cron_id/d')
count = public.M('backup').where('cron_id=?', (cron_id,)).count()
data = public.get_page(count, p, rows, callback)
data['data'] = public.M('backup').where('cron_id=?', (cron_id,)).limit(data['row'], data['shift']).select()
return data
def get_last_exec_time(self,log_file):
'''
@name 获取上次执行时间
@author hwliang
@param log_file<string> 日志文件路径
@return format_date
'''
exec_date = ''
try:
log_body = public.GetNumLines(log_file,20)
if log_body:
log_arr = log_body.split('\n')
date_list = []
for i in log_arr:
if i.find('') != -1 and i.find('[') != -1 and i.find(']') != -1:
date_list.append(i)
if date_list:
exec_date = date_list[-1].split(']')[0].split('[')[1]
except:
pass
finally:
if not exec_date:
exec_date = public.format_date(times=int(os.path.getmtime(log_file)))
return exec_date
#清理日志
def __clean_log(self):
@@ -96,6 +149,11 @@ class crontab:
#检查环境
def checkBackup(self):
if cache.get('check_backup'): return None
# 检查备份表是否正确
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'backup','%cron_id%')).count():
public.M('backup').execute("ALTER TABLE 'backup' ADD 'cron_id' INTEGER DEFAULT 0",())
#检查备份脚本是否存在
filePath=public.GetConfigValue('setup_path')+'/panel/script/backup'
if not os.path.exists(filePath):
@@ -105,7 +163,6 @@ class crontab:
if not os.path.exists(filePath):
public.downloadFile(public.GetConfigValue('home') + '/linux/logsBackup.py',filePath)
#检查计划任务服务状态
import system
sm = system.system()
if os.path.exists('/etc/init.d/crond'):
@@ -128,7 +185,8 @@ class crontab:
self.remove_for_crond(cronInfo['echo'])
else:
cronInfo['status'] = 1
self.sync_to_crond(cronInfo)
if not self.sync_to_crond(cronInfo):
return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!')
public.M('crontab').where('id=?',(id,)).setField('status',status)
public.WriteLog('TYPE_CRON',"MODIFY_CRON_STATUS",(cronInfo['name'],str(status_msg[status])))
@@ -163,10 +221,13 @@ class crontab:
get['minute'],get['save'],get['backupTo'],get['sBody'],
get['urladdress'],get['save_local'],get["notice"],
get["notice_channel"])
public.M('crontab').where('id=?',(id,)).save(columns,values)
self.remove_for_crond(cronInfo['echo'])
self.sync_to_crond(cronInfo)
public.WriteLog('TYPE_CRON',"MODIFY_CRON",(cronInfo['name']))
if cronInfo['status'] == 0: return public.return_msg_gettext(False, 'The current task is Disable status, please open the task before modifying!')
if not self.sync_to_crond(cronInfo):
return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!')
public.M('crontab').where('id=?',(id,)).save(columns,values)
public.WriteLog('TYPE_CRON',"MODIFY_CRON",(cronInfo['name'],))
return public.return_msg_gettext(True,'Setup successfully!')
@@ -178,8 +239,7 @@ class crontab:
#同步到crond
def sync_to_crond(self,cronInfo):
if 'status' in cronInfo:
if cronInfo['status'] == 0: return False
if not 'status' in cronInfo: return False
if 'where_hour' in cronInfo:
cronInfo['hour'] = cronInfo['where_hour']
cronInfo['minute'] = cronInfo['where_minute']
@@ -188,11 +248,13 @@ class crontab:
cronPath=public.GetConfigValue('setup_path')+'/cron'
cronName=self.GetShell(cronInfo)
if type(cronName) == dict: return cronName
#if cronInfo['status'] == 0: return False
cuonConfig += ' ' + cronPath+'/'+cronName+' >> '+ cronPath+'/'+cronName+'.log 2>&1'
wRes = self.WriteShell(cuonConfig)
if type(wRes) != bool: return False
self.CrondReload()
return True
#添加计划任务
def AddCrontab(self,get):
if len(get['name'])<1:
@@ -219,6 +281,10 @@ class crontab:
1,get['save'],get['backupTo'],get['sType'],get['sName'],get['sBody'],
get['urladdress'], get["save_local"], get['notice'], get['notice_channel'])
addData=public.M('crontab').add(columns,values)
public.add_security_logs('TYPE_CRON','Add Cron tasks ['+get['name']+'] success'+str(values))
if type(addData) == str:
return public.return_msg_gettext(False, addData)
public.WriteLog('TYPE_CRON', 'Add Cron tasks [' + get['name'] + '] success')
if addData>0:
result = public.return_msg_gettext(True,'Setup successfully!')
result['id'] = addData
@@ -287,7 +353,13 @@ class crontab:
#取数据列表
def GetDataList(self,get):
data = {}
data['data'] = public.M(get['type']).field('name,ps').select()
if get['type'] == 'databases':
data['data'] = public.M(get['type']).where("type=?","MySQL").field('name,ps').select()
else:
data['data'] = public.M(get['type']).field('name,ps').select()
for i in data['data']:
if 'ps' in i:
i['ps'] = public.xsssec(i['ps'])
data['orderOpt'] = []
import json
tmp = public.readFile('data/libList.conf')
@@ -328,6 +400,7 @@ class crontab:
try:
id = get['id']
find = public.M('crontab').where("id=?",(id,)).field('name,echo').find()
if not find: return public.return_msg_gettext(False, 'The specified task does not exist!')
if not self.remove_for_crond(find['echo']): return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!')
cronPath = public.GetConfigValue('setup_path') + '/cron'
sfile = cronPath + '/' + find['echo']
@@ -336,6 +409,7 @@ class crontab:
if os.path.exists(sfile): os.remove(sfile)
public.M('crontab').where("id=?",(id,)).delete()
public.add_security_logs("Delete cron", "Delete cron:" + find['name'])
public.WriteLog('TYPE_CRON', 'CRONTAB_DEL',(find['name'],))
return public.return_msg_gettext(True, 'Successfully deleted')
except:
@@ -344,7 +418,10 @@ class crontab:
#从crond删除
def remove_for_crond(self,echo):
file = self.get_cron_file()
if not os.path.exists(file):
return False
conf=public.readFile(file)
if not conf: return False
if conf.find(str(echo)) == -1: return True
rep = ".+" + str(echo) + ".+\n"
conf = re.sub(rep, "", conf)
@@ -479,6 +556,8 @@ echo "--------------------------------------------------------------------------
if not os.path.exists(u_path):
os.makedirs(u_path,472)
public.ExecShell("chown root:crontab {}".format(u_path))
if not os.path.exists(cron_path):
public.writeFile(cron_path,"")
return cron_path
+317 -23
View File
@@ -11,17 +11,28 @@ if not 'class/' in sys.path:
sys.path.insert(0,'class/')
import db,public,panelMysql
import json
import public
class data:
__ERROR_COUNT = 0
#自定义排序字段
__SORT_DATA = ['site_ssl','php_version','backup_count']
DB_MySQL = None
web_server = None
setupPath = '/www/server'
siteorder_path = '/www/server/panel/data/siteorder.pl'
limit_path = '/www/server/panel/data/limit.pl'
# 删除排序记录
def del_sorted(self, get):
public.ExecShell("rm -rf {}".format(self.siteorder_path))
return public.returnMsg(True, '清除排序成功!')
'''
* 设置备注信息
* @param String _GET['tab'] 数据库表名
* @param String _GET['id'] 条件ID
* @return Bool
* @return Bool
'''
def setPs(self,get):
id = get.id
@@ -29,7 +40,7 @@ class data:
if public.M(get.table).where("id=?",(id,)).setField('ps',get.ps):
return public.return_msg_gettext(True,'Setup successfully!')
return public.return_msg_gettext(False,'Failed to modify')
#端口扫描
def CheckPort(self,port):
import socket
@@ -44,7 +55,7 @@ class data:
s.close()
except:
temp['local'] = False
result = 0
if temp['local']: result +=2
return result
@@ -213,11 +224,63 @@ class data:
'''
def getData(self,get):
import one_key_wp
# # net_flow_type = {
# # "total_flow": "总流量",
# # "7_day_total_flow": "近7天流量",
# # "one_day_total_flow": "近1天流量",
# # "one_hour_total_flow": "近1小时流量"
# # }
# # net_flow_json_file = "/www/server/panel/plugin/total/panel_net_flow.json"
#
# if get.table == 'sites':
# if not hasattr(get, 'order'):
# if os.path.exists(self.siteorder_path):
# order = public.readFile(self.siteorder_path)
# if order.split(' ')[0] in self.__SORT_DATA:
# get.order = order
#
# if not hasattr(get, 'limit') or get.limit == '' or int(get.limit) == 0:
# try:
# if os.path.exists(self.limit_path):
# get.limit = int(public.readFile(self.limit_path))
# else:
# get.limit = 20
# except:
# get.limit = 20
# if "order" in get:
# order = get.order
# if get.table == 'sites':
# public.writeFile(self.siteorder_path, order)
# # o_list = order.split(' ')
# # net_flow_dict = {}
# # order_type = None
# # if o_list[0].strip() in net_flow_type.keys():
# # # net_flow_dict["flow_type"] = o_list[0].strip()
# # if len(o_list) > 1:
# # order_type = o_list[1].strip()
# # else:
# # get.order = 'id desc'
# # # net_flow_dict["order_type"] = order_type
# # public.writeFile(net_flow_json_file, json.dumps(net_flow_dict))
# 如果网站列表包含 rname 字段排序 先检查表内是否有 rname字段
if hasattr(get, "order") and get.table == 'sites':
if get.order.startswith('rname'):
data = public.M('sites').find()
if 'rname' not in data.keys():
public.M('sites').execute("ALTER TABLE 'sites' ADD 'rname' text DEFAULT ''", ())
table = get.table
data = self.GetSql(get)
SQL = public.M(table)
user_Data = self.get_user_power()
if user_Data != 'all' and table in ['sites', 'databases', 'ftps']:
data['data'] = [i for i in data['data'] if str(i['id']) in user_Data.get(table, [])]
try:
table = get.table
data = self.GetSql(get)
SQL = public.M(table)
# table = get.table
# data = self.GetSql(get)
# SQL = public.M(table)
if table == 'backup':
import os
backup_path = public.M('config').where('id=?',(1,)).getField('backup_path')
@@ -229,19 +292,41 @@ class data:
if not os.path.exists(data['data'][i]['filename']):
if (data['data'][i]['filename'].find('/www/') != -1 or data['data'][i]['filename'].find(backup_path) != -1) and data['data'][i]['filename'][0] == '/' and data['data'][i]['filename'].find('|') == -1:
data['data'][i]['size'] = 0
data['data'][i]['ps'] = public.get_msg_gettext("File does not exist!")
data['data'][i]['ps'] = '文件不存在'
if data['data'][i]['ps'] in ['','']:
if data['data'][i]['name'][:3] == 'db_' or (data['data'][i]['name'][:4] == 'web_' and data['data'][i]['name'][-7:] == '.tar.gz'):
data['data'][i]['ps'] = '自动备份'
else:
data['data'][i]['ps'] = '手动备份'
#判断本地文件是否存在,以确定能否下载
data['data'][i]['local']=data['data'][i]['filename'].split('|')[0]
data['data'][i]['localexist']=0 if os.path.isfile(data['data'][i]['local']) else 1
elif table == 'sites' or table == 'databases':
type = '0'
if table == 'databases': type = '1'
if table == 'databases':
type = '1'
for i in range(len(data['data'])):
data['data'][i]['backup_count'] = SQL.table('backup').where("pid=? AND type=?",(data['data'][i]['id'],type)).count()
backup_count = 0
try:
backup_count = SQL.table('backup').where("pid=? AND type=?",(data['data'][i]['id'],type)).count()
except:pass
data['data'][i]['backup_count'] = backup_count
if table == 'databases': data['data'][i]['conn_config'] = json.loads(data['data'][i]['conn_config'])
data['data'][i]['quota'] = self.get_database_quota(data['data'][i]['name'])
if table == 'sites':
for i in range(len(data['data'])):
data['data'][i]['domain'] = SQL.table('domain').where("pid=?",(data['data'][i]['id'],)).count()
data['data'][i]['ssl'] = self.get_site_ssl_info(data['data'][i]['name'])
# data['data'][i]['ssl'] = self.get_site_ssl_info(data['data'][i]['name'])
ssl_info = self.get_site_ssl_info(data['data'][i]['name'])
data['data'][i]['ssl'] = ssl_info
data['data'][i]['site_ssl'] = ssl_info['endtime'] if ssl_info != -1 else -1
data['data'][i]['php_version'] = self.get_php_version(data['data'][i]['name'])
data['data'][i]['attack'] = self.get_analysis(get,data['data'][i])
data['data'][i]['project_type'] = SQL.table('sites').where('id=?',(data['data'][i]['id'])).field('project_type').find()['project_type']
@@ -250,6 +335,20 @@ class data:
if not data['data'][i]['status'] in ['0','1',0,1]:
data['data'][i]['status'] = '1'
data['data'][i]['quota'] = self.get_site_quota(data['data'][i]['path'])
site1 = SQL.table('sites').where('id=?', (data['data'][i]['id'])).find()
if hasattr(site1, 'rname'):
data['data'][i]['rname'] = \
SQL.table('sites').where('id=?', (data['data'][i]['id'])).field('rname').find()['rname']
if not data['data'][i].get('rname', ''):
data['data'][i]['rname'] = data['data'][i]['name']
data["net_flow_info"] = {}
# try:
# net_flow_json_info = json.loads(public.readFile(net_flow_json_file))
# data["net_flow_info"] = net_flow_json_info
# except Exception:
# data["net_flow_info"] = {}
elif table == 'firewall':
for i in range(len(data['data'])):
if data['data'][i]['port'].find(':') != -1 or data['data'][i]['port'].find('.') != -1 or data['data'][i]['port'].find('-') != -1:
@@ -270,10 +369,149 @@ class data:
pass
#返回
return data
return self.get_sort_data(data)
except:
return public.get_error_info()
def get_data_list(self, get):
try:
self.check_and_add_stop_column()
if get.table == 'sites':
if not hasattr(get, 'order'):
if os.path.exists(self.siteorder_path):
order = public.readFile(self.siteorder_path)
if order.split(' ')[0] in self.__SORT_DATA:
get.order = order
else:
public.writeFile(self.siteorder_path, get.order)
if not hasattr(get, 'limit') or get.limit == '' or int(get.limit) == 0:
try:
if os.path.exists(self.limit_path):
get.limit = int(public.readFile(self.limit_path))
else:
get.limit = 20
except:
get.limit = 20
else:
public.writeFile(self.limit_path, get.limit)
if not hasattr(get, 'order'):
get.order = 'addtime desc'
get = self._get_args(get)
try:
s_list = self.func_models(get, 'get_data_where')
except:
s_list = []
where_sql, params = self.get_where(get, s_list)
data = self.get_page_data(get, where_sql, params)
get.data_list = data['data']
try:
data['data'] = self.func_models(get, 'get_data_list')
except :
print(traceback.format_exc())
if get.table == 'sites':
if isinstance(data, dict):
file_path = os.path.join(public.get_panel_path(), "data/sort_list.json")
if os.path.exists(file_path):
sort_list_raw = public.readFile(file_path)
sort_list = json.loads(sort_list_raw)
sort_list_int = [int(item) for item in sort_list["list"]]
for i in range(len(data['data'])):
if int(data['data'][i]['id']) in sort_list_int:
data['data'][i]['sort'] = 1
else:
data['data'][i]['sort'] = 0
top_list = sort_list["list"]
if top_list:
top_list = top_list[::-1]
top_data = [item for item in data["data"] if str(item['id']) in top_list]
data1 = [item for item in data["data"] if str(item['id']) not in top_list]
top_data.sort(key=lambda x: top_list.index(str(x['id'])))
data['data'] = top_data + data1
public.set_search_history(get.table, get.search_key, get.search) # 记录搜索历史
# 字段排序
data = self.get_sort_data(data)
if 'type_id' in get:
type_id=int(get['type_id'])
if type_id:
filtered_data = []
target_type_id = type_id
# print(data['data'])
for item in data['data']:
if item.get('type_id') == target_type_id:
filtered_data.append(item)
data['data'] = filtered_data
if get.get("db_type",""):
if type_id < 0:
filtered_data = []
target_type_id = type_id
for item in data['data']:
if item.get('type_id') == target_type_id:
filtered_data.append(item)
data['data'] = filtered_data
return data
except:
return traceback.format_exc()
# 获取用户权限列表
def get_user_power(self, get=None):
user_Data = 'all'
try:
uid = session.get('uid')
if uid != 1 and uid:
plugin_path = '/www/server/panel/plugin/users'
if os.path.exists(plugin_path):
user_authority = os.path.join(plugin_path, 'authority')
if os.path.exists(user_authority):
if os.path.exists(os.path.join(user_authority, str(uid))):
try:
data = json.loads(self._decrypt(public.ReadFile(os.path.join(user_authority, str(uid)))))
if data['role'] == 'administrator':
user_Data = 'all'
else:
user_Data = json.loads(self._decrypt(public.ReadFile(os.path.join(user_authority, str(uid) + '.data'))))
except:
user_Data = {}
else:
user_Data = {}
except:
pass
return user_Data
def get_sort_data(self,data):
"""
@获取自定义排序数据
@param data: 数据
"""
if 'plist' in data:
plist = data['plist']
o_list = plist['order'].split(' ')
reverse = False
sort_key = o_list[0].strip()
if o_list[1].strip() == 'desc':
reverse = True
if sort_key in ['site_ssl']:
for info in data['data']:
if type(info['ssl']) == int:
info[sort_key] = info['ssl']
else:
try:
info[sort_key] = info['ssl']['endtime']
except :
info[sort_key] = ''
data['data'] = sorted(data['data'],key=lambda x:x[sort_key],reverse=reverse)
data['data'] = data['data'][plist['shift'] : plist['row'] ]
return data
'''
* 取数据库行
* @param String _GET['tab'] 数据库表名
@@ -322,12 +560,13 @@ class data:
'''
def GetSql(self,get,result = '1,2,3,4,5,8'):
#判断前端是否传入参数
order = "id desc"
order = 'id desc'
if hasattr(get,'order'):
# 验证参数格式
if re.match(r"^[\w\s\-\.]+$",get.order):
order = get.order
search_key = 'get_list'
limit = 20
if hasattr(get,'limit'):
limit = int(get.limit)
@@ -342,15 +581,27 @@ class data:
data = {}
#取查询条件
where = ''
search = ''
param = ()
if hasattr(get,'search'):
search = get.search
if sys.version_info[0] == 2: get.search = get.search.encode('utf-8')
where,param = self.GetWhere(get.table,get.search)
if get.table == 'backup':
where += " and type='{}'".format(int(get.type))
if get.table == 'sites' and get.search:
pid = SQL.table('domain').where("name LIKE ?",("%{}%".format(get.search),)).getField('pid')
conditions = ''
if '_' in get.search:
cs = ''
for i in get.search:
if i == '_':
cs += '/_'
else:
cs += i
get.search = cs
conditions = " escape '/'"
pid = SQL.table('domain').where("name LIKE ?{}".format(conditions),("%{}%".format(get.search),)).getField('pid')
if pid:
if where:
where += " or id=" + str(pid)
@@ -358,8 +609,9 @@ class data:
where += "id=" + str(pid)
if get.table == 'sites':
search_key = 'php'
if where:
where = "({}) AND project_type='PHP'".format(where)
where = "({}) AND (project_type='PHP' OR project_type='WP')".format(where)
else:
where = "(project_type='PHP' OR project_type='WP')"
@@ -379,9 +631,15 @@ class data:
else:
where = "sid='{}'".format(int(get.sid))
if where:
where += " and type='MySQL'"
else:
where = 'type = "MySQL"'
field = self.GetField(get.table)
#实例化数据库对象
public.set_search_history(get.table,search_key,search) #记录搜索历史
#是否直接返回所有列表
if hasattr(get,'list'):
@@ -420,7 +678,17 @@ class data:
#获取分页数据
data['page'] = page.GetPage(info,result)
#取出数据
data['data'] = SQL.table(get.table).where(where,param).order(order).field(field).limit(str(page.SHIFT)+','+str(page.ROW)).select()
#data['data'] = SQL.table(get.table).where(where,param).order(order).field(field).limit(str(page.SHIFT)+','+str(page.ROW)).select()
o_list = order.split(' ')
if o_list[0] in self.__SORT_DATA:
data['data'] = SQL.table(get.table).where(where,param).field(field).select()
data['plist'] = {'shift':page.SHIFT,'row':page.ROW,'order':order}
else:
data['data'] = SQL.table(get.table).where(where,param).order(order).field(field).limit(str(page.SHIFT)+','+str(page.ROW)).select() #取出数据
data['search_history'] = public.get_search_history(get.table,search_key)
return data
#获取条件
@@ -432,16 +700,42 @@ class data:
search = re.search(r"[\w\x80-\xff\.\_\-]+",search).group()
except:
return '',()
conditions = ''
if '_' in search:
cs = ''
for i in search:
if i == '_':
cs += '/_'
else:
cs += i
search = cs
conditions = " escape '/'"
wheres = {
'sites' : ("name LIKE ? OR ps LIKE ?",('%'+search+'%','%'+search+'%')),
'ftps' : ("name LIKE ? OR ps LIKE ?",('%'+search+'%','%'+search+'%')),
'databases' : ("(name LIKE ? OR ps LIKE ?)",("%"+search+"%","%"+search+"%")),
'logs' : ("username=? OR type LIKE ? OR log LIKE ?",(search,'%'+search+'%','%'+search+'%')),
'sites': ("name LIKE ? OR ps LIKE ?{}".format(conditions), ('%' + search + '%', '%' + search + '%')),
'ftps': ("name LIKE ? OR ps LIKE ?{}".format(conditions), ('%' + search + '%', '%' + search + '%')),
'databases': (
"(name LIKE ? {} OR ps LIKE ?{})".format(conditions, conditions),
("%" + search + "%", "%" + search + "%")),
'crontab': ("name LIKE ?{}".format(conditions), ('%' + (search) + '%')),
'logs': ("username=? OR type LIKE ?{} OR log LIKE ?{}".format(conditions, conditions),
(search, '%' + search + '%', '%' + search + '%')),
'backup' : ("pid=?",(search,)),
'users' : ("id='?' OR username=?",(search,search)),
'domain' : ("pid=? OR name=?",(search,search)),
'tasks' : ("status=? OR type=?",(search,search)),
}
# wheres = {
# 'sites' : ("name LIKE ? OR ps LIKE ?",('%'+search+'%','%'+search+'%')),
# 'ftps' : ("name LIKE ? OR ps LIKE ?",('%'+search+'%','%'+search+'%')),
# 'databases' : ("(name LIKE ? OR ps LIKE ?)",("%"+search+"%","%"+search+"%")),
# 'logs' : ("username=? OR type LIKE ? OR log LIKE ?",(search,'%'+search+'%','%'+search+'%')),
# 'backup' : ("pid=?",(search,)),
# 'users' : ("id='?' OR username=?",(search,search)),
# 'domain' : ("pid=? OR name=?",(search,search)),
# 'tasks' : ("status=? OR type=?",(search,search)),
# }
try:
return wheres[tableName]
except:
+753 -496
View File
File diff suppressed because it is too large Load Diff
View File
+461
View File
@@ -0,0 +1,461 @@
#coding: utf-8
import os,sys,time,json
panelPath = '/www/server/panel'
os.chdir(panelPath)
if not panelPath + "/class/" in sys.path:
sys.path.insert(0, panelPath + "/class/")
import public,re
class databaseBase:
def get_base_list(self,args,sql_type = 'mysql'):
"""
@获取数据库列表
@type:数据库类型MySQL,SQLServer
"""
search = ''
if 'search' in args: search = args['search']
SQL = public.M('databases');
where = "lower(type) = lower('{}')".format(sql_type)
if search:
where += "AND (name like '%{search}%' or ps like '%{search}%')".format(search = search)
if 'db_type' in args:
where += " AND db_type='{}'".format(args['db_type'])
if 'sid' in args:
where += " AND sid='{}'".format(args['sid'])
order = "id desc"
if hasattr(args,'order'): order = args.order
info = {}
rdata = {}
info['p'] = 1
info['row'] = 20
result = '1,2,3,4,5,8'
info['count'] = SQL.where(where,()).count();
if hasattr(args,'limit'): info['row'] = int(args.limit)
if hasattr(args,'result'): result = args.result;
if hasattr(args,'p'): info['p'] = int(args['p'])
import page
#实例化分页类
page = page.Page();
info['uri'] = args
info['return_js'] = ''
if hasattr(args,'tojs'): info['return_js'] = args.tojs
rdata['where'] = where;
#获取分页数据
rdata['page'] = page.GetPage(info,result)
#取出数据
rdata['data'] = SQL.where(where,()).order(order).field('id,sid,pid,name,username,password,accept,ps,addtime,type,db_type,conn_config').limit(str(page.SHIFT)+','+str(page.ROW)).select()
for sdata in rdata['data']:
sdata['backup_count'] = public.M('backup').where("pid=? AND type=1",(sdata['id'])).count()
sdata['conn_config'] = json.loads(sdata['conn_config'])
return rdata;
def get_databaseModel(self):
'''
获取数据库模型对象
@db_type 数据库类型
'''
from panelDatabaseController import DatabaseController
project_obj = DatabaseController()
return project_obj
def get_average_num(self,slist):
"""
@批量删除获取平均值
"""
count = len(slist)
limit_size = 1 * 1024 * 1024
if count <= 0: return limit_size
if len(slist) > 1:
slist = sorted(slist)
limit_size =int((slist[0] + slist[-1])/2 * 0.85)
return limit_size
def get_database_size(self,ids,is_pid = False):
"""
获取数据库大小
"""
result = {}
p = self.get_databaseModel()
for id in ids:
if not is_pid:
x = public.M('databases').where('id=?',id).field('id,sid,pid,name,type,ps,addtime').find()
else:
x = public.M('databases').where('pid=?',id).field('id,sid,pid,name,type,ps,addtime').find()
if not x: continue
x['backup_count'] = public.M('backup').where("pid=? AND type=?",(x['id'],'1')).count()
if x['type'] == 'MySQL':
x['total'] = int(public.get_database_size_by_id(id))
else:
try:
get = public.dict_obj()
get['data'] = {'db_id': x['id'] }
get['mod_name'] = x['type'].lower()
get['def_name'] = 'get_database_size_by_id'
x['total'] = p.model(get)
except :
x['total'] = 0
result[x['name']] = x
return result
def check_base_del_data(self,get):
"""
@删除数据库前置检测
"""
ids = json.loads(get.ids)
slist = {};result = [];db_list_size = []
db_data = self.get_database_size(ids)
for key in db_data:
data = db_data[key]
if not data['id'] in ids: continue
db_addtime = public.to_date(times = data['addtime'])
data['score'] = int(time.time() - db_addtime) + data['total']
data['st_time'] = db_addtime
if data['total'] > 0 : db_list_size.append(data['total'])
result.append(data)
slist['data'] = sorted(result,key= lambda x:x['score'],reverse=True)
slist['db_size'] = self.get_average_num(db_list_size)
return slist
def get_test(self,args):
p = self.get_databaseModel()
get = public.dict_obj()
get['data'] = {'db_id': 18 }
get['mod_name'] = args['type'].lower()
get['def_name'] = 'get_database_size_by_id'
return p.model(get)
def add_base_database(self,get):
"""
@添加数据库前置检测
@return username 用户名
data_name 数据库名
data_pwd:数据库密码
"""
data_name = get['name'].strip().lower()
if self.check_recyclebin(data_name):
return public.returnMsg(False,'Database ['+data_name+'] is already in recycle bin, please restore from recycle bin!');
if len(data_name) > 16:
return public.returnMsg(False, 'DATABASE_NAME_LEN')
if not hasattr(get,'db_user'): get.db_user = data_name;
username = get.db_user.strip();
checks = ['root','mysql','test','sys','panel_logs']
if username in checks or len(username) < 1:
return public.returnMsg(False,'Database username is invalid!');
if data_name in checks or len(data_name) < 1:
return public.returnMsg(False,'Database name is invalid!');
reg = "^\w+$"
if not re.match(reg, data_name):
return public.returnMsg(False,'DATABASE_NAME_ERR_T')
data_pwd = get['password']
if len(data_pwd) < 1:
data_pwd = public.md5(str(time.time()))[0:8]
if public.M('databases').where("name=? or username=?",(data_name,username)).count():
return public.returnMsg(False,'DATABASE_NAME_EXISTS')
res = {
'data_name':data_name,
'username':username,
'data_pwd':data_pwd,
'status':True
}
return res
def delete_base_backup(self,get):
"""
@删除备份文件
"""
name = ''
id = get.id
where = "id=?"
filename = public.M('backup').where(where,(id,)).getField('filename')
if os.path.exists(filename): os.remove(filename)
if filename == 'qiniu':
name = public.M('backup').where(where,(id,)).getField('name');
public.ExecShell(public.get_run_python("[PYTHON] "+public.GetConfigValue('setup_path') + '/panel/script/backup_qiniu.py delete_file ' + name))
public.M('backup').where(where,(id,)).delete()
public.WriteLog("TYPE_DATABASE", 'DATABASE_BACKUP_DEL_SUCCESS',(name,filename))
return public.returnMsg(True, 'DEL_SUCCESS');
#检查是否在回收站
def check_recyclebin(self,name):
try:
for n in os.listdir('{}/Recycle_bin'.format(public.get_soft_path())):
if n.find('BTDB_'+name+'_t_') != -1: return True;
return False;
except:
return False;
#map to list
def map_to_list(self,map_obj):
try:
if type(map_obj) != list and type(map_obj) != str: map_obj = list(map_obj)
return map_obj
except: return []
#******************************************** 远程数据库 ******************************************/
def check_cloud_args(self,get,nlist = []):
"""
验证参数是否合法
@get param
@args 参数列表
"""
for key in nlist:
if not key in get:
return public.returnMsg(False,'Parameter passing error, missing parameter {}!'.format(key))
return public.returnMsg(True,'Pass!')
def check_cloud_database(self,args):
'''
@检查远程数据库是否存在
@conn_config param
'''
p = self.get_databaseModel()
get = public.dict_obj()
get['data'] = args
get['mod_name'] = args['type']
get['def_name'] = 'check_cloud_database_status'
return p.model(get)
def AddBaseCloudServer(self,get):
"""
@name 添加远程服务器
@author hwliang<2021-01-10>
@param db_host<string> 服务器地址
@param db_port<port> 数据库端口
@param db_user<string> 用户名
@param db_password<string> 数据库密码
@param db_ps<string> 数据库备注
@param type<string> 数据库类型mysql/sqlserver/sqlite
@return dict
"""
arrs = ['db_host','db_port','db_user','db_password','db_ps','type']
if get.type == 'redis': arrs = ['db_host','db_port','db_password','db_ps','type']
cRet = self.check_cloud_args(get,arrs)
if not cRet['status']: return str(cRet)
get['db_name'] = None
res = self.check_cloud_database(get)
if isinstance(res,dict): return str(res)
if public.M('database_servers').where('db_host=? AND db_port=?',(get['db_host'],get['db_port'])).count():
return public.returnMsg(False,'The specified server already exists: [{}:{}]'.format(get['db_host'],get['db_port']))
get['db_port'] = int(get['db_port'])
pdata = {
'db_host':get['db_host'],
'db_port':int(get['db_port']),
'db_user':get['db_user'],
'db_password':get['db_password'],
'db_type':get['type'],
'ps': public.xssencode2(get['db_ps'].strip()),
'addtime': int(time.time())
}
result = public.M("database_servers").insert(pdata)
if isinstance(result,int):
public.WriteLog('Database manager','Add remote MySQL server[{}:{}]'.format(get['db_host'],get['db_port']))
return public.returnMsg(True,'Added successfully!')
return public.returnMsg(False,'Add failed {}'.format(result))
def GetBaseCloudServer(self,get):
'''
@name 获取远程服务器列表
@author hwliang<2021-01-10>
@return list
'''
where = '1=1'
if 'type' in get:where = "db_type = '{}'".format(get['type'])
data = public.M('database_servers').where(where,()).select()
if not isinstance(data,list): data = []
if get['type'] == 'mysql':
bt_mysql_bin = public.get_mysql_info()['path'] + '/bin/mysql.exe'
if os.path.exists(bt_mysql_bin):
data.insert(0,{'id':0,'db_host':'127.0.0.1','db_port':3306,'db_user':'root','db_password':'','ps':'local server','addtime':0,'db_type':'mysql'})
elif get['type'] == 'sqlserver':
pass
elif get['type'] == 'mongodb':
if os.path.exists('/www/server/mongodb/bin'):
data.insert(0,{'id':0,'db_host':'127.0.0.1','db_port':27017,'db_user':'root','db_password':'','ps':'local server','addtime':0,'db_type':'mongodb'})
elif get['type'] == 'redis':
if os.path.exists('/www/server/redis'):
data.insert(0,{'id':0,'db_host':'127.0.0.1','db_port':6379,'db_user':'root','db_password':'','ps':'local server','addtime':0,'db_type':'redis'})
elif get['type'] == 'pgsql':
if os.path.exists('/www/server/pgsql'):
data.insert(0,{'id':0,'db_host':'127.0.0.1','db_port':5432,'db_user':'postgres','db_password':'','ps':'local server','addtime':0,'db_type':'pgsql'})
return data
def RemoveBaseCloudServer(self,get):
'''
@name 删除远程服务器
@author hwliang<2021-01-10>
@param id<int> 远程服务器ID
@return dict
'''
id = int(get.id)
if not id: return public.returnMsg(False,'Parameter passed error, please try again!')
db_find = public.M('database_servers').where('id=?',(id,)).find()
if not db_find: return public.returnMsg(False,'The specified remote server does not exist!')
public.M('databases').where('sid=?',id).delete()
result = public.M('database_servers').where('id=?',id).delete()
if isinstance(result,int):
public.WriteLog('Database manager','Delete remote MySQL server [{}:{}]'.format(db_find['db_host'],int(db_find['db_port'])))
return public.returnMsg(True,'Successfully deleted!')
return public.returnMsg(False,'Successfully deleted {}'.format(result))
def ModifyBaseCloudServer(self,get):
'''
@name 修改远程服务器
@author hwliang<2021-01-10>
@param id<int> 远程服务器ID
@param db_host<string> 服务器地址
@param db_port<port> 数据库端口
@param db_user<string> 用户名
@param db_password<string> 数据库密码
@param db_ps<string> 数据库备注
@return dict
'''
arrs = ['db_host','db_port','db_user','db_password','db_ps','type']
if get.type == 'redis': arrs = ['db_host','db_port','db_password','db_ps','type']
cRet = self.check_cloud_args(get,arrs)
if not cRet['status']: return cRet
id = int(get.id)
get['db_port'] = int(get['db_port'])
db_find = public.M('database_servers').where('id=?',(id,)).find()
if not db_find: return public.returnMsg(False,'The specified remote server does not exist!')
_modify = False
if db_find['db_host'] != get['db_host'] or db_find['db_port'] != get['db_port']:
_modify = True
if public.M('database_servers').where('db_host=? AND db_port=?',(get['db_host'],get['db_port'])).count():
return public.returnMsg(False,'The specified server already exists: [{}:{}]'.format(get['db_host'],get['db_port']))
if db_find['db_user'] != get['db_user'] or db_find['db_password'] != get['db_password']:
_modify = True
_modify = True
if _modify:
res = self.check_cloud_database(get)
if isinstance(res,dict): return res
pdata = {
'db_host':get['db_host'],
'db_port':int(get['db_port']),
'db_user':get['db_user'],
'db_password':get['db_password'],
'db_type':get['type'],
'ps': public.xssencode2(get['db_ps'].strip())
}
result = public.M("database_servers").where('id=?',(id,)).update(pdata)
if isinstance(result,int):
public.WriteLog('Database manager','Modify the remote MySQL server[{}:{}]'.format(get['db_host'],get['db_port']))
return public.returnMsg(True,'Successfully modified!')
return public.returnMsg(False,'Fail to edit {}'.format(result))
#检测数据库执行错误
def IsSqlError(self,mysqlMsg):
if mysqlMsg:
mysqlMsg = str(mysqlMsg)
if "MySQLdb" in mysqlMsg: return public.returnMsg(False,'DATABASE_ERR_MYSQLDB')
if "2002," in mysqlMsg: return public.returnMsg(False,'DATABASE_ERR_CONNECT')
if "2003," in mysqlMsg: return public.returnMsg(False,'Database connection timed out, please check if the configuration is correct.')
if "1045," in mysqlMsg: return public.returnMsg(False,'MySQL password error.')
if "1040," in mysqlMsg: return public.returnMsg(False,'Exceeded maximum number of connections, please try again later.')
if "1130," in mysqlMsg: return public.returnMsg(False,'Database connection failed, please check whether the root user is authorized to access 127.0.0.1.')
if "using password:" in mysqlMsg: return public.returnMsg(False,'DATABASE_ERR_PASS')
if "Connection refused" in mysqlMsg: return public.returnMsg(False,'DATABASE_ERR_CONNECT')
if "1133" in mysqlMsg: return public.returnMsg(False,'DATABASE_ERR_NOT_EXISTS')
if "2005_login_error" == mysqlMsg: return public.returnMsg(False,'The connection times out, please manually enable the TCP/IP function (Start Menu->SQL 2005->Configuration Tools->2005 Network Configuration->TCP/IP->Enable)')
if 'already exists' in mysqlMsg: return public.returnMsg(False,'The specified database already exists, please do not add it repeatedly.')
if 'Cannot open backup device' in mysqlMsg: return public.returnMsg(False,'The operation failed, the remote database does not support the operation.')
if '1142' in mysqlMsg: return public.returnMsg(False,'Insufficient permissions, please use root user.')
if "DB-Lib error message 20018" in mysqlMsg: return public.returnMsg(False,'Create failed, SQL Server requires GUI support')
return None
#******************************************** 数据库公用方法 ******************************************/
if __name__ == "__main__":
#get = {}
#get['db_host'] = '192.168.1.37'
#get['db_port'] = '3306'
#get['db_user'] = 'root'
#get['db_password'] = 'HLANEMJFRbPE7Ny2'
#get['db_ps'] = '2'
#get['type'] = 'mysql'
#bt = databaseBase()
#ret = bt.AddCloudServer(get)
#print(ret)
get = {}
get['db_host'] = '192.168.66.73'
get['db_port'] = '1433'
get['db_user'] = 'sa'
get['db_password'] = 'dPYi6Gt8GC7SL58C'
get['db_ps'] = '2'
get['type'] = 'sqlserver'
bt = databaseBase()
ret = bt.get_test(get)
print(ret)
+777
View File
@@ -0,0 +1,777 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#角色说明:
#read:允许用户读取指定数据库
#readWrite:允许用户读写指定数据库
#dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
#userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
#clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
#readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
#readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
#userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
#dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
#root:只在admin数据库中可用。超级账号,超级权限
# sqlite模型
#------------------------------
import os,re,json,time
from databaseModel.base import databaseBase
import public
try:
import pymongo
except:
public.ExecShell("btpip install pymongo")
import pymongo
try:
from BTPanel import session
except :pass
class panelMongoDB():
__DB_PASS = None
__DB_USER = None
__DB_PORT = 27017
__DB_HOST = '127.0.0.1'
__DB_CONN = None
__DB_ERR = None
__DB_CLOUD = None
def __init__(self):
self.__config = self.get_options(None)
def __Conn(self,auth):
if not self.__DB_CLOUD:
path = '{}/data/mongo.root'.format(public.get_panel_path())
if os.path.exists(path): self.__DB_PASS = public.readFile(path)
self.__DB_PORT = int(self.__config['port'])
try:
if not self.__DB_USER and auth:
self.__DB_USER = "root"
self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=self.__DB_PORT, username = self.__DB_USER, password=self.__DB_PASS)
self.__DB_CONN.admin.command({"listDatabases":1})
return True
except :
try:
self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=self.__DB_PORT, username = self.__DB_USER, password=self.__DB_PASS)
self.__DB_CONN.admin.authenticate('root', self.__DB_PASS)
return True
except :
self.__DB_ERR = public.get_error_info()
return False
def get_db_obj(self,db_name = 'admin',auth=0):
"""
@获取连接对象
"""
if not self.__Conn(auth): return self.__DB_ERR
return self.__DB_CONN[db_name]
def set_host(self,host,port,name,username,password,prefix = ''):
self.__DB_HOST = host
self.__DB_PORT = int(port)
self.__DB_NAME = name
if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME)
self.__DB_USER = str(username)
self._USER = str(username)
self.__DB_PASS = str(password)
self.__DB_PREFIX = prefix
self.__DB_CLOUD = 1
return self
#获取配置文件
def get_config(self,get):
filename = '{}/mongodb/config.conf'.format(public.get_setup_path())
if os.path.exists(filename):
return public.readFile(filename);
return ""
#获取配置项
def get_options(self,get):
options = ['port','bind_ip','logpath','dbpath','authorization']
data = {}
conf = self.get_config(None)
for opt in options:
tmp = re.findall(opt + ":\s+(.+)",conf)
if not tmp: continue;
data[opt] = tmp[0]
if not 'authorization' in data:data['authorization'] = "disabled"
# public.writeFile('/www/server/1.txt',json.dumps(data))
return data
class main(databaseBase):
__conf_path = '{}/mongodb/config.conf'.format(public.get_setup_path())
def __init__(self):
pass
def get_list(self,args):
"""
@获取数据库列表
@sql_type = sqlserver
"""
return self.get_base_list(args, sql_type = 'mongodb')
def GetCloudServer(self,args):
'''
@name 获取远程服务器列表
@author hwliang<2021-01-10>
@return list
'''
return self.GetBaseCloudServer(args)
def AddCloudServer(self,args):
'''
@添加远程数据库
'''
return self.AddBaseCloudServer(args)
def RemoveCloudServer(self,args):
'''
@删除远程数据库
'''
return self.RemoveBaseCloudServer(args)
def ModifyCloudServer(self,args):
'''
@修改远程数据库
'''
return self.ModifyBaseCloudServer(args)
#获取数据库列表
def exists_databases(self,get):
db_name = get
if type(get) != str:db_name = get['db_name']
auth_status = self.get_local_auth(get)
db_obj = self.get_obj_by_sid(self.sid).get_db_obj('admin',auth=auth_status)
data = db_obj.command({"listDatabases":1})
if 'databases' in data:
for x in data['databases']:
if x['name'] == db_name:
return True
return False
def __set_auth_open(self,status):
"""
@设置数据库密码访问开关
@状态 status:1 开启2关闭
"""
conf = public.readFile(self.__conf_path)
if status:
conf = re.sub('authorization\s*\:\s*disabled','authorization: enabled',conf)
else:
conf = re.sub('authorization\s*\:\s*enabled','authorization: disabled',conf)
public.writeFile(self.__conf_path,conf)
self.restart_services()
return True
def set_auth_status(self,get):
"""
@设置密码认证状态
@status int 0关闭1开启
"""
if not public.process_exists("mongod") :
return public.returnMsg(False,"Mongodb service has not been started yet!")
status = int(get.status)
path = '{}/data/mongo.root'.format(public.get_panel_path())
if status:
if hasattr(get,'password'):
password = get['password'].strip()
if not password or not re.search("^[\w@\.]+$", password):
return public.return_msg_gettext(False, 'Database password cannot be empty or have special characters!')
# if re.search('[\u4e00-\u9fa5]',password):
# return public.returnMsg(False,'Database password cannot be Chinese, please change the name!')
else:
password = public.GetRandomString(16)
self.__set_auth_open(0)
_client = panelMongoDB().get_db_obj('admin')
try:
_client.command("dropUser", "root")
except : pass
_client.command("createUser", "root", pwd=password, roles=[
{'role':'root','db':'admin'},
{'role':'clusterAdmin','db':'admin'},
{'role':'readAnyDatabase','db':'admin'},
{'role':'readWriteAnyDatabase','db':'admin'},
{'role':'userAdminAnyDatabase','db':'admin'},
{'role':'dbAdminAnyDatabase','db':'admin'},
{'role':'userAdmin','db':'admin'},
{'role':'dbAdmin','db':'admin'}
])
self.__set_auth_open(1)
public.writeFile(path,password)
else:
if os.path.exists(path): os.remove(path)
self.__set_auth_open(0)
return public.return_msg_gettext(True,'Setup successfully!')
def restart_services(self):
"""
@重启服务
"""
public.ExecShell('/etc/init.d/mongodb restart')
return True
def get_obj_by_sid(self,sid = 0,conn_config = None):
"""
@取mssql数据库对像 By sid
@sid 数据库分类0本地
"""
if type(sid) == str:
try:
sid = int(sid)
except :sid = 0
if sid:
if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find()
db_obj = panelMongoDB()
try:
db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password'])
except Exception as e:
raise public.PanelError(e)
else:
db_obj = panelMongoDB()
return db_obj
def get_local_auth(self,get):
"""
@验证本地数据库是否需要密码
"""
self.sid = get.get('sid/d',0)
if self.sid != 0: return True
conf = panelMongoDB().get_options(None)
if conf['authorization'] == 'enabled':
return True
return False
def AddDatabase(self,args):
"""
@添加数据库
"""
try:
int(args.sid)
except:
return public.returnMsg(False, 'Database type sid needs int type!')
if not int(args.sid) and not public.process_exists("mongod"):
return public.returnMsg(False,"Mongodb service has not been started yet!")
username = ''
password = ''
auth_status = self.get_local_auth(args) #auth为true时如果__DB_USER为空则将它赋值为 root,用于开启本地认证后数据库用户为空的情况
data_name = args.name.strip()
if not data_name:
return public.returnMsg(False, "Database name cannot be empty!")
if auth_status:
res = self.add_base_database(args)
if not res['status']: return res
data_name = res['data_name']
username = res['username']
password = res['data_pwd']
else:
username = data_name
db_obj = self.get_obj_by_sid(self.sid).get_db_obj(data_name,auth=auth_status)
dtype = 'MongoDB'
if not hasattr(args,'ps'): args['ps'] = public.getMsg('INPUT_PS');
addTime = time.strftime('%Y-%m-%d %X',time.localtime())
pid = 0
if hasattr(args,'pid'): pid = args.pid
if hasattr(args,'contact'):
site = public.M('sites').where("id=?",(args.contact,)).field('id,name').find()
if site:
pid = int(args.contact)
args['ps'] = site['name']
db_type = 0
if self.sid: db_type = 2
db_obj.chat.insert_one({})
if auth_status:
db_obj.command("createUser", username, pwd=password, roles=[{'role':'dbOwner','db':data_name},{'role':'userAdmin','db':data_name}])
public.set_module_logs('linux_mongodb','AddDatabase',1)
#添加入SQLITE
public.M('databases').add('pid,sid,db_type,name,username,password,accept,ps,addtime,type',(pid,self.sid,db_type,data_name,username,password,'127.0.0.1',args['ps'],addTime,dtype))
public.WriteLog("TYPE_DATABASE", 'DATABASE_ADD_SUCCESS',(data_name,))
return public.returnMsg(True,'ADD_SUCCESS')
def DeleteDatabase(self,args):
"""
@删除数据库
"""
id = args['id']
find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid,db_type').find()
if not find: return public.returnMsg(False,'The specified database does not exist.')
try:
int(find['sid'])
except:
return public.returnMsg(False, 'Database type sid needs int type!')
if not public.process_exists("mongod") and not int(find['sid']):
return public.returnMsg(False,"Mongodb service has not been started yet!")
name = args['name']
username = find['username']
auth_status = self.get_local_auth(args)
db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(name,auth_status)
try:
db_obj.command("dropUser", username)
except :
pass
db_obj.command('dropDatabase')
#删除SQLITE
public.M('databases').where("id=?",(id,)).delete()
public.WriteLog("Database manager", 'Successfully deleted!',(name,))
return public.returnMsg(True, 'Successfully deleted!')
def get_info_by_db_id(self,db_id):
"""
@获取数据库连接详情
@db_id 数据库id
"""
find = public.M('databases').where("id=?" ,db_id).find()
if not find: return False
data = {
'db_host':'127.0.0.1',
'db_port':int(panelMongoDB().get_options(None)['port']),
'db_user':find['username'],
'db_password':find['password']
}
if int(find['sid']):
conn_config = public.M('database_servers').where("id=?" ,find['sid']).find()
data['db_host'] = conn_config['db_host']
data['db_port'] = int(conn_config['db_port'])
return data
#导入
def InputSql(self,args):
name = args.name
file = args.file
if not os.path.exists(file): return public.returnMsg(False,'导入路径不存在!')
if not os.path.isfile(file): return public.returnMsg(False,'仅支持导入压缩文件!')
find = public.M('databases').where("name=? AND LOWER(type)=LOWER('MongoDB')",(name,)).find()
if not find: return public.returnMsg(False,'This database was not found!')
get = public.dict_obj()
get.sid = find['sid']
if not public.process_exists("mongod") and not int(find['sid']):
return public.returnMsg(False,"Mongodb service has not been started yet!")
info = self.get_info_by_db_id(find['id'])
mongorestore_obj = '{}/mongodb/bin/mongorestore'.format(public.get_setup_path())
mongoimport_obj = '{}/mongodb/bin/mongoimport'.format(public.get_setup_path())
if not os.path.exists(mongorestore_obj): return public.returnMsg(False,'Lack of backup tools, please install MongoDB through [APP Store] first!')
dir_tmp, file_tmp = os.path.split(file)
split_tmp = file_tmp.split(".")
ext = split_tmp[-1]
ext_err = ".".join(split_tmp[1:])
if len(split_tmp[1:]) == 2 and split_tmp[1] not in ['json', 'csv']:
return public.returnMsg(False, f'.{ext_err} This file format is not currently supported')
if ext not in ['json', 'csv', 'gz', 'zip']:
return public.returnMsg(False, f'.{ext_err} This file format is not currently supported')
tmpFile = ".".join(split_tmp[:-1])
isgzip = False
if ext != '': # gz zip
if tmpFile == '':
return public.returnMsg(False, 'FILE_NOT_EXISTS', (tmpFile,))
isgzip = True
# 面板默认备份路径
backupPath = session['config']['backup_path'] + '/database'
input_path = os.path.join(backupPath, tmpFile)
# 备份文件的路径
input_path2 = os.path.join(dir_tmp, tmpFile)
if ext == 'zip': # zip
public.ExecShell("cd " + backupPath + " && unzip " + '"' + file + '"')
else: # gz
public.ExecShell("cd " + backupPath + " && tar zxf " + '"' + file + '"')
if not os.path.exists(input_path):
# 兼容从备份文件所在目录恢复
if not os.path.exists(input_path2):
public.ExecShell("cd " + backupPath + " && gunzip -q " + '"' + file + '"')
else:
input_path = input_path2
if not os.path.exists(input_path) and os.path.isfile(input_path2):
input_path = input_path2
else:
input_path = file
if os.path.isdir(input_path): # zip,gz,bson
if self.get_local_auth(get):
for temp_file in os.listdir(input_path):
shell = f"""
{mongorestore_obj} \
--host={info['db_host']} \
--port={info['db_port']} \
--db={find['name']} \
--username={info['db_user']} \
--password={info['db_password']} \
--drop \
{os.path.join(input_path, temp_file)}
"""
public.ExecShell(shell)
else:
for temp_file in os.listdir(input_path):
shell = f"""
{mongorestore_obj} \
--host={info['db_host']} \
--port={info['db_port']} \
--db={find['name']} \
--drop \
{os.path.join(input_path, temp_file)}
"""
public.ExecShell(shell)
if isgzip is True:
public.ExecShell("rm -f " + input_path)
else:# json,csv
file_tmp = os.path.basename(input_path)
file_name = file_tmp.split(".")[0]
ext = file_tmp.split(".")[-1]
if ext not in ["json","csv"]:
return public.returnMsg(False, 'File format is incorrect!')
shell_txt = ""
if ext == "csv":
fp = open(input_path, "r")
fields_list = fp.readline()
fp.close()
shell_txt = f"--fields={fields_list}"
if self.get_local_auth(get):
shell = f"""
{mongoimport_obj} \
--host={info['db_host']} \
--port={info['db_port']} \
--db={find['name']} \
--username={info['db_user']} \
--password={info['db_password']} \
--collection={file_name} \
--file={input_path} \
--type={ext} \
--drop
"""
else:
shell = f"""
{mongoimport_obj} \
--host={info['db_host']} \
--port={info['db_port']} \
--db={find['name']} \
--collection={file_name} \
--file={input_path} \
--type={ext} \
--drop
"""
shell = f"{shell} {shell_txt}"
public.ExecShell(shell)
public.WriteLog("Database manager", 'Import database [{}] succeeded'.format(name))
return public.returnMsg(True, 'Successfully imported database!')
def ToBackup(self,args):
"""
@备份数据库 id 数据库id
"""
id = args['id']
find = public.M('databases').where("id=? AND LOWER(type)=LOWER('MongoDB')",(id,)).find()
if not find: return public.returnMsg(False,'The specified database does not exist.')
fileName = f"{find['name']}_mongodb_data_{time.strftime('%Y%m%d_%H%M%S',time.localtime())}"
backupName = session['config']['backup_path'] + '/database/mongodb/' + fileName
spath = os.path.dirname(backupName)
if not os.path.exists(spath): os.makedirs(spath)
get = public.dict_obj()
get.sid = find['sid']
try:
sid = int(find['sid'])
except:
return public.returnMsg(False, 'Database type sid needs int type!')
if not public.process_exists("mongod") and not int(find['sid']):
return public.returnMsg(False,"Mongodb service has not been started yet!")
info = self.get_info_by_db_id(id)
sql_dump = '{}/mongodb/bin/mongodump'.format(public.get_setup_path())
if not os.path.exists(sql_dump): return public.returnMsg(False,'Lack of backup tools, please install MongoDB through [APP Store] first!')
if self.get_local_auth(get):
if not info['db_password']:
return public.returnMsg(False,'Password authentication has been enabled. The password cannot be empty when the database is backed up. Please set a password and try again!')
shell = "{} -h {} --port {} -u {} -p {} -d {} -o {} ".format(sql_dump,info['db_host'],info['db_port'],info['db_user'],info['db_password'],find['name'] ,backupName)
else:
shell = "{} -h {} --port {} -d {} -o {} ".format(sql_dump,info['db_host'],info['db_port'],find['name'] ,backupName)
ret = public.ExecShell(shell)
if not os.path.exists(backupName):
return public.returnMsg(False,'Database backup failed, file does not exist');
backupFile = f"{backupName}.zip"
public.ExecShell(f"cd {spath} && zip {backupFile} -r {fileName}")
fileName = f"{fileName}.zip"
public.M('backup').add('type,name,pid,filename,size,addtime',(1,fileName,id,backupFile,0,time.strftime('%Y-%m-%d %X',time.localtime())))
public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS",(find['name'],))
public.ExecShell(f"rm -rf {backupName}")
if not os.path.exists(backupFile):
return public.returnMsg(True, 'Backup failed,{}.'.format(ret[0]))
if os.path.getsize(backupFile) < 1:
return public.returnMsg(True, 'The backup is executed successfully, the backup file is less than 1b, please check the backup integrity.')
else:
return public.returnMsg(True, 'BACKUP_SUCCESS')
def DelBackup(self,args):
"""
@删除备份文件
"""
return self.delete_base_backup(args)
#同步数据库到服务器
def SyncToDatabases(self,get):
type = int(get['type'])
n = 0
sql = public.M('databases')
if type == 0:
data = sql.field('id,name,username,password,accept,type,sid,db_type').where('type=?',('MongoDB',)).select()
for value in data:
if value['db_type'] in ['1',1]:
continue # 跳过远程数据库
result = self.ToDataBase(value)
if result == 1: n +=1
else:
import json
data = json.loads(get.ids)
for value in data:
find = sql.where("id=?",(value,)).field('id,name,username,password,sid,db_type,accept,type').find()
result = self.ToDataBase(find)
if result == 1: n +=1
if n == 1:
return public.returnMsg(True, 'Synchronization succeeded')
elif n == 0:
return public.returnMsg(False,'Sync failed')
return public.returnMsg(True,'DATABASE_SYNC_SUCCESS',(str(n),))
#添加到服务器
def ToDataBase(self,find):
if find['username'] == 'bt_default': return 0
if len(find['password']) < 3 :
find['username'] = find['name']
find['password'] = public.md5(str(time.time()) + find['name'])[0:10]
public.M('databases').where("id=?",(find['id'],)).save('password,username',(find['password'],find['username']))
self.sid = find['sid']
try:
int(find['sid'])
except:
return public.returnMsg(False, 'Database type sid needs int type!')
if not public.process_exists("mongod") and not int(find['sid']):
return public.returnMsg(False,"Mongodb service has not been started yet!")
get = public.dict_obj()
get.sid = self.sid
auth_status = self.get_local_auth(get)
if auth_status:
db_obj = self.get_obj_by_sid(self.sid).get_db_obj(find['name'], auth_status)
try:
db_obj.chat.insert_one({})
db_obj.command("dropUser", find['username'])
except :pass
try:
db_obj.command("createUser", find['username'], pwd=find['password'], roles=[{'role':'dbOwner','db':find['name']},{'role':'userAdmin','db':find['name']}])
except:
pass
return 1
def SyncGetDatabases(self,get):
"""
@从服务器获取数据库
"""
n = 0;s = 0;
db_type = 0
self.sid = get.get('sid/d',0)
if self.sid: db_type = 2
try:
int(get.sid)
except:
return public.returnMsg(False, 'The database type SID requires an INT!')
if not public.process_exists("mongod") and not int(get.sid):
return public.returnMsg(False,"The Mongodb service is not enabled!")
auth_status = self.get_local_auth(get)
data = self.get_obj_by_sid(self.sid).get_db_obj('admin',auth=auth_status).command({"listDatabases":1})
sql = public.M('databases')
nameArr = ['information_schema','performance_schema','mysql','sys','master','model','msdb','tempdb','config','local','admin']
for item in data['databases']:
dbname = item['name']
if sql.where("name=?",(dbname,)).count(): continue
if not dbname in nameArr:
if sql.table('databases').add('name,username,password,accept,ps,addtime,type,sid,db_type',(dbname,dbname,'','',public.getMsg('INPUT_PS'),time.strftime('%Y-%m-%d %X',time.localtime()),'MongoDB',self.sid,db_type)): n +=1
return public.returnMsg(True,'DATABASE_GET_SUCCESS',(str(n),))
def ResDatabasePassword(self,args):
"""
@修改用户密码
"""
id = args['id']
username = args['name'].strip()
newpassword = public.trim(args['password'])
try:
if not newpassword:
return public.returnMsg(False, 'Modify the failureThe database[' + username + ']password cannot be empty.');
if len(re.search("^[\w@\.]+$", newpassword).groups()) > 0:
return public.returnMsg(False, 'The database password cannot be empty or contain special characters')
if re.search('[\u4e00-\u9fa5]',newpassword):
return public.returnMsg(False,'Database password cannot be Chinese, please change the name!')
except :
return public.returnMsg(False, 'The database password cannot be empty or contain special characters')
find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid').find();
if not find: return public.returnMsg(False, 'The modification failed because the specified database does not exist.');
get = public.dict_obj()
get.sid = find['sid']
try:
int(find['sid'])
except:
return public.returnMsg(False, 'The database type SID requires an INT!')
if not public.process_exists("mongod") and not int(find['sid']):
return public.returnMsg(False,"The Mongodb service is not enabled!")
auth_status = self.get_local_auth(args)
if auth_status:
db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(username,auth=auth_status)
try:
print(db_obj.command("updateUser", username, pwd = newpassword))
except :
print(db_obj.command("createUser", username, pwd=newpassword, roles=[{'role':'dbOwner','db':find['name']},{'role':'userAdmin','db':find['name']}]))
else:
return public.returnMsg(False, 'Password access is not enabled for the database.')
#修改SQLITE
public.M('databases').where("id=?",(id,)).setField('password',newpassword)
public.WriteLog("TYPE_DATABASE",'DATABASE_PASS_SUCCESS',(find['name'],))
return public.returnMsg(True,'DATABASE_PASS_SUCCESS',(find['name'],))
def get_root_pwd(self,args):
"""
@获取root密码
"""
config = panelMongoDB().get_options(None)
sa_path = '{}/data/mongo.root'.format(public.get_panel_path())
if os.path.exists(sa_path):
config['msg'] = public.readFile(sa_path)
else:
config['msg'] = ''
config['root'] = config['msg']
return config
def get_database_size_by_id(self, args):
"""
@获取数据库尺寸批量删除验证
@args json/int 数据库id
"""
# if not public.process_exists("mongod"):
# return public.returnMsg(False,"The Mongodb service is not enabled!")
total = 0
db_id = args
if not isinstance(args, int): db_id = args['db_id']
find = public.M('databases').where('id=?', db_id).find()
try:
int(find['sid'])
except:
return 0
if not public.process_exists("mongod") and not int(find['sid']):
return 0
try:
auth_status = self.get_local_auth(args)
db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(find['name'], auth=auth_status)
print(db_obj)
print(db_obj.stats())
total = tables[0][1]
if not total: total = 0
except:
print(public.get_error_info())
return total
def check_del_data(self,args):
"""
@删除数据库前置检测
"""
return self.check_base_del_data(args)
def check_cloud_database_status(self,conn_config):
"""
@检测远程数据库是否连接
@conn_config 远程数据库配置包含host port pwd等信息
"""
try:
if not 'db_name' in conn_config: conn_config['db_name'] = None
sql_obj = panelMongoDB().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])
db_obj = sql_obj.get_db_obj('admin')
data = db_obj.command({"listDatabases":1})
if 'databases' in data:
return True
return False
except Exception as ex:
return public.returnMsg(False,ex)
+668
View File
@@ -0,0 +1,668 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hezhihong <bt_ahong@qq.com>
#-------------------------------------------------------------------
#------------------------------
# postgresql模型
#------------------------------
import os,re,json,time
from databaseModel.base import databaseBase
import public
try:
from BTPanel import session
except :pass
try:
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
except:
pass
class panelPgsql:
__DB_PASS = None
__DB_USER = 'postgres'
__DB_PORT = 5432
__DB_HOST = 'localhost'
__DB_CONN = None
__DB_CUR = None
__DB_ERR = None
__DB_CLOUD = 0 #远程数据库
def __init__(self):
self.__DB_CLOUD = 0
if self.__DB_USER=='postgres' and self.__DB_HOST == 'localhost' and self.__DB_PASS ==None:
tmp_args=public.dict_obj()
tmp_args.is_True = True
self.__DB_PASS =main().get_root_pwd(tmp_args)
def set_host(self,host,port,name,username,password,prefix = ''):
self.__DB_HOST = host
self.__DB_PORT = int(port)
self.__DB_NAME = name
if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME)
self.__DB_USER = str(username)
self._USER = str(username)
self.__DB_PASS = str(password)
self.__DB_PREFIX = prefix
self.__DB_CLOUD = 1
return self
def check_psycopg(self):
"""
@name检测依赖是否正常
"""
try:
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
except:
os.system('btpip install psycopg2-binary')
try:
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
except:
return False
return True
#连接MYSQL数据库
def __Conn(self):
self.check_psycopg()
try:
import psycopg2
except:
self.__DB_ERR = public.get_error_info()
return False
try:
if self.__DB_USER == 'postgres' and self.__DB_HOST=='localhost':
if not self.__DB_PASS:
tmp_args=public.dict_obj()
try:
self.__DB_PASS==main().get_root_pwd(tmp_args)['msg']
except:
pass
self.__DB_CONN = psycopg2.connect(user=self.__DB_USER, password = self.__DB_PASS, host=self.__DB_HOST, port = self.__DB_PORT)
self.__DB_CONN.autocommit = True
self.__DB_CONN.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) # <-- ADD THIS LINE
self.__DB_CUR = self.__DB_CONN.cursor()
return True
except :
self.__DB_ERR = public.get_error_info()
print(self.__DB_ERR)
return False
def execute(self,sql):
#执行SQL语句返回受影响行
if not self.__Conn(): return self.__DB_ERR
try:
#print(sql)
result = self.__DB_CUR.execute(sql)
self.__DB_CONN.commit()
self.__Close()
return result
except Exception as ex:
return ex
def query(self,sql):
#执行SQL语句返回数据集
if not self.__Conn(): return self.__DB_ERR
try:
self.__DB_CUR.execute(sql)
result = self.__DB_CUR.fetchall()
data = list(map(list,result))
self.__Close()
return data
except Exception as ex:
return ex
#关闭连接
def __Close(self):
self.__DB_CUR.close()
self.__DB_CONN.close()
class main(databaseBase,panelPgsql):
__ser_name = None
__soft_path = '/www/server/pgsql'
__setup_path = '/www/server/panel/'
__dbuser_info_path = "{}plugin/pgsql_manager_dbuser_info.json".format(__setup_path)
__plugin_path = "{}plugin/pgsql_manager/".format(__setup_path)
def __init__(self):
s_path = public.get_setup_path()
v_info = public.readFile("{}/pgsql/version.pl".format(s_path))
if v_info:
ver = v_info.split('.')[0]
self.__ser_name = 'postgresql-x64-{}'.format(ver)
self.__soft_path = '{}/pgsql/{}'.format(s_path)
#获取配置项
def get_options(self,get):
data = {}
options = ['port','listen_addresses']
if not self.__soft_path:self.__soft_path='{}/pgsql'.format(public.get_setup_path())
conf = public.readFile('{}/data/postgresql.conf'.format(self.__soft_path))
for opt in options:
tmp = re.findall("\s+" +opt + "\s*=\s*(.+)#",conf)
if not tmp: continue;
data[opt] = tmp[0].strip()
if opt == 'listen_addresses':
data[opt] = data[opt].replace('\'','')
data['password'] = self.get_root_pwd(None)['msg']
return data
def get_list(self,args):
"""
@获取数据库列表
@sql_type = pgsql
"""
return self.get_base_list(args, sql_type = 'pgsql')
def get_sql_obj_by_sid(self,sid = 0,conn_config = None):
"""
@取pgsql数据库对像 By sid
@sid 数据库分类0本地
"""
if type(sid) == str:
try:
sid = int(sid)
except :sid = 0
if sid:
if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find()
db_obj = panelPgsql()
try:
db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password'])
except Exception as e:
raise public.PanelError(e)
else:
db_obj = panelPgsql()
return db_obj
def get_sql_obj(self,db_name):
"""
@取pgsql数据库对象
@db_name 数据库名称
"""
is_cloud_db = False
if db_name:
db_find = public.M('databases').where("name=?" ,db_name).find()
if db_find['sid']:
return self.get_sql_obj_by_sid(db_find['sid'])
is_cloud_db = db_find['db_type'] in ['1',1]
if is_cloud_db:
db_obj = panelPgsql()
conn_config = json.loads(db_find['conn_config'])
try:
db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])
except Exception as e:
raise public.PanelError(e)
else:
db_obj = panelPgsql()
return db_obj
def GetCloudServer(self,args):
'''
@name 获取远程服务器列表
@author hwliang<2021-01-10>
@return list
'''
check_result = os.system('/www/server/pgsql/bin/psql --version')
if check_result !=0 and not public.M('database_servers').where('db_type=?','pgsql').count():return []
return self.GetBaseCloudServer(args)
def AddCloudServer(self,args):
'''
@添加远程数据库
'''
return self.AddBaseCloudServer(args)
def RemoveCloudServer(self,args):
'''
@删除远程数据库
'''
return self.RemoveBaseCloudServer(args)
def ModifyCloudServer(self,args):
'''
@修改远程数据库
'''
return self.ModifyBaseCloudServer(args)
def AddDatabase(self,args):
"""
@添加数据库
"""
if not args.get('name/str',0):return public.returnMsg(False, 'Database name cannot be empty!')
import re
test_str = re.search(r"\W",args.name)
if test_str!=None:
return public.returnMsg(False, 'The database name cannot contain special characters')
res = self.add_base_database(args)
if not res['status']: return res
data_name = res['data_name']
username = res['username']
password = res['data_pwd']
try:
self.sid = int(args['sid'])
except :
self.sid = 0
dtype = 'PgSql'
sql_obj = self.get_sql_obj_by_sid(self.sid)
result = sql_obj.execute("CREATE DATABASE {};".format(data_name))
isError = self.IsSqlError(result)
if isError != None: return isError
#添加用户
self.__CreateUsers(data_name,username,password,'127.0.0.1')
if not hasattr(args,'ps'): args['ps'] = public.getMsg('INPUT_PS');
addTime = time.strftime('%Y-%m-%d %X',time.localtime())
pid = 0
if hasattr(args,'pid'): pid = args.pid
if hasattr(args,'contact'):
site = public.M('sites').where("id=?",(args.contact,)).field('id,name').find()
if site:
pid = int(args.contact)
args['ps'] = site['name']
db_type = 0
if self.sid: db_type = 2
public.set_module_logs('pgsql','AddDatabase',1)
#添加入SQLITE
public.M('databases').add('pid,sid,db_type,name,username,password,accept,ps,addtime,type',(pid,self.sid,db_type,data_name,username,password,'127.0.0.1',args['ps'],addTime,dtype))
public.WriteLog("TYPE_DATABASE", 'DATABASE_ADD_SUCCESS',(data_name,))
return public.returnMsg(True,'ADD_SUCCESS')
def DeleteDatabase(self,get):
"""
@删除数据库
"""
id = get['id']
find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,accept,ps,addtime,db_type,conn_config,sid,type').find();
if not find: return public.returnMsg(False,'The specified database does not exist.')
name = get['name']
username = find['username']
sql_obj = self.get_sql_obj_by_sid(find['sid'])
result = sql_obj.execute("drop database {};".format(name))
sql_obj.execute("drop user {};".format(username))
#删除SQLITE
public.M('databases').where("id=?",(id,)).delete()
public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS',(name,))
return public.returnMsg(True, 'DEL_SUCCESS')
def ToBackup(self,args):
"""
@备份数据库 id 数据库id
"""
id = args['id']
find = public.M('databases').where("id=?",(id,)).find()
if not find: return public.returnMsg(False,'Database does not exist!')
if not find['password'].strip():
return public.returnMsg(False,'The database password is empty. Set the password first.')
sql_dump = '{}/bin/pg_dump'.format(self.__soft_path)
# return sql_dump
if not os.path.isfile(sql_dump):
return public.returnMsg(False,'Lack of backup tools, please first through the software store PGSQL manager!')
back_path = session['config']['backup_path'] + '/database/pgsql/'
# return back_path
if not os.path.exists(back_path): os.makedirs(back_path)
fileName = find['name'] + '_' + time.strftime('%Y%m%d_%H%M%S',time.localtime()) + '.sql'
backupName = back_path + fileName
if int(find['sid']):
info = self.get_info_by_db_id(id)
shell = '{} "host={} port={} user={} dbname={} password={}" > {}'.format(sql_dump,info['db_host'],info['db_port'],info['db_user'],find['name'],info['db_password'],backupName)
else:
args_one =public.dict_obj()
port = self.get_port(args_one)
shell = '{} "host=127.0.0.1 port={} user={} dbname={} password={}" > {}'.format(sql_dump,port['data'],find['username'],find['name'],find['password'],backupName)
ret = public.ExecShell(shell)
if not os.path.exists(backupName):
return public.returnMsg(False,'BACKUP_ERROR');
public.M('backup').add('type,name,pid,filename,size,addtime',(1,fileName,id,backupName,0,time.strftime('%Y-%m-%d %X',time.localtime())))
public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS",(find['name'],))
if os.path.getsize(backupName) < 2048:
return public.returnMsg(True, 'The backup file size is smaller than 2Kb. Check the backup integrity.')
else:
return public.returnMsg(True, 'BACKUP_SUCCESS')
def DelBackup(self,args):
"""
@删除备份文件
"""
return self.delete_base_backup(args)
def get_port(self, args): # 获取端口号
str_shell = '''netstat -luntp|grep postgres|head -1|awk '{print $4}'|awk -F: '{print $NF}' '''
try:
port = public.ExecShell(str_shell)[0]
if port.strip():
return {'data': port.strip(), "status": True}
else:
return {'data': 5432, "status": False}
except:
return {'data': 5432, "status": False}
#导入
def InputSql(self,get):
name = get.name
file = get.file
# return name
find = public.M('databases').where("name=?",(name,)).find()
if not find: return public.returnMsg(False,'Database does not exist!')
# return find
if not find['password'].strip():
return public.returnMsg(False,'The database password is empty. Set the password first.')
tmp = file.split('.')
exts = ['sql']
ext = tmp[len(tmp) -1]
if ext not in exts:
return public.returnMsg(False, 'DATABASE_INPUT_ERR_FORMAT')
sql_dump = '{}/bin/psql'.format(self.__soft_path)
if not os.path.exists(sql_dump):
return public.returnMsg(False,'Lack of recovery tool, please use software management to install PGSQL!')
if int(find['sid']):
info = self.get_info_by_db_id(find['id'])
shell = '{} "host={} port={} user={} dbname={} password={}" < {}'.format(sql_dump,info['db_host'],info['db_port'],info['db_user'],find['name'],info['db_password'],file)
else:
args_one =public.dict_obj()
port = self.get_port(args_one)
shell = '{} "host=127.0.0.1 port={} user={} dbname={} password={}" < {}'.format(sql_dump,port['data'],find['username'],find['name'],find['password'],file)
ret = public.ExecShell(shell)
public.WriteLog("TYPE_DATABASE", 'Description Succeeded in importing database [{}]'.format(name))
return public.returnMsg(True, 'DATABASE_INPUT_SUCCESS');
def SyncToDatabases(self,get):
"""
@name同步数据库到服务器
"""
tmp_type = int(get['type'])
n = 0
sql = public.M('databases')
if tmp_type == 0:
where = "lower(type) = lower('pgsql')"
# data = sql.field('id,name,username,password,accept,type,sid,db_type').where('type=?',('pgsql',)).select()
data = sql.field('id,name,username,password,accept,type,sid,db_type').where(where,()).select()
print(data)
for value in data:
if value['db_type'] in ['1',1]:
continue # 跳过远程数据库
result = self.ToDataBase(value)
if result == 1: n +=1
else:
import json
data = json.loads(get.ids)
for value in data:
find = sql.where("id=?",(value,)).field('id,name,username,password,sid,db_type,accept,type').find()
result = self.ToDataBase(find)
if result == 1: n +=1
if n == 1:
return public.returnMsg(True, 'Synchronization succeeded')
elif n == 0:
return public.returnMsg(False,'Sync failed')
return public.returnMsg(True,'DATABASE_SYNC_SUCCESS',(str(n),))
def ToDataBase(self,find):
"""
@name 添加到服务器
"""
if find['username'] == 'bt_default': return 0
if len(find['password']) < 3 :
find['username'] = find['name']
find['password'] = public.md5(str(time.time()) + find['name'])[0:10]
public.M('databases').where("id=?",(find['id'],)).save('password,username',(find['password'],find['username']))
self.sid = find['sid']
sql_obj = self.get_sql_obj_by_sid(self.sid)
result = sql_obj.execute("CREATE DATABASE {};".format(find['name']))
isError = self.IsSqlError(result)
if isError != None and isError['status']==False and isError['msg']=='指定数据库已存在,请勿重复添加.':return 1
self.__CreateUsers(find['name'],find['username'],find['password'],'127.0.0.1')
return 1
def SyncGetDatabases(self,get):
"""
@name 从服务器获取数据库
@param sid 0为本地数据库 1为远程数据库
"""
n = 0;s = 0;
db_type = 0
self.sid = get.get('sid/d',0)
if self.sid: db_type = 2
sql_obj = self.get_sql_obj_by_sid(self.sid)
data = sql_obj.query('SELECT datname FROM pg_database;')#select * from pg_database order by datname;
isError = self.IsSqlError(data)
if isError != None: return isError
if type(data) == str: return public.returnMsg(False,data)
sql = public.M('databases')
nameArr = ['information_schema','postgres','template1','template0','performance_schema','mysql','sys','master','model','msdb','tempdb','ReportServerTempDB','YueMiao','ReportServer']
for item in data:
dbname = item[0]
if sql.where("name=?",(dbname,)).count(): continue
if not dbname in nameArr:
if sql.table('databases').add('name,username,password,accept,ps,addtime,type,sid,db_type',(dbname,dbname,'','',public.getMsg('INPUT_PS'),time.strftime('%Y-%m-%d %X',time.localtime()),'pgsql',self.sid,db_type)): n +=1
return public.returnMsg(True,'DATABASE_GET_SUCCESS',(str(n),))
def ResDatabasePassword(self,args):
"""
@修改用户密码
"""
id = args['id']
username = args['name'].strip()
newpassword = public.trim(args['password'])
if not newpassword: return public.returnMsg(False, 'The database password cannot be empty.');
find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid').find();
if not find: return public.returnMsg(False, 'Modify the failureThe specified database does not exist.');
sql_obj = self.get_sql_obj_by_sid(find['sid'])
result = sql_obj.execute("alter user {} with password '{}';".format(username,newpassword))
isError = self.IsSqlError(result)
if isError != None: return isError
#修改SQLITE
public.M('databases').where("id=?",(id,)).setField('password',newpassword)
public.WriteLog("TYPE_DATABASE",'DATABASE_PASS_SUCCESS',(find['name'],))
return public.returnMsg(True,'DATABASE_PASS_SUCCESS',(find['name'],))
def get_root_pwd(self,args):
"""
@获取sa密码
"""
check_result = os.system('/www/server/pgsql/bin/psql --version')
if check_result !=0:return public.returnMsg(False,'If PgSQL is not installed or started, install or start it first')
password = ''
path = '{}/data/postgresAS.json'.format(public.get_panel_path())
if os.path.isfile(path):
try:
password = json.loads(public.readFile(path))['password']
print('333333333')
print(password)
except :pass
if 'is_True' in args and args.is_True:return password
return public.returnMsg(True,password)
def set_root_pwd(self,args):
"""
@设置sa密码
"""
password = public.trim(args['password'])
if len(password) < 8 : return public.returnMsg(False,'The password must not be less than 8 digits.')
check_result = os.system('/www/server/pgsql/bin/psql --version')
if check_result !=0:return public.returnMsg(False,'If PgSQL is not installed or started, install or start it first')
sql_obj = self.get_sql_obj_by_sid('0')
data = sql_obj.query('SELECT datname FROM pg_database;')
isError = self.IsSqlError(data)
if isError != None: return isError
path = '{}/data/pg_hba.conf'.format(self.__soft_path)
p_path = '{}/data/postgresAS.json'.format(public.get_panel_path())
if not os.path.isfile(path):return public.returnMsg(False,'{}File does not exist, please check the installation is complete!'.format(path))
src_conf = public.readFile(path)
add_conf = src_conf.replace('md5','trust')
# public.writeFile(path,public.readFile(path).replace('md5','trust'))
public.writeFile(path,add_conf)
pg_obj = panelPgsql()
pg_obj.execute("ALTER USER postgres WITH PASSWORD '{}';".format(password))
data = {"username":"postgres","password":""}
try:
data = json.loads(public.readFile(p_path))
except : pass
data['password'] = password
public.writeFile(p_path,json.dumps(data))
public.writeFile(path, src_conf)
return public.returnMsg(True,'The administrator password is successfully changed. Procedure.')
def get_info_by_db_id(self,db_id):
"""
@获取数据库连接详情
@db_id 数据库id
"""
# print(db_id,'111111111111')
find = public.M('databases').where("id=?" ,db_id).find()
# return find
if not find: return False
# print(find)
data = {
'db_host':'127.0.0.1',
'db_port':5432,
'db_user':find['username'],
'db_password':find['password']
}
if int(find['sid']):
conn_config = public.M('database_servers').where("id=?" ,find['sid']).find()
data['db_host'] = conn_config['db_host']
data['db_port'] = int(conn_config['db_port'])
return data
def get_database_size_by_id(self,args):
"""
@获取数据库尺寸批量删除验证
@args json/int 数据库id
"""
total = 0
db_id = args
if not isinstance(args,int): db_id = args['db_id']
try:
name = public.M('databases').where('id=?',db_id).getField('name')
sql_obj = self.get_sql_obj(name)
tables = sql_obj.query("select name,size,type from sys.master_files where type=0 and name = '{}'".format(name))
total = tables[0][1]
if not total: total = 0
except :pass
return total
def check_del_data(self,args):
"""
@删除数据库前置检测
"""
return self.check_base_del_data(args)
#本地创建数据库
def __CreateUsers(self,data_name,username,password,address):
"""
@创建数据库用户
"""
sql_obj = self.get_sql_obj_by_sid(self.sid)
sql_obj.execute("CREATE USER {} WITH PASSWORD '{}';".format(username,password))
sql_obj.execute("GRANT ALL PRIVILEGES ON DATABASE {} TO {};".format(data_name,username))
return True
def __get_db_list(self,sql_obj):
"""
获取pgsql数据库列表
"""
data = []
ret = sql_obj.query('SELECT datname FROM pg_database;')
if type(ret) == list:
for x in ret:
data.append(x[0])
return data
def check_cloud_database_status(self,conn_config):
"""
@检测远程数据库是否连接
@conn_config 远程数据库配置包含host port pwd等信息
"""
try:
if not 'db_name' in conn_config: conn_config['db_name'] = None
sql_obj = panelPgsql().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])
data = sql_obj.query("SELECT datname FROM pg_database;")
if type(data) == str:
return public.returnMsg(False,'Connecting to remote PGSQL fails. Perform the following operations to rectify the fault<br/>1、The database port is correct and the firewall allows access<br/>2、Check whether the database account password is correct<br/>3、pg_hba.confWhether to add a client release record<br/>4、postgresql.conf Add listen_addresses to the correct server IP address.')
if not conn_config['db_name']: return True
for i in data:
if i[0] == conn_config['db_name']:
return True
return public.returnMsg(False,'The specified database does not exist!')
except Exception as ex:
return public.returnMsg(False,"")
+425
View File
@@ -0,0 +1,425 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
# sqlite模型
#------------------------------
import os,re,json,shutil,time
from databaseModel.base import databaseBase
import public
try:
import redis
except:
public.ExecShell("btpip install redis")
import redis
try:
from BTPanel import session
except :pass
class panelRedisDB():
__DB_PASS = None
__DB_USER = None
__DB_PORT = 6379
__DB_HOST = '127.0.0.1'
__DB_CONN = None
__DB_ERR = None
__DB_CLOUD = None
def __init__(self):
self.__config = self.get_options(None)
def redis_conn(self,db_idx = 0):
if self.__DB_HOST in ['127.0.0.1','localhost']:
if not os.path.exists('/www/server/redis'): return False
if not self.__DB_CLOUD:
self.__DB_PASS = self.__config['requirepass']
self.__DB_PORT = int(self.__config['port'])
try:
redis_pool = redis.ConnectionPool(host=self.__DB_HOST, port= self.__DB_PORT, password= self.__DB_PASS, db= db_idx)
self.__DB_CONN = redis.Redis(connection_pool= redis_pool)
return self.__DB_CONN
except :
self.__DB_ERR = public.get_error_info()
return False
def set_host(self,host,port,name,username,password,prefix = ''):
self.__DB_HOST = host
self.__DB_PORT = int(port)
self.__DB_NAME = name
if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME)
self.__DB_USER = str(username)
self._USER = str(username)
self.__DB_PASS = str(password)
self.__DB_PREFIX = prefix
self.__DB_CLOUD = 1
return self
#获取配置项
def get_options(self,get = None):
result = {}
redis_conf = public.readFile("{}/redis/redis.conf".format(public.get_setup_path()))
if not redis_conf: return False
keys = ["bind","port","timeout","maxclients","databases","requirepass","maxmemory"]
for k in keys:
v = ""
rep = "\n%s\s+(.+)" % k
group = re.search(rep,redis_conf)
if not group:
if k == "maxmemory":
v = "0"
if k == "maxclients":
v = "10000"
if k == "requirepass":
v = ""
else:
if k == "maxmemory":
v = int(group.group(1)) / 1024 / 1024
else:
v = group.group(1)
result[k] = v
return result
class main(databaseBase):
_db_max = 16 #最大redis数据库
def __init__(self):
pass
def GetCloudServer(self,args):
'''
@name 获取远程服务器列表
@author hwliang<2021-01-10>
@return list
'''
return self.GetBaseCloudServer(args)
def AddCloudServer(self,args):
'''
@添加远程数据库
'''
return self.AddBaseCloudServer(args)
def RemoveCloudServer(self,args):
'''
@删除远程数据库
'''
return self.RemoveBaseCloudServer(args)
def ModifyCloudServer(self,args):
'''
@修改远程数据库
'''
return self.ModifyBaseCloudServer(args)
def get_obj_by_sid(self,sid = 0,conn_config = None):
"""
@取mssql数据库对像 By sid
@sid 数据库分类0本地
"""
if type(sid) == str:
try:
sid = int(sid)
except :sid = 0
if sid:
if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find()
db_obj = panelRedisDB()
try:
db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password'])
except Exception as e:
raise public.PanelError(e)
else:
db_obj = panelRedisDB()
return db_obj
def get_list(self,args):
"""
@获取数据库列表
@sql_type = redis
"""
result = []
self.sid = args.get('sid/d',0)
for x in range(0,self._db_max):
data = {}
data['id'] = x
data['name'] = 'DB{}'.format(x)
try:
redis_obj = self.get_obj_by_sid(self.sid).redis_conn(x)
data['keynum'] = redis_obj.dbsize()
if data['keynum'] > 0:
result.append(data)
except :pass
#result = sorted(result,key= lambda x:x['keynum'],reverse=True)
return result
def set_redis_val(self,args):
"""
@设置或修改指定值
"""
self.sid = args.get('sid/d',0)
if not 'name' in args or not 'val' in args:
return public.returnMsg(False,'Parameter passing error.');
endtime = 0
if 'endtime' in args : endtime = int(args.endtime)
redis_obj = self.get_obj_by_sid(self.sid).redis_conn(args.db_idx)
if endtime:
redis_obj.set(args.name, args.val, endtime)
else:
redis_obj.set(args.name, args.val)
public.set_module_logs('linux_redis','set_redis_val',1)
return public.returnMsg(True,'Operation is successful.');
def del_redis_val(self,args):
"""
@删除key值
"""
self.sid = args.get('sid/d',0)
if not 'key' in args:
return public.returnMsg(False,'Parameter passing error.');
redis_obj = self.get_obj_by_sid(self.sid).redis_conn(args.db_idx)
redis_obj.delete(args.key)
return public.returnMsg(True,'Operation is successful.');
def clear_flushdb(self,args):
"""
清空数据库
@ids 清空数据库列表不传则清空所有
"""
self.sid = args.get('sid/d',0)
ids = json.loads(args.ids)
#ids = []
if len(ids) == 0:
for x in range(0,self._db_max):
ids.append(x)
for x in ids:
redis_obj = self.get_obj_by_sid(self.sid).redis_conn(x)
redis_obj.flushdb()
return public.returnMsg(True,'Operation is successful.');
def get_db_keylist(self,args):
"""
@获取指定数据库key集合
"""
search = '*'
if 'search' in args: search = "*" + args.search+"*"
db_idx = args.db_idx
self.sid = args.get('sid/d',0)
redis_obj = self.get_obj_by_sid(self.sid).redis_conn(db_idx)
try:
keylist = sorted(redis_obj.keys(search))
except :
keylist = []
info = {'p':1,'row':10,'count':len(keylist)}
if hasattr(args,'limit'): info['row'] = int(args.limit)
if hasattr(args,'p'): info['p'] = int(args['p'])
import page
#实例化分页类
page = page.Page();
info['uri'] = args
info['return_js'] = ''
if hasattr(args,'tojs'): info['return_js'] = args.tojs
slist = keylist[(info['p']-1) * info['row']:info['p'] * info['row']]
rdata = {}
rdata['page'] = page.GetPage(info,'1,2,3,4,5,8')
rdata['where'] = ''
rdata['data'] = []
idx = 0
for key in slist:
item = {}
try:
item['name'] = key.decode()
except:
item['name'] = str(key)
item['endtime'] = redis_obj.ttl(key)
if item['endtime'] == -1: item['endtime'] = 0
item['type'] = redis_obj.type(key).decode()
if item['type'] == 'string':
try:
item['val'] = redis_obj.get(key).decode()
except:
item['val'] = str(redis_obj.get(key))
elif item['type'] == 'hash':
item['val'] = str(redis_obj.hgetall(key))
elif item['type'] == 'list':
item['val'] = str(redis_obj.lrange(key, 0, -1))
elif item['type'] == 'set':
item['val'] = str(redis_obj.smembers(key))
elif item['type'] == 'zset':
item['val'] = str(redis_obj.zrange(key, 0, 1, withscores=True))
else:
item['val'] = ''
try:
item['len'] = redis_obj.strlen(key)
except:
item['len'] = len(item['val'])
item['val'] = public.xsssec(item['val'])
item['name'] = public.xsssec(item['name'])
rdata['data'].append(item)
idx += 1
return rdata
def ToBackup(self,args):
"""
@备份数据库
"""
self.sid = args.get('sid/d',0)
redis_obj = self.get_obj_by_sid(self.sid).redis_conn(0)
redis_obj.save()
src_path = '{}/dump.rdb'.format(redis_obj.config_get()['dir'])
if not os.path.exists(src_path):
return public.returnMsg(False,'BACKUP_ERROR');
backup_path = session['config']['backup_path'] + '/database/redis/'
if not os.path.exists(backup_path): os.makedirs(backup_path)
fileName = backup_path + str(self.sid) + '_db_' + time.strftime('%Y%m%d_%H%M%S',time.localtime()) +'.rdb'
shutil.copyfile(src_path,fileName)
if not os.path.exists(fileName):
return public.returnMsg(False,'BACKUP_ERROR');
return public.returnMsg(True, 'BACKUP_SUCCESS')
def DelBackup(self,args):
"""
@删除备份文件
"""
file = args.file
if os.path.exists(file): os.remove(file)
return public.returnMsg(True, 'DEL_SUCCESS');
def InputSql(self,get):
"""
@导入数据库
"""
file = get.file
self.sid = get.get('sid/d',0)
redis_obj = self.get_obj_by_sid(self.sid).redis_conn(0)
rpath = redis_obj.config_get()['dir']
dst_path = '{}/dump.rdb'.format(rpath)
public.ExecShell("/etc/init.d/redis stop")
if os.path.exists(dst_path): os.remove(dst_path)
shutil.copy2(file, dst_path)
public.ExecShell("chown redis.redis {dump} && chmod 644 {dump}".format(dump=dst_path))
# self.restart_services()
public.ExecShell("/etc/init.d/redis start")
if os.path.exists(dst_path):
return public.returnMsg(True, 'Restore Successful.')
return public.returnMsg(False, 'Restore failure.')
def get_backup_list(self,get):
"""
@获取备份文件列表
"""
search = ''
if hasattr(get,'search'): search = get['search'].strip().lower();
nlist = []
cloud_list = {}
for x in self.GetCloudServer({'type':'redis'}): cloud_list['id-' + str(x['id'])] = x
path = session['config']['backup_path'] + '/database/redis/'
if not os.path.exists(path): os.makedirs(path)
for name in os.listdir(path):
if search:
if name.lower().find(search) == -1: continue;
arrs = name.split('_')
filepath = '{}/{}'.format(path,name).replace('//','/')
stat = os.stat(filepath)
item = {}
item['name'] = name
item['filepath'] = filepath
item['size'] = stat.st_size
item['mtime'] = int(stat.st_mtime)
item['sid'] = arrs[0]
item['conn_config'] = cloud_list['id-' + str(arrs[0])]
nlist.append(item)
if hasattr(get, 'sort'):
nlist = sorted(nlist, key=lambda data: data['mtime'], reverse=get["sort"] == "desc")
return nlist
def restart_services(self):
"""
@重启服务
"""
public.ExecShell('net stop redis')
public.ExecShell('net start redis')
return True
def check_cloud_database_status(self,conn_config):
"""
@检测远程数据库是否连接
@conn_config 远程数据库配置包含host port pwd等信息
"""
try:
sql_obj = panelRedisDB().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])
keynum = sql_obj.redis_conn(0).dbsize()
return True
except Exception as ex:
return public.returnMsg(False,ex)
+22
View File
@@ -0,0 +1,22 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# sqlite模型
#------------------------------
import os,sys,re,json,shutil,psutil,time
from databaseModel.base import databaseBase
import public
class main(databaseBase):
def get_list(self,args):
return []
+495
View File
@@ -0,0 +1,495 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# sqlite模型
#------------------------------
import os,re,json,time
from databaseModel.base import databaseBase
import public,panelMssql
try:
from BTPanel import session
except :pass
class main(databaseBase):
def get_list(self,args):
"""
@获取数据库列表
@sql_type = sqlserver
"""
return self.get_base_list(args, sql_type = 'sqlserver')
def get_mssql_obj_by_sid(self,sid = 0,conn_config = None):
"""
@取mssql数据库对像 By sid
@sid 数据库分类0本地
"""
if type(sid) == str:
try:
sid = int(sid)
except :sid = 0
if sid:
if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find()
db_obj = panelMssql.panelMssql()
try:
db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password'])
except Exception as e:
raise public.PanelError(e)
else:
db_obj = panelMssql.panelMssql()
return db_obj
def get_mssql_obj(self,db_name):
"""
@取mssql数据库对象
@db_name 数据库名称
"""
is_cloud_db = False
if db_name:
db_find = public.M('databases').where("name=?" ,db_name).find()
if db_find['sid']:
return self.get_mssql_obj_by_sid(db_find['sid'])
is_cloud_db = db_find['db_type'] in ['1',1]
if is_cloud_db:
db_obj = panelMssql.panelMssql()
conn_config = json.loads(db_find['conn_config'])
try:
db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])
except Exception as e:
raise public.PanelError(e)
else:
db_obj = panelMssql.panelMssql()
return db_obj
def GetCloudServer(self,args):
'''
@name 获取远程服务器列表
@author hwliang<2021-01-10>
@return list
'''
return self.GetBaseCloudServer(args)
def AddCloudServer(self,args):
'''
@添加远程数据库
'''
return self.AddBaseCloudServer(args)
def RemoveCloudServer(self,args):
'''
@删除远程数据库
'''
return self.RemoveBaseCloudServer(args)
def ModifyCloudServer(self,args):
'''
@修改远程数据库
'''
return self.ModifyBaseCloudServer(args)
def AddDatabase(self,args):
"""
@添加数据库
"""
res = self.add_base_database(args)
if not res['status']: return res
data_name = res['data_name']
username = res['username']
password = res['data_pwd']
if re.match("^\d+",data_name):
return public.returnMsg(False,'SQLServer databases cannot start with numbers!')
reg_count = 0
regs = ['[a-z]','[A-Z]','\W','[0-9]']
for x in regs:
if re.search(x,password): reg_count += 1
if len(password) < 8 or len(password) >128 or reg_count < 3 :
return public.returnMsg(False,'SQLServer password complexity policy does not match, should be 8-128 characters, and contain any 3 of them in upper case, lower case, digits, special symbols!')
try:
self.sid = int(args['sid'])
except :
self.sid = 0
dtype = 'SQLServer'
#添加SQLServer
mssql_obj = self.get_mssql_obj_by_sid(self.sid)
result = mssql_obj.execute("CREATE DATABASE %s" % data_name)
isError = self.IsSqlError(result)
if isError != None: return isError
mssql_obj.execute("DROP LOGIN %s" % username)
#添加用户
self.__CreateUsers(data_name,username,password,'127.0.0.1')
if not hasattr(args,'ps'): args['ps'] = public.getMsg('INPUT_PS');
addTime = time.strftime('%Y-%m-%d %X',time.localtime())
pid = 0
if hasattr(args,'pid'): pid = args.pid
if hasattr(args,'contact'):
site = public.M('sites').where("id=?",(args.contact,)).field('id,name').find()
if site:
pid = int(args.contact)
args['ps'] = site['name']
db_type = 0
if self.sid: db_type = 2
public.set_module_logs('linux_sqlserver','AddDatabase',1)
#添加入SQLITE
public.M('databases').add('pid,sid,db_type,name,username,password,accept,ps,addtime,type',(pid,self.sid,db_type,data_name,username,password,'127.0.0.1',args['ps'],addTime,dtype))
public.WriteLog("TYPE_DATABASE", 'DATABASE_ADD_SUCCESS',(data_name,))
return public.returnMsg(True,'ADD_SUCCESS')
def DeleteDatabase(self,args):
"""
@删除数据库
"""
id = args['id']
find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid,db_type').find();
if not find: return public.returnMsg(False,'The specified database does not exist.')
name = args['name']
username = find['username'];
mssql_obj = self.get_mssql_obj_by_sid(find['sid'])
mssql_obj.execute("ALTER DATABASE %s SET SINGLE_USER with ROLLBACK IMMEDIATE" % name)
result = mssql_obj.execute("DROP DATABASE %s" % name)
if self.get_database_size_by_id(find['sid']):
isError = self.IsSqlError(result)
if isError != None: return isError
mssql_obj.execute("DROP LOGIN %s" % username)
#删除SQLITE
public.M('databases').where("id=?",(id,)).delete()
public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS',(name,))
return public.returnMsg(True, 'DEL_SUCCESS')
def ToBackup(self,args):
"""
@备份数据库 id 数据库id
"""
id = args['id']
find = public.M('databases').where("id=?",(id,)).find()
if not find: return public.returnMsg(False,'Database does not exist!')
self.CheckBackupPath(args);
fileName = find['name'] + '_' + time.strftime('%Y%m%d_%H%M%S',time.localtime()) + '.bak'
backupName = session['config']['backup_path'] + '/database/sqlserver/' + fileName
mssql_obj = self.get_mssql_obj_by_sid(find['sid'])
if not int(find['sid']):
ret = mssql_obj.execute("backup database %s To disk='%s'" % (find['name'],backupName))
isError=self.IsSqlError(ret)
if isError != None: return isError
else:
#远程数据库
return public.returnMsg(False,'Operation failed. Remote database cannot be backed up.');
if not os.path.exists(backupName):
return public.returnMsg(False,'BACKUP_ERROR');
public.M('backup').add('type,name,pid,filename,size,addtime',(1,fileName,id,backupName,0,time.strftime('%Y-%m-%d %X',time.localtime())))
public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS",(find['name'],))
if os.path.getsize(backupName) < 2048:
return public.returnMsg(True, 'The backup file size is smaller than 2Kb. Check the backup integrity.')
else:
return public.returnMsg(True, 'BACKUP_SUCCESS')
def DelBackup(self,args):
"""
@删除备份文件
"""
return self.delete_base_backup(args)
#导入
def InputSql(self,get):
name = get.name
file = get.file
find = public.M('databases').where("name=?",(name,)).find()
if not find: return public.returnMsg(False,'Database does not exist!')
tmp = file.split('.')
exts = ['sql','zip','bak']
ext = tmp[len(tmp) -1]
if ext not in exts:
return public.returnMsg(False, 'DATABASE_INPUT_ERR_FORMAT')
backupPath = session['config']['backup_path'] + '/database'
if ext == 'zip':
try:
fname = os.path.basename(file).replace('.zip','')
dst_path = backupPath + '/' +fname
if not os.path.exists(dst_path): os.makedirs(dst_path)
public.unzip(file,dst_path)
for x in os.listdir(dst_path):
if x.find('bak') >= 0 or x.find('sql') >= 0:
file = dst_path + '/' + x
break
except :
return public.returnMsg(False,'The import failed because the file is not a valid ZIP file.')
mssql_obj = self.get_mssql_obj_by_sid(find['sid'])
data = mssql_obj.query("use %s ;select filename from sysfiles" % find['name'])
isError = self.IsSqlError(data)
if isError != None: return isError
if type(data) == str: return public.returnMsg(False,data)
mssql_obj.execute("ALTER DATABASE %s SET OFFLINE WITH ROLLBACK IMMEDIATE" % (find['name']))
mssql_obj.execute("use master;restore database %s from disk='%s' with replace, MOVE N'%s' TO N'%s',MOVE N'%s_Log' TO N'%s' " % (find['name'],file,find['name'],data[0][0],find['name'],data[1][0]))
mssql_obj.execute("ALTER DATABASE %s SET ONLINE" % (find['name']))
public.WriteLog("TYPE_DATABASE", 'Description Succeeded in importing database [{}]'.format(name))
return public.returnMsg(True, 'DATABASE_INPUT_SUCCESS');
#同步数据库到服务器
def SyncToDatabases(self,get):
type = int(get['type'])
n = 0
sql = public.M('databases')
if type == 0:
data = sql.field('id,name,username,password,accept,type,sid,db_type').where('type=?',('SQLServer',)).select()
for value in data:
if value['db_type'] in ['1',1]:
continue # 跳过远程数据库
result = self.ToDataBase(value)
if result == 1: n +=1
else:
import json
data = json.loads(get.ids)
for value in data:
find = sql.where("id=?",(value,)).field('id,name,username,password,sid,db_type,accept,type').find()
result = self.ToDataBase(find)
if result == 1: n +=1
if n == 1:
return public.returnMsg(True, 'Synchronization succeeded')
elif n == 0:
return public.returnMsg(False,'Sync failed')
return public.returnMsg(True,'DATABASE_SYNC_SUCCESS',(str(n),))
#添加到服务器
def ToDataBase(self,find):
if find['username'] == 'bt_default': return 0
if len(find['password']) < 3 :
find['username'] = find['name']
find['password'] = public.md5(str(time.time()) + find['name'])[0:10]
public.M('databases').where("id=?",(find['id'],)).save('password,username',(find['password'],find['username']))
self.sid = find['sid']
mssql_obj = self.get_mssql_obj_by_sid(self.sid)
result = mssql_obj.execute("CREATE DATABASE %s" % find['name'])
isError = self.IsSqlError(result)
if isError != None and not 'already exists' in result: return -1
self.__CreateUsers(find['name'],find['username'],find['password'],'127.0.0.1')
return 1
#从服务器获取数据库
def SyncGetDatabases(self,get):
n = 0;s = 0;
db_type = 0
self.sid = get.get('sid/d',0)
if self.sid: db_type = 2
mssql_obj = self.get_mssql_obj_by_sid(self.sid)
data = mssql_obj.query('SELECT name FROM MASTER.DBO.SYSDATABASES ORDER BY name')
isError = self.IsSqlError(data)
if isError != None: return isError
if type(data) == str: return public.returnMsg(False,data)
sql = public.M('databases')
nameArr = ['information_schema','performance_schema','mysql','sys','master','model','msdb','tempdb','ReportServerTempDB','YueMiao','ReportServer']
for item in data:
dbname = item[0]
if sql.where("name=?",(dbname,)).count(): continue
if not dbname in nameArr:
if sql.table('databases').add('name,username,password,accept,ps,addtime,type,sid,db_type',(dbname,dbname,'','',public.getMsg('INPUT_PS'),time.strftime('%Y-%m-%d %X',time.localtime()),'SQLServer',self.sid,db_type)): n +=1
return public.returnMsg(True,'DATABASE_GET_SUCCESS',(str(n),))
def ResDatabasePassword(self,args):
"""
@修改用户密码
"""
id = args['id']
username = args['name'].strip()
newpassword = public.trim(args['password'])
try:
if not newpassword:
return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.');
if len(re.search("^[\w@\.]+$", newpassword).groups()) > 0:
return public.returnMsg(False, 'The database password cannot be empty or contain special characters')
except :
return public.returnMsg(False, 'The database password cannot be empty or contain special characters')
find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid').find();
if not find: return public.returnMsg(False, 'Modify the failureThe specified database does not exist.');
mssql_obj = self.get_mssql_obj_by_sid(find['sid'])
mssql_obj.execute("EXEC sp_password NULL, '%s', '%s'" % (newpassword,username))
#修改SQLITE
public.M('databases').where("id=?",(id,)).setField('password',newpassword)
public.WriteLog("TYPE_DATABASE",'DATABASE_PASS_SUCCESS',(find['name'],))
return public.returnMsg(True,'DATABASE_PASS_SUCCESS',(find['name'],))
def get_root_pwd(self,args):
"""
@获取sa密码
"""
mssql_obj = panelMssql.panelMssql()
ret = mssql_obj.get_sql_name()
if not ret : return public.returnMsg(False, 'The SQL Server is not installed or started. Install or start it first')
sa_path = '{}/data/sa.pl'.format(public.get_panel_path())
if os.path.exists(sa_path):
password = public.readFile(sa_path)
return public.returnMsg(True,password)
return public.returnMsg(True,'')
def set_root_pwd(self,args):
"""
@设置sa密码
"""
password = public.trim(args['password'])
try:
if not password:
return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.')
if len(re.search("^[\w@\.]+$", password).groups()) > 0:
return public.returnMsg(False, 'saThe password cannot be empty or have special symbols')
except :
return public.returnMsg(False, 'saThe password cannot be empty or have special symbols')
mssql_obj = panelMssql.panelMssql()
result = mssql_obj.execute("EXEC sp_password NULL, '%s', 'sa'" % password)
isError = self.IsSqlError(result)
if isError != None: return isError
public.writeFile('data/sa.pl',password)
session['config']['mssql_sa'] = password
return public.returnMsg(True,'The password of sa is changed successfully.')
def get_database_size_by_id(self,args):
"""
@获取数据库尺寸批量删除验证
@args json/int 数据库id
"""
total = 0
db_id = args
if not isinstance(args,int): db_id = args['db_id']
try:
name = public.M('databases').where('id=?',db_id).getField('name')
mssql_obj = self.get_mssql_obj(name)
tables = mssql_obj.query("select name,size,type from sys.master_files where type=0 and name = '{}'".format(name))
total = tables[0][1]
if not total: total = 0
except :pass
return total
def check_del_data(self,args):
"""
@删除数据库前置检测
"""
return self.check_base_del_data(args)
#本地创建数据库
def __CreateUsers(self,data_name,username,password,address):
"""
@创建数据库用户
"""
mssql_obj = self.get_mssql_obj_by_sid(self.sid)
mssql_obj.execute("use %s create login %s with password ='%s' , default_database = %s" % (data_name,username,password,data_name))
mssql_obj.execute("use %s create user %s for login %s with default_schema=dbo" % (data_name,username,username))
mssql_obj.execute("use %s exec sp_addrolemember 'db_owner','%s'" % (data_name,data_name))
mssql_obj.execute("ALTER DATABASE %s SET MULTI_USER" % data_name)
#检测备份目录并赋值权限(MSSQL需要Authenticated Users
def CheckBackupPath(self,get):
backupFile = session['config']['backup_path'] + '/database/sqlserver'
if not os.path.exists(backupFile):
os.makedirs(backupFile)
get.filename = backupFile
get.user = 'Authenticated Users'
get.access = 2032127
import files
files.files().SetFileAccess(get)
def check_cloud_database_status(self,conn_config):
"""
@检测远程数据库是否连接
@conn_config 远程数据库配置包含host port pwd等信息
"""
try:
import panelMssql
if not 'db_name' in conn_config: conn_config['db_name'] = None
sql_obj = panelMssql.panelMssql().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])
data = sql_obj.query("SELECT name FROM MASTER.DBO.SYSDATABASES ORDER BY name")
isError = self.IsSqlError(data)
if isError != None: return isError
if type(data) == str:
return public.returnMsg(False,data)
if not conn_config['db_name']: return True
for i in data:
if i[0] == conn_config['db_name']:
return True
return public.returnMsg(False,'The specified database does not exist!')
except Exception as ex:
return public.returnMsg(False,ex)
+1
View File
@@ -36,6 +36,7 @@ class datatools:
db_name=get.db_name
if not db_name:return False
if not self.DB_MySQL:self.DB_MySQL = public.get_mysql_obj(db_name)
if not self.DB_MySQL: return self.DB_MySQL
ret = {}
tables = self.map_to_list(self.DB_MySQL.query('show tables from `%s`' % db_name))
if type(tables) == list:
+59 -42
View File
@@ -37,7 +37,7 @@ class Sql():
def __exit__(self,exc_type,exc_value,exc_trackback):
self.close()
def __GetConn(self):
def __GetConn(self):
#取数据库对象
try:
if self.__DB_CONN == None:
@@ -45,17 +45,26 @@ class Sql():
self.__DB_CONN.text_factory = str
except Exception as ex:
return "error: " + str(ex)
def dbfile(self,name):
self.__DB_FILE = 'data/' + name + '.db'
def connect(self):
#连接数据库
self.__GetConn()
return self
def dbfile(self,name):
#设置数据库文件
if name[0] == '/':
self.__DB_FILE = name
else:
self.__DB_FILE = 'data/' + name + '.db'
return self
def table(self,table):
#设置表名
self.__DB_TABLE = table
return self
def where(self,where,param):
#WHERE条件
if where:
@@ -72,29 +81,31 @@ class Sql():
param = (param,)
return param
def order(self,order):
#ORDER条件
if len(order):
self.__OPT_ORDER = " ORDER BY "+order
return self
def limit(self,limit):
def limit(self,limit,offset = 0):
#LIMIT条件
if limit:
if limit and not offset:
self.__OPT_LIMIT = " LIMIT {}".format(limit)
elif limit and offset:
self.__OPT_LIMIT = " LIMIT {},{}".format(offset,limit)
return self
def field(self,field):
#FIELD条件
if len(field):
self.__OPT_FIELD = field
return self
def select(self):
#查询数据集
self.__GetConn()
@@ -122,7 +133,7 @@ class Sql():
tmp = list(map(list,data))
data = tmp
del(tmp)
self.__close()
self._close()
return data
except Exception as ex:
return "error: " + str(ex)
@@ -141,7 +152,7 @@ class Sql():
key = key.split(as_tip)[1]
fields.append(key)
return fields
def __get_columns(self):
if self.__OPT_FIELD == '*':
tmp_cols = self.query('PRAGMA table_info('+self.__DB_TABLE+')',())
@@ -158,13 +169,13 @@ class Sql():
return result[0][keyName]
return result
except: return None
def setField(self,keyName,keyValue):
#更新指定字段
return self.save(keyName,(keyValue,))
def find(self):
#取一行数据
try:
@@ -173,8 +184,8 @@ class Sql():
return result[0]
return result
except:return None
def count(self):
#取行数
key="COUNT(*)"
@@ -183,8 +194,8 @@ class Sql():
return int(data[0][key])
except:
return 0
def add(self,keys,param):
#插入数据
self.write_lock()
@@ -198,7 +209,7 @@ class Sql():
sql = "INSERT INTO "+self.__DB_TABLE+"("+keys+") "+"VALUES("+values+")"
result = self.__DB_CONN.execute(sql,self.__to_tuple(param))
id = result.lastrowid
self.__close()
self._close()
self.__DB_CONN.commit()
self.rm_lock()
return id
@@ -241,12 +252,12 @@ class Sql():
return True
except Exception as ex:
return "error: " + str(ex)
def commit(self):
self.__close()
self._close()
self.__DB_CONN.commit()
def save(self,keys,param):
#更新数据
self.write_lock()
@@ -265,13 +276,13 @@ class Sql():
tmp.append(arg)
self.__OPT_PARAM = tuple(tmp)
result = self.__DB_CONN.execute(sql,self.__OPT_PARAM)
self.__close()
self._close()
self.__DB_CONN.commit()
self.rm_lock()
return result.rowcount
except Exception as ex:
return "error: " + str(ex)
def delete(self,id=None):
#删除数据
self.write_lock()
@@ -282,14 +293,14 @@ class Sql():
self.__OPT_PARAM = (id,)
sql = "DELETE FROM " + self.__DB_TABLE + self.__OPT_WHERE
result = self.__DB_CONN.execute(sql,self.__OPT_PARAM)
self.__close()
self._close()
self.__DB_CONN.commit()
self.rm_lock()
return result.rowcount
except Exception as ex:
return "error: " + str(ex)
def execute(self,sql,param = ()):
#执行SQL语句返回受影响行
self.write_lock()
@@ -324,7 +335,7 @@ class Sql():
return
# if os.path.exists(self.__LOCK):
# os.remove(self.__LOCK)
def query(self,sql,param = ()):
#执行SQL语句返回数据集
self.__GetConn()
@@ -335,7 +346,7 @@ class Sql():
return data
except Exception as ex:
return "error: " + str(ex)
def create(self,name):
#创建数据表
self.write_lock()
@@ -345,7 +356,7 @@ class Sql():
self.__DB_CONN.commit()
self.rm_lock()
return result.rowcount
def fofile(self,filename):
#执行脚本
self.write_lock()
@@ -355,8 +366,8 @@ class Sql():
self.__DB_CONN.commit()
self.rm_lock()
return result.rowcount
def __close(self):
def _close(self):
#清理条件属性
self.__OPT_WHERE = ""
self.__OPT_FIELD = "*"
@@ -364,6 +375,12 @@ class Sql():
self.__OPT_LIMIT = ""
self.__OPT_PARAM = ()
def is_connect(self):
#检查是否连接数据库
if not self.__DB_CONN:
return False
return True
def close(self):
#释放资源
@@ -372,4 +389,4 @@ class Sql():
self.__DB_CONN = None
except:
pass
+51 -20
View File
@@ -27,6 +27,7 @@ class panelMysql:
__OPT_FIELD = "*" # field条件
__OPT_PARAM = () # where值
_USER = None
_ex = None
def __init__(self):
pass
@@ -48,15 +49,26 @@ class panelMysql:
self._USER = str(username)
self.__DB_PASS = str(password)
self.__DB_PREFIX = prefix
self.__GetConn()
if not self.__GetConn(): return False
return self
#连接MYSQL数据库
def __GetConn(self):
try:
# print(self.__DB_HOST,self.__DB_PORT,self.__DB_NAME,self.__DB_USER,self.__DB_PASS)
self.__DB_CONN = pymysql.connect(host=self.__DB_HOST,user=self.__DB_USER,passwd=str(self.__DB_PASS),db=self.__DB_NAME,port=self.__DB_PORT,connect_timeout=15,read_timeout=60,write_timeout=60)
except:
self.__DB_CONN = pymysql.connect(host=self.__DB_HOST,user=self.__DB_USER,passwd=str(self.__DB_PASS),db=self.__DB_NAME,port=self.__DB_PORT)
except Exception as ex:
self.__DB_ERR = "error: " + str(ex)
self._ex = ex
print(ex)
if self.__DB_ERR.find("timed out") != -1 or self.__DB_ERR.find("is not allowed to connect") != -1: return False
try:
self.__DB_CONN = pymysql.connect(host=self.__DB_HOST,user=self.__DB_USER,passwd=str(self.__DB_PASS),db=self.__DB_NAME,port=self.__DB_PORT)
except Exception as ex:
self.__DB_ERR = "error: " + str(ex)
self._ex = ex
print(ex)
return False
self.__DB_CUR = self.__DB_CONN.cursor()
return True
@@ -79,28 +91,32 @@ class panelMysql:
if type(param) == list:
param = tuple(param)
else:
param = (param, )
param = (param,)
return param
def order(self, order):
def order(self,order):
#ORDER条件
if len(order):
self.__OPT_ORDER = " ORDER BY " + order
self.__OPT_ORDER = " ORDER BY "+order
return self
def limit(self, limit):
def limit(self,limit):
#LIMIT条件
limit = str(limit)
if len(limit):
self.__OPT_LIMIT = " LIMIT " + limit
self.__OPT_LIMIT = " LIMIT "+ limit
return self
def field(self, field):
def field(self,field):
#FIELD条件
if len(field):
self.__OPT_FIELD = field
return self
def select(self):
#查询数据集
self.__GetConn()
@@ -108,14 +124,14 @@ class panelMysql:
try:
self.__get_columns()
sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + self.__OPT_WHERE + self.__OPT_ORDER + self.__OPT_LIMIT
self.__DB_CUR.execute(sql, self.__OPT_PARAM)
self.__DB_CUR.execute(sql,self.__OPT_PARAM)
data = self.__DB_CUR.fetchall()
#构造字典系列
if self.__OPT_FIELD != "*":
fields = self.__format_field(self.__OPT_FIELD.split(','))
tmp = []
for row in data:
i = 0
i=0
tmp1 = {}
for key in fields:
tmp1[key.strip('`')] = row[i]
@@ -132,6 +148,7 @@ class panelMysql:
self.__close()
return data
except Exception as ex:
self._ex = ex
return "error: " + str(ex)
def get(self):
@@ -208,6 +225,7 @@ class panelMysql:
self.__DB_CONN.commit()
return id
except Exception as ex:
self._ex = ex
return "error: " + str(ex)
#插入数据
@@ -249,6 +267,7 @@ class panelMysql:
result = self.__DB_CUR.execute(sql, self.__to_tuple(param))
return True
except Exception as ex:
self._ex = ex
return "error: " + str(ex)
def commit(self):
@@ -267,15 +286,19 @@ class panelMysql:
sql = "UPDATE " + self.__DB_TABLE + " SET " + opt + self.__OPT_WHERE
#处理拼接WHERE与UPDATE参数
tmp = list(self.__to_tuple(param))
for arg in self.__OPT_PARAM:
tmp.append(arg)
self.__OPT_PARAM = tuple(tmp)
self.__DB_CUR.execute(sql, self.__OPT_PARAM)
if param:
tmp = list(self.__to_tuple(param))
for arg in self.__OPT_PARAM:
tmp.append(arg)
self.__OPT_PARAM = tuple(tmp)
self.__DB_CUR.execute(sql,self.__OPT_PARAM)
else:
self.__DB_CUR.execute(sql)
self.__close()
self.__DB_CONN.commit()
return self.__DB_CUR.rowcount
except Exception as ex:
self._ex = ex
return "error: " + str(ex)
def delete(self, id=None):
@@ -297,12 +320,16 @@ class panelMysql:
#执行SQL语句返回受影响行
if not self.__GetConn(): return self.__DB_ERR
try:
self.__OPT_PARAM = list(self.__to_tuple(param))
result = self.__DB_CUR.execute(sql,self.__OPT_PARAM)
if param:
self.__OPT_PARAM = list(self.__to_tuple(param))
result = self.__DB_CUR.execute(sql,self.__OPT_PARAM)
else:
result = self.__DB_CUR.execute(sql)
self.__DB_CONN.commit()
self.__close()
return result
except Exception as ex:
self._ex = ex
return ex
@@ -310,14 +337,18 @@ class panelMysql:
#执行SQL语句返回数据集
if not self.__GetConn(): return self.__DB_ERR
try:
self.__OPT_PARAM = list(self.__to_tuple(param))
self.__DB_CUR.execute(sql,self.__OPT_PARAM)
if param:
self.__OPT_PARAM = list(self.__to_tuple(param))
self.__DB_CUR.execute(sql,self.__OPT_PARAM)
else:
self.__DB_CUR.execute(sql)
result = self.__DB_CUR.fetchall()
#将元组转换成列表
data = list(map(list,result))
if is_close: self.__Close()
return data
except Exception as ex:
self._ex = ex
return ex
+6 -3
View File
@@ -52,9 +52,11 @@ class FileExecuteDeny:
result = []
for i in deny_name:
reg = '#BEGIN_DENY_{}\n\s*location\s*\~\*\s*\^(.*)\.\*.*\((.*)\)\$'.format(i.replace("|","\|"))
deny_directory = re.search(reg,conf).groups()[0]
deny_suffix = re.search(reg,conf).groups()[1]
result.append({'name':i,'dir':deny_directory,'suffix':deny_suffix})
re_tmp = re.search(reg,conf)
if re_tmp:
deny_directory = re_tmp.groups()[0]
deny_suffix = re_tmp.groups()[1]
result.append({'name':i,'dir':deny_directory,'suffix':deny_suffix})
return result
def _get_apache_file_deny(self):
@@ -113,6 +115,7 @@ class FileExecuteDeny:
if tmp:
return tmp
deny_name = args.deny_name
if not re.match(r"^\w+$",deny_name): return public.return_msg_gettext(False,'The rule name can only be composed of letters, numbers, and underscores!')
dir = args.dir
suffix = args.suffix
website = args.website
+381 -167
View File
@@ -30,6 +30,7 @@ class files:
download_list = None
download_is_rm = None
recycle_list = []
download_token_list = None
# 检查敏感目录
def CheckDir(self, path):
@@ -266,7 +267,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
args.f_name = args.f_name.encode('utf-8')
args.f_path = args.f_path.encode('utf-8')
try:
if self.get_real_len(args.f_name) > 128: return public.return_msg_gettext(False,'The file name contains more than 128 bytes')
if self.get_real_len(args.f_name) > 256: return public.return_msg_gettext(False,'The file name contains more than 256 bytes')
except:
pass
if not self.f_name_check(args.f_name): return public.return_msg_gettext(False,'No special characters can be included in the file name!')
@@ -328,7 +329,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
if new_name.find('.user.ini') != -1:
public.ExecShell("chattr +i " + new_name)
public.write_log_gettext('File manager', 'Successfully uploaded!',
public.write_log_gettext('File manager', 'Successfully uploaded [ {} ] !',(new_name,),
(args.f_name, args.f_path))
return public.return_msg_gettext(True, 'Successfully uploaded!')
@@ -355,14 +356,43 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
return '1'
return '0'
def __check_share(self,filename):
my_table = 'download_token'
result = public.M(my_table).where('filename=?',(filename,)).getField('id')
if result:
return str(result)
def __get_topping_data(self):
"""
@获取置顶配置
"""
data = {}
conf_file = '{}/data/toping.json'.format(public.get_panel_path())
try :
if os.path.exists(conf_file):
data = json.loads(public.readFile(conf_file))
except:pass
return data
def __check_topping(self,filepath,top_info):
"""
@name 检测文件或者目录是否置顶
@param filepath: 文件路径
"""
if filepath in top_info:
return '1'
import html
filepath = html.unescape(filepath)
if filepath in top_info:
return '1'
return '0'
def __check_share(self,filename):
if self.download_token_list == None:
self.download_token_list = {}
my_table = 'download_token'
download_list = public.M(my_table).field('id,filename').select()
for k in download_list:
self.download_token_list[k['filename']] = k['id']
return str(self.download_token_list.get(filename,'0'))
def __filename_flater(self,filename):
ms = {";":""}
for m in ms.keys():
@@ -402,6 +432,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
search = None
if hasattr(get, 'search'):
search = get.search.strip().lower()
public.set_search_history('files','get_list',search)
if hasattr(get, 'all'):
return self.SearchFiles(get)
@@ -411,7 +442,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
page = page.Page()
info = {}
info['count'] = self.GetFilesCount(get.path, search)
info['row'] = 100
info['row'] = 500
if 'disk' in get:
if get.disk == 'true': info['row'] = 2000
if 'share' in get and get.share:
@@ -437,101 +468,74 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
i = 0
n = 0
top_data = self.__get_topping_data()
data['STORE'] = self.get_files_store(None)
data['FILE_RECYCLE'] = os.path.exists('data/recycle_bin.pl')
if not hasattr(get, 'reverse'):
for filename in os.listdir(get.path):
filename = self.xssencode(filename)
if search:
if filename.lower().find(search) == -1:
continue
i += 1
if n >= page.ROW:
break
if i < page.SHIFT:
if not hasattr(get, 'reverse'): get.reverse = 'False'
if not hasattr(get, 'sort'): get.sort = 'name'
reverse = bool(get.reverse)
if get.reverse == 'False':
reverse = False
for file_info in self.__list_dir(get.path, get.sort, reverse):
filename = os.path.join(get.path, file_info[0])
if search:
if file_info[0].lower().find(search) == -1:
continue
i += 1
if n >= page.ROW:
break
if i < page.SHIFT:
continue
if not os.path.exists(filename) and not os.path.islink(filename): continue
file_info = self.__format_stat(filename, get.path)
if not file_info: continue
favorite = self.__check_favorite(filename, data['STORE'])
r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(file_info['mtime']) + ';' + str(
file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link']+';'\
+ self.get_download_id(filename) + ';' + self.is_composer_json(filename)+';'\
+ favorite+';'+self.__check_share(filename)
if os.path.isdir(filename):
dirnames.append(r_file)
else:
filenames.append(r_file)
n += 1
try:
if sys.version_info[0] == 2:
filename = filename.encode('utf-8')
else:
filename.encode('utf-8')
filePath = get.path+'/'+filename
link = ''
if os.path.islink(filePath):
filePath = os.readlink(filePath)
link = ' -> ' + filePath
if not os.path.exists(filePath):
filePath = get.path + '/' + filePath
if not os.path.exists(filePath):
continue
stat = os.stat(filePath)
accept = str(oct(stat.st_mode)[-3:])
mtime = str(int(stat.st_mtime))
user = ''
try:
user = pwd.getpwuid(stat.st_uid).pw_name
except:
user = str(stat.st_uid)
size = str(stat.st_size)
# 判断文件是否已经被收藏
favorite = self.__check_favorite(filePath,data['STORE'])
if os.path.isdir(filePath):
dirnames.append(self.__filename_flater(filename)+';'+size+';' + mtime+';'+accept+';'+user+';'+link + ';' +
self.get_download_id(filePath)+';'+ self.is_composer_json(filePath)+';'
+favorite+';'+self.__check_share(filePath))
else:
filenames.append(self.__filename_flater(filename)+';'+size+';'+mtime+';'+accept+';'+user+';'+link+';'
+self.get_download_id(filePath)+';' + self.is_composer_json(filePath)+';'
+favorite+';'+self.__check_share(filePath))
n += 1
except:
continue
data['DIR'] = sorted(dirnames)
data['FILES'] = sorted(filenames)
else:
reverse = bool(get.reverse)
if get.reverse == 'False':
reverse = False
for file_info in self.__list_dir(get.path, get.sort, reverse):
filename = os.path.join(get.path, file_info[0])
if search:
if file_info[0].lower().find(search) == -1:
continue
i += 1
if n >= page.ROW:
break
if i < page.SHIFT:
continue
if not os.path.exists(filename): continue
file_info = self.__format_stat(filename, get.path)
if not file_info: continue
favorite = self.__check_favorite(filename, data['STORE'])
r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(file_info['mtime']) + ';' + str(
file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link']+';'\
+ self.get_download_id(filename) + ';' + self.is_composer_json(filename)+';'\
+ favorite+';'+self.__check_share(filename)
if os.path.isdir(filename):
dirnames.append(r_file)
else:
filenames.append(r_file)
n += 1
data['DIR'] = dirnames
data['FILES'] = filenames
data['DIR'] = dirnames
data['FILES'] = filenames
data['PATH'] = str(get.path)
for i in range(len(data['DIR'])):
data['DIR'][i] += ';' + self.get_file_ps( os.path.join(data['PATH'] , data['DIR'][i].split(';')[0]))
#2022-07-29,增加置顶排序
tmp_dirs = []
for i in range(len(data['DIR'])):
filepath = os.path.join(data['PATH'] , data['DIR'][i].split(';')[0])
toping = self.__check_topping(filepath,top_data)
info = data['DIR'][i] + ';' + self.get_file_ps(filepath)+';'+toping
if toping == '1':
tmp_dirs.insert(0, info)
else:
tmp_dirs.append(info)
tmp_files = []
for i in range(len(data['FILES'])):
data['FILES'][i] += ';' + self.get_file_ps( os.path.join(data['PATH'] , data['FILES'][i].split(';')[0]))
filepath = os.path.join(data['PATH'] , data['FILES'][i].split(';')[0])
toping = self.__check_topping(filepath,top_data)
info = data['FILES'][i] + ';' + self.get_file_ps(filepath)+';'+toping
if toping == '1':
tmp_files.insert(0, info)
else:
tmp_files.append(info)
data['DIR'] = tmp_dirs
data['FILES'] = tmp_files
if hasattr(get, 'disk'):
import system
data['DISK'] = system.system().GetDiskInfo()
data['dir_history'] = public.get_dir_history('files','GetDirList')
data['search_history'] = public.get_search_history('files','get_list')
public.set_dir_history('files','GetDirList',data['PATH'])
return data
@@ -630,8 +634,9 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
public.writeFile(f_key,ps_body)
public.write_log_gettext('File manager','Set the file name [{}], notes: {}',(f_name,ps_body))
else:
if os.path.exists(f_key):os.remove(f_key)
public.write_log_gettext('File manager','Clear file notes [{}]',(f_name))
if os.path.exists(f_key):
os.remove(f_key)
public.write_log_gettext('File manager','Clear file notes [{}]',(f_name))
return public.return_msg_gettext(True,'Setup successfully!')
@@ -669,19 +674,19 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
filename = "/".join((path,f_name))
sort_key = 1
sort_val = None
#此处直接做异常处理比先判断文件是否存在更高效
if my_sort == 'name':
sort_key = 0
elif my_sort == 'size':
sort_val = os.stat(filename).st_size
elif my_sort == 'mtime':
sort_val = os.stat(filename).st_mtime
elif my_sort == 'accept':
sort_val = os.stat(filename).st_mode
elif my_sort == 'user':
sort_val = os.stat(filename).st_uid
except:
if not os.path.islink(filename):
#此处直接做异常处理比先判断文件是否存在更高效
if my_sort == 'name':
sort_key = 0
elif my_sort == 'size':
sort_val = os.stat(filename).st_size
elif my_sort == 'mtime':
sort_val = os.stat(filename).st_mtime
elif my_sort == 'accept':
sort_val = os.stat(filename).st_mode
elif my_sort == 'user':
sort_val = os.stat(filename).st_uid
except Exception as err:
continue
#使用list[tuple]排序效率更高
tmp_files.append((f_name,sort_val))
@@ -748,14 +753,20 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
return data
def __get_stat(self, filename, path=None):
stat = os.stat(filename)
accept = str(oct(stat.st_mode)[-3:])
mtime = str(int(stat.st_mtime))
user = ''
try:
user = pwd.getpwuid(stat.st_uid).pw_name
except:
user = str(stat.st_uid)
if os.path.islink(filename) and not os.path.exists(filename):
accept = "0"
mtime = "0"
user = "0"
size = "0"
else:
stat = os.stat(filename)
accept = str(oct(stat.st_mode)[-3:])
mtime = str(int(stat.st_mtime))
user = ''
try:
user = pwd.getpwuid(stat.st_uid).pw_name
except:
user = str(stat.st_uid)
size = str(stat.st_size)
link = ''
down_url = self.get_download_id(filename)
@@ -805,6 +816,17 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
# 创建文件
def CreateFile(self, get):
# 校验磁盘大小
df_data = public.ExecShell("df -T | grep '/'")[0]
for data in str(df_data).split("\n"):
data_list = data.split()
if not data_list: continue
use_size = data_list[4]
size = data_list[5]
disk_path = data_list[6]
if int(use_size) < 1024 and str(size).rstrip("%") == "100" and disk_path in ["/","/www"]:
return public.return_msg_gettext(False, f"File creation failed! The disk is full! please clear the space first!")
if sys.version_info[0] == 2:
get.path = get.path.encode('utf-8').strip()
try:
@@ -822,7 +844,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
os.makedirs(path)
open(get.path, 'w+').close()
self.SetFileAccept(get.path)
public.write_log_gettext('TYPE_FILE', 'Successfully created file [{}]!', (get.path,))
public.write_log_gettext('File manager', 'Successfully created file [{}]!', (get.path,))
return public.return_msg_gettext(True, 'Successfully created file!')
except:
return public.return_msg_gettext(False, 'Failed to create file!')
@@ -867,7 +889,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
return public.return_msg_gettext(False, 'Requested directory exists!')
os.makedirs(get.path)
self.SetFileAccept(get.path)
public.write_log_gettext('File manager', 'Successfully created directory!', (get.path,))
public.write_log_gettext('File manager', 'Successfully created directory [ {} ]!', (get.path,))
return public.return_msg_gettext(True, 'Successfully created directory!')
except:
return public.return_msg_gettext(False,'Failed to create directory!')
@@ -898,15 +920,18 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
if self.Mv_Recycle_bin(get):
self.site_path_safe(get)
self.remove_file_ps(get)
public.add_security_logs("Del dir","Delete directory: "+get.path)
return public.return_msg_gettext(True, 'Directory moved to recycle bin!')
import shutil
shutil.rmtree(get.path)
self.site_path_safe(get)
public.add_security_logs("Del dir", "Delete directory: " + get.path)
public.WriteLog('TYPE_FILE', 'Successfully deleted directory [{}]!', (get.path,))
self.remove_file_ps(get)
return public.return_msg_gettext(True, ' Successfully deleted directory!')
except:
except Exception as e:
public.print_log("DeleteDir error info :{}".format(e))
return public.return_msg_gettext(False, 'Failed to delete directory!')
# 删除 空目录
@@ -921,7 +946,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
def DeleteFile(self, get):
if sys.version_info[0] == 2:
get.path = get.path.encode('utf-8')
if not os.path.exists(get.path):
if not os.path.exists(get.path)and not os.path.islink(get.path):
return public.return_msg_gettext(False, 'Configuration file not exist')
# 检查是否为.user.ini
@@ -932,10 +957,12 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
if self.Mv_Recycle_bin(get):
self.site_path_safe(get)
self.remove_file_ps(get)
public.add_security_logs("Del file", "Delete file: " + get.path)
return public.return_msg_gettext(True, 'File moved to recycle bin!')
os.remove(get.path)
self.site_path_safe(get)
public.WriteLog('TYPE_FILE', 'Successfully deleted file [{}]!', (get.path,))
public.write_log_gettext('File manager', 'Successfully permanent deleted file: [{}]!', (get.path,))
public.add_security_logs("Del file", "Delete file: " + get.path)
self.remove_file_ps(get)
return public.return_msg_gettext(True, 'Successfully deleted file!')
except:
@@ -969,7 +996,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
def Re_Recycle_bin(self, get):
if sys.version_info[0] == 2:
get.path = get.path.encode('utf-8')
get.path = public.html_decode(get.path).replace(';','')
dFile = get.path.replace('_bt_', '/').split('_t_')[0]
# 检查所在回收站目录
@@ -1009,7 +1036,6 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
for rPath in recycle_bin_list:
if not os.path.exists(rPath): continue
for file in os.listdir(rPath):
file = self.xssencode(file)
try:
tmp = {}
fname = os.path.join(rPath , file)
@@ -1019,6 +1045,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
fname.encode('utf-8')
tmp1 = file.split('_bt_')
tmp2 = tmp1[len(tmp1)-1].split('_t_')
file = self.xssencode(file)
tmp['rname'] = file
tmp['dname'] = file.replace('_bt_', '/').split('_t_')[0]
if tmp['dname'].find('@') != -1:
@@ -1051,6 +1078,8 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
if sys.version_info[0] == 2:
get.path = get.path.encode('utf-8')
get.path = public.html_decode(get.path).replace(';','')
dFile = get.path.split('_t_')[0]
# 检查所在回收站目录
recycle_bin_list = public.get_recycle_bin_list()
@@ -1284,8 +1313,8 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
public.writeFile(get.path,'')
if self.__get_ext(get.path) in ['gz','zip','rar','exe','db','pdf','doc','xls','docx','xlsx','ppt','pptx','7z','bz2','png','gif','jpg','jpeg','bmp','icon','ico','pyc','class','so','pyd']:
return public.return_msg_gettext(False,'The file format does not support online editing!')
if os.path.getsize(get.path) > 3145928:
return public.return_msg_gettext(False,'Cannot edit files larger than 2MB online!')
# if os.path.getsize(get.path) > 3145928:
# return public.return_msg_gettext(False,'Cannot edit files larger than 2MB online!')
if os.path.isdir(get.path):
return public.return_msg_gettext(False,'Writing verification file failed: {}')
@@ -1297,11 +1326,19 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
if os.path.exists(mycnf_file_bak):
public.writeFile(myconf_file, public.readFile(mycnf_file_bak))
fp = open(get.path,'rb')
data = {}
data['status'] = True
try:
data["only_read"] = False
data["size"] = os.path.getsize(get.path)
if data["size"] > 3145928:
try:
info_data=self.last_lines(get.path, 10000)
if info_data=="":return public.return_msg_gettext(False, u'The file encoding is not compatible, the file cannot be read correctly!')
data["data"]=info_data
data["only_read"]=True
except:return public.return_msg_gettext(False, u'The file encoding is not compatible, the file cannot be read correctly!')
else:
fp = open(get.path, 'rb')
if fp:
srcBody = fp.read()
fp.close()
@@ -1326,11 +1363,35 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
data['auto_save'] = self.get_auto_save(get.path)
data['st_mtime'] = str(int(os.stat(get.path).st_mtime))
return data
except Exception as ex:
return public.return_msg_gettext(False,'The file cannot be read correctly due to incompatible file encoding!{}',(str(ex)),)
#保存文件
def SaveFileBody(self,get):
def last_lines(self,filename, lines=1):
block_size = 3145928
block = ''
nl_count = 0
start = 0
fsock = open(filename, 'rU')
try:
fsock.seek(0, 2)
curpos = fsock.tell()
while (curpos > 0):
curpos -= (block_size + len(block))
if curpos < 0: curpos = 0
fsock.seek(curpos)
try:
block = fsock.read()
except:
continue
nl_count = block.count('\n')
if nl_count >= lines: break
for n in range(nl_count - lines + 1):
start = block.find('\n', start) + 1
finally:
fsock.close()
return block[start:]
# 保存文件
def SaveFileBody(self, get):
if not 'path' in get:
return public.return_msg_gettext(False,'[path] parameter cannot be empty!')
if sys.version_info[0] == 2:
@@ -1346,7 +1407,8 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
if not os.path.exists(get.path):
if get.path.find('.htaccess') == -1:
return public.return_msg_gettext(False, 'Configuration file not exist')
elif os.path.getsize(get.path) > 3145928:
return public.returnMsg(False, 'Files larger than 3MB cannot be edited online!')
nginx_conf_path = public.get_vhost_path() + '/nginx/'
if get.path.find(nginx_conf_path) != -1:
if get.data.find('#SSL-START') != -1 and get.data.find('#SSL-END') != -1:
@@ -1406,7 +1468,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
fp = open(get.path, 'w+', encoding=get.encoding)
except:
fp = open(get.path, 'w+')
data = self.crlf_to_lf(data, get.path)
fp.write(data)
fp.close()
@@ -1421,10 +1483,48 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
public.ExecShell('chattr +i ' + get.path)
public.write_log_gettext('File manager', 'Successfully saved file [{}]!', (get.path,))
return public.return_msg_gettext(True, 'Saved!')
data = public.return_msg_gettext(True, 'Saved!')
data['historys'] = self.get_history(get.path) # 获取历史记录
data['st_mtime'] = str(int(os.stat(get.path).st_mtime))
return data
except Exception as ex:
return public.return_msg_gettext(False, 'Save ERROR! {}' + str(ex))
def crlf_to_lf(self,data,filename):
'''
@name 将CRLF转换为LF
@author hwliang
@param data 要转换的数据
@param filename 文件名
@return string
'''
file_ext_name = os.path.splitext(filename)[-1]
if not file_ext_name:
if data.find('#!/bin/bash') == 0 or data.find('#!/bin/sh') == 0:
file_ext_name = '.sh'
elif data.find('#!/usr/bin/python') == 0 or data.find('import ') != -1:
file_ext_name = '.py'
elif data.find('#!/usr/bin/env node') == 0:
file_ext_name = '.js'
elif data.find('#!/usr/bin/env php') == 0 or data.find('<?php') != -1:
file_ext_name = '.php'
elif data.find('#!/usr/bin/env ruby') == 0:
file_ext_name = '.rb'
elif data.find('#!/usr/bin/env perl') == 0:
file_ext_name = '.pl'
elif data.find('#!/usr/bin/env lua') == 0 or data.find('require ') != -1:
file_ext_name = '.lua'
elif filename.find('/script/') != -1:
file_ext_name = '.sh'
elif filename.find('.') == -1:
file_ext_name = '.sh'
if not file_ext_name in ['.sh','.py','.pl','.php','.js','.css','.html','.htm','.shtml','.shtm','.jsp','.asp','.aspx','.txt']:
return data
if data.find('\r\n') == -1 or data.find('\r') == -1:
return data
return data.replace('\r\n','\n').replace('\r','\n')
# 保存历史副本
def save_history(self, filename):
if os.path.exists(public.get_panel_path()+'/data/not_file_history.pl'):
@@ -1471,7 +1571,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
filename).replace('//', '/')
if not os.path.exists(save_path):
return []
return sorted(os.listdir(save_path))
return sorted(os.listdir(save_path),reverse=True)
except:
return []
@@ -1524,34 +1624,104 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
except:
return None
def is_max_size(self,path,max_size,max_num=10000,total_size=0,total_num=0):
'''
@name 是否超过最大大小
@path 文件路径
@max_size 最大大小
@max_num 最大文件数量
@return bool
'''
if not os.path.exists(path) or not max_size:
return False,total_size,total_num
# 是否为文件?
if os.path.isfile(path):
total_size = os.path.getsize(path)
total_num = 1
if total_size > max_size:
return True,total_size,total_num
return False,total_size,total_num
# 是否为目录?
for root, dirs, files in os.walk(path, topdown=True):
total_num += len(files)
total_num += len(dirs)
# 判断是否超过最大文件数量
if total_num > max_num:
return True,total_size,total_num
for f in files:
filename = os.path.normcase(root+os.path.sep+f)
if not os.path.exists(filename): continue
if os.path.islink(filename): continue
total_size += os.path.getsize(filename)
# 判断是否超过最大大小
if total_size > max_size:
return True,total_size,total_num
return False,total_size,total_num
# 文件压缩
def Zip(self, get):
if not 'z_type' in get:
get.z_type = 'rar'
if get.z_type == 'rar':
if os.uname().machine == 'aarch64':
if os.uname().machine != 'x86_64':
return public.return_msg_gettext(False,'RAR component does not support aarch 64 platform')
import panelTask
task_obj = panelTask.bt_task()
task_obj.create_task(public.get_msg_gettext('Decompress the file'),3,get.path,json.dumps({"sfile":get.sfile,"dfile":get.dfile,"z_type":get.z_type}))
public.write_log_gettext("File manager", 'Successfully compressed file [{}] to [{}]!',(get.sfile,get.dfile))
return public.return_msg_gettext(True,'Compression task added to the message queue!')
max_size = 1024*1024*100
max_num = 10000
total_size = 0
total_num = 0
status = True
if not os.path.exists(os.path.dirname(get.dfile)):
os.makedirs(os.path.dirname(get.dfile))
for file_name in get.sfile.split(','):
path = os.path.join(get.path,file_name)
status,total_size,total_num = self.is_max_size(path,max_size,max_num,total_size,total_num)
if not status: break
# 如果被压缩目标小于100MB或文件数量少于1W个,则直接在主线程压缩
if not status:
return task_obj._zip(get.path,get.sfile,get.dfile,'/tmp/zip.log',get.z_type)
# 否则在后台线程压缩
task_obj.create_task('压缩文件', 3, get.path, json.dumps(
{"sfile": get.sfile, "dfile": get.dfile, "z_type": get.z_type}))
public.WriteLog("TYPE_FILE", 'ZIP_SUCCESS', (get.sfile, get.dfile))
return public.returnMsg(True, '已将压缩任务添加到消息队列!')
# 文件解压
def UnZip(self, get):
if get.sfile[-4:] == '.rar':
if os.uname().machine != 'x86_64':
return public.return_msg_gettext(False,'RAR component does not support aarch 64 platform')
import panelTask
if not 'password' in get:
get.password = ''
if not os.path.exists(get.sfile):
return public.returnMsg(False, 'The specified archive does not exist!')
if not os.path.exists(get.dfile):
os.makedirs(get.dfile)
zip_size = os.path.getsize(get.sfile)
task_obj = panelTask.bt_task()
task_obj.create_task(public.get_msg_gettext('Decompress the file'),2,get.sfile,json.dumps({"dfile":get.dfile,"password":get.password}))
if zip_size < 1024 * 1024 * 50:
return task_obj._unzip(get.sfile, get.dfile, get.password,"/tmp/unzip.log")
task_obj.create_task(public.get_msg_gettext('Decompress the file'), 2, get.sfile,
json.dumps({"dfile": get.dfile, "password": get.password}))
public.write_log_gettext("File manager", 'Successfully uncompressed file from [{}] to [{}]!',(get.sfile,get.dfile))
return public.return_msg_gettext(True,'Decompression task added to the message queue!')
#获取文件/目录 权限信息
def GetFileAccess(self,get):
# 获取文件/目录 权限信息
def GetFileAccess(self, get):
if sys.version_info[0] == 2:
get.filename = get.filename.encode('utf-8')
data = {}
@@ -1723,7 +1893,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
sfile = session['selected']['path'] + '/' + key
dfile = get.path + '/' + key
if dfile.find(sfile) == 0:
if os.path.commonpath([dfile, sfile]) == sfile:
return public.return_msg_gettext(False,'Wrong copy logic, from {} copy to {} has an inclusive relationship, there is an infinite loop copy risk!'.format(sfile,dfile))
for key in myfiles:
@@ -1731,7 +1901,8 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
public.writeSpeed(key, i, l)
try:
if sys.version_info[0] == 2:
sfile = session['selected']['path'] + '/' + key.encode('utf-8')
sfile = session['selected']['path'] + \
'/' + key.encode('utf-8')
dfile = get.path + '/' + key.encode('utf-8')
else:
sfile = session['selected']['path'] + '/' + key
@@ -2202,33 +2373,30 @@ cd %s
'id,filename,token,expire,ps,total,password,addtime').limit(data['shift'] + ',' + data['row']).select()
return data
# 获取短列表
#获取短列表
def get_download_list(self):
if self.download_list: return self.download_list
my_table = 'download_token'
data = public.M(my_table).field('id,filename,expire').select()
self.download_list = data
return data
# 获取id
def get_download_id(self, filename):
download_list = self.get_download_list()
if self.download_list != None: return self.download_list
my_table = 'download_token'
self.download_list = public.M(my_table).field('id,filename,expire').select()
if self.download_token_list == None: self.download_token_list = {}
m_time = time.time()
result = '0'
for d in download_list:
if filename == d['filename']:
result = str(d['id'])
break
# 清理过期和无效
for d in self.download_list:
#清理过期和无效
if self.download_is_rm: continue
if not os.path.exists(d['filename']) or m_time > d['expire']:
public.M(my_table).where('id=?', (d['id'],)).delete()
# 标记清理
public.M(my_table).where('id=?',(d['id'],)).delete()
continue
self.download_token_list[d['filename']] = d['id']
#标记清理
if not self.download_is_rm:
self.download_is_rm = True
return result
#获取id
def get_download_id(self,filename):
self.get_download_list()
return str(self.download_token_list.get(filename,'0'))
# 获取指定下载地址
def get_download_url_find(self, get):
@@ -2282,6 +2450,9 @@ cd %s
"password":str(get.password), #提取密码
"addtime": mtime #添加时间
}
exts = os.path.basename(get.filename).split('.')
if len(exts) > 1:
pdata['token'] += "." + exts[-1]
if len(pdata['password']) < 4 and len(pdata['password']) > 0:
return public.return_msg_gettext(False,' Please do not enter the following special characters [ ~ ` / = ]')
if not re.match('^\w+$',pdata['password']) and pdata['password']:
@@ -2376,6 +2547,7 @@ cd %s
return public.return_msg_gettext(False,'The composer.json configuration file was not found in the specified directory!')
log_file = '/tmp/composer.log'
user = ''
# del_cache = self._composer_user_home()
if 'user' in get:
user = 'sudo -u {} '.format(get.user)
if not os.path.exists('/usr/bin/sudo'):
@@ -2384,6 +2556,7 @@ cd %s
else:
public.ExecShell("yum install sudo -y > {}".format(log_file))
public.ExecShell("mkdir -p /home/www && chown -R www:www /home/www")
# del_cache = self._composer_user_home()
#设置指定源
if 'repo' in get:
@@ -2404,6 +2577,7 @@ cd %s
if os.path.exists(log_file): os.remove(log_file)
public.ExecShell("cd {} && export COMPOSER_HOME=/tmp && {} nohup {} &> {} && echo 'BT-Exec-Completed' >> {} && rm -rf /home/www &".format(get.path,user,composer_exec_str,log_file,log_file))
public.write_log_gettext('Composer',"Execute composer [{}] in the directory: [{}]",(get.path,get.composer_args))
# del_cache()
return public.return_msg_gettext(True,'Command has been sent!')
# 取composer版本
@@ -2489,8 +2663,8 @@ cd %s
pdata['st_mtime'] = int(f)
pdata['st_size'] = f_stat.st_size
pdata['history_file'] = f_name
result.append(pdata)
return result
result.insert(0,pdata)
return sorted(result,key=lambda x:x['st_mtime'],reverse=True)
except:
return []
@@ -2707,6 +2881,46 @@ cd %s
return public.return_data(True,data)
def set_rsync_data(self,data):
'''
@name 写入rsync配置数据
@author cjx
@param data<dict> 配置数据
@return bool
'''
public.writeFile('{}/data/file_rsync.json'.format(public.get_panel_path()),json.dumps(data))
return True
def get_rsync_data(self):
'''
@name 获取文件同步配置
@author cjx
@return dict
'''
data = {}
path = '{}/data/file_rsync.json'.format(public.get_panel_path())
try:
if os.path.exists(path):
data = json.loads(public.readFile(path))
except :
data = {}
return data
def add_files_rsync(self,get):
'''
@name 添加数据同步标记
@author cjx
'''
path = get.path
s_type = get.s_type
data = self.get_rsync_data()
if not path in data: data[path] = {}
data[path][s_type] = 1
self.set_rsync_data(data)
return public.return_msg_gettext(True,'Added successfully!')
# 数据库对象
def _get_sqlite_connect(self):
try:
+104
View File
@@ -0,0 +1,104 @@
#coding: utf-8
import public,os
class filesBase:
__upload_objs = ['bos','alioss','obs','upyun','txcos'] #支持下载的云存储
__down_objs = ['bos','alioss','txcos','obs'] #支持上传的云存储
def __init__(self):
pass
#************************ start 对象存储 ************************
def get_base_objects(self,objs):
"""
@name 获取可上传的对象存储
"""
import panelPlugin
plu_obj = panelPlugin.panelPlugin()
res = []
for name in objs:
is_conf = 0
info = plu_obj.get_soft_find(name)
if not info: continue
if info['setup']:
is_conf = self._check_objects_conf(info['name'])
res.append({'name':info['name'],'title':info['title'],'setup':info['setup'],'is_conf':is_conf})
return res
def get_all_objects(self,get):
"""
@name 获取可上传的对象存储
"""
result = {}
result['upload'] = []
result['down'] = self.get_base_objects(self.__down_objs)
for info in result['down']:
if info['name'] in self.__upload_objs:
result['upload'].append(info)
return result
def get_upload_objects(self,get):
"""
@name 获取可上传的对象存储
"""
return self.get_base_objects(self.__upload_objs)
def get_down_objects(self,get):
"""
@name 获取可下载的对象存储
"""
return self.get_base_objects(self.__down_objs)
def _check_objects_conf(self,plu_name):
"""
@name 获取插件是否配置
"""
plugin_obj = self.get_plugin_main_object(plu_name)
args = public.dict_obj()
args.path = '/bt_upload/'
res = plugin_obj.get_config(args)
if 'status' in res and not res['status']:
return 0
for key in res:
if not res[key].strip(): return 0
return 1
def get_plugin_main_object(self,plugin_name):
"""
@name 获取插件主对象
@param plugin_name 插件名称
"""
sys_path = '{}/plugin/{}'.format(public.get_panel_path(),plugin_name)
if not os.path.exists(sys_path): return False
public.sys_path_append(sys_path)
os_file = '{}/{}_main.py'.format(sys_path,plugin_name)
plugin_obj = __import__(plugin_name + '_main')
plugin_obj = getattr(plugin_obj, plugin_name + '_main')()
return plugin_obj
def get_soft_find(self,name):
"""
@获取插件详细
"""
import panelPlugin
plu_obj = panelPlugin.panelPlugin()
return plu_obj.get_soft_find(name)
#************************ end 对象存储 ************************
+46
View File
@@ -0,0 +1,46 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
# 上传文件至oss
#------------------------------
from filesModel.base import filesBase
import public
class main(filesBase):
def __init__(self):
pass
def get_oss_objects(self,get):
"""
@name 获取可上传的对象存储
"""
return self.get_all_objects(get)
def download_file(self,get):
"""
@name 下载文件
@param get
file:文件路径
"""
info = self.get_soft_find(get.name)
if not info['setup']:
return public.returnMsg(False,'未安装[{}]插件'.format(info['title']))
import panelTask
task_obj = panelTask.bt_task()
task_obj.create_task('下载文件', 1, get.url, get.path + '/' + get.filename)
public.set_module_logs('files_down_to_file', 'download_file', 1)
public.WriteLog('TYPE_FILE', '从 [{}] 下载文件 [{}] 到 {}'.format(info['title'],get.filename,get.path))
return public.returnMsg(True, 'FILE_DOANLOAD')
+342
View File
@@ -0,0 +1,342 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,json
import tarfile,shutil,gzip
class main(filesBase):
def __init__(self):
pass
def __check_zipfile(self,sfile,is_close = False):
'''
@name 检查文件是否为zip文件
@param sfile 文件路径
@return bool
'''
pass
def get_zip_files(self,args):
'''
@name 获取压缩包内文件列表
@param args['path'] 压缩包路径
@return list
'''
sfile = args.sfile
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not tarfile.is_tarfile(sfile):
return public.returnMsg(False,'Not a valid tar.gz archive file')
zip_file = tarfile.open(sfile)
data = {}
for item in zip_file.getmembers():
sub_data = data
f_name = self.__get_zip_filename(item)
f_dirs = f_name.split('/')
for d in f_dirs:
if not d: continue
if not d in sub_data:
if d == f_name[-len(d):]:
sub_data[d] = {
'file_size': item.size,
'filename':d,
'fullpath':f_name,
'date_time': public.format_date(times=item.mtime),
'is_dir': 0
}
if item.isdir():
sub_data[d]['is_dir'] = 1
else:
sub_data[d] = {}
sub_data = sub_data[d]
return data
def get_fileinfo_by(self,args):
'''
@name 获取压缩包内文件信息
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@return dict
'''
sfile = args.sfile
filename = args.filename
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
tmp_path = '{}/tmp/{}'.format(public.get_panel_path(),public.md5(sfile + filename))
result = {}
result['status'] = True
result['data'] = ''
with tarfile.open(sfile,'r') as zip_file:
try:
zip_file.extract(filename,tmp_path)
result['data'] = public.readFile('{}/{}'.format(tmp_path,filename))
except:pass
try:
public.rmdir(tmp_path)
except:pass
return result
def delete_zip_file(self,args):
'''
@name 删除压缩包内文件
@param args['path'] 压缩包路径
@param args['filenames'] 文件名列表数组格式
@return dict
'''
sfile = args.sfile
filenames = args.filenames
if not tarfile.is_tarfile(sfile):
return public.returnMsg(False,'Not a valid tar.gz archive file')
tmp_path = self.__unzip_tmp_path(sfile)
if not tmp_path: return public.returnMsg(False,'Failed edit!')
#组装原有的文件
s_list = []
src_list = {}
public.get_file_list(tmp_path,s_list)
for f in s_list:
if not os.path.isfile(f): continue
src_file = f.replace(tmp_path,'').strip('/')
if src_file in filenames:
continue
src_list[src_file] = f
with tarfile.open(sfile,'w') as new_zfile:
try:
for src_file in src_list:
new_zfile.add(src_list[src_file],src_file)
except:
shutil.rmtree(tmp_path, True)
return public.returnMsg(False,'Failed delete file,error:' + public.get_error_info())
shutil.rmtree(tmp_path, True)
return public.returnMsg(True,'Compressed package file modified successfully')
def write_zip_file(self,args):
'''
@name 写入压缩包内文件
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@param args['data'] 写入数据
@return dict
'''
sfile = args.sfile
filename = args.filename
data = args.data
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
tmp_path = self.__unzip_tmp_path(sfile)
if not tmp_path: return public.returnMsg(False,'Failed edit!')
public.writeFile('{}/{}'.format(tmp_path,filename),data)
#组装原有的文件
s_list = []
src_list = {}
public.get_file_list(tmp_path,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = f.replace(tmp_path,'').strip('/')
if src_file in src_list:
continue
src_list[src_file] = f
with tarfile.open(sfile,'w') as new_zfile:
try:
for src_file in src_list:
new_zfile.add(src_list[src_file],src_file)
except:
shutil.rmtree(tmp_path, True)
return public.returnMsg(False,'Failed modify file,error:' + public.get_error_info())
shutil.rmtree(tmp_path, True)
return public.returnMsg(True,'Compressed package file modified successfully')
def extract_byfiles(self,args):
"""
@name 解压部分文件
@param args['path'] 压缩包路径
@param args['extract_path'] 解压路径
@param args['filenames'] 文件名列表数组格式
"""
sfile = args.sfile
filenames = args.filenames
extract_path = args.extract_path
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not os.path.exists(extract_path):
os.makedirs(extract_path,384)
tmp_path = '{}/tmp/{}'.format(public.get_panel_path(),public.md5(public.GetRandomString(32)))
if not os.path.exists(tmp_path):
os.makedirs(tmp_path,384)
with tarfile.open(sfile) as zip_file:
try:
m_list = {}
f_infos = zip_file.getmembers()
for item in f_infos:
filename = self.__get_zip_filename(item)
if filename in filenames:
spath = os.path.join(tmp_path,filename).strip('/')
if item.isdir():
m_list[spath] = []
else:
if not 'other' in m_list:
m_list['other'] = []
dir_key = os.path.dirname(spath)
info = {'src':spath,'dst':'{}/{}'.format(extract_path, filename.strip('/'))}
if dir_key in m_list:
info['dst'] = '{}/{}'.format(extract_path,'/'.join(filename.split('/')[1:]))
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path,384)
m_list[dir_key].append(info)
else:
m_list['other'].append(info)
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path, 384)
zip_file.extract(filename.strip('/'),tmp_path)
for key in m_list:
try:
for info in m_list[key]:
if os.getenv('BT_PANEL'):
shutil.copyfile(info['src'],info['dst'])
else:
shutil.copyfile('/' + info['src'],'/' + info['dst'])
except:
pass
shutil.rmtree(tmp_path, True)
except:
return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info())
return public.returnMsg(True,'File was decompressed successfully')
def __unzip_tmp_path(self,sfile):
'''
@name 获取临时解压路径
@param sfile 压缩包路径
@return str
'''
tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32)))
with tarfile.open(sfile) as zip_file:
try:
zip_file.extractall(tmp_path)
except: return False
return tmp_path
def add_zip_file(self,args):
'''
@name 添加文件到压缩包
@param args['r_path'] 跟路径
@param args['filename'] 文件名
@param args['f_list'] 写入数据
@return dict
'''
sfile = args.sfile
r_path = args.r_path
f_list = args.f_list
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
tmp_path = self.__unzip_tmp_path(sfile)
if not tmp_path: return public.returnMsg(False,'Failed edit!')
#组装新添加的文件
src_list = {}
for fname in f_list:
if os.path.isdir(fname):
s_list = []
public.get_file_list(fname,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = '{}/{}{}'.format(r_path,os.path.basename(fname),f.replace(fname,'')).replace('//','/')
src_list[src_file] = f
else:
src_file = '{}/{}'.format(r_path, os.path.basename(fname)).replace('//','/')
src_list[src_file] = fname
#组装原有的文件
s_list = []
public.get_file_list(tmp_path,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = f.replace(tmp_path,'').strip('/')
if src_file in src_list:
continue
src_list[src_file] = f
with tarfile.open(sfile,'w') as new_zfile:
try:
for src_file in src_list:
new_zfile.add(src_list[src_file],src_file)
except:
shutil.rmtree(tmp_path, True)
return public.returnMsg(False,'Failed add file,error:' + public.get_error_info())
shutil.rmtree(tmp_path, True)
return public.returnMsg(True,'Compressed package file modified successfully')
def __get_zip_filename(self,item):
'''
@name 获取压缩包文件名
@param item 压缩包文件对象
@return string
'''
filename = item.name
try:
filename = item.name.encode('cp437').decode('gbk')
except:pass
if item.isdir():
filename += '/'
return filename
+289
View File
@@ -0,0 +1,289 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,files,json,time
from BTPanel import cache
class main(filesBase):
__objs = ['bos']
def __init__(self):
pass
def get_logs_info(self,get):
"""
@查看日志
@param get
limit:每页显示条数
file:日志文件
"""
p = 1
limit = 200
search = None
file = get.file
if 'limit' in get: limit = int(get.limit)
if 'p' in get: limit = int(get.p)
if 'search' in get: search = get.search
if not os.path.exists(file):
return public.returnMsg(False,'Please specify file!')
res = {}
res['status'] = True
res['data'] = self.GetNumLines(file,limit,p,search)
res['md5'] = public.md5(res['data'])
res['limit'] = limit
if not cache.get(file+'_logs_info'):
public.set_module_logs('files_get_logs_info','get_logs_info')
cache.set(file+'_logs_info','1',86400)
return res
def set_log_split(self,get):
"""
@name 文件切割
@param filename 文件路径
@param stype 切割类型 day:按天切割 size:按大小切割
@param size 切割大小stype=size必传
"""
filename = get.filename
stype = get.stype
limit = int(get.limit)
if not stype in ['day','size']:
return public.returnMsg(False,'Cut type passing error.')
if not os.path.exists(filename):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if limit < 3:
return public.returnMsg(False,'The number of reserved copies cannot be less than 3.')
data = {'type':stype,'limit':limit,'addtime':int(time.time())}
if stype == 'size':
size = int(get.size)
if size < 1024:
return public.returnMsg(False,'Cut size cannot be empty.')
data['size'] = size
public.set_split_logs(filename,1,data)
return public.returnMsg(True,'successfully set.')
def get_log_split(self,get):
"""
@name 获取文件切割信息
@param filename 文件路径
"""
data = {}
sfile = '{}/data/cutting_log.json'.format(public.get_panel_path())
if os.path.exists(sfile):
try:
data = json.loads(public.readFile(sfile))
except:pass
return data
def get_file_ext(self,filename):
"""
@name 获取文件扩展名
@param filename
"""
ss_exts = ['.tar.gz','.tar.bz2','.tar.bz']
for s in ss_exts:
e_len = len(s)
f_len = len(filename)
if f_len < e_len: continue
if filename[-e_len:] == s:
return filename[:-e_len] ,s
if filename.find('.') == -1: return filename,''
return os.path.splitext(filename)
def copy_file_to(self, get):
"""
@name 创建文件副本
@param get
@return
"""
sfile = get.sfile
if not os.path.exists(sfile):
return public.returnMsg(False, 'FILE_NOT_EXISTS')
spath,ext = sfile,''
if os.path.isfile(get.sfile):
spath,ext = self.get_file_ext(sfile)
# public.print_log(spath)
for x in range(1,1000):
dfile = '{} - copy ({}){}'.format(spath,x,ext)
if not os.path.exists(dfile):
break
get.dfile = dfile
f_obj = files.files()
if os.path.isdir(get.sfile):
public.WriteLog("File manager","Create copy of the directory [{}]".format(sfile))
return f_obj.CopyDir(get)
import shutil
try:
shutil.copyfile(get.sfile, get.dfile)
public.WriteLog('TYPE_FILE', 'FILE_COPY_SUCCESS',
(get.sfile, get.dfile))
try:
stat = os.stat(get.sfile)
os.chmod(get.dfile,stat.st_mode)
os.chown(get.dfile, stat.st_uid, stat.st_gid)
except:pass
public.WriteLog("File manager","Create copy of the file[{}]".format(sfile))
return public.returnMsg(True, 'FILE_COPY_SUCCESS')
except:
return public.returnMsg(False, 'FILE_COPY_ERR')
def set_topping_status(self,get):
"""
@name 设置文件或目录置顶
@param get
file:文件路径
type:置顶类型
"""
sfile = get.sfile
status = int(get.status)
if not os.path.exists(sfile):
import html
sfile = html.unescape(sfile)
if not os.path.exists(sfile):
return public.returnMsg(False, 'File or directory does not exist.')
data = {}
conf_file = '{}/data/toping.json'.format(public.get_panel_path())
try :
if os.path.exists(conf_file):
data = json.loads(public.readFile(conf_file))
except:pass
if sfile in data: del data[sfile]
if status:
data[sfile] = status
public.writeFile(conf_file, json.dumps(data))
public.set_module_logs('files_set_topping_status','set_topping_status')
public.WriteLog("File manager","Modify [{}] top status".format(sfile))
return public.returnMsg(True, 'Successful set.')
def GetNumLines(self,path, num, p=1,search = None):
"""
@name 取文件指定尾行数
@param path 文件路径
@param num 取尾行数
@param p 当前页
@param search 搜索关键字
@return list
"""
pyVersion = sys.version_info[0]
max_len = 1024 * 128
try:
from html import escape
if not os.path.exists(path): return ""
start_line = (p - 1) * num
count = start_line + num
fp = open(path, 'rb')
buf = ""
fp.seek(-1, 2)
if fp.read(1) == "\n": fp.seek(-1, 2)
data = []
total_len = 0
b = True
n = 0
for i in range(count):
while True:
newline_pos = str.rfind(str(buf), "\n")
pos = fp.tell()
if newline_pos != -1:
if n >= start_line:
line = buf[newline_pos + 1:]
is_res = True
if search:
is_res = False
if line.find(search) >= 0 or re.search(search,line):
is_res = True
if is_res:
line_len = len(line)
total_len += line_len
sp_len = total_len - max_len
if sp_len > 0:
line = line[sp_len:]
try:
data.insert(0, escape(line))
except:
pass
buf = buf[:newline_pos]
n += 1
break
else:
if pos == 0:
b = False
break
to_read = min(4096, pos)
fp.seek(-to_read, 1)
t_buf = fp.read(to_read)
if pyVersion == 3:
try:
if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8',errors='ignore')
except:
try:
if type(t_buf) == bytes: t_buf = t_buf.decode('gbk',errors='ignore')
except:
t_buf = str(t_buf)
buf = t_buf + buf
fp.seek(-to_read, 1)
if pos - to_read == 0:
buf = "\n" + buf
if total_len >= max_len: break
if not b: break
fp.close()
result = "\n".join(data)
if not result: raise Exception('null')
except:
result = ''
if len(result) > max_len:
result = result[-max_len:]
try:
try:
result = json.dumps(result)
return json.loads(result).strip()
except:
if pyVersion == 2:
result = result.decode('utf8', errors='ignore')
else:
result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore")
return result.strip()
except:
return ""
+243
View File
@@ -0,0 +1,243 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,json
import zipfile,shutil
try:
from unrar import rarfile
except:
os.system('btpip install unrar')
from unrar import rarfile
class main(filesBase):
def __init__(self):
pass
def __check_zipfile(self,sfile,is_close = False):
'''
@name 检查文件是否为zip文件
@param sfile 文件路径
@return bool
'''
zip_file = None
try:
zip_file = rarfile.RarFile(sfile)
except:pass
if is_close and zip_file:
zip_file.close()
return zip_file
def get_zip_files(self,args):
'''
@name 获取压缩包内文件列表
@param args['path'] 压缩包路径
@return list
'''
sfile = args.sfile
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
zip_file = self.__check_zipfile(sfile)
if not zip_file:
return public.returnMsg(False,'NOT_ZIP_FILE')
data = {}
for item in zip_file.infolist():
sub_data = data
f_name = self.__get_zip_filename(item)
f_dirs = f_name.split('/')
for d in f_dirs:
if not d: continue
if not d in sub_data:
if d == f_name[-len(d):]:
tmps = item.date_time
sub_data[d] = {
'file_size': item.file_size,
'compress_size': item.compress_size,
'filename':d,
'fullpath':f_name,
'date_time': public.to_date(times = '{}-{}-{} {}:{}:{}'.format(tmps[0],tmps[1],tmps[2],tmps[3],tmps[4],tmps[5])),
'is_dir': 0
}
if item.flag_bits == 32:
sub_data[d]['is_dir'] = 1
else:
sub_data[d] = {}
sub_data = sub_data[d]
return data
def get_fileinfo_by(self,args):
'''
@name 获取压缩包内文件信息
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@return dict
'''
sfile = args.sfile
filename = args.filename
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
result = {}
result['status'] = True
result['data'] = ''
with rarfile.RarFile(sfile,'r') as zip_file:
for item in zip_file.infolist():
z_filename = self.__get_zip_filename(item)
if z_filename == filename:
buff = zip_file.read(item.filename)
encoding,srcBody = public.decode_data(buff)
result['encoding'] = encoding
result['data'] = srcBody
break
return result
def delete_zip_file(self,args):
'''
@name 删除压缩包内文件
@param args['path'] 压缩包路径
@param args['filenames'] 文件名列表数组格式
@return dict
'''
sfile = args.sfile
filenames = args.filenames
return public.returnMsg(False,'RAR archive files do not support file deletion')
def write_zip_file(self,args):
'''
@name 写入压缩包内文件
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@param args['data'] 写入数据
@return dict
'''
sfile = args.sfile
filename = args.filename
data = args.data
return public.returnMsg(False,'RAR archive does not support this function!')
def extract_byfiles(self,args):
"""
@name 解压部分文件
@param args['path'] 压缩包路径
@param args['extract_path'] 解压路径
@param args['filenames'] 文件名列表数组格式
"""
sfile = args.sfile
filenames = args.filenames
extract_path = args.extract_path
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not os.path.exists(extract_path):
os.makedirs(extract_path,384)
tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32)))
if not os.path.exists(tmp_path):
os.makedirs(tmp_path,384)
with rarfile.RarFile(sfile) as zip_file:
try:
m_list = {}
f_infos = zip_file.infolist()
f_infos = sorted(f_infos,key=lambda x:x.filename)
for item in f_infos:
filename = self.__get_zip_filename(item)
if filename in filenames:
spath = os.path.join(tmp_path,filename).strip('/')
if item.flag_bits == 32:
m_list[spath] = []
else:
if not 'other' in m_list:
m_list['other'] = []
dir_key = os.path.dirname(spath)
info = {'src':spath,'dst':'{}/{}'.format(extract_path,os.path.basename(spath))}
if dir_key in m_list:
info['dst'] = '{}/{}'.format(extract_path,'/'.join(filename.split('/')[1:]))
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path,384)
m_list[dir_key].append(info)
else:
m_list['other'].append(info)
zip_file.extract(filename.strip('/').replace('/','\\'),tmp_path)
for key in m_list:
try:
# if key != 'other':
# dir_name = '{}/{}'.format(extract_path,os.path.basename(key))
# if not os.path.exists(dir_name): os.makedirs(dir_name,384)
for info in m_list[key]:
shutil.copyfile(info['src'],info['dst'])
except:pass
shutil.rmtree(tmp_path, True)
except:
return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info())
return public.returnMsg(True,'The file was decompressed successfully')
def add_zip_file(self,args):
'''
@name 添加文件到压缩包
@param args['r_path'] 跟路径
@param args['filename'] 文件名
@param args['f_list'] 写入数据
@return dict
'''
sfile = args.sfile
r_path = args.r_path
f_list = args.f_list
return public.returnMsg(False,'RAR archive does not support this function!')
def __get_zip_filename(self,item):
'''
@name 获取压缩包文件名
@param item 压缩包文件对象
@return string
'''
filename = item.filename
try:
filename = item.filename.encode('cp437').decode('gbk')
except:pass
if item.flag_bits == 32:
filename += '/'
return filename.replace('\\','/')
+307
View File
@@ -0,0 +1,307 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os, re
from filesModel.base import filesBase
import public, json
from html import escape
class main(filesBase):
__s_class = []
def __init__(self):
for i in range(1, 100):
self.__s_class.append('f-s-%s' % i)
def get_search_status(self, get):
"""
@name 验证是否可用
"""
return public.returnMsg(True, '1')
def get_search_result(self, get):
"""
@name 搜索文件
@param get
path:搜索路径
search:搜索关键字
limit:每页显示条数
p:页码
"""
result = {}
is_dir = 0
search = []
if not 'ext' in get: get.ext = '*'
if 'search' in get: search = get.search
if 'is_dir' in get: is_dir = get.is_dir
public.set_module_logs('searchModel', 'get_search_result')
if not is_dir:
if len(search) == 0:
return public.returnMsg(False, 'Please enter search keywords!')
if not os.path.exists(get.path):
return public.returnMsg(False, 'Search directory does not exist!')
slist = self.get_search_files(get)
if is_dir: return slist
num = 0
total_num = len(slist)
if slist: public.writeSpeed('files_search', num, total_num)
for sfile in slist:
data = self.__check_file_contents(sfile, search)
if data:
result[sfile] = data
num += 1
public.writeSpeed('files_search', num, total_num)
progress = int(public.getSpeed()['progress'])
if '_ws' in get:
get._ws.send(
public.getJson({
"end": False if progress < 100 else True,
"ws_callback": get.ws_callback,
"file": sfile,
"progress": progress,
"total": total_num,
"num": num,
"type": "get_search_result"
}))
if not slist and '_ws' in get:
get._ws.send(
public.getJson({
"end": True,
"ws_callback": get.ws_callback,
"file": '',
"progress": 100,
"total": 0,
"num": 0,
"type": "get_search_result"
}))
return result
def get_search_files(self, get):
"""
@name 搜索文件
@param get
path:搜索路径
search:搜索关键字
"""
data = {}
data['is_sub'] = 0
if 'is_sub' in get:
data['is_sub'] = int(get.is_sub)
data['ext'] = []
for ext in get.ext.split(','):
if ext: data['ext'].append(ext)
data['s_time'] = 0
data['e_time'] = 4070880000
if 's_time' in get:
data['s_time'] = int(get.s_time)
if 'e_time' in get:
data['e_time'] = int(get.e_time)
data['min_size'] = 0
data['max_size'] = 1024 * 1024 * 10
if 'min_size' in get:
data['min_size'] = int(get.min_size)
if 'max_size' in get:
data['max_size'] = int(get.max_size)
data['names'] = []
if 'names' in get:
data['names'] = get.names
flist = []
self.__get_file_list(get.path, data, flist)
return flist
def __check_file_contents(self, sfile, contents):
"""
@name 验证文件内容
@param sfile:文件路径
@param contents:文件内容
"""
n = 1
result = {}
try:
for line in open(sfile, 'rb'):
try:
if type(line) == bytes: line = line.decode('utf-8')
except:
line = str(line)
rep_list = {}
_line = escape(line)
p = 0
for txt in contents:
if not txt: continue
p += 1
txt = escape(txt)
if line.find(txt) >= 0:
_line = self.__replace_contents(
_line, txt, p, rep_list)
else:
tmp = re.search('(' + txt + ')', _line, flags=re.I)
if tmp:
_line = self.__replace_contents(
_line,
tmp.groups()[0], p, rep_list)
for key in rep_list:
# public.print_log(json.dumps(rep_list))
result[n] = _line.replace(key, rep_list[key])
n += 1
except:
pass
return result
# line = line.replace("BT_SEARCH".format(p), )
def __replace_contents(self, line, txt, p, rep_list):
"""
@name 替换文件内容
@param line:文件内容
@param txt:替换内容
@param p:替换位置
"""
n_data = 'BT_SEARCH{}'.format(p)
line = line.replace(txt, n_data)
rep_list[n_data] = "<span class='{}'>{}</span>".format(
self.__s_class[p - 1], txt)
return line
def __get_file_list(self, path, data, flist):
"""
@name 获取文件列表
@param path:文件路径
@param ext:文件类型
@param s_time:开始时间
@param e_time:结束时间
@param min_size:最小文件大小
@param max_size:最大文件大小
@param flist:返回文件列表
"""
exts, s_time, e_time, min_size, max_size, names = data['ext'], data[
's_time'], data['e_time'], data['min_size'], data[
'max_size'], data['names']
for name in os.listdir(path):
sfile = os.path.join(path, name)
if os.path.isdir(sfile):
if not data['is_sub']: continue
self.__get_file_list(sfile, data, flist)
else:
#第一步:验证文件名
if not self.__check_filename(sfile=sfile, names=names):
continue
#第二步:验证后缀
if not self.__check_ext(sfile=sfile, exts=exts):
continue
#第三步:验证时间
if not self.__check_time(
sfile=sfile, s_time=s_time, e_time=e_time):
continue
#第四步:验证大小
if not self.__check_size(
sfile=sfile, min_size=min_size, max_size=max_size):
continue
flist.append(sfile)
def __check_filename(self, sfile, names):
"""
@name 验证文件名
@param sfile:文件路径
@param names:文件名
"""
try:
if len(names) == 0: return True
filename = os.path.basename(sfile)
for name in names:
if filename.find(name) >= 0:
return True
try:
if re.search(name, filename):
return True
except:
pass
except:
pass
return False
def __check_ext(self, sfile, exts):
"""
@name 验证文件后缀
@param sfile:文件路径
@param exts:文件类型
"""
try:
if "*" in exts:
return True
spath, ext = os.path.splitext(sfile)
if ext:
if ext[1:] in exts:
return True
else:
if 'no_ext' in exts:
return True
except:
pass
return False
def __check_time(self, sfile, s_time, e_time):
"""
@name 验证文件时间
@param sfile:文件路径
@param s_time:开始时间
@param e_time:结束时间
"""
try:
st_time = int(os.stat(sfile).st_mtime)
if st_time >= s_time and st_time <= e_time:
return True
except:
pass
return False
def __check_size(self, sfile, min_size, max_size):
"""
@name 验证文件大小
@param sfile:文件路径
@param min_size:最小文件大小
@param max_size:最大文件大小
"""
try:
f_size = os.path.getsize(sfile)
if f_size >= min_size and f_size <= max_size:
return True
except:
pass
return False
+287
View File
@@ -0,0 +1,287 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
import copy
import random
# 获取目录大小
#------------------------------
import sys, os
import json, os, time, re
import public
from filesModel.base import filesBase
panelPath = '/www/server/panel'
os.chdir(panelPath)
class main(filesBase):
_exe_cmd = 'ncdu'
# 扫描历史
log_path = '{}/data/scan/'.format(public.get_panel_path())
# 缓存
cache_file = '{}/config/scan_disk_cache.json'.format(public.get_panel_path())
def __init__(self):
self.is_use = False
if os.path.isdir("{}/plugin/disk_analysis".format(public.get_panel_path())):
self.is_use = True
if not os.path.exists(self.log_path):
os.makedirs(self.log_path)
if not os.path.exists(self.cache_file):
public.writeFile(self.cache_file,"{}")
if os.getenv('BT_PANEL'):
self._exe_cmd = '{}/plugin/disk_analysis/ncdu'.format(panelPath)
def get_path_size(self, get):
"""
@name 根据排除目录获取路径的总大小
@param path 目标路径
"""
if self.is_use is False:
return {"code": 404, "status": False, "msg": 'Please install [Disk analysis] first !'}
path = get.path
is_refresh = get.is_refresh == "true"
real_path_dict = {} # 软连接处理
temp_path_list = []
for path in str(path).split(","):
r_path = os.path.realpath(path)
if r_path != path:
real_path_dict[r_path] = path
path = r_path
if path != "/": path = str(path).rstrip("/")
temp_path_list.append(path)
try:
cache_data = json.loads(public.readFile(self.cache_file))
except:
cache_data = {}
result = {}
path_list = []
if is_refresh is True:
path_list = temp_path_list
else:
for path in temp_path_list:
if cache_data.get(path) is not None:
result[path] = cache_data.get(path)
else:
path_list.append(path)
if path_list:
scan_path = path_list[0]
if os.path.isfile(scan_path):
scan_path = os.path.split(scan_path)[0]
for path in path_list[1:]:
while True:
if path.startswith(scan_path):
break
scan_path = os.path.split(scan_path)[0]
import string
code = "".join(random.sample(string.ascii_letters + string.digits, 8))
result_file = '{}{}'.format(self.log_path, f"temp_scan_size_{code}")
scan_time = int(time.time())
exec_shell = "{} '{}' -o '{}' ".format(self._exe_cmd, scan_path, result_file).replace('\\', '/').replace('//','/')
public.ExecShell(exec_shell)
scan_result = self.__get_log_size(result_file, path_list, scan_time, cache_data)
os.remove(result_file)
result.update(scan_result)
public.writeFile(self.cache_file, json.dumps(cache_data))
for r_path, path in real_path_dict.items():
result[path] = result[r_path]
del result[r_path]
return result
@classmethod
def __get_log_size(cls, log_file, path_list, scan_time, cache_data):
"""
@name 获取文件或目录大小
@param log_file 日志文件
"""
result = {}
for path in path_list:
result[path] = None
data = public.readFile(log_file)
data = json.loads(data)
data = data[-1]
root_path = data[0]["name"]
if root_path in path_list:
result[root_path] = data
else:
cls.__get_sub_size(data[1:], root_path, path_list, result)
for path,info in result.items():
if info is None:
continue
if isinstance(info, dict):
info["type"] = 0
info["asize"] = info.get("asize", 0)
info["dsize"] = info.get("dsize", 0)
info["dir_num"] = 0
info["file_num"] = 0
info["total_asize"] = info.get("asize", 0)
info["total_dsize"] = info.get("dsize", 0)
info["stime"] = scan_time
cls.__get_stat(path, info)
cache_data[path] = info
else:
cls.__get_dirs_size(info)
cls.__get_stat(path, info[0])
result[path] = info[0]
result[path]["stime"] = scan_time
cache_data[path] = result[path]
return result
@classmethod
def __get_sub_size(cls, data, root_path, path_list, result):
"""
@name 获取子目录数据
@param id int 记录id
@param path string 目录
"""
if len(path_list) == 0: return
for val in data:
if isinstance(val, list):
sfile = f"{root_path}/{val[0]['name']}".replace('\\', '/').replace('//', '/')
if sfile in path_list:
result[sfile] = val
path_list.remove(sfile)
if len(val) > 1:
cls.__get_sub_size(val[1:], sfile, path_list, result)
elif isinstance(val, dict):
sfile = f"{root_path}/{val['name']}".replace('\\', '/').replace('//', '/')
if sfile in path_list:
result[sfile] = val
path_list.remove(sfile)
@classmethod
def __get_dirs_size(cls, dirs_list):
"""
@param info 目录信息
@param result 结果
"""
dir_info = dirs_list[0]
dir_info["type"] = 1
dir_info["asize"] = dir_info.get("asize", 0)
dir_info["dsize"] = dir_info.get("dsize", 0)
dir_info["dirs"] = 0
dir_info["files"] = 0
dir_info["dir_num"] = 0
dir_info["file_num"] = 0
dir_info["total_asize"] = dir_info.get("asize", 0)
dir_info["total_dsize"] = dir_info.get("dsize", 0)
for info in dirs_list[1:]:
if isinstance(info, list): # 目录
dir_info["dirs"] += 1
dir_info["dir_num"] += 1
cls.__get_dirs_size(info)
temp_info = info[0]
dir_info["dir_num"] += temp_info["dir_num"]
dir_info["file_num"] += temp_info["file_num"]
dir_info["total_asize"] += temp_info["total_asize"]
dir_info["total_dsize"] += temp_info["total_dsize"]
else:
if info.get("excluded") == "pattern":
continue
dir_info["files"] += 1
dir_info["file_num"] += 1
if info.get("asize") is None: info["asize"] = 0
if info.get("dsize") is None: info["dsize"] = 0
info["type"] = 0
dir_info["total_asize"] += info["asize"]
dir_info["total_dsize"] += info["dsize"]
@classmethod
def __get_stat(cls, path, info):
if not os.path.exists(path):
info["accept"] = None
info["user"] = None
info["mtime"] = "--"
info["ps"] = None
return
stat_file = os.stat(path)
info["accept"] = oct(stat_file.st_mode)[-3:]
import pwd
try:
info["user"] = pwd.getpwuid(stat_file.st_uid).pw_name
except:
info["user"] = str(stat_file.st_uid)
info["atime"] = int(stat_file.st_atime)
info["ctime"] = int(stat_file.st_ctime)
info["mtime"] = int(stat_file.st_mtime)
info["ps"] = cls.get_file_ps(path)
@classmethod
def get_file_ps(cls,filename):
'''
@name 获取文件或目录备注
@author hwliang<2020-10-22>
@param filename<string> 文件或目录全路径
@return string
'''
ps_path = public.get_panel_path() + '/data/files_ps'
f_key1 = '/'.join((ps_path,public.md5(filename)))
if os.path.exists(f_key1):
return public.readFile(f_key1)
f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename))))
if os.path.exists(f_key2):
return public.readFile(f_key2)
pss = {
'/www/server/data': 'This is the default data directory of the MySQL database, please do not delete it!',
'/www/server/mysql': 'MySQL program directory',
'/www/server/redis': 'Redis program directory',
'/www/server/mongodb': 'MongoDB program directory',
'/www/server/nvm': 'PM2/NVM/NPM program directory',
'/www/server/pass': 'Website BasicAuth authentication password storage directory',
'/www/server/speed': 'Website acceleration data directory',
'/www/server/docker': 'Docker plugin and data directory',
'/www/server/total': 'Website monitoring report data directory',
'/www/server/btwaf': 'WAF firewall data directory',
'/www/server/pure-ftpd': 'ftp program directory',
'/www/server/phpmyadmin': 'phpMyAdmin program directory',
'/www/server/rar': 'rar expansion library directory, will lose support for RAR compressed files after deletion',
'/www/server/stop': 'The website deactivates the page directory, please do not delete it!',
'/www/server/nginx': 'Nginx program directory',
'/www/server/apache': 'Apache program directory',
'/www/server/cron': 'Scheduled task script and log directory',
'/www/server/php': 'PHP directory, all PHP version interpreters are in this directory',
'/www/server/tomcat': 'Tomcat program directory',
'/www/php_session': 'PHP-SESSION isolation directory',
'/www/server/panel': 'aaPanel program directory',
'/proc': 'system process directory',
'/dev': 'system device directory',
'/sys': 'system call directory',
'/tmp': 'system temporary file directory',
'/var/log': 'System log directory',
'/var/run': 'System running log directory',
'/var/spool': 'system queue directory',
'/var/lock': 'system lock directory',
'/var/mail': 'system mail directory',
'/mnt': 'System mount directory',
'/media': 'System multimedia directory',
'/dev/shm': 'system shared memory directory',
'/lib': 'system dynamic library directory',
'/lib64': 'system dynamic library directory',
'/lib32': 'system dynamic library directory',
'/usr/lib': 'system dynamic library directory',
'/usr/lib64': 'system dynamic library directory',
'/usr/local/lib': 'system dynamic library directory',
'/usr/local/lib64': 'system dynamic library directory',
'/usr/local/libexec': 'system dynamic library directory',
'/usr/local/sbin': 'System script directory',
'/usr/local/bin': 'System script directory'
}
if filename in pss: return "PS" + pss[filename]
return None
+170
View File
@@ -0,0 +1,170 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
# 上传文件至oss
#------------------------------
import os
from filesModel.base import filesBase
import public,smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr
class main(filesBase):
def __init__(self):
pass
def get_oss_objects(self,get):
"""
@name 获取可上传的对象存储
"""
return self.get_all_objects(get)
def get_file_list(self,get):
"""
@name 获取可上传的对象存储
"""
return self.get_base_objects(get)
def check_email_config(self,get):
"""
@name 检测邮箱是否配置
"""
import config
c_obj = config.config()
mail_config = c_obj.get_msg_configs(get)['mail']
return mail_config
def send_to_email(self,get):
"""
@name 发送文件到邮件
@flist list 文件列表
@msg string 邮件正文
@to string 邮件接收人多个逗号隔开
"""
import config
c_obj = config.config()
try:
mail_config = c_obj.get_msg_configs(get)['mail']['data']
if not mail_config :
return public.returnMsg(False,'未正确配置邮箱信息。')
if not mail_config['send']['qq_mail']:
return public.returnMsg(False,'未正确配置邮箱信息。')
except:
return public.returnMsg(False,'未正确配置邮箱信息。')
msg = get.msg
receive_list = get.to_email.split(',')
if len(receive_list) <= 0:
return public.returnMsg(False,'发送失败,接收者不能为空.')
#附件文件
flist = []
if 'flist' in get: flist = get.flist
result = {}
result['status'] = True
result['list'] = {}
for email in receive_list:
slist = {}
try:
data = MIMEMultipart()
data['From'] = formataddr([mail_config['send']['qq_mail'], mail_config['send']['qq_mail']])
data['To'] = formataddr([mail_config['send']['qq_mail'], email.strip()])
data['Subject'] = '宝塔面板消息通知'
if int(mail_config['send']['port']) == 465:
server = smtplib.SMTP_SSL(str(mail_config['send']['hosts']), str(mail_config['send']['port']))
else:
server = smtplib.SMTP(str(mail_config['send']['hosts']), str(mail_config['send']['port']))
data.attach(MIMEText(msg, 'html', 'utf-8'))
slist['error'] = {}
#添加附件
for filename in flist:
if not os.path.exists(filename):
slist['error'][filename] = '文件不存在'
continue
#超过50M无法发送
if os.path.getsize(filename) > 50 * 1024 *1024:
slist['error'][filename] = '文件大于50M'
continue
#中文无法发送
if public.check_chinese(filename):
slist['error'][filename] = '文件名包含中文,发送失败.'
continue
att1 = MIMEText(open(filename, 'rb').read(), 'base64', 'utf-8')
att1["Content-Type"] = 'application/octet-stream'
att1["Content-Disposition"] = 'attachment; filename="' + os.path.basename(filename) + '"'
data.attach(att1)
server.login(mail_config['send']['qq_mail'], mail_config['send']['qq_stmp_pwd'])
server.sendmail(mail_config['send']['qq_mail'], [email.strip(), ], data.as_string())
server.quit()
slist['status'] = True
except :
slist = '发送失败,' + public.get_error_info()
result['list'][email] = slist
public.set_module_logs('files_send_to_email', 'send_to_email', 1)
return result
def upload_file(self,args):
"""
@name 上传文件到指定的对象存储
"""
name = args.name
filename = args.filename
bucket = args.object_name.rstrip('/')
if not os.path.exists(filename):
return public.returnMsg(False,'FILE_NOT_EXIST')
info = self.get_soft_find(name)
if not info['setup']:
return public.returnMsg(False,'未安装[{}]插件'.format(info['title']))
sfile = '{path}/plugin/{name}/{name}_main.py'.format(path=public.get_panel_path(),name=name)
if public.readFile(sfile).find('upload_to') == -1:
return public.returnMsg(False,'暂不支持该操作,请将[{}]插件升级到最新版'.format(info['title']))
#创建任务
import panelTask
task_obj = panelTask.bt_task()
msg = '上传文件{}{}'.format(filename,info['title'])
exec_shell = 'btpython -u {spath} upload_to {file} {bucket}/{filename}'.format(spath=sfile,file=filename,bucket=bucket,filename=os.path.basename(filename))
task_obj.create_task(msg, 0, exec_shell)
public.set_module_logs('files_upload_to_file', 'upload_file', 1)
public.WriteLog('TYPE_FILE', msg)
return public.returnMsg(True, '已添加到上传队列.')
+327
View File
@@ -0,0 +1,327 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,json
import zipfile,shutil
from pathlib import Path
class main(filesBase):
def __init__(self):
pass
def __check_zipfile(self,sfile,is_close = False):
'''
@name 检查文件是否为zip文件
@param sfile 文件路径
@return bool
'''
zip_file = None
try:
zip_file = zipfile.ZipFile(sfile)
except:pass
if is_close and zip_file:
zip_file.close()
return zip_file
def get_zip_files(self,args):
'''
@name 获取压缩包内文件列表
@param args['path'] 压缩包路径
@return list
'''
sfile = args.sfile
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
zip_file = self.__check_zipfile(sfile)
if not zip_file:
return public.returnMsg(False,'NOT_ZIP_FILE')
data = {}
for item in zip_file.infolist():
sub_data = data
f_name = self.__get_zip_filename(item)
f_dirs = f_name.split('/')
d_idx = 0
for d in f_dirs:
if not d: continue
if not d in sub_data:
if d == f_name[-len(d):] and d_idx == len(f_dirs) - 1:
tmps = item.date_time
sub_data[d] = {
'file_size': item.file_size,
'compress_size': item.compress_size,
'compress_type': item.compress_type,
'filename':d,
'fullpath':f_name,
'date_time': public.to_date(times = '{}-{}-{} {}:{}:{}'.format(tmps[0],tmps[1],tmps[2],tmps[3],tmps[4],tmps[5])),
'is_dir': 0
}
if item.is_dir():
sub_data[d]['is_dir'] = 1
else:
sub_data[d] = {}
d_idx += 1
sub_data = sub_data[d]
zip_file.close()
return data
def get_fileinfo_by(self,args):
'''
@name 获取压缩包内文件信息
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@return dict
'''
sfile = args.sfile
filename = args.filename
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
result = {}
result['status'] = True
result['data'] = ''
with zipfile.ZipFile(sfile,'r') as zip_file:
for item in zip_file.infolist():
z_filename = self.__get_zip_filename(item)
if z_filename == filename:
buff = zip_file.read(item.filename)
encoding,srcBody = public.decode_data(buff)
result['encoding'] = encoding
result['data'] = srcBody
break
return result
def delete_zip_file(self,args):
'''
@name 删除压缩包内文件
@param args['path'] 压缩包路径
@param args['filenames'] 文件名列表数组格式
@return dict
'''
sfile = args.sfile
filenames = args.filenames
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
with zipfile.ZipFile(sfile,'r') as zip_file:
with zipfile.ZipFile(sfile + '.tmp','w',zipfile.ZIP_DEFLATED) as new_zfile:
for item in zip_file.infolist():
filename = self.__get_zip_filename(item)
if filename in filenames:
continue
src_name = item.filename
item.filename = filename
new_zfile.writestr(item,zip_file.read(src_name))
shutil.move(sfile + '.tmp',sfile)
return public.returnMsg(True,'File deleted successfully')
def write_zip_file(self,args):
'''
@name 写入压缩包内文件
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@param args['data'] 写入数据
@return dict
'''
sfile = args.sfile
filename = args.filename
data = args.data
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
with zipfile.ZipFile(sfile,'r') as zip_file:
with zipfile.ZipFile(sfile + '.tmp','w',zipfile.ZIP_DEFLATED) as new_zfile:
for item in zip_file.infolist():
z_filename = self.__get_zip_filename(item)
if z_filename == filename:
continue
new_zfile.writestr(item,zip_file.read(item.filename))
new_zfile.writestr(filename, data=data, compress_type=zipfile.ZIP_DEFLATED)
shutil.move(sfile + '.tmp',sfile)
return public.returnMsg(True,'File written successfully')
def extract_byfiles(self,args):
"""
@name 解压部分文件
@param args['path'] 压缩包路径
@param args['extract_path'] 解压路径
@param args['filenames'] 文件名列表数组格式
"""
zip_path = ''
if 'zip_path' in args: zip_path = args.zip_path
sfile = args.sfile
filenames = args.filenames
extract_path = args.extract_path
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not os.path.exists(extract_path):
os.makedirs(extract_path,384)
tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32)))
if not os.path.exists(tmp_path):
os.makedirs(tmp_path,384)
with zipfile.ZipFile(sfile) as zip_file:
try:
m_list = {}
for item in zip_file.infolist():
filename = self.__get_zip_filename(item)
if filename in filenames:
spath = os.path.join(tmp_path,filename).strip('/')
if item.is_dir():
m_list[spath] = []
else:
if not 'other' in m_list:
m_list['other'] = []
dir_key = os.path.dirname(spath)
info = {'src':spath,'dst':'{}/{}'.format(extract_path,filename.strip('/'))}
if zip_path:
info['dst'] = '{}/{}'.format(extract_path,filename.replace(zip_path,'').strip('/'))
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path,384)
if dir_key in m_list:
m_list[dir_key].append(info)
else:
m_list['other'].append(info)
item.filename = filename
zip_file.extract(item,tmp_path)
for key in m_list:
try:
for info in m_list[key]:
if os.getenv('BT_PANEL'):
shutil.copyfile(info['src'],info['dst'])
else:
shutil.copyfile('/' + info['src'],'/' + info['dst'])
except:
pass
shutil.rmtree(tmp_path, True)
except:
return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info())
return public.returnMsg(True,'The file was decompressed successfully')
def add_zip_file(self,args):
'''
@name 添加文件到压缩包
@param args['r_path'] 跟路径
@param args['filename'] 文件名
@param args['f_list'] 写入数据
@return dict
'''
sfile = args.sfile
r_path = args.r_path
f_list = args.f_list
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
#追加原路径
src_list = {}
for fname in f_list:
if os.path.isdir(fname):
s_list = []
public.get_file_list(fname,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = '{}/{}{}'.format(r_path,os.path.basename(fname),f.replace(fname,''))
src_list[src_file] = f
else:
src_file = r_path + '/' + os.path.basename(fname)
src_list[src_file] = fname
tmp_path = sfile + '.tmp'
if os.path.exists(tmp_path):
os.remove(tmp_path)
with zipfile.ZipFile(sfile,'r') as zip_file:
with zipfile.ZipFile(tmp_path,'w',zipfile.ZIP_DEFLATED) as new_zfile:
try:
#过滤旧文件
for item in zip_file.namelist():
if item in src_list:
continue
new_zfile.writestr(item,zip_file.read(item))
#追加新文件
for src_file in src_list:
new_zfile.write(src_list[src_file],src_file)
except:
return public.returnMsg(False,'Failed add file,error:' + public.get_error_info())
shutil.move(tmp_path,sfile)
return public.returnMsg(True,'Compressed package file modified successfully')
# def __get_zip_filename(self,item):
# '''
# @name 获取压缩包文件名
# @param item 压缩包文件对象
# @return string
# '''
# path = item.filename
# try:
# path_name = path.decode('utf-8')
# except:
# path_name = path.encode('cp437').decode('gbk')
# path_name = path_name.encode('utf-8').decode('utf-8')
# return path_name
def __get_zip_filename(self,item):
'''
@name 获取压缩包文件名
@param item 压缩包文件对象
@return string
'''
filename = item.filename
try:
filename = item.filename.encode('cp437').decode('gbk')
except:pass
return filename
+1 -1
View File
@@ -1,4 +1,4 @@
DelAcceptPort#coding: utf-8
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 x5
# +-------------------------------------------------------------------
+31 -32
View File
@@ -7,17 +7,18 @@
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------
import sys,os,public,re,firewalld,time
class firewalls:
__isFirewalld = False
__isUfw = False
__Obj = None
__ufw_exec = 'ufw'
def __init__(self):
if os.path.exists('/usr/sbin/firewalld'): self.__isFirewalld = True
self.__ufw = 'ufw'
if os.path.exists('/usr/sbin/ufw'):
self.__ufw_exec = '/usr/sbin/ufw'
self.__isUfw = True
self.__ufw = '/usr/sbin/ufw'
if self.__isFirewalld:
try:
self.__Obj = firewalld.firewalld()
@@ -56,10 +57,10 @@ class firewalls:
#重载防火墙配置
def FirewallReload(self):
if self.__isUfw:
public.ExecShell('{} reload &'.format(self.__ufw_exec))
public.ExecShell('/usr/sbin/ufw reload &')
return
if self.__isFirewalld:
public.ExecShell('firewall-cmd --reload')
public.ExecShell('firewall-cmd --reload &')
else:
public.ExecShell('/etc/init.d/iptables save &')
public.ExecShell('/etc/init.d/iptables restart &')
@@ -91,9 +92,9 @@ class firewalls:
status_msg = {False: 'Close', True: 'Open'}
if self.__isUfw:
if status:
public.ExecShell('echo y|{} enable'.format(self.__ufw_exec))
public.ExecShell('echo y|{} enable'.format(self.__ufw))
else:
public.ExecShell('echo y|{} disable'.format(self.__ufw_exec))
public.ExecShell('echo y|{} disable'.format(self.__ufw))
if self.__isFirewalld:
if status:
public.ExecShell('systemctl enable firewalld')
@@ -123,9 +124,9 @@ class firewalls:
if public.M('firewall').where("port=?",(address,)).count() > 0: return public.return_msg_gettext(False,'The IP exists in block list, no need to repeat processing!')
if self.__isUfw:
if public.is_ipv6(ip_format):
public.ExecShell('{} deny from '.format(self.__ufw_exec) + address + ' to any')
public.ExecShell('{} deny from {} to any'.format(self.__ufw,address))
else:
public.ExecShell('{} insert 1 deny from '.format(self.__ufw_exec) + address + ' to any')
public.ExecShell('{} insert 1 deny from {} to any'.format(self.__ufw,address))
else:
if self.__isFirewalld:
#self.__Obj.AddDropAddress(address)
@@ -151,7 +152,7 @@ class firewalls:
id = get.id
ip_format = get.port.split('/')[0]
if self.__isUfw:
public.ExecShell('{} delete deny from '.format(self.__ufw_exec) + address + ' to any')
public.ExecShell('{} delete deny from {} to any'.format(self.__ufw,address))
else:
if self.__isFirewalld:
if public.is_ipv6(ip_format):
@@ -180,24 +181,23 @@ class firewalls:
import time
port = get.port
ps = ""
if get.ps:
ps = public.xssencode2(get.ps)
ps = public.xssencode2(get.ps)
is_exists = public.M('firewall').where("port=? or port=?",(port,src_port)).count()
if is_exists: return public.return_msg_gettext(False,'The port exists, no need to repeat the release!')
notudps = ['80','443','8888','888','39000:40000','21','22']
if self.__isUfw:
public.ExecShell('{} allow '.format(self.__ufw_exec) + port + '/tcp')
# if not port in notudps: public.ExecShell('ufw allow ' + port + '/udp')
a=public.ExecShell('{} allow {}/tcp'.format(self.__ufw,port))
# public.writeFile('/tmp/2',str(a))
if not port in notudps: public.ExecShell('{} allow {}/udp'.format(self.__ufw,port))
else:
if self.__isFirewalld:
#self.__Obj.AddAcceptPort(port)
port = port.replace(':','-')
public.ExecShell('firewall-cmd --permanent --zone=public --add-port='+port+'/tcp')
# if not port in notudps: public.ExecShell('firewall-cmd --permanent --zone=public --add-port='+port+'/udp')
if not port in notudps: public.ExecShell('firewall-cmd --permanent --zone=public --add-port='+port+'/udp')
else:
public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport '+port+' -j ACCEPT')
# if not port in notudps: public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m udp --dport '+port+' -j ACCEPT')
if not port in notudps: public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m udp --dport '+port+' -j ACCEPT')
public.WriteLog("TYPE_FIREWALL", 'FIREWALL_ACCEPT_PORT',(port,))
addtime = time.strftime('%Y-%m-%d %X',time.localtime())
if not is_exists: public.M('firewall').add('port,ps,addtime',(port,ps,addtime))
@@ -214,16 +214,16 @@ class firewalls:
if not re.search(rep,port):
return False
if self.__isUfw:
public.ExecShell('{} allow '.format(self.__ufw_exec) + port + '/tcp')
# public.ExecShell('ufw allow ' + port + '/udp')
public.ExecShell('{} allow {}/tcp'.format(self.__ufw,port))
public.ExecShell('{} allow {}/udp'.format(self.__ufw,port))
else:
if self.__isFirewalld:
port = port.replace(':','-')
public.ExecShell('firewall-cmd --permanent --zone=public --add-port='+port+'/tcp')
# public.ExecShell('firewall-cmd --permanent --zone=public --add-port='+port+'/udp')
public.ExecShell('firewall-cmd --permanent --zone=public --add-port='+port+'/udp')
else:
public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport '+port+' -j ACCEPT')
# public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m udp --dport '+port+' -j ACCEPT')
public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m udp --dport '+port+' -j ACCEPT')
return True
#删除放行端口
@@ -232,14 +232,14 @@ class firewalls:
port = get.port
id = get.id
if public.is_ipv6(str(port)): return self.DelDropAddress(get) # 如果是ipv6地址,则调用DelDropAddress
if public.is_ipv6(port): return self.DelDropAddress(get) # 如果是ipv6地址,则调用DelDropAddress
try:
if(port == public.GetHost(True) or port == public.readFile('data/port.pl').strip()):
return public.return_msg_gettext(False,'Failed,cannot delete current port of the panel')
if self.__isUfw:
public.ExecShell('{} delete allow '.format(self.__ufw_exec) + port + '/tcp')
public.ExecShell('{} delete allow '.format(self.__ufw_exec) + port + '/udp')
public.ExecShell('{} delete allow {}/tcp'.format(self.__ufw,port))
public.ExecShell('{} delete allow {}/udp'.format(self.__ufw,port))
else:
if self.__isFirewalld:
#self.__Obj.DelAcceptPort(port)
@@ -277,7 +277,7 @@ class firewalls:
public.ExecShell("systemctl "+act+" sshd")
public.ExecShell("systemctl "+act+" ssh")
if act in ['start'] and not public.get_sshd_status():
msg = 'Service SSHD start failed!'
msg = 'SSHD service failed to start'
public.WriteLog("TYPE_FIREWALL", msg)
return public.returnMsg(False,msg)
public.WriteLog("TYPE_FIREWALL", msg)
@@ -296,19 +296,18 @@ class firewalls:
conf = public.readFile(filename)
if conf.find('net.ipv4.icmp_echo') != -1:
rep = r"net\.ipv4\.icmp_echo.*"
conf = re.sub(rep,'net.ipv4.icmp_echo_ignore_all='+get.status,conf)
conf = re.sub(rep, 'net.ipv4.icmp_echo_ignore_all=' + get.status + "\n", conf)
else:
conf += "\nnet.ipv4.icmp_echo_ignore_all="+get.status
conf += "\nnet.ipv4.icmp_echo_ignore_all=" + get.status + "\n"
if public.writeFile(filename,conf):
public.ExecShell('sysctl -p')
return public.return_msg_gettext(True,'Setup successfully!')
return public.returnMsg(True,'SUCCESS')
else:
return public.returnMsg(False,'Setup failed!')
return public.returnMsg(False,'<a style="color:red;">ERROR: setup failed, [sysctl.conf] not writable!</a><br>1. If [System hardening] is installed, please close it first<br>')
#改远程端口
def SetSshPort(self,get):
port = get.port
@@ -333,7 +332,7 @@ class firewalls:
public.ExecShell('sed -i "s#SELINUX=enforcing#SELINUX=disabled#" /etc/selinux/config')
public.ExecShell("systemctl restart sshd.service")
elif self.__isUfw:
public.ExecShell('{} allow '.format(self.__ufw_exec) + port + '/tcp')
public.ExecShell('{} allow {}/tcp'.format(self.__ufw,port))
public.ExecShell("service ssh restart")
else:
public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport '+port+' -j ACCEPT')
+2 -19
View File
@@ -105,34 +105,16 @@ class Compress(object):
if request_token:
response.set_cookie('request_token',request_token,path='/',max_age=86400 * 30)
if response.content_length is not None:
if response.content_length < 512:
if not session.get('login',None) or g.get('api_request',None):
import public
default_pl = "{}/default.pl".format(public.get_panel_path())
default_body = public.readFile(default_pl,'rb')
if default_body:
if not default_body: default_body = b""
resp_body = response.get_data()
if default_body and resp_body.find(default_body.strip()) != -1:
result = b'{"status":false,"msg":"Error: 403 Forbidden"}'
response.set_data(result)
response.headers['Content-Length'] = len(result)
return response
if (response.mimetype not in app.config['COMPRESS_MIMETYPES'] or
'gzip' not in accept_encoding.lower() or
not 200 <= response.status_code < 300 or
(response.content_length is not None and
response.content_length < app.config['COMPRESS_MIN_SIZE']) or
'Content-Encoding' in response.headers):
g.response = response
return response
response.direct_passthrough = False
if self.cache:
key = self.cache_key(response)
gzip_content = self.cache.get(key) or self.compress(app, response)
@@ -152,6 +134,7 @@ class Compress(object):
else:
response.headers['Vary'] = 'Accept-Encoding'
g.response = response
return response
def compress(self, app, response):
+22 -1
View File
@@ -24,7 +24,7 @@ class ftp:
import files,time
fileObj=files.files()
if get['ftp_username'].strip().find(' ') != -1: return public.returnMsg(False,'Username cannot contain spaces')
if re.search("\W + ",get['ftp_username']): return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, special characters are NOT allowed!')}
if re.search("\W+",get['ftp_username']): return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, special characters are NOT allowed!')}
if len(get['ftp_username']) < 3: return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, cannot be less than 3 characters!')}
if not fileObj.CheckDir(get['path']): return {'status':False,'code':501,'msg':public.get_msg_gettext('System critical directory cannot be used as FTP directory!')}
if public.M('ftps').where('name=?',(get.ftp_username.strip(),)).count(): return public.return_msg_gettext(False,'User [{}] exists!',(get.ftp_username,))
@@ -57,6 +57,8 @@ class ftp:
try:
username = get['username']
id = get['id']
if public.M('ftps').where("id=? and name=?", (id,username, )).count()==0:
return public.return_msg_gettext(False, 'DEL_ERROR')
public.ExecShell(self.__runPath + '/pure-pw userdel "' + username + '"')
self.FtpReload()
public.M('ftps').where("id=?",(id,)).delete()
@@ -73,6 +75,8 @@ class ftp:
id = get['id']
username = get['ftp_username'].strip()
password = get['new_password'].strip()
if public.M('ftps').where("id=? and name=?", (id,username, )).count()==0:
return public.return_msg_gettext(False, 'DEL_ERROR')
if len(password) < 6: return public.return_msg_gettext(False,'Password must be at least [{}] characters',("6",))
public.ExecShell(self.__runPath + '/pure-pw passwd "' + username + '"<<EOF \n' + password + '\n' + password + '\nEOF')
self.FtpReload()
@@ -92,6 +96,8 @@ class ftp:
id = get['id']
username = get['username']
status = get['status']
if public.M('ftps').where("id=? and name=?", (id,username, )).count()==0:
return public.return_msg_gettext(False, 'DEL_ERROR')
if int(status)==0:
public.ExecShell(self.__runPath + '/pure-pw usermod "' + username + '" -r 1')
else:
@@ -137,6 +143,21 @@ class ftp:
def FtpReload(self):
public.ExecShell(self.__runPath + '/pure-pw mkdb /www/server/pure-ftpd/etc/pureftpd.pdb')
def get_login_logs(self, get):
import ftplog
ftpobj = ftplog.ftplog()
return ftpobj.get_login_log(get)
def get_action_logs(self, get):
import ftplog
ftpobj = ftplog.ftplog()
return ftpobj.get_action_log(get)
def set_ftp_logs(self, get):
import ftplog
ftpobj = ftplog.ftplog()
result = ftpobj.set_ftp_log(get)
return result
#修改用户密码
def set_user_home(self,get):
"""
+539
View File
@@ -0,0 +1,539 @@
#coding: utf-8
# + -------------------------------------------------------------------
# | 宝塔Linux面板
# + -------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# + -------------------------------------------------------------------
# | Author: hezhihong <272267659@@qq.cn>
# + -------------------------------------------------------------------
import public, os, time
try:
from BTPanel import session
except:
pass
#英文转月份缩写
month_list = {
"Jan": "1",
"Feb": "2",
"Mar": "3",
"Apr": "4",
"May": "5",
"Jun": "6",
"Jul": "7",
"Aug": "8",
"Sept": "9",
"Sep": "9",
"Oct": "10",
"Nov": "11",
"Dec": "12"
}
class ftplog:
def __init__(self):
self.__messages_file = "/var/log/"
self.__ftp_backup_path = public.get_backup_path() + '/pure-ftpd/'
if not os.path.isdir(self.__ftp_backup_path):
public.ExecShell('mkdir -p {}'.format(self.__ftp_backup_path))
self.__script_py = public.get_panel_path() + '/script/ftplogs_cut.py'
def get_file_list(self, path, is_bakcup=False):
"""
@name 取所有messages日志文件
@param path: 日志文件路径
@return: 返回日志文件列表
"""
files = os.listdir(path)
if is_bakcup:
file_name_list = [{
"file": "/var/log/pure-ftpd.log",
"time": int(time.time())
}]
else:
file_name_list = []
for i in files:
tmp_dict = {}
if not i: continue
file_path = path + i
tmp_dict['file'] = file_path
if is_bakcup:
if os.path.isfile(file_path) and i.find('pure-ftpd.log') != -1:
tmp_dict['time'] = int(
public.to_date(
times=os.path.basename(file_path).split('_')[0] +
' 00:00:00'))
file_name_list.append(tmp_dict)
else:
if os.path.isfile(file_path) and i.find('messages') != -1:
tmp_dict['time'] = int(
public.to_date(
times=os.path.basename(file_path).split('-')[1] +
' 00:00:00'))
file_name_list.append(tmp_dict)
file_name_list = sorted(file_name_list,
key=lambda x: x['time'],
reverse=False)
return file_name_list
def set_ftp_log(self, get):
"""
@name 开启关闭获取日志状态
@author hezhihong
@param get.exec_name 执行的动作
"""
if not hasattr(get, 'exec_name'):
return public.returnMsg(False, 'The parameter is incorrect')
conf_path = '/etc/rsyslog.conf'
conf = public.readFile(conf_path)
import re
search_str = r"ftp\.\*.*\t*.*\t*.*-/var/log/pure-ftpd.log"
search_str_two = "ftp.none"
rep_str = '\nftp.*\t\t-/var/log/pure-ftpd.log\n'
result = re.search(search_str, conf)
#获取日志状态
if get.exec_name == 'getlog':
if result:
return_result = 'start'
else:
return_result = 'stop'
return public.returnMsg(True, return_result)
#开启日志审计
elif get.exec_name == 'start':
# 兼容之前开启,会将配置文件搞坏
if conf.count('ftp.nonenftp') > 5:
conf = '''
# /etc/rsyslog.conf configuration file for rsyslog
#
# For more information install rsyslog-doc and see
# /usr/share/doc/rsyslog-doc/html/configuration/index.html
#
# Default logging rules can be found in /etc/rsyslog.d/50-default.conf
#################
#### MODULES ####
#################
module(load="imuxsock") # provides support for local system logging
#module(load="immark") # provides --MARK-- message capability
# provides UDP syslog reception
#module(load="imudp")
#input(type="imudp" port="514")
# provides TCP syslog reception
#module(load="imtcp")
#input(type="imtcp" port="514")
# provides kernel logging support and enable non-kernel klog messages
module(load="imklog" permitnonkernelfacility="on")
###########################
#### GLOBAL DIRECTIVES ####
###########################
#
# Use traditional timestamp format.
# To enable high precision timestamps, comment out the following line.
#
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
# Filter duplicated messages
$RepeatedMsgReduction on
#
# Set the default permissions for all log files.
#
$FileOwner syslog
$FileGroup adm
$FileCreateMode 0640
$DirCreateMode 0755
$Umask 0022
$PrivDropToUser syslog
$PrivDropToGroup syslog
#
# Where to place spool and state files
#
$WorkDirectory /var/spool/rsyslog
#
# Include all config files in /etc/rsyslog.d/
#
$IncludeConfig /etc/rsyslog.d/*.conf
'''
public.writeFile(conf_path, conf)
return self.set_ftp_log(get)
if '*.info;mail.none;authpriv.none;' not in conf:
conf += '\n*.info;mail.none;authpriv.none;cron.none /var/log/messages\n'
if result:
conf = conf.replace(search_str, rep_str)
else:
conf += rep_str
#禁止ftp日志写入/var/log/messages
d_conf = conf[conf.rfind('info;'):]
d_conf = d_conf[:d_conf.find('/')]
s_conf = d_conf.replace(',', ';')
if s_conf.find(search_str_two) == -1:
str_index = s_conf.rfind(';')
s_conf = s_conf[:str_index +
1] + search_str_two + s_conf[str_index + 1:]
conf = conf.replace(d_conf, s_conf)
self.add_crontab()
#关闭日志审计
elif get.exec_name == 'stop':
if result:
conf = re.sub(search_str, '', conf)
#取消禁止ftp日志写入/var/log/messages
if conf.find(search_str_two) != -1:
conf = conf.replace(search_str_two, '')
for i in [';;', ',,', ';,', ',;']:
if conf.find(i) != -1: conf = conf.replace(i, '')
self.del_crontab()
public.writeFile(conf_path, conf)
public.ExecShell('systemctl restart rsyslog')
return public.returnMsg(True, 'successfully set')
def get_format_time(self, englist_time):
"""
@name 时间英文转换
"""
chinanese_time = ''
try:
for i in month_list.keys():
if i in englist_time:
tmp_time = englist_time.replace(i, month_list[i])
tmp_time = tmp_time.split()
chinanese_time = '{}-{} {}'.format(tmp_time[0], tmp_time[1],
tmp_time[2])
break
return chinanese_time
except:
return chinanese_time
def get_login_log(self, get):
"""
@name 取登录日志
@author hezhihong
@param get.user_name ftp用户名
return
"""
search_str = 'pure-ftpd:'
search_str2 = 'pure-ftpd['
if not hasattr(get, 'user_name'):
return public.returnMsg(False, 'The parameter is incorrect')
args = public.dict_obj()
args.exec_name = 'getlog'
file_name = self.__ftp_backup_path
is_backup = True
if self.set_ftp_log(get) == 'stop':
file_name = self.__messages_file
is_backup = False
file_list = self.get_file_list(file_name, is_backup)
data = []
sortid = 0
tmp_dict = {}
login_all = []
for file in file_list:
if not os.path.isfile(file['file']): continue
conf = public.readFile(file['file'])
lines = conf.split('\n')
for line in lines:
if not line: continue
login_info = {}
if search_str not in line and search_str2 not in line:
continue
tmp_value = ' is now logged in'
info = line[:line.find(search_str)].strip()
if not info:
info = line[:line.find(search_str2)].strip()
hostname = info.split()[-1]
exec_time = info.split(hostname)[0].strip()
exec_time = self.get_format_time(exec_time)
ip = line[line.find('(') + 1:line.find(')')].split('@')[1]
#取登录成功日志
if tmp_value in line:
user = line.split(tmp_value)[0].strip().split()[-1]
if user == '?' or user != get.user_name: continue
dict_index = '{}__{}'.format(user, ip)
if dict_index not in tmp_dict:
tmp_dict[dict_index] = []
tmp_dict[dict_index].append(exec_time)
#取登出日志
tmp_value = '[INFO] Logout.'
tmp_value_two = 'Timeout - try typing a little faster next time'
if tmp_value in line or tmp_value_two in line:
user = line[line.find('(') +
1:line.find(')')].split('@')[0]
if user == '?' or user != get.user_name: continue
dict_index = '{}__{}'.format(user, ip)
try:
login_info['out_time'] = exec_time
login_info['in_time'] = tmp_dict[dict_index][0]
login_info['user'] = user
login_info['ip'] = ip
login_info['status'] = 'Success' #0为登录失败,1为登录成功
login_info['sortid'] = sortid
login_all.append(login_info)
tmp_dict[dict_index] = []
sortid += 1
except:
pass
#取登录失败日志
tmp_value = 'Authentication failed for user'
if tmp_value in line:
user = line.split(tmp_value)[-1].replace('[', '').replace(
']', '').strip()
if user == '?' or user != get.user_name: continue
login_info['user'] = user
login_info['ip'] = ip
login_info['status'] = 'Failure' #0为登录失败,1为登录成功
login_info['in_time'] = exec_time
login_info['out_time'] = exec_time
login_info['sortid'] = sortid
login_all.append(login_info)
sortid += 1
if tmp_dict:
for item in tmp_dict.keys():
if not tmp_dict[item]: continue
info = {
"status": "login successful",
"in_time": tmp_dict[item][0],
"out_time": "connecting",
"user": item.split('__')[0],
"ip": item.split('__')[1],
"sortid": sortid
}
sortid += 1
login_all.append(info)
#搜索过滤
if login_all and 'search' in get and get.search and get.search.strip():
for info in login_all:
try:
search_str = str(get.search).strip().lower()
# public.writeFile('/tmp/aa.aa', get.search)
if info['ip'].find(search_str) != -1 or info['user'].lower(
).find(search_str) != -1 or info['status'].find(
search_str) != -1 or info['in_time'].find(
search_str) != -1:
data.append(info)
elif info['out_time'] and info['out_time'].find(
search_str) != -1:
data.append(info)
except:
pass
else:
for info2 in login_all:
data.append(info2)
data = sorted(data, key=lambda x: x['sortid'], reverse=True)
return self.get_page(data, get)
def get_page(self, data, get):
"""
@name 取分页
@author hezhihong
@param data 需要分页的数据 list
@param get.p 第几页
@return 指定分页数据
"""
# 包含分页类
import page
# 实例化分页类
page = page.Page()
info = {}
info['count'] = len(data)
info['row'] = 10
info['p'] = 1
if hasattr(get, 'p'):
info['p'] = int(get['p'])
info['uri'] = {}
info['return_js'] = ''
# 获取分页数据
result = {}
result['page'] = page.GetPage(info, limit='1,2,3,4,5,8')
n = 0
result['data'] = []
for i in range(info['count']):
if n >= page.ROW: break
if i < page.SHIFT: continue
n += 1
result['data'].append(data[i])
return result
def get_action_log(self, get):
"""
@name 取操作日志
@author hezhihong
@param get.user_name ftp用户名
return {"upload":[],"download":[],"rename":[],"delete":[]}
"""
search_str = 'pure-ftpd:'
args = public.dict_obj()
args.exec_name = 'getlog'
file_name = self.__ftp_backup_path
is_backup = True
if self.set_ftp_log(get) == 'stop':
file_name = self.__messages_file
is_backup = False
file_list = self.get_file_list(file_name, is_backup)
if not hasattr(get, 'user_name'):
return public.returnMsg(False, 'The parameter is incorrect')
data = []
tmp_data = []
sortid = 0
for file in file_list:
if not os.path.isfile(file['file']): continue
conf = public.readFile(file['file'])
lines = conf.split('\n')
for line in lines:
if not line: continue
action_info = {}
if search_str not in line: continue
tmp_v = line.split(search_str)
hostname = tmp_v[0].strip().split()[3].strip()
action_time = tmp_v[0].replace(hostname, '').strip()
action_info['time'] = self.get_format_time(action_time)
upload_value = ' uploaded '
download_value = ' downloaded '
rename_value = 'successfully renamed or moved:'
delete_value = ' Deleted '
ip = line[line.find('(') + 1:line.find(')')].split('@')[1]
action_info['ip'] = ip
action_info['type'] = ''
#取操作用户
user = ''
if upload_value in line or download_value in line or rename_value in line or delete_value in line:
user = line[line.find('(') +
1:line.find(')')].split('@')[0]
action_info['sortid'] = sortid
sortid = sortid + 1
if not user or user != get.user_name: continue
#取上传日志
if (get.type == 'all'
or get.type == 'upload') and upload_value in line:
line_list = line.split()
upload_index = line_list.index('uploaded')
# action_info['file'] = line_list[upload_index - 1].replace(
# '//', '/')
action_info['file'] = line[line.find(']') +
1:line.rfind('(')].replace(
'uploaded',
'').replace('//',
'/').strip()
action_info['type'] = 'upload'
tmp_data.append(action_info)
#取下载日志
if (get.type == 'all'
or get.type == 'download') and download_value in line:
line_list = line.split()
upload_index = line_list.index('downloaded')
action_info['file'] = line_list[upload_index - 1].replace(
'//', '/')
action_info['type'] = 'download'
tmp_data.append(action_info)
#取重命名日志
if (get.type == 'all'
or get.type == 'rename') and rename_value in line:
action_info['file'] = line.split(rename_value)[1].replace(
'->', 'Renamed to').strip().replace('//', '/')
action_info['type'] = 'rename'
tmp_data.append(action_info)
#取删除日志
if (get.type == 'all'
or get.type == 'delete') and delete_value in line:
action_info['file'] = line.split()[-1].strip().replace(
'//', '/')
action_info['type'] = 'delete'
tmp_data.append(action_info)
# f.close
#搜索过滤
if tmp_data and 'search' in get and get.search and get.search.strip():
for info in tmp_data:
search_str = str(get.search).strip().lower()
if info['ip'].find(search_str) != -1 or info['file'].lower(
).find(search_str) != -1 or info['type'].find(
search_str) != -1 or info['time'].find(
search_str) != -1 or get.user_name.lower().find(
search_str) != -1:
data.append(info)
else:
for info2 in tmp_data:
data.append(info2)
data = sorted(data, key=lambda x: x['sortid'], reverse=True)
return self.get_page(data, get)
def del_crontab(self):
"""
@name 删除项目定时清理任务
@auther hezhihong<2022-10-31>
@return
"""
cron_name = '[Do not delete] FTP audit log cutting task'
cron_path = public.GetConfigValue('setup_path') + '/cron/'
cron_list = public.M('crontab').where("name=?", (cron_name, )).select()
if cron_list:
for i in cron_list:
if not i: continue
cron_echo = public.M('crontab').where(
"id=?", (i['id'], )).getField('echo')
args = {"id": i['id']}
import crontab
crontab.crontab().DelCrontab(args)
del_cron_file = cron_path + cron_echo
public.ExecShell(
"crontab -u root -l| grep -v '{}'|crontab -u root -".
format(del_cron_file))
def add_crontab(self):
"""
@name 构造日志切割任务
"""
python_path = ''
try:
python_path = public.ExecShell('which btpython')[0].strip("\n")
except:
try:
python_path = public.ExecShell('which python')[0].strip("\n")
except:
pass
if not python_path: return False
if not public.M('crontab').where('name=?',
('[Do not delete] FTP audit log cutting task', )).count():
cmd = '{} {}'.format(python_path, self.__script_py)
args = {
"name": "[Do not delete] FTP audit log cutting task",
"type": 'day',
"where1": '',
"hour": '0',
"minute": '1',
"sName": "",
"sType": 'toShell',
"notice": '0',
"notice_channel": '',
"save": '',
"save_local": '1',
"backupTo": '',
"sBody": cmd,
"urladdress": ''
}
import crontab
res = crontab.crontab().AddCrontab(args)
if res and "id" in res.keys():
return True
return False
return True
+119 -18
View File
@@ -16,33 +16,73 @@ import public
import json
import socket
import requests
import config
import requests.packages.urllib3.util.connection as urllib3_conn
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class http:
_ip_type = None
def __init__(self):
self._ip_type = config.config().get_request_iptype()
def get(self,url,timeout = 60,headers = {},verify = False,type = 'python'):
url = self.quote(url)
if type == 'python':
if type in ['python','src','php']:
old_family = urllib3_conn.allowed_gai_family
try:
# 默认使用IPv4
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
return requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify)
except:
if self._ip_type == 'ipv4':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
elif self._ip_type == 'ipv6':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6
result = requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify)
except Exception as ex:
# 可能使用了错误的family,尝试清除相关配置
if(str(ex).find('Cannot assign requested address') != -1):
v_file = '{}/data/v4.pl'.format(public.get_panel_path())
public.writeFile(v_file,'')
self._ip_type = 'auto'
try:
# IPV6
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6
return requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify)
if self._ip_type != 'ipv6':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6
result = requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify)
else:
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
result = requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify)
except:
# 使用CURL
result = self._get_curl(url,timeout,headers,verify)
urllib3_conn.allowed_gai_family = old_family
finally:
urllib3_conn.allowed_gai_family = old_family
elif type == 'curl':
result = self._get_curl(url,timeout,headers,verify)
if result.status_code == 0:
if self._ip_type == 'ipv4':
self._ip_type = 'ipv6'
elif self._ip_type == 'ipv6':
self._ip_type = 'ipv4'
else:
return result
result = self._get_curl(url,timeout,headers,verify)
if result.status_code != 0:
self.save_ip_type()
elif type == 'php':
result = self._get_php(url,timeout,headers,verify)
if result.status_code == 0:
if self._ip_type == 'ipv4':
self._ip_type = 'ipv6'
elif self._ip_type == 'ipv6':
self._ip_type = 'ipv4'
else:
return result
result = self._get_php(url,timeout,headers,verify)
if result.status_code != 0:
self.save_ip_type()
elif type == 'src':
if sys.version_info[0] == 2:
result = self._get_py2(url,timeout,headers,verify)
@@ -52,16 +92,25 @@ class http:
def post(self,url,data,timeout = 60,headers = {},verify = False,type = 'python'):
url = self.quote(url)
if type == 'python':
if type in ['python','src','php']:
old_family = urllib3_conn.allowed_gai_family
try:
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
return requests.post(url,data,timeout=timeout,headers=headers,verify=verify)
if self._ip_type == 'ipv4':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
elif self._ip_type == 'ipv6':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6
result = requests.post(url,data,timeout=timeout,headers=headers,verify=verify)
except:
public.print_log(public.get_error_info())
try:
# IPV6
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6
return requests.post(url,data,timeout=timeout,headers=headers,verify=verify)
if self._ip_type != 'ipv6':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6
result = requests.post(url,data,timeout=timeout,headers=headers,verify=verify)
else:
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
result = requests.post(url,data,timeout=timeout,headers=headers,verify=verify)
except:
# 使用CURL
result = self._post_curl(url,data,timeout,headers,verify)
@@ -69,8 +118,30 @@ class http:
elif type == 'curl':
result = self._post_curl(url,data,timeout,headers,verify)
if result.status_code == 0:
if self._ip_type == 'ipv4':
self._ip_type = 'ipv6'
elif self._ip_type == 'ipv6':
self._ip_type = 'ipv4'
else:
return result
result = self._post_curl(url,data,timeout,headers,verify)
# 保存有效的请求IP类型
if result.status_code != 0:
self.save_ip_type()
elif type == 'php':
result = self._post_php(url,data,timeout,headers,verify)
if result.status_code == 0:
if self._ip_type == 'ipv4':
self._ip_type = 'ipv6'
elif self._ip_type == 'ipv6':
self._ip_type = 'ipv4'
else:
return result
result = self._post_php(url,data,timeout,headers,verify)
if result.status_code != 0:
self.save_ip_type()
elif type == 'src':
if sys.version_info[0] == 2:
result = self._post_py2(url,data,timeout,headers,verify)
@@ -79,6 +150,16 @@ class http:
return result
def save_ip_type(self):
v_file = '{}/data/v4.pl'.format(public.get_panel_path())
v_body = 'auto'
if self._ip_type == 'ipv4':
v_body = '-4'
elif self._ip_type == 'ipv6':
v_body = '-6'
public.writeFile(v_file,v_body)
def download_file(self,url,filename,data = None,timeout = 1800,speed_file='/dev/shm/download_speed.pl'):
'''
@name 下载文件
@@ -151,6 +232,11 @@ class http:
php_version = self._get_php_version()
if not php_version:
raise Exception('No PHP version available!')
ip_type = ''
if self._ip_type == 'ipv6':
ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);'
elif self._ip_type == 'ipv4':
ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);'
tmp_file = '/dev/shm/http.php'
http_php = '''<?php
error_reporting(E_ERROR);
@@ -173,14 +259,14 @@ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $data['verify']);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $data['verify']);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $data['timeout']);
curl_setopt($ch, CURLOPT_TIMEOUT, $data['timeout']);
curl_setopt($ch, CURLOPT_POST, true);
{ip_type}
$result = curl_exec($ch);
$h_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($result, 0, $h_size);
$body = substr($result,$h_size,strlen($result));
curl_close($ch);
exit($header."\r\n\r\n".json_encode($body));
?>'''
?>'''.format(ip_type = ip_type)
public.writeFile(tmp_file,http_php)
#if 'Content-Type' in headers:
# if headers['Content-Type'].find('application/json') != -1:
@@ -263,6 +349,11 @@ exit($header."\r\n\r\n".json_encode($body));
php_version = self._get_php_version()
if not php_version:
raise Exception('No PHP version available!')
ip_type = ''
if self._ip_type == 'ipv6':
ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);'
elif self._ip_type == 'ipv4':
ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);'
tmp_file = '/dev/shm/http.php'
http_php = '''<?php
error_reporting(E_ERROR);
@@ -284,6 +375,7 @@ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $data['verify']);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $data['verify']);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $data['timeout']);
curl_setopt($ch, CURLOPT_TIMEOUT, $data['timeout']);
{ip_type}
curl_setopt($ch, CURLOPT_POST, false);
$result = curl_exec($ch);
$h_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
@@ -291,7 +383,8 @@ $header = substr($result, 0, $h_size);
$body = substr($result,$h_size,strlen($result));
curl_close($ch);
exit($header."\r\n\r\n".json_encode($body));
?>'''
?>'''.format(ip_type=ip_type)
public.writeFile(tmp_file,http_php)
data = json.dumps({"url":url,"timeout":timeout,"verify":verify,"headers":self._php_headers(headers)})
if php_version in ['53']:
@@ -305,8 +398,8 @@ exit($header."\r\n\r\n".json_encode($body));
if os.path.exists(tmp_file): os.remove(tmp_file)
r_body,r_headers,r_status_code = self._curl_format(result)
return response(json.loads(r_body).strip(),r_status_code,r_headers)
#取可用的PHP版本
def _get_php_version(self):
@@ -331,7 +424,15 @@ exit($header."\r\n\r\n".json_encode($body));
curl_bin = 'curl'
for cb in c_bin:
if os.path.exists(cb): curl_bin = cb
if os.path.exists(cb): return cb
if self._ip_type != 'auto':
v4_file = '{}/data/v4.pl'.format(public.get_panel_path())
v4_body = public.readFile(v4_file).strip()
if not self._ip_type in v4_body:
if self._ip_type == 'ipv4':
v4_body = '-4'
else:
v4_body = '-6'
curl_bin += ' {}'.format(v4_body)
return curl_bin
#格式化CURL响应头
Regular → Executable
+343 -108
View File
@@ -10,9 +10,123 @@ import time,public,db,os,sys,json,re,shutil
os.chdir('/www/server/panel')
def control_init():
public.chdck_salt()
clear_other_files()
sql_pacth()
#disable_putenv('putenv')
#clean_session()
#set_crond()
clean_max_log('/www/server/panel/plugin/rsync/lsyncd.log')
clean_max_log('/var/log/rsyncd.log',1024*1024*10)
clean_max_log('/root/.pm2/pm2.log',1024*1024*20)
remove_tty1()
clean_hook_log()
run_new()
clean_max_log('/www/server/cron',1024*1024*5,20)
clean_max_log("/www/server/panel/plugin/webhook/script",1024*1024*1)
#check_firewall()
check_dnsapi()
clean_php_log()
files_set_mode()
set_pma_access()
# public.set_open_basedir()
clear_fastcgi_safe()
update_py37()
run_script()
set_php_cli_env()
check_enable_php()
#sync_node_list()
check_default_curl_file()
null_html()
remove_other()
deb_bashrc()
upgrade_gevent()
upgrade_polkit()
#hide_docker()
rep_pyenv_link()
rm_apache_cgi_test()
def rm_apache_cgi_test():
'''
@name 删除apache测试cgi文件
@author hwliang
@return void
'''
test_cgi_file = '/www/server/apache/cgi-bin/test-cgi'
if os.path.exists(test_cgi_file):
os.remove(test_cgi_file)
def rep_pyenv_link():
'''
@name 修复pyenv环境软链
@author hwliang
@return void
'''
pyenv_bin = '/www/server/panel/pyenv/bin/python3'
btpython_bin = '/usr/bin/btpython'
pip_bin = '/www/server/panel/pyenv/bin/pip3'
btpip_bin = '/usr/bin/btpip'
# 检查btpython软链接
if not os.path.exists(pyenv_bin): return
if not os.path.exists(btpython_bin):
public.ExecShell("ln -sf {} {}".format(pyenv_bin,btpython_bin))
# 检查btpip软链接
if not os.path.exists(pip_bin): return
if not os.path.exists(btpip_bin):
public.ExecShell("ln -sf {} {}".format(pip_bin,btpip_bin))
def hide_docker():
'''
@name 隐藏docker菜单
@author hwliang
@return void
'''
tip_file = '{}/data/hide_docker.pl'.format(public.get_panel_path())
if os.path.exists(tip_file): return
# 正在使用docker-compose的用户不隐藏
docker_compose = "/usr/bin/docker-compose"
if os.path.exists(docker_compose): return
# 获取隐藏菜单配置
menu_key = 'memuDocker'
hide_menu_json = public.read_config('hide_menu')
if not isinstance(hide_menu_json,list):
hide_menu_json = []
if menu_key in hide_menu_json: return
# 保存隐藏菜单配置
hide_menu_json.append(menu_key)
public.save_config('hide_menu',hide_menu_json)
public.writeFile(tip_file,'True')
def upgrade_polkit():
'''
@name 修复polkit提权漏洞(CVE-2021-4034)
@author hwliang
@return void
'''
upgrade_log_file = '{}/logs/upgrade_polkit.log'.format(public.get_panel_path())
tip_file = '{}/data/upgrade_polkit.pl'.format(public.get_panel_path())
if os.path.exists(tip_file): return
os.system("nohup {} {}/script/polkit_upgrade.py &> {}".format(public.get_python_bin(),public.get_panel_path(),upgrade_log_file))
def clear_other_files():
dirPath = '/www/server/phpmyadmin/pma'
if os.path.exists(dirPath):
public.ExecShell("rm -rf {}".format(dirPath))
dirPath = '/www/server/nginx/waf'
if os.path.exists(dirPath):
public.ExecShell("rm -rf {}".format(dirPath))
public.ExecShell("/etc/init.d/nginx reload")
public.ExecShell("/etc/init.d/nginx start")
dirPath = '/www/server/adminer'
if os.path.exists(dirPath):
@@ -22,9 +136,60 @@ def control_init():
if os.path.exists(dirPath):
public.ExecShell("rm -rf {}".format(dirPath))
filename = '/www/server/nginx/off'
if os.path.exists(filename): os.remove(filename)
filename = "{}/vhost/nginx/waf.conf".format(public.get_panel_path())
if os.path.exists(filename):
os.remove(filename)
public.ExecShell("/etc/init.d/nginx reload")
public.ExecShell("/etc/init.d/nginx start")
c = public.to_string([99, 104, 97, 116, 116, 114, 32, 45, 105, 32, 47, 119, 119, 119, 47,
115, 101, 114, 118, 101, 114, 47, 112, 97, 110, 101, 108, 47, 99,
108, 97, 115, 115, 47, 42])
try:
init_file = '/etc/init.d/bt'
src_file = '/www/server/panel/init.sh'
md51 = public.md5(init_file)
md52 = public.md5(src_file)
if md51 != md52:
import shutil
shutil.copyfile(src_file,init_file)
if os.path.getsize(init_file) < 10:
public.ExecShell("chattr -i " + init_file)
public.ExecShell("\cp -arf %s %s" % (src_file,init_file))
public.ExecShell("chmod +x %s" % init_file)
except:pass
public.writeFile('/var/bt_setupPath.conf','/www')
public.ExecShell(c)
p_file = 'class/plugin2.so'
if os.path.exists(p_file): public.ExecShell("rm -f class/*.so")
public.ExecShell("chmod -R 600 /www/server/panel/data;chmod -R 600 /www/server/panel/config;chmod -R 700 /www/server/cron;chmod -R 600 /www/server/cron/*.log;chown -R root:root /www/server/panel/data;chown -R root:root /www/server/panel/config;chown -R root:root /www/server/phpmyadmin;chmod -R 755 /www/server/phpmyadmin")
if os.path.exists("/www/server/mysql"):
public.ExecShell("chown mysql:mysql /etc/my.cnf;chmod 600 /etc/my.cnf")
public.ExecShell("rm -rf /www/server/panel/temp/*")
stop_path = '/www/server/stop'
if not os.path.exists(stop_path):
os.makedirs(stop_path)
public.ExecShell("chown -R root:root {path};chmod -R 755 {path}".format(path=stop_path))
public.ExecShell('chmod 755 /www;chmod 755 /www/server')
if os.path.exists('/www/server/phpmyadmin/pma'):
public.ExecShell("rm -rf /www/server/phpmyadmin/pma")
if os.path.exists("/www/server/adminer"):
public.ExecShell("rm -rf /www/server/adminer")
if os.path.exists("/www/server/panel/adminer"):
public.ExecShell("rm -rf /www/server/panel/adminer")
if os.path.exists('/dev/shm/session.db'):
os.remove('/dev/shm/session.db')
time.sleep(1)
node_service_bin = '/usr/bin/nodejs-service'
node_service_src = '/www/server/panel/script/nodejs-service.py'
if os.path.exists(node_service_src): public.ExecShell("chmod 700 " + node_service_src)
if not os.path.exists(node_service_bin):
if os.path.exists(node_service_src):
public.ExecShell("ln -sf {} {}".format(node_service_src,node_service_bin))
def sql_pacth():
sql = db.Sql().dbfile('system')
if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'load_average')).count():
csql = '''CREATE TABLE IF NOT EXISTS `load_average` (
@@ -39,6 +204,9 @@ def control_init():
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%type_id%')).count():
public.M('sites').execute("alter TABLE sites add type_id integer DEFAULT 0",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'database_servers','%db_type%')).count():
public.M('databases').execute("alter TABLE database_servers add db_type REAL DEFAULT 'mysql'",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%edate%')).count():
public.M('sites').execute("alter TABLE sites add edate integer DEFAULT '0000-00-00'",())
@@ -60,6 +228,30 @@ def control_init():
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'databases','%sid%')).count():
public.M('databases').execute("alter TABLE databases add sid integer DEFAULT 0",())
ndb = public.M('databases').order("id desc").field('id,pid,name,username,password,accept,ps,addtime,type').select()
if type(ndb) == str: public.M('databases').execute("alter TABLE databases add type TEXT DEFAULT MySQL",())
# 计划任务表处理
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%status%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'status' INTEGER DEFAULT 1",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%save%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'save' INTEGER DEFAULT 3",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%backupTo%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'backupTo' TEXT DEFAULT off",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%sName%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sName' TEXT",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%sBody%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sBody' TEXT",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%sType%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sType' TEXT",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%urladdress%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'urladdress' TEXT",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%save_local%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'save_local' INTEGER DEFAULT 0",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%notice%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'notice' INTEGER DEFAULT 0",())
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%notice_channel%')).count():
public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'notice_channel' TEXT DEFAULT ''",())
sql = db.Sql()
if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'site_types')).count():
@@ -122,7 +314,17 @@ def control_init():
)'''
sql.execute(csql,())
if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'security')).count():
csql = '''CREATE TABLE IF NOT EXISTS `security` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`type` TEXT,
`log` TEXT,
`addtime` INTEGER DEFAULT 0
)'''
sql.execute(csql, ())
test_ping()
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'logs','%username%')).count():
public.M('logs').execute("alter TABLE logs add uid integer DEFAULT '1'",())
public.M('logs').execute("alter TABLE logs add username TEXT DEFAULT 'system'",())
@@ -141,82 +343,94 @@ def control_init():
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'users','%salt%')).count():
public.M('users').execute("ALTER TABLE 'users' ADD 'salt' TEXT",())
public.chdck_salt()
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'messages','%retry_num%')).count():
public.M('messages').execute("alter TABLE messages add send integer DEFAULT 0",())
public.M('messages').execute("alter TABLE messages add retry_num integer DEFAULT 0",())
def upgrade_gevent():
'''
@name 升级gevent
@author hwliang
@return void
'''
tip_file = '{}/data/upgrade_gevent.lock'.format(public.get_panel_path())
upgrade_script_file = '{}/script/upgrade_gevent.sh'.format(public.get_panel_path())
if os.path.exists(upgrade_script_file) and not os.path.exists(tip_file):
public.writeFile(tip_file,'1')
os.system("bash {}".format(upgrade_script_file))
if os.path.exists(tip_file): os.remove(tip_file)
def deb_bashrc():
'''
@name 针对debian/ubuntu未调用bashrc导致的问题
@author hwliang
@return void
'''
bashrc = '/root/.bashrc'
bash_profile = '/root/.bash_profile'
apt_get = '/usr/bin/apt-get'
if not os.path.exists(apt_get): return
if not os.path.exists(bashrc): return
if not os.path.exists(bash_profile): return
profile_body = public.readFile(bash_profile)
if not isinstance(profile_body,str): return
if profile_body.find('.bashrc') == -1:
public.writeFile(bash_profile,'source ~/.bashrc\n' + profile_body.strip() + "\n")
filename = '/www/server/nginx/off'
if os.path.exists(filename): os.remove(filename)
c = public.to_string([99, 104, 97, 116, 116, 114, 32, 45, 105, 32, 47, 119, 119, 119, 47,
115, 101, 114, 118, 101, 114, 47, 112, 97, 110, 101, 108, 47, 99,
108, 97, 115, 115, 47, 42])
try:
init_file = '/etc/init.d/bt'
src_file = '/www/server/panel/init.sh'
md51 = public.md5(init_file)
md52 = public.md5(src_file)
if md51 != md52:
import shutil
shutil.copyfile(src_file,init_file)
if os.path.getsize(init_file) < 10:
public.ExecShell("chattr -i " + init_file)
public.ExecShell("\cp -arf %s %s" % (src_file,init_file))
public.ExecShell("chmod +x %s" % init_file)
except:pass
public.writeFile('/var/bt_setupPath.conf','/www')
public.ExecShell(c)
p_file = 'class/plugin2.so'
if os.path.exists(p_file): public.ExecShell("rm -f class/*.so")
public.ExecShell("chmod -R 600 /www/server/panel/data;chmod -R 600 /www/server/panel/config;chmod -R 700 /www/server/cron;chmod -R 600 /www/server/cron/*.log;chown -R root:root /www/server/panel/data;chown -R root:root /www/server/panel/config;chown -R root:root /www/server/phpmyadmin;chmod -R 755 /www/server/phpmyadmin")
if os.path.exists("/www/server/mysql"):
public.ExecShell("chown mysql:mysql /etc/my.cnf;chmod 600 /etc/my.cnf")
public.ExecShell("rm -rf /www/server/panel/temp/*")
if not public.is_debug():
public.ExecShell("rm -f /www/server/panel/class/pluginAuth.py")
stop_path = '/www/server/stop'
if not os.path.exists(stop_path):
os.makedirs(stop_path)
public.ExecShell("chown -R root:root {path};chmod -R 755 {path}".format(path=stop_path))
public.ExecShell('chmod 755 /www;chmod 755 /www/server')
if os.path.exists('/www/server/phpmyadmin/pma'):
public.ExecShell("rm -rf /www/server/phpmyadmin/pma")
if os.path.exists("/www/server/adminer"):
public.ExecShell("rm -rf /www/server/adminer")
if os.path.exists("/www/server/panel/adminer"):
public.ExecShell("rm -rf /www/server/panel/adminer")
if os.path.exists('/dev/shm/session.db'):
os.remove('/dev/shm/session.db')
def remove_other():
rm_files = [
"class/pluginAuth.so",
"class/pluginAuth.cpython-310-x86_64-linux-gnu.so",
"class/pluginAuth.cpython-310-aarch64-linux-gnu.so",
"class/pluginAuth.cpython-37m-i386-linux-gnu.so",
"class/pluginAuth.cpython-37m-loongarch64-linux-gnu.so",
"class/pluginAuth.cpython-37m-aarch64-linux-gnu.so",
"class/pluginAuth.cpython-37m-x86_64-linux-gnu.so",
"class/pluginAuth.cpython-37m.so",
"class/libAuth.loongarch64.so",
"class/libAuth.x86.so",
"class/libAuth.x86-64.so",
"class/libAuth.glibc-2.14.x86_64.so",
"class/libAuth.aarch64.so",
"script/check_files.py"
]
node_service_bin = '/usr/bin/nodejs-service'
node_service_src = '/www/server/panel/script/nodejs-service.py'
if os.path.exists(node_service_src): public.ExecShell("chmod 700 " + node_service_src)
if not os.path.exists(node_service_bin):
if os.path.exists(node_service_src):
public.ExecShell("ln -sf {} {}".format(node_service_src,node_service_bin))
for f in rm_files:
if os.path.exists(f):
os.remove(f)
#disable_putenv('putenv')
#clean_session()
#set_crond()
test_ping()
set_wp_cache_dir()
clean_max_log('/www/server/panel/plugin/rsync/lsyncd.log')
clean_max_log('/var/log/rsyncd.log',1024*1024*10)
clean_max_log('/root/.pm2/pm2.log',1024*1024*20)
remove_tty1()
clean_hook_log()
run_new()
clean_max_log('/www/server/cron',1024*1024*5,20)
#check_firewall()
check_dnsapi()
clean_php_log()
files_set_mode()
set_pma_access()
# public.set_open_basedir()
clear_fastcgi_safe()
update_py37()
run_script()
set_php_cli_env()
check_enable_php()
def null_html():
null_files = ['/www/server/nginx/html/index.html','/www/server/apache/htdocs/index.html','/www/server/panel/data/404.html']
null_new_body='''<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>'''
for null_file in null_files:
if not os.path.exists(null_file): continue
null_body = public.readFile(null_file)
if not null_body: continue
if null_body.find('没有找到站点') != -1 or null_body.find('您请求的文件不存在') != -1:
public.writeFile(null_file,null_new_body)
def check_default_curl_file():
default_file = '{}/data/default_curl.pl'.format(public.get_panel_path())
if os.path.exists(default_file):
default_curl_body = public.readFile(default_file)
if default_curl_body:
public.WriteFile(default_file,default_curl_body.strip())
def set_wp_cache_dir():
import one_key_wp
@@ -242,9 +456,9 @@ def set_php_cli_env():
env_php_bin = '/usr/bin/php'
if os.path.exists(env_php_bin):
if os.path.islink(env_php_bin):
env_bin_version = os.readlink(env_php_bin).split('/')[-3]
php_cli_ini = "{}/{}/etc/php-cli.ini".format(php_path,env_bin_version)
bashrc_body += "alias php='php -c {}'\n".format(php_cli_ini)
php_cli_ini = "/etc/php-cli.ini"
if os.path.exists(php_cli_ini):
bashrc_body += "alias php='php -c {}'\n".format(php_cli_ini)
# 设置所有已安装的PHP版本环境变量和别名
@@ -313,43 +527,46 @@ def write_run_script_log(_log,rn='\n'):
def run_script():
os.system("{} {}/script/run_script.py".format(public.get_python_bin(),public.get_panel_path()))
run_tip = '/dev/shm/bt.pl'
if os.path.exists(run_tip): return
public.writeFile(run_tip,str(time.time()))
uptime = int(public.readFile('/proc/uptime').split()[0])
if uptime > 1800: return
run_config ='/www/server/panel/data/run_config'
script_logs = '/www/server/panel/logs/script_logs'
if not os.path.exists(run_config):
os.makedirs(run_config,384)
if not os.path.exists(script_logs):
os.makedirs(script_logs,384)
try:
os.system("{} {}/script/run_script.py".format(public.get_python_bin(),public.get_panel_path()))
run_tip = '/dev/shm/bt.pl'
if os.path.exists(run_tip): return
public.writeFile(run_tip,str(time.time()))
uptime = float(public.readFile('/proc/uptime').split()[0])
if uptime > 1800: return
run_config ='/www/server/panel/data/run_config'
script_logs = '/www/server/panel/logs/script_logs'
if not os.path.exists(run_config):
os.makedirs(run_config,384)
if not os.path.exists(script_logs):
os.makedirs(script_logs,384)
for sname in os.listdir(run_config):
script_conf_file = '{}/{}'.format(run_config,sname)
if not os.path.exists(script_conf_file): continue
script_info = json.loads(public.readFile(script_conf_file))
exec_log_file = '{}/{}'.format(script_logs,sname)
for sname in os.listdir(run_config):
script_conf_file = '{}/{}'.format(run_config,sname)
if not os.path.exists(script_conf_file): continue
script_info = json.loads(public.readFile(script_conf_file))
exec_log_file = '{}/{}'.format(script_logs,sname)
if not os.path.exists(script_info['script_file']) \
or script_info['script_file'].find('/www/server/panel/plugin/') != 0 \
or not re.match('^\w+$',script_info['script_file']):
os.remove(script_conf_file)
if os.path.exists(exec_log_file): os.remove(exec_log_file)
continue
if not os.path.exists(script_info['script_file']) \
or script_info['script_file'].find('/www/server/panel/plugin/') != 0 \
or not re.match('^\w+$',script_info['script_file']):
os.remove(script_conf_file)
if os.path.exists(exec_log_file): os.remove(exec_log_file)
continue
if script_info['script_type'] == 'python':
_bin = public.get_python_bin()
elif script_info['script_type'] == 'bash':
_bin = '/usr/bin/bash'
if not os.path.exists(_bin): _bin = 'bash'
if script_info['script_type'] == 'python':
_bin = public.get_python_bin()
elif script_info['script_type'] == 'bash':
_bin = '/usr/bin/bash'
if not os.path.exists(_bin): _bin = 'bash'
exec_script = 'nohup {} {} &> {} &'.format(_bin,script_info['script_file'],exec_log_file)
public.ExecShell(exec_script)
script_info['last_time'] = time.time()
public.writeFile(script_conf_file,json.dumps(script_info))
exec_script = 'nohup {} {} &> {} &'.format(_bin,script_info['script_file'],exec_log_file)
public.ExecShell(exec_script)
script_info['last_time'] = time.time()
public.writeFile(script_conf_file,json.dumps(script_info))
except:
pass
def clear_fastcgi_safe():
@@ -404,7 +621,20 @@ def files_set_mode():
["/dev/shm/session_py3","","root",600,True],
["/dev/shm/session_py2","","root",600,True],
["/www/server/phpmyadmin","","root",755,True],
["/www/server/coll","","root",700,True]
["/www/server/coll","","root",700,True],
["/www/server/panel/init.sh","","root",600,False],
["/www/server/panel/license.txt","","root",600,False],
["/www/server/panel/requirements.txt","","root",600,False],
["/www/server/panel/update.sh","","root",600,False],
["/www/server/panel/default.pl","","root",600,False],
["/www/server/panel/hooks","","root",600,True],
["/www/server/panel/cache","","root",600,True],
["/root","","root",550,False],
["/root/.ssh","","root",700,False],
["/root/.ssh/authorized_keys","","root",600,False],
["/root/.ssh/id_rsa.pub","","root",644,False],
["/root/.ssh/id_rsa","","root",600,False],
["/root/.ssh/known_hosts","","root",644,False]
]
recycle_list = public.get_recycle_bin_list()
@@ -420,6 +650,9 @@ def files_set_mode():
public.ExecShell("chown {U}:{U} {P}".format(P=m[0],U=m[2],R=rr[m[4]]))
public.ExecShell("chmod {M} {P}".format(P=m[0],M=m[3],R=rr[m[4]]))
# 移除面板目录下所有文件的所属组、其它用户的写权限
public.ExecShell("chmod -R go-w /www/server/panel")
#获取PMA目录
def get_pma_path():
pma_path = '/www/server/phpmyadmin'
@@ -584,7 +817,9 @@ def clean_hook_log():
def clean_php_log():
path = '/www/server/php'
if not os.path.exists(path): return False
php_list=public.get_php_versions()
for name in os.listdir(path):
if name not in php_list:continue
filename = path +'/'+name + '/var/log/php-fpm.log'
if os.path.exists(filename): clean_max_log(filename)
filename = path +'/'+name + '/var/log/php-fpm-test.log'
+260
View File
@@ -0,0 +1,260 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: lkq <lkq@bt.cn>
# |
# | 日志分析工具
# +-------------------------------------------------------------------
import os
import time
import public
class log_analysis:
path = '/www/server/panel/script/'
log_analysis_path = '/www/server/panel/script/log_analysis.sh'
def __init__(self):
if not os.path.exists(self.path + '/log/'): os.makedirs(self.path + '/log/')
if not os.path.exists(self.log_analysis_path):
log_analysis_data = '''help(){
echo "Usage: ./action.sh [options] [FILE] [OUTFILE] "
echo "Options:"
echo "xxx.sh san_log [FILE] Get the log list with the keywords xss|sql|mingsense information|php code execution in the successful request [OUTFILE] 11"
echo "xxx.sh san [FILE] Get list of logs with sql keyword in successful request [OUTFILE] 11 "
}
if [ $# == 0 ]
then
help
exit
fi
if [ ! -e $2 ]
then
echo -e "$2: log file does not exist"
exit
fi
if [ ! -d "log" ]
then
mkdir log
fi
echo "[*] Starting ..."
if [ $1 == "san_log" ]
then
echo "1">./log/$3
echo "Start getting xss cross-site scripting attack logs..."
grep -E ' (200|302|301|500|444|403|304) ' $2 | grep -i -E "(javascript|data:|alert\(|onerror=|%3Cimg%20src=x%20on.+=|%3Cscript|%3Csvg/|%3Ciframe/|%3Cscript%3E).*?HTTP/1.1" >./log/$3xss.log
echo "Analysis logs have been saved to./log/$3xss.log"
echo "Scan to attack count: "`cat ./log/$3xss.log |wc -l`
echo "20">./log/$3
echo "Start getting sql injection attack logs..."
echo "Analysis logs have been saved to./log/$3sql.log"
grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(from.+?information_schema.+|select.+(from|limit)|union(.*?)select|extractvalue\(|case when|extractvalue\(|updatexml\(|sleep\().*?HTTP/1.1" > ./log/$3sql.log
echo "Scan to attack count: "`cat ./log/$3sql.log |wc -l`
echo "40">./log/$3
echo -e "Start getting related logs such as file traversal/code execution/scanner information/configuration files"
grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(\.\.|WEB-INF|/etc|\w\{1,6\}\.jsp |\w\{1,6\}\.php|\w+\.xml |\w+\.log |\w+\.swp |\w*\.git |\w*\.svn |\w+\.json |\w+\.ini |\w+\.inc |\w+\.rar |\w+\.gz |\w+\.tgz|\w+\.bak |/resin-doc).*?HTTP/1.1" >./log/$3san.log
echo "Analysis logs have been saved to./log/$3san.log"
echo "Scan to attack count: "`cat ./log/$3san.log |wc -l`
echo "50">./log/$3
echo -e "Start getting the php code execution scan log"
grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(gopher://|php://|file://|phar://|dict://data://|eval\(|file_get_contents\(|phpinfo\(|require_once\(|copy\(|\_POST\[|file_put_contents\(|system\(|base64_decode\(|passthru\(|\/invokefunction\&|=call_user_func_array).*?HTTP/1.1" >./log/$3php.log
echo "Analysis logs have been saved to./log/$3php.log"
echo "Scan to attack count: "`cat ./log/$3php.log |wc -l`
echo "60">./log/$3
echo -e "The number and value of the most visited ip is being counted"
# cat $2|awk -F" " '{print $1}'|sort|uniq -c|sort -nrk 1 -t' '|head -100
awk '{print $1}' $2 |sort|uniq -c |sort -nr |head -100 >./log/$3ip.log
echo "80">./log/$3
echo -e "The number and value of the url of the most visited request interface is being counted"
awk '{print $7}' $2 |sort|uniq -c |sort -nr |head -100 >./log/$3url.log
echo "100">./log/$3
elif [ $1 == "san" ]
then
echo "1">./log/$3
echo "Start getting xss cross-site scripting attack logs..."
grep -E ' (200|302|301|500|444|403|304) ' $2 | grep -i -E "(javascript|data:|alert\(|onerror=|%3Cimg%20src=x%20on.+=|%3Cscript|%3Csvg/|%3Ciframe/|%3Cscript%3E).*?HTTP/1.1" >./log/$3xss.log
echo "Analysis logs have been saved to./log/$3xss.log"
echo "Scan to attack count: "`cat ./log/$3xss.log |wc -l`
echo "20">./log/$3
echo "Start getting sql injection attack logs..."
echo "Analysis logs have been saved to./log/$3sql.log"
grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(from.+?information_schema.+|select.+(from|limit)|union(.*?)select|extractvalue\(|case when|extractvalue\(|updatexml\(|sleep\().*?HTTP/1.1" > ./log/$3sql.log
echo "Scan to attack count: "`cat ./log/$3sql.log |wc -l`
echo "40">./log/$3
echo -e "Start getting related logs such as file traversal/code execution/scanner information/configuration files"
grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(\.\.|WEB-INF|/etc|\w\{1,6\}\.jsp |\w\{1,6\}\.php|\w+\.xml |\w+\.log |\w+\.swp |\w*\.git |\w*\.svn |\w+\.json |\w+\.ini |\w+\.inc |\w+\.rar |\w+\.gz |\w+\.tgz|\w+\.bak |/resin-doc).*?HTTP/1.1" >./log/$3san.log
echo "Analysis logs have been saved to./log/$3san.log"
echo "Scan to attack count: "`cat ./log/$3san.log |wc -l`
echo "60">./log/$3
echo -e "Start getting the php code execution scan log"
grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(gopher://|php://|file://|phar://|dict://data://|eval\(|file_get_contents\(|phpinfo\(|require_once\(|copy\(|\_POST\[|file_put_contents\(|system\(|base64_decode\(|passthru\(|\/invokefunction\&|=call_user_func_array).*?HTTP/1.1" >./log/$3php.log
echo "Analysis logs have been saved to./log/$3php.log"
echo "Scan to attack count: "`cat ./log/$3php.log |wc -l`
echo "100">./log/$3
else
help
fi
echo "[*] shut down"
'''
public.WriteFile(self.log_analysis_path, log_analysis_data)
def get_log_format(self, path):
'''
@获取日志格式
'''
f = open(path, 'r')
data = None
for i in f:
data = i.split()
break
f.close()
if not data: return False
if not public.check_ip(data[0]): return False
if len(data) < 6: return False
return True
def log_analysis(self, get):
'''
分析日志
@param path:需要分析的日志
@return 返回具体的分析结果
@ 需要使用异步的方式进行扫描
'''
path = get.path
log_path = public.Md5(path)
serverType = public.get_webserver()
if serverType == "nginx":
pass
elif serverType == 'apache':
#path = path.strip("-access_log") + '-access_log'
pass
elif serverType == 'openlitespeed':
# path = path.strip("_ols.access_log") + '_ols.access_log'
return public.ReturnMsg(False, 'openlitespeed is not supported yet')
public.print_log("path1:{}".format(path))
public.print_log("serverType:{}".format(serverType))
if not os.path.exists(path): return public.ReturnMsg(False, 'No log file')
if os.path.getsize(path) > 9433107294: return public.ReturnMsg(False, 'The log file is too large!')
if os.path.getsize(path) < 10: return public.ReturnMsg(False, 'log is empty')
# public.print_log("log_path{}".format(log_path))
# public.print_log("self.log_analysis_path{}".format(self.log_analysis_path))
# public.print_log("path{}".format(path))
if self.get_log_format(path):
public.ExecShell(
"cd %s && bash %s san_log %s %s &" % (self.path, self.log_analysis_path, path, log_path))
else:
public.ExecShell("cd %s && bash %s san %s %s &" % (self.path, self.log_analysis_path, path, log_path))
speed = self.path + '/log/' + log_path+".time"
public.WriteFile(speed,str(time.time())+"[]"+time.strftime('%Y-%m-%d %X',time.localtime())+"[]"+"0")
return public.ReturnMsg(True, 'Start scan successful')
def speed_log(self, get):
'''
扫描进度
@param path:扫描的日志文件
@return 返回进度
'''
path = get.path.strip()
log_path = public.Md5(path)
speed = self.path + '/log/' + log_path
if os.path.getsize(speed) < 1: return public.ReturnMsg(False, 'log is empty')
if not os.path.exists(speed): return public.ReturnMsg(False, 'The directory was not scanned')
try:
data = public.ReadFile(speed)
data = int(data)
if data==100:
time_data,start_time,status=public.ReadFile(self.path + '/log/' + log_path+".time").split("[]")
public.WriteFile(speed+".time",str(time.time()-float(time_data)) + "[]" + start_time + "[]" + "1")
return public.ReturnMsg(True, data)
except:
return public.ReturnMsg(True, 0)
def get_log_count(self, path, is_body=False):
count = 0
if is_body:
if not os.path.exists(path): return ''
data = ''
with open(path, 'r') as f:
for i in f:
count += 1
data = data.replace('<', '&lt;').replace('>', '&gt;') + i.replace('<', '&lt;').replace('>', '&gt;')
if count >= 300: break
return data
else:
if not os.path.exists(path): return count
with open(path, 'rb') as f:
for i in f:
count += 1
return count
def get_result(self, get):
'''
扫描结果
@param path:扫描的日志文件
@return 返回结果
'''
path = get.path.strip()
log_path = public.Md5(path)
speed = self.path + '/log/' + log_path
result = {}
if os.path.exists(speed):
result['is_status'] = True
else:
result['is_status'] = False
if os.path.exists(speed+".time"):
time_data, start_time, status = public.ReadFile(self.path + '/log/' + log_path + ".time").split("[]")
if status == '1' or start_time==1:
result['time']=time_data
result['start_time']=start_time
else:
result['time'] = "0"
result['start_time'] = "2022/2/22 22:22:22"
if 'time' not in result:
result['time'] = "0"
result['start_time'] = "2022/2/22 22:22:22"
result['xss'] = self.get_log_count(speed + 'xss.log')
result['sql'] = self.get_log_count(speed + 'sql.log')
result['san'] = self.get_log_count(speed + 'san.log')
result['php'] = self.get_log_count(speed + 'php.log')
result['ip'] = self.get_log_count(speed + 'ip.log')
result['url'] = self.get_log_count(speed + 'url.log')
return result
def get_detailed(self, get):
path = get.path.strip()
log_path = public.Md5(path)
speed = self.path + '/log/' + log_path
type_list = ['xss', 'sql', 'san', 'php', 'ip', 'url']
if get.type not in type_list: return public.ReturnMsg(False, 'Type mismatch')
if not os.path.exists(speed + get.type + '.log'): return public.ReturnMsg(False, 'Record does not exist')
return self.get_log_count(speed + get.type + '.log', is_body=True)
+141
View File
@@ -0,0 +1,141 @@
#coding: utf-8
import os,sys,time,json
panelPath = os.getenv('BT_PANEL')
if not panelPath:
panelPath = "/www/server/panel"
os.chdir(panelPath)
if not panelPath + "/class/" in sys.path:
sys.path.insert(0, panelPath + "/class/")
import public,re
class logsBase:
def __init__(self):
pass
def find_line_str(self,_line,search):
"""
@name 查找字符串
"""
if search:
if _line.lower().find(search.lower()) != -1:
return True
else:
return True
return False
def return_line_area(self,logs_list,ip_list):
"""
@name 日志行返回归属地
"""
if len(logs_list) <= 0: return logs_list
n_data = '\r\n'.join(logs_list)
res = public.get_ips_area(ip_list)
for ip in ip_list:
area = 'Unknown'
if 'status' in res:
area = '**** (Professional version exclusive)'
elif ip in res:
area = res[ip]['info']
n_data = n_data.replace(ip,'{}({})'.format(ip,area))
log_list = n_data.split('\r\n')
return log_list
def GetNumLines(self,path, num, p=1,search = None):
"""
@name 取文件指定尾行数
@param path 文件路径
@param num 取尾行数
@param p 当前页
@param search 搜索关键字
@return list
"""
pyVersion = sys.version_info[0]
max_len = 1024 * 128 * 1024
try:
from html import escape
if not os.path.exists(path): return ""
start_line = (p - 1) * num
count = start_line + num
fp = open(path, 'rb')
buf = ""
fp.seek(-1, 2)
if fp.read(1) == "\n": fp.seek(-1, 2)
data = []
total_len = 0
b = True
n = 0
for i in range(count):
while True:
newline_pos = str.rfind(str(buf), "\n")
pos = fp.tell()
if newline_pos != -1:
if n >= start_line:
line = buf[newline_pos + 1:]
is_res = True
if search:
is_res = False
if line.find(search) >= 0 or re.search(search,line):
is_res = True
if is_res:
line_len = len(line)
total_len += line_len
sp_len = total_len - max_len
if sp_len > 0:
line = line[sp_len:]
try:
data.insert(0, escape(line))
except:
pass
buf = buf[:newline_pos]
n += 1
break
else:
if pos == 0:
b = False
break
to_read = min(4096, pos)
fp.seek(-to_read, 1)
t_buf = fp.read(to_read)
if pyVersion == 3:
try:
if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8',errors='ignore')
except:
try:
if type(t_buf) == bytes: t_buf = t_buf.decode('gbk',errors='ignore')
except:
t_buf = str(t_buf)
buf = t_buf + buf
fp.seek(-to_read, 1)
if pos - to_read == 0:
buf = "\n" + buf
if total_len >= max_len: break
if not b: break
fp.close()
result = "\n".join(data)
if not result: raise Exception('null')
except:
result = ''
if len(result) > max_len:
result = result[-max_len:]
try:
try:
result = json.dumps(result)
return json.loads(result).strip()
except:
if pyVersion == 2:
result = result.decode('utf8', errors='ignore')
else:
result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore")
return result.strip()
except:
return ""
+469
View File
@@ -0,0 +1,469 @@
#coding: utf-8
# + -------------------------------------------------------------------
# | 宝塔Linux面板
# + -------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# + -------------------------------------------------------------------
# | Author: hezhihong <272267659@@qq.cn>
# + -------------------------------------------------------------------
import public, os, time
from logsModel.base import logsBase
try:
from BTPanel import session
except:
pass
#英文转月份缩写
month_list = {
"Jan": "1",
"Feb": "2",
"Mar": "3",
"Apr": "4",
"May": "5",
"Jun": "6",
"Jul": "7",
"Aug": "8",
"Sept": "9",
"Sep": "9",
"Oct": "10",
"Nov": "11",
"Dec": "12"
}
class main(logsBase):
def __init__(self):
self.__messages_file = "/var/log/"
self.__ftp_backup_path = public.get_backup_path() + '/pure-ftpd/'
if not os.path.isdir(self.__ftp_backup_path):
public.ExecShell('mkdir -p {}'.format(self.__ftp_backup_path))
self.__script_py = public.get_panel_path() + '/script/ftplogs_cut.py'
def get_file_list(self, path, is_bakcup=False):
"""
@name 取所有messages日志文件
@param path: 日志文件路径
@return: 返回日志文件列表
"""
files = os.listdir(path)
if is_bakcup:
file_name_list = [{
"file": "/var/log/pure-ftpd.log",
"time": int(time.time())
}]
else:
file_name_list = []
for i in files:
tmp_dict = {}
if not i: continue
file_path = path + i
tmp_dict['file'] = file_path
if is_bakcup:
if os.path.isfile(file_path) and i.find('pure-ftpd.log') != -1:
tmp_dict['time'] = int(
public.to_date(
times=os.path.basename(file_path).split('_')[0] +
' 00:00:00'))
file_name_list.append(tmp_dict)
else:
if os.path.isfile(file_path) and i.find('messages') != -1:
tmp_dict['time'] = int(
public.to_date(
times=os.path.basename(file_path).split('-')[1] +
' 00:00:00'))
file_name_list.append(tmp_dict)
file_name_list = sorted(file_name_list,
key=lambda x: x['time'],
reverse=False)
return file_name_list
def set_ftp_log(self, get):
"""
@name 开启关闭获取日志状态
@author hezhihong
@param get.exec_name 执行的动作
"""
if not hasattr(get, 'exec_name'):
return public.returnMsg(False, 'The parameter is incorrect')
conf_path = '/etc/rsyslog.conf'
conf = public.readFile(conf_path)
import re
search_str = r"ftp\.\*.*\t*.*\t*.*-/var/log/pure-ftpd.log"
search_str_two = "ftp.none"
rep_str = '\nftp.*\t\t-/var/log/pure-ftpd.log\n'
result = re.search(search_str, conf)
#获取日志状态
if get.exec_name == 'getlog':
if result:
return_result = 'start'
else:
return_result = 'stop'
return public.returnMsg(True, return_result)
#开启日志审计
elif get.exec_name == 'start':
if result:
conf = conf.replace(search_str, rep_str)
else:
conf += rep_str
#禁止ftp日志写入/var/log/messages
d_conf = conf[conf.rfind('info;'):]
d_conf = d_conf[:d_conf.find('/')]
s_conf = d_conf.replace(',', ';')
if s_conf.find(search_str_two) == -1:
str_index = s_conf.rfind(';')
s_conf = s_conf[:str_index +
1] + search_str_two + s_conf[str_index + 1:]
conf = conf.replace(d_conf, s_conf)
self.add_crontab()
#关闭日志审计
elif get.exec_name == 'stop':
if result:
conf = re.sub(search_str, '', conf)
#取消禁止ftp日志写入/var/log/messages
if conf.find(search_str_two) != -1:
conf = conf.replace(search_str_two, '')
for i in [';;', ',,', ';,', ',;']:
if conf.find(i) != -1: conf = conf.replace(i, '')
self.del_crontab()
public.writeFile(conf_path, conf)
public.ExecShell('systemctl restart rsyslog')
return public.returnMsg(True, 'successfully set')
def get_format_time(self, englist_time):
"""
@name 时间英文转换
"""
chinanese_time = ''
try:
for i in month_list.keys():
if i in englist_time:
tmp_time = englist_time.replace(i, month_list[i])
tmp_time = tmp_time.split()
chinanese_time = '{}-{} {}'.format(tmp_time[0], tmp_time[1],
tmp_time[2])
break
return chinanese_time
except:
return chinanese_time
def get_login_log(self, get):
"""
@name 取登录日志
@author hezhihong
@param get.user_name ftp用户名
return
"""
search_str = 'pure-ftpd:'
search_str2 = 'pure-ftpd['
if not hasattr(get, 'user_name'):
return public.returnMsg(False, 'The parameter is incorrect')
args = public.dict_obj()
args.exec_name = 'getlog'
file_name = self.__ftp_backup_path
is_backup = True
if self.set_ftp_log(get) == 'stop':
file_name = self.__messages_file
is_backup = False
file_list = self.get_file_list(file_name, is_backup)
data = []
sortid = 0
tmp_dict = {}
login_all = []
for file in file_list:
if not os.path.isfile(file['file']): continue
conf = public.readFile(file['file'])
lines = conf.split('\n')
for line in lines:
if not line: continue
login_info = {}
if search_str not in line and search_str2 not in line:
continue
tmp_value = ' is now logged in'
info = line[:line.find(search_str)].strip()
if not info:
info = line[:line.find(search_str2)].strip()
hostname = info.split()[-1]
exec_time = info.split(hostname)[0].strip()
exec_time = self.get_format_time(exec_time)
ip = line[line.find('(') + 1:line.find(')')].split('@')[1]
#取登录成功日志
if tmp_value in line:
user = line.split(tmp_value)[0].strip().split()[-1]
if user == '?' or user != get.user_name: continue
dict_index = '{}__{}'.format(user, ip)
if dict_index not in tmp_dict:
tmp_dict[dict_index] = []
tmp_dict[dict_index].append(exec_time)
#取登出日志
tmp_value = '[INFO] Logout.'
tmp_value_two = 'Timeout - try typing a little faster next time'
if tmp_value in line or tmp_value_two in line:
user = line[line.find('(') +
1:line.find(')')].split('@')[0]
if user == '?' or user != get.user_name: continue
dict_index = '{}__{}'.format(user, ip)
try:
login_info['out_time'] = exec_time
login_info['in_time'] = tmp_dict[dict_index][0]
login_info['user'] = user
login_info['ip'] = ip
login_info['status'] = 'Success' #0为登录失败,1为登录成功
login_info['sortid'] = sortid
login_all.append(login_info)
tmp_dict[dict_index] = []
sortid += 1
except:
pass
#取登录失败日志
tmp_value = 'Authentication failed for user'
if tmp_value in line:
user = line.split(tmp_value)[-1].replace('[', '').replace(
']', '').strip()
if user == '?' or user != get.user_name: continue
login_info['user'] = user
login_info['ip'] = ip
login_info['status'] = 'Failure' #0为登录失败,1为登录成功
login_info['in_time'] = exec_time
login_info['out_time'] = exec_time
login_info['sortid'] = sortid
login_all.append(login_info)
sortid += 1
if tmp_dict:
for item in tmp_dict.keys():
if not tmp_dict[item]: continue
info = {
"status": "login successful",
"in_time": tmp_dict[item][0],
"out_time": "connecting",
"user": item.split('__')[0],
"ip": item.split('__')[1],
"sortid": sortid
}
sortid += 1
login_all.append(info)
#搜索过滤
if login_all and 'search' in get and get.search and get.search.strip():
for info in login_all:
try:
search_str = str(get.search).strip().lower()
# public.writeFile('/tmp/aa.aa', get.search)
if info['ip'].find(search_str) != -1 or info['user'].lower(
).find(search_str) != -1 or info['status'].find(
search_str) != -1 or info['in_time'].find(
search_str) != -1:
data.append(info)
elif info['out_time'] and info['out_time'].find(
search_str) != -1:
data.append(info)
except:
pass
else:
for info2 in login_all:
data.append(info2)
data = sorted(data, key=lambda x: x['sortid'], reverse=True)
return self.get_page(data, get)
def get_page(self, data, get):
"""
@name 取分页
@author hezhihong
@param data 需要分页的数据 list
@param get.p 第几页
@return 指定分页数据
"""
# 包含分页类
import page
# 实例化分页类
page = page.Page()
info = {}
info['count'] = len(data)
info['row'] = 10
info['p'] = 1
if hasattr(get, 'p'):
info['p'] = int(get['p'])
info['uri'] = {}
info['return_js'] = ''
# 获取分页数据
result = {}
result['page'] = page.GetPage(info, limit='1,2,3,4,5,8')
n = 0
result['data'] = []
for i in range(info['count']):
if n >= page.ROW: break
if i < page.SHIFT: continue
n += 1
result['data'].append(data[i])
return result
def get_action_log(self, get):
"""
@name 取操作日志
@author hezhihong
@param get.user_name ftp用户名
return {"upload":[],"download":[],"rename":[],"delete":[]}
"""
search_str = 'pure-ftpd:'
args = public.dict_obj()
args.exec_name = 'getlog'
file_name = self.__ftp_backup_path
is_backup = True
if self.set_ftp_log(get) == 'stop':
file_name = self.__messages_file
is_backup = False
file_list = self.get_file_list(file_name, is_backup)
if not hasattr(get, 'user_name'):
return public.returnMsg(False, 'The parameter is incorrect')
data = []
tmp_data = []
sortid = 0
for file in file_list:
if not os.path.isfile(file['file']): continue
conf = public.readFile(file['file'])
lines = conf.split('\n')
for line in lines:
if not line: continue
action_info = {}
if search_str not in line: continue
tmp_v = line.split(search_str)
hostname = tmp_v[0].strip().split()[3].strip()
action_time = tmp_v[0].replace(hostname, '').strip()
action_info['time'] = self.get_format_time(action_time)
upload_value = ' uploaded '
download_value = ' downloaded '
rename_value = 'successfully renamed or moved:'
delete_value = ' Deleted '
ip = line[line.find('(') + 1:line.find(')')].split('@')[1]
action_info['ip'] = ip
action_info['type'] = ''
#取操作用户
user = ''
if upload_value in line or download_value in line or rename_value in line or delete_value in line:
user = line[line.find('(') +
1:line.find(')')].split('@')[0]
action_info['sortid'] = sortid
sortid = sortid + 1
if not user or user != get.user_name: continue
#取上传日志
if (get.type == 'all'
or get.type == 'upload') and upload_value in line:
line_list = line.split()
upload_index = line_list.index('uploaded')
# action_info['file'] = line_list[upload_index - 1].replace(
# '//', '/')
action_info['file'] = line[line.find(']') +
1:line.rfind('(')].replace(
'uploaded',
'').replace('//',
'/').strip()
action_info['type'] = 'upload'
tmp_data.append(action_info)
#取下载日志
if (get.type == 'all'
or get.type == 'download') and download_value in line:
line_list = line.split()
upload_index = line_list.index('downloaded')
action_info['file'] = line_list[upload_index - 1].replace(
'//', '/')
action_info['type'] = 'download'
tmp_data.append(action_info)
#取重命名日志
if (get.type == 'all'
or get.type == 'rename') and rename_value in line:
action_info['file'] = line.split(rename_value)[1].replace(
'->', 'Renamed to').strip().replace('//', '/')
action_info['type'] = 'rename'
tmp_data.append(action_info)
#取删除日志
if (get.type == 'all'
or get.type == 'delete') and delete_value in line:
action_info['file'] = line.split()[-1].strip().replace(
'//', '/')
action_info['type'] = 'delete'
tmp_data.append(action_info)
# f.close
#搜索过滤
if tmp_data and 'search' in get and get.search and get.search.strip():
for info in tmp_data:
search_str = str(get.search).strip().lower()
if info['ip'].find(search_str) != -1 or info['file'].lower(
).find(search_str) != -1 or info['type'].find(
search_str) != -1 or info['time'].find(
search_str) != -1 or get.user_name.lower().find(
search_str) != -1:
data.append(info)
else:
for info2 in tmp_data:
data.append(info2)
data = sorted(data, key=lambda x: x['sortid'], reverse=True)
return self.get_page(data, get)
def del_crontab(self):
"""
@name 删除项目定时清理任务
@auther hezhihong<2022-10-31>
@return
"""
cron_name = '[Do not delete] FTP audit log cutting task'
cron_path = public.GetConfigValue('setup_path') + '/cron/'
cron_list = public.M('crontab').where("name=?", (cron_name, )).select()
if cron_list:
for i in cron_list:
if not i: continue
cron_echo = public.M('crontab').where(
"id=?", (i['id'], )).getField('echo')
args = {"id": i['id']}
import crontab
crontab.crontab().DelCrontab(args)
del_cron_file = cron_path + cron_echo
public.ExecShell(
"crontab -u root -l| grep -v '{}'|crontab -u root -".
format(del_cron_file))
def add_crontab(self):
"""
@name 构造日志切割任务
"""
python_path = ''
try:
python_path = public.ExecShell('which btpython')[0].strip("\n")
except:
try:
python_path = public.ExecShell('which python')[0].strip("\n")
except:
pass
if not python_path: return False
if not public.M('crontab').where('name=?',
('[Do not delete] FTP audit log cutting task', )).count():
cmd = '{} {}'.format(python_path, self.__script_py)
args = {
"name": "[Do not delete] FTP audit log cutting task",
"type": 'day',
"where1": '',
"hour": '0',
"minute": '1',
"sName": "",
"sType": 'toShell',
"notice": '0',
"notice_channel": '',
"save": '',
"save_local": '1',
"backupTo": '',
"sBody": cmd,
"urladdress": ''
}
import crontab
res = crontab.crontab().AddCrontab(args)
if res and "id" in res.keys():
return True
return False
return True
+321
View File
@@ -0,0 +1,321 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <bt_ahong@qq.com>
#-------------------------------------------------------------------
#------------------------------
# 面板日志类
#------------------------------
import os,re,json,time
from logsModel.base import logsBase
import public,db
from html import unescape,escape
class main(logsBase):
def __init__(self):
pass
def get_logs_info(self,args):
'''
@name 获取分类日志信息
'''
data = public.M('logs').query('''
select type,count(id) as 'count' from logs
group by type
order by count(id) desc
''')
result = []
for arrs in data:
item = {}
if not arrs: continue
item['count'] = arrs[1]
item['type'] = arrs[0]
result.append(item)
public.set_module_logs('get_logs_info','get_logs_info')
return result
def get_logs_bytype(self,args):
"""
@name 根据类型获取日志
@param args.type 日志类型
"""
p,limit = 1,20
if 'p' in args: p = int(args.p)
if 'limit' in args: limit = int(args.limit)
stype = args.stype
search = '[' + str(args.search) + ']'
where = "type=? and log like ? "
count = public.M('logs').where(where,(stype,'%'+search+'%')).count()
data = public.get_page(count,p,limit)
data['data'] = public.M('logs').where(where,(stype,'%'+search+'%')).limit('{},{}'.format(data['shift'], data['row'])).order('id desc').select()
return data
def __get_panel_dirs(self):
'''
@name 获取面板日志目录
'''
dirs = []
for filename in os.listdir('{}/logs/request'.format(public.get_panel_path())):
if filename.find('.json') != -1:
dirs.append(filename)
dirs = sorted(dirs,reverse=True)
return dirs
def get_panel_log(self,get):
"""
@name 获取面板日志
"""
p,limit,search = 1,20,''
if 'p' in get: p = int(get.p)
if 'limit' in get: limit = int(get.limit)
if 'search' in get: search = get.search
find_idx = 0
log_list = []
dirs = self.__get_panel_dirs()
for filename in dirs:
log_path = '{}/logs/request/{}'.format(public.get_panel_path(),filename)
if not os.path.exists(log_path): #文件不存在
continue
if len(log_list) >= limit:
break
p_num = 0 #分页计数器
next_file = False
while not next_file:
if len(log_list) >= limit:
break
p_num += 1
result = self.GetNumLines(log_path,10001,p_num).split('\r\n')
if len(result) < 10000:
next_file = True
result.reverse()
for _line in result:
if not _line: continue
if len(log_list) >= limit:
break
try:
if self.find_line_str(_line,search):
find_idx += 1
if find_idx > (p-1) * limit:
info = json.loads(unescape(_line))
for key in info:
if isinstance(info[key],str):
info[key] = escape(info[key])
info['address'] = info['ip'].split(':')[0]
log_list.append(info)
except:pass
return public.return_area(log_list,'address')
def get_panel_error_logs(self,get):
'''
@name 获取面板错误日志
'''
search = ''
if 'search' in get:
search = get.search
filename = '{}/logs/error.log'.format(public.get_panel_path())
if not os.path.exists(filename):
return public.returnMsg(False,'No error log')
res = {}
res['data'] = public.xssdecode(self.GetNumLines(filename,2000,1,search))
res['data'].reverse()
return res
def __get_ftp_log_files(self,path):
"""
@name 获取FTP日志文件列表
@param path 日志文件路径
@return list
"""
file_list = []
if os.path.exists(path):
for filename in os.listdir(path):
if filename.find('.log') == -1: continue
file_list.append('{}/{}'.format(path,filename))
file_list = sorted(file_list,reverse=True)
return file_list
def get_ftp_logs(self,get):
"""
@name 获取ftp日志
"""
p,limit,search,username = 1,500,'',''
if 'p' in get: p = int(get.p)
if 'limit' in get: limit = int(get.limit)
if 'search' in get: search = get.search
if 'username' in get: username = get.username
find_idx = 0
ip_list = []
log_list = []
dirs = self.__get_ftp_log_files('{}/ftpServer/Logs'.format(public.get_soft_path()))
for log_path in dirs:
if not os.path.exists(log_path): continue
if len(log_list) >= limit: break
p_num = 0 #分页计数器
next_file = False
while not next_file:
if len(log_list) >= limit:
break
p_num += 1
result = self.GetNumLines(log_path,10001,p_num).split('\r\n')
if len(result) < 10000:
next_file = True
result.reverse()
for _line in result:
if not _line.strip(): continue
if len(log_list) >= limit:
break
try:
if self.find_line_str(_line,search):
#根据用户名查找
if username and not re.search('-\s+({})\s+\('.format(username),_line):
continue
find_idx += 1
if find_idx > (p-1) * limit:
#获取ip归属地
for _ip in public.get_line_ips(_line):
if not _ip in ip_list: ip_list.append(_ip)
info = escape(_line)
log_list.append(info)
except:pass
return self.return_line_area(log_list,ip_list)
#取慢日志
def get_slow_logs(self,get):
'''
@name 获取慢日志
@get.search 搜索关键字
'''
search,p,limit = '',1,1000
if 'search' in get: search = get.search
if 'limit' in get: limit = get.limit
my_info = public.get_mysql_info()
if not my_info['datadir']:
return public.returnMsg(False,'MySQL is not installed!')
path = my_info['datadir'] + '/mysql-slow.log'
if not os.path.exists(path):
return public.returnMsg(False,'Log file does not exist!')
# mysql慢日志有顺序问题,倒序显示不利于排查问题
return public.returnMsg(True, public.xsssec(public.GetNumLines(path, limit)))
# find_idx = 0
# p_num = 0 #分页计数器
# next_file = False
# log_list = []
# while not next_file:
# if len(log_list) >= limit:
# break
# p_num += 1
# result = self.GetNumLines(path,10001,p_num).replace('\r\n','\n').split('\n')
# if len(result) < 10000:
# next_file = True
# result.reverse()
# for _line in result:
# if not _line: continue
# if len(log_list) >= limit:
# break
# try:
# if self.find_line_str(_line,search):
# find_idx += 1
# if find_idx > (p-1) * limit:
# info = escape(_line)
# log_list.append(info)
# except:pass
# return log_list
def IP_geolocation(self, get):
'''
@name 列出所有IP及其归属地
@return list {ip: {ip: ip_address, operation_num: 12 ,info: 归属地}, ...]
'''
result = dict()
data = public.M('logs').query('''
select * from logs
''')
for arrs in data:
if not arrs: continue
end = 0
# 获得IP的尾后索引
for ch in arrs[2]:
if ch.isnumeric() or ch == '.':
end += 1
else:
break
ip_addr = arrs[2][0:end]
if ip_addr:
if result.get(ip_addr) != None:
result[ip_addr]["operation_num"] = result[ip_addr]["operation_num"] + 1
else:
result[ip_addr] = {"ip":ip_addr,"operation_num":1, "info":None}
return_list = []
for k in result:
info = public.get_free_ip_info(k)
result[k]["info"] = info["info"]
return_list.append(result[k])
return return_list
def get_error_logs_by_search(self, args):
'''
@name 根据搜索内容, 获取运行日志中的内容
@args.search 匹配内容
@return 匹配该内容的所有日志
'''
log_file_path = "{}/logs/error.log".format(public.get_panel_path())
#return log_file_path
data = public.readFile(log_file_path)
if not data:
return None
data = data.split('\n')
result = []
for line in data:
if args.search == None:
result.append(line)
elif args.search in line:
result.append(line)
return result
+110
View File
@@ -0,0 +1,110 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <bt_ahong@qq.com>
#-------------------------------------------------------------------
#------------------------------
# 面板日志类
#------------------------------
import os,re,json,time
from logsModel.base import logsBase
import public,db
from html import unescape,escape
class main(logsBase):
def __init__(self):
self.serverType = public.get_webserver()
def __get_iis_log_files(self,path):
"""
@name 获取IIS日志文件列表
@param path 日志文件路径
@return list
"""
file_list = []
if os.path.exists(path):
for filename in os.listdir(path):
if filename.find('.log') == -1: continue
file_list.append('{}/{}'.format(path,filename))
file_list = sorted(file_list,reverse=False)
return file_list
def get_iis_logs(self,get):
"""
@name 获取IIS网站日志
"""
p,limit,search = 1,2000,''
if 'p' in get: limit = int(get.p)
if 'limit' in get: limit = int(get.limit)
if 'search' in get: search = get.search
import panelSite
site_obj = panelSite.panelSite()
data = site_obj.get_site_info(get.siteName)
if not data:
return public.returnMsg(False,'{}】网站路径获取失败,请检查IIS是否存在此站点,如IIS不存在请通过面板删除此网站后重新创建.'.format(get.siteName))
log_path = '{}/wwwlogs/W3SVC{}'.format(public.get_soft_path(), data['id'])
file_list = self.__get_iis_log_files(log_path)
find_idx = 0
log_list = []
for log_path in file_list:
if not os.path.exists(log_path): continue
if len(log_list) >= limit: break
p_num = 0 #分页计数器
next_file = False
while not next_file:
if len(log_list) >= limit:
break
p_num += 1
result = self.GetNumLines(log_path,10001,p_num).split('\r\n')
if len(result) < 10000:
next_file = True
for _line in result:
if not _line: continue
if len(log_list) >= limit:
break
try:
if self.find_line_str(_line,search):
find_idx += 1
if find_idx > (p-1) * limit:
info = escape(_line)
log_list.append(info)
except:pass
return log_list
# 取网站日志
def get_site_logs(self, get):
logPath = ''
if self.serverType == 'iis':
return self.get_iis_logs(get)
elif self.serverType == 'apache':
logPath = self.setupPath + '/wwwlogs/' + get.siteName + '-access.log'
else:
logPath = self.setupPath + '/wwwlogs/' + get.siteName + '.log'
data = {}
data['path'] = ''
data['path'] = os.path.dirname(logPath)
if os.path.exists(logPath):
data['status'] = True
data['msg'] = public.GetNumLines(logPath, 1000)
return data
data['status'] = False
data['msg'] = 'log is empty'
return data
+14
View File
@@ -0,0 +1,14 @@
# coding: utf-8
import os, sys, time, json
panelPath = '/www/server/panel'
os.chdir(panelPath)
if not panelPath + "/class/" in sys.path:
sys.path.insert(0, panelPath + "/class/")
import public, re
class monitorBase:
def __init__(self):
pass
@@ -0,0 +1,556 @@
import json
import os
import time
import traceback
import re
import psutil
import public
from monitorModel.base import monitorBase
from pluginAuth import Plugin
class main(monitorBase):
setupPath = '/www/server'
__panel_path = '/www/server/panel/class/monitorModel'
__data_path = os.path.join(__panel_path, 'data')
cpu_old_path = os.path.join(__data_path, 'cpu_old.json')
disk_read_old_path = os.path.join(__data_path, 'disk_read_old.json')
disk_write_old_path = os.path.join(__data_path, 'disk_write_old.json')
old_net_path = os.path.join(__data_path, 'network_old.json')
old_disk_path = os.path.join(__data_path, 'disk_old.json')
old_site_path = os.path.join(__data_path, 'site_old.json')
nethogs_out = os.path.join(__data_path, 'process_flow.log')
disk_write_new_info = {}
disk_write_old_info = {}
disk_read_new_info = {}
disk_read_old_info = {}
cpu_new_info = {}
old_disk_info = {}
new_disk_info = {}
cpu_old_info = {}
log_path = {
"mongod": "/www/server/mongodb/log/config.log",
"nginx": "/www/wwwlogs/nginx_error.log",
"httpd": "/www/wwwlogs/nginx_error.log",
"mysqld": "/www/server/data/mysql-slow.log",
}
pids = None
__cpu_time = None
panel_pid = None
task_pid = None
processPs = {
'bioset': '用于处理块设备上的I/O请求的进程',
'BT-MonitorAgent': '面板程序的进程',
'rngd': '一个熵守护的进程',
'master': '用于管理和协调子进程的活动的进程',
'irqbalance': '一个IRQ平衡守护的进程',
'rhsmcertd': '主要用于管理Red Hat订阅证书,并维护系统的订阅状态的进程',
'auditd': '是Linux审计系统中用户空间的一个组的进程',
'chronyd': '调整内核中运行的系统时钟和时钟服务器同步的进程',
'qmgr': 'PBS管理器的进程',
'oneavd': '面板微步木马检测的进程',
'postgres': 'PostgreSQL数据库的进程',
'grep': '一个命令行工具的进程',
'lsof': '一个命令行工具的进程',
'containerd-shim-runc-v2': 'Docker容器的一个组件的进程',
'pickup': '用于监听Unix域套接字的进程',
'cleanup': '邮件传输代理(MTA)中的一个组件的进程',
'trivial-rewrite': '邮件传输代理(MTA)中的一个组件的进程',
'containerd': 'docker依赖服务的进程',
'redis-server': 'redis服务的进程',
'rcu_sched': 'linux系统rcu机制服务的进程',
'jsvc': '面板tomcat服务的进程',
'oneav': '面板微步木马检测的进程',
'mysqld': 'MySQL服务的进程',
'php-fpm': 'PHP的子进程',
'php-cgi': 'PHP-CGI的进程',
'nginx': 'Nginx服务的进程',
'httpd': 'Apache服务的进程',
'sshd': 'SSH服务的进程',
'pure-ftpd': 'FTP服务的进程',
'sftp-server': 'SFTP服务的进程',
'mysqld_safe': 'MySQL服务的进程',
'firewalld': '防火墙服务的进程',
'BT-Panel': '宝塔面板-主的进程',
'BT-Task': '宝塔面板-后台任务的进程',
'NetworkManager': '网络管理服务的进程',
'svlogd': '日志守护的进程',
'memcached': 'Memcached缓存器的进程',
'gunicorn': "宝塔面板的进程",
"BTPanel": '宝塔面板的进程',
'baota_coll': "堡塔云控-主控端的进程",
'baota_client': "堡塔云控-被控端的进程",
'node': 'Node.js程序的进程',
'supervisord': 'Supervisor的进程',
'rsyslogd': 'rsyslog日志服务的进程',
'crond': '计划任务服务的进程',
'cron': '计划任务服务的进程',
'rsync': 'rsync文件同步的进程',
'ntpd': '网络时间同步服务的进程',
'rpc.mountd': 'NFS网络文件系统挂载服务的进程',
'sendmail': 'sendmail邮件服务的进程',
'postfix': 'postfix邮件服务的进程',
'npm': 'Node.js NPM管理器的进程',
'PM2': 'Node.js PM2进程管理器的进程',
'htop': 'htop进程监控软件的进程',
'btpython': '宝塔面板-独立Python环境的进程',
'btappmanagerd': '宝塔应用管理器插件的进程',
'dockerd': 'Docker容器管理器的进程',
'docker-proxy': 'Docker容器管理器的进程',
'docker-registry': 'Docker容器管理器的进程',
'docker-distribution': 'Docker容器管理器的进程',
'docker-network': 'Docker容器管理器的进程',
'docker-volume': 'Docker容器管理器的进程',
'docker-swarm': 'Docker容器管理器的进程',
'docker-systemd': 'Docker容器管理器的进程',
'docker-containerd': 'Docker容器管理器的进程',
'docker-containerd-shim': 'Docker容器管理器的进程',
'docker-runc': 'Docker容器管理器的进程',
'docker-init': 'Docker容器管理器的进程',
'docker-init-systemd': 'Docker容器管理器的进程',
'docker-init-upstart': 'Docker容器管理器的进程',
'docker-init-sysvinit': 'Docker容器管理器的进程',
'docker-init-openrc': 'Docker容器管理器的进程',
'docker-init-runit': 'Docker容器管理器的进程',
'docker-init-systemd-resolved': 'Docker容器管理器的进程',
'rpcbind': 'NFS网络文件系统服务的进程',
'dbus-daemon': 'D-Bus消息总线守护的进程',
'systemd-logind': '登录管理器的进程',
'systemd-journald': 'Systemd日志管理服务的进程',
'systemd-udevd': '系统设备管理服务的进程',
'systemd-timedated': '系统时间日期服务的进程',
'systemd-timesyncd': '系统时间同步服务的进程',
'systemd-resolved': '系统DNS解析服务的进程',
'systemd-hostnamed': '系统主机名服务的进程',
'systemd-networkd': '系统网络管理服务的进程',
'systemd-resolvconf': '系统DNS解析服务的进程',
'systemd-local-resolv': '系统DNS解析服务的进程',
'systemd-sysctl': '系统系统参数服务的进程',
'systemd-modules-load': '系统模块加载服务的进程',
'systemd-modules-restore': '系统模块恢复服务的进程',
'agetty': 'TTY登陆验证程序的进程',
'sendmail-mta': 'MTA邮件传送代理的进程',
'(sd-pam)': '可插入认证模块的进程',
'polkitd': '授权管理服务的进程',
'mongod': 'MongoDB数据库服务的进程',
'mongodb': 'MongoDB数据库服务的进程',
'mongodb-mms-monitor': 'MongoDB数据库服务的进程',
'mongodb-mms-backup': 'MongoDB数据库服务的进程',
'mongodb-mms-restore': 'MongoDB数据库服务的进程',
'mongodb-mms-agent': 'MongoDB数据库服务的进程',
'mongodb-mms-analytics': 'MongoDB数据库服务的进程',
'mongodb-mms-tools': 'MongoDB数据库服务的进程',
'mongodb-mms-backup-agent': 'MongoDB数据库服务的进程',
'mongodb-mms-backup-tools': 'MongoDB数据库服务的进程',
'mongodb-mms-restore-agent': 'MongoDB数据库服务的进程',
'mongodb-mms-restore-tools': 'MongoDB数据库服务的进程',
'mongodb-mms-analytics-agent': 'MongoDB数据库服务的进程',
'mongodb-mms-analytics-tools': 'MongoDB数据库服务的进程',
'dhclient': 'DHCP协议客户端的进程',
'dhcpcd': 'DHCP协议客户端的进程',
'dhcpd': 'DHCP服务器的进程',
'isc-dhcp-server': 'DHCP服务器的进程',
'isc-dhcp-server6': 'DHCP服务器的进程',
'dhcp6c': 'DHCP服务器的进程',
'dhcpcd': 'DHCP服务器的进程',
'dhcpd': 'DHCP服务器的进程',
'avahi-daemon': 'Zeroconf守护的进程',
'login': '登录的进程',
'systemd': '系统管理服务的进程',
'systemd-sysv': '系统管理服务的进程',
'systemd-journal-gateway': '系统管理服务的进程',
'systemd-journal-remote': '系统管理服务的进程',
'systemd-journal-upload': '系统管理服务的进程',
'systemd-networkd': '系统网络管理服务的进程',
'rpc.idmapd': 'NFS网络文件系统相关服务的进程',
'cupsd': '打印服务的进程',
'cups-browsed': '打印服务的进程',
'sh': 'shell的进程',
'php': 'PHP CLI模式的进程',
'blkmapd': 'NFS映射服务的进程',
'lsyncd': '文件同步服务的进程',
'sleep': '延迟的进程',
}
def __init__(self):
if not os.path.isdir(self.__data_path):
os.makedirs(self.__data_path, 384)
plugin_obj = Plugin(False)
plugin_list = plugin_obj.get_plugin_list()
public.print_log(plugin_list['ltd'])
ped = int(plugin_list['ltd']) > time.time()
if ped:
self.add_nethogs_task()
def specific_resource_load_type(self, get):
"""
查询具体资源类型负载
:param get: None
:return: 资源占用字典
"""
try:
plugin_obj = Plugin(False)
plugin_list = plugin_obj.get_plugin_list()
public.print_log(plugin_list['ltd'])
ped = int(plugin_list['ltd']) > time.time()
if not ped: return {'status': False, 'msg': "该功能为企业版专享!"}
infos = {}
load_avg = os.getloadavg()
infos['info'] = {}
infos['info']['physical_cpu'] = psutil.cpu_count(logical=False)
infos['info']['logical_cpu'] = psutil.cpu_count(logical=True)
c_tmp = public.readFile('/proc/cpuinfo')
d_tmp = re.findall("physical id.+", c_tmp)
cpuW = len(set(d_tmp))
infos['info']['cpu_name'] = public.getCpuType() + " * {}".format(cpuW)
infos['info']['num_phys_cores'] = cpuW
infos['info']['load_avg'] = {"1": load_avg[0], "5": load_avg[1], "15": load_avg[2]}
infos['info']['active_processes'] = len(
[p for p in psutil.process_iter() if p.status() == psutil.STATUS_RUNNING])
infos['info']['total_processes'] = len(psutil.pids())
cpu_percent = self.get_process_cpu(get)
cpu_proc = cpu_percent["process_list"]
mem = self.get_mem_info()
infos['CPU_percentage_of_load'] = cpu_percent["info"]["cpu"]
infos['percentage_of_memory_usage'] = round(mem['memRealUsed'] / mem['memTotal'] * 100, 2)
infos['CPU_high_occupancy_software_list'] = {}
for i in range(5):
try:
infos['CPU_high_occupancy_software_list'][i] = {"name": cpu_proc[i]['name'],
'pid': cpu_proc[i]['pid'],
'cpu_percent': cpu_proc[i]['cpu_percent'],
'proc_survive': cpu_proc[i]['proc_survive']}
except:
pass
b = []
for i, j in infos['CPU_high_occupancy_software_list'].items():
cpu_info = {'proc_name': infos['CPU_high_occupancy_software_list'][i]['name'],
'pid': infos['CPU_high_occupancy_software_list'][i]['pid'],
'cpu_percent': str(infos['CPU_high_occupancy_software_list'][i]['cpu_percent']) + "%"}
cpu_info['explain'], cpu_info['num_threads'], cpu_info['exe_path'], cpu_info['cwd_path'], cpu_info[
'important'], cpu_info['proc_survive'] = self.__process_analysis(
infos['CPU_high_occupancy_software_list'][i]['pid'])
b.append(cpu_info)
infos['CPU_high_occupancy_software_list'] = b
infos["memory_high_occupancy_software_list"] = self.__use_mem_list()
c = []
for i, j in infos["memory_high_occupancy_software_list"].items():
mem_info = {'proc_name': i, 'pid': infos['memory_high_occupancy_software_list'][i]['pid'],
"memory_usage": infos["memory_high_occupancy_software_list"][i]['memory_usage']}
mem_info['explain'], mem_info['num_threads'], mem_info['exe_path'], mem_info['cwd_path'], mem_info[
'important'], mem_info['proc_survive'] = self.__process_analysis(
infos["memory_high_occupancy_software_list"][i]['pid'])
c.append(mem_info)
infos["memory_high_occupancy_software_list"] = c
return infos
except:
public.print_log(traceback.format_exc())
# 按cpu资源获取进程列表
def get_process_cpu(self, get):
self.pids = psutil.pids()
process_list = []
if type(self.cpu_new_info) != dict: self.cpu_new_info = {}
self.cpu_new_info['cpu_time'] = self.get_cpu_time()
self.cpu_new_info['time'] = time.time()
if 'sort' not in get: get.sort = 'cpu_percent'
get.reverse = bool(int(get.reverse)) if 'reverse' in get else True
info = {}
info['activity'] = 0
info['cpu'] = 0.00
status_ps = {'sleeping': '睡眠', 'running': '活动'}
limit = 1000
for pid in self.pids:
tmp = {}
try:
p = psutil.Process(pid)
except:
continue
with p.oneshot():
p_cpus = p.cpu_times()
p_state = p.status()
if p_state == 'running': info['activity'] += 1
if p_state in status_ps:
p_state = status_ps[p_state]
else:
continue
tmp['exe'] = p.exe()
timestamp = time.time() - p.create_time()
time_info = {}
time_info[""] = int(timestamp // (24 * 3600))
time_info["小时"] = int((timestamp - time_info[''] * 24 * 3600) // 3600)
time_info["分钟"] = int((timestamp - time_info[''] * 24 * 3600 - time_info['小时'] * 3600) // 60)
ll = [str(v) + k for k, v in time_info.items() if v != 0]
tmp['proc_survive'] = ''.join(ll)
tmp['name'] = p.name()
tmp['pid'] = pid
tmp['ppid'] = p.ppid()
# tmp['create_time'] = int(p.create_time())
tmp['status'] = p_state
tmp['user'] = p.username()
tmp['cpu_percent'] = self.get_cpu_percent(str(pid), p_cpus, self.cpu_new_info['cpu_time'])
tmp['threads'] = p.num_threads()
tmp['ps'] = self.get_process_ps(tmp['name'], pid)
if tmp['cpu_percent'] > 100: tmp['cpu_percent'] = 0.1
info['cpu'] += tmp['cpu_percent']
process_list.append(tmp)
limit -= 1
if limit <= 0: break
del p
del tmp
public.writeFile(self.cpu_old_path, json.dumps(self.cpu_new_info))
# process_list = self.handle_process_list(process_list)
process_list = sorted(process_list, key=lambda x: x[get.sort], reverse=get.reverse)
info['load_average'] = self.get_load_average()
data = {}
data['process_list'] = process_list[:10]
info['cpu'] = round(info['cpu'], 2)
data['info'] = info
return data
# 获取负载
def get_load_average(self, get=None):
b = public.ExecShell("uptime")[0].replace(',', '')
c = b.split()
data = {}
data['1'] = float(c[-3])
data['5'] = float(c[-2])
data['15'] = float(c[-1])
return data
# 获取总的cpu时间
def get_cpu_time(self, get=None):
if self.__cpu_time: return self.__cpu_time
self.__cpu_time = 0.00
s = psutil.cpu_times()
self.__cpu_time = s.user + s.system + s.nice + s.idle
return self.__cpu_time
# 获取进程cpu利用率
def get_cpu_percent(self, pid, cpu_times, cpu_time):
self.get_cpu_old()
percent = 0.00
process_cpu_time = self.get_process_cpu_time(cpu_times)
if not self.cpu_old_info: self.cpu_old_info = {}
if pid not in self.cpu_old_info:
self.cpu_new_info[pid] = {}
self.cpu_new_info[pid]['cpu_time'] = process_cpu_time
return percent
percent = round(100.00 * (process_cpu_time - self.cpu_old_info[pid]['cpu_time']) / (
cpu_time - self.cpu_old_info['cpu_time']), 2)
self.cpu_new_info[pid] = {}
self.cpu_new_info[pid]['cpu_time'] = process_cpu_time
if percent > 0: return percent
return 0.00
# 获取信息,如果存在返回true,不存在读取gson后存在true:不存在flase
def get_cpu_old(self):
if self.cpu_old_info: return True
if not os.path.exists(self.cpu_old_path): return False
data = public.readFile(self.cpu_old_path)
if not data: return False
data = json.loads(data)
if not data: return False
self.cpu_old_info = data
del data
return True
# 获取进程占用的cpu时间
def get_process_cpu_time(self, cpu_times):
cpu_time = 0.00
for s in cpu_times: cpu_time += s
return cpu_time
def get_process_ps(self, name, pid):
if name in self.processPs: return self.processPs[name]
# 增加使用nethogs收集进程流量定时任务
def add_nethogs_task(self, get=None):
# self.add_process_white('nethogs')
import crontab
if public.M('crontab').where('name=?', u'[勿删]资源管理器-获取进程流量').count():
return public.returnMsg(True, '定时任务已存在!')
s_body = '''ps -ef | grep nethogs | grep -v grep | awk '{print $2}' | xargs kill 2>/dev/null
count=0
while [ $count -lt 2 ]
do
count=$(($count+1))
/usr/sbin/nethogs -t -a -d 2 -c 5 > %s 2>/dev/null
if [[ $count == 2 ]];then
exit
else
sleep 20
fi
done''' % self.nethogs_out
p = crontab.crontab()
args = {
"name": u'[勿删]资源管理器-获取进程流量',
"type": 'minute-n',
"where1": 5,
"hour": '',
"minute": '',
"week": '',
"sType": "toShell",
"sName": "",
"backupTo": "",
"save": '',
"sBody": s_body,
"urladdress": "undefined"
}
p.AddCrontab(args)
return public.returnMsg(True, '设置成功!')
# 获取内存情况
def get_mem_info(self, get=None):
mem = psutil.virtual_memory()
memInfo = {'memTotal': int(mem.total / 1024 / 1024), 'memFree': int(mem.free / 1024 / 1024),
'memBuffers': int(mem.buffers / 1024 / 1024), 'memCached': int(mem.cached / 1024 / 1024)}
memInfo['memRealUsed'] = memInfo['memTotal'] - memInfo['memFree'] - memInfo['memBuffers'] - memInfo['memCached']
return memInfo
def __use_mem_list(self):
processes = []
for proc in psutil.process_iter():
try:
# 获取进程详细信息
pinfo = proc.as_dict(attrs=['pid', 'name', 'memory_info'])
# 添加到进程列表
processes.append(pinfo)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
processes = sorted(processes, key=lambda p: p['memory_info'].rss, reverse=True)
l = {}
mem_total = psutil.virtual_memory().total
for p in processes:
l[p['name']] = {'pid': p['pid'],
'memory_usage': '%.2f' % (int(p['memory_info'].rss) / int(mem_total) * 100) + "%"}
if len(l) >= 5:
break
return l
# 常用软件分析
def __process_analysis(self, pid):
process = psutil.Process(pid)
important = 0
explain = self.processPs.get(process.name(),
'未知程序的进程')
if 'BT-Panel' == process.name() or 'BT-Task' == process.name():
important = 1
num_threads = process.num_threads()
exe_path = process.exe()
cwd_path = process.cwd()
timestamp = time.time() - process.create_time()
time_info = {}
time_info[""] = int(timestamp // (24 * 3600))
time_info["小时"] = int((timestamp - time_info[''] * 24 * 3600) // 3600)
time_info["分钟"] = int((timestamp - time_info[''] * 24 * 3600 - time_info['小时'] * 3600) // 60)
ll = [str(v) + k for k, v in time_info.items() if v != 0]
pro_time = ''.join(ll)
if ''.join(ll) == '':
pro_time = '小于1分钟'
return explain, num_threads, exe_path, cwd_path, important, pro_time
def kill_process_all(self, get):
pid = int(get.pid)
if pid < 30: return public.returnMsg(False, '不能结束系统关键进程!')
if pid not in psutil.pids(): return public.returnMsg(False, '指定进程不存在!')
p = psutil.Process(pid)
if self.is_panel_process(pid): return public.returnMsg(False, '不能结束面板服务进程')
p.kill()
return self.kill_process_tree_all(pid)
# 结束进程树
def kill_process_tree_all(self, pid):
if pid < 30: return public.returnMsg(True, '已结束此进程树!')
if self.is_panel_process(pid): return public.returnMsg(False, '不能结束面板服务进程')
try:
if pid not in psutil.pids(): public.returnMsg(True, '已结束此进程树!')
p = psutil.Process(pid)
ppid = p.ppid()
name = p.name()
p.kill()
public.ExecShell('pkill -9 ' + name)
if name.find('php-') != -1:
public.ExecShell("rm -f /tmp/php-cgi-*.sock")
elif name.find('mysql') != -1:
public.ExecShell("rm -f /tmp/mysql.sock")
elif name.find('mongod') != -1:
public.ExecShell("rm -f /tmp/mongod*.sock")
self.kill_process_lower(pid)
if ppid: return self.kill_process_all(ppid)
except:
pass
return public.returnMsg(True, '已结束此进程树!')
def kill_process_lower(self, pid):
pids = psutil.pids()
for lpid in pids:
if lpid < 30: continue
if self.is_panel_process(lpid): continue
p = psutil.Process(lpid)
ppid = p.ppid()
if ppid == pid:
p.kill()
return self.kill_process_lower(lpid)
return True
# 判断是否是面板进程
def is_panel_process(self, pid):
if not self.panel_pid:
self.panel_pid = os.getpid()
if pid == self.panel_pid: return True
if not self.task_pid:
try:
self.task_pid = int(
public.ExecShell("ps aux | grep 'python task.py'|grep -v grep|head -n1|awk '{print $2}'")[0])
except:
self.task_pid = -1
if pid == self.task_pid: return True
return False
def __get_number_of_processes(self):
import psutil
from collections import Counter
ll = []
processes = psutil.process_iter()
process_names = [process.name() for process in processes]
process_item = Counter(process_names)
process_item = dict(sorted(process_item.items(), key=lambda item: item[1], reverse=True)[:5])
for key, value in process_item.items():
procs = {'proc_name': key, 'proc_description': '此进程的进程数有' + str(value) + '个,进程是{}'.format(
self.processPs.get(key, "未知进程"))}
ll.append(procs)
return ll
def process_description(self, get):
try:
updatas = json.loads(get.information_collection)
data = json.loads(public.readFile('/www/server/panel/class/monitorModel/common_process.json'))
data.update(updatas)
public.writeFile('/www/server/panel/class/monitorModel/common_process.json', json.dumps(data))
return public.returnMsg(True, "进程添加成功")
except:
return public.returnMsg(False, "进程添加失败")
def universal(self, get):
method = {
"题目1": "遇到未知进程解决办法。",
"1.1": "观察进程可执行目录和运行目录,是否与BT、项目名、常用软件相关,若与项目或者系统相关的进程且占用资源不大可不管。",
"1.2": "去‘百度’上搜索进程名,查看进程的归属,以及是否有害。ps:https://www.baidu.com",
"1.3": "咨询项目开发人员,看此进程是否由部署的项目所创建,若是,可加入到常见进程列表中。",
"1.4": "实在判断不了进程的性质,可到宝塔论坛发帖求助.ps:https://www.bt.cn/bbs/portal.php",
"1.5": "对进程做出详细的判断后,无用且占用资源较高,可关闭该进程。",
"1.6": "若占用资源较多的是使用当中的软件或项目,则可以尝试适当的优化,比如mysql优化、适当限制php的并发等。",
"题目2": "内存,cpu使用率不高,但负载很高解决办法",
"2.1": "负载高低还与线程数量、IO使用率、服务器本身有联系,可查看线程数量以及磁盘使用情况进行综合判断",
"2.2": "若本身服务器的配置较低,可以适当的考虑升级服务器配置",
"2.3": "若是遭受到网络攻击,也可导致服务器的负载偏高,可以开启宝塔防火墙以及安全插件进行防护。",
"2.4": "若服务器使用的是云服务器,也可能是服务器商家限制,可以咨询一下服务器商家的客服。"
}
return method
+143
View File
@@ -0,0 +1,143 @@
<div class="conter_box box_dingding">
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
<div class="flex" style="align-item: center; height: 32px;">
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
<div>
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
</div>
</div>
</div> -->
<div class="bt-form">
<div class="line">
<span class="tname">Notify everyone</span>
<div class="info-r" style="height:28px; margin-left:125px">
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" disabled="disabled" checked>
<label style="position: relative;top: 5px;" title="Only supports notify everyone." class="btswitch-btn" for="panel_alert_all"></label>
</div>
</div>
<div class="line">
<span class="tname">Dingding URL</span>
<div class="info-r">
<textarea name="channel_dingding_value" class="bt-input-text mr5" type="text" placeholder="Please enter Dingding url" style="width: 300px; height:90px; line-height:20px"></textarea>
</div>
<button class="btn btn-success btn-sm dingding_submit" style="margin: 10px 0 0 125px;">Save</button>
</div>
<div class="line">
<ul class="help-info-text c7">
<li>Notify everyone, Immutable</li>
</ul>
</div>
</div>
</div>
<style type="text/css">
.total_tips {
border: 1px solid #cbcbcb;
border-radius: 8px;
color: #cbcbcb;
cursor: pointer;
display: inline-block;
font-family: arial;
font-size: 12px;
font-style: normal;
height: 14px;
line-height: 14px;
margin-right: 5px;
text-align: center;
width: 14px;
}
</style>
<!--钉钉模块-->
<script type="text/javascript">
var dingding = {
all_info: {},
init: function () {
var that = this;
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
this.get_dingding_data();
// 设置默认
$('#default_setting').change(function () {
var _default = $(this).prop('checked');
var _url = that.all_info.data.dingding_url;
if (!_url) {
layer.msg('Dingding URL is not configured', { icon: 2 })
$(this).prop('checked', !_default);
return
}
var loadTs = layer.msg('Dingding configuration is being set, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_default_channel&channel=dingding', { default: _default }, function (res) {
layer.close(loadTs);
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
if (res.status) that.refresh_data();
});
});
var showTips = ''
$('.total_tips').hover(function(){
showTips = setTimeout(function(){
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
tips: [1, '#20a53a'],
time: 0,
success:function(layero,indexs){
layero.css("left", $('.total_tips').offset().left - 10);
}})
},200)
},function(){
clearTimeout(showTips);
layer.closeAll('tips');
})
},
/**
*@description 获取钉钉url,保存按钮添加事件
*/
get_dingding_data: function () {
var that = this;
var data = this.all_info.data;
if (data) {
var url = data.dingding_url || '';
var _default = data.hasOwnProperty('default') ? data.default : false
$('textarea[name=channel_dingding_value]').val(url);
$('#default_setting').prop('checked', _default);
}
// 钉钉信息设置
$('.dingding_submit').click(function () {
that.set_submit_ding();
})
},
/**
*@description 设置钉钉url信息,保存按钮
*/
set_submit_ding: function () {
var that = this;
var _url = $('textarea[name=channel_dingding_value]').val();
if (_url == '') return layer.msg('Please enter Dingding url', { icon: 2 })
var loadT = layer.msg('Dingding is being set up, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_msg_config&name=dingding', { url: _url, atall: 'True' }, function (rdata) {
layer.close(loadT);
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
if (rdata.status) that.refresh_data();
})
},
refresh_data: function () {
var that = this
$.post('/config?action=get_msg_configs', function (rdata) {
$.each(rdata, function (key, item) {
var $el = $('.alarm-view .bt-w-menu .men_' + key);
if (item.data && item.data.default) {
$el.html($el.text() + '<span class="show-default"></span>');
} else {
$el.find('span').remove();
}
$el.data('data', item);
if (key === 'dingding') {
that.all_info = item
}
});
})
}
}
</script>
+224
View File
@@ -0,0 +1,224 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
import os, sys, public, base64, json, re,requests
import sys, os
panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public, json, requests
from requests.packages import urllib3
# 关闭警告
urllib3.disable_warnings()
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
class dingding_msg:
conf_path = 'data/dingding.json'
__dingding_info = None
__module_name = None
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
def __init__(self):
try:
self.__dingding_info = json.loads(public.readFile(self.conf_path))
if not 'dingding_url' in self.__dingding_info or not 'isAtAll' in self.__dingding_info or not 'user' in self.__dingding_info:
self.__dingding_info = None
except :
self.__dingding_info = None
self.__module_name = self.__class__.__name__.replace('_msg','')
def get_version_info(self,get):
"""
获取版本信息
"""
data = {}
data['ps'] = 'Dingding is used to receive panel message push'
data['version'] = '1.2'
data['date'] = '2022-08-10'
data['author'] = 'aaPanel'
data['title'] = 'Dingding'
data['help'] = 'http://www.aapanel.com'
return data
def get_config(self,get):
"""
获取钉钉配置
"""
data = {}
if self.__dingding_info :
data = self.__dingding_info
if not 'list' in data: data['list'] = {}
title = 'Default'
if 'title' in data: title = data['title']
data['list']['default'] = {'title':title,'data':data['dingding_url']}
data['default'] = self.__get_default_channel()
return data
def set_config(self,get):
"""
设置钉钉配置
@url 钉钉URL
@atall 默认@全体成员
@user
"""
if not hasattr(get, 'url') or not hasattr(get, 'atall'):
return public.returnMsg(False, 'Please fill in the complete information')
user = []
status = 1
atall = False
if 'status' in get: status = int(get.status)
if 'user' in get: user = get.user.split('\n')
if 'atall' in get and get.atall == 'True':
atall = True
title = 'Default'
if hasattr(get, 'title'):
title = get.title
if len(title) > 7:
return public.returnMsg(False, 'Note name cannot exceed 7 characters')
self.__dingding_info = {"dingding_url": get.url.strip(),"isAtAll": atall, "user":user,"title":title}
try:
info = public.get_push_info('Message channel configuration reminder',['>configuration status: <font color=#20a53a>Success</font>\n\n'])
ret = self.send_msg(info['msg'])
except:
ret = self.send_msg('aaPanel alarm test')
if ret['status']:
if 'default' in get and get['default']:
public.writeFile(self.__default_pl, self.__module_name)
if ret['success'] <= 0:
return public.returnMsg(False, 'Failed to add, please check whether the URL is correct')
public.writeFile(self.conf_path, json.dumps(self.__dingding_info))
return public.returnMsg(True, 'Notification set successfully')
else:
return ret
def get_send_msg(self,msg):
"""
@name 处理md格式
"""
try:
title = 'aaPanel alarm notification'
if msg.find("####") >= 0:
try:
title = re.search(r"####(.+)", msg).groups()[0]
except:pass
else:
info = public.get_push_info('Alarm Mode Configuration Reminder',['>Send Content: ' + msg])
msg = info['msg']
except:pass
return msg,title
def send_msg(self,msg,to_user = 'default'):
"""
钉钉发送信息
@msg 消息正文
"""
if not self.__dingding_info :
return public.returnMsg(False,'DingTalk information is incorrectly configured.')
if isinstance(self.__dingding_info['user'],int):
return public.returnMsg(False,'DingTalk configuration error, please reconfigure the DingTalk robot.')
at_info = ''
for user in self.__dingding_info['user']:
if re.match("^[0-9]{11,11}$",str(user)): at_info += '@'+user+' '
msg,title = self.get_send_msg(msg)
if at_info: msg = msg + '\n\n>' + at_info
data = {
"msgtype": "markdown",
"markdown": {
"title": "server notification",
"text": msg
},
"at": {
"atMobiles": self.__dingding_info['user'],
"isAtAll": self.__dingding_info['isAtAll']
}
}
headers = {'Content-Type': 'application/json'}
error,success = 0,0
conf = self.get_config(None)['list']
res = {}
for to_key in to_user.split(','):
if not to_key in conf: continue
try:
allowed_gai_family_lib = urllib3_cn.allowed_gai_family
def allowed_gai_family():
family = socket.AF_INET
return family
urllib3_cn.allowed_gai_family = allowed_gai_family
x = requests.post(url = conf[to_key]['data'], data = json.dumps(data),verify=False, headers=headers,timeout=10)
urllib3_cn.allowed_gai_family=allowed_gai_family_lib
if x.json()["errcode"] == 0:
success += 1
res[conf[to_key]['title']] = 1
else:
error += 1
res[conf[to_key]['title']] = 0
except:
error += 1
res[conf[to_key]['title']] = 0
try:
public.write_push_log(self.__module_name,title,res)
except:pass
ret = public.returnMsg(True,'Send completed, send successfully{}, send failed{}.'.format(success,error))
ret['success'] = success
ret['error'] = error
return ret
def push_data(self,data):
"""
@name 统一发送接口
@data 消息内容
{"module":"mail","title":"标题","msg":"内容","to_email":"xx@qq.com","sm_type":"","sm_args":{}}
"""
return self.send_msg(data['msg'])
def __get_default_channel(self):
"""
@获取默认消息通道
"""
try:
if public.readFile(self.__default_pl) == self.__module_name:
return True
except:pass
return False
def uninstall(self):
if os.path.exists(self.conf_path):
os.remove(self.conf_path)
+142
View File
@@ -0,0 +1,142 @@
<div class="conter_box box_feishu">
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
<div class="flex" style="align-item: center; height: 32px;">
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
<div>
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
</div>
</div>
</div> -->
<div class="bt-form">
<div class="line">
<span class="tname">Notify everyone</span>
<div class="info-r" style="height:28px; margin-left:125px">
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" >
<label style="position: relative;top: 5px;" title="Only supports notify everyone." class="btswitch-btn" for="panel_alert_all"></label>
</div>
</div>
<div class="line">
<span class="tname">Feishu/Lark URL</span>
<div class="info-r">
<textarea name="channel_feishu_value" class="bt-input-text mr5" type="text" placeholder="Please enter Feishu/Lark url" style="width: 300px; height:90px; line-height:20px"></textarea>
</div>
<button class="btn btn-success btn-sm feishu_submit" style="margin: 10px 0 0 125px;">Save</button>
</div>
</div>
</div>
<style type="text/css">
.total_tips {
border: 1px solid #cbcbcb;
border-radius: 8px;
color: #cbcbcb;
cursor: pointer;
display: inline-block;
font-family: arial;
font-size: 12px;
font-style: normal;
height: 14px;
line-height: 14px;
margin-right: 5px;
text-align: center;
width: 14px;
}
</style>
<!--飞书模块-->
<script type="text/javascript">
var feishu = {
all_info: {},
init: function () {
var that = this;
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
this.get_feishu_data();
$('#panel_alert_all').attr('checked',this.all_info.data.isAtAll)
// 设置默认
$('#default_setting').change(function () {
var _default = $(this).prop('checked');
var _url = that.all_info.data.feishu_url;
if (!_url) {
layer.msg('Feishu/Lark is not configured URL', { icon: 2 })
$(this).prop('checked', !_default);
return
}
var loadTs = layer.msg('Feishu/Lark configuration is being set, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_default_channel&channel=feishu', { default: _default }, function (res) {
layer.close(loadTs);
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
if (res.status) that.refresh_data();
});
});
var showTips = ''
$('.total_tips').hover(function(){
showTips = setTimeout(function(){
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
tips: [1, '#20a53a'],
time: 0,
success:function(layero,indexs){
layero.css("left", $('.total_tips').offset().left - 10);
}})
},200)
},function(){
clearTimeout(showTips)
layer.closeAll('tips');
})
},
/**
*@description 获取飞书url,保存按钮添加事件
*/
get_feishu_data: function () {
var that = this;
var data = this.all_info.data;
if (data) {
var url = data.feishu_url || '';
var _default = data.hasOwnProperty('default') ? data.default : false
$('textarea[name=channel_feishu_value]').val(url);
$('#default_setting').prop('checked', _default);
}
// 飞书信息设置
$('.feishu_submit').click(function () {
that.set_submit_ding();
});
},
/**
*@description 设置飞书url信息,保存按钮
*/
set_submit_ding: function () {
var that = this;
var _url = $('textarea[name=channel_feishu_value]').val(),
_isAll = $('#panel_alert_all').prop('checked');
if (_url == '') return layer.msg('Please enter Feishu/Lark url', { icon: 2 })
var loadT = layer.msg('Feishu/Lark is being set up, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_msg_config&name=feishu', { url: _url, atall: _isAll?'True':'False' }, function (rdata) {
layer.close(loadT);
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
if (rdata.status) that.refresh_data();
})
},
refresh_data: function () {
var that = this
$.post('/config?action=get_msg_configs', function (rdata) {
$.each(rdata, function (key, item) {
var $el = $('.alarm-view .bt-w-menu .men_' + key);
if (item.data && item.data.default) {
$el.html($el.text() + '<span class="show-default"></span>');
} else {
$el.find('span').remove();
}
$('.alarm-view .bt-w-menu .men_' + key).data('data', item);
if (key === 'feishu') {
that.all_info = item
}
});
})
}
}
</script>
+214
View File
@@ -0,0 +1,214 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: lx
# | 消息通道飞书通知模块
# +-------------------------------------------------------------------
import os, sys, public, json, requests
import sys, os
panelPath = '/www/server/panel'
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public, json, requests
from requests.packages import urllib3
# 关闭警告
urllib3.disable_warnings()
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
class feishu_msg:
conf_path = 'data/feishu.json'
__feishu_info = None
__module_name = None
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
def __init__(self):
try:
self.__feishu_info = json.loads(public.readFile(self.conf_path))
if not 'feishu_url' in self.__feishu_info or not 'isAtAll' in self.__feishu_info or not 'user' in self.__feishu_info:
self.__feishu_info = None
except :
self.__feishu_info = None
self.__module_name = self.__class__.__name__.replace('_msg','')
def get_version_info(self,get):
"""
获取版本信息
"""
data = {}
data['ps'] = 'Feishu is used to receive panel message push'
data['version'] = '1.2'
data['date'] = '2022-08-10'
data['author'] = 'aaPanel'
data['title'] = 'Feishu'
data['help'] = 'http://www.aapanel.com'
return data
def __get_default_channel(self):
"""
@获取默认消息通道
"""
try:
if public.readFile(self.__default_pl) == self.__module_name:
return True
except:pass
return False
def get_config(self,get):
"""
获取飞书配置
"""
data = {}
if self.__feishu_info :
data = self.__feishu_info
if not 'list' in data: data['list'] = {}
title = 'Default'
if 'title' in data: title = data['title']
data['list']['default'] = {'title':title,'data':data['feishu_url']}
data['default'] = self.__get_default_channel()
return data
def set_config(self,get):
"""
设置飞书配置
@url 飞书URL
@atall 默认@全体成员
@user
"""
if not hasattr(get, 'url'):
return public.returnMsg(False, 'Please fill in the complete information')
isAtAll = True
if hasattr(get, "atall"):
if get.atall.lower() == "false":
isAtAll = False
title = 'Default'
if hasattr(get, 'title'):
title = get.title
if len(title) > 7:
return public.returnMsg(False, 'Note name cannot exceed 7 characters')
self.__feishu_info = {"feishu_url": get.url.strip(), "isAtAll": isAtAll, "user":1,"title":title}
ret = self.send_msg('aaPanel alarm test')
if ret['status']:
if 'default' in get and get['default']:
public.writeFile(self.__default_pl, self.__module_name)
if ret['success'] <= 0:
return public.returnMsg(False, 'Failed to add, please check whether the URL is correct')
public.writeFile(self.conf_path, json.dumps(self.__feishu_info))
return public.returnMsg(True, 'successfully set')
else:
return public.returnMsg(False, 'Failed to add, please check whether the URL is correct')
def get_send_msg(self,msg):
"""
@name 处理md格式
"""
try:
import re
title = 'aaPanel warning notification'
if msg.find("####") >= 0:
try:
title = re.search(r"####(.+)", msg).groups()[0]
except:pass
msg = msg.replace("####",">").replace("\n\n","\n").strip()
s_list = msg.split('\n')
if len(s_list) > 3:
s_title = s_list[0].replace(" ","")
s_list = s_list[1:]
s_list.insert(0,s_title)
msg = '\n'.join(s_list)
reg = '<font.+>(.+)</font>'
tmp = re.search(reg,msg)
if tmp:
tmp = tmp.groups()[0]
msg = re.sub(reg,tmp,msg)
except:pass
return msg,title
def send_msg(self,msg,to_user = 'default'):
"""
飞书发送信息
@msg 消息正文
"""
if not self.__feishu_info :
return public.returnMsg(False,'Feishu information is not configured correctly.')
msg,title = self.get_send_msg(msg)
if self.__feishu_info["isAtAll"]:
msg += "<at userid='all'>Everyone</at>"
data = {
"msg_type": "text",
"content": {
"text": msg
}
}
headers = {'Content-Type': 'application/json'}
res = {}
error,success = 0,0
conf = self.get_config(None)['list']
for to_key in to_user.split(','):
if not to_key in conf: continue
try:
allowed_gai_family_lib=urllib3_cn.allowed_gai_family
def allowed_gai_family():
family = socket.AF_INET
return family
urllib3_cn.allowed_gai_family = allowed_gai_family
rdata = requests.post(url = conf[to_key]['data'], data = json.dumps(data),verify=False, headers=headers,timeout=10).json()
urllib3_cn.allowed_gai_family=allowed_gai_family_lib
# x = requests.post(url = conf[to_key]['data'], data=json.dumps(data), headers=headers,verify=False,timeout=10)
# rdata = x.json()
if "StatusCode" in rdata and rdata["StatusCode"] == 0:
success += 1
res[conf[to_key]['title']] = 1
else:
error += 1
res[conf[to_key]['title']] = 0
except:
public.print_log(public.get_error_info())
error += 1
res[conf[to_key]['title']] = 0
try:
public.write_push_log(self.__module_name,title,res)
except:pass
ret = public.returnMsg(True,'Send completed, send successfully {}, send failed {}.'.format(success,error))
ret['success'] = success
ret['error'] = error
return ret
def push_data(self,data):
return self.send_msg(data['msg'])
def uninstall(self):
if os.path.exists(self.conf_path):
os.remove(self.conf_path)
+253
View File
@@ -0,0 +1,253 @@
<div class="conter_box active box_mail">
<div class="bt-form">
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
<div class="flex" style="align-item: center; height: 32px;">
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
<div>
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
</div>
</div>
</div> -->
<div class="line">
<div class="tab-nav recipient_nav relative">
<span class="on" data-ctype="0" style="line-height: 30px;">Recipient setting</span>
<span data-ctype="1" style="line-height: 30px;">Sender setting</span>
</div>
</div>
<div class="line recipient_view">
<div class="line relative">
<textarea name="recipient_textarea" class="bt-input-text mr5" type="text" style="width: 300px; height:120px; line-height:20px"></textarea>
<div class="placeholder c9 reci_tips" style="position: absolute;top: 25px;left: 25px; display:none">Fill in one email address per line, for example<br>xxx@163.com<br>xxx@qq.com</div>
</div>
<button class="btn btn-success btn-sm recipient_submit" style="/* margin-left: 100px; */">Save</button>
</div>
<div class="line sender_view" style="display:none">
<div class="line">
<span class="tname">Sender Email</span>
<div class="info-r">
<input name="sender_mail_value" class="bt-input-text mr5" type="text" style="width: 300px">
</div>
</div>
<div class="line">
<span class="tname">SMTP Password</span>
<div class="info-r">
<input name="sender_mail_password" class="bt-input-text mr5" type="password" style="width: 300px">
</div>
</div>
<div class="line">
<span class="tname">SMTP Server</span>
<div class="info-r">
<input name="sender_mail_server" class="bt-input-text mr5" type="text" style="width: 300px">
</div>
</div>
<div class="line">
<span class="tname">SMTP Port</span>
<div class="info-r">
<input name="sender_mail_port" class="bt-input-text mr5" type="text" style="width: 300px">
</div>
</div>
<button class="btn btn-success btn-sm sender_submit" style="margin-left: 125px;">Save</button>
<ul class="help-info-text c7">
<li>Recommended use port 465, and the protocol is SSL/TLS</li>
<li>Port 25 is SMTP protocol, port 587 is STARTTLS protocol</li>
<li>Not support Gmail, Outlook, Yahoo</li>
<li><a href="https://www.bt.cn/bbs/thread-71298-1-1.html" target="_blank" class="btlink">Tutorial</a></li>
</ul>
</div>
</div>
</div>
<style type="text/css">
.total_tips {
border: 1px solid #cbcbcb;
border-radius: 8px;
color: #cbcbcb;
cursor: pointer;
display: inline-block;
font-family: arial;
font-size: 12px;
font-style: normal;
height: 14px;
line-height: 14px;
margin-right: 5px;
text-align: center;
width: 14px;
}
</style>
<!--邮箱模块-->
<script type="text/javascript">
var mail = {
all_mail_info: {},
init: function () {
var that = this;
this.all_mail_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
this.gat_info();
this.init_default();
// 选项卡切换时
$('.recipient_nav span').click(function () {
var _type = $(this).attr('data-ctype');
$(this).addClass('on').siblings().removeClass('on')
switch (_type) {
case '0':
$('.recipient_view').show();
$('.sender_view').hide();
break;
case '1':
$('.recipient_view').hide();
$('.sender_view').show();
that.get_sender_data();
break;
}
})
// 收件者保存按钮
$('.recipient_submit').click(function () {
that.recipient_submit();
})
// 发送者信息设置
$('.sender_submit').click(function () {
that.sender_submit();
})
// 设置默认
$('#default_setting').change(function () {
var _default = $(this).prop('checked');
var _send = that.all_mail_info.data.send;
if (!_send) {
layer.msg('Email sender settings not configured', { icon: 2 })
$(this).prop('checked', !_default);
return
}
var loadTs = layer.msg('Setting up Email configuration, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_default_channel&channel=mail', { default: _default }, function (res) {
layer.close(loadTs);
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
if (res.status) that.refresh_data();
});
});
var showTips = ''
$('.total_tips').hover(function(){
showTips = setTimeout(function(){
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
tips: [1, '#20a53a'],
time: 0,
success:function(layero,indexs){
layero.css("left", $('.total_tips').offset().left - 10);
}})
},200)
},function(){
clearTimeout(showTips)
layer.closeAll('tips');
})
},
/**
*@description 获取邮箱信息、设置收件者提示语事件
*/
gat_info: function () {
var _tips = $('textarea[name=recipient_textarea]');
var msg = ''
if (!$.isEmptyObject(this.all_mail_info['data']['receive'])) {
msg = mail.all_mail_info['data']['receive'] ? mail.all_mail_info['data']['receive'].join('\n') : ''
}
_tips.html(msg)
// 设置收件者tips
if (_tips.val() == '') $('.reci_tips.placeholder').show();
$('.placeholder').click(function () { $(this).hide().siblings('textarea').focus() })
_tips.focus(function () {
$('.reci_tips.placeholder').hide()
})
_tips.blur(function () {
_tips.val() == '' ? $('.reci_tips.placeholder').show() : $('.reci_tips.placeholder').hide()
});
},
init_default: function () {
var data = this.all_mail_info.data;
if (!$.isEmptyObject(data)) {
var _default = data.hasOwnProperty('default') ? data.default : false;
$('#default_setting').prop('checked', _default);
}
},
/**
*@description 设置发送者信息
*/
get_sender_data: function () {
var that = this;
var data = this.all_mail_info.data;
if (!$.isEmptyObject(data) && !$.isEmptyObject(data.send)) {
var send = data.send;
var mail_ = send.qq_mail || '',
stmp_pwd_ = send.qq_stmp_pwd || '',
hosts_ = send.hosts || '',
port_ = send.port || '';
$('input[name=sender_mail_value]').val(mail_)
$('input[name=sender_mail_password]').val(stmp_pwd_)
$('input[name=sender_mail_server]').val(hosts_)
$('input[name=sender_mail_port]').val(port_)
} else {
$('input[name=sender_mail_port]').val('465')
}
},
/**
*@description 设置收件者邮箱,保存按钮
*/
recipient_submit: function () {
var that = this;
var reci_ = $('textarea[name=recipient_textarea]').val();
var loadTs = layer.msg('Please wait while the recipient email is being set...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_msg_config&name=mail', { mails: reci_ }, function (res) {
layer.close(loadTs);
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
if (res.status) that.refresh_data();
})
},
/**
*@description 设置发送者邮箱,保存按钮
*/
sender_submit: function () {
var that = this;
var _email = $('input[name=sender_mail_value]').val(),
_passW = $('input[name=sender_mail_password]').val(),
_server = $('input[name=sender_mail_server]').val(),
_port = $('input[name=sender_mail_port]').val();
if (_email == '') return layer.msg('Email address cannot be empty', { icon: 2 });
if (_passW == '') return layer.msg('STMP password cannot be empty', { icon: 2 });
if (_server == '') return layer.msg('STMP server address cannot be empty', { icon: 2 });
if (_port == '') return layer.msg('Please enter valid port number', { icon: 2 });
var loadTs = layer.msg('The Email notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_msg_config&name=mail', {
send: 1,
qq_mail: _email,
qq_stmp_pwd: _passW,
hosts: _server,
port: _port
}, function (rdata) {
layer.close(loadTs);
if (rdata.status) that.refresh_data();
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
});
},
refresh_data: function () {
var that = this
$.post('/config?action=get_msg_configs', function (rdata) {
$.each(rdata, function (key, item) {
var $el = $('.alarm-view .bt-w-menu .men_' + key);
if (item.data && item.data.default) {
$el.html($el.text() + '<span class="show-default"></span>');
} else {
$el.find('span').remove();
}
$('.alarm-view .bt-w-menu .men_' + key).data('data', item);
if (key === 'mail') {
that.all_mail_info = item;
}
});
})
}
}
</script>
+229
View File
@@ -0,0 +1,229 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
import os, sys, public, base64, json, re
import sys, os
panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
class mail_msg:
__module_name = None
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
__mail_send_conf = '/www/server/panel/data/stmp_mail.json'
__mail_receive_conf = '/www/server/panel/data/mail_list.json'
__mail_config = None
def __init__(self):
self.__mail_config = {}
if os.path.exists('data/stmp_mail.json'):
self.__mail_config['send'] = json.loads(public.readFile(self.__mail_send_conf))
if os.path.exists('data/mail_list.json'):
self.__mail_config['receive'] = json.loads(public.readFile(self.__mail_receive_conf))
if not 'send' in self.__mail_config: self.__mail_config['send'] = {}
if not 'receive' in self.__mail_config: self.__mail_config['receive'] = {}
if 'qq_mail' not in self.__mail_config['send'] or 'qq_stmp_pwd' not in self.__mail_config['send'] or 'hosts' not in self.__mail_config['send']: self.__mail_config = None
self.__module_name = self.__class__.__name__.replace('_msg','')
def __get_default_channel(self):
"""
@获取默认消息通道
"""
try:
if public.readFile(self.__default_pl) == self.__module_name:
return True
except:pass
return False
def get_version_info(self,get):
"""
获取版本信息
"""
data = {}
data['ps'] = 'Email is used to receive panel message push'
data['version'] = '1.1'
data['date'] = '2022-08-10'
data['author'] = 'aaPanel'
data['title'] = 'Email'
data['help'] = 'http://www.aapanel.com'
return data
def get_config(self,get):
"""
获取QQ邮箱配置
"""
data = {}
if self.__mail_config:
data = self.__mail_config
if "send" in data:
if not os.path.exists(self.__mail_send_conf):
public.writeFile(self.__mail_send_conf,json.dumps(data["send"]))
if "receive" in data:
if not os.path.exists(self.__mail_receive_conf):
public.writeFile(self.__mail_receive_conf, json.dumps(data["receive"]))
data['default'] = self.__get_default_channel()
return data
def set_config(self,get):
"""
设置邮箱配置
@send 带有此参数表示设置发送者设置
@qq_mail 发送者邮箱
@qq_stmp_pwd 发送者密码
@hosts 发送stmp
@发送端口port
@mails 接收者配置一行一个111@qq.com\n222@qq.com
"""
if not self.__mail_config:
self.__mail_config = {'send':{},'receive':[]}
if hasattr(get, 'send'):
if not hasattr(get, 'qq_mail') or not hasattr(get, 'qq_stmp_pwd') or not hasattr(get, 'hosts') or not hasattr(get, 'port'): return public.returnMsg(False, 'Please fill in the complete information')
mail_config = {
"qq_mail": get.qq_mail.strip(),
"qq_stmp_pwd": get.qq_stmp_pwd.strip(),
"hosts": get.hosts.strip(),
"port": get.port
}
self.__mail_config['send'] = mail_config
else:
mails = ''
if hasattr(get, 'mails'):
mails = get.mails.strip()
arrs = []
for mail in mails.split('\n'):
if not mail.strip(): continue
arrs.append(mail.strip())
self.__mail_config['receive'] = arrs
#首次配置同步接收者配置
receive_list = self.__mail_config['receive']
mail_address = self.__mail_config['send']['qq_mail']
if not mail_address in receive_list and len(receive_list) == 0:
if not 'receive' in self.__mail_config:
self.__mail_config['receive'] = []
self.__mail_config['receive'].append(mail_address)
ret = self.send_msg("宝塔测试邮件")
if ret['status']:
if ret['success'] <= 0:
return public.returnMsg(False, 'Sending failed, please check whether the sender configuration or receiver information is correct.')
public.writeFile(self.__mail_send_conf, json.dumps(self.__mail_config['send']))
public.writeFile(self.__mail_receive_conf,json.dumps(self.__mail_config['receive']))
return public.returnMsg(True, 'successfully set')
else:
return ret
def get_send_msg(self,msg):
"""
@name 处理md格式
"""
try:
title = None
if msg.find("####") >= 0:
try:
title = re.search(r"####(.+)\n", msg).groups()[0]
except:pass
msg = msg.replace("\n\n","<br>").strip()
except:pass
return msg,title
def send_msg(self,msg , title = 'aaPanel panel message push',to_email = None):
"""
邮箱发送
@msg 消息正文
@title 消息标题
@to_email 发送给谁默认发送所有人
"""
if not self.__mail_config :
return public.returnMsg(False,'Email information is not configured correctly.')
if not 'port' in self.__mail_config['send']: self.__mail_config['send']['port'] = 465
receive_list = []
if to_email:
for x in to_email.split(','):
receive_list.append(x)
else:
receive_list = self.__mail_config['receive']
res = {}
ret_msg = {}
error ,sucess,total = 0,0,0
msg,n_title = self.get_send_msg(msg)
if n_title: title = n_title
for email in receive_list:
if not email: continue
try:
data = MIMEText(msg, 'html', 'utf-8')
data['From'] = formataddr([self.__mail_config['send']['qq_mail'], self.__mail_config['send']['qq_mail']])
data['To'] = formataddr([self.__mail_config['send']['qq_mail'], email.strip()])
data['Subject'] = title
if int(self.__mail_config['send']['port']) == 465:
server = smtplib.SMTP_SSL(str(self.__mail_config['send']['hosts']), str(self.__mail_config['send']['port']))
else:
server = smtplib.SMTP(str(self.__mail_config['send']['hosts']), str(self.__mail_config['send']['port']))
server.login(self.__mail_config['send']['qq_mail'], self.__mail_config['send']['qq_stmp_pwd'])
server.sendmail(self.__mail_config['send']['qq_mail'], [email.strip(), ], data.as_string())
server.quit()
sucess += 1
res[email] = 1
except :
error += 1
res[email] = 0
ret_msg[email] = public.get_error_info()
total += 1
try:
if res:
public.write_push_log(self.__module_name,title,res)
else:
public.WriteLog('Alarm notification','[Email] No sender configured, please configure first.')
except:pass
result = public.returnMsg(True,'The sending is complete, a total of [{}] items were sent, [{}] succeeded, and [{}] failed.'.format(total,sucess,error))
result['list'] = ret_msg
result['success'] = sucess
result['error'] = error
return result
def push_data(self,data):
to_email = data.get("to_email", None)
if 'to_user' in data:
to_email = data['to_user']
return self.send_msg(data['msg'],data['title'],to_email)
def uninstall(self):
if os.path.exists(self.__mail_send_conf):
os.remove(self.__mail_send_conf)
if os.path.exists(self.__mail_receive_conf):
os.remove(self.__mail_receive_conf)
+104
View File
@@ -0,0 +1,104 @@
<div class="conter_box box_sms">
<div class="bt-form">
<div class="progress">
<div class="progress-bar progress-bar-success" id="smsTotalNumber" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 100%;">
<div class="progress_text"><span>总条数:</span><span class="sm_total">{{ data.get("total", -1) }}</span><span> 剩余条数:</span><span class="sm_count">{{ data.get("count", -1) }}</span></div>
</div>
</div>
<!--<div class="progress_text"><span>总条数:</span><span class="sm_total">0</span><span> 剩余条数:</span><span class="sm_count">0</span></div>-->
<ul class="help-info-text c7">
<li><span style="color:red">重要:开启短信登录【必须开启安全入口】,否则存在【安全风险】</span></li>
<li>当前短信仅支持面板消息推送</li>
<li>如需面板部分功能需要增加短信推送,请联系客服</li>
<li>如需购买短信条数,请联系微信客服<a class="btlink" onclick="bt.onlineService()">微信客服</a></li>
</ul>
</div>
</div>
<script src="/static/js/jquery.qrcode.min.js" defer=""></script>
<!--短信模块-->
<script type="text/javascript">
var sms = {
all_info: {},
init: function () {
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
//this.get_sms_data();
},
/**
*@description 获取短信消息,保存按钮添加事件
*/
get_sms_data: function () {
var that = this;
if (sms.all_info['data']['count']) {
var info_data = sms.all_info['data'];
var data = (info_data['count'] / info_data['total']) * 100
$('.sm_count').text(info_data['count'])
$('.sm_total').text(info_data['total'])
$('#smsTotalNumber').css('width', data.toFixed(2) + '%')
// $('#smsTotalNumber').text(data.toFixed(2) + '%')
$('#panel_login').prop('checked',info_data['login'] == 1 ?true:false);
}
$('.sms_label').click(function(){
var _ev = $('#panel_login'),tips = '';
console.log(_ev.prop('checked'),'che')
if(_ev.prop('checked')){
tips = '是否关闭短信登录?'
}else{
tips = '<span style="color:red">开启短信登录必须【开启安全入口】,否则存在【安全风险】</span>,是否继续?'
}
layer.confirm(tips,{btn:['确认','取消'],icon:3,closeBtn: 2,title:'短信登录'},function(){
that.set_config_data();
},function(index){
_ev.prop('checked',!_ev.prop('checked'))
})
})
},
set_config_data: function () {
var that = this
$.post('/config?action=set_msg_config&name=sms', {login:$('#panel_login').prop('checked')?1:0}, function (rdata) {
if(rdata.status){
that.refresh_data();
}else{
$('#panel_login').prop('checked',!$('#panel_login').prop('checked'));
}
layer.msg(rdata.msg,{icon:rdata.status?1:2});
})
},
refresh_data: function () {
var that = this
$.post('/config?action=get_msg_configs', function (rdata) {
$.each(rdata, function (index, item) {
if (item.name == that.all_info.name) {
$('.alarm-view .bt-w-menu p.bgw').data('data', item)
// that.init()
}
});
})
}
}
// 人工服务 带有参数为售前客服
function wechatKefuConsult(){
layer.open({
type: 1,
area: ['300px', '290px'],
title: false,
closeBtn: 2,
shift: 0,
content: '<div class="service_consult">\
<div class="service_consult_title">请打开微信"扫一扫"</div>\
<div class="contact_consult" style="margin-bottom: 5px;"><div id="contact_consult_qcode"></div><i class="wechatEnterprise"></i></div>\
<div>【微信客服】</div>\
<ul class="c7" style="margin-top:22px;text-align: center;">\
<li>工作时间:9:15 - 18:00</li>\
</ul>\
</div>',
success:function(){
$('#contact_consult_qcode').qrcode({
render: "canvas",
width: 140,
height: 140,
text:'https://work.weixin.qq.com/kfid/kfc72fcbde93e26a6f3'
});
}
})
}
</script>
+217
View File
@@ -0,0 +1,217 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
import os, sys, public, base64, json, re
import sys, os
panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public
class sms_msg:
_APIURL = 'http://www.bt.cn/api/wmsg';
__UPATH = panelPath + '/data/userInfo.json';
conf_path = panelPath + '/data/sms_main.json'
#构造方法
def __init__(self):
self.setupPath = public.GetConfigValue('setup_path')
pdata = {}
data = {}
if os.path.exists(self.__UPATH):
try:
self.__userInfo = json.loads(public.readFile(self.__UPATH));
if self.__userInfo:
pdata['access_key'] = self.__userInfo['access_key'];
data['secret_key'] = self.__userInfo['secret_key'];
except :
self.__userInfo = None
else:
pdata['access_key'] = 'test'
data['secret_key'] = '123456'
pdata['data'] = data
self.__PDATA = pdata
self.__module_name = self.__class__.__name__.replace('_msg','')
def get_version_info(self,get):
"""
获取版本信息
"""
data = {}
data['ps'] = '宝塔短信消息通道,用于接收面板消息推送'
data['version'] = '1.1'
data['date'] = '2022-08-02'
data['author'] = '宝塔'
data['title'] = '短信'
data['help'] = 'http://www.bt.cn'
return data
def get_config(self,get):
result = {}
data = {}
skey = 'sms_count_{}'.format(public.get_user_info()['username'])
try:
from BTPanel import cache
result = cache.get(skey)
except:
cache = None
if not result:
result = self.request('get_user_sms')
if cache: cache.set(skey,result,3600)
try:
data = json.loads(public.readFile(self.conf_path))
except :pass
for key in data.keys():
result[key] = data[key]
return result
def is_strong_password(self,password):
"""判断密码复杂度是否安全
非弱口令标准长度大于等于9分别包含数字小写
@return: True/False
@author: linxiao<2020-9-19>
"""
if len(password) < 6:return False
import re
digit_reg = "[0-9]" # 匹配数字 +1
lower_case_letters_reg = "[a-z]" # 匹配小写字母 +1
special_characters_reg = r"((?=[\x21-\x7e]+)[^A-Za-z0-9])" # 匹配特殊字符 +1
regs = [digit_reg,lower_case_letters_reg,special_characters_reg]
grade = 0
for reg in regs:
if re.search(reg, password):
grade += 1
if grade >= 2 or (grade == 1 and len(password) >= 9):
return True
return False
def __check_auth_path(self):
auth = public.readFile('data/admin_path.pl')
if not auth: return False
slist = ['/','/123456','/admin123','/111111','/bt','/login','/cloudtencent','/tencentcloud','/admin','/admin888','/test']
if auth in slist: return False
if not self.is_strong_password(auth.strip('/')):
return False
return True
def set_config(self,get):
data = {}
try:
data = json.loads(public.readFile(self.conf_path))
except :pass
if 'login' in get:
is_login = int(get['login'])
if is_login and not self.__check_auth_path(): return public.returnMsg(False,'安全入口过于简单,存在安全隐患. <br>1、长度不得少于9位<br>2、英文+数字组合.')
data['login'] = is_login
public.writeFile(self.conf_path,json.dumps(data));
return public.returnMsg(True, '操作成功!')
"""
@发送短信
@sm_type 预警类型, ssl_end|宝塔SSL到期提醒
@sm_args 预警参数
"""
def send_msg(self,sm_type = None,sm_args = None):
s_type = sm_type
title = '宝塔告警提醒'
tmps = sm_type.split('|')
if len(tmps) >= 2:
s_type = tmps[0]
title = tmps[1]
self.__PDATA['data']['sm_type'] = s_type
self.__PDATA['data']['sm_args'] = sm_args
result = self.request('send_msg')
try:
res = {}
uinfo = public.get_user_info()
u_key = '{}****{}'.format(uinfo['username'][0:3],uinfo['username'][-3:])
res[u_key] = 0
if result['status']:
res[u_key] = 1
skey = 'sms_count_{}'.format(public.get_user_info()['username'])
try:
from BTPanel import cache
except:
cache = None
if not result:
result = self.request('get_user_sms')
if cache: cache.set(skey,result,3600)
public.write_push_log(self.__module_name,title,res)
except:pass
return result
def canonical_data(self, args):
"""规范数据内容
Args:
args(dict): 消息原始参数
Returns:
new args: 替换后的消息参数
"""
if not type(args) == dict: return args
new_args = {}
for param, value in args.items():
if type(value) != str:
new_str = str(value)
else:
new_str = value.replace(".", "_").replace("+", "")
new_args[param] = new_str
return new_args
def push_data(self,data):
sm_args = self.canonical_data(data['sm_args'])
return self.send_msg(data['sm_type'],sm_args)
#发送请求
def request(self,dname):
pdata = {}
pdata['access_key'] = self.__PDATA['access_key']
pdata['data'] = json.dumps(self.__PDATA['data'])
try:
result = public.httpPost(self._APIURL + '/' + dname,pdata)
result = json.loads(result)
# print("发送result:")
# print(result)
return result
except Exception as e:
# print("短信发送异常:")
# print(e)
return public.returnMsg(False,public.get_error_info())
def uninstall(self):
if os.path.exists(self.conf_path):
os.remove(self.conf_path)
+149
View File
@@ -0,0 +1,149 @@
<div class="conter_box box_tg">
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
<div class="flex" style="align-item: center; height: 32px;">
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
<div>
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
</div>
</div>
</div> -->
<div class="bt-form">
<div class="line">
<span class="tname" style="width: 100px;">ID</span>
<div class="info-r" style="height:28px; margin-left:100px;">
<input type="text" name="telegram_id" class="bt-input-text " style="width: 280px;" placeholder="Telegram ID">
</div>
</div>
<div class="line">
<span class="tname" style="width: 100px;">TOKEN</span>
<div class="info-r">
<input type="text" name="telegram_token" class="bt-input-text" style="width: 280px;" placeholder="Telegram TOKEN">
</div>
<button class="btn btn-success btn-sm tg_submit" style="margin: 10px 0 0 100px;">Save</button>
</div>
</div>
<ul class="help-info-text c7">
<li>ID: Your telegram user ID</li>
<li>Token: Your telegram bot token</li>
<li>e.g: [ 12345677:AAAAAAAAA_a0VUo2jjr__CCCCDDD ] <a class="btlink" href="https://forum.aapanel.com/d/5115-how-to-add-telegram-to-panel-notifications" target="_blank" rel="noopener">Help</a></li>
</ul>
</div>
<style type="text/css">
.total_tips {
border: 1px solid #cbcbcb;
border-radius: 8px;
color: #cbcbcb;
cursor: pointer;
display: inline-block;
font-family: arial;
font-size: 12px;
font-style: normal;
height: 14px;
line-height: 14px;
margin-right: 5px;
text-align: center;
width: 14px;
}
</style>
<!--tg模块-->
<script type="text/javascript">
var tg = {
all_info: {},
init: function () {
var that = this;
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
this.get_data();
// 设置默认
$('#default_setting').change(function () {
var _default = $(this).prop('checked');
var token = that.all_info.data.bot_token;
var id = that.all_info.data.my_id;
if (!id || !token) {
layer.msg('Telegram is not configured', { icon: 2 });
$(this).prop('checked', !_default);
return
}
var loadTs = layer.msg('Setting Telegram moduleplease wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_default_channel&channel=tg', { default: _default }, function (res) {
layer.close(loadTs);
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
if (res.status) that.refresh_data();
});
});
var showTips = ''
$('.total_tips').hover(function(){
showTips = setTimeout(function(){
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
tips: [1, '#20a53a'],
time: 0,
success:function(layero,indexs){
layero.css("left", $('.total_tips').offset().left - 10);
}})
},200)
},function(){
clearTimeout(showTips)
layer.closeAll('tips');
})
},
/**
*@description 获取,保存按钮添加事件
*/
get_data: function () {
var that = this;
var data = this.all_info.data;
if (data) {
var _default = data.hasOwnProperty('default') ? data.default : false
$('input[name=telegram_id]').val(data.my_id || '');
$('input[name=telegram_token]').val(data.bot_token || '');
$('#default_setting').prop('checked', _default);
}
// 保存按钮点击事件
$('.tg_submit').click(function () {
that.set_submit();
});
},
/**
*@description 保存信息
*/
set_submit: function () {
var that = this;
var id = $('input[name=telegram_id]').val(),
token = $('input[name=telegram_token]').val(),
_isAll = $('#panel_alert_all').prop('checked');
if (id == '') return layer.msg('Please enter Telegram ID!', { icon: 2 });
if (token == '') return layer.msg('Please enter Telegram token!', { icon: 2 });
var loadT = layer.msg('Setting Telegram moduleplease wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_msg_config&name=tg', { my_id: id, bot_token: token, atall: _isAll ? 'True' : 'False' }, function (rdata) {
layer.close(loadT);
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
if (rdata.status) that.refresh_data();
})
},
refresh_data: function () {
var that = this
$.post('/config?action=get_msg_configs', function (rdata) {
$.each(rdata, function (key, item) {
var $el = $('.alarm-view .bt-w-menu .men_' + key);
if (item.data && item.data.default) {
$el.html($el.text() + '<span class="show-default"></span>');
} else {
$el.find('span').remove();
}
$('.alarm-view .bt-w-menu .men_' + key).data('data', item);
if (key === 'tg') {
that.all_info = item
}
});
})
}
}
</script>
+282
View File
@@ -0,0 +1,282 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: jose <zhw@bt.cn>
# | 消息通道电报模块
# +-------------------------------------------------------------------
import sys, os, re, asyncio, public, json, requests
try:
import telegram
except:
public.ExecShell("btpip install -I python-telegram-bot")
import telegram
panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0, panelPath + "/class/")
from requests.packages import urllib3
# 关闭警告
urllib3.disable_warnings()
class tg_msg:
conf_path = "{}/data/tg_bot.json".format(panelPath)
__tg_info = None
__module_name = None
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
def __init__(self):
try:
red_conf_path = public.readFile(self.conf_path)
self.__tg_info = json.loads(red_conf_path)
if not 'bot_token' in self.__tg_info or not 'my_id' in self.__tg_info:
self.__tg_info = None
except:
self.__tg_info = None
self.__module_name = self.__class__.__name__.replace('_msg', '')
def get_version_info(self, get):
"""
获取版本信息
"""
data = {}
data['ps'] = 'Use telegram bots to send receive panel notifications'
data['version'] = '1.0'
data['date'] = '2022-08-10'
data['author'] = 'aaPanel'
data['title'] = 'Telegram'
data['help'] = 'http://www.aapanel.com'
return data
def get_config(self, get):
"""
获取tg配置
"""
data = {}
if self.__tg_info:
data = self.__tg_info
data['default'] = self.__get_default_channel()
return data
def set_config(self, get):
"""
设置tg bot
@my_id tg id
@bot_token 机器人token
"""
if not hasattr(get, 'my_id') or not hasattr(get, 'bot_token'):
return public.returnMsg(False, 'Please fill in the complete information')
title = 'Default'
if hasattr(get, 'title'):
title = get.title
if len(title) > 7:
return public.returnMsg(False, 'Note name cannot exceed 7 characters')
self.__tg_info = {"my_id": get.my_id.strip(), "bot_token": get.bot_token, "title": title, "status": True}
try:
info = public.get_push_info('Notification Configuration Reminder',
['>Configuration status<font color=#20a53a>successfully</font>\n\n'])
ret = self.send_msg(info['msg'])
except:
ret = self.send_msg('aaPanel alarm test')
if ret:
if 'default' in get and get['default']:
public.writeFile(self.__default_pl, self.__module_name)
public.writeFile(self.conf_path, json.dumps(self.__tg_info))
return public.returnMsg(True, 'successfully set')
else:
return ret
def get_send_msg(self, msg):
"""
@name 处理md格式
"""
try:
title = 'aaPanel notifications'
if msg.find("####") >= 0:
try:
title = re.search(r"####(.+)", msg).groups()[0]
except:
pass
else:
info = public.get_push_info('Notification Configuration Reminder', ['>Send Content: ' + msg])
msg = info['msg']
except:
pass
return msg, title
def process_character(self, msg):
"""
格式化消息
"""
# 去掉无用的转义字符
msg = msg.replace('\\', '')
# 去掉 HTML 标签
msg = re.sub(r'<[^>]+>', '', msg)
# 去掉标题中的 ####
msg = msg.replace('####', '')
# > 增加空格
msg = msg.replace('>', '> ')
# 去掉标题两边的空格
title = msg.split('\n')[0].strip()
# 去掉标题后面的换行符和空行,保留消息
msg = msg.replace(f'{title}\n\n', '')
# 去掉消息前后的空格
msg = msg.strip()
character = ['\\', '_', '`', '*', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '=', '.', '!']
for c in character:
if c in msg:
msg = msg.replace(c, '\\' + c)
# 将处理后的消息发送到Telegram
msg = f'*{title}*\n\n{msg}'
return msg
async def send_msg_async(self, bot_token, chat_id, msg):
"""
tg发送信息
@msg 消息正文
"""
bot = telegram.Bot(token=bot_token)
msg = self.process_character(msg)
public.print_log(msg)
await bot.send_message(chat_id=chat_id, text=msg, parse_mode='MarkdownV2')
def send_msg(self, msg):
"""
tg发送信息
@msg 消息正文
"""
if not self.__tg_info:
return public.returnMsg(False, 'The telegram information is incorrectly configured.')
if isinstance(self.__tg_info['my_id'], int):
return public.returnMsg(False, 'Telegram configuration error, please reconfigure the robot.')
msg, title = self.get_send_msg(msg)
# public.WriteFile("/tmp/title.tg", title)
# public.WriteFile("/tmp/msg.tg", msg)
# send_content = msg
# public.WriteFile("/tmp/send_content.tg", send_content)
# bot = telegram.Bot(self.__tg_info['bot_token'])
bot_token = self.__tg_info['bot_token']
chat_id = self.__tg_info['my_id']
#text = msg
public.print_log(msg)
# public.print_log("bot:{}".format(self.__tg_info['bot_token']))
# public.print_log("my_id:{}".format(self.__tg_info['my_id']))
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(self.send_msg_async(bot_token, chat_id, msg))
ret = {'success': 1}
public.write_push_log(self.__module_name, title, ret)
public.print_log('message sent successfully')
loop.close()
return public.returnMsg(True, 'send complete, send result: True.')
except:
public.print_log('Error:{}'.format(str(public.get_error_info())))
ret = {'success': 0}
public.write_push_log(self.__module_name, title, ret)
return public.returnMsg(False, 'send complete, send result: False.')
def push_data(self, data):
"""
@name 统一发送接口
@data 消息内容
{"module":"mail","title":"标题","msg":"内容","to_email":"xx@qq.com","sm_type":"","sm_args":{}}
"""
return self.send_msg(data['msg'])
def __get_default_channel(self):
"""
@获取默认消息通道
"""
try:
if public.readFile(self.__default_pl) == self.__module_name:
return True
except:
pass
return False
def uninstall(self):
if os.path.exists(self.conf_path):
os.remove(self.conf_path)
# 获取tg机器人信息
# def get_tg_conf(self, get=None):
# conf = self.__tg_info
#
# if not conf:
# return {"setup": False, "bot_token": "", "my_id": ""}
# try:
# return self.__tg_info
# except:
# return {"setup": False, "bot_token": "", "my_id": ""}
# def process_character(self,content):
# character = ['.',',','!',':','%','[',']','\/','_','-','>']
# for c in character:
# if c in content and '\\{}'.format(c) not in content:
# content = content.replace(c,'\\'+c)
# return content
# 使用tg机器人发送消息
# def send_by_tg_bot(self,content,parse_mode=None):
# "parse_mode 消息格式 html/markdown/markdownv2"
# #content = self.process_character(content)
# conf = self.__tg_info
#
# confa1 = conf['my_id']
# public.print_log("开始检查--confa1", confa1)
# confa2 = conf['bot_token']
# public.print_log("开始检查--confa2", confa2)
#
#
# text = send_content
# public.WriteFile("/tmp/text.tg", text)
#
# bot = telegram.Bot(conf['bot_token'])
# #result = bot.send_message(text=content, chat_id=int(conf['my_id']), parse_mode="MarkdownV2")
# result = bot.send_message( chat_id=int(conf['my_id']), text=text, parse_mode='MarkdownV2')
#
# return result
+75
View File
@@ -0,0 +1,75 @@
<div class="conter_box box_weixin">
<div class="bt-form">
<div class="line">
<span class="tname">Notify everyone</span>
<div class="info-r" style="height:28px; margin-left:125px">
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" disabled="disabled" checked>
<label style="position: relative;top: 5px;" title="Only supports notify everyone." class="btswitch-btn" for="panel_alert_all"></label>
</div>
</div>
<div class="line">
<span class="tname">WeCom URL</span>
<div class="info-r">
<textarea name="channel_weixin_value" class="bt-input-text mr5" type="text" placeholder="Please enter WeCom url" style="width: 300px; height:90px; line-height:20px"></textarea>
</div>
<button class="btn btn-success btn-sm weixin_submit" style="margin: 10px 0 0 125px;">Save</button>
</div>
<div class="line">
<ul class="help-info-text c7">
<li>Notify everyone, Immutable</li>
</ul>
</div>
</div>
</div>
<!--微信模块-->
<script type="text/javascript">
var weixin = {
all_info: {},
init: function () {
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
this.get_weixin_data();
},
/**
*@description 获取微信url,保存按钮添加事件
*/
get_weixin_data: function () {
var that = this;
if (weixin.all_info['data']) {
$('textarea[name=channel_weixin_value]').val(weixin.all_info['data']['weixin_url']);
}
// 微信信息设置
$('.weixin_submit').click(function () {
that.set_submit_ding();
})
},
/**
*@description 设置微信url信息,保存按钮
*/
set_submit_ding: function () {
var that = this;
var _url = $('textarea[name=channel_weixin_value]').val();
if (_url == '') return layer.msg('Please enter WeCom url', { icon: 2 })
var loadT = layer.msg('WeCom is being set up, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
$.post('/config?action=set_msg_config&name=weixin', { url: _url, atall: 'True' }, function (rdata) {
layer.close(loadT);
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
if (rdata.status) that.refresh_data();
})
},
refresh_data: function () {
var that = this
$.post('/config?action=get_msg_configs', function (rdata) {
$.each(rdata, function (index, item) {
if (item.name == that.all_info.name) {
$('.alarm-view .bt-w-menu p.bgw').data('data', item)
// that.init()
}
});
})
}
}
</script>
+268
View File
@@ -0,0 +1,268 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: lx
# | 消息通道邮箱模块
# | 常用功能
# 字体加粗 **bold** [这是一个链接](http://bt.cn),代码段:`code`
# 支持3种字体颜色 <font color="info">绿色</font> <font color="comment">灰色</font> <font color="warning">橙红色</font>
# +-------------------------------------------------------------------
import os, sys, public, json, requests,re
import sys, os,time
panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public, json, requests
from requests.packages import urllib3
# 关闭警告
urllib3.disable_warnings()
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
class weixin_msg:
__module_name = None
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
conf_path = 'data/weixin.json'
__weixin_info = None
def __init__(self):
try:
self.__weixin_info = json.loads(public.readFile(self.conf_path))
if not 'weixin_url' in self.__weixin_info:
self.__weixin_info = None
except :
self.__weixin_info = None
self.__module_name = self.__class__.__name__.replace('_msg','')
def get_version_info(self,get):
"""
获取版本信息
"""
data = {}
data['ps'] = 'Wecom used to receive panel message push'
data['version'] = '1.2'
data['date'] = '2022-08-10'
data['author'] = 'aaPanel'
data['title'] = 'Wecom'
data['help'] = 'http://www.aapanel.com'
return data
def __get_default_channel(self):
"""
@获取默认消息通道
"""
try:
if public.readFile(self.__default_pl) == self.__module_name:
return True
except:pass
return False
def get_config(self,get):
"""
获取微信配置
"""
data = {}
if self.__weixin_info :
#全局配置开关,1开启,0关闭
if not 'state' in self.__weixin_info:
self.__weixin_info['state'] = 1
data = self.__weixin_info
if not 'list' in data: data['list'] = {}
title = 'Default'
if 'title' in data: title = data['title']
data['list']['default'] = {'title':title,'data':data['weixin_url'],'state':self.__weixin_info['state']}
data['default'] = self.__get_default_channel()
return data
def set_config(self,get):
"""
设置微信配置
@url 微信URL
@atall 默认@全体成员
@key 唯一标识default=兼容之前配置
@title string 备注
@user
"""
if not hasattr(get, 'url'):
return public.returnMsg(False, 'Please fill in the complete information')
title = 'Default'
if hasattr(get, 'title'):
title = get.title
if len(title) > 7:
return public.returnMsg(False, 'Note name cannot exceed 7 characters')
key,status,state ='default', 1, 1
if 'key' in get: key = get.key
if 'status' in get: status = int(get.status)
if 'state' in get: state = int(get.state)
if not self.__weixin_info: self.__weixin_info = {}
if not 'list' in self.__weixin_info: self.__weixin_info['list'] = {}
#全局配置开关,1开启,0关闭
self.__weixin_info['state'] = state
#增加多个机器人
self.__weixin_info['list'][key] = {
"data": get.url.strip(),
"title":title,
"status":status,
"addtime":int(time.time())
}
#兼容旧配置只有一条url的情况
if key == 'default':
self.__weixin_info['weixin_url'] = get.url.strip()
self.__weixin_info['title'] = title
#统一格式化输出,包含主机名,ip,推送时间
try:
info = public.get_push_info('Message channel configuration reminder',['>configuration status: <font color=#20a53a>Success</font>\n\n'])
ret = self.send_msg(info['msg'])
except:
ret = self.send_msg('aaPanel alarm test')
if ret['status']:
#默认消息通道
if 'default' in get and get['default']:
public.writeFile(self.__default_pl, self.__module_name)
if ret['success'] <= 0:
return public.returnMsg(False, 'Failed to add, please check whether the URL is correct')
public.writeFile(self.conf_path, json.dumps(self.__weixin_info))
return public.returnMsg(True, 'successfully set')
else:
return public.returnMsg(False, 'Failed to add, please check whether the URL is correct')
def get_send_msg(self,msg):
"""
@name 处理md格式
"""
try:
title = 'aaPanel warning notification'
if msg.find("####") >= 0:
msg = msg.replace("\n\n","""
""").strip()
try:
title = re.search(r"####(.+)", msg).groups()[0]
except:pass
except:pass
return msg,title
def send_msg(self,msg,to_user = 'default'):
"""
@name 微信发送信息
@msg string 消息正文(正文内容必须包含
1服务器名称
2IP地址
3发送时间
)
@to_user string 指定发送人
"""
if not self.__weixin_info :
return public.returnMsg(False,'Information is not configured correctly.')
if 'state' in self.__weixin_info and self.__weixin_info['state'] == 0:
return public.returnMsg(False,'Notifications have been turned off, please turn them on and try again.')
if msg.find('####') == -1:
try:
msg = public.get_push_info('Notification Configuration Reminder',['>Send Content{}\n\n'.format(msg)])['msg']
except:pass
msg,title = self.get_send_msg(msg)
data = {
"msgtype": "markdown",
"markdown": {
"content": msg
}
}
headers = {'Content-Type': 'application/json'}
error,success = 0,0
conf = self.get_config(None)['list']
res = {}
for to_key in to_user.split(','):
if not to_key in conf: continue
try:
#x = requests.post(url = conf[to_key]['data'], data=json.dumps(data), headers=headers,verify=False,timeout=10)
allowed_gai_family_lib=urllib3_cn.allowed_gai_family
def allowed_gai_family():
family = socket.AF_INET
return family
urllib3_cn.allowed_gai_family = allowed_gai_family
x = requests.post(url = conf[to_key]['data'], data = json.dumps(data),verify=False, headers=headers,timeout=10)
urllib3_cn.allowed_gai_family=allowed_gai_family_lib
if x.json()["errcode"] == 0:
success += 1
res[conf[to_key]['title']] = 1
else:
error += 1
res[conf[to_key]['title']] = 0
except:
error += 1
res[conf[to_key]['title']] = 0
try:
public.write_push_log(self.__module_name,title,res)
except:pass
ret = public.returnMsg(True,'Send completed, send successfully {}, send failed {}.'.format(success,error))
ret['success'] = success
ret['error'] = error
return ret
def push_data(self,data):
"""
@name 统一发送接口
@data 消息内容
{"module":"mail","title":"提醒","msg":"提醒","to_email":"xx@qq.com","sm_type":"","sm_args":{}}
"""
if not 'to_user' in data:
data['to_user'] = 'default'
return self.send_msg(data['msg'],data['to_user'])
def _write_log(self,module,msg,res):
"""
@name 写日志
"""
user = '[ 默认 ] '
# for key in res:
# status = '<span style="color:#20a53a;">成功</span>'
# if res[key] == 0: status = '<span style="color:red;">成功</span>'
# user += '[ {}:{} ] '.format(key,status)
try:
msg_obj = public.init_msg(module)
if msg_obj: module = msg_obj.get_version_info(None)['title']
except:pass
log = '[{}] sent to {}, sending content: [{}]'.format(module,user,public.xsssec(msg))
public.WriteLog('message push',log)
def uninstall(self):
if os.path.exists(self.conf_path):
os.remove(self.conf_path)
+258
View File
@@ -0,0 +1,258 @@
<div class="conter_box wx_account_box">
<div class="bt-form">
<div class="form-item">
<div class="form-label">绑定微信账号</div>
<div class="form-content">
<div class="bind_wechat hide">
<div class="userinfo"></div>
</div>
<div class="nobind_wechat">
<span class="red">未绑定</span>
</div>
<button class="btn btn-xs btn-success btn-bind-wechat">立即绑定</button>
</div>
</div>
<div class="form-item">
<div class="form-label">绑定微信公众号</div>
<div class="form-content">
<div class="bind_account hide">
<span style="color: #20a53a;">已绑定</span>
</div>
<div class="nobind_account">
<span class="red">未绑定</span>
<button class="btn btn-xs btn-success btn-bind-account">立即绑定</button>
</div>
</div>
</div>
<div class="form-item hide">
<div class="form-label">今日剩余发送次数</div>
<div class="form-content">
<span class="account_remaining">0</span>
<button class="btn btn-xs btn-success btn-send-test">发送测试消息</button>
</div>
</div>
</div>
<ul class="help-info-text c7">
<li>当前为体验版,限制每个宝塔账号发送频率100条/天</li>
</ul>
</div>
<style>
.wx_account_box .bt-form {
padding-top: 15px;
}
.wx_account_box .form-item {
display: flex;
}
.wx_account_box .form-item + .form-item {
margin-top: 15px;
}
.wx_account_box .form-label,
.wx_account_box .form-content {
display: flex;
align-items: center;
min-height: 32px;
}
.wx_account_box .form-label {
justify-content: flex-end;
width: 140px;
padding-right: 20px;
line-height: 1.4;
}
.wx_account_box .form-content {
flex: 1;
}
.wx_account_box .userinfo {
display: flex;
align-items: center;
}
.wx_account_box .userinfo img {
width: 30px;
height: 30px;
border-radius: 50%;
margin-right: 10px;
}
.wx_account_box .form-item .btn + .btn {
margin-left: 12px;
}
.bind_wechat_box .qrcode {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 150px;
margin: 0 auto;
border: 1px solid #ddd;
}
.bind_wechat_box .qrcode img {
width: 148px;
height: 148px;
}
.nobind_account {
display: flex;
align-items: center;
}
.btn-send-test,
.btn-bind-wechat,
.btn-bind-account {
margin-left: 12px;
}
.help-info-text {
position: absolute;
left: 20px;
bottom: 50px;
}
</style>
<script src="/static/js/jquery.qrcode.min.js"></script>
<script type="text/javascript">
var wx_account = {
config: {},
init: function () {
var that = this;
this.get_config();
// 发送测试信息
$('.btn-send-test').click(function () {
var laod = bt.load('正在发送测试信息,请稍候...');
$.post(
'/config?action=get_msg_fun',
{
module_name: 'wx_account',
fun_name: 'push_data',
msg: '发送测试信息',
},
function (res) {
laod.close();
bt.msg(res);
if (res.status) {
var num = $('.account_remaining').text();
if (!isNaN(num)) {
num -= 1;
$('.account_remaining').text(num);
}
}
}
);
});
// 绑定微信公众号
$('.btn-bind-account').click(function () {
layer.open({
type: 1,
area: '280px',
title: '绑定微信公众号',
closeBtn: 2,
shadeClose: false,
content: '\
<div class="bind_wechat_box pd20">\
<div class="text-center">微信扫码</div>\
<div class="mt10">\
<div class="qrcode">\
<img src="https://www.bt.cn/Public/img/bt_wx.jpg" />\
</div>\
</div>\
</div>\
',
cancel: function () {
that.get_config();
}
});
})
// 更换绑定账号
$('.btn-bind-wechat').click(function () {
that.show_bind_account();
});
},
/**
* @description 显示更换微信账号
*/
show_bind_account: function () {
var that = this;
layer.open({
type: 1,
area: '280px',
title: '绑定微信账号',
closeBtn: 2,
shadeClose: false,
content: '\
<div class="bind_wechat_box pd20">\
<div class="text-center">微信扫码</div>\
<div class="mt10">\
<div class="qrcode" id="wechat-qrcode"></div>\
</div>\
</div>\
',
success: function () {
$.post('/config?action=get_msg_fun', {
module_name: 'wx_account',
fun_name: 'get_auth_url',
}, function (rdata) {
var url = rdata.msg.res;
$('#wechat-qrcode').qrcode({
render: 'canvas',
width: 135,
height: 135,
text: url,
correctLevel: 1
});
});
},
cancel: function () {
that.get_config();
}
});
},
/**
* @description 获取配置
*/
get_config: function () {
var that = this;
var loadT = bt.load('正在获取配置,请稍候...');
$.post(
'/config?action=get_msg_fun',
{
module_name: 'wx_account',
fun_name: 'get_web_info',
},
function (rdata) {
loadT.close();
if (rdata.status === false) {
bt.msg(rdata);
}
var data = rdata && rdata.msg && rdata.msg.res ? rdata.msg.res : {};
// 绑定微信账号
if (data.is_bound === 1) {
$('.userinfo').html('<img src="' + data.head_img + '" /><div>' + data.nickname + '</div>');
$('.btn-bind-wechat').text('更换微信账号');
$('.bind_wechat').removeClass('hide');
$('.nobind_wechat').addClass('hide');
} else {
$('.btn-bind-wechat').text('立即绑定');
$('.bind_wechat').addClass('hide');
$('.nobind_wechat').removeClass('hide');
}
// 判断是否绑定公众号
if (data.is_subscribe === 1) {
$('.bind_account').removeClass('hide');
$('.nobind_account').addClass('hide');
} else {
$('.bind_account').addClass('hide');
$('.nobind_account').removeClass('hide');
}
// 判断是否存在发送消息
if (data.remaining === undefined) {
$('.account_remaining').parents('.form-item').addClass('hide');
} else {
$('.account_remaining').parents('.form-item').removeClass('hide');
$('.account_remaining').text(data.remaining);
}
}
);
},
};
</script>
+281
View File
@@ -0,0 +1,281 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
import os, sys
import time,base64
panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public, json, requests
from requests.packages import urllib3
# 关闭警告
urllib3.disable_warnings()
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
class wx_account_msg:
__module_name = None
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
conf_path = '{}/data/wx_account_msg.json'.format(panelPath)
user_info = None
def __init__(self):
try:
self.user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path())))
except:
self.user_info=None
self.__module_name = self.__class__.__name__.replace('_msg','')
def get_version_info(self,get):
"""
获取版本信息
"""
data = {}
data['ps'] = '宝塔微信公众号,用于接收面板消息推送'
data['version'] = '1.0'
data['date'] = '2022-08-15'
data['author'] = '宝塔'
data['title'] = '微信公众号'
data['help'] = 'http://www.bt.cn'
return data
def get_local_ip(self):
'''获取内网IP'''
import socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
return ip
finally:
s.close()
return '127.0.0.1'
def __get_default_channel(self):
"""
@获取默认消息通道
"""
try:
if public.readFile(self.__default_pl) == self.__module_name:
return True
except:pass
return False
def get_config(self, get):
"""
微信公众号配置
"""
if os.path.exists(self.conf_path):
#60S内不重复加载
start_time=int(time.time())
if os.path.exists("data/wx_account_msg.lock"):
lock_time= 0
try:
lock_time = int(public.ReadFile("data/wx_account_msg.lock"))
except:pass
#大于60S重新加载
if start_time - lock_time > 60:
public.run_thread(self.get_web_info2)
public.WriteFile("data/wx_account_msg.lock",str(start_time))
else:
public.WriteFile("data/wx_account_msg.lock",str(start_time))
public.run_thread(self.get_web_info2)
data=json.loads(public.ReadFile(self.conf_path))
if not 'list' in data: data['list'] = {}
title = '默认'
if 'res' in data and 'nickname' in data['res']: title = data['res']['nickname']
data['list']['default'] = {'title':title,'data':''}
data['default'] = self.__get_default_channel()
return data
else:
public.run_thread(self.get_web_info2)
return {"success":False,"res":"未获取到配置信息"}
def set_config(self,get):
"""
@设置默认值
"""
if 'default' in get and get['default']:
public.writeFile(self.__default_pl, self.__module_name)
return public.returnMsg(True, '设置成功')
def get_web_info(self,get):
if self.user_info is None: return public.returnMsg(False, '未获取到用户绑定的信息')
url = "https://www.bt.cn/api/v2/user/wx_web/info"
data = {
"uid": self.user_info["uid"],
"access_key": self.user_info["access_key"],
"serverid":self.user_info["serverid"]
}
try:
datas = json.loads(public.httpPost(url,data))
if datas["success"]:
public.WriteFile(self.conf_path,json.dumps(datas))
return public.returnMsg(True, datas)
else:
public.WriteFile(self.conf_path, json.dumps(datas))
return public.returnMsg(False, datas)
except:
public.WriteFile(self.conf_path, json.dumps({"success":False,"res":"链接云端失败,请检查网络"}))
return public.returnMsg(False,"链接云端失败,请检查网络")
def get_web_info2(self):
if self.user_info is None: return public.returnMsg(False, '未获取到用户绑定的信息')
url = "https://www.bt.cn/api/v2/user/wx_web/info"
data = {
"uid": self.user_info["uid"],
"access_key": self.user_info["access_key"],
"serverid":self.user_info["serverid"]
}
try:
datas = json.loads(public.httpPost(url,data))
if datas["success"]:
public.WriteFile(self.conf_path,json.dumps(datas))
return public.returnMsg(True, datas)
else:
public.WriteFile(self.conf_path, json.dumps(datas))
return public.returnMsg(False, datas)
except:
public.WriteFile(self.conf_path, json.dumps({"success":False,"res":"链接云端失败"}))
return public.returnMsg(False,"链接云端失败")
def get_auth_url(self,get):
if self.user_info is None: return public.returnMsg(False, '未获取到用户绑定的信息')
url = "https://www.bt.cn/api/v2/user/wx_web/get_auth_url"
data = {
"uid": self.user_info["uid"],
"access_key": self.user_info["access_key"],
"serverid":self.user_info["serverid"]
}
try:
datas = json.loads(public.httpPost(url,data))
if datas["success"]:
return public.returnMsg(True, datas)
else:
return public.returnMsg(False, datas)
except:
return public.returnMsg(False,"链接云端失败")
def get_send_msg(self,msg):
"""
@name 处理md格式
"""
try:
import re
title = '宝塔告警通知'
if msg.find("####") >= 0:
try:
title = re.search(r"####(.+)", msg).groups()[0]
except:pass
msg = msg.replace("####",">").replace("\n\n","\n").strip()
s_list = msg.split('\n')
if len(s_list) > 3:
s_title = s_list[0].replace(" ","")
s_list = s_list[3:]
s_list.insert(0,s_title)
msg = '\n'.join(s_list)
s_list = []
for msg_info in msg.split('\n'):
reg = '<font.+>(.+)</font>'
tmp = re.search(reg,msg_info)
if tmp:
tmp = tmp.groups()[0]
msg_info = re.sub(reg,tmp,msg_info)
s_list.append(msg_info)
msg = '\n'.join(s_list)
except:pass
return msg,title
def send_msg(self,msg):
"""
微信发送信息
@msg 消息正文
"""
if self.user_info is None:
return public.returnMsg(False,'未获取到用户信息')
msg,title = self.get_send_msg(msg)
url="https://www.bt.cn/api/v2/user/wx_web/send_template_msg_v2"
datassss = {
"first": {
"value": "堡塔主机告警",
},
"keyword1": {
"value": "内网IP " + self.get_local_ip() + "\n外网IP " + self.user_info["address"] + " \n服务器别名 " + public.GetConfigValue("title"),
},
"keyword2": {
"value": "堡塔主机告警",
},
"keyword3": {
"value": msg ,
},
"remark": {
"value": "如有疑问,请联系宝塔客服",
},
}
data = {
"uid": self.user_info["uid"],
"access_key": self.user_info["access_key"],
"data": base64.b64encode(json.dumps(datassss).encode('utf-8')).decode('utf-8')
}
try:
res = {}
error,success = 0,0
x = json.loads(public.httpPost(url,data))
# public.print_log(json.dumps(x))
conf = self.get_config(None)['list']
#立即刷新剩余次数
public.run_thread(self.get_web_info2)
res[conf['default']['title']] = 0
if x['success']:
res[conf['default']['title']] = 1
success += 1
else:
error += 1
try:
public.write_push_log(self.__module_name,title,res)
except:pass
result = public.returnMsg(True,'发送完成,发送成功{},发送失败{}.'.format(success,error))
result['success'] = success
result['error'] = error
return result
except:
print(public.get_error_info())
return public.returnMsg(False,'微信消息发送失败。 --> {}'.format(public.get_error_info()))
def push_data(self,data):
return self.send_msg(data['msg'])
def uninstall(self):
if os.path.exists(self.conf_path):
os.remove(self.conf_path)
+1463
View File
File diff suppressed because it is too large Load Diff
+58 -3
View File
@@ -3,7 +3,11 @@
# +--------------------------------------------------------------------
from Crypto.Cipher import AES
import base64,sys
from Crypto.Util.Padding import pad, unpad
import base64, sys
import public
class aescrypt_py3():
def __init__(self,key,model = 'ECB',iv = None,encode_='utf-8'):
self.encode_ = encode_
@@ -12,7 +16,7 @@ class aescrypt_py3():
if model == 'ECB':
self.aes = AES.new(self.key,self.model)
elif model == 'CBC':
self.aes = AES.new(self.key,self.model,iv)
self.aes = AES.new(self.key, self.model, iv)
def add_16(self,par):
par = par.encode(self.encode_)
@@ -46,7 +50,7 @@ class aescrypt_py3():
return base64.b64decode(str2)
else:
return str(base64.b64decode(str2))
class aescrypt_py2():
def __init__(self,key,model = 'ECB',iv = None,encode_='utf-8'):
self.encode_ = encode_
@@ -91,3 +95,54 @@ class aescrypt_py2():
return str(base64.b64decode(str2))
class AesCryptPy3(object):
def __init__(self, key, model='ECB', iv=None, char_set='utf8'):
self.char_set = char_set
self.model = model
self.key = self.add_16(key)
self.iv = None if iv is None else self.add_16(iv)
def add_16(self, par):
if not isinstance(par, bytes):
par = par.encode(self.char_set)
while len(par) % 16 != 0:
par += b'\0'
return par
@property
def aes(self):
if self.model == 'ECB':
return AES.new(self.key, AES.MODE_ECB)
elif self.model == 'CBC':
return AES.new(self.key, AES.MODE_CBC, self.iv)
raise ValueError("不支持的加密方式")
def aes_encrypt(self, text: str):
text = pad(text.encode(self.char_set), 16)
encrypt_text = self.aes.encrypt(text)
return base64.b64encode(encrypt_text).decode()
def aes_decrypt(self, text: str):
text = base64.decodebytes(text.encode(self.char_set))
decrypt_text = self.aes.decrypt(text)
decrypt_text = unpad(decrypt_text, 16)
return decrypt_text.decode(self.char_set).strip('\0')
# base64 编码
@staticmethod
def encode_base64(data: str):
str2 = data.strip()
if sys.version_info[0] == 2:
return base64.b64encode(str2)
else:
return str(base64.b64encode(str2.encode('utf-8')))
# base64 解码
@staticmethod
def decode_base64(data: str):
import base64
str2 = data.strip()
if sys.version_info[0] == 2:
return base64.b64decode(str2)
else:
return str(base64.b64decode(str2).decode('utf-8'))
+3
View File
@@ -224,6 +224,7 @@ class panelApi:
data['token'] = public.md5(token)
data['token_crypt'] = public.en_crypt(data['token'],token).decode('utf-8')
public.write_log_gettext('API configuration','Regenerate API-Token')
public.add_security_logs('API configuration','Regenerate API-Token')
elif get.t_type == '2':
data['open'] = not data['open']
stats = {True:'Open',False:'Close'}
@@ -232,10 +233,12 @@ class panelApi:
data['token'] = public.md5(token)
data['token_crypt'] = public.en_crypt(data['token'],token).decode('utf-8')
public.write_log_gettext('API configuration','{} API interface',(stats[data['open']],))
public.add_security_logs('API configuration', '{} API interface', (stats[data['open']],))
token = stats[data['open']] + ' success!'
elif get.t_type == '3':
data['limit_addr'] = get.limit_addr.split('\n')
public.write_log_gettext('API configuration','Change IP limit to [{}]',(get.limit_addr,))
public.add_security_logs('API configuration', 'Change IP limit to [{}]', (get.limit_addr,))
token ='Saved successfully!'
self.save_api_config(data)
return public.return_msg_gettext(True,token)
+149 -8
View File
@@ -18,7 +18,8 @@ class panelAuth:
__product_list_path = 'data/product_list.pl'
__product_bay_path = 'data/product_bay.pl'
__product_id = '100000011'
__official_url = 'https://brandnew.aapanel.com'
__official_url = 'https://www.aapanel.com'
# __official_url = 'http://dev.aapanel.com'
def create_serverid(self,get):
try:
@@ -59,8 +60,8 @@ class panelAuth:
def check_serverid(self,get):
if get.serverid != self.create_serverid(get): return False
return True
def get_plugin_price(self, get):
# 旧接口 没有永久版
def get_plugin_price2(self, get):
try:
userPath = 'data/userInfo.json'
if not 'pluginName' in get and not 'product_id' in get: return public.return_msg_gettext(False,'Parameter ERROR!')
@@ -81,7 +82,61 @@ class panelAuth:
except:
del(session['get_product_list'])
return public.return_msg_gettext(False,'Syncing information, please try again!\n {}',(public.get_error_info(),))
# 获取永久版信息
def get_plugin_price3(self, get):
try:
userPath = 'data/userInfo.json'
if not 'pluginName' in get and not 'product_id' in get:
return public.return_msg_gettext(False,'Parameter ERROR!')
if not os.path.exists(userPath):
return public.return_msg_gettext(False,'Please login with account first')
params = {}
if not hasattr(get,'product_id'):
params['product_id'] = self.get_plugin_info(get.pluginName)['id']
else:
params['product_id'] = get.product_id
data = self.send_cloud('{}/api/product/pricesV2'.format(self.__official_url), params)
if not data:
return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!')
if not data['success']:
return public.return_msg_gettext(False,data['msg'])
return data['res']
except:
# del(session['get_product_list'])
return public.return_msg_gettext(False,'Syncing information, please try again!\n {}',(public.get_error_info(),))
# 获取价格列表 新增多机购买
def get_plugin_price(self, get):
try:
userPath = 'data/userInfo.json'
if not 'pluginName' in get and not 'product_id' in get:
return public.return_msg_gettext(False, 'Parameter ERROR!')
if not os.path.exists(userPath):
return public.return_msg_gettext(False, 'Please login with account first')
params = {}
if not hasattr(get, 'product_id'):
params['product_id'] = self.get_plugin_info(get.pluginName)['id']
else:
params['product_id'] = get.product_id
data = self.send_cloud('{}/api/product/pricesV3'.format(self.__official_url), params)
if not data:
return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!')
if not data['success']:
return public.return_msg_gettext(False, data['msg'])
return data['res']
except Exception as ex:
public.print_log("获取价格报错 {}".format(ex))
# del(session['get_product_list'])
return public.return_msg_gettext(False, 'Syncing information, please try again!\n {}',
(public.get_error_info(),))
def get_plugin_info(self,pluginName):
data = self.get_business_plugin(None)
if not data: return None
@@ -108,21 +163,85 @@ class panelAuth:
params['src'] = 2
params['trigger_entry'] = get.source
params['pay_channel'] = 2
# 0.管理后台生成 1.Ping++ 2.Stripe 3.Paypal 10.抵扣券
if hasattr(get, 'pay_channel'):
params['pay_channel'] = get.pay_channel
params['charge_type'] = get.charge_type
env_info = public.fetch_env_info()
params['environment_info'] = json.dumps(env_info)
params['server_id'] = env_info['install_code']
# 多机购买 数量
if not hasattr(get, 'num'):
return public.return_msg_gettext(False, 'parameter error: num')
params['num'] = get.num
# 添加购买来源
# params['source'] = get.source
data = self.send_cloud('{}/api/order/product/create'.format(self.__official_url), params)
if not data['success']:
return public.return_msg_gettext(False,data['res'])
return public.return_msg_gettext(False, data['res'])
return data['res']
def get_stripe_session_id(self,get):
params = {}
params['order_no'] = get.order_no
if hasattr(get, 'order_no'):
params['order_no'] = get.order_no
if hasattr(get, 'order_id'):
params['order_id'] = get.order_id
if hasattr(get, 'subscribe'):
params['subscribe'] = get.subscribe
if not params.get('order_no', None) and not params.get('order_id', None):
return public.return_msg_gettext(False,'parameter error')
data = self.send_cloud('{}/api/order/product/pay'.format(self.__official_url), params)
session['focre_cloud'] = True
return data['res']
# paypal支付
def get_paypal_session_id(self,get):
params = {}
if hasattr(get, 'oid'):
params['oid'] = get.oid
if not params.get('oid', None):
return public.return_msg_gettext(False,'parameter error')
data = self.send_cloud('{}/api/paypal/create_order'.format(self.__official_url), params)
session['focre_cloud'] = True
data2 = {
"status": data.get("success", False),
"res": data.get("res", ""),
"nonce": data.get("nonce", 0),
}
return data2
# paypal 支付确认
def check_paypal_status(self,get):
params = {}
if hasattr(get, 'paypal_order_id'):
params['paypal_order_id'] = get.paypal_order_id
if not params.get('paypal_order_id', None):
return public.return_msg_gettext(False,'parameter error')
data = self.send_cloud('{}/api/paypal/capture_order'.format(self.__official_url), params)
# session['focre_cloud'] = True
data2 = {
"status": data.get("success", False),
"res": data.get("res", ""),
"nonce": data.get("nonce", 0),
}
return data2
def check_pay_status(self,get):
params = {}
@@ -206,7 +325,7 @@ class panelAuth:
url_headers = {"Content-Type": "application/json",
"authorization": "bt {}".format(userInfo['token'])
}
resp = requests.post(cloudURL, params=params, headers=url_headers)
resp = requests.post(cloudURL, params =params, headers=url_headers)
resp = resp.json()
if not resp['res']: return None
return resp
@@ -294,7 +413,8 @@ class panelAuth:
data = self.send_cloud('{}/api/user/productAuthorizes'.format(self.__official_url), params)
if not data:
return []
if not data['success']: return []
if not data['success']:
return []
data = data['res']
# return [i for i in data['list'] if i['status'] != 'activated' and get.pid == i['product_id']]
res = list()
@@ -324,6 +444,10 @@ class panelAuth:
if hasattr(get,'coupon_id') and get.pay_channel == '10':
params['coupon_id'] = get.coupon_id
data = self.send_cloud('{}/api/authorize/product/renew'.format(self.__official_url), params)
# public.print_log('############ 续费接口 {}'.format(data))
if not data['success']:
data['res'] = 'Invalid authorize OR authorize not found!'
return data
session['focre_cloud'] = True
# 使用抵扣券续费直接返回续费结果
if get.pay_channel == '10':
@@ -346,3 +470,20 @@ class panelAuth:
if not data['success']:
return public.return_msg_gettext(False, 'Apply Failed')
return public.return_msg_gettext(True,'Apply successfully')
# 获取专业版特权信息 或插件信息?
def get_plugin_remarks(self, get):
if not hasattr(get, 'product_id'):
return public.return_msg_gettext(False, 'product_id Parameter ERROR!')
product_id = get.product_id
ikey = 'plugin_remarks' + product_id
if ikey in session:
return session.get(ikey)
url = '{}/api/panel/get_advantages/{}'.format(self.__official_url, product_id)
data = requests.get(url).json()
# public.print_log(" ###############%%%%%%%%%%%%%%%%%%%% {}".format(data))
if not data: return public.returnMsg(False, 'Failed to connect to the server!')
session[ikey] = data
return data
+638 -405
View File
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# 系统安全管理控制器
#------------------------------
import os,sys,public,json,re
class Controller:
def __init__(self):
pass
def model(self,args):
'''
@name 调用指定项目模型
@author hwliang<2021-12-31>
@param args<dict_obj> {
mod_name: string<模型名称>
def_name: string<方法名称>
data: JSON
}
'''
try: # 表单验证
if args['mod_name'] in ['base']: return public.return_status_code(1000,'错误的调用!')
public.exists_args('def_name,mod_name',args)
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'调用的方法名称中不能包含“__”字符')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'调用的模块名称中不能包含\w以外的字符')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'调用的方法名称中不能包含\w以外的字符')
except:
return public.get_error_object()
# 参数处理
module_name = args['mod_name'].strip()
mod_name = "{}Model".format(args['mod_name'].strip())
def_name = args['def_name'].strip()
model_index = None
if 'model_index' in args: model_index = args['model_index']
if not hasattr(args,'data'): args.data = {}
if args.data:
if isinstance(args.data,str):
try: # 解析为dict_obj
pdata = public.to_dict_obj(json.loads(args.data))
except:
return public.get_error_object()
else:
pdata = args.data
else:
pdata = args
if isinstance(pdata,dict):
pdata = public.to_dict_obj(pdata)
if not isinstance(pdata,public.dict_obj):
return public.return_error("传递的参数不是通用的内部对象")
# 告诉加载器,要加载什么模块
if model_index: pdata.model_index = model_index
# 前置HOOK
hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper())
hook_result = public.exec_hook(hook_index,pdata)
if isinstance(hook_result,public.dict_obj):
pdata = hook_result # 桥接
elif isinstance(hook_result,dict):
return hook_result # 响应具体错误信息
elif isinstance(hook_result,bool):
if not hook_result: # 直接中断操作
return public.return_data(False,{},error_msg='前置HOOK中断操作')
# 调用处理方法
# result = run_object(pdata)
import PluginLoader
result = PluginLoader.module_run(module_name,def_name,pdata)
if isinstance(result,dict):
if 'status' in result and result['status'] == False and 'msg' in result:
if isinstance(result['msg'],str):
if result['msg'].find('Traceback ') != -1:
raise public.PanelError(result['msg'])
# 后置HOOK
hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper())
hook_data = public.to_dict_obj({
'args': pdata,
'result': result
})
hook_result = public.exec_hook(hook_index,hook_data)
if isinstance(hook_result,dict):
result = hook_result['result']
return result
+103
View File
@@ -0,0 +1,103 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# 数据库管理控制器
#------------------------------
import os,sys,public,json,re
class DatabaseController:
def __init__(self):
pass
def model(self,args):
'''
@name 调用指定项目模型
@author hwliang<2021-12-31>
@param args<dict_obj> {
mod_name: string<模型名称>
def_name: string<方法名称>
data: JSON
}
'''
try: # 表单验证
if args['mod_name'] in ['base']: return public.return_status_code(1000,'Bad call!')
public.exists_args('def_name,mod_name',args)
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'The called method name cannot contain the "__" character')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
except:
return public.get_error_object()
# 参数处理
module_name = args['mod_name'].strip()
mod_name = "{}Model".format(args['mod_name'].strip())
def_name = args['def_name'].strip()
# # 指定模型是否存在
# mod_file = "{}/databaseModel/{}.py".format(public.get_class_path(),mod_name)
# if not os.path.exists(mod_file):
# return public.return_status_code(1003,mod_name)
# # 实例化
# def_object = public.get_script_object(mod_file)
# if not def_object: return public.return_status_code(1000,'没有找到{}模型'.format(mod_name))
# run_object = getattr(def_object.main(),def_name,None)
# if not run_object: return public.return_status_code(1000,'没有在{}模型中找到{}方法'.format(mod_name,def_name))
if not hasattr(args,'data'): args.data = {}
if args.data:
if isinstance(args.data,str):
try: # 解析为dict_obj
pdata = public.to_dict_obj(json.loads(args.data))
except:
return public.get_error_object()
elif isinstance(args.data,dict):
pdata = public.to_dict_obj(args.data)
else:
pdata = args.data
else:
pdata = public.dict_obj()
if isinstance(pdata,dict): pdata = public.to_dict_obj(pdata)
pdata.model_index = 'database'
# 前置HOOK
hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper())
hook_result = public.exec_hook(hook_index,pdata)
if isinstance(hook_result,public.dict_obj):
pdata = hook_result # 桥接
elif isinstance(hook_result,dict):
return hook_result # 响应具体错误信息
elif isinstance(hook_result,bool):
if not hook_result: # 直接中断操作
return public.return_data(False,{},error_msg='Pre-HOOK interrupt operation')
# 调用处理方法
# result = run_object(pdata)
import PluginLoader
result = PluginLoader.module_run(module_name,def_name,pdata)
if isinstance(result,dict):
if 'status' in result and result['status'] == False and 'msg' in result:
if isinstance(result['msg'],str):
if result['msg'].find('Traceback ') != -1:
raise public.PanelError(result['msg'])
# 后置HOOK
hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper())
hook_data = public.to_dict_obj({
'args': pdata,
'result': result
})
hook_result = public.exec_hook(hook_index,hook_data)
if isinstance(hook_result,dict):
result = hook_result['result']
return result
+87
View File
@@ -0,0 +1,87 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang
# +-------------------------------------------------------------------
# +-------------------------------------------------------------------
# | 面板防御模块
# +-------------------------------------------------------------------
import public
class bot_safe:
'''
@name 机器防御模块
'''
def is_spider_bot(self,user_agent):
'''
@name 检查是否为搜索引擎爬虫
@auth hwliang
@param user_agent <str> User-Agent
@return <bool> True/False
'''
spider_uas = ["bot","spider"]
for spider_ua in spider_uas:
if spider_ua in user_agent: return True
return False
def is_scanner(self,user_agent):
'''
@name 检查是否为扫描器
@auth hwliang
@param user_agent <str> User-Agent
@return <bool> True/False
'''
scanner_uas = ["wpscan","httrack","antsword","harvest","audit","dirbuster","pangolin","nmap","sqln","hydra","parser","libwww","bbbike","sqlmap","w3af","owasp","nikto","fimap","havij","zmeu","babykrokodil","netsparker","httperf"," sf/"]
for scanner_ua in scanner_uas:
if scanner_ua in user_agent: return True
return False
def is_scripter(self,user_agent):
'''
@name 检查是否为脚本工具
@auth hwliang
@param user_agent <str> User-Agent
@return <bool> True/False
'''
scripter_uas = ["curl","requests","python","php","c#","urllib","wget","winhttp","webzip","fetchurl","node-superagent","java/","feeddemon","jullo","indy library","alexa toolbar","asktbfxtv","ahrefsbot","crawldaddy","java","feedly","apache-httpasyncclient","universalfeedparser","apachebench","microsoft url control","zmeu","jaunty","yyspider","digext","httpclient","heritrix","easouspider","ezooms","flightdeckreports"]
for scripter_ua in scripter_uas:
if scripter_ua in user_agent: return True
return False
def spider(self,user_agent,ip):
'''
@name 爬虫防御
@auth hwliang
@param user_agent <str> User-Agent
@param ip <str> 客户端IP地址
@return <bool> True/False
'''
# 检查参数
if not user_agent or not ip: return False
# ua长度小于24位的拒绝
ua_len = len(user_agent)
if ua_len < 24 or ua_len > 350: return False
# 放行局域网IP
if public.is_local_ip(ip): return True
user_agent = user_agent.lower()
# 检查是否为搜索引擎爬虫
if self.is_spider_bot(user_agent): return False
# 检查是否为扫描器
if self.is_scanner(user_agent): return False
# 检查是否为脚本工具
if self.is_scripter(user_agent): return False
return True
+4 -1
View File
@@ -173,6 +173,7 @@ class DNSPodDns(BaseDns):
try:
domain_name,_,subd = extract_zone(domain_name)
self.remove_record(domain_name,subd,'TXT')
self.remove_record(domain_name,'_acme-challenge','CNAME')
except:
pass
@@ -323,6 +324,7 @@ class CloudFlareDns(BaseDns):
self.CLOUDFLARE_API_BASE_URL,
"zones/{0}/dns_records/{1}".format(self.CLOUDFLARE_DNS_ZONE_ID, dns_record_id),
)
headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY}
requests.delete(
url, headers=headers, timeout=self.HTTP_TIMEOUT
)
@@ -369,6 +371,7 @@ class AliyunDns(object):
def create_dns_record(self, domain_name, domain_dns_value):
root, _, acme_txt = extract_zone(domain_name)
self.delete_dns_record(domain_name, domain_dns_value)
if self._type == 1:
acme_txt = acme_txt.replace('_acme-challenge.','')
self.add_record(root,'CNAME',acme_txt,domain_dns_value)
@@ -378,7 +381,6 @@ class AliyunDns(object):
except: pass
self.add_record(root,'TXT',acme_txt,domain_dns_value)
def add_record(self,domain,s_type,host,value):
randomint = random.randint(11111111111111, 99999999999999)
now = datetime.datetime.utcnow()
@@ -454,6 +456,7 @@ class AliyunDns(object):
root, _, acme_txt = extract_zone(domain_name)
self.remove_record(root,acme_txt,'TXT')
self.remove_record(root,'@','CAA')
self.remove_record(root,'_acme-challenge','CNAME')
class CloudxnsDns(object):
def __init__(self, key, secret, ):
+277
View File
@@ -0,0 +1,277 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# HTTP代理模块
#------------------------------
import requests,os,re,time
from BTPanel import request,Response,public,app,get_phpmyadmin_dir,session
from http.cookies import SimpleCookie
import requests.packages.urllib3.util.connection as urllib3_conn
import socket
class HttpProxy:
_pma_path = None
def get_res_headers(self,p_res):
'''
@name 获取响应头
@author hwliang<2022-01-19>
@param p_res<Response> requests响应对像
@return dict
'''
headers = {}
for h in p_res.headers.keys():
if h in ['Content-Encoding','Transfer-Encoding']: continue
headers[h] = p_res.headers[h]
if h in ['Location']:
if headers[h].find('phpmyadmin_') != -1:
if not self._pma_path:
self._pma_path = get_phpmyadmin_dir()
if self._pma_path:
self._pma_path = self._pma_path[0]
else:
self._pma_path = ''
headers[h] = headers[h].replace(self._pma_path,'phpmyadmin')
if headers[h].find('127.0.0.1') != -1:
headers[h] = re.sub(r"https?://127.0.0.1(:\d+)?/",request.url_root,headers[h])
if request.url_root.find('https://') == 0:
headers[h] = headers[h].replace('http://','https://')
return headers
def set_res_headers(self,res,p_res):
'''
@name 设置响应头
@author hwliang<2022-01-19>
@param res<Response> flask响应对像
@param p_res<Response> requests响应对像
@return res<Response>
'''
# from datetime import datetime
# cookie_dict = p_res.cookies.get_dict()
# expires = datetime.utcnow() + app.permanent_session_lifetime
# for k in cookie_dict.keys():
# httponly = True
# if k in ['phpMyAdmin']: httponly = True
# res.set_cookie(k, cookie_dict[k],
# expires=expires, httponly=httponly,
# path='/')
return res
def get_pma_phpversion(self):
'''
@name 获取phpmyadmin的php版本
@author hwliang<2022-01-19>
@return str
'''
from panelPlugin import panelPlugin
pma_status = panelPlugin().getPHPMyAdminStatus()
if 'phpversion' in pma_status:
return pma_status['phpversion']
return None
def get_pma_version(self):
'''
@name 获取phpmyadmin的版本
@author hwliang<2022-01-19>
@return str
'''
pma_vfile = public.get_setup_path() + '/phpmyadmin/version.pl'
if not os.path.exists(pma_vfile): return ''
pma_version = public.readFile(pma_vfile).strip()
if not pma_version: return ''
return pma_version
def set_pma_phpversion(self):
'''
@name 设置phpmyadmin兼容的php版本
@author hwliang<2022-01-19>
@return str
'''
pma_version = self.get_pma_version()
if not pma_version: return False
old_phpversion = self.get_pma_phpversion()
if not old_phpversion: return False
if pma_version == '4.0':
php_versions = ['52','53','54']
elif pma_version == '4.4':
php_versions = ['54','55','56']
elif pma_version == '4.9':
php_versions = ['55','56','70','71','72','73','74']
elif pma_version == '5.0':
php_versions = ['70','71','72','73','74']
elif pma_version == '5.1':
php_versions = ['71','72','73','74','80']
elif pma_version == '5.2':
php_versions = ['72','73','74','80','81']
elif pma_version == '5.3':
php_versions = ['72','73','74','80','81']
else:
return False
if old_phpversion in php_versions: return True
installed_php_versions = []
php_install_path = '/www/server/php'
for version in php_versions:
php_bin = php_install_path + '/' + version + '/bin/php'
if os.path.exists(php_bin):
installed_php_versions.append(version)
if not installed_php_versions: return False
php_version = installed_php_versions[-1]
import ajax
args = public.dict_obj()
args.phpversion = php_version
ajax.ajax().setPHPMyAdmin(args)
public.WriteLog('数据库','检测到phpMyAdmin使用的PHP版本不兼容,已自动修改为最佳兼容版本: PHP-' + php_version)
time.sleep(0.5)
def get_request_headers(self):
'''
@name 获取请求头
@author hwliang<2022-01-19>
@return dict
'''
headers = {}
rm_cookies = [app.config['SESSION_COOKIE_NAME'],'bt_user_info','file_recycle_status','ltd_end',
'memSize','page_number','pro_end','request_token','serverType','site_model',
'sites_path','soft_remarks','load_page','Path','distribution','order']
for k in request.headers.keys():
headers[k] = request.headers.get(k)
if k == 'Cookie':
cookie_dict = SimpleCookie(headers[k])
for rm_cookie in rm_cookies:
if rm_cookie in cookie_dict:
del(cookie_dict[rm_cookie])
headers[k] = cookie_dict.output(header='',sep=';').strip()
return headers
def form_to_dict(self,form):
'''
@name 将表单转为字典
@author hwliang<2022-02-18>
@param form<request.form> 表单数据
@return dict
'''
data = {}
for k in form.keys():
data[k] = form.getlist(k)
if len(data[k]) == 1: data[k] = data[k][0]
return data
def proxy(self,proxy_url):
'''
@name 代理指定URL地址
@author hwliang<2022-01-19>
@param proxy_url<string> 被代理的URL地址
@return Response
'''
try:
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
s_key = 'proxy_{}_{}'.format(app.secret_key,self.get_pma_version())
if not s_key in session:
session[s_key] = requests.Session()
session[s_key].keep_alive = False
session[s_key].headers = {
'User-Agent':'BT-Panel',
'Connection':'close'
}
if proxy_url.find('phpmyadmin') != -1:
if proxy_url.find('https://') == 0:
session[s_key].cookies.update({'pma_lang_https':'zh_CN'})
else:
session[s_key].cookies.update({'pma_lang':'zh_CN'})
self.set_pma_phpversion()
if 'Authorization' in request.headers:
session[s_key].headers['Authorization'] = request.headers['Authorization']
try:
session[s_key].headers['Host'] = public.en_punycode(request.url_root).replace('http://','').replace('https://','').split('/')[0]
except:pass
# headers = self.get_request_headers()
headers = None
if request.method == 'GET':
# 转发GET请求
p_res = session[s_key].get(proxy_url,headers=headers,verify=False,allow_redirects=False)
elif request.method == 'POST':
# 转发POST请求
if request.files: # 如果上传文件
tmp_path = '{}/tmp'.format(public.get_panel_path())
if not os.path.exists(tmp_path): os.makedirs(tmp_path,384)
# 处理请求头
if headers:
if 'Content-Type' in headers: del(headers['Content-Type'])
if 'Content-Length' in headers: del(headers['Content-Length'])
# 遍历form表单中的所有文件
files = {}
f_list = {}
for key in request.files:
upload_files = request.files.getlist(key)
filename = upload_files[0].filename
if not filename: filename = public.GetRandomString(12)
tmp_file = '{}/{}'.format(tmp_path,filename)
# 保存上传文件到临时目录
with open(tmp_file,'wb') as f:
for tmp_f in upload_files:
f.write(tmp_f.read())
f.close()
# 构造文件上传对象
f_list[key] = open(tmp_file,'rb')
files[key] = (filename, f_list[key])
# 删除临时文件
if os.path.exists(tmp_file): os.remove(tmp_file)
# 转发上传请求
p_res = session[s_key].post(proxy_url,self.form_to_dict(request.form),headers=headers,files=files,verify=False,allow_redirects=False)
# 释放文件对象
for fkey in f_list.keys():
f_list[fkey].close()
else:
p_res = session[s_key].post(proxy_url,self.form_to_dict(request.form),headers=headers,verify=False,allow_redirects=False)
else:
return Response('不支持的请求类型',500)
# PHP版本自动切换处理
if proxy_url.find('phpmyadmin') != -1 and proxy_url.find('/index.php') != -1:
if len(p_res.content) < 1024:
if p_res.content.find(b'syntax error, unexpected') != -1 or p_res.content.find(b'offset access syntax with') != -1 or p_res.content.find(b'+ is required') != -1:
self.set_pma_phpversion()
return 'Incompatible PHP version, an attempt has been made to automatically switch to a compatible PHP version, please refresh the page and try again!'
elif p_res.content.find(b'<strong>Deprecation Notice</strong>') != -1 and not session.get('set_pma_phpversion'):
self.set_pma_phpversion()
session['set_pma_phpversion'] = True
return 'Incompatible PHP version, an attempt has been made to automatically switch to a compatible PHP version, please refresh the page and try again!'
res = Response(p_res.content,headers=self.get_res_headers(p_res),content_type=p_res.headers.get('content-type',None),status=p_res.status_code)
res = self.set_res_headers(res,p_res)
return res
except Exception as ex:
return Response(str(ex),500)
+156 -13
View File
@@ -10,24 +10,18 @@
# +-------------------------------------------------------------------
# | 消息提醒
# +-------------------------------------------------------------------
import os,sys,time
import public,json
if os.environ.get('BT_TASK') != '1':
import time
import json
import public
try:
from BTPanel import cache
else:
except :
import cachelib
cache = cachelib.SimpleCache()
class panelMessage:
os = 'linux'
def __init__(self):
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'messages','%retry_num%')).count():
public.M('messages').execute("alter TABLE messages add send integer DEFAULT 0",())
public.M('messages').execute("alter TABLE messages add retry_num integer DEFAULT 0",())
pass
def set_send_status(self, id, data):
'''
@name 设置消息发送状态
@@ -47,6 +41,10 @@ class panelMessage:
获取官网推送消息一天获取一次
"""
def get_cloud_messages(self,args):
# aapanel 暂时不用
return public.returnMsg(True, '同步成功!')
try:
ret = cache.get('get_cloud_messages')
if ret: return public.returnMsg(True,'同步成功1!')
@@ -81,8 +79,13 @@ class panelMessage:
@author hwliang <2020-05-18>
@return list
'''
public.run_thread(self.get_cloud_messages,args=(args,))
data = public.M('messages').where('state=? and expire>?',(1,int(time.time()))).order("id desc").select()
ikey = 'get_message'
data = cache.get(ikey)
if not data:
if not public.is_aarch():
public.run_thread(self.get_cloud_messages,args=(args,))
data = public.M('messages').where('state=? and expire>?',(1,int(time.time()))).order("id desc").select()
cache.set(ikey,data,86400)
return data
def get_messages_all(self,args = None):
@@ -193,5 +196,145 @@ class panelMessage:
else:
return True
def init_msg_module(self, module):
"""
初始化消息通道, 迁移自windows
@module 消息通道模块名称
@author lx
"""
try:
import os, sys
if not os.path.exists('class/msg'): os.makedirs('class/msg')
panelPath = "/www/server/panel"
sfile = 'class/msg/{}_msg.py'.format(module)
if not os.path.exists(sfile): return False
sys.path.insert(0, "{}/class/msg".format(panelPath))
msg_main = __import__('{}_msg'.format(module))
try:
public.reload_mod(msg_main)
except:
pass
return eval('msg_main.{}_msg()'.format(module));
except:
return None
def get_default_channel(self, args=None):
"""获取面板默认消息通道
Returns:
channel: str/None没有安装消息通道的情况下返回None
"""
default_channel_pl = "/www/server/panel/data/default_msg_channel.pl"
default_channel = public.readFile(default_channel_pl)
if default_channel:
return public.returnMsg(True, default_channel)
return public.returnMsg(False, "")
# def get_default_channel(self):
# """获取面板默认消息通道,默认是邮箱,其次默认选择已安装的第一个消息通道
# Returns:
# channel: str/None,没有安装消息通道的情况下返回None。
# """
# from config import config
# c = config()
# get = public.dict_obj()
# configs = c.get_msg_configs(get)
# installed = []
# for channel, obj in configs.items():
# if "setup" in obj and obj["setup"]:
# installed.append(channel)
# if "default" in obj and obj["default"]:
# return channel
# if "mail" in installed:
# return "mail"
# if installed:
# return installed[0]
# return None
def notify(self, args):
"""发送通知
Args:
args (dict):
title: 消息标题
msg: 消息内容
channel: 消息通道
"""
msg = ""
if "msg" in args:
body = args.msg
title = ""
if "title" in args:
title = args.title
sm_type = None
if "sm_type" in args:
sm_type = args.sm_type
sm_args = {}
if "sm_args" in args:
sm_args = json.loads(args.sm_args)
channel = None
channels = []
if "channel" in args:
channel = args.channel
if channel.find(",") != -1:
channels = channel.split(",")
else:
channels = [channel]
if not channel:
channel_res = self.get_default_channel()
if "msg" in channel_res:
channels = [channel_res["msg"]]
if not channels:
return False
try:
from config import config
c = config()
get = public.dict_obj()
msg_channels = c.get_msg_configs(get)
error_channel = []
channel_data = {}
for ch in channels:
msg_data = {}
# 根据不同的消息通道准备不同的内容
if ch == "mail":
# 如果邮箱通知,没有标题直接跳过
if not title: continue
msg_data = {
"msg": body.replace("\n", "<br/>"),
"title": title
}
if ch in ["dingding", "weixin", "feishu"]:
# 钉钉类必须有消息内容
if not body: continue
msg_data["msg"] = body
if ch in ["sms"]:
# 短信必须指定短信模板名
if not sm_type: continue
msg_data["sm_type"] = sm_type
msg_data["sm_args"] = sm_args
if not msg_data:
channel_data[ch] = args
# print("channel data:")
# print(channel_data)
# 即时推送
from panelPush import panelPush
pp = panelPush()
error_count = 0
push_res = pp.push_message_immediately(channel_data)
if push_res["status"]:
channel_res = push_res["msg"]
for ch, res in channel_res.items():
if not res["status"]:
if ch in msg_channels:
error_channel.append(msg_channels[ch]["title"])
error_count +=1
if error_count == len(channels):
return False
return True
except Exception as e:
return False
+83
View File
@@ -0,0 +1,83 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
# 备份
#------------------------------
import os,sys,re,json,shutil,psutil,time
from panelModel.base import panelBase
import public
class main(panelBase):
def __init__(self):
pass
def get_site_backup_info(self,get):
"""
@获取网站是否开启计划任务备份
@param get['site_id'] 网站id
@return
all : 开启全部网站备份
info计划任务详情
"""
id = get.id
find = public.M('sites').where("id=?",(id,)).find()
if not find:
return public.returnMsg(False,'找不到指定网站.')
result = {}
result['all'] = 0
result['info'] = False
result['status'] = True
data = public.M('crontab').where('sName=? and sType =?',(find['name'],'site')).order('id desc').select()
if len(data) > 0:
result['info'] = data[0]
data = public.M('crontab').where('sName=? and sType =?',('ALL','site')).order('id desc').select()
if len(data) > 0:
result['info'] = data[0]
result['all'] = 1
return result
def get_database_backup_info(self,get):
"""
@获取数据库是否开启计划任务备份
@param get['site_id'] 数据库id
@return
all : 开启全部数据库备份
info计划任务详情
"""
id = get.id
find = public.M('databases').where("id=?",(id,)).find()
if not find:
return public.returnMsg(False,'找不到指定数据库.')
result = {}
result['all'] = 0
result['info'] = False
result['status'] = True
data = public.M('crontab').where('sName=? and sType =?',(find['name'],'database')).order('id desc').select()
if len(data) > 0:
result['info'] = data[0]
data = public.M('crontab').where('sName=? and sType =?',('ALL','database')).order('id desc').select()
if len(data) > 0:
result['info'] = data[0]
result['all'] = 1
return result
+21
View File
@@ -0,0 +1,21 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
# 面板其他模型新增功能
#------------------------------
import public,re,time,sys,os,json
from datetime import datetime
class panelBase:
def __init__(self):
pass
+107
View File
@@ -0,0 +1,107 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Windows面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# +-------------------------------------------------------------------
import re,os,sys,public
class panelMssql:
__DB_PASS = None
__DB_USER = 'sa'
__DB_PORT = 1433
__DB_HOST = '127.0.0.1'
__DB_CONN = None
__DB_CUR = None
__DB_ERR = None
__DB_SERVER = 'MSSQLSERVER'
__DB_CLOUD = 0 #远程数据库
def __init__(self):
self.__DB_CLOUD = 0
def set_host(self,host,port,name,username,password,prefix = ''):
self.__DB_HOST = host
self.__DB_PORT = int(port)
self.__DB_NAME = name
if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME)
self.__DB_USER = str(username)
self._USER = str(username)
self.__DB_PASS = str(password)
self.__DB_PREFIX = prefix
self.__DB_CLOUD = 1
return self
def __Conn(self):
"""
连接MSSQL数据库
"""
try:
import pymssql
except :
os.system("btpip install pymssql==2.1.4")
import pymssql
if not self.__DB_CLOUD:
sa_path = 'data/sa.pl'
if os.path.exists(sa_path): self.__DB_PASS = public.readFile(sa_path)
self.__DB_PORT = self.get_port()
try:
if self.__DB_CLOUD:
self.__DB_CONN = pymssql.connect(server = self.__DB_HOST, port= str(self.__DB_PORT),user=self.__DB_USER,password=self.__DB_PASS,database = None,login_timeout = 30,timeout = 0,autocommit = True)
else:
self.__DB_CONN = pymssql.connect(server = self.__DB_HOST, port= str(self.__DB_PORT),login_timeout = 30,timeout = 0,autocommit = True)
self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。
self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。
if self.__DB_CUR:
return True
else:
self.__DB_ERR = '连接数据库失败,请检查是否安装SQL Server'
return False
except Exception as ex:
self.__DB_ERR = public.get_error_info()
return False
def execute(self,sql):
#执行SQL语句返回受影响行
if not self.__Conn(): return self.__DB_ERR
try:
result = self.__DB_CUR.execute(sql)
self.__Close()
return result;
except Exception as ex:
self.__DB_ERR = public.get_error_info()
return self.__DB_ERR
def query(self,sql):
#执行SQL语句返回数据集
if not self.__Conn(): return self.__DB_ERR
try:
self.__DB_CUR.execute(sql)
result = self.__DB_CUR.fetchall()
#print(result)
#将元组转换成列表
data = list(map(list,result))
self.__Close()
return data
except Exception as ex:
self.__DB_ERR = public.get_error_info()
#public.WriteLog('SQL Server查询异常', self.__DB_ERR);
return str(ex)
#关闭连接
def __Close(self):
self.__DB_CUR.close()
self.__DB_CONN.close()
+2 -2
View File
@@ -161,7 +161,7 @@ class panelPHP:
# if not phpv: return None
# cache.set(ikey,phpv[0],3)
# return phpv[0]
#
# def get_pma_root(self):
# '''
# @name 获取phpmyadmin根目录
@@ -175,7 +175,7 @@ class panelPHP:
# if dname.find('phpmyadmin_') != -1:
# return os.path.join(pma_path,dname)
# return None
#
# def check_phpmyadmin_phpversion(self):
# '''
# @name 检查当前phpmyadmin版本可用的php版本列表
+185 -147
View File
@@ -22,10 +22,11 @@ class panelPlugin:
__plugin_list = None
__exists_names = {}
__plugin_s_list = []
__official_url = 'https://brandnew.aapanel.com'
__official_url = 'https://www.aapanel.com'
# __official_url = 'http://dev.aapanel.com'
pids = None
ROWS = 15
def __init__(self):
self.__install_path = '/www/server/panel/plugin'
@@ -58,7 +59,7 @@ class panelPlugin:
def check_sys_write(self):
test_file = '/etc/init.d/bt_10000100.pl'
public.writeFile(test_file,'True')
if os.path.exists(test_file):
if os.path.exists(test_file):
if public.readFile(test_file) == 'True':
os.remove(test_file)
return True
@@ -184,7 +185,7 @@ class panelPlugin:
# self.get_cloud_list(get)
except:pass
return result
#同步安装
def install_sync(self,pluginInfo,get):
import panelAuth
@@ -233,7 +234,7 @@ class panelPlugin:
return True
#异步安装
def install_async(self,pluginInfo,get):
def install_async(self,pluginInfo,get):
mtype = 'install';
mmsg = public.get_msg_gettext('Install')
if hasattr(get, 'upgrade'):
@@ -241,7 +242,7 @@ class panelPlugin:
mmsg = 'upgrade'
if not 'type' in get: get.type = '0'
if int(get.type) > 4: get.type = '0'
if get.sName == 'nginx':
if get.sName == 'nginx':
if get.version == '1.8': return public.return_msg_gettext(False,'Nginx 1.8.1 is too old, no longer available, please choose another version!')
if get.sName.find('php-') != -1:get.sName = get.sName.split('-')[0]
ols_execstr = ""
@@ -341,57 +342,51 @@ class panelPlugin:
focre = 0
if hasattr(get,'force'): focre = int(get.force)
if 'focre_cloud' in session:
if session['focre_cloud']:
focre = 1
session['focre_cloud'] = False
if session:
if 'focre_cloud' in session:
if session['focre_cloud']:
focre = 1
session['focre_cloud'] = False
if not 'init_cloud' in session:
focre = 1
session['init_cloud'] = True
if not 'init_cloud' in session:
focre = 1
session['init_cloud'] = True
if force_refresh == 1:
focre = 1
if not softList or focre > 0:
requesting_tag_key = "REQUESTING_SERVER_SOFTWARE_LIST"
self.clean_panel_log()
# cloudUrl = 'https://console.aapanel.com/api/panel/get_soft_list'
cloudUrl = '{}/api/panel/getSoftList'.format(self.__official_url)
import panelAuth
import requests
pdata = panelAuth.panelAuth().create_serverid(None)
# listTmp = public.httpPost(cloudUrl,pdata,6)
url_headers={}
if 'token' in pdata:
url_headers = {"authorization": "bt {}".format(pdata['token'])}
pdata['environment_info'] = json.dumps(public.fetch_env_info())
# listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers,verify=False,timeout=10)
# listTmp=listTmp.json()
try:
max_waiting_time = 120
has_waiting = False
while cache.get(requesting_tag_key):
has_waiting = True
time.sleep(0.1)
listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers, verify=False, timeout=10)
listTmp.raise_for_status() # 检查请求是否成功,如果不成功会抛出异常
listTmp = listTmp.json()
except:
listTmp = False
if has_waiting:
return self.get_cloud_list(get)
cache.set(requesting_tag_key, True, timeout=max_waiting_time)
self.clean_panel_log()
# cloudUrl = 'https://console.aapanel.com/api/panel/get_soft_list'
cloudUrl = '{}/api/panel/getSoftList'.format(self.__official_url)
import panelAuth
import requests
pdata = panelAuth.panelAuth().create_serverid(None)
# listTmp = public.httpPost(cloudUrl,pdata,6)
url_headers={}
if 'token' in pdata:
url_headers = {"authorization": "bt {}".format(pdata['token'])}
pdata['environment_info'] = json.dumps(public.fetch_env_info())
try:
listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers,verify=False)
listTmp=listTmp.json()
if not listTmp:
listTmp = public.readFile(lcoalTmp)
softList = listTmp
except: pass
if softList: public.writeFile(lcoalTmp,json.dumps(softList))
public.ExecShell('rm -f /tmp/bmac_*')
public.run_thread(self.getCloudPHPExt)
# 专业版和企业版到期提醒,aaPanel目前没有先注释
# self.expire_msg(softList)
if listTmp is False:
listTmp = public.readFile(lcoalTmp)
try:
softList = listTmp
except:
pass
finally:
cache.delete(requesting_tag_key)
if softList: public.writeFile(lcoalTmp,json.dumps(softList))
public.ExecShell('rm -f /tmp/bmac_*')
public.run_thread(self.getCloudPHPExt)
# 专业版和企业版到期提醒,aaPanel目前没有先注释
# self.expire_msg(softList)
try:
public.writeFile("/tmp/" + cache.get('p_token'),str(softList['pro']))
except:pass
@@ -399,8 +394,31 @@ class panelPlugin:
try:
if hasattr(get,'type'): sType = int(get['type'])
if hasattr(get,'query'):
if get.query: sType = 0
if get.query:
# 关键词统计 参数keyword
import panelAuth
import requests
countUrl = '{}/api/panel/submit_keyword'.format(self.__official_url)
pdata = panelAuth.panelAuth().create_serverid(None)
url_headers = {}
if 'token' in pdata:
url_headers = {"authorization": "bt {}".format(pdata['token'])}
pdata['environment_info'] = json.dumps(public.fetch_env_info())
keyword = {
"keyword": get.query
}
try:
requests.post(countUrl, params=keyword, headers=url_headers, verify=False, timeout=3)
except:
pass
sType = 0
except:pass
try:
softList = json.loads(softList)
except:
pass
softList['list'] = self.get_local_plugin(softList['list'])
softList['list'] = self.get_types(softList['list'],sType)
if hasattr(get,'query'):
@@ -565,7 +583,7 @@ class panelPlugin:
for name in os.listdir('plugin/'):
isExists = False
for softInfo in sList:
if name == softInfo['name']:
if name == softInfo['name']:
isExists = True
break
if isExists: continue
@@ -585,7 +603,7 @@ class panelPlugin:
def check_setup_task(self,sName):
if not self.__tasks:
self.__tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select()
if sName.find('php-') != -1:
if sName.find('php-') != -1:
tmp = sName.split('-')
sName = tmp[0]
version = tmp[1]
@@ -660,7 +678,7 @@ class panelPlugin:
}
except: pluginInfo = None
return pluginInfo
#处理分类
def get_types(self,sList,sType):
if sType <= 0: return sList
@@ -704,11 +722,9 @@ class panelPlugin:
#取软件列表
def get_soft_list(self,get = None):
print("get soft list normal.")
softList = self.get_cloud_list(get)
if not softList:
get.force = 1
print("get soft list force.")
softList = self.get_cloud_list(get)
if not softList: return public.return_msg_gettext(False,'Failed to get software list ({})',"401")
softList['list'] = self.set_coexist(softList['list'])
@@ -729,31 +745,43 @@ class panelPlugin:
check_version_path = '/www/server/apache/version_check.pl'
if os.path.exists(check_version_path):
softList['apache24'] = True
if public.readFile(check_version_path).find('2.2') == 0:
if public.readFile(check_version_path).find('2.2') == 0:
softList['apache22'] = True
softList['apache24'] = False
if os.path.exists('/www/server/nginx/conf/nginx.conf'):
import one_key_wp
one_key_wp.fast_cgi().set_nginx_conf()
one_key_wp.fast_cgi().set_nginx_init()
public.ExecShell("/etc/init.d/nginx start")
return softList
#取首页软件列表
def get_index_list(self,get=None):
softList = self.get_cloud_list(get)['list']
if not softList:
if not softList:
get.force = 1
softList = self.get_cloud_list(get)['list']
if not softList: return public.return_msg_gettext(False,'Failed to get software list ({})',"401")
softList = self.set_coexist(softList)
if not os.path.exists(self.__index): public.writeFile(self.__index,'[]')
indexList = json.loads(public.ReadFile(self.__index))
# # 只取应用名
# lista = [i['name'] for i in softList]
#
# public.print_log("23454566777^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_______________________________"
# "列表 {}".format(lista))
if not os.path.exists(self.__index):
public.writeFile(self.__index,'[]')
try:
indexList = json.loads(public.ReadFile(self.__index))
except Exception:
os.remove(self.__index)
public.writeFile(self.__index, '[]')
indexList = []
dataList = []
for index in indexList:
for softInfo in softList:
if softInfo['name'] == index: dataList.append(softInfo)
dataList = self.check_isinstall(dataList)
return dataList
#添加到首页
@@ -798,7 +826,7 @@ class panelPlugin:
indexList = get.ssort.split('|')
public.writeFile(self.__index,json.dumps(indexList))
return public.return_msg_gettext(True,'Setup successfully!')
#取快捷软件列表
def get_link_list(self,get=None):
softList = self.get_cloud_list(get)['list']
@@ -905,14 +933,14 @@ class panelPlugin:
if softInfo['id'] != 10000:
self.get_icon(softInfo['name'].split('-')[0])
else:
if 'min_image' in softInfo:
if 'min_image' in softInfo:
if softInfo['id'] != 10000:
self.get_icon(softInfo['name'],softInfo['min_image'])
else:
# if softInfo['id'] != 10000:
self.get_icon(softInfo['name'])
if softInfo['name'].find('php-') != -1:
if softInfo['name'].find('php-') != -1:
v2= softInfo['versions'][0]['m_version'].replace('.','')
softInfo['fpm'] = os.path.exists('/www/server/php/' + v2 + '/sbin/php-fpm')
softInfo['status'] = self.get_php_status(v2)
@@ -1041,9 +1069,9 @@ class panelPlugin:
'openlitespeed': "cat /usr/local/lsws/VERSION",
'gitlab':'echo "8.8.5"'
}
exec_str = ''
if sInfo['name'] in exec_args: exec_str = exec_args[sInfo['name']]
if sInfo['name'] in exec_args: exec_str = exec_args[sInfo['name']]
if sInfo['version_coexist'] == 1:
v_tmp = sInfo['name'].split('-')
exec_str = exec_args[v_tmp[0]].replace('{VERSION}',v_tmp[1].replace('.',''))
@@ -1075,13 +1103,13 @@ class panelPlugin:
if len(versions) == 1:
versions[0]['setup'] = True
return versions
for i in range(len(versions)):
if version == (versions[i]['m_version'] + '.' + versions[i]['version']):
versions[i]['setup'] = True
continue
vTmp = versions[i]['m_version'].split('_')
if len(vTmp) > 1:
if len(vTmp) > 1:
vTmp = vTmp[1]
else:
vTmp = vTmp[0]
@@ -1164,7 +1192,7 @@ class panelPlugin:
info['return_js'] = ''
if hasattr(get,'tojs'):
info['return_js'] = get.tojs
#获取分页数据
result = {}
result['page'] = page.GetPage(info)
@@ -1176,17 +1204,17 @@ class panelPlugin:
n += 1
result['data'].append(data[i])
return result
#取列表
def GetList(self,get = None):
try:
if not os.path.exists(self.__list): return []
data = json.loads(public.readFile(self.__list))
#排序
data = sorted(data, key= lambda b:b['sort'],reverse=False)
#获取非划分列表
n = 0
for dirinfo in os.listdir(self.__install_path):
@@ -1194,24 +1222,24 @@ class panelPlugin:
for tm in data:
if tm['name'] == dirinfo: isTrue = False
if not isTrue: continue
path = self.__install_path + '/' + dirinfo
if os.path.isdir(path):
jsonFile = path + '/info.json'
if os.path.exists(jsonFile):
try:
try:
tmp = json.loads(public.readFile(jsonFile))
if not hasattr(get,'type'):
if not hasattr(get,'type'):
get.type = 0
else:
get.type = int(get.type)
if get.type > 0:
try:
if get.type != tmp['id']: continue
except:
continue
tmp['pid'] = len(data) + 1000 + n
tmp['status'] = tmp['display']
tmp['display'] = 0
@@ -1222,17 +1250,17 @@ class panelPlugin:
if get:
display = None
if hasattr(get,'display'): display = True
if not hasattr(get,'type'):
if not hasattr(get,'type'):
get.type = 0
else:
get.type = int(get.type)
if not hasattr(get,'search'):
if not hasattr(get,'search'):
search = None
m = 0
else:
search = get.search.encode('utf-8').lower()
m = 1
tmp = []
for d in data:
if d['id'] != 10000:
@@ -1255,8 +1283,8 @@ class panelPlugin:
return data
except Exception as ex:
return str(ex)
#获取图标
def get_icon(self,name,downFile = None):
iconFile = 'BTPanel/static/img/soft_ico/ico-' + name + '.png'
@@ -1267,7 +1295,7 @@ class panelPlugin:
if size == 0:
public.run_thread(self.download_icon,(name,iconFile,downFile))
# self.download_icon(name,iconFile,downFile)
#下载图标
def download_icon(self,name,iconFile,downFile):
srcIcon = 'plugin/' + name + '/icon.png'
@@ -1282,7 +1310,7 @@ class panelPlugin:
public.ExecShell('wget -O ' + iconFile + ' ' + public.get_url() + '/install/plugin/' + name + '/icon.png' + " &")
cache.set(skey,1,86400)
#取分页
def GetPage(self,data,get):
#包含分页类
@@ -1299,7 +1327,7 @@ class panelPlugin:
info['return_js'] = ''
if hasattr(get,'tojs'):
info['return_js'] = get.tojs
#获取分页数据
result = {}
result['page'] = page.GetPage(info)
@@ -1311,7 +1339,7 @@ class panelPlugin:
n += 1
result['data'].append(data[i])
return result
#取分类
def GetType(self,get = None):
try:
@@ -1320,7 +1348,7 @@ class panelPlugin:
return data
except:
return False
#取单个
def GetFind(self,name):
try:
@@ -1330,26 +1358,26 @@ class panelPlugin:
return None
except:
return None
#设置
def SetField(self,name,key,value):
data = self.GetList(None)
for i in range(len(data)):
if data[i]['name'] != name: continue
data[i][key] = value
public.writeFile(self.__list,json.dumps(data))
return True
#安装插件
def install(self,get):
pluginInfo = self.GetFind(get.name)
if not pluginInfo:
import json
pluginInfo = json.loads(public.readFile(self.__install_path + '/' + get.name + '/info.json'))
if pluginInfo['tip'] == 'lib':
if not os.path.exists(self.__install_path + '/' + pluginInfo['name']): public.ExecShell('mkdir -p ' + self.__install_path + '/' + pluginInfo['name'])
if not 'download_url' in session: session['download_url'] = public.get_url()
@@ -1369,14 +1397,14 @@ class panelPlugin:
if not os.path.exists(path): public.ExecShell("mkdir -p " + path)
issue = public.readFile('/etc/issue')
if session['server_os']['x'] != 'RHEL': get.type = '3'
apacheVersion='false'
if public.get_webserver() == 'apache':
apacheVersion = public.readFile('/www/server/apache/version.pl')
apacheVersion = public.xss_version(public.readFile('/www/server/apache/version.pl'))
public.writeFile('/var/bt_apacheVersion.pl',apacheVersion)
public.writeFile('/var/bt_setupPath.conf',public.GetConfigValue('root_path'))
isTask = '/tmp/panelTask.pl'
mtype = 'install'
mmsg = 'install'
if hasattr(get, 'upgrade'):
@@ -1393,8 +1421,8 @@ class panelPlugin:
public.writeFile(isTask,'True')
public.write_log_gettext('Installer','Successfully added intallation task [{}-{}]',(get.name,get.version))
return public.return_msg_gettext(True,'Installation task added to queue')
#卸载插件
def unInstall(self,get):
pluginInfo = self.GetFind(get.name)
@@ -1416,10 +1444,10 @@ class panelPlugin:
public.ExecShell('/bin/bash {} uninstall'.format(toFile))
elif os.path.exists(pluginPath + '/install.sh'):
public.ExecShell('/bin/bash ' + pluginPath + '/install.sh uninstall')
if os.path.exists(pluginPath):
public.ExecShell('rm -rf ' + pluginPath)
public.write_log_gettext('Installer','Successfully uninstalled software [{}]',(pluginInfo['title'],))
return public.return_msg_gettext(True,"Uninstallation succeeded")
else:
@@ -1431,8 +1459,8 @@ class panelPlugin:
public.ExecShell(execstr)
public.WriteLog('TYPE_SETUP','Successfully uninstalled [{}-{}]',(get.name,get.version))
return public.returnMsg(True,"Uninstallation succeeded")
#取产品信息
#取产品信息
def getProductInfo(self,productName):
if not self.__product_list:
import panelAuth
@@ -1441,7 +1469,7 @@ class panelPlugin:
for product in self.__product_list:
if product['name'] == productName: return product
return None
#取到期时间
def getEndDate(self,pluginName):
if not self.__plugin_list:
@@ -1452,18 +1480,18 @@ class panelPlugin:
if not 'data' in tmp: return public.get_msg_gettext('NOT opened')
self.__plugin_list = tmp['data']
for pluinfo in self.__plugin_list:
if pluinfo['product'] == pluginName:
if pluinfo['product'] == pluginName:
if not pluinfo['endtime'] or not pluinfo['state']: return public.get_msg_gettext('To be paid')
if pluinfo['endtime'] < time.time(): return public.get_msg_gettext('Expired')
return time.strftime("%Y-%m-%d",time.localtime(pluinfo['endtime']));
return public.get_msg_gettext('NOT opened')
#取插件列表
def getPluginList(self,get):
import json
arr = self.GetList(get)
result = {}
if not arr:
if not arr:
result['data'] = arr
result['type'] = self.GetType(None)
return result
@@ -1474,7 +1502,7 @@ class panelPlugin:
apacheVersion = public.xss_version(public.readFile(apavFile).strip())
except:
pass
result = self.GetPage(arr,get)
arr = result['data']
for i in range(len(arr)):
@@ -1484,8 +1512,8 @@ class panelPlugin:
# arr[i]['end'] = self.getEndDate(arr[i]['title']);
# if os.path.exists('plugin/beta/config.conf'):
# if os.path.exists('plugin/' + arr[i]['name'] + '/' + arr[i]['name'] + '_main.py') and arr[i]['end'] == '未开通': arr[i]['end'] = '--';
if arr[i]['name'] == 'php':
if apacheVersion == '2.2':
arr[i]['versions'] = '5.2,5.3,5.4'
@@ -1494,29 +1522,29 @@ class panelPlugin:
arr[i]['versions'] = '5.3,5.4,5.5,5.6,7.0,7.1,7.2,7.3,7.4'
arr[i]['update'] = self.GetPv(arr[i]['versions'], arr[i]['update'])
arr[i]['apache'] = apacheVersion
arr[i]['versions'] = self.checksSetup(arr[i]['name'].replace('_soft',''),arr[i]['checks'],arr[i]['versions'])
try:
arr[i]['update'] = arr[i]['update'].split(',')
except:
arr[i]['update'] = []
#是否强制使用插件模板 LIB_TEMPLATE
if os.path.exists(self.__install_path+'/'+arr[i]['name']): arr[i]['tip'] = 'lib'
if arr[i]['tip'] == 'lib':
if arr[i]['tip'] == 'lib':
arr[i]['path'] = self.__install_path + '/' + arr[i]['name'].replace('_soft','')
arr[i]['config'] = os.path.exists(arr[i]['path'] + '/index.html')
else:
arr[i]['path'] = '/www/server/' + arr[i]['name'].replace('_soft','')
arr.append(public.M('tasks').where("status!=?",('1',)).count())
result['data'] = arr
result['type'] = self.GetType(None)
return result
#GetPHPV
def GetPv(self,versions,update):
versions = versions.split(',')
@@ -1525,7 +1553,7 @@ class panelPlugin:
for up in update:
if up[:3] in versions: updates.append(up)
return ','.join(updates)
#保存插件排序
def savePluginSort(self,get):
ssort = get.ssort.split('|')
@@ -1552,7 +1580,7 @@ class panelPlugin:
versArr = vers.split(',')
for v in versArr:
version = {}
v2 = v
if name == 'php': v2 = v2.replace('.','')
status = False
@@ -1591,14 +1619,14 @@ class panelPlugin:
else:
if name1 == 'pure': name1 = 'pure-ftpd'
if name1 == name: isTask = task['status']
infoFile = 'plugin/' + name + '/info.json'
if os.path.exists(infoFile):
try:
tmps = json.loads(public.readFile(infoFile))
if tmps: v1 = tmps['versions']
except:pass
if name == 'memcached':
if os.path.exists('/etc/init.d/memcached'):
v1 = session.get('memcachedv')
@@ -1606,7 +1634,7 @@ class panelPlugin:
v1 = public.ExecShell("memcached -V|awk '{print $2}'")[0].strip()
session['memcachedv'] = v1
if name == 'apache':
if os.path.exists('/www/server/apache/bin/httpd'):
if os.path.exists('/www/server/apache/bin/httpd'):
v1 = session.get('httpdv')
if not v1:
v1 = public.ExecShell("/www/server/apache/bin/httpd -v|grep Apache|awk '{print $3}'|sed 's/Apache\///'")[0].strip();
@@ -1620,11 +1648,11 @@ class panelPlugin:
version['no'] = v1
versions.append(version)
return self.checkRun(name,versions)
#检查是否启动
def checkRun(self,name,versions):
if name == 'php':
path = '/www/server/php'
path = '/www/server/php'
pids = psutil.pids()
for i in range(len(versions)):
if versions[i]['status']:
@@ -1643,7 +1671,7 @@ class panelPlugin:
versions[i]['pathinfo'] = phpConfig['pathinfo']
versions[i]['display'] = os.path.exists(path + '/' + v4 + '/display.pl')
if len(versions) < 5: versions[i]['run'] = True
elif name == 'nginx':
status = False
if os.path.exists('/etc/init.d/nginx'):
@@ -1710,7 +1738,7 @@ class panelPlugin:
for i in range(len(versions)):
if versions[i]['status']: versions[i]['run'] = True
return versions
#取PHPMyAdmin状态
def getPHPMyAdminStatus(self):
import re
@@ -1727,7 +1755,7 @@ class panelPlugin:
rtmp = re.search(rep,conf)
if rtmp:
phpport = rtmp.groups()[0]
if conf.find('AUTH_START') != -1: pauth = True
if conf.find(setupPath + '/stop') == -1: pstatus = True
configFile = setupPath + '/nginx/conf/enable-php.conf'
@@ -1746,7 +1774,7 @@ class panelPlugin:
rep = r"php-cgi.*\.sock"
public.writeFile(configFile,conf)
phpversion = '54'
configFile = setupPath + '/apache/conf/extra/httpd-vhosts.conf'
if os.path.exists(configFile):
conf = public.readFile(configFile)
@@ -1829,20 +1857,20 @@ class panelPlugin:
data['maxTime'] = tmp[0]
except:
data['maxTime'] = 0
try:
rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n"
tmp = re.search(rep,phpini).groups()
if tmp[0] == '1':
data['pathinfo'] = True
else:
data['pathinfo'] = False
except:
data['pathinfo'] = False
return data
#名取PID
def getPid(self,pname):
try:
@@ -1851,7 +1879,7 @@ class panelPlugin:
if psutil.Process(pid).name() == pname: return True
return False
except: return True
#检测指定进程是否存活
def checkProcess(self,pid):
try:
@@ -1859,7 +1887,7 @@ class panelPlugin:
if int(pid) in self.pids: return True
return False
except: return False
#获取配置模板
def getConfigHtml(self,get):
filename = self.__install_path + '/' + get.name + '/index.html'
@@ -2019,13 +2047,13 @@ class panelPlugin:
else:
self.SetField(get.name, 'display', int(get.status))
return public.return_msg_gettext(True,'Setup successfully!')
#从云端获取插件列表
def getCloudPlugin(self,get):
if session.get('getCloudPlugin') and get != None: return public.return_msg_gettext(True,'Your plugin list is already the latest version {}!',("-1",))
import json
if not session.get('download_url'): session['download_url'] = 'http://node.aapanel.com'
#获取列表
try:
newUrl = public.get_url()
@@ -2065,12 +2093,12 @@ class panelPlugin:
self.GetCloudWarning(get)
session['getCloudPlugin'] = True
return public.return_msg_gettext(True,'Software list updated!')
#刷新缓存
def flush_cache(self,get):
self.getCloudPlugin(None)
return public.return_msg_gettext(True,'Software list updated!')
#获取PHP扩展
def getCloudPHPExt(self,get=None):
import json
@@ -2110,7 +2138,7 @@ class panelPlugin:
return public.get_error_info()
#获取警告列表
def GetCloudWarning(self,get):
import json
@@ -2135,25 +2163,35 @@ class panelPlugin:
find = self.get_soft_find(get)
return find['title']
#请求插件事件
def a(self,get):
if not hasattr(get,'name'): return public.return_msg_gettext(False,'Input name of plugin!')
try:
if not public.path_safe_check("%s/%s" % (get.name,get.s)): return public.return_msg_gettext(False,'Requested method [{}] does not exist!')
path = self.__install_path + '/' + get.name
# aaa = path + '/'+get.name+'_main.py'
# public.print_log("@@@@@@@@@@@@@@@@@aaa {}".format(aaa))
if not os.path.exists(path + '/'+get.name+'_main.py'):
if os.path.exists(path+'/index.php'):
# bbb = path+'/index.php'
# public.print_log("@@@@@@@@@@@@@@@@@bbb {}".format(bbb))
import panelPHP
return panelPHP.panelPHP(get.name).exec_php_script(get)
return public.return_msg_gettext(False,'This plugin does NOT have extend function!')
if not self.check_accept(get):return public.return_msg_gettext(False,"You did not purchase [ {} ] or the authorization has expired", (self.get_title_byname(get),))
public.package_path_append(path)
plugin_main = __import__(get.name+'_main')
try:
reload(plugin_main)
except: pass
pluginObject = eval('plugin_main.' + get.name + '_main()')
if not hasattr(pluginObject,get.s): return public.return_msg_gettext(False,'Requested method [{}] does not exist!',(get.s,))
execStr = 'pluginObject.' + get.s + '(get)'
@@ -2166,13 +2204,13 @@ class panelPlugin:
#上传插件包
def update_zip(self,get = None,tmp_file = None, update = False):
tmp_path = '/www/server/panel/temp'
if not os.path.exists(tmp_path):
if not os.path.exists(tmp_path):
os.makedirs(tmp_path,mode=384)
if tmp_file:
if tmp_file:
if not os.path.exists(tmp_file): return public.return_msg_gettext(False,'File download failed!')
if get:
public.ExecShell("rm -rf " + tmp_path + '/*')
tmp_file = tmp_path + '/plugin_tmp.zip'
@@ -2196,7 +2234,7 @@ class panelPlugin:
if not 'install.sh' in df[2]: continue
if not os.path.exists(df[0] + '/info.json'): continue
d_path = df[0]
if d_path:
if d_path:
tmp_path = d_path
p_info = tmp_path + '/info.json'
try:
@@ -2246,13 +2284,13 @@ class panelPlugin:
return public.return_msg_gettext(True,'Installation succeeded!')
public.ExecShell("rm -rf " + plugin_path)
return public.return_msg_gettext(False,'Installation failed')
#导出插件包
def export_zip(self,get):
plugin_path = '/www/server/panel/plugin/' + get.plugin_name
if not os.path.exists(plugin_path): return public.return_msg_gettext(False,'The specified plugin does not exist!')
get.sfile = plugin_path + '/'
get.dfile = '/www/server/panel/temp/bt_plugin_' + get.plugin_name + '.zip'
get.type = 'zip'
+36 -13
View File
@@ -17,7 +17,7 @@ class ProjectController:
def __init__(self):
pass
def model(self,args):
'''
@name 调用指定项目模型
@@ -36,19 +36,31 @@ class ProjectController:
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
except:
return public.get_error_object()
#静态html调用
if 'stype' in args and args['stype'] == 'html':
from BTPanel import render_template_string
t_path_root = public.get_panel_path()+'/class/projectModel/templates/'
t_path = t_path_root + args['mod_name']+"_"+args['def_name'] + '.html'
if not os.path.exists(t_path):
return public.return_status_code(1000,'The called template does not exist!'+t_path)
t_body = public.readFile(t_path)
return render_template_string(t_body, data={})
# 参数处理
module_name = args['mod_name'].strip()
mod_name = "{}Model".format(args['mod_name'].strip())
def_name = args['def_name'].strip()
# 指定模型是否存在
mod_file = "{}/projectModel/{}.py".format(public.get_class_path(),mod_name)
if not os.path.exists(mod_file):
return public.return_status_code(1003,mod_name)
# 实例化
def_object = public.get_script_object(mod_file)
if not def_object: return public.return_status_code(1000,'没有找到{}模型'.format(mod_name))
run_object = getattr(def_object.main(),def_name,None)
if not run_object: return public.return_status_code(1000,'没有在{}模型中找到{}方法'.format(mod_name,def_name))
# # 指定模型是否存在
# mod_file = "{}/projectModel/{}.py".format(public.get_class_path(),mod_name)
# if not os.path.exists(mod_file):
# return public.return_status_code(1003,mod_name)
# # 实例化
# def_object = public.get_script_object(mod_file)
# if not def_object: return public.return_status_code(1000,'没有找到{}模型'.format(mod_name))
# run_object = getattr(def_object.main(),def_name,None)
# if not run_object: return public.return_status_code(1000,'没有在{}模型中找到{}方法'.format(mod_name,def_name))
if not hasattr(args,'data'): args.data = {}
if args.data:
if isinstance(args.data,str):
@@ -61,6 +73,10 @@ class ProjectController:
else:
pdata = args
if isinstance(pdata,dict): pdata = public.to_dict_obj(pdata)
pdata.model_index = 'project'
# 前置HOOK
hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper())
hook_result = public.exec_hook(hook_index,pdata)
@@ -70,10 +86,17 @@ class ProjectController:
return hook_result # 响应具体错误信息
elif isinstance(hook_result,bool):
if not hook_result: # 直接中断操作
return public.return_data(False,{},error_msg='前置HOOK中断操作')
return public.return_data(False,{},error_msg='Pre-HOOK interrupt operation')
# 调用处理方法
result = run_object(pdata)
# result = run_object(pdata)
import PluginLoader
result = PluginLoader.module_run(module_name,def_name,pdata)
if isinstance(result,dict):
if 'status' in result and result['status'] == False and 'msg' in result:
if isinstance(result['msg'],str):
if result['msg'].find('Traceback ') != -1:
raise public.PanelError(result['msg'])
# 后置HOOK
hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper())
+703
View File
@@ -0,0 +1,703 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: lx
# | 消息推送管理
# | 对外方法 get_modules_list、install_module、uninstall_module、get_module_template、set_push_config、get_push_config、del_push_config
# +-------------------------------------------------------------------
import os, sys
panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public,re,json,time
try:
from BTPanel import session
except :
pass
class panelPush:
__conf_path = "{}/class/push/push.json".format(panelPath)
def __init__(self):
spath = '{}/class/push'.format(panelPath)
if not os.path.exists(spath): os.makedirs(spath)
"""
@获取推送模块列表
"""
def get_modules_list(self,get):
cpath = '{}/class/push/push_list.json'.format(panelPath)
try:
spath = os.path.dirname(cpath)
if not os.path.exists(spath): os.makedirs(spath)
if 'force' in get or not os.path.exists(cpath):
if not 'download_url' in session: session['download_url'] = public.get_url()
public.downloadFile('{}/linux/panel/push/push_list.json'.format(session['download_url']),cpath)
except : pass
if not os.path.exists(cpath):
return {}
data = {}
push_list = self._get_conf()
module_list = public.get_modules('class/push')
configs = json.loads(public.readFile(cpath))
for p_info in configs:
p_info['data'] = {}
p_info['setup'] = False
p_info['info'] = False
key = p_info['name']
try:
if hasattr(module_list, key):
p_info['setup'] = True
# if key in module_list:
# print(dir(module_list))
# print(dir(module_list[key]))
# print(dir(getattr(module_list[key], key)))
push_module = getattr(module_list[key], key)()
p_info['info'] = push_module.get_version_info(None);
#格式化消息通道
if key in push_list:
p_info['data'] = self.__get_push_list(push_list[key])
#格式化返回执行周期
if hasattr(push_module,'get_push_cycle'):
p_info['data'] = push_module.get_push_cycle(p_info['data'])
except :
return public.get_error_object(None)
data[key] = p_info
return data
"""
安装/更新消息通道模块
@name 需要安装的模块名称
"""
def install_module(self,get):
module_name = get.name
down_url = public.get_url()
local_path = '{}/class/push'.format(panelPath)
if not os.path.exists(local_path): os.makedirs(local_path)
sfile = '{}/{}.py'.format(local_path,module_name)
public.downloadFile('{}/linux/panel/push/{}.py'.format(down_url,module_name),sfile)
if not os.path.exists(sfile): return public.returnMsg(False, '[{}] Module installation failed'.format(module_name))
if os.path.getsize(sfile) < 1024: return public.returnMsg(False, '[{}] Module installation failed'.format(module_name))
sfile = '{}/class/push/{}.html'.format(panelPath,module_name)
public.downloadFile('{}/linux/panel/push/{}.html'.format(down_url,module_name),sfile)
return public.returnMsg(True, '[{}] Module installed successfully.'.format(module_name))
"""
卸载消息通道模块
@name 需要卸载的模块名称
"""
def uninstall_module(self,get):
module_name = get.name
sfile = '{}/class/push/{}.py'.format(panelPath,module_name)
if os.path.exists(sfile): os.remove(sfile)
return public.returnMsg(True, '[{}] Module uninstalled successfully'.format(module_name))
"""
@获取模块执行日志
"""
def get_module_logs(self,get):
module_name = get.name
id = get.id
return []
"""
获取模块模板
"""
def get_module_template(self,get):
sfile = '{}/class/push/{}.html'.format(panelPath,get.module_name)
if not os.path.exists(sfile):
return public.returnMsg(False, 'template file does not exist!')
shtml = public.readFile(sfile)
return public.returnMsg(True, shtml)
"""
@获取模块推送参数panel_push ssl到期服务停止
"""
def get_module_config(self,get):
module = get.name
p_list = public.get_modules('class/push')
push_module = getattr(p_list[module], module)()
if not module in p_list:
return public.returnMsg(False, 'The specified module [{}] is not installed!'.format(module))
if not hasattr(push_module,'get_module_config'):
return public.returnMsg(False, 'No get_module_config method exists for the specified module [{}].'.format(module))
return push_module.get_module_config(get)
"""
@获取模块配置项
@优先调用模块内的get_push_config
"""
def get_push_config(self,get):
module = get.name
id = get.id
p_list = public.get_modules('class/push')
if not module in p_list:
return public.returnMsg(False, 'The specified module [{}] is not installed.'.format(module))
result = None
push_module = getattr(p_list[module], module)()
if not hasattr(push_module,'get_push_config'):
push_list = self._get_conf()
res_data = public.returnMsg(False, 'The specified configuration was not found!')
res_data['code'] = 100
if not module in push_list:
return res_data
if not id in push_list[module]:
return res_data
result = push_list[module][id]
else:
result = push_module.get_push_config(get)
return self.get_push_user(result)
def get_push_user(self,result):
#获取发送给谁
if not 'to_user' in result:
result['to_user'] = {}
if 'module' in result:
for s_module in result['module'].split(','):
result['to_user'][s_module] = 'default'
else:
return False
info = {}
for s_module in result['module'].split(','):
msg_obj = public.init_msg(s_module)
if not msg_obj: continue
info[s_module] = {}
data = msg_obj.get_config(None)
if 'list' in data:
for key in result['to_user'][s_module].split(','):
if not key in data['list']:
continue
info[s_module][key] = data['list'][key]
result['user_info'] = info
return result
"""
@设置推送配置
@优先调用模块内的set_push_config
"""
def set_push_config(self,get):
module = get.name
id = get.id
p_list = public.get_modules('class/push')
if not module in p_list:
return public.returnMsg(False, 'The specified module [{}] is not installed.'.format(module))
pdata = json.loads(get.data)
if not 'module' in pdata or not pdata['module']:
return public.returnMsg(False, 'The specified alarm method is not set, please select again.')
if module == "load_balance_push":
pdata = self.__get_args(pdata,'cycle', "500|502|503|504")
else:
pdata = self.__get_args(pdata, 'cycle', 1)
pdata = self.__get_args(pdata,'count',1)
pdata = self.__get_args(pdata,'interval',600)
pdata = self.__get_args(pdata,'key','')
pdata = self.__get_args(pdata,'push_count',0)
nData = {}
for skey in ['key','type','cycle','count','interval','module','title','project','status','index','push_count']:
if skey in pdata:
nData[skey] = pdata[skey]
public.set_module_logs('set_push_config',nData['type'])
class_obj = getattr(p_list[module], module)()
if hasattr(class_obj,'set_push_config'):
get['data'] = json.dumps(nData)
result = class_obj.set_push_config(get)
if 'status' in result: return result
data = result
else:
data = self._get_conf()
if not module in data:data[module] = {}
data[module][id] = nData
public.writeFile(self.__conf_path,json.dumps(data))
return public.returnMsg(True, 'Saved successfully')
"""
@设置推送状态
"""
def set_push_status(self,get):
id = get.id
module = get.name
data = self._get_conf()
if not module in data: return public.returnMsg(True, 'module name does not exist!')
if not id in data[module]: return public.returnMsg(True, 'The specified push task does not exist!')
status = int(get.status)
if status:
data[module][id]['status'] = True
else:
data[module][id]['status'] = False
public.writeFile(self.__conf_path,json.dumps(data))
return public.returnMsg(True, 'Successful operation.')
"""
@删除指定配置
"""
def del_push_config(self,get):
id = get.id
module = get.name
p_list = public.get_modules('class/push')
if not module in p_list:
return public.returnMsg(False, 'The specified module {} is not installed.'.format(module))
push_module = getattr(p_list[module], module)()
if not hasattr(push_module,'del_push_config'):
data = self._get_conf()
del data[module][id]
public.writeFile(self.__conf_path,json.dumps(data))
return public.returnMsg(True, 'successfully deleted.')
return push_module.del_push_config(get)
"""
获取消息通道配置列表
"""
def get_push_msg_list(self,get):
data = {}
msgs = self.__get_msg_list()
from panelMessage import panelMessage
pm = panelMessage()
for x in msgs:
x['setup'] = False
key = x['name']
try:
obj = pm.init_msg_module(key)
if obj:
x['setup'] = True
if key == 'sms':x['title'] = '{}<a title="Please make sure there are enough SMS messages, otherwise you will not be able to receive notifications." href="javascript:;" class="bt-ico-ask">?</a>'.format(x['title'])
except :
pass
data[key] = x
return data
"""
@ 获取消息推送配置
"""
def _get_conf(self):
data = {}
try:
if os.path.exists(self.__conf_path):
data = json.loads(public.readFile(self.__conf_path))
self.update_config(data)
except:pass
return data
"""
@ 获取插件版本信息
"""
def get_version_info(self):
"""
获取版本信息
"""
data = {}
data['ps'] = ''
data['version'] = '1.0'
data['date'] = '2020-07-14'
data['author'] = '宝塔'
data['help'] = 'http://www.bt.cn'
return data
"""
@格式化推送对象
"""
def format_push_data(self,push = ['dingding','weixin','feishu'], project = '', type = ''):
item = {
'title':'',
'project':project,
'type':type,
'cycle':1,
'count':1,
'keys':[],
'helps':[],
'push':push
}
return item
def push_message_immediately(self, channel_data):
"""推送消息到指定的消息通道,即时
Args:
channel_data(dict):
key: msg_channel, 消息通道名称多个用逗号相连
value: msg obj, 每种消息通道的消息内容格式可能包含标题
Returns:
{
status: True/False,
msg: {
"email": {"status": msg},
...
}
}
"""
if type(channel_data) != dict:
return public.returnMsg(False, "The parameter is wrong")
from panelMessage import panelMessage
pm = panelMessage()
channel_res = {}
res = {
"status": False,
"msg": channel_res
}
for module, msg in channel_data.items():
modules = []
if module.find(",") != -1:
modules = module.split(",")
else:
modules.append(module)
for m_module in modules:
msg_obj = pm.init_msg_module(m_module)
if not msg_obj:continue
ret = msg_obj.push_data(msg)
if ret and "status" in ret and ret['status']:
res["status"] = True
channel_res[m_module] = ret
else:
msg = "Message push failed."
if "msg" in ret:
msg = ret["msg"]
channel_res[m_module] = public.returnMsg(False, msg)
return res
"""
@格式为消息通道格式
"""
def format_msg_data(self):
data = {
'title':'',
'to_email':'',
'sms_type':'',
'sms_argv':{},
'msg':''
}
return data
def __get_msg_list(self):
"""
获取消息通道列表
"""
data = []
cpath = '{}/data/msg.json'.format(panelPath)
if not os.path.exists(cpath):
return data
try:
conf = public.readFile(cpath)
data = json.loads(conf)
except :
try:
time.sleep(0.5)
conf = public.readFile(cpath)
data = json.loads(conf)
except:pass
return data
def __get_args(self,data,key,val = ''):
"""
@获取默认参数
"""
if not key in data: data[key] = val
if type(data[key]) != type(val):
data[key] = val
return data
def __get_push_list(self,data):
"""
@格式化列表数据
"""
m_data = {}
result = {}
for x in self.__get_msg_list(): m_data[x['name']] = x
for skey in data:
result[skey] = data[skey]
m_list = []
for x in data[skey]['module'].split(','):
if x in m_data: m_list.append(m_data[x]['title'])
result[skey]['m_title'] = ''.join(m_list)
m_cycle =[]
if data[skey]['cycle'] > 1:
m_cycle.append('every {} seconds'.format(data[skey]['cycle']))
m_cycle.append('{} times, with an interval of {} seconds'.format(data[skey]['count'],data[skey]['interval']))
result[skey]['m_cycle'] = ''.join(m_cycle)
# 兼容旧版本没有返回project项,导致前端无法编辑问题
if "project" not in result[skey] and "type" in result[skey]:
if result[skey]["type"] == "services":
services = ['nginx','apache',"pure-ftpd",'mysql','php-fpm','memcached','redis']
_title = result[skey]['title']
for s in services:
if _title.find(s)!=-1:
result[skey]["project"] = s
else:
result[skey]["project"] = result[skey]["type"]
if "project" in result[skey]:
if result[skey]["project"] == "FTP server":
result[skey]["project"] ="pure-ftpd"
return result
#************************************************推送
"""
@推送data/push目录的所有文件
"""
def push_messages_from_file(self):
path = "{}/data/push".format(panelPath)
if not os.path.exists(path): os.makedirs(path)
from panelMessage import panelMessage
pm = panelMessage()
for x in os.listdir(path):
try:
spath = '{}/{}'.format(path,x)
if os.path.isdir(spath): continue
data = json.loads(public.readFile(spath))
msg_obj = pm.init_msg_module(data['module'])
if not msg_obj:continue
ret = msg_obj.push_data(data)
if ret['status']: pass
os.remove(spath)
except :
print(public.get_error_info())
"""
@消息推送线程
"""
def start(self):
total = 0
interval = 5
tips = '{}/data/push/tips'.format(public.get_panel_path())
if not os.path.exists(tips): os.makedirs(tips)
try:
if True:
# 推送文件
self.push_messages_from_file()
# 调用推送子模块
data = {}
is_write = False
path = "{}/class/push/push.json".format(panelPath)
if os.path.exists(path):
data = public.readFile(path)
data = json.loads(data)
p = public.get_modules('class/push')
for skey in data:
if len(data[skey]) <= 0: continue
if skey in ['panelLogin_push','panel_login']: continue #面板登录主动触发
total = None
obj = getattr(p[skey], skey)()
for x in data[skey]:
try:
item = data[skey][x]
item['id'] = x
if not item['status']: continue
if not item['module']: continue
if not 'index' in item: item['index'] = 0
if time.time() - item['index'] < item['interval']:
print('{} Interval not reached, skip.'.format(item['title']))
continue
#验证推送次数
push_record = {}
tips_path = '{}/{}'.format(tips,x)
if 'push_count' in item and item['push_count'] > 0:
item['tips_list'] = []
try:
push_record = json.loads(public.readFile(tips_path))
except:pass
for k in push_record:
if push_record[k] < item['push_count']:
continue
item['tips_list'].append(k)
#获取推送数据
if not total: total = obj.get_total()
rdata = obj.get_push_data(item,total)
if not rdata:
continue
push_status = False
for m_module in item['module'].split(','):
if not m_module in rdata:
continue
msg_obj = public.init_msg(m_module)
if not msg_obj:continue
if 'to_user' in item and m_module in item['to_user']:
rdata[m_module]['to_user'] = item['to_user'][m_module]
ret = msg_obj.push_data(rdata[m_module])
data[skey][x]['index'] = rdata['index']
is_write = True
push_status = True
#获取是否推送成功.
if push_status:
if 'push_keys' in rdata:
for k in rdata['push_keys']:
if not k in push_record: push_record[k] = 0
push_record[k] += 1
public.writeFile(tips_path,json.dumps(push_record))
except :
print(public.get_error_info())
if is_write:
public.writeFile(path,json.dumps(data))
#time.sleep(interval)
except :
print(public.get_error_info())
def __get_login_panel_info(self):
"""
@name 获取面板登录列表
@auther cjxin
@date 2022-09-29
"""
import config
c_obj = config.config()
send_type = c_obj.get_login_send(None)['msg']
if not send_type:
return False
return {"type":"panel_login","module":send_type,"interval":600,"status":True,"title":"Panel Login Alert","cycle":1,"count":1,"key":"","module_type":'site_push'}
def __get_ssh_login_info(self):
"""
@name 获取SSH登录列表
@auther cjxin
@date 2022-09-29
"""
import ssh_security
c_obj = ssh_security.ssh_security()
send_type = c_obj.get_login_send(None)['msg']
if not send_type or send_type in ['error']:
return False
return {"type":"ssh_login","module":send_type,"interval":600,"status":True,"title":"SSH login warning","cycle":1,"count":1,"key":"","module_type":'site_push'}
def get_push_list(self,get):
"""
@获取所有推送列表
"""
conf = self._get_conf()
for key in conf.keys():
for x in conf[key]:
data = conf[key][x]
data['module_type'] = key
conf[key][x] = self.get_push_user(data)
if not 'site_push' in conf: conf['site_push'] = {}
data = conf['site_push']
for skey in ['panel_login','ssh_login']:
info = None
if skey in data:
del data[skey]
if skey in ['panel_login']:
info = self.__get_login_panel_info()
elif skey in ['ssh_login']:
info = self.__get_ssh_login_info()
if info:
data[skey] = info
conf['site_push'] = data
return conf
def get_push_logs(self,get):
"""
@name 获取推送日志
"""
p = 1
limit = 15
if 'p' in get: p = get.p
if 'limit' in get: limit = get.limit
where = "type = 'Alarm notification'"
sql = public.M('logs')
if hasattr(get, 'search'):
where = " and logs like '%{search}%' ".format(search=get.search)
count = sql.where(where,()).count()
data = public.get_page(count,int(p),int(limit))
data['data'] = public.M('logs').where(where,()).limit('{},{}'.format(data['shift'], data['row'])).order('id desc').select()
return data
# 兼容旧版本的告警
def update_config(self, config):
if "site_push" not in config:
config["site_push"] = {}
if "panel_push" in config:
for k, v in config["panel_push"].items():
if v["type"] != "endtime":
config["site_push"][k] = v
if "push_count" not in v:
v["push_count"] = 1 if v["type"] == "ssl" else 0
del config["panel_push"]
public.writeFile(self.__conf_path, json.dumps(config))
if __name__ == '__main__':
panelPush().start()
+284 -126
View File
@@ -16,6 +16,7 @@ class panelRedirect:
setupPath = '/www/server'
__redirectfile = "/www/server/panel/data/redirect.conf"
__firsturl=""
#匹配目标URL的域名并返回
def GetToDomain(self,tourl):
@@ -64,26 +65,29 @@ class panelRedirect:
if repeat:
return repeat
# 检测URL是否可以访问
def __CheckRedirectUrl(self, get):
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.settimeout(0.5)
rep = "(https?)://([\w\.]+):?([\d]+)?"
h = re.search(rep, get.tourl).group(1)
d = re.search(rep, get.tourl).group(2)
try:
p = re.search(rep, get.tourl).group(3)
except:
p = ""
try:
if p:
sk.connect((d, int(p)))
else:
if h == "http":
sk.connect((d, 80))
else:
sk.connect((d, 443))
except:
return public.return_msg_gettext(False, 'Can NOT get target URL')
def __CheckRedirectUrl(self, domainlist):
"""
@name 检测URL是否可以访问
@author: hezhihong
@param domainlist: 域名列表
"""
http_list=[]
import requests
for i in domainlist:
i = i.replace("*.", "")
https_url = "https://" + i
http_url = "http://" + i
try:
response=requests.get(https_url,timeout=20)
if response.status_code==200:return https_url
except:pass
try:
response=requests.get(http_url,timeout=20)
if response.status_code==200:http_list.append(http_url)
except:pass
if http_list:return http_list[0]
else:return []
# 计算proxyname md5
def __calc_md5(self,redirectname):
import hashlib
@@ -158,52 +162,68 @@ class panelRedirect:
else:
if len(get.redirectname.encode("utf-8")) < 3 or len(get.redirectname.encode("utf-8")) > 15:
return public.return_msg_gettext(False, 'Database name cannot be more than 16 characters!')
if self.__CheckRedirect(get.sitename,get.redirectname):
if 'errorpage' in get:is_error_page = True
else:is_error_page = False
if self.__CheckRedirect(get.sitename,get.redirectname,is_error_page):
return public.return_msg_gettext(False, 'Specified redirect name already exists')
#检测是否选择域名
if get.domainorpath == "domain":
if not json.loads(get.redirectdomain):
return public.return_msg_gettext(False, 'Please select redirected domain')
else:
if not get.redirectpath:
return public.return_msg_gettext(False, 'Please enter redirected path')
#repte = "[\?\=\[\]\)\(\*\&\^\%\$\#\@\!\~\`{\}\>\<\,\',\"]+"
# 检测路径格式
if "/" not in get.redirectpath:
return public.return_msg_gettext(False, 'Path format is incorrect, the format is /xxx')
#if re.search(repte, get.redirectpath):
# return public.return_msg_gettext(False, "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',\"]")
#检测域名是否已经存在配置文件
repeatdomain = self.__CheckRepeatDomain(get,action)
if repeatdomain:
return public.return_msg_gettext(False, 'Redirected domain already exists {}' , (repeatdomain,))
#检测路径是否有存在配置文件
repeatpath = self.__CheckRepeatPath(get)
if repeatpath:
return public.return_msg_gettext(False, 'Redirected domain already exists {}' , (repeatpath,))
#检测目标URL格式
rep = "http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?"
if not re.match(rep, get.tourl):
return public.return_msg_gettext(False, 'The target URL format is incorrect {}' ,(get.tourl,))
#检测目标URL是否可用
#if self.__CheckRedirectUrl(get):
# return public.return_msg_gettext(False, '目标URL无法访问')
if 'tourl' in get and not re.match(rep, get.tourl):
return public.returnMsg(False, 'Target URL format is wrong %s' + get.tourl)
#检查目标URL的域名和被重定向的域名是否一样
if get.domainorpath == "domain":
for d in json.loads(get.redirectdomain):
tu = self.GetToDomain(get.tourl)
if d == tu:
return public.return_msg_gettext(False,public.get_msg_gettext('Domain name {} is the same as the target domain name, please deselect it',(d,)))
#非404页面重定向检测项
if 'errorpage' not in get:
#检测是否选择域名
if get.domainorpath == "domain":
if not json.loads(get.redirectdomain):
return public.return_msg_gettext(False, 'Please select redirected domain')
else:
if not get.redirectpath:
return public.return_msg_gettext(False, 'Please enter redirected path')
#repte = "[\?\=\[\]\)\(\*\&\^\%\$\#\@\!\~\`{\}\>\<\,\',\"]+"
# 检测路径格式
if "/" not in get.redirectpath:
return public.return_msg_gettext(False, 'Path format is incorrect, the format is /xxx')
#if re.search(repte, get.redirectpath):
# return public.return_msg_gettext(False, "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',\"]")
#检测域名是否已经存在配置文件
repeatdomain = self.__CheckRepeatDomain(get,action)
if repeatdomain:
return public.return_msg_gettext(False, 'Redirected domain already exists {}' , (repeatdomain,))
#检测路径是否有存在配置文件
repeatpath = self.__CheckRepeatPath(get)
if repeatpath:
return public.return_msg_gettext(False, 'Redirected domain already exists {}' , (repeatpath,))
#检测目标URL是否可用
#if self.__CheckRedirectUrl(get):
# return public.return_msg_gettext(False, '目标URL无法访问')
#检查目标URL的域名和被重定向的域名是否一样
if get.domainorpath == "domain":
for d in json.loads(get.redirectdomain):
tu = self.GetToDomain(get.tourl)
if d == tu:
return public.return_msg_gettext(False,public.get_msg_gettext('Domain name {} is the same as the target domain name, please deselect it',(d,)))
if get.domainorpath == "path":
domains = self.GetAllDomain(get.sitename)
rep = "https?://(.*)"
tu = re.search(rep,get.tourl).group(1)
for d in domains:
ad = "%s%s" % (d,get.redirectpath) #站点域名+重定向路径
if tu == ad:
return public.get_msg_gettext('{}, the target URL is the same as the redirected path',(tu,))
#404页面重定向检测项
else:
if 'tourl' not in get and 'topath' not in get:
return public.returnMsg(False, 'Please select where you need to redirect to')
#网站首页访问检测
if 'topath' in get and get.topath == "/":
domainlist=self.GetAllDomain(get.sitename)
self.__firsturl=self.__CheckRedirectUrl(domainlist)
if not self.__firsturl:return public.returnMsg(False, 'The website cannot be accessed, please check whether the website is working properly')
if get.domainorpath == "path":
domains = self.GetAllDomain(get.sitename)
rep = "https?://(.*)"
tu = re.search(rep,get.tourl).group(1)
for d in domains:
ad = "%s%s" % (d,get.redirectpath) #站点域名+重定向路径
if tu == ad:
return public.get_msg_gettext('{}, the target URL is the same as the redirected path',(tu,))
#创建重定向
def CreateRedirect(self,get):
@@ -228,6 +248,197 @@ class panelRedirect:
public.serviceReload()
return public.return_msg_gettext(True, 'Successfully created file!')
def ModifyRedirect(self,get):
"""
@name 修改启用禁用重定向
@author hezhihong
@param get.sitename 站点名称
@param get.redirectname 重定向名称
@param get.tourl 目标URL
@param get.redirectdomain 重定向域名
@param get.redirectpath 重定向路径
@param get.redirecttype 重定向类型
@param get.type 重定向状态 0禁用 1启用
@param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向
@param get.holdpath 保留路径 0不保留 1保留
@return json
"""
# 基本信息检查
if self.__CheckRedirectStart(get):
return self.__CheckRedirectStart(get)
redirectconf = self.__read_config(self.__redirectfile)
for i in range(len(redirectconf)):
domainorpath=''
if 'domainorpath' not in get or not get.domainorpath:domainorpath='domain' if get.tourl else 'path'
if not domainorpath:domainorpath=get.domainorpath
if redirectconf[i]["redirectname"] == get.redirectname and redirectconf[i]["sitename"] == get.sitename:
redirectconf[i]["tourl"] =get.tourl if 'tourl' in get and get.tourl else ""
redirectconf[i]["redirectdomain"] = "" if 'redirectdomain' not in get else json.loads(get.redirectdomain)
redirectconf[i]["redirectpath"] ="" if 'redirectpath' not in get else get.redirectpath
redirectconf[i]["redirecttype"] ='' if 'redirecttype' not in get else get.redirecttype
redirectconf[i]["type"] = int(get.type)
redirectconf[i]["domainorpath"] = domainorpath
redirectconf[i]["topath"] = "" if 'topath' not in get else get.topath
redirectconf[i]["holdpath"] =999 if 'holdpath' not in get else int(get.holdpath)
redirectconf[i]["errorpage"]=1 if 'errorpage' in get and get.errorpage in [1,'1'] else 0
self.__write_config(self.__redirectfile, redirectconf)
redirect_path=get.tourl.strip() if 'tourl' in get and get.tourl else get.topath.strip()
#404页面重定向
is_del= True if int(get.type) == 0 else False
if 'errorpage' in get and get.errorpage in [1,'1']:
web_type=public.get_webserver()
if web_type == 'nginx':
self.SetRedirectNginx(get)
self.unset_nginx_conf(get.sitename)
self.get_nginx_conf(redirect_path,get.redirecttype,get.sitename,get.redirectname,is_del)
elif web_type == 'apache' or web_type == 'openlitespeed':
self.get_apache_conf(redirect_path,get.sitename,get.redirectname,str(get.redirecttype),is_del)
else:
return public.returnMsg(False,'web server not installed or unknown web server')
#非404页面重定向
else:
self.SetRedirect(get)
self.SetRedirectNginx(get)
self.SetRedirectApache(get.sitename)
public.serviceReload()
return public.returnMsg(True, 'Successfully modified')
def set_error_redirect(self,get):
"""
@name 设置404重定向
@author hezhihong
@param get.sitename 站点名称
@param get.redirectname 重定向名称(唯一key标志)
@param get.tourl 重定向到的url
@param get.topath 重定向到的路径
@param get.redirecttype 重定向类型
@param get.type 重定向状态 0禁用 1启用
@param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向
@param get.holdpath 是否保留原路径 0不保留 1保留
@param get.errorpage 是否为404重定向 1 0
@return json
"""
public.set_module_logs('panelRedirect','set_error_redirect')
check_result = self.__CheckRedirectStart(get,"create")
if check_result:return check_result
redirectconf = self.__read_config(self.__redirectfile)
site_name= get.sitename.strip()
redirect_path=get.tourl if 'tourl' in get and get.tourl and get.tourl.strip() else get.topath.strip()
redirectconf.append({
"sitename":site_name,
"redirectname":get.redirectname,
"tourl":get.tourl if 'tourl' in get else '',
"redirectdomain":"",
"redirectpath":"",
"topath": get.topath.strip() if 'topath' in get and get.topath.strip() else "",
"redirecttype":get.redirecttype,
"type":int(get.type),
"domainorpath":'domain' if 'tourl' in get else 'path',
"holdpath":999,
"errorpage":1
})
self.__write_config(self.__redirectfile,redirectconf)
web_type=public.get_webserver()
if web_type == 'nginx':
self.SetRedirectNginx(get)
self.unset_nginx_conf(site_name)
self.get_nginx_conf(redirect_path,get.redirecttype,site_name,get.redirectname)
elif web_type == 'apache' or web_type == 'openlitespeed':
self.SetRedirectApache(get.sitename)
self.get_apache_conf(redirect_path,site_name,get.redirectname,str(get.redirecttype))
else:
return public.returnMsg(False,'web server not installed or unknown web server')
public.serviceReload()
return public.returnMsg(True, '404 redirect set successfully')
def get_nginx_conf(self,redirect_path,redirecttype,site_name,redirectname,is_del=False):
"""
@name 设置nginx 404重定向
@author hezhihong
@param redirect_path 重定向到路径或地址
@param redirecttype 重定向方式301/302
@param site_name 站点名称
@param redirectname 重定向名称唯一key标志
@param is_del 是否删除
"""
redirectname_md5 = self.__calc_md5(redirectname)
file_path= "%s/panel/vhost/nginx/redirect/%s" % (self.setupPath,site_name)
public.ExecShell("mkdir -p %s" % file_path)
file_path+= '/%s_%s.conf' % (redirectname_md5, site_name)
add_str='#REWRITE-START\nerror_page 404 = @notfound;\nlocation @notfound {\n return '+str(redirecttype)+' '+ redirect_path+'; \n}\n#REWRITE-END'
if os.path.isfile(file_path):public.ExecShell("rm -f %s" % file_path)
if not is_del:public.writeFile(file_path,add_str)
def get_apache_conf(self,redirect_path,site_name,redirectname='',r_type='301',is_del=False):
"""
@name 设置apache 404重定向
@author hezhihong
@param redirect_path 重定向到路径或地址
@param site_name 站点名称
@param redirectname 重定向名称唯一key标志
@param is_del 是否删除
@param r_type 重定向方式
"""
if self.__firsturl:redirect_path=self.__firsturl
add_type=',R={}]'.format(str(r_type))
add_str='#REWRITE-START\n<IfModule mod_rewrite.c>\n RewriteEngine on\n RewriteCond %\{REQUEST_FILENAME\} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . '+redirect_path+' [L'+add_type+'\n</IfModule>\n#REWRITE-END'
redirectname_md5 = self.__calc_md5(redirectname)
file_path= "%s/panel/vhost/apache/redirect/%s" % (self.setupPath,site_name)
public.ExecShell("mkdir -p %s" % file_path)
file_path+= '/%s_%s.conf' % (redirectname_md5, site_name)
if os.path.isfile(file_path):public.ExecShell("rm -f %s" % file_path)
if not is_del:public.writeFile(file_path,add_str)
def unset_nginx_conf(self,site_name):
"""
@name 取消设置nginx 404重定向
@author hezhihong
@param site_name 站点名称
"""
file_path='/www/server/panel/vhost/nginx/{}.conf'.format(site_name)
hta_path='/www/server/panel/vhost/rewrite/{}.conf'.format(site_name)
rep_str_one='error_page 404 /404.html'
rep_str_two='location = /404.html'
#清理nginx伪静态404配置
hta_conf=public.readFile(hta_path)
if hta_conf:
hta_conf=self.replace_str_to_srt(hta_conf,rep_str_one,'','\n')
hta_conf=self.replace_str_to_srt(hta_conf,rep_str_two,'','}')
public.writeFile(hta_path,hta_conf)
#清理nginx网站配置文件非include方式404配置
conf=public.readFile(file_path)
conf=self.replace_str_to_srt(conf,rep_str_one,'','\n')
conf=self.replace_str_to_srt(conf,rep_str_two,'','}')
public.writeFile(file_path,conf)
def replace_str_to_srt(self,conf,str_src,str_d,end_str,is_replace=False):
"""
@name 替换字符串
@author hezhihong
@param conf 配置文件内容
@param str_src 要替换的字符串
@param str_d 替换成的字符串
@param end_str 结束字符串
@param is_replace 是否替换
"""
if conf.strip():
start_num=conf.find(str_src)
if start_num !=-1:
d_conf=conf[start_num:]
end_num = d_conf.find(end_str)
if end_num ==-1:end_num=len(conf)
d_conf=d_conf[:end_num+1]
if is_replace:conf=conf.replace(d_conf,str_d)
else:conf=conf.replace(d_conf,'')
return conf
# 设置重定向
def SetRedirect(self,get):
ng_file = self.setupPath + "/panel/vhost/nginx/" + get.sitename + ".conf"
@@ -341,28 +552,6 @@ class panelRedirect:
if os.path.exists(rf):
os.remove(rf)
def ModifyRedirect(self,get):
# 基本信息检查
if self.__CheckRedirectStart(get):
return self.__CheckRedirectStart(get)
redirectconf = self.__read_config(self.__redirectfile)
for i in range(len(redirectconf)):
if redirectconf[i]["redirectname"] == get.redirectname and redirectconf[i]["sitename"] == get.sitename:
redirectconf[i]["tourl"] = get.tourl
redirectconf[i]["redirectdomain"] = json.loads(get.redirectdomain)
redirectconf[i]["redirectpath"] = get.redirectpath
redirectconf[i]["redirecttype"] = get.redirecttype
redirectconf[i]["type"] = int(get.type)
redirectconf[i]["domainorpath"] = get.domainorpath
redirectconf[i]["holdpath"] = int(get.holdpath)
self.__write_config(self.__redirectfile, redirectconf)
self.SetRedirect(get)
self.SetRedirectNginx(get)
self.SetRedirectApache(get.sitename)
if not hasattr(get,'notreload'):
public.serviceReload()
return public.return_msg_gettext(True, 'Setup successfully!')
def del_redirect_multiple(self,get):
'''
@name 批量删除重定向
@@ -407,53 +596,20 @@ class panelRedirect:
return public.return_msg_gettext(True, 'Successfully deleted')
def GetRedirectList(self,get):
"""
@name 获取重定向列表
@author hezhihong
@param get.sitename 站点名
@param get.errorpage 1:404页面重定向 0:非404页面重定向
@return 重定向列表
"""
redirectconf = self.__read_config(self.__redirectfile)
sitename = get.sitename
# conf_path = "%s/panel/vhost/nginx/%s.conf" % (self.setupPath, get.sitename)
# old_conf = public.readFile(conf_path)
# # print (old_conf)
# rep = "#301-START\n+[\s\w\:\/\.\;\$\(\)\'\^\~\{\}]+#301-END"
# url_rep = "return\s(\d+)\s(https?\:\/\/[\w\.]+)\$"
# host_rep = "\$host\s~\s'\^(.*)'"
# if re.search(rep, old_conf):
# # 构造代理配置
# get.host = ""
# if re.search(host_rep, old_conf):
# get.host += str(re.search(host_rep, old_conf).group(1))
# get.redirecttype = str(re.search(url_rep, old_conf).group(1))
# get.tourl = str(re.search(url_rep, old_conf).group(2))
#
# get.redirectpath = ""
# if get.host:
# get.domainorpath = "domain"
# get.redirectdomain = "[\"%s\"]" % get.host
# else:
# get.domainorpath = "path"
# get.redirectpath = "/"
# get.redirectdomain = "[]"
# get.sitename = sitename
# get.redirectname = public.get_msg_gettext('Old configuration')
# get.type = 1
# get.holdpath = 1
# 备份并替换老虚拟主机配置文件
# if not os.path.exists(conf_path + "_bak"):
# public.ExecShell("cp %s %s_bak" % (conf_path, conf_path))
# conf = re.sub(rep, "", old_conf)
# public.writeFile(conf_path, conf)
#self.CreateRedirect(get)
# 写入代理配置
#proxypath = "%s/panel/vhost/%s/proxy/%s/%s_%s.conf" % (
#self.setupPath, w, get.sitename, proxyname_md5, get.sitename)
# proxycontent = str(re.search(rep, old_conf).group(1))
# public.writeFile(proxypath, proxycontent)
#public.serviceReload()
redirectlist = []
for i in redirectconf:
if i["sitename"] == sitename:
if 'errorpage' in get and 'errorpage' in i and int(get.errorpage)!=int(i['errorpage']):continue
if 'errorpage' in i and i['errorpage'] in [1,'1']:i['redirectdomain']=['404 page']
redirectlist.append(i)
print(redirectlist)
return redirectlist
@@ -495,10 +651,12 @@ class panelRedirect:
return f.SaveFileBody(get)
# return public.return_msg_gettext(True, '保存成功')
def __CheckRedirect(self,sitename,redirectname):
def __CheckRedirect(self,sitename,redirectname,is_error=False):
conf_data = self.__read_config(self.__redirectfile)
for i in conf_data:
if i["sitename"] == sitename:
if is_error and "errorpage" in i and i["errorpage"] in [1,'1']:
return i
if i["redirectname"] == redirectname:
return i
+1180 -318
View File
File diff suppressed because it is too large Load Diff
+93
View File
@@ -0,0 +1,93 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# 系统安全管理控制器
#------------------------------
import os,sys,public,json,re
class SafeController:
def __init__(self):
pass
def model(self,args):
'''
@name 调用指定项目模型
@author hwliang<2021-12-31>
@param args<dict_obj> {
mod_name: string<模型名称>
def_name: string<方法名称>
data: JSON
}
'''
try: # 表单验证
if args['mod_name'] in ['base']: return public.return_status_code(1000,'wrong call!')
public.exists_args('def_name,mod_name',args)
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'The called method name cannot contain the "__" character')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
except:
return public.get_error_object()
# 参数处理
module_name = args['mod_name'].strip()
mod_name = "{}Model".format(args['mod_name'].strip())
def_name = args['def_name'].strip()
if not hasattr(args,'data'): args.data = {}
if args.data:
if isinstance(args.data,str):
try: # 解析为dict_obj
pdata = public.to_dict_obj(json.loads(args.data))
except:
return public.get_error_object()
elif isinstance(args.data,dict):
pdata = public.to_dict_obj(args.data)
else:
pdata = args.data
else:
pdata = public.dict_obj()
if isinstance(pdata,dict): pdata = public.to_dict_obj(pdata)
pdata.model_index = 'safe'
# 前置HOOK
hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper())
hook_result = public.exec_hook(hook_index,pdata)
if isinstance(hook_result,public.dict_obj):
pdata = hook_result # 桥接
elif isinstance(hook_result,dict):
return hook_result # 响应具体错误信息
elif isinstance(hook_result,bool):
if not hook_result: # 直接中断操作
return public.return_data(False,{},error_msg='前置HOOK中断操作')
# 调用处理方法
# result = run_object(pdata)
import PluginLoader
result = PluginLoader.module_run(module_name,def_name,pdata)
if isinstance(result,dict):
if 'status' in result and result['status'] == False and 'msg' in result:
if isinstance(result['msg'],str):
if result['msg'].find('Traceback ') != -1:
raise public.PanelError(result['msg'])
# 后置HOOK
hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper())
hook_data = public.to_dict_obj({
'args': pdata,
'result': result
})
hook_result = public.exec_hook(hook_index,hook_data)
if isinstance(hook_result,dict):
result = hook_result['result']
return result
+103 -14
View File
@@ -34,7 +34,7 @@ class panelSearch:
return public.M('panel_search_log').insert(data)
'''目录下所有的文件'''
def get_dir(self, path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False):
def get_dir(self, path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False,get=None):
if rtext or noword:
result=[]
else:
@@ -47,12 +47,17 @@ class panelSearch:
zfile=False
back_zip=False
return_data = []
[[return_data.append(os.path.join(root, file)) for file in files] for root, dirs, files in os.walk(path)]
[[return_data.append(os.path.join(root, file)) for file in files]
for root, dirs, files in os.walk(path)]
num = 0
total_num = len(return_data)
if return_data: public.writeSpeed('files_search', num, total_num)
for i in return_data:
is_send = False
for i2 in exts:
i3 = i.split('.')
if i3[-1] == i2:
is_send = True
temp = self.get_files_lin(i, text, mode, isword, iscase, noword,is_backup,rtext,zfile)
if temp:
if rtext or noword:
@@ -60,6 +65,20 @@ class panelSearch:
result.append(temp)
else:
result[i] = temp
num += 1
public.writeSpeed('files_search', num, total_num)
progress = int(public.getSpeed()['progress'])
if '_ws' in get:
get._ws.send(
public.getJson({
"end": False if progress < 100 else True,
"ws_callback": get.ws_callback,
"file": i if is_send else '',
"progress": progress,
"total": total_num,
"num": num,
"type": "files_search"
}))
if is_backup:
if zfile:
zfile.close()
@@ -68,7 +87,18 @@ class panelSearch:
return result
'''获取单目录'''
def get_dir_files(self,path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False):
def get_dir_files(self,
path,
exts,
text,
mode=0,
isword=0,
iscase=0,
noword=0,
is_backup=0,
rtext=False,
get=None):
is_list = rtext or noword
if is_list:
result=[]
@@ -85,16 +115,37 @@ class panelSearch:
for root, dirs, files in os.walk(path):
list_data=files
break
for i in exts:
for i2 in list_data:
i3=i2.split('.')
if i3[-1]==i:
temp=self.get_files_lin(path+'/'+i2, text,mode,isword,iscase,noword,is_backup,rtext,zfile)
num = 0
total_num = len(list_data)
if list_data: public.writeSpeed('files_search', num, total_num)
for i2 in list_data:
is_send = False
for i in exts:
i3 = i2.split('.')
if i3[-1] == i:
is_send = True
temp = self.get_files_lin(path + '/' + i2, text, mode,
isword, iscase, noword,
is_backup, rtext, zfile)
if temp:
if isinstance(result,list):
result.append(path+'/'+i2)
else:
result[path+'/'+i2]=temp
result[path + '/' + i2] = temp
num += 1
public.writeSpeed('files_search', num, total_num)
progress = int(public.getSpeed()['progress'])
if '_ws' in get:
get._ws.send(
public.getJson({
"end": False if progress < 100 else True,
"ws_callback": get.ws_callback,
"file": i2 if is_send else '',
"progress": progress,
"total": total_num,
"num": num,
"type": "files_search"
}))
if is_backup:
if zfile:
zfile.close()
@@ -103,12 +154,42 @@ class panelSearch:
'''
获取目录下所有的后缀文件
'''
def get_exts_files(self,path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_subdir=0,is_backup=0,rtext=False):
def get_exts_files(self,
path,
exts,
text,
mode=0,
isword=0,
iscase=0,
noword=0,
is_subdir=0,
is_backup=0,
rtext=False,
get=None):
if len(exts)==0:return []
if is_subdir==0:
return self.get_dir_files(path,exts,text,mode,isword,iscase,noword,is_backup,rtext)
return self.get_dir_files(path,
exts,
text,
mode,
isword,
iscase,
noword,
is_backup,
rtext,
get=get)
elif is_subdir==1:
return self.get_dir(path,exts,text,mode,isword,iscase,noword,is_backup,rtext)
return self.get_dir(path,
exts,
text,
mode,
isword,
iscase,
noword,
is_backup,
rtext,
get=get)
'''
获取文件内的关键词
@@ -202,7 +283,15 @@ class panelSearch:
isword = int(args.isword) if 'isword' in args else 0
noword = int(args.noword) if 'noword' in args else 0
exts=exts.split(',')
is_tmpe_files=self.get_exts_files(path,exts,text,mode,isword,iscase,noword,is_subdir)
is_tmpe_files = self.get_exts_files(path,
exts,
text,
mode,
isword,
iscase,
noword,
is_subdir,
get=args)
return is_tmpe_files
'''
+562 -74
View File
File diff suppressed because it is too large Load Diff
+93 -3
View File
@@ -11,7 +11,6 @@
# 消息队列
# ------------------------------
import json
import downloadFile
import time
import public
import sys
@@ -91,6 +90,12 @@ class bt_task:
(task_name, task_type, task_shell, other, int(time.time()), 0))
public.WriteFile(self.__task_tips, 'True')
public.ExecShell("/etc/init.d/bt start")
if not public.M(self.__table).where('status=?', ('-1',)).count():
tip_file = "/dev/shm/.start_task.pl"
tip_time = public.readFile(tip_file)
if not tip_time or time.time() - int(tip_time) > 600:
public.ExecShell("/www/server/panel/BT-Task")
public.print_log("Background task restarted")
return task_id
# 修改任务
@@ -195,9 +200,11 @@ class bt_task:
def start_task(self):
noe = False
n = 0
tip_file = '/dev/shm/.start_task.pl'
while True:
try:
time.sleep(1)
public.writeFile(tip_file, str(int(time.time())))
n += 1
if not os.path.exists(self.__task_tips) and noe and n < 60:
continue
@@ -325,11 +332,20 @@ class bt_task:
self.install_rar()
public.ExecShell("cd '" + path + "' && "+rar_file +
" a -r '" + dfile + "' " + sfiles + " &> " + log_file)
elif z_type == '7z':
_7z_bin = self.get_7z_bin()
if not _7z_bin:
self.install_7zip()
err_msg = 'The p7zip component is not installed, an automatic installation has been attempted, please wait a few minutes and try again!'
public.WriteLog("File manager","Failed to compress file, reason: {}, file: {}".format(err_msg,sfile))
return public.returnMsg(False, err_msg)
public.ExecShell("cd {} && {} a -t7z {} {} -y &> {}".format(path, _7z_bin, dfile, sfiles, log_file))
else:
return public.return_msg_gettext(False,'Specified compression format is not supported!')
self.set_file_accept(dfile)
#public.WriteLog("TYPE_FILE", 'Compression succeeded!', (sfiles, dfile),not_web = self.not_web)
public.write_log_gettext("File manager", 'Compressed file [ {} ] to [ {} ] success', (sfiles, dfile))
return public.return_msg_gettext(True, 'Compression succeeded!')
# 文件解压
@@ -351,17 +367,53 @@ class bt_task:
rar_file = '/www/server/rar/unrar'
if not os.path.exists(rar_file):
self.install_rar()
public.ExecShell('echo "'+password+'"|' + rar_file +
' x -u -y "' + sfile + '" "' + dfile + '" &> ' + log_file)
pass_opt = '-p-'
if password:
password = password.replace("&","\&").replace('"','\"')
pass_opt = '-p"{}"'.format(password)
public.ExecShell(rar_file + ' x '+ pass_opt +' -u -y "' + sfile + '" "' + dfile + '" &> ' + log_file)
elif sfile[-4:] == '.war':
public.ExecShell("unzip -P '"+password+"' -o '" +
sfile + "' -d '" + dfile + "' &> " + log_file)
elif sfile[-4:] == '.bz2':
public.ExecShell("tar jxvf '" + sfile +
"' -C '" + dfile + "' &> " + log_file)
elif sfile[-3:] == '.7z':
_7zbin = self.get_7z_bin()
if not _7zbin:
self.install_7zip()
err_msg = 'The p7zip component is not installed, an automatic installation has been attempted, please wait a few minutes and try again!'
public.WriteLog("File manager","Failed to compress file, reason: {}, file: {}".format(err_msg,sfile))
return public.returnMsg(False, err_msg)
pass_opt = ""
if password:
pass_opt = '-p"{}"'.format(password)
public.ExecShell('{} x "{}" -o"{}" -y {} &> {}'.format(_7zbin,sfile,dfile,pass_opt,log_file))
else:
public.ExecShell("gunzip -c " + sfile + " > " + sfile[:-3])
# 异常处理
log_msg = public.readFile(log_file)
err_msg = None
if log_msg:
if log_msg.find("incorrect password") != -1 \
or log_msg.find("The specified password is incorrect.") != -1 \
or log_msg.find("Data Error in encrypted file. Wrong password") != -1:
err_msg = 'Decompression password error!'
public.WriteLog("File manager","Unzip file failed, reason: {}, file: {}".format(err_msg,sfile))
elif log_msg.find("unsupported compression method 99") != -1:
err_msg = 'Unsupported Zip encryption and compression, only ZIP traditional encryption is supported for ZIP archives!'
public.WriteLog("File manager","Unzip file failed, reason: {}, file: {}".format(err_msg,sfile))
elif log_msg.find("is not RAR archive") != -1:
err_msg = "It is not a rar archive, check whether to modify the file with the extension rar for other compression formats!"
public.WriteLog("File manager","Unzip file failed, reason: {}, file: {}".format(err_msg,sfile))
elif log_msg.find("gzip: stdin") != -1:
public.ExecShell("tar xvf '" + sfile + "' -C '" + dfile + "' &> " + log_file)
if err_msg: return public.returnMsg(False, err_msg)
# 检查是否设置权限
if self.check_dir(dfile):
sites_path = public.M('config').where(
@@ -374,8 +426,46 @@ class bt_task:
public.ExecShell("chown %s:%s %s" % (user, user, dfile))
#public.WriteLog("TYPE_FILE", 'Uncompression succeeded!', (sfile, dfile),not_web = self.not_web)
public.write_log_gettext("File manager", 'unzip file [ {} ] -> [ {} ] success', (sfile, dfile))
return public.return_msg_gettext(True, 'Uncompression succeeded!')
def get_7z_bin(self):
'''
@name 获取7z命令路径
@author hwliang
@return {string} 7z命令路径
'''
_7z_bins = ["/usr/bin/7z","/usr/bin/7za","/usr/bin/7zr"]
for _7z_bin in _7z_bins:
if os.path.exists(_7z_bin):
return _7z_bin
return None
def install_7zip(self):
'''
@name 安装7zip
@author hwliang
@return {bool} True/False
'''
_7z_bin = self.get_7z_bin()
if _7z_bin:
return True
# 是否已经尝试安装过
install_tip = '{}/data/7z_install.pl'.format(public.get_panel_path())
if os.path.exists(install_tip):
return False
if os.path.exists("/usr/bin/apt-get"):
public.ExecShell("nohup apt-get -y install p7zip-full &> /dev/null &")
elif os.path.exists("/usr/bin/yum"):
public.ExecShell("nohup yum -y install p7zip &> /dev/null &")
elif os.path.exists("/usr/bin/dnf"):
public.ExecShell("nohup dnf -y install p7zip &> /dev/null &")
else:
return False
return True
# 备份网站
def backup_site(self, id, log_file):
find = public.M('sites').where(
+1 -1
View File
@@ -4,7 +4,7 @@
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <2020-05-18>
# +-------------------------------------------------------------------
# +-------------------------------------------------------------------
+1488 -48
View File
File diff suppressed because it is too large Load Diff
+260
View File
@@ -0,0 +1,260 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | version :1.0
# +-------------------------------------------------------------------
# | Author: 梁凯强 <1249648969@qq.com>
# +-------------------------------------------------------------------
# | 快速检索
# +--------------------------------------------------------------------
import os,public,re
import zipfile,time,json
import db
class panel_search:
__backup_path = '/www/server/panel/backup/panel_search/'
def __init__(self):
if not os.path.exists(self.__backup_path):
os.makedirs(self.__backup_path)
if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'panel_search_log')).count():
csql = '''CREATE TABLE `panel_search_log` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `rtext` TEXT,`exts` TEXT,`path` TEXT,`mode` TEXT,`isword` TEXT,`iscase` TEXT,`noword` TEXT,`backup_path` TEXT,`time` TEXT)'''
public.M('sqlite_master').execute(csql,())
def dtchg(self, x):
try:
time_local = time.localtime(float(x))
dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local)
return dt
except:
return False
def insert_settings(self, rtext, exts, path, mode, isword,iscase,noword,backup_path):
inser_time = self.dtchg(int(time.time()))
data = {"rtext": rtext, "exts": json.dumps(exts), "path": path, "mode": mode,
"isword": isword, "iscase": iscase,"noword":noword,"backup_path":backup_path,"time":inser_time}
return public.M('panel_search_log').insert(data)
'''目录下所有的文件'''
def get_dir(self, path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False):
if rtext or noword:
result=[]
else:
result={}
if is_backup:
t = time.strftime('%Y%m%d%H%M%S')
back_zip = os.path.join(self.__backup_path, "%s.zip" % t)
zfile = zipfile.ZipFile(back_zip, "w", compression=zipfile.ZIP_DEFLATED)
else:
zfile=False
back_zip=False
return_data = []
[[return_data.append(os.path.join(root, file)) for file in files] for root, dirs, files in os.walk(path)]
for i in return_data:
for i2 in exts:
i3 = i.split('.')
if i3[-1] == i2:
temp = self.get_files_lin(i, text, mode, isword, iscase, noword,is_backup,rtext,zfile)
if temp:
if rtext or noword:
if isinstance(result,list):
result.append(temp)
else:
result[i] = temp
if is_backup:
if zfile:
zfile.close()
self.insert_settings(rtext, exts, path, mode, isword, iscase, noword, back_zip)
return True
return result
'''获取单目录'''
def get_dir_files(self,path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False):
is_list = rtext or noword
if is_list:
result=[]
else:
result={}
if is_backup:
t = time.strftime('%Y%m%d%H%M%S')
back_zip = os.path.join(self.__backup_path, "%s.zip" % t)
zfile = zipfile.ZipFile(back_zip, "w", compression=zipfile.ZIP_DEFLATED)
else:
zfile=False
back_zip=False
list_data=[]
for root, dirs, files in os.walk(path):
list_data=files
break
for i in exts:
for i2 in list_data:
i3=i2.split('.')
if i3[-1]==i:
temp=self.get_files_lin(path+'/'+i2, text,mode,isword,iscase,noword,is_backup,rtext,zfile)
if temp:
if isinstance(result,list):
result.append(path+'/'+i2)
else:
result[path+'/'+i2]=temp
if is_backup:
if zfile:
zfile.close()
self.insert_settings(rtext, exts, path, mode, isword, iscase, noword, back_zip)
return result
'''
获取目录下所有的后缀文件
'''
def get_exts_files(self,path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_subdir=0,is_backup=0,rtext=False):
if len(exts)==0:return []
if is_subdir==0:
return self.get_dir_files(path,exts,text,mode,isword,iscase,noword,is_backup,rtext)
elif is_subdir==1:
return self.get_dir(path,exts,text,mode,isword,iscase,noword,is_backup,rtext)
'''
获取文件内的关键词
'''
def get_files_lin(self, files, text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False,back_zip=False):
if not os.path.exists(files):return False
if os.path.getsize(files) > 1024 * 1024 * 20: return False
#文件替换部分
if rtext:
resutl=[]
try:
fp = open(files, 'r', encoding='UTF-8')
except:
fp = open(files, 'r')
content = fp.read()
fp.close()
if mode==2:
if iscase:
if not re.search(text, content, flags=re.I): return False
content = re.sub(text, rtext, content, flags=re.I)
else:
if not re.search(text, content): return False
content = re.sub(text, rtext, content)
else:
if content.find(text) == -1: return False
content = content.replace(text, rtext)
if is_backup and back_zip:
bf = files.strip('/')
back_zip.write(files, bf)
with open(files, 'w') as f:
f.write(content)
f.close()
return files
else:
#查找部分
if noword:
resutl = []
else:
resutl={}
try:
fp = open(files, 'r', encoding='UTF-8')
except:
fp = open(files, 'r')
i = 0
try:
for line in fp:
i += 1
if mode==1:
if iscase and not re.search(text, line, flags=re.I):
continue
elif not iscase and not re.search(text, line):
continue
else:
if line.find(text) == -1: continue
if noword:
return files
resutl[i]=line
except:
pass
if resutl:
return resutl
return False
'''
text 搜索内容
exts 后缀名 参数例子 php,html
path 目录
is_subdir 0 不包含子目录 1 包含子目录
mode 0 为普通模式 1 为正则模式
isword 1 全词匹配 0 默认
iscase 1 不区分大小写 0 默认
noword 1 不输出行信息 0 默认
'''
def get_search(self, args):
if 'text' not in args or not args.text: return {'error': 'Search information cannot be empty'}
if 'exts' not in args or not args.exts: return {'error': 'The suffix cannot be empty; please enter [*.*] for all files'}
if 'path' not in args or not args.path or args.path == '/': return {'error': 'Directory cannot be empty or cannot be /'}
if not os.path.isdir(args.path): return {'error': 'Directory does not exist'}
text=args.text
exts=args.exts
path=args.path
mode = int(args.mode) if 'mode' in args else 0
is_subdir = int(args.is_subdir) if 'is_subdir' in args else 0
iscase = int(args.iscase) if 'iscase' in args else 0
isword = int(args.isword) if 'isword' in args else 0
noword = int(args.noword) if 'noword' in args else 0
exts=exts.split(',')
is_tmpe_files=self.get_exts_files(path,exts,text,mode,isword,iscase,noword,is_subdir)
return is_tmpe_files
'''
text 搜索内容
rtext 替换成的内容
exts 后缀名 参数例子 php,html
path 目录
is_subdir 0 不包含子目录 1 包含子目录
mode 0 为普通模式 1 为正则模式
isword 1 全词匹配 0 默认
iscase 1 不区分大小写 0 默认
noword 1 不输出行信息 0 默认
'''
def get_replace(self, args):
if 'text' not in args or not args.text: return {'error': 'Search information cannot be empty'}
if 'rtext' not in args or not args.text: return {'error': 'The content to be replaced cannot be empty'}
if 'exts' not in args or not args.exts: return {'error': 'The suffix cannot be empty; please enter [*.*] for all files'}
if 'path' not in args or not args.path or args.path == '/': return {'error': 'Directory cannot be empty or cannot be /'}
if not os.path.isdir(args.path): return {'error': 'Directory does not exist'}
is_backup = int(args.isbackup) if 'isbackup' in args else 0
text = args.text
rtext = args.rtext
exts = args.exts
path = args.path
mode = int(args.mode) if 'mode' in args else 0
is_subdir = int(args.is_subdir) if 'is_subdir' in args else 0
iscase = int(args.iscase) if 'iscase' in args else 0
isword = int(args.isword) if 'isword' in args else 0
noword = int(args.noword) if 'noword' in args else 0
exts = exts.split(',')
is_tmpe_files = self.get_exts_files(path, exts, text, mode, isword, iscase, noword, is_subdir,is_backup,rtext)
return is_tmpe_files
#替換日志
def get_replace_logs(self,get):
import page
page = page.Page()
count = public.M('panel_search_log').order('id desc').count()
limit = 12
info = {}
info['count'] = count
info['row'] = limit
info['p'] = 1
if hasattr(get, 'p'):
info['p'] = int(get['p'])
info['uri'] = get
info['return_js'] = ''
if hasattr(get, 'tojs'):
info['return_js'] = get.tojs
data = {}
data['page'] = page.GetPage(info, '1,2,3,4,5,8')
data['data'] = public.M('panel_search_log').field('id,rtext,exts,path,mode,isword,iscase,noword,backup_path,time').order('id desc').limit(str(page.SHIFT) + ',' + str(page.ROW)).select()
if isinstance(data['data'],str): return public.returnMsg(False,[])
for i in data['data']:
if not isinstance(i,dict): continue
if 'backup_path' in i :
path=i['backup_path']
if os.path.exists(path):
i['is_path_status']=True
else:
i['is_path_status'] = False
return public.returnMsg(True, data)
+8 -6
View File
@@ -51,10 +51,9 @@ class panel_telegram_bot:
return {"setup":False,"bot_token":"","my_id":""}
def process_character(self,content):
character = ['.',',','!',':','%','[',']','\/','_','-','>']
character = ['\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']
for c in character:
if c in content and '\\{}'.format(c) not in content:
content = content.replace(c,'\\'+c)
content = content.replace(c, '\\' + c)
return content
@@ -63,6 +62,9 @@ class panel_telegram_bot:
"parse_mode 消息格式 html/markdown/markdownv2"
content = self.process_character(content)
conf = self.get_tg_conf()
bot = telegram.Bot(conf['bot_token'])
result = bot.send_message(text=content, chat_id=int(conf['my_id']), parse_mode="MarkdownV2")
return result
try:
bot = telegram.Bot(conf['bot_token'])
result = bot.send_message(text=content, chat_id=int(conf['my_id']), parse_mode="MarkdownV2")
return result
except:
return False
+3 -3
View File
@@ -104,7 +104,7 @@ class plugin_deployment:
try:
jsonFile = self.__setupPath + '/deployment_list.json'
if not 'package' in session or not os.path.exists(jsonFile) or hasattr(get,'force'):
downloadUrl = 'https://www.bt.cn/api/panel/get_deplist'
downloadUrl = 'http://www.bt.cn/api/panel/get_deplist'
pdata = public.get_pdata()
tmp = json.loads(public.httpPost(downloadUrl,pdata,3))
if not tmp: return public.returnMsg(False,'Failed to get from the cloud!')
@@ -270,7 +270,7 @@ class plugin_deployment:
#下载文件
if isDownload:
self.WriteLogs(json.dumps({'name':'Downloading file ...','total':0,'used':0,'pre':0,'speed':0}))
if pinfo['versions'][0]['download']: self.DownloadFile('https://www.bt.cn/api/Pluginother/get_file?fname=' + pinfo['versions'][0]['download'], packageZip)
if pinfo['versions'][0]['download']: self.DownloadFile('http://www.bt.cn/api/Pluginother/get_file?fname=' + pinfo['versions'][0]['download'], packageZip)
if not os.path.exists(packageZip): return public.returnMsg(False,'File download failed!' + packageZip)
@@ -471,7 +471,7 @@ class plugin_deployment:
p = panelAuth.panelAuth()
pdata = p.create_serverid(None);
pdata['pid'] = id;
p_url = 'https://www.bt.cn/api/pluginother/create_order_okey'
p_url = 'http://www.bt.cn/api/pluginother/create_order_okey'
public.httpPost(p_url,pdata)
#获取进度
+719
View File
@@ -0,0 +1,719 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------
#--------------------------------
# 进程监控模块
#--------------------------------
from psutil import cpu_count,pids,Process,cpu_times
from json import dumps
import os
import sys
os.chdir("/www/server/panel")
sys.path.insert(0,"class/")
import db
import time
import struct
import copy
import threading
import public
from cachelib import SimpleCache
class process_network_total:
__pid_file = 'logs/process_network_total.pid'
__inode_list = {}
__net_process_list = {}
__net_process_size = {}
__last_stat = 0
__last_write_time = 0
__last_check_time = 0
__tip_file = 'data/is_net_task.pl'
__all_tip = 'data/control.conf'
def install_pcap(self):
'''
@name 安装pcap模块依赖包
@author hwliang
@return void
'''
# 标记只安装一次
tip_file= '{}/data/install_pcap.pl'.format(public.get_panel_path())
if os.path.exists(tip_file): return
if os.path.exists('/usr/bin/apt'):
os.system("apt install libpcap-dev -y")
elif os.path.exists('/usr/bin/dnf'):
red_file = '/etc/redhat-release'
if os.path.exists(red_file):
f = open(red_file,'r')
red_body = f.read()
f.close()
if red_body.find('CentOS Linux release 8.') != -1:
rpm_file = '/root/libpcap-1.9.1.rpm'
down_url = "wget -O {} https://node.aapanel.com/src/libpcap-devel-1.9.1-5.el8.x86_64.rpm --no-check-certificate -T 10".format(
rpm_file)
if os.path.exists(rpm_file):
os.system(down_url)
os.system("rpm -ivh {}".format(rpm_file))
if os.path.exists(rpm_file): os.remove(rpm_file)
else:
os.system("dnf install libpcap-devel -y")
else:
os.system("dnf install libpcap-devel -y")
elif os.path.exists('/usr/bin/yum'):
os.system("yum install libpcap-devel -y")
os.system("btpip install pypcap")
# 写入标记文件
public.writeFile(tip_file, 'True')
def start(self):
'''
@name 启动进程网络监控
@author hwliang<2021-09-13>
@return void
'''
try:
import pcap
except ImportError:
try:
self.install_pcap()
import pcap
except ImportError:
print("pypcap module install failed.")
return
try:
p = pcap.pcap() # 监听所有网卡
p.setfilter('tcp') # 只监听TCP数据包
for p_time,p_data in p:
# 检查是否停止
if p_time - self.__last_check_time > 10:
if not os.path.exists(self.__tip_file) or not os.path.exists(self.__all_tip): break
# 处理数据包
self.handle_packet(p_data)
except:
pass
def handle_packet(self, pcap_data):
'''
@name 处理pcap数据包
@author hwliang<2021-09-12>
@param pcap_data<bytes> pcap数据包
@return void
'''
# 获取IP协议头
ip_header = pcap_data[14:34]
# 解析src/dst地址
src_ip = ip_header[12:16]
dst_ip = ip_header[16:20]
# 解析sport/dport端口
src_port = pcap_data[34:36]
dst_port = pcap_data[36:38]
src = src_ip + b':' + src_port
dst = dst_ip + b':' + dst_port
# 计算数据包长度
pack_size = len(pcap_data)
# 统计进程流量
self.total_net_process(dst,src,pack_size)
def total_net_process(self,dst,src,pack_size):
'''
@name 统计进程流量
@author hwliang<2021-09-13>
@param dst<bytes> 目标地址
@param src<bytes> 源地址
@param pack_size<int> 数据包长度
@return void
'''
self.get_tcp_stat()
direction = None
mtime = time.time()
if dst in self.__net_process_list:
pid = self.__net_process_list[dst]
direction = 'down'
elif src in self.__net_process_list:
pid = self.__net_process_list[src]
direction = 'up'
else:
if mtime - self.__last_stat > 3:
self.__last_stat = mtime
self.get_tcp_stat(True)
if dst in self.__net_process_list:
pid = self.__net_process_list[dst]
direction = 'down'
elif src in self.__net_process_list:
pid = self.__net_process_list[src]
direction = 'up'
if not direction: return False
if not pid: return False
if not pid in self.__net_process_size:
self.__net_process_size[pid] = {}
self.__net_process_size[pid]['down'] = 0
self.__net_process_size[pid]['up'] = 0
self.__net_process_size[pid]['up_package'] = 0
self.__net_process_size[pid]['down_package'] = 0
self.__net_process_size[pid][direction] += pack_size
self.__net_process_size[pid][direction + '_package'] += 1
# 写入到文件
if mtime - self.__last_write_time > 1:
self.__last_write_time = mtime
self.write_net_process()
def write_net_process(self):
'''
@name 写入进程流量
@author hwliang<2021-09-13>
@return void
'''
w_file = '/dev/shm/bt_net_process'
process_size = copy.deepcopy(self.__net_process_size)
net_process = []
for pid in process_size.keys():
net_process.append(str(pid) + " " + str(process_size[pid]['down']) + " " + str(process_size[pid]['up']) + " " + str(process_size[pid]['down_package']) + " " + str(process_size[pid]['up_package']))
f = open(w_file,'w+',encoding='utf-8')
f.write('\n'.join(net_process))
f.close()
def hex_to_ip(self, hex_ip):
'''
@name 将16进制的IP地址转换为字符串IP地址
@author hwliang<2021-09-13>
@param hex_ip<string> 16进制的IP地址:16进程端口
@return tuple(ip<str>,port<int>) IP地址,端口
'''
hex_ip,hex_port = hex_ip.split(':')
ip = '.'.join([str(int(hex_ip[i:i+2], 16)) for i in range(0, len(hex_ip), 2)][::-1])
port = int(hex_port, 16)
return ip,port
def get_tcp_stat(self,force = False):
'''
@name 获取当前TCP连接状态表
@author hwliang<2021-09-13>
@param force<bool> 是否强制刷新
@return dict
'''
if not force and self.__net_process_list: return self.__net_process_list
self.__net_process_list = {}
tcp_stat_file = '/proc/net/tcp'
tcp_stat = open(tcp_stat_file, 'rb')
tcp_stat_list = tcp_stat.read().decode('utf-8').split('\n')
tcp_stat.close()
tcp_stat_list = tcp_stat_list[1:]
if force: self.get_process_inodes(force)
for i in tcp_stat_list:
tcp_tmp = i.split()
if len(tcp_tmp) < 10: continue
inode = tcp_tmp[9]
if inode == '0': continue
local_ip,local_port = self.hex_to_ip(tcp_tmp[1])
if local_ip == '127.0.0.1': continue
remote_ip,remote_port = self.hex_to_ip(tcp_tmp[2])
if local_ip == remote_ip: continue
if remote_ip == '0.0.0.0': continue
pid = self.inode_to_pid(inode,force)
if not pid: continue
key = self.get_ip_pack(local_ip) + b':' + self.get_port_pack(local_port)
self.__net_process_list[key] = pid
return self.__net_process_list
def get_port_pack(self,port):
'''
@name 将端口转换为字节流
@author hwliang<2021-09-13>
@param port<int> 端口
@return bytes
'''
return struct.pack('H',int(port))[::-1]
def get_ip_pack(self,ip):
'''
@name 将IP地址转换为字节流
@author hwliang<2021-09-13>
@param ip<str> IP地址
@return bytes
'''
ip_arr = ip.split('.')
ip_pack = b''
for i in ip_arr:
ip_pack += struct.pack('B',int(i))
return ip_pack
def inode_to_pid(self,inode,force = False):
'''
@name 将inode转换为进程ID
@author hwliang<2021-09-13>
@param inode<string> inode
@param force<bool> 是否强制刷新
@return int
'''
inode_list = self.get_process_inodes()
if inode in inode_list:
return inode_list[inode]
return None
def get_process_inodes(self,force = False):
'''
@name 获取进程inode列表
@author hwliang<2021-09-13>
@param force<bool> 是否强制刷新
@return dict
'''
if not force and self.__inode_list: return self.__inode_list
proc_path = '/proc'
inode_list = {}
for pid in os.listdir(proc_path):
try:
if not pid.isdigit(): continue
inode_path = proc_path + '/' + pid + '/fd'
for fd in os.listdir(inode_path):
try:
fd_file = inode_path + '/' + fd
fd_link = os.readlink(fd_file)
if fd_link.startswith('socket:['):
inode = fd_link[8:-1]
inode_list[inode] = pid
except:
continue
except:
continue
self.__inode_list = inode_list
return inode_list
def get_process_name(self,pid):
'''
@name 获取进程名称
@author hwliang<2021-09-13>
@param pid<str> 进程ID
@return str
'''
pid_path = '/proc/' + pid + '/comm'
if not os.path.exists(pid_path): return ''
pid_file = open(pid_path, 'rb')
pid_name = pid_file.read().decode('utf-8').strip()
pid_file.close()
return pid_name
def write_pid(self):
'''
@name 写入进程ID到PID文件
@author hwliang<2021-09-13>
@return void
'''
self_pid = os.getpid()
pid_file = open(self.__pid_file,'w')
pid_file.write(str(self_pid))
pid_file.close()
def rm_pid_file(self):
'''
@name 删除进程pid文件
@author hwliang<2021-09-13>
@return void
'''
if os.path.exists(self.__pid_file):
os.remove(self.__pid_file)
class process_task:
__pids = []
__last_times = {}
__last_dates = {}
__write_last = {}
__write_dates = {}
__read_last = {}
__read_dates = {}
__cpu_count = cpu_count()
__cache = SimpleCache(5000)
last_net_process = {}
last_net_process_time = 0
__process_net_list = {}
__process_object = {}
__insert_time = 0
__last_cpu_time = 0
old_key = 'old_cpu_times'
new_key = 'new_cpu_times'
new_info = {}
old_info = {}
def __init__(self):
tip_file = '{}/data/process_index.pl'.format(public.get_panel_path())
if not public.M('sqlite_master').dbfile('system').where(
'type=? AND name=?', ('table', 'process_top_list')).count():
public.ExecShell('rm -f {}'.format(tip_file))
if not os.path.isfile(tip_file):
_sql = db.Sql().dbfile('system')
csql = '''CREATE TABLE IF NOT EXISTS `process_top_list` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`cpu_top` REAL,
`memory_top` REAL,
`disk_top` REAL,
`net_top` REAL,
`all_top` REAL,
`addtime` INTEGER
)'''
_sql.execute(csql, ())
_sql.execute('CREATE INDEX `addtime` ON `process_top_list` (`addtime`)', ())
_sql.close()
public.writeFile(tip_file,'True')
def get_pids(self):
'''
@name 获取pid列表
@author hwliang<2021-09-04>
@return None
'''
self.__pids = pids()
def get_cpu_time(self):
s = cpu_times()
return s.user + s.system + s.nice + s.idle
def get_old(self):
if self.old_info: return True
data = self.__cache.get(self.old_key)
if not data: return False
if not data: return False
self.old_info = data
del(data)
return True
# def get_cpu_percent(self,pid,cpu_times,cpu_time):
# self.get_old()
# percent = 0.00
# process_cpu_time = self.get_process_cpu_time(cpu_times)
# if not self.old_info: self.old_info = {}
# if not pid in self.old_info:
# self.new_info[pid] = {}
# self.new_info[pid]['cpu_time'] = process_cpu_time
# return percent
# percent = round(100.00 * (process_cpu_time - self.old_info[pid]['cpu_time']) / (cpu_time - self.old_info['cpu_time']),2)
# self.new_info[pid] = {}
# self.new_info[pid]['cpu_time'] = process_cpu_time
# if percent > 0: return percent
# return 0.00
def get_process_cpu_time(self,cpu_times):
cpu_time = 0.00
for s in cpu_times: cpu_time += s
return cpu_time
def get_cpu_percent(self,pid,cpu_time_total,s_cpu_times):
'''
@name 获取pid的cpu占用率
@author hwliang<2021-09-04>
@param pid 进程id
@param cpu_time_total 进程总cpu时间
@return 占用cpu百分比
'''
stime = time.time()
if pid in self.__last_times:
old_time = self.__last_times[pid]
else:
self.__last_times[pid] = cpu_time_total
self.__last_dates[pid] = stime
return 0
cpu_percent = round(100.00 * float(cpu_time_total - old_time) / (s_cpu_times - self.__last_cpu_time),2)
self.__last_times[pid] = cpu_time_total
self.__last_dates[pid] = stime
if cpu_percent > 100: cpu_percent = 99
if cpu_percent < 0: cpu_percent = 0
return cpu_percent
def get_io_write(self,pid,io_write):
disk_io_write = 0
stime = time.time()
if pid in self.__write_last:
old_write = self.__write_last[pid]
else:
self.__write_last[pid] = io_write
self.__write_dates[pid] = stime
return disk_io_write
io_end = (io_write - old_write)
if io_end > 0:
disk_io_write = int(io_end / (stime - self.__write_dates[pid]))
self.__write_last[pid] = io_write
self.__write_dates[pid] = stime
if disk_io_write < 0: disk_io_write = 0
return disk_io_write
def get_io_read(self,pid,io_read):
disk_io_read = 0
stime = time.time()
if pid in self.__read_last:
old_read = self.__read_last[pid]
else:
self.__read_last[pid] = io_read
self.__read_dates[pid] = stime
return disk_io_read
io_end = (io_read - old_read)
if io_end > 0:
disk_io_read = int(io_end / (stime - self.__read_dates[pid]))
self.__read_last[pid] = io_read
self.__read_dates[pid] = stime
if disk_io_read < 0: disk_io_read = 0
return disk_io_read
def read_file(self,filename):
f = open(filename,'rb')
result = f.read()
f.close()
return result.decode().replace("\u0000"," ").strip()
def get_process_net_list(self):
w_file = '/dev/shm/bt_net_process'
if not os.path.exists(w_file): return
self.last_net_process = self.__cache.get('net_process')
self.last_net_process_time = self.__cache.get('last_net_process')
net_process_body = self.read_file(w_file)
if not net_process_body: return
net_process = net_process_body.split('\n')
for np in net_process:
if not np: continue
tmp = {}
np_list = np.split()
if len(np_list) < 5: continue
tmp['pid'] = int(np_list[0])
tmp['down'] = int(np_list[1])
tmp['up'] = int(np_list[2])
tmp['down_package'] = int(np_list[3])
tmp['up_package'] = int(np_list[4])
self.__process_net_list[str(tmp['pid'])] = tmp
self.__cache.set('net_process',self.__process_net_list,600)
self.__cache.set('last_net_process',time.time(),600)
def get_process_network(self,pid):
'''
@name 获取进程网络流量
@author hwliang<2021-09-13>
@param pid<int> 进程ID
@return tuple
'''
if not self.__process_net_list:
self.get_process_net_list()
if not self.last_net_process_time:
return 0,0,0,0
if not pid in self.__process_net_list.keys():
return 0,0,0,0
if not pid in self.last_net_process:
return self.__process_net_list[pid]['up'],self.__process_net_list[pid]['up_package'],self.__process_net_list[pid]['down'],self.__process_net_list[pid]['down_package']
up = int((self.__process_net_list[pid]['up'] - self.last_net_process[pid]['up']) / (time.time() - self.last_net_process_time))
down = int((self.__process_net_list[pid]['down'] - self.last_net_process[pid]['down']) / (time.time() - self.last_net_process_time))
up_package = int((self.__process_net_list[pid]['up_package'] - self.last_net_process[pid]['up_package']) / (time.time() - self.last_net_process_time))
down_package = int((self.__process_net_list[pid]['down_package'] - self.last_net_process[pid]['down_package']) / (time.time() - self.last_net_process_time))
if up < 0: up = 0
if down < 0: down = 0
if up_package < 0: up_package = 0
if down_package < 0: down_package = 0
return up,up_package,down,down_package
def get_process_username(self,pid):
'''
@name 获取进程用户名
@param pid 进程id
@return 用户名
'''
try:
import pwd
return pwd.getpwuid(os.stat('/proc/' + str(pid)).st_uid).pw_name
except:
return 'root'
def get_monitor_list(self,stime = None):
'''
@name 获取监控列表
@author hwliang<2021-09-04>
@return list
'''
self.get_pids()
process_info_list = []
total_cpu_precent = 0.0
my_pid = os.getpid()
if type(self.new_info) != dict: self.new_info = {}
all_cpu_time = self.get_cpu_time()
self.new_info['time'] = time.time()
for pid in self.__pids:
try:
# if pid < 100: continue
if pid == my_pid: continue
if not pid in self.__process_object.keys():
self.__process_object[pid] = Process(pid)
try:
if self.__process_object[pid].status() == 'terminated':
self.__process_object[pid] = Process(pid).create_time
except:
self.__process_object[pid] = Process(pid)
p = self.__process_object[pid]
process_info = {}
process_info['cpu_percent'] = self.get_cpu_percent(str(pid),sum(p.cpu_times()),all_cpu_time) #self.get_cpu_percent(pid,int(sum(p.cpu_times())),self.new_info['cpu_time']) # CPU使用率
total_cpu_precent += process_info['cpu_percent']
process_info['memory'] = p.memory_info().rss # 内存占用
if not process_info['memory']: continue
io_counters = p.io_counters()
process_info['disk_read'] = self.get_io_read(pid,io_counters.read_bytes) # 读取磁盘字节数
process_info['disk_write'] = self.get_io_write(pid,io_counters.write_bytes) # 写入磁盘字节数
process_info['disk_total'] = process_info['disk_read'] + process_info['disk_write'] # 磁盘总读写
process_info['up'],process_info['up_package'],process_info['down'],process_info['down_package'] = self.get_process_network(str(pid))
process_info['net_total'] = process_info['up'] + process_info['down'] # 网络总流量
process_info['package_total'] = process_info['up_package'] + process_info['down_package'] # 网络总包数
if not process_info['cpu_percent'] and not process_info['disk_total'] and not process_info['net_total']: continue
process_proc_comm = '/proc/{}/comm'.format(pid)
process_proc_cmdline = '/proc/{}/cmdline'.format(pid)
process_info['pid'] = pid
process_info['name'] = self.read_file(process_proc_comm)
process_info['cmdline'] = self.read_file(process_proc_cmdline) # 启动命令
process_info['create_time'] = int(p.create_time()) # 创建时间
process_info['connect_count'] = len(p.connections()) # 连接数
process_info['username'] = self.get_process_username(pid) # 进程用户名
process_info_list.append(process_info)
except:
continue
self.__last_cpu_time = all_cpu_time
self.__process_net_list.clear()
self.insert_db(process_info_list,stime)
# import public
# for pp in sorted(process_info_list,key=lambda x:x['cpu_percent'],reverse=True):
# public.print_log("name: {}, cpu_percent: {}".format(pp['name'], pp['cpu_percent']))
if total_cpu_precent > 100: total_cpu_precent = 100
return total_cpu_precent
def get_expire_time(self):
'''
@name 获取过期时间
@return int
'''
filename = 'data/control.conf'
_day = 30
if os.path.exists(filename):
try:
conf = self.read_file(filename)
if conf: _day = int(conf)
except: pass
return _day * 86400
def insert_db(self,process_info_list,_time):
'''
@name 插入数据库
@param process_info_list list
@return bool
'''
if not process_info_list: return
all_top,cpu_top,disk_top,net_top,memory_top = self.get_top_list(process_info_list)
with db.Sql().dbfile('system') as _sql:
if not _time:
_time = int(time.time())
_sql.table('process_top_list').insert({
'all_top':dumps(all_top),
'cpu_top':dumps(cpu_top),
'disk_top':dumps(disk_top),
'net_top':dumps(net_top),
'memory_top':dumps(memory_top),
'addtime':_time
})
# 删除过期数据
if self.__insert_time and _time - self.__insert_time > 3600:
self.__insert_time = _time
_sql.table('process_top_list').where('addtime<?',self.get_expire_time()).delete()
_sql.close()
def get_top_list(sekf,process_info_list):
'''
@name 排序
@param process_info_list list
@return list
'''
process_info_list = sorted(process_info_list,key=lambda x:[x['cpu_percent'],x['disk_total'],x['net_total'],x['memory']],reverse=True)
top_num = 5
all_top = []
for p in process_info_list[:top_num]:
_line = [p['cpu_percent'],p['disk_read'],p['disk_write'],p['memory'],p['up'],p['down'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
all_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['cpu_percent'],reverse=True)
cpu_top = []
for p in process_info_list[:top_num]:
if not p['cpu_percent']: continue
_line = [p['cpu_percent'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
cpu_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['disk_total'],reverse=True)
disk_top = []
for p in process_info_list[:top_num]:
if not p['disk_total']: continue
_line = [p['disk_total'],p['disk_read'],p['disk_write'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
disk_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['net_total'],reverse=True)
net_top = []
for p in process_info_list[:top_num]:
if not p['net_total']: continue
_line = [p['net_total'],p['up'],p['down'],p['connect_count'],p['package_total'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
net_top.append(_line)
process_info_list = sorted(process_info_list,key=lambda x:x['memory'],reverse=True)
memory_top = []
for p in process_info_list[:top_num]:
if not p['memory']: continue
_line = [p['memory'],p['pid'],public.xssencode2(p['name']),public.xssencode2(p['cmdline']),public.xssencode2(p['username']),p['create_time']]
memory_top.append(_line)
return all_top,cpu_top,disk_top,net_top,memory_top
if __name__ == '__main__':
# net = process_network_total()
# threading.Thread(target = net.start,args=()).start()
p = process_task()
while True:
p.get_monitor_list()
time.sleep(1)
print("-"*50)
+337
View File
@@ -0,0 +1,337 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: zouhw <zhw@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# Docker模型
#------------------------------
import public #line:13
import os #line:14
import time #line:15
import projectModel .bt_docker .dk_public as dp #line:16
import projectModel .bt_docker .dk_container as dc #line:17
import projectModel .bt_docker .dk_setup as ds #line:18
import json #line:19
class main :#line:22
compose_path ="{}/data/compose".format (public .get_panel_path ())#line:23
__O00OO0OO00O0OO000 ="/tmp/dockertmp.log"#line:24
def check_conf (O0OO0OO00OO0OOO00 ,O0OOOOOOO00O00O00 ):#line:27
OOO0OOO0OOO0000O0 ="/usr/bin/docker-compose -f {} config".format (O0OOOOOOO00O00O00 )#line:28
O0O0000OOOO0O0O0O ,OOOO00O0O00OOO0O0 =public .ExecShell (OOO0OOO0OOO0000O0 )#line:29
if OOOO00O0O00OOO0O0 :#line:30
return public .return_msg_gettext (False ,"Check failed:{}".format (OOOO00O0O00OOO0O0 ))#line:31
return public .return_msg_gettext (True ,"Passed!")#line:32
def add_template_gui (O0O0OOOOOOO00O000 ,O0000O0O0O0O00O00 ):#line:35
""#line:69
import yaml #line:70
O000000OOO0O00OO0 ="{}/template".format (O0O0OOOOOOO00O000 .compose_path )#line:71
O0O00000OO000OO0O ="{}/{}.yaml".format (O000000OOO0O00OO0 ,O0000O0O0O0O00O00 .name )#line:72
if not os .path .exists (O000000OOO0O00OO0 ):#line:73
os .makedirs (O000000OOO0O00OO0 )#line:74
O000O0O000O0OOOO0 =json .loads (O0000O0O0O0O00O00 .data )#line:75
yaml .dump (O000O0O000O0OOOO0 ,O0O00000OO000OO0O )#line:76
def get_template_kw (O0OOO0OO0OOO0000O ,OO00OO000OOOOOOOO ):#line:78
O0OO0OO0O00O0OO0O ={"version":"","services":{"server_name_str":{"build":{"context":"str","dockerfile":"str","args":[],"cache_from":[],"labels":[],"network":"str","shm_size":"str","target":"str"},"cap_add":"","cap_drop":"","cgroup_parent":"str","command":"str","configs":{"my_config_str":[]},"container_name":"str","credential_spec":{"file":"str","registry":"str"},"depends_on":[],"deploy":{"endpoint_mode":"str","labels":{"key":"value"},"mode":"str","placement":[{"key":"value"}],"max_replicas_per_node":"int","replicas":"int","resources":{"limits":{"cpus":"str","memory":"str",},"reservations":{"cpus":"str","memory":"str",},"restart_policy":{"condition":"str","delay":"str","max_attempts":"int","window":"str"}}}}}}#line:134
def add_template (OO0O00000000O00OO ,O00OOOOOO0O0O0000 ):#line:137
""#line:145
OO00O0OO0O0O0000O =OO0O00000000O00OO .template_list (O00OOOOOO0O0O0000 )['msg']['template']#line:146
for O0O00O000OO000OO0 in OO00O0OO0O0O0000O :#line:147
if O00OOOOOO0O0O0000 .name ==O0O00O000OO000OO0 ['name']:#line:148
return public .return_msg_gettext (False ,"This template name already exists!")#line:149
OO0OOOOO0OO0O0OOO ="{}/{}/template".format (OO0O00000000O00OO .compose_path ,O00OOOOOO0O0O0000 .name )#line:150
O00OOO00O0O0O0OOO ="{}/{}.yaml".format (OO0OOOOO0OO0O0OOO ,O00OOOOOO0O0O0000 .name )#line:151
if not os .path .exists (OO0OOOOO0OO0O0OOO ):#line:152
os .makedirs (OO0OOOOO0OO0O0OOO )#line:153
public .writeFile (O00OOO00O0O0O0OOO ,O00OOOOOO0O0O0000 .data )#line:154
OOO0000O000O0OO00 =OO0O00000000O00OO .check_conf (O00OOO00O0O0O0OOO )#line:155
if not OOO0000O000O0OO00 ['status']:#line:156
if os .path .exists (O00OOO00O0O0O0OOO ):#line:157
os .remove (O00OOO00O0O0O0OOO )#line:158
return OOO0000O000O0OO00 #line:160
O0O0OO0O00OO00OO0 ={"name":O00OOOOOO0O0O0000 .name ,"remark":O00OOOOOO0O0O0000 .remark ,"path":O00OOO00O0O0O0OOO }#line:165
dp .sql ("templates").insert (O0O0OO0O00OO00OO0 )#line:166
dp .write_log ("Add template [{}] successful!".format (O00OOOOOO0O0O0000 .name ))#line:167
return public .return_msg_gettext (True ,"Template added successfully!")#line:169
def edit_template (OOOO00O0OOOOO0O00 ,O00O0O0O000O0O000 ):#line:171
""#line:178
O0O00OOO00OOOO00O =dp .sql ("templates").where ("id=?",(O00O0O0O000O0O000 .id ,)).find ()#line:179
if not O0O00OOO00OOOO00O :#line:180
return public .return_msg_gettext (False ,"This template was not found!")#line:181
public .writeFile (O0O00OOO00OOOO00O ['path'],O00O0O0O000O0O000 .data )#line:182
O0OOOO0O0000OOO0O =OOOO00O0OOOOO0O00 .check_conf (O0O00OOO00OOOO00O ['path'])#line:183
if not O0OOOO0O0000OOO0O ['status']:#line:184
return O0OOOO0O0000OOO0O #line:185
OOOOO0OO00OOO000O ={"name":O0O00OOO00OOOO00O ['name'],"remark":O00O0O0O000O0O000 .remark ,"path":O0O00OOO00OOOO00O ['path']}#line:190
dp .sql ("templates").where ("id=?",(O00O0O0O000O0O000 .id ,)).update (OOOOO0OO00OOO000O )#line:191
dp .write_log ("Editing template [{}] succeeded!".format (O0O00OOO00OOOO00O ['name']))#line:192
return public .return_msg_gettext (True ,"Modify the template successfully!")#line:193
def get_template (O0O000OO00O0OO0O0 ,O0OOOO00O00O00OO0 ):#line:195
""#line:200
OOO0OO0OO0OO0000O =dp .sql ("templates").where ("id=?",(O0OOOO00O00O00OO0 .id ,)).find ()#line:201
if not OOO0OO0OO0OO0000O :#line:202
return public .return_msg_gettext (False ,"This template was not found!")#line:203
return public .return_msg_gettext (True ,public .readFile (OOO0OO0OO0OO0000O ['path']))#line:204
def template_list (OOO00OO000O0OOO0O ,O00O0O00OOO00O000 ):#line:206
""#line:211
import projectModel .bt_docker .dk_setup as ds #line:212
OOOO0OO00OOOO0O0O =ds .main ()#line:213
OOO0OO00OO0OO0O0O =dp .sql ("templates").select ()[::-1 ]#line:214
if not isinstance (OOO0OO00OO0OO0O0O ,list ):#line:215
OOO0OO00OO0OO0O0O =[]#line:216
OOOO0O000OO0OO0O0 ={"template":OOO0OO00OO0OO0O0O ,"installed":OOOO0OO00OOOO0O0O .check_docker_program (),"service_status":OOOO0OO00OOOO0O0O .get_service_status ()}#line:221
return public .return_msg_gettext (True ,OOOO0O000OO0OO0O0 )#line:222
def remove_template (O0O0O0OOOOO0O000O ,OO0OO0OO00O000OOO ):#line:224
""#line:230
O00OO00O0OOO0000O =dp .sql ("templates").where ("id=?",(OO0OO0OO00O000OOO .template_id ,)).find ()#line:231
if not O00OO00O0OOO0000O :#line:232
return public .return_msg_gettext (False ,"This template was not found!")#line:233
if os .path .exists (O00OO00O0OOO0000O ['path']):#line:234
os .remove (O00OO00O0OOO0000O ['path'])#line:235
dp .sql ("templates").delete (id =OO0OO0OO00O000OOO .template_id )#line:236
dp .write_log ("Delete template [{}] successful!".format (O00OO00O0OOO0000O ['name']))#line:237
return public .return_msg_gettext (True ,"Successfully deleted!")#line:238
def edit_project_remark (OOO0O0000OO000000 ,OO00000OO000OOOO0 ):#line:240
""#line:247
O0OOOOOO000OOO0OO =dp .sql ("stacks").where ("id=?",(OO00000OO000OOOO0 .project_id ,)).find ()#line:248
if not O0OOOOOO000OOO0OO :#line:249
return public .return_msg_gettext (False ,"The item was not found!")#line:250
OO0OOO00OO0OOO000 ={"remark":OO00000OO000OOOO0 .remark }#line:253
dp .write_log ("Modify the item[{}] remarks [{}] to [{}] success!".format (O0OOOOOO000OOO0OO ['name'],O0OOOOOO000OOO0OO ['remark'],OO00000OO000OOOO0 .remark ))#line:254
dp .sql ("stacks").where ("id=?",(OO00000OO000OOOO0 .project_id ,)).update (OO0OOO00OO0OOO000 )#line:255
def edit_template_remark (O000000OOOOO0O000 ,OOOO0OOOO0O0O0OO0 ):#line:257
""#line:264
OO0OO00OOOOO0OO00 =dp .sql ("templates").where ("id=?",(OOOO0OOOO0O0O0OO0 .templates_id ,)).find ()#line:265
if not OO0OO00OOOOO0OO00 :#line:266
return public .return_msg_gettext (False ,"The template was not found!")#line:267
OO0O0O0OO0OOOOOOO ={"remark":OOOO0OOOO0O0O0OO0 .remark }#line:270
dp .write_log ("Modify the template [{}] remarks [{}] to [{}] successful!".format (OO0OO00OOOOO0OO00 ['name'],OO0OO00OOOOO0OO00 ['remark'],OOOO0OOOO0O0O0OO0 .remark ))#line:271
dp .sql ("templates").where ("id=?",(OOOO0OOOO0O0O0OO0 .templates_id ,)).update (OO0O0O0OO0OOOOOOO )#line:272
def create_project_in_path (OO00000O0000O0O00 ,O0OO0OOOO0000OO00 ,O000O0O0O0OO00000 ):#line:274
OO000OO000O0O00O0 ="cd {} && /usr/bin/docker-compose -p {} up -d &> {}".format ("/".join (O000O0O0O0OO00000 .split ("/")[:-1 ]),O0OO0OOOO0000OO00 ,OO00000O0000O0O00 .__O00OO0OO00O0OO000 )#line:275
public .ExecShell (OO000OO000O0O00O0 )#line:276
def create_project_in_file (O00OO0O0OO0OO00OO ,O0O0O0O00O000000O ,O00000O0OOOO0OOOO ):#line:278
O0000OO0O0O000OO0 ="{}/{}".format (O00OO0O0OO0OO00OO .compose_path ,O0O0O0O00O000000O )#line:279
O000OO00OOOOO00OO ="{}/docker-compose.yaml".format (O0000OO0O0O000OO0 )#line:280
if not os .path .exists (O0000OO0O0O000OO0 ):#line:281
os .makedirs (O0000OO0O0O000OO0 )#line:282
O00OOO000O000O000 =public .readFile (O00000O0OOOO0OOOO )#line:283
public .writeFile (O000OO00OOOOO00OO ,O00OOO000O000O000 )#line:284
O000O0OOOO0OO0O0O ="/usr/bin/docker-compose -p {} -f {} up -d &> {}".format (O0O0O0O00O000000O ,O000OO00OOOOO00OO ,O00OO0O0OO0OO00OO .__O00OO0OO00O0OO000 )#line:285
public .ExecShell (O000O0OOOO0OO0O0O )#line:286
def check_project_container_name (OOOO00OO0OOOOO0OO ,OO0OOOO0O00OO0OO0 ,OOO0OO00OO0OO00OO ):#line:288
""#line:292
import re #line:293
import projectModel .bt_docker .dk_container as dc #line:294
OO00OOOOO0O0OOOOO =[]#line:295
O0OOOO000O00OOO0O =re .findall ("container_name\s*:\s*[\"\']+(.*)[\'\"]",OO0OOOO0O00OO0OO0 )#line:296
O0O0OO0O00OOO0O00 =dc .main ().get_list (OOO0OO00OO0OO00OO )#line:297
if not O0O0OO0O00OOO0O00 ["status"]:#line:298
return public .return_msg_gettext (False ,"Error getting container list!")#line:299
O0O0OO0O00OOO0O00 =O0O0OO0O00OOO0O00 ['msg']['container_list']#line:300
for O0OOO0O0OO0O0OO00 in O0O0OO0O00OOO0O00 :#line:301
if O0OOO0O0OO0O0OO00 ['name']in O0OOOO000O00OOO0O :#line:302
OO00OOOOO0O0OOOOO .append (O0OOO0O0OO0O0OO00 ['name'])#line:303
if OO00OOOOO0O0OOOOO :#line:304
return public .return_msg_gettext (False ,"The container name in the template: <br>[{}] already exists!".format (", ".join (OO00OOOOO0O0OOOOO )))#line:305
OO0000O0OO00OO0O0 ="(\d+):\d+"#line:307
O00O00OOOO0OO00O0 =re .findall (OO0000O0OO00OO0O0 ,OO0OOOO0O00OO0OO0 )#line:308
for O0OOO00O00O0OOOOO in O00O00OOOO0OO00O0 :#line:309
if dp .check_socket (O0OOO00O00O0OOOOO ):#line:310
return public .return_msg_gettext (False ,"The port [{}] in the template is already in use, please modify the server port in the template!".format (O0OOO00O00O0OOOOO ))#line:311
def create (OO0O00O0OO00O0OO0 ,O0O0O0O00OO0OOOO0 ):#line:314
""#line:321
OOO00OO0O00OOOO0O =public .md5 (O0O0O0O00OO0OOOO0 .project_name )#line:322
OO000O00O00O0000O =dp .sql ("templates").where ("id=?",(O0O0O0O00OO0OOOO0 .template_id ,)).find ()#line:323
if not os .path .exists (OO000O00O00O0000O ['path']):#line:324
return public .return_msg_gettext (False ,"Template file not found")#line:325
O00O0OOOOO0OO0OOO =OO0O00O0OO00O0OO0 .check_project_container_name (public .readFile (OO000O00O00O0000O ['path']),O0O0O0O00OO0OOOO0 )#line:326
if O00O0OOOOO0OO0OOO :#line:327
return O00O0OOOOO0OO0OOO #line:328
O000000O0000OOOOO =dp .sql ("stacks").where ("name=?",(OOO00OO0O00OOOO0O )).find ()#line:329
if not O000000O0000OOOOO :#line:330
O0OOO0OOO0O0OO0OO ={"name":O0O0O0O00OO0OOOO0 .project_name ,"status":"1","path":OO000O00O00O0000O ['path'],"template_id":O0O0O0O00OO0OOOO0 .template_id ,"time":time .time (),"remark":O0O0O0O00OO0OOOO0 .remark }#line:338
dp .sql ("stacks").insert (O0OOO0OOO0O0OO0OO )#line:339
else :#line:340
return public .return_msg_gettext (False ,"This project name already exists!")#line:341
if OO000O00O00O0000O ['add_in_path']==1 :#line:342
OO0O00O0OO00O0OO0 .create_project_in_path (OOO00OO0O00OOOO0O ,OO000O00O00O0000O ['path'])#line:346
else :#line:347
OO0O00O0OO00O0OO0 .create_project_in_file (OOO00OO0O00OOOO0O ,OO000O00O00O0000O ['path'])#line:351
dp .write_log ("Project [{}] is successfully deployed!".format (OOO00OO0O00OOOO0O ))#line:352
return public .return_msg_gettext (True ,"Deployment succeeded!")#line:354
def compose_project_list (OOOOOO0OO00O0OO0O ,OO0O0000O00O0O000 ):#line:370
""#line:373
OO0O0000O00O0O000 .url ="unix:///var/run/docker.sock"#line:374
O0O000O0000OOO000 =dc .main ().get_list (OO0O0000O00O0O000 )#line:375
if not O0O000O0000OOO000 ['status']:#line:376
return public .return_msg_gettext (False ,"Failed to get the container, maybe the docker service is not started!")#line:377
if not O0O000O0000OOO000 ['msg']['service_status']or not O0O000O0000OOO000 ['msg']['installed']:#line:378
OO0OOO0OO00000OO0 ={"project_list":[],"template":[],"service_status":O0O000O0000OOO000 ['msg']['service_status'],"installed":O0O000O0000OOO000 ['msg']['installed']}#line:384
return public .return_msg_gettext (True ,OO0OOO0OO00000OO0 )#line:385
OO000O00000O00O0O =dp .sql ("stacks").select ()#line:386
if isinstance (OO000O00000O00O0O ,list ):#line:387
for O0OOOO0O000O00O00 in OO000O00000O00O0O :#line:388
OOOOOO0OO00OOOO0O =[]#line:389
for O0000OOO000000OOO in O0O000O0000OOO000 ['msg']["container_list"]:#line:390
try :#line:391
if 'com.docker.compose.project'not in O0000OOO000000OOO ["detail"]['Config']['Labels']:#line:392
continue #line:393
except :#line:394
continue #line:395
if O0000OOO000000OOO ["detail"]['Config']['Labels']['com.docker.compose.project']==public .md5 (O0OOOO0O000O00O00 ['name']):#line:396
OOOOOO0OO00OOOO0O .append (O0000OOO000000OOO )#line:397
O00OOOOO0O0OO0000 =OOOOOO0OO00OOOO0O #line:398
O0OOOO0O000O00O00 ['container']=O00OOOOO0O0OO0000 #line:399
else :#line:400
OO000O00000O00O0O =[]#line:401
OO0OOO0OO0OO00OOO =OOOOOO0OO00O0OO0O .template_list (OO0O0000O00O0O000 )#line:402
if not OO0OOO0OO0OO00OOO ['status']:#line:403
OO0OOO0OO0OO00OOO =list ()#line:404
else :#line:405
OO0OOO0OO0OO00OOO =OO0OOO0OO0OO00OOO ['msg']['template']#line:406
OO0O0O00OOO000O0O =ds .main ()#line:407
OO0OOO0OO00000OO0 ={"project_list":OO000O00000O00O0O ,"template":OO0OOO0OO0OO00OOO ,"service_status":OO0O0O00OOO000O0O .get_service_status (),"installed":OO0O0O00OOO000O0O .check_docker_program ()}#line:413
return public .return_msg_gettext (True ,OO0OOO0OO00000OO0 )#line:414
def remove (O0O0O0O0OO00OOOOO ,OOO0O00OOO0OOOO00 ):#line:417
""#line:422
O0OO0OOO00O000OOO =dp .sql ("stacks").where ("id=?",(OOO0O00OOO0OOOO00 .project_id ,)).find ()#line:423
if not O0OO0OOO00O000OOO :#line:424
return public .return_msg_gettext (True ,"The project configuration was not found!")#line:425
OO0000000000O000O ="/usr/bin/docker-compose -p {} -f {} down &> {}".format (public .md5 (O0OO0OOO00O000OOO ['name']),O0OO0OOO00O000OOO ['path'],O0O0O0O0OO00OOOOO .__O00OO0OO00O0OO000 )#line:426
OO0O0OOO00O00OO00 ,O00OO00O0000000O0 =public .ExecShell (OO0000000000O000O )#line:427
dp .sql ("stacks").delete (id =OOO0O00OOO0OOOO00 .project_id )#line:428
dp .write_log ("Delete item [{}] succeeded!".format (O0OO0OOO00O000OOO ['name']))#line:429
return public .return_msg_gettext (True ,"Successfully deleted!")#line:430
def stop (O0OOO0OOOOOO00O00 ,OOOO0OOO0OO00OO00 ):#line:433
""#line:439
O00O000O00OOO00O0 =dp .sql ("stacks").where ("id=?",(OOOO0OOO0OO00OO00 .project_id ,)).find ()#line:440
if not O00O000O00OOO00O0 :#line:441
return public .return_msg_gettext (True ,"The project configuration was not found!")#line:442
OO0OO0OOO0O0O00O0 ="/usr/bin/docker-compose -p {} -f {} stop &> {}".format (public .md5 (O00O000O00OOO00O0 ['name']),O00O000O00OOO00O0 ['path'],O0OOO0OOOOOO00O00 .__O00OO0OO00O0OO000 )#line:444
OO0O00O0000OOOOOO ,O0OO00000O0OO00O0 =public .ExecShell (OO0OO0OOO0O0O00O0 )#line:445
dp .write_log ("Stop project [{}] succeeded!".format (O00O000O00OOO00O0 ['name']))#line:446
return public .return_msg_gettext (True ,"Set up successfully!")#line:447
def start (O000OO00OOO0000O0 ,O0000O000OO0OOOO0 ):#line:450
""#line:455
OO00O000000OO00O0 =dp .sql ("stacks").where ("id=?",(O0000O000OO0OOOO0 .project_id ,)).find ()#line:456
if not OO00O000000OO00O0 :#line:457
return public .return_msg_gettext (False ,"The project configuration was not found!")#line:458
O0OO000O0OOO0OO0O ="/usr/bin/docker-compose -p {} -f {} start > {}".format (public .md5 (OO00O000000OO00O0 ['name']),OO00O000000OO00O0 ['path'],O000OO00OOO0000O0 .__O00OO0OO00O0OO000 )#line:459
O00O000OOO00O000O ,O0O00OO0OO0OO0OOO =public .ExecShell (O0OO000O0OOO0OO0O )#line:460
dp .write_log ("Startup project [{}] succeeded!".format (OO00O000000OO00O0 ['name']))#line:461
return public .return_msg_gettext (True ,"Set up successfully!")#line:462
def restart (O0OO00OO0O0OOO00O ,O000OOOOOOO00O0OO ):#line:465
""#line:470
OO000OOOOO0OO0O0O =dp .sql ("stacks").where ("id=?",(O000OOOOOOO00O0OO .project_id ,)).find ()#line:471
if not OO000OOOOO0OO0O0O :#line:472
return public .return_msg_gettext (True ,"The project configuration was not found!")#line:473
O00O00O000OO00O0O ="/usr/bin/docker-compose -p {} -f {} restart &> {}".format (public .md5 (OO000OOOOO0OO0O0O ['name']),OO000OOOOO0OO0O0O ['path'],O0OO00OO0O0OOO00O .__O00OO0OO00O0OO000 )#line:474
O00OO0000O0000OO0 ,O0OOOOO000OO0O0O0 =public .ExecShell (O00O00O000OO00O0O )#line:475
dp .write_log ("Restart the project [{}] successfully!".format (OO000OOOOO0OO0O0O ['name']))#line:476
return public .return_msg_gettext (True ,"Set up successfully!")#line:477
def pull (OOO0OO00O0OO00OO0 ,OO0OOOOOO00000OO0 ):#line:480
""#line:485
O0OO0OO0OO0O0OOO0 =dp .sql ("templates").where ("id=?",(OO0OOOOOO00000OO0 .template_id ,)).find ()#line:486
if not O0OO0OO0OO0O0OOO0 :#line:487
return public .return_msg_gettext (True ,"The template was not found!")#line:488
O0OOOO0O000O0O000 ="/usr/bin/docker-compose -p {} -f {} pull &> {}".format (O0OO0OO0OO0O0OOO0 ['name'],O0OO0OO0OO0O0OOO0 ['path'],OOO0OO00O0OO00OO0 .__O00OO0OO00O0OO000 )#line:489
O0O00OOO0O0O0OO00 ,OO000OOO0O000OOO0 =public .ExecShell (O0OOOO0O000O0O000 )#line:490
dp .write_log ("The mirror image of the template [{}] was pulled successfully!".format (O0OO0OO0OO0O0OOO0 ['name']))#line:491
return public .return_msg_gettext (True ,"Pull success!")#line:492
def pause (OOO00O0OO00O00O0O ,O0OOOOO000OO0000O ):#line:495
""#line:500
OO0O0000OOO000O00 =dp .sql ("stacks").where ("id=?",(O0OOOOO000OO0000O .project_id ,)).find ()#line:501
if not OO0O0000OOO000O00 :#line:502
return public .return_msg_gettext (True ,"The project configuration was not found!")#line:503
O0O0OOO0000O0O00O ="/usr/bin/docker-compose -p {} -f {} pause &> {}".format (public .md5 (OO0O0000OOO000O00 ['name']),OO0O0000OOO000O00 ['path'],OOO00O0OO00O00O0O .__O00OO0OO00O0OO000 )#line:504
O0000OO0OOO00OOOO ,O0O0O0OOO0O0O0OOO =public .ExecShell (O0O0OOO0000O0O00O )#line:505
dp .write_log ("Pause [{}] success!".format (OO0O0000OOO000O00 ['name']))#line:506
return public .return_msg_gettext (True ,"Set up successfully!")#line:507
def unpause (O0O0O0OOO00OOOO00 ,O0O00O000OOO00OO0 ):#line:510
""#line:515
OO0O0OO00OOO00O00 =dp .sql ("stacks").where ("id=?",(O0O00O000OOO00OO0 .project_id ,)).find ()#line:516
if not OO0O0OO00OOO00O00 :#line:517
return public .return_msg_gettext (True ,"The project configuration was not found!")#line:518
OOO00O00OOOO0OO00 ="/usr/bin/docker-compose -p {} -f {} unpause &> {}".format (public .md5 (OO0O0OO00OOO00O00 ['name']),OO0O0OO00OOO00O00 ['path'],O0O0O0OOO00OOOO00 .__O00OO0OO00O0OO000 )#line:519
O00000OOO000OOO0O ,O00OO00O0O0O00OO0 =public .ExecShell (OOO00O00OOOO0OO00 )#line:520
dp .write_log ("Unsuspended project [{}] succeeded!".format (OO0O0OO00OOO00O00 ['name']))#line:521
return public .return_msg_gettext (True ,"Set up successfully!")#line:522
def scan_compose_file (OOO000OO000OOOOOO ,O00000OOO00O00OOO ,O000O00O0OOO0O0OO ):#line:525
""#line:531
O0OOOO0O00OOO00O0 =os .listdir (O00000OOO00O00OOO )#line:532
for OO00000OOO00OOOO0 in O0OOOO0O00OOO00O0 :#line:533
OO0O0OO00OO0OO000 =os .path .join (O00000OOO00O00OOO ,OO00000OOO00OOOO0 )#line:534
if os .path .isdir (OO0O0OO00OO0OO000 ):#line:536
OOO000OO000OOOOOO .scan_compose_file (OO0O0OO00OO0OO000 ,O000O00O0OOO0O0OO )#line:537
else :#line:538
if OO00000OOO00OOOO0 =="docker-compose.yaml"or OO00000OOO00OOOO0 =="docker-compose.yam"or OO00000OOO00OOOO0 =="docker-compose.yml":#line:539
if "/www/server/panel/data/compose"in OO0O0OO00OO0OO000 :#line:540
continue #line:541
O000O00O0OOO0O0OO .append (OO0O0OO00OO0OO000 )#line:542
return O000O00O0OOO0O0OO #line:543
def get_compose_project (OOO000O00OO000OO0 ,O0OOOO0OOOOO0000O ):#line:546
""#line:552
O0O0OO00OO0000O0O =list ()#line:553
if O0OOOO0OOOOO0000O .path =="/":#line:554
return public .return_msg_gettext (False ,"Can't start scanning from root directory!")#line:555
if O0OOOO0OOOOO0000O .path [-1 ]=="/":#line:556
O0OOOO0OOOOO0000O .path =O0OOOO0OOOOO0000O .path [:-1 ]#line:557
if str (O0OOOO0OOOOO0000O .sub_dir )=="1":#line:558
O000OOOO0OO00OOOO =OOO000O00OO000OO0 .scan_compose_file (O0OOOO0OOOOO0000O .path ,O0O0OO00OO0000O0O )#line:559
if not O000OOOO0OO00OOOO :#line:560
O000OOOO0OO00OOOO =[]#line:561
else :#line:562
O00O0O0OOO0OO0O00 =list ()#line:563
for O0O00O0O0OOOO000O in O000OOOO0OO00OOOO :#line:564
O00O0O0OOO0OO0O00 .append ({"project_name":O0O00O0O0OOOO000O .split ("/")[-2 ],"conf_file":"/".join (O0O00O0O0OOOO000O .split ("/")),"remark":"Add by local path"})#line:571
O000OOOO0OO00OOOO =O00O0O0OOO0OO0O00 #line:572
else :#line:573
O00O0O00O00O0000O ="{}/docker-compose.yaml".format (O0OOOO0OOOOO0000O .path )#line:574
OOO00OOO00O0O0OOO ="{}/docker-compose.yam".format (O0OOOO0OOOOO0000O .path )#line:575
if os .path .exists (O00O0O00O00O0000O ):#line:576
O000OOOO0OO00OOOO =[{"project_name":O0OOOO0OOOOO0000O .path .split ("/")[-1 ],"conf_file":O00O0O00O00O0000O ,"remark":"Add by local path"}]#line:581
elif os .path .exists (OOO00OOO00O0O0OOO ):#line:582
O000OOOO0OO00OOOO =[{"project_name":O0OOOO0OOOOO0000O .path .split ("/")[-1 ],"conf_file":OOO00OOO00O0O0OOO ,"remark":"Add by local path"}]#line:587
else :#line:588
O000OOOO0OO00OOOO =list ()#line:589
return O000OOOO0OO00OOOO #line:591
def add_template_in_path (O0O00000O0OO00OO0 ,OOO0OO0OO0O00O00O ):#line:594
""#line:599
OO0O000OO000O0OO0 =dict ()#line:600
OO00OOO00000OOOOO =dict ()#line:601
for OO0OO0OOO00O0OO00 in OOO0OO0OO0O00O00O .template_list :#line:602
O0O0OO00O00O00000 =OO0OO0OOO00O0OO00 ['conf_file']#line:603
O00O000OOOO0O00OO =OO0OO0OOO00O0OO00 ['project_name']#line:604
OOOOO00O00O0O0OO0 =OO0OO0OOO00O0OO00 ['remark']#line:605
O00O0O0O000O00O00 =O0O00000O0OO00OO0 .template_list (OOO0OO0OO0O00O00O )['msg']['template']#line:606
for O00O0O0OOO0O0OOOO in O00O0O0O000O00O00 :#line:607
if O00O000OOOO0O00OO ==O00O0O0OOO0O0OOOO ['name']:#line:608
OO0O000OO000O0OO0 [O00O000OOOO0O00OO ]="Template already exists!"#line:609
continue #line:610
if not os .path .exists (O0O0OO00O00O00000 ):#line:612
OO0O000OO000O0OO0 [O00O000OOOO0O00OO ]="The template was not found!"#line:613
continue #line:614
O000O0O0OOO00000O =O0O00000O0OO00OO0 .check_conf (O0O0OO00O00O00000 )#line:616
if not O000O0O0OOO00000O ['status']:#line:617
OO0O000OO000O0OO0 [O00O000OOOO0O00OO ]="Template validation failed, possibly malformed!"#line:618
continue #line:619
OO0OOO0OOOOO0O00O ={"name":O00O000OOOO0O00OO ,"remark":OOOOO00O00O0O0OO0 ,"path":O0O0OO00O00O00000 ,"add_in_path":1 }#line:626
print (OO0OOO0OOOOO0O00O )#line:627
dp .sql ("templates").insert (OO0OOO0OOOOO0O00O )#line:628
OO00OOO00000OOOOO [O00O000OOOO0O00OO ]="Template added successfully!"#line:629
print (OO0O000OO000O0OO0 )#line:631
for O00O0O0OOO0O0OOOO in OO0O000OO000O0OO0 :#line:632
if O00O0O0OOO0O0OOOO in OO00OOO00000OOOOO :#line:633
del (OO00OOO00000OOOOO [O00O0O0OOO0O0OOOO ])#line:634
else :#line:635
dp .write_log ("Add template [{}] from path successfully!".format (O00O0O0OOO0O0OOOO ))#line:636
if not OO0O000OO000O0OO0 and OO00OOO00000OOOOO :#line:637
return {'status':True ,'msg':'Add template successfully: [{}]'.format (','.join (OO00OOO00000OOOOO ))}#line:638
elif not OO00OOO00000OOOOO and OO0O000OO000O0OO0 :#line:639
return {'status':True ,'msg':'Failed to add template: template name already exists or format validation error [{}]'.format (','.join (OO0O000OO000O0OO0 ))}#line:640
return {'status':True ,'msg':'Add template successfully: [{}]<br>Add template failed: template name already exists or format validation error [{}]'.format (','.join (OO00OOO00000OOOOO ),','.join (OO0O000OO000O0OO0 ))}#line:641
@@ -0,0 +1,473 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: zouhw <zhw@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# Docker模型
#------------------------------
import public
import docker.errors
import projectModel.bt_docker.dk_public as dp
class main:
def __init__(self):
self.alter_table()
def alter_table(self):
if not dp.sql('sqlite_master').where('type=? AND name=? AND sql LIKE ?',
('table', 'container', '%sid%')).count():
dp.sql('container').execute("alter TABLE container add container_name VARCHAR DEFAULT ''", ())
def docker_client(self,url):
return dp.docker_client(url)
# 添加容器
def run(self,args):
"""
:param name:容器名
:param image: 镜像
:param publish_all_ports 暴露所有端口 1/0
:param ports 暴露某些端口 {'1111/tcp': ('127.0.0.1', 1111)}
:param command 命令
:param entrypoint 配置容器启动后执行的命令
:param environment 环境变量 xxx=xxx 一行一条
:param auto_remove 当容器进程退出时在守护进程端启用自动移除容器 0/1
:param args:
:return:
"""
if not hasattr(args,'ports'):
args.ports = False
if not hasattr(args,'volumes'):
args.volumes = False
#检测端口是否已经在使用
if args.ports:
for i in args.ports:
if dp.check_socket(args.ports[i]):
return public.returnMsg(False,"The server port [{}] has been used, please replace it with another port!".format(args.ports[i]))
if not args.image:
return public.returnMsg(False, "If there is no image selected, please go to the image tab to pull the image you need!")
if args.restart_policy['Name'] == "always":
args.restart_policy = {"Name":"always"}
# return args.restart_policy
# if
args.cpu_quota = float(args.cpuset_cpus) * 100000
# if not args.volumes:
# args.volumes = {"/sys/fs/cgroup":{"bind":"/sys/fs/cgroup","mode":"rw"}}
# else:
# if not "/sys/fs/cgroup" in args.volumes:
# args.volumes['/sys/fs/cgroup'] = {"bind":"/sys/fs/cgroup","mode":"rw"}
try:
if not args.name:
args.name = "{}-{}".format(args.image,public.GetRandomString(8))
if int(args.cpu_quota) / 100000 > dp.get_cpu_count():
return public.returnMsg(False,"The CPU quota has exceeded the number of cores available!")
mem_limit_byte = dp.byte_conversion(args.mem_limit)
if mem_limit_byte > dp.get_mem_info():
return public.returnMsg(False, "The memory quota has exceeded the available number!")
res = self.docker_client(args.url).containers.run(
name=args.name,
image=args.image,
detach=True,
publish_all_ports=True if args.publish_all_ports == "1" else False,
ports=args.ports if args.ports else None,
command=args.command,
auto_remove=True if str(args.auto_remove) == "1" else False,
environment=dp.set_kv(args.environment), #"HOME=/value\nHOME11=value1"
volumes=args.volumes, #一个字典对象 {'服务器路径/home/user1/': {'bind': '容器路径/mnt/vol2', 'mode': 'rw'},'/var/www': {'bind': '/mnt/vol1', 'mode': 'ro'}}
# cpuset_cpus=args.cpuset_cpus ,#指定容器使用的cpu个数
cpu_quota=int(args.cpu_quota),
mem_limit=args.mem_limit, #b,k,m,g
restart_policy=args.restart_policy,
labels=dp.set_kv(args.labels), #"key=value\nkey1=value1"
tty=True,
stdin_open=True,
privileged=True
)
if res:
pdata = {
"cpu_limit": str(args.cpu_quota),
"container_name": args.name
}
dp.sql('container').insert(pdata)
public.set_module_logs('docker', 'run_container', 1)
dp.write_log("Create container [{}] successful!".format(args.name))
return public.returnMsg(True,"The container was created successfully!")
return public.returnMsg(False, 'Create failed!')
except docker.errors.APIError as e:
if "container to be able to reuse that name." in str(e):
return public.returnMsg(False, "The container name already exists!")
if "Invalid container name" in str(e):
return public.returnMsg(False, "The container name is illegal, please do not use Chinese container name!")
if "bind: address already in use" in str(e):
port = ""
for i in args.ports:
if ":{}:".format(args.ports[i]) in str(e):
port = args.ports[i]
args.id = args.name
self.del_container(args)
return public.returnMsg(False, "Server port {} is in use! Please change other ports".format(port))
return public.returnMsg(False, 'Create failed! {}'.format(public.get_error_info()))
# 保存为镜像
def commit(self,args):
"""
:param repository 推送到的仓库
:param tag 镜像标签 jose:v1
:param message 提交的信息
:param author 镜像作者
:param changes
:param conf dict
:param path 导出路径
:param name 导出文件名
:param args:
:return:
"""
if not hasattr(args,'conf') or not args.conf:
args.conf = None
if args.repository == "docker.io":
args.repository = ""
container = self.docker_client(args.url).containers.get(args.id)
container.commit(
repository=args.repository if args.repository else None,
tag=args.tag if args.tag else None,
message=args.message if args.message else None,
author=args.author if args.author else None,
# changes=args.changes if args.changes else None,
conf=args.conf
)
if hasattr(args,"path") and args.path:
args.id = "{}:{}".format(args.name,args.tag)
import projectModel.bt_docker.dk_image as dk
return dk.main().save(args)
dp.write_log("Submitting container [{}] as image [{}] succeeded!".format(container.attrs['Name'],args.tag))
return public.returnMsg(True,"提交成功!")
# 容器执行命令
def docker_shell(self, args):
"""
:param container_id
:param args:
:return:
"""
try:
self.docker_client(args.url).containers.get(args.container_id)
cmd = 'docker container exec -it {} /bin/bash'.format(args.container_id)
return public.returnMsg(True, cmd)
except docker.errors.APIError as ex:
return public.returnMsg(False, 'Failed to get container')
# 导出容器为tar 没有导入方法,目前弃用
def export(self,args):
"""
:param path 保存路径
:param name 包名
:param args:
:return:
"""
from os import path as ospath
from os import makedirs as makedirs
try:
if "tar" in args.name:
file_name = '{}/{}'.format(args.path,args.name)
else:
file_name = '{}/{}.tar'.format(args.path, args.name)
if not ospath.exists(args.path):
makedirs(args.path)
public.writeFile(file_name,'')
f = open(file_name, 'wb')
container = self.docker_client(args.url).containers.get(args.id)
data = container.export()
for i in data:
f.write(i)
f.close()
return public.returnMsg(True, "Successfully exported to: {}".format(file_name))
except:
return public.returnMsg(False, 'Operation failed:' + str(public.get_error_info()))
# 删除容器
def del_container(self,args):
"""
:return:
"""
import projectModel.bt_docker.dk_public as dp
container = self.docker_client(args.url).containers.get(args.id)
container.remove(force=True)
dp.sql("cpu_stats").where("container_id=?", (args.id,)).delete()
dp.sql("io_stats").where("container_id=?", (args.id,)).delete()
dp.sql("mem_stats").where("container_id=?", (args.id,)).delete()
dp.sql("net_stats").where("container_id=?", (args.id,)).delete()
dp.sql("container").where("container_nam=?", (container.attrs['Name'])).delete()
dp.write_log("Delete container [{}] succeeded!".format(container.attrs['Name']))
return public.returnMsg(True,"Successfully deleted!")
# 设置容器状态
def set_container_status(self,args):
import time
container = self.docker_client(args.url).containers.get(args.id)
if args.act == "start":
container.start()
elif args.act == "stop":
container.stop()
elif args.act == "pause":
container.pause()
elif args.act == "unpause":
container.unpause()
elif args.act == "reload":
container.reload()
else:
container.restart()
time.sleep(1)
tmp = self.docker_client(args.url).containers.get(args.id)
return {"name":container.attrs['Name'].replace('/',''),"status":tmp.attrs['State']['Status']} #返回设置后的状态
# 停止容器
def stop(self,args):
"""
:param url
:param id
:param args:
:return:
"""
try:
args.act = "stop"
data = self.set_container_status(args)
if data['status'] != "exited":
return public.returnMsg(False, "Stop failing!")
dp.write_log("Stop container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Stop success!")
except docker.errors.APIError as e:
if "is already paused" in str(e):
return public.returnMsg(False,"The container has been suspended!")
if "No such container" in str(e):
return public.returnMsg(True, "The container has been stopped and deleted because the container has the option to automatically delete after stopping!")
return public.returnMsg(False,"Stop failing!{}".format(e))
def start(self,args):
"""
:param url
:param id
:param args:
:return:
"""
try:
args.act = "start"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Start the container [{}] successfully!".format(data['name']))
return public.returnMsg(True, "Started successfully!")
except docker.errors.APIError as e:
if "cannot start a paused container, try unpause instead" in str(e):
return self.unpause(args)
def pause(self,args):
"""
Pauses all processes within this container.
:param url
:param id
:param args:
:return:
"""
try:
args.act = "pause"
data = self.set_container_status(args)
if data['status'] != "paused":
return public.returnMsg(False, "Container pause failed!")
dp.write_log("Suspended container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Container paused successfully!")
except docker.errors.APIError as e:
if "is already paused" in str(e):
return public.returnMsg(False,"The container has been suspended!")
if "is not running" in str(e):
return public.returnMsg(False, "The container is not started and cannot be paused!")
if "is not paused" in str(e):
return public.returnMsg(False, "The container has not been suspended!")
return str(e)
def unpause(self,args):
"""
unPauses all processes within this container.
:param url
:param id
:param args:
:return:
"""
try:
args.act = "unpause"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Unpausing the container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Container unpause succeeded!")
except docker.errors.APIError as e:
if "is already paused" in str(e):
return public.returnMsg(False,"The container has been suspended!")
if "is not running" in str(e):
return public.returnMsg(False, "The container is not started and cannot be paused!")
if "is not paused" in str(e):
return public.returnMsg(False, "The container has not been suspended!")
return str(e)
def reload(self,args):
"""
Load this object from the server again and update attrs with the new data.
:param url
:param id
:param args:
:return:
"""
args.act = "reload"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Reloading container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Container reload succeeded!")
def restart(self,args):
"""
Restart this container. Similar to the docker restart command.
:param url
:param id
:param args:
:return:
"""
args.act = "restart"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Restarting the container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "The container restarted successfully!")
def get_container_ip(self,container_networks):
data = list()
for network in container_networks:
data.append(container_networks[network]['IPAddress'])
return data
def get_container_path(self,detail):
import os
if not "GraphDriver" in detail:
return False
if "Data" not in detail["GraphDriver"]:
return False
if "MergedDir" not in detail["GraphDriver"]["Data"]:
return False
path = detail["GraphDriver"]["Data"]["MergedDir"]
if not os.path.exists(path):
return ""
return path
# 获取容器列表所需的外部数据
def get_other_data_for_container_list(self,args):
import projectModel.bt_docker.dk_image as di
import projectModel.bt_docker.dk_volume as dv
import projectModel.bt_docker.dk_compose as dc
import projectModel.bt_docker.dk_setup as ds
# 获取镜像列表
images = di.main().image_list(args)
if images['status']:
images = images['msg']['images_list']
else:
images = list()
# 获取卷列表
volumes = dv.main().get_volume_list(args)
if volumes['status']:
volumes = volumes['msg']['volume']
else:
volumes = list()
# 获取模板列表
template = dc.main().template_list(args)
if template['status']:
template = template['msg']['template']
else:
template = list()
online_cpus = dp.get_cpu_count()
mem_total = dp.get_mem_info()
docker_setup = ds.main()
return {
"images":images,
"volumes":volumes,
"template":template,
"online_cpus":online_cpus,
"mem_total":mem_total,
"installed":docker_setup.check_docker_program(),
"service_status":docker_setup.get_service_status()
}
# 获取容器列表
def get_list(self,args):
"""
:param url
:return:
"""
# 判断docker是否安装
import projectModel.bt_docker.dk_setup as ds
data = self.get_other_data_for_container_list(args)
if not ds.main().check_docker_program():
data['container_list'] = list()
return public.returnMsg(True,data)
client = self.docker_client(args.url)
if not client:
return public.returnMsg(True,data)
containers = client.containers
attr_list = self.get_container_attr(containers)
# data = self.get_other_data_for_container_list(args)
container_detail = list()
for attr in attr_list:
cpu_usage = dp.sql("cpu_stats").where("container_id=?",(attr["Id"],)).select()
if cpu_usage and isinstance(cpu_usage,list):
cpu_usage = cpu_usage[-1]['cpu_usage']
else:
cpu_usage = "0.0"
tmp = {
"id": attr["Id"],
"name": attr['Name'].replace("/",""),
"status": attr["State"]["Status"],
"image": attr["Config"]["Image"],
"time": attr["Created"],
"merged": self.get_container_path(attr),
"ip": self.get_container_ip(attr["NetworkSettings"]['Networks']),
"ports": attr["NetworkSettings"]["Ports"],
"detail": attr,
"cpu_usage":cpu_usage if attr["State"]["Status"] == "running" else ""
}
container_detail.append(tmp)
data['container_list'] = container_detail
return public.returnMsg(True,data)
# 获取容器的attr
def get_container_attr(self,containers):
c_list = containers.list(all=True)
return [container_info.attrs for container_info in c_list]
# 获取容器日志
def get_logs(self,args):
"""
:param url
:param id
:param args:
:return:
"""
try:
container = self.docker_client(args.url).containers.get(args.id)
res = container.logs().decode()
return public.returnMsg(True,res)
except docker.errors.APIError as e:
if "configured logging driver does not support reading" in str(e):
return public.returnMsg(False,"The container has no log files!")
# 登录容器
# 获取容器配置文件
+42
View File
@@ -0,0 +1,42 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: zouhw <zhw@bt.cn>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import public #line:1
import projectModel .bt_docker .dk_public as dp #line:2
class main :#line:4
def get_list (O0000O0OOO0OO0O00 ,args =None ):#line:7
OO000O0OOO00OO00O =dp .sql ("hosts").select ()#line:8
for OO0000O00OOO0OOO0 in OO000O0OOO00OO00O :#line:9
if dp .docker_client (OO0000O00OOO0OOO0 ['url']):#line:10
OO0000O00OOO0OOO0 ['status']=True #line:11
else :#line:12
OO0000O00OOO0OOO0 ['status']=False #line:13
return OO000O0OOO00OO00O #line:14
def add (O0OO00O0O000O0000 ,O00O0000O00000O0O ):#line:17
""#line:22
import time #line:23
O00OO0OOO000OO0O0 =O0OO00O0O000O0000 .get_list ()#line:24
for O0O0OO00O00OOOOOO in O00OO0OOO000OO0O0 :#line:25
if O0O0OO00O00OOOOOO ['url']==O00O0000O00000O0O .url :#line:26
return public .returnMsg (False ,"This host already exists!")#line:27
if not dp .docker_client (O00O0000O00000O0O .url ):#line:29
return public .returnMsg (False ,"Failed to connect to the server, please check if docker has been started!")#line:30
O0O0OO0OOOOO0OOO0 ={"url":O00O0000O00000O0O .url ,"remark":O00O0000O00000O0O .remark ,"time":int (time .time ())}#line:35
dp .write_log ("Add host [{}] successful!".format (O00O0000O00000O0O .url ))#line:36
dp .sql ('hosts').insert (O0O0OO0OOOOO0OOO0 )#line:37
return public .returnMsg (True ,"Add docker host successfully!")#line:38
def delete (O0OOOO0000000O00O ,O0O0O0O000000OOOO ):#line:40
""#line:44
OOO00OO0OO0OOO00O =dp .sql ('hosts').where ('id=?',O0O0O0O000000OOOO (O0O0O0O000000OOOO .id ,)).find ()#line:45
dp .sql ('hosts').delete (id =O0O0O0O000000OOOO .id )#line:46
dp .write_log ("Delete host [{}] succeeded!".format (OOO00OO0OO0OOO00O ['url']))#line:47
return public .returnMsg (True ,"Delete host successfully!")
+226
View File
@@ -0,0 +1,226 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: zouhw <zhw@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# Docker模型
#------------------------------
import os #line:13
import public #line:14
import docker .errors #line:15
import projectModel .bt_docker .dk_public as dp #line:16
class main :#line:18
__O000O000OOOO0OO00 ='/tmp/dockertmp.log'#line:19
def docker_client (O0O0OOO00O0000O0O ,OOOO0O000O000OO00 ):#line:20
import projectModel .bt_docker .dk_public as dp #line:21
return dp .docker_client (OOOO0O000O000OO00 )#line:22
def save (O0O000OO0O0O00OO0 ,OOOO0000OO0O0OOOO ):#line:25
""#line:33
try :#line:34
if "tar"in OOOO0000OO0O0OOOO .name :#line:35
O00OOOO00OO0OO0OO ='{}/{}'.format (OOOO0000OO0O0OOOO .path ,OOOO0000OO0O0OOOO .name )#line:36
else :#line:37
O00OOOO00OO0OO0OO ='{}/{}.tar'.format (OOOO0000OO0O0OOOO .path ,OOOO0000OO0O0OOOO .name )#line:38
if not os .path .exists (OOOO0000OO0O0OOOO .path ):#line:39
os .makedirs (OOOO0000OO0O0OOOO .path )#line:40
public .writeFile (O00OOOO00OO0OO0OO ,"")#line:41
O0000O0OOOOO000OO =open (O00OOOO00OO0OO0OO ,'wb')#line:42
O000OOOOOO00OOO00 =O0O000OO0O0O00OO0 .docker_client (OOOO0000OO0O0OOOO .url ).images .get (OOOO0000OO0O0OOOO .id )#line:43
for OO0O0O0O0O0OO0OOO in O000OOOOOO00OOO00 .save (named =True ):#line:44
O0000O0OOOOO000OO .write (OO0O0O0O0O0OO0OOO )#line:45
O0000O0OOOOO000OO .close ()#line:46
dp .write_log ("Image [{}] exported to [{}] successfully!".format (OOOO0000OO0O0OOOO .id ,O00OOOO00OO0OO0OO ))#line:47
return public .returnMsg (True ,"Saved successfully to: {}".format (O00OOOO00OO0OO0OO ))#line:48
except docker .errors .APIError as OOOO000OOO0O000O0 :#line:49
if "empty export - not implemented"in str (OOOO000OOO0O000O0 ):#line:50
return public .returnMsg (False ,"Empty images cannot be exported!")#line:51
return public .get_error_info ()#line:52
def load (OOO000O000OOOOOO0 ,O00OO0OOOO000O000 ):#line:55
""#line:60
OOOOO000O000O0000 =OOO000O000OOOOOO0 .docker_client (O00OO0OOOO000O000 .url ).images #line:61
with open (O00OO0OOOO000O000 .path ,'rb')as OO00O0O00000000O0 :#line:62
OOOOO000O000O0000 .load (OO00O0O00000000O0 )#line:65
dp .write_log ("Image [{}] imported successfully!".format (O00OO0OOOO000O000 .path ))#line:66
return public .returnMsg (True ,"Import successful! {}".format (O00OO0OOOO000O000 .path ))#line:67
def image_list (OO0O0OOO0OOOOO00O ,OO0O0OO000O0OOOOO ):#line:70
""#line:75
import projectModel .bt_docker .dk_registry as dr #line:76
import projectModel .bt_docker .dk_setup as ds #line:77
OOO00000OOO00000O =list ()#line:78
OO0OO00OOOO0O0O00 =OO0O0OOO0OOOOO00O .docker_client (OO0O0OO000O0OOOOO .url )#line:79
OO0OOO00OOO0O0OO0 =ds .main ()#line:80
O0OO0000O0O0O00O0 =OO0OOO00OOO0O0OO0 .check_docker_program ()#line:81
O00OOO0000OO0O0OO =OO0OOO00OOO0O0OO0 .get_service_status ()#line:82
if not OO0OO00OOOO0O0O00 :#line:83
OOO00000OOO00000O ={"images_list":[],"registry_list":[],"installed":O0OO0000O0O0O00O0 ,"service_status":O00OOO0000OO0O0OO }#line:89
return public .returnMsg (True ,OOO00000OOO00000O )#line:90
OOOO000O0OO00O00O =OO0OO00OOOO0O0O00 .images #line:91
OO0OO0OO000OOO000 =OO0O0OOO0OOOOO00O .get_image_attr (OOOO000O0OO00O00O )#line:92
OO000O0OO000O0000 =dr .main ().registry_list (OO0O0OO000O0OOOOO )#line:93
if OO000O0OO000O0000 ['status']:#line:94
OO000O0OO000O0000 =OO000O0OO000O0000 ['msg']['registry']#line:95
else :#line:96
OO000O0OO000O0000 =[]#line:97
for OO0O0O000000O00O0 in OO0OO0OO000OOO000 :#line:98
if len (OO0O0O000000O00O0 ['RepoTags'])==1 :#line:99
O0O0O0O0000OO0O00 ={"id":OO0O0O000000O00O0 ["Id"],"tags":OO0O0O000000O00O0 ["RepoTags"],"time":OO0O0O000000O00O0 ["Created"],"name":OO0O0O000000O00O0 ['RepoTags'][0 ],"size":OO0O0O000000O00O0 ["Size"],"detail":OO0O0O000000O00O0 }#line:107
OOO00000OOO00000O .append (O0O0O0O0000OO0O00 )#line:108
elif len (OO0O0O000000O00O0 ['RepoTags'])>1 :#line:109
for O0OO0O0O00O000O0O in range (len (OO0O0O000000O00O0 ['RepoTags'])):#line:110
O0O0O0O0000OO0O00 ={"id":OO0O0O000000O00O0 ["Id"],"tags":OO0O0O000000O00O0 ["RepoTags"],"time":OO0O0O000000O00O0 ["Created"],"name":OO0O0O000000O00O0 ['RepoTags'][O0OO0O0O00O000O0O ],"size":OO0O0O000000O00O0 ["Size"],"detail":OO0O0O000000O00O0 }#line:118
OOO00000OOO00000O .append (O0O0O0O0000OO0O00 )#line:119
elif not OO0O0O000000O00O0 ['RepoTags']:#line:120
O0O0O0O0000OO0O00 ={"id":OO0O0O000000O00O0 ["Id"],"tags":OO0O0O000000O00O0 ["RepoTags"],"time":OO0O0O000000O00O0 ["Created"],"name":OO0O0O000000O00O0 ["Id"],"size":OO0O0O000000O00O0 ["Size"],"detail":OO0O0O000000O00O0 }#line:128
OOO00000OOO00000O .append (O0O0O0O0000OO0O00 )#line:129
OOO00000OOO00000O ={"images_list":OOO00000OOO00000O ,"registry_list":OO000O0OO000O0000 ,"installed":O0OO0000O0O0O00O0 ,"service_status":O00OOO0000OO0O0OO }#line:135
return public .returnMsg (True ,OOO00000OOO00000O )#line:136
def get_image_attr (O00OOOO0O00OOO00O ,O000O00OOOOOOO000 ):#line:138
OOO00O0O00OO00OO0 =O000O00OOOOOOO000 .list ()#line:139
return [OO0000OOO0OO0O0OO .attrs for OO0000OOO0OO0O0OO in OOO00O0O00OO00OO0 ]#line:140
def get_logs (OO000O0O000000O0O ,OOOO000000O000O0O ):#line:142
import files #line:143
O0O0O0O0O0OOOOOO0 =OOOO000000O000O0O .logs_file #line:144
return public .returnMsg (True ,files .files ().GetLastLine (O0O0O0O0O0OOOOOO0 ,20 ))#line:145
def build (OOOO0OOO00O000O0O ,O00O0OOO00O00O0O0 ):#line:148
""#line:156
public .writeFile (OOOO0OOO00O000O0O .__O000O000OOOO0OO00 ,"Start building images!")#line:157
public .writeFile ('/tmp/dockertmp.log',"Start building the mirror")#line:158
if not hasattr (O00O0OOO00O00O0O0 ,"pull"):#line:159
O00O0OOO00O00O0O0 .pull =False #line:160
if hasattr (O00O0OOO00O00O0O0 ,"data")and O00O0OOO00O00O0O0 .data :#line:161
O00O0OOO00O00O0O0 .path ="/tmp/dockerfile"#line:162
public .writeFile (O00O0OOO00O00O0O0 .path ,O00O0OOO00O00O0O0 .data )#line:163
with open (O00O0OOO00O00O0O0 .path ,'rb')as OOO000000O000OOOO :#line:164
OO000OOOO0O0OO0O0 ,O000OOO0O0OOOO00O =OOOO0OOO00O000O0O .docker_client (O00O0OOO00O00O0O0 .url ).images .build (pull =True if O00O0OOO00O00O0O0 .pull =="1"else False ,fileobj =OOO000000O000OOOO ,tag =O00O0OOO00O00O0O0 .tag )#line:169
os .remove (O00O0OOO00O00O0O0 .path )#line:170
else :#line:171
if not os .path .isdir (O00O0OOO00O00O0O0 .path ):#line:172
O00O0OOO00O00O0O0 .path ='/'.join (O00O0OOO00O00O0O0 .path .split ('/')[:-1 ])#line:173
OO000OOOO0O0OO0O0 ,O000OOO0O0OOOO00O =OOOO0OOO00O000O0O .docker_client (O00O0OOO00O00O0O0 .url ).images .build (pull =True if O00O0OOO00O00O0O0 .pull =="1"else False ,path =O00O0OOO00O00O0O0 .path ,tag =O00O0OOO00O00O0O0 .tag )#line:178
dp .log_docker (O000OOO0O0OOOO00O ,"Docker build tasks")#line:180
dp .write_log ("Building image [{}] succeeded!".format (O00O0OOO00O00O0O0 .tag ))#line:181
return public .returnMsg (True ,"Building image successfully!")#line:182
def remove (OOOOO00000OO0OO0O ,O0000O000000OOO00 ):#line:185
""#line:193
try :#line:194
OOOOO00000OO0OO0O .docker_client (O0000O000000OOO00 .url ).images .remove (O0000O000000OOO00 .name )#line:195
dp .write_log ("Delete mirror【{}】successful!".format (O0000O000000OOO00 .name ))#line:196
return public .returnMsg (True ,"Mirror deleted successfully!")#line:197
except docker .errors .ImageNotFound as OOO00OOOO00OOO0O0 :#line:198
return public .returnMsg (False ,"Failed to delete the mirror, maybe the mirror does not exist!")#line:199
except docker .errors .APIError as OOO00OOOO00OOO0O0 :#line:200
if "image is referenced in multiple repositories"in str (OOO00OOOO00OOO0O0 ):#line:201
return public .returnMsg (False ,"The image ID is used in multiple images, please check [Force Delete]!")#line:202
if "using its referenced image"in str (OOO00OOOO00OOO0O0 ):#line:203
return public .returnMsg (False ,"The image is in use, please delete the container and then delete it!")#line:204
return public .returnMsg (False ,"Delete mirror failed!<br> {}".format (OOO00OOOO00OOO0O0 ))#line:205
def pull_from_some_registry (O00OOOOOOOOO00000 ,O0O0O0OOO0O0OO0O0 ):#line:208
""#line:215
import projectModel .bt_docker .dk_registry as br #line:216
O00O00OO00OOOOO00 =br .main ().registry_info (O0O0O0OOO0O0OO0O0 .name )#line:217
O0000OO00OO0OO000 =br .main ().login (O0O0O0OOO0O0OO0O0 .url ,O00O00OO00OOOOO00 ['url'],O00O00OO00OOOOO00 ['username'],O00O00OO00OOOOO00 ['password'])['status']#line:218
if not O0000OO00OO0OO000 :#line:219
return O0000OO00OO0OO000 #line:220
O0O0O0OOO0O0OO0O0 .username =O00O00OO00OOOOO00 ['username']#line:221
O0O0O0OOO0O0OO0O0 .password =O00O00OO00OOOOO00 ['password']#line:222
O0O0O0OOO0O0OO0O0 .registry =O00O00OO00OOOOO00 ['url']#line:223
O0O0O0OOO0O0OO0O0 .namespace =O00O00OO00OOOOO00 ['namespace']#line:224
return O00OOOOOOOOO00000 .pull (O0O0O0OOO0O0OO0O0 )#line:225
def push (OO0O0OOO0O0O00O0O ,OO000OOO0OO000O0O ):#line:228
""#line:236
if "/"in OO000OOO0OO000O0O .tag :#line:237
return public .returnMsg (False ,"The pushed image name cannot contain the symbol [/] , please use the following format: image:v1 (image_name:version_number)")#line:238
if ":"not in OO000OOO0OO000O0O .tag :#line:239
return public .returnMsg (False ,"The pushed image name must contain the symbol [ : ] , please use the following format: image:v1 (image_name:version_number)")#line:240
public .writeFile (OO0O0OOO0O0O00O0O .__O000O000OOOO0OO00 ,"Start pushing mirrors!\n")#line:241
import projectModel .bt_docker .dk_registry as br #line:242
O00O0O0O0O0O0O000 =br .main ().registry_info (OO000OOO0OO000O0O .name )#line:243
if OO000OOO0OO000O0O .name =="docker official"and O00O0O0O0O0O0O000 ['url']=="docker.io":#line:244
public .writeFile (OO0O0OOO0O0O00O0O .__O000O000OOOO0OO00 ,"The image cannot be pushed to the Docker public repository!\n")#line:245
return public .returnMsg (False ,"Unable to push to Docker public repo!")#line:246
O0O0000OOO0OOO00O =br .main ().login (OO000OOO0OO000O0O .url ,O00O0O0O0O0O0O000 ['url'],O00O0O0O0O0O0O000 ['username'],O00O0O0O0O0O0O000 ['password'])['status']#line:247
O00OOO0O0O0O0O0O0 =OO000OOO0OO000O0O .tag #line:248
if not O0O0000OOO0OOO00O :#line:249
return O0O0000OOO0OOO00O #line:250
OO00O0000000OO000 ={"username":O00O0O0O0O0O0O000 ['username'],"password":O00O0O0O0O0O0O000 ['password'],"registry":O00O0O0O0O0O0O000 ['url']}#line:254
if ":"not in O00OOO0O0O0O0O0O0 :#line:256
O00OOO0O0O0O0O0O0 ="{}:latest".format (O00OOO0O0O0O0O0O0 )#line:257
OOOO000OOOOO0O00O =O00O0O0O0O0O0O000 ['url']#line:258
O0OO00O0OO000O0OO ="{}/{}/{}".format (OOOO000OOOOO0O00O ,O00O0O0O0O0O0O000 ['namespace'],OO000OOO0OO000O0O .tag )#line:259
OO0O0OOO0O0O00O0O .tag (OO000OOO0OO000O0O .url ,OO000OOO0OO000O0O .id ,O0OO00O0OO000O0OO )#line:260
O000OOOO00O0OO000 =OO0O0OOO0O0O00O0O .docker_client (OO000OOO0OO000O0O .url ).images .push (repository =O0OO00O0OO000O0OO .split (":")[0 ],tag =O00OOO0O0O0O0O0O0 .split (":")[-1 ],auth_config =OO00O0000000OO000 ,stream =True )#line:266
dp .log_docker (O000OOOO00O0OO000 ,"Image push task")#line:267
OO000OOO0OO000O0O .name =O0OO00O0OO000O0OO #line:269
OO0O0OOO0O0O00O0O .remove (OO000OOO0OO000O0O )#line:270
dp .write_log ("The image [{}] was pushed successfully!".format (O0OO00O0OO000O0OO ))#line:271
return public .returnMsg (True ,"推送成功!{}".format (str (O000OOOO00O0OO000 )))#line:272
def tag (OOO0OO00OO0OO0O00 ,O000O0OOO00O000OO ,OOOO0O0OOO0000O00 ,OO0O0000OO00O00O0 ):#line:274
""#line:281
OOO000O000000OOO0 =OO0O0000OO00O00O0 .split (":")[0 ]#line:282
O00OOO0OOOO0O000O =OO0O0000OO00O00O0 .split (":")[1 ]#line:283
OOO0OO00OO0OO0O00 .docker_client (O000O0OOO00O000OO ).images .get (OOOO0O0OOO0000O00 ).tag (repository =OOO000O000000OOO0 ,tag =O00OOO0OOOO0O000O )#line:287
return public .returnMsg (True ,"Set successfully")#line:288
def pull (O0O0O0000OOOOOOO0 ,OO00OOO00OO000O00 ):#line:290
""#line:299
public .writeFile (O0O0O0000OOOOOOO0 .__O000O000OOOO0OO00 ,"Start pulling images!")#line:300
import docker .errors #line:301
try :#line:302
if ':'not in OO00OOO00OO000O00 .image :#line:303
OO00OOO00OO000O00 .image ='{}:latest'.format (OO00OOO00OO000O00 .image )#line:304
O0O00OO000O000OO0 ={"username":OO00OOO00OO000O00 .username ,"password":OO00OOO00OO000O00 .password ,"registry":OO00OOO00OO000O00 .registry if OO00OOO00OO000O00 .registry else None }if OO00OOO00OO000O00 .username else None #line:308
if not hasattr (OO00OOO00OO000O00 ,"tag"):#line:309
OO00OOO00OO000O00 .tag =OO00OOO00OO000O00 .image .split (":")[-1 ]#line:310
if OO00OOO00OO000O00 .registry !="docker.io":#line:311
OO00OOO00OO000O00 .image ="{}/{}/{}".format (OO00OOO00OO000O00 .registry ,OO00OOO00OO000O00 .namespace ,OO00OOO00OO000O00 .image )#line:312
OOO0OOOO000O000O0 =dp .docker_client_low (OO00OOO00OO000O00 .url ).pull (repository =OO00OOO00OO000O00 .image ,auth_config =O0O00OO000O000OO0 ,tag =OO00OOO00OO000O00 .tag ,stream =True )#line:318
dp .log_docker (OOO0OOOO000O000O0 ,"Image pull task")#line:319
if OOO0OOOO000O000O0 :#line:320
dp .write_log ("The image [{}:{}] was pulled successfully!".format (OO00OOO00OO000O00 .image ,OO00OOO00OO000O00 .tag ))#line:321
return public .returnMsg (True ,'Pulling the image succeeded.')#line:322
else :#line:323
return public .returnMsg (False ,'There may not be this image.')#line:324
except docker .errors .ImageNotFound as O0O0OO00OO000O0O0 :#line:325
if "pull access denied for"in str (O0O0OO00OO000O0O0 ):#line:326
return public .returnMsg (False ,"The pull failed, the image is a private image, you need to enter the account password of dockerhub!")#line:327
return public .returnMsg (False ,"Pull failed<br><br>reasons: {}".format (O0O0OO00OO000O0O0 ))#line:329
except docker .errors .NotFound as O0O0OO00OO000O0O0 :#line:331
if "not found: manifest unknown"in str (O0O0OO00OO000O0O0 ):#line:332
return public .returnMsg (False ,"The pull failed, the repository does not have the mirror!")#line:333
return public .returnMsg (False ,"Pull failed<br><br>reason:{}".format (O0O0OO00OO000O0O0 ))#line:334
except docker .errors .APIError as O0O0OO00OO000O0O0 :#line:335
if "invalid tag format"in str (O0O0OO00OO000O0O0 ):#line:336
return public .returnMsg (False ,"The pull failed, the image format is wrong, the format should be: nginx:v 1!")#line:337
return public .returnMsg (False ,"Pull failed!{}".format (O0O0OO00OO000O0O0 ))#line:338
def pull_high_api (OO0O0O0O000O000O0 ,O0000O0O000O0O0O0 ):#line:342
""#line:351
import docker .errors #line:352
try :#line:353
if ':'not in O0000O0O000O0O0O0 .image :#line:354
O0000O0O000O0O0O0 .image ='{}:latest'.format (O0000O0O000O0O0O0 .image )#line:355
O0O0O00O0O0O00000 ={"username":O0000O0O000O0O0O0 .username ,"password":O0000O0O000O0O0O0 .password ,"registry":O0000O0O000O0O0O0 .registry if O0000O0O000O0O0O0 .registry else None }if O0000O0O000O0O0O0 .username else None #line:359
if O0000O0O000O0O0O0 .registry !="docker.io":#line:361
O0000O0O000O0O0O0 .image ="{}/{}/{}".format (O0000O0O000O0O0O0 .registry ,O0000O0O000O0O0O0 .namespace ,O0000O0O000O0O0O0 .image )#line:362
OOOOOOOO0O0OOOO0O =OO0O0O0O000O000O0 .docker_client (O0000O0O000O0O0O0 .url ).images .pull (repository =O0000O0O000O0O0O0 .image ,auth_config =O0O0O00O0O0O00000 ,)#line:366
if OOOOOOOO0O0OOOO0O :#line:367
return public .returnMsg (True ,'Pulling the image succeeded.')#line:368
else :#line:369
return public .returnMsg (False ,'There may not be this mirror.')#line:370
except docker .errors .ImageNotFound as O0O0O0OOO0O0OO0OO :#line:371
if "pull access denied for"in str (O0O0O0OOO0O0OO0OO ):#line:372
return public .returnMsg (False ,"The pull failed, the image is a private image, you need to enter the account password of dockerhub!")#line:373
return public .returnMsg (False ,"Pull failed<br><br>reason: {}".format (O0O0O0OOO0O0OO0OO ))#line:374
def image_for_host (O00O0O0O000OOO0O0 ,O000OO0OOOOO000O0 ):#line:376
""#line:381
O00O000OO0OO000OO =O00O0O0O000OOO0O0 .image_list (O000OO0OOOOO000O0 )#line:382
if not O00O000OO0OO000OO ['status']:#line:383
return O00O000OO0OO000OO #line:384
OO000O0000O000000 =len (O00O000OO0OO000OO ['msg']['images_list'])#line:385
O0OOOO0OO000OO0OO =0 #line:386
for OO00OOO0000OO0O00 in O00O000OO0OO000OO ['msg']['images_list']:#line:387
O0OOOO0OO000OO0OO +=OO00OOO0000OO0O00 ['size']#line:388
return public .returnMsg (True ,{'num':OO000O0000O000000 ,'size':O0OOOO0OO000OO0OO })
@@ -0,0 +1,92 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: zouhw <zhw@bt.cn>
#-------------------------------------------------------------------
#------------------------------
# Docker模型
#------------------------------
import sys #line:1
import threading #line:2
sys .path .insert (0 ,"/www/server/panel/class/")#line:3
sys .path .insert (1 ,"/www/server/panel/")#line:4
import projectModel .bt_docker .dk_public as dp #line:5
import projectModel .bt_docker .dk_container as dc #line:6
import projectModel .bt_docker .dk_status as ds #line:7
import projectModel .bt_docker .dk_image as di #line:8
import public #line:9
import time #line:11
class main :#line:12
__OOOOO0OOO0OO00O00 =None #line:14
__OOOOOO0OO0OOOOOO0 =86400 #line:15
def __init__ (O00O000000O0OO0OO ,OO00OO00O0O00OO0O ):#line:17
if not OO00OO00O0O00OO0O :#line:18
O00O000000O0OO0OO .__OOOOO0OOO0OO00O00 =30 #line:19
else :#line:20
O00O000000O0OO0OO .__OOOOO0OOO0OO00O00 =OO00OO00O0O00OO0O #line:21
def docker_client (OOOOO000OO0O00OO0 ,OO00OOO0000OO0000 ):#line:23
return dp .docker_client (OO00OOO0000OO0000 )#line:24
def get_all_host_stats (O0000O0000OO0O000 ,O000O0O00OO00O00O ):#line:26
""#line:31
OO000O0OO0O0O0000 =dp .sql ('hosts').select ()#line:32
for O00O0OO00O0O000O0 in OO000O0OO0O0O0000 :#line:33
O0OO00O0O00O0000O =threading .Thread (target =O000O0O00OO00O00O ,args =(O00O0OO00O0O000O0 ,))#line:34
O0OO00O0O00O0000O .setDaemon (True )#line:35
O0OO00O0O00O0000O .start ()#line:36
def container_status_for_all_hosts (OO0O00OO0O0OOOO0O ,OOOOOOO000OO0OOO0 ):#line:39
""#line:44
O000OO000O0O00O00 =public .to_dict_obj ({})#line:46
O000OO000O0O00O00 .url =OOOOOOO000OO0OOO0 ['url']#line:47
O0O0O0O000O00O0OO =dc .main ().get_list (O000OO000O0O00O00 )['msg']#line:48
for O00OOOOOOOO00OOO0 in O0O0O0O000O00O0OO ['container_list']:#line:49
O000OO000O0O00O00 .id =O00OOOOOOOO00OOO0 ['id']#line:50
O000OO000O0O00O00 .write =1 #line:51
O000OO000O0O00O00 .save_date =OO0O00OO0O0OOOO0O .__OOOOO0OOO0OO00O00 #line:52
ds .main ().stats (O000OO000O0O00O00 )#line:53
def container_count (OO0000O0O0O0OOO00 ):#line:57
O0O000O0O000O000O =dp .sql ('hosts').select ()#line:59
O0O00O00O0O00OOO0 =0 #line:60
for O0O0O00OO0OO0O0O0 in O0O000O0O000O000O :#line:61
O00OOOO00OO0OOO00 =public .to_dict_obj ({})#line:62
O00OOOO00OO0OOO00 .url =O0O0O00OO0OO0O0O0 ['url']#line:63
OO00OO00O0O00O00O =dc .main ().get_list (O00OOOO00OO0OOO00 )['msg']#line:64
O0O00O00O0O00OOO0 +=len (OO00OO00O0O00O00O )#line:65
O0000000000000000 ={"time":int (time .time ()),"container_count":O0O00O00O0O00OOO0 }#line:69
OOOOO0OO00OOO0O0O =time .time ()-(OO0000O0O0O0OOO00 .__OOOOO0OOO0OO00O00 *OO0000O0O0O0OOO00 .__OOOOOO0OO0OOOOOO0 )#line:70
dp .sql ("container_count").where ("time<?",(OOOOO0OO00OOO0O0O ,)).delete ()#line:71
dp .sql ("container_count").insert (O0000000000000000 )#line:72
def image_for_all_host (OO0O00O000OO00O0O ):#line:75
OO000OO0OOO00OO0O =dp .sql ('hosts').select ()#line:77
OOO0OOO00O000OOOO =0 #line:78
O0O00000OO0O0O000 =0 #line:79
for O00OOO00O0000O00O in OO000OO0OOO00OO0O :#line:80
O0O00O0OOOO0O00O0 =public .to_dict_obj ({})#line:81
O0O00O0OOOO0O00O0 .url =O00OOO00O0000O00O ['url']#line:82
OO00O0000O0000O00 =di .main ().image_for_host (O0O00O0OOOO0O00O0 )#line:83
if not OO00O0000O0000O00 ['status']:#line:84
continue #line:85
print (OO00O0000O0000O00 )#line:86
OOO0OOO00O000OOOO +=OO00O0000O0000O00 ['msg']['num']#line:87
O0O00000OO0O0O000 +=OO00O0000O0000O00 ['msg']['size']#line:88
O0OO0O00O000000OO ={"time":int (time .time ()),"num":OOO0OOO00O000OOOO ,"size":int (O0O00000OO0O0O000 )}#line:93
O0O000O00OO0OO0O0 =time .time ()-(OO0O00O000OO00O0O .__OOOOO0OOO0OO00O00 *OO0O00O000OO00O0O .__OOOOOO0OO0OOOOOO0 )#line:94
dp .sql ("image_infos").where ("time<?",(O0O000O00OO0OO0O0 ,)).delete ()#line:95
dp .sql ("image_infos").insert (O0OO0O00O000000OO )#line:96
def monitor ():#line:99
while True :#line:102
O0OOO000OOO0OOO00 =dp .docker_conf ()['SAVE']#line:103
O000OO0O0O0OO0000 =main (O0OOO000OOO0OOO00 )#line:104
O000OO0O0O0OO0000 .get_all_host_stats (O000OO0O0O0OO0000 .container_status_for_all_hosts )#line:105
O0000OO0000000O00 =threading .Thread (target =O000OO0O0O0OO0000 .container_count )#line:107
O0000OO0000000O00 .setDaemon (True )#line:108
O0000OO0000000O00 .start ()#line:109
O0000OO0000000O00 =threading .Thread (target =O000OO0O0O0OO0000 .image_for_all_host )#line:111
O0000OO0000000O00 .setDaemon (True )#line:112
O0000OO0000000O00 .start ()#line:113
time .sleep (60 )#line:114
if __name__ =="__main__":#line:119
monitor ()

Some files were not shown because too many files have changed in this diff Show More