mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
2082 lines
88 KiB
Python
2082 lines
88 KiB
Python
#coding: utf-8
|
||
# +-------------------------------------------------------------------
|
||
# | aaPanel
|
||
# +-------------------------------------------------------------------
|
||
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
|
||
# +-------------------------------------------------------------------
|
||
# | Author: hwliang <hwl@aapanel.com>
|
||
# +-------------------------------------------------------------------
|
||
import sys,os,re,time,json
|
||
from datetime import datetime, timedelta
|
||
|
||
import requests
|
||
|
||
if not 'class/' in sys.path:
|
||
sys.path.insert(0,'class/')
|
||
import db,public,panelMysql
|
||
import json
|
||
import public
|
||
from public.validate import Param
|
||
|
||
try:
|
||
from BTPanel import cache
|
||
except:
|
||
cache = None
|
||
|
||
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, public.lang("Clear sorting successfully"))
|
||
|
||
|
||
'''
|
||
* 设置备注信息
|
||
* @param String _GET['tab'] 数据库表名
|
||
* @param String _GET['id'] 条件ID
|
||
* @return Bool
|
||
'''
|
||
def setPs(self,get):
|
||
# 校验参数
|
||
try:
|
||
get.validate([
|
||
Param('table').Require().String(),
|
||
Param('ps').Require().String(),
|
||
Param('id').Require().Integer(),
|
||
], [
|
||
public.validate.trim_filter(),
|
||
])
|
||
except Exception as ex:
|
||
public.print_log("error info: {}".format(ex))
|
||
return public.return_message(-1, 0, str(ex))
|
||
|
||
id = get.id
|
||
get.ps = public.xssencode2(get.ps)
|
||
if public.M(get.table).where("id=?",(id,)).setField('ps',get.ps):
|
||
# public.get_msg_gettext(True, public.lang("Setup successfully!"))
|
||
return public.return_message(0, 0, public.lang("Setup successfully"))
|
||
# public.get_msg_gettext(False, public.lang("Failed to modify"))
|
||
return public.return_message(-1, 0, public.lang("Failed to modify"))
|
||
|
||
#端口扫描
|
||
def CheckPort(self,port):
|
||
import socket
|
||
localIP = '127.0.0.1'
|
||
temp = {}
|
||
temp['port'] = port
|
||
temp['local'] = True
|
||
try:
|
||
s = socket.socket()
|
||
s.settimeout(0.01)
|
||
s.connect((localIP,port))
|
||
s.close()
|
||
except:
|
||
temp['local'] = False
|
||
|
||
result = 0
|
||
if temp['local']: result +=2
|
||
return result
|
||
|
||
# 转换时间
|
||
def strf_date(self, sdate):
|
||
return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S'))
|
||
|
||
def get_cert_end(self,pem_file):
|
||
try:
|
||
import OpenSSL
|
||
result = {}
|
||
x509 = OpenSSL.crypto.load_certificate(
|
||
OpenSSL.crypto.FILETYPE_PEM, public.readFile(pem_file))
|
||
# 取产品名称
|
||
issuer = x509.get_issuer()
|
||
result['issuer'] = ''
|
||
if hasattr(issuer, 'CN'):
|
||
result['issuer'] = issuer.CN
|
||
if not result['issuer']:
|
||
is_key = [b'0', '0']
|
||
issue_comp = issuer.get_components()
|
||
if len(issue_comp) == 1:
|
||
is_key = [b'CN', 'CN']
|
||
for iss in issue_comp:
|
||
if iss[0] in is_key:
|
||
result['issuer'] = iss[1].decode()
|
||
break
|
||
# 取到期时间
|
||
result['notAfter'] = self.strf_date(
|
||
bytes.decode(x509.get_notAfter())[:-1])
|
||
# 取申请时间
|
||
result['notBefore'] = self.strf_date(
|
||
bytes.decode(x509.get_notBefore())[:-1])
|
||
# 取可选名称
|
||
result['dns'] = []
|
||
for i in range(x509.get_extension_count()):
|
||
s_name = x509.get_extension(i)
|
||
if s_name.get_short_name() in [b'subjectAltName', 'subjectAltName']:
|
||
s_dns = str(s_name).split(',')
|
||
for d in s_dns:
|
||
result['dns'].append(d.split(':')[1])
|
||
subject = x509.get_subject().get_components()
|
||
# 取主要认证名称
|
||
if len(subject) == 1:
|
||
result['subject'] = subject[0][1].decode()
|
||
else:
|
||
result['subject'] = result['dns'][0]
|
||
return result
|
||
except:
|
||
return public.get_cert_data(pem_file)
|
||
|
||
|
||
def get_site_ssl_info(self,siteName):
|
||
try:
|
||
s_file = 'vhost/nginx/{}.conf'.format(siteName)
|
||
is_apache = False
|
||
if not os.path.exists(s_file):
|
||
s_file = 'vhost/apache/{}.conf'.format(siteName)
|
||
is_apache = True
|
||
|
||
if not os.path.exists(s_file):
|
||
return -1
|
||
|
||
s_conf = public.readFile(s_file)
|
||
if not s_conf: return -1
|
||
ssl_file = None
|
||
if is_apache:
|
||
if s_conf.find('SSLCertificateFile') == -1:
|
||
return -1
|
||
s_tmp = re.findall(r"SSLCertificateFile\s+(.+\.pem)",s_conf)
|
||
if not s_tmp: return -1
|
||
ssl_file = s_tmp[0]
|
||
else:
|
||
if s_conf.find('ssl_certificate') == -1:
|
||
return -1
|
||
s_tmp = re.findall(r"ssl_certificate\s+(.+\.pem);",s_conf)
|
||
if not s_tmp: return -1
|
||
ssl_file = s_tmp[0]
|
||
ssl_info = self.get_cert_end(ssl_file)
|
||
if not ssl_info: return -1
|
||
ssl_info['endtime'] = int(int(time.mktime(time.strptime(ssl_info['notAfter'], "%Y-%m-%d")) - time.time()) / 86400)
|
||
return ssl_info
|
||
except: return -1
|
||
#return "{}:{}".format(ssl_info['issuer'],ssl_info['notAfter'])
|
||
|
||
# 查询网站对应的PHP版本
|
||
def get_php_version(self, siteName):
|
||
try:
|
||
|
||
if not self.web_server:
|
||
self.web_server = public.get_webserver()
|
||
|
||
detail = 'detail/' if self.web_server == 'openlitespeed' else ''
|
||
sub = ''
|
||
if '/' in siteName:
|
||
parent_site_name = public.get_parent_site_name(siteName)
|
||
if parent_site_name:
|
||
siteName = parent_site_name[0] + '/' + parent_site_name[1]
|
||
sub = 'sub_dir/'
|
||
|
||
conf_path = f"{self.setupPath}/panel/vhost/{self.web_server}/{detail}{sub}{siteName}.conf"
|
||
conf = public.readFile(conf_path.replace('//', '/'))
|
||
|
||
if self.web_server == 'nginx':
|
||
rep = r"enable-php-(\w{2,5})[-\w]*\.conf"
|
||
elif self.web_server == 'apache':
|
||
rep = r"php-cgi-(\w{2,5})\.sock"
|
||
else:
|
||
rep = r"path\s*/usr/local/lsws/lsphp(\d+)/bin/lsphp"
|
||
tmp = re.search(rep,conf).groups()
|
||
if tmp[0] == '00':
|
||
return 'Static'
|
||
if tmp[0] == 'other':
|
||
return 'Other'
|
||
|
||
return tmp[0][0] + '.' + tmp[0][1]
|
||
except:
|
||
return 'Static'
|
||
|
||
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 get_database_size(self,databaseName):
|
||
try:
|
||
if not self.DB_MySQL:self.DB_MySQL = panelMysql.panelMysql()
|
||
db_size = self.map_to_list(self.DB_MySQL.query("select sum(DATA_LENGTH)+sum(INDEX_LENGTH) from information_schema.tables where table_schema='{}'".format(databaseName)))[0][0]
|
||
if not db_size: return 0
|
||
return int(db_size)
|
||
except:
|
||
return 0
|
||
|
||
def get_site_quota(self,path):
|
||
'''
|
||
@name 获取网站目录配额信息
|
||
@author hwliang<2022-02-15>
|
||
@param path<string> 网站目录
|
||
@return dict
|
||
'''
|
||
res = {
|
||
"used": 0,
|
||
"size": 0,
|
||
"quota_push": {
|
||
"size": 0,
|
||
"used": 0,
|
||
},
|
||
"quota_storage": {
|
||
"size": 0,
|
||
"used": 0,
|
||
}
|
||
}
|
||
try:
|
||
from projectModelV2.quotaModel import main
|
||
quota_info = main().get_quota_path(path)
|
||
|
||
if isinstance(quota_info, dict):
|
||
res.update(quota_info)
|
||
res['size'] = int(quota_info['quota_push']['size']) + int(quota_info['quota_storage']['size'])
|
||
return res
|
||
return res
|
||
except:
|
||
# from traceback import format_exc
|
||
# public.print_log(format_exc())
|
||
return res
|
||
#最新版本v2版本
|
||
# try:
|
||
# from projectModelV2.quotaModel import main
|
||
# quota_info = main().get_quota_path(path)
|
||
# if isinstance(quota_info,dict):
|
||
# return quota_info
|
||
# return res
|
||
# except: return res
|
||
|
||
def get_database_quota(self,db_name):
|
||
'''
|
||
@name 获取网站目录配额信息
|
||
@author hwliang<2022-02-15>
|
||
@param path<string> 网站目录
|
||
@return dict
|
||
'''
|
||
res = {
|
||
"used": 0,
|
||
"size": 0,
|
||
"quota_push": {
|
||
"size": 0,
|
||
"used": 0,
|
||
},
|
||
"quota_storage": {
|
||
"size": 0,
|
||
"used": 0,
|
||
}
|
||
}
|
||
try:
|
||
from projectModelV2.quotaModel import main
|
||
quota_info = main().get_quota_mysql(db_name)
|
||
|
||
if isinstance(quota_info, dict):
|
||
res.update(quota_info)
|
||
res['size'] = int(quota_info['quota_push']['size']) + int(quota_info['quota_storage']['size'])
|
||
return res
|
||
|
||
return res
|
||
except:
|
||
return res
|
||
#最新版本v2版本
|
||
# try:
|
||
# from projectModelV2.quotaModel import main
|
||
# quota_info = main().get_quota_mysql(db_name)
|
||
# if isinstance(quota_info,dict):
|
||
# return quota_info
|
||
# return res
|
||
# except: return res
|
||
|
||
# 获取php默认备份配置
|
||
def get_php_backup_config(self):
|
||
config_file = "/www/server/panel/data/php_backup_config.json"
|
||
default = {'bak_type': "file", 'bak_local': "local", 'bak_remote': ""}
|
||
try:
|
||
if os.path.exists(config_file):
|
||
conf = json.loads(public.readFile(config_file))
|
||
default['bak_type'] = conf.get('bak_type', "file")
|
||
default['bak_local'] = conf.get('bak_local', "local")
|
||
default['bak_remote'] = conf.get('bak_remote', "")
|
||
except Exception:
|
||
pass
|
||
return default
|
||
|
||
'''
|
||
* 取数据列表
|
||
* @param String _GET['tab'] 数据库表名
|
||
* @param Int _GET['count'] 每页的数据行数
|
||
* @param Int _GET['p'] 分页号 要取第几页数据
|
||
* @return Json page.分页数 , count.总行数 data.取回的数据
|
||
'''
|
||
def getData(self, get):
|
||
|
||
# 校验参数
|
||
try:
|
||
get.validate([
|
||
Param('table').Require().String(),
|
||
Param('search').String(),
|
||
Param('limit').Integer(),
|
||
Param('p').Integer(),
|
||
Param('type').String(),
|
||
Param('project_type'),
|
||
], [
|
||
public.validate.trim_filter(),
|
||
])
|
||
except Exception as ex:
|
||
public.print_log("error info: {}".format(ex))
|
||
return public.return_message(-1, 0, str(ex))
|
||
|
||
# 如果网站列表包含 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 ''", ())
|
||
# 先检查tasks表内是否有 install_status、message字段
|
||
if get.table == 'tasks':
|
||
data = public.M('tasks').find()
|
||
if not isinstance(data, dict):
|
||
return public.return_message(0, 0, "No data available")
|
||
if 'install_status' not in data.keys():
|
||
public.M('tasks').execute("ALTER TABLE 'tasks' ADD 'install_status' INTEGER DEFAULT 1", ())
|
||
if 'message' not in data.keys():
|
||
public.M('tasks').execute("ALTER TABLE 'tasks' ADD 'message' 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)
|
||
if table == 'backup':
|
||
import os
|
||
backup_path = public.M('config').where('id=?',(1,)).getField('backup_path')
|
||
for i in range(len(data['data'])):
|
||
if data['data'][i]['size'] == 0:
|
||
if os.path.exists(data['data'][i]['filename']):
|
||
data['data'][i]['size'] = os.path.getsize(data['data'][i]['filename'])
|
||
else:
|
||
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'] = '文件不存在'
|
||
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'] = '手动备份'
|
||
|
||
if data['data'][i]['backup_type'] != 1:
|
||
data['data'][i]['backup_type'] = 0
|
||
|
||
#判断本地文件是否存在,以确定能否下载
|
||
bak_method = data['data'][i]['filename'].split('|')
|
||
if len(bak_method) == 3:
|
||
data['data'][i]['bak_method'] = bak_method[1]
|
||
else:
|
||
data['data'][i]['bak_method'] = 'local'
|
||
|
||
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
|
||
|
||
bak_config = self.get_php_backup_config()
|
||
data['bak_type'] = bak_config.get('bak_type')
|
||
data['bak_local'] = bak_config.get('bak_local')
|
||
data['bak_remote'] = bak_config.get('bak_remote')
|
||
elif table == 'sites' or table == 'databases':
|
||
type = '0'
|
||
site_ids=[]
|
||
if table == 'databases':
|
||
type = '1'
|
||
for i in range(len(data['data'])):
|
||
#将data['data'][i]['id']添加到site_ids列表中
|
||
site_ids.append(data['data'][i]['id'])
|
||
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'])
|
||
try:
|
||
# 兼容wp备份时间,转换格式
|
||
if get.get('project_type','') == 'WP2':
|
||
backup_records = public.S('wordpress_backups').where_in('s_id', site_ids).group('s_id').field(
|
||
's_id', 'count(*) as cnt', 'max(bak_time) as last_backup_time').select()
|
||
backup_info_map = {}
|
||
for record in backup_records:
|
||
s_id = record['s_id']
|
||
last_backup_time = record['last_backup_time']
|
||
if isinstance(last_backup_time, (int, float)):
|
||
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_backup_time))
|
||
else:
|
||
try:
|
||
formatted_time = last_backup_time.strftime("%Y-%m-%d %H:%M:%S")
|
||
except Exception:
|
||
formatted_time = ''
|
||
formatted_record = {
|
||
's_id': s_id,
|
||
'cnt': record['cnt'],
|
||
'last_backup_time': formatted_time
|
||
}
|
||
|
||
# 添加到映射表
|
||
backup_info_map[s_id] = formatted_record
|
||
else:
|
||
backup_info_map = {j['pid']: j for j in public.S('backup').where_in('pid', site_ids).where("type", type).group('pid').field('pid','count(*) as cnt','max(addtime) as last_backup_time').select()}
|
||
except Exception as e:
|
||
backup_info_map = {}
|
||
|
||
for i in range(len(data['data'])): data['data'][i]['backup_count'] = backup_info_map.get(data['data'][i]['id'], {}).get("cnt", 0)
|
||
|
||
if table == 'sites':
|
||
if get.get('project_type','') == 'WP2':
|
||
# 检测类型表,是否正确
|
||
import one_key_wp_v2
|
||
ok, msg = one_key_wp_v2.checklist_fields()
|
||
if not ok and msg== "no such table: wordpress_onekey":
|
||
return public.return_message(0, 0, msg)
|
||
elif not ok:
|
||
return public.return_message(-1, 0, msg)
|
||
|
||
# 获取网站类型
|
||
if get.get('site_type', ''):
|
||
site_sql = public.M("wordpress_onekey").select()
|
||
site_type = public.M("wp_site_types").where('`id` = ?', (get.get('site_type', ''),)).find()
|
||
filtered_data = []
|
||
|
||
# 加载网站图标
|
||
import os
|
||
multi_webservice_status = public.get_multi_webservice_status()
|
||
webservice = public.get_webserver()
|
||
nginx_b64_path = public.image_to_base64(os.path.join(public.get_panel_path(), "BTPanel/static/img/soft_ico/ico-nginx.png"))
|
||
apache_b64_path = public.image_to_base64(os.path.join(public.get_panel_path(), "BTPanel/static/img/apache.png"))
|
||
ols_b64_path = public.image_to_base64(os.path.join(public.get_panel_path(), "BTPanel/static/img/soft_ico/ico-openlitespeed.png"))
|
||
|
||
# 按流量排序
|
||
re_data = None
|
||
if get.get('re_order'):
|
||
re_data = self.get_site_request(public.to_dict_obj({'site_type':'PHP'}))
|
||
if re_data['status'] == 0:
|
||
re_data = re_data['message']
|
||
|
||
for i in range(len(data['data'])):
|
||
# 添加流量排序
|
||
if re_data:
|
||
if data["data"][i]['name'] in re_data:
|
||
data['data'][i]['re_total'] = re_data[data["data"][i]['name']]['total']['request']
|
||
else:
|
||
data['data'][i]['re_total'] = 0
|
||
|
||
data["data"][i]["last_backup_time"] = backup_info_map.get(data['data'][i]['id'], {}).get("last_backup_time", "")
|
||
|
||
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'])
|
||
|
||
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']
|
||
data['data'][i]['ps']=data['data'][i]['ps'].replace("For panel Lets Encrypt certificate","For panel Let's Encrypt certificate",1)
|
||
if data['data'][i]['project_type'] in ['WP', 'WP2']:
|
||
import one_key_wp
|
||
one_key_wp_obj = one_key_wp.one_key_wp()
|
||
data['data'][i]['cache_status'] = False
|
||
data['data'][i]['wp_version'] = '0.0.0'
|
||
try:
|
||
data['data'][i]['cache_status'] = one_key_wp_obj.get_cache_status(data['data'][i]['id'])
|
||
data['data'][i]['wp_version'] = one_key_wp_obj.get_wp_version(data['data'][i]['id'])
|
||
except:
|
||
pass
|
||
data['data'][i]['login_url'] = '/v2/wp/login/{}'.format(data['data'][i]['id'])
|
||
|
||
|
||
if data['data'][i]['project_type'] == 'WP2':
|
||
from wp_toolkit import wpbackup
|
||
data['data'][i]['backup_count'] = wpbackup(data['data'][i]['id']).backup_count()
|
||
wordpress_scan_path = "/www/server/panel/data/wordpress_wp_scan.json"
|
||
data['data'][i]['scan']={"last_time": 0,"vulnerabilities": 0,"status": True}
|
||
import os
|
||
if os.path.exists(wordpress_scan_path):
|
||
try:
|
||
wordpress_scan_info = json.loads(public.readFile(wordpress_scan_path))
|
||
if data['data'][i]['path'] in wordpress_scan_info:
|
||
data['data'][i]['scan'] = wordpress_scan_info[data['data'][i]['path']]
|
||
except:
|
||
pass
|
||
|
||
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"] = {}
|
||
|
||
# 网站图标
|
||
data['data'][i]['ico']=""
|
||
try:
|
||
import os
|
||
ico_b64_path = os.path.join(public.get_panel_path(), "data/site_favs", data['data'][i]['name'] + ".b64")
|
||
|
||
default = public.readFile(ico_b64_path) if os.path.exists(ico_b64_path) else ''
|
||
|
||
if multi_webservice_status:
|
||
if default:
|
||
data['data'][i]['ico'] = default
|
||
elif 'apache' == data['data'][i]['service_type']:
|
||
data['data'][i]['ico'] = apache_b64_path
|
||
elif 'nginx' == data['data'][i]['service_type'] or not data['data'][i]['service_type']:
|
||
data['data'][i]['ico'] = nginx_b64_path
|
||
elif 'openlitespeed' == data['data'][i]['service_type']:
|
||
data['data'][i]['ico'] = ols_b64_path
|
||
else:
|
||
data['data'][i]['ico'] = default
|
||
else:
|
||
if default:
|
||
data['data'][i]['ico'] = default
|
||
elif webservice == 'nginx':
|
||
data['data'][i]['ico'] = nginx_b64_path
|
||
elif webservice == 'apache':
|
||
data['data'][i]['ico'] = apache_b64_path
|
||
elif webservice == 'openlitespeed':
|
||
data['data'][i]['ico'] = ols_b64_path
|
||
else:
|
||
data['data'][i]['ico'] = default
|
||
|
||
# 判断是否启动类型筛选,启动后为数据添加类型
|
||
if get.get('site_type', ''):
|
||
for type_ in site_sql:
|
||
if data['data'][i]['id'] == type_['s_id'] and site_type['name'] == type_[
|
||
'site_type']:
|
||
data['data'][i]['site_type'] = type_['site_type']
|
||
filtered_data.append(data['data'][i])
|
||
|
||
except:
|
||
public.print_log(public.get_error_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"] = {}
|
||
|
||
# 判断是否进行了类型筛选
|
||
if get.get('site_type', ''):
|
||
data['data'] = filtered_data
|
||
|
||
if get.get('re_order') and re_data:
|
||
if get.get('re_order') == 'desc':
|
||
data['data'] = sorted(data['data'], key=lambda x: x["re_total"], reverse=True)
|
||
else:
|
||
data['data'] = sorted(data['data'], key=lambda x: x["re_total"])
|
||
|
||
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:
|
||
data['data'][i]['status'] = -1
|
||
else:
|
||
data['data'][i]['status'] = self.CheckPort(int(data['data'][i]['port']))
|
||
|
||
elif table == 'ftps':
|
||
for i in range(len(data['data'])):
|
||
data['data'][i]['quota'] = self.get_site_quota(data['data'][i]['path'])
|
||
|
||
try:
|
||
for _find in data['data']:
|
||
_keys = _find.keys()
|
||
for _key in _keys:
|
||
_find[_key] = public.xsssec(_find[_key])
|
||
except:
|
||
pass
|
||
|
||
#返回
|
||
res = self.get_sort_data(data)
|
||
return public.return_message(0, 0, res)
|
||
except:
|
||
res = public.get_error_info()
|
||
# return public.get_error_info()
|
||
return public.return_message(0, 0, res)
|
||
|
||
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['shift'] + plist['row']]
|
||
return data
|
||
|
||
'''
|
||
* 取数据库行
|
||
* @param String _GET['tab'] 数据库表名
|
||
* @param Int _GET['id'] 索引ID
|
||
* @return Json
|
||
'''
|
||
def getFind(self,get):
|
||
tableName = get.table
|
||
id = get.id
|
||
field = self.GetField(get.table)
|
||
SQL = public.M(tableName)
|
||
where = "id=?"
|
||
find = SQL.where(where,(id,)).field(field).find()
|
||
try:
|
||
_keys = find.keys()
|
||
for _key in _keys:
|
||
find[_key] = public.xsssec(find[_key])
|
||
except:
|
||
pass
|
||
return find
|
||
|
||
|
||
'''
|
||
* 取字段值
|
||
* @param String _GET['tab'] 数据库表名
|
||
* @param String _GET['key'] 字段
|
||
* @param String _GET['id'] 条件ID
|
||
* @return String
|
||
'''
|
||
def getKey(self,get):
|
||
|
||
# 校验参数
|
||
try:
|
||
get.validate([
|
||
Param('table').Require().String(),
|
||
Param('key').Require().String(),
|
||
Param('id').Require().Integer(),
|
||
], [
|
||
public.validate.trim_filter(),
|
||
])
|
||
except Exception as ex:
|
||
public.print_log("error info: {}".format(ex))
|
||
return public.return_message(-1, 0, str(ex))
|
||
|
||
tableName = get.table
|
||
keyName = get.key
|
||
id = get.id
|
||
SQL = db.Sql().table(tableName)
|
||
where = "id=?"
|
||
result = SQL.where(where,(id,)).getField(keyName)
|
||
if result is None:
|
||
return public.return_message(-1, 0, None)
|
||
res = result
|
||
if isinstance(result, str):
|
||
res = public.xsssec(result)
|
||
|
||
return public.return_message(0, 0, res)
|
||
|
||
|
||
'''
|
||
* 获取数据与分页
|
||
* @param string table 表
|
||
* @param string where 查询条件
|
||
* @param int limit 每页行数
|
||
* @param mixed result 定义分页数据结构
|
||
* @return array
|
||
'''
|
||
def GetSql(self,get,result = '1,2,3,4,5,8'):
|
||
#判断前端是否传入参数
|
||
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)
|
||
if limit < 1: limit = 20
|
||
|
||
if hasattr(get,'result'):
|
||
# 验证参数格式
|
||
if re.match(r"^[\d\,]+$",get.result):
|
||
result = get.result
|
||
|
||
SQL = db.Sql()
|
||
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:
|
||
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)
|
||
else:
|
||
where += "id=" + str(pid)
|
||
|
||
if get.table == 'sites':
|
||
search_key = 'php'
|
||
|
||
# 额外对 project_type 字段做处理
|
||
if 'project_type' in get:
|
||
extra_where = "`project_type` = '{}'".format(get.project_type)
|
||
if where:
|
||
where = r"({}) AND {}".format(where, extra_where)
|
||
else:
|
||
where = extra_where
|
||
else:
|
||
extra_where = "`project_type` IN ('PHP', 'WP')"
|
||
if where:
|
||
where = r"({}) AND {}".format(where, extra_where)
|
||
else:
|
||
where = extra_where
|
||
|
||
if hasattr(get,'type'):
|
||
if get.type != '-1':
|
||
where += " AND type_id={}".format(int(get.type))
|
||
|
||
if get.table == 'databases':
|
||
if hasattr(get,'db_type'):
|
||
if where:
|
||
where += " AND db_type='{}'".format(int(get.db_type))
|
||
else:
|
||
where = "db_type='{}'".format(int(get.db_type))
|
||
if hasattr(get,'sid'):
|
||
if where:
|
||
where += " AND sid='{}'".format(int(get.sid))
|
||
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'):
|
||
data = SQL.table(get.table).where(where,param).field(field).order(order).select()
|
||
return data
|
||
|
||
#取总行数
|
||
count = SQL.table(get.table).where(where,param).count()
|
||
#get.uri = get
|
||
#包含分页类
|
||
import page
|
||
#实例化分页类
|
||
page = page.Page()
|
||
|
||
info = {}
|
||
info['count'] = count
|
||
info['row'] = limit
|
||
|
||
info['p'] = 1
|
||
if hasattr(get,'p'):
|
||
info['p'] = int(get['p'])
|
||
if info['p'] <1: info['p'] = 1
|
||
|
||
try:
|
||
from flask import request
|
||
info['uri'] = public.url_encode(request.full_path)
|
||
except:
|
||
info['uri'] = ''
|
||
info['return_js'] = ''
|
||
if hasattr(get,'tojs'):
|
||
if re.match(r"^[\w\.\-]+$",get.tojs):
|
||
info['return_js'] = get.tojs
|
||
|
||
data['where'] = where
|
||
|
||
#获取分页数据
|
||
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()
|
||
|
||
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
|
||
|
||
#获取条件
|
||
def GetWhere(self,tableName,search):
|
||
if not search: return "",()
|
||
|
||
if type(search) == bytes: search = search.encode('utf-8').strip()
|
||
try:
|
||
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 ?{}".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:
|
||
return '',()
|
||
|
||
# 获取返回的字段
|
||
def GetField(self,tableName):
|
||
fields = {
|
||
'sites' : "id,name,path,status,ps,addtime,edate,service_type",
|
||
'ftps' : "id,pid,name,password,status,ps,addtime,path",
|
||
'databases' : "id,sid,pid,name,username,password,accept,ps,addtime,db_type,conn_config",
|
||
'logs' : "id,uid,username,type,log,addtime",
|
||
'backup' : "id,pid,name,filename,addtime,size,ps,backup_type",
|
||
'users' : "id,username,phone,email,login_ip,login_time",
|
||
'firewall' : "id,port,ps,addtime",
|
||
'domain' : "id,pid,name,port,addtime",
|
||
'tasks' : "id,name,type,status,addtime,start,end,install_status"
|
||
}
|
||
try:
|
||
return fields[tableName]
|
||
except:
|
||
return ''
|
||
|
||
def get_analysis(self,get,i):
|
||
import log_analysis
|
||
get.path = '/www/wwwlogs/{}.log'.format(i['name'])
|
||
get.action = 'get_result'
|
||
data = log_analysis.log_analysis().get_result(get)
|
||
return int(data['php']) + int(data['san']) + int(data['sql']) + int(data['xss'])
|
||
|
||
|
||
|
||
|
||
def get_date_range(self,
|
||
start_date: str = None,
|
||
end_date: str = None,
|
||
date_format: str = "%Y-%m-%d"
|
||
) -> list[str]:
|
||
"""
|
||
根据起止日期获取日期列表,支持指定日期格式
|
||
|
||
参数:
|
||
start_date: 开始日期字符串(格式与date_format一致,默认None表示end_date往前推29天)
|
||
end_date: 结束日期字符串(格式与date_format一致,默认None表示今天)
|
||
date_format: 日期字符串格式(默认"%Y-%m-%d")
|
||
|
||
返回:
|
||
日期字符串列表(按从end_date到start_date的顺序排列,包含两端日期)
|
||
|
||
异常:
|
||
ValueError: 日期格式错误或start_date晚于end_date
|
||
"""
|
||
# 处理默认结束日期(默认为今天)
|
||
if end_date is None:
|
||
end_date_obj = datetime.now().date()
|
||
else:
|
||
try:
|
||
end_date_obj = datetime.strptime(end_date, date_format).date()
|
||
except ValueError:
|
||
raise ValueError(f"结束日期格式错误,应为{date_format}")
|
||
|
||
# 处理默认开始日期(默认是结束日期往前推29天,共30天)
|
||
if start_date is None:
|
||
start_date_obj = end_date_obj - timedelta(days=29)
|
||
else:
|
||
try:
|
||
start_date_obj = datetime.strptime(start_date, date_format).date()
|
||
except ValueError:
|
||
raise ValueError(f"开始日期格式错误,应为{date_format}")
|
||
|
||
# 校验日期逻辑(开始日期不能晚于结束日期)
|
||
if start_date_obj > end_date_obj:
|
||
raise ValueError(f"开始日期({start_date})不能晚于结束日期({end_date})")
|
||
|
||
# 生成日期列表(从start_date递增到end_date,自然从小到大)
|
||
date_list = []
|
||
current_date = start_date_obj # 从开始日期开始
|
||
while current_date <= end_date_obj: # 循环到结束日期为止
|
||
date_list.append(current_date.strftime(date_format))
|
||
current_date += timedelta(days=1) # 日期递增
|
||
|
||
return date_list
|
||
|
||
|
||
|
||
def get_site_data(self,
|
||
base_dir: str,
|
||
site_name: str,
|
||
date_list: list[str]
|
||
) -> dict[str, any]:
|
||
"""
|
||
获取单个站点的统计数据
|
||
|
||
参数:
|
||
base_dir: 基础数据目录
|
||
site_name: 站点名称
|
||
date_list: 日期列表(YYYY-MM-DD格式)
|
||
|
||
返回:
|
||
包含list和total的站点数据字典
|
||
"""
|
||
site_dir = os.path.join(base_dir, site_name)
|
||
daily_data = []
|
||
total_requests = 0
|
||
|
||
# 处理每个日期的数据
|
||
for date_str in date_list:
|
||
# 转换日期格式为YYYYMMDD(整数类型)
|
||
date_int = int(date_str.replace("-", ""))
|
||
file_path = os.path.join(site_dir, f"{date_str}.json")
|
||
|
||
# 初始化当日请求数为0
|
||
requests = 0
|
||
|
||
# 尝试读取文件
|
||
if os.path.exists(file_path) and os.path.isfile(file_path):
|
||
try:
|
||
with open(file_path, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
# 确保requests字段存在且为数字
|
||
if isinstance(data.get("requests"), (int, float)):
|
||
requests = int(data["requests"])
|
||
except (json.JSONDecodeError, Exception):
|
||
# 解析错误或其他异常时保持请求数为0
|
||
pass
|
||
|
||
# 添加当日数据
|
||
daily_data.append({
|
||
"request": requests,
|
||
"date": date_int
|
||
})
|
||
# 累计总请求数
|
||
total_requests += requests
|
||
|
||
return {
|
||
"list": daily_data,
|
||
"total": {
|
||
"request": total_requests
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
def get_multi_site_stats(self,
|
||
base_dir: str = "/www/server/site_total/data/total",
|
||
site_names: list[str] = None,
|
||
start_date: str = None,
|
||
end_date: str = None
|
||
) -> dict[str, any]:
|
||
"""
|
||
获取多个站点的统计数据,返回指定格式的结果
|
||
|
||
参数:
|
||
base_dir: 基础数据目录
|
||
site_names: 要查询的站点名称列表,默认包含示例站点
|
||
|
||
返回:
|
||
符合指定格式的统计数据字典
|
||
"""
|
||
message = {}
|
||
# 默认站点列表
|
||
if site_names is None:
|
||
return message
|
||
|
||
# 获取日期列表
|
||
date_list = self.get_date_range(start_date, end_date)
|
||
|
||
# 收集所有站点数据
|
||
|
||
for site in site_names:
|
||
# 检查站点目录是否存在
|
||
site_dir = os.path.join(base_dir, site)
|
||
if os.path.exists(site_dir) and os.path.isdir(site_dir):
|
||
message[site] = self.get_site_data(base_dir, site, date_list)
|
||
else:
|
||
# 站点不存在时返回空数据结构
|
||
message[site] = {
|
||
"list": [{"request": 0, "date": int(date.replace("-", ""))} for date in date_list],
|
||
"total": {"request": 0}
|
||
}
|
||
|
||
# 构建最终返回格式
|
||
return message
|
||
|
||
def __check_auth(self):
|
||
from plugin_auth_v2 import Plugin as Plugin
|
||
plugin_obj = Plugin(False)
|
||
plugin_list = plugin_obj.get_plugin_list()
|
||
import PluginLoader
|
||
self.__IS_PRO_MEMBER = PluginLoader.get_auth_state() > 0
|
||
return int(plugin_list["pro"]) > time.time() or self.__IS_PRO_MEMBER
|
||
|
||
# 获取网站监控报表数据
|
||
def getSiteThirtyTotal(self, get=None):
|
||
"""
|
||
2026/2/26 调整由前端传入site_type控制输出
|
||
"""
|
||
project_type = ['PHP', 'WP2',' proxy' , 'Node' , 'Python', 'Go']
|
||
if get is not None:
|
||
if get.get("site_type", None) in project_type:
|
||
project_type = [get.get("site_type")]
|
||
|
||
#检测插件是否安装
|
||
if not os.path.exists(os.path.join(public.get_panel_path(),"plugin/monitor/info.json")):
|
||
site_names = [ i.get('name','') for i in public.S('sites').where_in('project_type',project_type).field('name').select() if i.get('name','') !='' ]
|
||
start_date= None if get==None else get.get('start_date',None)
|
||
end_date= None if get==None else get.get('end_date',None)
|
||
return {'status': 0, "timestamp": int(time.time()), "message": self.get_multi_site_stats(site_names=site_names,start_date=start_date,end_date=end_date)}
|
||
cache_file = os.path.join(public.get_panel_path(), 'plugin/monitor/site_thirty_total.json')
|
||
result ={}
|
||
try:
|
||
version=self.get_plugin_version(os.path.join(public.get_panel_path(),"plugin/monitor/info.json"))
|
||
version_list=version.split(".")
|
||
if len(version_list)<3:return {'status': 0, "timestamp": int(time.time()), "message": result}
|
||
if int(version_list[0])<4:return {'status': 0, "timestamp": int(time.time()), "message": result}
|
||
if int(version_list[0])==4 and int(version_list[1])<1:return {'status': 0, "timestamp": int(time.time()), "message": result}
|
||
if int(version_list[0])==4 and int(version_list[1])<1 and int(version_list[2])<2:return {'status': 0, "timestamp": int(time.time()), "message": result}
|
||
#取网站域名列表
|
||
try:
|
||
domain_list = public.S('sites').where_in('project_type',project_type).field('name').select()
|
||
except Exception as ex:
|
||
domain_list = []
|
||
now_time = int(time.time())
|
||
start_date = public.format_date(format="%Y-%m-%d", times=now_time - 86400 * 30)
|
||
end_date = public.format_date(format="%Y-%m-%d", times=now_time)
|
||
|
||
cache_info = {}
|
||
|
||
if get!=None:
|
||
content = public.readFile(cache_file)
|
||
|
||
if content:
|
||
cache_info = json.loads(content)
|
||
|
||
|
||
#取网站统计信息
|
||
for domain in domain_list:
|
||
result[domain["name"]]={"list":[],"total":{"request":0}}
|
||
site_requests= {}
|
||
if get==None:
|
||
# 等待500ms
|
||
time.sleep(0.1)
|
||
args=public.dict_obj()
|
||
args.start_date=start_date
|
||
args.end_date=end_date
|
||
args.part_type="date"
|
||
args.SiteName=domain["name"]
|
||
site_requests = public.run_plugin("monitor","get_site_total_list_custom",args)
|
||
try:
|
||
if "list" in site_requests:
|
||
result[domain["name"]]["total"]["request"]=site_requests["total"]["request"]
|
||
for site in site_requests["list"]:
|
||
# 等待100ms
|
||
time.sleep(0.03)
|
||
result[domain["name"]]["list"].append({"request":site["request"],"date":site["date"]})
|
||
|
||
|
||
except Exception as e:
|
||
# public.print_log('Error__________ {}'.format(str(e)))
|
||
pass
|
||
|
||
else:
|
||
try:
|
||
if cache_info and "list" in cache_info[domain["name"]]:
|
||
result[domain["name"]]=cache_info[domain["name"]]
|
||
except Exception as e:
|
||
# public.print_log('Error__________ {}'.format(str(e)))
|
||
result[domain["name"]]={}
|
||
except Exception as e:
|
||
# public.print_log(public.get_error_info())
|
||
pass
|
||
|
||
if get==None:
|
||
public.writeFile(cache_file, json.dumps(result))
|
||
|
||
return {'status': 0, "timestamp": int(time.time()), "message": result}
|
||
|
||
# 简化版获取网站请求数用于排序
|
||
def get_site_request(self,get=None):
|
||
project_type = ['PHP', 'WP2',' proxy' , 'Node' , 'Python', 'Go']
|
||
if get is not None:
|
||
if get.get("site_type") and get.get("site_type") in project_type:
|
||
project_type = [get.get("site_type")]
|
||
|
||
#保留免费版
|
||
if not os.path.exists(os.path.join(public.get_panel_path(),"plugin/monitor/info.json")):
|
||
site_names = [ i.get('name','') for i in public.S('sites').where_in('project_type',project_type).field('name').select() if i.get('name','') !='' ]
|
||
start_date= None if get==None else get.get('start_date',None)
|
||
end_date= None if get==None else get.get('end_date',None)
|
||
return {'status': 0, "timestamp": int(time.time()), "message": self.get_multi_site_stats(site_names=site_names,start_date=start_date,end_date=end_date)}
|
||
|
||
# 直接读取监控报表数据缓存
|
||
path = os.path.join(public.get_panel_path(), "plugin/monitor/site_thirty_total.json")
|
||
try:
|
||
re_data = {}
|
||
if os.path.exists(path):
|
||
re_data = json.loads(public.readFile(path))
|
||
return {'status': 0, "timestamp": int(time.time()), "message": re_data}
|
||
except:
|
||
return {'status': -1, "timestamp": int(time.time()), "message": {}}
|
||
|
||
|
||
# 获取waf报表数据
|
||
def getSiteWafConfig(self, get=None):
|
||
cache_file = os.path.join(public.get_panel_path(), "plugin/btwaf/site_waf_config_php.json")
|
||
if get!=None:
|
||
try:
|
||
result=json.loads(public.readFile(cache_file))
|
||
if "status" in result:
|
||
result={}
|
||
except Exception as e:
|
||
result={}
|
||
return {'status': 0, "timestamp": int(time.time()), "message": result}
|
||
|
||
result ={}
|
||
try:
|
||
version=self.get_plugin_version(os.path.join(public.get_panel_path(),"plugin/btwaf/info.json"))
|
||
version_list=version.split(".")
|
||
#9.6.8版本以上才支持waf报表
|
||
if len(version_list)<3:return result
|
||
if int(version_list[0])<9:return result
|
||
if int(version_list[0])==9 and int(version_list[1])<6:return result
|
||
if int(version_list[0])==9 and int(version_list[1])<6 and int(version_list[2])<8:return result
|
||
get=public.dict_obj()
|
||
get.p=1
|
||
get.limit=10000
|
||
get.search=""
|
||
result = public.run_plugin("btwaf","get_site_config3",get)
|
||
if "status" in result:
|
||
result={}
|
||
public.writeFile(cache_file, json.dumps(result))
|
||
except Exception as e:
|
||
public.print_log(public.get_error_info())
|
||
pass
|
||
return result
|
||
|
||
|
||
def get_plugin_version(self,filename):
|
||
if os.path.exists(filename):
|
||
try:
|
||
with open(filename, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
return data.get("versions")
|
||
except: return "0.0.0"
|
||
return "0.0.0"
|
||
|
||
|
||
def find_stored_favicons(self):
|
||
try:
|
||
# 检查是否开关是否开启
|
||
icon_path = '/www/server/panel/config/auto_favicon.conf'
|
||
if os.path.exists(icon_path):
|
||
return
|
||
|
||
cur_time = int(time.time())
|
||
last_find_stored_favicons_time = cache.get('last_find_stored_favicons_time')
|
||
|
||
if last_find_stored_favicons_time and cur_time - last_find_stored_favicons_time < 3600 * 12:
|
||
return
|
||
|
||
import requests
|
||
import base64
|
||
site_favs_root = os.path.join(public.get_panel_path(), "data/site_favs")
|
||
|
||
if not os.path.exists(site_favs_root):
|
||
os.makedirs(site_favs_root, 0o755)
|
||
|
||
sites = public.S('sites').field('id', 'name', 'path').select()
|
||
|
||
reg_obj = re.compile(
|
||
r'<link (?:rel="(?:shortcut|icon| )+" *|type="image/x-icon" *|href="([^"]+)" *)+[^>]*>')
|
||
|
||
for site in sites:
|
||
site_name = site['name']
|
||
site_path = site['path']
|
||
ico_path = os.path.join(site_path, "favicon.ico")
|
||
stored_ico_path = os.path.join(site_favs_root, site_name + '.ico')
|
||
|
||
if not os.path.exists(ico_path) and (not os.path.exists(stored_ico_path) or os.path.getmtime(stored_ico_path) < cur_time - 86400):
|
||
# 尝试请求favicon.ico
|
||
domains = public.S('domain').where('pid=?', (site['id'],)).field('name', 'port').select()
|
||
# 只在前2个域名中查找
|
||
if len(domains) > 2:
|
||
domains = domains[:2]
|
||
|
||
for domain in domains:
|
||
domain_name = domain['name']
|
||
|
||
port = domain['port'] if domain['port'] else 80
|
||
protocol = 'https' if port == 443 else 'http'
|
||
url = "{}://127.0.0.1{}/".format(protocol, ':{}'.format(port) if port not in [80, 443] else '')
|
||
|
||
# public.print_log('url: {} {}'.format(domain, url))
|
||
|
||
try:
|
||
# 首先尝试直接请求favicon.ico
|
||
ico_url = "{}/favicon.ico".format(url.strip('/'))
|
||
|
||
# public.print_log('ico_url: {}'.format(ico_url))
|
||
|
||
# 等待1s
|
||
time.sleep(1)
|
||
|
||
try:
|
||
ico_response = requests.get(ico_url, headers={
|
||
'host': domain_name,
|
||
'user-agent': 'aaPanel',
|
||
}, verify=False, timeout=15)
|
||
|
||
if ico_response.status_code == 200 and ico_response.headers.get('Content-Type','').lower() == 'image/x-icon':
|
||
ico_content = ico_response.content
|
||
with open(stored_ico_path, 'wb') as f:
|
||
f.write(ico_content)
|
||
# public.print_log('Successfully fetched favicon.ico from {}'.format(ico_url))
|
||
break
|
||
except:
|
||
pass
|
||
|
||
# 等待500ms
|
||
time.sleep(1)
|
||
|
||
max_redirects = 2
|
||
|
||
for _ in range(max_redirects):
|
||
# 无法获取favicon.ico,尝试从首页中获取
|
||
response = requests.get(url, headers={
|
||
'host': domain_name,
|
||
'user-agent': 'aaPanel',
|
||
}, verify=False, timeout=15)
|
||
|
||
# 检查是否重定向
|
||
if response.status_code >= 300 and response.status_code < 400:
|
||
new_url = response.headers.get('location')
|
||
|
||
# 从new_url中提取域名和端口
|
||
if new_url:
|
||
# public.print_log('Redirected to: {}'.format(new_url))
|
||
parsed_url = requests.utils.urlparse(new_url)
|
||
domain_name = parsed_url.netloc.split(':')[0]
|
||
continue
|
||
|
||
# 等待500ms
|
||
time.sleep(0.5)
|
||
continue
|
||
|
||
break
|
||
|
||
if response.status_code == 200:
|
||
# 尝试从首页中获取favicon.ico
|
||
m = reg_obj.search(response.text)
|
||
|
||
# public.print_log('matched ico_url: {}'.format(m.group(1) if m else 'None'))
|
||
|
||
if m:
|
||
ico_url = m.group(1)
|
||
headers = {
|
||
'user-agent': 'aaPanel',
|
||
}
|
||
if not ico_url.startswith('http'):
|
||
headers['host'] = domain_name
|
||
# 如果favicon.ico是相对路径,拼接完整URL
|
||
if ico_url.startswith('//') and ico_url[2:].startswith(domain_name):
|
||
ico_url = url + ico_url[2:].split('/', 2)[-1]
|
||
else:
|
||
ico_url = url + ico_url.lstrip('/')
|
||
|
||
# 等待500ms
|
||
time.sleep(0.5)
|
||
|
||
try:
|
||
ico_response = requests.get(ico_url, headers=headers, verify=False, timeout=15)
|
||
if ico_response.status_code == 200:
|
||
ico_content = ico_response.content
|
||
# 校验图片格式
|
||
is_valid = False
|
||
# 检查是否为.ico
|
||
if len(ico_content) >= 4 and ico_content[:4] == b'\x00\x00\x01\x00':
|
||
is_valid = True
|
||
# 检查是否为PNG
|
||
elif len(ico_content) >= 8 and ico_content[:8] == b'\x89PNG\r\n\x1a\n':
|
||
is_valid = True
|
||
# 检查是否为JPG
|
||
elif len(ico_content) >= 3 and ico_content[:3] == b'\xff\xd8\xff':
|
||
is_valid = True
|
||
# 检查是否为GIF
|
||
elif len(ico_content) >= 6 and (
|
||
ico_content[:6] == b'GIF87a' or ico_content[:6] == b'GIF89a'):
|
||
is_valid = True
|
||
|
||
if is_valid:
|
||
with open(stored_ico_path, 'wb') as f:
|
||
f.write(ico_content)
|
||
break
|
||
except requests.RequestException as e:
|
||
public.print_log("Error fetching favicon from {}: {}".format(ico_url, str(e)),
|
||
_level='error')
|
||
|
||
break # 成功获取favicon.ico后跳出循环
|
||
except requests.RequestException as e:
|
||
public.print_log("Error fetching favicon for {}: {}".format(domain_name, str(e)),
|
||
_level='error')
|
||
|
||
if not os.path.exists(ico_path):
|
||
# 如果仍然没有favicon.ico,尝试从存储的favicon中读取
|
||
if os.path.exists(stored_ico_path):
|
||
ico_path = stored_ico_path
|
||
|
||
if os.path.exists(ico_path):
|
||
try:
|
||
with open(ico_path, 'rb') as f:
|
||
ico_content = f.read()
|
||
base64_ico = "data:image/x-icon;base64," + base64.b64encode(ico_content).decode('utf-8')
|
||
public.writeFile(os.path.join(site_favs_root, site_name + '.b64'), base64_ico)
|
||
except Exception as e:
|
||
public.print_log("Error storing favicon for {}: {}".format(site_name, str(e)), _level='error')
|
||
|
||
cache.set('last_find_stored_favicons_time', cur_time, timeout=3600 * 12)
|
||
except Exception as e:
|
||
public.print_log(str(e))
|
||
|
||
# 获取wp类型
|
||
def get_wp_classification(self, get=None):
|
||
data = public.M("wp_site_types").select()
|
||
return public.return_message(0, 0, data)
|
||
|
||
# 获取站点备份数和最后备份时间
|
||
@staticmethod
|
||
def _get_backup_count(site_id):
|
||
res = {"backup_count": 0, "last_backup_time": ""}
|
||
try:
|
||
if public.M('sqlite_master').where('type=? AND name=?', ('table', 'wordpress_backups')).count():
|
||
backups = public.M('wordpress_backups').where('s_id=?', (site_id,)).field(
|
||
'COUNT(id) as count, MAX(bak_time) as last_time'
|
||
).find()
|
||
if backups:
|
||
res = {
|
||
'backup_count': backups['count'],
|
||
'last_backup_time': public.format_date("%Y-%m-%d %H:%M:%S", backups['last_time'])
|
||
}
|
||
return res
|
||
except Exception as e:
|
||
public.print_log("Error fetching backup count for site_id {}: {}".format(site_id, str(e)))
|
||
return res
|
||
|
||
# 获取wp网站列表(新)get_wp_site_list
|
||
def get_wp_site_list(self, get=None):
|
||
try:
|
||
# 环境检测与参数初始化
|
||
import one_key_wp_v2
|
||
ok, msg = one_key_wp_v2.checklist_fields()
|
||
|
||
# 未安装wp toolkit时
|
||
if not ok and msg == "no such table: wordpress_onekey":
|
||
return public.return_message(0, 0, {"data": [], "total": 0})
|
||
elif not ok:
|
||
return public.return_message(-1, 0, msg)
|
||
|
||
# 校验参数
|
||
try:
|
||
p = int(get.get('p', 1))
|
||
limit = int(get.get('limit', 100))
|
||
search = get.get('search', '').strip()
|
||
site_type = get.get('site_type', '').strip()
|
||
order = get.get('order', 'addtime DESC')
|
||
table = get.get('table', '')
|
||
if p < 1: p = 1
|
||
if limit < 1: limit = 100
|
||
except:
|
||
return public.return_message(-1, 0, "Parameter error")
|
||
|
||
# 域名查询特殊路由
|
||
if table == 'domain':
|
||
data = self.GetSql(get)
|
||
return public.return_message(0, 0, data)
|
||
|
||
one_key_wp_obj = one_key_wp_v2.one_key_wp()
|
||
|
||
# 构造基础查询
|
||
query = public.S('sites').prefix('').where('project_type = ?', 'WP2').alias('s')
|
||
|
||
# 处理搜索条件
|
||
if search:
|
||
query = query.where('s.name LIKE ? or s.ps LIKE ?', (f'%{search}%', f'%{search}%'))
|
||
|
||
# 获取网站类型过滤
|
||
if site_type:
|
||
query = query.join(
|
||
'wordpress_onekey wok',
|
||
's.id = wok.s_id',
|
||
'INNER'
|
||
).join(
|
||
'wp_site_types wst',
|
||
'wok.site_type = wst.name',
|
||
'INNER',
|
||
).where('wst.id = ?', site_type)
|
||
|
||
# 检查备份表
|
||
try:
|
||
has_backup_table = public.S('wordpress_backups').exists()
|
||
except:
|
||
has_backup_table = False
|
||
|
||
# 定义需要查询的字段(确保包含 parent_id 用于层级判断)
|
||
fields = [
|
||
's.id', 's.name', 's.path', 's.status', 's.addtime',
|
||
's.ps', 'edate', 'project_type', 'service_type', 's.parent_id'
|
||
]
|
||
|
||
if has_backup_table:
|
||
query = query.left_join('wordpress_backups wb', 's.id = wb.s_id')
|
||
query = query.field(*(fields + [
|
||
'COALESCE(COUNT(wb.id), 0) AS backup_count',
|
||
'MAX(wb.bak_time) AS last_backup_time'
|
||
])).group('s.id')
|
||
else:
|
||
query = query.field(*fields)
|
||
|
||
# 基础排序
|
||
order_list = order.split(' ')
|
||
if order_list[0] not in ['site_ssl', 're_total']:
|
||
query = query.order(order_list[0], order_list[1])
|
||
|
||
# 环境状态与图标初始化
|
||
multi_webservice_status = public.get_multi_webservice_status()
|
||
webservice = public.get_webserver()
|
||
panel_path = public.get_panel_path()
|
||
|
||
nginx_ico = public.image_to_base64(os.path.join(panel_path, "BTPanel/static/img/soft_ico/ico-nginx.png"))
|
||
apache_ico = public.image_to_base64(os.path.join(panel_path, "BTPanel/static/img/apache.png"))
|
||
ols_ico = public.image_to_base64(
|
||
os.path.join(panel_path, "BTPanel/static/img/soft_ico/ico-openlitespeed.png"))
|
||
|
||
# 获取流量信息
|
||
re_data = {}
|
||
if get.get('re_order') or True:
|
||
re_res = self.get_site_request(public.to_dict_obj({'site_type': 'WP2'}))
|
||
if re_res.get('status') == 0:
|
||
re_data = re_res.get('message')
|
||
|
||
# 执行数据库查询
|
||
all_data = query.select()
|
||
if not all_data:
|
||
return public.return_message(0, 0, {"data": [], "total": 0})
|
||
|
||
# 构建初始查找映射
|
||
data_map = {}
|
||
for item in all_data:
|
||
data_map[item['id']] = item
|
||
|
||
|
||
# 保存原始结果ID,用于区分"原始主站"与"补充主站"
|
||
original_ids = set(data_map.keys())
|
||
|
||
# search 非空时,若子站被搜出但主站不在结果中,补充主站
|
||
missing_parent_ids = set()
|
||
if search:
|
||
for item in all_data:
|
||
pid = item.get('parent_id', 0)
|
||
if pid and pid != -1 and pid not in data_map:
|
||
missing_parent_ids.add(pid)
|
||
|
||
if missing_parent_ids:
|
||
try:
|
||
parent_rows = public.S('sites').prefix('').where_in(
|
||
'id', list(missing_parent_ids)
|
||
).where('project_type = ?', 'WP2').select()
|
||
if parent_rows:
|
||
for p_item in parent_rows:
|
||
data_map[p_item['id']] = p_item
|
||
all_data.extend(parent_rows)
|
||
except Exception as ex:
|
||
public.print_log('get_wp_site_list fetch missing parents error: {}'.format(ex))
|
||
|
||
# 统计子站点数量逻辑
|
||
for item in all_data:
|
||
item['sub_site'] = 0
|
||
|
||
# 遍历全量数据,内存一次过滤,避免多次sql查询
|
||
for item in all_data:
|
||
pid = item.get('parent_id', 0)
|
||
if pid > 0 and pid in data_map:
|
||
data_map[pid]['sub_site'] += 1
|
||
|
||
# 处理主站点数据并过滤
|
||
result_list = []
|
||
for item in all_data:
|
||
pid = item.get('parent_id', 0)
|
||
# search模式:命中子站时只返回其主站,命中主站也只返回主站
|
||
if pid not in [0, -1]:
|
||
if search or pid in original_ids:
|
||
continue
|
||
|
||
try:
|
||
# 流量统计
|
||
item['re_total'] = re_data.get(item['name'], {}).get('total', {}).get('request', 0)
|
||
# 备份时间格式化
|
||
item['last_backup_time'] = public.format_date("%Y-%m-%d %H:%M:%S", item.get('last_backup_time', ''))
|
||
# 维护模式检测
|
||
item['maintenance'] = os.path.exists(os.path.join(item['path'], '.maintenance'))
|
||
# PHP版本
|
||
item['php_version'] = self.get_php_version(item['name'])
|
||
# 登录入口
|
||
item['login_url'] = '/v2/wp/login/{}'.format(item['id'])
|
||
|
||
# WP版本
|
||
try:
|
||
item['wp_version'] = one_key_wp_obj.get_wp_version(item['id'])
|
||
item['cache_status'] = one_key_wp_obj.get_cache_status(item['id'])
|
||
|
||
|
||
except:
|
||
item['wp_version'] = '0.0.0'
|
||
item['cache_status'] = False
|
||
|
||
# SSL状态处理
|
||
ssl_info = self.get_site_ssl_info(item['name'])
|
||
item['ssl'] = ssl_info
|
||
item['site_ssl'] = ssl_info['endtime'] if (ssl_info and ssl_info != -1) else -1
|
||
|
||
# 图标处理
|
||
ico_path = os.path.join(panel_path, "data/site_favs", item['name'] + ".b64")
|
||
user_ico = public.readFile(ico_path) if os.path.exists(ico_path) else ''
|
||
if user_ico:
|
||
item['ico'] = user_ico
|
||
else:
|
||
st = item.get('service_type', '')
|
||
current_ws = st if multi_webservice_status else webservice
|
||
if current_ws == 'apache':
|
||
item['ico'] = apache_ico
|
||
elif current_ws == 'openlitespeed':
|
||
item['ico'] = ols_ico
|
||
else:
|
||
item['ico'] = nginx_ico
|
||
|
||
result_list.append(item)
|
||
except Exception as ex:
|
||
public.print_log('get_wp_site_list enrich item error [id={}, name={}]: {}'.format(
|
||
item.get('id'), item.get('name'), ex))
|
||
item['site_ssl'] = item.get('site_ssl', -1)
|
||
item['ssl'] = item.get('ssl', -1)
|
||
item['wp_version'] = item.get('wp_version', '0.0.0')
|
||
item['cache_status'] = item.get('cache_status', False)
|
||
item['php_version'] = item.get('php_version', 'Static')
|
||
item['re_total'] = item.get('re_total', 0)
|
||
item['maintenance'] = item.get('maintenance', False)
|
||
item['ico'] = item.get('ico', '')
|
||
result_list.append(item)
|
||
|
||
# 排序处理
|
||
if order_list[0] == 'site_ssl':
|
||
is_rev = order_list[1].lower() == 'desc'
|
||
result_list = sorted(result_list, key=lambda x: x['site_ssl'], reverse=is_rev)
|
||
elif get.get('re_order'):
|
||
is_rev = get.get('re_order').lower() == 'desc'
|
||
result_list = sorted(result_list, key=lambda x: x['re_total'], reverse=is_rev)
|
||
|
||
# 8分页返回
|
||
total = len(result_list)
|
||
start = (p - 1) * limit
|
||
end = p * limit
|
||
paged_data = result_list[start:end]
|
||
|
||
for d in paged_data:
|
||
if d.get('sub_site') > 0:
|
||
d['id'] = f'group_{d['id']}'
|
||
|
||
return public.return_message(0, 0, {'data': paged_data, 'total': total})
|
||
|
||
except Exception as e:
|
||
import traceback
|
||
traceback.print_exc()
|
||
return public.return_message(-1, 0, "Internal Server Error: " + str(e))
|
||
|
||
# 子网站获取
|
||
def get_wp_sub_site(self, get=None):
|
||
"""
|
||
获取指定主站下的子站列表(在内存中进行排序和分页)
|
||
@param get.site_id: 主站ID (s_id)
|
||
@param get.limit: 每页数量 (默认10)
|
||
@param get.p: 当前页码 (默认1)
|
||
"""
|
||
try:
|
||
import os
|
||
try:
|
||
parent_id = int(get.get('site_id'))
|
||
limit = int(get.get('limit', 10))
|
||
p = int(get.get('p', 1))
|
||
search = get.get('search', '').strip()
|
||
if p < 1: p = 1
|
||
except:
|
||
return False, "Parameter error"
|
||
|
||
import one_key_wp_v2
|
||
one_key_wp_obj = one_key_wp_v2.one_key_wp()
|
||
|
||
query = public.S('sites').prefix('').where('parent_id = ? AND project_type = ?', (parent_id, 'WP2'))
|
||
if search:
|
||
query = query.where('(name LIKE ? OR ps LIKE ?)', (f'%{search}%', f'%{search}%'))
|
||
data = query.select()
|
||
|
||
if not data:
|
||
return public.return_message(0, 0, {
|
||
'data': [],
|
||
'total': 0
|
||
})
|
||
|
||
data = sorted(data, key=lambda x: x.get('addtime', 0), reverse=True)
|
||
|
||
# 执行分页切片
|
||
total_count = len(data)
|
||
start = (p - 1) * limit
|
||
end = start + limit
|
||
paged_data = data[start:end]
|
||
|
||
# 流量
|
||
re_data = {}
|
||
try:
|
||
re_res = self.get_site_request(public.to_dict_obj({'site_type': 'WP2'}))
|
||
if re_res.get('status') == 0:
|
||
re_data = re_res.get('message')
|
||
except:
|
||
pass
|
||
|
||
# 重写路径
|
||
rewrite_path = None
|
||
server_type = public.get_webserver()
|
||
site_main = public.S('sites').where('id = ?', (parent_id,)).field('name,service_type').find()
|
||
if public.get_multi_webservice_status():
|
||
if site_main:
|
||
server_type = site_main['service_type'] if site_main['service_type'] else 'nginx'
|
||
|
||
if server_type == 'nginx':
|
||
rewrite_path = f"{public.get_vhost_path()}/nginx/sub_dir/rewrite/{site_main['name']}/"
|
||
|
||
for item in paged_data:
|
||
path_parts = item['name'].strip('/').split('/')
|
||
item['display_name'] = '/'.join(path_parts[1:]) if len(path_parts) > 1 else item['name']
|
||
# 流量统计
|
||
item['re_total'] = re_data.get(item['name'], {}).get('total', {}).get('request', 0)
|
||
item['maintenance'] = os.path.exists(os.path.join(item['path'], '.maintenance'))
|
||
item['php_version'] = self.get_php_version(item['name'])
|
||
item['login_url'] = '/v2/wp/login/{}'.format(item['id'])
|
||
|
||
if rewrite_path:
|
||
item['rewrite_path'] = rewrite_path + item['display_name'].replace('/','_') + '.conf'
|
||
else:
|
||
item['rewrite_path'] = f"{item['path']}/.htaccess"
|
||
|
||
# WP版本
|
||
try:
|
||
item['wp_version'] = one_key_wp_obj.get_wp_version(item['id'])
|
||
item['cache_status'] = one_key_wp_obj.get_cache_status(item['id'])
|
||
except:
|
||
item['wp_version'] = '0.0.0'
|
||
item['cache_status'] = 0
|
||
|
||
# 备份信息处理
|
||
item.update(self._get_backup_count(item['id']))
|
||
|
||
return True, {
|
||
'data': paged_data,
|
||
'total': total_count
|
||
}
|
||
|
||
except Exception as e:
|
||
return False, e
|
||
|
||
# 主站获取
|
||
def get_wp_main_site(self, site_id):
|
||
"""
|
||
根据 ID 获取单个 WordPress 站点的完整详情(保留原列表所有字段)
|
||
"""
|
||
try:
|
||
import one_key_wp_v2
|
||
one_key_wp_obj = one_key_wp_v2.one_key_wp()
|
||
panel_path = public.get_panel_path()
|
||
|
||
# 查询站点基础信息 (对应原 fields 列表)
|
||
item = public.M('sites').where('id=?', (site_id,)).field(
|
||
'id,name,path,status,addtime,ps,edate,project_type,service_type,parent_id'
|
||
).find()
|
||
|
||
if not item: return None
|
||
|
||
# 备份信息字段 (backup_count, last_backup_time)
|
||
item.update(self._get_backup_count(site_id))
|
||
|
||
# 子站点统计 (sub_site)
|
||
item['main_site'] = True
|
||
|
||
# 流量统计字段 (re_total)
|
||
item['re_total'] = 0
|
||
try:
|
||
re_res = self.get_site_request(public.to_dict_obj({'site_type': 'WP2'}))
|
||
if re_res.get('status') == 0:
|
||
re_data = re_res.get('message', {})
|
||
item['re_total'] = re_data.get(item['name'], {}).get('total', {}).get('request', 0)
|
||
except:
|
||
pass
|
||
|
||
# 维护模式与基础环境 (maintenance, php_version, login_url)
|
||
item['maintenance'] = os.path.exists(os.path.join(item['path'], '.maintenance'))
|
||
item['php_version'] = self.get_php_version(item['name'])
|
||
item['login_url'] = '/v2/wp/login/{}'.format(item['id'])
|
||
item['display_name']= item['name']
|
||
|
||
# WordPress 特有字段 (wp_version, cache_status)
|
||
try:
|
||
item['wp_version'] = one_key_wp_obj.get_wp_version(item['id'])
|
||
item['cache_status'] = one_key_wp_obj.get_cache_status(item['id'])
|
||
except:
|
||
item['wp_version'] = '0.0.0'
|
||
item['cache_status'] = False
|
||
|
||
# SSL 状态字段 (ssl, site_ssl)
|
||
ssl_info = self.get_site_ssl_info(item['name'])
|
||
item['ssl'] = ssl_info
|
||
item['site_ssl'] = ssl_info['endtime'] if (ssl_info and ssl_info != -1) else -1
|
||
|
||
# 图标处理 (ico)
|
||
ico_path = os.path.join(panel_path, "data/site_favs", item['name'] + ".b64")
|
||
if os.path.exists(ico_path):
|
||
item['ico'] = public.readFile(ico_path)
|
||
else:
|
||
# 如果没有自定义图标,根据服务类型匹配默认图标
|
||
webservice = public.get_webserver()
|
||
st = item.get('service_type', '')
|
||
# 这里的逻辑参考原函数对多服务状态的判断
|
||
current_ws = st if public.get_multi_webservice_status() else webservice
|
||
|
||
ico_map = {
|
||
'apache': 'apache.png',
|
||
'openlitespeed': 'soft_ico/ico-openlitespeed.png',
|
||
'nginx': 'soft_ico/ico-nginx.png'
|
||
}
|
||
icon_file = ico_map.get(current_ws, 'soft_ico/ico-nginx.png')
|
||
item['ico'] = public.image_to_base64(os.path.join(panel_path, "BTPanel/static/img", icon_file))
|
||
|
||
return item
|
||
|
||
except:
|
||
return None
|
||
|
||
# 获取子站列表
|
||
def get_wp_sub_sites(self, get=None):
|
||
"""
|
||
获取指定主站下的子站列表(在内存中进行排序和分页)
|
||
@param get.site_id: 主站ID (s_id)
|
||
@param get.limit: 每页数量 (默认10)
|
||
@param get.p: 当前页码 (默认1)
|
||
"""
|
||
try:
|
||
import os
|
||
try:
|
||
parent_id = json.loads(get.get('site_id', []))
|
||
limit = int(get.get('limit', 10))
|
||
p = int(get.get('p', 1))
|
||
search = get.get('search', '').strip()
|
||
if p < 1: p = 1
|
||
|
||
parent_id = [ int(site_id) for site_id in parent_id ]
|
||
except:
|
||
return public.return_message(-1, 0, "Parameter error")
|
||
|
||
data = []
|
||
for site_id in parent_id:
|
||
ok, msg = self.get_wp_sub_site(public.to_dict_obj({'site_id':site_id,'limit':limit,'p':p,'search':search}))
|
||
if not ok:
|
||
return public.return_message(-1, 0, "Failed to obtain sub-sites: " + str(msg))
|
||
# 添加主站, 只有p=1时插入主站标识
|
||
if p < 2:
|
||
main = self.get_wp_main_site(site_id)
|
||
if not main:
|
||
return public.return_message(-1, 0, "Could not obtain the main station.")
|
||
if main['parent_id'] != -1:
|
||
data.append(main)
|
||
data.extend(msg['data'])
|
||
return public.return_message(0, 0, data)
|
||
except Exception as e:
|
||
import traceback
|
||
traceback.print_exc()
|
||
return public.return_message(-1, 0, "Failed to obtain sub-sites: " + str(e))
|
||
|
||
# 获取所有真实站点列表
|
||
def get_wp_real_sites(self, get=None):
|
||
"""获取所有真实站点列表(返回所有site_id的完整信息, 包括组, 如果有组按组返回)"""
|
||
try:
|
||
get.validate([
|
||
Param('site_id').String().Require(),
|
||
], [
|
||
public.validate.trim_filter(),
|
||
])
|
||
get.site_id = json.loads(get.get('site_id', []))
|
||
if not get.site_id:
|
||
return public.success_v2([])
|
||
except Exception as ex:
|
||
public.print_log('error info: {}'.format(ex))
|
||
return public.return_message(-1, 0, str(ex))
|
||
try:
|
||
fields = 'id,name,parent_id'
|
||
parent_ids = set()
|
||
direct_ids = [] # 保序
|
||
direct_ids_set = set()
|
||
for sid in get.site_id:
|
||
if isinstance(sid, str) and sid.startswith('group_'):
|
||
try:
|
||
pid = int(sid.split('_', 1)[1])
|
||
parent_ids.add(pid)
|
||
if pid not in direct_ids_set:
|
||
direct_ids.append(pid)
|
||
direct_ids_set.add(pid)
|
||
except ValueError:
|
||
continue
|
||
else:
|
||
try:
|
||
sid_int = int(sid)
|
||
if sid_int not in direct_ids_set:
|
||
direct_ids.append(sid_int)
|
||
direct_ids_set.add(sid_int)
|
||
except (ValueError, TypeError):
|
||
continue
|
||
# 天然去重map
|
||
result_map = {}
|
||
|
||
def _query_by_ids(key_field: str, ids: list):
|
||
"""根据字段批量查询站点, 构造到map中"""
|
||
if not ids:
|
||
return
|
||
rows = public.M('sites').where(
|
||
'{} IN ({})'.format(key_field, ','.join(['?'] * len(ids))), ids
|
||
).field(fields).select()
|
||
for item in rows or []:
|
||
result_map[item['id']] = item
|
||
|
||
_query_by_ids('parent_id', list(parent_ids))
|
||
_query_by_ids('id', list(direct_ids))
|
||
|
||
# 每组父站排第一,后跟其子站点
|
||
res = []
|
||
for did in direct_ids:
|
||
if did in result_map:
|
||
res.append(result_map.pop(did))
|
||
# 提取该子站点
|
||
children = [
|
||
item for item in result_map.values() if item.get('parent_id') == did
|
||
]
|
||
res.extend(children)
|
||
for c in children:
|
||
result_map.pop(c['id'])
|
||
# 其余追加
|
||
res.extend(result_map.values())
|
||
return public.success_v2(res)
|
||
except Exception as e:
|
||
import traceback
|
||
traceback.print_exc()
|
||
return public.fail_v2(f"Failed to obtain real sites: {str(e)}")
|
||
|
||
# 获取aacloud数据,一次性返回
|
||
def get_aacloud_data(self,get):
|
||
webname = public.GetConfigValue("title")
|
||
version = ''
|
||
conf_path = os.path.join(public.get_panel_path(), 'class/common.py')
|
||
try:
|
||
data = public.readFile(conf_path)
|
||
|
||
match = re.search(r"g\.version\s*=\s*['\"](.*?)['\"]", data)
|
||
if match:
|
||
version = match.group(1)
|
||
except:
|
||
pass
|
||
|
||
return {'webname' : webname,'version': version}
|