 #coding: utf-8
# +-------------------------------------------------------------------
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
from flask import session,request
import public,os,json,time,apache,psutil,hashlib,re
from public.validate import Param

class ajax:
    # __official_url = public.OfficialApiBase()
    
    @property
    def __official_url(self):
        return public.OfficialApiBase()

    def GetApacheStatus(self, get):
        a = apache.apache()
        status = a.GetApacheStatus()
        if status:
            return public.success_v2(status)
        else:
            return public.fail_v2("Get status failed!")

    def GetProcessCpuPercent(self,i,process_cpu):
        try:
            pp = psutil.Process(i)
            if pp.name() not in process_cpu.keys():
                process_cpu[pp.name()] = float(pp.cpu_percent(interval=0.01))
            process_cpu[pp.name()] += float(pp.cpu_percent(interval=0.01))
        except:
            pass
    def GetNginxStatus(self,get):
        try:
            if not os.path.exists('/www/server/nginx/sbin/nginx'):
                return public.return_message(-1, 0, public.lang("Nginx is not install"))
            process_cpu = {}
            worker = int(public.ExecShell("ps aux|grep nginx|grep 'worker process'|wc -l")[0])-1
            workermen = int(public.ExecShell("ps aux|grep nginx|grep 'worker process'|awk '{memsum+=$6};END {print memsum}'")[0]) / 1024
            for proc in psutil.process_iter():
                if proc.name() == "nginx":
                    self.GetProcessCpuPercent(proc.pid,process_cpu)
            time.sleep(0.1)
            #取Nginx负载状态
            self.CheckStatusConf()
            result = public.httpGet('http://127.0.0.1/nginx_status')
            is_curl = False
            tmp = []
            if result:
                tmp = result.split()
            if len(tmp) < 15: is_curl = True

            if is_curl:
                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['requests'] = tmp[10]
                data['Reading'] = tmp[13]
                data['Writing'] = tmp[15]
                data['Waiting'] = tmp[17]
            else:
                data['accepts'] = tmp[9]
                data['handled'] = tmp[7]
                data['requests'] = tmp[8]
                data['Reading'] = tmp[11]
                data['Writing'] = tmp[13]
                data['Waiting'] = tmp[15]
            data['active'] = tmp[2]
            data['worker'] = worker
            data['workercpu'] = round(float(process_cpu["nginx"]), 2)
            data['workermen'] = "%s%s" % (int(workermen), "MB")
            return public.return_message(0, 0, data)
        except Exception as ex:
            public.write_log_gettext('Get Info','Nginx load status acquisition failed:{}',(ex,))
            return public.return_message(-1, 0, public.lang("Data acquisition failed!"))

    def GetPHPStatus(self,get):
        #取指定PHP版本的负载状态
        try:
            version = get.version
            uri = "/phpfpm_"+version+"_status?json"
            result = public.request_php(version,uri,'')
            tmp = json.loads(result)
            fTime = time.localtime(int(tmp['start time']))
            tmp['start time'] = time.strftime('%Y-%m-%d %H:%M:%S',fTime)
            return public.return_message(0, 0, tmp)
        except Exception as ex:
            public.write_log_gettext('Get Info',"PHP load status acquisition failed: {}",(public.get_error_info(),))
            return public.return_message(-1, 0, public.lang("PHP load status acquisition failed!"))

    def CheckStatusConf(self):
        if public.get_webserver() != 'nginx': return
        filename = session['setupPath'] + '/panel/vhost/nginx/phpfpm_status.conf'
        if os.path.exists(filename):
            if public.ReadFile(filename).find('nginx_status')!=-1: return

        conf = '''server {
    listen 80;
    server_name 127.0.0.1;
    allow 127.0.0.1;
    location /nginx_status {
        stub_status on;
        access_log off;
    }
}'''
        public.writeFile(filename,conf)
        public.serviceReload()


    def GetTaskCount(self,get):
        num = public.M('tasks').where("status!=?", ('1',)).count()

        #取任务数量
        return public.return_message(0,0,num)

    def GetSoftList(self,get):
        #取软件列表
        import json,os
        tmp = public.readFile('data/softList.conf')
        if not isinstance(tmp, str) or not tmp.strip():
            return public.return_message(0, 0, [])
        try:
            data = json.loads(tmp)
        except Exception as e:
            return public.return_message(0, 0, [])

        tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select()
        for i in range(len(data)):
            if data[i]['name'] == 'Openlitespeed':
                data[i]['check'] = public.GetConfigValue('openlitespeed_path') + '/' + data[i]['check']
            else:
                data[i]['check'] = public.GetConfigValue('root_path')+'/'+data[i]['check']
            for n in range(len(data[i]['versions'])):
                #处理任务标记
                isTask = '1'
                for task in tasks:
                    tmp = public.getStrBetween('[',']',task['name'])
                    if not tmp:continue
                    tmp1 = tmp.split('-')
                    if data[i]['name'] == 'PHP':
                        if tmp1[0].lower() == data[i]['name'].lower() and tmp1[1] == data[i]['versions'][n]['version']: isTask = task['status'];
                    else:
                        if tmp1[0].lower() == data[i]['name'].lower(): isTask = task['status']

                #检查安装状态
                if data[i]['name'] == 'PHP':
                    data[i]['versions'][n]['task'] = isTask
                    checkFile = data[i]['check'].replace('VERSION',data[i]['versions'][n]['version'].replace('.',''))
                else:
                    data[i]['task'] = isTask
                    if data[i]['name'] != 'Openlitespeed':
                        version = public.readFile(public.GetConfigValue('root_path')+'/server/'+data[i]['name'].lower()+'/version.pl')
                        if not version:continue
                        if version.find(data[i]['versions'][n]['version']) == -1:continue
                    checkFile = data[i]['check']
                data[i]['versions'][n]['status'] = os.path.exists(checkFile)
        return public.return_message(0, 0, data)


    def GetLibList(self,get):
        #取插件列表
        import json,os
        tmp = public.readFile('data/libList.conf')
        data = json.loads(tmp)
        for i in range(len(data)):
            data[i]['status'] = self.CheckLibInstall(data[i]['check'])
            data[i]['optstr'] = self.GetLibOpt(data[i]['status'], data[i]['opt'])
        return public.return_message(0, 0, data)

    def CheckLibInstall(self,checks):
        for cFile in checks:
            if os.path.exists(cFile): return public.GetMsg('Already installed')
        return public.GetMsg('Not installed')

    #取插件操作选项
    def GetLibOpt(self,status,libName):
        optStr = ''
        if status == public.GetMsg('Not installed'):
            optStr = '<a class="link" href="javascript:InstallLib(\''+libName+'\');">'+public.GetMsg('Uninstallaton succeeded')+'</a>'
        else:
            libConfig = public.GetMsg('Old configuration')
            if(libName == 'beta'): libConfig = public.GetMsg('Beta tester profile')

            optStr = '<a class="link" href="javascript:SetLibConfig(\''+libName+'\');">'+libConfig+'</a> | <a class="link" href="javascript:UninstallLib(\''+libName+'\');">'+public.lang("Uninstallaton succeeded")+'</a>';
        return optStr

    #取插件AS
    def GetQiniuAS(self,get):
        filename = public.GetConfigValue('setup_path') + '/panel/data/'+get.name+'As.conf'
        if not os.path.exists(filename): public.writeFile(filename,'')
        data = {}
        data['AS'] = public.readFile(filename).split('|')
        data['info'] = self.GetLibInfo(get.name)
        if len(data['AS']) < 3:
            data['AS'] = ['','','','']
        return public.return_message(0, 0, data)


    #设置插件AS
    def SetQiniuAS(self,get):
        info = self.GetLibInfo(get.name)
        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_message(-1, 0, public.lang("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")

        if result[0].find("ERROR:") == -1:
            public.write_log_gettext("Plugin manager","Set plugin [" +info['name']+ "]AS!")
            return public.return_message(0, 0, public.lang("Successfully set"))
        return public.return_message(-1, 0, public.lang('ERROR: Unable to connect to the {} server, please check if the [AK/SK/Storage] setting is correct!', info['name']))

    #设置内测  todo 主要知道返回值结构
    def SetBeta(self,get):
        data = {}
        data['username'] = get.bbs_name
        data['qq'] = get.qq
        data['email'] = get.email
        result = public.httpPost(public.GetConfigValue('home') + '/Api/LinuxBeta',data)
        import json
        data = json.loads(result)
        if data['status']:
            public.writeFile('data/beta.pl',get.bbs_name + '|' + get.qq + '|' + get.email)
        return data
    #取内测资格状态
    def GetBetaStatus(self,get):
        try:
            return public.readFile('data/beta.pl').strip()
        except:
            return 'False'


    #获取指定插件信息
    def GetLibInfo(self,name):
        import json
        tmp = public.readFile('data/libList.conf')
        data = json.loads(tmp)
        for lib in data:
            if name == lib['opt']: return lib
        return False

    #获取文件列表
    def GetQiniuFileList(self,get):
        try:
            import json
            result = public.ExecShell(public.get_python_bin() + " " + public.GetConfigValue('setup_path') + "/panel/script/backup_"+get.name+".py list")
            return public.return_message(0, 0, json.loads(result[0]))
        except:
            return public.return_message(-1, 0, public.lang("Failed to get the list, please check if the [AK/SK/Storage] setting is correct!"))



    #取网络连接列表
    def GetNetWorkList(self,get):
        import psutil
        netstats = psutil.net_connections()
        networkList = []
        for netstat in netstats:
            tmp = {}
            if netstat.type == 1:
                tmp['type'] = 'tcp'
            else:
                tmp['type'] = 'udp'
            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
            networkList.append(tmp)
            del (p)
            del (tmp)
        networkList = sorted(networkList,
                             key=lambda x: x['status'],
                             reverse=True)
        return public.return_message(0, 0,  networkList)

    #取进程列表
    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()
                #进程名称
                if self.GoToProcess(tmp['name']): continue

                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)  #进程占用的内存比例
                pio = p.io_counters()
                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)
            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)
        return public.return_message(0, 0, processList)

    #结束指定进程
    def KillProcess(self, get):
        #return public.returnMsg(False, public.lang("演示服务器，禁止此操作!"));
        import psutil
        p = psutil.Process(int(get.pid))
        name = p.name()
        if name == 'python': return public.return_message(-1, 0, public.lang("Error, cannot end task processes!"))

        p.kill()
        public.write_log_gettext('Task manager','Ended processes[{}][{}] Successfully!',(get.pid,name))
        return public.return_message(0, 0,'Ended processes[{}][{}] Successfully!',(get.pid,name))

    def GoToProcess(self,name):
        ps = ['sftp-server','login','nm-dispatcher','irqbalance','qmgr','wpa_supplicant','lvmetad','auditd','master','dbus-daemon','tapdisk','sshd','init','ksoftirqd','kworker','kmpathd','kmpath_handlerd','python','kdmflush','bioset','crond','kthreadd','migration','rcu_sched','kjournald','iptables','systemd','network','dhclient','systemd-journald','NetworkManager','systemd-logind','systemd-udevd','polkitd','tuned','rsyslogd']

        for key in ps:
            if key == name: return True

        return False


    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 desc').select()
        return self.ToAddtime(data, None)

    def GetDiskIo(self, get):
        #取指定时间段的磁盘Io
        __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.return_message(0, 0,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
                    if tmp1.get("swap_total") == 0:
                        tmp1["swap_top"] = '[]'
                    tmp.append(tmp1)
                    del tmp1
                data = tmp
        except:
            return public.return_message(0, 0,[])
        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):
        try:
            #取指定时间段的CpuIo
            __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")
                cols.append("swap_top")
                __OPT_FIELD = ','.join(cols)
            field = 'cpu_top,swap_top'
            check_result = public.M('process_top_list').dbfile('system').order("id desc").field(field).limit(1).select()
            if type(check_result) == str:
                public.M('process_top_list').dbfile('system').execute("ALTER TABLE 'process_top_list' ADD 'swap_top' REAL DEFAULT '[]'",())
            sql = """SELECT cpuio.*, 
            process_top_list.cpu_top, 
            process_top_list.memory_top, 
            process_top_list.swap_top 
            FROM cpuio 
            LEFT JOIN process_top_list ON cpuio.addtime = process_top_list.addtime 
            WHERE cpuio.addtime >= {start} AND cpuio.addtime <= {end} 
            ORDER BY cpuio.addtime DESC"""
            data = public.M('cpuio').dbfile('system').query(sql.format(start=get.start, end=get.end), ())
            if isinstance(data, str) and data.find(
                    'error: no such table: process_top_list') != -1:
                return public.return_message(0, 0,public.M('cpuio').dbfile('system').where(
                    "addtime>=? AND addtime<=?",
                    (get.start, get.end
                     )).field('id,pro,mem,swap_total,swap_percent,swap_used,swap_free,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 public.return_message(0, 0,[])
            return self.ToAddtime(data, True, 'cpu')
        except:
            public.print_log(public.get_error_info())

    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.return_message(0, 0,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 public.return_message(0, 0,[])
        return self.ToAddtime(data, True, 'cpu')

    def get_process_tops(self, get):
        '''
            @name 获取进程开销排行
            @author hwliang<2021-09-07>
            @param get<dict_obj>{
                start: int<开始时间>
                end: int<结束时间>
            }
            @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()
        return self.ToAddtime(data)

    def get_process_cpu_high(self, get):
        '''
            @name 获取CPU占用高的进程列表
            @author hwliang<2021-09-07>
            @param get<dict_obj>{
                start: int<开始时间>
                end: int<结束时间>
            }
            @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()
        return self.ToAddtime(data)

    @staticmethod
    def trans_json(data: str, defualt=None):
        try:
            import ujson as json
        except ImportError:
            import json
        try:
            if data:
                return json.loads(data)
            return defualt
        except Exception:
            return defualt

    def ToAddtime(self, data, tomem=False, types=None):
        import time
        #格式化addtime列

        if tomem:
            import psutil
            mPre = (psutil.virtual_memory().total / 1024 / 1024) / 100
        length = len(data)
        he = 1
        if length > 100: he = 1
        if length > 1000: he = 3
        if length > 10000: he = 15
        if he == 1:
            for i in range(length):
                try:
                    if types:
                        key = '{}_top'.format(types)
                        if key in data[i]:
                            data[i][key] = self.trans_json(data[i][key], [])
                        if 'memory_top' in data[i]:
                            data[i]['memory_top'] = self.trans_json(data[i]['memory_top'], [])
                        if 'swap_top' in data[i]:
                            if not data[i]['swap_top']:data[i]['swap_top']=[]
                            else:
                                data[i]['swap_top'] = json.loads(
                                    data[i]['swap_top'])
                    data[i]['addtime'] = time.strftime(
                        '%m/%d %H:%M',
                        time.localtime(float(data[i]['addtime'])))
                    if 'swap_top' in data[i]:
                        if not data[i]['swap_top']:
                            data[i]['swap_top'] = []
                    else:data[i]['swap_top'] = []
                    if 'process_list' in data[i]:
                        data[i]['process_list'] = self.trans_json(data[i]['process_list'], [])
                    if tomem and 'mem' in data[i] 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'] = self.trans_json(data[i]['down_packets'])
                            data[i]['up_packets'] = self.trans_json(data[i]['up_packets'])
                except:
                    continue
            return public.return_message(0, 0, data)
        else:
            count = 0
            tmp = []
            couns = 0
            for value in data:
                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] = self.trans_json(value[key], [])
                                if 'memory_top' in value and value['memory_top']:
                                    value['memory_top'] = self.trans_json(value['memory_top'], [])
                                if 'swap_top' in value:
                                    if not value['swap_top']:value['swap_top']=[]
                                    else:
                                        value['swap_top'] = json.loads(
                                            value['swap_top'])
                                else:
                                    value['swap_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'] = self.trans_json(value['down_packets'])
                                    value['up_packets'] = self.trans_json(value['up_packets'])
                            tmp.append(value)
                    continue
                try:
                    if types:
                        key='{}_top'.format(types)
                        if key in value:
                            value[key] = self.trans_json(value[key], [])
                        if 'memory_top' in value:
                            value['memory_top'] = self.trans_json(value['memory_top'], [])
                        if 'swap_top' in value:
                            if not value['swap_top']:value['swap_top']=[]
                            else:
                                value['swap_top'] = json.loads(
                                    value['swap_top'])
                        else:
                            value['swap_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'] = self.trans_json(value['down_packets'])
                            value['up_packets'] = self.trans_json(value['up_packets'])
                    tmp.append(value)
                    count = 0
                except: continue
            return public.return_message(0, 0,  tmp)



    def GetInstalleds(self,softlist):
        softs = ''
        for soft in softlist['data']:
            try:
                for v in soft['versions']:
                    if v['status']: softs += soft['name'] + '-' + v['version'] + '|'
            except:
                pass
        return softs



    #获取SSH爆破次数
    def get_ssh_intrusion(self):
        fp = open('/var/log/secure','rb')
        l = fp.readline()
        intrusion_total = 0
        while l:
            if l.find('Failed password for root') != -1:  intrusion_total += 1
            l = fp.readline()
        fp.close()
        return intrusion_total

    #申请内测版
    def apple_beta(self,get):
        try:
            # userInfo = json.loads(public.ReadFile('data/userInfo.json'))
            # p_data = {}
            # p_data['uid'] = userInfo['uid']
            # p_data['access_key'] = userInfo['access_key']
            # p_data['username'] = userInfo['username']
            # result = public.HttpPost(public.GetConfigValue('home') + '/api/panel/apple_beta',p_data,5)
            public.writeFile('/www/server/panel/data/is_beta.pl','true')
            try:
                return public.return_message(0, 0, public.lang("Successful application!"))
            except: return public.return_message(-1, 0, public.lang("Fail to connect to the server!"))
        except: return public.return_message(-1, 0, public.lang("Please bind your account first!"))

    def to_not_beta(self,get):
        try:
            # userInfo = json.loads(public.ReadFile('data/userInfo.json'))
            # p_data = {}
            # p_data['uid'] = userInfo['uid']
            # p_data['access_key'] = userInfo['access_key']
            # p_data['username'] = userInfo['username']
            # result = public.HttpPost(public.GetConfigValue('home') + '/api/panel/to_not_beta',p_data,5)
            try:
                beta_file = '/www/server/panel/data/is_beta.pl'
                if os.path.exists(beta_file):
                    os.remove(beta_file)
                return public.return_message(0, 0, public.lang("Successful application!"))
            except: return public.return_message(-1, 0, public.lang("Fail to connect to the server!"))
        except: return public.return_message(-1, 0, public.lang("Please bind your account first!"))

    def to_beta(self):
        try:
            userInfo = json.loads(public.ReadFile('data/userInfo.json'))
            p_data = {}
            p_data['uid'] = userInfo['uid']
            p_data['access_key'] = userInfo['access_key']
            p_data['username'] = userInfo['username']
            public.HttpPost(public.GetConfigValue('home') + '/api/panel/to_beta',p_data,5)
        except: pass

    def get_uid(self):
        try:
            userInfo = json.loads(public.ReadFile('data/userInfo.json'))
            return userInfo['uid']
        except: return 0

    #获取最新的5条测试版更新日志
    def get_beta_logs(self,get):
        try:
            data = json.loads(public.HttpGet('{}/api/panel/getBetaVersionLogs'.format(self.__official_url)))
            return public.return_message(0,0,data)
        except:
            return public.return_message(-1, 0, public.lang("Fail to connect to the server!"))
    #
    def get_version_logs(self,get):

        LOG_TYPE_MAP = {
            'beta': '/api/panel/getBetaVersionLogs',
            'official': '/api/panel/getOfficialVersionLogs',
            'pro': '/api/panel/getProVersionLogs'
        }

        # 确定当前面板类型
        if os.path.exists('/www/server/panel/data/panel_pro.pl'):
            log_type = 'pro'
        elif os.path.exists('/www/server/panel/data/is_beta.pl'):
            log_type = 'beta'
        else:
            log_type = 'official'


        # 检查是否支持该类型
        if log_type not in LOG_TYPE_MAP:
            return public.return_message(-1, 0, public.lang("Parameter error!"))

        api_path = LOG_TYPE_MAP[log_type]

        # pro 版后续再做
        if api_path is None:
            return public.return_message(0, 0, {"list": []})

        url = self.__official_url + api_path

        try:
            data = json.loads(public.HttpGet(url))
            return public.return_message(0,0,data)
        except:
            return public.return_message(-1, 0, public.lang("Fail to connect to the server!"))


    def get_other_info(self):
        other = {}
        other['ds'] = []
        ds = public.M('domain').field('name').select()
        for d in ds:
            other['ds'].append(d['name'])
        return ','.join(other['ds'])


    # # 修复面板
    # def RepPanel(self, get):
    #     """修复面板"""
    #     disk = psutil.disk_usage(public.get_panel_path())
    #     if disk.free < 50 * 1024 * 1024:
    #         return public.fail_v2(public.lang("Disk is not enough [50MB] , can't continue!"))
    #     pids = psutil.pids()
    #     for pid in pids:
    #         try:
    #             p = psutil.Process(pid)
    #             if "python3" in p.name():
    #                 if "repair_panel" in p.cmdline()[-1]:
    #                     public.ExecShell("kill -9 {}".format(pid))
    #         except:
    #             pass
    #
    #     if not 'force' in get:
    #         data = {'local':{},'cloud':{}, 'upgrade':0}
    #         data['local']['version'] = '{}'.format(public.version())
    #         update_time = public.readFile("{}/config/update_time.pl".format(public.get_panel_path()))
    #         if not update_time:
    #             update_time = os.path.getmtime('{}/class/common.py'.format(public.get_panel_path()))
    #
    #         data['local']['update_time'] = int(update_time)
    #         try:
    #             down_url = 'https://node.aapanel.com/install/update/LinuxPanel_EN-{}.pl'.format(data['local']['version'])
    #             if os.path.exists("/tmp/LinuxPanel_EN-{}.pl".format(data['local']['version'])):
    #                 os.remove("/tmp/LinuxPanel_EN-{}.pl".format(data['local']['version']))
    #             public.downloadFile(down_url, "/tmp/LinuxPanel_EN-{}.pl".format(data['local']['version']))
    #             try:
    #                 pl_info = json.loads(public.readFile("/tmp/LinuxPanel_EN-{}.pl".format(data['local']['version'])))
    #                 if int(pl_info['update_time']) > int(data['local']['update_time']):
    #                     data['upgrade'] = 1
    #                     data['cloud'] = {}
    #                     data['cloud']['hash'] = pl_info['hash']
    #                     data['cloud']['update_time'] = pl_info['update_time']
    #                     data['cloud']['version'] = data['local']['version']
    #             except Exception as e:
    #                 public.print_log(f"error: {e}")
    #                 data['upgrade'] = 0
    #         except Exception as e1:
    #             public.print_log(f"error1: {e1}")
    #             data['upgrade'] = 1
    #         return public.success_v2(data)
    #     else:
    #         # ==================== 日志进度, 新分支 =====================
    #         update_time_file = "{}/config/update_time.pl".format(public.get_panel_path())
    #         if os.path.exists(update_time_file):
    #             os.remove(update_time_file)
    #         try:
    #             pl_info = json.loads(public.readFile("/tmp/LinuxPanel_EN-{}.pl".format(public.version())))
    #             public.writeFile(update_time_file, str(pl_info['update_time']))
    #         except:
    #             pass
    #
    #         logPath = '/tmp/upgrade_panel.log'
    #         public.writeFile(logPath, "")
    #         shell = 'nohup {} -u {}/script/upgrade_panel_optimized.py repair_panel {} &>{} &'.format(
    #             public.get_python_bin(), public.get_panel_path(), public.version(), logPath
    #         )
    #         public.ExecShell(shell)
    #         return public.success_v2(public.lang("Panel repair task has been started, please check"))


    # 修复面板

    def __get_remote_version_info(self) -> dict:
        """远程版本信息"""
        import psutil
        import system
        import panelPlugin
        logs = public.get_debug_log()
        mem = psutil.virtual_memory()
        mplugin = panelPlugin.panelPlugin()
        mplugin.ROWS = 10000
        panelsys = system.system()
        data = {
            'ds': '',
            'sites': str(public.M('sites').count()),
            'ftps': str(public.M('ftps').count()),
            'databases': str(public.M('databases').count()),
            'system': panelsys.GetSystemVersion() + '|' + str(mem.total / 1024 / 1024) + 'MB|' + str(
                public.getCpuType()
            ) + '*' + str(psutil.cpu_count()) + '|' + str(public.get_webserver()) + '|' + \
                      session['version']
        }
        data['system'] += '||' + self.GetInstalleds(mplugin.getPluginList(None))
        data['logs'] = logs
        data['client'] = request.headers.get('User-Agent')
        data['oem'] = ''
        data['intrusion'] = 0
        data['uid'] = self.get_uid()
        data['o'] = public.get_oem_name()
        sUrl = '{}/api/panel/updateLinuxEn'.format(self.__official_url)
        updateInfoRaw = public.httpPost(sUrl, data, timeout=60)
        if not updateInfoRaw or len(updateInfoRaw) == 0:
            return {}
        try:
            updateInfo = json.loads(updateInfoRaw)
        except:
            return {}
        session['updateInfo'] = updateInfo
        return updateInfo

    def __generate_upgrade_cmd(self, fun: str, version: str, is_pro: bool = False) -> str:
        """生成升级修复命令"""
        py_bin = public.get_python_bin()
        panel_path = public.get_panel_path()
        script_path = 'script/upgrade_panel_optimized.py'
        logPath = '/tmp/upgrade_panel.log'
        pro_str = 'is_pro' if is_pro else ''
        return f"nohup {py_bin} -u {panel_path}/{script_path} {fun} {version} {pro_str} &>{logPath} &"

    def RepPanel(self, get):
        """修复面板到正式版"""
        disk = psutil.disk_usage(public.get_panel_path())
        if disk.free < 50 * 1024 * 1024:
            return public.fail_v2(public.lang("Disk is not enough [50MB] , can't continue!"))
        pids = psutil.pids()
        for pid in pids:
            try:
                p = psutil.Process(pid)
                if "python3" in p.name():
                    if "repair_panel" in p.cmdline()[-1]:
                        public.ExecShell("kill -9 {}".format(pid))
            except:
                pass
        # is pro
        is_pro = True if os.path.exists('/www/server/panel/data/panel_pro.pl') else False
        updateInfo = self.__get_remote_version_info()
        if not updateInfo:
            return public.fail_v2(public.lang("Failed to connect server!"))

        if is_pro:
            target_version = updateInfo.get('pro', {}).get('version')
        else:
            target_version = updateInfo.get('version')

        if not target_version:
            return public.fail_v2(public.lang("Failed to connect server!"))
        public.print_log(f"target_version = {target_version}")
        if not 'force' in get:
            data = {
                'local': {},
                'cloud': {},
                'upgrade': 1, # 总是恢复
            }
            data['local']['version'] = '{}'.format(public.version())
            update_time = public.readFile("{}/config/update_time.pl".format(public.get_panel_path()))
            if not update_time:
                update_time = os.path.getmtime('{}/class/common.py'.format(public.get_panel_path()))
            data['local']['update_time'] = int(update_time)
            data['cloud']['version'] = target_version
            return public.success_v2(data)
        else:
            # ==================== 日志进度, 新分支 =====================
            shell = self.__generate_upgrade_cmd('repair_panel', target_version, is_pro)
            public.print_log(f"shell = {shell}")
            public.ExecShell(shell)
            return public.success_v2(public.lang("Panel repair task has been started, please check"))

    def is_cron_hardened(self, safe_status):
        """
        判断系统是否启用了对 cron 的加固功能。

        参数:
        safe_status (dict): 安全状态信息，来自 get_safe_status() 函数的返回结果

        返回:
        bool: 如果 cron 加固开启，返回 True，否则返回 False
        """

        if not safe_status.get('open', False):
            return False  # 如果整体的加固功能关闭，直接返回 False
        # 遍历返回的加固项目列表
        for item in safe_status.get('list', []):
            if item.get('key') == 'cron':
                return item.get('open', False)  # 如果找到了 cron 相关的加固项，返回其状态

        return False  # 如果没有找到 cron 相关的加固项，默认返回 False

    # 预约延迟更新面板
    def ScheduleUpdate(self, get):
        try:
            get.validate([
                Param('execute_time').String(),
                Param('version').String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            return public.return_message(-1, 0, str(ex))

        # 系统加固检测
        import PluginLoader
        if os.path.exists("/etc/init.d/bt_syssafe"):
            safe_status = PluginLoader.plugin_run("syssafe", "get_safe_status", "")
            # 检查 cron 是否加固
            if self.is_cron_hardened(safe_status):
                return public.fail_v2(public.lang('File write operation failed. If the System Hardening plugin is enabled, please temporarily disable it first.'))


        execute_time = get.execute_time.strip()
        version = get.version.strip()

        if not version or len(version) > 20:
            return public.fail_v2(public.lang("Invalid version number"))

        # 校验时间格式
        from datetime import datetime
        try:
            execute_dt = datetime.strptime(execute_time, "%Y-%m-%d %H:%M:%S")
        except ValueError:
            return public.fail_v2(public.lang(f'Execute time format error: {execute_time}, required: YYYY-MM-DD HH:MM:SS'))

        if execute_dt <= datetime.now():
            return public.fail_v2(public.lang('Execute time must be in the future'))


        script_content = f"""#!/bin/bash
# Delay the update of the panel script and trigger it by the systemd timer at the specified time.
VERSION="{version}"

echo "====================================="
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Scheduled update script started"
echo "Target version: ${{VERSION}}"
echo "====================================="

# Check if Python is available.
if [ ! -f /www/server/panel/pyenv/bin/python3 ]; then
    echo "[ERROR] python3 binary not found at /www/server/panel/pyenv/bin/python3"
    exit 1
fi

# Check if the upgrade script exists
if [ ! -f /www/server/panel/script/upgrade_panel_optimized.py ]; then
    echo "[ERROR] upgrade_panel_optimized.py not found"
    exit 1
fi

# Determine whether it is the pro version
IS_PRO=""
if [ -f /www/server/panel/data/panel_pro.pl ]; then
    IS_PRO="is_pro"
fi

PY_BIN=/www/server/panel/pyenv/bin/python3
SCRIPT_PATH=/www/server/panel/script/upgrade_panel_optimized.py

echo "[INFO] Starting upgrade..."
$PY_BIN -u $SCRIPT_PATH upgrade_panel ${{VERSION}} ${{IS_PRO}} 2>&1
echo "[INFO] Upgrade script exited with code: $?"


# Make sure that the panel service has been started.
echo "[INFO] Checking panel service status..."
sleep 3
if [ -f /etc/init.d/bt ]; then
    /etc/init.d/bt start 2>&1
    sleep 2
    bt status 2>&1
    echo "[INFO] Panel service restart completed"
fi

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Scheduled update script finished"
echo "=====================================" """

        # 调用 add_once_crontab 创建一次性定时任务
        import crontab_v2
        crontab_obj = crontab_v2.crontab()

        # 清理旧的延迟更新任务，避免堆积
        old_tasks = public.M('crontab').where("type=? AND sType=? AND name LIKE ?", ('once', 'toShell', 'Delay the update of the panel to version%')).field('id,name').select()
        for task in old_tasks:
            try:
                del_get = public.dict_obj()
                del_get.id = task['id']
                crontab_obj.DelCrontab(del_get)
            except:
                pass

        cron_get = public.dict_obj()
        cron_get.name = f'Delay the update of the panel to version {version}'
        cron_get.sType = 'toShell'
        cron_get.sBody = script_content
        cron_get.where1 = execute_time
        cron_get.type = 'once'
        cron_get.save = '0'
        cron_get.backupTo = ''
        cron_get.version = version
        cron_get.kill_mode = 'process'  # 防止systemd杀死面板子进程

        result = crontab_obj.add_once_crontab(cron_get)

        # 将版本加入忽略列表 页面不再弹出更新提示
        no_path = '{}/data/no_update.pl'.format(public.get_panel_path())
        try:
            data = json.loads(public.readFile(no_path)) if os.path.exists(no_path) else []
        except:
            data = []
        if version not in data:
            data.append(version)
            public.writeFile(no_path, json.dumps(data))

        return public.return_message(0, 0, public.lang("Setup successful. The panel will be updated to version {} after {}", version,execute_time))

    # 更新面板
    def UpdatePanel(self,get):
        if 'check' in get:
            # 校验参数
            try:
                get.validate([
                    Param('check').Bool(),
                    Param('version').String(),
                ], [
                    public.validate.trim_filter(),
                ])
            except Exception as ex:
                public.print_log("error info: {}".format(ex))
                return public.return_message(-1, 0, str(ex))

        import psutil
        disk = psutil.disk_usage(public.get_panel_path())
        if disk.free < 50 * 1024 * 1024:
            return public.fail_v2(public.lang("Disk is not enough [50MB] , can't continue!"))

        try:
            # if not public.IsRestart():
            #     return public.return_message(-1, 0, public.lang("Please run the program when all install tasks finished!"))

            import json
            conf_status = public.M('config').where("id=?",('1',)).field('status').find()
            if int(session['config']['status']) == 0 and int(conf_status['status']) == 0:
                public.arequests('get', '{}/api/setupCount/setupPanel?type=Linux'.format(self.__official_url))
                public.M('config').where("id=?",('1',)).setField('status',1)

            #取回远程版本信息
            if 'updateInfo' in session and hasattr(get,'check') == False:
                updateInfo = session['updateInfo']
            else:
                updateInfo = self.__get_remote_version_info()
                if not updateInfo:
                    return public.return_message(-1, 0, public.lang("Failed to connect server! -1"))
                session['updateInfo'] = updateInfo

            # 判断是否测试版
            updateInfo['is_beta'] = 0

            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

            # # 更新时默认安装pflogsumm
            # if not os.path.exists('/usr/sbin/pflogsumm'):
            #     linux_distr = public.get_linux_distribution().lower()
            #     if linux_distr == 'centos7':
            #         public.ExecShell('yum install postfix-pflogsumm -y')
            #     elif linux_distr == 'centos8':
            #         public.ExecShell('yum install postfix-pflogsumm -y')
            #     elif linux_distr == 'ubuntu':
            #         public.ExecShell('apt install pflogsumm -y')

            # 判断邮局版本并更新
            # if os.path.exists('/www/server/panel/plugin/mail_sys/info.json'):
            #     versions = public.get_plugin_info("mail_sys")['versions']
            #     # 5开头的版本  且版本不是最新的
            #     if versions.startswith('5') and versions < "5.2":
            #         import panelPlugin
            #         args = public.dict_obj()
            #         args.sName = "mail_sys"
            #         args.version = "5.2"
            #         args.upgrade = "5.2"
            #         panelPlugin.panelPlugin().install_plugin(args)

            # 重启安装jwt
            if not os.path.exists('/www/server/panel/pyenv/lib/python3.12/site-packages/jwt'):
                public.ExecShell('btpip install pyjwt ')

            # 重启面板 默认开启系统监控
            # public.writeFile('data/control.conf', '30')
            #pro
            if os.path.exists('data/panel_pro.pl'): updateInfo['version']=session['version']

            # 判断本地版本是否最新
            updateInfo['local_is_latest'] = False
            #pro
            updateInfo['is_pro'] = 0  # 设置初始值 确保key存在
            if os.path.exists('/www/server/panel/data/panel_pro.pl'):
                updateInfo['is_pro'] = 1

            #检查是否需要升级
            if not hasattr(get,'toUpdate'):
                # 基础的更新时间判断
                # down_url = f'{public.get_url()}/install/update/LinuxPanel_EN-{public.version()}.pl'
                # tmp_pl = f"/tmp/LinuxPanel_EN-{public.version()}.pl"
                # if os.path.exists(tmp_pl):
                #     os.remove(tmp_pl)
                # public.downloadFile(down_url, tmp_pl)
                # try:
                #     pl_info = json.loads(public.readFile(tmp_pl))
                #     update_time = public.readFile("{}/config/update_time.pl".format(public.get_panel_path()))
                #     if not update_time:
                #         update_time = os.path.getmtime('{}/class/common.py'.format(public.get_panel_path()))
                #     if int(pl_info['update_time']) > int(update_time):
                #         updateInfo['local_is_latest'] = True
                # except:
                #     updateInfo['local_is_latest'] = True
                # aa特有判断, 覆盖local_is_latest
                if updateInfo['is_beta'] == 1:
                    if updateInfo['beta']['version'] == session['version']:
                        updateInfo['local_is_latest'] = True
                else:
                    if updateInfo['version'] == session['version']:
                        updateInfo['local_is_latest'] = True
                if updateInfo['is_pro'] == 1:
                    dpkg = Dpkg
                    try:
                        aa = dpkg.compare_versions(session['version'],updateInfo['pro']['version'])
                        updateInfo['local_is_latest'] = False if aa == -1 else True
                    except:
                        public.print_log(public.get_error_info())
                        updateInfo['local_is_latest'] = False

                return public.return_message(0, 0, updateInfo)

            # ==================== 日志进度, 新分支 =====================
            # 存在toUpdate, version时, 确认升级到指定版本
            if hasattr(get, 'version') and hasattr(get, 'toUpdate'):
                get.version = get.get("version", None)
                if get.version is None:
                    return public.fail_v2('version is error')
                if updateInfo['is_beta'] == 1: self.to_beta()
                shell = self.__generate_upgrade_cmd('upgrade_panel', get.version, updateInfo.get('is_pro', 0) == 1)
                public.ExecShell(shell)
                return public.success_v2('Update task has been started, please check')
            # ==================== 日志进度, 新分支 End =====================

            # 旧升级方法
            #是否执行升级程序
            if(updateInfo['force'] == True or hasattr(get,'toUpdate') == True or os.path.exists('data/autoUpdate.pl') == True):
                if not public.IsRestart():
                    return public.return_message(-1, 0, public.lang("Please run the program when all install tasks finished!"))
                panel_update_name = '/LinuxPanel_EN-'
                if updateInfo['is_beta'] == 1: updateInfo['version'] = updateInfo['beta']['version']
                if updateInfo['is_pro'] == 1:
                    updateInfo['version'] = updateInfo['pro']['version']
                    panel_update_name = '/LinuxPanelPro_EN-'

                setupPath = public.GetConfigValue('setup_path')
                uptype = 'update'
                httpUrl = public.get_url()
                if httpUrl: updateInfo['downUrl'] = httpUrl + '/install/' + uptype + panel_update_name + updateInfo['version'] + '.zip'
                public.downloadFile(updateInfo['downUrl'],'panel.zip')
                if os.path.getsize('panel.zip') < 1048576: return public.return_message(-1, 0, public.lang("File download failed, please try again or update manually!"))
                cmd = 'unzip -o panel.zip -d {}/ && chmod 700 {}/panel/BT-Panel'.format(setupPath, setupPath)
                public.print_log(cmd)
                public.ExecShell(cmd)

                # 清除pycache编译缓存
                remove_py_caches = [
                    '{}/__pycache__'.format(public.get_panel_path()),
                    '{}/class/__pycache__'.format(public.get_panel_path()),
                    '{}/class_v2/__pycache__'.format(public.get_panel_path()),
                ]

                for pycache_dir in remove_py_caches:
                    if os.path.exists(pycache_dir):
                        os.system('rm -rf {}'.format(pycache_dir))

                # if os.path.exists('/www/server/panel/runserver.py'): public.ExecShell('rm -f /www/server/panel/*.pyc')
                # if os.path.exists('/www/server/panel/class/common.py'): public.ExecShell('rm -f /www/server/panel/class/*.pyc')

                if os.path.exists('panel.zip'):os.remove("panel.zip")
                session['version'] = updateInfo['version']
                if 'getCloudPlugin' in session: del(session['getCloudPlugin'])
                if updateInfo['is_beta'] == 1: self.to_beta()
                public.ExecShell("/etc/init.d/bt start")
                public.writeFile('data/restart.pl','True')
                return public.return_message(0,0, public.gettext_msg('Successful to update to {}',(updateInfo['version'],)))

            public.ExecShell('rm -rf /www/server/phpinfo/*')
            return public.return_message(0, 0, updateInfo)
        except Exception as ex:
            return public.return_message(-1, 0, public.get_error_info())
            # return public.return_message(-1, 0, public.lang("Failed to connect server!"))

    #检查是否安装任何
    def CheckInstalled(self,get):
        checks = ['nginx','apache','php','pure-ftpd','mysql']
        import os
        for name in checks:
            filename = public.GetConfigValue('root_path') + "/server/" + name
            if os.path.exists(filename): return True
        return False


    #取已安装软件列表
    def GetInstalled(self,get):
        import system_v2 as system_v2
        data = system_v2.system().GetConcifInfo()
        return data

    #取PHP配置
    def GetPHPConfig(self,get):
        if not hasattr(get,'version') or not get.version:
            return public.return_message(-1, 0, public.lang("Parameter error!"))
        # 统一转为字符串（防 int/float）
        ver_str = str(get.version).strip()

        # 如果太短，自动补 '0'（兼容 "8" → "80"）
        if len(ver_str) < 2:
            ver_str = ver_str.ljust(2, '0')  # "8" → "80", "" → "00"

        # 是否为数字
        if not ver_str.isdigit():
            return public.return_message(-1, 0, public.lang("Version must be numeric!"))

        import re,json
        def match_php_ext_search(lib, search):
            if not search:
                return True
            search_text = ' '.join([
                str(lib.get('name', '')),
                str(lib.get('type', '')),
                str(lib.get('msg', '')),
            ]).lower()
            return search in search_text

        search = str(getattr(get, 'search', '')).strip().lower()
        filename = public.GetConfigValue('setup_path') + '/php/' + get.version + '/etc/php.ini'
        if public.get_webserver() == 'openlitespeed':
            filename = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version,get.version[0],get.version[1])
            if os.path.exists('/etc/redhat-release'):
                filename = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini'
        if not os.path.exists(filename): return public.return_message(-1, 0, public.lang("Requested PHP version does NOT exist!"))
        phpini = public.readFile(filename)
        data = {}
        rep = "disable_functions\\s*=\\s{0,1}(.*)\n"

        tmp = re.search(rep,phpini)
        if tmp:
            data['disable_functions'] = tmp.groups()[0]

        rep = r"upload_max_filesize\s*=\s*([0-9]+)(M|m|K|k)"

        tmp = re.search(rep,phpini)
        if tmp:
            data['max'] = tmp.groups()[0]

        rep = u"\n;*\\s*cgi\\.fix_pathinfo\\s*=\\s*([0-9]+)\\s*\n"
        tmp = re.search(rep,phpini)
        if tmp:
            if tmp.groups()[0] == '0':
                data['pathinfo'] = False
            else:
                data['pathinfo'] = True

        self.getCloudPHPExt(get)
        phplib = json.loads(public.readFile('data/phplib.conf'))
        libs = []
        tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select()
        phpini_ols = None
        for lib in phplib:
            lib['task'] = '1'
            for task in tasks:
                tmp = public.getStrBetween('[',']',task['name'])
                if not tmp:continue
                tmp1 = tmp.split('-')
                if tmp1[0].lower() == lib['name'].lower():
                    lib['task'] = task['status']
                    lib['phpversions'] = []
                    lib['phpversions'].append(tmp1[1])
            if public.get_webserver() == 'openlitespeed':
                lib['status'] = False
                get.php_version = "{}.{}".format(get.version[0],get.version[1])
                if not phpini_ols:
                    phpini_ols = self.php_info(get)
                    if phpini_ols['status'] != 0:
                        return False
                    phpini_ols = phpini_ols['message']['phpinfo']['modules'].lower().split()
                for i in phpini_ols:
                    if lib['check'][:-3].lower() == i :
                        lib['status'] = True
                        break
                    if "ioncube" in lib['check'][:-3].lower() and "ioncube" == i:
                        lib['status'] = True
                        break
            else:
                if phpini.find(lib['check']) == -1:
                    lib['status'] = False
                else:
                    lib['status'] = True

            # 过滤版本不匹配的插件
            if get.version not in lib['versions']:continue
            if not match_php_ext_search(lib, search):continue
            libs.append(lib)

        data['libs'] = libs
        return public.return_message(0, 0, data)

    #获取PHP扩展
    def getCloudPHPExt(self,get):
        try:
            self._process_chinese_ext_description()
            if 'php_ext' in session: return True
            if not self._get_cloud_phplib():
                return False
            session['php_ext'] = True
            return True
        except:
            return False

    # 处理PHP插件变描述中文
    def _process_chinese_ext_description(self):
        chinese = None
        phplib = json.loads(public.readFile('data/phplib.conf'))
        for p in phplib:
            if "缓存器" in p['type']:
                chinese = True
                break
        if chinese:
            self._get_cloud_phplib()

    # 下载云端php扩展配置
    def _get_cloud_phplib(self):
        if not session.get('download_url'): session['download_url'] = public.OfficialDownloadBase()
        download_url = session['download_url'] + '/install/lib/phplib_en.json'
        tstr = public.httpGet(download_url)
        data = json.loads(tstr)
        if not data: return False
        public.writeFile('data/phplib.conf', json.dumps(data))
        return True

    #取PHPINFO信息
    def GetPHPInfo(self,get):
        if public.get_webserver() == "openlitespeed":
            shell_str = "/usr/local/lsws/lsphp{}/bin/php -i".format(get.version)
            return public.return_message(0,0,public.ExecShell(shell_str)[0])
        sPath = '/www/server/phpinfo'
        if os.path.exists(sPath):
            public.ExecShell("rm -rf " + sPath)
        p_file = '/dev/shm/phpinfo.php'
        public.writeFile(p_file,'<?php phpinfo(); ?>')
        phpinfo = public.request_php(get.version,'/phpinfo.php','/dev/shm')
        if os.path.exists(p_file): os.remove(p_file)
        return public.return_message(0,0,phpinfo.decode())

    #清理日志
    def delClose(self,get):
        if not 'uid' in session: session['uid'] = 1
        if session['uid'] != 1: return public.return_message(-1, 0, public.lang("Permission denied!"))
        if 'tmp_login_id' in session:
            return public.return_message(-1, 0, public.lang("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("Clear the log", 'The number of log entries cleared is:{}'.format(public.M('logs').count()))
        # 清空日志
        public.M('logs').where('id>?',(0,)).delete()
        public.write_log_gettext('Panel setting','Panel Logs emptied!')
        return public.return_message(0, 0, public.lang("Panel Logs emptied!"))

    def __get_webserver_conffile(self):
        webserver = public.get_webserver()
        if webserver == 'nginx':
            filename = public.GetConfigValue('setup_path') + '/nginx/conf/nginx.conf'
        elif webserver == 'openlitespeed':
            filename = public.GetConfigValue('setup_path') + "/panel/vhost/openlitespeed/detail/phpmyadmin.conf"
        else:
            filename = public.GetConfigValue('setup_path') + '/apache/conf/extra/httpd-vhosts.conf'
        return filename, webserver

    # 获取phpmyadmin ssl配置
    def get_phpmyadmin_conf(self):
        if public.get_webserver() == "nginx":
            conf_file = "/www/server/panel/vhost/nginx/phpmyadmin.conf"
            rep = r"listen\s*(\d+)"
        else:
            conf_file = "/www/server/panel/vhost/apache/phpmyadmin.conf"
            rep = r"Listen\s*(\d+)"
        return {"conf_file":conf_file,"rep":rep}

    # 设置phpmyadmin路径
    def set_phpmyadmin_session(self):
        import re
        conf_file = self.get_phpmyadmin_conf()
        conf = public.readFile(conf_file["conf_file"])
        rep = conf_file["rep"]
        if conf:
            port = re.search(rep,conf).group(1)
            if session['phpmyadminDir']:
                path = session['phpmyadminDir'].split("/")[-1]
                ip = public.GetHost()
                session['phpmyadminDir'] = "https://{}:{}/{}".format(ip, port, path)

    # 获取phpmyadmin ssl状态
    def get_phpmyadmin_ssl(self,get):
        import re
        conf_file = self.get_phpmyadmin_conf()
        conf = public.readFile(conf_file["conf_file"])
        rep = conf_file["rep"]
        if conf:
            search_port = re.search(rep, conf)
            if search_port:
                port = search_port.group(1)
                return public.success_v2({"status":True,"port":port})
        return public.success_v2({"status":False,"port":""})

    # 修改php ssl端口
    def change_phpmyadmin_ssl_port(self,get):
        if public.get_webserver() == "openlitespeed":
            return public.fail_v2(public.lang('The current web server is openlitespeed. This function is not supported yet.'))

        import re
        try:
            port = int(get.port)
            if 1 > port > 65535:
                return public.fail_v2(public.lang('Port range is incorrect!'))
        except:
            return public.fail_v2(public.lang('Please enter the correct port number'))

        service_type = ["nginx","apache"]
        if public.get_multi_webservice_status():
            service_type = ["nginx"]

        is_changed = False
        oldPort = None
        for i in service_type:
            file = "/www/server/panel/vhost/{}/phpmyadmin.conf".format(i)
            conf = public.readFile(file)
            if not conf:
                return public.fail_v2(public.lang('Did not find the {} configuration file, please try to close the ssl port settings before opening',i))
            rulePort = ['80', '443', '21', '20', '8080', '8081', '8089', '11211', '6379', '8188', '8189', '8190', '8288', '8289', '8290']
            if get.port in rulePort:
                return public.fail_v2(public.lang('Please do NOT use the usual port as the phpMyAdmin port!'))

            if i == "nginx":
                if not os.path.exists("/www/server/panel/vhost/nginx/phpmyadmin.conf"):
                    return public.fail_v2(public.lang('Did not find the nginx phpmyadmin ssl configuration file, please try to close the ssl port settings before opening'))

                rep = r"listen\s*([0-9]+)\s*.*;"
                oldPort = re.search(rep, conf)
                if not oldPort:
                    return public.fail_v2(public.lang('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 = r"Listen\s*([0-9]+)\s*\n"
                oldPort = re.search(rep, conf)
                if not oldPort:
                    return public.fail_v2(public.lang('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 = r"VirtualHost\s*\*:[0-9]+"
                conf = re.sub(rep, "VirtualHost *:" + get.port, conf, 1)

            if oldPort != get.port and is_changed is False:
                is_changed = True

            public.writeFile(file, conf)
            public.serviceReload()
            if i=="apache":
                import firewalls
                # aapanel 使用 get_msg_gettext
                get.ps = public.lang("New phpMyAdmin SSL Port")
                fw = firewalls.firewalls()
                fw.AddAcceptPort(get)
                public.serviceReload()
                public.write_log_gettext('Software manager', 'Modified access port to {} for phpMyAdmin!', (get.port,))
                get.id = public.M('firewall').where('port=?', (oldPort,)).getField('id')
                get.port = oldPort
                fw.DelAcceptPort(get)

        from firewallModelV2.comModel import main as firewall_main
        fwd = firewall_main()
        if is_changed and oldPort:
            fwd.set_port_rule(public.to_dict_obj(
                {"port": oldPort, "operation": "remove", "reload": "0"}
            ))

        fwd.set_port_rule(public.to_dict_obj(
            {"port": get.port, "brief": "phpmyadmin"}
        ))
        return public.success_v2(public.lang('Setup successfully!'))

    def _get_phpmyadmin_auth(self):
        import re
        nginx_conf = '/www/server/nginx/conf/nginx.conf'
        reg = '#AUTH_START(.|\n)*#AUTH_END'
        if os.path.exists(nginx_conf):
            nginx_conf = public.readFile(nginx_conf)
            auth_tmp = re.search(reg, nginx_conf)
            if auth_tmp:
                return True
        apache_conf = '/www/server/apache/conf/extra/httpd-vhosts.conf'
        if os.path.exists(apache_conf):
            apache_conf = public.readFile(apache_conf)
            auth_tmp = re.search(reg, apache_conf)
            if auth_tmp:
                return True


    def _get_phpmyadmin_web_conf(self):
        filename, webserver = self.__get_webserver_conffile()
        if public.get_webserver() == 'openlitespeed':
            filename = "/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf"
        return filename, webserver

    # 设置phpmyadmin ssl
    def set_phpmyadmin_ssl(self,get):
        if public.get_webserver() == "openlitespeed":
            return public.return_message(-1, 0, public.lang("The current web server is openlitespeed. "
                                                            "This function is not supported yet."))
        if not os.path.exists("/www/server/panel/ssl/certificate.pem"):
            return public.return_message(-1, 0, public.lang("The panel certificate does not exist. "
                                                            "Please apply for the panel certificate and try again."))

        if get.v == "1":
            # if public access is only 127.0.0.1
            web_conf, webserver = self._get_phpmyadmin_web_conf()
            web_conf_str = public.readFile(web_conf)
            if not web_conf_str or not webserver:
                return public.fail_v2(public.lang('Operation failed'))
            import re
            if webserver == 'nginx':
                nginx_reg = r'/www/server/phpmyadmin.*\s+allow\s+127\.0\.0\.1;\s+allow\s+::1;\s+deny\s+all;'
                if re.search(nginx_reg, web_conf_str, re.IGNORECASE):
                    return public.return_message(
                        -1, 0, public.lang("The current web server is local and private access.")
                    )
            elif webserver == 'apache':
                apache_reg = r'/www/server/phpmyadmin.*(\s+.*){5}Allow\s+from\s+127.0.0.1\s+::1\s+localhost'
                if re.search(apache_reg, web_conf_str, re.IGNORECASE):
                    return public.return_message(
                        -1, 0, public.lang("The current web server is local and private access.")
                    )
            else:
                return public.return_message(-1, 0, public.lang("The current web server is not supported."))

            # 获取auth信息
            auth = ""
            if self._get_phpmyadmin_auth():
                auth = """
        #AUTH_START
        auth_basic "Authorization";
        auth_basic_user_file /www/server/pass/phpmyadmin.pass;
        #AUTH_END
"""
            # nginx配置文件
            ssl_conf = r"""server
    {
        listen 887 ssl;
        server_name phpmyadmin;
        index index.html index.htm index.php;
        root  /www/server/phpmyadmin;
        #SSL-START SSL相关配置，请勿删除或修改下一行带注释的404规则
        #error_page 404/404.html;
        ssl_certificate    /www/server/panel/ssl/certificate.pem;
        ssl_certificate_key    /www/server/panel/ssl/privateKey.pem;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
        error_page 497  https://$host$request_uri;
        #SSL-END
        %s
        include enable-php.conf;
        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }
        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }
        location ~ /\.
        {
            deny all;
        }
        access_log  /www/wwwlogs/access.log;
    }""" % auth
            public.writeFile("/www/server/panel/vhost/nginx/phpmyadmin.conf",ssl_conf)
            import panelPlugin
            get.sName = "phpmyadmin"
            v = panelPlugin.panelPlugin().get_soft_find(get)
            if self._get_phpmyadmin_auth():
                auth = """
        #AUTH_START
        AuthType basic
        AuthName "Authorization "
        AuthUserFile /www/server/pass/phpmyadmin.pass
        Require user jose
        #AUTH_END
            """
            # apache配置,多服务下不写入apache
            if not public.get_multi_webservice_status():
                ssl_conf = r'''Listen 887
    <VirtualHost *:887>
        ServerAdmin webmaster@example.com
        DocumentRoot "/www/server/phpmyadmin"
        ServerName 0b842aa5.phpmyadmin
        ServerAlias phpmyadmin.com
        #ErrorLog "/www/wwwlogs/BT_default_error.log"
        #CustomLog "/www/wwwlogs/BT_default_access.log" combined
        
        #SSL
        SSLEngine On
        SSLCertificateFile /www/server/panel/ssl/certificate.pem
        SSLCertificateKeyFile /www/server/panel/ssl/privateKey.pem
        SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
        SSLProtocol All -SSLv2 -SSLv3
        SSLHonorCipherOrder On
        
        #PHP
        <FilesMatch \.php$>
               SetHandler "proxy:{}"
        </FilesMatch>
        
        #DENY FILES
        <Files ~ (\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)$>
          Order allow,deny
          Deny from all
        </Files>
        
        #PATH
        <Directory "/www/wwwroot/bt.youbadbad.cn/">
    {}
           SetOutputFilter DEFLATE
           Options FollowSymLinks
           AllowOverride All
           Require all granted
           DirectoryIndex index.php index.html index.htm default.php default.html default.htm
        </Directory>
    </VirtualHost>'''.format(public.get_php_proxy(v["ext"]["phpversion"],'apache'),auth)
                public.writeFile("/www/server/panel/vhost/apache/phpmyadmin.conf", ssl_conf)

            import firewalls
            fw = firewalls.firewalls()
            fw.AddAcceptPort(public.to_dict_obj({
                'port': '887',
                'ps': public.lang("New phpMyAdmin SSL Port"),
            }))
        else:
            if os.path.exists("/www/server/panel/vhost/nginx/phpmyadmin.conf"):
                os.remove("/www/server/panel/vhost/nginx/phpmyadmin.conf")
            if os.path.exists("/www/server/panel/vhost/apache/phpmyadmin.conf"):
                os.remove("/www/server/panel/vhost/apache/phpmyadmin.conf")
            public.serviceReload()
            return public.return_message(0, 0, public.lang('Setup successfully!'))
        public.serviceReload()
        return public.return_message(0, 0, public.lang('Open successfully, please manually release phpmyadmin ssl port'))


    #设置PHPMyAdmin
    def setPHPMyAdmin(self,get):
        import re
        filename, webserver = self._get_phpmyadmin_web_conf()
        conf = public.readFile(filename)
        if not conf or not webserver:
            return public.fail_v2(public.lang('Operation failed'))

        if hasattr(get,'port'):
            mainPort = public.readFile('data/port.pl').strip()
            rulePort = ['80','443','21','20','8080','8081','8089','11211','6379','8188', '8189', '8190', '8288', '8289', '8290']
            oldPort = "888"
            if get.port in rulePort:
                return public.fail_v2(public.lang('Please do NOT use the usual port as the phpMyAdmin port!'))
            if webserver == 'nginx':
                rep = r"listen\s+([0-9]+)\s*;"
                oldPort = re.search(rep,conf).groups()[0]
                conf = re.sub(rep,'listen ' + get.port + ';\n',conf)
            elif webserver == 'apache':
                rep = r"Listen\s+([0-9]+)\s*\n"
                oldPort = re.search(rep,conf).groups()[0]
                conf = re.sub(rep,"Listen " + get.port + "\n",conf,1)
                rep = r"VirtualHost\s+\*:[0-9]+"
                conf = re.sub(rep,"VirtualHost *:" + get.port,conf,1)
            else:
                filename = '/www/server/panel/vhost/openlitespeed/listen/888.conf'
                conf = public.readFile(filename)
                reg = r"address\s+\*:(\d+)"
                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.success_v2(public.lang('Setup successfully!'))

            public.writeFile(filename,conf)
            import firewalls
            get.ps = public.lang("New phpMyAdmin Port")
            fw = firewalls.firewalls()
            fw.AddAcceptPort(get)
            public.serviceReload()
            public.write_log_gettext('Software manager','Modified access port to {} for phpMyAdmin!',(get.port,))
            get.id = public.M('firewall').where('port=?',(oldPort,)).getField('id')
            get.port = oldPort
            fw.DelAcceptPort(get)
            return public.success_v2(public.lang('Setup successfully!'))

        if hasattr(get,'phpversion'):
            if webserver == 'nginx':
                filename = public.GetConfigValue('setup_path') + '/nginx/conf/enable-php.conf'
                conf = public.readFile(filename)
                rep = r"(unix:/tmp/php-cgi.*\.sock|127.0.0.1:\d+)"
                conf = re.sub(rep,public.get_php_proxy(get.phpversion,'nginx'),conf,1)
            elif webserver == 'apache':
                rep = r"(unix:/tmp/php-cgi.*\.sock\|fcgi://localhost|fcgi://127.0.0.1:\d+)"
                conf = re.sub(rep,public.get_php_proxy(get.phpversion,'apache'),conf,1)
            else:
                reg = r'/usr/local/lsws/lsphp\d+/bin/lsphp'
                conf = re.sub(reg,'/usr/local/lsws/lsphp{}/bin/lsphp'.format(get.phpversion),conf)
            public.writeFile(filename,conf)
            public.serviceReload()
            public.write_log_gettext('Software manager','Modified PHP runtime version to PHP-{} for phpMyAdmin!',(get.phpversion,))

            return public.success_v2(public.lang('Setup successfully!'))

        if hasattr(get,'password'):
            import panel_site_v2
            if(get.password == 'close'):
                return panel_site_v2.panelSite().CloseHasPwd(get)
            else:
                return panel_site_v2.panelSite().SetHasPwd(get)

        if hasattr(get,'status'):
            pma_path = public.GetConfigValue('setup_path') + '/phpmyadmin'
            stop_path = public.GetConfigValue('setup_path') + '/stop'
            if conf.find(stop_path) != -1:
                conf = conf.replace(stop_path,pma_path)
                msg = public.getMsg('START')

            if webserver == 'nginx':
                sub_string = '''{};
        allow 127.0.0.1;
        allow ::1;
        deny all'''.format(pma_path)
                if conf.find(sub_string) != -1:
                    conf = conf.replace(sub_string,pma_path)
                    msg = public.getMsg('START')
                else:
                    conf = conf.replace(pma_path,sub_string)
                    msg = public.getMsg('STOP')
                    # 移除ssl
                    if os.path.exists("/www/server/panel/vhost/nginx/phpmyadmin.conf"):
                        os.remove("/www/server/panel/vhost/nginx/phpmyadmin.conf")
            elif webserver == 'apache':
                src_string = 'AllowOverride All'
                sub_string = '''{}
        Deny from all
        Allow from 127.0.0.1 ::1 localhost'''.format(src_string)
                if conf.find(sub_string) != -1:
                    conf = conf.replace(sub_string,src_string)
                    msg = public.getMsg('START')
                else:
                    conf = conf.replace(src_string,sub_string)
                    msg = public.getMsg('STOP')
                    # 移除ssl
                    if os.path.exists("/www/server/panel/vhost/apache/phpmyadmin.conf"):
                        os.remove("/www/server/panel/vhost/apache/phpmyadmin.conf")
            else:
                access_control_start = 'accessControl  {\n'
                ols_conf = """accessControl  {
    allow                   127.0.0.1
    allow                   ::1
    deny                    All
}
"""

                if conf.find(access_control_start) == -1:
                    conf = conf + '\n' + ols_conf
                    msg = public.getMsg('START')
                else:
                    try:
                        start_idx = conf.find(access_control_start)
                        end_idx = conf.find('}', start_idx + len(access_control_start)) + 1
                        conf = conf[:start_idx] + conf[end_idx:]
                        msg = public.getMsg('STOP')
                    except:
                        return public.return_message(-1,0,public.lang('There is an error in the configuration of phpMyAdmin.'))

            public.writeFile(filename,conf)
            public.serviceReload()
            public.write_log_gettext('Software manager','phpMyAdmin already {}!',(msg,))

            return public.success_v2(public.lang('phpMyAdmin already {}!', msg))

        return  public.return_message(-1,0,public.lang('Parameter error!'))

    def ToPunycode(self,get):
        import re
        get.domain = get.domain.encode('utf8')
        tmp = get.domain.split('.')
        newdomain = ''
        for dkey in tmp:
                #匹配非ascii字符
                match = re.search(u"[\x80-\xff]+",dkey)
                if not match:
                        newdomain += dkey + '.'
                else:
                        newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.'

        return newdomain[0:-1]

    #保存PHP排序
    def phpSort(self,get):
        if public.writeFile('/www/server/php/sort.pl',get.ssort): return public.return_message(0, 0, public.lang("Setup successfully!"))
        return public.return_message(-1, 0, public.lang("Operation failed"))

    #获取广告代码
    def GetAd(self,get):
        try:
            return public.HttpGet(public.GetConfigValue('home') + '/Api/GetAD?name='+get.name + '&soc=' + get.soc)
        except:
            return ''

    #获取进度
    def GetSpeed(self,get):
        return public.getSpeed()

    #检查登陆状态
    def CheckLogin(self,get):
        return True

    #获取警告标识
    def GetWarning(self,get):
        warningFile = 'data/warning.json'
        if not os.path.exists(warningFile): return public.return_message(-1, 0, public.lang("Warning list does NOT exist!"))
        import json,time;
        wlist = json.loads(public.readFile(warningFile))
        wlist['time'] = int(time.time())
        return wlist

    #设置警告标识
    def SetWarning(self,get):
        wlist = self.GetWarning(get)
        id = int(get.id)
        import time,json;
        for i in xrange(len(wlist['data'])):
            if wlist['data'][i]['id'] == id:
                wlist['data'][i]['ignore_count'] += 1
                wlist['data'][i]['ignore_time'] = int(time.time())

        warningFile = 'data/warning.json'
        public.writeFile(warningFile,json.dumps(wlist))
        return public.return_message(0, 0, public.lang("Setup successfully!"))

    #获取memcached状态
    def GetMemcachedStatus(self,get):
        try:
            import telnetlib, re
            conf = public.readFile('/etc/init.d/memcached')
            result = {}
            result['bind'] = re.search('IP=(.+)',conf).groups()[0]
            result['port'] = int(re.search(r'PORT=(\d+)',conf).groups()[0])
            result['maxconn'] = int(re.search(r'MAXCONN=(\d+)',conf).groups()[0])
            result['cachesize'] = int(re.search(r'CACHESIZE=(\d+)',conf).groups()[0])
            tn = telnetlib.Telnet(result['bind'],result['port'])
            tn.write(b"stats\n")
            tn.write(b"quit\n")
            data = tn.read_all()
            if type(data) == bytes: data = data.decode('utf-8')
            data = data.replace('STAT','').replace('END','').split("\n")
            res = ['cmd_get','get_hits','get_misses','limit_maxbytes','curr_items','bytes','evictions','limit_maxbytes','bytes_written','bytes_read','curr_connections'];
            for d in data:
                if len(d)<3: continue
                t = d.split()
                if not t[0] in res: continue
                result[t[0]] = int(t[1])
            result['hit'] = 1
            if result['get_hits'] > 0 and result['cmd_get'] > 0:
                result['hit'] = float(result['get_hits']) / float(result['cmd_get']) * 100
            return public.success_v2(result)
        except Exception as e:
            public.print_log("error %s" % e)
            return public.fail_v2("Get memcached status failed!")

    #设置memcached缓存大小
    def SetMemcachedCache(self,get):
        try:
            get.validate([
                Param('bind').Require().String(),
                Param('port').Require().Integer(),
                Param('cachesize').Require().Integer(),
                Param('maxconn').Require().Integer(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.fail_v2(str(ex))

        import re
        confFile = '/etc/init.d/memcached'
        conf = public.readFile(confFile)
        conf = re.sub('IP=.+', 'IP=' + get.bind, conf)
        conf = re.sub(r'PORT=\d+', 'PORT=' + get.port, conf)
        conf = re.sub(r'MAXCONN=\d+', 'MAXCONN=' + get.maxconn, conf)
        conf = re.sub(r'CACHESIZE=\d+', 'CACHESIZE=' + get.cachesize, conf)
        public.writeFile(confFile, conf)
        public.ExecShell(confFile + ' reload')
        return public.success_v2("Setup successfully!")

    #取redis状态
    def GetRedisStatus(self,get):
        import re
        c = public.readFile('/www/server/redis/redis.conf')
        port = re.findall('\n\\s*port\\s+(\\d+)',c)[0]
        password = re.findall('\n\\s*requirepass\\s+(.+)',c)
        if password:
            password = ' -a ' + password[0]
        else:
            password = ''
        data = public.ExecShell('/www/server/redis/src/redis-cli -p ' + port + password + ' info')[0];
        res = [
               'tcp_port',
               'uptime_in_days',    #已运行天数
               'connected_clients', #连接的客户端数量
               'used_memory',       #Redis已分配的内存总量
               'used_memory_rss',   #Redis占用的系统内存总量
               'used_memory_peak',  #Redis所用内存的高峰值
               'mem_fragmentation_ratio',   #内存碎片比率
               'total_connections_received',#运行以来连接过的客户端的总数量
               'total_commands_processed',  #运行以来执行过的命令的总数量
               'instantaneous_ops_per_sec', #服务器每秒钟执行的命令数量
               'keyspace_hits',             #查找数据库键成功的次数
               'keyspace_misses',           #查找数据库键失败的次数
               'latest_fork_usec'           #最近一次 fork() 操作耗费的毫秒数
               ]
        data = data.split("\n")
        result = {}
        for d in data:
            if len(d)<3: continue
            t = d.strip().split(':')
            if not t[0] in res: continue
            result[t[0]] = t[1]
        return result

    #取PHP-FPM日志
    def GetFpmLogs(self,get):
        import re
        fpm_path = '/www/server/php/' + get.version + '/etc/php-fpm.conf'
        if not os.path.exists(fpm_path): return public.return_message(-1, 0, public.lang("Log file does NOT exist!"))
        fpm_conf = public.readFile(fpm_path)
        log_tmp = re.findall(r"error_log\s*=\s*(.+)",fpm_conf)
        if not log_tmp: return public.return_message(-1, 0, public.lang("Log file does NOT exist!"))
        log_file = log_tmp[0].strip()
        if log_file.find('var/log') == 0:
            log_file = '/www/server/php/' +get.version + '/'+ log_file
        return public.return_message(0, 0, public.GetNumLines(log_file,1000))

    #取PHP慢日志
    def GetFpmSlowLogs(self,get):
        import re
        fpm_path = '/www/server/php/' + get.version + '/etc/php-fpm.conf'
        if not os.path.exists(fpm_path): return public.return_message(-1, 0, public.lang("Log file does NOT exist!"))
        fpm_conf = public.readFile(fpm_path)
        log_tmp = re.findall(r"slowlog\s*=\s*(.+)",fpm_conf)
        if not log_tmp: return public.return_message(-1, 0, public.lang("Log file does NOT exist!"))
        log_file = log_tmp[0].strip()
        if log_file.find('var/log') == 0:
            log_file = '/www/server/php/' +get.version + '/'+ log_file
        return public.return_message(0, 0, public.GetNumLines(log_file,1000))

    #取指定日志
    def GetOpeLogs(self,get):
        try:
            lines = get.get('lines', 1000)
            lines = int(lines)
        except Exception as ex:
            lines = 1000
        if not os.path.exists(get.path): return public.return_message(-1, 0, public.lang("Log file does NOT exist!"))
        return public.return_message(0, 0, public.xsssec(public.GetNumLines(get.path,lines)))

    # 获取授权信息
    def get_pd(self,get):
        # # 校验参数
        # try:
        #     get.validate([
        #         Param('status').Integer(),
        #     ], [
        #         public.validate.trim_filter(),
        #     ])
        # except Exception as ex:
        #     public.print_log("error info: {}".format(ex))
        #     return public.return_message(-1, 0, str(ex))
        #
        # from BTPanel import cache
        # tmp = -1
        # try:
        #     import panelPlugin
        #     # get = public.dict_obj()
        #     # get.init = 1
        #     tmp1 = panelPlugin.panelPlugin().get_cloud_list(get)
        # except:
        #     tmp1 = None
        # if tmp1:
        #     tmp = tmp1[public.to_string([112, 114, 111])]
        #     ltd = tmp1.get('ltd', -1)
        # else:
        #     ltd = -1
        #     tmp4 = cache.get(public.to_string([112, 95, 116, 111, 107, 101, 110]))
        #     if tmp4:
        #         tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4
        #         if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1')
        #         tmp = public.readFile(tmp_f)
        #         if tmp: tmp = int(tmp)
        # if not ltd: ltd = -1
        # if tmp == None: tmp = -1
        # if ltd < 1:
        #     if ltd == -2:
        #         tmp3 = public.to_string(
        #             [60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, 100,
        #              45, 103, 114, 97, 121, 34, 62, 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, 109, 97,
        #              114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807,
        #              26399, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116,
        #              108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111,
        #              102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62, 82, 69, 78, 69, 87,
        #              60, 47, 97,
        #              62, 60, 47, 115, 112, 97, 110, 62])
        #     elif tmp == -1:
        #         tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98,
        #                                  116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32, 111, 110, 99, 108, 105, 99,
        #                                  107,
        #                                  61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112,
        #                                  114,
        #                                  111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107,
        #                                  32, 116, 111, 32,
        #                                  103, 101, 116, 32, 80, 82, 79, 34, 62, 20813, 36153, 29256, 60, 47, 115, 112,
        #                                  97, 110, 62])
        #     elif tmp == -2:
        #         tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116,
        #                                  112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 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, 109, 97, 114, 103, 105, 110, 45,
        #                                  114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, 26399,
        #                                  60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34,
        #                                  98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61,
        #                                  34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, 114,
        #                                  111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97,
        #                                  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)
        #         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,
        #                                      112, 114, 111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 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, 109, 97, 114,
        #                                      103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123,
        #                                      48, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115,
        #                                      115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105,
        #                                      99,
        #                                      107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119,
        #                                      95,
        #                                      112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60,
        #                                      47, 115, 112, 97, 110, 62]).format(tmp2)
        #     else:
        #         tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112,
        #                                  114, 111, 45, 103, 114, 97, 121, 34, 32, 111, 110, 99, 108, 105, 99, 107,
        #                                  61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 112,
        #                                  114, 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99,
        #                                  107, 32, 116,
        #                                  111, 32, 103, 101, 116, 32, 80, 82, 79, 34, 62, 70, 82,
        #                                  69, 69, 60, 47, 115, 112, 97, 110, 62])
        # else:
        #     tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116,
        #                              100, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 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, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53,
        #                              112, 120, 34, 62, 123, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108,
        #                              97, 115, 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105,
        #                              99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95,
        #                              112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115,
        #                              112, 97, 110, 62]).format(
        #         time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(ltd)))
        # return_message={"bt_pro":tmp3,"time_stamp":tmp,"itd":ltd}
        #
        # return public.return_message(0,0,return_message)
        return public.return_message(0, 0, public.get_pd(get))

    #检查用户绑定是否正确
    def check_user_auth(self,get):
        # import requests
        m_key = 'check_user_auth'
        if m_key in session: return session[m_key]
        u_path = 'data/userInfo.json'
        try:
            userInfo = json.loads(public.ReadFile(u_path))
        except:
            if os.path.exists(u_path): os.remove(u_path)
            return public.return_message(-1, 0, public.lang("Account binding has expired, please re-bind on the [Settings] page!"))
        url_headers = {"authorization":"bt {}".format(userInfo['token'])}
        # resp = requests.post('{}/api/user/verifyToken'.format(self.__official_url),headers=url_headers,verify=False)
        resp = public.HttpPost.post('{}/api/user/verifyToken'.format(self.__official_url), headers=url_headers, verify=False)
        resp = resp.json()
        if not resp['success']:
            if os.path.exists(u_path): os.remove(u_path)
            return public.return_message(-1, 0, public.lang("Account binding has expired, please re-bind on the [Settings] page!"))
        else:
            session[m_key] = public.return_message(0, 0,public.lang('Binding is valid!'))
            return session[m_key]


    #PHP探针
    def php_info(self,args):
        php_version = args.php_version.replace('.','')
        php_path = '/www/server/php/'
        if public.get_webserver() == 'openlitespeed':
            php_path = '/usr/local/lsws/lsphp'
        php_bin = php_path + php_version + '/bin/php'
        php_ini = php_path + php_version + '/etc/php.ini'
        if not os.path.exists('/etc/redhat-release') and public.get_webserver() == 'openlitespeed':
            php_ini = php_path + php_version + '/etc/php/'+args.php_version+'/litespeed/php.ini'
        if not os.path.exists(php_ini) and not os.path.exists(php_bin):
            return public.return_message(-1, 0, public.lang("Requested PHP version does NOT exist!"))
        tmp = public.ExecShell(php_bin + ' -c {} /www/server/panel/class/php_info.php'.format(php_ini))[0]
        if tmp.find('Warning: JIT is incompatible') != -1:
            tmp = tmp.strip().split('\n')[-1]
        try:
            result = json.loads(tmp)
            result['phpinfo'] = {}
            if "modules" not in result:
                result['modules'] = []
            if 'php_version' in result:
                result['phpinfo']['php_version'] = result['php_version']
        except:
            result = {
                'php_version': php_version,
                'phpinfo': {},
                'modules': [],
                'ini': ''
            }
        result['phpinfo'] = {}
        result['phpinfo']['php_version'] = result['php_version']
        result['phpinfo']['php_path'] = php_path
        result['phpinfo']['php_bin'] = php_bin
        result['phpinfo']['php_ini'] = php_ini
        result['phpinfo']['modules'] = ' '.join(result['modules'])
        result['phpinfo']['ini'] = result['ini']
        result['phpinfo']['keys'] = { "1cache": "Buffer", "2crypt": "Encryption and decryption library", "0db": "Database-driven", "4network": "Network Communication Library", "5io_string": "File and string processing libraries", "3photo":"Image processing library","6other":"Other third-party libraries"}
        del(result['php_version'])
        del(result['modules'])
        del(result['ini'])
        return public.return_message(0,0,result)

    #取指定行
    def get_lines(self,args):
        # 校验参数
        try:
            args.validate([
                Param('filename').String(),
                Param('num').Integer(),

            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))
        if not os.path.exists(args.filename): return public.return_message(-1, 0, public.lang("Logs emptied"))
        num = args.get('num/d',10)
        s_body = public.GetNumLines(args.filename,num)
        return public.return_message(0,0,s_body)

    def log_analysis(self,get):
        public.set_module_logs('log_analysis', 'log_analysis', 1)
        import log_analysis_v2 as log_analysis
        log_analysis=log_analysis.log_analysis()
        return log_analysis.log_analysis(get)


    def speed_log(self,get):
        import log_analysis_v2 as log_analysis
        log_analysis=log_analysis.log_analysis()
        return log_analysis.speed_log(get)



    def get_result(self,get):
        # 校验参数
        try:
            get.validate([
                Param('path').String(),
                Param('action').String(),

            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))


        import log_analysis_v2 as log_analysis
        log_analysis=log_analysis.log_analysis()
        return log_analysis.get_result(get)

    def get_detailed(self,get):
        import log_analysis_v2 as log_analysis
        log_analysis=log_analysis.log_analysis()
        return log_analysis.get_detailed(get)

    def download_pay_type(self, path):
        public.downloadFile(public.get_url() + '/install/lib/pay_type_en.json', path)
        return True

    def get_pay_type(self, get):
        """
            @name 获取推荐列表
        """
        spath = '{}/data/pay_type.json'.format(public.get_panel_path())
        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,))
        file_content = public.readFile("data/pay_type.json")
        if not file_content:
            return public.return_message(0,0,self.get_default_pay_type())
        try:
            data = json.loads(file_content)
        except json.decoder.JSONDecodeError:
            os.remove(spath)
            public.run_thread(self.download_pay_type, (spath,))
            file_content = public.readFile("data/pay_type.json")
            if not file_content:
                data = self.get_default_pay_type()
            else:
                data = json.loads(file_content)
        except Exception:
            data = self.get_default_pay_type()

        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

        # 增加首页通知
        notice = self.getPanelAnnounce(None).get("message", {'show': False, 'reason': 'closed'})
        notice['name'] = "Notice"
        data.insert(0, notice)
        return public.return_message(0,0,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 获取首页软件列表推荐
        """
        nList = []
        webserver = public.get_webserver()
        for x in sList:
            for plugin_info in plugin_list['list']:
                if x['name'] == plugin_info['name']:
                    if not 'endtime' in plugin_info or plugin_info['endtime'] >= 0:
                        x['isBuy'] = True
            is_check = False
            if 'dependent' in x:
                if x['dependent'] == webserver: is_check = True
            else:
                is_check = True
            if is_check:
                info = plu_panel.get_soft_find(x['name'])
                if info:
                    if stype == 1:
                        # if plugin_list['pro'] >= 0: continue
                        if not info['setup']:
                            x['install'] = info['setup']
                            nList.append(x)
                    else:
                        x['install'] = info['setup']
                        nList.append(x)
        return nList

    def ignore_version(self, get):
        """
        @忽略版本更新
        :param version 忽略的版本号
        """
        # 校验参数
        try:
            get.validate([
                Param('version').String(),

            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        version = get.version
        path = '{}/data/no_update.pl'.format(public.get_panel_path())
        try:
            data = json.loads(public.readFile(path))
        except:
            data = []

        if not version in data: data.append(version)

        public.writeFile(path, json.dumps(data))
        try:
            del (session['updateInfo'])
        except:
            pass

        return public.return_message(0, 0, public.lang("Ignore success, this version will no longer be reminded to update."))

    def __get_panel_announce_path(self):
        return os.path.join(public.get_panel_path(), 'data', 'panel_announce.json')

    def __get_panel_announce_cache_path(self):
        return os.path.join(public.get_panel_path(), 'data', 'panel_announce_cache.json')

    @staticmethod
    def __to_bool(value, default=True):
        if isinstance(value, bool):
            return value
        if isinstance(value, int):
            return value == 1
        if isinstance(value, str):
            value = value.strip().lower()
            if value in ('1', 'true', 'yes', 'on', 'open', 'checked'):
                return True
            if value in ('0', 'false', 'no', 'off', 'close', 'closed', ''):
                return False
        return default

    def __get_panel_announce_config(self):
        config = {
            'home_announce': True,
            'read_md5': ''
        }
        path = self.__get_panel_announce_path()
        if not os.path.exists(path):
            return config
        try:
            data = json.loads(public.readFile(path) or '{}')
            if isinstance(data, dict):
                config['home_announce'] = self.__to_bool(data.get('home_announce', config['home_announce']))
                config['read_md5'] = str(data.get('read_md5', '') or '').lower()
        except:
            pass
        return config

    def __write_panel_announce_config(self, config):
        data = self.__get_panel_announce_config()
        data.update(config)
        data['home_announce'] = self.__to_bool(data.get('home_announce', True))
        data['read_md5'] = str(data.get('read_md5', '') or '').lower()
        return public.writeFile(self.__get_panel_announce_path(), json.dumps(data))

    def __read_panel_announce_cache(self):
        path = self.__get_panel_announce_cache_path()
        if not os.path.exists(path):
            return {}
        try:
            data = json.loads(public.readFile(path) or '{}')
            if isinstance(data, dict):
                return data
        except:
            pass
        return {}

    def __write_panel_announce_cache(self, data):
        cache_data = {
            'time': int(time.time()),
            'data': data
        }
        return public.writeFile(self.__get_panel_announce_cache_path(), json.dumps(cache_data))

    def __get_panel_announce_data(self, url):
        cache_ttl = 3600*24
        cache_data = self.__read_panel_announce_cache()
        cache_time = int(cache_data.get('time', 0) or 0)
        cached_response = cache_data.get('data')
        if isinstance(cached_response, dict) and int(time.time()) - cache_time < cache_ttl:
            return cached_response

        try:
            data = json.loads(public.HttpGet(url))
            if isinstance(data, dict):
                self.__write_panel_announce_cache(data)
            return data
        except:
            if isinstance(cached_response, dict):
                return cached_response
            raise

    @staticmethod
    def __parse_panel_announce(announce):
        result = {
            'type': '',
            'content': announce.strip(),
            'url': '',
            'raw': announce
        }
        content = announce.strip()

        type_match = re.search(r'\[TYPE_([A-Z_]+)\]', content)
        if type_match:
            type_key = type_match.group(1).lower()
            type_map = {
                'pro': 'pro',
                'update': 'update',
                'security': 'security'
            }
            result['type'] = type_map.get(type_key, type_key)
            content = re.sub(r'\[TYPE_[A-Z_]+\]\s*', '', content, count=1)

        url_match = re.search(r'\[URL\s*=\s*["\']?([^\]"\']+)["\']?\]', content)
        if url_match:
            result['url'] = url_match.group(1).strip()
            content = re.sub(r'\[URL\s*=\s*["\']?([^\]"\']+)["\']?\]\s*', '', content, count=1)

        result['content'] = content.strip()
        return result

    def getPanelAnnounce(self, get):
        api_path = '/api/panel/getPanelAnnounce'
        url = self.__official_url + api_path

        config = self.__get_panel_announce_config()
        if not config.get('home_announce', True):
            return public.return_message(0, 0, {'show': False, 'reason': 'closed'})

        try:
            data = self.__get_panel_announce_data(url)
            if data.get("success",False):
                announce = data.get("res", {}).get("announce", "")
                if announce:
                    announce_md5 = hashlib.md5(announce.encode('utf-8')).hexdigest()
                    if announce_md5 == config.get('read_md5', ''):
                        return public.return_message(0, 0, {
                            'show': False,
                            'reason': 'read',
                            'md5': announce_md5
                        })

                    announce_info = self.__parse_panel_announce(announce)
                    announce_info.update({
                        'show': True,
                        'md5': announce_md5
                    })
                    return public.return_message(0, 0, announce_info)
            return public.return_message(0, 0, {'show': False, 'reason': 'empty'})
        except Exception as ex:
            # public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, {'show': False, 'reason': "error info: {}".format(ex)})

    def markPanelAnnounceRead(self, get):
        try:
            get.validate([
                Param('md5').Require().String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        announce_md5 = get.md5.strip().lower()
        if not re.match(r'^[a-f0-9]{32}$', announce_md5):
            return public.return_message(-1, 0, public.lang("Parameter error!"))

        if not self.__write_panel_announce_config({'read_md5': announce_md5}):
            return public.return_message(-1, 0, public.lang("Operation failed"))
        return public.return_message(0, 0, public.lang("Setup successfully!"))

class Dpkg:
    def __init__(self):
        self._fileinfo = None
        self._control_str = None
        self._headers = None
        self._message = None
        self._upstream_version = None
        self._debian_revision = None
        self._epoch = None

    @staticmethod
    def get_epoch(version_str):
        try:
            e_index = version_str.index(":")
        except ValueError:
            return 0, version_str

        try:
            epoch = int(version_str[0:e_index])
        except ValueError as ex:
            print(f"Corrupt dpkg version '{version_str}': epochs can only be ints, and "
                  "epochless versions cannot use the colon character.")
        return epoch, version_str[e_index + 1:]

    @staticmethod
    def get_upstream(version_str):
        try:
            d_index = version_str.rindex("-")
        except ValueError:
            return version_str, "0"

        return version_str[0:d_index], version_str[d_index + 1:]

    @staticmethod
    def split_full_version(version_str):
        epoch, full_ver = Dpkg.get_epoch(version_str)
        upstream_rev, debian_rev = Dpkg.get_upstream(full_ver)
        return epoch, upstream_rev, debian_rev

    @staticmethod
    def get_alphas(revision_str):
        for i, char in enumerate(revision_str):
            if char.isdigit():
                if i == 0:
                    return "", revision_str
                return revision_str[0:i], revision_str[i:]
        return revision_str, ""

    @staticmethod
    def get_digits(revision_str):
        if not revision_str:
            return 0, ""
        for i, char in enumerate(revision_str):
            if not char.isdigit():
                if i == 0:
                    return 0, revision_str
                return int(revision_str[0:i]), revision_str[i:]
        return int(revision_str), ""

    @staticmethod
    def listify(revision_str):
        result = []
        while revision_str:
            rev_1, remains = Dpkg.get_alphas(revision_str)
            rev_2, remains = Dpkg.get_digits(remains)
            result.extend([rev_1, rev_2])
            revision_str = remains
        return result

    @staticmethod
    def dstringcmp(a, b):
        if a == b:
            return 0
        try:
            for i, char in enumerate(a):
                if char == b[i]:
                    continue
                if char == "~":
                    return -1
                if b[i] == "~":
                    return 1
                if char.isalpha() and not b[i].isalpha():
                    return -1
                if not char.isalpha() and b[i].isalpha():
                    return 1
                if ord(char) > ord(b[i]):
                    return 1
                if ord(char) < ord(b[i]):
                    return -1
        except IndexError:
            if char == "~":
                return -1
            return 1
        if b[len(a)] == "~":
            return 1
        return -1

    @staticmethod
    def compare_revision_strings(rev1, rev2):
        if rev1 == rev2:
            return 0
        list1 = Dpkg.listify(rev1)
        list2 = Dpkg.listify(rev2)
        if list1 == list2:
            return 0
        try:
            for i, item in enumerate(list1):
                if i >= len(list2):
                    raise IndexError
                if not isinstance(item, list2[i].__class__):
                    print(f"Cannot compare '{item}' to {list2[i]}, something has gone horribly awry.")
                if item == list2[i]:
                    continue
                if isinstance(item, int):
                    if item > list2[i]:
                        return 1
                    if item < list2[i]:
                        return -1
                else:
                    return Dpkg.dstringcmp(item, list2[i])
        except IndexError:
            if list1[len(list2)][0][0] == "~":
                return -1
            return 1
        if list2[len(list1)][0][0] == "~":
            return 1
        return -1

    @staticmethod
    def compare_versions(ver1, ver2):
        if ver1 == ver2:
            return 0
        epoch1, upstream1, debian1 = Dpkg.split_full_version(str(ver1))
        epoch2, upstream2, debian2 = Dpkg.split_full_version(str(ver2))

        if epoch1 < epoch2:
            return -1
        if epoch1 > epoch2:
            return 1

        upstr_res = Dpkg.compare_revision_strings(upstream1, upstream2)
        if upstr_res != 0:
            return upstr_res

        debian_res = Dpkg.compare_revision_strings(debian1, debian2)
        if debian_res != 0:
            return debian_res

        return 0

