mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
update to 6.8.37
This commit is contained in:
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Regular → Executable
+456
-280
File diff suppressed because it is too large
Load Diff
Regular → Executable
+421
-114
@@ -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,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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+90
-11
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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 failure,The 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)
|
||||
@@ -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 failure,The 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,"")
|
||||
@@ -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)
|
||||
@@ -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 []
|
||||
@@ -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 failure,The 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)
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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 对象存储 ************************
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
@@ -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('\\','/')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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, '已添加到上传队列.')
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
DelAcceptPort#coding: utf-8
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | 宝塔Linux面板 x5
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
+31
-32
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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'
|
||||
|
||||
@@ -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('<', '<').replace('>', '>') + i.replace('<', '<').replace('>', '>')
|
||||
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)
|
||||
@@ -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 ""
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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 module,please 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 module,please 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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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、服务器名称
|
||||
2、IP地址
|
||||
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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+58
-3
@@ -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'))
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
Executable
+99
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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, ):
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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'
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+93
-3
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
#获取进度
|
||||
|
||||
@@ -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)
|
||||
@@ -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!")
|
||||
|
||||
|
||||
|
||||
# 登录容器
|
||||
|
||||
|
||||
# 获取容器配置文件
|
||||
@@ -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!")
|
||||
@@ -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
Reference in New Issue
Block a user