mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-09-28 04:14:51 +02:00
6.8.16
This commit is contained in:
+105
-38
@@ -42,6 +42,7 @@ if not hooks:
|
||||
# import db
|
||||
dns_client = None
|
||||
app.config['DEBUG'] = os.path.exists('data/debug.pl')
|
||||
app.config['SSL'] = os.path.exists('data/ssl.pl')
|
||||
|
||||
#设置BasicAuth
|
||||
basic_auth_conf = 'config/basic_auth.json'
|
||||
@@ -65,6 +66,13 @@ app.config['SESSION_USE_SIGNER'] = True
|
||||
app.config['SESSION_KEY_PREFIX'] = 'BT_:'
|
||||
app.config['SESSION_COOKIE_NAME'] = public.md5(app.secret_key)
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = 86400 * 30
|
||||
if app.config['SSL']:
|
||||
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
||||
app.config['SESSION_COOKIE_SECURE'] = True
|
||||
else:
|
||||
app.config['SESSION_COOKIE_SAMESITE'] = None
|
||||
|
||||
|
||||
Session(app)
|
||||
|
||||
import common
|
||||
@@ -212,6 +220,23 @@ def error_404(e):
|
||||
}
|
||||
return Response(errorStr,status=404,headers=headers)
|
||||
|
||||
|
||||
# Flask 403页面勾子
|
||||
@app.errorhandler(403)
|
||||
def error_403(e):
|
||||
errorStr = '''<html>
|
||||
<head><title>403 Forbidden</title></head>
|
||||
<body>
|
||||
<center><h1>403 Forbidden</h1></center>
|
||||
<hr><center>nginx</center>
|
||||
</body>
|
||||
</html>'''
|
||||
headers = {
|
||||
"Content-Type": "text/html"
|
||||
}
|
||||
return Response(errorStr, status=403, headers=headers)
|
||||
|
||||
|
||||
# Flask 500页面勾子
|
||||
@app.errorhandler(500)
|
||||
def error_500(e):
|
||||
@@ -219,9 +244,6 @@ def error_500(e):
|
||||
|
||||
During handling of the above exception, another exception occurred:'''
|
||||
error_info = public.get_error_info().strip().split(ss)[-1].strip()
|
||||
if error_info.find("`GLIBC_2.14'") != -1:
|
||||
public.downloadFile('https://download.bt.cn/auth/libAuth_gcc_4.4.7_x{}.so'.format(public.get_sysbit()),'/www/server/panel/class/libAuth.x86-64.so')
|
||||
error_info += "\n已尝试自动修复此问题,请刷新页面重试!"
|
||||
request_info = '''REQUEST_DATE: {request_date}
|
||||
PAN_VERSION: {panel_version}
|
||||
OS_VERSION: {os_version}
|
||||
@@ -235,7 +257,7 @@ REQUEST_FORM: {request_form}
|
||||
full_path = request.full_path,
|
||||
request_form = request.form.to_dict(),
|
||||
user_agent = request.headers.get('User-Agent'),
|
||||
panel_version = public.get_panel_version(),
|
||||
panel_version = public.version(),
|
||||
os_version = public.get_os_version()
|
||||
)
|
||||
|
||||
@@ -294,7 +316,7 @@ def site(pdata = None):
|
||||
import panelSite
|
||||
siteObject = panelSite.panelSite()
|
||||
|
||||
defs = ('upload_csv','create_website_multiple','del_redirect_multiple','del_proxy_multiple','delete_dir_auth_multiple',
|
||||
defs = ('check_del_data','upload_csv','create_website_multiple','del_redirect_multiple','del_proxy_multiple','delete_dir_auth_multiple',
|
||||
'delete_dir_bind_multiple','delete_domain_multiple','set_site_etime_multiple','set_site_php_version_multiple',
|
||||
'delete_website_multiple','set_site_status_multiple','get_site_err_log','get_site_domains','GetRedirectFile',
|
||||
'SaveRedirectFile','DeleteRedirect','GetRedirectList','CreateRedirect','ModifyRedirect',
|
||||
@@ -348,7 +370,7 @@ def database(pdata = None):
|
||||
return render_template('database.html',data=data)
|
||||
import database
|
||||
databaseObject = database.database()
|
||||
defs = ('get_mysql_user','check_mysql_ssl_status','write_ssl_to_mysql','GetdataInfo','GetInfo','ReTable','OpTable','AlTable','GetSlowLogs','GetRunStatus',
|
||||
defs = ('check_del_data','get_database_size', 'get_mysql_user','check_mysql_ssl_status','write_ssl_to_mysql','GetdataInfo','GetInfo','ReTable','OpTable','AlTable','GetSlowLogs','GetRunStatus',
|
||||
'SetDbConf','GetDbStatus','BinLog','GetErrorLog','GetMySQLInfo','SetDataDir','SetMySQLPort',
|
||||
'AddDatabase','DeleteDatabase','SetupPassword','ResDatabasePassword','ToBackup','DelBackup',
|
||||
'InputSql','SyncToDatabases','SyncGetDatabases','GetDatabaseAccess','SetDatabaseAccess')
|
||||
@@ -496,7 +518,7 @@ def ssh_security(pdata=None):
|
||||
firewallObject = ssh_security.ssh_security()
|
||||
defs = ('san_ssh_security', 'set_password', 'set_sshkey', 'stop_key', 'get_config',
|
||||
'stop_password', 'get_key', 'return_ip', 'add_return_ip', 'del_return_ip', 'start_jian', 'stop_jian',
|
||||
'get_jian', 'get_logs','set_root','stop_root','start_auth_method','stop_auth_method','get_auth_method','check_so_file','get_so_file')
|
||||
'get_jian', 'get_logs','set_root','stop_root','start_auth_method','stop_auth_method','get_auth_method','check_so_file','get_so_file','get_pin')
|
||||
return publicObject(firewallObject, defs, None, pdata)
|
||||
|
||||
|
||||
@@ -599,7 +621,8 @@ def files(pdata = None):
|
||||
return render_template('files.html',data=data)
|
||||
import files
|
||||
filesObject = files.files()
|
||||
defs = ('get_file_attribute','get_file_hash','CreateLink','get_progress','restore_website','fix_permissions','get_all_back',
|
||||
defs = ('files_search','files_replace','get_replace_logs',
|
||||
'get_file_attribute','get_file_hash','CreateLink','get_progress','restore_website','fix_permissions','get_all_back',
|
||||
'restore_path_permissions','del_path_premissions','get_path_premissions','back_path_permissions',
|
||||
'CheckExistsFiles','GetExecLog','GetSearch','ExecShell','GetExecShellMsg','exec_git','exec_composer','create_download_url',
|
||||
'UploadFile','GetDir','CreateFile','CreateDir','DeleteDir','DeleteFile','get_download_url_list','remove_download_url','modify_download_url',
|
||||
@@ -675,7 +698,7 @@ def config(pdata = None):
|
||||
return render_template( 'config.html',data=data)
|
||||
import config
|
||||
defs = (
|
||||
'set_empty','set_backup_notification','get_panel_ssl_status','set_file_deny', 'del_file_deny', 'get_file_deny',
|
||||
'send_by_telegram','set_empty','set_backup_notification','get_panel_ssl_status','set_file_deny', 'del_file_deny', 'get_file_deny',
|
||||
'get_httpd_access_log_format_parameter','set_httpd_format_log_to_website','get_httpd_access_log_format',
|
||||
'del_httpd_access_log_format','add_httpd_access_log_format','get_nginx_access_log_format_parameter',
|
||||
'set_format_log_to_website','get_nginx_access_log_format','del_nginx_access_log_format',
|
||||
@@ -691,7 +714,7 @@ def config(pdata = None):
|
||||
'get_php_config','get_config','SavePanelSSL','GetPanelSSL','GetPHPConf','SetPHPConf',
|
||||
'GetPanelList','AddPanelInfo','SetPanelInfo','DelPanelInfo','ClickPanelInfo','SetPanelSSL',
|
||||
'SetTemplates','Set502','setPassword','setUsername','setPanel','setPathInfo','setPHPMaxSize',
|
||||
'getFpmConfig','setFpmConfig','setPHPMaxTime','syncDate','setPHPDisable','SetControl',
|
||||
'getFpmConfig','setFpmConfig','setPHPMaxTime','syncDate','setPHPDisable','SetControl','get_settings2','del_tg_info','set_tg_bot',
|
||||
'ClosePanel','AutoUpdatePanel','SetPanelLock','return_mail_list','del_mail_list','add_mail_address','user_mail_send','get_user_mail','set_dingding','get_dingding',
|
||||
'get_settings','user_stmp_mail_send','user_dingding_send','get_login_send','set_login_send','clear_login_send','get_login_log','login_ipwhite'
|
||||
)
|
||||
@@ -708,7 +731,7 @@ def ajax(pdata = None):
|
||||
'check_user_auth','to_not_beta','get_beta_logs','apple_beta','GetApacheStatus','GetCloudHtml',
|
||||
'get_load_average','GetOpeLogs','GetFpmLogs','GetFpmSlowLogs','SetMemcachedCache','GetMemcachedStatus',
|
||||
'GetRedisStatus','GetWarning','SetWarning','CheckLogin','GetSpeed','GetAd','phpSort','ToPunycode',
|
||||
'GetBetaStatus','SetBeta','setPHPMyAdmin','delClose','KillProcess','GetPHPInfo','GetQiniuFileList',
|
||||
'GetBetaStatus','SetBeta','setPHPMyAdmin','delClose','KillProcess','GetPHPInfo','GetQiniuFileList','get_process_tops','get_process_cpu_high',
|
||||
'UninstallLib','InstallLib','SetQiniuAS','GetQiniuAS','GetLibList','GetProcessList','GetNetWorkList',
|
||||
'GetNginxStatus','GetPHPStatus','GetTaskCount','GetSoftList','GetNetWorkIo','GetDiskIo','GetCpuIo',
|
||||
'CheckInstalled','UpdatePanel','GetInstalled','GetPHPConfig','SetPHPConfig')
|
||||
@@ -777,8 +800,8 @@ def task(pdata = None):
|
||||
if comReturn: return comReturn
|
||||
import panelTask
|
||||
toObject = panelTask.bt_task()
|
||||
defs = ('get_task_lists','remove_task','get_task_find')
|
||||
result = publicObject(toObject,defs,None,pdata)
|
||||
defs = ('get_task_lists', 'remove_task', 'get_task_find', "get_task_log_by_id")
|
||||
result = publicObject(toObject, defs, None, pdata)
|
||||
return result
|
||||
|
||||
@app.route('/plugin',methods=method_all)
|
||||
@@ -853,7 +876,7 @@ def download():
|
||||
attachment_filename=os.path.basename(filename),
|
||||
cache_timeout=0)
|
||||
|
||||
@app.route('/cloud',methods=method_get)
|
||||
@app.route('/cloud',methods=method_all)
|
||||
def panel_cloud():
|
||||
#从对像存储下载备份文件接口
|
||||
comReturn = comm.local()
|
||||
@@ -877,7 +900,36 @@ def panel_cloud():
|
||||
if plugin_name == 'ftp':
|
||||
if download_url.find("ftp") != 0:download_url = "ftp://" + download_url
|
||||
else:
|
||||
if download_url.find('http') != 0:download_url = 'http://' + download_url
|
||||
if download_url.find('http') != 0: download_url = 'http://' + download_url
|
||||
|
||||
if "toserver" in get and get.toserver=="true":
|
||||
download_dir = "/tmp/"
|
||||
if "download_dir" in get:
|
||||
download_dir = get.download_dir
|
||||
local_file = os.path.join(download_dir, get.name)
|
||||
|
||||
input_from_local = False
|
||||
if "input_from_local" in get:
|
||||
input_from_local = True if get.input_from_local == "true" else False
|
||||
|
||||
if input_from_local:
|
||||
if os.path.isfile(local_file):
|
||||
return {
|
||||
"status": True,
|
||||
"msg": "The file already exists and will be restored locally.",
|
||||
"task_id": -1,
|
||||
"local_file": local_file
|
||||
}
|
||||
from panelTask import bt_task
|
||||
task_obj = bt_task()
|
||||
task_id = task_obj.create_task('Download file', 1, download_url, local_file)
|
||||
return {
|
||||
"status": True,
|
||||
"msg": "The download task was created successfully",
|
||||
"local_file": local_file,
|
||||
"task_id": task_id
|
||||
}
|
||||
|
||||
return redirect(download_url)
|
||||
|
||||
#======================普通路由区============================#
|
||||
@@ -951,6 +1003,7 @@ def login():
|
||||
os.remove(sess_file)
|
||||
except:
|
||||
pass
|
||||
g.dologin = True
|
||||
return redirect(login_path)
|
||||
|
||||
if is_auth_path:
|
||||
@@ -1166,20 +1219,23 @@ def panel_public():
|
||||
|
||||
@app.route('/favicon.ico',methods=method_get)
|
||||
def send_favicon():
|
||||
#图标
|
||||
# 图标
|
||||
comReturn = comm.local()
|
||||
if comReturn: return abort(404)
|
||||
s_file = '/www/server/panel/BTPanel/static/favicon.ico'
|
||||
if not os.path.exists(s_file): return abort(404)
|
||||
return send_file(s_file,conditional=True,add_etags=True)
|
||||
|
||||
|
||||
@app.route('/service_status',methods = method_get)
|
||||
def service_status():
|
||||
#检查面板当前状态
|
||||
try:
|
||||
if not 'login' in session: session.clear()
|
||||
except:
|
||||
pass
|
||||
return 'True'
|
||||
# @app.route('/service_status', methods=method_get)
|
||||
# def service_status():
|
||||
# # 检查面板当前状态
|
||||
# try:
|
||||
# if not 'login' in session: session.clear()
|
||||
# except:
|
||||
# pass
|
||||
# return 'True'
|
||||
|
||||
|
||||
@app.route('/coll',methods=method_all)
|
||||
@app.route('/coll/',methods=method_all)
|
||||
@@ -1209,13 +1265,15 @@ def panel_other(name=None,fun = None,stype=None):
|
||||
if len(tmp) == 1: tmp.append('')
|
||||
stype = tmp[1]
|
||||
|
||||
|
||||
if not name: name = 'coll'
|
||||
if not public.path_safe_check("%s/%s/%s" % (name,fun,stype)): return abort(404)
|
||||
if name.find('./') != -1 or not re.match(r"^[\w-]+$",name): return abort(404)
|
||||
if not name: return public.returnJson(False,'PLUGIN_INPUT_ERR'),json_header
|
||||
p_path = os.path.join('/www/server/panel/plugin/', name)
|
||||
if not os.path.exists(p_path): return abort(404)
|
||||
if not os.path.exists(p_path):
|
||||
if name == 'btwaf' and fun == 'index':
|
||||
return render_template('error3.html',data={})
|
||||
return abort(404)
|
||||
|
||||
#是否响插件应静态文件
|
||||
if fun == 'static':
|
||||
@@ -1295,9 +1353,7 @@ def panel_other(name=None,fun = None,stype=None):
|
||||
return public.returnJson(False,public.getMsg('PUBLIC_ERR_RETURN').format(r_type)),json_header
|
||||
return data
|
||||
except:
|
||||
error_info = public.get_error_info()
|
||||
public.submit_error(error_info)
|
||||
return error_info.replace('\n','<br>\n')
|
||||
return public.get_error_object(None, plugin_name=name)
|
||||
|
||||
|
||||
@app.route('/hook',methods=method_all)
|
||||
@@ -1660,7 +1716,16 @@ def is_login(result):
|
||||
result = make_response(result)
|
||||
request_token = public.GetRandomString(48)
|
||||
session['request_token'] = request_token
|
||||
result.set_cookie('request_token',request_token,max_age=86400*30)
|
||||
samesite = app.config['SESSION_COOKIE_SAMESITE']
|
||||
secure = app.config['SESSION_COOKIE_SECURE']
|
||||
if app.config['SSL'] and request.full_path.find('/login?tmp_token=') == 0:
|
||||
samesite = 'None'
|
||||
secure = True
|
||||
result.set_cookie('request_token', request_token,
|
||||
max_age=86400 * 30,
|
||||
samesite= samesite,
|
||||
secure=secure
|
||||
)
|
||||
return result
|
||||
|
||||
# js随机数模板使用,用于不更新版本号时更新前端文件不需要用户强制刷新浏览器
|
||||
@@ -1787,7 +1852,7 @@ def ws_panel_thread(get):
|
||||
check_str = '{}{}'.format(get.mod_name, get.def_name)
|
||||
if not re.match("^\w+$", check_str) or get.mod_name in ['public', 'common', 'db', 'db_mysql', 'downloadFile',
|
||||
'jobs']:
|
||||
get._ws.send(public.getJson(public.return_status_code(1000, '不安全的mod_name,def_name参数内容')))
|
||||
get._ws.send(public.getJson(public.return_status_code(1000, 'Unsafe mod_name, def_name parameter content')))
|
||||
return
|
||||
if not hasattr(get, 'args'):
|
||||
get._ws.send(public.getJson(public.return_status_code(1001, 'args')))
|
||||
@@ -1795,21 +1860,21 @@ def ws_panel_thread(get):
|
||||
|
||||
mod_file = '{}/{}.py'.format(public.get_class_path(), get.mod_name)
|
||||
if not os.path.exists(mod_file):
|
||||
get._ws.send(public.getJson(public.return_status_code(1000, '指定模块{}不存在'.format(get.mod_name))))
|
||||
get._ws.send(public.getJson(public.return_status_code(1000, 'Specified module {} does not exist'.format(get.mod_name))))
|
||||
return
|
||||
_obj = public.get_script_object(mod_file)
|
||||
if not _obj:
|
||||
get._ws.send(public.getJson(public.return_status_code(1000, '指定模块{}不存在'.format(get.mod_name))))
|
||||
get._ws.send(public.getJson(public.return_status_code(1000, 'Specified module {} does not exist'.format(get.mod_name))))
|
||||
return
|
||||
_cls = getattr(_obj, get.mod_name)
|
||||
if not _cls:
|
||||
get._ws.send(
|
||||
public.getJson(public.return_status_code(1000, '在{}模块中没有找到{}对像'.format(get.mod_name, get.mod_name))))
|
||||
public.getJson(public.return_status_code(1000, 'The {} object was not found in the {} module'.format(get.mod_name, get.mod_name))))
|
||||
return
|
||||
_def = getattr(_cls(), get.def_name)
|
||||
if not _def:
|
||||
get._ws.send(
|
||||
public.getJson(public.return_status_code(1000, '在{}对像中没有找到{}方法'.format(get.mod_name, get.def_name))))
|
||||
public.getJson(public.return_status_code(1000, 'The {} object was not found in the {} module'.format(get.mod_name, get.def_name))))
|
||||
return
|
||||
result = {
|
||||
'callback': get.ws_callback,
|
||||
@@ -1931,7 +1996,9 @@ def sock_recv(cmdstring, ws):
|
||||
sock_pids[p.pid] = ws
|
||||
kill_closed()
|
||||
while p.poll() is None:
|
||||
ws.send(p.stdout.readline().decode())
|
||||
send_line = p.stdout.readline().decode()
|
||||
if not send_line or send_line.find('tail: ') != -1: continue
|
||||
ws.send(send_line)
|
||||
ws.send(p.stdout.read().decode())
|
||||
except:
|
||||
kill_closed()
|
||||
@@ -1954,10 +2021,10 @@ def close_sock_shell():
|
||||
skey = public.md5(cmdstring)
|
||||
pid = cache.get(skey)
|
||||
if not pid:
|
||||
return json.dumps(public.return_data(False, [], error_msg='指定sock已终止!')), json_header
|
||||
return json.dumps(public.return_data(False, [], error_msg='The specified sock has been terminated!')), json_header
|
||||
os.kill(pid, 9)
|
||||
cache.delete(skey)
|
||||
return json.dumps(public.return_data(True, '操作成功!')), json_header
|
||||
return json.dumps(public.return_data(True, 'Successful operation!')), json_header
|
||||
|
||||
def check_csrf_websocket(ws,args):
|
||||
'''
|
||||
|
||||
+582
-11
@@ -249,6 +249,10 @@ html {
|
||||
padding-bottom: 70px
|
||||
}
|
||||
|
||||
.mt5 {
|
||||
margin-top: 5px
|
||||
}
|
||||
|
||||
.mt10 {
|
||||
margin-top: 10px
|
||||
}
|
||||
@@ -3129,6 +3133,15 @@ html .menu .menu_exit:hover {
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.databaseImportTable>tbody>tr>td {
|
||||
vertical-align: middle;
|
||||
padding: 5px 10px;
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.table>tbody{
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
@@ -9360,7 +9373,6 @@ select[disabled]{
|
||||
}
|
||||
.redirect_check label span,
|
||||
.redirect_checkbox label span,
|
||||
.ssl_checkbox label span,
|
||||
.redirect_tourl label span{
|
||||
max-width: 55px;
|
||||
display: inline-block;
|
||||
@@ -9392,6 +9404,9 @@ select[disabled]{
|
||||
.redirect_tourl .radio_item input{
|
||||
margin-right: 5px;
|
||||
}
|
||||
.site_ssl_check i.form-checkbox {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
|
||||
/*首页概览网卡选择*/
|
||||
@@ -9498,7 +9513,8 @@ select[name="network-io"]{
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.bt_table .cust—checkbox{
|
||||
.bt_table .cust—checkbox,
|
||||
.bt-form .form-checkbox{
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -9510,11 +9526,14 @@ select[name="network-io"]{
|
||||
border-radius: 2px;
|
||||
}
|
||||
.bt_table .cust—checkbox.active,
|
||||
.bt_table .cust—checkbox.selected{
|
||||
.bt_table .cust—checkbox.selected,
|
||||
.bt-form .form-checkbox.active,
|
||||
.bt-form .form-checkbox.selected{
|
||||
background-color: #20a53a;
|
||||
border-color: #20a53a;
|
||||
}
|
||||
.bt_table .cust—checkbox.active::after{
|
||||
.bt_table .cust—checkbox.active::after,
|
||||
.bt-form .form-checkbox.active::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
@@ -9528,7 +9547,8 @@ select[name="network-io"]{
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.bt_table .cust—checkbox.selected::after{
|
||||
.bt_table .cust—checkbox.selected::after,
|
||||
.bt-form .form-checkbox.selected::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
@@ -9540,15 +9560,23 @@ select[name="network-io"]{
|
||||
width: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.bt_table .cust—checkbox+input{
|
||||
.bt_table .cust—checkbox+input,
|
||||
.bt-form .form-checkbox+input{
|
||||
display: none;
|
||||
}
|
||||
.bt_table .cursor-pointer{
|
||||
.bt_table .cursor-pointer,
|
||||
.bt-form .cursor-pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
.bt_table thead .sort-active span{
|
||||
font-weight: 500;
|
||||
}
|
||||
.vertical_middle{
|
||||
vertical-align: middle;
|
||||
}
|
||||
.bt-form .form-checkbox-label span{
|
||||
font-weight: 400;
|
||||
}
|
||||
/* .bt_table thead .sort-active .glyphicon{
|
||||
color: #20a53a;
|
||||
} */
|
||||
@@ -9606,7 +9634,7 @@ select[name="network-io"]{
|
||||
outline: none;
|
||||
padding-left: 8px;
|
||||
vertical-align: top;
|
||||
width:230px;
|
||||
width:240px;
|
||||
}
|
||||
.tootls_group .bt_search .search_input:focus{
|
||||
border-color: #20a53a;
|
||||
@@ -9735,15 +9763,19 @@ select[name="network-io"]{
|
||||
border-radius: 2px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
color:#666;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bt_table_select_group {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.bt_select_updown{
|
||||
height: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.bt_select_updown .bt_select_value{
|
||||
height: 28px;
|
||||
@@ -9751,6 +9783,9 @@ select[name="network-io"]{
|
||||
padding:0 10px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.bt_select_updown .bt_select_list{
|
||||
display: none;
|
||||
@@ -9776,6 +9811,9 @@ select[name="network-io"]{
|
||||
line-height: 28px;
|
||||
color: #333;
|
||||
padding:0 10px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.bt_select_updown .bt_select_list .item.disabled{
|
||||
opacity: .65;
|
||||
@@ -9957,6 +9995,174 @@ select[name="network-io"]{
|
||||
}
|
||||
/*批量创建站点end*/
|
||||
|
||||
|
||||
/*站点删除安全验证*/
|
||||
|
||||
.delete_site_layer #site_delete_form,.verify_site_layer_info .check_delete_site_main {
|
||||
padding: 30px 25px !important;
|
||||
}
|
||||
|
||||
.delete_site_layer .check_title{
|
||||
font-size: 16px;
|
||||
padding-left: 55px;
|
||||
font-weight: bold;
|
||||
color: #111;
|
||||
}
|
||||
.delete_site_layer .check_type_group{
|
||||
padding:20px 0 20px 55px;
|
||||
}
|
||||
.delete_site_layer .layui-layer-content .layui-layer-ico{
|
||||
left: 40px !important;
|
||||
top: 27px !important;
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.delete_site_layer .check_type_group label{
|
||||
height:24px;
|
||||
line-height:24px;
|
||||
cursor:pointer;
|
||||
margin-right:35px;
|
||||
margin-bottom:0;
|
||||
}
|
||||
.delete_site_layer .check_type_group label span{
|
||||
display:inline-block;
|
||||
font-weight:normal;
|
||||
vertical-align:middle;
|
||||
margin:0 0 0 5px;
|
||||
position:relative;
|
||||
top: 0;
|
||||
}
|
||||
.delete_site_layer .check_type_group input{
|
||||
height:15px;
|
||||
width:15px;
|
||||
vertical-align:middle;
|
||||
margin:0;
|
||||
cursor:pointer;
|
||||
}
|
||||
.delete_site_layer .webDelete .vcode{
|
||||
margin: 0 15px 5px 15px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
.delete_site_layer .webDelete .vcode input{
|
||||
width: 80px !important;
|
||||
border: 1px solid #ccc;
|
||||
padding-left: 5px;
|
||||
outline: none;
|
||||
}
|
||||
.delete_site_layer .webDelete .vcode input:focus,
|
||||
.delete_site_layer .webDelete .vcode input:active{
|
||||
border-color:#20a53a !important;
|
||||
}
|
||||
|
||||
/*END*//*验证站点删除二次信息*/
|
||||
|
||||
|
||||
.verify_site_layer_info .layui-layer-btn0{
|
||||
background-color: #e00000 !important;
|
||||
}
|
||||
.verify_site_layer_info .layui-layer-btn0:hover{
|
||||
background-color: #ff0000 !important;
|
||||
}
|
||||
.verify_site_layer_info.active .layui-layer-btn0{
|
||||
background: #e000008c !important;
|
||||
cursor: no-drop;
|
||||
}
|
||||
.check_delete_site_main {
|
||||
height:100%;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.check_delete_site_main .layui-layer-ico{
|
||||
left: 30px !important;
|
||||
top: 28px !important;
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.check_layer_title{
|
||||
font-size:19px;
|
||||
font-weight:bold;
|
||||
padding-left: 50px;
|
||||
}
|
||||
.check_layer_content{
|
||||
margin: 25px 0;
|
||||
border: 1px solid #ececec;
|
||||
overflow-y: auto;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.check_layer_item{
|
||||
padding: 12px;
|
||||
max-height: 80px;
|
||||
border-radius: 2px;
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
.check_layer_item:last-child{
|
||||
border-bottom:none;
|
||||
}
|
||||
.check_layer_item:first-child{
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
.check_layer_item:hover{
|
||||
background: #fcfcfc;
|
||||
}
|
||||
.check_layer_content .check_layer_site span,
|
||||
.check_layer_content .check_layer_database span{
|
||||
display:inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.check_layer_content span i.warning{
|
||||
color: #EE3838;
|
||||
font-weight: bold;
|
||||
}
|
||||
.check_layer_content span i{
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
.check_layer_content .check_layer_site,
|
||||
.check_layer_content .check_layer_database{
|
||||
display:inline-block;
|
||||
font-size:13px;
|
||||
height:25px;
|
||||
line-height:25px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
.check_layer_content .check_layer_site span:nth-child(1),
|
||||
.check_layer_content .check_layer_database span:nth-child(1){
|
||||
width:180px;
|
||||
}
|
||||
.check_layer_content .check_layer_site span:nth-child(2),
|
||||
.check_layer_content .check_layer_database span:nth-child(2){
|
||||
width: 310px;
|
||||
line-height:26px;
|
||||
height:25px;
|
||||
}
|
||||
|
||||
.check_layer_content .check_layer_database span:nth-child(2) .glyphicon{
|
||||
line-height: 15px;
|
||||
}
|
||||
.check_layer_content .check_layer_site span:nth-child(3),
|
||||
.check_layer_content .check_layer_database span:nth-child(3){
|
||||
width:150px;
|
||||
}
|
||||
|
||||
.check_layer_error{
|
||||
color:red;
|
||||
font-size: 13px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.check_layer_message{
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
margin-top: 5px;
|
||||
}
|
||||
/*end*/
|
||||
|
||||
|
||||
.custom_layer .layui-layer-content .tab-body .tab-con{
|
||||
max-height: 505px;
|
||||
overflow: auto;
|
||||
@@ -10368,4 +10574,369 @@ background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODIiIGhlaWdodD0iODIiIHh
|
||||
}
|
||||
.pro_introduce_content .item span:nth-child(2){
|
||||
padding-left: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
#bt_database_table .tootls_group .pull-left button:nth-child(4):after{
|
||||
content: ' ';
|
||||
position: relative;
|
||||
left: -76px;
|
||||
border-left: 1px solid #ececec;
|
||||
}
|
||||
/*node项目管理*/
|
||||
.node_project_dialog .mask_module{
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
top: 0;
|
||||
width: 610px;
|
||||
left: 170px;
|
||||
z-index: 99;
|
||||
}
|
||||
.node_project_dialog .mask_module .node_mask_module_text{
|
||||
background: #fff;
|
||||
/*color: #000;*/
|
||||
width:280px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
padding: 15px 30px;
|
||||
left: 36%;
|
||||
top: 43%;
|
||||
margin-left: -140px;
|
||||
border: 1px solid #efefef;
|
||||
display: inline-block;
|
||||
border-radius:2px;
|
||||
}
|
||||
.node_project_dialog .node_project_pid_datail h3.tname {
|
||||
margin:10px 10px 10px 0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
#node_module_list .divtable,
|
||||
#project_domian_list .divtable{
|
||||
border: 1px solid #ddd;
|
||||
word-break: break-all;
|
||||
}
|
||||
#node_module_list .table,
|
||||
#project_domian_list .table{
|
||||
border:none;
|
||||
}
|
||||
/*node项目管理 end*/
|
||||
|
||||
|
||||
/*文件内容查找替换*/
|
||||
/*查找textarea样式*/
|
||||
.replace_content_view .replace_content_line:first-child{
|
||||
position: relative;
|
||||
height: 36px;
|
||||
}
|
||||
.replace_content_view .replace_content_line:first-child textarea{
|
||||
width: 485px;
|
||||
border: 1px solid #ccc;
|
||||
border-right: none;
|
||||
height: 36px;
|
||||
position: absolute;
|
||||
padding: 10px 0 0 10px;
|
||||
}
|
||||
.replace_content_view .replace_content_line:first-child i.history_search{
|
||||
position: absolute;
|
||||
right: 85px;
|
||||
top: 1px;
|
||||
cursor: pointer;
|
||||
line-height: 34px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.replace_content_view .replace_content_line:first-child i.history_search:hover{
|
||||
background-color: #f0f0f1;
|
||||
border-left: 1px solid #e2e2e2;
|
||||
}
|
||||
.replace_content_view .replace_content_line:first-child .normalBtnStyle{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
/*查找textarea样式 end*/
|
||||
/*历史搜索列表*/
|
||||
.replace_content_view .history_search_list {
|
||||
position: absolute;
|
||||
top: 36px;
|
||||
left: 10px;
|
||||
background-color: #fff;
|
||||
width: 485px;
|
||||
font-size: 14px;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ececec;
|
||||
box-shadow: 4px 2px 5px 0 #ececec;
|
||||
}
|
||||
.replace_content_view .history_search_list li{
|
||||
border-bottom: 1px solid #ececec;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.replace_content_view .history_search_list li:hover{
|
||||
background: #f0f0f0;
|
||||
color: #555;
|
||||
}
|
||||
/*历史搜索列表 end*/
|
||||
|
||||
.replace_content_view .checkbox_config {
|
||||
color: #666;
|
||||
border-top: none;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.replace_content_view .checkbox_config:nth-child(2n){
|
||||
margin-left: 10px;
|
||||
}
|
||||
.replace_content_view .checkbox_config .laberText {
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.replace_content_view .file_path_switch {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
color: #666;
|
||||
width: 65px;
|
||||
line-height: 31px;
|
||||
top: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.replace_content_view .checkbox_config .file_find_radio,
|
||||
.replace_content_view .checkbox_config .file_find_checkbox,
|
||||
.replace_content_view .file_path_switch .file_find_checkbox {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
vertical-align: sub;
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.replace_content_view .checkbox_config .file_find_radio.active,
|
||||
.replace_content_view .checkbox_config .file_find_checkbox.active,
|
||||
.replace_content_view .file_path_switch .file_find_checkbox.active {
|
||||
background-color: #20a53a;
|
||||
border-color: #20a53a;
|
||||
}
|
||||
|
||||
.replace_content_view .checkbox_config .file_find_radio.active::after,
|
||||
.replace_content_view .checkbox_config .file_find_checkbox.active::after,
|
||||
.replace_content_view .file_path_switch .file_find_checkbox.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -2.5px;
|
||||
margin-top: -6px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid #fff;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.replace_content_view .checkbox_config .file_find_radio{
|
||||
border-radius: 20px;
|
||||
}
|
||||
.replace_content_view .replace_content_box .glyphicon-folder-open{
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 145px;
|
||||
}
|
||||
.replace_content_view .file_path_switch .laberText {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
.replace_content_view .bt-input-text {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.replace_content_view .replace_content_line {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.replace_content_view .replace_content_line .tname {
|
||||
display: block;
|
||||
float: left;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
width: 50px;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.replace_content_view .replace_content_line .info-r {
|
||||
margin-left: 50px;
|
||||
position: relative;
|
||||
}
|
||||
/*按钮样式*/
|
||||
.replace_content_view .normalBtnStyle {
|
||||
min-width: 85px;
|
||||
height: 36px;
|
||||
}
|
||||
.replace_content_view .normalBtnStyle:hover{
|
||||
background-color:#20a53a;
|
||||
color: #fff;
|
||||
}
|
||||
.replace_content_view .checkBtn {
|
||||
border: none;
|
||||
background-color: #20a53a;
|
||||
color: #fff;
|
||||
}
|
||||
.replace_content_view .replaceBtn {
|
||||
color: #20a53a;
|
||||
background-color: #fff;
|
||||
border: 1px solid #20a53a;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.replace_content_view .matchModel {
|
||||
width: 570px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
/*匹配容器*/
|
||||
.replace_content_view .match_container .header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #d4d4d4;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.replace_content_view .match_container .main {
|
||||
overflow: auto;
|
||||
height: 290px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.replace_content_view .match_result_file_content{
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
word-break: normal;
|
||||
border-top: 1px solid #eee;
|
||||
display: none;
|
||||
}
|
||||
.replace_content_view .match_result_file_content .match_result_detail{
|
||||
font-size: 13px;
|
||||
padding: 5px 5px 5px 15px;
|
||||
}
|
||||
.replace_content_view .match_result_file_content .match_result_detail:hover{
|
||||
background-color: #eee;
|
||||
}
|
||||
.replace_content_view .match_result_file_content.matchShow{
|
||||
display: block;
|
||||
}
|
||||
.replace_content_view .match_result_file_content .match_result_detail i{
|
||||
color: red;
|
||||
font-style: inherit;
|
||||
background-color: aliceblue;
|
||||
}
|
||||
.replace_content_view .match_content_item {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.replace_content_view .match_content_title{
|
||||
background-color: #fbfbfb;
|
||||
padding: 5px 15px 5px 5px;
|
||||
}
|
||||
.replace_content_view .match_result_file_title{
|
||||
cursor: pointer;
|
||||
width: 490px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
}
|
||||
.replace_content_view .match_content_title i.glyphicon{
|
||||
margin-right: 10px;
|
||||
margin-left:5px;
|
||||
}
|
||||
/*文件内容查找替换 end*/
|
||||
/*删除数据库弹窗样式*/
|
||||
.promptDelete p {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.promptDelete .confirm-info-box {
|
||||
position: relative;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.promptDelete input {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.promptDelete .placeholder {
|
||||
line-height: 24px;
|
||||
left: 7px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
/*删除数据库弹窗样式 end*/
|
||||
/*站点删除安全验证*/
|
||||
.delete_site_layer #site_delete_form,.verify_site_layer_info .check_delete_site_main {
|
||||
padding: 30px 25px !important;
|
||||
}
|
||||
|
||||
.delete_site_layer .check_title{
|
||||
font-size: 16px;
|
||||
padding-left: 55px;
|
||||
font-weight: bold;
|
||||
color: #111;
|
||||
}
|
||||
.delete_site_layer .check_type_group{
|
||||
padding:20px 0 20px 55px;
|
||||
}
|
||||
.delete_site_layer .layui-layer-content .layui-layer-ico{
|
||||
left: 40px !important;
|
||||
top: 27px !important;
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.delete_site_layer .check_type_group label{
|
||||
height:24px;
|
||||
line-height:24px;
|
||||
cursor:pointer;
|
||||
margin-right:35px;
|
||||
margin-bottom:0;
|
||||
}
|
||||
.delete_site_layer .check_type_group label span{
|
||||
display:inline-block;
|
||||
font-weight:normal;
|
||||
vertical-align:middle;
|
||||
margin:0 0 0 5px;
|
||||
position:relative;
|
||||
top: 0;
|
||||
}
|
||||
.delete_site_layer .check_type_group input{
|
||||
height:15px;
|
||||
width:15px;
|
||||
vertical-align:middle;
|
||||
margin:0;
|
||||
cursor:pointer;
|
||||
}
|
||||
.delete_site_layer .webDelete .vcode{
|
||||
margin: 0 15px 5px 15px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
.delete_site_layer .webDelete .vcode input{
|
||||
width: 80px !important;
|
||||
border: 1px solid #ccc;
|
||||
padding-left: 5px;
|
||||
outline: none;
|
||||
}
|
||||
.delete_site_layer .webDelete .vcode input:focus,
|
||||
.delete_site_layer .webDelete .vcode input:active{
|
||||
border-color:#20a53a !important;
|
||||
}
|
||||
/*END*/
|
||||
+2
-273
@@ -235,7 +235,7 @@ var three_channel_status = {};
|
||||
});
|
||||
get_three_channel(function(res){
|
||||
three_channel_status = res;
|
||||
$('#channel_auth').val(!res.user_mail.user_name && !res.dingding.dingding ? 'Email is not set':(res.user_mail.user_name? 'Email is set':(res.dingding.dingding? 'dingding is set': '')))
|
||||
$('#channel_auth').val(!res.user_mail.user_name && !res.telegram.setup ? 'Email is not set':(res.user_mail.user_name? 'Email is set':(res.telegram.setup? 'Telegram is set': '')))
|
||||
});
|
||||
get_login_send(function(rdata){
|
||||
$('#panel_report').val(!rdata.status ? 'Email is not set':(rdata.msg.mail? 'Already set':'Not set'))
|
||||
@@ -243,7 +243,7 @@ var three_channel_status = {};
|
||||
})()
|
||||
|
||||
function get_three_channel(callback){
|
||||
$.post('/config?action=get_settings',function(res){
|
||||
$.post('/config?action=get_settings2',function(res){
|
||||
if(callback) callback(res);
|
||||
});
|
||||
}
|
||||
@@ -1142,277 +1142,6 @@ function modify_basic_auth() {
|
||||
}
|
||||
});
|
||||
}
|
||||
function open_three_channel_auth(){
|
||||
get_channel_settings(function(rdata){
|
||||
var isOpen = rdata.dingding.info.msg.isAtAll == 'True' ? 'checked': '';
|
||||
var isDing = rdata.dingding.info.msg == 'No information'? '': rdata.dingding.info.msg.dingding_url;
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "600px",
|
||||
title: "Setting up notification",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form mes_channel">\
|
||||
<div class="bt-w-main">\
|
||||
<div class="bt-w-menu">\
|
||||
<p class="bgw">Email</p>\
|
||||
</div>\
|
||||
<div class="bt-w-con pd15">\
|
||||
<div class="plugin_body">\
|
||||
<div class="conter_box active" >\
|
||||
<div class="bt-form">\
|
||||
<div class="line">\
|
||||
<button class="btn btn-success btn-sm" onclick="add_receive_info()">Add recipient</button>\
|
||||
<button class="btn btn-default btn-sm" onclick="sender_info_edit()">Sender settings</button>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<div class="divtable">\
|
||||
<table class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0"><thead><tr><th>Email</th><th width="80px">Operating</th></tr></thead></table>\
|
||||
<table class="table table-hover"><tbody id="receive_table"></tbody></table>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="conter_box" style="display:none">\
|
||||
<div class="bt-form">\
|
||||
<div class="line">\
|
||||
<span class="tname">Notice all</span>\
|
||||
<div class="info-r" style="height:28px; margin-left:125px">\
|
||||
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" '+ isOpen+'>\
|
||||
<label style="position: relative;top: 5px;" class="btswitch-btn" for="panel_alert_all"></label>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">DingDing URL</span>\
|
||||
<div class="info-r">\
|
||||
<textarea name="channel_dingding_value" class="bt-input-text mr5" type="text" style="width: 300px; height:90px; line-height:20px">'+isDing+'</textarea>\
|
||||
</div>\
|
||||
<button class="btn btn-success btn-sm" onclick="SetChannelDing()" style="margin: 10px 0 0 125px;">Save</button>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>'
|
||||
})
|
||||
$(".bt-w-menu p").click(function () {
|
||||
var index = $(this).index();
|
||||
$(this).addClass('bgw').siblings().removeClass('bgw');
|
||||
$('.conter_box').eq(index).show().siblings().hide();
|
||||
});
|
||||
get_receive_list();
|
||||
})
|
||||
}
|
||||
function sender_info_edit(){
|
||||
var loadT = layer.msg('Getting profile, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=get_settings',function(rdata){
|
||||
layer.close(loadT);
|
||||
var qq_mail = rdata.user_mail.info.msg.qq_mail == undefined ? '' : rdata.user_mail.info.msg.qq_mail,
|
||||
qq_stmp_pwd = rdata.user_mail.info.msg.qq_stmp_pwd == undefined? '' : rdata.user_mail.info.msg.qq_stmp_pwd,
|
||||
hosts = rdata.user_mail.info.msg.hosts == undefined? '' : rdata.user_mail.info.msg.hosts,
|
||||
port = rdata.user_mail.info.msg.port == undefined? '' : rdata.user_mail.info.msg.port;
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "485px",
|
||||
title: "Set sender email information",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 pb70">\
|
||||
<div class="line">\
|
||||
<span class="tname">Sender email</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_value" class="bt-input-text mr5" type="text" style="width: 300px" value="'+qq_mail+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">smtp password</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_password" class="bt-input-text mr5" type="password" style="width: 300px" value="'+qq_stmp_pwd+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">smtp server</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_server" class="bt-input-text mr5" type="text" style="width: 300px" value="'+hosts+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">smtp port</span>\
|
||||
<div class="info-r">\
|
||||
<select class="bt-input-text mr5" id="port_select" style="width:'+(select_port(port)?'300px':'100px')+'"></select>\
|
||||
<input name="channel_email_port" class="bt-input-text mr5" type="Number" style="display:'+(select_port(port)? 'none':'inline-block')+'; width: 190px" value="'+port+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<ul class="help-info-text c7">\
|
||||
<li>465 port is recommended, the protocol is SSL/TLS</li>\
|
||||
<li>Port 25 is SMTP protocol, port 587 is STARTTLS protocol</li>\
|
||||
</ul>\
|
||||
<div class="bt-form-submit-btn">\
|
||||
<button type="button" class="btn btn-danger btn-sm smtp_closeBtn">Close</button>\
|
||||
<button class="btn btn-success btn-sm SetChannelEmail">Save</button></div>\
|
||||
</div>',
|
||||
success:function(layers,index){
|
||||
var _option = '';
|
||||
if(select_port(port)){
|
||||
if(port == '465' || port == ''){
|
||||
_option = '<option value="465" selected="selected">465</option><option value="25">25</option><option value="587">587</option><option value="other">Customize</option>'
|
||||
}else if(port == '25'){
|
||||
_option = '<option value="465">465</option><option value="25" selected="selected">25</option><option value="587">587</option><option value="other">Customize</option>'
|
||||
}else{
|
||||
_option = '<option value="465">465</option><option value="25">25</option><option value="587" selected="selected">587</option><option value="other">Customize</option>'
|
||||
}
|
||||
}else{
|
||||
_option = '<option value="465">465</option><option value="25">25</option><option value="587" >587</option><option value="other" selected="selected">Customize</option>'
|
||||
}
|
||||
$("#port_select").html(_option)
|
||||
$("#port_select").change(function(e){
|
||||
if(e.target.value == 'other'){
|
||||
$("#port_select").css("width","100px");
|
||||
$('input[name=channel_email_port]').css("display","inline-block");
|
||||
}else{
|
||||
$("#port_select").css("width","300px");
|
||||
$('input[name=channel_email_port]').css("display","none");
|
||||
}
|
||||
})
|
||||
$(".SetChannelEmail").click(function(){
|
||||
var _email = $('input[name=channel_email_value]').val();
|
||||
var _passW = $('input[name=channel_email_password]').val();
|
||||
var _server = $('input[name=channel_email_server]').val();
|
||||
if($('#port_select').val() == 'other'){
|
||||
_port = $('input[name=channel_email_port]').val();
|
||||
}else{
|
||||
_port = $('#port_select').val()
|
||||
}
|
||||
if(_email == ''){
|
||||
return layer.msg('Email address cannot be empty!',{icon:2});
|
||||
}else if(_passW == ''){
|
||||
return layer.msg('STMP password cannot be empty!',{icon:2});
|
||||
}else if(_server == ''){
|
||||
return layer.msg('STMP server address cannot be empty!',{icon:2});
|
||||
}else if(_port == ''){
|
||||
return layer.msg('STMP server port cannot be empty!',{icon:2});
|
||||
}
|
||||
var loadT = layer.msg('The notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
layer.close(index)
|
||||
$.post('/config?action=user_mail_send',{email:_email,stmp_pwd:_passW,hosts:_server,port:_port},function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
})
|
||||
})
|
||||
$(".smtp_closeBtn").click(function(){
|
||||
layer.close(index)
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
function select_port(port){
|
||||
switch(port){
|
||||
case '25':
|
||||
return true;
|
||||
case '465':
|
||||
return true;
|
||||
case '587':
|
||||
return true;
|
||||
case '':
|
||||
return true;
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
function get_channel_settings(callback){
|
||||
var loadT = layer.msg('Getting profile, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=get_settings',function(rdata){
|
||||
layer.close(loadT);
|
||||
if (callback) callback(rdata);
|
||||
})
|
||||
}
|
||||
function add_receive_info(){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "400px",
|
||||
title: "Add recipient email",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 pb70">\
|
||||
<div class="line">\
|
||||
<span class="tname">Recipient mailbox</span>\
|
||||
<div class="info-r">\
|
||||
<input name="creater_email_value" class="bt-input-text mr5" type="text" style="width: 240px" value="">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="bt-form-submit-btn">\
|
||||
<button type="button" class="btn btn-danger btn-sm smtp_closeBtn">Close</button>\
|
||||
<button class="btn btn-success btn-sm CreaterReceive">Create</button>\
|
||||
</div>\
|
||||
</div>',
|
||||
success:function(layers,index){
|
||||
$(".CreaterReceive").click(function(){
|
||||
var _receive = $('input[name=creater_email_value]').val(),_that = this;
|
||||
if(_receive != ''){
|
||||
var loadT = layer.msg('Please wait while creating recipient list...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
layer.close(index)
|
||||
$.post('/config?action=add_mail_address',{email:_receive},function(rdata){
|
||||
layer.close(loadT);
|
||||
// 刷新收件列表
|
||||
get_receive_list();
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2});
|
||||
})
|
||||
}else{
|
||||
layer.msg('Recipient mailbox cannot be empty!',{icon:2});
|
||||
}
|
||||
})
|
||||
|
||||
$(".smtp_closeBtn").click(function(){
|
||||
layer.close(index)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
function get_receive_list(){
|
||||
$.post('/config?action=get_settings',function(rdata){
|
||||
var _html = '',_list = rdata.user_mail.mail_list;
|
||||
if(_list.length > 0){
|
||||
for(var i= 0; i<_list.length;i++){
|
||||
_html += '<tr>\
|
||||
<td>'+ _list[i] +'</td>\
|
||||
<td width="80px"><a onclick="del_email(\''+ _list[i] + '\')" href="javascript:;" style="color:#20a53a">Del</a></td>\
|
||||
</tr>'
|
||||
}
|
||||
}else{
|
||||
_html = '<tr>No Data</tr>'
|
||||
}
|
||||
$('#receive_table').html(_html);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function del_email(mail){
|
||||
var loadT = layer.msg('Deleting ['+ mail +'], please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }),_this = this;
|
||||
$.post('/config?action=del_mail_list',{email:mail},function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
_this.get_receive_list()
|
||||
})
|
||||
}
|
||||
// 设置钉钉
|
||||
function SetChannelDing(){
|
||||
var _url = $('textarea[name=channel_dingding_value]').val();
|
||||
var _all = $('#panel_alert_all').prop("checked");
|
||||
if(_url != ''){
|
||||
var loadT = layer.msg('Please wait while generating dingding channel...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_dingding',{url:_url,atall:_all == true? 'True':'False'},function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
})
|
||||
}else{
|
||||
layer.msg('Please enter the dingding URL',{icon:2})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+30
-300
@@ -147,29 +147,29 @@ function edit_task_info(id){
|
||||
}
|
||||
});
|
||||
}
|
||||
if(obj.from.notice_channel == 'dingding') {
|
||||
obj.sBody.title = '钉钉'
|
||||
if(obj.from.notice_channel == 'telegram') {
|
||||
obj.sBody.title = 'Telegram'
|
||||
}else if(obj.from.notice_channel == 'mail') {
|
||||
obj.sBody.title = 'Email'
|
||||
}else if(obj.from.notice_channel == 'dingding,mail') {
|
||||
}else if(obj.from.notice_channel == 'telegram,mail') {
|
||||
obj.sBody.title = 'All'
|
||||
} else {
|
||||
obj.sBody.title = 'No Data'
|
||||
}
|
||||
if(obj.from.sType == 'site' || obj.from.sType == 'database' || obj.from.sType == 'path') {
|
||||
$.post('/config?action=get_settings',{type: 'sites'},function(rdata){
|
||||
if(rdata.user_mail.user_name && rdata.dingding.dingding) {
|
||||
$.post('/config?action=get_settings2',{type: 'sites'},function(rdata){
|
||||
if(rdata.user_mail.user_name && rdata.telegram.setup) {
|
||||
obj.sBody.messageChannelBtnText = 'All'
|
||||
obj.sBody.channelInitVal= 'user_name,dingding'
|
||||
obj.sBody.messageChannelDom = '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="dingding,mail">All</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="dingding">钉钉</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="mail">邮箱</a></li>'
|
||||
} else if(!rdata.user_mail.user_name && !rdata.dingding.dingding){
|
||||
obj.sBody.channelInitVal= 'user_name,telegram'
|
||||
obj.sBody.messageChannelDom = '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="telegram,mail">All</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="telegram">Telegram</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="mail">Email</a></li>'
|
||||
} else if(!rdata.user_mail.user_name && !rdata.telegram.setup){
|
||||
obj.sBody.messageChannelBtnText = 'No Data'
|
||||
obj.sBody.channelInitVal= ''
|
||||
obj.sBody.messageChannelDom += '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="">No Data</a></li>'
|
||||
} else if(rdata.dingding.dingding) {
|
||||
obj.sBody.messageChannelBtnText = '钉钉'
|
||||
obj.sBody.channelInitVal= 'dingding'
|
||||
obj.sBody.messageChannelDom += '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="dingding">钉钉</a></li>'
|
||||
} else if(rdata.telegram.setup) {
|
||||
obj.sBody.messageChannelBtnText = 'Telegram'
|
||||
obj.sBody.channelInitVal= 'telegram'
|
||||
obj.sBody.messageChannelDom += '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="telegram">Telegram</a></li>'
|
||||
} else if(rdata.user_mail.user_name) {
|
||||
obj.sBody.messageChannelBtnText = 'Email'
|
||||
obj.sBody.channelInitVal= 'mail'
|
||||
@@ -465,7 +465,7 @@ function edit_task_info(id){
|
||||
}
|
||||
// 修改木马查杀 消息通道
|
||||
function edit_message_channel(type){
|
||||
$.post('/config?action=get_settings',function(res){
|
||||
$.post('/config?action=get_settings2',function(res){
|
||||
var tMess = "";
|
||||
if(res.user_mail.user_name && !res.dingding.dingding){
|
||||
tMess = '<div class="check_alert" style="margin-right:20px;display: inline-block;">\
|
||||
@@ -975,20 +975,20 @@ function toBackup(type){
|
||||
<span><input type="number" name="save" id="save" value="'+save_num+'" maxlength="4" max="100" min="1"></span>\
|
||||
</div>';
|
||||
if (type == 'sites' || type == 'path' || type == 'databases') {
|
||||
$.post('/config?action=get_settings',data,function(rdata){
|
||||
$.post('/config?action=get_settings2',data,function(rdata){
|
||||
var messageChannelDom = '', messageChannelBtnText = '', channelInitVal = ''
|
||||
if(rdata.user_mail.user_name && rdata.dingding.dingding) {
|
||||
if(rdata.user_mail.user_name && rdata.telegram.setup) {
|
||||
messageChannelBtnText = 'ALL'
|
||||
channelInitVal= 'user_name,dingding'
|
||||
messageChannelDom = '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="dingding,mail">ALL</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="dingding">钉钉</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="mail">邮箱</a></li>'
|
||||
} else if(!rdata.user_mail.user_name && !rdata.dingding.dingding){
|
||||
channelInitVal= 'user_name,telegram'
|
||||
messageChannelDom = '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="telegram,mail">ALL</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="telegram">Telegram</a></li><li><a role="menuitem" tabindex="-1" href="javascript:;" value="mail">Email</a></li>'
|
||||
} else if(!rdata.user_mail.user_name && !rdata.telegram.setup){
|
||||
messageChannelBtnText = 'No Data'
|
||||
channelInitVal= ''
|
||||
messageChannelDom += '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="">No Data</a></li>'
|
||||
} else if(rdata.dingding.dingding) {
|
||||
} else if(rdata.telegram.setup) {
|
||||
messageChannelBtnText = '钉钉'
|
||||
channelInitVal= 'dingding'
|
||||
messageChannelDom += '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="dingding">钉钉</a></li>'
|
||||
channelInitVal= 'telegram'
|
||||
messageChannelDom += '<li><a role="menuitem" tabindex="-1" href="javascript:;" value="telegram">Telegram</a></li>'
|
||||
} else if(rdata.user_mail.user_name) {
|
||||
messageChannelBtnText = 'Email'
|
||||
channelInitVal= 'mail'
|
||||
@@ -1016,7 +1016,7 @@ function toBackup(type){
|
||||
</ul>\
|
||||
</div>\
|
||||
</div>\
|
||||
<a role="menuitem" tabindex="-1" href="javascript:;" onclick="open_three_channel_auth()" value="0" style="color: #20a53a;">Set notifications</a>\
|
||||
<a role="menuitem" tabindex="-1" href="javascript:;" onclick="MessageChannelSettings()" value="0" style="color: #20a53a;">Set notifications</a>\
|
||||
<span id="selnoticeBox" onclick="selSave_local()" style="display:none;"><input type="checkbox" value="0" style="margin-left: 20px;margin-right: 10px;" id="save_local">Keep local backup</span>\
|
||||
</p>';
|
||||
if(type == 'sites' || type == "path") {
|
||||
@@ -1203,20 +1203,20 @@ function webShell(){
|
||||
})
|
||||
}
|
||||
function message_channel_start(){
|
||||
$.post('/config?action=get_settings',function(res){
|
||||
$.post('/config?action=get_settings2',function(res){
|
||||
var wBody = "";
|
||||
if(!res.user_mail.user_name && !res.dingding.dingding){
|
||||
if(!res.user_mail.user_name && !res.telegram.setup){
|
||||
wBody = '<span style="color:red;">No message channel is set, please go to panel settings to add a message channel configuration<a href="https://www.bt.cn/bbs/thread-42312-1-1.html" target="_blank" class="bt-ico-ask" style="cursor: pointer;">?</a></span>';
|
||||
$(".plan-submit").css({"pointer-events":"none","background-color":"#e6e6e6","color":"#333"});
|
||||
}else if(res.user_mail.user_name && !res.dingding.dingding){
|
||||
}else if(res.user_mail.user_name && !res.telegram.setup){
|
||||
wBody = '<div class="check_alert" style="margin-right:20px;display: inline-block;">\
|
||||
<input type="radio" name="alert" title="Email" value="mail" checked="">\
|
||||
<label style="font-weight: normal;font-size: 14px;margin-left: 6px;display: inline;">Email</label>\
|
||||
</div>'
|
||||
}else if(!res.user_mail.user_name && res.dingding.dingding){
|
||||
}else if(!res.user_mail.user_name && res.telegram.setup){
|
||||
wBody = '<div class="check_alert" style="display: inline-block;">\
|
||||
<input type="radio" name="alert" title="dingding" value="dingding" checked="">\
|
||||
<label style="font-weight: normal;font-size: 14px;margin-left: 6px;display: inline;">dingding</label>\
|
||||
<input type="radio" name="alert" title="telegram" value="telegram" checked="">\
|
||||
<label style="font-weight: normal;font-size: 14px;margin-left: 6px;display: inline;">telegram</label>\
|
||||
</div>'
|
||||
}else{
|
||||
wBody ='<div class="check_alert" style="margin-right:20px;display: inline-block;">\
|
||||
@@ -1224,8 +1224,8 @@ function message_channel_start(){
|
||||
<label style="font-weight: normal;font-size: 14px;margin-left: 6px;display: inline;">Email</label>\
|
||||
</div>\
|
||||
<div class="check_alert" style="display: inline-block;">\
|
||||
<input type="radio" name="alert" title="dingding" value="dingding">\
|
||||
<label style="font-weight: normal;font-size: 14px;margin-left: 6px;display: inline;">dingding</label>\
|
||||
<input type="radio" name="alert" title="telegram" value="telegram">\
|
||||
<label style="font-weight: normal;font-size: 14px;margin-left: 6px;display: inline;">telegram</label>\
|
||||
</div>'
|
||||
}
|
||||
$(".message_start").html(wBody);
|
||||
@@ -1253,259 +1253,6 @@ function fileupload(){
|
||||
$("#sFile").click();
|
||||
}
|
||||
|
||||
// 计划任务2021/3/24新增任务通知新增
|
||||
function open_three_channel_auth(){
|
||||
get_channel_settings(function(rdata){
|
||||
var isOpen = rdata.dingding.info.msg.isAtAll == 'True' ? 'checked': '';
|
||||
var isDing = rdata.dingding.info.msg == 'No Data'? '': rdata.dingding.info.msg.dingding_url;
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "600px",
|
||||
title: "Set notifications",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form">\
|
||||
<div class="bt-w-main">\
|
||||
<div class="bt-w-menu" style="width:130px">\
|
||||
<p class="bgw">Email</p>\
|
||||
</div>\
|
||||
<div class="bt-w-con pd15" style="margin-left:130px">\
|
||||
<div class="plugin_body">\
|
||||
<div class="conter_box active" >\
|
||||
<div class="bt-form">\
|
||||
<div class="line">\
|
||||
<button class="btn btn-success btn-sm" onclick="add_receive_info()">Add recipient</button>\
|
||||
<button class="btn btn-default btn-sm" onclick="sender_info_edit()">Sender settings</button>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<div class="divtable">\
|
||||
<table class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0"><thead><tr><th>Email</th><th width="80px">OPT</th></tr></thead></table>\
|
||||
<table class="table table-hover"><tbody id="receive_table"></tbody></table>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="conter_box" style="display:none">\
|
||||
<div class="bt-form">\
|
||||
<div class="line">\
|
||||
<span class="tname">Notify all</span>\
|
||||
<div class="info-r" style="height:28px; margin-left:100px">\
|
||||
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" '+ isOpen+'>\
|
||||
<label style="position: relative;top: 5px;" class="btswitch-btn" for="panel_alert_all"></label>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>'
|
||||
})
|
||||
$(".bt-w-menu p").click(function () {
|
||||
var index = $(this).index();
|
||||
$(this).addClass('bgw').siblings().removeClass('bgw');
|
||||
$('.conter_box').eq(index).show().siblings().hide();
|
||||
});
|
||||
get_receive_list();
|
||||
})
|
||||
}
|
||||
function add_receive_info(){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "400px",
|
||||
title: "Add recipient email",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 pb70">\
|
||||
<div class="line">\
|
||||
<span class="tname">Recipient mailbox\n</span>\
|
||||
<div class="info-r">\
|
||||
<input name="creater_email_value" class="bt-input-text mr5" type="text" style="width: 220px" value="">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="bt-form-submit-btn">\
|
||||
<button type="button" class="btn btn-danger btn-sm smtp_closeBtn">Close</button>\
|
||||
<button class="btn btn-success btn-sm CreaterReceive">Create</button>\
|
||||
</div>\
|
||||
</div>',
|
||||
success:function(layers,index){
|
||||
$(".CreaterReceive").click(function(){
|
||||
var _receive = $('input[name=creater_email_value]').val(),_that = this;
|
||||
if(_receive != ''){
|
||||
var loadT = layer.msg('Creating recipient list, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
layer.close(index)
|
||||
$.post('/config?action=add_mail_address',{email:_receive},function(rdata){
|
||||
layer.close(loadT);
|
||||
// 刷新收件列表
|
||||
get_receive_list();
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2});
|
||||
})
|
||||
}else{
|
||||
layer.msg('The recipient mailbox cannot be empty!',{icon:2});
|
||||
}
|
||||
})
|
||||
|
||||
$(".smtp_closeBtn").click(function(){
|
||||
layer.close(index)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function sender_info_edit(){
|
||||
var loadT = layer.msg('Fetching configuration, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=get_settings',function(rdata){
|
||||
layer.close(loadT);
|
||||
var qq_mail = rdata.user_mail.info.msg.qq_mail == undefined ? '' : rdata.user_mail.info.msg.qq_mail,
|
||||
qq_stmp_pwd = rdata.user_mail.info.msg.qq_stmp_pwd == undefined? '' : rdata.user_mail.info.msg.qq_stmp_pwd,
|
||||
hosts = rdata.user_mail.info.msg.hosts == undefined? '' : rdata.user_mail.info.msg.hosts,
|
||||
port = rdata.user_mail.info.msg.port == undefined? '' : rdata.user_mail.info.msg.port
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "480px",
|
||||
title: "Set sender email information",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
btn:['Save','Close'],
|
||||
content: '<div class="bt-form pd20">\
|
||||
<div class="line">\
|
||||
<span class="tname">Sender email</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_value" class="bt-input-text mr5" type="text" style="width: 280px" value="'+qq_mail+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">smtp password</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_password" class="bt-input-text mr5" type="password" style="width: 280px" value="'+qq_stmp_pwd+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">smtp server</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_server" class="bt-input-text mr5" type="text" style="width: 280px" value="'+hosts+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">smtp port</span>\
|
||||
<div class="info-r">\
|
||||
<select class="bt-input-text mr5" id="port_select" style="width:280px"></select>\
|
||||
<input name="channel_email_port" class="bt-input-text mr5 mt5" type="Number" style="display:'+(select_port(port)? 'none':'inline-block')+'; width: 280px;margin-top:15px;" value="'+port+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<ul class="help-info-text c7" style="margin-left:15px">\
|
||||
<li>465 port is recommended, the protocol is SSL/TLS</li>\
|
||||
<li>Port 25 is SMTP protocol, port 587 is STARTTLS protocol</li>\
|
||||
</ul></div>',
|
||||
success:function(layers,index){
|
||||
var _option = '';
|
||||
if(select_port(port)){
|
||||
if(port == '465' || port == ''){
|
||||
_option = '<option value="465" selected="selected">465</option><option value="25">25</option><option value="587">587</option><option value="other">Customize</option>'
|
||||
}else if(port == '25'){
|
||||
_option = '<option value="465">465</option><option value="25" selected="selected">25</option><option value="587">587</option><option value="other">Customize</option>'
|
||||
}else{
|
||||
_option = '<option value="465">465</option><option value="25">25</option><option value="587" selected="selected">587</option><option value="other">Customize</option>'
|
||||
}
|
||||
}else{
|
||||
_option = '<option value="465">465</option><option value="25">25</option><option value="587" >587</option><option value="other" selected="selected">Customize</option>'
|
||||
}
|
||||
console.log(port)
|
||||
$("#port_select").html(_option)
|
||||
$("#port_select").change(function(e){
|
||||
if(e.target.value == 'other'){
|
||||
// $("#port_select").css("width","100px");
|
||||
$('input[name=channel_email_port]').css("display","inline-block");
|
||||
}else{
|
||||
// $("#port_select").css("width","300px");
|
||||
$('input[name=channel_email_port]').css("display","none");
|
||||
}
|
||||
})
|
||||
},
|
||||
yes:function(){
|
||||
var _email = $('input[name=channel_email_value]').val();
|
||||
var _passW = $('input[name=channel_email_password]').val();
|
||||
var _server = $('input[name=channel_email_server]').val(),_port
|
||||
if($('#port_select').val() == 'other'){
|
||||
_port = $('input[name=channel_email_port]').val();
|
||||
}else{
|
||||
_port = $('#port_select').val()
|
||||
}
|
||||
if(_email == ''){
|
||||
return layer.msg('Email address cannot be empty!',{icon:2});
|
||||
}else if(_passW == ''){
|
||||
return layer.msg('STMP password cannot be empty!',{icon:2});
|
||||
}else if(_server == ''){
|
||||
return layer.msg('STMP server address cannot be empty!',{icon:2})
|
||||
}else if(_port == ''){
|
||||
return layer.msg('STMP server port cannot be empty!',{icon:2})
|
||||
}
|
||||
var loadT = layer.msg('The notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=user_mail_send',{email:_email,stmp_pwd:_passW,hosts:_server,port:_port},function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
if(rdata.status){
|
||||
layer.close(index)
|
||||
get_channel_settings();
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function get_channel_settings(callback){
|
||||
var loadT = layer.msg('Fetching configuration, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=get_settings',function(rdata){
|
||||
layer.close(loadT);
|
||||
if (callback) callback(rdata);
|
||||
})
|
||||
}
|
||||
|
||||
function get_receive_list(){
|
||||
$.post('/config?action=get_settings',function(rdata){
|
||||
var _html = '',_list = rdata.user_mail.mail_list;
|
||||
if(_list.length > 0){
|
||||
for(var i= 0; i<_list.length;i++){
|
||||
_html += '<tr>\
|
||||
<td>'+ _list[i] +'</td>\
|
||||
<td width="80px"><a onclick="del_email(\''+ _list[i] + '\')" href="javascript:;" style="color:#20a53a">Del</a></td>\
|
||||
</tr>'
|
||||
}
|
||||
}else{
|
||||
_html = '<tr>No data</tr>'
|
||||
}
|
||||
$('#receive_table').html(_html);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function del_email(mail){
|
||||
var loadT = layer.msg('Deleting ['+mail+'], please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }),_this = this;
|
||||
$.post('/config?action=del_mail_list',{email:mail},function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
_this.get_receive_list()
|
||||
})
|
||||
}
|
||||
|
||||
// // 设置钉钉
|
||||
// function SetChannelDing(){
|
||||
// var _url = $('textarea[name=channel_dingding_value]').val();
|
||||
// var _all = $('#panel_alert_all').prop("checked");
|
||||
// if(_url != ''){
|
||||
// var loadT = layer.msg('正在生成钉钉通道中,请稍候...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
// $.post('/config?action=set_dingding',{url:_url,atall:_all == true? 'True':'False'},function(rdata){
|
||||
// layer.close(loadT);
|
||||
// layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
// })
|
||||
// }else{
|
||||
// layer.msg('请输入钉钉url',{icon:2})
|
||||
// }
|
||||
// }
|
||||
|
||||
function selSave_local() {
|
||||
if($('#save_local').val() == '0') {
|
||||
@@ -1547,20 +1294,3 @@ function getselectnoticename(){
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function select_port(port){
|
||||
switch(port){
|
||||
case '25':
|
||||
return true;
|
||||
case '465':
|
||||
return true;
|
||||
case '587':
|
||||
return true;
|
||||
case '':
|
||||
return true;
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// --计划任务2021/3/24新增任务通知新增结束
|
||||
+366
-116
@@ -1,71 +1,240 @@
|
||||
|
||||
var database_table = {}
|
||||
var database = {
|
||||
get_list: function (page, search) {
|
||||
if (page == undefined) page = 1;
|
||||
if (!search) search = $("#SearchValue").val();
|
||||
bt.database.get_list(page, search, function (rdata) {
|
||||
$('#databasePage').html(rdata.page);
|
||||
var _tab = bt.render({
|
||||
table: '#DataBody',
|
||||
columns: [
|
||||
{ field: 'id', type: 'checkbox', width: 30 },
|
||||
{
|
||||
field: 'name', title: lan.database.add_name, width: '20%'
|
||||
init: function () {
|
||||
this.database_table_view();
|
||||
var _this = this;
|
||||
$('#SearchValue').keydown(function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
var val = $(this).val();
|
||||
_this.database_table_view(val);
|
||||
}
|
||||
});
|
||||
},
|
||||
database_table_view:function(search){
|
||||
$('#bt_database_table').empty();
|
||||
database_table = bt_tools.table({
|
||||
el: '#bt_database_table',
|
||||
url: '/data?action=getData',
|
||||
param: {
|
||||
table: 'databases',
|
||||
search:search|| ''
|
||||
}, //参数
|
||||
minWidth: '1000px',
|
||||
default: "Database list is empty", // 数据为空时的默认提示
|
||||
column:[
|
||||
{ fid: 'id', type: 'checkbox', width: 30 },
|
||||
{
|
||||
fid: 'name', title: lan.database.add_name, width: '20%'
|
||||
},
|
||||
{
|
||||
fid: 'username', title: lan.database.user, sort: function () {
|
||||
database_table.$refresh_table_list(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
fid:'password',
|
||||
title:lan.database.add_pass,
|
||||
type:'password',
|
||||
copy:true,
|
||||
eye_open:true
|
||||
},
|
||||
{
|
||||
fid:'backup',
|
||||
title: lan.database.backup,
|
||||
width: 130,
|
||||
template: function (item) {
|
||||
var backup = lan.database.backup_empty,
|
||||
_class = "bt_warning";
|
||||
if (item.backup_count > 0) backup = lan.database.backup_ok, _class = "bt_success";
|
||||
return '<span><a href="javascript:;" class="btlink ' + _class + '" onclick="database.database_detail('+ item.id+',\''+item.name+'\')">' + backup + (item.backup_count > 0 ? ('(' + item.backup_count + ')') : '') + '</a> | ' +
|
||||
'<a href="javascript:database.input_database(\''+item.name+'\')" class="btlink">'+lan.database.input+'</a></span>';
|
||||
}
|
||||
},
|
||||
// {
|
||||
// fid: 'ps', title: lan.database.add_ps, templet: function (item) {
|
||||
// var _ps = "<span class='c9 input-edit webNote' onclick=\"bt.pub.set_data_by_key('databases','ps',this)\" >"
|
||||
// if (item.password) {
|
||||
// _ps += item.ps
|
||||
// } else {
|
||||
// _ps += lan.database.cant_get_pass+'<span style="color:red">'+lan.database.edit_pass+'</span>'+lan.database.button_set_pass+'!';
|
||||
// }
|
||||
// _ps += "</span>";
|
||||
// return _ps;
|
||||
// }
|
||||
// },
|
||||
{
|
||||
fid: 'ps',
|
||||
title: lan.database.add_ps,
|
||||
type: 'input',
|
||||
blur: function (row, index, ev) {
|
||||
bt.pub.set_data_ps({
|
||||
id: row.id,
|
||||
table: 'databases',
|
||||
ps: ev.target.value
|
||||
}, function (res) {
|
||||
layer.msg(res.msg, (res.status ? {} : {
|
||||
icon: 2
|
||||
}));
|
||||
});
|
||||
},
|
||||
{
|
||||
field: 'username', title: lan.database.user, sort: function () {
|
||||
database.get_list();
|
||||
keyup: function (row, index, ev) {
|
||||
if (ev.keyCode === 13) {
|
||||
$(this).blur();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
title: lan.database.operation,
|
||||
width: 280,
|
||||
align: 'right',
|
||||
group: [{
|
||||
title: lan.database.admin,
|
||||
tips: lan.database.admin_title,
|
||||
event: function(row) {
|
||||
bt.database.open_phpmyadmin(row.name,row.username,row.password);
|
||||
}
|
||||
},{
|
||||
title: lan.database.auth,
|
||||
tips:lan.database.set_db_auth,
|
||||
event: function(row) {
|
||||
bt.database.set_data_access(row.username);
|
||||
}
|
||||
},{
|
||||
title:lan.database.tools,
|
||||
tips:lan.database.mysql_tools,
|
||||
event: function(row){
|
||||
database.rep_tools(row.name);
|
||||
}
|
||||
},{
|
||||
title:lan.database.edit_pass,
|
||||
tips:lan.database.edit_pass_title,
|
||||
event: function(row){
|
||||
database.set_data_pass(row.id,row.username,row.password);
|
||||
}
|
||||
},{
|
||||
title:lan.database.del,
|
||||
tips:lan.database.del_title,
|
||||
event: function(row){
|
||||
database.del_database(row.id,row.name);
|
||||
}
|
||||
}]
|
||||
}
|
||||
],
|
||||
sortParam: function (data) {
|
||||
return {
|
||||
'order': data.name + ' ' + data.sort
|
||||
};
|
||||
},
|
||||
tootls: [{ // 按钮组
|
||||
type: 'group',
|
||||
positon: ['left', 'top'],
|
||||
list: [{
|
||||
title: lan.database.add_title,
|
||||
active: true,
|
||||
event: function () {
|
||||
bt.database.add_database(function (res){
|
||||
if(res.status) database_table.$refresh_table_list(true);
|
||||
})
|
||||
}
|
||||
},{
|
||||
title: lan.database.edit_root,
|
||||
event: function () {
|
||||
bt.database.set_root('root')
|
||||
}
|
||||
},{
|
||||
title: 'phpMyAdmin',
|
||||
event: function () {
|
||||
bt.database.open_phpmyadmin('','root', bt.config.mysql_root)
|
||||
}
|
||||
},{
|
||||
title: 'Sync all',
|
||||
style: {'margin-left':'30px'},
|
||||
event: function () {
|
||||
database.sync_to_database(1)
|
||||
}
|
||||
},{
|
||||
title: 'Get DB from server',
|
||||
event: function () {
|
||||
database.sync_to_database(1)
|
||||
}
|
||||
}]
|
||||
},{
|
||||
type: 'batch', //batch_btn
|
||||
positon: ['left', 'bottom'],
|
||||
placeholder: 'Select batch operation',
|
||||
buttonValue: 'Execute',
|
||||
disabledSelectValue: 'Select the website to execute!!',
|
||||
selectList: [{
|
||||
title:'Sync to Server',
|
||||
url:'/database?action=SyncToDatabases&type=1',
|
||||
paramName: 'ids', //列表参数名,可以为空
|
||||
paramId: 'id', // 需要传入批量的id
|
||||
th:'Database Name',
|
||||
beforeRequest: function(list) {
|
||||
var arry = [];
|
||||
$.each(list, function (index, item) {
|
||||
arry.push(item.id);
|
||||
});
|
||||
return JSON.stringify(arry)
|
||||
},
|
||||
success: function (res, list, that) {
|
||||
layer.closeAll();
|
||||
var html = '';
|
||||
$.each(list, function (index, item) {
|
||||
html += '<tr><td>' + item.name + '</td><td><div style="float:right;"><span style="color:' + (res.status ? '#20a53a' : 'red') + '">' + res.msg + '</span></div></td></tr>';
|
||||
});
|
||||
that.$batch_success_table({
|
||||
title: 'Batch sync selected',
|
||||
th: 'Database Name',
|
||||
html: html
|
||||
});
|
||||
}
|
||||
},{
|
||||
title: "Delete database",
|
||||
url: '/database?action=DeleteDatabase',
|
||||
load: true,
|
||||
param: function (row) {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'password', title: lan.database.add_pass, templet: function (item) {
|
||||
var _html = '<span class="dataBase"><span class="password" data-pw="' + item.password + '">**********</span>';
|
||||
_html += '<span onclick="bt.pub.show_hide_pass(this)" class="glyphicon glyphicon-eye-open cursor pw-ico" style="margin-left:10px"></span>';
|
||||
_html += '<span class="ico-copy cursor btcopy" style="margin-left:10px" title="'+lan.database.copy_pass+'" data-pw="' + item.password + '" onclick="bt.pub.copy_pass(\'' + item.password + '\')"></span></span>';
|
||||
return _html;
|
||||
callback: function (that) { // 手动执行,data参数包含所有选中的站点
|
||||
var ids = [];
|
||||
for (var i = 0; i < that.check_list.length; i++) {
|
||||
ids.push(that.check_list[i].id);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'backup', title: lan.database.backup, templet: function (item) {
|
||||
var backup = '<span class="dataBase">';
|
||||
var _msg = lan.database.backup_empty;
|
||||
if (item.backup_count > 0) _msg = lan.database.backup_ok;
|
||||
backup += "<a href='javascript:;' class='btlink' onclick=\"database.database_detail('" + item.id + "','" + item.name + "')\">" + _msg + "</a> | "
|
||||
backup += "<a class='btlink' href=\"javascript:database.input_database('" + item.name + "');\" title='" + lan.database.input_title + "'>" + lan.database.input + "</a>";
|
||||
backup += '</span>';
|
||||
return backup;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'ps', title: lan.database.add_ps, templet: function (item) {
|
||||
var _ps = "<span class='c9 input-edit webNote' onclick=\"bt.pub.set_data_by_key('databases','ps',this)\" >"
|
||||
if (item.password) {
|
||||
_ps += item.ps
|
||||
} else {
|
||||
_ps += lan.database.cant_get_pass+'<span style="color:red">'+lan.database.edit_pass+'</span>'+lan.database.button_set_pass+'!';
|
||||
}
|
||||
_ps += "</span>";
|
||||
return _ps;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'opt', title: lan.database.operation, align: 'right', templet: function (item) {
|
||||
var option = "<span class=\"dataBase\"><a href=\"javascript:;\" class=\"btlink\" onclick=\"bt.database.open_phpmyadmin('" + item.name + "','" + item.username + "','" + item.password + "')\" title=\""+lan.database.admin_title+"\">"+lan.database.admin+"</a> | ";
|
||||
option += "<a href=\"javascript:;\" class=\"btlink\" onclick=\"database.rep_tools('" + item.name + "')\" title=\""+lan.database.mysql_tools+"\">"+lan.database.tools+"</a> | ";
|
||||
option += "<a href=\"javascript:;\" class=\"btlink\" onclick=\"bt.database.set_data_access('" + item.username + "')\" title=\""+lan.database.set_db_auth+"\">"+lan.database.auth+"</a> | ";
|
||||
option += "<a href=\"javascript:;\" class=\"btlink\" onclick=\"database.set_data_pass(" + item.id + ",'" + item.username + "','" + item.password + "')\" title=\""+lan.database.edit_pass_title+"\">"+lan.database.edit_pass+"</a> | ";
|
||||
option += "<a href=\"javascript:;\" class=\"btlink\" onclick=\"database.del_database(" + item.id + ",'" + item.name + "')\" title=\""+lan.database.del_title+"\">"+lan.database.del+"</a></span>";
|
||||
return option;
|
||||
}
|
||||
},
|
||||
],
|
||||
data: rdata.data
|
||||
});
|
||||
$(window).resize(function() {
|
||||
database.forSize();
|
||||
});
|
||||
database.forSize();
|
||||
})
|
||||
database.del_database(ids,function(param){
|
||||
that.start_batch(param, function (list) {
|
||||
layer.closeAll()
|
||||
var html = '';
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var item = list[i];
|
||||
html += '<tr><td>' + item.name + '</td><td><div style="float:right;"><span style="color:' + (item.request.status ? '#20a53a' : 'red') + '">' + item.request.msg + '</span></div></td></tr>';
|
||||
}
|
||||
database_table.$batch_success_table({
|
||||
title: 'Batch deletion',
|
||||
th: 'Database Name',
|
||||
html: html
|
||||
});
|
||||
});
|
||||
database_table.$refresh_table_list(true);
|
||||
})
|
||||
}
|
||||
}]
|
||||
}, { //分页显示
|
||||
type: 'page',
|
||||
positon: ['right', 'bottom'], // 默认在右下角
|
||||
pageParam: 'p', //分页请求字段,默认为 : p
|
||||
page: 1, //当前分页 默认:1
|
||||
numberParam: 'limit', //分页数量请求字段默认为 : limit
|
||||
number: 20, //分页数量默认 : 20条
|
||||
numberList: [10, 20, 50, 100, 200], // 分页显示数量列表
|
||||
numberStatus: true, // 是否支持分页数量选择,默认禁用
|
||||
jump: true, //是否支持跳转分页,默认禁用
|
||||
}]
|
||||
});
|
||||
},
|
||||
rep_tools: function (db_name, res) {
|
||||
var loadT = layer.msg(lan.database.get_data, { icon: 16, time: 0 });
|
||||
@@ -75,7 +244,7 @@ var database = {
|
||||
layer.msg(rdata.msg, { icon: 2 });
|
||||
return;
|
||||
}
|
||||
var types = { InnoDB: "MyISAM", MyISAM: "InnoDB" };
|
||||
var types = { InnoDB: "InnoDB", MyISAM: "MyISAM" };
|
||||
var tbody = '';
|
||||
for (var i = 0; i < rdata.tables.length; i++) {
|
||||
if (!types[rdata.tables[i].type]) continue;
|
||||
@@ -218,17 +387,17 @@ var database = {
|
||||
if (!isNaN($(this).val())) data.push($(this).val());
|
||||
});
|
||||
bt.database.sync_to_database({ type: type, ids: JSON.stringify(data) }, function (rdata) {
|
||||
if (rdata.status) database.get_list();
|
||||
if (rdata.status) database_table.$refresh_table_list(true);
|
||||
});
|
||||
},
|
||||
sync_database: function () {
|
||||
bt.database.sync_database(function (rdata) {
|
||||
if (rdata.status) database.get_list();
|
||||
if (rdata.status) database_table.$refresh_table_list(true);
|
||||
})
|
||||
},
|
||||
add_database: function () {
|
||||
bt.database.add_database(function (rdata) {
|
||||
if (rdata.status) database.get_list();
|
||||
if (rdata.status) database_table.$refresh_table_list(true);
|
||||
})
|
||||
},
|
||||
batch_database: function (type, arr, result) {
|
||||
@@ -249,7 +418,7 @@ var database = {
|
||||
switch (type) {
|
||||
case 'del':
|
||||
if (arr.length < 1) {
|
||||
database.get_list();
|
||||
database_table.$refresh_table_list(true);
|
||||
bt.msg({ msg: lan.get('del_all_database_ok', [result.count]), icon: 1, time: 5000 });
|
||||
return;
|
||||
}
|
||||
@@ -265,17 +434,130 @@ var database = {
|
||||
break;
|
||||
}
|
||||
},
|
||||
del_database: function (id, name) {
|
||||
bt.show_confirm(lan.get('del', [name]), lan.get('confirm_del', [name]), function () {
|
||||
bt.database.del_database({ id: id, name: name }, function (rdata) {
|
||||
if (rdata.status) database.get_list();
|
||||
bt.msg(rdata);
|
||||
})
|
||||
});
|
||||
del_database: function (wid, dbname, callback) {
|
||||
var rendom = bt.get_random_code(),num1 = rendom['num1'],num2 = rendom['num2'],title = '';
|
||||
title = typeof dbname === "function" ?'Batch delete databases':'Delete database [ '+ dbname +' ]';
|
||||
layer.open({
|
||||
type:1,
|
||||
title:title,
|
||||
icon:0,
|
||||
skin:'delete_site_layer',
|
||||
area: "530px",
|
||||
closeBtn: 2,
|
||||
shadeClose: true,
|
||||
content:"<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
|
||||
"<i class=\'layui-layer-ico layui-layer-ico0\'></i>" +
|
||||
"<div class=\'f13 check_title\' style=\'margin-bottom: 20px;\'>The deletion may affect the business!</div>" +
|
||||
"<div style=\'color:red;margin:18px 0 18px 18px;font-size:14px;font-weight: bold;\'>Note: The data is priceless, please operate with caution! ! !"+(!recycle_bin_db_open?'<br>Risk: The database recycle bin is not enabled, deleting the database will disappear forever!':'')+"</div>" +
|
||||
"<div class=\'vcode\'>" + lan.bt.cal_msg + "<span class=\'text\'>"+ num1 +" + "+ num2 +"</span>=<input type=\'number\' id=\'vcodeResult\' value=\'\'></div>" +
|
||||
"</div>",
|
||||
btn:[lan.public.ok,lan.public.cancel],
|
||||
yes:function(indexs){
|
||||
var vcodeResult = $('#vcodeResult'),data = {id: wid,name: dbname};
|
||||
if(vcodeResult.val() === ''){
|
||||
layer.tips('Calculation result cannot be empty', vcodeResult, {tips: [1, 'red'],time:3000})
|
||||
vcodeResult.focus()
|
||||
return false;
|
||||
}else if(parseInt(vcodeResult.val()) !== (num1 + num2)){
|
||||
layer.tips('Incorrect calculation result', vcodeResult, {tips: [1, 'red'],time:3000})
|
||||
vcodeResult.focus()
|
||||
return false;
|
||||
}
|
||||
if(typeof dbname === "function"){
|
||||
delete data.id;
|
||||
delete data.name;
|
||||
}
|
||||
layer.close(indexs)
|
||||
var arrs = wid instanceof Array ? wid : [wid]
|
||||
var ids = JSON.stringify(arrs), countDown = 9;
|
||||
if (arrs.length == 1) countDown = 4
|
||||
title = typeof dbname === "function" ?'Confirm the information again, delete the database in batches':'Confirm the information again, Delete Database [ ' + dbname + ' ]';
|
||||
var loadT = bt.load('Checking database data information, please wait...')
|
||||
bt.send('check_del_data', 'database/check_del_data', {ids: ids}, function (res) {
|
||||
loadT.close()
|
||||
layer.open({
|
||||
type:1,
|
||||
title:title,
|
||||
closeBtn: 2,
|
||||
skin: 'verify_site_layer_info active',
|
||||
area: '740px',
|
||||
content: '<div class="check_delete_site_main pd30">' +
|
||||
'<i class="layui-layer-ico layui-layer-ico0"></i>' +
|
||||
'<div class="check_layer_title">aaPanel kindly reminds you, please calm down for a few seconds, and then confirm whether you want to delete the data.</div>' +
|
||||
'<div class="check_layer_content">' +
|
||||
'<div class="check_layer_item">' +
|
||||
'<div class="check_layer_site"></div>' +
|
||||
'<div class="check_layer_database"></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="check_layer_error ' + (recycle_bin_db_open ? 'hide' : '') + '"><span class="glyphicon glyphicon-info-sign"></span>Risk: The database recycle bin is not enabled. After the database is deleted, the database will disappear forever!</div>' +
|
||||
'<div class="check_layer_message">Please read the above information to be deleted carefully to prevent the database from being deleted by mistake. Confirm the deletion and there is still <span style="color:red;font-weight: bold;">' + countDown + '</span> seconds to operate.</div>' +
|
||||
'</div>',
|
||||
btn: ['Delete (Can be operated after ' + countDown + ' seconds)', 'Cancel'],
|
||||
success: function (layers) {
|
||||
var html = '', rdata = res.data;
|
||||
var filterData = rdata.filter(function(el){
|
||||
return ids.indexOf(el.id) != -1
|
||||
})
|
||||
for (var i = 0; i < filterData.length; i++) {
|
||||
var item = filterData[i], newTime = parseInt(new Date().getTime() / 1000),
|
||||
t_icon = '<span class="glyphicon glyphicon-info-sign" style="color: red;width:15px;height: 15px;;vertical-align: middle;"></span>';
|
||||
|
||||
database_html = (function(item){
|
||||
var is_time_rule = (newTime - item.st_time) > (86400 * 30) && (item.total > 1024 * 10),
|
||||
is_database_rule = res.db_size <= item.total,
|
||||
database_time = bt.format_data(item.st_time, 'yyyy-MM-dd'),
|
||||
database_size = bt.format_size(item.total);
|
||||
|
||||
var f_size = '<i ' + (is_database_rule ? 'class="warning"' : '') + ' style = "vertical-align: middle;" > ' + database_size + '</i> ' + (is_database_rule ? t_icon : '');
|
||||
var t_size = 'Note: This database is large and may be important data. Please operate with caution.\nDatabase: ' + database_size;
|
||||
|
||||
return '<div class="check_layer_database">' +
|
||||
'<span title="Database: ' + item.name + '">Database: ' + item.name + '</span>' +
|
||||
'<span '+ (item.total > 0 ? 'title="' + t_size+'"' : '')+'>Size: ' + f_size +'</span>' +
|
||||
'<span title="' + (is_time_rule && item.total != 0 ? 'Important: This database was created earlier and may be important data. Please operate with caution.' : '') + 'Time:' + database_time+'">Ctime:<i ' + (is_time_rule && item.total != 0 ? 'class="warning"' : '') + '>' + database_time + '</i></span>' +
|
||||
'</div>'
|
||||
}(item))
|
||||
if(database_html !== '') html += '<div class="check_layer_item">' + database_html +'</div>';
|
||||
}
|
||||
if(html === '') html = '<div style="text-align: center;width: 100%;height: 100%;line-height: 300px;font-size: 15px;">No data</div>'
|
||||
$('.check_layer_content').html(html)
|
||||
var interVal = setInterval(function () {
|
||||
countDown--;
|
||||
$(layers).find('.layui-layer-btn0').text('Delete (Can be operated after ' + countDown + ' seconds)')
|
||||
$(layers).find('.check_layer_message span').text(countDown)
|
||||
}, 1000);
|
||||
setTimeout(function () {
|
||||
$(layers).find('.layui-layer-btn0').text('Delete');
|
||||
$(layers).find('.check_layer_message').html('<span style="color:red">Note: Please read carefully the above information to be deleted to prevent the database from being deleted by mistake</span>')
|
||||
$(layers).removeClass('active');
|
||||
clearInterval(interVal)
|
||||
}, countDown * 1000)
|
||||
},
|
||||
yes:function(indes,layers){
|
||||
if($(layers).hasClass('active')){
|
||||
layer.tips('Please confirm the message, there are '+ countDown +' seconds left', $(layers).find('.layui-layer-btn0') , {tips: [1, 'red'],time:3000})
|
||||
return;
|
||||
}
|
||||
if(typeof dbname === "function"){
|
||||
dbname(data)
|
||||
}else{
|
||||
bt.database.del_database(data, function (rdata) {
|
||||
layer.closeAll()
|
||||
if(rdata.status) database_table.$refresh_table_list(true);
|
||||
if (callback) callback(rdata);
|
||||
bt.msg(rdata);
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
set_data_pass: function (id, username, password) {
|
||||
var bs = bt.database.set_data_pass(function (rdata) {
|
||||
if (rdata.status) database.get_list();
|
||||
if(rdata.status) database_table.$refresh_table_list(true);
|
||||
bt.msg(rdata);
|
||||
})
|
||||
$('.name' + bs).val(username);
|
||||
@@ -333,7 +615,7 @@ var database = {
|
||||
$('#btn_data_backup').unbind('click').click(function () {
|
||||
bt.database.backup_data(id, dataname, function (rdata) {
|
||||
if (rdata.status) database.database_detail(id, dataname);
|
||||
database.get_list();
|
||||
if(rdata.status) database_table.$refresh_table_list(true);
|
||||
if (!rdata.status) layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
})
|
||||
})
|
||||
@@ -345,39 +627,6 @@ var database = {
|
||||
bt_upload_file.open(path, '.sql,.gz,.tar.gz,.zip', lan.database.input_up_type, function () {
|
||||
database.input_database(name);
|
||||
});
|
||||
|
||||
/*
|
||||
var index = layer.open({
|
||||
type: 1,
|
||||
closeBtn: 2,
|
||||
title: lan.files.up_title + ' --- <span style="color:red;">' + lan.database.input_up_type + '</span>',
|
||||
area: ['500px', '500px'],
|
||||
shadeClose: false,
|
||||
content: '<div class="fileUploadDiv"><input type="hidden" id="input-val" value="' + path + '" />\
|
||||
<input type="file" id="file_input" multiple="true" autocomplete="off" />\
|
||||
<button type="button" id="opt" autocomplete="off">'+ lan.files.up_add + '</button>\
|
||||
<button type="button" id="up" autocomplete="off" >'+ lan.files.up_start + '</button>\
|
||||
<span id="totalProgress" style="position: absolute;top: 7px;right: 147px;"></span>\
|
||||
<span style="float:right;margin-top: 9px;">\
|
||||
<font>'+ lan.files.up_coding + ':</font>\
|
||||
<select id="fileCodeing" >\
|
||||
<option value="byte">'+ lan.files.up_bin + '</option>\
|
||||
<option value="utf-8">UTF-8</option>\
|
||||
<option value="gb18030">GB2312</option>\
|
||||
</select>\
|
||||
</span>\
|
||||
<button type="button" id="filesClose" autocomplete="off">'+ lan.public.close + '</button>\
|
||||
<ul id="up_box"></ul></div>'
|
||||
, end: function () {
|
||||
database.input_database(name);
|
||||
}
|
||||
});
|
||||
$("#filesClose").click(function () {
|
||||
layer.close(index);
|
||||
database.input_database(name);
|
||||
});
|
||||
UploadStart(true);
|
||||
*/
|
||||
},
|
||||
input_database: function (name) {
|
||||
var path = bt.get_cookie('backup_path') + "/database";
|
||||
@@ -400,7 +649,7 @@ var database = {
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="pd15"><button class="btn btn-default btn-sm" onclick="database.upload_files(\'' + name + '\')">' + lan.database.input_local_up + '</button><div class="divtable mtb15" style="max-height:300px; overflow:auto">'
|
||||
+ '<table id="DataInputList" class="table table-hover"></table>'
|
||||
+ '<table id="DataInputList" class="table table-hover databaseImportTable" style="table-layout: fixed;"></table>'
|
||||
+ '</div>'
|
||||
+ bt.render_help([lan.database.input_ps1, lan.database.input_ps2, (bt.os != 'Linux' ? lan.database.input_ps3.replace(/\/www.*\/database/, path) : lan.database.input_ps3)])
|
||||
+ '</div>'
|
||||
@@ -410,19 +659,19 @@ var database = {
|
||||
var _tab = bt.render({
|
||||
table: '#DataInputList',
|
||||
columns: [
|
||||
{ field: 'name', title: lan.files.file_name },
|
||||
{ field: 'name', title: lan.files.file_name, width:190 },
|
||||
{
|
||||
field: 'etime', title: lan.files.file_etime, templet: function (item) {
|
||||
field: 'etime', title: lan.files.file_etime, width:130, templet: function (item) {
|
||||
return bt.format_data(item.etime);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'size', title: lan.files.file_size, templet: function (item) {
|
||||
field: 'size', title: lan.files.file_size, width:70, templet: function (item) {
|
||||
return bt.format_size(item.size)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'opt', title: 'Operating', align: 'right', templet: function (item) {
|
||||
field: 'opt', title: 'Operating', align: 'right', width:90, templet: function (item) {
|
||||
return '<a class="btlink" herf="javascrpit:;" onclick="bt.database.input_sql(\'' + bt.rtrim(rdata.PATH, '/') + "/" + item.name + '\',\'' + name + '\')">'+lan.database.input+'</a> | <a class="btlink" onclick="database.remove_input_file(\'' + bt.rtrim(rdata.PATH, '/') + "/" + item.name + '\',\'' + name + '\')">Del</a>';
|
||||
}
|
||||
},
|
||||
@@ -448,4 +697,5 @@ var database = {
|
||||
td_width = ticket_with*0.8-30-$('#DataBody th:eq(2)').width()-$('#DataBody th:eq(3)').width()-$('#DataBody th:eq(4)').width()-$('#DataBody th:eq(6)').width();
|
||||
$('#DataBody .webNote').css('max-width',td_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
database.init();
|
||||
+244
-22
@@ -220,7 +220,7 @@ var bt_file = {
|
||||
content:'<div style="padding:15px 15px 10px 15px;"><div class="upload_btn_groud"><div class="btn-group"><button type="button" class="btn btn-primary btn-sm upload_file_btn">Upload file</button><button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="caret"></span><span class="sr-only">Toggle Dropdown</span></button><ul class="dropdown-menu"><li><a href="#" data-type="file">Upload file</a></li><li><a href="#" data-type="dir">Upload path</a></li></ul></div><div class="file_upload_info" style="display:none;"><span>Total process <i class="uploadProgress"></i>, uploading <i class="uploadNumber"></i>,</span><span style="display:none">Upload fail <i class="uploadError"></i></span><span>Speed <i class="uploadSpeed">Getting</i>,</span><span>Expect time <i class="uploadEstimate">Getting</i></span><i></i></div></div><div class="upload_file_body '+ (html==''?'active':'') +'">'+ (html!=''?('<ul class="dropUpLoadFileHead" style="padding-right:'+ (is_show?'15':'0') +'px"><li class="fileTitle"><span class="filename">File name</span><span class="filesize">File size</span><span class="fileStatus">File status</span></li></ul><ul class="dropUpLoadFile list-list">'+ html +'</ul>'):'<span>Please drag the file here'+ (!that.is_webkit?'<i style="display: block;font-style: normal;margin-top: 10px;color: red;font-size: 17px;">The current browser does not support drag upload. Commend to use Chrome browser or WebKit kernel for browsing</i>':'') +'</span>') +'</div></div>',
|
||||
success:function(layers){
|
||||
$('#mask_layer').hide();
|
||||
layers.find('.layui-layer-btn2').css('margin', '0 300px 0 0');
|
||||
layers.find('.layui-layer-btn2').css('float', 'left');
|
||||
$('.file_dir_uploads .layui-layer-max').hide();
|
||||
$('.upload_btn_groud .upload_file_btn').click(function(){$('.upload_btn_groud .dropdown-menu [data-type=file]').click()});
|
||||
$('.upload_btn_groud .dropdown-menu a').click(function(){
|
||||
@@ -725,6 +725,7 @@ var bt_file = {
|
||||
if(!res.msg) layer.msg('Refresh succeeded')
|
||||
});
|
||||
});
|
||||
// 上传
|
||||
$('.upload_file').on('click',function(e){
|
||||
that.file_drop.dialog_view();
|
||||
});
|
||||
@@ -1117,7 +1118,7 @@ var bt_file = {
|
||||
left: ev.clientX - $(this).offset().left
|
||||
};
|
||||
// 鼠标按下后拖动
|
||||
$(document).unbind('mousemove').mousemove(function(ev){
|
||||
bt_file.window_mousemove = function(ev) {
|
||||
// 鼠标按下后移动到的位置
|
||||
var endPos = {
|
||||
top: ev.clientY - con_t > 0 && ev.clientY - con_t < container.height() ? ev.clientY - con_t : (ev.clientY - (con_t+container.height()) > 1 ?container.height():0),
|
||||
@@ -1130,6 +1131,7 @@ var bt_file = {
|
||||
if(bt.get_cookie('rank') == 'list'){ //在列表模式下减去表头高度
|
||||
fixedPoint.top = fixedPoint.top + 40
|
||||
}
|
||||
var enter_files_box = that.enter_files_box()
|
||||
// 拖拽范围的宽高
|
||||
var w = Math.min(Math.abs(endPos.left - startPos.left), con_l + container.width() - fixedPoint.left);
|
||||
var h = Math.min(Math.abs(endPos.top - startPos.top), con_t + container.height() - fixedPoint.top);
|
||||
@@ -1155,17 +1157,17 @@ var bt_file = {
|
||||
}
|
||||
if(startPos.top == endPos.top || startPos.left == endPos.left) return true;
|
||||
// 设置拖拽盒子位置
|
||||
that.enter_files_box().show().css({
|
||||
enter_files_box.show().css({
|
||||
left: fixedPoint.left+'px',
|
||||
top: fixedPoint.top+'px',
|
||||
width: w+'px',
|
||||
height: h+'px'
|
||||
});
|
||||
|
||||
var box_offset_top = that.enter_files_box().offset().top;
|
||||
var box_offset_left = that.enter_files_box().offset().left;
|
||||
var box_offset_w = that.enter_files_box().offset().left + that.enter_files_box().width();
|
||||
var box_offset_h = that.enter_files_box().offset().top + that.enter_files_box().height();
|
||||
var box_offset_top = enter_files_box.offset().top;
|
||||
var box_offset_left = enter_files_box.offset().left;
|
||||
var box_offset_w = enter_files_box.offset().left + enter_files_box.width();
|
||||
var box_offset_h = enter_files_box.offset().top + enter_files_box.height();
|
||||
$(container).find('.file_tr').each(function(i,item){
|
||||
var offset_top = $(item).offset().top;
|
||||
var offset_left = $(item).offset().left;
|
||||
@@ -1185,15 +1187,15 @@ var bt_file = {
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 鼠标抬起
|
||||
$(document).on('mouseup',function(){
|
||||
var _move_array = [];
|
||||
var box_offset_top = that.enter_files_box().offset().top;
|
||||
var box_offset_left = that.enter_files_box().offset().left;
|
||||
var box_offset_w = that.enter_files_box().offset().left + that.enter_files_box().width();
|
||||
var box_offset_h = that.enter_files_box().offset().top + that.enter_files_box().height();
|
||||
bt_file.window_mouseup = function() {
|
||||
var _move_array = [],enter_files_box = that.enter_files_box();
|
||||
var box_offset_top = enter_files_box.offset().top;
|
||||
var box_offset_left = enter_files_box.offset().left;
|
||||
var box_offset_w = enter_files_box.offset().left + enter_files_box.width();
|
||||
var box_offset_h = enter_files_box.offset().top + enter_files_box.height();
|
||||
$(container).find('.file_tr').each(function(i,item){
|
||||
var offset_top = $(item).offset().top;
|
||||
var offset_left = $(item).offset().left;
|
||||
@@ -1211,9 +1213,12 @@ var bt_file = {
|
||||
}
|
||||
});
|
||||
that.render_file_selected(_move_array); //渲染数据
|
||||
that.enter_files_box().remove(); //删除盒子
|
||||
enter_files_box.remove(); //删除盒子
|
||||
$('.file_list_content').unbind('mousewheel'); //解绑滚轮事件
|
||||
})
|
||||
$(document).unbind('mousemove',bt_file.window_mousemove);
|
||||
}
|
||||
$(document).one('mouseup',bt_file.window_mouseup);
|
||||
$(document).on('mousemove',bt_file.window_mousemove);
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
})
|
||||
@@ -1264,6 +1269,10 @@ var bt_file = {
|
||||
item.type_tips = item.type == 'file'?'File':'Directory';
|
||||
that.file_groud_event(item);
|
||||
});
|
||||
// 文件搜索
|
||||
$('.replace_content').on('click', function() {
|
||||
that.replace_content_view()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @descripttion: 文件拖拽范围
|
||||
@@ -1578,7 +1587,7 @@ var bt_file = {
|
||||
shadeClose: false,
|
||||
skin:'download_file_view',
|
||||
content:html[0].outerHTML,
|
||||
btn:['Comfirm','Close'],
|
||||
btn:['Confirm','Close'],
|
||||
success:function(){
|
||||
form.setEvent();
|
||||
},
|
||||
@@ -1822,7 +1831,7 @@ var bt_file = {
|
||||
* @return: 无返回值
|
||||
*/
|
||||
render_file_selected:function(_array){
|
||||
$(document).unbind('mouseup').unbind('mousemove');
|
||||
// $(document).unbind('mouseup').unbind('mousemove');
|
||||
var that = this,tmp = [];
|
||||
that.clear_table_active()
|
||||
$.each(_array,function(index,item){
|
||||
@@ -1930,7 +1939,7 @@ var bt_file = {
|
||||
*/
|
||||
del_favorites:function(path){
|
||||
var that = this
|
||||
layer.confirm('Comfirm delete path【'+path+'】?', { title: 'Delete favorites', closeBtn: 2, icon: 3 }, function (index) {
|
||||
layer.confirm('Confirm delete path【'+path+'】?', { title: 'Delete favorites', closeBtn: 2, icon: 3 }, function (index) {
|
||||
that.$http('del_files_store',{path:path},function(res){
|
||||
if(res.status){
|
||||
that.render_favorites_type_list();
|
||||
@@ -2519,6 +2528,219 @@ var bt_file = {
|
||||
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 文件内容搜索替换
|
||||
* @return void
|
||||
*/
|
||||
replace_content_view: function() {
|
||||
layer.open({
|
||||
title: 'Search Files Content',
|
||||
type: 1,
|
||||
skin: 'replace_content_view',
|
||||
area: '710px',
|
||||
zIndex:19900,
|
||||
closeBtn: 2,
|
||||
content: '<div class="replace_content_box" style="padding:20px 40px">' +
|
||||
'<div class="replace_content_line">' +
|
||||
'<span class="tname">Search</span>' +
|
||||
'<div class="info-r">' +
|
||||
'<input class="bt-input-text" id="replaceContentValue" AUTOCOMPLETE="off" type="text" placeholder="Enter the content of the file you search for" style="width:486px">' +
|
||||
'<i class="history_search iconfont icon-xiala"></i>'+
|
||||
'<button class="normalBtnStyle checkBtn" onClick="bt_file.searchReplaceContent()" style="vertical-align: top; ">Search</button>' +
|
||||
'<ul class="history_search_list hide"></ul>'+
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="replace_content_line">' +
|
||||
'<span class="tname">Suffix</span>' +
|
||||
'<div class="info-r">' +
|
||||
'<input name="replaceFileExtsType" id="replaceFileExtsType" class="bt-input-text" placeholder="e.g: php,html" type="text" value="html,php" style="width:570px">' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="replace_content_line" style="margin-bottom: 10px;">' +
|
||||
'<span class="tname">Folder</span>' +
|
||||
'<div class="info-r">' +
|
||||
'<input class="bt-input-text" value="'+bt_file.file_path+'" type="text" style="width:570px" id="replaceContentPath">' +
|
||||
'<div class="file_path_switch replaceHasChild">' +
|
||||
'<i class="file_find_checkbox"></i>'+
|
||||
'<span class="laberText">Subdir</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="replace_content_line">' +
|
||||
'<span class="tname">Mode</span>' +
|
||||
'<div class="info-r matchModel">' +
|
||||
'<div>' +
|
||||
'<div class="checkbox_config normalModel">' +
|
||||
'<i class="file_find_radio active"></i>'+
|
||||
'<span class="laberText">Words</span>' +
|
||||
'</div>' +
|
||||
'<div class="checkbox_config regularMatchRe">' +
|
||||
'<i class="file_find_radio"></i>'+
|
||||
'<span class="laberText">Regex</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<div class="checkbox_config allMatchRe hide_option">' +
|
||||
'<i class="file_find_checkbox"></i>'+
|
||||
'<span class="laberText">Match whole word</span>' +
|
||||
'</div>' +
|
||||
'<div class="checkbox_config distinguishCaseRe">' +
|
||||
'<i class="file_find_checkbox"></i>'+
|
||||
'<span class="laberText">Match case</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="line match_container">'+
|
||||
'<div class="header">'+
|
||||
'<div class="tips-title matchRresult"></div>'+
|
||||
'</div>'+
|
||||
'<div class="main matchContent_main"><div style="color: #bcbcbc; font-size: 16px; text-align: center;line-height: 35px;">Type search content to search in the file</br><span style="font-size:14px">Options to narrow down the search</span></div></div>'+
|
||||
'</div>'+
|
||||
'<span class="glyphicon cursor mr5 glyphicon-folder-open" onClick="bt.select_path(\'replaceContentPath\')"></span>' +
|
||||
'</div>',
|
||||
success:function(){
|
||||
//单选、复选框按钮事件
|
||||
$('.checkbox_config,.file_path_switch').click(function(e){
|
||||
if(e.target.localName == 'i' || e.target.localName == 'span'){
|
||||
var is_radio = $(this).find('i').hasClass('file_find_radio'),i_box = $(this).find('i');
|
||||
if(is_radio){//是否单选
|
||||
i_box.addClass('active').parent('div').siblings().find('i').removeClass('active');
|
||||
if($(this).find('.laberText').text() == 'Words'){
|
||||
$('.hide_option').removeClass('hide')
|
||||
}else{ //正则模式下取消全词匹配
|
||||
$('.hide_option').addClass('hide').find('i').removeClass('active')
|
||||
}
|
||||
}else{//是否复选
|
||||
if(i_box.hasClass('active')){
|
||||
i_box.removeClass('active')
|
||||
}else{
|
||||
i_box.addClass('active')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// 历史输入
|
||||
$('.history_search').click(function(e){
|
||||
var list = JSON.parse(bt.get_cookie('file_search_list')),h_html = '';
|
||||
if($.type(list) === 'undefined' || list == null){
|
||||
h_html = '<span style=" padding: 5px 10px; ">No records</span>';
|
||||
$('.history_search_list').html(h_html).removeClass('hide');
|
||||
}else{
|
||||
$('.history_search_list').empty();
|
||||
$.each(list,function(index,item){
|
||||
$('.history_search_list').append($('<li></li>').attr('data-key',item).text(item)).removeClass('hide')
|
||||
})
|
||||
}
|
||||
$(document).one('click', function() {
|
||||
$('.history_search_list').addClass('hide');
|
||||
e.stopPropagation();
|
||||
});
|
||||
e.stopPropagation();
|
||||
})
|
||||
//选择历史输入
|
||||
$('.history_search_list').on('click','li',function(){
|
||||
$('#replaceContentValue').val($(this).data('key'));
|
||||
$('.history_search_list').addClass('hide');
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 文件内容搜索結果
|
||||
* @returns void
|
||||
*/
|
||||
searchReplaceContent: function() {
|
||||
var that = this,
|
||||
file_num = 0, //文件数量
|
||||
match_num = 0, //文件内查询到的数量
|
||||
match_file_html = '',
|
||||
data = {
|
||||
text: $('#replaceContentValue').val(),
|
||||
exts: $("#replaceFileExtsType").val() || 'html,php',
|
||||
path: $('#replaceContentPath').val(), //路径
|
||||
is_subdir: !$('.replaceHasChild').find('i').hasClass('active') ? '0' : '1', //0不包含子目录 1 包含子目录
|
||||
mode: !$('.regularMatchRe').find('i').hasClass('active') ? '0' : '1', //为普通模式 1 为正则模式
|
||||
isword: !$('.allMatchRe').find('i').hasClass('active') ? '0' : '1', //全词匹配 0 默认
|
||||
iscase: !$('.distinguishCaseRe').find('i').hasClass('active') ? '1' : '0', //不区分大小写 0 默认
|
||||
noword: '0' //不输出行信息 0 默认
|
||||
}
|
||||
this.$http('files_search',data,function(res) {
|
||||
var reg = new RegExp("("+that.escodeChange(data.text)+")");
|
||||
if(res.error) return layer.msg(res.error, {icon: 2});
|
||||
if (data.text.indexOf('\n') < 0 && data.text != '') that.setSearchHistoryList(data.text); //设置搜索历史列表
|
||||
if($.isEmptyObject(res)){
|
||||
$('.replace_content_view .matchContent_main').html('<div style=" color: #bcbcbc; font-size: 16px; text-align: center; ">No data found</div>')
|
||||
$('.matchRresult').html('')
|
||||
return
|
||||
}
|
||||
$.each(res,function(fileName,item){
|
||||
file_num++;
|
||||
var contentNum = Object.keys(item).length
|
||||
match_file_html+='<div class="match_content_item" data-file="'+fileName+'">'+
|
||||
'<div class="match_content_title">'+
|
||||
'<span class="match_result_file_title" title="'+fileName+' (Match '+contentNum+' times)"><i class="glyphicon glyphicon-triangle-bottom"></i>'+fileName+' (Match '+contentNum+' times)</span>'+
|
||||
'<a class="btlink pull-right editFile" data-filename="'+fileName+'">Edit</a>'+
|
||||
'</div>'+
|
||||
'<div class="match_result_file_content matchShow">'
|
||||
$.each(item,function(index,lineItem){
|
||||
match_num++
|
||||
var html = $('<div></div>').text(lineItem.trim()).html().replace(reg,'<i style="font-weight: bold">'+ $('<div></div>').text(data.text).html() +'</i>')
|
||||
match_file_html += '<div class="match_result_detail"><span style="font-weight: bold"> line '+index+'</span>: '+html+'</div>';
|
||||
})
|
||||
match_file_html+= '</div></div>'
|
||||
})
|
||||
$('.matchRresult').html('Search results: <span style="color: #20a53a;">'+ match_num +'</span> matches in <span style="font-weight: bold;">'+ file_num +'</span> files')
|
||||
$('.matchContent_main').html(match_file_html);
|
||||
// 隐藏显示内容
|
||||
$('.matchContent_main .match_result_file_title').click(function(e){
|
||||
var parent_box = $(this).parents('.match_content_item'),
|
||||
is_icon_top = $(this).find('i').hasClass('glyphicon-triangle-top') //是否图标向上(未打开)
|
||||
if(is_icon_top){
|
||||
parent_box.find('.match_result_file_content').addClass('matchShow')
|
||||
$(this).find('i').removeClass('glyphicon-triangle-top').addClass('glyphicon-triangle-bottom')
|
||||
}else{
|
||||
$(this).find('i').removeClass('glyphicon-triangle-bottom').addClass('glyphicon-triangle-top')
|
||||
parent_box.find('.match_result_file_content').removeClass('matchShow')
|
||||
}
|
||||
e.stopPropagation()
|
||||
})
|
||||
//编辑跳转
|
||||
$('.editFile').click(function(){
|
||||
openEditorView(0,$(this).data('filename'),function(val,aceEitor){
|
||||
aceEitor.ace.find(data.text)
|
||||
aceEitor.ace.execCommand('find')
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 设置搜索历史列表
|
||||
* @param {String} text 查找的文本
|
||||
* @returns void
|
||||
*/
|
||||
setSearchHistoryList:function(text){
|
||||
var h_cookie = JSON.parse(bt.get_cookie('file_search_list'));
|
||||
if($.type(h_cookie) === 'undefined' || h_cookie == null){
|
||||
bt.set_cookie('file_search_list', JSON.stringify([text]))
|
||||
}else{
|
||||
if($.inArray(text,h_cookie) != -1) return true; //如果已在列表中则跳过
|
||||
h_cookie.unshift(text) //数组首位添加查找内容
|
||||
if(h_cookie.length > 7) h_cookie.pop() //超过7位时删除最后一条搜索记录
|
||||
bt.set_cookie('file_search_list', JSON.stringify(h_cookie))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 转义查找输入的内容特殊字符
|
||||
* @param {String} e 查找的内容
|
||||
* @returns 返回转义结果
|
||||
*/
|
||||
escodeChange:function(e){
|
||||
if(/(\+|\-|\$|\||\!|\(|\)|\{|\}|\[|\]|\^|\”|\~|\*|\?|\:|\\)/g.test(e)){
|
||||
e = e.replace(/(\+|\-|\$|\||\!|\(|\)|\{|\}|\[|\]|\^|\”|\~|\*|\?|\:|\\)/g,'\\$1').replace(/&/g, "&").replace(/\>/g, ">").replace(/\</g, "<");
|
||||
}
|
||||
return e
|
||||
},
|
||||
/**
|
||||
* @description 回收站视图
|
||||
* @return void
|
||||
@@ -3744,7 +3966,7 @@ var bt_file = {
|
||||
if(that.is_recycle){
|
||||
bt.confirm({
|
||||
title:'Delete '+ data.type_tips +'[ '+ data.filename +' ]',
|
||||
msg:'<span>Comfirm delete '+ data.type_tips +'[ '+ data.path +' ],it will move to recycle bin after delete, continue?</span>'
|
||||
msg:'<span>Confirm delete '+ data.type_tips +'[ '+ data.path +' ],it will move to recycle bin after delete, continue?</span>'
|
||||
},function(){
|
||||
that.del_file_req(data,function(res){
|
||||
that.reader_file_list({path:that.file_path})
|
||||
@@ -4519,7 +4741,7 @@ var bt_file = {
|
||||
*/
|
||||
remove_download_url:function(data,callback){
|
||||
var that = this;
|
||||
layer.confirm('Comfirm to stop sharing【'+ data.fileName +'】, continue?',{ title: 'Cancel sharing', closeBtn: 2, icon: 3 }, function () {
|
||||
layer.confirm('Confirm to stop sharing【'+ data.fileName +'】, continue?',{ title: 'Cancel sharing', closeBtn: 2, icon: 3 }, function () {
|
||||
this.layerT = bt.load('Canceling sharing files, please wait...');
|
||||
bt.send('remove_download_url','files/remove_download_url',{id:data.id},function(res){
|
||||
if (callback) callback(res);
|
||||
@@ -4787,7 +5009,7 @@ var bt_file = {
|
||||
shadeClose: false,
|
||||
closeBtn:2,
|
||||
skin:'unpack_file_view',
|
||||
btn:['Comfirm','Cancel'],
|
||||
btn:['Confirm','Cancel'],
|
||||
content: html[0].outerHTML,
|
||||
success:function(){
|
||||
if(data.ext == 'gz') _type = 'tar' //解压格式
|
||||
|
||||
+297
-2
@@ -3815,7 +3815,7 @@ function messagebox(){
|
||||
|
||||
function get_message_data(page,callback){
|
||||
if(typeof page === "function") callback = page,page = 1;
|
||||
var loadT = bt.load('正在获取消息列表,请稍后...');
|
||||
var loadT = bt.load('Getting message list, please wait...');
|
||||
bt.send("getData","data/getData",{
|
||||
tojs:'reader_message_list',
|
||||
table:'tasks',
|
||||
@@ -4893,4 +4893,299 @@ acme = {
|
||||
if (callback) callback(res)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 消息通道 **/
|
||||
function MessageChannelSettings(){
|
||||
MessageChannel.get_channel_settings(function (rdata){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "600px",
|
||||
title: "Setting up notification",
|
||||
skin:"layer-channel-auth",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form">\
|
||||
<div class="bt-w-main">\
|
||||
<div class="bt-w-menu" style="width: 110px;">\
|
||||
<p class="bgw">Email</p>\
|
||||
<p>Telegram</p>\
|
||||
</div>\
|
||||
<div class="bt-w-con pd15" style="margin-left: 110px">\
|
||||
<div class="plugin_body">\
|
||||
<div class="conter_box active" >\
|
||||
<div class="bt-form">\
|
||||
<div class="line">\
|
||||
<button class="btn btn-success btn-sm" onclick="MessageChannel.add_receive_info()">Add recipient</button>\
|
||||
<button class="btn btn-default btn-sm" onclick="MessageChannel.sender_info_edit()">Sender settings</button>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<div class="divtable">\
|
||||
<table class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0"><thead><tr><th>Email</th><th width="80px">Operating</th></tr></thead></table>\
|
||||
<table class="table table-hover"><tbody id="receive_table"></tbody></table>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="conter_box" style="display:none">\
|
||||
<div class="line">\
|
||||
<span class="tname">ID</span>\
|
||||
<div class="info-r">\
|
||||
<input name="telegram_id" class="bt-input-text mr5" type="text" placeholder="Telegram ID" style="width: 300px" value="'+rdata.telegram.my_id+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">TOKEN</span>\
|
||||
<div class="info-r">\
|
||||
<input name="telegram_token" class="bt-input-text mr5" type="text" placeholder="Telegram TOKEN" style="width: 300px" value="'+rdata.telegram.bot_token+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname"></span>\
|
||||
<button class="btn btn-success btn-sm addTelegram" style="margin-right: 10px;">Save</button>\
|
||||
'+(rdata.telegram.setup?'<button class="btn btn-default btn-sm delTelegram">Clear set</button>':'')+'\
|
||||
</div>\
|
||||
<ul class="help-info-text c7" style="margin-top: 315px;">\
|
||||
<li>ID: Your telegram user ID</li>\
|
||||
<li>Token: Your telegram bot token </li>\
|
||||
<li>e.g: [ 12345677:AAAAAAAAA_a0VUo2jjr__CCCCDDD ] <a class="btlink" href="https://forum.aapanel.com/d/5115-how-to-add-telegram-to-panel-notifications" target="_blank" rel="noopener"> Help</a></li>\
|
||||
</ul>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>',
|
||||
success:function(){
|
||||
$('.addTelegram').click(function(){
|
||||
var _id = $('[name=telegram_id]').val(),_token = $('[name=telegram_token]').val();
|
||||
if(_id == '' || _token == '') return layer.msg('input box cannot be empty!');
|
||||
var loadT = layer.msg('The notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_tg_bot',{bot_token:_token,my_id:_id},function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
})
|
||||
})
|
||||
$('.delTelegram').click(function(){
|
||||
var loadTs = layer.msg('Deleting notification, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=del_tg_info',function(rdata){
|
||||
layer.close(loadTs);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
if(rdata.status){
|
||||
$('[name=telegram_id]').val('');
|
||||
$('[name=telegram_token]').val('')
|
||||
$('.delTelegram').hide();
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
$(".bt-w-menu p").click(function () {
|
||||
var index = $(this).index();
|
||||
$(this).addClass('bgw').siblings().removeClass('bgw');
|
||||
$('.conter_box').eq(index).show().siblings().hide();
|
||||
});
|
||||
MessageChannel.get_receive_list();
|
||||
})
|
||||
}
|
||||
var MessageChannel = {
|
||||
//获取推送设置
|
||||
get_channel_settings:function(callback){
|
||||
var loadT = layer.msg('Getting profile, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=get_settings2',function(rdata){
|
||||
layer.close(loadT);
|
||||
if (callback) callback(rdata);
|
||||
})
|
||||
},
|
||||
// 获取收件者列表
|
||||
get_receive_list:function(){
|
||||
$.post('/config?action=get_settings2',function(rdata){
|
||||
var _html = '',_list = rdata.user_mail.mail_list;
|
||||
if(_list.length > 0){
|
||||
for(var i= 0; i<_list.length;i++){
|
||||
_html += '<tr>\
|
||||
<td>'+ _list[i] +'</td>\
|
||||
<td width="80px" style="text-align:right;"><a onclick="MessageChannel.del_email(\''+ _list[i] + '\')" href="javascript:;" style="color:#20a53a">Del</a></td>\
|
||||
</tr>'
|
||||
}
|
||||
}else{
|
||||
_html = '<tr><td colspan="2">No Data</td></tr>'
|
||||
}
|
||||
$('#receive_table').html(_html);
|
||||
})
|
||||
},
|
||||
// 添加收件者
|
||||
add_receive_info:function (){
|
||||
var _this = this
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "400px",
|
||||
title: "Add recipient email",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 pb70">\
|
||||
<div class="line">\
|
||||
<span class="tname">Recipient mailbox</span>\
|
||||
<div class="info-r">\
|
||||
<input name="creater_email_value" class="bt-input-text mr5" type="text" style="width: 240px" value="">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="bt-form-submit-btn">\
|
||||
<button type="button" class="btn btn-danger btn-sm smtp_closeBtn">Close</button>\
|
||||
<button class="btn btn-success btn-sm CreaterReceive">Create</button>\
|
||||
</div>\
|
||||
</div>',
|
||||
success:function(layers,index){
|
||||
$(".CreaterReceive").click(function(){
|
||||
var _receive = $('input[name=creater_email_value]').val();
|
||||
if(_receive != ''){
|
||||
var loadT = layer.msg('Please wait while creating recipient list...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
layer.close(index)
|
||||
$.post('/config?action=add_mail_address',{email:_receive},function(rdata){
|
||||
layer.close(loadT);
|
||||
// 刷新收件列表
|
||||
_this.get_receive_list();
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2});
|
||||
})
|
||||
}else{
|
||||
layer.msg('Recipient mailbox cannot be empty!!',{icon:2});
|
||||
}
|
||||
})
|
||||
|
||||
$(".smtp_closeBtn").click(function(){
|
||||
layer.close(index)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 删除收件者
|
||||
del_email:function(mail){
|
||||
var loadT = layer.msg('Deleting['+mail+'],please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }),_this = this;
|
||||
$.post('/config?action=del_mail_list',{email:mail},function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
_this.get_receive_list()
|
||||
})
|
||||
},
|
||||
// 设置发送者邮箱信息
|
||||
sender_info_edit:function (){
|
||||
var loadT = layer.msg('Getting profile, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=get_settings2',function(rdata){
|
||||
layer.close(loadT);
|
||||
var qq_mail = rdata.user_mail.info.msg.qq_mail ? rdata.user_mail.info.msg.qq_mail:'',
|
||||
qq_stmp_pwd = rdata.user_mail.info.msg.qq_stmp_pwd? rdata.user_mail.info.msg.qq_stmp_pwd:'',
|
||||
hosts = rdata.user_mail.info.msg.hosts? rdata.user_mail.info.msg.hosts:'',
|
||||
port = rdata.user_mail.info.msg.port? rdata.user_mail.info.msg.port:'',
|
||||
is_custom = $.inArray(port,['25','465','587','']) != -1 //是否自定义
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: "460px",
|
||||
title: "Set sender email information",
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 pb70">\
|
||||
<div class="line">\
|
||||
<span class="tname">Sender email</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_value" class="bt-input-text mr5" type="text" style="width: 300px" value="'+qq_mail+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">SMTP password</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_password" class="bt-input-text mr5" type="password" style="width: 300px" value="'+qq_stmp_pwd+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">SMTP server</span>\
|
||||
<div class="info-r">\
|
||||
<input name="channel_email_server" class="bt-input-text mr5" type="text" style="width: 300px" value="'+hosts+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="line">\
|
||||
<span class="tname">SMTP port</span>\
|
||||
<div class="info-r">\
|
||||
<select class="bt-input-text mr5" id="port_select" style="width:'+(is_custom?'300px':'100px')+'"></select>\
|
||||
<input name="channel_email_port" class="bt-input-text mr5" type="Number" style="display:'+(is_custom? 'none':'inline-block')+'; width: 190px" value="'+port+'">\
|
||||
</div>\
|
||||
</div>\
|
||||
<ul class="help-info-text c7">\
|
||||
<li>465 port is recommended, the protocol is SSL/TLS</li>\
|
||||
<li>Port 25 is SMTP protocol, port 587 is STARTTLS protocol</li>\
|
||||
</ul>\
|
||||
<div class="bt-form-submit-btn">\
|
||||
'+(qq_mail != ''?'<button type="button" class="btn btn-default btn-sm pull-left set_empty">Clear set</button>':'')+'\
|
||||
<button type="button" class="btn btn-danger btn-sm smtp_closeBtn">Close</button>\
|
||||
<button class="btn btn-success btn-sm SetChannelEmail">Save</button></div>\
|
||||
</div>',
|
||||
success:function(layers,index){
|
||||
var _option = '';
|
||||
if(is_custom){
|
||||
if(port == '465' || port == ''){
|
||||
_option = '<option value="465" selected="selected">465</option><option value="25">25</option><option value="587">587</option><option value="other">Customize</option>'
|
||||
}else if(port == '25'){
|
||||
_option = '<option value="465">465</option><option value="25" selected="selected">25</option><option value="587">587</option><option value="other">Customize</option>'
|
||||
}else{
|
||||
_option = '<option value="465">465</option><option value="25">25</option><option value="587" selected="selected">587</option><option value="other">Customize</option>'
|
||||
}
|
||||
}else{
|
||||
_option = '<option value="465">465</option><option value="25">25</option><option value="587" >587</option><option value="other" selected="selected">Customize</option>'
|
||||
}
|
||||
$("#port_select").html(_option)
|
||||
$("#port_select").change(function(e){
|
||||
if(e.target.value == 'other'){
|
||||
$("#port_select").css("width","100px");
|
||||
$('input[name=channel_email_port]').css("display","inline-block");
|
||||
}else{
|
||||
$("#port_select").css("width","300px");
|
||||
$('input[name=channel_email_port]').css("display","none");
|
||||
}
|
||||
})
|
||||
$(".SetChannelEmail").click(function(){
|
||||
var _email = $('input[name=channel_email_value]').val();
|
||||
var _passW = $('input[name=channel_email_password]').val();
|
||||
var _server = $('input[name=channel_email_server]').val(),
|
||||
_port = ''
|
||||
if($('#port_select').val() == 'other'){
|
||||
_port = $('input[name=channel_email_port]').val();
|
||||
}else{
|
||||
_port = $('#port_select').val()
|
||||
}
|
||||
if(!_email) return layer.msg('Email address cannot be empty!',{icon:2});
|
||||
if(!_passW) return layer.msg('STMP password cannot be empty!',{icon:2});
|
||||
if(!_server)return layer.msg('STMP server address cannot be empty!',{icon:2})
|
||||
if(!_port) return layer.msg('STMP server port cannot be empty!',{icon:2})
|
||||
|
||||
var loadT = layer.msg('The notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=user_mail_send',{email:_email,stmp_pwd:_passW,hosts:_server,port:_port},function(rdata){
|
||||
layer.close(loadT);
|
||||
if(rdata.status){
|
||||
layer.close(index)
|
||||
MessageChannel.get_channel_settings();
|
||||
}
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
})
|
||||
})
|
||||
$(".smtp_closeBtn").click(function(){
|
||||
layer.close(index)
|
||||
})
|
||||
$('.set_empty').click(function(){
|
||||
var loadTs = layer.msg('notification, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_empty',{type:'mail'},function(rdata){
|
||||
layer.close(loadTs);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2})
|
||||
if(rdata.status){
|
||||
layer.close(index)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
/** 消息通道 end**/
|
||||
@@ -227,6 +227,25 @@ var bt = {
|
||||
var num = min + Math.round(rand * range); //四舍五入
|
||||
return num;
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成计算数字(加强计算,用于删除重要数据二次确认)
|
||||
* */
|
||||
get_random_code: function () {
|
||||
var flist = [20, 21, 22, 23]
|
||||
|
||||
var num1 = bt.get_random_num(13, 19);
|
||||
var t1 = num1 % 10;
|
||||
|
||||
var num2 = bt.get_random_num(13, 29);
|
||||
var t2 = num2 % 10;
|
||||
|
||||
while ($.inArray(num2, flist) >= 0 || (t1 + t2) <= 10 || t1 == t2) {
|
||||
num2 = bt.get_random_num(13, 29);
|
||||
t2 = num2 % 10;
|
||||
}
|
||||
return { 'num1': num1, 'num2': num2 }
|
||||
},
|
||||
/**
|
||||
* @description 设置本地存储,local和session
|
||||
* @param {String} type 存储类型,可以为空,默认为session类型。
|
||||
@@ -294,9 +313,16 @@ var bt = {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
select_path: function(id, type) {
|
||||
/**
|
||||
* @description 选择文件目录或文件
|
||||
* @param id {string} 元素ID
|
||||
* @param type {string || function} 选择方式,文件或目录
|
||||
* @param success {function} 成功后的回调
|
||||
*/
|
||||
select_path: function(id, type,success,default_path) {
|
||||
_this = this;
|
||||
_this.set_cookie("SetName", "");
|
||||
if(typeof type !== 'string') success = type,type = 'dir';
|
||||
var loadT = bt.open({
|
||||
type: 1,
|
||||
area: "680px",
|
||||
@@ -322,40 +348,64 @@ var bt = {
|
||||
$("." + id).val(path).change();
|
||||
loadT.close();
|
||||
})
|
||||
}
|
||||
});
|
||||
_this.set_cookie('ChangePath', loadT.form);
|
||||
var paths = $("#" + id).val();
|
||||
if ($('#defaultPath').length > 0 && $("#" + id).parents('.tab-body').length > 0) {
|
||||
paths = $('#defaultPath').text();
|
||||
}
|
||||
_this.get_file_list(paths, type);
|
||||
//选择
|
||||
$('#bt_select').on('click',function () {
|
||||
var path = bt.format_path($("#PathPlace").find("span").text());
|
||||
if(type === 'file' && !$('#tbody tr.active').length){
|
||||
layer.msg('Select the file first!',{icon:0})
|
||||
return false;
|
||||
}
|
||||
if ($('#tbody tr').hasClass('active')) {
|
||||
path = $('#tbody tr.active .bt_open_dir').attr('path');
|
||||
}
|
||||
path = bt.rtrim(path, '/');
|
||||
$("#" + id).val(path).change();
|
||||
$("." + id).val(path).change();
|
||||
if(typeof success === "function") success(path)
|
||||
loadT.close();
|
||||
})
|
||||
var element = $("#" + id),paths = element.val(),defaultPath = $('#defaultPath');
|
||||
if (defaultPath.length > 0 && element.parents('.tab-body').length > 0) {
|
||||
paths = defaultPath.text();
|
||||
}
|
||||
if(default_path){
|
||||
paths = default_path;
|
||||
}
|
||||
_this.get_file_list(paths, type);
|
||||
}
|
||||
});
|
||||
_this.set_cookie('ChangePath', loadT.form);
|
||||
// var paths = $("#" + id).val();
|
||||
// if ($('#defaultPath').length > 0 && $("#" + id).parents('.tab-body').length > 0) {
|
||||
// paths = $('#defaultPath').text();
|
||||
// }
|
||||
// _this.get_file_list(paths, type);
|
||||
|
||||
function ActiveDisk() {
|
||||
var a = $("#PathPlace").find("span").text().substring(0, 1);
|
||||
switch (a) {
|
||||
case "C":
|
||||
$(".path-con-left dd:nth-of-type(1)").css("background", "#eee").siblings().removeAttr("style");
|
||||
break;
|
||||
case "D":
|
||||
$(".path-con-left dd:nth-of-type(2)").css("background", "#eee").siblings().removeAttr("style");
|
||||
break;
|
||||
case "E":
|
||||
$(".path-con-left dd:nth-of-type(3)").css("background", "#eee").siblings().removeAttr("style");
|
||||
break;
|
||||
case "F":
|
||||
$(".path-con-left dd:nth-of-type(4)").css("background", "#eee").siblings().removeAttr("style");
|
||||
break;
|
||||
case "G":
|
||||
$(".path-con-left dd:nth-of-type(5)").css("background", "#eee").siblings().removeAttr("style");
|
||||
break;
|
||||
case "H":
|
||||
$(".path-con-left dd:nth-of-type(6)").css("background", "#eee").siblings().removeAttr("style");
|
||||
break;
|
||||
default:
|
||||
$(".path-con-left dd").removeAttr("style")
|
||||
}
|
||||
}
|
||||
// function ActiveDisk() {
|
||||
// var a = $("#PathPlace").find("span").text().substring(0, 1);
|
||||
// switch (a) {
|
||||
// case "C":
|
||||
// $(".path-con-left dd:nth-of-type(1)").css("background", "#eee").siblings().removeAttr("style");
|
||||
// break;
|
||||
// case "D":
|
||||
// $(".path-con-left dd:nth-of-type(2)").css("background", "#eee").siblings().removeAttr("style");
|
||||
// break;
|
||||
// case "E":
|
||||
// $(".path-con-left dd:nth-of-type(3)").css("background", "#eee").siblings().removeAttr("style");
|
||||
// break;
|
||||
// case "F":
|
||||
// $(".path-con-left dd:nth-of-type(4)").css("background", "#eee").siblings().removeAttr("style");
|
||||
// break;
|
||||
// case "G":
|
||||
// $(".path-con-left dd:nth-of-type(5)").css("background", "#eee").siblings().removeAttr("style");
|
||||
// break;
|
||||
// case "H":
|
||||
// $(".path-con-left dd:nth-of-type(6)").css("background", "#eee").siblings().removeAttr("style");
|
||||
// break;
|
||||
// default:
|
||||
// $(".path-con-left dd").removeAttr("style")
|
||||
// }
|
||||
// }
|
||||
},
|
||||
get_file_list:function(path, type){
|
||||
var _that = this;
|
||||
@@ -424,9 +474,52 @@ var bt = {
|
||||
})
|
||||
})
|
||||
},
|
||||
prompt_confirm: function (title, msg, callback) {
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: title,
|
||||
area: "480px",
|
||||
closeBtn: 2,
|
||||
btn: ['OK', 'Cancel'],
|
||||
content: "<div class='bt-form promptDelete pd20'>\
|
||||
<p>" + msg + "</p>\
|
||||
<div class='confirm-info-box'>\
|
||||
<input onpaste='return false;' id='prompt_input_box' type='text' value=''>\
|
||||
<div class='placeholder c9 prompt_input_tips' >If you confirm the operation, enter it manually '<font style='color: red'>" + title + "</font>'</div>\
|
||||
<div style='margin-top:5px;display: none;' class='prompt_input_ps'>The verification code is incorrect. Please enter it manually '<font style='color: red'>" + title + "</font>'</div></div>\
|
||||
</div>",
|
||||
success: function () {
|
||||
var black_txt_ = $('#prompt_input_box')
|
||||
|
||||
|
||||
|
||||
$('.placeholder').click(function () {
|
||||
$(this).hide().siblings('input').focus()
|
||||
})
|
||||
black_txt_.focus(function () {
|
||||
$('.prompt_input_tips.placeholder').hide()
|
||||
})
|
||||
black_txt_.blur(function () {
|
||||
black_txt_.val() == '' ? $('.prompt_input_tips.placeholder').show() : $('.prompt_input_tips.placeholder').hide()
|
||||
});
|
||||
black_txt_.keyup(function () {
|
||||
if (black_txt_.val() == '') {
|
||||
$('.prompt_input_tips.placeholder').show();
|
||||
$('.prompt_input_ps').hide();
|
||||
} else {
|
||||
$('.prompt_input_tips.placeholder').hide();
|
||||
}
|
||||
})
|
||||
},
|
||||
yes: function (layers, index) {
|
||||
var result = $("#prompt_input_box").val().trim();
|
||||
if (result == title) {
|
||||
layer.close(layers)
|
||||
if (callback) callback()
|
||||
} else {
|
||||
$('.prompt_input_ps').show();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
show_confirm: function(title, msg, fun, error) {
|
||||
if (error == undefined) {
|
||||
error = ""
|
||||
@@ -683,7 +776,7 @@ var bt = {
|
||||
_tab.click(function() {
|
||||
$('#' + obj).find('span').removeClass('on');
|
||||
$(this).addClass('on');
|
||||
var _contents = $('.tab-con');
|
||||
var _contents = $('#' + obj).next('.tab-con');
|
||||
_contents.html('');
|
||||
$(this).data('callback')(_contents);
|
||||
})
|
||||
@@ -3072,14 +3165,25 @@ bt.system = {
|
||||
},
|
||||
rep_panel: function(callback) {
|
||||
var loading = bt.load(lan.index.rep_panel_the)
|
||||
bt.send('RepPanel', 'system/RepPanel', {}, function(rdata) {
|
||||
loading.close();
|
||||
if (rdata) {
|
||||
if (callback) callback({ status: rdata, msg: lan.index.rep_panel_ok });
|
||||
bt.system.reload_panel();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'system?action=RepPanel',
|
||||
error: function (err) {
|
||||
setTimeout(() => {
|
||||
loading.close();
|
||||
bt.system.reload_panel(function () {
|
||||
location.reload();
|
||||
});
|
||||
}, 1000 * 60 * 5);
|
||||
},
|
||||
success: function (rdata) {
|
||||
loading.close();
|
||||
if (rdata) {
|
||||
if (callback) callback({ status: rdata, msg: lan.index.rep_panel_ok });
|
||||
bt.system.reload_panel();
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
},
|
||||
get_warning: function(callback) {
|
||||
bt.send('GetWarning', 'ajax/GetWarning', {}, function(rdata) {
|
||||
@@ -3884,10 +3988,7 @@ bt.soft = {
|
||||
bt.soft.pro.get_product_discount_by(config.pid,function(rdata){
|
||||
//rdata = {"36": {"discount": 1, "did": 0, "price": 3564, "name": "正常", "sprice": 3564}, "24": {"discount": 1, "did": 0, "price": 2376, "name": "正常", "sprice": 2376}, "12": {"discount": 1, "did": 0, "price": 1188, "name": "正常", "sprice": 1188}, "6": {"discount": 1, "did": 0, "price": 594, "name": "正常", "sprice": 594}, "3": {"discount": 1, "did": 0, "price": 297, "name": "正常", "sprice": 297}, "1": {"discount": 1, "did": 0, "price": 99, "name": "正常", "sprice": 99}, "pid": "100000045"};
|
||||
if(typeof rdata.status === "boolean"){
|
||||
if(!rdata.status) {
|
||||
bt.msg({status:false, msg:rdata.msg})
|
||||
return false;
|
||||
}
|
||||
if(!rdata.status) return false;
|
||||
}
|
||||
that.product_cache[config.pid] = rdata;
|
||||
setTimeout(function(){ delete that.product_cache[config.pid] },60000);
|
||||
@@ -4021,8 +4122,7 @@ bt.soft = {
|
||||
if (rdata.status === false){
|
||||
bt.set_cookie('force', 1);
|
||||
if (soft) soft.flush_cache();
|
||||
// layer.msg(rdata.msg, { icon: 2 });
|
||||
bt.msg({status:false,msg:rdata.msg})
|
||||
layer.msg(rdata.msg, { icon: 2 });
|
||||
return;
|
||||
}
|
||||
config.pay = parseInt($('#libPay-mode .pay-cycle-btn.active').data('condition'));
|
||||
@@ -4486,8 +4586,7 @@ bt.soft = {
|
||||
if (rdata.status === false) {
|
||||
bt.set_cookie('force', 1);
|
||||
if (soft) soft.flush_cache();
|
||||
// layer.msg(rdata.msg, { icon: 2 });
|
||||
bt.msg({status:false,msg:rdata.msg})
|
||||
layer.msg(rdata.msg, { icon: 2 });
|
||||
return;
|
||||
}
|
||||
$(".pay-wx").html('');
|
||||
|
||||
+2862
-941
File diff suppressed because it is too large
Load Diff
+1705
-2664
File diff suppressed because it is too large
Load Diff
@@ -1006,7 +1006,7 @@ var lan = {
|
||||
"mysql_status_ps4":"If too low, increase innodb_buffer_pool_size",
|
||||
"mysql_status_ps5":"If too low, increase query_cache_size",
|
||||
"mysql_status_ps6":"If too high, increase tmp_table_size",
|
||||
"mysql_status_ps7":"If too high, increase table_open_cache",
|
||||
"mysql_status_ps7":"If too high, increase table_cache_size",
|
||||
"mysql_status_ps8":"If not 0, please check index of database table",
|
||||
"mysql_status_ps9":"If not 0, please check index of database table",
|
||||
"mysql_status_ps10":"If too high, increase sort_buffer_size",
|
||||
@@ -1513,7 +1513,7 @@ var lan = {
|
||||
"deploy_success_cret": "Remind: ",
|
||||
"try_renew_cret": "The SSL will be auto renewed 1 month before the expiration date",
|
||||
"cert_brand": "Brand: ",
|
||||
"expire_time": "Expire: ",
|
||||
"expire_time": "Expire date: ",
|
||||
"auth_domain": "Certified domain(s): ",
|
||||
"ssl_renew": "Renew",
|
||||
"deploy_success_tips": "<b>Successfully deployed: </b>Please replace the new certificate before the certificate expires",
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
"MOVE_ERR":"Failed to move file or directory!",
|
||||
"FILE_GET_ERR":"Failed to obtain file content, format is not supported!",
|
||||
"FILE_SAVE_SUCCESS":"Saved!",
|
||||
"FILE_SAVE_ERR":"Save ERROR!",
|
||||
"FILE_SAVE_ERR":"Save ERROR! {1}",
|
||||
"ZIP_SUCCESS":"Compression succeeded!",
|
||||
"ZIP_ERR":"Compression failed!",
|
||||
"UNZIP_SUCCESS":"Uncompression succeeded!",
|
||||
@@ -350,7 +350,11 @@
|
||||
"MAX_PROCESSES": "Max processes",
|
||||
"MAX_CONNECTIONS": "Max connections",
|
||||
"NOT_LIMITED_BY_0": "0 means not limited",
|
||||
"MAX_SPARE_SERVERS": "Max spare servers",
|
||||
"MAX_SPARE_THREADS": "Maximum number of idle threads",
|
||||
"MIN_SPARE_THREADS": "Minimum number of idle threads available to handle request spikes",
|
||||
"THREADS_PER_CHILD": "Number of threads created by each child process",
|
||||
"MAX_REQUEST_WORKERS": "Maximum number of connections that will be processed simultaneously",
|
||||
"MaxConnectionsPerChild": "Limit on the number of connections that an individual child server will handle during its life",
|
||||
"CONFIG_ERROR": "Configuration ERROR",
|
||||
"SECRET_KEY_CHECK_FALSE": "Secret key verification failed",
|
||||
"CHECK_IP_FALSE": "IP verification failed, your access IP is",
|
||||
@@ -1235,5 +1239,4 @@
|
||||
"BACKUP_MSG": "Backup to {1}, a total of {3} {2}, and failures {4}.",
|
||||
"BACKUP_MSG1": "Backup failed {1}/total {2} sites",
|
||||
"NOTIFICATION": "Notification"
|
||||
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
<span class="set-tit text-right" title="Notification">Notification</span>
|
||||
<div class="btn_tips">
|
||||
<input id="channel_auth" name="channel_auth" class="inputtxt bt-input-text disable" type="text" value="" disabled>
|
||||
<span class="modify btn btn-xs btn-success channel_auth" style="margin-left: -38px;" onclick="open_three_channel_auth()">Set</span>
|
||||
<span class="modify btn btn-xs btn-success channel_auth" style="margin-left: -38px;" onclick="MessageChannelSettings()">Set</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mtb15">
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
#DataBody .dataBase,#DataBody .webNote{
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="main-content pb55">
|
||||
<div class="container-fluid">
|
||||
<div class="pos-box bgw mtb15">
|
||||
@@ -17,40 +9,22 @@
|
||||
<a class="plr10 c4" href="/">{{data['lan']['H1']}}</a>/<span class="plr10 c4">{{data['lan']['H2']}}</span>
|
||||
</div>
|
||||
<div class="search pull-right">
|
||||
<form target="hid" onsubmit='database.get_list(1,$("#SearchValue").val())'>
|
||||
<form target="hid" onsubmit='return false;'>
|
||||
<input type="text" id="SearchValue" class="ser-text pull-left" placeholder="{{data['lan']['SEARCH']}}" />
|
||||
<button type="button" class="ser-sub pull-left" onclick='database.get_list(1,$("#SearchValue").val())'></button>
|
||||
<button type="button" class="ser-sub pull-left" onclick='database.database_table_view($("#SearchValue").val())'></button>
|
||||
</form>
|
||||
<iframe name='hid' id="hid" style="display:none"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="safe bgw mtb15 pd15">
|
||||
<div class="info-title-tips">
|
||||
<p><span class="glyphicon glyphicon-alert" style="color: #f39c12; margin-right: 10px;"></span> After adding the database, be sure to <a class="btlink" href="/crontab">[Cron]</a> page adds scheduled backup tasks to ensure your data security.</p>
|
||||
</div>
|
||||
<button onclick="database.add_database()" title="{{data['lan']['BTNT1']}}" class="btn btn-success btn-sm" type="button" style="margin-right: 5px;">{{data['lan']['BTN1']}}</button>
|
||||
<button onclick="bt.database.set_root()" title="{{data['lan']['BTNT2']}}" class="btn btn-default btn-sm" type="button" style="margin-right: 5px;">{{data['lan']['BTN2']}}</button>
|
||||
<button onclick="bt.database.open_phpmyadmin('','root','{{data['mysql_root']}}')" title="{{data['lan']['BTNT3']}}" class="btn btn-default btn-sm" type="button" style="margin-right: 5px;">{{data['lan']['BTN3']}}</button>
|
||||
<!--<a href="/adminer/index.php" target="_blank" title="Lightweight database management tool that supports remote database management" class="btn btn-default btn-sm" type="button" style="margin-right: 5px;">Adminer</a>-->
|
||||
<span style="float:right">
|
||||
<button batch="true" style="float: right;display: none;margin-left:10px;" onclick="database.batch_database('del');" title="{{data['lan']['BTNT4']}}" class="btn btn-default btn-sm">{{data['lan']['BTN4']}}</button>
|
||||
<button onclick="bt.recycle_bin.open_recycle_bin(6)" id="dataRecycle" title="{{data['lan']['BTNT4']}}" class="btn btn-default btn-sm" style="margin-left: 5px;"><span class="glyphicon glyphicon-trash" style="margin-right: 5px;"></span>{{data['lan']['RECYCLE_BIN']}}</button>
|
||||
</span>
|
||||
<div class="divtable mtb10">
|
||||
<div class="tablescroll">
|
||||
<table id="DataBody" class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0" style="border: 0 none;">
|
||||
</table>
|
||||
</div>
|
||||
<div id='databasePage' class="dataTables_paginate paging_bootstrap page">
|
||||
</div>
|
||||
<div class="table_toolbar">
|
||||
<span class="sync btn btn-default btn-sm" style="margin-right:5px" onclick="database.sync_to_database(1)" title="{{data['lan']['TP1']}}">{{data['lan']['SP1']}}</span>
|
||||
<span class="sync btn btn-default btn-sm" style="margin-right:5px" onclick="database.sync_to_database(0)" title="{{data['lan']['TP2']}}">{{data['lan']['SP2']}}</span>
|
||||
<span class="sync btn btn-default btn-sm" onclick="database.sync_database()" title="{{data['lan']['TP3']}}">{{data['lan']['SP3']}}</span>
|
||||
</div>
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-alert" style="color: #f39c12; margin-right: 10px;"></span> After adding the database, be sure to <a class="btlink" href="/crontab">[Cron]</a> page adds scheduled backup tasks to ensure your data security.
|
||||
</p>
|
||||
</div>
|
||||
<div id="bt_database_table"></div>
|
||||
</div>
|
||||
<form id="toPHPMyAdmin" public-data="{{session['phpmyadminDir']}}/index.php" action="/phpmyadmin/index.php" method="post" style="display: none;" target="_blank">
|
||||
<form id="toPHPMyAdmin" public-data="{{session['phpmyadminDir']}}/index.php" action="{{session['phpmyadminDir']}}/index.php" method="post" style="display: none;" target="_blank">
|
||||
<input type="text" name="pma_username" id="pma_username" value="" />
|
||||
<input type="password" name="pma_password" id="pma_password" value="" />
|
||||
<input type="text" name="server" value="1" />
|
||||
@@ -59,7 +33,6 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
@@ -72,7 +45,7 @@
|
||||
layer.msg('{{data["lan"]["JS1"]}}<a href="/soft#i" style="color:#20a53a;float: right;">{{data["lan"]["JS2"]}}</a>',{icon:7,shade: [0.3, '#000'],time:0});
|
||||
$(".layui-layer-shade").css("margin-left", "180px");
|
||||
{% else %}
|
||||
database.get_list();
|
||||
// database.get_list();
|
||||
{% endif %}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="main-content pb55" style="min-height: 525px;">
|
||||
<div class="container-fluid">
|
||||
<div class="site_table_view bgw mtb15 pd15">
|
||||
<div style="padding: 40px;background: #f8f8f8;text-align: center;vertical-align: middle;">
|
||||
<h1 style="font-size: 37px;">Sorry, you need to meet the following conditions to use:</h1>
|
||||
<p style="font-size: 20px;margin-top: 38px;color: red;">Please upgrade Nginx firewall version to 7.8.0 or above</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
@@ -59,6 +59,11 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav_group">
|
||||
<div class="nav_btn replace_content">
|
||||
<span class="nav_btn_title">Search Files Content</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav_group">
|
||||
<div class="nav_btn favorites_file_path">
|
||||
<span class="nav_btn_title" data-menu="favorites">Favorites</span><i class="iconfont icon-xiala"></i>
|
||||
@@ -113,7 +118,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav_group manage_backup">
|
||||
<div class="nav_btn" title="Backup permissions"><span class="glyphicon glyphicon-trash"></span><span class="nav_btn_title">Backup PMSN</span></div>
|
||||
<div class="nav_btn"><span class="glyphicon glyphicon-trash"></span><span class="nav_btn_title">Backup PMSN</span></div>
|
||||
</div>
|
||||
<div class="nav_group recycle_bin">
|
||||
<div class="nav_btn"><span class="glyphicon glyphicon-trash"></span><span class="nav_btn_title">Recycle bin</span></div>
|
||||
@@ -422,5 +427,5 @@
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/js/polyfill.js"></script>
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/ace/ace.js" defer></script>
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/ace/ext-language_tools.js" defer></script>
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/js/files.min.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/js/files.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
{% endblock %}
|
||||
@@ -419,7 +419,7 @@
|
||||
$('#ssh_root_login').change(function(){
|
||||
var start = $('#ssh_root_login').prop("checked");
|
||||
if(start){
|
||||
$.post('/config?action=get_settings',function(res){
|
||||
$.post('/config?action=get_settings2',function(res){
|
||||
if(res['user_mail']['user_name']){
|
||||
$.post('/ssh_security?action=start_jian', {}, function (res) {layer.msg(res.msg,{icon:res.status?1:2})})
|
||||
}else{
|
||||
|
||||
@@ -63,6 +63,40 @@
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
var recycle_bin_db_open = !!{{g['recycle_bin_db_open']}},recycle_bin_open = !!{{g['recycle_bin_open']}}
|
||||
var ie_version = (function() {
|
||||
var userAgent = navigator.userAgent,
|
||||
isLessIE11 = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1,
|
||||
isEdge = userAgent.indexOf('Edge') > -1 && !isLessIE11,
|
||||
isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1;
|
||||
if (isLessIE11) {
|
||||
var IEReg = new RegExp('MSIE (\\d+\\.\\d+);');
|
||||
IEReg.test(userAgent);
|
||||
var IEVersionNum = parseFloat(RegExp['$1']);
|
||||
if (IEVersionNum === 7) {// IE7
|
||||
return 7
|
||||
} else if (IEVersionNum === 8) {// IE8
|
||||
return 8
|
||||
} else if (IEVersionNum === 9) {// IE9
|
||||
return 9
|
||||
} else if (IEVersionNum === 10) { // IE10
|
||||
return 10
|
||||
} else {
|
||||
return 6
|
||||
}
|
||||
} else if (isEdge) { // edge
|
||||
return 'edge'
|
||||
} else if (isIE11) {// IE11
|
||||
return 11
|
||||
} else {// 不是ie浏览器
|
||||
return -1
|
||||
}
|
||||
}());
|
||||
if(ie_version != -1 && ie_version < 10 && ie_version != 'edge'){
|
||||
window.location.href = '/tips';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -97,64 +131,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{g.cdn_url}}/js/jquery-1.10.2.min.js"></script>
|
||||
<script src="{{g.cdn_url}}/layer/layer.js?version={{g['version']}}"></script>
|
||||
<script src="{{g.cdn_url}}/language/{{session['lan']}}/lan.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script src="{{g.cdn_url}}/js/clipboard.min.js" defer></script>
|
||||
<script src="{{g.cdn_url}}/laydate/laydate.js" defer></script>
|
||||
<script src="{{g.cdn_url}}/js/jquery.qrcode.min.js" defer></script>
|
||||
<!-- 以下文件未来将被剔除 -->
|
||||
<script src="{{g.cdn_url}}/js/bootstrap.min.js"></script>
|
||||
<script src="{{g.cdn_url}}/js/public.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script src="{{g.cdn_url}}/js/public_backup.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script src="{{g.cdn_url}}/codemirror/lib/codemirror.js" defer></script>
|
||||
<!-- End -->
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/js/tools.min.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script src="{{g.cdn_url}}/layer/layer.js?version={{g['version']}}"></script>
|
||||
<script src="{{g.cdn_url}}/language/{{session['lan']}}/lan.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script src="{{g.cdn_url}}/js/clipboard.min.js" defer></script>
|
||||
<script src="{{g.cdn_url}}/laydate/laydate.js" defer></script>
|
||||
<script src="{{g.cdn_url}}/js/jquery.qrcode.min.js" defer></script>
|
||||
<!-- 以下文件未来将被剔除 -->
|
||||
<script src="{{g.cdn_url}}/js/bootstrap.min.js"></script>
|
||||
<script src="{{g.cdn_url}}/js/public.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script src="{{g.cdn_url}}/js/public_backup.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script src="{{g.cdn_url}}/codemirror/lib/codemirror.js" defer></script>
|
||||
<!-- End -->
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/js/tools.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
{% for js_f in g['other_js'] %}
|
||||
<script type="text/javascript" src="{{js_f}}"></script>
|
||||
{% endfor %}
|
||||
{% block scripts %}{% endblock %}
|
||||
<script type="text/javascript">
|
||||
if (navigator.appName == "Microsoft Internet Explorer" && parseInt(navigator.appVersion.split(";")[1].replace(/[ ]/g, "").replace("MSIE", "")) < 9) {
|
||||
$(".main-content").css("margin-top", "50px");
|
||||
$('.top-tips').show();
|
||||
}
|
||||
function IEVersion() {
|
||||
// 取得浏览器的userAgent字符串
|
||||
var userAgent = navigator.userAgent;
|
||||
// 判断是否为小于IE11的浏览器
|
||||
var isLessIE11 = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1;
|
||||
// 判断是否为IE的Edge浏览器
|
||||
var isEdge = userAgent.indexOf('Edge') > -1 && !isLessIE11;
|
||||
// 判断是否为IE11浏览器
|
||||
var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1;
|
||||
if (isLessIE11) {
|
||||
var IEReg = new RegExp('MSIE (\\d+\\.\\d+);');
|
||||
// 正则表达式匹配浏览器的userAgent字符串中MSIE后的数字部分,,这一步不可省略!!!
|
||||
IEReg.test(userAgent);
|
||||
// 取正则表达式中第一个小括号里匹配到的值
|
||||
var IEVersionNum = parseFloat(RegExp['$1']);
|
||||
if (IEVersionNum === 7) {// IE7
|
||||
return 7
|
||||
} else if (IEVersionNum === 8) {// IE8
|
||||
return 8
|
||||
} else if (IEVersionNum === 9) {// IE9
|
||||
return 9
|
||||
} else if (IEVersionNum === 10) { // IE10
|
||||
|
||||
return 10
|
||||
} else {// IE版本<7
|
||||
return 6
|
||||
}
|
||||
} else if (isEdge) { // edge
|
||||
|
||||
return 'edge'
|
||||
} else if (isIE11) {// IE11
|
||||
return 11
|
||||
} else {// 不是ie浏览器
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if(IEVersion() != -1 && IEVersion() < 9){
|
||||
$(".main-content").css("margin-top", "50px");
|
||||
$('.top-tips').show();
|
||||
}
|
||||
$("#setBox").click(function() {
|
||||
if ($(this).prop("checked")) {
|
||||
$("input[name=id]").prop("checked", true);
|
||||
@@ -280,6 +273,7 @@
|
||||
});
|
||||
task_stat();
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -29,7 +29,19 @@
|
||||
<div class="info-title-tips">
|
||||
<p><span class="glyphicon glyphicon-alert" style="color: #f39c12; margin-right: 10px;"></span> {{data['lan']['PS']}}after the site is successfully established, please<a class="btlink" href="/crontab">[Cron]</a>add scheduled backup tasks to the page!</p>
|
||||
</div>
|
||||
<div id="bt_site_table"></div>
|
||||
<div class="tab-nav" id="cutMode">
|
||||
<span class="on">PHP Project</span>
|
||||
<span >Node Project</span>
|
||||
</div>
|
||||
<div class="tab-con" style="padding:10px 0;overflow: inherit;">
|
||||
<div class="tab-con-block">
|
||||
<div id="bt_site_table"></div>
|
||||
</div>
|
||||
<div class="tab-con-block ">
|
||||
<div id="bt_node_table"></div>
|
||||
</div>
|
||||
<div class="mask_layer hide"><div class="prompt_description"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,6 +49,25 @@
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
bt.set_cookie('sites_path', "{{session['config']['sites_path']}}");
|
||||
bt.set_cookie('serverType', "{{session['webserver']}}");
|
||||
var isSetup = "{{data['isSetup']}}" === 'True'?true:false
|
||||
// {% if not data['isSetup'] %}
|
||||
// layer.msg(lan.site.install_web_server_first+'<a href="/soft" style="color:#20a53a; float: right;">'+lan.site.to_install+'</a>', { icon: 7, shade: [0.3, '#000'], time: 0 });
|
||||
// $(".layui-layer-shade").css("margin-left", "180px");
|
||||
// {% else %}
|
||||
// //site.get_list();
|
||||
// site.plugin_firewall();
|
||||
// {% endif %}
|
||||
// function reverse(array){
|
||||
// var reverse_array = [];
|
||||
// for(var i = array.length - 1; i >= 0 ; i-- ){
|
||||
// reverse_array.push(array[i]);
|
||||
// }
|
||||
// return reverse_array
|
||||
// }
|
||||
</script>
|
||||
<script type="text/javascript" src="/static/laydate/laydate.js?date=20180301"></script>
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/js/site.js?version={{g['version']}}&repair={{data['js_random']}}"></script>
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/ace/ace.js?version={{g['version']}}" defer></script>
|
||||
@@ -44,23 +75,6 @@
|
||||
<script type="text/javascript" src="{{g.cdn_url}}/js/bootstrap-select.min.js" defer></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
bt.set_cookie('sites_path', "{{session['config']['sites_path']}}");
|
||||
bt.set_cookie('serverType', "{{session['webserver']}}");
|
||||
|
||||
{% if not data['isSetup'] %}
|
||||
layer.msg(lan.site.install_web_server_first+'<a href="/soft" style="color:#20a53a; float: right;">'+lan.site.to_install+'</a>', { icon: 7, shade: [0.3, '#000'], time: 0 });
|
||||
$(".layui-layer-shade").css("margin-left", "180px");
|
||||
{% else %}
|
||||
//site.get_list();
|
||||
site.plugin_firewall();
|
||||
{% endif %}
|
||||
function reverse(array){
|
||||
var reverse_array = [];
|
||||
for(var i = array.length - 1; i >= 0 ; i-- ){
|
||||
reverse_array.push(array[i]);
|
||||
}
|
||||
return reverse_array
|
||||
}
|
||||
|
||||
if(isSetup) site.plugin_firewall()
|
||||
</script>
|
||||
{% endblock %}
|
||||
+77
-21
@@ -82,7 +82,7 @@ class acme_v2:
|
||||
return self._apis
|
||||
|
||||
# 尝试从云端获取
|
||||
res = requests.get(self._url)
|
||||
res = requests.get(self._url,verify=False)
|
||||
if not res.status_code in [200, 201]:
|
||||
result = res.json()
|
||||
if "type" in result:
|
||||
@@ -754,7 +754,8 @@ fullchain.pem Paste into certificate input box
|
||||
# 替换服务器上的同域名同品牌证书
|
||||
def sub_all_cert(self, key_file, pem_file):
|
||||
cert_init = self.get_cert_init(pem_file) # 获取新证书的基本信息
|
||||
paths = ['vhost/cert', 'vhost/ssl']
|
||||
paths = ['/www/server/panel/vhost/cert', '/www/server/panel/vhost/ssl','/www/server/panel']
|
||||
is_panel = False
|
||||
for path in paths:
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
@@ -765,7 +766,12 @@ fullchain.pem Paste into certificate input box
|
||||
to_info = to_path + '/info.json'
|
||||
# 判断目标证书是否存在
|
||||
if not os.path.exists(to_pem_file):
|
||||
continue
|
||||
if not p_name in ['ssl']: continue
|
||||
to_pem_file = to_path + '/certificate.pem'
|
||||
to_key_file = to_path + '/privateKey.pem'
|
||||
if not os.path.exists(to_pem_file):
|
||||
continue
|
||||
is_panel = True
|
||||
# 获取目标证书的基本信息
|
||||
to_cert_init = self.get_cert_init(to_pem_file)
|
||||
# 判断证书品牌是否一致
|
||||
@@ -793,6 +799,7 @@ fullchain.pem Paste into certificate input box
|
||||
write_log(public.getMsg('ACME_CERT_REPLACE',(to_path,)))
|
||||
# 重载web服务
|
||||
public.serviceReload()
|
||||
if is_panel: public.restart_panel()
|
||||
|
||||
# 检查指定证书是否在订单列表
|
||||
def check_order_exists(self, pem_file):
|
||||
@@ -1251,8 +1258,6 @@ fullchain.pem Paste into certificate input box
|
||||
def apply_cert(self, domains, auth_type='dns', auth_to='Dns_com|None|None', **args):
|
||||
write_log("", "wb+")
|
||||
try:
|
||||
if 'auto_wildcard' in args and args['auto_wildcard']:
|
||||
self._auto_wildcard = True
|
||||
self.get_apis()
|
||||
index = None
|
||||
if 'index' in args:
|
||||
@@ -1289,23 +1294,42 @@ fullchain.pem Paste into certificate input box
|
||||
# 申请证书 - api
|
||||
def apply_cert_api(self, args):
|
||||
# 是否为指定站点
|
||||
if re.match(r"^\d+$", args.auth_to):
|
||||
import panelSite
|
||||
path = public.M('sites').where('id=?',(args.id,)).getField('path')
|
||||
args.auth_to = path + '/' + panelSite.panelSite().GetRunPath(args)
|
||||
args.auth_to = args.auth_to.replace("//","/")
|
||||
if args.auth_to[-1] == '/':
|
||||
args.auth_to = args.auth_to[:-1]
|
||||
if public.M('sites').where('id=? and project_type=?', (args.id, 'Java')).count():
|
||||
project_info = public.M('sites').where('id=?', (args.id,)).getField('project_config')
|
||||
try:
|
||||
project_info = json.loads(project_info)
|
||||
if not 'ssl_path' in project_info:
|
||||
return public.returnMsg(False, 'There is a problem with the current Java project configuration file, please rebuild')
|
||||
if not os.path.exists(project_info['ssl_path']):
|
||||
os.makedirs(project_info['ssl_path'])
|
||||
path = project_info['ssl_path']
|
||||
args.auth_to=path
|
||||
check_result = self.check_auth_env(args)
|
||||
if check_result: return check_result
|
||||
|
||||
if not os.path.exists(args.auth_to):
|
||||
return public.returnMsg(False, 'ACME_DIR_ERR')
|
||||
|
||||
check_result = self.check_auth_env(args)
|
||||
if check_result: return check_result
|
||||
|
||||
if args.auto_wildcard == '1':
|
||||
self._auto_wildcard = True
|
||||
return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to)
|
||||
if args.auto_wildcard == '1':
|
||||
self._auto_wildcard = True
|
||||
return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to)
|
||||
except:
|
||||
return public.returnMsg(False, 'There is a problem with the current Java project configuration file, please rebuild')
|
||||
else:
|
||||
if re.match(r"^\d+$", args.auth_to):
|
||||
import panelSite
|
||||
path = public.M('sites').where('id=?', (args.id,)).getField('path')
|
||||
args.auth_to = path + '/' + panelSite.panelSite().GetRunPath(args)
|
||||
args.auth_to = args.auth_to.replace("//", "/")
|
||||
if args.auth_to[-1] == '/':
|
||||
args.auth_to = args.auth_to[:-1]
|
||||
|
||||
if not os.path.exists(args.auth_to):
|
||||
return public.returnMsg(False, 'ACME_DIR_ERR')
|
||||
|
||||
check_result = self.check_auth_env(args)
|
||||
if check_result: return check_result
|
||||
|
||||
if args.auto_wildcard == '1':
|
||||
self._auto_wildcard = True
|
||||
return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to)
|
||||
|
||||
#检查认证环境
|
||||
def check_auth_env(self,args):
|
||||
@@ -1458,6 +1482,7 @@ fullchain.pem Paste into certificate input box
|
||||
if self._config['orders'][i]['cert_timeout'] > s_time or self._config['orders'][i]['auth_to'] == 'dns':
|
||||
continue
|
||||
if self.find_site_stopped(self._config['orders'][i]['domains']) == '0':
|
||||
write_log("|-The website has been suspended, skip certificate renewal!")
|
||||
continue
|
||||
|
||||
#已删除的网站直接跳过续签
|
||||
@@ -1466,6 +1491,21 @@ fullchain.pem Paste into certificate input box
|
||||
auth_to = self.get_ssl_used_site(self._config['orders'][i]['save_path'])
|
||||
if not auth_to: continue
|
||||
self._config['orders'][i]['auth_to'] = auth_to
|
||||
|
||||
# 是否到了允许重试的时间
|
||||
if 'next_retry_time' in self._config['orders'][i]:
|
||||
timeout = self._config['orders'][i]['next_retry_time'] - int(time.time())
|
||||
if timeout > 0:
|
||||
write_log('|-The domain name skipped this time: {}, because the last renewal failed, you still need to wait {} hours and try again'.format(self._config['orders'][index]['domains'],int(timeout / 60 / 60)))
|
||||
continue
|
||||
|
||||
# 是否到了最大重试次数
|
||||
if 'retry_count' in self._config['orders'][i]:
|
||||
if self._config['orders'][i]['retry_count'] >= 3:
|
||||
write_log('|-Skip the domain name this time: {}, this certificate will not be renewed due to failure to renew 3 times in a row'.format(self._config['orders'][index]['domains']))
|
||||
continue
|
||||
|
||||
# 加入到续签订单
|
||||
order_index.append(i)
|
||||
|
||||
if not order_index:
|
||||
@@ -1500,11 +1540,27 @@ fullchain.pem Paste into certificate input box
|
||||
write_log(public.getMsg('ACME_DOWNLOAD_CERT'))
|
||||
cert = self.download_cert(index)
|
||||
self._config['orders'][index]['renew_time'] = int(time.time())
|
||||
|
||||
# 清理失败重试记录
|
||||
self._config['orders'][index]['retry_count'] = 0
|
||||
self._config['orders'][index]['next_retry_time'] = 0
|
||||
|
||||
# 保存证书配置
|
||||
self.save_config()
|
||||
cert['status'] = True
|
||||
cert['msg'] = public.getMsg('ACME_RENEW_SUCCESS')
|
||||
write_log(public.getMsg('ACME_RENEW_SUCCESS1'))
|
||||
except Exception as e:
|
||||
if str(e).find('请稍候重试') == -1: # 受其它证书影响和连接CA失败的的不记录重试次数
|
||||
# 设置下次重试时间
|
||||
self._config['orders'][index]['next_retry_time'] = int(time.time() + (86400 * 2))
|
||||
# 记录重试次数
|
||||
if not 'retry_count' in self._config['orders'][index].keys():
|
||||
self._config['orders'][index]['retry_count'] = 1
|
||||
self._config['orders'][index]['retry_count'] += 1
|
||||
# 保存证书配置
|
||||
self.save_config()
|
||||
|
||||
write_log("|-" + str(e).split('>>>>')[0])
|
||||
write_log("-" * 70)
|
||||
return cert
|
||||
|
||||
+69
-25
@@ -333,7 +333,38 @@ class ajax:
|
||||
def get_load_average(self,get):
|
||||
data = public.M('load_average').dbfile('system').where("addtime>=? AND addtime<=?",(get.start,get.end)).field('id,pro,one,five,fifteen,addtime').order('id asc').select()
|
||||
return self.ToAddtime(data)
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def ToAddtime(self,data,tomem = False):
|
||||
import time
|
||||
@@ -349,12 +380,16 @@ class ajax:
|
||||
if length > 10000: he = 15
|
||||
if he == 1:
|
||||
for i in range(length):
|
||||
data[i]['addtime'] = time.strftime('%m/%d %H:%M',time.localtime(float(data[i]['addtime'])))
|
||||
if tomem and data[i]['mem'] > 100: data[i]['mem'] = data[i]['mem'] / mPre
|
||||
if tomem in [None]:
|
||||
if type(data[i]['down_packets']) == str:
|
||||
data[i]['down_packets'] = json.loads(data[i]['down_packets'])
|
||||
data[i]['up_packets'] = json.loads(data[i]['up_packets'])
|
||||
try:
|
||||
data[i]['addtime'] = time.strftime('%m/%d %H:%M',time.localtime(float(data[i]['addtime'])))
|
||||
if 'process_list' in data[i]:
|
||||
data[i]['process_list'] = json.loads(data[i]['process_list'])
|
||||
if tomem and data[i]['mem'] > 100: data[i]['mem'] = data[i]['mem'] / mPre
|
||||
if tomem in [None]:
|
||||
if type(data[i]['down_packets']) == str:
|
||||
data[i]['down_packets'] = json.loads(data[i]['down_packets'])
|
||||
data[i]['up_packets'] = json.loads(data[i]['up_packets'])
|
||||
except: continue
|
||||
return data
|
||||
else:
|
||||
count = 0
|
||||
@@ -363,14 +398,16 @@ class ajax:
|
||||
if count < he:
|
||||
count += 1
|
||||
continue
|
||||
value['addtime'] = time.strftime('%m/%d %H:%M',time.localtime(float(value['addtime'])))
|
||||
if tomem and value['mem'] > 100: value['mem'] = value['mem'] / mPre
|
||||
if tomem in [None]:
|
||||
if type(value['down_packets']) == str:
|
||||
value['down_packets'] = json.loads(value['down_packets'])
|
||||
value['up_packets'] = json.loads(value['up_packets'])
|
||||
tmp.append(value)
|
||||
count = 0
|
||||
try:
|
||||
value['addtime'] = time.strftime('%m/%d %H:%M',time.localtime(float(value['addtime'])))
|
||||
if tomem and value['mem'] > 100: value['mem'] = value['mem'] / mPre
|
||||
if tomem in [None]:
|
||||
if type(value['down_packets']) == str:
|
||||
value['down_packets'] = json.loads(value['down_packets'])
|
||||
value['up_packets'] = json.loads(value['up_packets'])
|
||||
tmp.append(value)
|
||||
count = 0
|
||||
except: continue
|
||||
return tmp
|
||||
|
||||
def GetInstalleds(self,softlist):
|
||||
@@ -578,19 +615,25 @@ class ajax:
|
||||
phpini = public.readFile(filename)
|
||||
data = {}
|
||||
rep = "disable_functions\s*=\s{0,1}(.*)\n"
|
||||
tmp = re.search(rep,phpini).groups()
|
||||
data['disable_functions'] = tmp[0]
|
||||
|
||||
tmp = re.search(rep,phpini)
|
||||
if tmp:
|
||||
data['disable_functions'] = tmp.groups()[0]
|
||||
|
||||
rep = "upload_max_filesize\s*=\s*([0-9]+)(M|m|K|k)"
|
||||
tmp = re.search(rep,phpini).groups()
|
||||
data['max'] = tmp[0]
|
||||
|
||||
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).groups()
|
||||
if tmp[0] == '0':
|
||||
data['pathinfo'] = False
|
||||
else:
|
||||
data['pathinfo'] = True
|
||||
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 = []
|
||||
@@ -1176,7 +1219,7 @@ class ajax:
|
||||
if os.path.exists(u_path): os.remove(u_path)
|
||||
return public.returnMsg(False,'AJAX_USER_BE_OVERDUE')
|
||||
url_headers = {"authorization":"bt {}".format(userInfo['token'])}
|
||||
resp = requests.post('{}/api/user/verifyToken'.format(self.__official_url),headers=url_headers)
|
||||
resp = requests.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)
|
||||
@@ -1185,6 +1228,7 @@ class ajax:
|
||||
session[m_key] = public.returnMsg(True,'AJAX_USER_IS_VALID')
|
||||
return session[m_key]
|
||||
|
||||
|
||||
#PHP探针
|
||||
def php_info(self,args):
|
||||
php_version = args.php_version.replace('.','')
|
||||
|
||||
+16
-14
@@ -26,7 +26,7 @@ class panelSetup:
|
||||
ua = g.ua.lower()
|
||||
if ua.find('spider') != -1 or g.ua.find('bot') != -1:
|
||||
return redirect('https://www.google.com')
|
||||
g.version = '6.8.14'
|
||||
g.version = '6.8.16'
|
||||
g.title = public.GetConfigValue('title')
|
||||
g.uri = request.path
|
||||
g.debug = os.path.exists('data/debug.pl')
|
||||
@@ -44,6 +44,11 @@ class panelSetup:
|
||||
g.cdn_url = '/static'
|
||||
session['title'] = g.title
|
||||
|
||||
g.recycle_bin_open = 0
|
||||
if os.path.exists("data/recycle_bin.pl"): g.recycle_bin_open = 1
|
||||
|
||||
g.recycle_bin_db_open = 0
|
||||
if os.path.exists("data/recycle_bin_db.pl"): g.recycle_bin_db_open = 1
|
||||
g.is_aes = False
|
||||
self.other_import()
|
||||
return None
|
||||
@@ -150,31 +155,27 @@ class panelAdmin(panelSetup):
|
||||
g.api_request = True
|
||||
else:
|
||||
if session['login'] == False:
|
||||
public.WriteLog('Login auth', 'The current session has been logged out')
|
||||
session.clear()
|
||||
return redirect('/login')
|
||||
|
||||
if 'tmp_login_expire' in session:
|
||||
s_file = 'data/session/{}'.format(session['tmp_login_id'])
|
||||
if session['tmp_login_expire'] < time.time():
|
||||
public.WriteLog('Login auth', 'Temporary authorization has expired {}'.format(public.get_client_ip()))
|
||||
session.clear()
|
||||
if os.path.exists(s_file): os.remove(s_file)
|
||||
return redirect('/login')
|
||||
if not os.path.exists(s_file):
|
||||
public.WriteLog('Login auth', 'Forced withdrawal due to cancellation of temporary authorization {}'.format(public.get_client_ip()))
|
||||
session.clear()
|
||||
return redirect('/login')
|
||||
ua_md5 = public.md5(g.ua)
|
||||
if ua_md5 != session.get('login_user_agent',ua_md5):
|
||||
public.WriteLog('Login auth', 'UA verification failed {}'.format(public.get_client_ip()))
|
||||
session.clear()
|
||||
return redirect('/login')
|
||||
|
||||
if api_check:
|
||||
now_time = time.time()
|
||||
session_timeout = session.get('session_timeout',0)
|
||||
if session_timeout < time.time() and session_timeout != 0:
|
||||
public.WriteLog('Login auth', 'The session has expired {}'.format(public.get_client_ip()))
|
||||
if session_timeout < now_time and session_timeout != 0:
|
||||
session.clear()
|
||||
return redirect('/login?dologin=True&go=0')
|
||||
|
||||
@@ -182,16 +183,17 @@ class panelAdmin(panelSetup):
|
||||
login_token = session.get('login_token','')
|
||||
if login_token:
|
||||
if login_token != public.get_login_token_auth():
|
||||
public.WriteLog('Login auth', 'Session ID does not match {}'.format(public.get_client_ip()))
|
||||
session.clear()
|
||||
return redirect('/login?dologin=True&go=1')
|
||||
|
||||
if api_check:
|
||||
filename = 'data/sess_files/' + public.get_sess_key()
|
||||
if not os.path.exists(filename):
|
||||
public.WriteLog('Login auth', 'Trigger CSRF defense {}'.format(public.get_client_ip()))
|
||||
session.clear()
|
||||
return redirect('/login?dologin=True&go=2')
|
||||
# if api_check:
|
||||
# filename = 'data/sess_files/' + public.get_sess_key()
|
||||
# if not os.path.exists(filename):
|
||||
# session.clear()
|
||||
# return redirect('/login?dologin=True&go=2')
|
||||
|
||||
# 标记新的会话过期时间
|
||||
session['session_timeout'] = time.time() + public.get_session_timeout()
|
||||
except:
|
||||
public.WriteLog('Login auth',public.get_error_info())
|
||||
session.clear()
|
||||
|
||||
+23
-3
@@ -55,6 +55,14 @@ class config:
|
||||
else:
|
||||
return public.returnMsg(True, 'EMAIL_NOT_EXISTS')
|
||||
|
||||
def del_tg_info(self,get):
|
||||
import panel_telegram_bot
|
||||
return panel_telegram_bot.panel_telegram_bot().del_tg_bot(get)
|
||||
|
||||
def set_tg_bot(self,get):
|
||||
import panel_telegram_bot
|
||||
return panel_telegram_bot.panel_telegram_bot().set_tg_bot(get)
|
||||
|
||||
#添加接受邮件地址
|
||||
def add_mail_address(self, get):
|
||||
if not hasattr(get, 'email'): return public.returnMsg(False, 'INPUT_EMAIL')
|
||||
@@ -139,6 +147,15 @@ class config:
|
||||
ret['user_mail'] = {"user_name": user_mail, "mail_list": self.__mail_list,"info":self.get_user_mail(get)}
|
||||
ret['dingding'] = {"dingding": dingding,"info":self.get_dingding(get)}
|
||||
return ret
|
||||
|
||||
def get_settings2(self, get=None):
|
||||
import panel_telegram_bot
|
||||
tg = panel_telegram_bot.panel_telegram_bot()
|
||||
tg = tg.get_tg_conf()
|
||||
conf = self.get_settings()
|
||||
conf['telegram'] = tg
|
||||
return conf
|
||||
|
||||
# 设置钉钉报警
|
||||
def set_dingding(self, get):
|
||||
if not (hasattr(get, 'url') or hasattr(get, 'atall')):
|
||||
@@ -179,7 +196,8 @@ class config:
|
||||
userInfo = public.M('users').where("id=?",(1,)).field('username,password').find()
|
||||
token = public.Md5(userInfo['username'] + '/' + userInfo['password'])
|
||||
public.writeFile(self._setup_path+'/data/login_token.pl',token)
|
||||
|
||||
skey = 'login_token'
|
||||
cache.set(skey,token)
|
||||
sess_path = 'data/sess_files'
|
||||
if not os.path.exists(sess_path):
|
||||
os.makedirs(sess_path,384)
|
||||
@@ -286,6 +304,9 @@ class config:
|
||||
|
||||
def setPanel(self,get):
|
||||
if not public.IsRestart(): return public.returnMsg(False,'EXEC_ERR_TASK')
|
||||
if 'limitip' in get:
|
||||
if get.limitip.find('/') != -1:
|
||||
return public.returnMsg(False,'The authorized IP format is incorrect, and the subnet segment writing is not supported')
|
||||
isReWeb = False
|
||||
sess_out_path = 'data/session_timeout.pl'
|
||||
if 'session_timeout' in get:
|
||||
@@ -733,8 +754,6 @@ class config:
|
||||
|
||||
#设置面板SSL
|
||||
def SetPanelSSL(self,get):
|
||||
ssl_path = "{}/ssl".format(public.get_panel_path())
|
||||
if not os.path.exists(ssl_path): os.makedirs(ssl_path,384)
|
||||
if hasattr(get,"email"):
|
||||
#rep_mail = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$"
|
||||
rep_mail = r"[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?"
|
||||
@@ -748,6 +767,7 @@ class config:
|
||||
sslConf = self._setup_path+'/data/ssl.pl'
|
||||
if os.path.exists(sslConf):
|
||||
public.ExecShell('rm -f ' + sslConf)
|
||||
g.rm_ssl = True
|
||||
return public.returnMsg(True,'PANEL_SSL_CLOSE')
|
||||
else:
|
||||
public.ExecShell('pip install cffi')
|
||||
|
||||
+3
-1
@@ -121,6 +121,7 @@ class crontab:
|
||||
def set_cron_status(self,get):
|
||||
id = get['id']
|
||||
cronInfo = public.M('crontab').where('id=?',(id,)).field(self.field).find()
|
||||
status_msg = ['Stop','Start']
|
||||
status = 1
|
||||
if cronInfo['status'] == status:
|
||||
status = 0
|
||||
@@ -130,7 +131,7 @@ class crontab:
|
||||
self.sync_to_crond(cronInfo)
|
||||
|
||||
public.M('crontab').where('id=?',(id,)).setField('status',status)
|
||||
public.WriteLog('TYPE_CRON',"MODIFY_CRON_STATUS",(cronInfo['name'],str(status)))
|
||||
public.WriteLog('TYPE_CRON',"MODIFY_CRON_STATUS",(cronInfo['name'],str(status_msg[status])))
|
||||
return public.returnMsg(True,'SET_SUCCESS')
|
||||
|
||||
#修改计划任务
|
||||
@@ -290,6 +291,7 @@ class crontab:
|
||||
data['orderOpt'] = []
|
||||
import json
|
||||
tmp = public.readFile('data/libList.conf')
|
||||
if not tmp: return data
|
||||
libs = json.loads(tmp)
|
||||
for lib in libs:
|
||||
if not 'opt' in lib: continue
|
||||
|
||||
+8
-1
@@ -294,7 +294,14 @@ class data:
|
||||
where = type_where
|
||||
else:
|
||||
where += " and " + type_where
|
||||
|
||||
if get.table == 'sites':
|
||||
if where:
|
||||
where = "({}) AND project_type='PHP'".format(where)
|
||||
else:
|
||||
where = "project_type='PHP'"
|
||||
|
||||
|
||||
|
||||
field = self.GetField(get.table)
|
||||
#实例化数据库对象
|
||||
|
||||
|
||||
+111
-66
@@ -10,7 +10,7 @@
|
||||
#------------------------------
|
||||
# 数据库管理类
|
||||
#------------------------------
|
||||
import public,db,re,time,os,sys,panelMysql
|
||||
import public,db,re,time,os,sys,panelMysql,json
|
||||
from BTPanel import session
|
||||
import datatool
|
||||
class database(datatool.datatools):
|
||||
@@ -30,7 +30,7 @@ class database(datatool.datatools):
|
||||
ssl = get.ssl
|
||||
if ssl == "REQUIRE SSL" and not self.check_mysql_ssl_status(get):
|
||||
return public.returnMsg(False,'MYSQL_SSL_ERR')
|
||||
data_name = get['name'].strip()
|
||||
data_name = get['name'].strip().lower()
|
||||
if self.CheckRecycleBin(data_name): return public.returnMsg(False,'DATABASE_DEL_RECYCLE_BIN',(data_name,))
|
||||
if len(data_name) > 64: return public.returnMsg(False, 'DATABASE_NAME_LEN')
|
||||
reg = r"^[\w\.-]+$"
|
||||
@@ -332,7 +332,20 @@ SetLink
|
||||
panelMysql.panelMysql().execute("drop user '" + username + "'@'" + us[0] + "'")
|
||||
panelMysql.panelMysql().execute("flush privileges")
|
||||
rPath = '/www/Recycle_bin/'
|
||||
public.writeFile(rPath + 'BTDB_' + name +'_t_' + str(time.time()),json.dumps(data))
|
||||
data['rmtime'] = int(time.time())
|
||||
rm_path = '{}/BTDB_{}_t_{}'.format(rPath,name,data['rmtime'])
|
||||
if os.path.exists(rm_path): rm_path += '.1'
|
||||
rm_config_file = '{}/config.json'.format(rm_path)
|
||||
datadir = public.get_datadir()
|
||||
db_path = '{}/{}'.format(datadir,name)
|
||||
if not os.path.exists(db_path):
|
||||
return public.returnMsg(False,'Means that the database data does not exist!')
|
||||
|
||||
public.ExecShell("mv -f {} {}".format(db_path,rm_path))
|
||||
if not os.path.exists(rm_path):
|
||||
return public.returnMsg(False,'Failed to move database data to the recycle bin!')
|
||||
public.writeFile(rm_config_file,json.dumps(data))
|
||||
# public.writeFile(rPath + 'BTDB_' + name +'_t_' + str(time.time()),json.dumps(data))
|
||||
public.M('databases').where("name=?",(name,)).delete()
|
||||
public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS',(name,))
|
||||
return public.returnMsg(True,'RECYCLE_BIN_DB')
|
||||
@@ -340,39 +353,66 @@ SetLink
|
||||
#永久删除数据库
|
||||
def DeleteTo(self,filename):
|
||||
import json
|
||||
data = json.loads(public.readFile(filename))
|
||||
if public.M('databases').where("name=?",( data['name'],)).count():
|
||||
if os.path.isfile(filename):
|
||||
data = json.loads(public.readFile(filename))
|
||||
if public.M('databases').where("name=?",( data['name'],)).count():
|
||||
os.remove(filename)
|
||||
return public.returnMsg(True,'DEL_SUCCESS')
|
||||
result = panelMysql.panelMysql().execute("drop database `" + data['name'] + "`")
|
||||
isError=self.IsSqlError(result)
|
||||
if isError != None: return isError
|
||||
panelMysql.panelMysql().execute("drop user '" + data['username'] + "'@'localhost'")
|
||||
users = panelMysql.panelMysql().query("select Host from mysql.user where User='" + data['username'] + "' AND Host!='localhost'")
|
||||
for us in users:
|
||||
panelMysql.panelMysql().execute("drop user '" + data['username'] + "'@'" + us[0] + "'")
|
||||
panelMysql.panelMysql().execute("flush privileges")
|
||||
os.remove(filename)
|
||||
return public.returnMsg(True,'DEL_SUCCESS')
|
||||
result = panelMysql.panelMysql().execute("drop database `" + data['name'] + "`")
|
||||
isError=self.IsSqlError(result)
|
||||
if isError != None: return isError
|
||||
panelMysql.panelMysql().execute("drop user '" + data['username'] + "'@'localhost'")
|
||||
users = panelMysql.panelMysql().query("select Host from mysql.user where User='" + data['username'] + "' AND Host!='localhost'")
|
||||
for us in users:
|
||||
panelMysql.panelMysql().execute("drop user '" + data['username'] + "'@'" + us[0] + "'")
|
||||
panelMysql.panelMysql().execute("flush privileges")
|
||||
os.remove(filename)
|
||||
public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS',(data['name'],))
|
||||
else:
|
||||
import shutil
|
||||
if os.path.exists(filename):
|
||||
data = json.loads(public.readFile(filename + '/config.json'))
|
||||
shutil.rmtree(filename)
|
||||
try:
|
||||
public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS',(data['name'],))
|
||||
except:
|
||||
pass
|
||||
return public.returnMsg(True,'DEL_SUCCESS')
|
||||
|
||||
#恢复数据库
|
||||
def RecycleDB(self,filename):
|
||||
import json
|
||||
data = json.loads(public.readFile(filename))
|
||||
_isdir = False
|
||||
if os.path.isfile(filename):
|
||||
data = json.loads(public.readFile(filename))
|
||||
else:
|
||||
re_config_file = filename + '/config.json'
|
||||
data = json.loads(public.readFile(re_config_file))
|
||||
db_path = "{}/{}".format(public.get_datadir(),data['name'])
|
||||
if os.path.exists(db_path):
|
||||
return public.returnMsg(False,'There is a database with the same name in the current database. To ensure data security, stop recovery!')
|
||||
_isdir = True
|
||||
|
||||
if public.M('databases').where("name=?",( data['name'],)).count():
|
||||
os.remove(filename)
|
||||
if not _isdir: os.remove(filename)
|
||||
return public.returnMsg(True,'RECYCLEDB')
|
||||
|
||||
|
||||
if not _isdir:
|
||||
os.remove(filename)
|
||||
else:
|
||||
public.ExecShell('mv -f {} {}'.format(filename,db_path))
|
||||
if not os.path.exists(db_path):
|
||||
return public.returnMsg(False,'Data recovery failed!')
|
||||
db_config_file = "{}/config.json".format(db_path)
|
||||
if os.path.exists(db_config_file): os.remove(db_config_file)
|
||||
|
||||
# 设置文件权限
|
||||
public.ExecShell("chown -R mysql:mysql {}".format(db_path))
|
||||
public.ExecShell("chmod -R 660 {}".format(db_path))
|
||||
public.ExecShell("chmod 700 {}".format(db_path))
|
||||
|
||||
self.__CreateUsers(data['name'],data['username'],data['password'],data['accept'])
|
||||
#result = panelMysql.panelMysql().execute("grant all privileges on `" + data['name'] + "`.* to '" + data['username'] + "'@'localhost' identified by '" + data['password'] + "'")
|
||||
#isError=self.IsSqlError(result)
|
||||
#if isError != None: return isError
|
||||
#panelMysql.panelMysql().execute("grant all privileges on `" + data['name'] + "`.* to '" + data['username'] + "'@'" + data['accept'] + "' identified by '" + data['password'] + "'")
|
||||
#panelMysql.panelMysql().execute("flush privileges")
|
||||
|
||||
public.M('databases').add('id,pid,name,username,password,accept,ps,addtime',(data['id'],data['pid'],data['name'],data['username'],data['password'],data['accept'],data['ps'],data['addtime']))
|
||||
os.remove(filename)
|
||||
return public.returnMsg(True,"RECYCLEDB")
|
||||
|
||||
#设置ROOT密码
|
||||
@@ -965,49 +1005,54 @@ SetLink
|
||||
else:
|
||||
return public.returnMsg(False,"CHANVE_FAIL")
|
||||
|
||||
# 检查用户管理表是否存在
|
||||
def _check_table_exist(self):
|
||||
result = public.M('sqlite_master').where("name=?", ('mysql_user',)).getField('name')
|
||||
if not result:
|
||||
self._create_mysql_user_tb()
|
||||
def get_average_num(self,slist):
|
||||
"""
|
||||
@获取平均值
|
||||
"""
|
||||
count = len(slist)
|
||||
limit_size = 1 * 1024 * 1024
|
||||
if count <= 0: return limit_size
|
||||
|
||||
# 数据库对象
|
||||
def _get_sqlite_connect(self):
|
||||
import sqlite3
|
||||
try:
|
||||
if not self.sqlite_connection:
|
||||
self.sqlite_connection = sqlite3.connect('data/default.db')
|
||||
except Exception as ex:
|
||||
return "error: " + str(ex)
|
||||
if len(slist) > 1:
|
||||
slist = sorted(slist)
|
||||
limit_size =int((slist[0] + slist[-1])/2 * 0.85)
|
||||
return limit_size
|
||||
|
||||
# 创多用户表
|
||||
def _create_mysql_user_tb(self):
|
||||
self._get_sqlite_connect()
|
||||
sql="""
|
||||
CREATE TABLE mysql_user(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
pid INTEGER ,
|
||||
username CHAR,
|
||||
password CHAR,
|
||||
accept CHAR,
|
||||
ps CHAR,
|
||||
addtime CHAR
|
||||
);"""
|
||||
self.sqlite_connection.execute(sql)
|
||||
def get_database_size(self,get):
|
||||
"""
|
||||
获取数据库大小
|
||||
"""
|
||||
result = {}
|
||||
tables = public.get_database_size()
|
||||
data = public.M('databases').field('id,pid,name,ps,addtime').select()
|
||||
public.print_log(data)
|
||||
for x in data:
|
||||
name = x['name']
|
||||
x['total'] = 0
|
||||
x['backup_count'] = public.M('backup').where("pid=? AND type=?",(x['id'],'1')).count()
|
||||
if name in tables: x['total'] = tables[name]
|
||||
|
||||
# 根据id获取用户列表
|
||||
def get_mysql_user(self,get):
|
||||
self._create_mysql_user_tb()
|
||||
result = public.M('mysql_user').where("pid=?", (get.id,)).select()
|
||||
result[name] = x
|
||||
return result
|
||||
|
||||
def add_mysql_user(self,get):
|
||||
'''
|
||||
* 添加mysql用户
|
||||
* @param get.id 面板数据库id
|
||||
* @param get.username 添加的用户
|
||||
* @param get.password 添加的用户密码
|
||||
* @param get.permission 添加的用户权限
|
||||
* @param get.host 允许在哪里访问
|
||||
* @return Bool
|
||||
'''
|
||||
def check_del_data(self,get):
|
||||
"""
|
||||
@删除数据库前置检测
|
||||
"""
|
||||
ids = json.loads(get.ids)
|
||||
slist = {};result = [];db_list_size = []
|
||||
db_data = self.get_database_size(None)
|
||||
for key in db_data:
|
||||
data = db_data[key]
|
||||
if not data['id'] in ids: continue
|
||||
|
||||
db_addtime = public.to_date(times = data['addtime'])
|
||||
data['score'] = int(time.time() - db_addtime) + data['total']
|
||||
data['st_time'] = db_addtime
|
||||
|
||||
if data['total'] > 0 : db_list_size.append(data['total'])
|
||||
result.append(data)
|
||||
|
||||
slist['data'] = sorted(result,key= lambda x:x['score'],reverse=True)
|
||||
slist['db_size'] = self.get_average_num(db_list_size)
|
||||
return slist
|
||||
@@ -0,0 +1,337 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | 宝塔Linux面板
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: hwliang <hwl@bt.cn>
|
||||
# +-------------------------------------------------------------------
|
||||
import re, os, sys, public, json
|
||||
import pymysql
|
||||
|
||||
|
||||
class mysql:
|
||||
__DB_PASS = ''
|
||||
__DB_USER = ''
|
||||
__DB_NAME = ''
|
||||
__DB_PORT = 3306
|
||||
__DB_HOST = 'localhost'
|
||||
__DB_PREFIX = ''
|
||||
__DB_CONN = None
|
||||
__DB_CUR = None
|
||||
__DB_ERR = None
|
||||
__DB_NET = None
|
||||
__DB_TABLE = "" # 被操作的表名称
|
||||
__OPT_WHERE = "" # where条件
|
||||
__OPT_LIMIT = "" # limit条件
|
||||
__OPT_ORDER = "" # order条件
|
||||
__OPT_FIELD = "*" # field条件
|
||||
__OPT_PARAM = () # where值
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def set_name(self, name):
|
||||
self.__DB_NAME = name
|
||||
return self
|
||||
|
||||
def set_host(self, host, port, name, username, password, prefix=''):
|
||||
self.__DB_HOST = host
|
||||
self.__DB_PORT = port
|
||||
self.__DB_NAME = name
|
||||
self.__DB_USER = username
|
||||
self.__DB_PASS = password
|
||||
self.__DB_PREFIX = prefix
|
||||
return self
|
||||
|
||||
#连接MYSQL数据库
|
||||
def __GetConn(self):
|
||||
if self.__DB_NET: return True
|
||||
try:
|
||||
self.__DB_CONN = pymysql.connect(host=self.__DB_HOST,
|
||||
port=self.__DB_PORT,
|
||||
user=self.__DB_USER,
|
||||
passwd=self.__DB_PASS)
|
||||
|
||||
self.__DB_CUR = self.__DB_CONN.cursor()
|
||||
self.__DB_NET = True
|
||||
return True
|
||||
except pymysql.Error as e:
|
||||
self.__DB_ERR = e
|
||||
return False
|
||||
|
||||
def table(self, table):
|
||||
#设置表名
|
||||
self.__DB_TABLE = self.__DB_PREFIX + table
|
||||
return self
|
||||
|
||||
def where(self, where, param):
|
||||
#WHERE条件
|
||||
if where:
|
||||
self.__OPT_WHERE = " WHERE " + where
|
||||
self.__OPT_PARAM = self.__to_tuple(param)
|
||||
return self
|
||||
|
||||
def __to_tuple(self, param):
|
||||
#将参数转换为tuple
|
||||
if type(param) != tuple:
|
||||
if type(param) == list:
|
||||
param = tuple(param)
|
||||
else:
|
||||
param = (param, )
|
||||
return param
|
||||
|
||||
def order(self, order):
|
||||
#ORDER条件
|
||||
if len(order):
|
||||
self.__OPT_ORDER = " ORDER BY " + order
|
||||
return self
|
||||
|
||||
def limit(self, limit):
|
||||
#LIMIT条件
|
||||
limit = str(limit)
|
||||
if len(limit):
|
||||
self.__OPT_LIMIT = " LIMIT " + limit
|
||||
return self
|
||||
|
||||
def field(self, field):
|
||||
#FIELD条件
|
||||
if len(field):
|
||||
self.__OPT_FIELD = field
|
||||
return self
|
||||
|
||||
def select(self):
|
||||
#查询数据集
|
||||
self.__GetConn()
|
||||
try:
|
||||
self.__get_columns()
|
||||
sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + self.__OPT_WHERE + self.__OPT_ORDER + self.__OPT_LIMIT
|
||||
self.__DB_CUR.execute(sql, self.__OPT_PARAM)
|
||||
data = self.__DB_CUR.fetchall()
|
||||
#构造字典系列
|
||||
if self.__OPT_FIELD != "*":
|
||||
fields = self.__format_field(self.__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
|
||||
del (tmp)
|
||||
else:
|
||||
#将元组转换成列表
|
||||
tmp = list(map(list, data))
|
||||
data = tmp
|
||||
del (tmp)
|
||||
self.__close()
|
||||
return data
|
||||
except Exception as ex:
|
||||
return public.get_error_info()
|
||||
|
||||
def get(self):
|
||||
self.__get_columns()
|
||||
return self.select()
|
||||
|
||||
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 __get_columns(self):
|
||||
if self.__OPT_FIELD == '*':
|
||||
tmp_cols = self.query(
|
||||
"select COLUMN_NAME from information_schema.COLUMNS where table_name = '{}' and table_schema = '{}';"
|
||||
.format(self.__DB_TABLE, self.__DB_NAME), False)
|
||||
cols = []
|
||||
for col in tmp_cols:
|
||||
cols.append('`' + col[0] + '`')
|
||||
if len(cols) > 0: self.__OPT_FIELD = ','.join(cols)
|
||||
|
||||
def getField(self, keyName):
|
||||
#取回指定字段
|
||||
try:
|
||||
result = self.field(keyName).select()
|
||||
if len(result) != 0:
|
||||
return result[0][keyName]
|
||||
return result
|
||||
except:
|
||||
return None
|
||||
|
||||
def setField(self, keyName, keyValue):
|
||||
#更新指定字段
|
||||
return self.save(keyName, (keyValue, ))
|
||||
|
||||
def find(self):
|
||||
#取一行数据
|
||||
try:
|
||||
result = self.limit("1").select()
|
||||
if len(result) == 1:
|
||||
return result[0]
|
||||
return result
|
||||
except:
|
||||
return None
|
||||
|
||||
def count(self):
|
||||
#取行数
|
||||
key = "COUNT(*)"
|
||||
data = self.field(key).select()
|
||||
try:
|
||||
return int(data[0][key])
|
||||
except:
|
||||
return 0
|
||||
|
||||
def add(self, keys, param):
|
||||
#插入数据
|
||||
self.__GetConn()
|
||||
self.__DB_CONN.text_factory = str
|
||||
try:
|
||||
values = ""
|
||||
for key in keys.split(','):
|
||||
values += "%s,"
|
||||
values = values[0:len(values) - 1]
|
||||
sql = "INSERT INTO " + self.__DB_TABLE + "(" + keys + ") " + "VALUES(" + values + ")"
|
||||
self.__DB_CUR.execute(sql, self.__to_tuple(param))
|
||||
id = self.__DB_CUR.lastrowid
|
||||
self.__close()
|
||||
self.__DB_CONN.commit()
|
||||
return id
|
||||
except Exception as ex:
|
||||
return "error: " + str(ex)
|
||||
|
||||
#插入数据
|
||||
def insert(self, pdata):
|
||||
if not pdata: return False
|
||||
keys, param = self.__format_pdata(pdata)
|
||||
return self.add(keys, param)
|
||||
|
||||
#更新数据
|
||||
def update(self, pdata):
|
||||
if not pdata: return False
|
||||
keys, param = self.__format_pdata(pdata)
|
||||
return self.save(keys, param)
|
||||
|
||||
#构造数据
|
||||
def __format_pdata(self, pdata):
|
||||
keys = pdata.keys()
|
||||
keys_tmp = []
|
||||
for k in keys:
|
||||
keys_tmp.append("`{}`".format(k))
|
||||
keys_str = ','.join(keys_tmp)
|
||||
|
||||
param = []
|
||||
for k in keys:
|
||||
#if pdata[k] == None: pdata[k] = ''
|
||||
param.append(pdata[k])
|
||||
return keys_str, tuple(param)
|
||||
|
||||
def addAll(self, keys, param):
|
||||
#插入数据
|
||||
self.__GetConn()
|
||||
self.__DB_CONN.text_factory = str
|
||||
try:
|
||||
values = ""
|
||||
for key in keys.split(','):
|
||||
values += "%s,"
|
||||
values = values[0:len(values) - 1]
|
||||
sql = "INSERT INTO " + self.__DB_TABLE + "(" + keys + ") " + "VALUES(" + values + ")"
|
||||
result = self.__DB_CUR.execute(sql, self.__to_tuple(param))
|
||||
return True
|
||||
except Exception as ex:
|
||||
return "error: " + str(ex)
|
||||
|
||||
def commit(self):
|
||||
self.__close()
|
||||
self.__DB_CONN.commit()
|
||||
|
||||
def save(self, keys, param):
|
||||
#更新数据
|
||||
self.__GetConn()
|
||||
self.__DB_CONN.text_factory = str
|
||||
try:
|
||||
opt = ""
|
||||
for key in keys.split(','):
|
||||
opt += key + "=%s,"
|
||||
opt = opt[0:len(opt) - 1]
|
||||
sql = "UPDATE " + self.__DB_TABLE + " SET " + opt + self.__OPT_WHERE
|
||||
|
||||
#处理拼接WHERE与UPDATE参数
|
||||
tmp = list(self.__to_tuple(param))
|
||||
for arg in self.__OPT_PARAM:
|
||||
tmp.append(arg)
|
||||
self.__OPT_PARAM = tuple(tmp)
|
||||
self.__DB_CUR.execute(sql, self.__OPT_PARAM)
|
||||
self.__close()
|
||||
self.__DB_CONN.commit()
|
||||
return self.__DB_CUR.rowcount
|
||||
except Exception as ex:
|
||||
return "error: " + str(ex)
|
||||
|
||||
def delete(self, id=None):
|
||||
#删除数据
|
||||
self.__GetConn()
|
||||
try:
|
||||
if id:
|
||||
self.__OPT_WHERE = " WHERE id=%s"
|
||||
self.__OPT_PARAM = (id, )
|
||||
sql = "DELETE FROM " + self.__DB_TABLE + self.__OPT_WHERE
|
||||
self.__DB_CUR.execute(sql, self.__OPT_PARAM)
|
||||
self.__close()
|
||||
self.__DB_CONN.commit()
|
||||
return self.__DB_CUR.rowcount
|
||||
except Exception as ex:
|
||||
return "error: " + str(ex)
|
||||
|
||||
def execute(self, sql, is_close=True):
|
||||
#执行SQL语句返回受影响行
|
||||
if not self.__GetConn(): return self.__DB_ERR
|
||||
try:
|
||||
result = self.__DB_CUR.execute(sql)
|
||||
self.__DB_CONN.commit()
|
||||
if is_close: self.__close()
|
||||
return result
|
||||
except Exception as ex:
|
||||
return ex
|
||||
|
||||
def query(self, sql, is_close=True):
|
||||
#执行SQL语句返回数据集
|
||||
if not self.__GetConn(): return self.__DB_ERR
|
||||
try:
|
||||
self.__DB_CUR.execute(sql)
|
||||
result = self.__DB_CUR.fetchall()
|
||||
#将元组转换成列表
|
||||
data = list(map(list, result))
|
||||
if is_close: self.__Close()
|
||||
return data
|
||||
except Exception as ex:
|
||||
return ex
|
||||
|
||||
#关闭连接
|
||||
def __Close(self):
|
||||
self.__DB_CUR.close()
|
||||
self.__DB_CONN.close()
|
||||
|
||||
def __close(self):
|
||||
#清理条件属性
|
||||
self.__OPT_WHERE = ""
|
||||
self.__OPT_FIELD = "*"
|
||||
self.__OPT_ORDER = ""
|
||||
self.__OPT_LIMIT = ""
|
||||
self.__OPT_PARAM = ()
|
||||
|
||||
def close(self):
|
||||
#释放资源
|
||||
try:
|
||||
self.__DB_CUR.close()
|
||||
self.__DB_CUR.close()
|
||||
except:
|
||||
pass
|
||||
@@ -335,6 +335,11 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
|
||||
get.path = get.path.encode('utf-8')
|
||||
if get.path == '':
|
||||
get.path = '/www'
|
||||
|
||||
# 转换包含~的路径
|
||||
if get.path.find('~') != -1:
|
||||
get.path = os.path.expanduser(get.path)
|
||||
|
||||
get.path = self.xssdecode(get.path)
|
||||
if not os.path.exists(get.path):
|
||||
get.path = '/www/wwwroot'
|
||||
@@ -925,6 +930,8 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
|
||||
else:
|
||||
tmp['size'] = os.path.getsize(fname)
|
||||
if os.path.isdir(fname):
|
||||
if file[:5] == 'BTDB_':
|
||||
tmp['size'] = public.get_path_size(fname)
|
||||
data['dirs'].append(tmp)
|
||||
else:
|
||||
data['files'].append(tmp)
|
||||
@@ -2410,6 +2417,22 @@ cd %s
|
||||
attribute['history'] = self.get_history_info(filename) # 历史文件
|
||||
return attribute
|
||||
|
||||
def files_search(self,args):
|
||||
import panelSearch
|
||||
adad=panelSearch.panelSearch()
|
||||
return adad.get_search(args)
|
||||
|
||||
|
||||
def files_replace(self,args):
|
||||
import panelSearch
|
||||
adad=panelSearch.panelSearch()
|
||||
return adad.get_replace(args)
|
||||
|
||||
def get_replace_logs(self,args):
|
||||
import panelSearch
|
||||
adad=panelSearch.panelSearch()
|
||||
return adad.get_replace_logs(args)
|
||||
|
||||
# 数据库对象
|
||||
def _get_sqlite_connect(self):
|
||||
try:
|
||||
|
||||
+28
-2
@@ -1,8 +1,8 @@
|
||||
import sys
|
||||
import sys,os
|
||||
from gzip import GzipFile
|
||||
from io import BytesIO
|
||||
|
||||
from flask import request, current_app,session,Response
|
||||
from flask import request, current_app,session,Response,g
|
||||
|
||||
|
||||
if sys.version_info[:2] == (2, 6):
|
||||
@@ -79,6 +79,32 @@ class Compress(object):
|
||||
app = self.app or current_app
|
||||
accept_encoding = request.headers.get('Accept-Encoding', '')
|
||||
response.headers['Server'] = 'nginx'
|
||||
response.headers['Connection'] = 'keep-alive'
|
||||
|
||||
if 'dologin' in g and app.config['SSL']:
|
||||
try:
|
||||
for k,v in request.cookies.items():
|
||||
response.set_cookie(k,'',expires='Thu, 01-Jan-1970 00:00:00 GMT',path='/')
|
||||
except:
|
||||
pass
|
||||
|
||||
if 'rm_ssl' in g:
|
||||
import public
|
||||
try:
|
||||
for k,v in request.cookies.items():
|
||||
response.set_cookie(k,'',expires='Thu, 01-Jan-1970 00:00:00 GMT',path='/')
|
||||
except:
|
||||
pass
|
||||
session_name = app.config['SESSION_COOKIE_NAME']
|
||||
session_id = public.get_session_id()
|
||||
response.set_cookie(session_name,'',expires='Thu, 01-Jan-1970 00:00:00 GMT',path='/')
|
||||
response.set_cookie(session_name, session_id, path='/', max_age=86400 * 30,httponly=True)
|
||||
|
||||
request_token = request.cookies.get('request_token','')
|
||||
if request_token:
|
||||
response.set_cookie('request_token',request_token,path='/',max_age=86400 * 30)
|
||||
|
||||
|
||||
if (response.mimetype not in app.config['COMPRESS_MIMETYPES'] or
|
||||
'gzip' not in accept_encoding.lower() or
|
||||
not 200 <= response.status_code < 300 or
|
||||
|
||||
@@ -275,7 +275,7 @@ exit($header."\r\n\r\n".json_encode($body));
|
||||
|
||||
#取CURL路径
|
||||
def _curl_bin(self):
|
||||
c_bin = ['/usr/local/curl2/bin/curl','/usr/local/curl/bin/curl','/usr/bin/curl']
|
||||
c_bin = ['/usr/local/curl2/bin/curl','/usr/local/curl/bin/curl','/usr/local/bin/curl','/usr/bin/curl']
|
||||
for cb in c_bin:
|
||||
if os.path.exists(cb): return cb
|
||||
return 'curl'
|
||||
|
||||
+122
-3
@@ -6,7 +6,7 @@
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: hwliang <hwl@bt.cn>
|
||||
# +-------------------------------------------------------------------
|
||||
import time,public,db,os,sys,json,re
|
||||
import time,public,db,os,sys,json,re,shutil
|
||||
os.chdir('/www/server/panel')
|
||||
|
||||
def control_init():
|
||||
@@ -37,9 +37,17 @@ def control_init():
|
||||
)'''
|
||||
sql.execute(csql,())
|
||||
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%type_id%')).count():
|
||||
public.M('sites').execute("alter TABLE sites add edate integer DEFAULT '0000-00-00'",())
|
||||
public.M('sites').execute("alter TABLE sites add type_id integer DEFAULT 0",())
|
||||
|
||||
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%edate%')).count():
|
||||
public.M('sites').execute("alter TABLE sites add edate integer DEFAULT '0000-00-00'",())
|
||||
|
||||
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%project_type%')).count():
|
||||
public.M('sites').execute("alter TABLE sites add project_type STRING DEFAULT 'PHP'",())
|
||||
|
||||
if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%project_config%')).count():
|
||||
public.M('sites').execute("alter TABLE sites add project_config STRING DEFAULT '{}'",())
|
||||
|
||||
sql = db.Sql()
|
||||
if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'site_types')).count():
|
||||
csql = '''CREATE TABLE IF NOT EXISTS `site_types` (
|
||||
@@ -137,6 +145,9 @@ def control_init():
|
||||
public.ExecShell("chmod -R 600 /www/server/panel/data;chmod -R 600 /www/server/panel/config;chmod -R 700 /www/server/cron;chmod -R 600 /www/server/cron/*.log;chown -R root:root /www/server/panel/data;chown -R root:root /www/server/panel/config;chown -R root:root /www/server/phpmyadmin;chmod -R 755 /www/server/phpmyadmin")
|
||||
if os.path.exists("/www/server/mysql"):
|
||||
public.ExecShell("chown mysql:mysql /etc/my.cnf;chmod 600 /etc/my.cnf")
|
||||
public.ExecShell("rm -rf /www/server/panel/temp/*")
|
||||
if not public.is_debug():
|
||||
public.ExecShell("rm -f /www/server/panel/class/pluginAuth.py")
|
||||
stop_path = '/www/server/stop'
|
||||
if not os.path.exists(stop_path):
|
||||
os.makedirs(stop_path)
|
||||
@@ -164,11 +175,119 @@ def control_init():
|
||||
#check_firewall()
|
||||
check_dnsapi()
|
||||
clean_php_log()
|
||||
#update_py37()
|
||||
files_set_mode()
|
||||
set_pma_access()
|
||||
# public.set_open_basedir()
|
||||
clear_fastcgi_safe()
|
||||
update_py37()
|
||||
run_script()
|
||||
set_php_cli_env()
|
||||
|
||||
|
||||
def set_php_cli_env():
|
||||
'''
|
||||
@name 设置php-cli环境变量
|
||||
@author hwliang<2021-09-07>
|
||||
@return void
|
||||
'''
|
||||
php_path = '/www/server/php'
|
||||
bashrc = '/root/.bashrc'
|
||||
if not os.path.exists(php_path): return
|
||||
if not os.path.exists(bashrc): return
|
||||
# 清理所有别名
|
||||
public.ExecShell('sed -i "/alias php/d" {}'.format(bashrc))
|
||||
bashrc_body = public.readFile(bashrc)
|
||||
if not bashrc_body: return
|
||||
|
||||
# 设置默认环境变量版本别名
|
||||
env_php_bin = '/usr/bin/php'
|
||||
if os.path.exists(env_php_bin):
|
||||
if os.path.islink(env_php_bin):
|
||||
env_bin_version = os.readlink(env_php_bin).split('/')[-3]
|
||||
php_cli_ini = "{}/{}/etc/php-cli.ini".format(php_path,env_bin_version)
|
||||
bashrc_body += "alias php='php -c {}'\n".format(php_cli_ini)
|
||||
|
||||
|
||||
# 设置所有已安装的PHP版本环境变量和别名
|
||||
php_versions_list = ['52','53','54','55','56','70','71','72','73','74','80','81','82','83','84','90','91']
|
||||
for php_version in php_versions_list:
|
||||
php_ini = "{}/{}/etc/php.ini".format(php_path,php_version)
|
||||
php_cli_ini = "{}/{}/etc/php-cli.ini".format(php_path,php_version)
|
||||
env_php_bin = "/usr/bin/php{}".format(php_version)
|
||||
php_bin = "{}/{}/bin/php".format(php_path,php_version)
|
||||
php_ize = '/usr/bin/php{}-phpize'.format(php_version)
|
||||
php_ize_src = "{}/{}/bin/phpize".format(php_path,php_version)
|
||||
php_fpm = '/usr/bin/php{}-php-fpm'.format(php_version)
|
||||
php_fpm_src = "{}/{}/sbin/php-fpm".format(php_path,php_version)
|
||||
php_pecl = '/usr/bin/php{}-pecl'.format(php_version)
|
||||
php_pecl_src = "{}/{}/bin/pecl".format(php_path,php_version)
|
||||
php_pear = '/usr/bin/php{}-pear'.format(php_version)
|
||||
php_pear_src = "{}/{}/bin/pear".format(php_path,php_version)
|
||||
|
||||
if os.path.exists(php_bin):
|
||||
# 设置每个版本的环境变量
|
||||
if not os.path.exists(env_php_bin): os.symlink(php_bin,env_php_bin)
|
||||
if not os.path.exists(php_ize) and os.path.exists(php_ize_src): os.symlink(php_ize_src,php_ize)
|
||||
if not os.path.exists(php_fpm) and os.path.exists(php_fpm_src): os.symlink(php_fpm_src,php_fpm)
|
||||
if not os.path.exists(php_pecl) and os.path.exists(php_pecl_src): os.symlink(php_pecl_src,php_pecl)
|
||||
if not os.path.exists(php_pear) and os.path.exists(php_pear_src): os.symlink(php_pear_src,php_pear)
|
||||
public.ExecShell("\cp -f {} {}".format(php_ini,php_cli_ini)) # 每次复制新的php.ini到php-cli.ini
|
||||
public.ExecShell('sed -i "/disable_functions/d" {}'.format(php_cli_ini)) # 清理禁用函数
|
||||
bashrc_body += "alias php{}='php{} -c {}'\n".format(php_version,php_version,php_cli_ini) # 设置别名
|
||||
else:
|
||||
# 清理已卸载的环境变量
|
||||
if os.path.exists(env_php_bin): os.remove(env_php_bin)
|
||||
if os.path.exists(php_ize): os.remove(php_ize)
|
||||
if os.path.exists(php_fpm): os.remove(php_fpm)
|
||||
if os.path.exists(php_pecl): os.remove(php_pecl)
|
||||
if os.path.exists(php_pear): os.remove(php_pear)
|
||||
public.writeFile(bashrc,bashrc_body)
|
||||
|
||||
|
||||
def write_run_script_log(_log,rn='\n'):
|
||||
_log_file = '/www/server/panel/logs/run_script.log'
|
||||
public.writeFile(_log_file,_log + rn,'a+')
|
||||
|
||||
|
||||
def run_script():
|
||||
os.system("{} {}/script/run_script.py".format(public.get_python_bin(),public.get_panel_path()))
|
||||
run_tip = '/dev/shm/bt.pl'
|
||||
if os.path.exists(run_tip): return
|
||||
public.writeFile(run_tip,str(time.time()))
|
||||
uptime = int(public.readFile('/proc/uptime').split()[0])
|
||||
if uptime > 1800: return
|
||||
run_config ='/www/server/panel/data/run_config'
|
||||
script_logs = '/www/server/panel/logs/script_logs'
|
||||
if not os.path.exists(run_config):
|
||||
os.makedirs(run_config,384)
|
||||
if not os.path.exists(script_logs):
|
||||
os.makedirs(script_logs,384)
|
||||
|
||||
for sname in os.listdir(run_config):
|
||||
script_conf_file = '{}/{}'.format(run_config,sname)
|
||||
if not os.path.exists(script_conf_file): continue
|
||||
script_info = json.loads(public.readFile(script_conf_file))
|
||||
exec_log_file = '{}/{}'.format(script_logs,sname)
|
||||
|
||||
if not os.path.exists(script_info['script_file']) \
|
||||
or script_info['script_file'].find('/www/server/panel/plugin/') != 0 \
|
||||
or not re.match('^\w+$',script_info['script_file']):
|
||||
os.remove(script_conf_file)
|
||||
if os.path.exists(exec_log_file): os.remove(exec_log_file)
|
||||
continue
|
||||
|
||||
|
||||
if script_info['script_type'] == 'python':
|
||||
_bin = public.get_python_bin()
|
||||
elif script_info['script_type'] == 'bash':
|
||||
_bin = '/usr/bin/bash'
|
||||
if not os.path.exists(_bin): _bin = 'bash'
|
||||
|
||||
exec_script = 'nohup {} {} &> {} &'.format(_bin,script_info['script_file'],exec_log_file)
|
||||
public.ExecShell(exec_script)
|
||||
script_info['last_time'] = time.time()
|
||||
public.writeFile(script_conf_file,json.dumps(script_info))
|
||||
|
||||
|
||||
def clear_fastcgi_safe():
|
||||
try:
|
||||
|
||||
+64
-12
@@ -216,6 +216,15 @@ class backup:
|
||||
if self._is_save_local:
|
||||
_not_save_local = False
|
||||
|
||||
pdata = {
|
||||
'type': '2',
|
||||
'name': spath,
|
||||
'pid': 0,
|
||||
'filename': dfile,
|
||||
'addtime': public.format_date(),
|
||||
'size': os.path.getsize(dfile)
|
||||
}
|
||||
public.M('backup').insert(pdata)
|
||||
if _not_save_local:
|
||||
if os.path.exists(dfile):
|
||||
os.remove(dfile)
|
||||
@@ -252,7 +261,7 @@ class backup:
|
||||
#尝试删除本地文件
|
||||
if os.path.exists(backup['filename']):
|
||||
try:
|
||||
os.remove(self._local_backdir + '/'+ data_type +'/' + backup['name'])
|
||||
os.remove(backup['filename'])
|
||||
except:
|
||||
pass
|
||||
self.echo_info(public.getMsg("BACKUP_CLEAN",(backup['filename'],)))
|
||||
@@ -402,6 +411,16 @@ class backup:
|
||||
if self._is_save_local:
|
||||
_not_save_local = False
|
||||
|
||||
pdata = {
|
||||
'type': 0,
|
||||
'name': fname,
|
||||
'pid': pid,
|
||||
'filename': dfile,
|
||||
'addtime': public.format_date(),
|
||||
'size': os.path.getsize(dfile)
|
||||
}
|
||||
public.M('backup').insert(pdata)
|
||||
|
||||
if _not_save_local:
|
||||
if os.path.exists(dfile):
|
||||
os.remove(dfile)
|
||||
@@ -619,6 +638,16 @@ class backup:
|
||||
if self._is_save_local:
|
||||
_not_save_local = False
|
||||
|
||||
pdata = {
|
||||
'type': '1',
|
||||
'name': fname,
|
||||
'pid': pid,
|
||||
'filename': dfile,
|
||||
'addtime': public.format_date(),
|
||||
'size': os.path.getsize(dfile)
|
||||
}
|
||||
public.M('backup').insert(pdata)
|
||||
|
||||
if _not_save_local:
|
||||
if os.path.exists(dfile):
|
||||
os.remove(dfile)
|
||||
@@ -659,7 +688,7 @@ class backup:
|
||||
aaPanel reminds you that the cron you set failed to execute:
|
||||
* Server IP: {}
|
||||
* Time: {}
|
||||
* Task name: {}{}
|
||||
* Task name: {} {}
|
||||
* The following is a list of {} that failed to backup:
|
||||
<table style="color:red;">
|
||||
{}
|
||||
@@ -667,7 +696,16 @@ class backup:
|
||||
Please deal with it as soon as possible to avoid unnecessary trouble due to the failure of the backup task.
|
||||
- Notification by aaPanel""".format(
|
||||
server_ip, now, task_name, remark, backup_type, msg)
|
||||
return notice_content
|
||||
tg_content = """📣‼*aaPanel reminds you that the cron failed to execute*‼
|
||||
|
||||
* Server IP*: {}
|
||||
* Time*: {}
|
||||
* Task name*: {} {}
|
||||
* The following is a list of {} that failed to backup*:
|
||||
{}
|
||||
--Notification by aaPanel""".format(
|
||||
server_ip, now, task_name, remark, backup_type, msg)
|
||||
return {"mail":notice_content,"tg":tg_content}
|
||||
|
||||
def generate_failture_notice(self, task_name, msg, remark):
|
||||
# from send_mail import send_mail
|
||||
@@ -681,7 +719,7 @@ class backup:
|
||||
aaPanel reminds you that the cron you set failed to execute:
|
||||
* Server IP: {}
|
||||
* Time: {}
|
||||
* Task name:{}{}
|
||||
* Task name: {}{}
|
||||
* Error messages:
|
||||
<span style="color:red;">
|
||||
{}
|
||||
@@ -689,7 +727,17 @@ class backup:
|
||||
Please deal with it as soon as possible to avoid unnecessary trouble due to the failure of the backup task.
|
||||
-- Notification by aaPanel""".format(
|
||||
server_ip, now, task_name, remark, msg)
|
||||
return notice_content
|
||||
tg_content = """📣‼*aaPanel reminds you that the cron failed to execute*‼
|
||||
|
||||
* Server IP*: {}
|
||||
* Time*: {}
|
||||
* Task name*: {} {}
|
||||
* Error messages*:
|
||||
{}
|
||||
|
||||
-- Notification by aaPanel""".format(
|
||||
server_ip, now, task_name, remark, msg)
|
||||
return {'mail':notice_content,'tg':tg_content}
|
||||
|
||||
def get_cron_info(self, cron_name):
|
||||
""" 通过计划任务名称查找计划任务配置参数 """
|
||||
@@ -778,9 +826,10 @@ class backup:
|
||||
else:
|
||||
self.echo_error(public.getMsg('NOTIFICATION_ERR'))
|
||||
|
||||
def send_notification(self, channel, title, msg = ""):
|
||||
def send_notification(self, channel, title, msg = {}):
|
||||
try:
|
||||
from send_mail import send_mail
|
||||
from config import config
|
||||
tondao = []
|
||||
if channel.find(",") >= 0:
|
||||
tongdao = channel.split(",")
|
||||
@@ -788,14 +837,16 @@ class backup:
|
||||
tongdao = [channel]
|
||||
|
||||
sm = send_mail()
|
||||
c = config()
|
||||
send_res = []
|
||||
error_count = 0
|
||||
channel_names = {
|
||||
"mail": "email",
|
||||
# "dingidng": "钉钉"
|
||||
"telegram": "telegram"
|
||||
}
|
||||
error_channel = []
|
||||
settings = sm.get_settings()
|
||||
# settings = sm.get_settings()
|
||||
settings = c.get_settings2()
|
||||
for td in tongdao:
|
||||
_res = False
|
||||
if td == "mail":
|
||||
@@ -804,14 +855,15 @@ class backup:
|
||||
mail_list = settings['user_mail']['mail_list']
|
||||
if len(mail_list) == 1:
|
||||
mail_list = mail_list[0]
|
||||
_res = sm.qq_smtp_send(mail_list, title=title, body=msg.replace("\n", "<br/>"))
|
||||
_res = sm.qq_smtp_send(mail_list, title=title, body=msg['mail'].replace("\n", "<br/>"))
|
||||
if not _res:
|
||||
error_count += 1
|
||||
error_channel.append(channel_names[td])
|
||||
if td == "dingding":
|
||||
if len(settings["dingding"]['info']) == 0:
|
||||
if td == "telegram":
|
||||
import panel_telegram_bot
|
||||
if not settings["telegram"]['setup']:
|
||||
continue
|
||||
_res = sm.dingding_send(msg)
|
||||
_res = panel_telegram_bot.panel_telegram_bot().send_by_tg_bot(msg['tg'])
|
||||
send_res.append(_res)
|
||||
if not _res:
|
||||
error_count += 1
|
||||
|
||||
+39
-35
@@ -348,7 +348,7 @@ class panelPlugin:
|
||||
if 'token' in pdata:
|
||||
url_headers = {"authorization": "bt {}".format(pdata['token'])}
|
||||
pdata['environment_info'] = json.dumps(public.fetch_env_info())
|
||||
listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers)
|
||||
listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers,verify=False)
|
||||
listTmp=listTmp.json()
|
||||
if not listTmp:
|
||||
listTmp = public.readFile(lcoalTmp)
|
||||
@@ -632,10 +632,11 @@ class panelPlugin:
|
||||
#处理分类
|
||||
def get_types(self,sList,sType):
|
||||
if sType <= 0: return sList
|
||||
if sType != 12:
|
||||
sType = [sType]
|
||||
else:
|
||||
sType = [sType,8]
|
||||
sType = [sType]
|
||||
# if sType != 12:
|
||||
# sType = [sType]
|
||||
# else:
|
||||
# sType = [sType,8]
|
||||
newList = []
|
||||
for sInfo in sList:
|
||||
if sInfo['type'] in sType: newList.append(sInfo)
|
||||
@@ -682,7 +683,8 @@ class panelPlugin:
|
||||
soft_list_tmp = []
|
||||
softList['list'] = self.check_isinstall(softList['list'])
|
||||
for val in softList['list']:
|
||||
if val['setup']: soft_list_tmp.append(val)
|
||||
if 'setup' in val:
|
||||
if val['setup']: soft_list_tmp.append(val)
|
||||
softList['list'] = soft_list_tmp
|
||||
softList['list'] = self.get_page(softList['list'],get)
|
||||
else:
|
||||
@@ -804,38 +806,40 @@ class panelPlugin:
|
||||
def set_coexist(self,sList):
|
||||
softList = []
|
||||
for sInfo in sList:
|
||||
if sInfo['version_coexist'] == 1:
|
||||
for versionA in sInfo['versions']:
|
||||
sTmp = sInfo.copy()
|
||||
v = versionA['m_version'].replace('.','')
|
||||
sTmp['title'] = sTmp['title']+'-'+versionA['m_version']
|
||||
sTmp['name'] = sTmp['name']+'-'+versionA['m_version']
|
||||
sTmp['version'] = sTmp['version'].replace('{VERSION}',v)
|
||||
sTmp['manager_version'] = sTmp['manager_version'].replace('{VERSION}',v)
|
||||
sTmp['install_checks'] = sTmp['install_checks'].replace('{VERSION}',v)
|
||||
if 'uninsatll_checks' not in sTmp:
|
||||
sTmp['uninsatll_checks'] = sTmp['uninstall_checks'].replace('{VERSION}',v)
|
||||
else:
|
||||
sTmp['uninsatll_checks'] = sTmp['uninsatll_checks'].replace('{VERSION}',v)
|
||||
sTmp['s_version'] = sTmp['s_version'].replace('{VERSION}',v)
|
||||
sTmp['versions'] = []
|
||||
sTmp['versions'].append(versionA)
|
||||
softList.append(sTmp)
|
||||
else:
|
||||
softList.append(sInfo)
|
||||
try:
|
||||
if sInfo['version_coexist'] == 1 and 'versions' in sInfo:
|
||||
for versionA in sInfo['versions']:
|
||||
try:
|
||||
sTmp = sInfo.copy()
|
||||
v = versionA['m_version'].replace('.','')
|
||||
sTmp['title'] = sTmp['title']+'-'+versionA['m_version']
|
||||
sTmp['name'] = sTmp['name']+'-'+versionA['m_version']
|
||||
sTmp['version'] = sTmp['version'].replace('{VERSION}',v)
|
||||
sTmp['manager_version'] = sTmp['manager_version'].replace('{VERSION}',v)
|
||||
sTmp['install_checks'] = sTmp['install_checks'].replace('{VERSION}',v)
|
||||
if 'uninsatll_checks' not in sTmp:
|
||||
sTmp['uninsatll_checks'] = sTmp['uninstall_checks'].replace('{VERSION}',v)
|
||||
else:
|
||||
sTmp['uninsatll_checks'] = sTmp['uninsatll_checks'].replace('{VERSION}',v)
|
||||
sTmp['s_version'] = sTmp['s_version'].replace('{VERSION}',v)
|
||||
sTmp['versions'] = []
|
||||
sTmp['versions'].append(versionA)
|
||||
softList.append(sTmp)
|
||||
except: continue
|
||||
else:
|
||||
softList.append(sInfo)
|
||||
except: continue
|
||||
return softList
|
||||
|
||||
#检测是否安装
|
||||
def check_isinstall(self,sList):
|
||||
try:
|
||||
if not os.path.exists(self.__index): public.writeFile(self.__index,'[]')
|
||||
indexList = json.loads(public.ReadFile(self.__index))
|
||||
for i in range(len(sList)):
|
||||
sList[i]['index_display'] = sList[i]['name'] in indexList
|
||||
sList[i] = self.check_status(sList[i])
|
||||
return sList
|
||||
except:
|
||||
public.writeFile(self.__index,'[]')
|
||||
if not os.path.exists(self.__index): public.writeFile(self.__index,'[]')
|
||||
indexList = json.loads(public.ReadFile(self.__index))
|
||||
for i in range(len(sList)):
|
||||
sList[i]['index_display'] = sList[i]['name'] in indexList
|
||||
sList[i] = self.check_status(sList[i])
|
||||
return sList
|
||||
|
||||
|
||||
#检查软件状态
|
||||
def check_status(self,softInfo):
|
||||
@@ -2077,7 +2081,7 @@ class panelPlugin:
|
||||
import panelPHP
|
||||
return panelPHP.panelPHP(get.name).exec_php_script(get)
|
||||
return public.returnMsg(False,'PLUGIN_INPUT_B')
|
||||
if not self.check_accept(get):return public.returnMsg(False,public.to_string([24744, 26410, 36141, 20080, 91, 37, 115, 93, 25110, 25480, 26435, 24050, 21040, 26399, 33]) % (self.get_title_byname(get),))
|
||||
if not self.check_accept(get):return public.returnMsg(False,"You did not purchase [ %s ] or the authorization has expired" % (self.get_title_byname(get),))
|
||||
public.package_path_append(path)
|
||||
plugin_main = __import__(get.name+'_main')
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#coding: utf-8
|
||||
#-------------------------------------------------------------------
|
||||
# 宝塔Linux面板
|
||||
#-------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
|
||||
#-------------------------------------------------------------------
|
||||
# Author: hwliang <hwl@bt.cn>
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
#------------------------------
|
||||
# 项目管理控制器
|
||||
#------------------------------
|
||||
import os,sys,public,json,re
|
||||
|
||||
class ProjectController:
|
||||
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def model(self,args):
|
||||
'''
|
||||
@name 调用指定项目模型
|
||||
@author hwliang<2021-07-15>
|
||||
@param args<dict_obj> {
|
||||
mod_name: string<模型名称>
|
||||
def_name: string<方法名称>
|
||||
data: JSON
|
||||
}
|
||||
'''
|
||||
try: # 表单验证
|
||||
if args['mod_name'] in ['base']: return public.return_status_code(1000,'错误的调用!')
|
||||
public.exists_args('def_name,mod_name',args)
|
||||
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'调用的方法名称中不能包含“__”字符')
|
||||
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'调用的模块名称中不能包含\w以外的字符')
|
||||
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'调用的方法名称中不能包含\w以外的字符')
|
||||
except:
|
||||
return public.get_error_object()
|
||||
# 参数处理
|
||||
mod_name = "{}Model".format(args['mod_name'].strip())
|
||||
def_name = args['def_name'].strip()
|
||||
|
||||
# 指定模型是否存在
|
||||
mod_file = "{}/projectModel/{}.py".format(public.get_class_path(),mod_name)
|
||||
if not os.path.exists(mod_file):
|
||||
return public.return_status_code(1003,mod_name)
|
||||
# 实例化
|
||||
def_object = public.get_script_object(mod_file)
|
||||
if not def_object: return public.return_status_code(1000,'没有找到{}模型'.format(mod_name))
|
||||
run_object = getattr(def_object.main(),def_name,None)
|
||||
if not run_object: return public.return_status_code(1000,'没有在{}模型中找到{}方法'.format(mod_name,def_name))
|
||||
if not hasattr(args,'data'): args.data = {}
|
||||
if args.data:
|
||||
if isinstance(args.data,str):
|
||||
try: # 解析为dict_obj
|
||||
pdata = public.to_dict_obj(json.loads(args.data))
|
||||
except:
|
||||
return public.get_error_object()
|
||||
else:
|
||||
pdata = args.data
|
||||
else:
|
||||
pdata = public.dict_obj()
|
||||
|
||||
# 前置HOOK
|
||||
hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper())
|
||||
hook_result = public.exec_hook(hook_index,pdata)
|
||||
if isinstance(hook_result,public.dict_obj):
|
||||
pdata = hook_result # 桥接
|
||||
elif isinstance(hook_result,dict):
|
||||
return hook_result # 响应具体错误信息
|
||||
elif isinstance(hook_result,bool):
|
||||
if not hook_result: # 直接中断操作
|
||||
return public.return_data(False,{},error_msg='前置HOOK中断操作')
|
||||
|
||||
# 调用处理方法
|
||||
result = run_object(pdata)
|
||||
|
||||
# 后置HOOK
|
||||
hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper())
|
||||
hook_data = public.to_dict_obj({
|
||||
'args': pdata,
|
||||
'result': result
|
||||
})
|
||||
hook_result = public.exec_hook(hook_index,hook_data)
|
||||
if isinstance(hook_result,dict):
|
||||
result = hook_result['result']
|
||||
return result
|
||||
|
||||
|
||||
|
||||
+79
-17
@@ -283,7 +283,7 @@ class panelSSL:
|
||||
content = args.content
|
||||
|
||||
import http_requests
|
||||
res = http_requests.get(url,s_type='curl',timeout=6)
|
||||
res = http_requests.get(url,s_type='curl',timeout=6,verify=False)
|
||||
result = res.text
|
||||
if not result: return 0
|
||||
|
||||
@@ -356,6 +356,7 @@ class panelSSL:
|
||||
#发送请求
|
||||
def request(self,dname):
|
||||
self.__PDATA['data'] = json.dumps(self.__PDATA['data'])
|
||||
result= public.returnMsg(False,'The request failed, please try again later!')
|
||||
try:
|
||||
result = public.httpPost(self.__APIURL2 + '/' + dname,self.__PDATA)
|
||||
result = json.loads(result)
|
||||
@@ -689,27 +690,88 @@ class panelSSL:
|
||||
|
||||
#获取证书名称
|
||||
def GetCertName(self,get):
|
||||
return self.get_cert_init(get.certPath)
|
||||
# try:
|
||||
# openssl = '/usr/local/openssl/bin/openssl'
|
||||
# if not os.path.exists(openssl): openssl = 'openssl'
|
||||
# result = public.ExecShell(openssl + " x509 -in "+get.certPath+" -noout -subject -enddate -startdate -issuer")
|
||||
# tmp = result[0].split("\n")
|
||||
# data = {}
|
||||
# data['subject'] = tmp[0].split('=')[-1]
|
||||
# data['notAfter'] = self.strfToTime(tmp[1].split('=')[1])
|
||||
# data['notBefore'] = self.strfToTime(tmp[2].split('=')[1])
|
||||
# if tmp[3].find('O=') == -1:
|
||||
# data['issuer'] = tmp[3].split('CN=')[-1]
|
||||
# else:
|
||||
# data['issuer'] = tmp[3].split('O=')[-1].split(',')[0]
|
||||
# if data['issuer'].find('/') != -1: data['issuer'] = data['issuer'].split('/')[0]
|
||||
# result = public.ExecShell(openssl + " x509 -in "+get.certPath+" -noout -text|grep DNS")
|
||||
# data['dns'] = result[0].replace('DNS:','').replace(' ','').strip().split(',')
|
||||
# return data
|
||||
# except:
|
||||
# print(public.get_error_info())
|
||||
# return None
|
||||
|
||||
|
||||
# 获取指定证书基本信息
|
||||
def get_cert_init(self, pem_file):
|
||||
if not os.path.exists(pem_file):
|
||||
return None
|
||||
try:
|
||||
openssl = '/usr/local/openssl/bin/openssl'
|
||||
if not os.path.exists(openssl): openssl = 'openssl'
|
||||
result = public.ExecShell(openssl + " x509 -in "+get.certPath+" -noout -subject -enddate -startdate -issuer")
|
||||
tmp = result[0].split("\n")
|
||||
data = {}
|
||||
data['subject'] = tmp[0].split('=')[-1]
|
||||
data['notAfter'] = self.strfToTime(tmp[1].split('=')[1])
|
||||
data['notBefore'] = self.strfToTime(tmp[2].split('=')[1])
|
||||
if tmp[3].find('O=') == -1:
|
||||
data['issuer'] = tmp[3].split('CN=')[-1]
|
||||
import OpenSSL
|
||||
result = {}
|
||||
x509 = OpenSSL.crypto.load_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, public.readFile(pem_file))
|
||||
# 取产品名称
|
||||
issuer = x509.get_issuer()
|
||||
result['issuer'] = ''
|
||||
if hasattr(issuer, 'CN'):
|
||||
result['issuer'] = issuer.CN
|
||||
if not result['issuer']:
|
||||
is_key = [b'0', '0']
|
||||
issue_comp = issuer.get_components()
|
||||
if len(issue_comp) == 1:
|
||||
is_key = [b'CN', 'CN']
|
||||
for iss in issue_comp:
|
||||
if iss[0] in is_key:
|
||||
result['issuer'] = iss[1].decode()
|
||||
break
|
||||
# 取到期时间
|
||||
result['notAfter'] = self.strf_date(
|
||||
bytes.decode(x509.get_notAfter())[:-1])
|
||||
# 取申请时间
|
||||
result['notBefore'] = self.strf_date(
|
||||
bytes.decode(x509.get_notBefore())[:-1])
|
||||
# 取可选名称
|
||||
result['dns'] = []
|
||||
for i in range(x509.get_extension_count()):
|
||||
s_name = x509.get_extension(i)
|
||||
if s_name.get_short_name() in [b'subjectAltName', 'subjectAltName']:
|
||||
s_dns = str(s_name).split(',')
|
||||
for d in s_dns:
|
||||
result['dns'].append(d.split(':')[1])
|
||||
subject = x509.get_subject().get_components()
|
||||
# 取主要认证名称
|
||||
if len(subject) == 1:
|
||||
result['subject'] = subject[0][1].decode()
|
||||
else:
|
||||
data['issuer'] = tmp[3].split('O=')[-1].split(',')[0]
|
||||
if data['issuer'].find('/') != -1: data['issuer'] = data['issuer'].split('/')[0]
|
||||
result = public.ExecShell(openssl + " x509 -in "+get.certPath+" -noout -text|grep DNS")
|
||||
data['dns'] = result[0].replace('DNS:','').replace(' ','').strip().split(',')
|
||||
return data
|
||||
if not result['dns']:
|
||||
for sub in subject:
|
||||
if sub[0] == b'CN':
|
||||
result['subject'] = sub[1].decode()
|
||||
break
|
||||
result['dns'].append(result['subject'])
|
||||
else:
|
||||
result['subject'] = result['dns'][0]
|
||||
return result
|
||||
except:
|
||||
print(public.get_error_info())
|
||||
return None
|
||||
|
||||
|
||||
# 转换时间
|
||||
def strf_date(self, sdate):
|
||||
return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S'))
|
||||
|
||||
#转换时间
|
||||
def strfToTime(self,sdate):
|
||||
import time
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | version :1.0
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 梁凯强 <1249648969@qq.com>
|
||||
# +-------------------------------------------------------------------
|
||||
# | 快速检索
|
||||
# +--------------------------------------------------------------------
|
||||
import os,public,re
|
||||
import zipfile,time,json
|
||||
import db
|
||||
class panelSearch:
|
||||
__backup_path = '/www/server/panel/backup/panel_search/'
|
||||
|
||||
def __init__(self):
|
||||
if not os.path.exists(self.__backup_path):
|
||||
os.makedirs(self.__backup_path)
|
||||
if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'panel_search_log')).count():
|
||||
csql = '''CREATE TABLE `panel_search_log` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `rtext` TEXT,`exts` TEXT,`path` TEXT,`mode` TEXT,`isword` TEXT,`iscase` TEXT,`noword` TEXT,`backup_path` TEXT,`time` TEXT)'''
|
||||
public.M('sqlite_master').execute(csql,())
|
||||
|
||||
def dtchg(self, x):
|
||||
try:
|
||||
time_local = time.localtime(float(x))
|
||||
dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local)
|
||||
return dt
|
||||
except:
|
||||
return False
|
||||
|
||||
def insert_settings(self, rtext, exts, path, mode, isword,iscase,noword,backup_path):
|
||||
inser_time = self.dtchg(int(time.time()))
|
||||
data = {"rtext": rtext, "exts": json.dumps(exts), "path": path, "mode": mode,
|
||||
"isword": isword, "iscase": iscase,"noword":noword,"backup_path":backup_path,"time":inser_time}
|
||||
return public.M('panel_search_log').insert(data)
|
||||
|
||||
'''目录下所有的文件'''
|
||||
def get_dir(self, path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False):
|
||||
if rtext or noword:
|
||||
result=[]
|
||||
else:
|
||||
result={}
|
||||
if is_backup:
|
||||
t = time.strftime('%Y%m%d%H%M%S')
|
||||
back_zip = os.path.join(self.__backup_path, "%s.zip" % t)
|
||||
zfile = zipfile.ZipFile(back_zip, "w", compression=zipfile.ZIP_DEFLATED)
|
||||
else:
|
||||
zfile=False
|
||||
back_zip=False
|
||||
return_data = []
|
||||
[[return_data.append(os.path.join(root, file)) for file in files] for root, dirs, files in os.walk(path)]
|
||||
for i in return_data:
|
||||
for i2 in exts:
|
||||
i3 = i.split('.')
|
||||
if i3[-1] == i2:
|
||||
|
||||
temp = self.get_files_lin(i, text, mode, isword, iscase, noword,is_backup,rtext,zfile)
|
||||
if temp:
|
||||
if rtext or noword:
|
||||
if isinstance(result,list):
|
||||
result.append(temp)
|
||||
else:
|
||||
result[i] = temp
|
||||
if is_backup:
|
||||
if zfile:
|
||||
zfile.close()
|
||||
self.insert_settings(rtext, exts, path, mode, isword, iscase, noword, back_zip)
|
||||
return True
|
||||
return result
|
||||
|
||||
'''获取单目录'''
|
||||
def get_dir_files(self,path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False):
|
||||
is_list = rtext or noword
|
||||
if is_list:
|
||||
result=[]
|
||||
else:
|
||||
result={}
|
||||
if is_backup:
|
||||
t = time.strftime('%Y%m%d%H%M%S')
|
||||
back_zip = os.path.join(self.__backup_path, "%s.zip" % t)
|
||||
zfile = zipfile.ZipFile(back_zip, "w", compression=zipfile.ZIP_DEFLATED)
|
||||
else:
|
||||
zfile=False
|
||||
back_zip=False
|
||||
list_data=[]
|
||||
for root, dirs, files in os.walk(path):
|
||||
list_data=files
|
||||
break
|
||||
for i in exts:
|
||||
for i2 in list_data:
|
||||
i3=i2.split('.')
|
||||
if i3[-1]==i:
|
||||
temp=self.get_files_lin(path+'/'+i2, text,mode,isword,iscase,noword,is_backup,rtext,zfile)
|
||||
if temp:
|
||||
if isinstance(result,list):
|
||||
result.append(path+'/'+i2)
|
||||
else:
|
||||
result[path+'/'+i2]=temp
|
||||
if is_backup:
|
||||
if zfile:
|
||||
zfile.close()
|
||||
self.insert_settings(rtext, exts, path, mode, isword, iscase, noword, back_zip)
|
||||
return result
|
||||
'''
|
||||
获取目录下所有的后缀文件
|
||||
'''
|
||||
def get_exts_files(self,path,exts,text,mode=0,isword=0,iscase=0,noword=0,is_subdir=0,is_backup=0,rtext=False):
|
||||
if len(exts)==0:return []
|
||||
if is_subdir==0:
|
||||
return self.get_dir_files(path,exts,text,mode,isword,iscase,noword,is_backup,rtext)
|
||||
elif is_subdir==1:
|
||||
return self.get_dir(path,exts,text,mode,isword,iscase,noword,is_backup,rtext)
|
||||
|
||||
'''
|
||||
获取文件内的关键词
|
||||
'''
|
||||
def get_files_lin(self, files, text,mode=0,isword=0,iscase=0,noword=0,is_backup=0,rtext=False,back_zip=False):
|
||||
if not os.path.exists(files):return False
|
||||
if os.path.getsize(files) > 1024 * 1024 * 20: return False
|
||||
#文件替换部分
|
||||
if rtext:
|
||||
resutl=[]
|
||||
try:
|
||||
fp = open(files, 'r', encoding='UTF-8')
|
||||
except:
|
||||
fp = open(files, 'r')
|
||||
content = fp.read()
|
||||
fp.close()
|
||||
if mode==2:
|
||||
if iscase:
|
||||
if not re.search(text, content, flags=re.I): return False
|
||||
content = re.sub(text, rtext, content, flags=re.I)
|
||||
else:
|
||||
if not re.search(text, content): return False
|
||||
content = re.sub(text, rtext, content)
|
||||
else:
|
||||
if iscase:
|
||||
if not re.search(text, content, flags=re.I): return False
|
||||
content = re.sub(text, rtext, content, flags=re.I)
|
||||
else:
|
||||
if content.find(text) == -1: return False
|
||||
content = content.replace(text, rtext)
|
||||
if is_backup and back_zip:
|
||||
bf = files.strip('/')
|
||||
back_zip.write(files, bf)
|
||||
with open(files, 'w') as f:
|
||||
f.write(content)
|
||||
f.close()
|
||||
return files
|
||||
else:
|
||||
#查找部分
|
||||
if noword:
|
||||
resutl = []
|
||||
else:
|
||||
resutl={}
|
||||
try:
|
||||
fp = open(files, 'r', encoding='UTF-8')
|
||||
except:
|
||||
fp = open(files, 'r')
|
||||
i = 0
|
||||
try:
|
||||
for line in fp:
|
||||
i += 1
|
||||
if mode==1:
|
||||
if iscase and not re.search(text, line, flags=re.I):
|
||||
continue
|
||||
elif not iscase and not re.search(text, line):
|
||||
continue
|
||||
else:
|
||||
if iscase:
|
||||
if not re.search(text, line, flags=re.I): continue
|
||||
else:
|
||||
if line.find(text) == -1: continue
|
||||
if noword:
|
||||
return files
|
||||
resutl[i]=line
|
||||
except:
|
||||
pass
|
||||
if resutl:
|
||||
return resutl
|
||||
return False
|
||||
'''
|
||||
text 搜索内容
|
||||
exts 后缀名 参数例子 php,html
|
||||
path 目录
|
||||
is_subdir 0 不包含子目录 1 包含子目录
|
||||
mode 0 为普通模式 1 为正则模式
|
||||
isword 1 全词匹配 0 默认
|
||||
iscase 1 不区分大小写 0 默认
|
||||
noword 1 不输出行信息 0 默认
|
||||
'''
|
||||
def get_search(self, args):
|
||||
if 'text' not in args or not args.text: return {'error': 'Search content cannot be empty'}
|
||||
if 'exts' not in args or not args.exts: return {'error': 'The suffix cannot be empty; please enter [ *.* ] to search all files'}
|
||||
if 'path' not in args or not args.path or args.path == '/': return {'error': 'The directory cannot be empty or /'}
|
||||
if not os.path.isdir(args.path): return {'error': 'Directory does not exist'}
|
||||
text=args.text
|
||||
exts=args.exts
|
||||
path=args.path
|
||||
mode = int(args.mode) if 'mode' in args else 0
|
||||
is_subdir = int(args.is_subdir) if 'is_subdir' in args else 0
|
||||
iscase = int(args.iscase) if 'iscase' in args else 0
|
||||
isword = int(args.isword) if 'isword' in args else 0
|
||||
noword = int(args.noword) if 'noword' in args else 0
|
||||
exts=exts.split(',')
|
||||
is_tmpe_files=self.get_exts_files(path,exts,text,mode,isword,iscase,noword,is_subdir)
|
||||
return is_tmpe_files
|
||||
|
||||
'''
|
||||
text 搜索内容
|
||||
rtext 替换成的内容
|
||||
exts 后缀名 参数例子 php,html
|
||||
path 目录
|
||||
is_subdir 0 不包含子目录 1 包含子目录
|
||||
mode 0 为普通模式 1 为正则模式
|
||||
isword 1 全词匹配 0 默认
|
||||
iscase 1 不区分大小写 0 默认
|
||||
noword 1 不输出行信息 0 默认
|
||||
'''
|
||||
def get_replace(self, args):
|
||||
if 'text' not in args or not args.text: return {'error': 'Search content cannot be empty'}
|
||||
if 'rtext' not in args or not args.text: return {'error': 'The content to be replaced cannot be empty'}
|
||||
if 'exts' not in args or not args.exts: return {'error': 'The suffix cannot be empty; please enter [ *.* ] to search all files'}
|
||||
if 'path' not in args or not args.path or args.path == '/': return {'error': 'The directory cannot be empty or /'}
|
||||
if not os.path.isdir(args.path): return {'error': 'Directory does not exist'}
|
||||
is_backup = int(args.isbackup) if 'isbackup' in args else 0
|
||||
text = args.text
|
||||
rtext = args.rtext
|
||||
exts = args.exts
|
||||
path = args.path
|
||||
mode = int(args.mode) if 'mode' in args else 0
|
||||
is_subdir = int(args.is_subdir) if 'is_subdir' in args else 0
|
||||
iscase = int(args.iscase) if 'iscase' in args else 0
|
||||
isword = int(args.isword) if 'isword' in args else 0
|
||||
noword = int(args.noword) if 'noword' in args else 0
|
||||
exts = exts.split(',')
|
||||
is_tmpe_files = self.get_exts_files(path, exts, text, mode, isword, iscase, noword, is_subdir,is_backup,rtext)
|
||||
return is_tmpe_files
|
||||
|
||||
#替換日志
|
||||
def get_replace_logs(self,get):
|
||||
import page
|
||||
page = page.Page()
|
||||
count = public.M('panel_search_log').order('id desc').count()
|
||||
limit = 12
|
||||
info = {}
|
||||
info['count'] = count
|
||||
info['row'] = limit
|
||||
info['p'] = 1
|
||||
if hasattr(get, 'p'):
|
||||
info['p'] = int(get['p'])
|
||||
info['uri'] = get
|
||||
info['return_js'] = ''
|
||||
if hasattr(get, 'tojs'):
|
||||
info['return_js'] = get.tojs
|
||||
data = {}
|
||||
data['page'] = page.GetPage(info, '1,2,3,4,5,8')
|
||||
data['data'] = public.M('panel_search_log').field('id,rtext,exts,path,mode,isword,iscase,noword,backup_path,time').order('id desc').limit(str(page.SHIFT) + ',' + str(page.ROW)).select()
|
||||
if isinstance(data['data'],str): return public.returnMsg(False,[])
|
||||
for i in data['data']:
|
||||
if not isinstance(i,dict): continue
|
||||
if 'backup_path' in i :
|
||||
path=i['backup_path']
|
||||
if os.path.exists(path):
|
||||
i['is_path_status']=True
|
||||
else:
|
||||
i['is_path_status'] = False
|
||||
return public.returnMsg(True, data)
|
||||
+213
-35
@@ -32,7 +32,7 @@ class panelSite(panelRedirect):
|
||||
is_ipv6 = False
|
||||
|
||||
def __init__(self):
|
||||
self.setupPath = '/www/server'
|
||||
self.setupPath = public.get_setup_path()
|
||||
path = self.setupPath + '/panel/vhost/nginx'
|
||||
if not os.path.exists(path): public.ExecShell("mkdir -p " + path + " && chmod -R 644 " + path)
|
||||
path = self.setupPath + '/panel/vhost/apache'
|
||||
@@ -43,7 +43,7 @@ class panelSite(panelRedirect):
|
||||
if not os.path.exists(path + '/index.html'):
|
||||
public.ExecShell('mkdir -p ' + path)
|
||||
public.ExecShell('wget -O ' + path + '/index.html ' + public.get_url() + '/stop_en.html &')
|
||||
self.__proxyfile = '/www/server/panel/data/proxyfile.json'
|
||||
self.__proxyfile = '{}/data/proxyfile.json'.format(public.get_panel_path())
|
||||
self.OldConfigFile()
|
||||
if os.path.exists(self.nginx_conf_bak): os.remove(self.nginx_conf_bak)
|
||||
if os.path.exists(self.apache_conf_bak): os.remove(self.apache_conf_bak)
|
||||
@@ -84,6 +84,7 @@ class panelSite(panelRedirect):
|
||||
|
||||
# 添加apache端口
|
||||
def apacheAddPort(self, port):
|
||||
port = str(port)
|
||||
filename = self.setupPath + '/apache/conf/extra/httpd-ssl.conf'
|
||||
if os.path.exists(filename):
|
||||
ssl_conf = public.readFile(filename)
|
||||
@@ -1747,6 +1748,10 @@ listener SSL443 {
|
||||
|
||||
# Nginx配置
|
||||
file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf'
|
||||
|
||||
# Node项目
|
||||
if not os.path.exists(file): file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf'
|
||||
|
||||
ng_file = file
|
||||
conf = public.readFile(file)
|
||||
|
||||
@@ -1793,6 +1798,7 @@ listener SSL443 {
|
||||
|
||||
# Apache配置
|
||||
file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf'
|
||||
if not os.path.exists(file): file = self.setupPath + '/panel/vhost/apache/node_' + siteName + '.conf'
|
||||
conf = public.readFile(file)
|
||||
ap_static_security = self._get_ap_static_security(conf)
|
||||
if conf:
|
||||
@@ -1907,8 +1913,10 @@ listener SSL443 {
|
||||
# HttpToHttps
|
||||
def HttpToHttps(self, get):
|
||||
siteName = get.siteName
|
||||
# Nginx配置
|
||||
file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf'
|
||||
#Nginx配置
|
||||
file = self.setupPath + '/panel/vhost/nginx/'+siteName+'.conf'
|
||||
if not os.path.exists(file):
|
||||
file = self.setupPath + '/panel/vhost/nginx/node_'+siteName+'.conf'
|
||||
conf = public.readFile(file)
|
||||
if conf:
|
||||
if conf.find('ssl_certificate') == -1: return public.returnMsg(False, 'NOT_OPEN_SSL')
|
||||
@@ -1918,10 +1926,12 @@ listener SSL443 {
|
||||
rewrite ^(/.*)$ https://$host$1 permanent;
|
||||
}
|
||||
#HTTP_TO_HTTPS_END"""
|
||||
conf = conf.replace('#error_page 404/404.html;', to)
|
||||
public.writeFile(file, conf)
|
||||
conf = conf.replace('#error_page 404/404.html;',to)
|
||||
public.writeFile(file,conf)
|
||||
|
||||
file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf'
|
||||
file = self.setupPath + '/panel/vhost/apache/'+siteName+'.conf'
|
||||
if not os.path.exists(file):
|
||||
file = self.setupPath + '/panel/vhost/apache/node_'+siteName+'.conf'
|
||||
conf = public.readFile(file)
|
||||
if conf:
|
||||
httpTohttos = '''combined
|
||||
@@ -1979,6 +1989,9 @@ listener SSL443 {
|
||||
# 是否跳转到https
|
||||
def IsToHttps(self, siteName):
|
||||
file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf'
|
||||
if not os.path.exists(file):
|
||||
file = self.setupPath + '/panel/vhost/nginx/node_'+siteName+'.conf'
|
||||
if not os.path.exists(file): return False
|
||||
conf = public.readFile(file)
|
||||
if conf:
|
||||
if conf.find('HTTP_TO_HTTPS_START') != -1: return True
|
||||
@@ -1990,6 +2003,8 @@ listener SSL443 {
|
||||
siteName = get.siteName
|
||||
|
||||
file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf'
|
||||
if not os.path.exists(file):
|
||||
file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf'
|
||||
conf = public.readFile(file)
|
||||
if conf:
|
||||
rep = "\n\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END"
|
||||
@@ -2031,6 +2046,8 @@ listener SSL443 {
|
||||
public.writeFile(file, conf)
|
||||
|
||||
file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf'
|
||||
if not os.path.exists(file):
|
||||
file = self.setupPath + '/panel/vhost/apache/node_' + siteName + '.conf'
|
||||
conf = public.readFile(file)
|
||||
if conf:
|
||||
rep = "\n<VirtualHost \*\:443>(.|\n)*<\/VirtualHost>"
|
||||
@@ -2090,6 +2107,9 @@ listener SSL443 {
|
||||
key = public.readFile(keypath)
|
||||
csr = public.readFile(csrpath)
|
||||
file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/' + siteName + '.conf'
|
||||
|
||||
# 是否为node项目
|
||||
if not os.path.exists(file): file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/node_' + siteName + '.conf'
|
||||
if public.get_webserver() == "openlitespeed":
|
||||
file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/detail/' + siteName + '.conf'
|
||||
conf = public.readFile(file)
|
||||
@@ -2393,11 +2413,16 @@ listener SSL443 {
|
||||
domains = ''
|
||||
id = public.M('sites').where("name=?", (siteName,)).getField('id')
|
||||
tmp = public.M('domain').where("pid=?", (id,)).field('name').select()
|
||||
node = public.M('sites').where('id=? and project_type=?', (id, 'Node')).count()
|
||||
if node:
|
||||
node = 'node_'
|
||||
else:
|
||||
node = ''
|
||||
for key in tmp:
|
||||
domains += key['name'] + ','
|
||||
try:
|
||||
if public.get_webserver() == 'nginx':
|
||||
conf = public.readFile(self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf')
|
||||
if (public.get_webserver() == 'nginx'):
|
||||
conf = public.readFile(self.setupPath + '/panel/vhost/nginx/' + node + siteName + '.conf')
|
||||
if conf.find('301-START') == -1:
|
||||
result['domain'] = domains[:-1]
|
||||
result['src'] = ""
|
||||
@@ -2411,7 +2436,7 @@ listener SSL443 {
|
||||
src = ''
|
||||
if tmp: src = tmp.groups()[0]
|
||||
elif public.get_webserver() == 'apache':
|
||||
conf = public.readFile(self.setupPath + '/panel/vhost/apache/' + siteName + '.conf')
|
||||
conf = public.readFile(self.setupPath + '/panel/vhost/apache/' + node + siteName + '.conf')
|
||||
if conf.find('301-START') == -1:
|
||||
result['domain'] = domains[:-1]
|
||||
result['src'] = ""
|
||||
@@ -3122,6 +3147,7 @@ server
|
||||
public.WriteLog("TYPE_SITE", "SITE_PHPVERSION_SUCCESS", (siteName, version))
|
||||
return public.returnMsg(True, 'SITE_PHPVERSION_SUCCESS', (siteName, version))
|
||||
except:
|
||||
return public.get_error_info()
|
||||
return public.returnMsg(False, 'PHP_SETUP_FAILED')
|
||||
|
||||
# 是否开启目录防御
|
||||
@@ -3677,6 +3703,15 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s]
|
||||
if re.search(rep, data):
|
||||
return True
|
||||
|
||||
def old_proxy_conf(self,conf,ng_conf_file,get):
|
||||
rep = 'location\s*\~\*.*gif\|png\|jpg\|css\|js\|woff\|woff2\)\$'
|
||||
if not re.search(rep,conf):
|
||||
return conf
|
||||
|
||||
self.RemoveProxy(get)
|
||||
self.CreateProxy(get)
|
||||
return public.readFile(ng_conf_file)
|
||||
|
||||
# 修改反向代理
|
||||
def ModifyProxy(self, get):
|
||||
proxyname_md5 = self.__calc_md5(get.proxyname)
|
||||
@@ -3689,6 +3724,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s]
|
||||
if self.__CheckStart(get):
|
||||
return self.__CheckStart(get)
|
||||
conf = self.__read_config(self.__proxyfile)
|
||||
random_string = public.GetRandomString(8)
|
||||
for i in range(len(conf)):
|
||||
if conf[i]["proxyname"] == get.proxyname and conf[i]["sitename"] == get.sitename:
|
||||
if int(get.type) != 1:
|
||||
@@ -3705,6 +3741,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s]
|
||||
public.ExecShell("mv {f}_bak {f}".format(f=ng_conf_file))
|
||||
public.ExecShell("mv {f}_bak {f}".format(f=ols_conf_file))
|
||||
ng_conf = public.readFile(ng_conf_file)
|
||||
ng_conf = self.old_proxy_conf(ng_conf, ng_conf_file, get)
|
||||
# 修改nginx配置
|
||||
# 如果代理URL后缀带有URI则删除URI,正则匹配不支持proxypass处带有uri
|
||||
php_pass_proxy = get.proxysite
|
||||
@@ -3730,13 +3767,22 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s]
|
||||
ng_conf = re.sub(cache_rep, "proxy_cache_valid 200 304 301 302 {0}m;".format(get.cachetime),
|
||||
ng_conf)
|
||||
else:
|
||||
# ng_cache = """
|
||||
# proxy_ignore_headers Set-Cookie Cache-Control expires;
|
||||
# proxy_cache cache_one;
|
||||
# proxy_cache_key $host$uri$is_args$args;
|
||||
# proxy_cache_valid 200 304 301 302 %sm;""" % (get.cachetime)
|
||||
ng_cache = """
|
||||
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
|
||||
{
|
||||
expires 12h;
|
||||
}
|
||||
proxy_ignore_headers Set-Cookie Cache-Control expires;
|
||||
proxy_cache cache_one;
|
||||
proxy_cache_key $host$uri$is_args$args;
|
||||
proxy_cache_valid 200 304 301 302 %sm;""" % (get.cachetime)
|
||||
if self.check_annotate(ng_conf):
|
||||
cache_rep = '\n\s*#Set\s*Nginx\s*Cache(.|\n)*no-cache;'
|
||||
cache_rep = '\n\s*#Set\s*Nginx\s*Cache(.|\n)*no-cache;\s*\n*\s*\}'
|
||||
ng_conf = re.sub(cache_rep, '\n\t#Set Nginx Cache\n' + ng_cache, ng_conf)
|
||||
else:
|
||||
# cache_rep = '#proxy_set_header\s+Connection\s+"upgrade";'
|
||||
@@ -3745,16 +3791,30 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s]
|
||||
r"\n\tproxy_set_header\s+REMOTE-HOST\s+\$remote_addr;\n\t#Set Nginx Cache" + ng_cache,
|
||||
ng_conf)
|
||||
else:
|
||||
no_cache = """
|
||||
#Set Nginx Cache
|
||||
set $static_file%s 0;
|
||||
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
|
||||
{
|
||||
set $static_file%s 1;
|
||||
expires 12h;
|
||||
}
|
||||
if ( $static_file%s = 0 )
|
||||
{
|
||||
add_header Cache-Control no-cache;
|
||||
}""" % (random_string, random_string, random_string)
|
||||
if self.check_annotate(ng_conf):
|
||||
rep = r'\n\s*#Set\s*Nginx\s*Cache(.|\n)*\d+m;'
|
||||
ng_conf = re.sub(rep,
|
||||
"\n\t#Set Nginx Cache\n\tproxy_ignore_headers Set-Cookie Cache-Control expires;\n\tadd_header Cache-Control no-cache;",
|
||||
ng_conf)
|
||||
# ng_conf = re.sub(rep,
|
||||
# "\n\t#Set Nginx Cache\n\tproxy_ignore_headers Set-Cookie Cache-Control expires;\n\tadd_header Cache-Control no-cache;",
|
||||
# ng_conf)
|
||||
ng_conf = re.sub(rep,no_cache,ng_conf)
|
||||
else:
|
||||
rep = r"\s+proxy_cache\s+cache_one.*[\n\s\w\_\";\$]+m;"
|
||||
ng_conf = re.sub(rep,
|
||||
r"\n\t#Set Nginx Cache\n\tproxy_ignore_headers Set-Cookie Cache-Control expires;\n\tadd_header Cache-Control no-cache;",
|
||||
ng_conf)
|
||||
# ng_conf = re.sub(rep,
|
||||
# r"\n\t#Set Nginx Cache\n\tproxy_ignore_headers Set-Cookie Cache-Control expires;\n\tadd_header Cache-Control no-cache;",
|
||||
# ng_conf)
|
||||
ng_conf = re.sub(rep,no_cache,ng_conf)
|
||||
|
||||
sub_rep = "sub_filter"
|
||||
subfilter = json.loads(get.subfilter)
|
||||
@@ -3841,18 +3901,36 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s]
|
||||
type = int(get.type)
|
||||
cache = int(get.cache)
|
||||
cachetime = int(get.cachetime)
|
||||
proxysite = get.proxysite
|
||||
proxydir = get.proxydir
|
||||
ng_file = self.setupPath + "/panel/vhost/nginx/" + sitename + ".conf"
|
||||
ap_file = self.setupPath + "/panel/vhost/apache/" + sitename + ".conf"
|
||||
p_conf = self.__read_config(self.__proxyfile)
|
||||
random_string = public.GetRandomString(8)
|
||||
# 配置Nginx
|
||||
# 构造清理缓存连接
|
||||
|
||||
# 构造缓存配置
|
||||
ng_cache = """
|
||||
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
|
||||
{
|
||||
expires 12h;
|
||||
}
|
||||
proxy_ignore_headers Set-Cookie Cache-Control expires;
|
||||
proxy_cache cache_one;
|
||||
proxy_cache_key $host$uri$is_args$args;
|
||||
proxy_cache_valid 200 304 301 302 %sm;""" % (cachetime)
|
||||
no_cache = """
|
||||
set $static_file%s 0;
|
||||
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
|
||||
{
|
||||
set $static_file%s 1;
|
||||
expires 12h;
|
||||
}
|
||||
if ( $static_file%s = 0 )
|
||||
{
|
||||
add_header Cache-Control no-cache;
|
||||
}""" % (random_string,random_string,random_string)
|
||||
# rep = "(https?://[\w\.]+)"
|
||||
# proxysite1 = re.search(rep,get.proxysite).group(1)
|
||||
ng_proxy = '''
|
||||
@@ -3903,28 +3981,32 @@ location ^~ %s
|
||||
ng_sub_filter = ''
|
||||
# 构造反向代理
|
||||
# 如果代理URL后缀带有URI则删除URI,正则匹配不支持proxypass处带有uri
|
||||
php_pass_proxy = get.proxysite
|
||||
if get.proxysite[-1] == '/' or get.proxysite.count('/') > 2 or '?' in get.proxysite:
|
||||
php_pass_proxy = re.search('(https?\:\/\/[\w\.]+)', get.proxysite).group(0)
|
||||
# php_pass_proxy = get.proxysite
|
||||
# if get.proxysite[-1] == '/' or get.proxysite.count('/') > 2 or '?' in get.proxysite:
|
||||
# php_pass_proxy = re.search('(https?\:\/\/[\w\.]+)', get.proxysite).group(0)
|
||||
if advanced == 1:
|
||||
if proxydir[-1] != '/':
|
||||
proxydir = '{}/'.format(proxydir)
|
||||
if proxysite[-1] != '/':
|
||||
proxysite = '{}/'.format(proxysite)
|
||||
if type == 1 and cache == 1:
|
||||
ng_proxy_cache += ng_proxy % (
|
||||
get.proxydir, php_pass_proxy, get.todomain, get.proxydir, get.proxysite, get.todomain,
|
||||
proxydir, proxydir, proxysite, get.todomain,
|
||||
public.GetMsg("NGINX_PERSISTENCE"), ng_sub_filter, ng_cache, get.proxydir)
|
||||
if type == 1 and cache == 0:
|
||||
ng_proxy_cache += ng_proxy % (
|
||||
get.proxydir, php_pass_proxy, get.todomain, get.proxydir, get.proxysite, get.todomain,
|
||||
public.GetMsg("NGINX_PERSISTENCE"), ng_sub_filter, '\tadd_header Cache-Control no-cache;',
|
||||
get.proxydir, get.proxydir, proxysite, get.todomain,
|
||||
public.GetMsg("NGINX_PERSISTENCE"), ng_sub_filter, no_cache,
|
||||
get.proxydir)
|
||||
else:
|
||||
if type == 1 and cache == 1:
|
||||
ng_proxy_cache += ng_proxy % (
|
||||
get.proxydir, php_pass_proxy, get.todomain, get.proxydir, get.proxysite, get.todomain,
|
||||
get.proxydir, get.proxydir, get.proxysite, get.todomain,
|
||||
public.GetMsg("NGINX_PERSISTENCE"), ng_sub_filter, ng_cache, get.proxydir)
|
||||
if type == 1 and cache == 0:
|
||||
ng_proxy_cache += ng_proxy % (
|
||||
get.proxydir, php_pass_proxy, get.todomain, get.proxydir, get.proxysite, get.todomain,
|
||||
public.GetMsg("NGINX_PERSISTENCE"), ng_sub_filter, '\tadd_header Cache-Control no-cache;',
|
||||
get.proxydir, get.proxydir, get.proxysite, get.todomain,
|
||||
public.GetMsg("NGINX_PERSISTENCE"), ng_sub_filter, no_cache,
|
||||
get.proxydir)
|
||||
public.writeFile(ng_proxyfile, ng_proxy_cache)
|
||||
|
||||
@@ -4007,6 +4089,8 @@ location ^~ %s
|
||||
|
||||
# 取伪静态规则应用列表
|
||||
def GetRewriteList(self, get):
|
||||
if get.siteName.find('node_') == 0:
|
||||
get.siteName = get.siteName.replace('node_', '')
|
||||
rewriteList = {}
|
||||
ws = public.get_webserver()
|
||||
if ws == "openlitespeed":
|
||||
@@ -4250,15 +4334,16 @@ location ^~ %s
|
||||
rep = "\n#errorlog(.|\n)*compressArchive\s*1\s*\n#}"
|
||||
tmp = re.search(rep, conf)
|
||||
tmp = tmp.group()
|
||||
result = ''
|
||||
if s == 'on':
|
||||
for l in tmp.strip().splitlines():
|
||||
result += "\n#" + l
|
||||
else:
|
||||
for l in tmp.splitlines():
|
||||
result += "\n" + l[1:]
|
||||
conf = re.sub(rep, "\n" + result.strip(), conf)
|
||||
public.writeFile(filename, conf)
|
||||
if tmp:
|
||||
result = ''
|
||||
if s == 'on':
|
||||
for l in tmp.strip().splitlines():
|
||||
result += "\n#"+l
|
||||
else:
|
||||
for l in tmp.splitlines():
|
||||
result += "\n"+l[1:]
|
||||
conf = re.sub(rep,"\n"+result.strip(),conf)
|
||||
public.writeFile(filename,conf)
|
||||
|
||||
public.serviceReload()
|
||||
return public.returnMsg(True, 'SUCCESS')
|
||||
@@ -4488,6 +4573,7 @@ location ^~ %s
|
||||
def GetSiteRunPath(self, get):
|
||||
siteName = public.M('sites').where('id=?', (get.id,)).getField('name')
|
||||
sitePath = public.M('sites').where('id=?', (get.id,)).getField('path')
|
||||
if not siteName: return {"runPath":"/",'dirs':[]}
|
||||
path = sitePath
|
||||
if public.get_webserver() == 'nginx':
|
||||
filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf'
|
||||
@@ -5000,3 +5086,95 @@ RewriteRule \.(BTPFILE)$ /404.html [R,NC]
|
||||
def modify_dir_auth_pass(self, get):
|
||||
sd = site_dir_auth.SiteDirAuth()
|
||||
return sd.modify_dir_auth_pass(get)
|
||||
|
||||
def _check_path_total(self,path, limit):
|
||||
"""
|
||||
根据路径获取文件/目录大小
|
||||
@path 文件或者目录路径
|
||||
return int
|
||||
"""
|
||||
|
||||
if not os.path.exists(path): return 0;
|
||||
if not os.path.isdir(path): return os.path.getsize(path)
|
||||
size_total = 0
|
||||
for nf in os.walk(path):
|
||||
for f in nf[2]:
|
||||
filename = nf[0] + '/' + f
|
||||
if not os.path.exists(filename): continue;
|
||||
if os.path.islink(filename): continue;
|
||||
size_total += os.path.getsize(filename)
|
||||
if size_total >= limit: return limit
|
||||
return size_total
|
||||
|
||||
def get_average_num(self,slist):
|
||||
"""
|
||||
@获取平均值
|
||||
"""
|
||||
count = len(slist)
|
||||
limit_size = 1 * 1024 * 1024
|
||||
if count <= 0: return limit_size
|
||||
print(slist)
|
||||
if len(slist) > 1:
|
||||
slist = sorted(slist)
|
||||
limit_size =int((slist[0] + slist[-1])/2 * 0.85)
|
||||
return limit_size
|
||||
|
||||
|
||||
def check_del_data(self,get):
|
||||
"""
|
||||
@删除前置检测
|
||||
@ids = [1,2,3]
|
||||
"""
|
||||
ids = json.loads(get['ids'])
|
||||
slist = {}
|
||||
result = []
|
||||
|
||||
import database
|
||||
db_data = database.database().get_database_size(None)
|
||||
|
||||
limit_size = 50 * 1024 * 1024
|
||||
f_list_size = [];db_list_size = []
|
||||
for id in ids:
|
||||
data = public.M('sites').where("id=?",(id,)).field('id,name,path,addtime').find();
|
||||
if not data: continue
|
||||
|
||||
addtime = public.to_date(times = data['addtime'])
|
||||
|
||||
data['st_time'] = addtime
|
||||
data['limit'] = False
|
||||
data['backup_count'] = public.M('backup').where("pid=? AND type=?",(data['id'],'0')).count()
|
||||
f_size = self._check_path_total(data['path'],limit_size)
|
||||
data['total'] = f_size;
|
||||
data['score'] = 0
|
||||
|
||||
#目录太小不计分
|
||||
if f_size > 0:
|
||||
f_list_size.append(f_size)
|
||||
|
||||
# 10k 目录不参与排序
|
||||
if f_size > 10 * 1024: data['score'] = int(time.time() - addtime) + f_size
|
||||
|
||||
if data['total'] >= limit_size: data['limit'] = True
|
||||
data['database'] = False
|
||||
|
||||
find = public.M('databases').field('id,pid,name,ps,addtime').where('pid=?',(data['id'],)).find()
|
||||
if find:
|
||||
db_addtime = public.to_date(times = find['addtime'])
|
||||
|
||||
data['database'] = db_data[find['name']]
|
||||
data['database']['st_time'] = db_addtime
|
||||
|
||||
db_score = 0;
|
||||
db_size = data['database']['total']
|
||||
|
||||
if db_size > 0:
|
||||
db_list_size.append(db_size)
|
||||
if db_size > 50 * 1024: db_score += int(time.time() - db_addtime) + db_size
|
||||
|
||||
data['score'] += db_score
|
||||
result.append(data)
|
||||
|
||||
slist['data'] = sorted(result,key= lambda x:x['score'],reverse=True)
|
||||
slist['file_size'] = self.get_average_num(f_list_size)
|
||||
slist['db_size'] = self.get_average_num(db_list_size)
|
||||
return slist
|
||||
@@ -0,0 +1,112 @@
|
||||
#coding: utf-8
|
||||
#-------------------------------------------------------------------
|
||||
# 宝塔Linux面板
|
||||
#-------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
|
||||
#-------------------------------------------------------------------
|
||||
# Author: hwliang <hwl@bt.cn>
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
#------------------------------
|
||||
# 网站管理控制器
|
||||
#------------------------------
|
||||
import os,sys,public
|
||||
|
||||
class panelSiteController:
|
||||
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def get_parser_list(self,args):
|
||||
'''
|
||||
@name 获取支持的解释器列表
|
||||
@author hwliang<2021-07-13>
|
||||
@param args<dict_obj>
|
||||
@return list
|
||||
'''
|
||||
return public.return_data(True,public.read_config('parser'))
|
||||
|
||||
|
||||
def get_parser_versions(self,args):
|
||||
'''
|
||||
@name 获取指定解释器可用版本列表
|
||||
@author hwliang<2021-07-13>
|
||||
@param args<dict_obj>{
|
||||
parser_name: string<解释器名称>
|
||||
}
|
||||
@return list
|
||||
'''
|
||||
try:
|
||||
public.exists_args('parser_name',args)
|
||||
except Exception as ex:
|
||||
return public.return_data(False,None,1001,ex)
|
||||
parser_name = args.parser_name.strip()
|
||||
config_data = public.read_config('parser')
|
||||
versions = []
|
||||
result = public.return_data(False,versions)
|
||||
for parser_data in config_data:
|
||||
if parser_data['name'] != parser_name: continue
|
||||
if not parser_data['show']: return result
|
||||
if not parser_data['versions']: return result
|
||||
for version in parser_data['versions']:
|
||||
if isinstance(version['check'],str):
|
||||
version['check'] = [version['check']]
|
||||
for check in version['check']:
|
||||
if os.path.exists(check):
|
||||
versions.append(version)
|
||||
return public.return_data(True,versions)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def create_site(self,args):
|
||||
'''
|
||||
@name 创建网站
|
||||
@author hwliang<2021-07-13>
|
||||
@param args<dict_obj> {
|
||||
data: {
|
||||
siteName: string<网站名称>,
|
||||
domains: list<域名列表>, // 如:["www.bt.cn:80","bt.cn:80"]
|
||||
parser_type: string<解释器类型>, // 从 get_parser_list 接口中获取
|
||||
parser_version: string<解释器版本>, // 从 get_parser_versions 接口中获取
|
||||
ps: string<网站备注>,
|
||||
type_id: int<分类标识>,
|
||||
path: string<网站根目录>,
|
||||
stream_info: { // TCP、UDP时传入
|
||||
is_stream: bool<是否为stream>,
|
||||
pool: string<协议类型TCP/UDP>,
|
||||
dst_address: string<目标地址>,
|
||||
dst_port: int<目标端口>,
|
||||
local_port: int<本地映射端口>
|
||||
},
|
||||
process_info: { //绑定进程时传入
|
||||
is_process: bool<是否为启动指定文件>,
|
||||
cwd: string<运行目录>,
|
||||
run_file: string<启动文件>,
|
||||
run_args: string<启动参数>,
|
||||
run_cmd: string<启动命令> //与 run_file/run_args 互斥
|
||||
env: list<环境变量>
|
||||
},
|
||||
ftp_info: { //需要同时创建FTP时传入
|
||||
create: bool<是否创建>,
|
||||
username: string<用户名>,
|
||||
password: string<密码>,
|
||||
path: string<根目录>
|
||||
},
|
||||
database_info: { //需要同时创建数据库时传入
|
||||
create: bool<是否创建>,
|
||||
username: string<用户名>,
|
||||
password: string<密码>,
|
||||
db_name: string<数据库名>,
|
||||
codeing: string<字符集>
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
+16
-2
@@ -87,11 +87,11 @@ class bt_task:
|
||||
# 创建任务
|
||||
def create_task(self, task_name, task_type, task_shell, other=''):
|
||||
self.clean_log()
|
||||
public.M(self.__table).add('name,type,shell,other,addtime,status',
|
||||
task_id = public.M(self.__table).add('name,type,shell,other,addtime,status',
|
||||
(task_name, task_type, task_shell, other, int(time.time()), 0))
|
||||
public.WriteFile(self.__task_tips, 'True')
|
||||
public.ExecShell("/etc/init.d/bt start")
|
||||
return True
|
||||
return task_id
|
||||
|
||||
# 修改任务
|
||||
def modify_task(self, id, key, value):
|
||||
@@ -215,6 +215,20 @@ class bt_task:
|
||||
except:
|
||||
print(public.get_error_info())
|
||||
|
||||
# 前端通过任务ID取某一个任务的日志
|
||||
def get_task_log_by_id(self, get):
|
||||
task_id = get.id
|
||||
task_type = get.task_type
|
||||
log_data = {}
|
||||
if "num" in get:
|
||||
num = int(get.num)
|
||||
log_data = self.get_task_log(task_id, task_type, num)
|
||||
else:
|
||||
log_data = self.get_task_log(task_id, task_type)
|
||||
task_obj = self.get_task_find(task_id)
|
||||
log_data["status"] = task_obj["status"]
|
||||
return log_data
|
||||
|
||||
# 取任务执行日志
|
||||
def get_task_log(self, id, task_type, num=5):
|
||||
log_file = self.__task_path + str(id) + '.log'
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | 宝塔Linux面板 x3
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: zhw <zhw@bt.cn>
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import public
|
||||
import json
|
||||
import os
|
||||
|
||||
py_bin = public.get_python_bin()
|
||||
pip = public.get_pip_bin()
|
||||
|
||||
try:
|
||||
import telegram
|
||||
except:
|
||||
public.ExecShell('{} install telegram'.format(pip))
|
||||
import telegram
|
||||
|
||||
class panel_telegram_bot:
|
||||
panel_path = public.get_panel_path()
|
||||
__tg_conf_file = '{}/data/tg_bot.json'.format(panel_path)
|
||||
|
||||
# 设置tg机器人
|
||||
def set_tg_bot(self,get):
|
||||
"""
|
||||
bot_token:12345677:CCCCCCCC-a0VUo2jjrCCfffaaaaCCDDD
|
||||
my_id:1234567890
|
||||
"""
|
||||
data = {"setup":True,"bot_token":get.bot_token,"my_id":get.my_id}
|
||||
public.writeFile(self.__tg_conf_file,json.dumps(data))
|
||||
return public.returnMsg(True,"Setup successfully")
|
||||
|
||||
# 删除tg机器人
|
||||
def del_tg_bot(self,get):
|
||||
if os.path.exists(self.__tg_conf_file):
|
||||
os.remove(self.__tg_conf_file)
|
||||
return public.returnMsg(True, "Remove successfully")
|
||||
|
||||
# 获取tg机器人信息
|
||||
def get_tg_conf(self,get=None):
|
||||
conf = public.readFile(self.__tg_conf_file)
|
||||
if not conf:
|
||||
return {"setup":False,"bot_token":"","my_id":""}
|
||||
try:
|
||||
return json.loads(conf)
|
||||
except:
|
||||
return {"setup":False,"bot_token":"","my_id":""}
|
||||
|
||||
def process_character(self,content):
|
||||
character = ['.',',','!',':','%','[',']','\/','_','-','>']
|
||||
for c in character:
|
||||
if c in content and '\\{}'.format(c) not in content:
|
||||
content = content.replace(c,'\\'+c)
|
||||
return content
|
||||
|
||||
|
||||
# 使用tg机器人发送消息
|
||||
def send_by_tg_bot(self,content,parse_mode=None):
|
||||
"parse_mode 消息格式 html/markdown/markdownv2"
|
||||
content = self.process_character(content)
|
||||
conf = self.get_tg_conf()
|
||||
bot = telegram.Bot(conf['bot_token'])
|
||||
result = bot.send_message(text=content, chat_id=int(conf['my_id']), parse_mode="MarkdownV2")
|
||||
return result
|
||||
@@ -0,0 +1,351 @@
|
||||
'''This implements an ANSI (VT100) terminal emulator as a subclass of screen.
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
# references:
|
||||
# http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
# http://www.retards.org/terminals/vt102.html
|
||||
# http://vt100.net/docs/vt102-ug/contents.html
|
||||
# http://vt100.net/docs/vt220-rm/
|
||||
# http://www.termsys.demon.co.uk/vtansi.htm
|
||||
|
||||
from . import screen
|
||||
from . import FSM
|
||||
import string
|
||||
|
||||
#
|
||||
# The 'Do.*' functions are helper functions for the ANSI class.
|
||||
#
|
||||
def DoEmit (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.write_ch(fsm.input_symbol)
|
||||
|
||||
def DoStartNumber (fsm):
|
||||
|
||||
fsm.memory.append (fsm.input_symbol)
|
||||
|
||||
def DoBuildNumber (fsm):
|
||||
|
||||
ns = fsm.memory.pop()
|
||||
ns = ns + fsm.input_symbol
|
||||
fsm.memory.append (ns)
|
||||
|
||||
def DoBackOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back ()
|
||||
|
||||
def DoBack (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back (count)
|
||||
|
||||
def DoDownOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down ()
|
||||
|
||||
def DoDown (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down (count)
|
||||
|
||||
def DoForwardOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward ()
|
||||
|
||||
def DoForward (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward (count)
|
||||
|
||||
def DoUpReverse (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up_reverse()
|
||||
|
||||
def DoUpOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up ()
|
||||
|
||||
def DoUp (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up (count)
|
||||
|
||||
def DoHome (fsm):
|
||||
|
||||
c = int(fsm.memory.pop())
|
||||
r = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home (r,c)
|
||||
|
||||
def DoHomeOrigin (fsm):
|
||||
|
||||
c = 1
|
||||
r = 1
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home (r,c)
|
||||
|
||||
def DoEraseDown (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_down()
|
||||
|
||||
def DoErase (fsm):
|
||||
|
||||
arg = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.erase_down()
|
||||
elif arg == 1:
|
||||
screen.erase_up()
|
||||
elif arg == 2:
|
||||
screen.erase_screen()
|
||||
|
||||
def DoEraseEndOfLine (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_end_of_line()
|
||||
|
||||
def DoEraseLine (fsm):
|
||||
|
||||
arg = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.erase_end_of_line()
|
||||
elif arg == 1:
|
||||
screen.erase_start_of_line()
|
||||
elif arg == 2:
|
||||
screen.erase_line()
|
||||
|
||||
def DoEnableScroll (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.scroll_screen()
|
||||
|
||||
def DoCursorSave (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_save_attrs()
|
||||
|
||||
def DoCursorRestore (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_restore_attrs()
|
||||
|
||||
def DoScrollRegion (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
r2 = int(fsm.memory.pop())
|
||||
r1 = int(fsm.memory.pop())
|
||||
screen.scroll_screen_rows (r1,r2)
|
||||
|
||||
def DoMode (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
mode = fsm.memory.pop() # Should be 4
|
||||
# screen.setReplaceMode ()
|
||||
|
||||
def DoLog (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
fout = open ('log', 'a')
|
||||
fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n')
|
||||
fout.close()
|
||||
|
||||
class term (screen.screen):
|
||||
|
||||
'''This class is an abstract, generic terminal.
|
||||
This does nothing. This is a placeholder that
|
||||
provides a common base class for other terminals
|
||||
such as an ANSI terminal. '''
|
||||
|
||||
def __init__ (self, r=24, c=80, *args, **kwargs):
|
||||
|
||||
screen.screen.__init__(self, r,c,*args,**kwargs)
|
||||
|
||||
class ANSI (term):
|
||||
'''This class implements an ANSI (VT100) terminal.
|
||||
It is a stream filter that recognizes ANSI terminal
|
||||
escape sequences and maintains the state of a screen object. '''
|
||||
|
||||
def __init__ (self, r=24,c=80,*args,**kwargs):
|
||||
|
||||
term.__init__(self,r,c,*args,**kwargs)
|
||||
|
||||
#self.screen = screen (24,80)
|
||||
self.state = FSM.FSM ('INIT',[self])
|
||||
self.state.set_default_transition (DoLog, 'INIT')
|
||||
self.state.add_transition_any ('INIT', DoEmit, 'INIT')
|
||||
self.state.add_transition ('\x1b', 'INIT', None, 'ESC')
|
||||
self.state.add_transition_any ('ESC', DoLog, 'INIT')
|
||||
self.state.add_transition ('(', 'ESC', None, 'G0SCS')
|
||||
self.state.add_transition (')', 'ESC', None, 'G1SCS')
|
||||
self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT')
|
||||
self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT')
|
||||
self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT')
|
||||
self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT')
|
||||
self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad.
|
||||
self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND')
|
||||
self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT')
|
||||
self.state.add_transition ('[', 'ESC', None, 'ELB')
|
||||
# ELB means Escape Left Bracket. That is ^[[
|
||||
self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT')
|
||||
self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT')
|
||||
self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT')
|
||||
self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT')
|
||||
self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT')
|
||||
self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT')
|
||||
self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT')
|
||||
self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT')
|
||||
self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT')
|
||||
self.state.add_transition ('?', 'ELB', None, 'MODECRAP')
|
||||
self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1')
|
||||
self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT')
|
||||
self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT')
|
||||
self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT')
|
||||
self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT')
|
||||
self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT')
|
||||
self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT')
|
||||
self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT')
|
||||
### It gets worse... the 'm' code can have infinite number of
|
||||
### number;number;number before it. I've never seen more than two,
|
||||
### but the specs say it's allowed. crap!
|
||||
self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT')
|
||||
### LED control. Same implementation problem as 'm' code.
|
||||
self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT')
|
||||
|
||||
# \E[?47h switch to alternate screen
|
||||
# \E[?47l restores to normal screen from alternate screen.
|
||||
self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM')
|
||||
self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM')
|
||||
self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
|
||||
self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
|
||||
|
||||
#RM Reset Mode Esc [ Ps l none
|
||||
self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON')
|
||||
self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT')
|
||||
self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2')
|
||||
self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT')
|
||||
self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT')
|
||||
### It gets worse... the 'm' code can have infinite number of
|
||||
### number;number;number before it. I've never seen more than two,
|
||||
### but the specs say it's allowed. crap!
|
||||
self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT')
|
||||
### LED control. Same problem as 'm' code.
|
||||
self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT')
|
||||
self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
|
||||
|
||||
# Create a state for 'q' and 'm' which allows an infinite number of ignored numbers
|
||||
self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT')
|
||||
self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X')
|
||||
self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT')
|
||||
self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT')
|
||||
self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT')
|
||||
self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X')
|
||||
|
||||
def process (self, c):
|
||||
"""Process a single character. Called by :meth:`write`."""
|
||||
if isinstance(c, bytes):
|
||||
c = self._decode(c)
|
||||
self.state.process(c)
|
||||
|
||||
def process_list (self, l):
|
||||
|
||||
self.write(l)
|
||||
|
||||
def write (self, s):
|
||||
"""Process text, writing it to the virtual screen while handling
|
||||
ANSI escape codes.
|
||||
"""
|
||||
if isinstance(s, bytes):
|
||||
s = self._decode(s)
|
||||
for c in s:
|
||||
self.process(c)
|
||||
|
||||
def flush (self):
|
||||
pass
|
||||
|
||||
def write_ch (self, ch):
|
||||
'''This puts a character at the current cursor position. The cursor
|
||||
position is moved forward with wrap-around, but no scrolling is done if
|
||||
the cursor hits the lower-right corner of the screen. '''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
#\r and \n both produce a call to cr() and lf(), respectively.
|
||||
ch = ch[0]
|
||||
|
||||
if ch == u'\r':
|
||||
self.cr()
|
||||
return
|
||||
if ch == u'\n':
|
||||
self.crlf()
|
||||
return
|
||||
if ch == chr(screen.BS):
|
||||
self.cursor_back()
|
||||
return
|
||||
self.put_abs(self.cur_r, self.cur_c, ch)
|
||||
old_r = self.cur_r
|
||||
old_c = self.cur_c
|
||||
self.cursor_forward()
|
||||
if old_c == self.cur_c:
|
||||
self.cursor_down()
|
||||
if old_r != self.cur_r:
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
else:
|
||||
self.scroll_up ()
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
self.erase_line()
|
||||
|
||||
def do_sgr (self, fsm):
|
||||
'''Select Graphic Rendition, e.g. color. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
|
||||
def do_decsca (self, fsm):
|
||||
'''Select character protection attribute. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
|
||||
def do_modecrap (self, fsm):
|
||||
'''Handler for \x1b[?<number>h and \x1b[?<number>l. If anyone
|
||||
wanted to actually use these, they'd need to add more states to the
|
||||
FSM rather than just improve or override this method. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''This module implements a Finite State Machine (FSM). In addition to state
|
||||
this FSM also maintains a user defined "memory". So this FSM can be used as a
|
||||
Push-down Automata (PDA) since a PDA is a FSM + memory.
|
||||
|
||||
The following describes how the FSM works, but you will probably also need to
|
||||
see the example function to understand how the FSM is used in practice.
|
||||
|
||||
You define an FSM by building tables of transitions. For a given input symbol
|
||||
the process() method uses these tables to decide what action to call and what
|
||||
the next state will be. The FSM has a table of transitions that associate:
|
||||
|
||||
(input_symbol, current_state) --> (action, next_state)
|
||||
|
||||
Where "action" is a function you define. The symbols and states can be any
|
||||
objects. You use the add_transition() and add_transition_list() methods to add
|
||||
to the transition table. The FSM also has a table of transitions that
|
||||
associate:
|
||||
|
||||
(current_state) --> (action, next_state)
|
||||
|
||||
You use the add_transition_any() method to add to this transition table. The
|
||||
FSM also has one default transition that is not associated with any specific
|
||||
input_symbol or state. You use the set_default_transition() method to set the
|
||||
default transition.
|
||||
|
||||
When an action function is called it is passed a reference to the FSM. The
|
||||
action function may then access attributes of the FSM such as input_symbol,
|
||||
current_state, or "memory". The "memory" attribute can be any object that you
|
||||
want to pass along to the action functions. It is not used by the FSM itself.
|
||||
For parsing you would typically pass a list to be used as a stack.
|
||||
|
||||
The processing sequence is as follows. The process() method is given an
|
||||
input_symbol to process. The FSM will search the table of transitions that
|
||||
associate:
|
||||
|
||||
(input_symbol, current_state) --> (action, next_state)
|
||||
|
||||
If the pair (input_symbol, current_state) is found then process() will call the
|
||||
associated action function and then set the current state to the next_state.
|
||||
|
||||
If the FSM cannot find a match for (input_symbol, current_state) it will then
|
||||
search the table of transitions that associate:
|
||||
|
||||
(current_state) --> (action, next_state)
|
||||
|
||||
If the current_state is found then the process() method will call the
|
||||
associated action function and then set the current state to the next_state.
|
||||
Notice that this table lacks an input_symbol. It lets you define transitions
|
||||
for a current_state and ANY input_symbol. Hence, it is called the "any" table.
|
||||
Remember, it is always checked after first searching the table for a specific
|
||||
(input_symbol, current_state).
|
||||
|
||||
For the case where the FSM did not match either of the previous two cases the
|
||||
FSM will try to use the default transition. If the default transition is
|
||||
defined then the process() method will call the associated action function and
|
||||
then set the current state to the next_state. This lets you define a default
|
||||
transition as a catch-all case. You can think of it as an exception handler.
|
||||
There can be only one default transition.
|
||||
|
||||
Finally, if none of the previous cases are defined for an input_symbol and
|
||||
current_state then the FSM will raise an exception. This may be desirable, but
|
||||
you can always prevent this just by defining a default transition.
|
||||
|
||||
Noah Spurrier 20020822
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
class ExceptionFSM(Exception):
|
||||
|
||||
'''This is the FSM Exception class.'''
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return 'ExceptionFSM: ' + str(self.value)
|
||||
|
||||
class FSM:
|
||||
|
||||
'''This is a Finite State Machine (FSM).
|
||||
'''
|
||||
|
||||
def __init__(self, initial_state, memory=None):
|
||||
|
||||
'''This creates the FSM. You set the initial state here. The "memory"
|
||||
attribute is any object that you want to pass along to the action
|
||||
functions. It is not used by the FSM. For parsing you would typically
|
||||
pass a list to be used as a stack. '''
|
||||
|
||||
# Map (input_symbol, current_state) --> (action, next_state).
|
||||
self.state_transitions = {}
|
||||
# Map (current_state) --> (action, next_state).
|
||||
self.state_transitions_any = {}
|
||||
self.default_transition = None
|
||||
|
||||
self.input_symbol = None
|
||||
self.initial_state = initial_state
|
||||
self.current_state = self.initial_state
|
||||
self.next_state = None
|
||||
self.action = None
|
||||
self.memory = memory
|
||||
|
||||
def reset (self):
|
||||
|
||||
'''This sets the current_state to the initial_state and sets
|
||||
input_symbol to None. The initial state was set by the constructor
|
||||
__init__(). '''
|
||||
|
||||
self.current_state = self.initial_state
|
||||
self.input_symbol = None
|
||||
|
||||
def add_transition (self, input_symbol, state, action=None, next_state=None):
|
||||
|
||||
'''This adds a transition that associates:
|
||||
|
||||
(input_symbol, current_state) --> (action, next_state)
|
||||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged.
|
||||
|
||||
You can also set transitions for a list of symbols by using
|
||||
add_transition_list(). '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
self.state_transitions[(input_symbol, state)] = (action, next_state)
|
||||
|
||||
def add_transition_list (self, list_input_symbols, state, action=None, next_state=None):
|
||||
|
||||
'''This adds the same transition for a list of input symbols.
|
||||
You can pass a list or a string. Note that it is handy to use
|
||||
string.digits, string.whitespace, string.letters, etc. to add
|
||||
transitions that match character classes.
|
||||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged. '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
for input_symbol in list_input_symbols:
|
||||
self.add_transition (input_symbol, state, action, next_state)
|
||||
|
||||
def add_transition_any (self, state, action=None, next_state=None):
|
||||
|
||||
'''This adds a transition that associates:
|
||||
|
||||
(current_state) --> (action, next_state)
|
||||
|
||||
That is, any input symbol will match the current state.
|
||||
The process() method checks the "any" state associations after it first
|
||||
checks for an exact match of (input_symbol, current_state).
|
||||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged. '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
self.state_transitions_any [state] = (action, next_state)
|
||||
|
||||
def set_default_transition (self, action, next_state):
|
||||
|
||||
'''This sets the default transition. This defines an action and
|
||||
next_state if the FSM cannot find the input symbol and the current
|
||||
state in the transition list and if the FSM cannot find the
|
||||
current_state in the transition_any list. This is useful as a final
|
||||
fall-through state for catching errors and undefined states.
|
||||
|
||||
The default transition can be removed by setting the attribute
|
||||
default_transition to None. '''
|
||||
|
||||
self.default_transition = (action, next_state)
|
||||
|
||||
def get_transition (self, input_symbol, state):
|
||||
|
||||
'''This returns (action, next state) given an input_symbol and state.
|
||||
This does not modify the FSM state, so calling this method has no side
|
||||
effects. Normally you do not call this method directly. It is called by
|
||||
process().
|
||||
|
||||
The sequence of steps to check for a defined transition goes from the
|
||||
most specific to the least specific.
|
||||
|
||||
1. Check state_transitions[] that match exactly the tuple,
|
||||
(input_symbol, state)
|
||||
|
||||
2. Check state_transitions_any[] that match (state)
|
||||
In other words, match a specific state and ANY input_symbol.
|
||||
|
||||
3. Check if the default_transition is defined.
|
||||
This catches any input_symbol and any state.
|
||||
This is a handler for errors, undefined states, or defaults.
|
||||
|
||||
4. No transition was defined. If we get here then raise an exception.
|
||||
'''
|
||||
|
||||
if (input_symbol, state) in self.state_transitions:
|
||||
return self.state_transitions[(input_symbol, state)]
|
||||
elif state in self.state_transitions_any:
|
||||
return self.state_transitions_any[state]
|
||||
elif self.default_transition is not None:
|
||||
return self.default_transition
|
||||
else:
|
||||
raise ExceptionFSM ('Transition is undefined: (%s, %s).' %
|
||||
(str(input_symbol), str(state)) )
|
||||
|
||||
def process (self, input_symbol):
|
||||
|
||||
'''This is the main method that you call to process input. This may
|
||||
cause the FSM to change state and call an action. This method calls
|
||||
get_transition() to find the action and next_state associated with the
|
||||
input_symbol and current_state. If the action is None then the action
|
||||
is not called and only the current state is changed. This method
|
||||
processes one complete input symbol. You can process a list of symbols
|
||||
(or a string) by calling process_list(). '''
|
||||
|
||||
self.input_symbol = input_symbol
|
||||
(self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state)
|
||||
if self.action is not None:
|
||||
self.action (self)
|
||||
self.current_state = self.next_state
|
||||
self.next_state = None
|
||||
|
||||
def process_list (self, input_symbols):
|
||||
|
||||
'''This takes a list and sends each element to process(). The list may
|
||||
be a string or any iterable object. '''
|
||||
|
||||
for s in input_symbols:
|
||||
self.process (s)
|
||||
|
||||
##############################################################################
|
||||
# The following is an example that demonstrates the use of the FSM class to
|
||||
# process an RPN expression. Run this module from the command line. You will
|
||||
# get a prompt > for input. Enter an RPN Expression. Numbers may be integers.
|
||||
# Operators are * / + - Use the = sign to evaluate and print the expression.
|
||||
# For example:
|
||||
#
|
||||
# 167 3 2 2 * * * 1 - =
|
||||
#
|
||||
# will print:
|
||||
#
|
||||
# 2003
|
||||
##############################################################################
|
||||
|
||||
import sys
|
||||
import string
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
#
|
||||
# These define the actions.
|
||||
# Note that "memory" is a list being used as a stack.
|
||||
#
|
||||
|
||||
def BeginBuildNumber (fsm):
|
||||
fsm.memory.append (fsm.input_symbol)
|
||||
|
||||
def BuildNumber (fsm):
|
||||
s = fsm.memory.pop ()
|
||||
s = s + fsm.input_symbol
|
||||
fsm.memory.append (s)
|
||||
|
||||
def EndBuildNumber (fsm):
|
||||
s = fsm.memory.pop ()
|
||||
fsm.memory.append (int(s))
|
||||
|
||||
def DoOperator (fsm):
|
||||
ar = fsm.memory.pop()
|
||||
al = fsm.memory.pop()
|
||||
if fsm.input_symbol == '+':
|
||||
fsm.memory.append (al + ar)
|
||||
elif fsm.input_symbol == '-':
|
||||
fsm.memory.append (al - ar)
|
||||
elif fsm.input_symbol == '*':
|
||||
fsm.memory.append (al * ar)
|
||||
elif fsm.input_symbol == '/':
|
||||
fsm.memory.append (al / ar)
|
||||
|
||||
def DoEqual (fsm):
|
||||
print(str(fsm.memory.pop()))
|
||||
|
||||
def Error (fsm):
|
||||
print('That does not compute.')
|
||||
print(str(fsm.input_symbol))
|
||||
|
||||
def main():
|
||||
|
||||
'''This is where the example starts and the FSM state transitions are
|
||||
defined. Note that states are strings (such as 'INIT'). This is not
|
||||
necessary, but it makes the example easier to read. '''
|
||||
|
||||
f = FSM ('INIT', [])
|
||||
f.set_default_transition (Error, 'INIT')
|
||||
f.add_transition_any ('INIT', None, 'INIT')
|
||||
f.add_transition ('=', 'INIT', DoEqual, 'INIT')
|
||||
f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER')
|
||||
f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER')
|
||||
f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT')
|
||||
f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT')
|
||||
|
||||
print()
|
||||
print('Enter an RPN Expression.')
|
||||
print('Numbers may be integers. Operators are * / + -')
|
||||
print('Use the = sign to evaluate and print the expression.')
|
||||
print('For example: ')
|
||||
print(' 167 3 2 2 * * * 1 - =')
|
||||
inputstr = (input if PY3 else raw_input)('> ') # analysis:ignore
|
||||
f.process_list(inputstr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,85 @@
|
||||
'''Pexpect is a Python module for spawning child applications and controlling
|
||||
them automatically. Pexpect can be used for automating interactive applications
|
||||
such as ssh, ftp, passwd, telnet, etc. It can be used to automate setup
|
||||
scripts for duplicating software package installations on different servers. It
|
||||
can be used for automated software testing. Pexpect is in the spirit of Don
|
||||
Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python
|
||||
require TCL and Expect or require C extensions to be compiled. Pexpect does not
|
||||
use C, Expect, or TCL extensions. It should work on any platform that supports
|
||||
the standard Python pty module. The Pexpect interface focuses on ease of use so
|
||||
that simple tasks are easy.
|
||||
|
||||
There are two main interfaces to the Pexpect system; these are the function,
|
||||
run() and the class, spawn. The spawn class is more powerful. The run()
|
||||
function is simpler than spawn, and is good for quickly calling program. When
|
||||
you call the run() function it executes a given program and then returns the
|
||||
output. This is a handy replacement for os.system().
|
||||
|
||||
For example::
|
||||
|
||||
pexpect.run('ls -la')
|
||||
|
||||
The spawn class is the more powerful interface to the Pexpect system. You can
|
||||
use this to spawn a child program then interact with it by sending input and
|
||||
expecting responses (waiting for patterns in the child's output).
|
||||
|
||||
For example::
|
||||
|
||||
child = pexpect.spawn('scp foo user@example.com:.')
|
||||
child.expect('Password:')
|
||||
child.sendline(mypassword)
|
||||
|
||||
This works even for commands that ask for passwords or other input outside of
|
||||
the normal stdio streams. For example, ssh reads input directly from the TTY
|
||||
device which bypasses stdin.
|
||||
|
||||
Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett,
|
||||
Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids
|
||||
vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin,
|
||||
Jacques-Etienne Baudoux, Geoffrey Marshall, Francisco Lourenco, Glen Mabey,
|
||||
Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume
|
||||
Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, John
|
||||
Spiegel, Jan Grant, and Shane Kerr. Let me know if I forgot anyone.
|
||||
|
||||
Pexpect is free, open source, and all that good stuff.
|
||||
http://pexpect.sourceforge.net/
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .utils import split_command_line, which, is_executable_file
|
||||
from .expect import Expecter, searcher_re, searcher_string
|
||||
|
||||
if sys.platform != 'win32':
|
||||
# On Unix, these are available at the top level for backwards compatibility
|
||||
from .pty_spawn import spawn, spawnu
|
||||
from .run import run, runu
|
||||
|
||||
__version__ = '4.8.0'
|
||||
__revision__ = ''
|
||||
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
|
||||
'which', 'split_command_line', '__version__', '__revision__']
|
||||
|
||||
|
||||
|
||||
# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :
|
||||
@@ -0,0 +1,103 @@
|
||||
import asyncio
|
||||
import errno
|
||||
import signal
|
||||
|
||||
from pexpect import EOF
|
||||
|
||||
@asyncio.coroutine
|
||||
def expect_async(expecter, timeout=None):
|
||||
# First process data that was previously read - if it maches, we don't need
|
||||
# async stuff.
|
||||
idx = expecter.existing_data()
|
||||
if idx is not None:
|
||||
return idx
|
||||
if not expecter.spawn.async_pw_transport:
|
||||
pw = PatternWaiter()
|
||||
pw.set_expecter(expecter)
|
||||
transport, pw = yield from asyncio.get_event_loop()\
|
||||
.connect_read_pipe(lambda: pw, expecter.spawn)
|
||||
expecter.spawn.async_pw_transport = pw, transport
|
||||
else:
|
||||
pw, transport = expecter.spawn.async_pw_transport
|
||||
pw.set_expecter(expecter)
|
||||
transport.resume_reading()
|
||||
try:
|
||||
return (yield from asyncio.wait_for(pw.fut, timeout))
|
||||
except asyncio.TimeoutError as e:
|
||||
transport.pause_reading()
|
||||
return expecter.timeout(e)
|
||||
|
||||
@asyncio.coroutine
|
||||
def repl_run_command_async(repl, cmdlines, timeout=-1):
|
||||
res = []
|
||||
repl.child.sendline(cmdlines[0])
|
||||
for line in cmdlines[1:]:
|
||||
yield from repl._expect_prompt(timeout=timeout, async_=True)
|
||||
res.append(repl.child.before)
|
||||
repl.child.sendline(line)
|
||||
|
||||
# Command was fully submitted, now wait for the next prompt
|
||||
prompt_idx = yield from repl._expect_prompt(timeout=timeout, async_=True)
|
||||
if prompt_idx == 1:
|
||||
# We got the continuation prompt - command was incomplete
|
||||
repl.child.kill(signal.SIGINT)
|
||||
yield from repl._expect_prompt(timeout=1, async_=True)
|
||||
raise ValueError("Continuation prompt found - input was incomplete:")
|
||||
return u''.join(res + [repl.child.before])
|
||||
|
||||
class PatternWaiter(asyncio.Protocol):
|
||||
transport = None
|
||||
|
||||
def set_expecter(self, expecter):
|
||||
self.expecter = expecter
|
||||
self.fut = asyncio.Future()
|
||||
|
||||
def found(self, result):
|
||||
if not self.fut.done():
|
||||
self.fut.set_result(result)
|
||||
self.transport.pause_reading()
|
||||
|
||||
def error(self, exc):
|
||||
if not self.fut.done():
|
||||
self.fut.set_exception(exc)
|
||||
self.transport.pause_reading()
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
def data_received(self, data):
|
||||
spawn = self.expecter.spawn
|
||||
s = spawn._decoder.decode(data)
|
||||
spawn._log(s, 'read')
|
||||
|
||||
if self.fut.done():
|
||||
spawn._before.write(s)
|
||||
spawn._buffer.write(s)
|
||||
return
|
||||
|
||||
try:
|
||||
index = self.expecter.new_data(s)
|
||||
if index is not None:
|
||||
# Found a match
|
||||
self.found(index)
|
||||
except Exception as e:
|
||||
self.expecter.errored()
|
||||
self.error(e)
|
||||
|
||||
def eof_received(self):
|
||||
# N.B. If this gets called, async will close the pipe (the spawn object)
|
||||
# for us
|
||||
try:
|
||||
self.expecter.spawn.flag_eof = True
|
||||
index = self.expecter.eof()
|
||||
except EOF as e:
|
||||
self.error(e)
|
||||
else:
|
||||
self.found(index)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
if isinstance(exc, OSError) and exc.errno == errno.EIO:
|
||||
# We may get here without eof_received being called, e.g on Linux
|
||||
self.eof_received()
|
||||
elif exc is not None:
|
||||
self.error(exc)
|
||||
@@ -0,0 +1,16 @@
|
||||
# Different platforms have different names for the systemwide bashrc
|
||||
if [[ -f /etc/bashrc ]]; then
|
||||
source /etc/bashrc
|
||||
fi
|
||||
if [[ -f /etc/bash.bashrc ]]; then
|
||||
source /etc/bash.bashrc
|
||||
fi
|
||||
if [[ -f ~/.bashrc ]]; then
|
||||
source ~/.bashrc
|
||||
fi
|
||||
|
||||
# Reset PS1 so pexpect can find it
|
||||
PS1="$"
|
||||
|
||||
# Unset PROMPT_COMMAND, so that it can't change PS1 to something unexpected.
|
||||
unset PROMPT_COMMAND
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Exception classes used by Pexpect"""
|
||||
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
class ExceptionPexpect(Exception):
|
||||
'''Base class for all exceptions raised by this module.
|
||||
'''
|
||||
|
||||
def __init__(self, value):
|
||||
super(ExceptionPexpect, self).__init__(value)
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def get_trace(self):
|
||||
'''This returns an abbreviated stack trace with lines that only concern
|
||||
the caller. In other words, the stack trace inside the Pexpect module
|
||||
is not included. '''
|
||||
|
||||
tblist = traceback.extract_tb(sys.exc_info()[2])
|
||||
tblist = [item for item in tblist if ('pexpect/__init__' not in item[0])
|
||||
and ('pexpect/expect' not in item[0])]
|
||||
tblist = traceback.format_list(tblist)
|
||||
return ''.join(tblist)
|
||||
|
||||
|
||||
class EOF(ExceptionPexpect):
|
||||
'''Raised when EOF is read from a child.
|
||||
This usually means the child has exited.'''
|
||||
|
||||
|
||||
class TIMEOUT(ExceptionPexpect):
|
||||
'''Raised when a read time exceeds the timeout. '''
|
||||
@@ -0,0 +1,371 @@
|
||||
import time
|
||||
|
||||
from .exceptions import EOF, TIMEOUT
|
||||
|
||||
class Expecter(object):
|
||||
def __init__(self, spawn, searcher, searchwindowsize=-1):
|
||||
self.spawn = spawn
|
||||
self.searcher = searcher
|
||||
# A value of -1 means to use the figure from spawn, which should
|
||||
# be None or a positive number.
|
||||
if searchwindowsize == -1:
|
||||
searchwindowsize = spawn.searchwindowsize
|
||||
self.searchwindowsize = searchwindowsize
|
||||
self.lookback = None
|
||||
if hasattr(searcher, 'longest_string'):
|
||||
self.lookback = searcher.longest_string
|
||||
|
||||
def do_search(self, window, freshlen):
|
||||
spawn = self.spawn
|
||||
searcher = self.searcher
|
||||
if freshlen > len(window):
|
||||
freshlen = len(window)
|
||||
index = searcher.search(window, freshlen, self.searchwindowsize)
|
||||
if index >= 0:
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._buffer.write(window[searcher.end:])
|
||||
spawn.before = spawn._before.getvalue()[
|
||||
0:-(len(window) - searcher.start)]
|
||||
spawn._before = spawn.buffer_type()
|
||||
spawn._before.write(window[searcher.end:])
|
||||
spawn.after = window[searcher.start:searcher.end]
|
||||
spawn.match = searcher.match
|
||||
spawn.match_index = index
|
||||
# Found a match
|
||||
return index
|
||||
elif self.searchwindowsize or self.lookback:
|
||||
maintain = self.searchwindowsize or self.lookback
|
||||
if spawn._buffer.tell() > maintain:
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._buffer.write(window[-maintain:])
|
||||
|
||||
def existing_data(self):
|
||||
# First call from a new call to expect_loop or expect_async.
|
||||
# self.searchwindowsize may have changed.
|
||||
# Treat all data as fresh.
|
||||
spawn = self.spawn
|
||||
before_len = spawn._before.tell()
|
||||
buf_len = spawn._buffer.tell()
|
||||
freshlen = before_len
|
||||
if before_len > buf_len:
|
||||
if not self.searchwindowsize:
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
window = spawn._before.getvalue()
|
||||
spawn._buffer.write(window)
|
||||
elif buf_len < self.searchwindowsize:
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._before.seek(
|
||||
max(0, before_len - self.searchwindowsize))
|
||||
window = spawn._before.read()
|
||||
spawn._buffer.write(window)
|
||||
else:
|
||||
spawn._buffer.seek(max(0, buf_len - self.searchwindowsize))
|
||||
window = spawn._buffer.read()
|
||||
else:
|
||||
if self.searchwindowsize:
|
||||
spawn._buffer.seek(max(0, buf_len - self.searchwindowsize))
|
||||
window = spawn._buffer.read()
|
||||
else:
|
||||
window = spawn._buffer.getvalue()
|
||||
return self.do_search(window, freshlen)
|
||||
|
||||
def new_data(self, data):
|
||||
# A subsequent call, after a call to existing_data.
|
||||
spawn = self.spawn
|
||||
freshlen = len(data)
|
||||
spawn._before.write(data)
|
||||
if not self.searchwindowsize:
|
||||
if self.lookback:
|
||||
# search lookback + new data.
|
||||
old_len = spawn._buffer.tell()
|
||||
spawn._buffer.write(data)
|
||||
spawn._buffer.seek(max(0, old_len - self.lookback))
|
||||
window = spawn._buffer.read()
|
||||
else:
|
||||
# copy the whole buffer (really slow for large datasets).
|
||||
spawn._buffer.write(data)
|
||||
window = spawn.buffer
|
||||
else:
|
||||
if len(data) >= self.searchwindowsize or not spawn._buffer.tell():
|
||||
window = data[-self.searchwindowsize:]
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._buffer.write(window[-self.searchwindowsize:])
|
||||
else:
|
||||
spawn._buffer.write(data)
|
||||
new_len = spawn._buffer.tell()
|
||||
spawn._buffer.seek(max(0, new_len - self.searchwindowsize))
|
||||
window = spawn._buffer.read()
|
||||
return self.do_search(window, freshlen)
|
||||
|
||||
def eof(self, err=None):
|
||||
spawn = self.spawn
|
||||
|
||||
spawn.before = spawn._before.getvalue()
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._before = spawn.buffer_type()
|
||||
spawn.after = EOF
|
||||
index = self.searcher.eof_index
|
||||
if index >= 0:
|
||||
spawn.match = EOF
|
||||
spawn.match_index = index
|
||||
return index
|
||||
else:
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
msg = str(spawn)
|
||||
msg += '\nsearcher: %s' % self.searcher
|
||||
if err is not None:
|
||||
msg = str(err) + '\n' + msg
|
||||
|
||||
exc = EOF(msg)
|
||||
exc.__cause__ = None # in Python 3.x we can use "raise exc from None"
|
||||
raise exc
|
||||
|
||||
def timeout(self, err=None):
|
||||
spawn = self.spawn
|
||||
|
||||
spawn.before = spawn._before.getvalue()
|
||||
spawn.after = TIMEOUT
|
||||
index = self.searcher.timeout_index
|
||||
if index >= 0:
|
||||
spawn.match = TIMEOUT
|
||||
spawn.match_index = index
|
||||
return index
|
||||
else:
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
msg = str(spawn)
|
||||
msg += '\nsearcher: %s' % self.searcher
|
||||
if err is not None:
|
||||
msg = str(err) + '\n' + msg
|
||||
|
||||
exc = TIMEOUT(msg)
|
||||
exc.__cause__ = None # in Python 3.x we can use "raise exc from None"
|
||||
raise exc
|
||||
|
||||
def errored(self):
|
||||
spawn = self.spawn
|
||||
spawn.before = spawn._before.getvalue()
|
||||
spawn.after = None
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
|
||||
def expect_loop(self, timeout=-1):
|
||||
"""Blocking expect"""
|
||||
spawn = self.spawn
|
||||
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
|
||||
try:
|
||||
idx = self.existing_data()
|
||||
if idx is not None:
|
||||
return idx
|
||||
while True:
|
||||
# No match at this point
|
||||
if (timeout is not None) and (timeout < 0):
|
||||
return self.timeout()
|
||||
# Still have time left, so read more data
|
||||
incoming = spawn.read_nonblocking(spawn.maxread, timeout)
|
||||
if self.spawn.delayafterread is not None:
|
||||
time.sleep(self.spawn.delayafterread)
|
||||
idx = self.new_data(incoming)
|
||||
# Keep reading until exception or return.
|
||||
if idx is not None:
|
||||
return idx
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
except EOF as e:
|
||||
return self.eof(e)
|
||||
except TIMEOUT as e:
|
||||
return self.timeout(e)
|
||||
except:
|
||||
self.errored()
|
||||
raise
|
||||
|
||||
|
||||
class searcher_string(object):
|
||||
'''This is a plain string search helper for the spawn.expect_any() method.
|
||||
This helper class is for speed. For more powerful regex patterns
|
||||
see the helper class, searcher_re.
|
||||
|
||||
Attributes:
|
||||
|
||||
eof_index - index of EOF, or -1
|
||||
timeout_index - index of TIMEOUT, or -1
|
||||
|
||||
After a successful match by the search() method the following attributes
|
||||
are available:
|
||||
|
||||
start - index into the buffer, first byte of match
|
||||
end - index into the buffer, first byte after match
|
||||
match - the matching string itself
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, strings):
|
||||
'''This creates an instance of searcher_string. This argument 'strings'
|
||||
may be a list; a sequence of strings; or the EOF or TIMEOUT types. '''
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._strings = []
|
||||
self.longest_string = 0
|
||||
for n, s in enumerate(strings):
|
||||
if s is EOF:
|
||||
self.eof_index = n
|
||||
continue
|
||||
if s is TIMEOUT:
|
||||
self.timeout_index = n
|
||||
continue
|
||||
self._strings.append((n, s))
|
||||
if len(s) > self.longest_string:
|
||||
self.longest_string = len(s)
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object.'''
|
||||
|
||||
ss = [(ns[0], ' %d: %r' % ns) for ns in self._strings]
|
||||
ss.append((-1, 'searcher_string:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append((self.eof_index, ' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append((self.timeout_index,
|
||||
' %d: TIMEOUT' % self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
'''This searches 'buffer' for the first occurrence of one of the search
|
||||
strings. 'freshlen' must indicate the number of bytes at the end of
|
||||
'buffer' which have not been searched before. It helps to avoid
|
||||
searching the same, possibly big, buffer over and over again.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, this returns -1. '''
|
||||
|
||||
first_match = None
|
||||
|
||||
# 'freshlen' helps a lot here. Further optimizations could
|
||||
# possibly include:
|
||||
#
|
||||
# using something like the Boyer-Moore Fast String Searching
|
||||
# Algorithm; pre-compiling the search through a list of
|
||||
# strings into something that can scan the input once to
|
||||
# search for all N strings; realize that if we search for
|
||||
# ['bar', 'baz'] and the input is '...foo' we need not bother
|
||||
# rescanning until we've read three more bytes.
|
||||
#
|
||||
# Sadly, I don't know enough about this interesting topic. /grahn
|
||||
|
||||
for index, s in self._strings:
|
||||
if searchwindowsize is None:
|
||||
# the match, if any, can only be in the fresh data,
|
||||
# or at the very end of the old data
|
||||
offset = -(freshlen + len(s))
|
||||
else:
|
||||
# better obey searchwindowsize
|
||||
offset = -searchwindowsize
|
||||
n = buffer.find(s, offset)
|
||||
if n >= 0 and (first_match is None or n < first_match):
|
||||
first_match = n
|
||||
best_index, best_match = index, s
|
||||
if first_match is None:
|
||||
return -1
|
||||
self.match = best_match
|
||||
self.start = first_match
|
||||
self.end = self.start + len(self.match)
|
||||
return best_index
|
||||
|
||||
|
||||
class searcher_re(object):
|
||||
'''This is regular expression string search helper for the
|
||||
spawn.expect_any() method. This helper class is for powerful
|
||||
pattern matching. For speed, see the helper class, searcher_string.
|
||||
|
||||
Attributes:
|
||||
|
||||
eof_index - index of EOF, or -1
|
||||
timeout_index - index of TIMEOUT, or -1
|
||||
|
||||
After a successful match by the search() method the following attributes
|
||||
are available:
|
||||
|
||||
start - index into the buffer, first byte of match
|
||||
end - index into the buffer, first byte after match
|
||||
match - the re.match object returned by a successful re.search
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, patterns):
|
||||
'''This creates an instance that searches for 'patterns' Where
|
||||
'patterns' may be a list or other sequence of compiled regular
|
||||
expressions, or the EOF or TIMEOUT types.'''
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._searches = []
|
||||
for n, s in enumerate(patterns):
|
||||
if s is EOF:
|
||||
self.eof_index = n
|
||||
continue
|
||||
if s is TIMEOUT:
|
||||
self.timeout_index = n
|
||||
continue
|
||||
self._searches.append((n, s))
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object.'''
|
||||
|
||||
#ss = [(n, ' %d: re.compile("%s")' %
|
||||
# (n, repr(s.pattern))) for n, s in self._searches]
|
||||
ss = list()
|
||||
for n, s in self._searches:
|
||||
ss.append((n, ' %d: re.compile(%r)' % (n, s.pattern)))
|
||||
ss.append((-1, 'searcher_re:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append((self.eof_index, ' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append((self.timeout_index, ' %d: TIMEOUT' %
|
||||
self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
'''This searches 'buffer' for the first occurrence of one of the regular
|
||||
expressions. 'freshlen' must indicate the number of bytes at the end of
|
||||
'buffer' which have not been searched before.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, returns -1.'''
|
||||
|
||||
first_match = None
|
||||
# 'freshlen' doesn't help here -- we cannot predict the
|
||||
# length of a match, and the re module provides no help.
|
||||
if searchwindowsize is None:
|
||||
searchstart = 0
|
||||
else:
|
||||
searchstart = max(0, len(buffer) - searchwindowsize)
|
||||
for index, s in self._searches:
|
||||
match = s.search(buffer, searchstart)
|
||||
if match is None:
|
||||
continue
|
||||
n = match.start()
|
||||
if first_match is None or n < first_match:
|
||||
first_match = n
|
||||
the_match = match
|
||||
best_index = index
|
||||
if first_match is None:
|
||||
return -1
|
||||
self.start = first_match
|
||||
self.match = the_match
|
||||
self.end = self.match.end()
|
||||
return best_index
|
||||
@@ -0,0 +1,148 @@
|
||||
'''This is like pexpect, but it will work with any file descriptor that you
|
||||
pass it. You are responsible for opening and close the file descriptor.
|
||||
This allows you to use Pexpect with sockets and named pipes (FIFOs).
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
from .spawnbase import SpawnBase
|
||||
from .exceptions import ExceptionPexpect, TIMEOUT
|
||||
from .utils import select_ignore_interrupts, poll_ignore_interrupts
|
||||
import os
|
||||
|
||||
__all__ = ['fdspawn']
|
||||
|
||||
class fdspawn(SpawnBase):
|
||||
'''This is like pexpect.spawn but allows you to supply your own open file
|
||||
descriptor. For example, you could use it to read through a file looking
|
||||
for patterns, or to control a modem or serial device. '''
|
||||
|
||||
def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, encoding=None, codec_errors='strict', use_poll=False):
|
||||
'''This takes a file descriptor (an int) or an object that support the
|
||||
fileno() method (returning an int). All Python file-like objects
|
||||
support fileno(). '''
|
||||
|
||||
if type(fd) != type(0) and hasattr(fd, 'fileno'):
|
||||
fd = fd.fileno()
|
||||
|
||||
if type(fd) != type(0):
|
||||
raise ExceptionPexpect('The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.')
|
||||
|
||||
try: # make sure fd is a valid file descriptor
|
||||
os.fstat(fd)
|
||||
except OSError:
|
||||
raise ExceptionPexpect('The fd argument is not a valid file descriptor.')
|
||||
|
||||
self.args = None
|
||||
self.command = None
|
||||
SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
self.child_fd = fd
|
||||
self.own_fd = False
|
||||
self.closed = False
|
||||
self.name = '<file descriptor %d>' % fd
|
||||
self.use_poll = use_poll
|
||||
|
||||
def close (self):
|
||||
"""Close the file descriptor.
|
||||
|
||||
Calling this method a second time does nothing, but if the file
|
||||
descriptor was closed elsewhere, :class:`OSError` will be raised.
|
||||
"""
|
||||
if self.child_fd == -1:
|
||||
return
|
||||
|
||||
self.flush()
|
||||
os.close(self.child_fd)
|
||||
self.child_fd = -1
|
||||
self.closed = True
|
||||
|
||||
def isalive (self):
|
||||
'''This checks if the file descriptor is still valid. If :func:`os.fstat`
|
||||
does not raise an exception then we assume it is alive. '''
|
||||
|
||||
if self.child_fd == -1:
|
||||
return False
|
||||
try:
|
||||
os.fstat(self.child_fd)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def terminate (self, force=False): # pragma: no cover
|
||||
'''Deprecated and invalid. Just raises an exception.'''
|
||||
raise ExceptionPexpect('This method is not valid for file descriptors.')
|
||||
|
||||
# These four methods are left around for backwards compatibility, but not
|
||||
# documented as part of fdpexpect. You're encouraged to use os.write
|
||||
# directly.
|
||||
def send(self, s):
|
||||
"Write to fd, return number of bytes written"
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
return os.write(self.child_fd, b)
|
||||
|
||||
def sendline(self, s):
|
||||
"Write to fd with trailing newline, return number of bytes written"
|
||||
s = self._coerce_send_string(s)
|
||||
return self.send(s + self.linesep)
|
||||
|
||||
def write(self, s):
|
||||
"Write to fd, return None"
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
"Call self.write() for each item in sequence"
|
||||
for s in sequence:
|
||||
self.write(s)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=-1):
|
||||
"""
|
||||
Read from the file descriptor and return the result as a string.
|
||||
|
||||
The read_nonblocking method of :class:`SpawnBase` assumes that a call
|
||||
to os.read will not block (timeout parameter is ignored). This is not
|
||||
the case for POSIX file-like objects such as sockets and serial ports.
|
||||
|
||||
Use :func:`select.select`, timeout is implemented conditionally for
|
||||
POSIX systems.
|
||||
|
||||
:param int size: Read at most *size* bytes.
|
||||
:param int timeout: Wait timeout seconds for file descriptor to be
|
||||
ready to read. When -1 (default), use self.timeout. When 0, poll.
|
||||
:return: String containing the bytes read
|
||||
"""
|
||||
if os.name == 'posix':
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
rlist = [self.child_fd]
|
||||
wlist = []
|
||||
xlist = []
|
||||
if self.use_poll:
|
||||
rlist = poll_ignore_interrupts(rlist, timeout)
|
||||
else:
|
||||
rlist, wlist, xlist = select_ignore_interrupts(
|
||||
rlist, wlist, xlist, timeout
|
||||
)
|
||||
if self.child_fd not in rlist:
|
||||
raise TIMEOUT('Timeout exceeded.')
|
||||
return super(fdspawn, self).read_nonblocking(size)
|
||||
@@ -0,0 +1,188 @@
|
||||
"""Provides an interface like pexpect.spawn interface using subprocess.Popen
|
||||
"""
|
||||
import os
|
||||
import threading
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import shlex
|
||||
|
||||
try:
|
||||
from queue import Queue, Empty # Python 3
|
||||
except ImportError:
|
||||
from Queue import Queue, Empty # Python 2
|
||||
|
||||
from .spawnbase import SpawnBase, PY3
|
||||
from .exceptions import EOF
|
||||
from .utils import string_types
|
||||
|
||||
class PopenSpawn(SpawnBase):
|
||||
def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, cwd=None, env=None, encoding=None,
|
||||
codec_errors='strict', preexec_fn=None):
|
||||
super(PopenSpawn, self).__init__(timeout=timeout, maxread=maxread,
|
||||
searchwindowsize=searchwindowsize, logfile=logfile,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
|
||||
# Note that `SpawnBase` initializes `self.crlf` to `\r\n`
|
||||
# because the default behaviour for a PTY is to convert
|
||||
# incoming LF to `\r\n` (see the `onlcr` flag and
|
||||
# https://stackoverflow.com/a/35887657/5397009). Here we set
|
||||
# it to `os.linesep` because that is what the spawned
|
||||
# application outputs by default and `popen` doesn't translate
|
||||
# anything.
|
||||
if encoding is None:
|
||||
self.crlf = os.linesep.encode ("ascii")
|
||||
else:
|
||||
self.crlf = self.string_type (os.linesep)
|
||||
|
||||
kwargs = dict(bufsize=0, stdin=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
|
||||
cwd=cwd, preexec_fn=preexec_fn, env=env)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
kwargs['startupinfo'] = startupinfo
|
||||
kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
|
||||
if isinstance(cmd, string_types) and sys.platform != 'win32':
|
||||
cmd = shlex.split(cmd, posix=os.name == 'posix')
|
||||
|
||||
self.proc = subprocess.Popen(cmd, **kwargs)
|
||||
self.pid = self.proc.pid
|
||||
self.closed = False
|
||||
self._buf = self.string_type()
|
||||
|
||||
self._read_queue = Queue()
|
||||
self._read_thread = threading.Thread(target=self._read_incoming)
|
||||
self._read_thread.setDaemon(True)
|
||||
self._read_thread.start()
|
||||
|
||||
_read_reached_eof = False
|
||||
|
||||
def read_nonblocking(self, size, timeout):
|
||||
buf = self._buf
|
||||
if self._read_reached_eof:
|
||||
# We have already finished reading. Use up any buffered data,
|
||||
# then raise EOF
|
||||
if buf:
|
||||
self._buf = buf[size:]
|
||||
return buf[:size]
|
||||
else:
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF).')
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
elif timeout is None:
|
||||
timeout = 1e6
|
||||
|
||||
t0 = time.time()
|
||||
while (time.time() - t0) < timeout and size and len(buf) < size:
|
||||
try:
|
||||
incoming = self._read_queue.get_nowait()
|
||||
except Empty:
|
||||
break
|
||||
else:
|
||||
if incoming is None:
|
||||
self._read_reached_eof = True
|
||||
break
|
||||
|
||||
buf += self._decoder.decode(incoming, final=False)
|
||||
|
||||
r, self._buf = buf[:size], buf[size:]
|
||||
|
||||
self._log(r, 'read')
|
||||
return r
|
||||
|
||||
def _read_incoming(self):
|
||||
"""Run in a thread to move output from a pipe to a queue."""
|
||||
fileno = self.proc.stdout.fileno()
|
||||
while 1:
|
||||
buf = b''
|
||||
try:
|
||||
buf = os.read(fileno, 1024)
|
||||
except OSError as e:
|
||||
self._log(e, 'read')
|
||||
|
||||
if not buf:
|
||||
# This indicates we have reached EOF
|
||||
self._read_queue.put(None)
|
||||
return
|
||||
|
||||
self._read_queue.put(buf)
|
||||
|
||||
def write(self, s):
|
||||
'''This is similar to send() except that there is no return value.
|
||||
'''
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
'''This calls write() for each element in the sequence.
|
||||
|
||||
The sequence can be any iterable object producing strings, typically a
|
||||
list of strings. This does not add line separators. There is no return
|
||||
value.
|
||||
'''
|
||||
for s in sequence:
|
||||
self.send(s)
|
||||
|
||||
def send(self, s):
|
||||
'''Send data to the subprocess' stdin.
|
||||
|
||||
Returns the number of bytes written.
|
||||
'''
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
if PY3:
|
||||
return self.proc.stdin.write(b)
|
||||
else:
|
||||
# On Python 2, .write() returns None, so we return the length of
|
||||
# bytes written ourselves. This assumes they all got written.
|
||||
self.proc.stdin.write(b)
|
||||
return len(b)
|
||||
|
||||
def sendline(self, s=''):
|
||||
'''Wraps send(), sending string ``s`` to child process, with os.linesep
|
||||
automatically appended. Returns number of bytes written. '''
|
||||
|
||||
n = self.send(s)
|
||||
return n + self.send(self.linesep)
|
||||
|
||||
def wait(self):
|
||||
'''Wait for the subprocess to finish.
|
||||
|
||||
Returns the exit code.
|
||||
'''
|
||||
status = self.proc.wait()
|
||||
if status >= 0:
|
||||
self.exitstatus = status
|
||||
self.signalstatus = None
|
||||
else:
|
||||
self.exitstatus = None
|
||||
self.signalstatus = -status
|
||||
self.terminated = True
|
||||
return status
|
||||
|
||||
def kill(self, sig):
|
||||
'''Sends a Unix signal to the subprocess.
|
||||
|
||||
Use constants from the :mod:`signal` module to specify which signal.
|
||||
'''
|
||||
if sys.platform == 'win32':
|
||||
if sig in [signal.SIGINT, signal.CTRL_C_EVENT]:
|
||||
sig = signal.CTRL_C_EVENT
|
||||
elif sig in [signal.SIGBREAK, signal.CTRL_BREAK_EVENT]:
|
||||
sig = signal.CTRL_BREAK_EVENT
|
||||
else:
|
||||
sig = signal.SIGTERM
|
||||
|
||||
os.kill(self.proc.pid, sig)
|
||||
|
||||
def sendeof(self):
|
||||
'''Closes the stdin pipe from the writing end.'''
|
||||
self.proc.stdin.close()
|
||||
@@ -0,0 +1,860 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import pty
|
||||
import tty
|
||||
import errno
|
||||
import signal
|
||||
from contextlib import contextmanager
|
||||
|
||||
import ptyprocess
|
||||
from ptyprocess.ptyprocess import use_native_pty_fork
|
||||
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .spawnbase import SpawnBase
|
||||
from .utils import (
|
||||
which, split_command_line, select_ignore_interrupts, poll_ignore_interrupts
|
||||
)
|
||||
|
||||
@contextmanager
|
||||
def _wrap_ptyprocess_err():
|
||||
"""Turn ptyprocess errors into our own ExceptionPexpect errors"""
|
||||
try:
|
||||
yield
|
||||
except ptyprocess.PtyProcessError as e:
|
||||
raise ExceptionPexpect(*e.args)
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
class spawn(SpawnBase):
|
||||
'''This is the main class interface for Pexpect. Use this class to start
|
||||
and control child applications. '''
|
||||
|
||||
# This is purely informational now - changing it has no effect
|
||||
use_native_pty_fork = use_native_pty_fork
|
||||
|
||||
def __init__(self, command, args=[], timeout=30, maxread=2000,
|
||||
searchwindowsize=None, logfile=None, cwd=None, env=None,
|
||||
ignore_sighup=False, echo=True, preexec_fn=None,
|
||||
encoding=None, codec_errors='strict', dimensions=None,
|
||||
use_poll=False):
|
||||
'''This is the constructor. The command parameter may be a string that
|
||||
includes a command and any arguments to the command. For example::
|
||||
|
||||
child = pexpect.spawn('/usr/bin/ftp')
|
||||
child = pexpect.spawn('/usr/bin/ssh user@example.com')
|
||||
child = pexpect.spawn('ls -latr /tmp')
|
||||
|
||||
You may also construct it with a list of arguments like so::
|
||||
|
||||
child = pexpect.spawn('/usr/bin/ftp', [])
|
||||
child = pexpect.spawn('/usr/bin/ssh', ['user@example.com'])
|
||||
child = pexpect.spawn('ls', ['-latr', '/tmp'])
|
||||
|
||||
After this the child application will be created and will be ready to
|
||||
talk to. For normal use, see expect() and send() and sendline().
|
||||
|
||||
Remember that Pexpect does NOT interpret shell meta characters such as
|
||||
redirect, pipe, or wild cards (``>``, ``|``, or ``*``). This is a
|
||||
common mistake. If you want to run a command and pipe it through
|
||||
another command then you must also start a shell. For example::
|
||||
|
||||
child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
|
||||
child.expect(pexpect.EOF)
|
||||
|
||||
The second form of spawn (where you pass a list of arguments) is useful
|
||||
in situations where you wish to spawn a command and pass it its own
|
||||
argument list. This can make syntax more clear. For example, the
|
||||
following is equivalent to the previous example::
|
||||
|
||||
shell_cmd = 'ls -l | grep LOG > logs.txt'
|
||||
child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
|
||||
child.expect(pexpect.EOF)
|
||||
|
||||
The maxread attribute sets the read buffer size. This is maximum number
|
||||
of bytes that Pexpect will try to read from a TTY at one time. Setting
|
||||
the maxread size to 1 will turn off buffering. Setting the maxread
|
||||
value higher may help performance in cases where large amounts of
|
||||
output are read back from the child. This feature is useful in
|
||||
conjunction with searchwindowsize.
|
||||
|
||||
When the keyword argument *searchwindowsize* is None (default), the
|
||||
full buffer is searched at each iteration of receiving incoming data.
|
||||
The default number of bytes scanned at each iteration is very large
|
||||
and may be reduced to collaterally reduce search cost. After
|
||||
:meth:`~.expect` returns, the full buffer attribute remains up to
|
||||
size *maxread* irrespective of *searchwindowsize* value.
|
||||
|
||||
When the keyword argument ``timeout`` is specified as a number,
|
||||
(default: *30*), then :class:`TIMEOUT` will be raised after the value
|
||||
specified has elapsed, in seconds, for any of the :meth:`~.expect`
|
||||
family of method calls. When None, TIMEOUT will not be raised, and
|
||||
:meth:`~.expect` may block indefinitely until match.
|
||||
|
||||
|
||||
The logfile member turns on or off logging. All input and output will
|
||||
be copied to the given file object. Set logfile to None to stop
|
||||
logging. This is the default. Set logfile to sys.stdout to echo
|
||||
everything to standard output. The logfile is flushed after each write.
|
||||
|
||||
Example log input and output to a file::
|
||||
|
||||
child = pexpect.spawn('some_command')
|
||||
fout = open('mylog.txt','wb')
|
||||
child.logfile = fout
|
||||
|
||||
Example log to stdout::
|
||||
|
||||
# In Python 2:
|
||||
child = pexpect.spawn('some_command')
|
||||
child.logfile = sys.stdout
|
||||
|
||||
# In Python 3, we'll use the ``encoding`` argument to decode data
|
||||
# from the subprocess and handle it as unicode:
|
||||
child = pexpect.spawn('some_command', encoding='utf-8')
|
||||
child.logfile = sys.stdout
|
||||
|
||||
The logfile_read and logfile_send members can be used to separately log
|
||||
the input from the child and output sent to the child. Sometimes you
|
||||
don't want to see everything you write to the child. You only want to
|
||||
log what the child sends back. For example::
|
||||
|
||||
child = pexpect.spawn('some_command')
|
||||
child.logfile_read = sys.stdout
|
||||
|
||||
You will need to pass an encoding to spawn in the above code if you are
|
||||
using Python 3.
|
||||
|
||||
To separately log output sent to the child use logfile_send::
|
||||
|
||||
child.logfile_send = fout
|
||||
|
||||
If ``ignore_sighup`` is True, the child process will ignore SIGHUP
|
||||
signals. The default is False from Pexpect 4.0, meaning that SIGHUP
|
||||
will be handled normally by the child.
|
||||
|
||||
The delaybeforesend helps overcome a weird behavior that many users
|
||||
were experiencing. The typical problem was that a user would expect() a
|
||||
"Password:" prompt and then immediately call sendline() to send the
|
||||
password. The user would then see that their password was echoed back
|
||||
to them. Passwords don't normally echo. The problem is caused by the
|
||||
fact that most applications print out the "Password" prompt and then
|
||||
turn off stdin echo, but if you send your password before the
|
||||
application turned off echo, then you get your password echoed.
|
||||
Normally this wouldn't be a problem when interacting with a human at a
|
||||
real keyboard. If you introduce a slight delay just before writing then
|
||||
this seems to clear up the problem. This was such a common problem for
|
||||
many users that I decided that the default pexpect behavior should be
|
||||
to sleep just before writing to the child application. 1/20th of a
|
||||
second (50 ms) seems to be enough to clear up the problem. You can set
|
||||
delaybeforesend to None to return to the old behavior.
|
||||
|
||||
Note that spawn is clever about finding commands on your path.
|
||||
It uses the same logic that "which" uses to find executables.
|
||||
|
||||
If you wish to get the exit status of the child you must call the
|
||||
close() method. The exit or signal status of the child will be stored
|
||||
in self.exitstatus or self.signalstatus. If the child exited normally
|
||||
then exitstatus will store the exit return code and signalstatus will
|
||||
be None. If the child was terminated abnormally with a signal then
|
||||
signalstatus will store the signal value and exitstatus will be None::
|
||||
|
||||
child = pexpect.spawn('some_command')
|
||||
child.close()
|
||||
print(child.exitstatus, child.signalstatus)
|
||||
|
||||
If you need more detail you can also read the self.status member which
|
||||
stores the status returned by os.waitpid. You can interpret this using
|
||||
os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG.
|
||||
|
||||
The echo attribute may be set to False to disable echoing of input.
|
||||
As a pseudo-terminal, all input echoed by the "keyboard" (send()
|
||||
or sendline()) will be repeated to output. For many cases, it is
|
||||
not desirable to have echo enabled, and it may be later disabled
|
||||
using setecho(False) followed by waitnoecho(). However, for some
|
||||
platforms such as Solaris, this is not possible, and should be
|
||||
disabled immediately on spawn.
|
||||
|
||||
If preexec_fn is given, it will be called in the child process before
|
||||
launching the given command. This is useful to e.g. reset inherited
|
||||
signal handlers.
|
||||
|
||||
The dimensions attribute specifies the size of the pseudo-terminal as
|
||||
seen by the subprocess, and is specified as a two-entry tuple (rows,
|
||||
columns). If this is unspecified, the defaults in ptyprocess will apply.
|
||||
|
||||
The use_poll attribute enables using select.poll() over select.select()
|
||||
for socket handling. This is handy if your system could have > 1024 fds
|
||||
'''
|
||||
super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize,
|
||||
logfile=logfile, encoding=encoding, codec_errors=codec_errors)
|
||||
self.STDIN_FILENO = pty.STDIN_FILENO
|
||||
self.STDOUT_FILENO = pty.STDOUT_FILENO
|
||||
self.STDERR_FILENO = pty.STDERR_FILENO
|
||||
self.str_last_chars = 100
|
||||
self.cwd = cwd
|
||||
self.env = env
|
||||
self.echo = echo
|
||||
self.ignore_sighup = ignore_sighup
|
||||
self.__irix_hack = sys.platform.lower().startswith('irix')
|
||||
if command is None:
|
||||
self.command = None
|
||||
self.args = None
|
||||
self.name = '<pexpect factory incomplete>'
|
||||
else:
|
||||
self._spawn(command, args, preexec_fn, dimensions)
|
||||
self.use_poll = use_poll
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object. '''
|
||||
|
||||
s = []
|
||||
s.append(repr(self))
|
||||
s.append('command: ' + str(self.command))
|
||||
s.append('args: %r' % (self.args,))
|
||||
s.append('buffer (last %s chars): %r' % (self.str_last_chars,self.buffer[-self.str_last_chars:]))
|
||||
s.append('before (last %s chars): %r' % (self.str_last_chars,self.before[-self.str_last_chars:] if self.before else ''))
|
||||
s.append('after: %r' % (self.after,))
|
||||
s.append('match: %r' % (self.match,))
|
||||
s.append('match_index: ' + str(self.match_index))
|
||||
s.append('exitstatus: ' + str(self.exitstatus))
|
||||
if hasattr(self, 'ptyproc'):
|
||||
s.append('flag_eof: ' + str(self.flag_eof))
|
||||
s.append('pid: ' + str(self.pid))
|
||||
s.append('child_fd: ' + str(self.child_fd))
|
||||
s.append('closed: ' + str(self.closed))
|
||||
s.append('timeout: ' + str(self.timeout))
|
||||
s.append('delimiter: ' + str(self.delimiter))
|
||||
s.append('logfile: ' + str(self.logfile))
|
||||
s.append('logfile_read: ' + str(self.logfile_read))
|
||||
s.append('logfile_send: ' + str(self.logfile_send))
|
||||
s.append('maxread: ' + str(self.maxread))
|
||||
s.append('ignorecase: ' + str(self.ignorecase))
|
||||
s.append('searchwindowsize: ' + str(self.searchwindowsize))
|
||||
s.append('delaybeforesend: ' + str(self.delaybeforesend))
|
||||
s.append('delayafterclose: ' + str(self.delayafterclose))
|
||||
s.append('delayafterterminate: ' + str(self.delayafterterminate))
|
||||
return '\n'.join(s)
|
||||
|
||||
def _spawn(self, command, args=[], preexec_fn=None, dimensions=None):
|
||||
'''This starts the given command in a child process. This does all the
|
||||
fork/exec type of stuff for a pty. This is called by __init__. If args
|
||||
is empty then command will be parsed (split on spaces) and args will be
|
||||
set to parsed arguments. '''
|
||||
|
||||
# The pid and child_fd of this object get set by this method.
|
||||
# Note that it is difficult for this method to fail.
|
||||
# You cannot detect if the child process cannot start.
|
||||
# So the only way you can tell if the child process started
|
||||
# or not is to try to read from the file descriptor. If you get
|
||||
# EOF immediately then it means that the child is already dead.
|
||||
# That may not necessarily be bad because you may have spawned a child
|
||||
# that performs some task; creates no stdout output; and then dies.
|
||||
|
||||
# If command is an int type then it may represent a file descriptor.
|
||||
if isinstance(command, type(0)):
|
||||
raise ExceptionPexpect('Command is an int type. ' +
|
||||
'If this is a file descriptor then maybe you want to ' +
|
||||
'use fdpexpect.fdspawn which takes an existing ' +
|
||||
'file descriptor instead of a command string.')
|
||||
|
||||
if not isinstance(args, type([])):
|
||||
raise TypeError('The argument, args, must be a list.')
|
||||
|
||||
if args == []:
|
||||
self.args = split_command_line(command)
|
||||
self.command = self.args[0]
|
||||
else:
|
||||
# Make a shallow copy of the args list.
|
||||
self.args = args[:]
|
||||
self.args.insert(0, command)
|
||||
self.command = command
|
||||
|
||||
command_with_path = which(self.command, env=self.env)
|
||||
if command_with_path is None:
|
||||
raise ExceptionPexpect('The command was not found or was not ' +
|
||||
'executable: %s.' % self.command)
|
||||
self.command = command_with_path
|
||||
self.args[0] = self.command
|
||||
|
||||
self.name = '<' + ' '.join(self.args) + '>'
|
||||
|
||||
assert self.pid is None, 'The pid member must be None.'
|
||||
assert self.command is not None, 'The command member must not be None.'
|
||||
|
||||
kwargs = {'echo': self.echo, 'preexec_fn': preexec_fn}
|
||||
if self.ignore_sighup:
|
||||
def preexec_wrapper():
|
||||
"Set SIGHUP to be ignored, then call the real preexec_fn"
|
||||
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
||||
if preexec_fn is not None:
|
||||
preexec_fn()
|
||||
kwargs['preexec_fn'] = preexec_wrapper
|
||||
|
||||
if dimensions is not None:
|
||||
kwargs['dimensions'] = dimensions
|
||||
|
||||
if self.encoding is not None:
|
||||
# Encode command line using the specified encoding
|
||||
self.args = [a if isinstance(a, bytes) else a.encode(self.encoding)
|
||||
for a in self.args]
|
||||
|
||||
self.ptyproc = self._spawnpty(self.args, env=self.env,
|
||||
cwd=self.cwd, **kwargs)
|
||||
|
||||
self.pid = self.ptyproc.pid
|
||||
self.child_fd = self.ptyproc.fd
|
||||
|
||||
|
||||
self.terminated = False
|
||||
self.closed = False
|
||||
|
||||
def _spawnpty(self, args, **kwargs):
|
||||
'''Spawn a pty and return an instance of PtyProcess.'''
|
||||
return ptyprocess.PtyProcess.spawn(args, **kwargs)
|
||||
|
||||
def close(self, force=True):
|
||||
'''This closes the connection with the child application. Note that
|
||||
calling close() more than once is valid. This emulates standard Python
|
||||
behavior with files. Set force to True if you want to make sure that
|
||||
the child is terminated (SIGKILL is sent if the child ignores SIGHUP
|
||||
and SIGINT). '''
|
||||
|
||||
self.flush()
|
||||
with _wrap_ptyprocess_err():
|
||||
# PtyProcessError may be raised if it is not possible to terminate
|
||||
# the child.
|
||||
self.ptyproc.close(force=force)
|
||||
self.isalive() # Update exit status from ptyproc
|
||||
self.child_fd = -1
|
||||
self.closed = True
|
||||
|
||||
def isatty(self):
|
||||
'''This returns True if the file descriptor is open and connected to a
|
||||
tty(-like) device, else False.
|
||||
|
||||
On SVR4-style platforms implementing streams, such as SunOS and HP-UX,
|
||||
the child pty may not appear as a terminal device. This means
|
||||
methods such as setecho(), setwinsize(), getwinsize() may raise an
|
||||
IOError. '''
|
||||
|
||||
return os.isatty(self.child_fd)
|
||||
|
||||
def waitnoecho(self, timeout=-1):
|
||||
'''This waits until the terminal ECHO flag is set False. This returns
|
||||
True if the echo mode is off. This returns False if the ECHO flag was
|
||||
not set False before the timeout. This can be used to detect when the
|
||||
child is waiting for a password. Usually a child application will turn
|
||||
off echo mode when it is waiting for the user to enter a password. For
|
||||
example, instead of expecting the "password:" prompt you can wait for
|
||||
the child to set ECHO off::
|
||||
|
||||
p = pexpect.spawn('ssh user@example.com')
|
||||
p.waitnoecho()
|
||||
p.sendline(mypassword)
|
||||
|
||||
If timeout==-1 then this method will use the value in self.timeout.
|
||||
If timeout==None then this method to block until ECHO flag is False.
|
||||
'''
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
if not self.getecho():
|
||||
return True
|
||||
if timeout < 0 and timeout is not None:
|
||||
return False
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
time.sleep(0.1)
|
||||
|
||||
def getecho(self):
|
||||
'''This returns the terminal echo mode. This returns True if echo is
|
||||
on or False if echo is off. Child applications that are expecting you
|
||||
to enter a password often set ECHO False. See waitnoecho().
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False. '''
|
||||
return self.ptyproc.getecho()
|
||||
|
||||
def setecho(self, state):
|
||||
'''This sets the terminal echo mode on or off. Note that anything the
|
||||
child sent before the echo will be lost, so you should be sure that
|
||||
your input buffer is empty before you call setecho(). For example, the
|
||||
following will work as expected::
|
||||
|
||||
p = pexpect.spawn('cat') # Echo is on by default.
|
||||
p.sendline('1234') # We expect see this twice from the child...
|
||||
p.expect(['1234']) # ... once from the tty echo...
|
||||
p.expect(['1234']) # ... and again from cat itself.
|
||||
p.setecho(False) # Turn off tty echo
|
||||
p.sendline('abcd') # We will set this only once (echoed by cat).
|
||||
p.sendline('wxyz') # We will set this only once (echoed by cat)
|
||||
p.expect(['abcd'])
|
||||
p.expect(['wxyz'])
|
||||
|
||||
The following WILL NOT WORK because the lines sent before the setecho
|
||||
will be lost::
|
||||
|
||||
p = pexpect.spawn('cat')
|
||||
p.sendline('1234')
|
||||
p.setecho(False) # Turn off tty echo
|
||||
p.sendline('abcd') # We will set this only once (echoed by cat).
|
||||
p.sendline('wxyz') # We will set this only once (echoed by cat)
|
||||
p.expect(['1234'])
|
||||
p.expect(['1234'])
|
||||
p.expect(['abcd'])
|
||||
p.expect(['wxyz'])
|
||||
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False.
|
||||
'''
|
||||
return self.ptyproc.setecho(state)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=-1):
|
||||
'''This reads at most size characters from the child application. It
|
||||
includes a timeout. If the read does not complete within the timeout
|
||||
period then a TIMEOUT exception is raised. If the end of file is read
|
||||
then an EOF exception will be raised. If a logfile is specified, a
|
||||
copy is written to that log.
|
||||
|
||||
If timeout is None then the read may block indefinitely.
|
||||
If timeout is -1 then the self.timeout value is used. If timeout is 0
|
||||
then the child is polled and if there is no data immediately ready
|
||||
then this will raise a TIMEOUT exception.
|
||||
|
||||
The timeout refers only to the amount of time to read at least one
|
||||
character. This is not affected by the 'size' parameter, so if you call
|
||||
read_nonblocking(size=100, timeout=30) and only one character is
|
||||
available right away then one character will be returned immediately.
|
||||
It will not wait for 30 seconds for another 99 characters to come in.
|
||||
|
||||
On the other hand, if there are bytes available to read immediately,
|
||||
all those bytes will be read (up to the buffer size). So, if the
|
||||
buffer size is 1 megabyte and there is 1 megabyte of data available
|
||||
to read, the buffer will be filled, regardless of timeout.
|
||||
|
||||
This is a wrapper around os.read(). It uses select.select() or
|
||||
select.poll() to implement the timeout. '''
|
||||
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file.')
|
||||
|
||||
if self.use_poll:
|
||||
def select(timeout):
|
||||
return poll_ignore_interrupts([self.child_fd], timeout)
|
||||
else:
|
||||
def select(timeout):
|
||||
return select_ignore_interrupts([self.child_fd], [], [], timeout)[0]
|
||||
|
||||
# If there is data available to read right now, read as much as
|
||||
# we can. We do this to increase performance if there are a lot
|
||||
# of bytes to be read. This also avoids calling isalive() too
|
||||
# often. See also:
|
||||
# * https://github.com/pexpect/pexpect/pull/304
|
||||
# * http://trac.sagemath.org/ticket/10295
|
||||
if select(0):
|
||||
try:
|
||||
incoming = super(spawn, self).read_nonblocking(size)
|
||||
except EOF:
|
||||
# Maybe the child is dead: update some attributes in that case
|
||||
self.isalive()
|
||||
raise
|
||||
while len(incoming) < size and select(0):
|
||||
try:
|
||||
incoming += super(spawn, self).read_nonblocking(size - len(incoming))
|
||||
except EOF:
|
||||
# Maybe the child is dead: update some attributes in that case
|
||||
self.isalive()
|
||||
# Don't raise EOF, just return what we read so far.
|
||||
return incoming
|
||||
return incoming
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
|
||||
if not self.isalive():
|
||||
# The process is dead, but there may or may not be data
|
||||
# available to read. Note that some systems such as Solaris
|
||||
# do not give an EOF when the child dies. In fact, you can
|
||||
# still try to read from the child_fd -- it will block
|
||||
# forever or until TIMEOUT. For that reason, it's important
|
||||
# to do this check before calling select() with timeout.
|
||||
if select(0):
|
||||
return super(spawn, self).read_nonblocking(size)
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Braindead platform.')
|
||||
elif self.__irix_hack:
|
||||
# Irix takes a long time before it realizes a child was terminated.
|
||||
# Make sure that the timeout is at least 2 seconds.
|
||||
# FIXME So does this mean Irix systems are forced to always have
|
||||
# FIXME a 2 second delay when calling read_nonblocking? That sucks.
|
||||
if timeout is not None and timeout < 2:
|
||||
timeout = 2
|
||||
|
||||
# Because of the select(0) check above, we know that no data
|
||||
# is available right now. But if a non-zero timeout is given
|
||||
# (possibly timeout=None), we call select() with a timeout.
|
||||
if (timeout != 0) and select(timeout):
|
||||
return super(spawn, self).read_nonblocking(size)
|
||||
|
||||
if not self.isalive():
|
||||
# Some platforms, such as Irix, will claim that their
|
||||
# processes are alive; timeout on the select; and
|
||||
# then finally admit that they are not alive.
|
||||
self.flag_eof = True
|
||||
raise EOF('End of File (EOF). Very slow platform.')
|
||||
else:
|
||||
raise TIMEOUT('Timeout exceeded.')
|
||||
|
||||
def write(self, s):
|
||||
'''This is similar to send() except that there is no return value.
|
||||
'''
|
||||
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
'''This calls write() for each element in the sequence. The sequence
|
||||
can be any iterable object producing strings, typically a list of
|
||||
strings. This does not add line separators. There is no return value.
|
||||
'''
|
||||
|
||||
for s in sequence:
|
||||
self.write(s)
|
||||
|
||||
def send(self, s):
|
||||
'''Sends string ``s`` to the child process, returning the number of
|
||||
bytes written. If a logfile is specified, a copy is written to that
|
||||
log.
|
||||
|
||||
The default terminal input mode is canonical processing unless set
|
||||
otherwise by the child process. This allows backspace and other line
|
||||
processing to be performed prior to transmitting to the receiving
|
||||
program. As this is buffered, there is a limited size of such buffer.
|
||||
|
||||
On Linux systems, this is 4096 (defined by N_TTY_BUF_SIZE). All
|
||||
other systems honor the POSIX.1 definition PC_MAX_CANON -- 1024
|
||||
on OSX, 256 on OpenSolaris, and 1920 on FreeBSD.
|
||||
|
||||
This value may be discovered using fpathconf(3)::
|
||||
|
||||
>>> from os import fpathconf
|
||||
>>> print(fpathconf(0, 'PC_MAX_CANON'))
|
||||
256
|
||||
|
||||
On such a system, only 256 bytes may be received per line. Any
|
||||
subsequent bytes received will be discarded. BEL (``'\a'``) is then
|
||||
sent to output if IMAXBEL (termios.h) is set by the tty driver.
|
||||
This is usually enabled by default. Linux does not honor this as
|
||||
an option -- it behaves as though it is always set on.
|
||||
|
||||
Canonical input processing may be disabled altogether by executing
|
||||
a shell, then stty(1), before executing the final program::
|
||||
|
||||
>>> bash = pexpect.spawn('/bin/bash', echo=False)
|
||||
>>> bash.sendline('stty -icanon')
|
||||
>>> bash.sendline('base64')
|
||||
>>> bash.sendline('x' * 5000)
|
||||
'''
|
||||
|
||||
if self.delaybeforesend is not None:
|
||||
time.sleep(self.delaybeforesend)
|
||||
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
return os.write(self.child_fd, b)
|
||||
|
||||
def sendline(self, s=''):
|
||||
'''Wraps send(), sending string ``s`` to child process, with
|
||||
``os.linesep`` automatically appended. Returns number of bytes
|
||||
written. Only a limited number of bytes may be sent for each
|
||||
line in the default terminal mode, see docstring of :meth:`send`.
|
||||
'''
|
||||
s = self._coerce_send_string(s)
|
||||
return self.send(s + self.linesep)
|
||||
|
||||
def _log_control(self, s):
|
||||
"""Write control characters to the appropriate log files"""
|
||||
if self.encoding is not None:
|
||||
s = s.decode(self.encoding, 'replace')
|
||||
self._log(s, 'send')
|
||||
|
||||
def sendcontrol(self, char):
|
||||
'''Helper method that wraps send() with mnemonic access for sending control
|
||||
character to the child (such as Ctrl-C or Ctrl-D). For example, to send
|
||||
Ctrl-G (ASCII 7, bell, '\a')::
|
||||
|
||||
child.sendcontrol('g')
|
||||
|
||||
See also, sendintr() and sendeof().
|
||||
'''
|
||||
n, byte = self.ptyproc.sendcontrol(char)
|
||||
self._log_control(byte)
|
||||
return n
|
||||
|
||||
def sendeof(self):
|
||||
'''This sends an EOF to the child. This sends a character which causes
|
||||
the pending parent output buffer to be sent to the waiting child
|
||||
program without waiting for end-of-line. If it is the first character
|
||||
of the line, the read() in the user program returns 0, which signifies
|
||||
end-of-file. This means to work as expected a sendeof() has to be
|
||||
called at the beginning of a line. This method does not send a newline.
|
||||
It is the responsibility of the caller to ensure the eof is sent at the
|
||||
beginning of a line. '''
|
||||
|
||||
n, byte = self.ptyproc.sendeof()
|
||||
self._log_control(byte)
|
||||
|
||||
def sendintr(self):
|
||||
'''This sends a SIGINT to the child. It does not require
|
||||
the SIGINT to be the first character on a line. '''
|
||||
|
||||
n, byte = self.ptyproc.sendintr()
|
||||
self._log_control(byte)
|
||||
|
||||
@property
|
||||
def flag_eof(self):
|
||||
return self.ptyproc.flag_eof
|
||||
|
||||
@flag_eof.setter
|
||||
def flag_eof(self, value):
|
||||
self.ptyproc.flag_eof = value
|
||||
|
||||
def eof(self):
|
||||
'''This returns True if the EOF exception was ever raised.
|
||||
'''
|
||||
return self.flag_eof
|
||||
|
||||
def terminate(self, force=False):
|
||||
'''This forces a child process to terminate. It starts nicely with
|
||||
SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This
|
||||
returns True if the child was terminated. This returns False if the
|
||||
child could not be terminated. '''
|
||||
|
||||
if not self.isalive():
|
||||
return True
|
||||
try:
|
||||
self.kill(signal.SIGHUP)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
self.kill(signal.SIGCONT)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
self.kill(signal.SIGINT)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
if force:
|
||||
self.kill(signal.SIGKILL)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
except OSError:
|
||||
# I think there are kernel timing issues that sometimes cause
|
||||
# this to happen. I think isalive() reports True, but the
|
||||
# process is dead to the kernel.
|
||||
# Make one last attempt to see if the kernel is up to date.
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def wait(self):
|
||||
'''This waits until the child exits. This is a blocking call. This will
|
||||
not read any data from the child, so this will block forever if the
|
||||
child has unread output and has terminated. In other words, the child
|
||||
may have printed output then called exit(), but, the child is
|
||||
technically still alive until its output is read by the parent.
|
||||
|
||||
This method is non-blocking if :meth:`wait` has already been called
|
||||
previously or :meth:`isalive` method returns False. It simply returns
|
||||
the previously determined exit status.
|
||||
'''
|
||||
|
||||
ptyproc = self.ptyproc
|
||||
with _wrap_ptyprocess_err():
|
||||
# exception may occur if "Is some other process attempting
|
||||
# "job control with our child pid?"
|
||||
exitstatus = ptyproc.wait()
|
||||
self.status = ptyproc.status
|
||||
self.exitstatus = ptyproc.exitstatus
|
||||
self.signalstatus = ptyproc.signalstatus
|
||||
self.terminated = True
|
||||
|
||||
return exitstatus
|
||||
|
||||
def isalive(self):
|
||||
'''This tests if the child process is running or not. This is
|
||||
non-blocking. If the child was terminated then this will read the
|
||||
exitstatus or signalstatus of the child. This returns True if the child
|
||||
process appears to be running or False if not. It can take literally
|
||||
SECONDS for Solaris to return the right status. '''
|
||||
|
||||
ptyproc = self.ptyproc
|
||||
with _wrap_ptyprocess_err():
|
||||
alive = ptyproc.isalive()
|
||||
|
||||
if not alive:
|
||||
self.status = ptyproc.status
|
||||
self.exitstatus = ptyproc.exitstatus
|
||||
self.signalstatus = ptyproc.signalstatus
|
||||
self.terminated = True
|
||||
|
||||
return alive
|
||||
|
||||
def kill(self, sig):
|
||||
|
||||
'''This sends the given signal to the child application. In keeping
|
||||
with UNIX tradition it has a misleading name. It does not necessarily
|
||||
kill the child unless you send the right signal. '''
|
||||
|
||||
# Same as os.kill, but the pid is given for you.
|
||||
if self.isalive():
|
||||
os.kill(self.pid, sig)
|
||||
|
||||
def getwinsize(self):
|
||||
'''This returns the terminal window size of the child tty. The return
|
||||
value is a tuple of (rows, cols). '''
|
||||
return self.ptyproc.getwinsize()
|
||||
|
||||
def setwinsize(self, rows, cols):
|
||||
'''This sets the terminal window size of the child tty. This will cause
|
||||
a SIGWINCH signal to be sent to the child. This does not change the
|
||||
physical window size. It changes the size reported to TTY-aware
|
||||
applications like vi or curses -- applications that respond to the
|
||||
SIGWINCH signal. '''
|
||||
return self.ptyproc.setwinsize(rows, cols)
|
||||
|
||||
|
||||
def interact(self, escape_character=chr(29),
|
||||
input_filter=None, output_filter=None):
|
||||
|
||||
'''This gives control of the child process to the interactive user (the
|
||||
human at the keyboard). Keystrokes are sent to the child process, and
|
||||
the stdout and stderr output of the child process is printed. This
|
||||
simply echos the child stdout and child stderr to the real stdout and
|
||||
it echos the real stdin to the child stdin. When the user types the
|
||||
escape_character this method will return None. The escape_character
|
||||
will not be transmitted. The default for escape_character is
|
||||
entered as ``Ctrl - ]``, the very same as BSD telnet. To prevent
|
||||
escaping, escape_character may be set to None.
|
||||
|
||||
If a logfile is specified, then the data sent and received from the
|
||||
child process in interact mode is duplicated to the given log.
|
||||
|
||||
You may pass in optional input and output filter functions. These
|
||||
functions should take bytes array and return bytes array too. Even
|
||||
with ``encoding='utf-8'`` support, meth:`interact` will always pass
|
||||
input_filter and output_filter bytes. You may need to wrap your
|
||||
function to decode and encode back to UTF-8.
|
||||
|
||||
The output_filter will be passed all the output from the child process.
|
||||
The input_filter will be passed all the keyboard input from the user.
|
||||
The input_filter is run BEFORE the check for the escape_character.
|
||||
|
||||
Note that if you change the window size of the parent the SIGWINCH
|
||||
signal will not be passed through to the child. If you want the child
|
||||
window size to change when the parent's window size changes then do
|
||||
something like the following example::
|
||||
|
||||
import pexpect, struct, fcntl, termios, signal, sys
|
||||
def sigwinch_passthrough (sig, data):
|
||||
s = struct.pack("HHHH", 0, 0, 0, 0)
|
||||
a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(),
|
||||
termios.TIOCGWINSZ , s))
|
||||
if not p.closed:
|
||||
p.setwinsize(a[0],a[1])
|
||||
|
||||
# Note this 'p' is global and used in sigwinch_passthrough.
|
||||
p = pexpect.spawn('/bin/bash')
|
||||
signal.signal(signal.SIGWINCH, sigwinch_passthrough)
|
||||
p.interact()
|
||||
'''
|
||||
|
||||
# Flush the buffer.
|
||||
self.write_to_stdout(self.buffer)
|
||||
self.stdout.flush()
|
||||
self._buffer = self.buffer_type()
|
||||
mode = tty.tcgetattr(self.STDIN_FILENO)
|
||||
tty.setraw(self.STDIN_FILENO)
|
||||
if escape_character is not None and PY3:
|
||||
escape_character = escape_character.encode('latin-1')
|
||||
try:
|
||||
self.__interact_copy(escape_character, input_filter, output_filter)
|
||||
finally:
|
||||
tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode)
|
||||
|
||||
def __interact_writen(self, fd, data):
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
while data != b'' and self.isalive():
|
||||
n = os.write(fd, data)
|
||||
data = data[n:]
|
||||
|
||||
def __interact_read(self, fd):
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
return os.read(fd, 1000)
|
||||
|
||||
def __interact_copy(
|
||||
self, escape_character=None, input_filter=None, output_filter=None
|
||||
):
|
||||
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
while self.isalive():
|
||||
if self.use_poll:
|
||||
r = poll_ignore_interrupts([self.child_fd, self.STDIN_FILENO])
|
||||
else:
|
||||
r, w, e = select_ignore_interrupts(
|
||||
[self.child_fd, self.STDIN_FILENO], [], []
|
||||
)
|
||||
if self.child_fd in r:
|
||||
try:
|
||||
data = self.__interact_read(self.child_fd)
|
||||
except OSError as err:
|
||||
if err.args[0] == errno.EIO:
|
||||
# Linux-style EOF
|
||||
break
|
||||
raise
|
||||
if data == b'':
|
||||
# BSD-style EOF
|
||||
break
|
||||
if output_filter:
|
||||
data = output_filter(data)
|
||||
self._log(data, 'read')
|
||||
os.write(self.STDOUT_FILENO, data)
|
||||
if self.STDIN_FILENO in r:
|
||||
data = self.__interact_read(self.STDIN_FILENO)
|
||||
if input_filter:
|
||||
data = input_filter(data)
|
||||
i = -1
|
||||
if escape_character is not None:
|
||||
i = data.rfind(escape_character)
|
||||
if i != -1:
|
||||
data = data[:i]
|
||||
if data:
|
||||
self._log(data, 'send')
|
||||
self.__interact_writen(self.child_fd, data)
|
||||
break
|
||||
self._log(data, 'send')
|
||||
self.__interact_writen(self.child_fd, data)
|
||||
|
||||
|
||||
def spawnu(*args, **kwargs):
|
||||
"""Deprecated: pass encoding to spawn() instead."""
|
||||
kwargs.setdefault('encoding', 'utf-8')
|
||||
return spawn(*args, **kwargs)
|
||||
@@ -0,0 +1,537 @@
|
||||
'''This class extends pexpect.spawn to specialize setting up SSH connections.
|
||||
This adds methods for login, logout, and expecting the shell prompt.
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
from pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
__all__ = ['ExceptionPxssh', 'pxssh']
|
||||
|
||||
# Exception classes used by this module.
|
||||
class ExceptionPxssh(ExceptionPexpect):
|
||||
'''Raised for pxssh exceptions.
|
||||
'''
|
||||
|
||||
if sys.version_info > (3, 0):
|
||||
from shlex import quote
|
||||
else:
|
||||
_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
||||
|
||||
def quote(s):
|
||||
"""Return a shell-escaped version of the string *s*."""
|
||||
if not s:
|
||||
return "''"
|
||||
if _find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use single quotes, and put single quotes into double quotes
|
||||
# the string $'b is then quoted as '$'"'"'b'
|
||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
class pxssh (spawn):
|
||||
'''This class extends pexpect.spawn to specialize setting up SSH
|
||||
connections. This adds methods for login, logout, and expecting the shell
|
||||
prompt. It does various tricky things to handle many situations in the SSH
|
||||
login process. For example, if the session is your first login, then pxssh
|
||||
automatically accepts the remote certificate; or if you have public key
|
||||
authentication setup then pxssh won't wait for the password prompt.
|
||||
|
||||
pxssh uses the shell prompt to synchronize output from the remote host. In
|
||||
order to make this more robust it sets the shell prompt to something more
|
||||
unique than just $ or #. This should work on most Borne/Bash or Csh style
|
||||
shells.
|
||||
|
||||
Example that runs a few commands on a remote server and prints the result::
|
||||
|
||||
from pexpect import pxssh
|
||||
import getpass
|
||||
try:
|
||||
s = pxssh.pxssh()
|
||||
hostname = raw_input('hostname: ')
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login(hostname, username, password)
|
||||
s.sendline('uptime') # run a command
|
||||
s.prompt() # match the prompt
|
||||
print(s.before) # print everything before the prompt.
|
||||
s.sendline('ls -l')
|
||||
s.prompt()
|
||||
print(s.before)
|
||||
s.sendline('df')
|
||||
s.prompt()
|
||||
print(s.before)
|
||||
s.logout()
|
||||
except pxssh.ExceptionPxssh as e:
|
||||
print("pxssh failed on login.")
|
||||
print(e)
|
||||
|
||||
Example showing how to specify SSH options::
|
||||
|
||||
from pexpect import pxssh
|
||||
s = pxssh.pxssh(options={
|
||||
"StrictHostKeyChecking": "no",
|
||||
"UserKnownHostsFile": "/dev/null"})
|
||||
...
|
||||
|
||||
Note that if you have ssh-agent running while doing development with pxssh
|
||||
then this can lead to a lot of confusion. Many X display managers (xdm,
|
||||
gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI
|
||||
dialog box popup asking for a password during development. You should turn
|
||||
off any key agents during testing. The 'force_password' attribute will turn
|
||||
off public key authentication. This will only work if the remote SSH server
|
||||
is configured to allow password logins. Example of using 'force_password'
|
||||
attribute::
|
||||
|
||||
s = pxssh.pxssh()
|
||||
s.force_password = True
|
||||
hostname = raw_input('hostname: ')
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login (hostname, username, password)
|
||||
|
||||
`debug_command_string` is only for the test suite to confirm that the string
|
||||
generated for SSH is correct, using this will not allow you to do
|
||||
anything other than get a string back from `pxssh.pxssh.login()`.
|
||||
'''
|
||||
|
||||
def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True,
|
||||
options={}, encoding=None, codec_errors='strict',
|
||||
debug_command_string=False, use_poll=False):
|
||||
|
||||
spawn.__init__(self, None, timeout=timeout, maxread=maxread,
|
||||
searchwindowsize=searchwindowsize, logfile=logfile,
|
||||
cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo,
|
||||
encoding=encoding, codec_errors=codec_errors, use_poll=use_poll)
|
||||
|
||||
self.name = '<pxssh>'
|
||||
|
||||
#SUBTLE HACK ALERT! Note that the command that SETS the prompt uses a
|
||||
#slightly different string than the regular expression to match it. This
|
||||
#is because when you set the prompt the command will echo back, but we
|
||||
#don't want to match the echoed command. So if we make the set command
|
||||
#slightly different than the regex we eliminate the problem. To make the
|
||||
#set command different we add a backslash in front of $. The $ doesn't
|
||||
#need to be escaped, but it doesn't hurt and serves to make the set
|
||||
#prompt command different than the regex.
|
||||
|
||||
# used to match the command-line prompt
|
||||
self.UNIQUE_PROMPT = r"\[PEXPECT\][\$\#] "
|
||||
self.PROMPT = self.UNIQUE_PROMPT
|
||||
|
||||
# used to set shell command-line prompt to UNIQUE_PROMPT.
|
||||
self.PROMPT_SET_SH = r"PS1='[PEXPECT]\$ '"
|
||||
self.PROMPT_SET_CSH = r"set prompt='[PEXPECT]\$ '"
|
||||
self.SSH_OPTS = ("-o'RSAAuthentication=no'"
|
||||
+ " -o 'PubkeyAuthentication=no'")
|
||||
# Disabling host key checking, makes you vulnerable to MITM attacks.
|
||||
# + " -o 'StrictHostKeyChecking=no'"
|
||||
# + " -o 'UserKnownHostsFile /dev/null' ")
|
||||
# Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from
|
||||
# displaying a GUI password dialog. I have not figured out how to
|
||||
# disable only SSH_ASKPASS without also disabling X11 forwarding.
|
||||
# Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying!
|
||||
#self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
|
||||
self.force_password = False
|
||||
|
||||
self.debug_command_string = debug_command_string
|
||||
|
||||
# User defined SSH options, eg,
|
||||
# ssh.otions = dict(StrictHostKeyChecking="no",UserKnownHostsFile="/dev/null")
|
||||
self.options = options
|
||||
|
||||
def levenshtein_distance(self, a, b):
|
||||
'''This calculates the Levenshtein distance between a and b.
|
||||
'''
|
||||
|
||||
n, m = len(a), len(b)
|
||||
if n > m:
|
||||
a,b = b,a
|
||||
n,m = m,n
|
||||
current = range(n+1)
|
||||
for i in range(1,m+1):
|
||||
previous, current = current, [i]+[0]*n
|
||||
for j in range(1,n+1):
|
||||
add, delete = previous[j]+1, current[j-1]+1
|
||||
change = previous[j-1]
|
||||
if a[j-1] != b[i-1]:
|
||||
change = change + 1
|
||||
current[j] = min(add, delete, change)
|
||||
return current[n]
|
||||
|
||||
def try_read_prompt(self, timeout_multiplier):
|
||||
'''This facilitates using communication timeouts to perform
|
||||
synchronization as quickly as possible, while supporting high latency
|
||||
connections with a tunable worst case performance. Fast connections
|
||||
should be read almost immediately. Worst case performance for this
|
||||
method is timeout_multiplier * 3 seconds.
|
||||
'''
|
||||
|
||||
# maximum time allowed to read the first response
|
||||
first_char_timeout = timeout_multiplier * 0.5
|
||||
|
||||
# maximum time allowed between subsequent characters
|
||||
inter_char_timeout = timeout_multiplier * 0.1
|
||||
|
||||
# maximum time for reading the entire prompt
|
||||
total_timeout = timeout_multiplier * 3.0
|
||||
|
||||
prompt = self.string_type()
|
||||
begin = time.time()
|
||||
expired = 0.0
|
||||
timeout = first_char_timeout
|
||||
|
||||
while expired < total_timeout:
|
||||
try:
|
||||
prompt += self.read_nonblocking(size=1, timeout=timeout)
|
||||
expired = time.time() - begin # updated total time expired
|
||||
timeout = inter_char_timeout
|
||||
except TIMEOUT:
|
||||
break
|
||||
|
||||
return prompt
|
||||
|
||||
def sync_original_prompt (self, sync_multiplier=1.0):
|
||||
'''This attempts to find the prompt. Basically, press enter and record
|
||||
the response; press enter again and record the response; if the two
|
||||
responses are similar then assume we are at the original prompt.
|
||||
This can be a slow function. Worst case with the default sync_multiplier
|
||||
can take 12 seconds. Low latency connections are more likely to fail
|
||||
with a low sync_multiplier. Best case sync time gets worse with a
|
||||
high sync multiplier (500 ms with default). '''
|
||||
|
||||
# All of these timing pace values are magic.
|
||||
# I came up with these based on what seemed reliable for
|
||||
# connecting to a heavily loaded machine I have.
|
||||
self.sendline()
|
||||
time.sleep(0.1)
|
||||
|
||||
try:
|
||||
# Clear the buffer before getting the prompt.
|
||||
self.try_read_prompt(sync_multiplier)
|
||||
except TIMEOUT:
|
||||
pass
|
||||
|
||||
self.sendline()
|
||||
x = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
self.sendline()
|
||||
a = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
self.sendline()
|
||||
b = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
ld = self.levenshtein_distance(a,b)
|
||||
len_a = len(a)
|
||||
if len_a == 0:
|
||||
return False
|
||||
if float(ld)/len_a < 0.4:
|
||||
return True
|
||||
return False
|
||||
|
||||
### TODO: This is getting messy and I'm pretty sure this isn't perfect.
|
||||
### TODO: I need to draw a flow chart for this.
|
||||
### TODO: Unit tests for SSH tunnels, remote SSH command exec, disabling original prompt sync
|
||||
def login (self, server, username=None, password='', terminal_type='ansi',
|
||||
original_prompt=r"[#$]", login_timeout=10, port=None,
|
||||
auto_prompt_reset=True, ssh_key=None, quiet=True,
|
||||
sync_multiplier=1, check_local_ip=True,
|
||||
password_regex=r'(?i)(?:password:)|(?:passphrase for key)',
|
||||
ssh_tunnels={}, spawn_local_ssh=True,
|
||||
sync_original_prompt=True, ssh_config=None, cmd='ssh'):
|
||||
'''This logs the user into the given server.
|
||||
|
||||
It uses 'original_prompt' to try to find the prompt right after login.
|
||||
When it finds the prompt it immediately tries to reset the prompt to
|
||||
something more easily matched. The default 'original_prompt' is very
|
||||
optimistic and is easily fooled. It's more reliable to try to match the original
|
||||
prompt as exactly as possible to prevent false matches by server
|
||||
strings such as the "Message Of The Day". On many systems you can
|
||||
disable the MOTD on the remote server by creating a zero-length file
|
||||
called :file:`~/.hushlogin` on the remote server. If a prompt cannot be found
|
||||
then this will not necessarily cause the login to fail. In the case of
|
||||
a timeout when looking for the prompt we assume that the original
|
||||
prompt was so weird that we could not match it, so we use a few tricks
|
||||
to guess when we have reached the prompt. Then we hope for the best and
|
||||
blindly try to reset the prompt to something more unique. If that fails
|
||||
then login() raises an :class:`ExceptionPxssh` exception.
|
||||
|
||||
In some situations it is not possible or desirable to reset the
|
||||
original prompt. In this case, pass ``auto_prompt_reset=False`` to
|
||||
inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
|
||||
uses a unique prompt in the :meth:`prompt` method. If the original prompt is
|
||||
not reset then this will disable the :meth:`prompt` method unless you
|
||||
manually set the :attr:`PROMPT` attribute.
|
||||
|
||||
Set ``password_regex`` if there is a MOTD message with `password` in it.
|
||||
Changing this is like playing in traffic, don't (p)expect it to match straight
|
||||
away.
|
||||
|
||||
If you require to connect to another SSH server from the your original SSH
|
||||
connection set ``spawn_local_ssh`` to `False` and this will use your current
|
||||
session to do so. Setting this option to `False` and not having an active session
|
||||
will trigger an error.
|
||||
|
||||
Set ``ssh_key`` to a file path to an SSH private key to use that SSH key
|
||||
for the session authentication.
|
||||
Set ``ssh_key`` to `True` to force passing the current SSH authentication socket
|
||||
to the desired ``hostname``.
|
||||
|
||||
Set ``ssh_config`` to a file path string of an SSH client config file to pass that
|
||||
file to the client to handle itself. You may set any options you wish in here, however
|
||||
doing so will require you to post extra information that you may not want to if you
|
||||
run into issues.
|
||||
|
||||
Alter the ``cmd`` to change the ssh client used, or to prepend it with network
|
||||
namespaces. For example ```cmd="ip netns exec vlan2 ssh"``` to execute the ssh in
|
||||
network namespace named ```vlan```.
|
||||
'''
|
||||
|
||||
session_regex_array = ["(?i)are you sure you want to continue connecting", original_prompt, password_regex, "(?i)permission denied", "(?i)terminal type", TIMEOUT]
|
||||
session_init_regex_array = []
|
||||
session_init_regex_array.extend(session_regex_array)
|
||||
session_init_regex_array.extend(["(?i)connection closed by remote host", EOF])
|
||||
|
||||
ssh_options = ''.join([" -o '%s=%s'" % (o, v) for (o, v) in self.options.items()])
|
||||
if quiet:
|
||||
ssh_options = ssh_options + ' -q'
|
||||
if not check_local_ip:
|
||||
ssh_options = ssh_options + " -o'NoHostAuthenticationForLocalhost=yes'"
|
||||
if self.force_password:
|
||||
ssh_options = ssh_options + ' ' + self.SSH_OPTS
|
||||
if ssh_config is not None:
|
||||
if spawn_local_ssh and not os.path.isfile(ssh_config):
|
||||
raise ExceptionPxssh('SSH config does not exist or is not a file.')
|
||||
ssh_options = ssh_options + ' -F ' + ssh_config
|
||||
if port is not None:
|
||||
ssh_options = ssh_options + ' -p %s'%(str(port))
|
||||
if ssh_key is not None:
|
||||
# Allow forwarding our SSH key to the current session
|
||||
if ssh_key==True:
|
||||
ssh_options = ssh_options + ' -A'
|
||||
else:
|
||||
if spawn_local_ssh and not os.path.isfile(ssh_key):
|
||||
raise ExceptionPxssh('private ssh key does not exist or is not a file.')
|
||||
ssh_options = ssh_options + ' -i %s' % (ssh_key)
|
||||
|
||||
# SSH tunnels, make sure you know what you're putting into the lists
|
||||
# under each heading. Do not expect these to open 100% of the time,
|
||||
# The port you're requesting might be bound.
|
||||
#
|
||||
# The structure should be like this:
|
||||
# { 'local': ['2424:localhost:22'], # Local SSH tunnels
|
||||
# 'remote': ['2525:localhost:22'], # Remote SSH tunnels
|
||||
# 'dynamic': [8888] } # Dynamic/SOCKS tunnels
|
||||
if ssh_tunnels!={} and isinstance({},type(ssh_tunnels)):
|
||||
tunnel_types = {
|
||||
'local':'L',
|
||||
'remote':'R',
|
||||
'dynamic':'D'
|
||||
}
|
||||
for tunnel_type in tunnel_types:
|
||||
cmd_type = tunnel_types[tunnel_type]
|
||||
if tunnel_type in ssh_tunnels:
|
||||
tunnels = ssh_tunnels[tunnel_type]
|
||||
for tunnel in tunnels:
|
||||
if spawn_local_ssh==False:
|
||||
tunnel = quote(str(tunnel))
|
||||
ssh_options = ssh_options + ' -' + cmd_type + ' ' + str(tunnel)
|
||||
|
||||
if username is not None:
|
||||
ssh_options = ssh_options + ' -l ' + username
|
||||
elif ssh_config is None:
|
||||
raise TypeError('login() needs either a username or an ssh_config')
|
||||
else: # make sure ssh_config has an entry for the server with a username
|
||||
with open(ssh_config, 'rt') as f:
|
||||
lines = [l.strip() for l in f.readlines()]
|
||||
|
||||
server_regex = r'^Host\s+%s\s*$' % server
|
||||
user_regex = r'^User\s+\w+\s*$'
|
||||
config_has_server = False
|
||||
server_has_username = False
|
||||
for line in lines:
|
||||
if not config_has_server and re.match(server_regex, line, re.IGNORECASE):
|
||||
config_has_server = True
|
||||
elif config_has_server and 'hostname' in line.lower():
|
||||
pass
|
||||
elif config_has_server and 'host' in line.lower():
|
||||
server_has_username = False # insurance
|
||||
break # we have left the relevant section
|
||||
elif config_has_server and re.match(user_regex, line, re.IGNORECASE):
|
||||
server_has_username = True
|
||||
break
|
||||
|
||||
if lines:
|
||||
del line
|
||||
|
||||
del lines
|
||||
|
||||
if not config_has_server:
|
||||
raise TypeError('login() ssh_config has no Host entry for %s' % server)
|
||||
elif not server_has_username:
|
||||
raise TypeError('login() ssh_config has no user entry for %s' % server)
|
||||
|
||||
cmd += " %s %s" % (ssh_options, server)
|
||||
if self.debug_command_string:
|
||||
return(cmd)
|
||||
|
||||
# Are we asking for a local ssh command or to spawn one in another session?
|
||||
if spawn_local_ssh:
|
||||
spawn._spawn(self, cmd)
|
||||
else:
|
||||
self.sendline(cmd)
|
||||
|
||||
# This does not distinguish between a remote server 'password' prompt
|
||||
# and a local ssh 'passphrase' prompt (for unlocking a private key).
|
||||
i = self.expect(session_init_regex_array, timeout=login_timeout)
|
||||
|
||||
# First phase
|
||||
if i==0:
|
||||
# New certificate -- always accept it.
|
||||
# This is what you get if SSH does not have the remote host's
|
||||
# public key stored in the 'known_hosts' cache.
|
||||
self.sendline("yes")
|
||||
i = self.expect(session_regex_array)
|
||||
if i==2: # password or passphrase
|
||||
self.sendline(password)
|
||||
i = self.expect(session_regex_array)
|
||||
if i==4:
|
||||
self.sendline(terminal_type)
|
||||
i = self.expect(session_regex_array)
|
||||
if i==7:
|
||||
self.close()
|
||||
raise ExceptionPxssh('Could not establish connection to host')
|
||||
|
||||
# Second phase
|
||||
if i==0:
|
||||
# This is weird. This should not happen twice in a row.
|
||||
self.close()
|
||||
raise ExceptionPxssh('Weird error. Got "are you sure" prompt twice.')
|
||||
elif i==1: # can occur if you have a public key pair set to authenticate.
|
||||
### TODO: May NOT be OK if expect() got tricked and matched a false prompt.
|
||||
pass
|
||||
elif i==2: # password prompt again
|
||||
# For incorrect passwords, some ssh servers will
|
||||
# ask for the password again, others return 'denied' right away.
|
||||
# If we get the password prompt again then this means
|
||||
# we didn't get the password right the first time.
|
||||
self.close()
|
||||
raise ExceptionPxssh('password refused')
|
||||
elif i==3: # permission denied -- password was bad.
|
||||
self.close()
|
||||
raise ExceptionPxssh('permission denied')
|
||||
elif i==4: # terminal type again? WTF?
|
||||
self.close()
|
||||
raise ExceptionPxssh('Weird error. Got "terminal type" prompt twice.')
|
||||
elif i==5: # Timeout
|
||||
#This is tricky... I presume that we are at the command-line prompt.
|
||||
#It may be that the shell prompt was so weird that we couldn't match
|
||||
#it. Or it may be that we couldn't log in for some other reason. I
|
||||
#can't be sure, but it's safe to guess that we did login because if
|
||||
#I presume wrong and we are not logged in then this should be caught
|
||||
#later when I try to set the shell prompt.
|
||||
pass
|
||||
elif i==6: # Connection closed by remote host
|
||||
self.close()
|
||||
raise ExceptionPxssh('connection closed')
|
||||
else: # Unexpected
|
||||
self.close()
|
||||
raise ExceptionPxssh('unexpected login response')
|
||||
if sync_original_prompt:
|
||||
if not self.sync_original_prompt(sync_multiplier):
|
||||
self.close()
|
||||
raise ExceptionPxssh('could not synchronize with original prompt')
|
||||
# We appear to be in.
|
||||
# set shell prompt to something unique.
|
||||
if auto_prompt_reset:
|
||||
if not self.set_unique_prompt():
|
||||
self.close()
|
||||
raise ExceptionPxssh('could not set shell prompt '
|
||||
'(received: %r, expected: %r).' % (
|
||||
self.before, self.PROMPT,))
|
||||
return True
|
||||
|
||||
def logout (self):
|
||||
'''Sends exit to the remote shell.
|
||||
|
||||
If there are stopped jobs then this automatically sends exit twice.
|
||||
'''
|
||||
self.sendline("exit")
|
||||
index = self.expect([EOF, "(?i)there are stopped jobs"])
|
||||
if index==1:
|
||||
self.sendline("exit")
|
||||
self.expect(EOF)
|
||||
self.close()
|
||||
|
||||
def prompt(self, timeout=-1):
|
||||
'''Match the next shell prompt.
|
||||
|
||||
This is little more than a short-cut to the :meth:`~pexpect.spawn.expect`
|
||||
method. Note that if you called :meth:`login` with
|
||||
``auto_prompt_reset=False``, then before calling :meth:`prompt` you must
|
||||
set the :attr:`PROMPT` attribute to a regex that it will use for
|
||||
matching the prompt.
|
||||
|
||||
Calling :meth:`prompt` will erase the contents of the :attr:`before`
|
||||
attribute even if no prompt is ever matched. If timeout is not given or
|
||||
it is set to -1 then self.timeout is used.
|
||||
|
||||
:return: True if the shell prompt was matched, False if the timeout was
|
||||
reached.
|
||||
'''
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
|
||||
if i==1:
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_unique_prompt(self):
|
||||
'''This sets the remote prompt to something more unique than ``#`` or ``$``.
|
||||
This makes it easier for the :meth:`prompt` method to match the shell prompt
|
||||
unambiguously. This method is called automatically by the :meth:`login`
|
||||
method, but you may want to call it manually if you somehow reset the
|
||||
shell prompt. For example, if you 'su' to a different user then you
|
||||
will need to manually reset the prompt. This sends shell commands to
|
||||
the remote host to set the prompt, so this assumes the remote host is
|
||||
ready to receive commands.
|
||||
|
||||
Alternatively, you may use your own prompt pattern. In this case you
|
||||
should call :meth:`login` with ``auto_prompt_reset=False``; then set the
|
||||
:attr:`PROMPT` attribute to a regular expression. After that, the
|
||||
:meth:`prompt` method will try to match your prompt pattern.
|
||||
'''
|
||||
|
||||
self.sendline("unset PROMPT_COMMAND")
|
||||
self.sendline(self.PROMPT_SET_SH) # sh-style
|
||||
i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0: # csh-style
|
||||
self.sendline(self.PROMPT_SET_CSH)
|
||||
i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
# vi:ts=4:sw=4:expandtab:ft=python:
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Generic wrapper for read-eval-print-loops, a.k.a. interactive shells
|
||||
"""
|
||||
import os.path
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import pexpect
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
if PY3:
|
||||
basestring = str
|
||||
|
||||
PEXPECT_PROMPT = u'[PEXPECT_PROMPT>'
|
||||
PEXPECT_CONTINUATION_PROMPT = u'[PEXPECT_PROMPT+'
|
||||
|
||||
class REPLWrapper(object):
|
||||
"""Wrapper for a REPL.
|
||||
|
||||
:param cmd_or_spawn: This can either be an instance of :class:`pexpect.spawn`
|
||||
in which a REPL has already been started, or a str command to start a new
|
||||
REPL process.
|
||||
:param str orig_prompt: The prompt to expect at first.
|
||||
:param str prompt_change: A command to change the prompt to something more
|
||||
unique. If this is ``None``, the prompt will not be changed. This will
|
||||
be formatted with the new and continuation prompts as positional
|
||||
parameters, so you can use ``{}`` style formatting to insert them into
|
||||
the command.
|
||||
:param str new_prompt: The more unique prompt to expect after the change.
|
||||
:param str extra_init_cmd: Commands to do extra initialisation, such as
|
||||
disabling pagers.
|
||||
"""
|
||||
def __init__(self, cmd_or_spawn, orig_prompt, prompt_change,
|
||||
new_prompt=PEXPECT_PROMPT,
|
||||
continuation_prompt=PEXPECT_CONTINUATION_PROMPT,
|
||||
extra_init_cmd=None):
|
||||
if isinstance(cmd_or_spawn, basestring):
|
||||
self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8')
|
||||
else:
|
||||
self.child = cmd_or_spawn
|
||||
if self.child.echo:
|
||||
# Existing spawn instance has echo enabled, disable it
|
||||
# to prevent our input from being repeated to output.
|
||||
self.child.setecho(False)
|
||||
self.child.waitnoecho()
|
||||
|
||||
if prompt_change is None:
|
||||
self.prompt = orig_prompt
|
||||
else:
|
||||
self.set_prompt(orig_prompt,
|
||||
prompt_change.format(new_prompt, continuation_prompt))
|
||||
self.prompt = new_prompt
|
||||
self.continuation_prompt = continuation_prompt
|
||||
|
||||
self._expect_prompt()
|
||||
|
||||
if extra_init_cmd is not None:
|
||||
self.run_command(extra_init_cmd)
|
||||
|
||||
def set_prompt(self, orig_prompt, prompt_change):
|
||||
self.child.expect(orig_prompt)
|
||||
self.child.sendline(prompt_change)
|
||||
|
||||
def _expect_prompt(self, timeout=-1, async_=False):
|
||||
return self.child.expect_exact([self.prompt, self.continuation_prompt],
|
||||
timeout=timeout, async_=async_)
|
||||
|
||||
def run_command(self, command, timeout=-1, async_=False):
|
||||
"""Send a command to the REPL, wait for and return output.
|
||||
|
||||
:param str command: The command to send. Trailing newlines are not needed.
|
||||
This should be a complete block of input that will trigger execution;
|
||||
if a continuation prompt is found after sending input, :exc:`ValueError`
|
||||
will be raised.
|
||||
:param int timeout: How long to wait for the next prompt. -1 means the
|
||||
default from the :class:`pexpect.spawn` object (default 30 seconds).
|
||||
None means to wait indefinitely.
|
||||
:param bool async_: On Python 3.4, or Python 3.3 with asyncio
|
||||
installed, passing ``async_=True`` will make this return an
|
||||
:mod:`asyncio` Future, which you can yield from to get the same
|
||||
result that this method would normally give directly.
|
||||
"""
|
||||
# Split up multiline commands and feed them in bit-by-bit
|
||||
cmdlines = command.splitlines()
|
||||
# splitlines ignores trailing newlines - add it back in manually
|
||||
if command.endswith('\n'):
|
||||
cmdlines.append('')
|
||||
if not cmdlines:
|
||||
raise ValueError("No command was given")
|
||||
|
||||
if async_:
|
||||
from ._async import repl_run_command_async
|
||||
return repl_run_command_async(self, cmdlines, timeout)
|
||||
|
||||
res = []
|
||||
self.child.sendline(cmdlines[0])
|
||||
for line in cmdlines[1:]:
|
||||
self._expect_prompt(timeout=timeout)
|
||||
res.append(self.child.before)
|
||||
self.child.sendline(line)
|
||||
|
||||
# Command was fully submitted, now wait for the next prompt
|
||||
if self._expect_prompt(timeout=timeout) == 1:
|
||||
# We got the continuation prompt - command was incomplete
|
||||
self.child.kill(signal.SIGINT)
|
||||
self._expect_prompt(timeout=1)
|
||||
raise ValueError("Continuation prompt found - input was incomplete:\n"
|
||||
+ command)
|
||||
return u''.join(res + [self.child.before])
|
||||
|
||||
def python(command=sys.executable):
|
||||
"""Start a Python shell and return a :class:`REPLWrapper` object."""
|
||||
return REPLWrapper(command, u">>> ", u"import sys; sys.ps1={0!r}; sys.ps2={1!r}")
|
||||
|
||||
def bash(command="bash"):
|
||||
"""Start a bash shell and return a :class:`REPLWrapper` object."""
|
||||
bashrc = os.path.join(os.path.dirname(__file__), 'bashrc.sh')
|
||||
child = pexpect.spawn(command, ['--rcfile', bashrc], echo=False,
|
||||
encoding='utf-8')
|
||||
|
||||
# If the user runs 'env', the value of PS1 will be in the output. To avoid
|
||||
# replwrap seeing that as the next prompt, we'll embed the marker characters
|
||||
# for invisible characters in the prompt; these show up when inspecting the
|
||||
# environment variable, but not when bash displays the prompt.
|
||||
ps1 = PEXPECT_PROMPT[:5] + u'\\[\\]' + PEXPECT_PROMPT[5:]
|
||||
ps2 = PEXPECT_CONTINUATION_PROMPT[:5] + u'\\[\\]' + PEXPECT_CONTINUATION_PROMPT[5:]
|
||||
prompt_change = u"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(ps1, ps2)
|
||||
|
||||
return REPLWrapper(child, u'\\$', prompt_change,
|
||||
extra_init_cmd="export PAGER=cat")
|
||||
@@ -0,0 +1,157 @@
|
||||
import sys
|
||||
import types
|
||||
|
||||
from .exceptions import EOF, TIMEOUT
|
||||
from .pty_spawn import spawn
|
||||
|
||||
def run(command, timeout=30, withexitstatus=False, events=None,
|
||||
extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
|
||||
|
||||
'''
|
||||
This function runs the given command; waits for it to finish; then
|
||||
returns all output as a string. STDERR is included in output. If the full
|
||||
path to the command is not given then the path is searched.
|
||||
|
||||
Note that lines are terminated by CR/LF (\\r\\n) combination even on
|
||||
UNIX-like systems because this is the standard for pseudottys. If you set
|
||||
'withexitstatus' to true, then run will return a tuple of (command_output,
|
||||
exitstatus). If 'withexitstatus' is false then this returns just
|
||||
command_output.
|
||||
|
||||
The run() function can often be used instead of creating a spawn instance.
|
||||
For example, the following code uses spawn::
|
||||
|
||||
from pexpect import *
|
||||
child = spawn('scp foo user@example.com:.')
|
||||
child.expect('(?i)password')
|
||||
child.sendline(mypassword)
|
||||
|
||||
The previous code can be replace with the following::
|
||||
|
||||
from pexpect import *
|
||||
run('scp foo user@example.com:.', events={'(?i)password': mypassword})
|
||||
|
||||
**Examples**
|
||||
|
||||
Start the apache daemon on the local machine::
|
||||
|
||||
from pexpect import *
|
||||
run("/usr/local/apache/bin/apachectl start")
|
||||
|
||||
Check in a file using SVN::
|
||||
|
||||
from pexpect import *
|
||||
run("svn ci -m 'automatic commit' my_file.py")
|
||||
|
||||
Run a command and capture exit status::
|
||||
|
||||
from pexpect import *
|
||||
(command_output, exitstatus) = run('ls -l /bin', withexitstatus=1)
|
||||
|
||||
The following will run SSH and execute 'ls -l' on the remote machine. The
|
||||
password 'secret' will be sent if the '(?i)password' pattern is ever seen::
|
||||
|
||||
run("ssh username@machine.example.com 'ls -l'",
|
||||
events={'(?i)password':'secret\\n'})
|
||||
|
||||
This will start mencoder to rip a video from DVD. This will also display
|
||||
progress ticks every 5 seconds as it runs. For example::
|
||||
|
||||
from pexpect import *
|
||||
def print_ticks(d):
|
||||
print d['event_count'],
|
||||
run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
|
||||
events={TIMEOUT:print_ticks}, timeout=5)
|
||||
|
||||
The 'events' argument should be either a dictionary or a tuple list that
|
||||
contains patterns and responses. Whenever one of the patterns is seen
|
||||
in the command output, run() will send the associated response string.
|
||||
So, run() in the above example can be also written as::
|
||||
|
||||
run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
|
||||
events=[(TIMEOUT,print_ticks)], timeout=5)
|
||||
|
||||
Use a tuple list for events if the command output requires a delicate
|
||||
control over what pattern should be matched, since the tuple list is passed
|
||||
to pexpect() as its pattern list, with the order of patterns preserved.
|
||||
|
||||
Note that you should put newlines in your string if Enter is necessary.
|
||||
|
||||
Like the example above, the responses may also contain a callback, either
|
||||
a function or method. It should accept a dictionary value as an argument.
|
||||
The dictionary contains all the locals from the run() function, so you can
|
||||
access the child spawn object or any other variable defined in run()
|
||||
(event_count, child, and extra_args are the most useful). A callback may
|
||||
return True to stop the current run process. Otherwise run() continues
|
||||
until the next event. A callback may also return a string which will be
|
||||
sent to the child. 'extra_args' is not used by directly run(). It provides
|
||||
a way to pass data to a callback function through run() through the locals
|
||||
dictionary passed to a callback.
|
||||
|
||||
Like :class:`spawn`, passing *encoding* will make it work with unicode
|
||||
instead of bytes. You can pass *codec_errors* to control how errors in
|
||||
encoding and decoding are handled.
|
||||
'''
|
||||
if timeout == -1:
|
||||
child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env,
|
||||
**kwargs)
|
||||
else:
|
||||
child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile,
|
||||
cwd=cwd, env=env, **kwargs)
|
||||
if isinstance(events, list):
|
||||
patterns= [x for x,y in events]
|
||||
responses = [y for x,y in events]
|
||||
elif isinstance(events, dict):
|
||||
patterns = list(events.keys())
|
||||
responses = list(events.values())
|
||||
else:
|
||||
# This assumes EOF or TIMEOUT will eventually cause run to terminate.
|
||||
patterns = None
|
||||
responses = None
|
||||
child_result_list = []
|
||||
event_count = 0
|
||||
while True:
|
||||
try:
|
||||
index = child.expect(patterns)
|
||||
if isinstance(child.after, child.allowed_string_types):
|
||||
child_result_list.append(child.before + child.after)
|
||||
else:
|
||||
# child.after may have been a TIMEOUT or EOF,
|
||||
# which we don't want appended to the list.
|
||||
child_result_list.append(child.before)
|
||||
if isinstance(responses[index], child.allowed_string_types):
|
||||
child.send(responses[index])
|
||||
elif (isinstance(responses[index], types.FunctionType) or
|
||||
isinstance(responses[index], types.MethodType)):
|
||||
callback_result = responses[index](locals())
|
||||
sys.stdout.flush()
|
||||
if isinstance(callback_result, child.allowed_string_types):
|
||||
child.send(callback_result)
|
||||
elif callback_result:
|
||||
break
|
||||
else:
|
||||
raise TypeError("parameter `event' at index {index} must be "
|
||||
"a string, method, or function: {value!r}"
|
||||
.format(index=index, value=responses[index]))
|
||||
event_count = event_count + 1
|
||||
except TIMEOUT:
|
||||
child_result_list.append(child.before)
|
||||
break
|
||||
except EOF:
|
||||
child_result_list.append(child.before)
|
||||
break
|
||||
child_result = child.string_type().join(child_result_list)
|
||||
if withexitstatus:
|
||||
child.close()
|
||||
return (child_result, child.exitstatus)
|
||||
else:
|
||||
return child_result
|
||||
|
||||
def runu(command, timeout=30, withexitstatus=False, events=None,
|
||||
extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
|
||||
"""Deprecated: pass encoding to run() instead.
|
||||
"""
|
||||
kwargs.setdefault('encoding', 'utf-8')
|
||||
return run(command, timeout=timeout, withexitstatus=withexitstatus,
|
||||
events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
|
||||
env=env, **kwargs)
|
||||
@@ -0,0 +1,431 @@
|
||||
'''This implements a virtual screen. This is used to support ANSI terminal
|
||||
emulation. The screen representation and state is implemented in this class.
|
||||
Most of the methods are inspired by ANSI screen control codes. The
|
||||
:class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI
|
||||
escape codes.
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
import codecs
|
||||
import copy
|
||||
import sys
|
||||
|
||||
import warnings
|
||||
|
||||
warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. "
|
||||
"We recommend using pyte to emulate a terminal screen: "
|
||||
"https://pypi.python.org/pypi/pyte"),
|
||||
stacklevel=2)
|
||||
|
||||
NUL = 0 # Fill character; ignored on input.
|
||||
ENQ = 5 # Transmit answerback message.
|
||||
BEL = 7 # Ring the bell.
|
||||
BS = 8 # Move cursor left.
|
||||
HT = 9 # Move cursor to next tab stop.
|
||||
LF = 10 # Line feed.
|
||||
VT = 11 # Same as LF.
|
||||
FF = 12 # Same as LF.
|
||||
CR = 13 # Move cursor to left margin or newline.
|
||||
SO = 14 # Invoke G1 character set.
|
||||
SI = 15 # Invoke G0 character set.
|
||||
XON = 17 # Resume transmission.
|
||||
XOFF = 19 # Halt transmission.
|
||||
CAN = 24 # Cancel escape sequence.
|
||||
SUB = 26 # Same as CAN.
|
||||
ESC = 27 # Introduce a control sequence.
|
||||
DEL = 127 # Fill character; ignored on input.
|
||||
SPACE = u' ' # Space or blank character.
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
if PY3:
|
||||
unicode = str
|
||||
|
||||
def constrain (n, min, max):
|
||||
|
||||
'''This returns a number, n constrained to the min and max bounds. '''
|
||||
|
||||
if n < min:
|
||||
return min
|
||||
if n > max:
|
||||
return max
|
||||
return n
|
||||
|
||||
class screen:
|
||||
'''This object maintains the state of a virtual text screen as a
|
||||
rectangular array. This maintains a virtual cursor position and handles
|
||||
scrolling as characters are added. This supports most of the methods needed
|
||||
by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
|
||||
like arrays).
|
||||
|
||||
Characters are represented internally using unicode. Methods that accept
|
||||
input characters, when passed 'bytes' (which in Python 2 is equivalent to
|
||||
'str'), convert them from the encoding specified in the 'encoding'
|
||||
parameter to the constructor. Methods that return screen contents return
|
||||
unicode strings, with the exception of __str__() under Python 2. Passing
|
||||
``encoding=None`` limits the API to only accept unicode input, so passing
|
||||
bytes in will raise :exc:`TypeError`.
|
||||
'''
|
||||
def __init__(self, r=24, c=80, encoding='latin-1', encoding_errors='replace'):
|
||||
'''This initializes a blank screen of the given dimensions.'''
|
||||
|
||||
self.rows = r
|
||||
self.cols = c
|
||||
self.encoding = encoding
|
||||
self.encoding_errors = encoding_errors
|
||||
if encoding is not None:
|
||||
self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors)
|
||||
else:
|
||||
self.decoder = None
|
||||
self.cur_r = 1
|
||||
self.cur_c = 1
|
||||
self.cur_saved_r = 1
|
||||
self.cur_saved_c = 1
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
self.w = [ [SPACE] * self.cols for _ in range(self.rows)]
|
||||
|
||||
def _decode(self, s):
|
||||
'''This converts from the external coding system (as passed to
|
||||
the constructor) to the internal one (unicode). '''
|
||||
if self.decoder is not None:
|
||||
return self.decoder.decode(s)
|
||||
else:
|
||||
raise TypeError("This screen was constructed with encoding=None, "
|
||||
"so it does not handle bytes.")
|
||||
|
||||
def _unicode(self):
|
||||
'''This returns a printable representation of the screen as a unicode
|
||||
string (which, under Python 3.x, is the same as 'str'). The end of each
|
||||
screen line is terminated by a newline.'''
|
||||
|
||||
return u'\n'.join ([ u''.join(c) for c in self.w ])
|
||||
|
||||
if PY3:
|
||||
__str__ = _unicode
|
||||
else:
|
||||
__unicode__ = _unicode
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a printable representation of the screen. The end of
|
||||
each screen line is terminated by a newline. '''
|
||||
encoding = self.encoding or 'ascii'
|
||||
return self._unicode().encode(encoding, 'replace')
|
||||
|
||||
def dump (self):
|
||||
'''This returns a copy of the screen as a unicode string. This is similar to
|
||||
__str__/__unicode__ except that lines are not terminated with line
|
||||
feeds.'''
|
||||
|
||||
return u''.join ([ u''.join(c) for c in self.w ])
|
||||
|
||||
def pretty (self):
|
||||
'''This returns a copy of the screen as a unicode string with an ASCII
|
||||
text box around the screen border. This is similar to
|
||||
__str__/__unicode__ except that it adds a box.'''
|
||||
|
||||
top_bot = u'+' + u'-'*self.cols + u'+\n'
|
||||
return top_bot + u'\n'.join([u'|'+line+u'|' for line in unicode(self).split(u'\n')]) + u'\n' + top_bot
|
||||
|
||||
def fill (self, ch=SPACE):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.fill_region (1,1,self.rows,self.cols, ch)
|
||||
|
||||
def fill_region (self, rs,cs, re,ce, ch=SPACE):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
rs = constrain (rs, 1, self.rows)
|
||||
re = constrain (re, 1, self.rows)
|
||||
cs = constrain (cs, 1, self.cols)
|
||||
ce = constrain (ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
for r in range (rs, re+1):
|
||||
for c in range (cs, ce + 1):
|
||||
self.put_abs (r,c,ch)
|
||||
|
||||
def cr (self):
|
||||
'''This moves the cursor to the beginning (col 1) of the current row.
|
||||
'''
|
||||
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
|
||||
def lf (self):
|
||||
'''This moves the cursor down with scrolling.
|
||||
'''
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_down()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up ()
|
||||
self.erase_line()
|
||||
|
||||
def crlf (self):
|
||||
'''This advances the cursor with CRLF properties.
|
||||
The cursor will line wrap and the screen may scroll.
|
||||
'''
|
||||
|
||||
self.cr ()
|
||||
self.lf ()
|
||||
|
||||
def newline (self):
|
||||
'''This is an alias for crlf().
|
||||
'''
|
||||
|
||||
self.crlf()
|
||||
|
||||
def put_abs (self, r, c, ch):
|
||||
'''Screen array starts at 1 index.'''
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)[0]
|
||||
else:
|
||||
ch = ch[0]
|
||||
self.w[r-1][c-1] = ch
|
||||
|
||||
def put (self, ch):
|
||||
'''This puts a characters at the current cursor position.
|
||||
'''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.put_abs (self.cur_r, self.cur_c, ch)
|
||||
|
||||
def insert_abs (self, r, c, ch):
|
||||
'''This inserts a character at (r,c). Everything under
|
||||
and to the right is shifted right one character.
|
||||
The last character of the line is lost.
|
||||
'''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
for ci in range (self.cols, c, -1):
|
||||
self.put_abs (r,ci, self.get_abs(r,ci-1))
|
||||
self.put_abs (r,c,ch)
|
||||
|
||||
def insert (self, ch):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.insert_abs (self.cur_r, self.cur_c, ch)
|
||||
|
||||
def get_abs (self, r, c):
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
return self.w[r-1][c-1]
|
||||
|
||||
def get (self):
|
||||
|
||||
self.get_abs (self.cur_r, self.cur_c)
|
||||
|
||||
def get_region (self, rs,cs, re,ce):
|
||||
'''This returns a list of lines representing the region.
|
||||
'''
|
||||
|
||||
rs = constrain (rs, 1, self.rows)
|
||||
re = constrain (re, 1, self.rows)
|
||||
cs = constrain (cs, 1, self.cols)
|
||||
ce = constrain (ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
sc = []
|
||||
for r in range (rs, re+1):
|
||||
line = u''
|
||||
for c in range (cs, ce + 1):
|
||||
ch = self.get_abs (r,c)
|
||||
line = line + ch
|
||||
sc.append (line)
|
||||
return sc
|
||||
|
||||
def cursor_constrain (self):
|
||||
'''This keeps the cursor within the screen area.
|
||||
'''
|
||||
|
||||
self.cur_r = constrain (self.cur_r, 1, self.rows)
|
||||
self.cur_c = constrain (self.cur_c, 1, self.cols)
|
||||
|
||||
def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H
|
||||
|
||||
self.cur_r = r
|
||||
self.cur_c = c
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down)
|
||||
|
||||
self.cur_c = self.cur_c - count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back)
|
||||
|
||||
self.cur_r = self.cur_r + count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_forward (self,count=1): # <ESC>[{COUNT}C
|
||||
|
||||
self.cur_c = self.cur_c + count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_up (self,count=1): # <ESC>[{COUNT}A
|
||||
|
||||
self.cur_r = self.cur_r - count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index)
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_up()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up()
|
||||
|
||||
def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f
|
||||
'''Identical to Cursor Home.'''
|
||||
|
||||
self.cursor_home (r, c)
|
||||
|
||||
def cursor_save (self): # <ESC>[s
|
||||
'''Save current cursor position.'''
|
||||
|
||||
self.cursor_save_attrs()
|
||||
|
||||
def cursor_unsave (self): # <ESC>[u
|
||||
'''Restores cursor position after a Save Cursor.'''
|
||||
|
||||
self.cursor_restore_attrs()
|
||||
|
||||
def cursor_save_attrs (self): # <ESC>7
|
||||
'''Save current cursor position.'''
|
||||
|
||||
self.cur_saved_r = self.cur_r
|
||||
self.cur_saved_c = self.cur_c
|
||||
|
||||
def cursor_restore_attrs (self): # <ESC>8
|
||||
'''Restores cursor position after a Save Cursor.'''
|
||||
|
||||
self.cursor_home (self.cur_saved_r, self.cur_saved_c)
|
||||
|
||||
def scroll_constrain (self):
|
||||
'''This keeps the scroll region within the screen region.'''
|
||||
|
||||
if self.scroll_row_start <= 0:
|
||||
self.scroll_row_start = 1
|
||||
if self.scroll_row_end > self.rows:
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen (self): # <ESC>[r
|
||||
'''Enable scrolling for entire display.'''
|
||||
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r
|
||||
'''Enable scrolling from row {start} to row {end}.'''
|
||||
|
||||
self.scroll_row_start = rs
|
||||
self.scroll_row_end = re
|
||||
self.scroll_constrain()
|
||||
|
||||
def scroll_down (self): # <ESC>D
|
||||
'''Scroll display down one line.'''
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s+1:e+1] = copy.deepcopy(self.w[s:e])
|
||||
|
||||
def scroll_up (self): # <ESC>M
|
||||
'''Scroll display up one line.'''
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s:e] = copy.deepcopy(self.w[s+1:e+1])
|
||||
|
||||
def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K
|
||||
'''Erases from the current cursor position to the end of the current
|
||||
line.'''
|
||||
|
||||
self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols)
|
||||
|
||||
def erase_start_of_line (self): # <ESC>[1K
|
||||
'''Erases from the current cursor position to the start of the current
|
||||
line.'''
|
||||
|
||||
self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c)
|
||||
|
||||
def erase_line (self): # <ESC>[2K
|
||||
'''Erases the entire current line.'''
|
||||
|
||||
self.fill_region (self.cur_r, 1, self.cur_r, self.cols)
|
||||
|
||||
def erase_down (self): # <ESC>[0J -or- <ESC>[J
|
||||
'''Erases the screen from the current line down to the bottom of the
|
||||
screen.'''
|
||||
|
||||
self.erase_end_of_line ()
|
||||
self.fill_region (self.cur_r + 1, 1, self.rows, self.cols)
|
||||
|
||||
def erase_up (self): # <ESC>[1J
|
||||
'''Erases the screen from the current line up to the top of the
|
||||
screen.'''
|
||||
|
||||
self.erase_start_of_line ()
|
||||
self.fill_region (self.cur_r-1, 1, 1, self.cols)
|
||||
|
||||
def erase_screen (self): # <ESC>[2J
|
||||
'''Erases the screen with the background color.'''
|
||||
|
||||
self.fill ()
|
||||
|
||||
def set_tab (self): # <ESC>H
|
||||
'''Sets a tab at the current position.'''
|
||||
|
||||
pass
|
||||
|
||||
def clear_tab (self): # <ESC>[g
|
||||
'''Clears tab at the current position.'''
|
||||
|
||||
pass
|
||||
|
||||
def clear_all_tabs (self): # <ESC>[3g
|
||||
'''Clears all tabs.'''
|
||||
|
||||
pass
|
||||
|
||||
# Insert line Esc [ Pn L
|
||||
# Delete line Esc [ Pn M
|
||||
# Delete character Esc [ Pn P
|
||||
# Scrolling region Esc [ Pn(top);Pn(bot) r
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
from io import StringIO, BytesIO
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import errno
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .expect import Expecter, searcher_string, searcher_re
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
text_type = str if PY3 else unicode
|
||||
|
||||
class _NullCoder(object):
|
||||
"""Pass bytes through unchanged."""
|
||||
@staticmethod
|
||||
def encode(b, final=False):
|
||||
return b
|
||||
|
||||
@staticmethod
|
||||
def decode(b, final=False):
|
||||
return b
|
||||
|
||||
class SpawnBase(object):
|
||||
"""A base class providing the backwards-compatible spawn API for Pexpect.
|
||||
|
||||
This should not be instantiated directly: use :class:`pexpect.spawn` or
|
||||
:class:`pexpect.fdpexpect.fdspawn`.
|
||||
"""
|
||||
encoding = None
|
||||
pid = None
|
||||
flag_eof = False
|
||||
|
||||
def __init__(self, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, encoding=None, codec_errors='strict'):
|
||||
self.stdin = sys.stdin
|
||||
self.stdout = sys.stdout
|
||||
self.stderr = sys.stderr
|
||||
|
||||
self.searcher = None
|
||||
self.ignorecase = False
|
||||
self.before = None
|
||||
self.after = None
|
||||
self.match = None
|
||||
self.match_index = None
|
||||
self.terminated = True
|
||||
self.exitstatus = None
|
||||
self.signalstatus = None
|
||||
# status returned by os.waitpid
|
||||
self.status = None
|
||||
# the child file descriptor is initially closed
|
||||
self.child_fd = -1
|
||||
self.timeout = timeout
|
||||
self.delimiter = EOF
|
||||
self.logfile = logfile
|
||||
# input from child (read_nonblocking)
|
||||
self.logfile_read = None
|
||||
# output to send (send, sendline)
|
||||
self.logfile_send = None
|
||||
# max bytes to read at one time into buffer
|
||||
self.maxread = maxread
|
||||
# Data before searchwindowsize point is preserved, but not searched.
|
||||
self.searchwindowsize = searchwindowsize
|
||||
# Delay used before sending data to child. Time in seconds.
|
||||
# Set this to None to skip the time.sleep() call completely.
|
||||
self.delaybeforesend = 0.05
|
||||
# Used by close() to give kernel time to update process status.
|
||||
# Time in seconds.
|
||||
self.delayafterclose = 0.1
|
||||
# Used by terminate() to give kernel time to update process status.
|
||||
# Time in seconds.
|
||||
self.delayafterterminate = 0.1
|
||||
# Delay in seconds to sleep after each call to read_nonblocking().
|
||||
# Set this to None to skip the time.sleep() call completely: that
|
||||
# would restore the behavior from pexpect-2.0 (for performance
|
||||
# reasons or because you don't want to release Python's global
|
||||
# interpreter lock).
|
||||
self.delayafterread = 0.0001
|
||||
self.softspace = False
|
||||
self.name = '<' + repr(self) + '>'
|
||||
self.closed = True
|
||||
|
||||
# Unicode interface
|
||||
self.encoding = encoding
|
||||
self.codec_errors = codec_errors
|
||||
if encoding is None:
|
||||
# bytes mode (accepts some unicode for backwards compatibility)
|
||||
self._encoder = self._decoder = _NullCoder()
|
||||
self.string_type = bytes
|
||||
self.buffer_type = BytesIO
|
||||
self.crlf = b'\r\n'
|
||||
if PY3:
|
||||
self.allowed_string_types = (bytes, str)
|
||||
self.linesep = os.linesep.encode('ascii')
|
||||
def write_to_stdout(b):
|
||||
try:
|
||||
return sys.stdout.buffer.write(b)
|
||||
except AttributeError:
|
||||
# If stdout has been replaced, it may not have .buffer
|
||||
return sys.stdout.write(b.decode('ascii', 'replace'))
|
||||
self.write_to_stdout = write_to_stdout
|
||||
else:
|
||||
self.allowed_string_types = (basestring,) # analysis:ignore
|
||||
self.linesep = os.linesep
|
||||
self.write_to_stdout = sys.stdout.write
|
||||
else:
|
||||
# unicode mode
|
||||
self._encoder = codecs.getincrementalencoder(encoding)(codec_errors)
|
||||
self._decoder = codecs.getincrementaldecoder(encoding)(codec_errors)
|
||||
self.string_type = text_type
|
||||
self.buffer_type = StringIO
|
||||
self.crlf = u'\r\n'
|
||||
self.allowed_string_types = (text_type, )
|
||||
if PY3:
|
||||
self.linesep = os.linesep
|
||||
else:
|
||||
self.linesep = os.linesep.decode('ascii')
|
||||
# This can handle unicode in both Python 2 and 3
|
||||
self.write_to_stdout = sys.stdout.write
|
||||
# storage for async transport
|
||||
self.async_pw_transport = None
|
||||
# This is the read buffer. See maxread.
|
||||
self._buffer = self.buffer_type()
|
||||
# The buffer may be trimmed for efficiency reasons. This is the
|
||||
# untrimmed buffer, used to create the before attribute.
|
||||
self._before = self.buffer_type()
|
||||
|
||||
def _log(self, s, direction):
|
||||
if self.logfile is not None:
|
||||
self.logfile.write(s)
|
||||
self.logfile.flush()
|
||||
second_log = self.logfile_send if (direction=='send') else self.logfile_read
|
||||
if second_log is not None:
|
||||
second_log.write(s)
|
||||
second_log.flush()
|
||||
|
||||
# For backwards compatibility, in bytes mode (when encoding is None)
|
||||
# unicode is accepted for send and expect. Unicode mode is strictly unicode
|
||||
# only.
|
||||
def _coerce_expect_string(self, s):
|
||||
if self.encoding is None and not isinstance(s, bytes):
|
||||
return s.encode('ascii')
|
||||
return s
|
||||
|
||||
def _coerce_send_string(self, s):
|
||||
if self.encoding is None and not isinstance(s, bytes):
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
def _get_buffer(self):
|
||||
return self._buffer.getvalue()
|
||||
|
||||
def _set_buffer(self, value):
|
||||
self._buffer = self.buffer_type()
|
||||
self._buffer.write(value)
|
||||
|
||||
# This property is provided for backwards compatibility (self.buffer used
|
||||
# to be a string/bytes object)
|
||||
buffer = property(_get_buffer, _set_buffer)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=None):
|
||||
"""This reads data from the file descriptor.
|
||||
|
||||
This is a simple implementation suitable for a regular file. Subclasses using ptys or pipes should override it.
|
||||
|
||||
The timeout parameter is ignored.
|
||||
"""
|
||||
|
||||
try:
|
||||
s = os.read(self.child_fd, size)
|
||||
except OSError as err:
|
||||
if err.args[0] == errno.EIO:
|
||||
# Linux-style EOF
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Exception style platform.')
|
||||
raise
|
||||
if s == b'':
|
||||
# BSD-style EOF
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Empty string style platform.')
|
||||
|
||||
s = self._decoder.decode(s, final=False)
|
||||
self._log(s, 'read')
|
||||
return s
|
||||
|
||||
def _pattern_type_err(self, pattern):
|
||||
raise TypeError('got {badtype} ({badobj!r}) as pattern, must be one'
|
||||
' of: {goodtypes}, pexpect.EOF, pexpect.TIMEOUT'\
|
||||
.format(badtype=type(pattern),
|
||||
badobj=pattern,
|
||||
goodtypes=', '.join([str(ast)\
|
||||
for ast in self.allowed_string_types])
|
||||
)
|
||||
)
|
||||
|
||||
def compile_pattern_list(self, patterns):
|
||||
'''This compiles a pattern-string or a list of pattern-strings.
|
||||
Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of
|
||||
those. Patterns may also be None which results in an empty list (you
|
||||
might do this if waiting for an EOF or TIMEOUT condition without
|
||||
expecting any pattern).
|
||||
|
||||
This is used by expect() when calling expect_list(). Thus expect() is
|
||||
nothing more than::
|
||||
|
||||
cpl = self.compile_pattern_list(pl)
|
||||
return self.expect_list(cpl, timeout)
|
||||
|
||||
If you are using expect() within a loop it may be more
|
||||
efficient to compile the patterns first and then call expect_list().
|
||||
This avoid calls in a loop to compile_pattern_list()::
|
||||
|
||||
cpl = self.compile_pattern_list(my_pattern)
|
||||
while some_condition:
|
||||
...
|
||||
i = self.expect_list(cpl, timeout)
|
||||
...
|
||||
'''
|
||||
|
||||
if patterns is None:
|
||||
return []
|
||||
if not isinstance(patterns, list):
|
||||
patterns = [patterns]
|
||||
|
||||
# Allow dot to match \n
|
||||
compile_flags = re.DOTALL
|
||||
if self.ignorecase:
|
||||
compile_flags = compile_flags | re.IGNORECASE
|
||||
compiled_pattern_list = []
|
||||
for idx, p in enumerate(patterns):
|
||||
if isinstance(p, self.allowed_string_types):
|
||||
p = self._coerce_expect_string(p)
|
||||
compiled_pattern_list.append(re.compile(p, compile_flags))
|
||||
elif p is EOF:
|
||||
compiled_pattern_list.append(EOF)
|
||||
elif p is TIMEOUT:
|
||||
compiled_pattern_list.append(TIMEOUT)
|
||||
elif isinstance(p, type(re.compile(''))):
|
||||
compiled_pattern_list.append(p)
|
||||
else:
|
||||
self._pattern_type_err(p)
|
||||
return compiled_pattern_list
|
||||
|
||||
def expect(self, pattern, timeout=-1, searchwindowsize=-1, async_=False, **kw):
|
||||
'''This seeks through the stream until a pattern is matched. The
|
||||
pattern is overloaded and may take several types. The pattern can be a
|
||||
StringType, EOF, a compiled re, or a list of any of those types.
|
||||
Strings will be compiled to re types. This returns the index into the
|
||||
pattern list. If the pattern was not a list this returns index 0 on a
|
||||
successful match. This may raise exceptions for EOF or TIMEOUT. To
|
||||
avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern
|
||||
list. That will cause expect to match an EOF or TIMEOUT condition
|
||||
instead of raising an exception.
|
||||
|
||||
If you pass a list of patterns and more than one matches, the first
|
||||
match in the stream is chosen. If more than one pattern matches at that
|
||||
point, the leftmost in the pattern list is chosen. For example::
|
||||
|
||||
# the input is 'foobar'
|
||||
index = p.expect(['bar', 'foo', 'foobar'])
|
||||
# returns 1('foo') even though 'foobar' is a "better" match
|
||||
|
||||
Please note, however, that buffering can affect this behavior, since
|
||||
input arrives in unpredictable chunks. For example::
|
||||
|
||||
# the input is 'foobar'
|
||||
index = p.expect(['foobar', 'foo'])
|
||||
# returns 0('foobar') if all input is available at once,
|
||||
# but returns 1('foo') if parts of the final 'bar' arrive late
|
||||
|
||||
When a match is found for the given pattern, the class instance
|
||||
attribute *match* becomes an re.MatchObject result. Should an EOF
|
||||
or TIMEOUT pattern match, then the match attribute will be an instance
|
||||
of that exception class. The pairing before and after class
|
||||
instance attributes are views of the data preceding and following
|
||||
the matching pattern. On general exception, class attribute
|
||||
*before* is all data received up to the exception, while *match* and
|
||||
*after* attributes are value None.
|
||||
|
||||
When the keyword argument timeout is -1 (default), then TIMEOUT will
|
||||
raise after the default value specified by the class timeout
|
||||
attribute. When None, TIMEOUT will not be raised and may block
|
||||
indefinitely until match.
|
||||
|
||||
When the keyword argument searchwindowsize is -1 (default), then the
|
||||
value specified by the class maxread attribute is used.
|
||||
|
||||
A list entry may be EOF or TIMEOUT instead of a string. This will
|
||||
catch these exceptions and return the index of the list entry instead
|
||||
of raising the exception. The attribute 'after' will be set to the
|
||||
exception type. The attribute 'match' will be None. This allows you to
|
||||
write code like this::
|
||||
|
||||
index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT])
|
||||
if index == 0:
|
||||
do_something()
|
||||
elif index == 1:
|
||||
do_something_else()
|
||||
elif index == 2:
|
||||
do_some_other_thing()
|
||||
elif index == 3:
|
||||
do_something_completely_different()
|
||||
|
||||
instead of code like this::
|
||||
|
||||
try:
|
||||
index = p.expect(['good', 'bad'])
|
||||
if index == 0:
|
||||
do_something()
|
||||
elif index == 1:
|
||||
do_something_else()
|
||||
except EOF:
|
||||
do_some_other_thing()
|
||||
except TIMEOUT:
|
||||
do_something_completely_different()
|
||||
|
||||
These two forms are equivalent. It all depends on what you want. You
|
||||
can also just expect the EOF if you are waiting for all output of a
|
||||
child to finish. For example::
|
||||
|
||||
p = pexpect.spawn('/bin/ls')
|
||||
p.expect(pexpect.EOF)
|
||||
print p.before
|
||||
|
||||
If you are trying to optimize for speed then see expect_list().
|
||||
|
||||
On Python 3.4, or Python 3.3 with asyncio installed, passing
|
||||
``async_=True`` will make this return an :mod:`asyncio` coroutine,
|
||||
which you can yield from to get the same result that this method would
|
||||
normally give directly. So, inside a coroutine, you can replace this code::
|
||||
|
||||
index = p.expect(patterns)
|
||||
|
||||
With this non-blocking form::
|
||||
|
||||
index = yield from p.expect(patterns, async_=True)
|
||||
'''
|
||||
if 'async' in kw:
|
||||
async_ = kw.pop('async')
|
||||
if kw:
|
||||
raise TypeError("Unknown keyword arguments: {}".format(kw))
|
||||
|
||||
compiled_pattern_list = self.compile_pattern_list(pattern)
|
||||
return self.expect_list(compiled_pattern_list,
|
||||
timeout, searchwindowsize, async_)
|
||||
|
||||
def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
|
||||
async_=False, **kw):
|
||||
'''This takes a list of compiled regular expressions and returns the
|
||||
index into the pattern_list that matched the child output. The list may
|
||||
also contain EOF or TIMEOUT(which are not compiled regular
|
||||
expressions). This method is similar to the expect() method except that
|
||||
expect_list() does not recompile the pattern list on every call. This
|
||||
may help if you are trying to optimize for speed, otherwise just use
|
||||
the expect() method. This is called by expect().
|
||||
|
||||
|
||||
Like :meth:`expect`, passing ``async_=True`` will make this return an
|
||||
asyncio coroutine.
|
||||
'''
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
if 'async' in kw:
|
||||
async_ = kw.pop('async')
|
||||
if kw:
|
||||
raise TypeError("Unknown keyword arguments: {}".format(kw))
|
||||
|
||||
exp = Expecter(self, searcher_re(pattern_list), searchwindowsize)
|
||||
if async_:
|
||||
from ._async import expect_async
|
||||
return expect_async(exp, timeout)
|
||||
else:
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1,
|
||||
async_=False, **kw):
|
||||
|
||||
'''This is similar to expect(), but uses plain string matching instead
|
||||
of compiled regular expressions in 'pattern_list'. The 'pattern_list'
|
||||
may be a string; a list or other sequence of strings; or TIMEOUT and
|
||||
EOF.
|
||||
|
||||
This call might be faster than expect() for two reasons: string
|
||||
searching is faster than RE matching and it is possible to limit the
|
||||
search to just the end of the input buffer.
|
||||
|
||||
This method is also useful when you don't want to have to worry about
|
||||
escaping regular expression characters that you want to match.
|
||||
|
||||
Like :meth:`expect`, passing ``async_=True`` will make this return an
|
||||
asyncio coroutine.
|
||||
'''
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
if 'async' in kw:
|
||||
async_ = kw.pop('async')
|
||||
if kw:
|
||||
raise TypeError("Unknown keyword arguments: {}".format(kw))
|
||||
|
||||
if (isinstance(pattern_list, self.allowed_string_types) or
|
||||
pattern_list in (TIMEOUT, EOF)):
|
||||
pattern_list = [pattern_list]
|
||||
|
||||
def prepare_pattern(pattern):
|
||||
if pattern in (TIMEOUT, EOF):
|
||||
return pattern
|
||||
if isinstance(pattern, self.allowed_string_types):
|
||||
return self._coerce_expect_string(pattern)
|
||||
self._pattern_type_err(pattern)
|
||||
|
||||
try:
|
||||
pattern_list = iter(pattern_list)
|
||||
except TypeError:
|
||||
self._pattern_type_err(pattern_list)
|
||||
pattern_list = [prepare_pattern(p) for p in pattern_list]
|
||||
|
||||
exp = Expecter(self, searcher_string(pattern_list), searchwindowsize)
|
||||
if async_:
|
||||
from ._async import expect_async
|
||||
return expect_async(exp, timeout)
|
||||
else:
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1):
|
||||
'''This is the common loop used inside expect. The 'searcher' should be
|
||||
an instance of searcher_re or searcher_string, which describes how and
|
||||
what to search for in the input.
|
||||
|
||||
See expect() for other arguments, return value and exceptions. '''
|
||||
|
||||
exp = Expecter(self, searcher, searchwindowsize)
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def read(self, size=-1):
|
||||
'''This reads at most "size" bytes from the file (less if the read hits
|
||||
EOF before obtaining size bytes). If the size argument is negative or
|
||||
omitted, read all data until EOF is reached. The bytes are returned as
|
||||
a string object. An empty string is returned when EOF is encountered
|
||||
immediately. '''
|
||||
|
||||
if size == 0:
|
||||
return self.string_type()
|
||||
if size < 0:
|
||||
# delimiter default is EOF
|
||||
self.expect(self.delimiter)
|
||||
return self.before
|
||||
|
||||
# I could have done this more directly by not using expect(), but
|
||||
# I deliberately decided to couple read() to expect() so that
|
||||
# I would catch any bugs early and ensure consistent behavior.
|
||||
# It's a little less efficient, but there is less for me to
|
||||
# worry about if I have to later modify read() or expect().
|
||||
# Note, it's OK if size==-1 in the regex. That just means it
|
||||
# will never match anything in which case we stop only on EOF.
|
||||
cre = re.compile(self._coerce_expect_string('.{%d}' % size), re.DOTALL)
|
||||
# delimiter default is EOF
|
||||
index = self.expect([cre, self.delimiter])
|
||||
if index == 0:
|
||||
### FIXME self.before should be ''. Should I assert this?
|
||||
return self.after
|
||||
return self.before
|
||||
|
||||
def readline(self, size=-1):
|
||||
'''This reads and returns one entire line. The newline at the end of
|
||||
line is returned as part of the string, unless the file ends without a
|
||||
newline. An empty string is returned if EOF is encountered immediately.
|
||||
This looks for a newline as a CR/LF pair (\\r\\n) even on UNIX because
|
||||
this is what the pseudotty device returns. So contrary to what you may
|
||||
expect you will receive newlines as \\r\\n.
|
||||
|
||||
If the size argument is 0 then an empty string is returned. In all
|
||||
other cases the size argument is ignored, which is not standard
|
||||
behavior for a file-like object. '''
|
||||
|
||||
if size == 0:
|
||||
return self.string_type()
|
||||
# delimiter default is EOF
|
||||
index = self.expect([self.crlf, self.delimiter])
|
||||
if index == 0:
|
||||
return self.before + self.crlf
|
||||
else:
|
||||
return self.before
|
||||
|
||||
def __iter__(self):
|
||||
'''This is to support iterators over a file-like object.
|
||||
'''
|
||||
return iter(self.readline, self.string_type())
|
||||
|
||||
def readlines(self, sizehint=-1):
|
||||
'''This reads until EOF using readline() and returns a list containing
|
||||
the lines thus read. The optional 'sizehint' argument is ignored.
|
||||
Remember, because this reads until EOF that means the child
|
||||
process should have closed its stdout. If you run this method on
|
||||
a child that is still running with its stdout open then this
|
||||
method will block until it timesout.'''
|
||||
|
||||
lines = []
|
||||
while True:
|
||||
line = self.readline()
|
||||
if not line:
|
||||
break
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def fileno(self):
|
||||
'''Expose file descriptor for a file-like interface
|
||||
'''
|
||||
return self.child_fd
|
||||
|
||||
def flush(self):
|
||||
'''This does nothing. It is here to support the interface for a
|
||||
File-like object. '''
|
||||
pass
|
||||
|
||||
def isatty(self):
|
||||
"""Overridden in subclass using tty"""
|
||||
return False
|
||||
|
||||
# For 'with spawn(...) as child:'
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, etype, evalue, tb):
|
||||
# We rely on subclasses to implement close(). If they don't, it's not
|
||||
# clear what a context manager should do.
|
||||
self.close()
|
||||
@@ -0,0 +1,187 @@
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import select
|
||||
import time
|
||||
import errno
|
||||
|
||||
try:
|
||||
InterruptedError
|
||||
except NameError:
|
||||
# Alias Python2 exception to Python3
|
||||
InterruptedError = select.error
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
string_types = (str,)
|
||||
else:
|
||||
string_types = (unicode, str)
|
||||
|
||||
|
||||
def is_executable_file(path):
|
||||
"""Checks that path is an executable regular file, or a symlink towards one.
|
||||
|
||||
This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``.
|
||||
"""
|
||||
# follow symlinks,
|
||||
fpath = os.path.realpath(path)
|
||||
|
||||
if not os.path.isfile(fpath):
|
||||
# non-files (directories, fifo, etc.)
|
||||
return False
|
||||
|
||||
mode = os.stat(fpath).st_mode
|
||||
|
||||
if (sys.platform.startswith('sunos')
|
||||
and os.getuid() == 0):
|
||||
# When root on Solaris, os.X_OK is True for *all* files, irregardless
|
||||
# of their executability -- instead, any permission bit of any user,
|
||||
# group, or other is fine enough.
|
||||
#
|
||||
# (This may be true for other "Unix98" OS's such as HP-UX and AIX)
|
||||
return bool(mode & (stat.S_IXUSR |
|
||||
stat.S_IXGRP |
|
||||
stat.S_IXOTH))
|
||||
|
||||
return os.access(fpath, os.X_OK)
|
||||
|
||||
|
||||
def which(filename, env=None):
|
||||
'''This takes a given filename; tries to find it in the environment path;
|
||||
then checks if it is executable. This returns the full path to the filename
|
||||
if found and executable. Otherwise this returns None.'''
|
||||
|
||||
# Special case where filename contains an explicit path.
|
||||
if os.path.dirname(filename) != '' and is_executable_file(filename):
|
||||
return filename
|
||||
if env is None:
|
||||
env = os.environ
|
||||
p = env.get('PATH')
|
||||
if not p:
|
||||
p = os.defpath
|
||||
pathlist = p.split(os.pathsep)
|
||||
for path in pathlist:
|
||||
ff = os.path.join(path, filename)
|
||||
if is_executable_file(ff):
|
||||
return ff
|
||||
return None
|
||||
|
||||
|
||||
def split_command_line(command_line):
|
||||
|
||||
'''This splits a command line into a list of arguments. It splits arguments
|
||||
on spaces, but handles embedded quotes, doublequotes, and escaped
|
||||
characters. It's impossible to do this with a regular expression, so I
|
||||
wrote a little state machine to parse the command line. '''
|
||||
|
||||
arg_list = []
|
||||
arg = ''
|
||||
|
||||
# Constants to name the states we can be in.
|
||||
state_basic = 0
|
||||
state_esc = 1
|
||||
state_singlequote = 2
|
||||
state_doublequote = 3
|
||||
# The state when consuming whitespace between commands.
|
||||
state_whitespace = 4
|
||||
state = state_basic
|
||||
|
||||
for c in command_line:
|
||||
if state == state_basic or state == state_whitespace:
|
||||
if c == '\\':
|
||||
# Escape the next character
|
||||
state = state_esc
|
||||
elif c == r"'":
|
||||
# Handle single quote
|
||||
state = state_singlequote
|
||||
elif c == r'"':
|
||||
# Handle double quote
|
||||
state = state_doublequote
|
||||
elif c.isspace():
|
||||
# Add arg to arg_list if we aren't in the middle of whitespace.
|
||||
if state == state_whitespace:
|
||||
# Do nothing.
|
||||
None
|
||||
else:
|
||||
arg_list.append(arg)
|
||||
arg = ''
|
||||
state = state_whitespace
|
||||
else:
|
||||
arg = arg + c
|
||||
state = state_basic
|
||||
elif state == state_esc:
|
||||
arg = arg + c
|
||||
state = state_basic
|
||||
elif state == state_singlequote:
|
||||
if c == r"'":
|
||||
state = state_basic
|
||||
else:
|
||||
arg = arg + c
|
||||
elif state == state_doublequote:
|
||||
if c == r'"':
|
||||
state = state_basic
|
||||
else:
|
||||
arg = arg + c
|
||||
|
||||
if arg != '':
|
||||
arg_list.append(arg)
|
||||
return arg_list
|
||||
|
||||
|
||||
def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None):
|
||||
|
||||
'''This is a wrapper around select.select() that ignores signals. If
|
||||
select.select raises a select.error exception and errno is an EINTR
|
||||
error then it is ignored. Mainly this is used to ignore sigwinch
|
||||
(terminal resize). '''
|
||||
|
||||
# if select() is interrupted by a signal (errno==EINTR) then
|
||||
# we loop back and enter the select() again.
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
return select.select(iwtd, owtd, ewtd, timeout)
|
||||
except InterruptedError:
|
||||
err = sys.exc_info()[1]
|
||||
if err.args[0] == errno.EINTR:
|
||||
# if we loop back we have to subtract the
|
||||
# amount of time we already waited.
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
if timeout < 0:
|
||||
return([], [], [])
|
||||
else:
|
||||
# something else caused the select.error, so
|
||||
# this actually is an exception.
|
||||
raise
|
||||
|
||||
|
||||
def poll_ignore_interrupts(fds, timeout=None):
|
||||
'''Simple wrapper around poll to register file descriptors and
|
||||
ignore signals.'''
|
||||
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
|
||||
poller = select.poll()
|
||||
for fd in fds:
|
||||
poller.register(fd, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
|
||||
|
||||
while True:
|
||||
try:
|
||||
timeout_ms = None if timeout is None else timeout * 1000
|
||||
results = poller.poll(timeout_ms)
|
||||
return [afd for afd, _ in results]
|
||||
except InterruptedError:
|
||||
err = sys.exc_info()[1]
|
||||
if err.args[0] == errno.EINTR:
|
||||
# if we loop back we have to subtract the
|
||||
# amount of time we already waited.
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
if timeout < 0:
|
||||
return []
|
||||
else:
|
||||
# something else caused the select.error, so
|
||||
# this actually is an exception.
|
||||
raise
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
class projectBase:
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
"""Run a subprocess in a pseudo terminal"""
|
||||
from .ptyprocess import PtyProcess, PtyProcessUnicode, PtyProcessError
|
||||
|
||||
__version__ = '0.6.0'
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Substitute for the forkpty system call, to support Solaris.
|
||||
"""
|
||||
import os
|
||||
import errno
|
||||
|
||||
from pty import (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, CHILD)
|
||||
from .util import PtyProcessError
|
||||
|
||||
def fork_pty():
|
||||
'''This implements a substitute for the forkpty system call. This
|
||||
should be more portable than the pty.fork() function. Specifically,
|
||||
this should work on Solaris.
|
||||
|
||||
Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to
|
||||
resolve the issue with Python's pty.fork() not supporting Solaris,
|
||||
particularly ssh. Based on patch to posixmodule.c authored by Noah
|
||||
Spurrier::
|
||||
|
||||
http://mail.python.org/pipermail/python-dev/2003-May/035281.html
|
||||
|
||||
'''
|
||||
|
||||
parent_fd, child_fd = os.openpty()
|
||||
if parent_fd < 0 or child_fd < 0:
|
||||
raise OSError("os.openpty() failed")
|
||||
|
||||
pid = os.fork()
|
||||
if pid == CHILD:
|
||||
# Child.
|
||||
os.close(parent_fd)
|
||||
pty_make_controlling_tty(child_fd)
|
||||
|
||||
os.dup2(child_fd, STDIN_FILENO)
|
||||
os.dup2(child_fd, STDOUT_FILENO)
|
||||
os.dup2(child_fd, STDERR_FILENO)
|
||||
|
||||
else:
|
||||
# Parent.
|
||||
os.close(child_fd)
|
||||
|
||||
return pid, parent_fd
|
||||
|
||||
def pty_make_controlling_tty(tty_fd):
|
||||
'''This makes the pseudo-terminal the controlling tty. This should be
|
||||
more portable than the pty.fork() function. Specifically, this should
|
||||
work on Solaris. '''
|
||||
|
||||
child_name = os.ttyname(tty_fd)
|
||||
|
||||
# Disconnect from controlling tty, if any. Raises OSError of ENXIO
|
||||
# if there was no controlling tty to begin with, such as when
|
||||
# executed by a cron(1) job.
|
||||
try:
|
||||
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
|
||||
os.close(fd)
|
||||
except OSError as err:
|
||||
if err.errno != errno.ENXIO:
|
||||
raise
|
||||
|
||||
os.setsid()
|
||||
|
||||
# Verify we are disconnected from controlling tty by attempting to open
|
||||
# it again. We expect that OSError of ENXIO should always be raised.
|
||||
try:
|
||||
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
|
||||
os.close(fd)
|
||||
raise PtyProcessError("OSError of errno.ENXIO should be raised.")
|
||||
except OSError as err:
|
||||
if err.errno != errno.ENXIO:
|
||||
raise
|
||||
|
||||
# Verify we can open child pty.
|
||||
fd = os.open(child_name, os.O_RDWR)
|
||||
os.close(fd)
|
||||
|
||||
# Verify we now have a controlling tty.
|
||||
fd = os.open("/dev/tty", os.O_WRONLY)
|
||||
os.close(fd)
|
||||
@@ -0,0 +1,842 @@
|
||||
import codecs
|
||||
import errno
|
||||
import fcntl
|
||||
import io
|
||||
import os
|
||||
import pty
|
||||
import resource
|
||||
import signal
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
import time
|
||||
|
||||
try:
|
||||
import builtins # Python 3
|
||||
except ImportError:
|
||||
import __builtin__ as builtins # Python 2
|
||||
|
||||
# Constants
|
||||
from pty import (STDIN_FILENO, CHILD)
|
||||
|
||||
from .util import which, PtyProcessError
|
||||
|
||||
_platform = sys.platform.lower()
|
||||
|
||||
# Solaris uses internal __fork_pty(). All others use pty.fork().
|
||||
_is_solaris = (
|
||||
_platform.startswith('solaris') or
|
||||
_platform.startswith('sunos'))
|
||||
|
||||
if _is_solaris:
|
||||
use_native_pty_fork = False
|
||||
from . import _fork_pty
|
||||
else:
|
||||
use_native_pty_fork = True
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
if PY3:
|
||||
def _byte(i):
|
||||
return bytes([i])
|
||||
else:
|
||||
def _byte(i):
|
||||
return chr(i)
|
||||
|
||||
class FileNotFoundError(OSError): pass
|
||||
class TimeoutError(OSError): pass
|
||||
|
||||
_EOF, _INTR = None, None
|
||||
|
||||
def _make_eof_intr():
|
||||
"""Set constants _EOF and _INTR.
|
||||
|
||||
This avoids doing potentially costly operations on module load.
|
||||
"""
|
||||
global _EOF, _INTR
|
||||
if (_EOF is not None) and (_INTR is not None):
|
||||
return
|
||||
|
||||
# inherit EOF and INTR definitions from controlling process.
|
||||
try:
|
||||
from termios import VEOF, VINTR
|
||||
fd = None
|
||||
for name in 'stdin', 'stdout':
|
||||
stream = getattr(sys, '__%s__' % name, None)
|
||||
if stream is None or not hasattr(stream, 'fileno'):
|
||||
continue
|
||||
try:
|
||||
fd = stream.fileno()
|
||||
except ValueError:
|
||||
continue
|
||||
if fd is None:
|
||||
# no fd, raise ValueError to fallback on CEOF, CINTR
|
||||
raise ValueError("No stream has a fileno")
|
||||
intr = ord(termios.tcgetattr(fd)[6][VINTR])
|
||||
eof = ord(termios.tcgetattr(fd)[6][VEOF])
|
||||
except (ImportError, OSError, IOError, ValueError, termios.error):
|
||||
# unless the controlling process is also not a terminal,
|
||||
# such as cron(1), or when stdin and stdout are both closed.
|
||||
# Fall-back to using CEOF and CINTR. There
|
||||
try:
|
||||
from termios import CEOF, CINTR
|
||||
(intr, eof) = (CINTR, CEOF)
|
||||
except ImportError:
|
||||
# ^C, ^D
|
||||
(intr, eof) = (3, 4)
|
||||
|
||||
_INTR = _byte(intr)
|
||||
_EOF = _byte(eof)
|
||||
|
||||
# setecho and setwinsize are pulled out here because on some platforms, we need
|
||||
# to do this from the child before we exec()
|
||||
|
||||
def _setecho(fd, state):
|
||||
errmsg = 'setecho() may not be called on this platform (it may still be possible to enable/disable echo when spawning the child process)'
|
||||
|
||||
try:
|
||||
attr = termios.tcgetattr(fd)
|
||||
except termios.error as err:
|
||||
if err.args[0] == errno.EINVAL:
|
||||
raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
|
||||
raise
|
||||
|
||||
if state:
|
||||
attr[3] = attr[3] | termios.ECHO
|
||||
else:
|
||||
attr[3] = attr[3] & ~termios.ECHO
|
||||
|
||||
try:
|
||||
# I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and
|
||||
# blocked on some platforms. TCSADRAIN would probably be ideal.
|
||||
termios.tcsetattr(fd, termios.TCSANOW, attr)
|
||||
except IOError as err:
|
||||
if err.args[0] == errno.EINVAL:
|
||||
raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
|
||||
raise
|
||||
|
||||
def _setwinsize(fd, rows, cols):
|
||||
# Some very old platforms have a bug that causes the value for
|
||||
# termios.TIOCSWINSZ to be truncated. There was a hack here to work
|
||||
# around this, but it caused problems with newer platforms so has been
|
||||
# removed. For details see https://github.com/pexpect/pexpect/issues/39
|
||||
TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561)
|
||||
# Note, assume ws_xpixel and ws_ypixel are zero.
|
||||
s = struct.pack('HHHH', rows, cols, 0, 0)
|
||||
fcntl.ioctl(fd, TIOCSWINSZ, s)
|
||||
|
||||
class PtyProcess(object):
|
||||
'''This class represents a process running in a pseudoterminal.
|
||||
|
||||
The main constructor is the :meth:`spawn` classmethod.
|
||||
'''
|
||||
string_type = bytes
|
||||
if PY3:
|
||||
linesep = os.linesep.encode('ascii')
|
||||
crlf = '\r\n'.encode('ascii')
|
||||
|
||||
@staticmethod
|
||||
def write_to_stdout(b):
|
||||
try:
|
||||
return sys.stdout.buffer.write(b)
|
||||
except AttributeError:
|
||||
# If stdout has been replaced, it may not have .buffer
|
||||
return sys.stdout.write(b.decode('ascii', 'replace'))
|
||||
else:
|
||||
linesep = os.linesep
|
||||
crlf = '\r\n'
|
||||
write_to_stdout = sys.stdout.write
|
||||
|
||||
encoding = None
|
||||
|
||||
argv = None
|
||||
env = None
|
||||
launch_dir = None
|
||||
|
||||
def __init__(self, pid, fd):
|
||||
_make_eof_intr() # Ensure _EOF and _INTR are calculated
|
||||
self.pid = pid
|
||||
self.fd = fd
|
||||
readf = io.open(fd, 'rb', buffering=0)
|
||||
writef = io.open(fd, 'wb', buffering=0, closefd=False)
|
||||
self.fileobj = io.BufferedRWPair(readf, writef)
|
||||
|
||||
self.terminated = False
|
||||
self.closed = False
|
||||
self.exitstatus = None
|
||||
self.signalstatus = None
|
||||
# status returned by os.waitpid
|
||||
self.status = None
|
||||
self.flag_eof = False
|
||||
# Used by close() to give kernel time to update process status.
|
||||
# Time in seconds.
|
||||
self.delayafterclose = 0.1
|
||||
# Used by terminate() to give kernel time to update process status.
|
||||
# Time in seconds.
|
||||
self.delayafterterminate = 0.1
|
||||
|
||||
@classmethod
|
||||
def spawn(
|
||||
cls, argv, cwd=None, env=None, echo=True, preexec_fn=None,
|
||||
dimensions=(24, 80), pass_fds=()):
|
||||
'''Start the given command in a child process in a pseudo terminal.
|
||||
|
||||
This does all the fork/exec type of stuff for a pty, and returns an
|
||||
instance of PtyProcess.
|
||||
|
||||
If preexec_fn is supplied, it will be called with no arguments in the
|
||||
child process before exec-ing the specified command.
|
||||
It may, for instance, set signal handlers to SIG_DFL or SIG_IGN.
|
||||
|
||||
Dimensions of the psuedoterminal used for the subprocess can be
|
||||
specified as a tuple (rows, cols), or the default (24, 80) will be used.
|
||||
|
||||
By default, all file descriptors except 0, 1 and 2 are closed. This
|
||||
behavior can be overridden with pass_fds, a list of file descriptors to
|
||||
keep open between the parent and the child.
|
||||
'''
|
||||
# Note that it is difficult for this method to fail.
|
||||
# You cannot detect if the child process cannot start.
|
||||
# So the only way you can tell if the child process started
|
||||
# or not is to try to read from the file descriptor. If you get
|
||||
# EOF immediately then it means that the child is already dead.
|
||||
# That may not necessarily be bad because you may have spawned a child
|
||||
# that performs some task; creates no stdout output; and then dies.
|
||||
|
||||
if not isinstance(argv, (list, tuple)):
|
||||
raise TypeError("Expected a list or tuple for argv, got %r" % argv)
|
||||
|
||||
# Shallow copy of argv so we can modify it
|
||||
argv = argv[:]
|
||||
command = argv[0]
|
||||
|
||||
command_with_path = which(command)
|
||||
if command_with_path is None:
|
||||
raise FileNotFoundError('The command was not found or was not ' +
|
||||
'executable: %s.' % command)
|
||||
command = command_with_path
|
||||
argv[0] = command
|
||||
|
||||
# [issue #119] To prevent the case where exec fails and the user is
|
||||
# stuck interacting with a python child process instead of whatever
|
||||
# was expected, we implement the solution from
|
||||
# http://stackoverflow.com/a/3703179 to pass the exception to the
|
||||
# parent process
|
||||
|
||||
# [issue #119] 1. Before forking, open a pipe in the parent process.
|
||||
exec_err_pipe_read, exec_err_pipe_write = os.pipe()
|
||||
|
||||
if use_native_pty_fork:
|
||||
pid, fd = pty.fork()
|
||||
else:
|
||||
# Use internal fork_pty, for Solaris
|
||||
pid, fd = _fork_pty.fork_pty()
|
||||
|
||||
# Some platforms must call setwinsize() and setecho() from the
|
||||
# child process, and others from the master process. We do both,
|
||||
# allowing IOError for either.
|
||||
|
||||
if pid == CHILD:
|
||||
# set window size
|
||||
try:
|
||||
_setwinsize(STDIN_FILENO, *dimensions)
|
||||
except IOError as err:
|
||||
if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
|
||||
raise
|
||||
|
||||
# disable echo if spawn argument echo was unset
|
||||
if not echo:
|
||||
try:
|
||||
_setecho(STDIN_FILENO, False)
|
||||
except (IOError, termios.error) as err:
|
||||
if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
|
||||
raise
|
||||
|
||||
# [issue #119] 3. The child closes the reading end and sets the
|
||||
# close-on-exec flag for the writing end.
|
||||
os.close(exec_err_pipe_read)
|
||||
fcntl.fcntl(exec_err_pipe_write, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
|
||||
|
||||
# Do not allow child to inherit open file descriptors from parent,
|
||||
# with the exception of the exec_err_pipe_write of the pipe
|
||||
# and pass_fds.
|
||||
# Impose ceiling on max_fd: AIX bugfix for users with unlimited
|
||||
# nofiles where resource.RLIMIT_NOFILE is 2^63-1 and os.closerange()
|
||||
# occasionally raises out of range error
|
||||
max_fd = min(1048576, resource.getrlimit(resource.RLIMIT_NOFILE)[0])
|
||||
spass_fds = sorted(set(pass_fds) | {exec_err_pipe_write})
|
||||
for pair in zip([2] + spass_fds, spass_fds + [max_fd]):
|
||||
os.closerange(pair[0]+1, pair[1])
|
||||
|
||||
if cwd is not None:
|
||||
os.chdir(cwd)
|
||||
|
||||
if preexec_fn is not None:
|
||||
try:
|
||||
preexec_fn()
|
||||
except Exception as e:
|
||||
ename = type(e).__name__
|
||||
tosend = '{}:0:{}'.format(ename, str(e))
|
||||
if PY3:
|
||||
tosend = tosend.encode('utf-8')
|
||||
|
||||
os.write(exec_err_pipe_write, tosend)
|
||||
os.close(exec_err_pipe_write)
|
||||
os._exit(1)
|
||||
|
||||
try:
|
||||
if env is None:
|
||||
os.execv(command, argv)
|
||||
else:
|
||||
os.execvpe(command, argv, env)
|
||||
except OSError as err:
|
||||
# [issue #119] 5. If exec fails, the child writes the error
|
||||
# code back to the parent using the pipe, then exits.
|
||||
tosend = 'OSError:{}:{}'.format(err.errno, str(err))
|
||||
if PY3:
|
||||
tosend = tosend.encode('utf-8')
|
||||
os.write(exec_err_pipe_write, tosend)
|
||||
os.close(exec_err_pipe_write)
|
||||
os._exit(os.EX_OSERR)
|
||||
|
||||
# Parent
|
||||
inst = cls(pid, fd)
|
||||
|
||||
# Set some informational attributes
|
||||
inst.argv = argv
|
||||
if env is not None:
|
||||
inst.env = env
|
||||
if cwd is not None:
|
||||
inst.launch_dir = cwd
|
||||
|
||||
# [issue #119] 2. After forking, the parent closes the writing end
|
||||
# of the pipe and reads from the reading end.
|
||||
os.close(exec_err_pipe_write)
|
||||
exec_err_data = os.read(exec_err_pipe_read, 4096)
|
||||
os.close(exec_err_pipe_read)
|
||||
|
||||
# [issue #119] 6. The parent reads eof (a zero-length read) if the
|
||||
# child successfully performed exec, since close-on-exec made
|
||||
# successful exec close the writing end of the pipe. Or, if exec
|
||||
# failed, the parent reads the error code and can proceed
|
||||
# accordingly. Either way, the parent blocks until the child calls
|
||||
# exec.
|
||||
if len(exec_err_data) != 0:
|
||||
try:
|
||||
errclass, errno_s, errmsg = exec_err_data.split(b':', 2)
|
||||
exctype = getattr(builtins, errclass.decode('ascii'), Exception)
|
||||
|
||||
exception = exctype(errmsg.decode('utf-8', 'replace'))
|
||||
if exctype is OSError:
|
||||
exception.errno = int(errno_s)
|
||||
except:
|
||||
raise Exception('Subprocess failed, got bad error data: %r'
|
||||
% exec_err_data)
|
||||
else:
|
||||
raise exception
|
||||
|
||||
try:
|
||||
inst.setwinsize(*dimensions)
|
||||
except IOError as err:
|
||||
if err.args[0] not in (errno.EINVAL, errno.ENOTTY, errno.ENXIO):
|
||||
raise
|
||||
|
||||
return inst
|
||||
|
||||
def __repr__(self):
|
||||
clsname = type(self).__name__
|
||||
if self.argv is not None:
|
||||
args = [repr(self.argv)]
|
||||
if self.env is not None:
|
||||
args.append("env=%r" % self.env)
|
||||
if self.launch_dir is not None:
|
||||
args.append("cwd=%r" % self.launch_dir)
|
||||
|
||||
return "{}.spawn({})".format(clsname, ", ".join(args))
|
||||
|
||||
else:
|
||||
return "{}(pid={}, fd={})".format(clsname, self.pid, self.fd)
|
||||
|
||||
@staticmethod
|
||||
def _coerce_send_string(s):
|
||||
if not isinstance(s, bytes):
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
@staticmethod
|
||||
def _coerce_read_string(s):
|
||||
return s
|
||||
|
||||
def __del__(self):
|
||||
'''This makes sure that no system resources are left open. Python only
|
||||
garbage collects Python objects. OS file descriptors are not Python
|
||||
objects, so they must be handled explicitly. If the child file
|
||||
descriptor was opened outside of this class (passed to the constructor)
|
||||
then this does not close it. '''
|
||||
|
||||
if not self.closed:
|
||||
# It is possible for __del__ methods to execute during the
|
||||
# teardown of the Python VM itself. Thus self.close() may
|
||||
# trigger an exception because os.close may be None.
|
||||
try:
|
||||
self.close()
|
||||
# which exception, shouldn't we catch explicitly .. ?
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def fileno(self):
|
||||
'''This returns the file descriptor of the pty for the child.
|
||||
'''
|
||||
return self.fd
|
||||
|
||||
def close(self, force=True):
|
||||
'''This closes the connection with the child application. Note that
|
||||
calling close() more than once is valid. This emulates standard Python
|
||||
behavior with files. Set force to True if you want to make sure that
|
||||
the child is terminated (SIGKILL is sent if the child ignores SIGHUP
|
||||
and SIGINT). '''
|
||||
if not self.closed:
|
||||
self.flush()
|
||||
self.fileobj.close() # Closes the file descriptor
|
||||
# Give kernel time to update process status.
|
||||
time.sleep(self.delayafterclose)
|
||||
if self.isalive():
|
||||
if not self.terminate(force):
|
||||
raise PtyProcessError('Could not terminate the child.')
|
||||
self.fd = -1
|
||||
self.closed = True
|
||||
#self.pid = None
|
||||
|
||||
def flush(self):
|
||||
'''This does nothing. It is here to support the interface for a
|
||||
File-like object. '''
|
||||
|
||||
pass
|
||||
|
||||
def isatty(self):
|
||||
'''This returns True if the file descriptor is open and connected to a
|
||||
tty(-like) device, else False.
|
||||
|
||||
On SVR4-style platforms implementing streams, such as SunOS and HP-UX,
|
||||
the child pty may not appear as a terminal device. This means
|
||||
methods such as setecho(), setwinsize(), getwinsize() may raise an
|
||||
IOError. '''
|
||||
|
||||
return os.isatty(self.fd)
|
||||
|
||||
def waitnoecho(self, timeout=None):
|
||||
'''This waits until the terminal ECHO flag is set False. This returns
|
||||
True if the echo mode is off. This returns False if the ECHO flag was
|
||||
not set False before the timeout. This can be used to detect when the
|
||||
child is waiting for a password. Usually a child application will turn
|
||||
off echo mode when it is waiting for the user to enter a password. For
|
||||
example, instead of expecting the "password:" prompt you can wait for
|
||||
the child to set ECHO off::
|
||||
|
||||
p = pexpect.spawn('ssh user@example.com')
|
||||
p.waitnoecho()
|
||||
p.sendline(mypassword)
|
||||
|
||||
If timeout==None then this method to block until ECHO flag is False.
|
||||
'''
|
||||
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
if not self.getecho():
|
||||
return True
|
||||
if timeout < 0 and timeout is not None:
|
||||
return False
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
time.sleep(0.1)
|
||||
|
||||
def getecho(self):
|
||||
'''This returns the terminal echo mode. This returns True if echo is
|
||||
on or False if echo is off. Child applications that are expecting you
|
||||
to enter a password often set ECHO False. See waitnoecho().
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False. '''
|
||||
|
||||
try:
|
||||
attr = termios.tcgetattr(self.fd)
|
||||
except termios.error as err:
|
||||
errmsg = 'getecho() may not be called on this platform'
|
||||
if err.args[0] == errno.EINVAL:
|
||||
raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
|
||||
raise
|
||||
|
||||
self.echo = bool(attr[3] & termios.ECHO)
|
||||
return self.echo
|
||||
|
||||
def setecho(self, state):
|
||||
'''This sets the terminal echo mode on or off. Note that anything the
|
||||
child sent before the echo will be lost, so you should be sure that
|
||||
your input buffer is empty before you call setecho(). For example, the
|
||||
following will work as expected::
|
||||
|
||||
p = pexpect.spawn('cat') # Echo is on by default.
|
||||
p.sendline('1234') # We expect see this twice from the child...
|
||||
p.expect(['1234']) # ... once from the tty echo...
|
||||
p.expect(['1234']) # ... and again from cat itself.
|
||||
p.setecho(False) # Turn off tty echo
|
||||
p.sendline('abcd') # We will set this only once (echoed by cat).
|
||||
p.sendline('wxyz') # We will set this only once (echoed by cat)
|
||||
p.expect(['abcd'])
|
||||
p.expect(['wxyz'])
|
||||
|
||||
The following WILL NOT WORK because the lines sent before the setecho
|
||||
will be lost::
|
||||
|
||||
p = pexpect.spawn('cat')
|
||||
p.sendline('1234')
|
||||
p.setecho(False) # Turn off tty echo
|
||||
p.sendline('abcd') # We will set this only once (echoed by cat).
|
||||
p.sendline('wxyz') # We will set this only once (echoed by cat)
|
||||
p.expect(['1234'])
|
||||
p.expect(['1234'])
|
||||
p.expect(['abcd'])
|
||||
p.expect(['wxyz'])
|
||||
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False.
|
||||
'''
|
||||
_setecho(self.fd, state)
|
||||
|
||||
self.echo = state
|
||||
|
||||
def read(self, size=1024):
|
||||
"""Read and return at most ``size`` bytes from the pty.
|
||||
|
||||
Can block if there is nothing to read. Raises :exc:`EOFError` if the
|
||||
terminal was closed.
|
||||
|
||||
Unlike Pexpect's ``read_nonblocking`` method, this doesn't try to deal
|
||||
with the vagaries of EOF on platforms that do strange things, like IRIX
|
||||
or older Solaris systems. It handles the errno=EIO pattern used on
|
||||
Linux, and the empty-string return used on BSD platforms and (seemingly)
|
||||
on recent Solaris.
|
||||
"""
|
||||
try:
|
||||
s = self.fileobj.read1(size)
|
||||
except (OSError, IOError) as err:
|
||||
if err.args[0] == errno.EIO:
|
||||
# Linux-style EOF
|
||||
self.flag_eof = True
|
||||
raise EOFError('End Of File (EOF). Exception style platform.')
|
||||
raise
|
||||
if s == b'':
|
||||
# BSD-style EOF (also appears to work on recent Solaris (OpenIndiana))
|
||||
self.flag_eof = True
|
||||
raise EOFError('End Of File (EOF). Empty string style platform.')
|
||||
|
||||
return s
|
||||
|
||||
def readline(self):
|
||||
"""Read one line from the pseudoterminal, and return it as unicode.
|
||||
|
||||
Can block if there is nothing to read. Raises :exc:`EOFError` if the
|
||||
terminal was closed.
|
||||
"""
|
||||
try:
|
||||
s = self.fileobj.readline()
|
||||
except (OSError, IOError) as err:
|
||||
if err.args[0] == errno.EIO:
|
||||
# Linux-style EOF
|
||||
self.flag_eof = True
|
||||
raise EOFError('End Of File (EOF). Exception style platform.')
|
||||
raise
|
||||
if s == b'':
|
||||
# BSD-style EOF (also appears to work on recent Solaris (OpenIndiana))
|
||||
self.flag_eof = True
|
||||
raise EOFError('End Of File (EOF). Empty string style platform.')
|
||||
|
||||
return s
|
||||
|
||||
def _writeb(self, b, flush=True):
|
||||
n = self.fileobj.write(b)
|
||||
if flush:
|
||||
self.fileobj.flush()
|
||||
return n
|
||||
|
||||
def write(self, s, flush=True):
|
||||
"""Write bytes to the pseudoterminal.
|
||||
|
||||
Returns the number of bytes written.
|
||||
"""
|
||||
return self._writeb(s, flush=flush)
|
||||
|
||||
def sendcontrol(self, char):
|
||||
'''Helper method that wraps send() with mnemonic access for sending control
|
||||
character to the child (such as Ctrl-C or Ctrl-D). For example, to send
|
||||
Ctrl-G (ASCII 7, bell, '\a')::
|
||||
|
||||
child.sendcontrol('g')
|
||||
|
||||
See also, sendintr() and sendeof().
|
||||
'''
|
||||
char = char.lower()
|
||||
a = ord(char)
|
||||
if 97 <= a <= 122:
|
||||
a = a - ord('a') + 1
|
||||
byte = _byte(a)
|
||||
return self._writeb(byte), byte
|
||||
d = {'@': 0, '`': 0,
|
||||
'[': 27, '{': 27,
|
||||
'\\': 28, '|': 28,
|
||||
']': 29, '}': 29,
|
||||
'^': 30, '~': 30,
|
||||
'_': 31,
|
||||
'?': 127}
|
||||
if char not in d:
|
||||
return 0, b''
|
||||
|
||||
byte = _byte(d[char])
|
||||
return self._writeb(byte), byte
|
||||
|
||||
def sendeof(self):
|
||||
'''This sends an EOF to the child. This sends a character which causes
|
||||
the pending parent output buffer to be sent to the waiting child
|
||||
program without waiting for end-of-line. If it is the first character
|
||||
of the line, the read() in the user program returns 0, which signifies
|
||||
end-of-file. This means to work as expected a sendeof() has to be
|
||||
called at the beginning of a line. This method does not send a newline.
|
||||
It is the responsibility of the caller to ensure the eof is sent at the
|
||||
beginning of a line. '''
|
||||
|
||||
return self._writeb(_EOF), _EOF
|
||||
|
||||
def sendintr(self):
|
||||
'''This sends a SIGINT to the child. It does not require
|
||||
the SIGINT to be the first character on a line. '''
|
||||
|
||||
return self._writeb(_INTR), _INTR
|
||||
|
||||
def eof(self):
|
||||
'''This returns True if the EOF exception was ever raised.
|
||||
'''
|
||||
|
||||
return self.flag_eof
|
||||
|
||||
def terminate(self, force=False):
|
||||
'''This forces a child process to terminate. It starts nicely with
|
||||
SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This
|
||||
returns True if the child was terminated. This returns False if the
|
||||
child could not be terminated. '''
|
||||
|
||||
if not self.isalive():
|
||||
return True
|
||||
try:
|
||||
self.kill(signal.SIGHUP)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
self.kill(signal.SIGCONT)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
self.kill(signal.SIGINT)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
if force:
|
||||
self.kill(signal.SIGKILL)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
except OSError:
|
||||
# I think there are kernel timing issues that sometimes cause
|
||||
# this to happen. I think isalive() reports True, but the
|
||||
# process is dead to the kernel.
|
||||
# Make one last attempt to see if the kernel is up to date.
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def wait(self):
|
||||
'''This waits until the child exits. This is a blocking call. This will
|
||||
not read any data from the child, so this will block forever if the
|
||||
child has unread output and has terminated. In other words, the child
|
||||
may have printed output then called exit(), but, the child is
|
||||
technically still alive until its output is read by the parent. '''
|
||||
|
||||
if self.isalive():
|
||||
pid, status = os.waitpid(self.pid, 0)
|
||||
else:
|
||||
return self.exitstatus
|
||||
self.exitstatus = os.WEXITSTATUS(status)
|
||||
if os.WIFEXITED(status):
|
||||
self.status = status
|
||||
self.exitstatus = os.WEXITSTATUS(status)
|
||||
self.signalstatus = None
|
||||
self.terminated = True
|
||||
elif os.WIFSIGNALED(status):
|
||||
self.status = status
|
||||
self.exitstatus = None
|
||||
self.signalstatus = os.WTERMSIG(status)
|
||||
self.terminated = True
|
||||
elif os.WIFSTOPPED(status): # pragma: no cover
|
||||
# You can't call wait() on a child process in the stopped state.
|
||||
raise PtyProcessError('Called wait() on a stopped child ' +
|
||||
'process. This is not supported. Is some other ' +
|
||||
'process attempting job control with our child pid?')
|
||||
return self.exitstatus
|
||||
|
||||
def isalive(self):
|
||||
'''This tests if the child process is running or not. This is
|
||||
non-blocking. If the child was terminated then this will read the
|
||||
exitstatus or signalstatus of the child. This returns True if the child
|
||||
process appears to be running or False if not. It can take literally
|
||||
SECONDS for Solaris to return the right status. '''
|
||||
|
||||
if self.terminated:
|
||||
return False
|
||||
|
||||
if self.flag_eof:
|
||||
# This is for Linux, which requires the blocking form
|
||||
# of waitpid to get the status of a defunct process.
|
||||
# This is super-lame. The flag_eof would have been set
|
||||
# in read_nonblocking(), so this should be safe.
|
||||
waitpid_options = 0
|
||||
else:
|
||||
waitpid_options = os.WNOHANG
|
||||
|
||||
try:
|
||||
pid, status = os.waitpid(self.pid, waitpid_options)
|
||||
except OSError as e:
|
||||
# No child processes
|
||||
if e.errno == errno.ECHILD:
|
||||
raise PtyProcessError('isalive() encountered condition ' +
|
||||
'where "terminated" is 0, but there was no child ' +
|
||||
'process. Did someone else call waitpid() ' +
|
||||
'on our process?')
|
||||
else:
|
||||
raise
|
||||
|
||||
# I have to do this twice for Solaris.
|
||||
# I can't even believe that I figured this out...
|
||||
# If waitpid() returns 0 it means that no child process
|
||||
# wishes to report, and the value of status is undefined.
|
||||
if pid == 0:
|
||||
try:
|
||||
### os.WNOHANG) # Solaris!
|
||||
pid, status = os.waitpid(self.pid, waitpid_options)
|
||||
except OSError as e: # pragma: no cover
|
||||
# This should never happen...
|
||||
if e.errno == errno.ECHILD:
|
||||
raise PtyProcessError('isalive() encountered condition ' +
|
||||
'that should never happen. There was no child ' +
|
||||
'process. Did someone else call waitpid() ' +
|
||||
'on our process?')
|
||||
else:
|
||||
raise
|
||||
|
||||
# If pid is still 0 after two calls to waitpid() then the process
|
||||
# really is alive. This seems to work on all platforms, except for
|
||||
# Irix which seems to require a blocking call on waitpid or select,
|
||||
# so I let read_nonblocking take care of this situation
|
||||
# (unfortunately, this requires waiting through the timeout).
|
||||
if pid == 0:
|
||||
return True
|
||||
|
||||
if pid == 0:
|
||||
return True
|
||||
|
||||
if os.WIFEXITED(status):
|
||||
self.status = status
|
||||
self.exitstatus = os.WEXITSTATUS(status)
|
||||
self.signalstatus = None
|
||||
self.terminated = True
|
||||
elif os.WIFSIGNALED(status):
|
||||
self.status = status
|
||||
self.exitstatus = None
|
||||
self.signalstatus = os.WTERMSIG(status)
|
||||
self.terminated = True
|
||||
elif os.WIFSTOPPED(status):
|
||||
raise PtyProcessError('isalive() encountered condition ' +
|
||||
'where child process is stopped. This is not ' +
|
||||
'supported. Is some other process attempting ' +
|
||||
'job control with our child pid?')
|
||||
return False
|
||||
|
||||
def kill(self, sig):
|
||||
"""Send the given signal to the child application.
|
||||
|
||||
In keeping with UNIX tradition it has a misleading name. It does not
|
||||
necessarily kill the child unless you send the right signal. See the
|
||||
:mod:`signal` module for constants representing signal numbers.
|
||||
"""
|
||||
|
||||
# Same as os.kill, but the pid is given for you.
|
||||
if self.isalive():
|
||||
os.kill(self.pid, sig)
|
||||
|
||||
def getwinsize(self):
|
||||
"""Return the window size of the pseudoterminal as a tuple (rows, cols).
|
||||
"""
|
||||
TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912)
|
||||
s = struct.pack('HHHH', 0, 0, 0, 0)
|
||||
x = fcntl.ioctl(self.fd, TIOCGWINSZ, s)
|
||||
return struct.unpack('HHHH', x)[0:2]
|
||||
|
||||
def setwinsize(self, rows, cols):
|
||||
"""Set the terminal window size of the child tty.
|
||||
|
||||
This will cause a SIGWINCH signal to be sent to the child. This does not
|
||||
change the physical window size. It changes the size reported to
|
||||
TTY-aware applications like vi or curses -- applications that respond to
|
||||
the SIGWINCH signal.
|
||||
"""
|
||||
return _setwinsize(self.fd, rows, cols)
|
||||
|
||||
|
||||
class PtyProcessUnicode(PtyProcess):
|
||||
"""Unicode wrapper around a process running in a pseudoterminal.
|
||||
|
||||
This class exposes a similar interface to :class:`PtyProcess`, but its read
|
||||
methods return unicode, and its :meth:`write` accepts unicode.
|
||||
"""
|
||||
if PY3:
|
||||
string_type = str
|
||||
else:
|
||||
string_type = unicode # analysis:ignore
|
||||
|
||||
def __init__(self, pid, fd, encoding='utf-8', codec_errors='strict'):
|
||||
super(PtyProcessUnicode, self).__init__(pid, fd)
|
||||
self.encoding = encoding
|
||||
self.codec_errors = codec_errors
|
||||
self.decoder = codecs.getincrementaldecoder(encoding)(errors=codec_errors)
|
||||
|
||||
def read(self, size=1024):
|
||||
"""Read at most ``size`` bytes from the pty, return them as unicode.
|
||||
|
||||
Can block if there is nothing to read. Raises :exc:`EOFError` if the
|
||||
terminal was closed.
|
||||
|
||||
The size argument still refers to bytes, not unicode code points.
|
||||
"""
|
||||
b = super(PtyProcessUnicode, self).read(size)
|
||||
return self.decoder.decode(b, final=False)
|
||||
|
||||
def readline(self):
|
||||
"""Read one line from the pseudoterminal, and return it as unicode.
|
||||
|
||||
Can block if there is nothing to read. Raises :exc:`EOFError` if the
|
||||
terminal was closed.
|
||||
"""
|
||||
b = super(PtyProcessUnicode, self).readline()
|
||||
return self.decoder.decode(b, final=False)
|
||||
|
||||
def write(self, s):
|
||||
"""Write the unicode string ``s`` to the pseudoterminal.
|
||||
|
||||
Returns the number of bytes written.
|
||||
"""
|
||||
b = s.encode(self.encoding)
|
||||
return super(PtyProcessUnicode, self).write(b)
|
||||
@@ -0,0 +1,71 @@
|
||||
try:
|
||||
from shutil import which # Python >= 3.3
|
||||
except ImportError:
|
||||
import os, sys
|
||||
|
||||
# This is copied from Python 3.4.1
|
||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
"""Given a command, mode, and a PATH string, return the path which
|
||||
conforms to the given mode on the PATH, or None if there is no such
|
||||
file.
|
||||
|
||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
||||
path.
|
||||
|
||||
"""
|
||||
# Check that a given file can be accessed with the correct mode.
|
||||
# Additionally check that `file` is not a directory, as on Windows
|
||||
# directories pass the os.access check.
|
||||
def _access_check(fn, mode):
|
||||
return (os.path.exists(fn) and os.access(fn, mode)
|
||||
and not os.path.isdir(fn))
|
||||
|
||||
# If we're given a path with a directory part, look it up directly rather
|
||||
# than referring to PATH directories. This includes checking relative to the
|
||||
# current directory, e.g. ./script
|
||||
if os.path.dirname(cmd):
|
||||
if _access_check(cmd, mode):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
if path is None:
|
||||
path = os.environ.get("PATH", os.defpath)
|
||||
if not path:
|
||||
return None
|
||||
path = path.split(os.pathsep)
|
||||
|
||||
if sys.platform == "win32":
|
||||
# The current directory takes precedence on Windows.
|
||||
if not os.curdir in path:
|
||||
path.insert(0, os.curdir)
|
||||
|
||||
# PATHEXT is necessary to check on Windows.
|
||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
||||
# See if the given file matches any of the expected path extensions.
|
||||
# This will allow us to short circuit when given "python.exe".
|
||||
# If it does match, only test that one, otherwise we have to try
|
||||
# others.
|
||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||
files = [cmd]
|
||||
else:
|
||||
files = [cmd + ext for ext in pathext]
|
||||
else:
|
||||
# On other platforms you don't have things like PATHEXT to tell you
|
||||
# what file suffixes are executable, so just pass on cmd as-is.
|
||||
files = [cmd]
|
||||
|
||||
seen = set()
|
||||
for dir in path:
|
||||
normdir = os.path.normcase(dir)
|
||||
if not normdir in seen:
|
||||
seen.add(normdir)
|
||||
for thefile in files:
|
||||
name = os.path.join(dir, thefile)
|
||||
if _access_check(name, mode):
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
class PtyProcessError(Exception):
|
||||
"""Generic error class for this package."""
|
||||
+491
-56
@@ -452,7 +452,7 @@ def getMsg(key,args = ()):
|
||||
|
||||
# 获取Web服务器
|
||||
def GetWebServer():
|
||||
if os.path.exists('/www/server/apache/bin/apachectl'):
|
||||
if os.path.exists('{}/apache/bin/apachectl'.format(get_setup_path())):
|
||||
webserver = 'apache'
|
||||
elif os.path.exists('/usr/local/lsws/bin/lswsctrl'):
|
||||
webserver = 'openlitespeed'
|
||||
@@ -464,31 +464,66 @@ def get_webserver():
|
||||
return GetWebServer()
|
||||
|
||||
def ServiceReload():
|
||||
# 重载Web服务配置
|
||||
if os.path.exists('/www/server/nginx/sbin/nginx'):
|
||||
#重载Web服务配置
|
||||
if os.path.exists('{}/nginx/sbin/nginx'.format(get_setup_path())):
|
||||
result = ExecShell('/etc/init.d/nginx reload')
|
||||
if result[1].find('nginx.pid') != -1:
|
||||
ExecShell('pkill -9 nginx && sleep 1')
|
||||
ExecShell('/etc/init.d/nginx start')
|
||||
elif os.path.exists('/www/server/apache/bin/apachectl'):
|
||||
elif os.path.exists('{}/apache/bin/apachectl'.format(get_setup_path())):
|
||||
result = ExecShell('/etc/init.d/httpd reload')
|
||||
else:
|
||||
result = ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl restart')
|
||||
return result
|
||||
|
||||
def serviceReload():
|
||||
return ServiceReload()
|
||||
|
||||
|
||||
def ExecShell(cmdstring, timeout=None, shell=True,cwd=None,env=None):
|
||||
|
||||
def get_preexec_fn(run_user):
|
||||
'''
|
||||
@name 获取指定执行用户预处理函数
|
||||
@author hwliang<2021-08-19>
|
||||
@param run_user<string> 运行用户
|
||||
@return 预处理函数
|
||||
'''
|
||||
import pwd
|
||||
pid = pwd.getpwnam(run_user)
|
||||
uid = pid.pw_uid
|
||||
gid = pid.pw_gid
|
||||
|
||||
def _exec_rn():
|
||||
os.setgid(gid)
|
||||
os.setuid(uid)
|
||||
return _exec_rn
|
||||
|
||||
|
||||
def ExecShell(cmdstring, timeout=None, shell=True,cwd=None,env=None,user = None):
|
||||
'''
|
||||
@name 执行命令
|
||||
@author hwliang<2021-08-19>
|
||||
@param cmdstring 命令 [必传]
|
||||
@param timeout 超时时间
|
||||
@param shell 是否通过shell运行
|
||||
@param cwd 进入的目录
|
||||
@param env 环境变量
|
||||
@param user 执行用户名
|
||||
@return 命令执行结果
|
||||
'''
|
||||
a = ''
|
||||
e = ''
|
||||
import subprocess,tempfile
|
||||
|
||||
preexec_fn = None
|
||||
tmp_dir = '/dev/shm'
|
||||
if user:
|
||||
preexec_fn = get_preexec_fn(user)
|
||||
tmp_dir = '/tmp'
|
||||
try:
|
||||
rx = md5(cmdstring)
|
||||
succ_f = tempfile.SpooledTemporaryFile(max_size=4096,mode='wb+',suffix='_succ',prefix='btex_' + rx ,dir='/dev/shm')
|
||||
err_f = tempfile.SpooledTemporaryFile(max_size=4096,mode='wb+',suffix='_err',prefix='btex_' + rx ,dir='/dev/shm')
|
||||
sub = subprocess.Popen(cmdstring, close_fds=True, shell=shell,bufsize=128,stdout=succ_f,stderr=err_f,cwd=cwd,env=env)
|
||||
succ_f = tempfile.SpooledTemporaryFile(max_size=4096,mode='wb+',suffix='_succ',prefix='btex_' + rx ,dir=tmp_dir)
|
||||
err_f = tempfile.SpooledTemporaryFile(max_size=4096,mode='wb+',suffix='_err',prefix='btex_' + rx ,dir=tmp_dir)
|
||||
sub = subprocess.Popen(cmdstring, close_fds=True, shell=shell,bufsize=128,stdout=succ_f,stderr=err_f,cwd=cwd,env=env,preexec_fn=preexec_fn)
|
||||
if timeout:
|
||||
s = 0
|
||||
d = 0.01
|
||||
@@ -594,9 +629,9 @@ def get_client_ip():
|
||||
return GetClientIp()
|
||||
|
||||
def phpReload(version):
|
||||
# 重载PHP配置
|
||||
#重载PHP配置
|
||||
import os
|
||||
if os.path.exists('/www/server/php/' + version + '/libphp5.so'):
|
||||
if os.path.exists(get_setup_path()+'/php/' + version + '/libphp5.so'):
|
||||
ExecShell('/etc/init.d/httpd reload')
|
||||
else:
|
||||
ExecShell('/etc/init.d/php-fpm-'+version+' reload')
|
||||
@@ -893,12 +928,13 @@ def get_plugin_title(plugin_name):
|
||||
@return string
|
||||
'''
|
||||
|
||||
info_file = '/www/server/panel/plugin/{}/info.json'.format(plugin_name)
|
||||
info_file = '{}/{}/info.json'.format(get_plugin_path(),plugin_name)
|
||||
try:
|
||||
return json.loads(readFile(info_file))['title']
|
||||
except:
|
||||
return plugin_name
|
||||
|
||||
|
||||
def get_error_object(plugin_title = None,plugin_name = None):
|
||||
'''
|
||||
@name 获取格式化错误响应对像
|
||||
@@ -931,11 +967,11 @@ REQUEST_FORM: {request_form}
|
||||
full_path = request.full_path,
|
||||
request_form = request.form.to_dict(),
|
||||
user_agent = request.headers.get('User-Agent'),
|
||||
panel_version = get_panel_version(),
|
||||
panel_version = version(),
|
||||
os_version = get_os_version()
|
||||
)
|
||||
|
||||
result =readFile('/www/server/panel/BTPanel/templates/default/plugin_error.html').format(
|
||||
result =readFile('{}/BTPanel/templates/default/plugin_error.html'.format(get_panel_path())).format(
|
||||
plugin_name=plugin_title,
|
||||
request_info=request_info,
|
||||
error_title=error_info.split("\n")[-1],
|
||||
@@ -959,8 +995,9 @@ def format_date(format="%Y-%m-%d %H:%M:%S",times = None):
|
||||
|
||||
# 检查Web服务器配置文件是否有错误
|
||||
def checkWebConfig():
|
||||
f1 = '/www/server/panel/vhost/'
|
||||
f2 = '/www/server/panel/plugin/'
|
||||
f1 = '{}/'.format(get_vhost_path())
|
||||
f2 = '{}/'.format(get_plugin_path())
|
||||
setup_path = get_setup_path()
|
||||
if not os.path.exists(f2 + 'btwaf'):
|
||||
f3 = f1 + 'nginx/btwaf.conf'
|
||||
if os.path.exists(f3): os.remove(f3)
|
||||
@@ -974,19 +1011,19 @@ def checkWebConfig():
|
||||
f3 = f1 + 'nginx/total.conf'
|
||||
if os.path.exists(f3): os.remove(f3)
|
||||
else:
|
||||
if os.path.exists('/www/server/apache/modules/mod_lua.so'):
|
||||
writeFile(f1 + 'apache/btwaf.conf', 'LoadModule lua_module modules/mod_lua.so')
|
||||
writeFile(f1 + 'apache/total.conf', 'LuaHookLog /www/server/total/httpd_log.lua run_logs')
|
||||
if os.path.exists(setup_path + '/apache/modules/mod_lua.so'):
|
||||
writeFile(f1 + 'apache/btwaf.conf','LoadModule lua_module modules/mod_lua.so')
|
||||
writeFile(f1 + 'apache/total.conf','LuaHookLog {}/total/httpd_log.lua run_logs'.format(setup_path))
|
||||
else:
|
||||
f3 = f1 + 'apache/total.conf'
|
||||
if os.path.exists(f3): os.remove(f3)
|
||||
|
||||
if get_webserver() == 'nginx':
|
||||
result = ExecShell("ulimit -n 8192 ; /www/server/nginx/sbin/nginx -t -c /www/server/nginx/conf/nginx.conf")
|
||||
result = ExecShell("ulimit -n 8192 ; {setup_path}/nginx/sbin/nginx -t -c {setup_path}/nginx/conf/nginx.conf".format(setup_path = setup_path))
|
||||
searchStr = 'successful'
|
||||
elif get_webserver() == 'apache':
|
||||
# else:
|
||||
result = ExecShell("ulimit -n 8192 ; /www/server/apache/bin/apachectl -t")
|
||||
result = ExecShell("ulimit -n 8192 ; {setup_path}/apache/bin/apachectl -t".format(setup_path = setup_path))
|
||||
searchStr = 'Syntax OK'
|
||||
else:
|
||||
result = ["1","1"]
|
||||
@@ -1060,7 +1097,7 @@ def CheckMyCnf():
|
||||
if os.path.exists(confFile):
|
||||
conf = readFile(confFile)
|
||||
if conf.find('[mysqld]') != -1: return True
|
||||
versionFile = '/www/server/mysql/version.pl'
|
||||
versionFile = get_setup_path() + '/mysql/version.pl'
|
||||
if not os.path.exists(versionFile): return False
|
||||
|
||||
versions = ['5.1','5.5','5.6','5.7','8.0','AliSQL']
|
||||
@@ -1464,17 +1501,17 @@ def get_page(count, p=1, rows=12, callback='', result='1,2,3,4,5,8'):
|
||||
# 取面板版本
|
||||
def version():
|
||||
try:
|
||||
comm = ReadFile('/www/server/panel/class/common.py')
|
||||
comm = ReadFile('{}/common.py'.format(get_class_path()))
|
||||
return re.search("g\.version\s*=\s*'(\d+\.\d+\.\d+)'",comm).groups()[0]
|
||||
except:
|
||||
return get_panel_version()
|
||||
|
||||
def get_panel_version():
|
||||
comm = ReadFile('/www/server/panel/class/common.py')
|
||||
comm = ReadFile('{}/common.py'.format(get_class_path()))
|
||||
s_key = 'g.version = '
|
||||
s_len = len(s_key)
|
||||
s_leff = comm.find(s_key) + s_len
|
||||
version = comm[s_leff:s_leff+6].strip().strip("'")
|
||||
version = comm[s_leff:s_leff+10].strip().strip("'")
|
||||
return version
|
||||
|
||||
|
||||
@@ -1490,7 +1527,10 @@ def get_os_version():
|
||||
else:
|
||||
version = version.replace('release ','').replace('Linux','').replace('(Core)','').strip()
|
||||
v_info = sys.version_info
|
||||
version = "{} {}(Py{}.{}.{})".format(version,os.uname().machine,v_info.major,v_info.minor,v_info.micro)
|
||||
try:
|
||||
version = "{} {}(Py{}.{}.{})".format(version,os.uname().machine,v_info.major,v_info.minor,v_info.micro)
|
||||
except:
|
||||
version = "{} (Py{}.{}.{})".format(version,v_info.major,v_info.minor,v_info.micro)
|
||||
return version
|
||||
|
||||
#取文件或目录大小
|
||||
@@ -1560,7 +1600,7 @@ def write_request_log(reques = None):
|
||||
if request.path in ['/service_status','/favicon.ico','/task','/system','/ajax','/control','/data','/ssl']:
|
||||
return False
|
||||
|
||||
log_path = '/www/server/panel/logs/request'
|
||||
log_path = '{}/logs/request'.format(get_panel_path())
|
||||
log_file = getDate(format='%Y-%m-%d') + '.json'
|
||||
if not os.path.exists(log_path): os.makedirs(log_path)
|
||||
|
||||
@@ -1646,6 +1686,32 @@ def get_database_character(db_name):
|
||||
except:
|
||||
return 'utf8'
|
||||
|
||||
def get_database_codestr(codeing):
|
||||
wheres = {
|
||||
'utf8' : 'utf8_general_ci',
|
||||
'utf8mb4' : 'utf8mb4_general_ci',
|
||||
'gbk' : 'gbk_chinese_ci',
|
||||
'big5' : 'big5_chinese_ci'
|
||||
}
|
||||
return wheres[codeing]
|
||||
|
||||
|
||||
def get_database_size():
|
||||
"""
|
||||
@获取数据库大小
|
||||
"""
|
||||
data = {}
|
||||
try:
|
||||
import panelMysql
|
||||
tables = panelMysql.panelMysql().query("select table_schema, (sum(DATA_LENGTH)+sum(INDEX_LENGTH)) as data from information_schema.TABLES group by table_schema")
|
||||
if type(tables) == list:
|
||||
for x in tables:
|
||||
if len(x) < 2:continue
|
||||
if x[1] == None:continue
|
||||
data[x[0]] = int(x[1])
|
||||
except: return data
|
||||
return data
|
||||
|
||||
def en_punycode(domain):
|
||||
if sys.version_info[0] == 2:
|
||||
domain = domain.encode('utf8')
|
||||
@@ -1781,17 +1847,17 @@ def check_domain_panel():
|
||||
|
||||
#是否离线模式
|
||||
def is_local():
|
||||
s_file = '/www/server/panel/data/not_network.pl'
|
||||
s_file = '{}/data/not_network.pl'.format(get_panel_path())
|
||||
return os.path.exists(s_file)
|
||||
|
||||
|
||||
#自动备份面板数据
|
||||
def auto_backup_panel():
|
||||
try:
|
||||
panel_paeh = '/www/server/panel'
|
||||
panel_paeh = get_panel_path()
|
||||
paths = panel_paeh + '/data/not_auto_backup.pl'
|
||||
if os.path.exists(paths): return False
|
||||
b_path = '/www/backup/panel'
|
||||
b_path = '{}/panel'.format(get_backup_path())
|
||||
backup_path = b_path + '/' + format_date('%Y-%m-%d')
|
||||
if os.path.exists(backup_path): return True
|
||||
if os.path.getsize(panel_paeh + '/data/default.db') > 104857600 * 2: return False
|
||||
@@ -1946,7 +2012,7 @@ def get_fpm_address(php_version,bind=False):
|
||||
@return tuple or string
|
||||
'''
|
||||
fpm_address = '/tmp/php-cgi-{}.sock'.format(php_version)
|
||||
php_fpm_file = '/www/server/php/{}/etc/php-fpm.conf'.format(php_version)
|
||||
php_fpm_file = '{}/php/{}/etc/php-fpm.conf'.format(get_setup_path(),php_version)
|
||||
try:
|
||||
fpm_conf = readFile(php_fpm_file)
|
||||
tmp = re.findall(r"listen\s*=\s*(.+)",fpm_conf)
|
||||
@@ -2020,9 +2086,10 @@ def get_site_php_version(siteName):
|
||||
@return string
|
||||
'''
|
||||
web_server = get_webserver()
|
||||
conf = readFile('/www/server/panel/vhost/'+web_server+'/'+siteName+'.conf')
|
||||
vhost_path = get_vhost_path()
|
||||
conf = readFile(vhost_path + '/'+web_server+'/'+siteName+'.conf')
|
||||
if web_server == 'openlitespeed':
|
||||
conf = readFile('/www/server/panel/vhost/' + web_server + '/detail/' + siteName + '.conf')
|
||||
conf = readFile(vhost_path + '/' + web_server + '/detail/' + siteName + '.conf')
|
||||
return get_php_version_conf(conf)
|
||||
|
||||
|
||||
@@ -2087,7 +2154,7 @@ def sync_php_address(php_version):
|
||||
@param php_version string PHP版本
|
||||
@return void
|
||||
'''
|
||||
if not os.path.exists('/www/server/php/{}/bin/php'.format(php_version)): # 指定PHP版本是否安装
|
||||
if not os.path.exists('{}/php/{}/bin/php'.format(get_setup_path(),php_version)): # 指定PHP版本是否安装
|
||||
return False
|
||||
ngx_rep = r"(unix:/tmp/php-cgi.*\.sock|127.0.0.1:\d+)"
|
||||
apa_rep = r"(unix:/tmp/php-cgi.*\.sock\|fcgi://localhost|fcgi://127.0.0.1:\d+)"
|
||||
@@ -2096,7 +2163,7 @@ def sync_php_address(php_version):
|
||||
is_write = False
|
||||
|
||||
#nginx的PHP配置文件
|
||||
nginx_conf_path = '/www/server/nginx/conf'
|
||||
nginx_conf_path = '{}/nginx/conf'.format(get_setup_path())
|
||||
|
||||
if os.path.exists(nginx_conf_path):
|
||||
for f_name in os.listdir(nginx_conf_path):
|
||||
@@ -2111,14 +2178,14 @@ def sync_php_address(php_version):
|
||||
# is_write = True
|
||||
|
||||
#apache的网站配置文件
|
||||
apache_conf_path = '/www/server/panel/vhost/apache'
|
||||
apache_conf_path = '{}/apache'.format(get_vhost_path())
|
||||
if os.path.exists(apache_conf_path):
|
||||
for f_name in os.listdir(apache_conf_path):
|
||||
conf_file = '/'.join((apache_conf_path,f_name))
|
||||
if sub_php_address(conf_file,apa_rep,apa_proxy,php_version):
|
||||
is_write = True
|
||||
#apache的phpmyadmin
|
||||
conf_file = '/www/server/apache/conf/extra/httpd-vhosts.conf'
|
||||
conf_file = '{}/apache/conf/extra/httpd-vhosts.conf'.format(get_setup_path())
|
||||
if os.path.exists(conf_file):
|
||||
if sub_php_address(conf_file,apa_rep,apa_proxy,php_version):
|
||||
is_write = True
|
||||
@@ -2209,11 +2276,17 @@ def set_cdn_url(cdn_url):
|
||||
return True
|
||||
|
||||
def get_python_bin():
|
||||
bin_file = '/www/server/panel/pyenv/bin/python'
|
||||
bin_file = '{}/pyenv/bin/python'.format(get_panel_path())
|
||||
if os.path.exists(bin_file):
|
||||
return bin_file
|
||||
return '/usr/bin/python'
|
||||
|
||||
def get_pip_bin():
|
||||
bin_file = '{}/pyenv/bin/pip'.format(get_panel_path())
|
||||
if os.path.exists(bin_file):
|
||||
return bin_file
|
||||
return '/usr/bin/pip'
|
||||
|
||||
def aes_encrypt(data,key):
|
||||
import panelAes
|
||||
if sys.version_info[0] == 2:
|
||||
@@ -2314,11 +2387,11 @@ def get_session_id():
|
||||
|
||||
#尝试自动恢复面板数据库
|
||||
def rep_default_db():
|
||||
db_path = '/www/server/panel/data/'
|
||||
db_path = '{}/data/'.format(get_panel_path())
|
||||
db_file = db_path + 'default.db'
|
||||
db_tmp_backup = db_path + 'default_' + format_date("%Y%m%d_%H%M%S") + ".db"
|
||||
|
||||
panel_backup = '/www/backup/panel'
|
||||
panel_backup = '{}/panel'.format(get_backup_path())
|
||||
bak_list = os.listdir(panel_backup)
|
||||
if not bak_list: return False
|
||||
bak_list = sorted(bak_list,reverse=True)
|
||||
@@ -2362,7 +2435,7 @@ def chdck_salt():
|
||||
|
||||
|
||||
def get_login_token():
|
||||
token_s = readFile('/www/server/panel/data/login_token.pl')
|
||||
token_s = readFile('{}/data/login_token.pl'.format(get_panel_path()))
|
||||
if not token_s: return GetRandomString(32)
|
||||
return token_s
|
||||
|
||||
@@ -2528,14 +2601,15 @@ def get_curl_bin():
|
||||
#设置防跨站配置
|
||||
def set_open_basedir():
|
||||
try:
|
||||
fastcgi_file = '/www/server/nginx/conf/fastcgi.conf'
|
||||
fastcgi_file = '{}/nginx/conf/fastcgi.conf'.format(get_setup_path())
|
||||
|
||||
if os.path.exists(fastcgi_file):
|
||||
fastcgi_body = readFile(fastcgi_file)
|
||||
if fastcgi_body.find('bt_safe_dir') == -1:
|
||||
fastcgi_body = fastcgi_body + "\n"+'fastcgi_param PHP_ADMIN_VALUE "$bt_safe_dir=$bt_safe_open";'
|
||||
writeFile(fastcgi_file,fastcgi_body)
|
||||
|
||||
proxy_file = '/www/server/nginx/conf/proxy.conf'
|
||||
proxy_file = '{}/nginx/conf/proxy.conf'.format(get_setup_path())
|
||||
if os.path.exists(proxy_file):
|
||||
proxy_body = readFile(proxy_file)
|
||||
if proxy_body.find('bt_safe_dir') == -1:
|
||||
@@ -2547,7 +2621,7 @@ map "baota_open" $bt_safe_open {
|
||||
} '''
|
||||
writeFile(proxy_file,proxy_body)
|
||||
|
||||
open_basedir_path = '/www/server/panel/vhost/open_basedir/nginx'
|
||||
open_basedir_path = '{}/open_basedir/nginx'.format(get_vhost_path())
|
||||
if not os.path.exists(open_basedir_path):
|
||||
os.makedirs(open_basedir_path,384)
|
||||
|
||||
@@ -2623,7 +2697,7 @@ def cloud_check_domain(domain):
|
||||
@return void
|
||||
'''
|
||||
try:
|
||||
check_domain_path = '/www/server/panel/data/check_domain/'
|
||||
check_domain_path = '{}/data/check_domain/'.format(get_panel_path())
|
||||
if not os.path.exists(check_domain_path):
|
||||
os.makedirs(check_domain_path,384)
|
||||
pdata = get_user_info()
|
||||
@@ -2634,9 +2708,13 @@ def cloud_check_domain(domain):
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_mac_address():
|
||||
import uuid
|
||||
mac=uuid.UUID(int = uuid.getnode()).hex[-12:]
|
||||
return ":".join([mac[e:e+2] for e in range(0,11,2)])
|
||||
|
||||
def get_user_info():
|
||||
user_file = '/www/server/panel/data/userInfo.json'
|
||||
user_file = '{}/data/userInfo.json'.format(get_panel_path())
|
||||
if not os.path.exists(user_file): return {}
|
||||
userInfo = {}
|
||||
try:
|
||||
@@ -2866,6 +2944,11 @@ class dict_obj:
|
||||
elif format in ['phone','mobile','m']:
|
||||
if not re.match("^[0-9]{11,11}$",result):
|
||||
raise ValueError("Parameters: {}, mobile phone number format required".format(key))
|
||||
elif format in ['port']:
|
||||
result_port = int(result)
|
||||
if result_port > 65535 or result_port < 0:
|
||||
raise ValueError("Parameters: {}, the required port number is 0-65535".format(key))
|
||||
result = result_port
|
||||
elif re.match(r"^[<>=]\d+$",result):
|
||||
operator = format[0]
|
||||
length = int(format[1:].strip())
|
||||
@@ -2889,13 +2972,6 @@ class dict_obj:
|
||||
raise ValueError("The specified parameter value range is incorrect, {}:{}".format(key,limit))
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#实例化定目录下的所有模块
|
||||
class get_modules:
|
||||
|
||||
@@ -3124,7 +3200,7 @@ def login_send_body(is_type,username,login_ip,port):
|
||||
send_mail("aapanel login reminder","aapanel login reminder:Your server "+get_ip()+" successfully logged in via "+is_type+", account number: "+username+", login IP: "+login_ip+":"+port+", login time: "+time.strftime('%Y -%m-%d %X',time.localtime()), True)
|
||||
if os.path.exists(login_send_dingding):
|
||||
if check_ip_white(send_login_white,login_ip):return False
|
||||
send_dingding("aapanel login reminder:Your server "+get_ip()+" successfully logged in via "+is_type+", account number: "+username+", login IP: "+login_ip+":"+port+", login time: "+time.strftime('%Y -%m-%d %X',time.localtime()), True)
|
||||
send_dingding("aapanel login reminder:Your server "+get_ip()+" successfully logged in via "+is_type+", account number: "+username+", login IP: "+login_ip+":"+port+", login time: "+time.strftime('%Y -%m-%d %X',time.localtime())+'\n\n > Login status: <font color=#20a53a>Succeed</font>', True)
|
||||
|
||||
#普通模式下调用发送消息【设置登陆告警后的设置】
|
||||
#title= 发送的title
|
||||
@@ -3211,6 +3287,15 @@ class PanelError(Exception):
|
||||
def __str__(self):
|
||||
return ("面板运行时发生错误: {}".format(repr(self.value)))
|
||||
|
||||
def get_sysbit():
|
||||
'''
|
||||
@name 获取操作系统位数
|
||||
@author hwliang<2021-07-07>
|
||||
@return int 32 or 64
|
||||
'''
|
||||
import struct
|
||||
return struct.calcsize('P') * 8
|
||||
|
||||
def get_setup_path():
|
||||
'''
|
||||
@name 获取安装路径
|
||||
@@ -3227,6 +3312,241 @@ def get_panel_path():
|
||||
'''
|
||||
return '{}/panel'.format(get_setup_path())
|
||||
|
||||
def get_plugin_path(plugin_name = None):
|
||||
'''
|
||||
@name 取指定插件目录
|
||||
@author hwliang<2021-07-14>
|
||||
@param plugin_name<string> 插件名称 不传则返回插件根目录
|
||||
@return string
|
||||
'''
|
||||
|
||||
root_path = "{}/plugin".format(get_panel_path())
|
||||
if not plugin_name: return root_path
|
||||
return "{}/{}".format(root_path,plugin_name)
|
||||
|
||||
def get_class_path():
|
||||
'''
|
||||
@name 取类库所在路径
|
||||
@author hwliang<2021-07-14>
|
||||
@return string
|
||||
'''
|
||||
return "{}/class".format(get_panel_path())
|
||||
|
||||
def get_logs_path():
|
||||
'''
|
||||
@name 取日志目录
|
||||
@author hwliang<2021-07-14>
|
||||
@return string
|
||||
'''
|
||||
return '/www/wwwlogs'
|
||||
|
||||
def get_vhost_path():
|
||||
'''
|
||||
@name 取虚拟主机目录
|
||||
@author hwliang<2021-08-14>
|
||||
@return string
|
||||
'''
|
||||
return '{}/vhost'.format(get_panel_path())
|
||||
|
||||
|
||||
def get_backup_path():
|
||||
'''
|
||||
@name 取备份目录
|
||||
@author hwliang<2021-07-14>
|
||||
@return string
|
||||
'''
|
||||
default_backup_path = '/www/backup'
|
||||
backup_path = M('config').where("id=?",(1,)).getField('backup_path')
|
||||
if not backup_path: return default_backup_path
|
||||
if os.path.exists(backup_path): return backup_path
|
||||
return default_backup_path
|
||||
|
||||
def get_site_path():
|
||||
'''
|
||||
@name 取站点默认存储目录
|
||||
@author hwliang<2021-07-14>
|
||||
@return string
|
||||
'''
|
||||
default_site_path = '/www/wwwroot'
|
||||
site_path = M('config').where("id=?",(1,)).getField('sites_path')
|
||||
if not site_path: return default_site_path
|
||||
if os.path.exists(site_path): return site_path
|
||||
return default_site_path
|
||||
|
||||
|
||||
def read_config(config_name,ext_name = 'json'):
|
||||
'''
|
||||
@name 读取指定配置文件
|
||||
@author hwliang<2021-07-14>
|
||||
@param config_name<string> 配置文件名称(不含扩展名)
|
||||
@param ext_name<string> 配置文件扩展名,默认为json
|
||||
@return string 如果发生错误,将抛出PanelError异常
|
||||
'''
|
||||
config_file = "{}/config/{}.{}".format(get_panel_path(),config_name,ext_name)
|
||||
if not os.path.exists(config_file):
|
||||
raise PanelError('指定配置文件{} 不存在'.format(config_name))
|
||||
|
||||
config_str = readFile(config_file)
|
||||
if ext_name == 'json':
|
||||
try:
|
||||
config_body = json.loads(config_str)
|
||||
except Exception as ex:
|
||||
raise PanelError('配置文件不是标准的可解析JSON内容!\n{}'.format(ex))
|
||||
return config_body
|
||||
return config_str
|
||||
|
||||
def save_config(config_name,config_body,ext_name = 'json'):
|
||||
'''
|
||||
@name 保存配置文件
|
||||
@author hwliang<2021-07-14>
|
||||
@param config_name<string> 配置文件名称(不含扩展名)
|
||||
@param config_body<mixed> 被保存的内容, ext_name为json,请传入可解析为json的参数类型,如list,dict,int,str等
|
||||
@param ext_name<string> 配置文件扩展名,默认为json
|
||||
@return string 如果发生错误,将抛出PanelError异常
|
||||
'''
|
||||
|
||||
config_file = "{}/config/{}.{}".format(get_panel_path(),config_name,ext_name)
|
||||
if ext_name == 'json':
|
||||
try:
|
||||
config_body = json.dumps(config_body)
|
||||
except Exception as ex:
|
||||
raise PanelError('配置内容无法被转换为json格式!\n{}'.format(ex))
|
||||
|
||||
return writeFile(config_file,config_body)
|
||||
|
||||
def get_config_value(config_name,key,default='',ext_name='json'):
|
||||
'''
|
||||
@name 获取指定配置文件的指定配置项
|
||||
@author hwliang<2021-07-14>
|
||||
@param config_name<string> 配置文件名称(不含扩展名)
|
||||
@param key<string> 配置项
|
||||
@param default<mixed> 获不存在则返回的默认值,默认为空字符串
|
||||
@param ext_name<string> 配置文件扩展名,默认为json
|
||||
@return mixed 如果发生错误,将抛出PanelError异常
|
||||
'''
|
||||
config_data = read_config(config_name,ext_name)
|
||||
return config_data.get(key,default)
|
||||
|
||||
def set_config_value(config_name,key,value,ext_name='json'):
|
||||
'''
|
||||
@name 设置指定配置文件的指定配置项
|
||||
@author hwliang<2021-07-14>
|
||||
@param config_name<string> 配置文件名称(不含扩展名)
|
||||
@param key<string> 配置项
|
||||
@param value<mixed> 配置值
|
||||
@param ext_name<string> 配置文件扩展名,默认为json
|
||||
@return mixed 如果发生错误,将抛出PanelError异常
|
||||
'''
|
||||
config_data = read_config(config_name,ext_name)
|
||||
config_data[key] = value
|
||||
return save_config(config_name,config_data,ext_name)
|
||||
|
||||
|
||||
def return_data(status,data = {},status_code=None,error_msg = None):
|
||||
'''
|
||||
@name 格式化响应内容
|
||||
@author hwliang<2021-07-14>
|
||||
@param status<bool> 状态
|
||||
@param data<mixed> 响应数据
|
||||
@param status_code<int> 状态码
|
||||
@param error_msg<string> 错误消息内容
|
||||
@return dict
|
||||
|
||||
'''
|
||||
if status_code is None:
|
||||
status_code = 1 if status else 0
|
||||
if error_msg is None:
|
||||
error_msg = '' if status else '未知错误'
|
||||
|
||||
result = {
|
||||
'status':status,
|
||||
"status_code":status_code,
|
||||
'error_msg':str(error_msg),
|
||||
'data':data
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def return_error(error_msg,status_code = -1,data = []):
|
||||
'''
|
||||
@name 格式化错误响应内容
|
||||
@author hwliang<2021-07-15>
|
||||
@param error_msg<string> 错误消息
|
||||
@param status_code<int> 状态码,默认为-1
|
||||
@param data<mixed> 响应数据
|
||||
@return dict
|
||||
'''
|
||||
return return_data(False,data,status_code,str(error_msg))
|
||||
|
||||
|
||||
def error(error_msg,status_code = -1,data = []):
|
||||
'''
|
||||
@name 格式化错误响应内容
|
||||
@author hwliang<2021-07-15>
|
||||
@param error_msg<string> 错误消息
|
||||
@param status_code<int> 状态码,默认为-1
|
||||
@param data<mixed> 响应数据
|
||||
@return dict
|
||||
'''
|
||||
return return_error(error_msg,status_code,data)
|
||||
|
||||
def success(data = [],status_code = 1,error_msg = ''):
|
||||
'''
|
||||
@name 格式化成功响应内容
|
||||
@author hwliang<2021-07-15>
|
||||
@param data<mixed> 响应数据
|
||||
@param status_code<int> 状态码,默认为0
|
||||
@return dict
|
||||
'''
|
||||
return return_data(True,data,status_code,error_msg)
|
||||
|
||||
|
||||
def return_status_code(status_code,format_body,data = []):
|
||||
'''
|
||||
@name 按状态码返回
|
||||
@author hwliang<2021-07-15>
|
||||
@param status_code<int> 状态码
|
||||
@param format_body<string> 错误内容
|
||||
@param data<mixed> 响应数据
|
||||
@return dict
|
||||
'''
|
||||
error_msg = get_config_value('status_code',str(status_code))
|
||||
if not error_msg: raise PanelError('指定状态码不存在!')
|
||||
return return_data(error_msg[0],data,status_code,error_msg[1].format(format_body))
|
||||
|
||||
|
||||
def to_dict_obj(data):
|
||||
'''
|
||||
@name 将dict转换为dict_obj
|
||||
@author hwliang<2021-07-15>
|
||||
@param data<dict> 要被转换的数据
|
||||
@return dict_obj
|
||||
'''
|
||||
if not isinstance(data,dict):
|
||||
raise PanelError('错误的数据类型,只支持将dict转换为dict_obj')
|
||||
pdata = dict_obj()
|
||||
for key in data.keys():
|
||||
pdata[key] = data[key]
|
||||
return pdata
|
||||
|
||||
def get_script_object(filename):
|
||||
'''
|
||||
@name 从脚本文件获取对像
|
||||
@author hwliang<2021-07-19>
|
||||
@param filename<string> 文件名
|
||||
@return object
|
||||
'''
|
||||
_obj = sys.modules.get(filename,None)
|
||||
if _obj: return _obj
|
||||
from types import ModuleType
|
||||
_obj = sys.modules.setdefault(filename, ModuleType(filename))
|
||||
_code = readFile(filename)
|
||||
_code_object = compile(_code,filename, 'exec')
|
||||
_obj.__file__ = filename
|
||||
_obj.__package__ = ''
|
||||
exec(_code_object, _obj.__dict__)
|
||||
return _obj
|
||||
|
||||
def check_hooks():
|
||||
'''
|
||||
@name 自动注册HOOK
|
||||
@@ -3291,6 +3611,21 @@ def get_hook_index(mod_name, def_name):
|
||||
end_index = '{}_{}_END'.format(mod_name, def_name)
|
||||
return last_index, end_index
|
||||
|
||||
|
||||
def flush_plugin_list():
|
||||
'''
|
||||
@name 刷新插件列表
|
||||
@author hwliang<2021-07-22>
|
||||
@return bool
|
||||
'''
|
||||
skey = 'TNaMJdG3mDHKRS6Y'
|
||||
from BTPanel import cache
|
||||
from pluginAuth import Plugin
|
||||
if cache.get(skey): cache.delete(skey)
|
||||
Plugin(False).get_plugin_list(True)
|
||||
return True
|
||||
|
||||
|
||||
def get_session_timeout():
|
||||
'''
|
||||
@name 获取session过期时间
|
||||
@@ -3306,7 +3641,10 @@ def get_session_timeout():
|
||||
session_timeout = 86400
|
||||
if not os.path.exists(sess_out_path):
|
||||
return session_timeout
|
||||
session_timeout = int(readFile(sess_out_path))
|
||||
try:
|
||||
session_timeout = int(readFile(sess_out_path))
|
||||
except:
|
||||
session_timeout = 86400
|
||||
cache.set(skey,session_timeout,3600)
|
||||
return session_timeout
|
||||
|
||||
@@ -3328,4 +3666,101 @@ def get_login_token_auth():
|
||||
return login_token
|
||||
login_token = readFile(login_token_file)
|
||||
cache.set(skey,login_token,3600)
|
||||
return login_token
|
||||
return login_token
|
||||
|
||||
|
||||
def listen_ipv6():
|
||||
'''
|
||||
@name 是否监听ipv6
|
||||
@author hwliang<2021-08-12>
|
||||
@return bool
|
||||
'''
|
||||
ipv6_file = '{}/data/ipv6.pl'.format(get_panel_path())
|
||||
return os.path.exists(ipv6_file)
|
||||
|
||||
def get_panel_log_file():
|
||||
'''
|
||||
@name 获取panel日志文件
|
||||
@author hwliang<2021-08-12>
|
||||
@return string
|
||||
'''
|
||||
return "{}/logs/error.log".format(get_panel_path())
|
||||
|
||||
|
||||
def print_log(_info,_level = 'DEBUG'):
|
||||
'''
|
||||
@name 写入日志
|
||||
@author hwliang<2021-08-12>
|
||||
@param _info<string> 要写入到日志文件的信息
|
||||
@param _level<string> 日志级别
|
||||
@return void
|
||||
'''
|
||||
log_body = "[{}][{}] - {}\n".format(format_date(),_level.upper(),_info)
|
||||
return WriteFile(get_panel_log_file(),log_body,'a+')
|
||||
|
||||
|
||||
|
||||
def to_date(format = "%Y-%m-%d %H:%M:%S",times = None):
|
||||
'''
|
||||
@name 格式时间转时间戳
|
||||
@author hwliang<2021-08-17>
|
||||
@param format<string> 时间格式
|
||||
@param times<date> 时间
|
||||
@return int
|
||||
'''
|
||||
ts = time.strptime(times, "%Y-%m-%d %H:%M:%S")
|
||||
return time.mktime(ts)
|
||||
|
||||
|
||||
def get_glibc_version():
|
||||
'''
|
||||
@name 获取glibc版本
|
||||
@author hwliang<2021-08-17>
|
||||
@return string
|
||||
'''
|
||||
try:
|
||||
cmd_result = ExecShell("ldd --version")[0]
|
||||
if not cmd_result: return ''
|
||||
glibc_version = cmd_result.split("\n")[0].split()[-1]
|
||||
except:
|
||||
return ''
|
||||
return glibc_version
|
||||
|
||||
|
||||
def is_apache_nginx():
|
||||
'''
|
||||
@name 是否是apache或nginx
|
||||
@author hwliang<2021-08-17>
|
||||
@return bool
|
||||
'''
|
||||
setup_path = get_setup_path()
|
||||
return os.path.exists(setup_path + '/apache') or os.path.exists(setup_path + '/nginx')
|
||||
|
||||
def error_403(e):
|
||||
from BTPanel import Response
|
||||
errorStr = '''<html>
|
||||
<head><title>403 Forbidden</title></head>
|
||||
<body>
|
||||
<center><h1>403 Forbidden</h1></center>
|
||||
<hr><center>nginx</center>
|
||||
</body>
|
||||
</html>'''
|
||||
headers = {
|
||||
"Content-Type": "text/html"
|
||||
}
|
||||
return Response(errorStr, status=403, headers=headers)
|
||||
|
||||
def error_404(e):
|
||||
from BTPanel import Response
|
||||
errorStr = '''<html>
|
||||
<head><title>404 Not Found</title></head>
|
||||
<body>
|
||||
<center><h1>404 Not Found</h1></center>
|
||||
<hr><center>nginx</center>
|
||||
</body>
|
||||
</html>'''
|
||||
headers = {
|
||||
"Content-Type": "text/html"
|
||||
}
|
||||
return Response(errorStr, status=404, headers=headers)
|
||||
|
||||
|
||||
+23
-13
@@ -131,9 +131,9 @@ class send_mail:
|
||||
msg['To']=formataddr(email)
|
||||
msg['Subject'] = title
|
||||
if int(self.__qq_mail_user['port']) == 465:
|
||||
server = smtplib.SMTP_SSL(str(self.__qq_mail_user['hosts']), str(self.__qq_mail_user['port']))
|
||||
server = smtplib.SMTP_SSL(str(self.__qq_mail_user['hosts']), str(self.__qq_mail_user['port']),timeout=5)
|
||||
else:
|
||||
server = smtplib.SMTP(str(self.__qq_mail_user['hosts']), str(self.__qq_mail_user['port']))
|
||||
server = smtplib.SMTP(str(self.__qq_mail_user['hosts']), str(self.__qq_mail_user['port']),timeout=5)
|
||||
server.login(self.__qq_mail_user['qq_mail'], self.__qq_mail_user['qq_stmp_pwd'])
|
||||
if type(email)==str:
|
||||
server.sendmail(self.__qq_mail_user['qq_mail'], [email.strip()], msg.as_string())
|
||||
@@ -141,6 +141,7 @@ class send_mail:
|
||||
server.sendmail(self.__qq_mail_user['qq_mail'], email, msg.as_string())
|
||||
server.quit()
|
||||
except Exception:
|
||||
print("发送错误,可能密码错误")
|
||||
ret = False
|
||||
return ret
|
||||
|
||||
@@ -206,18 +207,27 @@ class send_mail:
|
||||
# 钉钉机器人
|
||||
def dingding_send(self, content):
|
||||
if 'dingding_url' not in self.__dingding_info or 'isAtAll' not in self.__dingding_info or 'user' not in self.__dingding_info: return -1
|
||||
data = {
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": content
|
||||
},
|
||||
"at": {
|
||||
"atMobiles": [
|
||||
self.__dingding_info['user']
|
||||
],
|
||||
"isAtAll": self.__dingding_info['isAtAll']
|
||||
if 'weixin.qq.com' in self.__dingding_info['dingding_url']:
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"content": content
|
||||
}
|
||||
}
|
||||
else:
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "Server notification",
|
||||
"text": content
|
||||
},
|
||||
"at": {
|
||||
"atMobiles": [
|
||||
self.__dingding_info['user']
|
||||
],
|
||||
"isAtAll": self.__dingding_info['isAtAll']
|
||||
}
|
||||
}
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
try:
|
||||
x = requests.post(url=self.__dingding_info['dingding_url'], data=json.dumps(data), headers=headers,
|
||||
|
||||
+13
-3
@@ -84,12 +84,22 @@ class setPanelLets:
|
||||
pssl = panelSSL.panelSSL()
|
||||
gcl = pssl.GetCertList(get)
|
||||
for i in gcl:
|
||||
for v in i.values():
|
||||
if get.domain == v:
|
||||
if get.domain in i['dns'] or get.domain == i['subject']:
|
||||
try:
|
||||
time_stamp = int(i['notAfter'])
|
||||
except:
|
||||
time_array = time.strptime(i['notAfter'],"%Y-%m-%d")
|
||||
time_stamp = int(time.mktime(time_array))
|
||||
now = time.time()
|
||||
if time_stamp > int(now):
|
||||
return i
|
||||
for d in i['dns']:
|
||||
d = d.split('.')
|
||||
if '*' in d and d[1:] == get.domain.split('.')[1:]:
|
||||
try:
|
||||
time_stamp = int(i['notAfter'])
|
||||
except:
|
||||
time_array = time.strptime(i['notAfter'],"%Y-%m-%d")
|
||||
time_array = time.strptime(i['notAfter'], "%Y-%m-%d")
|
||||
time_stamp = int(time.mktime(time_array))
|
||||
now = time.time()
|
||||
if time_stamp > int(now):
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | version :1.0
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 梁凯强 <1249648969@qq.com>
|
||||
# +-------------------------------------------------------------------
|
||||
# | SSH 双因子认证
|
||||
# +--------------------------------------------------------------------
|
||||
import public,re,os
|
||||
import platform,time
|
||||
|
||||
class ssh_authentication:
|
||||
__SSH_CONFIG='/etc/ssh/sshd_config'
|
||||
__PAM_CONFIG='/etc/pam.d/sshd'
|
||||
__python_pam='/usr/pam_python_so'
|
||||
__config_pl='/www/server/panel/data/pam_btssh_authentication.pl'
|
||||
|
||||
|
||||
def __init__(self):
|
||||
'''检查pam_python目录是否存在'''
|
||||
if not os.path.exists(self.__python_pam):
|
||||
public.ExecShell("mkdir -p " + self.__python_pam)
|
||||
public.ExecShell("chmod 600 " + self.__python_pam)
|
||||
if not os.path.exists(self.__config_pl):
|
||||
public.ExecShell("echo '%s' >>%s"%(public.GetRandomString(32),self.__config_pl))
|
||||
public.ExecShell("chmod 600 " + self.__config_pl)
|
||||
|
||||
def wirte(self, file, ret):
|
||||
result = public.writeFile(file, ret)
|
||||
return result
|
||||
|
||||
#重启SSH
|
||||
def restart_ssh(self):
|
||||
act = 'restart'
|
||||
if os.path.exists('/etc/redhat-release'):
|
||||
version = public.readFile('/etc/redhat-release')
|
||||
if isinstance(version, str):
|
||||
if version.find(' 7.') != -1 or version.find(' 8.') != -1:
|
||||
public.ExecShell("systemctl " + act + " sshd.service")
|
||||
else:
|
||||
public.ExecShell("/etc/init.d/sshd " + act)
|
||||
else:
|
||||
public.ExecShell("/etc/init.d/sshd " + act)
|
||||
else:
|
||||
public.ExecShell("/etc/init.d/sshd " + act)
|
||||
|
||||
#查找PAM目录
|
||||
def get_pam_dir(self):
|
||||
#Centos 系列
|
||||
if os.path.exists('/etc/redhat-release'):
|
||||
version = public.readFile('/etc/redhat-release')
|
||||
if isinstance(version, str):
|
||||
if version.find(' 7.') != -1:
|
||||
return 'auth requisite %s/pam_btssh_authentication.so'%(self.__python_pam)
|
||||
elif version.find(' 8.') != -1:
|
||||
return 'auth requisite %s/pam_btssh_authentication.so'%(self.__python_pam)
|
||||
else:
|
||||
return False
|
||||
#Ubuntu
|
||||
elif os.path.exists('/etc/lsb-release'):
|
||||
version = public.readFile('/etc/lsb-release')
|
||||
if isinstance(version, str):
|
||||
if version.find('16.') != -1:
|
||||
return 'auth requisite %s/pam_btssh_authentication.so'%(self.__python_pam)
|
||||
elif version.find('20.') != -1:
|
||||
return 'auth requisite %s/pam_btssh_authentication.so'%(self.__python_pam)
|
||||
elif version.find('18.') != -1:
|
||||
return 'auth requisite %s/pam_btssh_authentication.so'%(self.__python_pam)
|
||||
else:
|
||||
return False
|
||||
#debian
|
||||
elif os.path.exists('/etc/debian_version'):
|
||||
version = public.readFile('/etc/debian_version')
|
||||
if isinstance(version, str):
|
||||
if version.find('9.') != -1:
|
||||
return 'auth requisite %s/pam_btssh_authentication.so'%(self.__python_pam)
|
||||
elif version.find('10.') != -1:
|
||||
return 'auth requisite %s/pam_btssh_authentication.so'%(self.__python_pam)
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
#判断PAMSO文件是否存在
|
||||
def isPamSoExists(self):
|
||||
check2=self.get_pam_dir()
|
||||
if not check2: return False
|
||||
check=check2.split()
|
||||
if len(check)<3: return False
|
||||
if os.path.exists(check[2]):
|
||||
#判断文件大小
|
||||
if os.path.getsize(check[2])<10240:
|
||||
self.install_pam_python(check)
|
||||
return self.isPamSoExists()
|
||||
return check2
|
||||
else:
|
||||
self.install_pam_python(check)
|
||||
return self.isPamSoExists()
|
||||
|
||||
#安装pam_python
|
||||
def install_pam_python(self,check):
|
||||
so_path=check[2]
|
||||
so_name=check[2].split('/')[-1]
|
||||
public.ExecShell('/usr/local/curl/bin/curl -o %s http://download.bt.cn/btwaf_rule/pam_python_so/%s'%(so_path,so_name))
|
||||
public.ExecShell("chmod 600 " + so_path)
|
||||
return True
|
||||
|
||||
#开启双因子认证
|
||||
def start_ssh_authentication(self):
|
||||
check=self.isPamSoExists()
|
||||
if not check:return False
|
||||
if os.path.exists(self.__PAM_CONFIG):
|
||||
auth_data=public.readFile(self.__PAM_CONFIG)
|
||||
if isinstance(auth_data, str):
|
||||
if auth_data.find("\n"+check) != -1:
|
||||
return True
|
||||
else:
|
||||
auth_data=auth_data+"\n"+check
|
||||
public.writeFile(self.__PAM_CONFIG,auth_data)
|
||||
return True
|
||||
return False
|
||||
|
||||
#关闭双因子认证
|
||||
def stop_ssh_authentication(self):
|
||||
check=self.isPamSoExists()
|
||||
if not check:return False
|
||||
if os.path.exists(self.__PAM_CONFIG):
|
||||
auth_data=public.readFile(self.__PAM_CONFIG)
|
||||
if isinstance(auth_data, str):
|
||||
if auth_data.find("\n"+check) != -1:
|
||||
auth_data=auth_data.replace("\n"+check,'')
|
||||
public.writeFile(self.__PAM_CONFIG,auth_data)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
#检查是否开启双因子认证
|
||||
def check_ssh_authentication(self):
|
||||
check=self.isPamSoExists()
|
||||
if not check:return False
|
||||
if os.path.exists(self.__PAM_CONFIG):
|
||||
auth_data=public.readFile(self.__PAM_CONFIG)
|
||||
if isinstance(auth_data, str):
|
||||
if auth_data.find("\n"+check) != -1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
#设置SSH应答模式
|
||||
def set_ssh_login_user(self):
|
||||
ssh_password = '\nChallengeResponseAuthentication\s\w+'
|
||||
file = public.readFile(self.__SSH_CONFIG)
|
||||
if isinstance(file, str):
|
||||
if len(re.findall(ssh_password, file)) == 0:
|
||||
file_result = file + '\nChallengeResponseAuthentication yes'
|
||||
else:
|
||||
file_result = re.sub(ssh_password, '\nChallengeResponseAuthentication yes', file)
|
||||
self.wirte(self.__SSH_CONFIG, file_result)
|
||||
self.restart_ssh()
|
||||
return public.returnMsg(True, 'Successfully opened')
|
||||
|
||||
#关闭SSH应答模式
|
||||
def close_ssh_login_user(self):
|
||||
file = public.readFile(self.__SSH_CONFIG)
|
||||
ssh_password = '\nChallengeResponseAuthentication\s\w+'
|
||||
if isinstance(file, str):
|
||||
file_result = re.sub(ssh_password, '\nChallengeResponseAuthentication no', file)
|
||||
self.wirte(self.__SSH_CONFIG, file_result)
|
||||
self.restart_ssh()
|
||||
return public.returnMsg(True, 'Closed successfully')
|
||||
|
||||
#查看SSH应答模式
|
||||
def check_ssh_login_user(self):
|
||||
file = public.readFile(self.__SSH_CONFIG)
|
||||
ssh_password = '\nChallengeResponseAuthentication\s\w+'
|
||||
if isinstance(file, str):
|
||||
ret = re.findall(ssh_password, file)
|
||||
if not ret:
|
||||
return False
|
||||
else:
|
||||
if ret[-1].split()[-1] == 'yes':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
#关闭密码访问
|
||||
def stop_password(self):
|
||||
'''
|
||||
关闭密码访问
|
||||
无参数传递
|
||||
'''
|
||||
file = public.readFile(self.__SSH_CONFIG)
|
||||
if isinstance(file, str):
|
||||
if file.find('PasswordAuthentication') != -1:
|
||||
file_result = file.replace('\nPasswordAuthentication yes', '\nPasswordAuthentication no')
|
||||
self.wirte(self.__SSH_CONFIG, file_result)
|
||||
self.restart_ssh()
|
||||
return public.returnMsg(True, 'Closed successfully')
|
||||
else:
|
||||
return public.returnMsg(False, 'No password authentication')
|
||||
return public.returnMsg(False, 'No password authentication')
|
||||
|
||||
#开启密码登录
|
||||
def start_password(self):
|
||||
'''
|
||||
开启密码登陆
|
||||
get: 无需传递参数
|
||||
'''
|
||||
file = public.readFile(self.__SSH_CONFIG)
|
||||
if isinstance(file, str):
|
||||
if file.find('PasswordAuthentication') != -1:
|
||||
file_result = file.replace('\nPasswordAuthentication no', '\nPasswordAuthentication yes')
|
||||
self.wirte(self.__SSH_CONFIG, file_result)
|
||||
self.restart_ssh()
|
||||
return public.returnMsg(True, 'Turn on password authentication successfully')
|
||||
else:
|
||||
file_result = file + '\nPasswordAuthentication yes'
|
||||
self.wirte(self.__SSH_CONFIG, file_result)
|
||||
self.restart_ssh()
|
||||
return public.returnMsg(True, 'Turn on password authentication successfully')
|
||||
return public.returnMsg(False, 'No password authentication')
|
||||
|
||||
#查看密码登录状态
|
||||
def check_password(self):
|
||||
'''
|
||||
查看密码登录状态
|
||||
无参数传递
|
||||
'''
|
||||
file = public.readFile(self.__SSH_CONFIG)
|
||||
ssh_password = '\nPasswordAuthentication\s\w+'
|
||||
if isinstance(file, str):
|
||||
ret = re.findall(ssh_password, file)
|
||||
if not ret:
|
||||
return False
|
||||
else:
|
||||
if ret[-1].split()[-1] == 'yes':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
#开启SSH 双因子认证
|
||||
def start_ssh_authentication_two_factors(self):
|
||||
if not self.get_pam_dir():return public.returnMsg(False,'The system is not supported')
|
||||
check=self.isPamSoExists()
|
||||
if not check:return 'False'
|
||||
if not self.check_ssh_login_user():
|
||||
self.set_ssh_login_user()
|
||||
if not self.check_ssh_authentication():
|
||||
self.start_ssh_authentication()
|
||||
#如果开启的话,就关闭密码认证
|
||||
# if self.check_password():
|
||||
# self.stop_password()
|
||||
#检查是否开启双因子认证
|
||||
if self.check_ssh_authentication() and self.check_ssh_login_user():
|
||||
return public.returnMsg(True,'Successfully opened')
|
||||
return public.returnMsg(True,'Failed to open')
|
||||
|
||||
|
||||
#关闭SSH 双因子认证
|
||||
def close_ssh_authentication_two_factors(self):
|
||||
if not self.get_pam_dir():return public.returnMsg(False,'The system is not supported')
|
||||
check=self.isPamSoExists()
|
||||
if not check:return False
|
||||
if self.check_ssh_authentication():
|
||||
self.stop_ssh_authentication()
|
||||
#检查是否关闭双因子认证
|
||||
#如果是关闭的SSH,那么就开启
|
||||
# if not self.check_password():
|
||||
# self.start_password()
|
||||
if not self.check_ssh_authentication():
|
||||
return public.returnMsg(True,'Closed')
|
||||
if self.stop_ssh_authentication():
|
||||
return public.returnMsg(True,'Closed')
|
||||
|
||||
|
||||
#检查是否开启双因子认证
|
||||
def check_ssh_authentication_two_factors(self):
|
||||
if not self.get_pam_dir():return public.returnMsg(False,'The system is not supported')
|
||||
check=self.isPamSoExists()
|
||||
if not check:return False
|
||||
if not self.check_ssh_login_user():
|
||||
return public.returnMsg(False,'Inactive')
|
||||
if not self.check_ssh_authentication():
|
||||
return public.returnMsg(False,'Inactive')
|
||||
return public.returnMsg(True,'Activated')
|
||||
|
||||
def is_check_so(self):
|
||||
'''判断SO文件是否存在'''
|
||||
if not self.get_pam_dir():return public.returnMsg(False,'The system is not supported')
|
||||
config_data=self.get_pam_dir()
|
||||
if not config_data:return False
|
||||
config_data2=config_data.split()
|
||||
ret={}
|
||||
ret['so_path']=config_data2[2].split('/')[-1]
|
||||
if os.path.exists(config_data2[2]):
|
||||
ret['so_status']=True
|
||||
else:
|
||||
ret['so_status']=False
|
||||
return public.returnMsg(True,ret)
|
||||
|
||||
def download_so(self):
|
||||
'''下载so文件'''
|
||||
if not self.get_pam_dir():return public.returnMsg(False,'The system is not supported')
|
||||
config_data=self.get_pam_dir()
|
||||
if not config_data:return False
|
||||
config_data=config_data.split()
|
||||
self.install_pam_python(config_data)
|
||||
#判断下载的文件大小
|
||||
if os.path.exists(config_data[2]) :
|
||||
if os.path.getsize(config_data[2])>10240:
|
||||
return public.returnMsg(True,"Download the file successfully")
|
||||
return public.returnMsg(False,"download failed")
|
||||
|
||||
#获取Linux系统的主机名
|
||||
def get_pin(self):
|
||||
import platform,time
|
||||
data=platform.uname()
|
||||
tme_data=time.strftime('%Y-%m-%d%H:%M',time.localtime(time.time()))
|
||||
#获取秒
|
||||
tis_data=time.strftime('%S',time.localtime(time.time()))
|
||||
ip_list=public.ReadFile('/www/server/panel/data/pam_btssh_authentication.pl')
|
||||
ret={}
|
||||
if isinstance(ip_list,str):
|
||||
info=data[0]+data[1]+data[2]+tme_data+ip_list
|
||||
md5_info=public.Md5(info)
|
||||
ret['pin']=md5_info[:6]
|
||||
ret['time']=60-int(tis_data)
|
||||
return ret
|
||||
else:
|
||||
ret['pin']='error'
|
||||
ret['time']=60
|
||||
return ret
|
||||
+2
-1
@@ -46,7 +46,8 @@ class userlogin:
|
||||
return public.returnJson(False,'LOGIN_USER_ERR',(str(num),)),json_header
|
||||
_key_file = "/www/server/panel/data/two_step_auth.txt"
|
||||
#登陆告警
|
||||
public.login_send_body("Userinfo",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT')))
|
||||
public.run_thread(public.login_send_body,("Userinfo",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT'))))
|
||||
# public.login_send_body("Userinfo",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT')))
|
||||
if hasattr(post,'vcode'):
|
||||
if self.limit_address('?',v="vcode") < 1: return public.returnJson(False,'You have failed verification many times, forbidden for 10 minutes'),json_header
|
||||
import pyotp
|
||||
|
||||
@@ -450,6 +450,53 @@ def bt_cli(u_input = 0):
|
||||
u_input = input(public.GetMsg("INPUT_CMD_NUM"))
|
||||
if sys.version_info[0] == 3: u_input = int(u_input)
|
||||
except: u_input = 0
|
||||
try:
|
||||
if u_input in ['log','logs','error','err','tail','debug','info']:
|
||||
os.system("tail -f {}".format(public.get_panel_log_file()))
|
||||
return
|
||||
if u_input[:6] in ['install','update']:
|
||||
print("Tip: Example of command parameter transfer (compile and install php7.4):bt install/0/php/7.4")
|
||||
print(sys.argv)
|
||||
install_args = u_input.split('/')
|
||||
if len(install_args) < 2:
|
||||
try:
|
||||
install_input = input("Please select the installation method (0 compile install, 1 speed install, default: 1):")
|
||||
install_input = int(install_input)
|
||||
except:
|
||||
install_input = 1
|
||||
else:
|
||||
install_input = int(install_args[1])
|
||||
print(raw_tip)
|
||||
soft_list = 'nginx apache php mysql memcached redis pure-ftpd phpmyadmin pm2 docker openlitespeed mongodb'
|
||||
soft_list_arr = soft_list.split(' ')
|
||||
if len(install_args) < 3:
|
||||
install_soft = ''
|
||||
while not install_soft:
|
||||
print("Supported software:{}".format(soft_list))
|
||||
print(raw_tip)
|
||||
install_soft = input("Please enter the name of the software to be installed (eg: nginx):")
|
||||
if install_soft not in soft_list_arr:
|
||||
print("Software that does not support command line installation")
|
||||
install_soft = ''
|
||||
else:
|
||||
install_soft = install_args[2]
|
||||
|
||||
print(raw_tip)
|
||||
if len(install_args) < 4:
|
||||
install_version = ''
|
||||
while not install_version:
|
||||
print(raw_tip)
|
||||
install_version = input("Please enter the version number to be installed (for example: 1.18):")
|
||||
else:
|
||||
install_version = install_args[3]
|
||||
|
||||
print(raw_tip)
|
||||
os.system("bash /www/server/panel/install/install_soft.sh {} {} {} {}".format(install_input,install_args[0],install_soft,install_version))
|
||||
exit()
|
||||
|
||||
print("Unsupported command")
|
||||
exit()
|
||||
except: pass
|
||||
|
||||
nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,22,23,24,25,26]
|
||||
if not u_input in nums:
|
||||
@@ -639,7 +686,11 @@ if __name__ == "__main__":
|
||||
update_to6()
|
||||
elif type == "cli":
|
||||
clinum = 0
|
||||
if len(sys.argv) > 2: clinum = int(sys.argv[2])
|
||||
try:
|
||||
if len(sys.argv) > 2:
|
||||
clinum = int(sys.argv[2]) if sys.argv[2][:6] not in ['instal','update'] else sys.argv[2]
|
||||
except:
|
||||
clinum = sys.argv[2]
|
||||
bt_cli(clinum)
|
||||
else:
|
||||
print('ERROR: Parameter error')
|
||||
|
||||
Reference in New Issue
Block a user